From fa42bcb7396e06e335bbbff67f5898b98f8d9aa0 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期五, 24 七月 2026 17:09:28 +0800
Subject: [PATCH] refactor(mes): 移除工单模块中的BOM相关功能
---
src/views/mes/pro/workorder/modules/process-list.vue | 260 +++++++++++++++++++++++++++++++++++++++++++++++----
1 files changed, 236 insertions(+), 24 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..fd2a7e7 100644
--- a/src/views/mes/pro/workorder/modules/process-list.vue
+++ b/src/views/mes/pro/workorder/modules/process-list.vue
@@ -5,12 +5,16 @@
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 { 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 {
@@ -19,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';
@@ -45,6 +50,17 @@
const isCreateMode = computed(() => props.formType === 'create');
+// 鎷栨嫿鎺掑簭瀹炰緥
+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,
destroyOnClose: true,
@@ -54,15 +70,65 @@
gridOptions: {
autoResize: true,
border: true,
- columns: useProcessGridColumns(),
+ columns: useProcessGridColumns(isEditable.value),
data: [],
minHeight: 240,
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();
+ 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) {
+ sortableInstance.destroy();
+ sortableInstance = null;
+ }
+}
/** 鍔犺浇宸ュ簭鍒楄〃 */
async function getList() {
@@ -94,6 +160,7 @@
outputItemCode: item.outputItemCode,
outputItemName: item.outputItemName,
remark: item.remark,
+ bomItems: item.bomItems,
}));
}
} else {
@@ -106,6 +173,8 @@
}
gridApi.setGridOptions({ data: list.value });
emit('processListChange', list.value);
+ // 鍒濆鍖栨嫋鎷芥帓搴�
+ initSortable();
} finally {
gridApi.setLoading(false);
}
@@ -135,25 +204,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 +217,115 @@
}
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();
+ });
+ }
+}
+
+/** 鎵撳紑鏂板鐗╂枡寮圭獥 */
+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();
+});
// 鐩戝惉浜у搧/宸ュ崟鍙樺寲
watch(
@@ -183,8 +338,34 @@
</script>
<template>
- <div>
+ <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="[
@@ -197,6 +378,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>
@@ -212,6 +399,11 @@
<template #actions="{ row }">
<TableAction
:actions="[
+ {
+ label: '鏂板鐗╂枡',
+ type: 'link',
+ onClick: handleMaterialInput.bind(null, row),
+ },
{
label: $t('common.edit'),
type: 'link',
@@ -231,6 +423,26 @@
]"
/>
</template>
+ <template #expand_content="{ 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"
+ closable
+ color="blue"
+ @close="handleRemoveBomItem(row, idx)"
+ >
+ {{ bom.itemName || bom.itemCode }}
+ <span class="text-gray-400 ml-1">脳{{ bom.quantity }}</span>
+ </Tag>
+ </div>
+ </div>
+ </template>
</Grid>
</div>
-</template>
\ No newline at end of file
+</template>
--
Gitblit v1.9.3