From 5372fa6dfe5c16163880de9f899578af35671e78 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期四, 30 七月 2026 09:24:02 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_pro2.0' into dev_pro2.0

---
 src/views/mes/pd/project/modules/document-audit-list.vue |    3 
 src/api/mes/pd/document-audit/index.ts                   |    1 
 src/views/mes/pro/task/data.ts                           |   44 ++++++++++++--
 src/api/mes/pro/task/index.ts                            |    8 ++
 src/views/mes/dv/machinery/data.ts                       |   25 ++++++++
 src/views/crm/customer/pool/index.vue                    |    1 
 src/views/mes/pd/project/detail/index.vue                |   11 +--
 src/views/crm/customer/detail/index.vue                  |   38 ++++++------
 src/views/mes/pro/workorder-center/index.vue             |    8 ++
 src/api/mes/dv/machinery/index.ts                        |    4 +
 src/views/mes/pro/feedback/index.vue                     |   11 +++
 src/views/mes/pro/task/index.vue                         |    8 ++
 src/views/mes/pro/workorder/index.vue                    |   12 +++
 13 files changed, 137 insertions(+), 37 deletions(-)

diff --git a/src/api/mes/dv/machinery/index.ts b/src/api/mes/dv/machinery/index.ts
index f57a772..c46b39f 100644
--- a/src/api/mes/dv/machinery/index.ts
+++ b/src/api/mes/dv/machinery/index.ts
@@ -17,6 +17,10 @@
     status?: number; // 璁惧鐘舵��
     lastMaintenTime?: Date; // 鏈�杩戜繚鍏绘椂闂�
     lastCheckTime?: Date; // 鏈�杩戠偣妫�鏃堕棿
+    productionEfficiency?: number; // 鐢熶骇鏁堢巼
+    conveyorBelt?: number; // 浼犻�佸饱甯︽暟閲�
+    workstationId?: number; // 宸ヤ綔绔欑紪鍙�
+    workstationName?: string; // 宸ヤ綔绔欏悕绉�
     remark?: string; // 澶囨敞
     createTime?: Date; // 鍒涘缓鏃堕棿
   }
diff --git a/src/api/mes/pd/document-audit/index.ts b/src/api/mes/pd/document-audit/index.ts
index ed0db38..b6e21c8 100644
--- a/src/api/mes/pd/document-audit/index.ts
+++ b/src/api/mes/pd/document-audit/index.ts
@@ -29,6 +29,7 @@
   }
 
   export interface PageParams extends PageParam {
+    projectId?: number;
     auditNo?: string;
     documentId?: number;
     documentName?: string;
diff --git a/src/api/mes/pro/task/index.ts b/src/api/mes/pro/task/index.ts
index 2e36c40..515185b 100644
--- a/src/api/mes/pro/task/index.ts
+++ b/src/api/mes/pro/task/index.ts
@@ -166,3 +166,11 @@
     { params },
   );
 }
+
+/** 鏍规嵁宸ヤ綔绔欏拰鏁伴噺璁$畻鐢熶骇鏃堕暱 */
+export function calculateTaskDuration(workstationId: number, quantity: number) {
+  return requestClient.get<{ duration: number }>(
+    '/mes/pro/task/calculate-duration',
+    { params: { workstationId, quantity } },
+  );
+}
diff --git a/src/views/crm/customer/detail/index.vue b/src/views/crm/customer/detail/index.vue
index 85caeac..526affd 100644
--- a/src/views/crm/customer/detail/index.vue
+++ b/src/views/crm/customer/detail/index.vue
@@ -133,18 +133,18 @@
 }
 
 /** 瀹㈡埛鏀惧叆鍏捣 */
