From 99c00652cf3895009eacdb05025e02967a670367 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期三, 05 八月 2026 11:36:00 +0800
Subject: [PATCH] feat(mes): 实现生产报工批次属性管理和SRM投标优化功能

---
 src/views/crm/product/components/edit-table.vue                |   29 ++
 src/views/mes/md/item/type/components/select.vue               |   18 -
 src/views/mes/pro/feedback/modules/form.vue                    |   46 ++++
 src/views/mes/pro/feedback/modules/batch-input-dialog.vue      |  177 +++++++++++++++++++
 src/views/crm/product/components/data.ts                       |   26 ++
 src/views/srm/tender/detail.vue                                |   42 ++++
 src/views/mes/workbench/components/tabs/FeedbackHistoryTab.vue |   40 +++
 src/views/mes/pro/feedback/index.vue                           |   37 ----
 src/views/erp/purchase/request/index.vue                       |    4 
 src/views/mes/workbench/components/tabs/FeedbackTab.vue        |    6 
 src/api/crm/business/index.ts                                  |   13 
 src/views/basicData/mdm/data.ts                                |   45 ++++
 src/api/mes/pro/feedback/index.ts                              |   33 +++
 src/api/mdm/warehouse/index.ts                                 |   19 ++
 14 files changed, 438 insertions(+), 97 deletions(-)

diff --git a/src/api/crm/business/index.ts b/src/api/crm/business/index.ts
index f30b5d3..8f6b241 100644
--- a/src/api/crm/business/index.ts
+++ b/src/api/crm/business/index.ts
@@ -38,11 +38,14 @@
   /** 鍟嗘満浜у搧淇℃伅 */
   export interface BusinessProduct {
     id: number;
-    productId: number;
-    productName: string;
-    productNo: string;
-    productUnit: number;
-    productPrice: number;
+    itemId: number;
+    itemName: string;
+    itemCode: string;
+    barCode: string;
+    itemSpecification: string;
+    itemUnitId: number;
+    itemUnitName: string;
+    itemPrice: number;
     businessPrice: number;
     count: number;
     totalPrice: number;
diff --git a/src/api/mdm/warehouse/index.ts b/src/api/mdm/warehouse/index.ts
new file mode 100644
index 0000000..1ff141f
--- /dev/null
+++ b/src/api/mdm/warehouse/index.ts
@@ -0,0 +1,19 @@
+import { requestClient } from '#/api/request';
+
+export namespace MdmWarehouseApi {
+  export interface Warehouse {
+    id?: number;
+    code?: string;
+    name?: string;
+    address?: string;
+    type?: number;
+    status?: number;
+  }
+}
+
+/** 鏌ヨ鍏ㄩ儴鍚敤鐨勪粨搴撳垪琛� */
+export function getWarehouseSimpleList() {
+  return requestClient.get<MdmWarehouseApi.Warehouse[]>(
+    '/mdm/warehouse/list-all-simple',
+  );
+}
diff --git a/src/api/mes/pro/feedback/index.ts b/src/api/mes/pro/feedback/index.ts
index 78eecc3..7666071 100644
--- a/src/api/mes/pro/feedback/index.ts
+++ b/src/api/mes/pro/feedback/index.ts
@@ -110,8 +110,37 @@
 }
 
 /** 瀹℃壒鐢熶骇鎶ュ伐锛堣繑鍥炴槸鍚﹀凡瀹℃壒瀹屾垚锛� */
