From b0d4df5f39525ae7fe252e8ee65d85fd71dca721 Mon Sep 17 00:00:00 2001
From: zouyu <2723363702@qq.com>
Date: 星期四, 07 五月 2026 14:53:32 +0800
Subject: [PATCH] 手动下单:检验中订单撤销报错问题修复

---
 src/views/system/user/index.vue |  309 ++++++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 242 insertions(+), 67 deletions(-)

diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue
index 95dc281..7ff8d4e 100644
--- a/src/views/system/user/index.vue
+++ b/src/views/system/user/index.vue
@@ -8,8 +8,8 @@
             <div class="head-container addButton">
               <el-input v-model="deptName" placeholder="閮ㄩ棬鍚嶇О" clearable size="small" prefix-icon="el-icon-search"
                 style="margin-bottom: 20px" />
-              <el-button style="margin-left: 4px" type="primary" plain icon="el-icon-plus" size="mini" circle
-                @click="addSchema"></el-button>
+              <!-- <el-button style="margin-left: 4px" type="primary" plain icon="el-icon-plus" size="mini" circle
+                @click="addSchema"></el-button> -->
             </div>
             <div class="head-container">
               <el-tree :data="deptOptions" :props="defaultProps" :expand-on-click-node="false"
@@ -28,14 +28,14 @@
                     @keyup.enter.native="handleQuery" />
                 </el-form-item>
                 <el-form-item label="鐘舵��" prop="status">
-                  <el-select v-model="queryParams.status" placeholder="鐢ㄦ埛鐘舵��" clearable>
+                  <el-select v-model="queryParams.status" placeholder="鐢ㄦ埛鐘舵��" clearable @change="handleQuery">
                     <el-option v-for="dict in dict.type.sys_normal_disable" :key="dict.value" :label="dict.label"
                       :value="dict.value" />
                   </el-select>
                 </el-form-item>
                 <el-form-item>
-                  <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">鏌� 璇�</el-button>
-                  <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">閲� 缃�</el-button>
+                  <el-button type="primary" size="mini" @click="handleQuery">鏌ヨ</el-button>
+                  <el-button size="mini" @click="resetQuery">閲嶇疆</el-button>
                 </el-form-item>
               </el-form>
             </div>
@@ -47,10 +47,16 @@
             </div>
           </div>
           <el-col>
-            <el-table v-loading="loading" :data="userList">
+            <el-table ref="dragTable" v-loading="loading" row-key="userId" :data="userList" :header-cell-style="{ background: '#f8f8f9', color: '#515a6e' }" border>
+              <el-table-column label="鎷栨嫿" align="center" width="60">
+                <template slot-scope="scope">
+                  <i class="el-icon-rank drag-handle" :data-user-id="scope.row.userId"></i>
+                </template>
+              </el-table-column>
               <el-table-column label="搴忓彿" align="center" type="index" />
               <el-table-column label="濮撳悕" align="center" key="nickName" prop="nickName" :show-overflow-tooltip="true" />
               <el-table-column label="璐﹀彿" align="center" key="userName" prop="userName" :show-overflow-tooltip="true" />
+              <el-table-column label="瑙掕壊" align="center" key="roleName" prop="roleName" :show-overflow-tooltip="true" />
               <el-table-column label="鐘舵��" align="center" key="status">
                 <template slot-scope="scope">
                   <el-switch v-model="scope.row.status" active-value="0" inactive-value="1"
@@ -62,7 +68,7 @@
                 <template slot-scope="scope">
                   <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
                     v-hasPermi="['system:user:edit']">淇敼</el-button>
-                  <!--                  <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['system:user:remove']">鍒犻櫎</el-button>-->
+                  <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['system:user:remove']">鍒犻櫎</el-button>
                   <!--                  <el-dropdown size="mini" @command="(command) => handleCommand(command, scope.row)" v-hasPermi="['system:user:resetPwd', 'system:user:edit']">-->
                   <!--                    <el-button size="mini" type="text" icon="el-icon-d-arrow-right">鏇村</el-button>-->
                   <!--                    <el-dropdown-menu slot="dropdown">-->