-async function handlePutPool(): Promise<boolean | undefined> {
-  try {
-    await confirm(`纭畾灏嗗鎴枫��${customer.value.name}銆戞斁鍏ュ叕娴峰悧锛焋);
-  } catch {
-    return false;
-  }
-  // 鏀惧叆鍏捣
-  await putCustomerPool(customerId.value);
-  // 鎻愮ず骞惰繑鍥炴垚鍔�
-  message.success('鏀惧叆鍏捣鎴愬姛');
-  return true;
-}
+// async function handlePutPool(): Promise<boolean | undefined> {
+//   try {
+//     await confirm(`纭畾灏嗗鎴枫��${customer.value.name}銆戞斁鍏ュ叕娴峰悧锛焋);
+//   } catch {
+//     return false;
+//   }
+//   // 鏀惧叆鍏捣
+//   await putCustomerPool(customerId.value);
+//   // 鎻愮ず骞惰繑鍥炴垚鍔�
+//   message.success('鏀惧叆鍏捣鎴愬姛');
+//   return true;
+// }
 
 /** 鏇存柊鎴愪氦鐘舵�佹搷浣� */
 async function handleUpdateDealStatus(): Promise<boolean | undefined> {
@@ -229,13 +229,13 @@
             ifShow: !customer.ownerUserId,
             onClick: handleDistributeForm,
           },
-          {
-            label: '鏀惧叆鍏捣',
-            type: 'default',
-            ifShow:
-              !!customer.ownerUserId && permissionListRef?.validateOwnerUser,
-            onClick: handlePutPool,
-          },
+          // {
+          //   label: '鏀惧叆鍏捣',
+          //   type: 'default',
+          //   ifShow:
+          //     !!customer.ownerUserId && permissionListRef?.validateOwnerUser,
+          //   onClick: handlePutPool,
+          // },
         ]"
       />
     </template>
diff --git a/src/views/crm/customer/pool/index.vue b/src/views/crm/customer/pool/index.vue
index 13313a4..6fa6410 100644
--- a/src/views/crm/customer/pool/index.vue
+++ b/src/views/crm/customer/pool/index.vue
@@ -43,6 +43,7 @@
             pageNo: page.currentPage,
             pageSize: page.pageSize,
             pool: true,
+            sceneType: 4,
             ...formValues,
           });
         },
diff --git a/src/views/mes/dv/machinery/data.ts b/src/views/mes/dv/machinery/data.ts
index ef002c5..8b39782 100644
--- a/src/views/mes/dv/machinery/data.ts
+++ b/src/views/mes/dv/machinery/data.ts
@@ -113,6 +113,29 @@
       rules: z.number().default(MesDvMachineryStatusEnum.STOP),
     },
     {
+      fieldName: 'productionEfficiency',
+      label: '鐢熶骇鏁堢巼',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        precision: 2,
+        placeholder: '璇疯緭鍏ョ敓浜ф晥鐜�(姣忓皬鏃�)',
+      },
+    },
+    {
+      fieldName: 'conveyorBelt',
+      label: '浼犻�佸饱甯�',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        precision: 0,
+        placeholder: '璇疯緭鍏ヤ紶閫佸饱甯︽暟閲�',
+      },
+      help: '2浠h〃鍙屽�嶆晥鐜�',
+    },
+    {
       fieldName: 'specification',
       label: '瑙勬牸鍨嬪彿',
       component: 'Input',
@@ -233,6 +256,8 @@
         props: { type: DICT_TYPE.MES_DV_MACHINERY_STATUS },
       },
     },