-export function approveFeedback(id: number) {
-  return requestClient.put<boolean>(`/mes/pro/feedback/approve?id=${id}`);
+export function approveFeedback(data: { id: number; [key: string]: unknown }) {
+  return requestClient.put<boolean>(`/mes/pro/feedback/approve`, data);
+}
+
+/** 鎵规闇�姹傛鏌ュ搷搴� */
+export interface BatchCheckResp {
+  needBatchInput?: boolean;
+  isBatchManaged?: boolean;
+  produceDateFlag?: boolean;
+  expireDateFlag?: boolean;
+  receiptDateFlag?: boolean;
+  vendorFlag?: boolean;
+  clientFlag?: boolean;
+  salesOrderCodeFlag?: boolean;
+  purchaseOrderCodeFlag?: boolean;
+  workOrderFlag?: boolean;
+  taskFlag?: boolean;
+  workstationFlag?: boolean;
+  toolFlag?: boolean;
+  moldFlag?: boolean;
+  lotNumberFlag?: boolean;
+  qualityStatusFlag?: boolean;
+  autoFilledFields?: string[];
+}
+
+/** 妫�鏌ユ姤宸ュ鎵瑰墠鏄惁闇�瑕佸~鍐欐壒娆″睘鎬� */
+export function getBatchRequirements(feedbackId: number) {
+  return requestClient.get<BatchCheckResp>(
+    `/mes/pro/feedback/batch-requirements`,
+    { params: { id: feedbackId } },
+  );
 }
 
 /** 蹇�熸姤宸ヨ姹� */
diff --git a/src/views/basicData/mdm/data.ts b/src/views/basicData/mdm/data.ts
index 953b9f2..a9d6ca9 100644
--- a/src/views/basicData/mdm/data.ts
+++ b/src/views/basicData/mdm/data.ts
@@ -11,12 +11,17 @@
 import { MesAutoCodeRuleCode } from '@vben/constants';
 import { getItemTypeSimpleList } from '#/api/mes/md/item/type';
 import { getUnitPage } from '#/api/mdm/unit';
+import { getWarehouseSimpleList } from '#/api/mes/wm/warehouse';
 
 import { Button } from 'ant-design-vue';
 
 // 缂撳瓨鍗曚綅鍒楄〃
 let unitListCache: any[] | null = null;
 let unitListPromise: Promise<any[]> | null = null;
+
+// 缂撳瓨浠撳簱鍒楄〃
+let warehouseListCache: any[] | null = null;
+let warehouseListPromise: Promise<any[]> | null = null;
 
 /** 灏嗘墎骞冲垎绫诲垪琛ㄨ浆涓� TreeSelect 鎵�闇�鐨勬爲褰㈢粨鏋� */
 export function buildCategoryTree(list: any[]): any[] {
@@ -53,9 +58,27 @@
   return unitListPromise;
 }
 
+/** 鑾峰彇浠撳簱鍒楄〃锛堝甫缂撳瓨锛� */
+async function getWarehouseListCached() {
+  if (warehouseListCache) {
+    return warehouseListCache;
+  }
+  if (warehouseListPromise) {
+    return warehouseListPromise;
+  }
+  warehouseListPromise = getWarehouseSimpleList().then((res) => {
+    warehouseListCache = res || [];
+    return warehouseListCache;
+  }).finally(() => {
+    warehouseListPromise = null;
+  });
+  return warehouseListPromise;
+}
+
 /** 娓呴櫎鍗曚綅鍒楄〃缂撳瓨 */
 export function clearUnitListCache() {
   unitListCache = null;
+  warehouseListCache = null;
 }
 
 /** 鏂板/淇敼鐨勮〃鍗� */
@@ -188,11 +211,29 @@
       },
     },
     {
-      component: 'Input',
+      component: 'ApiSelect',
       fieldName: 'warehouseId',
       label: '榛樿浠撳簱',
       componentProps: {
-        placeholder: '璇疯緭鍏ラ粯璁や粨搴揑D',
+        placeholder: '璇烽�夋嫨榛樿浠撳簱',
+        allowClear: true,
+        api: getWarehouseListCached,
+        labelField: 'name',
+        valueField: 'id',
+        onChange: async (value: number | undefined) => {
+          const code = value
+            ? ((await getWarehouseListCached()).find((w: any) => w.id === value)?.code || '')
+            : '';
+          formApi?.setFieldValue('warehouseCode', code);
+        },
+      },
+    },
+    {
+      component: 'Input',
+      fieldName: 'warehouseCode',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
       },
     },
     {
diff --git a/src/views/crm/product/components/data.ts b/src/views/crm/product/components/data.ts
index 75d25ba..462508a 100644
--- a/src/views/crm/product/components/data.ts
+++ b/src/views/crm/product/components/data.ts
@@ -14,7 +14,7 @@
       title: '鐗╂枡缂栫爜',
     },
     {
-      field: 'itemBarCode',
+      field: 'barCode',
       title: '鐗╂枡鏉$爜',
     },
     {
@@ -52,7 +52,22 @@
 }
 
 /** 浜у搧缂栬緫琛ㄦ牸鐨勫垪瀹氫箟 */
