| | |
| | | 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'; |
| | |
| | | |
| | | const isCreateMode = computed(() => props.formType === 'create'); |
| | | |
| | | // 拖拽排序 |
| | | const sortableInstance = ref<any>(null); |
| | | |
| | | const [ProcessFormModal, processFormModalApi] = useVbenModal({ |
| | | connectedComponent: ProcessForm, |
| | | destroyOnClose: true, |
| | |
| | | gridOptions: { |
| | | autoResize: true, |
| | | border: true, |
| | | columns: useProcessGridColumns(), |
| | | columns: useProcessGridColumns(isEditable.value), |
| | | data: [], |
| | | minHeight: 240, |
| | | pagerConfig: { enabled: false }, |
| | |
| | | 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() { |
| | |
| | | outputItemCode: item.outputItemCode, |
| | | outputItemName: item.outputItemName, |
| | | remark: item.remark, |
| | | bomItems: item.bomItems, |
| | | })); |
| | | } |
| | | } else { |
| | |
| | | } |
| | | gridApi.setGridOptions({ data: list.value }); |
| | | emit('processListChange', list.value); |
| | | // 初始化拖拽排序 |
| | | initSortable(); |
| | | } finally { |
| | | gridApi.setLoading(false); |
| | | } |
| | |
| | | .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) { |
| | |
| | | } |
| | | 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( |
| | |
| | | </script> |
| | | |
| | | <template> |
| | | <div> |
| | | <div class="workorder-process-list"> |
| | | <ProcessFormModal @success="handleFormSuccess" /> |
| | | <div v-if="isEditable" class="mb-3 flex items-center justify-start"> |
| | | <TableAction |
| | |
| | | /> |
| | | </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> |
| | |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #expand="{ row }"> |
| | | <div |
| | | v-if="row.bomItems && row.bomItems.length > 0" |
| | | class="bg-gray-50 px-8 py-3" |
| | | > |
| | | <div class="text-xs text-gray-500 mb-2 font-medium">投料明细</div> |
| | | <div class="flex flex-wrap gap-2"> |
| | | <Tag |
| | | v-for="(bom, idx) in row.bomItems" |
| | | :key="idx" |
| | | color="blue" |
| | | > |
| | | {{ bom.itemName || bom.itemCode }} |
| | | <span class="text-gray-400 ml-1">×{{ bom.quantity }}</span> |
| | | </Tag> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </Grid> |
| | | </div> |
| | | </template> |