@@ -74,6 +80,7 @@
               </el-table-column>
             </el-table>
             <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum"
+                        :page-sizes="[20,50,100,200,500]"
               :limit.sync="queryParams.pageSize" @pagination="getList" />
           </el-col>
         </pane>
@@ -113,47 +120,61 @@
         </el-row>
         <el-row>
           <el-col :span="12">
+            <el-form-item label="宀椾綅">
+              <el-select style="width:100%" v-model="form.postIds" multiple placeholder="璇烽�夋嫨">
+                <el-option
+                  v-for="item in postOptions"
+                  :key="item.postId"
+                  :label="item.postName"
+                  :value="item.postId"
+                  :disabled="item.status == 1"
+                ></el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
             <el-form-item label="瑙掕壊" prop="roleIds">
-              <el-select v-model="form.roleIds" multiple placeholder="璇烽�夋嫨瑙掕壊" clearable>
+              <el-select style="width:100%" v-model="form.roleIds" multiple placeholder="璇烽�夋嫨瑙掕壊" clearable>
                 <el-option v-for="item in roleOptions" :key="item.roleId" :label="item.roleName" :value="item.roleId"
                   :disabled="item.status == 1"></el-option>
               </el-select>
             </el-form-item>
           </el-col>
+        </el-row>
+        <el-row>
           <el-col :span="12">
             <el-form-item label="瀵嗙爜" prop="password">
               <el-input v-model="form.password" placeholder="璇疯緭鍏ョ敤鎴峰瘑鐮�" type="password" maxlength="20" show-password />
             </el-form-item>
           </el-col>
-        </el-row>
-        <el-row>
           <el-col :span="12">
             <el-form-item label="濮撳悕EN" prop="nameEn">
               <el-input v-model="form.nameEn" placeholder="璇疯緭鍏ュ鍚岴N" maxlength="50" />
             </el-form-item>
           </el-col>
+        </el-row>
+        <el-row>
           <el-col :span="12">
             <el-form-item label="閭" prop="email">
               <el-input v-model="form.email" placeholder="璇疯緭鍏ュ唴瀹�"></el-input>
             </el-form-item>
           </el-col>
-        </el-row>
-        <el-row>
           <el-col :span="12">
             <el-form-item label="鍗曚綅" prop="company">
               <el-select v-model="form.company" placeholder="璇烽�夋嫨鍗曚綅" style="width: 100%" clearable>
-                <el-option v-for="item in postOptions" :key="item.id" :label="item.company"
+                <el-option v-for="item in companyOptions" :key="item.id" :label="item.company"
                   :value="item.id"></el-option>
               </el-select>
             </el-form-item>
           </el-col>
-          <el-col :span="12">
-            <el-form-item label="褰掑睘閮ㄩ棬" prop="deptId">
-              <treeselect v-model="form.deptId" :options="enabledDeptOptions" :show-count="true" placeholder="璇烽�夋嫨褰掑睘閮ㄩ棬" />
-            </el-form-item>
-          </el-col>
         </el-row>
         <el-row>
+          <el-col :span="12">
+            <el-form-item label="褰掑睘閮ㄩ棬" prop="deptId">
+              <treeselect v-model="form.deptId" :options="enabledDeptOptions" :show-count="true"
+                          placeholder="璇烽�夋嫨褰掑睘閮ㄩ棬" />
+            </el-form-item>
+          </el-col>
           <el-col :span="12">
             <el-form-item label="绛惧悕">
               <el-upload class="avatar-uploader" :action="uploadAction" :show-file-list="false"
@@ -164,11 +185,13 @@
               </el-upload>
             </el-form-item>
           </el-col>
+        </el-row>
+        <el-row>
           <el-col :span="12">
             <el-form-item label="涓汉鐓х墖">
               <el-upload class="avatar-uploader" :action="uploadAction" :show-file-list="false"