-export function useProductEditTableColumns(): VxeTableGridOptions['columns'] {
+export function useProductEditTableColumns(
+  showBusinessPrice: boolean = false,
+): VxeTableGridOptions['columns'] {
+  const priceColumn = showBusinessPrice
+    ? {
+        field: 'businessPrice',
+        title: '鍟嗘満浠锋牸锛堝厓锛�',
+        minWidth: 100,
+        slots: { default: 'businessPrice' },
+      }
+    : {
+        field: 'contractPrice',
+        title: '鍚堝悓浠凤紙鍏冿級',
+        minWidth: 100,
+        slots: { default: 'contractPrice' },
+      };
   return [
     { type: 'seq', title: '搴忓彿', minWidth: 50 },
     {
@@ -87,12 +102,7 @@
       minWidth: 100,
       formatter: 'formatAmount2',
     },
-    {
-      field: 'contractPrice',
-      title: '鍚堝悓浠凤紙鍏冿級',
-      minWidth: 100,
-      slots: { default: 'contractPrice' },
-    },
+    priceColumn,
     {
       field: 'count',
       title: '鏁伴噺',
diff --git a/src/views/crm/product/components/edit-table.vue b/src/views/crm/product/components/edit-table.vue
index 113080c..f7cb97a 100644
--- a/src/views/crm/product/components/edit-table.vue
+++ b/src/views/crm/product/components/edit-table.vue
@@ -3,7 +3,7 @@
 import type { CrmContractApi } from '#/api/crm/contract';
 import type { MdmItemApi } from '#/api/mdm/item';
 
-import { nextTick, ref, watch } from 'vue';
+import { computed, nextTick, ref, watch } from 'vue';
 
 import { erpPriceMultiply } from '@vben/utils';
 
@@ -24,6 +24,12 @@
 }>();
 
 const emit = defineEmits(['update:products']);
+
+/** 鏄惁涓哄晢鏈虹被鍨� */
+const isBusiness = computed(() => props.bizType === BizTypeEnum.CRM_BUSINESS);
+
+/** 褰撳墠涓氬姟绫诲瀷瀵瑰簲鐨勪环鏍煎瓧娈靛悕 */
+const priceField = computed(() => isBusiness.value ? 'businessPrice' : 'contractPrice');
 
 /** 琛ㄦ牸鍐呴儴鏁版嵁 */
 const tableData = ref<any[]>([]);
@@ -46,28 +52,29 @@
   row.itemId = product.id;
   row.itemName = product.name;
   row.itemCode = product.code;
-  row.itemBarCode = product.barCode;
+  row.barCode = product.barCode;
+  row.itemUnitId = product.unitMeasureId;
   row.itemUnitName = product.unitMeasureName;
   row.itemUnitName2 = product.unitMeasureName2;
   row.itemUnitName3 = product.unitMeasureName3;
   row.itemSpecification = product.specification;
   row.itemPrice = product.salesPrice;
-  row.contractPrice = product.salesPrice;
+  row[priceField.value] = product.salesPrice;
   row.count = row.count || 1;
-  row.totalPrice = erpPriceMultiply(row.contractPrice, row.count) ?? 0;
+  row.totalPrice = erpPriceMultiply(row[priceField.value], row.count) ?? 0;
   handleUpdateValue(row);
 }
 
 /** 閲戦鍙樺姩鏃堕噸鏂拌绠楀悎璁� */
 function handlePriceChange(row: any) {
-  row.totalPrice = erpPriceMultiply(row.contractPrice, row.count) ?? 0;
+  row.totalPrice = erpPriceMultiply(row[priceField.value], row.count) ?? 0;
   handleUpdateValue(row);
 }
 
 /** 灏嗘渶鏂版暟鎹啓鍥炲苟閫氱煡鐖剁粍浠� */
 function handleUpdateValue(row: any) {
   const index = tableData.value.findIndex((item) => item.id === row.id);
-  row.totalPrice = erpPriceMultiply(row.contractPrice, row.count) ?? 0;
+  row.totalPrice = erpPriceMultiply(row[priceField.value], row.count) ?? 0;
   if (index === -1) {
     row.id = tableData.value.length + 1;
     tableData.value.push(row);
@@ -83,7 +90,7 @@
       trigger: 'click',
       mode: 'cell',
     },
-    columns: useProductEditTableColumns(),
+    columns: useProductEditTableColumns(isBusiness.value),
     data: tableData.value,
     border: true,
     showOverflow: true,
@@ -137,6 +144,14 @@
         @change="handlePriceChange(row)"
       />
     </template>
+    <template #businessPrice="{ row }">
+      <InputNumber
+        v-model:value="row.businessPrice"
+        :min="0.001"
+        :precision="2"
+        @change="handlePriceChange(row)"
+      />
+    </template>
     <template #count="{ row }">
       <InputNumber
         v-model:value="row.count"
diff --git a/src/views/erp/purchase/request/index.vue b/src/views/erp/purchase/request/index.vue
index 27336e9..cd97ba8 100644
--- a/src/views/erp/purchase/request/index.vue
+++ b/src/views/erp/purchase/request/index.vue
@@ -122,6 +122,10 @@
 
 /** 鏌ョ湅瀹℃壒璇︽儏 */
 function handleViewProcess(row: ErpPurchaseRequestApi.PurchaseRequest) {
+  if (!row.processInstanceId) {
+    message.warning('璇ョ敵璇峰皻鏈彁浜ゅ鎵�');
+    return;
+  }
   router.push({ name: 'BpmProcessInstanceDetail', query: { id: row.processInstanceId } });
 }
 
diff --git a/src/views/mes/md/item/type/components/select.vue b/src/views/mes/md/item/type/components/select.vue
index 11cac62..0f5c0d2 100644
--- a/src/views/mes/md/item/type/components/select.vue
+++ b/src/views/mes/md/item/type/components/select.vue
@@ -48,22 +48,6 @@
   },
 });
 
