| 2 天以前 | zhangwencui | ![]() |
| 2 天以前 | zhangwencui | ![]() |
| 3 天以前 | gaoluyang | ![]() |
| 3 天以前 | gaoluyang | ![]() |
| package.json | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| pnpm-lock.yaml | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| pnpm-workspace.yaml | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/erp/purchase/order/modules/form.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/erp/purchase/order/modules/process-select-modal.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/mes/pro/workorder/data.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/mes/pro/workorder/modules/form.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/mes/pro/workorder/modules/process-list.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| vite.config.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
package.json
@@ -63,6 +63,7 @@ "highlight.js": "catalog:", "livekit-client": "^2.20.0", "pinia": "catalog:", "sortablejs": "catalog:", "steady-xml": "catalog:", "tinymce": "catalog:", "tyme4ts": "catalog:", pnpm-lock.yaml
@@ -647,6 +647,9 @@ pinia: specifier: 'catalog:' version: 3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)) sortablejs: specifier: 'catalog:' version: 1.15.7 steady-xml: specifier: 'catalog:' version: 0.1.0 @@ -7724,8 +7727,8 @@ orderedmap@2.1.1: resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} own-keys@1.0.2: resolution: {integrity: sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==} engines: {node: '>= 0.4'} oxfmt@0.56.0: @@ -14218,7 +14221,7 @@ object-inspect: 1.13.4 object-keys: 1.1.1 object.assign: 4.1.7 own-keys: 1.0.1 own-keys: 1.0.2 regexp.prototype.flags: 1.5.4 safe-array-concat: 1.1.4 safe-push-apply: 1.0.0 @@ -16099,8 +16102,9 @@ orderedmap@2.1.1: {} own-keys@1.0.1: own-keys@1.0.2: dependencies: call-bound: 1.0.4 get-intrinsic: 1.3.0 object-keys: 1.1.1 safe-push-apply: 1.0.0 pnpm-workspace.yaml
@@ -210,10 +210,10 @@ allowBuilds: '@carbon/icons': true '@parcel/watcher': true '@vue-office/docx': set this to true or false '@vue-office/excel': set this to true or false '@vue-office/pdf': set this to true or false '@vue-office/pptx': set this to true or false '@vue-office/docx': true '@vue-office/excel': true '@vue-office/pdf': true '@vue-office/pptx': true core-js: true core-js-pure: true esbuild: true src/views/erp/purchase/order/modules/form.vue
@@ -1,7 +1,7 @@ <script lang="ts" setup> import type { FormType } from '../data'; import type { ErpPurchaseOrderApi, ProcessDefinition } from '#/api/erp/purchase/order'; import type { ErpPurchaseOrderApi } from '#/api/erp/purchase/order'; import { computed, ref } from 'vue'; @@ -14,7 +14,6 @@ import { confirmReceipt, createPurchaseOrder, getPurchaseApproveProcessList, getPurchaseOrder, updatePurchaseOrder, } from '#/api/erp/purchase/order'; @@ -22,13 +21,11 @@ import { useFormSchema } from '../data'; import PurchaseOrderItemForm from './item-form.vue'; import ProcessSelectModal from './process-select-modal.vue'; const emit = defineEmits(['success']); const formData = ref<ErpPurchaseOrderApi.PurchaseOrder>(); const formType = ref<FormType>('create'); const itemFormRef = ref<InstanceType<typeof PurchaseOrderItemForm>>(); const selectedProcessId = ref<string>(''); const getTitle = computed(() => { if (formType.value === 'create') { @@ -58,11 +55,6 @@ }, }); const [ProcessModal, processModalApi] = useVbenModal({ connectedComponent: ProcessSelectModal, destroyOnClose: true, }); /** æ´æ°éè´è®¢å项 */ function handleUpdateItems(items: ErpPurchaseOrderApi.PurchaseOrderItem[]) { formData.value = modalApi.getData<ErpPurchaseOrderApi.PurchaseOrder>(); @@ -86,12 +78,6 @@ }); } /** æµç¨éæ©æå */ function handleProcessSelectSuccess(processId: string) { selectedProcessId.value = processId; submitOrder(); } /** æäº¤è®¢å */ async function submitOrder() { const data = (await formApi.getValues()) as ErpPurchaseOrderApi.PurchaseOrder; @@ -101,9 +87,6 @@ })); if (data.fileUrl && Array.isArray(data.fileUrl)) { data.fileUrl = data.fileUrl.length > 0 ? data.fileUrl[0] : ''; } if (selectedProcessId.value) { data.processDefinitionId = selectedProcessId.value; } await (formType.value === 'create' ? createPurchaseOrder(data) @@ -145,35 +128,15 @@ } modalApi.lock(); if (formType.value === 'create') { try { const processes = await getPurchaseApproveProcessList(); if (processes.length === 1) { selectedProcessId.value = processes[0].id; await submitOrder(); } else if (processes.length > 1) { processModalApi.setData(processes).open(); } else { message.error('éè´å®¡æ ¸åç±»ä¸æ²¡æå¯ç¨çæµç¨æ¨¡åï¼è¯·å å建并å叿µç¨'); } } catch (error: any) { message.error(error.message || 'è·åæµç¨å表失败'); } finally { modalApi.unlock(); } } else { try { await submitOrder(); } finally { modalApi.unlock(); } try { await submitOrder(); } finally { modalApi.unlock(); } }, async onOpenChange(isOpen: boolean) { if (!isOpen) { formData.value = undefined; selectedProcessId.value = ''; return; } const data = modalApi.getData<{ formType: FormType; id?: number }>(); @@ -205,7 +168,6 @@ class="w-3/4" :show-confirm-button="formType !== 'detail'" > <ProcessModal @success="handleProcessSelectSuccess" /> <Form class="mx-3"> <template #items> <PurchaseOrderItemForm src/views/erp/purchase/order/modules/process-select-modal.vue
ÎļþÒÑɾ³ý src/views/mes/pro/workorder/data.ts
@@ -747,7 +747,6 @@ field: 'drag', title: '', width: 40, fixed: 'left', slots: { default: 'dragHandle' }, } as const, ] @@ -755,6 +754,7 @@ { type: 'expand', width: 50, slots: { content: 'expand_content' }, } as const, { field: 'sort', @@ -835,7 +835,7 @@ }, { title: 'æä½', width: 120, width: 200, fixed: 'right', slots: { default: 'actions' }, }, src/views/mes/pro/workorder/modules/form.vue
@@ -332,7 +332,8 @@ <template> <Modal :title="getTitle" class="w-3/5"> class="w-3/5" content-class="!max-h-[80vh] overflow-y-auto"> <Form class="mx-4" /> <!-- BOM / å·¥åº / ç©æéæ± Tab --> <template v-if="showTabs"> @@ -350,7 +351,8 @@ </div> </Tabs.TabPane> <Tabs.TabPane key="process" tab="å·¥åº"> tab="å·¥åº" forceRender> <ProcessList ref="processListRef" :form-type="formType" 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ï¼Sortable 已移å¨èç¹ï¼éæ¾ååä½ï¼ç±æ°æ®é©±å¨é渲æ 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> </template> vite.config.ts
@@ -1,14 +1,24 @@ import { resolve } from "node:path"; import { defineConfig } from "./internal/vite-config/src"; export default defineConfig(async () => { return { application: {}, vite: { resolve: { alias: { '@vue-office/pdf': resolve(__dirname, 'node_modules/@vue-office/pdf/lib/v3/vue-office-pdf.mjs'), '@vue-office/docx': resolve(__dirname, 'node_modules/@vue-office/docx/lib/v3/vue-office-docx.mjs'), '@vue-office/excel': resolve(__dirname, 'node_modules/@vue-office/excel/lib/v3/vue-office-excel.mjs'), '@vue-office/excel-css': resolve(__dirname, 'node_modules/@vue-office/excel/lib/v3/index.css'), '@vue-office/pptx': resolve(__dirname, 'node_modules/@vue-office/pptx/lib/v3/vue-office-pptx.mjs'), }, }, oxc: { enable: false, }, optimizeDeps: { include: ["vue", "vue-router", "pinia", "ant-design-vue", "dayjs", "@vueuse/core", "axios"], include: ["vue", "vue-router", "pinia", "ant-design-vue", "dayjs", "@vueuse/core", "axios", "@vue-office/pdf", "@vue-office/docx", "@vue-office/excel", "@vue-office/pptx"], exclude: ["dhtmlx-gantt", "tinymce", "bpmn-js", "bpmn-js-properties-panel", "video.js"], }, build: { @@ -28,6 +38,9 @@ if (["tinymce", "@tinymce/tinymce-vue", "@form-create/ant-design-vue"].some(dep => id.includes(dep))) { return "vendor-editor"; } if (["@vue-office"].some(dep => id.includes(dep))) { return "vendor-office-preview"; } } }, },