8 小时以前 46b2ae1c889e181ebe186de3758fc09804696029
feat(workorder): 工单工序列表支持拖拽排序功能

- 在 useProcessGridColumns 函数中添加 draggable 参数控制拖拽列显示
- 集成 @vueuse/integrations/useSortable 实现拖拽排序功能
- 添加初始化和销毁拖拽排序实例的方法
- 为工序列表添加拖拽手柄图标和样式
- 优化新增编辑成功回调逻辑并适配拖拽排序场景
- 在删除工序时同步更新排序字段并重新初始化拖拽功能
- 添加组件卸载时自动清理拖拽实例的钩子函数
已修改2个文件
127 ■■■■ 文件已修改
src/views/mes/pro/workorder/data.ts 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/workorder/modules/process-list.vue 112 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/workorder/data.ts
@@ -737,8 +737,21 @@
}
/** 订单工序列表的字段 */
export function useProcessGridColumns(): VxeTableGridOptions<MesProWorkOrderProcessApi.WorkOrderProcess>['columns'] {
export function useProcessGridColumns(
  draggable = false,
): VxeTableGridOptions<MesProWorkOrderProcessApi.WorkOrderProcess>['columns'] {
  return [
    ...(draggable
      ? [
          {
            field: 'drag',
            title: '',
            width: 40,
            fixed: 'left',
            slots: { default: 'dragHandle' },
          } as const,
        ]
      : []),
    {
      field: 'sort',
      title: '序号',
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 {
    // 编辑模式:调用接口删除
    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 {
    // 编辑模式:调用接口删除
    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>