-/** 閫掑綊灏嗘湁瀛愯妭鐐圭殑鍒嗘敮鑺傜偣鏍囪涓� disabled */
-function markParentsDisabled(
-  nodes: MesMdItemTypeApi.ItemType[],
-): ItemTypeNode[] {
-  return nodes.map((node) => {
-    const children = node.children?.length
-      ? markParentsDisabled(node.children)
-      : undefined;
-    return {
-      ...node,
-      children,
-      disabled: Boolean(children?.length),
-    };
-  });
-}
-
 /** 鏍规嵁褰撳墠鍊煎悓姝� tooltip 灞曠ず鐨勫垎绫昏鎯� */
 function syncSelectedItem(value: number | undefined) {
   selectedItem.value =
@@ -81,7 +65,7 @@
 /** 鏌ヨ鐗╂枡鍒嗙被鏍� */
 async function getItemTypeTree() {
   allList.value = await getItemTypeSimpleList();
-  itemTypeTree.value = markParentsDisabled(handleTree(allList.value));
+  itemTypeTree.value = handleTree(allList.value) as ItemTypeNode[];
   syncSelectedItem(props.modelValue);
 }
 
diff --git a/src/views/mes/pro/feedback/index.vue b/src/views/mes/pro/feedback/index.vue
index b60997e..adb6ae1 100644
--- a/src/views/mes/pro/feedback/index.vue
+++ b/src/views/mes/pro/feedback/index.vue
@@ -22,8 +22,6 @@
 
 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();
@@ -31,16 +29,6 @@
 
 const [FormModal, formModalApi] = useVbenModal({
   connectedComponent: Form,
-  destroyOnClose: true,
-});
-
-const [QuickFeedbackModal, quickFeedbackModalApi] = useVbenModal({
-  connectedComponent: QuickFeedbackForm,
-  destroyOnClose: true,
-});
-
-const [BatchFeedbackModal, batchFeedbackModalApi] = useVbenModal({
-  connectedComponent: BatchFeedbackForm,
   destroyOnClose: true,
 });
 
@@ -108,16 +96,6 @@
   }
 }
 
