From 46b2ae1c889e181ebe186de3758fc09804696029 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期四, 23 七月 2026 09:54:23 +0800
Subject: [PATCH] feat(workorder): 工单工序列表支持拖拽排序功能

---
 src/views/mes/pro/workorder/modules/process-list.vue |  112 +++++++++++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 90 insertions(+), 22 deletions(-)

diff --git a/src/views/mes/pro/workorder/modules/process-list.vue b/src/views/mes/pro/workorder/modules/process-list.vue
index 6559581..35ac698 100644
--- a/src/views/mes/pro/workorder/modules/process-list.vue
+++ b/src/views/mes/pro/workorder/modules/process-list.vue
@@ -5,11 +5,13 @@
 import type { MesProWorkOrderApi } from '#/api/mes/pro/workorder';
 import type { MesProWorkOrderProcessApi } from '#/api/mes/pro/workorder/process';
 
-import { computed, ref, watch } from 'vue';
+import { computed, nextTick, onUnmounted, ref, watch } from 'vue';
 
 import { useVbenModal } from '@vben/common-ui';
 import { MesProWorkOrderStatusEnum } from '@vben/constants';
+import { IconifyIcon } from '@vben/icons';
 
+import { useSortable } from '@vueuse/integrations/useSortable';
 import { message, Tag } from 'ant-design-vue';
 
 import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
@@ -45,6 +47,9 @@
 
 const isCreateMode = computed(() => props.formType === 'create');
 
+// 鎷栨嫿鎺掑簭
+const sortableInstance = ref<any>(null);
+
 const [ProcessFormModal, processFormModalApi] = useVbenModal({
   connectedComponent: ProcessForm,
   destroyOnClose: true,
@@ -54,7 +59,7 @@
   gridOptions: {
     autoResize: true,
     border: true,
-    columns: useProcessGridColumns(),
+    columns: useProcessGridColumns(isEditable.value),
     data: [],
     minHeight: 240,
     pagerConfig: { enabled: false },
@@ -63,6 +68,50 @@
     toolbarConfig: { enabled: false },
   } as VxeTableGridOptions<MesProWorkOrderProcessApi.WorkOrderProcess>,
 });
+
+/** 鍒濆鍖�/閿�姣佹嫋鎷芥帓搴� */
+async function initSortable() {
+  destroySortable();
+  if (!isEditable.value || list.value.length === 0) return;
+  await nextTick();
+  sortableInstance.value = useSortable(
+    '.workorder-process-list .vxe-table--body-wrapper:not(.fixed-right--wrapper) .vxe-table--body tbody',
+    list.value,
+    {
+      animation: 150,
+      draggable: '.vxe-body--row',
+      handle: '.drag-handle',
+      onEnd: ({ newDraggableIndex, oldDraggableIndex }) => {
+        if (
+          newDraggableIndex !== undefined &&
+          oldDraggableIndex !== undefined &&
+          oldDraggableIndex !== newDraggableIndex
+        ) {
+          // 閲嶆柊鎺掑簭鏁扮粍
+          list.value.splice(
+            newDraggableIndex,
+            0,
+            list.value.splice(oldDraggableIndex, 1)[0]!,
+          );
+          // 鏇存柊 sort 瀛楁
+          list.value.forEach((item, index) => {
+            item.sort = index + 1;
+          });
+          gridApi.setGridOptions({ data: list.value });
+          emit('processListChange', list.value);
+        }
+      },
+    },
+  );
+}
+
+/** 閿�姣佹嫋鎷芥帓搴忓疄渚� */
+function destroySortable() {
+  if (sortableInstance.value) {
+    sortableInstance.value.stop?.();
+    sortableInstance.value = null;
+  }
+}
 
 /** 鍔犺浇宸ュ簭鍒楄〃 */
 async function getList() {
@@ -106,6 +155,8 @@
     }
     gridApi.setGridOptions({ data: list.value });
     emit('processListChange', list.value);
+    // 鍒濆鍖栨嫋鎷芥帓搴�
+    initSortable();
   } finally {
     gridApi.setLoading(false);
   }
@@ -135,25 +186,6 @@
     .open();
 }
 
-/** 鍒犻櫎宸ュ簭 */
-function handleDelete(row: MesProWorkOrderProcessApi.WorkOrderProcess) {
-  if (isCreateMode.value) {
-    // 鏂板妯″紡锛氫粠鍒楄〃鍒犻櫎
-    const index = list.value.findIndex((item) => item.processId === row.processId);
-    if (index > -1) {
-      list.value.splice(index, 1);
-      gridApi.setGridOptions({ data: list.value });
-      emit('processListChange', list.value);
-    }
-  } else {
-    // 缂栬緫妯″紡锛氳皟鐢ㄦ帴鍙e垹闄�
-    deleteWorkOrderProcess(row.id!).then(() => {
-      message.success($t('ui.actionMessage.deleteSuccess', [row.processName]));
-      getList();
-    });
-  }
-}
-
 /** 鏂板/缂栬緫鎴愬姛鍥炶皟 */
 function handleFormSuccess(data: MesProWorkOrderProcessApi.WorkOrderProcess, isEdit: boolean) {
   if (isCreateMode.value) {
@@ -167,10 +199,40 @@
     }
     gridApi.setGridOptions({ data: list.value });
     emit('processListChange', list.value);
+    initSortable();
   } else {
     getList();
   }
 }
+
+/** 鍒犻櫎宸ュ簭 */
+function handleDelete(row: MesProWorkOrderProcessApi.WorkOrderProcess) {
+  if (isCreateMode.value) {
+    // 鏂板妯″紡锛氫粠鍒楄〃鍒犻櫎
+    const index = list.value.findIndex((item) => item.processId === row.processId);
+    if (index > -1) {
+      list.value.splice(index, 1);
+      // 鏇存柊 sort 瀛楁
+      list.value.forEach((item, idx) => {
+        item.sort = idx + 1;
+      });
+      gridApi.setGridOptions({ data: list.value });
+      emit('processListChange', list.value);
+      initSortable();
+    }
+  } else {
+    // 缂栬緫妯″紡锛氳皟鐢ㄦ帴鍙e垹闄�
+    deleteWorkOrderProcess(row.id!).then(() => {
+      message.success($t('ui.actionMessage.deleteSuccess', [row.processName]));
+      getList();
+    });
+  }
+}
+
+// 缁勪欢鍗歌浇鏃堕攢姣佹帓搴忓疄渚�
+onUnmounted(() => {
+  destroySortable();
+});
 
 // 鐩戝惉浜у搧/宸ュ崟鍙樺寲
 watch(
@@ -183,7 +245,7 @@
 </script>
 
 <template>
-  <div>
+  <div class="workorder-process-list">
     <ProcessFormModal @success="handleFormSuccess" />
     <div v-if="isEditable" class="mb-3 flex items-center justify-start">
       <TableAction
@@ -197,6 +259,12 @@
       />
     </div>
     <Grid class="w-full" table-title="宸ュ簭">
+      <template #dragHandle>
+        <IconifyIcon
+          icon="ic:round-drag-indicator"
+          class="drag-handle cursor-move text-xl text-gray-400"
+        />
+      </template>
       <template #keyFlag="{ row }">
         <Tag v-if="row.keyFlag" color="red">鍏抽敭</Tag>
         <span v-else>-</span>

--
Gitblit v1.9.3