-                :headers="upload.headers" accept=".png, .jpg, .jpeg, .gif" :on-error="handleUploadError1"
-                :on-success="handleUploadSuccess1" :before-upload="handleBeforeUpload1">
+                         :headers="upload.headers" accept=".png, .jpg, .jpeg, .gif" :on-error="handleUploadError1"
+                         :on-success="handleUploadSuccess1" :before-upload="handleBeforeUpload1">
                 <img v-if="form.pictureUrl" :src="javaApi + '/img/' + form.pictureUrl" class="avatar" alt="">
                 <i v-else class="el-icon-plus avatar-uploader-icon"></i>
               </el-upload>
@@ -211,7 +234,7 @@
           <el-col :span="8" style="height: 70vh;overflow: hidden;" v-if="companiesList.length != 1">
             <el-input v-model="search2" placeholder="杈撳叆鍏抽敭瀛楁悳绱�" clearable size="small" @clear="searchFilter2"
               @keyup.enter.native="searchFilter2" prefix-icon="el-icon-search" style="margin-bottom: 20px;width: 90%" />
-            <el-tree :data="datathirdParty" node-key="companyId" :props="defaultProps" @node-click="nodeClick2"
+            <el-tree :data="datathirdParty" node-key="companyId" :props="defaultProps1" @node-click="nodeClick2"
               style="height: calc(100% - 42px);" @node-expand="nodeOpen0" :filter-node-method="filterNode2" ref="tree2"
               highlight-current>
             </el-tree>
@@ -223,12 +246,13 @@
                 style="width: 50%" @keyup.enter.native="searchPerson()"></el-input>
             </div>
             <el-table height="67vh" stripe :data="personList" v-loading="personLoad" ref="personTable"
+                      :header-cell-style="{ background: '#f8f8f9', color: '#515a6e' }" border
               @selection-change="handleSelectionChange">
               <el-table-column type="selection" width="50">
               </el-table-column>
               <el-table-column prop="employeeID" label="鍛樺伐鍙�">
               </el-table-column>
-              <el-table-column prop="nickName" label="鍛樺伐濮撳悕">
+              <el-table-column prop="name" label="鍛樺伐濮撳悕">
               </el-table-column>
               <el-table-column prop="department" label="閮ㄩ棬" min-width="200">
               </el-table-column>
@@ -275,13 +299,22 @@
   resetUserPwd,
   changeUserStatus,
   deptTreeSelect,
-  selectCompaniesList, selectSimpleList, addPersonUser, uploadFile, selectRoleList, selectCustomEnum, addDepartment
+  selectCompaniesList,
+  selectSimpleList,
+  addPersonUser,
+  uploadFile,
+  selectRoleList,
+  selectCustomEnum,
+  addDepartment,
+  updateUserSort
 } from "@/api/system/user";
+import {optionSelect} from '@/api/system/post'
 import { getToken } from "@/utils/auth";
 import Treeselect from "@riophae/vue-treeselect";
 import "@riophae/vue-treeselect/dist/vue-treeselect.css";
 import { Splitpanes, Pane } from "splitpanes";
 import "splitpanes/dist/splitpanes.css";