-/** 蹇�熸姤宸� */
-function handleQuickCreate() {
-  quickFeedbackModalApi.setData({}).open();
-}
-
-/** 鎵归噺鎶ュ伐 */
-function handleBatchCreate() {
-  batchFeedbackModalApi.setData({}).open();
-}
-
 const [Grid, gridApi] = useVbenVxeGrid({
   formOptions: { schema: useGridFormSchema() },
   gridOptions: {
@@ -144,8 +122,6 @@
 <template>
   <Page auto-content-height>
     <FormModal @success="handleRefresh" />
-    <QuickFeedbackModal @success="handleRefresh" />
-    <BatchFeedbackModal @success="handleRefresh" />
     <Grid table-title="鐢熶骇鎶ュ伐鍒楄〃">
       <template #toolbar-tools>
         <TableAction
@@ -156,19 +132,6 @@
               icon: ACTION_ICON.ADD,
               auth: ['mes:pro-feedback:create'],
               onClick: handleCreate,
-            },
-            {
-              label: '蹇�熸姤宸�',
-              type: 'primary',
-              icon: ACTION_ICON.ADD,
-              auth: ['mes:pro-feedback:create'],
-              onClick: handleQuickCreate,
-            },
-            {
-              label: '鎵归噺鎶ュ伐',
-              type: 'primary',
-              auth: ['mes:pro-feedback:create'],
-              onClick: handleBatchCreate,
             },
             {
               label: $t('ui.actionTitle.export'),
diff --git a/src/views/mes/pro/feedback/modules/batch-input-dialog.vue b/src/views/mes/pro/feedback/modules/batch-input-dialog.vue
new file mode 100644
index 0000000..922e79b
--- /dev/null
+++ b/src/views/mes/pro/feedback/modules/batch-input-dialog.vue
@@ -0,0 +1,177 @@
+<script lang="ts" setup>
+import type { BatchCheckResp } from '#/api/mes/pro/feedback';
+
+import { computed, ref } from 'vue';
+
+import { DICT_TYPE } from '@vben/constants';
+import { getDictOptions } from '@vben/hooks';
+import { useVbenModal } from '@vben/common-ui';
+
+import { useVbenForm } from '#/adapter/form';
+
+const emit = defineEmits<{
+  confirm: [data: Record<string, unknown>];
+}>();
+
+const checkResult = ref<BatchCheckResp>({});
+const batchData = ref<Record<string, unknown>>({});
+
+const skipFieldSet = computed<Set<string>>(() => {
+  const autoFields = checkResult.value.autoFilledFields ?? [];
+  return new Set([...autoFields, 'workOrderId', 'taskId', 'workstationId', 'produceDate']);
+});
+
+const needTool = computed(() => checkResult.value.toolFlag && !skipFieldSet.value.has('toolId'));
+const needMold = computed(() => checkResult.value.moldFlag && !skipFieldSet.value.has('moldId'));
+const needQualityStatus = computed(() => checkResult.value.qualityStatusFlag && !skipFieldSet.value.has('qualityStatus'));
+const needExpireDate = computed(() => checkResult.value.expireDateFlag && !skipFieldSet.value.has('expireDate'));
+const needReceiptDate = computed(() => checkResult.value.receiptDateFlag && !skipFieldSet.value.has('receiptDate'));
+const needLotNumber = computed(() => checkResult.value.lotNumberFlag && !skipFieldSet.value.has('lotNumber'));
+const needVendor = computed(() => checkResult.value.vendorFlag && !skipFieldSet.value.has('vendorId'));
+const needClient = computed(() => checkResult.value.clientFlag && !skipFieldSet.value.has('clientId'));
+const needSalesOrderCode = computed(() => checkResult.value.salesOrderCodeFlag && !skipFieldSet.value.has('salesOrderCode'));
+const needPurchaseOrderCode = computed(() => checkResult.value.purchaseOrderCodeFlag && !skipFieldSet.value.has('purchaseOrderCode'));
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: { class: 'w-full' },
+    formItemClass: 'col-span-1',
+    labelWidth: 110,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-2',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  onConfirm: async () => {
+    const { valid } = await formApi.validate();
+    if (!valid) return;
+    const values = await formApi.getValues();
+    emit('confirm', values as Record<string, unknown>);
+    await modalApi.close();
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) return;
+    const data = modalApi.getData<{ checkResult: BatchCheckResp }>();
+    checkResult.value = data.checkResult ?? {};
+    batchData.value = {};
+
+    // 鍔ㄦ�佹瀯寤鸿〃鍗� schema
+    const schemas: any[] = [];
+    if (needExpireDate.value) {
+      schemas.push({
+        fieldName: 'expireDate',
+        label: '鏈夋晥鏈�',
+        component: 'DatePicker',
+        componentProps: { class: 'w-full', format: 'YYYY-MM-DD', valueFormat: 'YYYY-MM-DD HH:mm:ss', showTime: true, placeholder: '璇烽�夋嫨鏈夋晥鏈�' },
+        rules: 'required',
+      });
+    }
+    if (needReceiptDate.value) {
+      schemas.push({
+        fieldName: 'receiptDate',
+        label: '鍏ュ簱鏃ユ湡',
+        component: 'DatePicker',
+        componentProps: { class: 'w-full', format: 'YYYY-MM-DD', valueFormat: 'YYYY-MM-DD HH:mm:ss', showTime: true, placeholder: '璇烽�夋嫨鍏ュ簱鏃ユ湡' },
+        rules: 'required',
+      });
+    }
+    if (needLotNumber.value) {
+      schemas.push({
+        fieldName: 'lotNumber',
+        label: '鐢熶骇鎵瑰彿',
+        component: 'Input',
+        componentProps: { placeholder: '璇疯緭鍏ョ敓浜ф壒鍙�' },
+        rules: 'required',
+      });
+    }
+    if (needTool.value) {
+      schemas.push({
+        fieldName: 'toolId',
+        label: '宸ュ叿',
+        component: 'Input',
+      });
+    }
+    if (needMold.value) {
+      schemas.push({
+        fieldName: 'moldId',
+        label: '妯″叿',
+        component: 'Input',
+      });
+    }
+    if (needQualityStatus.value) {
+      schemas.push({
+        fieldName: 'qualityStatus',
+        label: '璐ㄩ噺鐘舵��',
+        component: 'Select',
+        componentProps: {
+          options: getDictOptions(DICT_TYPE.MES_WM_QUALITY_STATUS, 'number'),
+          placeholder: '璇烽�夋嫨璐ㄩ噺鐘舵��',
+        },
+        rules: 'required',
+      });
+    }
+    if (needVendor.value) {
+      schemas.push({
+        fieldName: 'vendorId',
+        label: '渚涘簲鍟�',
+        component: 'Input',
+        componentProps: { placeholder: '璇疯緭鍏ヤ緵搴斿晢ID' },
+        rules: 'required',
+      });
+    }
+    if (needClient.value) {
+      schemas.push({
+        fieldName: 'clientId',
+        label: '瀹㈡埛',
+        component: 'Input',
+        componentProps: { placeholder: '璇疯緭鍏ュ鎴稩D' },
+        rules: 'required',
+      });
+    }
+    if (needSalesOrderCode.value) {
+      schemas.push({
+        fieldName: 'salesOrderCode',
+        label: '閿�鍞鍗曠紪鍙�',
+        component: 'Input',
+        componentProps: { placeholder: '璇疯緭鍏ラ攢鍞鍗曠紪鍙�' },
+        rules: 'required',
+      });
+    }
+    if (needPurchaseOrderCode.value) {
+      schemas.push({
+        fieldName: 'purchaseOrderCode',
+        label: '閲囪喘璁㈠崟缂栧彿',
+        component: 'Input',
+        componentProps: { placeholder: '璇疯緭鍏ラ噰璐鍗曠紪鍙�' },
+        rules: 'required',
+      });
+    }
+
+    formApi.setState({ schema: schemas });
+    // 榛樿鍊�
+    const defaults: Record<string, unknown> = {};
+    if (needExpireDate.value) defaults.expireDate = undefined;
+    if (needReceiptDate.value) defaults.receiptDate = undefined;
+    await formApi.setValues(defaults);
+  },
+});
+
+defineExpose({
+  open(data: { checkResult: BatchCheckResp }) {
+    modalApi.setData(data);
+    modalApi.open();
+  },
+});
+</script>
+
+<template>
+  <Modal title="濉啓鎵规灞炴��" class="w-2/5">
+    <div class="mb-3 text-sm text-gray-500">
+      璇ョ墿鏂欓渶瑕佹壒娆$鐞嗭紝璇峰~鍐欎互涓嬪繀濉壒娆″睘鎬у悗缁х画瀹℃壒銆�
+    </div>
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/mes/pro/feedback/modules/form.vue b/src/views/mes/pro/feedback/modules/form.vue
index 00ae80a..d0e4edf 100644
--- a/src/views/mes/pro/feedback/modules/form.vue
+++ b/src/views/mes/pro/feedback/modules/form.vue
@@ -5,6 +5,8 @@
 
 import { computed, ref } from 'vue';
 
