From 51bdfbf624f857f125ed55d42b17bb85aa526b97 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期四, 23 七月 2026 17:48:44 +0800
Subject: [PATCH] 银川 1.生产订单新增修改时候工序列表拖拽问题解决 2.生产订单新增修改时候工序可以新增物料并更新
---
src/views/mes/pro/workorder/modules/process-list.vue | 201 ++++++++++++++++++++++++++++++++++++++++---------
1 files changed, 163 insertions(+), 38 deletions(-)
diff --git a/src/views/mes/pro/workorder/modules/process-list.vue b/src/views/mes/pro/workorder/modules/process-list.vue
index f5b0cdc..fd2a7e7 100644
--- a/src/views/mes/pro/workorder/modules/process-list.vue
+++ b/src/views/mes/pro/workorder/modules/process-list.vue
@@ -11,8 +11,10 @@
import { MesProWorkOrderStatusEnum } from '@vben/constants';
import { IconifyIcon } from '@vben/icons';
-import { useSortable } from '@vueuse/integrations/useSortable';
-import { message, Tag } from 'ant-design-vue';
+import type { MesMdItemApi } from '#/api/mes/md/item';
+
+import Sortable from 'sortablejs';
+import { Button, InputNumber, message, Modal, Tag } from 'ant-design-vue';
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
@@ -21,6 +23,7 @@
} from '#/api/mes/pro/workorder/process';
import { getRouteProcessListByProduct } from '#/api/mes/pro/route/process';
import { $t } from '#/locales';
+import { MdItemSelectDialog } from '#/views/mes/md/item/components';
import { useProcessGridColumns } from '../data';
import ProcessForm from './process-form.vue';
@@ -47,8 +50,16 @@
const isCreateMode = computed(() => props.formType === 'create');
-// 鎷栨嫿鎺掑簭
-const sortableInstance = ref<any>(null);
+// 鎷栨嫿鎺掑簭瀹炰緥
+let sortableInstance: Sortable | null = null;
+
+// 鐗╂枡鎶曞叆
+const itemSelectDialogRef = ref<InstanceType<typeof MdItemSelectDialog>>();
+const currentMaterialRow = ref<MesProWorkOrderProcessApi.WorkOrderProcess>();
+
+// 鐢ㄦ枡姣斾緥寮圭獥
+const quantityModalOpen = ref(false);
+const pendingItems = ref<(MesMdItemApi.Item & { quantity: number })[]>([]);
const [ProcessFormModal, processFormModalApi] = useVbenModal({
connectedComponent: ProcessForm,
@@ -65,51 +76,57 @@
pagerConfig: { enabled: false },
rowConfig: { isHover: true, keyField: 'processId' },
showOverflow: true,
+ expandConfig: {
+ padding: true,
+ },
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);
- }
- },
- },
+ const el = document.querySelector<HTMLElement>(
+ '.workorder-process-list .vxe-table--body-wrapper.body--wrapper .vxe-table--body tbody',
);
+ if (!el) return;
+ sortableInstance = Sortable.create(el, {
+ animation: 150,
+ draggable: '.vxe-body--row',
+ handle: '.drag-handle',
+ onEnd: (evt) => {
+ const { oldIndex, newIndex, item } = evt;
+ if (
+ oldIndex === undefined ||
+ newIndex === undefined ||
+ oldIndex === newIndex
+ ) {
+ return;
+ }
+ // 杩樺師 DOM锛歋ortable 宸茬Щ鍔ㄨ妭鐐癸紝闇�鏀惧洖鍘熶綅锛岀敱鏁版嵁椹卞姩閲嶆覆鏌�
+ if (item && oldIndex < el.children.length) {
+ const refNode = el.children[oldIndex] || null;
+ el.insertBefore(item, refNode);
+ }
+ // 鏇存柊鏁版嵁椤哄簭
+ list.value.splice(newIndex, 0, list.value.splice(oldIndex, 1)[0]!);
+ // 鏇存柊 sort 瀛楁
+ list.value.forEach((p, index) => {
+ p.sort = index + 1;
+ });
+ gridApi.setGridOptions({ data: list.value });
+ emit('processListChange', list.value);
+ },
+ });
}
/** 閿�姣佹嫋鎷芥帓搴忓疄渚� */
function destroySortable() {
- if (sortableInstance.value) {
- sortableInstance.value.stop?.();
- sortableInstance.value = null;
+ if (sortableInstance) {
+ sortableInstance.destroy();
+ sortableInstance = null;
}
}
@@ -230,6 +247,81 @@
}
}
+/** 鎵撳紑鏂板鐗╂枡寮圭獥 */
+function handleMaterialInput(row: MesProWorkOrderProcessApi.WorkOrderProcess) {
+ currentMaterialRow.value = row;
+ const selectedIds = (row.bomItems || [])
+ .map((item) => item.itemId)
+ .filter((id): id is number => id !== undefined);
+ itemSelectDialogRef.value?.open(selectedIds, { multiple: true });
+}
+
+/** 鐗╂枡閫変腑鍥炶皟锛氬幓閲嶅悗鎵撳紑鐢ㄦ枡姣斾緥濉啓寮圭獥 */
+function handleItemSelected(rows: MesMdItemApi.Item[]) {
+ const row = currentMaterialRow.value;
+ if (!row || rows.length === 0) return;
+ const existingIds = new Set(
+ (row.bomItems || [])
+ .map((item) => item.itemId)
+ .filter((id): id is number => id !== undefined),
+ );
+ const newItems = rows.filter((item) => item.id && !existingIds.has(item.id));
+ if (newItems.length === 0) {
+ message.warning('鎵�閫夌墿鏂欏潎宸插瓨鍦�');
+ return;
+ }
+ pendingItems.value = newItems.map((item) => ({ ...item, quantity: 1 }));
+ quantityModalOpen.value = true;
+}
+
+/** 纭鐢ㄦ枡姣斾緥锛屾坊鍔犲埌 bomItems */
+function handleQuantityConfirm() {
+ const row = currentMaterialRow.value;
+ if (!row) return;
+ // 鍦� list 涓壘鍒板搴旇骞舵浛鎹紝纭繚鍝嶅簲寮忔洿鏂�
+ const rowIndex = list.value.findIndex(
+ (item) => item.processId === row.processId,
+ );
+ if (rowIndex === -1) return;
+ const target = list.value[rowIndex]!;
+ const newBomItems = [
+ ...(target.bomItems || []),
+ ...pendingItems.value.map((item) => ({
+ itemId: item.id,
+ itemCode: item.code,
+ itemName: item.name,
+ quantity: item.quantity,
+ remark: undefined,
+ })),
+ ];
+ // 鍒涘缓鏂扮殑 row 瀵硅薄鍜屾柊鐨� list 鏁扮粍锛屽己鍒跺搷搴斿紡鏇存柊
+ list.value[rowIndex] = { ...target, bomItems: newBomItems };
+ quantityModalOpen.value = false;
+ pendingItems.value = [];
+ gridApi.setGridOptions({ data: list.value });
+ emit('processListChange', list.value);
+ message.success('鐗╂枡娣诲姞鎴愬姛');
+}
+
+/** 鍒犻櫎鎶曟枡鏄庣粏 */
+function handleRemoveBomItem(
+ row: MesProWorkOrderProcessApi.WorkOrderProcess,
+ index: number,
+) {
+ const rowIndex = list.value.findIndex(
+ (item) => item.processId === row.processId,
+ );
+ if (rowIndex === -1) return;
+ const target = list.value[rowIndex]!;
+ if (!target.bomItems) return;
+ list.value[rowIndex] = {
+ ...target,
+ bomItems: target.bomItems.filter((_, i) => i !== index),
+ };
+ gridApi.setGridOptions({ data: list.value });
+ emit('processListChange', list.value);
+}
+
// 缁勪欢鍗歌浇鏃堕攢姣佹帓搴忓疄渚�
onUnmounted(() => {
destroySortable();
@@ -248,6 +340,32 @@
<template>
<div class="workorder-process-list">
<ProcessFormModal @success="handleFormSuccess" />
+ <MdItemSelectDialog ref="itemSelectDialogRef" @selected="handleItemSelected" />
+ <Modal
+ v-model:open="quantityModalOpen"
+ title="濉啓鐢ㄦ枡姣斾緥"
+ width="500px"
+ @ok="handleQuantityConfirm"
+ >
+ <div class="space-y-3 py-2">
+ <div
+ v-for="(item, idx) in pendingItems"
+ :key="item.id"
+ class="flex items-center gap-3"
+ >
+ <span class="shrink-0 text-sm text-gray-700">
+ {{ item.name || item.code }}
+ </span>
+ <InputNumber
+ v-model:value="item.quantity"
+ :min="0.01"
+ :precision="2"
+ class="!w-32"
+ />
+ <span class="text-xs text-gray-400">鐢ㄩ噺姣斾緥</span>
+ </div>
+ </div>
+ </Modal>
<div v-if="isEditable" class="mb-3 flex items-center justify-start">
<TableAction
:actions="[
@@ -282,6 +400,11 @@
<TableAction
:actions="[
{
+ label: '鏂板鐗╂枡',
+ type: 'link',
+ onClick: handleMaterialInput.bind(null, row),
+ },
+ {
label: $t('common.edit'),
type: 'link',
ifShow: isEditable,
@@ -300,7 +423,7 @@
]"
/>
</template>
- <template #expand="{ row }">
+ <template #expand_content="{ row }">
<div
v-if="row.bomItems && row.bomItems.length > 0"
class="bg-gray-50 px-8 py-3"
@@ -310,7 +433,9 @@
<Tag
v-for="(bom, idx) in row.bomItems"
:key="idx"
+ closable
color="blue"
+ @close="handleRemoveBomItem(row, idx)"
>
{{ bom.itemName || bom.itemCode }}
<span class="text-gray-400 ml-1">脳{{ bom.quantity }}</span>
@@ -320,4 +445,4 @@
</template>
</Grid>
</div>
-</template>
\ No newline at end of file
+</template>
--
Gitblit v1.9.3