+import Sortable from "sortablejs";
 
 export default {
   nickName: "User",
@@ -291,6 +324,7 @@
     return {
       // 閬僵灞�
       loading: true,
+      multipleSelection: [],
       // 閫変腑鏁扮粍
       ids: [],
       // 闈炲崟涓鐢�
@@ -319,6 +353,8 @@
       dateRange: [],
       // 宀椾綅閫夐」
       postOptions: [],
+      //鍗曚綅閫夐」
+      companyOptions:[],
       // 瑙掕壊閫夐」
       roleOptions: [],
       // 琛ㄥ崟鍙傛暟
@@ -326,6 +362,10 @@
       defaultProps: {
         children: "children",
         label: "label"
+      },
+      defaultProps1: {
+        children: "children",
+        label: "companyName"
       },
       // 鐢ㄦ埛瀵煎叆鍙傛暟
       upload: {
@@ -346,7 +386,7 @@
       // 鏌ヨ鍙傛暟
       queryParams: {
         pageNum: 1,
-        pageSize: 10,
+        pageSize: 20,
         nickName: undefined,
         phonenumber: undefined,
         status: undefined,
@@ -379,9 +419,35 @@
         roleIds: [
           { required: true, message: "璇烽�夋嫨瑙掕壊", trigger: "change" }
         ],
-        // password: [
-        //   { required: true, message: "瀵嗙爜涓嶈兘涓虹┖", trigger: "blur" },
-        // ],
+        password: [
+          { required: false, message: "瀵嗙爜涓嶈兘涓虹┖", trigger: "blur" },
+          { min: 8, max: 20, message: "瀵嗙爜闀垮害蹇呴』鍦�8-20涓瓧绗︿箣闂�", trigger: "blur" },
+          {
+            validator: (rule, value, callback) => {
+              if (!value) {
+                callback();
+                return;
+              }
+              // 妫�鏌ユ槸鍚﹀寘鍚ぇ鍐欏瓧姣�
+              const hasUpperCase = /[A-Z]/.test(value);
+              // 妫�鏌ユ槸鍚﹀寘鍚皬鍐欏瓧姣�
+              const hasLowerCase = /[a-z]/.test(value);
+              // 妫�鏌ユ槸鍚﹀寘鍚壒娈婄鍙�
+              const hasSpecialChar = /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(value);
+
+              if (!hasUpperCase) {
+                callback(new Error('瀵嗙爜蹇呴』鍖呭惈鑷冲皯涓�涓ぇ鍐欏瓧姣�'));
+              } else if (!hasLowerCase) {
+                callback(new Error('瀵嗙爜蹇呴』鍖呭惈鑷冲皯涓�涓皬鍐欏瓧姣�'));
+              } else if (!hasSpecialChar) {
+                callback(new Error('瀵嗙爜蹇呴』鍖呭惈鑷冲皯涓�涓壒娈婄鍙�'));
+              } else {
+                callback();
+              }
+            },
+            trigger: "blur"
+          }
+        ],
         phonenumber: [
           {
             required: true,
@@ -409,6 +475,8 @@
         fatherId: 10001,
         nickName: '',
       },
+      sortTable: null,
+      sortSaving: false,
     };
   },
   watch: {
@@ -424,16 +492,103 @@
       this.initPassword = response.msg;
     });
   },
+  beforeDestroy() {
+    this.destroyDrag()
+  },
   methods: {
+    // 琛ㄦ牸琛屾嫋鎷芥帓搴�
+    destroyDrag() {
+      if (this.sortTable) {
+        this.sortTable.destroy()
+        this.sortTable = null
+      }
+    },
+    syncDragDisabledState() {
+      if (this.sortTable) {
+        this.sortTable.option('disabled', this.loading || this.sortSaving)
+      }
+    },
+    initDrag() {
+      this.destroyDrag()
+      if (!this.$refs.dragTable || !this.userList || this.userList.length === 0) {
+        return
+      }
+
+      // 鑾峰彇 el-table 鐨� tbody 鍏冪礌锛堟嫋鎷界殑鐩爣瀹瑰櫒锛�
+      const tbody = this.$refs.dragTable.$el.querySelector(
+        '.el-table__body-wrapper tbody'
+      )
+      if (!tbody) {
+        return
+      }
+
+      // 鍒濆鍖� Sortable
+      this.sortTable = Sortable.create(tbody, {
+        animation: 150, // 鎷栨嫿鍔ㄧ敾杩囨浮鏃堕暱
+        ghostClass: 'sortable-ghost', // 鎷栨嫿鍗犱綅绗︽牱寮�
+        chosenClass: 'sortable-chosen', // 閫変腑琛屾牱寮�
+        dragClass: 'sortable-drag', // 鎷栨嫿鍏冪礌鏍峰紡
+        handle: '.drag-handle',
+        disabled: this.loading || this.sortSaving,
+        // 鎷栨嫿缁撴潫瑙﹀彂锛堟牳蹇冮�昏緫锛�
+        onEnd: async({ oldIndex, newIndex }) => {
+          if (
+            this.loading ||
+            this.sortSaving ||
+            oldIndex === newIndex ||
+            oldIndex === undefined ||
+            newIndex === undefined
+          ) {
+            return
+          }
+
+          const previousList = [...this.userList]
+          const row = this.userList.splice(oldIndex, 1)[0]
+          if (!row) {
+            this.userList = previousList
+            return
+          }
+          this.userList.splice(newIndex, 0, row)
+
+          const pageOffset =
+            (this.queryParams.pageNum - 1) * this.queryParams.pageSize
+          const data = this.userList.map((item, index) => ({
+            id: item.userId,
+            sort: pageOffset + index + 1
+          }))
+
+          this.sortSaving = true
+          this.syncDragDisabledState()
+          try {
+            await updateUserSort(data)
+            this.$message.success('鏇存柊鎺掑簭鎴愬姛')
+          } catch (error) {
+            this.userList = previousList
+            this.$message.error('鏇存柊鎺掑簭澶辫触')
+          } finally {
+            this.sortSaving = false
+            this.$nextTick(() => {
+              this.syncDragDisabledState()
+            })
+          }
+        }
+      })
+    },
     /** 鏌ヨ鐢ㄦ埛鍒楄〃 */
     getList() {
-      this.loading = true;
+      this.loading = true
+      this.syncDragDisabledState()
       listUser(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
-        this.userList = response.rows;
-        this.total = response.total;
-        this.loading = false;
-      }
-      );
+        this.userList = response.rows
+        this.total = response.total
+        this.loading = false
+        this.$nextTick(() => {
+          this.initDrag()
+        })
+      }).catch(() => {
+        this.loading = false
+        this.destroyDrag()
+      })
     },
     // 鎵撳紑娣诲姞鏋舵瀯寮规
     addSchema() {
@@ -447,10 +602,6 @@
       }
       this.addLoad = true
       addDepartment(this.addOb).then(res => {
-        if (res.code === 201) {
-          this.addLoad = false
-          return
-        }
         this.$message.success('娣诲姞鎴愬姛')
         this.addDia = false
         this.getList()
@@ -543,6 +694,7 @@
     },
     // 澶氶�夋閫変腑鏁版嵁
     handleSelectionChange(selection) {
+      this.multipleSelection = selection;
       this.ids = selection.map(item => item.userId);
       this.single = selection.length != 1;
       this.multiple = !selection.length;
@@ -561,30 +713,31 @@
       }
     },
     // 鑾峰彇涓夋柟浜哄憳