+import dayjs from 'dayjs';
+
 import { useVbenModal } from '@vben/common-ui';
 import { MesAutoCodeRuleCode, MesProFeedbackStatusEnum } from '@vben/constants';
 import { useUserStore } from '@vben/stores';
@@ -16,6 +18,7 @@
 import {
   approveFeedback,
   createFeedback,
+  getBatchRequirements,
   getFeedback,
   rejectFeedback,
   submitFeedback,
@@ -25,6 +28,7 @@
 import { $t } from '#/locales';
 
 import { useFormSchema } from '../data';
+import BatchInputDialog from './batch-input-dialog.vue';
 import ItemConsumeList from './item-consume-list.vue';
 import ProductProduceList from './product-produce-list.vue';
 
@@ -161,14 +165,17 @@
   }
 }
 
-/** 瀹℃壒閫氳繃 */
-async function handleApprove() {
-  if (!formData.value?.id) {
-    return;
-  }
+const batchDialogRef = ref<InstanceType<typeof BatchInputDialog>>();
+
+/** 鎵ц瀹℃壒锛堝惈鎵规灞炴�э級 */
+async function doApprove(batchFields?: Record<string, unknown>) {
+  if (!formData.value?.id) return;
   modalApi.lock();
   try {
-    const finished = await approveFeedback(formData.value.id);
+    const finished = await approveFeedback({
+      id: formData.value.id,
+      ...batchFields,
+    });
     await modalApi.close();
     emit('success');
     message.success(
@@ -177,6 +184,30 @@
   } finally {
     modalApi.unlock();
   }
+}
+
+/** 瀹℃壒閫氳繃 */
+async function handleApprove() {
+  if (!formData.value?.id) {
+    return;
+  }
+  try {
+    const checkResult = await getBatchRequirements(formData.value.id);
+    if (checkResult?.needBatchInput) {
+      // 闇�瑕佸~鍐欐壒娆″睘鎬э紝寮圭獥鏀堕泦
+      batchDialogRef.value?.open({ checkResult });
+    } else {
+      await doApprove();
+    }
+  } catch {
+    // 妫�鏌ュけ璐ユ椂鐩存帴瀹℃壒锛堝吋瀹规棫閫昏緫锛�
+    await doApprove();
+  }
+}
+
+/** 鎵规寮圭獥纭鍥炶皟 */
+function handleBatchConfirm(batchData: Record<string, unknown>) {
+  doApprove(batchData);
 }
 
 /** 瀹℃壒涓嶉�氳繃 */
@@ -248,7 +279,7 @@
       );
       await formApi.setValues({
         code,
-        feedbackTime: Date.now(),
+        feedbackTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
         feedbackUserId: userStore.userInfo?.id,
       });
       // 璁板綍鍒濆蹇収锛堝惈鏁伴噺瀵归綈缁撴灉锛夛紝submit 鏃剁敤浜庤烦杩囨棤鍙樻洿鐨勪繚瀛樿姹�
@@ -314,5 +345,6 @@
         </Popconfirm>
       </div>
     </template>
+    <BatchInputDialog ref="batchDialogRef" @confirm="handleBatchConfirm" />
   </Modal>
 </template>
diff --git a/src/views/mes/workbench/components/tabs/FeedbackHistoryTab.vue b/src/views/mes/workbench/components/tabs/FeedbackHistoryTab.vue
index 4048770..8620c70 100644
--- a/src/views/mes/workbench/components/tabs/FeedbackHistoryTab.vue
+++ b/src/views/mes/workbench/components/tabs/FeedbackHistoryTab.vue
@@ -16,6 +16,7 @@
 import {
   approveFeedback,
   deleteFeedback,
+  getBatchRequirements,
   getFeedbackPage,
   rejectFeedback,
   submitFeedback,
@@ -23,6 +24,7 @@
 import { $t } from '#/locales';
 
 import FeedbackForm from '../../../pro/feedback/modules/form.vue';
+import BatchInputDialog from '../../../pro/feedback/modules/batch-input-dialog.vue';
 
 defineOptions({ name: 'WorkbenchFeedbackHistoryTab' });
 
@@ -38,6 +40,19 @@
   connectedComponent: FeedbackForm,
   destroyOnClose: true,
 });