+    { field: 'productionEfficiency', title: '鐢熶骇鏁堢巼(/h)', width: 120 },
+    { field: 'conveyorBelt', title: '浼犻�佸饱甯�', width: 100 },
     {
       field: 'lastCheckTime',
       title: '鏈�杩戠偣妫�鏃堕棿',
diff --git a/src/views/mes/pd/project/detail/index.vue b/src/views/mes/pd/project/detail/index.vue
index be5f1f1..36da5ce 100644
--- a/src/views/mes/pd/project/detail/index.vue
+++ b/src/views/mes/pd/project/detail/index.vue
@@ -59,7 +59,7 @@
 );
 
 const materialFormType = computed<FormType>(() =>
-  isPendingPublish.value || isPendingUpload.value || isPendingAudit.value ? 'update' : 'detail',
+  isPendingPublish.value || isPendingUpload.value ? 'update' : 'detail',
 );
 
 const [Descriptions] = useDescription({
@@ -169,35 +169,30 @@
             label: $t('common.edit'),
             type: 'primary',
             icon: ACTION_ICON.EDIT,
-            auth: ['mes:pd-project:update'],
             ifShow: isFormEditable,
             onClick: handleEdit,
           },
           {
             label: '鍙戝竷',
             type: 'primary',
-            auth: ['mes:pd-project:update'],
             ifShow: isPendingPublish,
             onClick: handlePublish,
           },
           {
             label: '纭涓婁紶',
             type: 'primary',
-            auth: ['mes:pd-project:update'],
             ifShow: isPendingUpload,
             onClick: handleUpload,
           },
           {
             label: '瀹℃牳閫氳繃',
             type: 'primary',
-            auth: ['mes:pd-project:audit'],
             ifShow: isPendingAudit,
             onClick: handleAudit,
           },
           {
             label: '褰掓。',
             type: 'default',
-            auth: ['mes:pd-project:archive'],
             ifShow: isPendingArchive,
             onClick: handleArchive,
           },
@@ -223,7 +218,7 @@
         <Tabs.TabPane key="workflow" tab="娴佺▼杩涘害" :force-render="true">
           <WorkflowTimeline :status="project.status ?? 0" />
         </Tabs.TabPane>
-        <Tabs.TabPane v-if="!isPendingAudit" key="material" tab="璁捐璧勬枡" :force-render="true">
+        <Tabs.TabPane v-if="!isPendingAudit" key="material" tab="璁捐璧勬枡">
           <p v-if="isPendingPublish" class="mb-3 text-sm text-amber-600">
             璇峰厛鍙戝竷浠诲姟鍚庡啀涓婁紶璁捐璧勬枡
           </p>
@@ -233,7 +228,7 @@
             :project-status="project.status"
           />
         </Tabs.TabPane>
-        <Tabs.TabPane v-if="isPendingAudit" key="audit" tab="璧勬枡瀹℃牳" :force-render="true">
+        <Tabs.TabPane v-if="isPendingAudit" key="audit" tab="璧勬枡瀹℃牳">
           <DocumentAuditList :project-id="projectId" @success="loadProjectDetail" />
         </Tabs.TabPane>
       </Tabs>
diff --git a/src/views/mes/pd/project/modules/document-audit-list.vue b/src/views/mes/pd/project/modules/document-audit-list.vue
index 9b29cac..609dde5 100644
--- a/src/views/mes/pd/project/modules/document-audit-list.vue
+++ b/src/views/mes/pd/project/modules/document-audit-list.vue
@@ -63,6 +63,7 @@
           return await getPdDocumentAuditPage({
             pageNo: page.currentPage,
             pageSize: page.pageSize,
+            projectId: props.projectId,
             ...formValues,
           });
         },
@@ -84,7 +85,7 @@
             label: '瀹℃牳',
             type: 'link',
             icon: ACTION_ICON.EDIT,
-            auth: ['mes:pd-document-audit:update'],
+            ifShow: row.auditStatus === 0,
             onClick: handleAudit.bind(null, row),
           },
         ]"
diff --git a/src/views/mes/pro/feedback/index.vue b/src/views/mes/pro/feedback/index.vue
index 239da4a..1793ebb 100644
--- a/src/views/mes/pro/feedback/index.vue
+++ b/src/views/mes/pro/feedback/index.vue
@@ -17,10 +17,13 @@
 } from '#/api/mes/pro/feedback';
 import { $t } from '#/locales';
 
+import { ref } from 'vue';
+
 import { useGridColumns, useGridFormSchema } from './data';
 import Form from './modules/form.vue';
 import QuickFeedbackForm from '../workorder-center/modules/quick-feedback.vue';
 import BatchFeedbackForm from './modules/batch-feedback.vue';
+import ArchiveDetail from '#/views/mes/pd/archive/modules/detail.vue';
 
 const userStore = useUserStore();
 const currentUserId = userStore.userInfo?.id; // 褰撳墠鐧诲綍鐢ㄦ埛 ID锛岀敤浜庡鎵逛汉鏉冮檺鍒ゆ柇
@@ -39,6 +42,8 @@
   connectedComponent: BatchFeedbackForm,
   destroyOnClose: true,
 });
+
+const archiveDetailRef = ref<InstanceType<typeof ArchiveDetail>>();
 
 /** 鍒锋柊琛ㄦ牸 */
 function handleRefresh() {
@@ -205,9 +210,15 @@
                 confirm: handleDelete.bind(null, row),
               },
             },
+            {
+              label: '褰掓。',
+              type: 'link',
+              onClick: () => archiveDetailRef.value?.open(row.itemCode),
+            },
           ]"
         />
       </template>
     </Grid>
+    <ArchiveDetail ref="archiveDetailRef" />
   </Page>
 </template>