-    openthirdParty() {
-      this.addthirdParty = true;
-      this.thirdPartyLoading = true;
-      selectCompaniesList().then(res => {
-        this.companiesList = this.HaveJson(res.data);
-        if (this.companiesList.length == 1) {
-          selectSimpleList({ companyId: this.companiesList[0].companyId }).then(res => {
-            this.thirdPartyLoading = false;
-            this.personListCopy = JSON.parse(JSON.stringify(res.data))
-            this.personList = res.data
-            this.$refs.personTable.doLayout()
-          })
-        } else {
-          this.thirdPartyLoading = false;
+    async openthirdParty() {
+      try {
+        this.addthirdParty = true;
+        this.thirdPartyLoading = true;
+
+        const companiesResponse = await selectCompaniesList();
+        this.companiesList = this.HaveJson(companiesResponse.data);
+
+        if (this.companiesList.length === 1) {
+          const personResponse = await selectSimpleList({ companyId: this.companiesList[0].companyId });
+          this.personListCopy = JSON.parse(JSON.stringify(personResponse.data));
+          this.personList = personResponse.data;
+          this.$refs.personTable.doLayout();
         }
-        this.datathirdParty = this.tranListToTreeData(res.data, "ROOT");
-      }).catch(() => {
+
+        this.datathirdParty = this.tranListToTreeData(this.companiesList, "ROOT");
+        console.log(' this.datathirdParty---',  this.datathirdParty)
+      } catch (error) {
+        console.error("Error fetching third party data:", error);
+      } finally {
         this.thirdPartyLoading = false;
-      })
-      if (this.componentData.entity.roleId > 10000) this.$message.warning('鐢变簬鏈�変腑鍏蜂綋瑙掕壊锛屾柊澧炵敤鎴峰皢鎴愪负榛樿瑙掕壊')
+      }
     },
     tranListToTreeData(list, rootValue) {
-      const arr = [];
-      list.forEach((item) => {
+      return list.reduce((arr, item) => {
         if (item.parentCompanyId === rootValue) {
           const children = this.tranListToTreeData(list, item.companyId);
           if (children.length) {
@@ -592,8 +745,8 @@
           }
           arr.push(item);
         }
-      });
-      return arr;
+        return arr;
+      }, []);
     },
     searchPerson() {
       let arr = JSON.parse(JSON.stringify(this.personListCopy))
@@ -625,7 +778,7 @@
       }
     },
     nodeOpen0(data, node, el) {
-      this.currentCompaniesList[node.level - nodeOpen01] = data.id
+      // this.currentCompaniesList[node.level - nodeOpen01] = data.id
     },
     filterNode2(value, data) {
       if (!value) return true;
@@ -653,16 +806,13 @@
       addPersonUser({
         company: arr,
         person: this.multipleSelection,
-        roleId: this.componentData.entity.roleId
+        // roleId: this.componentData.entity.roleId
       }).then(res => {
-        if (res.code === 201) {
-          this.addLoad = false
-          return
-        }
         this.$message.success('鎿嶄綔鎴愬姛')
         this.multipleSelection = []
         this.$refs.personTable.clearSelection()
         this.addLoad = false
+        this.addthirdParty = false
         this.userSearch2 = ''
         this.getList()
       }).catch(e => {
@@ -674,10 +824,11 @@
       this.reset();
       this.open = true;
       selectCustomEnum().then(res => {
-        this.postOptions = res.data;
+        this.companyOptions = res.data;
       })
       getUser().then(response => {
         this.roleOptions = response.roles;
+        this.postOptions = response.posts
         this.title = "娣诲姞鐢ㄦ埛";
       });
     },
@@ -685,7 +836,7 @@
     handleUpdate(row) {
       this.reset();
       selectCustomEnum().then(res => {
-        this.postOptions = res.data;
+        this.companyOptions = res.data;
       })
       const userId = row.userId || this.ids;
       getUser(userId).then(response => {
@@ -693,6 +844,8 @@
         this.form.password = ''
         this.roleOptions = response.roles;
         this.$set(this.form, "roleIds", response.roleIds);
+        this.postOptions = response.posts
+        this.$set(this.form, "postIds", response.postIds);
         this.open = true;
         this.title = "淇敼鐢ㄦ埛";
       });
@@ -853,6 +1006,28 @@
 </script>
 
 <style scoped lang="scss">
+:deep(.drag-handle) {
+  cursor: grab;
+  color: #909399;
+  font-size: 16px;
+  display: inline-block;
+  user-select: none;
+}
+:deep(.drag-handle:hover) {
+  color: #409EFF;
+  cursor: grab;
+}
+:deep(.drag-handle:active) {
+  cursor: grabbing;
+}
+:deep(.sortable-ghost) {
+  opacity: 0.8;
+  background: #f0f9eb;
+}
+:deep(.sortable-chosen) {
+  cursor: move;
+  background: #e1f3d8;
+}
 .search_form {
   display: flex;
   justify-content: space-between;

--
Gitblit v1.9.3