+
+const batchDialogRef = ref<InstanceType<typeof BatchInputDialog>>();
+const approvingRow = ref<MesProFeedbackApi.Feedback>();
+
+/** 鎵ц瀹℃壒锛堝惈鎵规灞炴�э級 */
+async function doApprove(row: MesProFeedbackApi.Feedback, batchFields?: Record<string, unknown>) {
+  const finished = await approveFeedback({ id: row.id!, ...batchFields });
+  message.success(
+    finished ? '鎶ュ伐鍗曞凡瀹℃壒瀹屾垚' : '鎶ュ伐鎴愬姛锛岃绛夊緟璐ㄩ噺妫�楠屽畬鎴愶紒',
+  );
+  refreshGrid();
+  emit('reported');
+}
 
 /** 鏌ョ湅璇︽儏 */
 function handleDetail(row: MesProFeedbackApi.Feedback) {
@@ -67,12 +82,24 @@
 
 /** 瀹℃壒閫氳繃锛堜粎瀹℃壒涓級 */
 async function handleApprove(row: MesProFeedbackApi.Feedback) {
-  const finished = await approveFeedback(row.id!);
-  message.success(
-    finished ? '鎶ュ伐鍗曞凡瀹℃壒瀹屾垚' : '鎶ュ伐鎴愬姛锛岃绛夊緟璐ㄩ噺妫�楠屽畬鎴愶紒',
-  );
-  refreshGrid();
-  emit('reported');
+  try {
+    const checkResult = await getBatchRequirements(row.id!);
+    if (checkResult?.needBatchInput) {
+      approvingRow.value = row;
+      batchDialogRef.value?.open({ checkResult });
+    } else {
+      await doApprove(row);
+    }
+  } catch {
+    await doApprove(row);
+  }
+}
+
+/** 鎵规寮圭獥纭鍥炶皟 */
+function handleBatchConfirm(batchData: Record<string, unknown>) {
+  if (approvingRow.value) {
+    doApprove(approvingRow.value, batchData);
+  }
 }
 
 /** 椹冲洖锛堜粎瀹℃壒涓級 */
@@ -202,6 +229,7 @@
 <template>
   <div class="feedback-history-tab">
     <FormModal @success="refreshGrid" />
+    <BatchInputDialog ref="batchDialogRef" @confirm="handleBatchConfirm" />
 
     <div v-if="!taskId" class="feedback-history-tab__empty">
       璇蜂粠宸︿晶宸ュ崟鍒楄〃閫夋嫨涓�鏉′换鍔℃煡鐪嬫姤宸ヨ褰�
diff --git a/src/views/mes/workbench/components/tabs/FeedbackTab.vue b/src/views/mes/workbench/components/tabs/FeedbackTab.vue
index e7e449a..8439b83 100644
--- a/src/views/mes/workbench/components/tabs/FeedbackTab.vue
+++ b/src/views/mes/workbench/components/tabs/FeedbackTab.vue
@@ -4,6 +4,8 @@
 
 import { computed, ref, watch } from 'vue';
 
+import dayjs from 'dayjs';
+
 import {
   MesAutoCodeRuleCode,
   MesProFeedbackStatusEnum,
@@ -116,7 +118,7 @@
   await formApi.setValues({
     code,
     type: 1,
-    feedbackTime: Date.now(),
+    feedbackTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
     feedbackUserId: userStore.userInfo?.id,
     taskId: task.id,
     workOrderId: task.workOrderId,
@@ -242,7 +244,7 @@
 /** 瀹℃壒閫氳繃 */
 async function handleApprove() {
   if (!feedbackId.value) return;
-  const finished = await approveFeedback(feedbackId.value);
+  const finished = await approveFeedback({ id: feedbackId.value });
   message.success(
     finished ? '鎶ヨ鍗曞凡瀹℃壒瀹屾垚' : '鎶ュ伐鎴愬姛锛岃绛夊緟璐ㄩ噺妫�楠屽畬鎴愶紒',
   );
diff --git a/src/views/srm/tender/detail.vue b/src/views/srm/tender/detail.vue
index 839df72..4dfbeda 100644
--- a/src/views/srm/tender/detail.vue
+++ b/src/views/srm/tender/detail.vue
@@ -253,6 +253,40 @@
 const awardFormVisible = ref(false);
 const awardForm = ref<SrmTenderApi.BidAwardVO>({ tenderProjectId: projectId, supplierId: undefined!, bidId: undefined!, awardNo: '' });
 
+/** 宸叉姇鏍囦笖鏈挙鏍囩殑渚涘簲鍟嗗垪琛紙浠庢姇鏍囪褰曚腑鎻愬彇锛� */
+const bidSuppliers = computed(() => {
+  const activeBids = bidList.value.filter((b) => b.bidStatus !== BID_STATUS.WITHDRAWN);
+  const seen = new Map<number, { id: number; name: string }>();
+  activeBids.forEach((b) => {
+    if (b.supplierId && !seen.has(b.supplierId)) {
+      seen.set(b.supplierId, { id: b.supplierId, name: b.supplierName || '' });
+    }
+  });
+  return [...seen.values()];
+});
+
+/** 鍙�夋姇鏍囪褰曪細鎺掗櫎鎾ゆ爣锛屼笖濡傛灉閫変簡渚涘簲鍟嗗垯鍙樉绀鸿渚涘簲鍟嗙殑鎶曟爣 */
+const activeBids = computed(() => {
+  return bidList.value.filter((b) => {
+    if (b.bidStatus === BID_STATUS.WITHDRAWN) return false;
+    if (awardForm.value.supplierId && b.supplierId !== awardForm.value.supplierId) return false;
+    return true;
+  });
+});
+
+/** 閫夋嫨渚涘簲鍟嗘椂娓呯┖宸查�夌殑鎶曟爣 */
+function onAwardSupplierChange() {
+  awardForm.value.bidId = undefined!;
+}
+
+/** 閫夋嫨鎶曟爣鏃惰嚜鍔ㄥ甫鍏ヨ鎶曟爣鐨勬姤浠烽噾棰� */
+function onAwardBidChange(bidId: number) {
+  const bid = bidList.value.find((b) => b.id === bidId);
+  if (bid?.bidTotalAmount != null) {
+    awardForm.value.awardAmount = bid.bidTotalAmount;
+  }
+}
+
 async function loadAward() {
   try { award.value = await getAwardByProject(projectId); } catch { award.value = undefined; }
 }
@@ -859,15 +893,15 @@
           <a-row :gutter="12">
             <a-col :span="12">
               <a-form-item label="渚涘簲鍟�" required>
-                <a-select v-model:value="awardForm.supplierId" show-search placeholder="閫夋嫨渚涘簲鍟�" :filter-option="filterOption">
-                  <a-select-option v-for="s in supplierList" :key="s.id" :value="s.id" :label="s.name">{{ s.name }}</a-select-option>
+                <a-select v-model:value="awardForm.supplierId" show-search placeholder="閫夋嫨渚涘簲鍟�" :filter-option="filterOption" @change="onAwardSupplierChange">
+                  <a-select-option v-for="s in bidSuppliers" :key="s.id" :value="s.id" :label="s.name">{{ s.name }}</a-select-option>
                 </a-select>
               </a-form-item>
             </a-col>
             <a-col :span="12">
               <a-form-item label="鎶曟爣" required>
-                <a-select v-model:value="awardForm.bidId" show-search placeholder="閫夋嫨鎶曟爣" :filter-option="filterOption">
-                  <a-select-option v-for="b in bidList" :key="b.id" :value="b.id" :label="`${b.bidNo} - ${b.supplierName}`">{{ b.bidNo }} - {{ b.supplierName }}</a-select-option>
+                <a-select v-model:value="awardForm.bidId" show-search placeholder="閫夋嫨鎶曟爣" :filter-option="filterOption" :disabled="!awardForm.supplierId" @change="onAwardBidChange">
+                  <a-select-option v-for="b in activeBids" :key="b.id" :value="b.id" :label="`${b.bidNo} - ${b.supplierName}`">{{ b.bidNo }} - {{ b.supplierName }}</a-select-option>
                 </a-select>
               </a-form-item>
             </a-col>

--
Gitblit v1.9.3