diff --git a/src/views/mes/pro/task/data.ts b/src/views/mes/pro/task/data.ts
index bba413d..a24e576 100644
--- a/src/views/mes/pro/task/data.ts
+++ b/src/views/mes/pro/task/data.ts
@@ -10,7 +10,10 @@
 import { DICT_TYPE } from '@vben/constants';
 import { getDictOptions } from '@vben/hooks';
 
+import { message } from 'ant-design-vue';
+
 import CrmCustomerSelect from '#/components/crm-customer-select.vue';
+import { calculateTaskDuration } from '#/api/mes/pro/task';
 import { getRangePickerDefaultProps } from '#/utils';
 import { MdItemSelect } from '#/views/mes/md/item/components';
 import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
@@ -386,6 +389,7 @@
       component: markRaw(MdWorkstationSelect),
       componentProps: {
         placeholder: '璇烽�夋嫨宸ヤ綔绔�',
+        onChange: () => recalcDuration(formApi),
       },
       rules: 'selectRequired',
     },
@@ -399,6 +403,7 @@
         min: 0.01,
         placeholder: '璇疯緭鍏ユ帓浜ф暟閲�',
         precision: 2,
+        onChange: () => recalcDuration(formApi),
       },
       rules: 'required',
     },
@@ -413,11 +418,17 @@
       component: 'DatePicker',
       componentProps: {
         class: '!w-full',
-        placeholder: '璇烽�夋嫨寮�濮嬫椂闂�',
+        placeholder: '璇峰厛閫夋嫨宸ヤ綔绔�',
         showTime: true,
         valueFormat: 'YYYY-MM-DD HH:mm:ss',
-        // 寮�濮嬫椂闂村彉鏇达細閲嶆柊璁$畻缁撴潫鏃堕棿
         onChange: () => recalcEndTime(formApi),
+      },
+      dependencies: {
+        triggerFields: ['workstationId'],
+        componentProps: (values) => ({
+          disabled: !values.workstationId,
+          placeholder: values.workstationId ? '璇烽�夋嫨寮�濮嬫椂闂�' : '璇峰厛閫夋嫨宸ヤ綔绔�',
+        }),
       },
       rules: 'required',
     },
@@ -459,6 +470,24 @@
   ];
 }
 
+/** 閫夋嫨宸ヤ綔绔欐垨淇敼鏁伴噺鍚庯紝鑷姩璁$畻鐢熶骇鏃堕暱 */
+async function recalcDuration(formApi?: VbenFormApi) {
+  if (!formApi) return;
+  const values = await formApi.getValues();
+  if (!values.workstationId || !values.quantity) return;
+  try {
+    const res = await calculateTaskDuration(values.workstationId, values.quantity);
+    await formApi.setFieldValue('duration', res.duration);
+    if (res.duration === 0) {
+      message.warning('璇ュ伐浣滅珯鏈粦瀹氳澶囷紝鎴栫粦瀹氳澶囨湭閰嶇疆鐢熶骇鏁堢巼');
+    }
+    // 閲嶆柊璁$畻缁撴潫鏃堕棿
+    await recalcEndTime(formApi);
+  } catch {
+    // 璁$畻澶辫触涓嶉樆濉炵敤鎴锋搷浣�
+  }
+}
+
 /** 璁$畻缁撴潫鏃堕棿锛氬紑濮嬫椂闂� + 鐢熶骇鏃堕暱锛堝皬鏃讹級 */
 async function recalcEndTime(formApi?: VbenFormApi) {
   if (!formApi) {
@@ -466,11 +495,12 @@
   }
   const values = await formApi.getValues();
   if (values.startTime && values.duration) {
-    const start = Number(values.startTime);
-    await formApi.setFieldValue(
-      'endTime',
-      start + values.duration * 3600 * 1000,
-    );
+    const start = new Date(values.startTime).getTime();
+    const endTime = start + values.duration * 3600 * 1000;
+    const pad = (n: number) => String(n).padStart(2, '0');
+    const d = new Date(endTime);
+    const formatted = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
+    await formApi.setFieldValue('endTime', formatted);
   }
 }
 
diff --git a/src/views/mes/pro/task/index.vue b/src/views/mes/pro/task/index.vue
index f062c80..e4002ea 100644
--- a/src/views/mes/pro/task/index.vue
+++ b/src/views/mes/pro/task/index.vue
@@ -17,6 +17,7 @@
 import { getGanttTaskList, previewAutoGenerate, autoGenerateTasks } from '#/api/mes/pro/task';
 import { getWorkOrderPage } from '#/api/mes/pro/workorder';
 
+import ArchiveDetail from '#/views/mes/pd/archive/modules/detail.vue';
 import { GanttChart } from './components';
 import { useGridColumns, useGridFormSchema } from './data';
 import ScheduleForm from './modules/schedule-form.vue';
@@ -24,6 +25,7 @@
 
 const router = useRouter();
 const ganttTasks = ref<any[]>([]); // 鐢樼壒鍥鹃瑙堟暟鎹�
+const archiveDetailRef = ref<InstanceType<typeof ArchiveDetail>>();
 
 const [ScheduleModal, scheduleModalApi] = useVbenModal({
   connectedComponent: ScheduleForm,
@@ -166,9 +168,15 @@
               ifShow: row.status === MesProWorkOrderStatusEnum.CONFIRMED,
               onClick: handleSchedule.bind(null, row),
             },
+            {
+              label: '褰掓。',
+              type: 'link',
+              onClick: () => archiveDetailRef.value?.open(row.productCode),
+            },
           ]"
         />
       </template>
     </Grid>
+    <ArchiveDetail ref="archiveDetailRef" />
   </Page>
 </template>
diff --git a/src/views/mes/pro/workorder-center/index.vue b/src/views/mes/pro/workorder-center/index.vue
index 74b05b7..99ada7d 100644
--- a/src/views/mes/pro/workorder-center/index.vue
+++ b/src/views/mes/pro/workorder-center/index.vue
@@ -18,11 +18,13 @@
 } from '#/api/mes/pro/workorder';
 import { previewAutoGenerate, autoGenerateTasks } from '#/api/mes/pro/task';
 
+import ArchiveDetail from '#/views/mes/pd/archive/modules/detail.vue';
 import { useGridColumns, useGridFormSchema, useTaskGridColumns } from './data';
 import QuickFeedbackForm from './modules/quick-feedback.vue';
 import SchedulePreviewModal from './modules/schedule-preview.vue';
 
 defineOptions({ name: 'WorkorderCenter' });
+const archiveDetailRef = ref<InstanceType<typeof ArchiveDetail>>();
 
 const router = useRouter();
 
@@ -207,6 +209,11 @@
                 confirm: handleScheduleExecute.bind(null, row),
               },
             },
+            {
+              label: '褰掓。',
+              type: 'link',
+              onClick: () => archiveDetailRef.value?.open(row.productCode),
+            },
           ]"
         />
       </template>
@@ -309,5 +316,6 @@
         </Collapse.Panel>
       </Collapse>
     </Card>
+    <ArchiveDetail ref="archiveDetailRef" />
   </Page>
 </template>
diff --git a/src/views/mes/pro/workorder/index.vue b/src/views/mes/pro/workorder/index.vue
index 6510afd..e3c8248 100644
--- a/src/views/mes/pro/workorder/index.vue
+++ b/src/views/mes/pro/workorder/index.vue
@@ -22,7 +22,8 @@
     getWorkOrderPage,
   } from "#/api/mes/pro/workorder";
   import { $t } from "#/locales";
-  import { BarcodeDetail } from "#/views/wls/barcode/components";
+  import ArchiveDetail from '#/views/mes/pd/archive/modules/detail.vue';
+import { BarcodeDetail } from '#/views/wls/barcode/components';
 
   import { useGridColumns, useGridFormSchema } from "./data";
   import Form from "./modules/form.vue";
@@ -32,7 +33,8 @@
     destroyOnClose: true,
   });
 
-  const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>(); // 鏉$爜璇︽儏寮圭獥
+  const archiveDetailRef = ref<InstanceType<typeof ArchiveDetail>>();
+const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>(); // 鏉$爜璇︽儏寮圭獥
 
   /** 鍒锋柊琛ㄦ牸 */
   function handleRefresh() {
@@ -225,9 +227,15 @@
               auth: ['mes:pro-work-order:query'],
               onClick: handleBarcode.bind(null, row),
             },
+            {
+              label: '褰掓。',
+              type: 'link',
+              onClick: () => archiveDetailRef.value?.open(row.productCode),
+            },
           ]" />
       </template>
     </Grid>
     <BarcodeDetail ref="barcodeDetailRef" />
+    <ArchiveDetail ref="archiveDetailRef" />
   </Page>
 </template>

--
Gitblit v1.9.3