From a3057955f855b8a266c017df00645845e7471410 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期五, 07 八月 2026 18:01:59 +0800
Subject: [PATCH] 银川 1.不合格品处理页面开发联调 2.招投标管理 - 可配置版前端联调方案

---
 src/views/wls/stocktaking/plan/data.ts                              |    4 
 src/views/mes/qc/ncr/modules/form.vue                               |   81 +++
 src/views/mes/qc/iqc/modules/form.vue                               |   16 
 src/views/mes/qc/ipqc/data.ts                                       |    1 
 src/views/mes/qc/iqc/data.ts                                        |   21 
 src/api/srm/tender-config/index.ts                                  |   16 
 src/views/srm/tender/detail.vue                                     |  120 ++++
 src/views/mes/qc/oqc/modules/form.vue                               |   16 
 src/views/srm/tender/config/index.vue                               |   65 ++
 src/api/mes/qc/ncr/index.ts                                         |   90 +++
 src/views/srm/tender/data.ts                                        |   37 +
 src/views/srm/tender/index.vue                                      |  107 +++
 src/views/srm/tender/modules/form.vue                               |   10 
 src/api/srm/tender/index.ts                                         |    9 
 src/views/mes/qc/ncr/index.vue                                      |  339 +++++++++++++
 src/views/mes/qc/ipqc/modules/form.vue                              |   16 
 src/views/mes/qc/ncr/data.ts                                        |  447 +++++++++++++++++
 src/views/srm/enums.ts                                              |   29 +
 src/packages/constants/src/biz-mes-enum.ts                          |    6 
 src/views/wls/stocktaking/plan/components/condition-value-input.vue |   30 
 src/views/erp/sale/order/data.ts                                    |    4 
 src/views/mes/qc/oqc/data.ts                                        |    1 
 src/views/mes/qc/rqc/modules/form.vue                               |   16 
 src/views/erp/sale/order/modules/form.vue                           |    2 
 src/packages/constants/src/dict-enum.ts                             |    2 
 src/views/mes/qc/rqc/data.ts                                        |    1 
 26 files changed, 1,438 insertions(+), 48 deletions(-)

diff --git a/src/api/mes/qc/ncr/index.ts b/src/api/mes/qc/ncr/index.ts
new file mode 100644
index 0000000..060d8d3
--- /dev/null
+++ b/src/api/mes/qc/ncr/index.ts
@@ -0,0 +1,90 @@
+import type { PageParam, PageResult } from '@vben/request';
+
+import { requestClient } from '#/api/request';
+
+export namespace MesQcNcrApi {
+  /** MES 涓嶅悎鏍煎搧澶勭悊鍗� */
+  export interface Ncr {
+    id?: number;
+    code?: string;
+    name?: string;
+    qcType?: number;
+    qcId?: number;
+    qcCode?: string;
+    itemId?: number;
+    itemCode?: string;
+    itemName?: string;
+    itemSpecification?: string;
+    defectLevel?: number;
+    unqualifiedQuantity?: number;
+    defectDescription?: string;
+    disposition?: number;
+    responsibleDeptId?: number;
+    responsibleDeptName?: string;
+    reviewerUserId?: number;
+    reviewerNickname?: string;
+    reviewDate?: string;
+    reviewOpinion?: string;
+    handlerUserId?: number;
+    handlerNickname?: string;
+    handleDate?: string;
+    handleResult?: string;
+    rootCause?: string;
+    correctiveAction?: string;
+    preventiveAction?: string;
+    closeUserId?: number;
+    closeUserNickname?: string;
+    closeDate?: string;
+    closeOpinion?: string;
+    status?: number;
+    remark?: string;
+    createTime?: string;
+  }
+}
+
+/** 鏌ヨ涓嶅悎鏍煎搧澶勭悊鍗曞垎椤� */
+export function getNcrPage(params: PageParam) {
+  return requestClient.get<PageResult<MesQcNcrApi.Ncr>>('/mes/qc/ncr/page', {
+    params,
+  });
+}
+
+/** 鏌ヨ涓嶅悎鏍煎搧澶勭悊鍗曡鎯� */
+export function getNcr(id: number) {
+  return requestClient.get<MesQcNcrApi.Ncr>(`/mes/qc/ncr/get?id=${id}`);
+}
+
+/** 鏂板涓嶅悎鏍煎搧澶勭悊鍗� */
+export function createNcr(data: MesQcNcrApi.Ncr) {
+  return requestClient.post('/mes/qc/ncr/create', data);
+}
+
+/** 淇敼涓嶅悎鏍煎搧澶勭悊鍗� */
+export function updateNcr(data: MesQcNcrApi.Ncr) {
+  return requestClient.put('/mes/qc/ncr/update', data);
+}
+
+/** 鍒犻櫎涓嶅悎鏍煎搧澶勭悊鍗� */
+export function deleteNcr(id: number) {
+  return requestClient.delete(`/mes/qc/ncr/delete?id=${id}`);
+}
+
+/** 璇勫涓嶅悎鏍煎搧澶勭悊鍗� */
+export function reviewNcr(data: { defectLevel?: number; disposition: number; id: number; responsibleDeptId?: number; reviewOpinion?: string }) {
+  return requestClient.put('/mes/qc/ncr/review', data);
+}
+
+/** 澶勭疆涓嶅悎鏍煎搧澶勭悊鍗� */
+export function disposeNcr(data: { handleResult?: string; handlerUserId?: number; id: number }) {
+  return requestClient.put('/mes/qc/ncr/dispose', data);
+}
+
+/** 鍏抽棴涓嶅悎鏍煎搧澶勭悊鍗� */
+export function closeNcr(data: { closeOpinion?: string; id: number }) {
+  return requestClient.put('/mes/qc/ncr/close', data);
+}
+
+/** 瀵煎嚭涓嶅悎鏍煎搧澶勭悊鍗� */
+export function exportNcr(params: any) {
+  return requestClient.download('/mes/qc/ncr/export-excel', { params });
+}
diff --git a/src/api/srm/tender-config/index.ts b/src/api/srm/tender-config/index.ts
new file mode 100644
index 0000000..976938d
--- /dev/null
+++ b/src/api/srm/tender-config/index.ts
@@ -0,0 +1,16 @@
+import { requestClient } from '#/api/request';
+
+export namespace SrmTenderConfigApi {
+  export interface TenderConfigVO {
+    id?: number;
+    defaultBiddingMode?: string;
+  }
+}
+
+export function getTenderConfig() {
+  return requestClient.get<SrmTenderConfigApi.TenderConfigVO>('/srm/tender-config/get');
+}
+
+export function saveTenderConfig(data: SrmTenderConfigApi.TenderConfigVO) {
+  return requestClient.put('/srm/tender-config/save', data);
+}
diff --git a/src/api/srm/tender/index.ts b/src/api/srm/tender/index.ts
index 5ac7ad8..a44c5b9 100644
--- a/src/api/srm/tender/index.ts
+++ b/src/api/srm/tender/index.ts
@@ -21,6 +21,7 @@
     purchaseRequestId?: number;
     awardId?: number;
     purchaseOrderNo?: string;
+    biddingMode?: string;
     createTime?: string;
   }
 
@@ -149,6 +150,14 @@
   return requestClient.put(`/srm/tender-project/start-evaluation?id=${id}`);
 }
 
+export function startPriceComparing(id: number) {
+  return requestClient.put(`/srm/tender-project/start-price-comparing?id=${id}`);
+}
+
+export function confirmTenderProject(data: { id: number; bidId: number }) {
+  return requestClient.put('/srm/tender-project/confirm', data);
+}
+
 // ========== 鐗╂枡瀛愯〃 ==========
 
 export function getMaterialList(tenderProjectId: number) {
diff --git a/src/packages/constants/src/biz-mes-enum.ts b/src/packages/constants/src/biz-mes-enum.ts
index 0a1b051..1ac585c 100644
--- a/src/packages/constants/src/biz-mes-enum.ts
+++ b/src/packages/constants/src/biz-mes-enum.ts
@@ -484,6 +484,12 @@
   RQC: 4, // 閫�璐ф楠�
 } as const;
 
+/** MES 璐ㄦ妫�娴嬬粨鏋滄灇涓� */
+export const MesQcCheckResultEnum = {
+  PASS: 1, // 鏍¢獙閫氳繃
+  FAIL: 2, // 鏍¢獙涓嶉�氳繃
+} as const;
+
 /** MES 璐ㄦ鍗曠姸鎬佹灇涓� */
 export const MesQcStatusEnum = {
   DRAFT: MesOrderStatusConstants.DRAFT,
diff --git a/src/packages/constants/src/dict-enum.ts b/src/packages/constants/src/dict-enum.ts
index e899ad2..ab5df28 100644
--- a/src/packages/constants/src/dict-enum.ts
+++ b/src/packages/constants/src/dict-enum.ts
@@ -286,6 +286,8 @@
   MES_WM_TRANSFER_TYPE: 'mes_wm_transfer_type', // MES 杞Щ鍗曠被鍨�
   MES_PD_PARAM_TYPE: 'mes_pd_param_type', // MES 宸ヨ壓鍙傛暟绫诲瀷
   MES_PD_TEMPLATE_TYPE: 'mes_pd_template_type', // MES 宸ヨ壓妯℃澘绫诲瀷
+  MES_NCR_STATUS: 'mes_ncr_status', // MES 涓嶅悎鏍煎搧澶勭悊鍗曠姸鎬�
+  MES_NCR_DISPOSITION: 'mes_ncr_disposition', // MES 涓嶅悎鏍煎搧澶勭疆鏂瑰紡
 } as const;
 
 /** ========== WMS - 浠撳偍绠$悊妯″潡 ========== */
diff --git a/src/views/erp/sale/order/data.ts b/src/views/erp/sale/order/data.ts
index 7886d14..9597b94 100644
--- a/src/views/erp/sale/order/data.ts
+++ b/src/views/erp/sale/order/data.ts
@@ -216,6 +216,7 @@
       title: '搴撳瓨',
       minWidth: 80,
       formatter: 'formatAmount3',
+      visible: false,
     },
     {
       field: 'productUnitName',
@@ -226,11 +227,13 @@
       field: 'productUnitName2',
       title: '杈呭崟浣�1',
       minWidth: 80,
+      visible: false,
     },
     {
       field: 'productUnitName3',
       title: '杈呭崟浣�2',
       minWidth: 80,
+      visible: false,
     },
     {
       field: 'needProduction',
@@ -243,6 +246,7 @@
       title: '澶囨敞',
       minWidth: 150,
       slots: { default: 'remark' },
+      visible: false,
     },
     {
       field: 'count',
diff --git a/src/views/erp/sale/order/modules/form.vue b/src/views/erp/sale/order/modules/form.vue
index 5dac3de..638f3ae 100644
--- a/src/views/erp/sale/order/modules/form.vue
+++ b/src/views/erp/sale/order/modules/form.vue
@@ -154,7 +154,7 @@
 <template>
   <Modal
     :title="getTitle"
-    class="w-3/4"
+    class="w-5/6"
     :show-confirm-button="formType !== 'detail'"
   >
     <Form class="mx-3">
diff --git a/src/views/mes/qc/ipqc/data.ts b/src/views/mes/qc/ipqc/data.ts
index eeeff54..58f1e88 100644
--- a/src/views/mes/qc/ipqc/data.ts
+++ b/src/views/mes/qc/ipqc/data.ts
@@ -250,6 +250,7 @@
         precision: 2,
       },
       rules: 'required',
+      help: '涓嶅悎鏍煎搧鏁伴噺澶т簬0鏃讹紝淇濆瓨鍚庡皢鑷姩鐢熸垚涓嶅悎鏍煎鐞嗗崟',
     },
     {
       fieldName: 'laborScrapQuantity',
diff --git a/src/views/mes/qc/ipqc/modules/form.vue b/src/views/mes/qc/ipqc/modules/form.vue
index af80f2c..0969fc7 100644
--- a/src/views/mes/qc/ipqc/modules/form.vue
+++ b/src/views/mes/qc/ipqc/modules/form.vue
@@ -6,7 +6,7 @@
 import { computed, ref } from 'vue';
 
 import { confirm, useVbenModal } from '@vben/common-ui';
-import { MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants';
+import { MesQcCheckResultEnum, MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants';
 
 import { Button, Descriptions, message, Tabs } from 'ant-design-vue';
 
@@ -69,6 +69,13 @@
     return false;
   }
   const data = (await formApi.getValues()) as MesQcIpqcApi.Ipqc;
+  if (
+    (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) &&
+    data.checkResult === MesQcCheckResultEnum.FAIL
+  ) {
+    message.warning('涓嶅悎鏍煎搧鏁伴噺涓�0鏃讹紝妫�娴嬬粨鏋滀笉鑳戒负"鏍¢獙涓嶉�氳繃"');
+    return false;
+  }
   if (formData.value?.id) {
     await updateIpqc({ ...data, id: formData.value.id });
     formData.value = { ...formData.value, ...data };
@@ -108,6 +115,13 @@
     const current = JSON.stringify(await formApi.getValues());
     if (current !== originalSnapshot.value) {
       const data = (await formApi.getValues()) as MesQcIpqcApi.Ipqc;
+      if (
+        (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) &&
+        data.checkResult === MesQcCheckResultEnum.FAIL
+      ) {
+        message.warning('涓嶅悎鏍煎搧鏁伴噺涓�0鏃讹紝妫�娴嬬粨鏋滀笉鑳戒负"鏍¢獙涓嶉�氳繃"');
+        return;
+      }
       await updateIpqc({ ...data, id });
       formData.value = { ...formData.value, ...data };
       originalSnapshot.value = current;
diff --git a/src/views/mes/qc/iqc/data.ts b/src/views/mes/qc/iqc/data.ts
index da23bdb..40cb523 100644
--- a/src/views/mes/qc/iqc/data.ts
+++ b/src/views/mes/qc/iqc/data.ts
@@ -11,10 +11,11 @@
 import { Button } from 'ant-design-vue';
 
 import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getSupplierSimpleList } from '#/api/srm/supplier';
 import { getSimpleUserList } from '#/api/system/user';
 import { getRangePickerDefaultProps } from '#/utils';
 import { MdmItemSelect } from '#/views/basicData/mdm/components';
-import { MdVendorSelect } from '#/views/mes/md/vendor/components';
+
 
 /** 琛ㄥ崟绫诲瀷 */
 export type FormType = 'create' | 'detail' | 'update';
@@ -136,16 +137,23 @@
     {
       fieldName: 'vendorId',
       label: '渚涘簲鍟�',
-      component: markRaw(MdVendorSelect),
+      component: 'ApiSelect',
       componentProps: {
+        allowClear: true,
+        api: getSupplierSimpleList,
+        labelField: 'name',
         placeholder: '璇烽�夋嫨渚涘簲鍟�',
+        valueField: 'id',
       },
-      rules: 'selectRequired',
       dependencies: {
         triggerFields: ['sourceDocId'],
         componentProps: (values) => ({
           disabled: !!values.sourceDocId,
+          allowClear: true,
+          api: getSupplierSimpleList,
+          labelField: 'name',
           placeholder: '璇烽�夋嫨渚涘簲鍟�',
+          valueField: 'id',
         }),
       },
     },
@@ -200,6 +208,7 @@
         placeholder: '璇疯緭鍏ヤ笉鍚堟牸鍝佹暟閲�',
       },
       rules: 'required',
+      help: '涓嶅悎鏍煎搧鏁伴噺澶т簬0鏃讹紝淇濆瓨鍚庡皢鑷姩鐢熸垚涓嶅悎鏍煎鐞嗗崟',
     },
     {
       fieldName: 'receiveDate',
@@ -274,9 +283,13 @@
     {
       fieldName: 'vendorId',
       label: '渚涘簲鍟�',
-      component: markRaw(MdVendorSelect),
+      component: 'ApiSelect',
       componentProps: {
+        allowClear: true,
+        api: getSupplierSimpleList,
+        labelField: 'name',
         placeholder: '璇烽�夋嫨渚涘簲鍟�',
+        valueField: 'id',
       },
     },
     {
diff --git a/src/views/mes/qc/iqc/modules/form.vue b/src/views/mes/qc/iqc/modules/form.vue
index 2f0bd83..45c1c35 100644
--- a/src/views/mes/qc/iqc/modules/form.vue
+++ b/src/views/mes/qc/iqc/modules/form.vue
@@ -6,7 +6,7 @@
 import { computed, ref } from 'vue';
 
 import { confirm, useVbenModal } from '@vben/common-ui';
-import { MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants';
+import { MesQcCheckResultEnum, MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants';
 
 import { Button, Descriptions, message, Tabs } from 'ant-design-vue';
 
@@ -69,6 +69,13 @@
     return false;
   }
   const data = (await formApi.getValues()) as MesQcIqcApi.Iqc;
+  if (
+    (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) &&
+    data.checkResult === MesQcCheckResultEnum.FAIL
+  ) {
+    message.warning('涓嶅悎鏍煎搧鏁伴噺涓�0鏃讹紝妫�娴嬬粨鏋滀笉鑳戒负"鏍¢獙涓嶉�氳繃"');
+    return false;
+  }
   if (formData.value?.id) {
     await updateIqc({ ...data, id: formData.value.id });
     formData.value = { ...formData.value, ...data };
@@ -108,6 +115,13 @@
     const current = JSON.stringify(await formApi.getValues());
     if (current !== originalSnapshot.value) {
       const data = (await formApi.getValues()) as MesQcIqcApi.Iqc;
+      if (
+        (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) &&
+        data.checkResult === MesQcCheckResultEnum.FAIL
+      ) {
+        message.warning('涓嶅悎鏍煎搧鏁伴噺涓�0鏃讹紝妫�娴嬬粨鏋滀笉鑳戒负"鏍¢獙涓嶉�氳繃"');
+        return;
+      }
       await updateIqc({ ...data, id });
       formData.value = { ...formData.value, ...data };
       originalSnapshot.value = current;
diff --git a/src/views/mes/qc/ncr/data.ts b/src/views/mes/qc/ncr/data.ts
new file mode 100644
index 0000000..f6bdbfb
--- /dev/null
+++ b/src/views/mes/qc/ncr/data.ts
@@ -0,0 +1,447 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesQcNcrApi } from '#/api/mes/qc/ncr';
+
+import { h, markRaw } from 'vue';
+
+import { DICT_TYPE } from '@vben/constants';
+import { getDictOptions } from '@vben/hooks';
+
+import { Button } from 'ant-design-vue';
+
+import { getSimpleUserList } from '#/api/system/user';
+import { DeptTreeSelect } from '#/views/system/dept/components';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+
+/** NCR 鐘舵�佹灇涓� */
+export const NCR_STATUS = {
+  PENDING_REVIEW: 0,
+  REVIEWED: 1,
+  DISPOSED: 2,
+  CLOSED: 3,
+} as const;
+
+export const NCR_STATUS_OPTIONS = [
+  { label: '寰呰瘎瀹�', value: 0 },
+  { label: '宸茶瘎瀹�', value: 1 },
+  { label: '宸插缃�', value: 2 },
+  { label: '宸插叧闂�', value: 3 },
+];
+
+/** 妫�楠岀被鍨嬫灇涓� */
+export const QC_TYPE_OPTIONS = [
+  { label: 'IQC', value: 1 },
+  { label: 'IPQC', value: 2 },
+  { label: 'OQC', value: 3 },
+  { label: 'RQC', value: 4 },
+];
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+  formApi?: VbenFormApi,
+): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'qcId',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'status',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'code',
+      label: 'NCR鍗曞彿',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏CR鍗曞彿',
+      },
+      rules: 'required',
+      suffix: () =>
+        h(
+          Button,
+          {
+            type: 'default',
+            onClick: async () => {
+              const now = new Date();
+              const pad = (n: number) => String(n).padStart(2, '0');
+              const code = `NCR${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
+              await formApi?.setFieldValue('code', code);
+            },
+          },
+          { default: () => '鑷姩鐢熸垚' },
+        ),
+    },
+    {
+      fieldName: 'name',
+      label: 'NCR鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏CR鍚嶇О',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'qcType',
+      label: '妫�楠岀被鍨�',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: QC_TYPE_OPTIONS,
+        placeholder: '璇烽�夋嫨妫�楠岀被鍨�',
+      },
+    },
+    {
+      fieldName: 'itemId',
+      label: '涓嶅悎鏍肩墿鏂�',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨涓嶅悎鏍肩墿鏂�',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'defectLevel',
+      label: '缂洪櫡绛夌骇',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(DICT_TYPE.MES_DEFECT_LEVEL, 'number'),
+        placeholder: '璇烽�夋嫨缂洪櫡绛夌骇',
+      },
+    },
+    {
+      fieldName: 'unqualifiedQuantity',
+      label: '涓嶅悎鏍兼暟閲�',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ヤ笉鍚堟牸鏁伴噺',
+        precision: 2,
+      },
+    },
+    {
+      fieldName: 'defectDescription',
+      label: '涓嶅悎鏍兼弿杩�',
+      component: 'Textarea',
+      componentProps: {
+        placeholder: '璇疯緭鍏ヤ笉鍚堟牸鎻忚堪',
+        rows: 3,
+      },
+    },
+    {
+      fieldName: 'rootCause',
+      label: '鏍规湰鍘熷洜',
+      component: 'Textarea',
+      componentProps: {
+        placeholder: '璇疯緭鍏ユ牴鏈師鍥�',
+        rows: 2,
+      },
+    },
+    {
+      fieldName: 'correctiveAction',
+      label: '绾犳鎺柦',
+      component: 'Textarea',
+      componentProps: {
+        placeholder: '璇疯緭鍏ョ籂姝f帾鏂�',
+        rows: 2,
+      },
+    },
+    {
+      fieldName: 'preventiveAction',
+      label: '棰勯槻鎺柦',
+      component: 'Textarea',
+      componentProps: {
+        placeholder: '璇疯緭鍏ラ闃叉帾鏂�',
+        rows: 2,
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 2,
+      },
+    },
+  ];
+}
+
+/** 璇勫琛ㄥ崟 */
+export function useReviewFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'disposition',
+      label: '澶勭疆鏂瑰紡',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(DICT_TYPE.MES_NCR_DISPOSITION, 'number'),
+        placeholder: '璇烽�夋嫨澶勭疆鏂瑰紡',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'defectLevel',
+      label: '缂洪櫡绛夌骇',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(DICT_TYPE.MES_DEFECT_LEVEL, 'number'),
+        placeholder: '璇烽�夋嫨缂洪櫡绛夌骇',
+      },
+    },
+    {
+      fieldName: 'responsibleDeptId',
+      label: '璐d换閮ㄩ棬',
+      component: markRaw(DeptTreeSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨璐d换閮ㄩ棬',
+      },
+    },
+    {
+      fieldName: 'reviewOpinion',
+      label: '璇勫鎰忚',
+      component: 'Textarea',
+      componentProps: {
+        placeholder: '璇疯緭鍏ヨ瘎瀹℃剰瑙�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 澶勭疆琛ㄥ崟 */
+export function useDisposeFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'handlerUserId',
+      label: '澶勭悊浜�',
+      component: 'ApiSelect',
+      componentProps: {
+        allowClear: true,
+        api: getSimpleUserList,
+        labelField: 'nickname',
+        placeholder: '璇烽�夋嫨澶勭悊浜�',
+        valueField: 'id',
+      },
+    },
+    {
+      fieldName: 'handleResult',
+      label: '澶勭悊缁撴灉璇存槑',
+      component: 'Textarea',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ鐞嗙粨鏋滆鏄�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鍏抽棴琛ㄥ崟 */
+export function useCloseFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'closeOpinion',
+      label: '鍏抽棴鎰忚',
+      component: 'Textarea',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ叧闂剰瑙�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: 'NCR鍗曞彿',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏CR鍗曞彿',
+      },
+    },
+    {
+      fieldName: 'qcType',
+      label: '妫�楠岀被鍨�',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: QC_TYPE_OPTIONS,
+        placeholder: '璇烽�夋嫨妫�楠岀被鍨�',
+      },
+    },
+    {
+      fieldName: 'itemId',
+      label: '涓嶅悎鏍肩墿鏂�',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨鐗╂枡',
+      },
+    },
+    {
+      fieldName: 'defectLevel',
+      label: '缂洪櫡绛夌骇',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(DICT_TYPE.MES_DEFECT_LEVEL, 'number'),
+        placeholder: '璇烽�夋嫨缂洪櫡绛夌骇',
+      },
+    },
+    {
+      fieldName: 'disposition',
+      label: '澶勭疆鏂瑰紡',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(DICT_TYPE.MES_NCR_DISPOSITION, 'number'),
+        placeholder: '璇烽�夋嫨澶勭疆鏂瑰紡',
+      },
+    },
+    {
+      fieldName: 'status',
+      label: '鐘舵��',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: NCR_STATUS_OPTIONS,
+        placeholder: '璇烽�夋嫨鐘舵��',
+      },
+    },
+    {
+      fieldName: 'createTime',
+      label: '鍒涘缓鏃堕棿',
+      component: 'DatePicker',
+      componentProps: {
+        allowClear: true,
+        class: '!w-full',
+        placeholder: '璇烽�夋嫨鍒涘缓鏃堕棿',
+        showTime: true,
+        type: 'date',
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesQcNcrApi.Ncr>['columns'] {
+  return [
+    {
+      field: 'code',
+      title: 'NCR鍗曞彿',
+      width: 200,
+    },
+    {
+      field: 'qcCode',
+      title: '鏉ユ簮妫�楠屽崟缂栧彿',
+      width: 180,
+    },
+    {
+      field: 'itemName',
+      title: '鐗╂枡鍚嶇О',
+      minWidth: 150,
+    },
+    {
+      field: 'itemSpecification',
+      title: '瑙勬牸鍨嬪彿',
+      width: 130,
+    },
+    {
+      field: 'qcType',
+      title: '妫�楠岀被鍨�',
+      width: 100,
+      slots: { default: 'qcType' },
+    },
+    {
+      field: 'defectLevel',
+      title: '缂洪櫡绛夌骇',
+      width: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_DEFECT_LEVEL },
+      },
+    },
+    {
+      field: 'unqualifiedQuantity',
+      title: '涓嶅悎鏍兼暟閲�',
+      width: 110,
+    },
+    {
+      field: 'disposition',
+      title: '澶勭疆鏂瑰紡',
+      width: 110,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_NCR_DISPOSITION },
+      },
+    },
+    {
+      field: 'status',
+      title: '鐘舵��',
+      width: 90,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_NCR_STATUS },
+      },
+    },
+    {
+      field: 'createTime',
+      title: '鍒涘缓鏃堕棿',
+      width: 180,
+      formatter: 'formatDateTime',
+    },
+    {
+      title: '鎿嶄綔',
+      width: 220,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
diff --git a/src/views/mes/qc/ncr/index.vue b/src/views/mes/qc/ncr/index.vue
new file mode 100644
index 0000000..6204415
--- /dev/null
+++ b/src/views/mes/qc/ncr/index.vue
@@ -0,0 +1,339 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesQcNcrApi } from '#/api/mes/qc/ncr';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+import { downloadFileFromBlobPart } from '@vben/utils';
+
+import { message, Tag } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { useVbenForm } from '#/adapter/form';
+import {
+  closeNcr,
+  deleteNcr,
+  disposeNcr,
+  exportNcr,
+  getNcrPage,
+  reviewNcr,
+} from '#/api/mes/qc/ncr';
+import { $t } from '#/locales';
+
+import {
+  NCR_STATUS,
+  QC_TYPE_OPTIONS,
+  useCloseFormSchema,
+  useDisposeFormSchema,
+  useGridColumns,
+  useGridFormSchema,
+  useReviewFormSchema,
+} from './data';
+import Form from './modules/form.vue';
+
+defineOptions({ name: 'NcrIndex' });
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+/** 璇勫寮圭獥 */
+const [ReviewForm, reviewFormApi] = useVbenForm({
+  commonConfig: {
+    componentProps: { class: 'w-full' },
+    formItemClass: 'col-span-2',
+    labelWidth: 120,
+  },
+  layout: 'horizontal',
+  schema: useReviewFormSchema(),
+  showDefaultActions: false,
+});
+const [ReviewModal, reviewModalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await reviewFormApi.validate();
+    if (!valid) return;
+    reviewModalApi.lock();
+    try {
+      await reviewNcr(
+        (await reviewFormApi.getValues()) as {
+          defectLevel?: number;
+          disposition: number;
+          id: number;
+          responsibleDeptId?: number;
+          reviewOpinion?: string;
+        },
+      );
+      await reviewModalApi.close();
+      message.success('璇勫鎴愬姛');
+      handleRefresh();
+    } finally {
+      reviewModalApi.unlock();
+    }
+  },
+  onOpenChange(isOpen: boolean) {
+    if (!isOpen) return;
+    const data = reviewModalApi.getData<MesQcNcrApi.Ncr>();
+    reviewFormApi.setValues({ id: data?.id });
+  },
+});
+
+/** 澶勭疆寮圭獥 */
+const [DisposeForm, disposeFormApi] = useVbenForm({
+  commonConfig: {
+    componentProps: { class: 'w-full' },
+    formItemClass: 'col-span-2',
+    labelWidth: 120,
+  },
+  layout: 'horizontal',
+  schema: useDisposeFormSchema(),
+  showDefaultActions: false,
+});
+const [DisposeModal, disposeModalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await disposeFormApi.validate();
+    if (!valid) return;
+    disposeModalApi.lock();
+    try {
+      await disposeNcr(
+        (await disposeFormApi.getValues()) as {
+          handleResult?: string;
+          handlerUserId?: number;
+          id: number;
+        },
+      );
+      await disposeModalApi.close();
+      message.success('澶勭疆鎴愬姛');
+      handleRefresh();
+    } finally {
+      disposeModalApi.unlock();
+    }
+  },
+  onOpenChange(isOpen: boolean) {
+    if (!isOpen) return;
+    const data = disposeModalApi.getData<MesQcNcrApi.Ncr>();
+    disposeFormApi.setValues({ id: data?.id });
+  },
+});
+
+/** 鍏抽棴寮圭獥 */
+const [CloseForm, closeFormApi] = useVbenForm({
+  commonConfig: {
+    componentProps: { class: 'w-full' },
+    formItemClass: 'col-span-2',
+    labelWidth: 120,
+  },
+  layout: 'horizontal',
+  schema: useCloseFormSchema(),
+  showDefaultActions: false,
+});
+const [CloseModal, closeModalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await closeFormApi.validate();
+    if (!valid) return;
+    closeModalApi.lock();
+    try {
+      await closeNcr(
+        (await closeFormApi.getValues()) as {
+          closeOpinion?: string;
+          id: number;
+        },
+      );
+      await closeModalApi.close();
+      message.success('鍏抽棴鎴愬姛');
+      handleRefresh();
+    } finally {
+      closeModalApi.unlock();
+    }
+  },
+  onOpenChange(isOpen: boolean) {
+    if (!isOpen) return;
+    const data = closeModalApi.getData<MesQcNcrApi.Ncr>();
+    closeFormApi.setValues({ id: data?.id });
+  },
+});
+
+function getQcTypeLabel(type?: number): string {
+  return QC_TYPE_OPTIONS.find((o) => o.value === type)?.label || '';
+}
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓 */
+function handleCreate() {
+  formModalApi.setData(null).open();
+}
+
+/** 缂栬緫 */
+function handleEdit(row: MesQcNcrApi.Ncr) {
+  formModalApi.setData(row).open();
+}
+
+/** 鍒犻櫎 */
+async function handleDelete(row: MesQcNcrApi.Ncr) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.name]),
+    duration: 0,
+  });
+  try {
+    await deleteNcr(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 璇勫 */
+function handleReview(row: MesQcNcrApi.Ncr) {
+  reviewModalApi.setData(row).open();
+}
+
+/** 澶勭疆 */
+function handleDispose(row: MesQcNcrApi.Ncr) {
+  disposeModalApi.setData(row).open();
+}
+
+/** 鍏抽棴 */
+function handleClose(row: MesQcNcrApi.Ncr) {
+  closeModalApi.setData(row).open();
+}
+
+/** 瀵煎嚭 */
+async function handleExport() {
+  const data = await exportNcr(await gridApi.formApi.getValues());
+  downloadFileFromBlobPart({ fileName: '涓嶅悎鏍煎搧澶勭悊鍗�.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getNcrPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesQcNcrApi.Ncr>,
+});
+</script>
+
+<template>
+  <Page auto-content-height>
+    <FormModal @success="handleRefresh" />
+    <ReviewModal title="璇勫涓嶅悎鏍煎搧澶勭悊鍗�" class="w-2/5">
+      <ReviewForm class="mx-4" />
+    </ReviewModal>
+    <DisposeModal title="澶勭疆涓嶅悎鏍煎搧澶勭悊鍗�" class="w-2/5">
+      <DisposeForm class="mx-4" />
+    </DisposeModal>
+    <CloseModal title="鍏抽棴涓嶅悎鏍煎搧澶勭悊鍗�" class="w-2/5">
+      <CloseForm class="mx-4" />
+    </CloseModal>
+
+    <Grid table-title="涓嶅悎鏍煎搧澶勭悊">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['涓嶅悎鏍煎搧澶勭悊鍗�']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:qc-ncr:create'],
+              onClick: handleCreate,
+            },
+            {
+              label: $t('ui.actionTitle.export'),
+              type: 'primary',
+              icon: ACTION_ICON.DOWNLOAD,
+              auth: ['mes:qc-ncr:export'],
+              onClick: handleExport,
+            },
+          ]"
+        />
+      </template>
+      <template #qcType="{ row }">
+        <Tag v-if="row.qcType !== undefined && row.qcType !== null">
+          {{ getQcTypeLabel(row.qcType) }}
+        </Tag>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          v-if="row.status === NCR_STATUS.PENDING_REVIEW"
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:qc-ncr:update'],
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mes:qc-ncr:delete'],
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.name]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+            {
+              label: '璇勫',
+              type: 'link',
+              icon: 'lucide:clipboard-check',
+              auth: ['mes:qc-ncr:review'],
+              onClick: handleReview.bind(null, row),
+            },
+          ]"
+        />
+        <TableAction
+          v-else-if="row.status === NCR_STATUS.REVIEWED"
+          :actions="[
+            {
+              label: '澶勭疆',
+              type: 'link',
+              icon: 'lucide:play',
+              auth: ['mes:qc-ncr:dispose'],
+              onClick: handleDispose.bind(null, row),
+            },
+          ]"
+        />
+        <TableAction
+          v-else-if="row.status === NCR_STATUS.DISPOSED"
+          :actions="[
+            {
+              label: '鍏抽棴',
+              type: 'link',
+              icon: 'lucide:check-circle',
+              auth: ['mes:qc-ncr:close'],
+              onClick: handleClose.bind(null, row),
+            },
+          ]"
+        />
+        <span v-else class="text-gray-400">--</span>
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/mes/qc/ncr/modules/form.vue b/src/views/mes/qc/ncr/modules/form.vue
new file mode 100644
index 0000000..a4a6cdc
--- /dev/null
+++ b/src/views/mes/qc/ncr/modules/form.vue
@@ -0,0 +1,81 @@
+<script lang="ts" setup>
+import type { MesQcNcrApi } from '#/api/mes/qc/ncr';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { createNcr, getNcr, updateNcr } from '#/api/mes/qc/ncr';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+
+defineOptions({ name: 'NcrForm' });
+
+const emit = defineEmits(['success']);
+const formData = ref<MesQcNcrApi.Ncr>();
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['涓嶅悎鏍煎搧澶勭悊鍗�'])
+    : $t('ui.actionTitle.create', ['涓嶅悎鏍煎搧澶勭悊鍗�']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-2',
+    labelWidth: 120,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    const data = (await formApi.getValues()) as MesQcNcrApi.Ncr;
+    try {
+      await (formData.value?.id ? updateNcr(data) : createNcr(data));
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    formApi.setState({ schema: useFormSchema(formApi) });
+    const data = modalApi.getData<MesQcNcrApi.Ncr>();
+    if (!data || !data.id) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      formData.value = await getNcr(data.id);
+      await formApi.setValues(formData.value);
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/mes/qc/oqc/data.ts b/src/views/mes/qc/oqc/data.ts
index 62ef238..0183a35 100644
--- a/src/views/mes/qc/oqc/data.ts
+++ b/src/views/mes/qc/oqc/data.ts
@@ -233,6 +233,7 @@
         placeholder: '璇疯緭鍏ヤ笉鍚堟牸鍝佹暟閲�',
       },
       rules: 'required',
+      help: '涓嶅悎鏍煎搧鏁伴噺澶т簬0鏃讹紝淇濆瓨鍚庡皢鑷姩鐢熸垚涓嶅悎鏍煎鐞嗗崟',
     },
     {
       fieldName: 'inspectorUserId',
diff --git a/src/views/mes/qc/oqc/modules/form.vue b/src/views/mes/qc/oqc/modules/form.vue
index b66de48..e9e7258 100644
--- a/src/views/mes/qc/oqc/modules/form.vue
+++ b/src/views/mes/qc/oqc/modules/form.vue
@@ -6,7 +6,7 @@
 import { computed, ref } from 'vue';
 
 import { confirm, useVbenModal } from '@vben/common-ui';
-import { MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants';
+import { MesQcCheckResultEnum, MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants';
 
 import { Button, Descriptions, message, Tabs } from 'ant-design-vue';
 
@@ -69,6 +69,13 @@
     return false;
   }
   const data = (await formApi.getValues()) as MesQcOqcApi.Oqc;
+  if (
+    (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) &&
+    data.checkResult === MesQcCheckResultEnum.FAIL
+  ) {
+    message.warning('涓嶅悎鏍煎搧鏁伴噺涓�0鏃讹紝妫�娴嬬粨鏋滀笉鑳戒负"鏍¢獙涓嶉�氳繃"');
+    return false;
+  }
   if (formData.value?.id) {
     await updateOqc({ ...data, id: formData.value.id });
     formData.value = { ...formData.value, ...data };
@@ -107,6 +114,13 @@
     const current = JSON.stringify(await formApi.getValues());
     if (current !== originalSnapshot.value) {
       const data = (await formApi.getValues()) as MesQcOqcApi.Oqc;
+      if (
+        (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) &&
+        data.checkResult === MesQcCheckResultEnum.FAIL
+      ) {
+        message.warning('涓嶅悎鏍煎搧鏁伴噺涓�0鏃讹紝妫�娴嬬粨鏋滀笉鑳戒负"鏍¢獙涓嶉�氳繃"');
+        return;
+      }
       await updateOqc({ ...data, id });
       formData.value = { ...formData.value, ...data };
       originalSnapshot.value = current;
diff --git a/src/views/mes/qc/rqc/data.ts b/src/views/mes/qc/rqc/data.ts
index 4ea4739..92d5e1e 100644
--- a/src/views/mes/qc/rqc/data.ts
+++ b/src/views/mes/qc/rqc/data.ts
@@ -210,6 +210,7 @@
         precision: 2,
       },
       rules: 'required',
+      help: '涓嶅悎鏍煎搧鏁伴噺澶т簬0鏃讹紝淇濆瓨鍚庡皢鑷姩鐢熸垚涓嶅悎鏍煎鐞嗗崟',
     },
     {
       fieldName: 'inspectDate',
diff --git a/src/views/mes/qc/rqc/modules/form.vue b/src/views/mes/qc/rqc/modules/form.vue
index 96ffd82..b931684 100644
--- a/src/views/mes/qc/rqc/modules/form.vue
+++ b/src/views/mes/qc/rqc/modules/form.vue
@@ -6,7 +6,7 @@
 import { computed, ref } from 'vue';
 
 import { confirm, useVbenModal } from '@vben/common-ui';
-import { MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants';
+import { MesQcCheckResultEnum, MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants';
 
 import { Button, Descriptions, message, Tabs } from 'ant-design-vue';
 
@@ -69,6 +69,13 @@
     return false;
   }
   const data = (await formApi.getValues()) as MesQcRqcApi.Rqc;
+  if (
+    (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) &&
+    data.checkResult === MesQcCheckResultEnum.FAIL
+  ) {
+    message.warning('涓嶅悎鏍煎搧鏁伴噺涓�0鏃讹紝妫�娴嬬粨鏋滀笉鑳戒负"鏍¢獙涓嶉�氳繃"');
+    return false;
+  }
   if (formData.value?.id) {
     await updateRqc({ ...data, id: formData.value.id });
     formData.value = { ...formData.value, ...data };
@@ -107,6 +114,13 @@
     const current = JSON.stringify(await formApi.getValues());
     if (current !== originalSnapshot.value) {
       const data = (await formApi.getValues()) as MesQcRqcApi.Rqc;
+      if (
+        (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) &&
+        data.checkResult === MesQcCheckResultEnum.FAIL
+      ) {
+        message.warning('涓嶅悎鏍煎搧鏁伴噺涓�0鏃讹紝妫�娴嬬粨鏋滀笉鑳戒负"鏍¢獙涓嶉�氳繃"');
+        return;
+      }
       await updateRqc({ ...data, id });
       formData.value = { ...formData.value, ...data };
       originalSnapshot.value = current;
diff --git a/src/views/srm/enums.ts b/src/views/srm/enums.ts
index 4d014f1..6077634 100644
--- a/src/views/srm/enums.ts
+++ b/src/views/srm/enums.ts
@@ -172,7 +172,7 @@
   labels: { 1: '鍏紑鎷涙爣', 2: '閭�璇锋嫑鏍�', 3: '绔炰簤鎬ц皥鍒�' },
 };
 
-/** 鎷涙爣椤圭洰鐘舵�� 鈥� 瀵归綈 TenderStatusEnum (0=鑽夌 1=鍙戝竷涓� 2=鎶曟爣涓� 3=璇勬爣涓� 4=宸插畾鏍� 5=宸插叧闂�) */
+/** 鎷涙爣椤圭洰鐘舵�� 鈥� 瀵归綈 TenderStatusEnum (0=鑽夌 1=鍙戝竷涓� 2=鎶曟爣涓� 3=璇勬爣涓� 4=宸插畾鏍� 5=宸插叧闂� 10=姣斾环涓� 20=宸茬‘璁�) */
 export const TENDER_STATUS = {
   DRAFT: 0,
   PUBLISHED: 1,
@@ -180,6 +180,8 @@
   EVALUATING: 3,
   AWARDED: 4,
   CLOSED: 5,
+  PRICE_COMPARING: 10,
+  CONFIRMED: 20,
 } as const;
 
 export const TENDER_STATUS_OPTIONS: EnumOption[] = [
@@ -189,6 +191,8 @@
   { label: '璇勬爣涓�', value: 3 },
   { label: '宸插畾鏍�', value: 4 },
   { label: '宸插叧闂�', value: 5 },
+  { label: '姣斾环涓�', value: 10 },
+  { label: '宸茬‘璁�', value: 20 },
 ];
 
 export const TENDER_STATUS_CELLTAG: { data: CellTagItem[]; labels: Record<number, string> } = {
@@ -199,8 +203,10 @@
     { value: 3, color: 'orange' },
     { value: 4, color: 'green' },
     { value: 5, color: 'red' },
+    { value: 10, color: 'purple' },
+    { value: 20, color: 'green' },
   ],
-  labels: { 0: '鑽夌', 1: '鍙戝竷涓�', 2: '鎶曟爣涓�', 3: '璇勬爣涓�', 4: '宸插畾鏍�', 5: '宸插叧闂�' },
+  labels: { 0: '鑽夌', 1: '鍙戝竷涓�', 2: '鎶曟爣涓�', 3: '璇勬爣涓�', 4: '宸插畾鏍�', 5: '宸插叧闂�', 10: '姣斾环涓�', 20: '宸茬‘璁�' },
 };
 
 /** 鎷涙爣鐘舵�� Ant Design Tag 棰滆壊鏄犲皠 */
@@ -211,6 +217,25 @@
   [TENDER_STATUS.EVALUATING]: 'orange',
   [TENDER_STATUS.AWARDED]: 'green',
   [TENDER_STATUS.CLOSED]: 'red',
+  [TENDER_STATUS.PRICE_COMPARING]: 'purple',
+  [TENDER_STATUS.CONFIRMED]: 'green',
+};
+
+// ========== 鎷涙姇鏍囨ā寮� 鈥� FULL=瀹屾暣鐗� SIMPLE=绠�鏄撶増 ==========
+
+export const BIDDING_MODE = {
+  FULL: 'FULL',
+  SIMPLE: 'SIMPLE',
+} as const;
+
+export const BIDDING_MODE_OPTIONS: EnumOption[] = [
+  { label: '瀹屾暣鐗�', value: 'FULL' },
+  { label: '绠�鏄撶増', value: 'SIMPLE' },
+];
+
+export const BIDDING_MODE_MAP: Record<string, string> = {
+  FULL: '瀹屾暣鐗�',
+  SIMPLE: '绠�鏄撶増',
 };
 
 // ========== 鎶曟爣 鈥� 瀵归綈 BidStatusEnum (0=鎶ュ悕 10=宸叉姇鏍� 20=鎾ゆ爣 30=涓爣 40=鏈腑鏍�) ==========
diff --git a/src/views/srm/tender/config/index.vue b/src/views/srm/tender/config/index.vue
new file mode 100644
index 0000000..8ece764
--- /dev/null
+++ b/src/views/srm/tender/config/index.vue
@@ -0,0 +1,65 @@
+<script lang="ts" setup>
+import { ref } from 'vue';
+
+import { Page } from '@vben/common-ui';
+
+import { message, Radio, Button, Card, Spin } from 'ant-design-vue';
+
+import { getTenderConfig, saveTenderConfig } from '#/api/srm/tender-config';
+import { BIDDING_MODE, BIDDING_MODE_OPTIONS } from '#/views/srm/enums';
+
+defineOptions({ name: 'SrmTenderConfig' });
+
+const loading = ref(false);
+const saving = ref(false);
+const defaultBiddingMode = ref<string>(BIDDING_MODE.FULL);
+
+async function loadConfig() {
+  loading.value = true;
+  try {
+    const res = await getTenderConfig();
+    if (res?.defaultBiddingMode) {
+      defaultBiddingMode.value = res.defaultBiddingMode;
+    }
+  } finally {
+    loading.value = false;
+  }
+}
+
+async function handleSave() {
+  saving.value = true;
+  try {
+    await saveTenderConfig({ defaultBiddingMode: defaultBiddingMode.value });
+    message.success('淇濆瓨鎴愬姛');
+    await loadConfig();
+  } finally {
+    saving.value = false;
+  }
+}
+
+loadConfig();
+</script>
+
+<template>
+  <Page>
+    <Spin :spinning="loading">
+      <Card title="鎷涙姇鏍囬粯璁ゆā寮忛厤缃�" class="max-w-lg">
+        <div class="mb-4 text-gray-500 text-sm">
+          璁剧疆鏂板缓鎷涙爣椤圭洰鏃剁殑榛樿鎷涙姇鏍囨ā寮忥紝鐢ㄦ埛鍦ㄥ垱寤洪」鐩椂浠嶅彲鎵嬪姩鏇存敼銆�
+        </div>
+        <div class="flex items-center gap-4">
+          <span class="font-medium">榛樿妯″紡锛�</span>
+          <Radio.Group
+            v-model:value="defaultBiddingMode"
+            :options="BIDDING_MODE_OPTIONS"
+          />
+        </div>
+        <div class="mt-6">
+          <Button type="primary" :loading="saving" @click="handleSave">
+            淇濆瓨
+          </Button>
+        </div>
+      </Card>
+    </Spin>
+  </Page>
+</template>
diff --git a/src/views/srm/tender/data.ts b/src/views/srm/tender/data.ts
index c06e4dd..a3b1005 100644
--- a/src/views/srm/tender/data.ts
+++ b/src/views/srm/tender/data.ts
@@ -2,6 +2,8 @@
 import type { VxeTableGridOptions } from '#/adapter/vxe-table';
 
 import {
+  BIDDING_MODE_MAP,
+  BIDDING_MODE_OPTIONS,
   TENDER_STATUS_CELLTAG,
   TENDER_STATUS_OPTIONS,
   TENDER_TYPE_CELLTAG,
@@ -146,6 +148,25 @@
       },
     },
     {
+      fieldName: 'biddingMode',
+      label: '鎷涙姇鏍囨ā寮�',
+      component: 'Select',
+      rules: 'required',
+      componentProps: {
+        placeholder: '璇烽�夋嫨鎷涙姇鏍囨ā寮�',
+        options: BIDDING_MODE_OPTIONS,
+      },
+      dependencies: {
+        triggerFields: ['_partial', 'id'],
+        show: (values) => !values._partial,
+        componentProps: (values) => ({
+          disabled: !!values.id,
+          placeholder: '璇烽�夋嫨鎷涙姇鏍囨ā寮�',
+          options: BIDDING_MODE_OPTIONS,
+        }),
+      },
+    },
+    {
       fieldName: 'tenderDesc',
       label: '鎷涙爣璇存槑',
       component: 'Textarea',
@@ -197,6 +218,16 @@
         options: TENDER_STATUS_OPTIONS,
       },
     },
+    {
+      fieldName: 'biddingMode',
+      label: '鎷涙姇鏍囨ā寮�',
+      component: 'Select',
+      componentProps: {
+        placeholder: '璇烽�夋嫨鎷涙姇鏍囨ā寮�',
+        allowClear: true,
+        options: BIDDING_MODE_OPTIONS,
+      },
+    },
   ];
 }
 
@@ -217,6 +248,12 @@
       minWidth: 100,
       cellRender: { name: 'CellTag', props: TENDER_STATUS_CELLTAG },
     },
+    {
+      field: 'biddingMode',
+      title: '鎷涙爣妯″紡',
+      minWidth: 100,
+      formatter: ({ cellValue }) => BIDDING_MODE_MAP[cellValue] || cellValue,
+    },
     { field: 'budgetAmount', title: '棰勭畻閲戦', minWidth: 120 },
     { field: 'purchaseOrg', title: '閲囪喘缁勭粐', minWidth: 100 },
     { field: 'startTime', title: '寮�濮嬫椂闂�', minWidth: 160 },
diff --git a/src/views/srm/tender/detail.vue b/src/views/srm/tender/detail.vue
index 4dfbeda..7b2f1de 100644
--- a/src/views/srm/tender/detail.vue
+++ b/src/views/srm/tender/detail.vue
@@ -17,6 +17,7 @@
   createBidOpen, getBidOpen,
   getEvaluationList, createBidEvaluation, updateBidEvaluation, calculateRanking,
   createAward, deleteAward, getAward, getAwardByProject, approveAward, generatePurchaseOrder,
+  confirmTenderProject,
 } from '#/api/srm/tender';
 import { getSupplierSimpleList } from '#/api/srm/supplier';
 import { getItemSimpleList } from '#/api/mdm/item';
@@ -31,6 +32,7 @@
   AWARD_STATUS,
   AWARD_STATUS_MAP,
   getLabel,
+  BIDDING_MODE,
 } from '../enums';
 
 defineOptions({ name: 'SrmTenderDetail' });
@@ -318,6 +320,36 @@
   loadProject();
 }
 
+// ========== 绠�鏄撴ā寮忥細纭涓爣 ==========
+const confirmBidId = ref<number>();
+const confirmModalVisible = ref(false);
+
+async function handleConfirmBidAsWinner(bidId: number) {
+  await confirmTenderProject({ id: projectId, bidId });
+  message.success('纭涓爣鎴愬姛锛岄噰璐鍗曞凡鑷姩鐢熸垚');
+  loadProject();
+  loadBids();
+  loadAward();
+}
+
+function openSimpleConfirmModal() {
+  confirmBidId.value = undefined;
+  confirmModalVisible.value = true;
+}
+
+async function handleSimpleConfirm() {
+  if (!confirmBidId.value) {
+    message.warning('璇烽�夋嫨涓爣鐨勬姇鏍囪褰�');
+    return;
+  }
+  await confirmTenderProject({ id: projectId, bidId: confirmBidId.value });
+  message.success('纭涓爣鎴愬姛锛岄噰璐鍗曞凡鑷姩鐢熸垚');
+  confirmModalVisible.value = false;
+  loadProject();
+  loadBids();
+  loadAward();
+}
+
 // ========== 鍒濆鍖� ==========
 loadProject();
 loadMaterials();
@@ -344,23 +376,37 @@
 const canApproveAward = computed(() => award.value?.awardStatus === AWARD_STATUS.PENDING_APPROVAL);
 const canGeneratePO = computed(() => award.value?.awardStatus === AWARD_STATUS.AWARDED);
 
-const stepItems = [
-  { title: '鑽夌', status: TENDER_STATUS.DRAFT },
-  { title: '鍙戝竷', status: TENDER_STATUS.PUBLISHED },
-  { title: '鎶曟爣涓�', status: TENDER_STATUS.BIDDING },
-  { title: '璇勬爣涓�', status: TENDER_STATUS.EVALUATING },
-  { title: '宸插畾鏍�', status: TENDER_STATUS.AWARDED },
-];
+// 绠�鏄撴ā寮�
+const isSimpleMode = computed(() => project.value?.biddingMode === BIDDING_MODE.SIMPLE);
+const isPriceComparing = computed(() => project.value?.tenderStatus === TENDER_STATUS.PRICE_COMPARING);
+
+const stepItems = computed(() => {
+  if (isSimpleMode.value) {
+    return [
+      { title: '鑽夌', status: TENDER_STATUS.DRAFT },
+      { title: '姣斾环涓�', status: TENDER_STATUS.PRICE_COMPARING },
+      { title: '宸茬‘璁�', status: TENDER_STATUS.CONFIRMED },
+    ];
+  }
+  return [
+    { title: '鑽夌', status: TENDER_STATUS.DRAFT },
+    { title: '鍙戝竷', status: TENDER_STATUS.PUBLISHED },
+    { title: '鎶曟爣涓�', status: TENDER_STATUS.BIDDING },
+    { title: '璇勬爣涓�', status: TENDER_STATUS.EVALUATING },
+    { title: '宸插畾鏍�', status: TENDER_STATUS.AWARDED },
+  ];
+});
 const currentStep = computed(() => {
   const s = project.value?.tenderStatus ?? 0;
-  if (s === TENDER_STATUS.CLOSED) return 4;
-  const idx = stepItems.findIndex((item) => item.status >= s);
+  const items = stepItems.value;
+  if (s === TENDER_STATUS.CLOSED) return items.length - 1;
+  const idx = items.findIndex((item) => item.status >= s);
   return idx === -1 ? 0 : idx;
 });
 const stepStatus = computed(() => {
   const s = project.value?.tenderStatus;
   if (s === TENDER_STATUS.CLOSED) return 'error';
-  if (s === TENDER_STATUS.AWARDED) return 'finish';
+  if (s === TENDER_STATUS.AWARDED || s === TENDER_STATUS.CONFIRMED) return 'finish';
   return 'process';
 });
 
@@ -395,7 +441,7 @@
   { title: '鎶曟爣閲戦', dataIndex: 'bidTotalAmount', width: 120 },
   { title: '鎶曟爣鏃堕棿', dataIndex: 'bidTime', width: 160 },
   { title: '鐘舵��', dataIndex: 'bidStatus', width: 100, customRender: ({ text }: any) => BID_STATUS_MAP[text] || text },
-  { title: '鎿嶄綔', key: 'actions', width: 180 },
+  { title: '鎿嶄綔', key: 'actions', width: 240 },
 ];
 
 const quoteColumns = [
@@ -438,7 +484,16 @@
         <span class="text-gray-400 text-xs">{{ project.tenderNo }}</span>
         <a-tag :color="statusColor">{{ statusLabel }}</a-tag>
         <a-tag color="blue">{{ typeLabel }}</a-tag>
+        <a-tag v-if="isSimpleMode" color="purple">绠�鏄撶増</a-tag>
         <div class="flex-1" />
+        <a-button
+          v-if="isPriceComparing"
+          type="primary"
+          size="small"
+          @click="openSimpleConfirmModal"
+        >
+          纭涓爣
+        </a-button>
         <a-button size="small" @click="loadProject(); loadMaterials(); loadBids(); loadEvaluations(); loadAward();">
           <template #icon><IconifyIcon icon="ant-design:reload-outlined" /></template>
           鍒锋柊
@@ -539,7 +594,7 @@
               </template>
               <div class="flex items-center justify-between mb-3">
                 <span class="text-xs text-gray-400">鍏� <b class="text-gray-600">{{ bidCount }}</b> 涓姇鏍�</span>
-                <a-button v-if="isPublishedOrBidding" type="primary" size="small" @click="bidForm = { tenderProjectId: projectId, supplierId: undefined!, bidNo: '' }; bidFormVisible = true">
+                <a-button v-if="isPublishedOrBidding || isDraft || isPriceComparing" type="primary" size="small" @click="bidForm = { tenderProjectId: projectId, supplierId: undefined!, bidNo: '' }; bidFormVisible = true">
                   <template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template>
                   鏂板鎶曟爣
                 </a-button>
@@ -557,7 +612,10 @@
                   <template v-if="column.key === 'actions'">
                     <a-space>
                       <a-button size="small" type="link" @click="loadQuotes(record.id); activeTab = 'quote'"><IconifyIcon icon="ant-design:dollar-outlined" />鎶ヤ环</a-button>
-                      <a-popconfirm v-if="isPublishedOrBidding && record.bidStatus === BID_STATUS.REGISTERED" title="纭鎾ゆ爣锛�" @confirm="handleWithdrawBid(record.id)">
+                      <a-popconfirm v-if="isPriceComparing && record.bidStatus !== BID_STATUS.WITHDRAWN" title="纭閫夋嫨璇ユ姇鏍囦负涓爣锛熷皢鑷姩鐢熸垚閲囪喘璁㈠崟銆�" ok-text="纭涓爣" cancel-text="鍙栨秷" @confirm="handleConfirmBidAsWinner(record.id)">
+                        <a-button size="small" type="link" style="color: #52c41a;"><IconifyIcon icon="ant-design:check-circle-outlined" />纭涓爣</a-button>
+                      </a-popconfirm>
+                      <a-popconfirm v-if="(isPublishedOrBidding || isDraft || isPriceComparing) && record.bidStatus === BID_STATUS.REGISTERED" title="纭鎾ゆ爣锛�" @confirm="handleWithdrawBid(record.id)">
                         <a-button size="small" type="link" danger><IconifyIcon icon="ant-design:close-circle-outlined" />鎾ゆ爣</a-button>
                       </a-popconfirm>
                     </a-space>
@@ -580,7 +638,7 @@
                   <a-button size="small" type="link" class="ml-auto" @click="activeTab = 'bid'">鍒囨崲鎶曟爣</a-button>
                 </div>
                 <div class="text-right mb-3">
-                  <a-button v-if="isPublishedOrBidding && selectedBid.bidStatus !== BID_STATUS.WITHDRAWN" type="primary" size="small" @click="handleAddQuote">
+                  <a-button v-if="(isPublishedOrBidding || isDraft || isPriceComparing) && selectedBid?.bidStatus !== BID_STATUS.WITHDRAWN" type="primary" size="small" @click="handleAddQuote">
                     <template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template>
                     鏂板鎶ヤ环
                   </a-button>
@@ -600,7 +658,7 @@
                       {{ record.warrantyPeriod != null ? `${record.warrantyPeriod} 涓湀` : '-' }}
                     </template>
                     <template v-if="column.key === 'actions'">
-                      <a-button v-if="isPublishedOrBidding && selectedBid?.bidStatus !== BID_STATUS.WITHDRAWN" size="small" type="link" @click="handleEditQuote(record)"><IconifyIcon icon="ant-design:edit-outlined" />缂栬緫</a-button>
+                      <a-button v-if="(isPublishedOrBidding || isDraft || isPriceComparing) && selectedBid?.bidStatus !== BID_STATUS.WITHDRAWN" size="small" type="link" @click="handleEditQuote(record)"><IconifyIcon icon="ant-design:edit-outlined" />缂栬緫</a-button>
                     </template>
                   </template>
                 </a-table>
@@ -612,7 +670,7 @@
             </a-tab-pane>
 
             <!-- Tab4: 寮�鏍� -->
-            <a-tab-pane key="bidOpen">
+            <a-tab-pane v-if="!isSimpleMode" key="bidOpen">
               <template #tab>
                 <a-space :size="4"><IconifyIcon icon="ant-design:unlock-outlined" />寮�鏍�</a-space>
               </template>
@@ -639,7 +697,7 @@
             </a-tab-pane>
 
             <!-- Tab5: 璇勬爣 -->
-            <a-tab-pane key="evaluation">
+            <a-tab-pane v-if="!isSimpleMode" key="evaluation">
               <template #tab>
                 <a-space :size="4"><IconifyIcon icon="ant-design:trophy-outlined" />璇勬爣</a-space>
               </template>
@@ -685,7 +743,7 @@
             </a-tab-pane>
 
             <!-- Tab6: 瀹氭爣 -->
-            <a-tab-pane key="award">
+            <a-tab-pane v-if="!isSimpleMode" key="award">
               <template #tab>
                 <a-space :size="4"><IconifyIcon icon="ant-design:check-circle-outlined" />瀹氭爣</a-space>
               </template>
@@ -886,6 +944,32 @@
         </a-form>
       </a-modal>
 
+      <!-- 绠�鏄撴ā寮忥細纭涓爣寮圭獥 -->
+      <a-modal v-model:open="confirmModalVisible" title="纭涓爣" :width="460" @ok="handleSimpleConfirm">
+        <a-form layout="vertical" class="mt-4">
+          <a-form-item label="涓爣鎶曟爣璁板綍" required>
+            <a-select
+              v-model:value="confirmBidId"
+              show-search
+              placeholder="閫夋嫨涓爣鐨勬姇鏍囪褰�"
+              :filter-option="filterOption"
+            >
+              <a-select-option
+                v-for="b in bidList.filter((i) => i.bidStatus !== BID_STATUS.WITHDRAWN)"
+                :key="b.id"
+                :value="b.id"
+                :label="`${b.supplierName} - ${b.bidNo}`"
+              >
+                {{ b.supplierName }} - {{ b.bidNo }} (楼{{ b.bidTotalAmount?.toLocaleString?.() ?? b.bidTotalAmount }})
+              </a-select-option>
+            </a-select>
+          </a-form-item>
+          <div class="text-gray-400 text-xs">
+            纭鍚庡皢鑷姩鐢熸垚閲囪喘璁㈠崟锛岃鎿嶄綔涓嶅彲鎾ら攢銆�
+          </div>
+        </a-form>
+      </a-modal>
+
       <!-- 瀹氭爣琛ㄥ崟 -->
       <a-modal v-model:open="awardFormVisible" title="鍒涘缓瀹氭爣" :width="500" @ok="handleCreateAward">
         <a-form layout="vertical" class="mt-4">
diff --git a/src/views/srm/tender/index.vue b/src/views/srm/tender/index.vue
index 7e09d1a..44f6ed7 100644
--- a/src/views/srm/tender/index.vue
+++ b/src/views/srm/tender/index.vue
@@ -4,24 +4,28 @@
 
 import { Page, useVbenModal } from '@vben/common-ui';
 
-import { message } from 'ant-design-vue';
+import { message, Select } from 'ant-design-vue';
+import { ref } from 'vue';
 import { useRouter } from 'vue-router';
 
 import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
 import {
   closeTender,
+  confirmTenderProject,
   deleteTenderProject,
   generatePurchaseOrder,
+  getBidListByProject,
   getTenderProjectPage,
   publishTender,
   startBidding,
   startEvaluation,
+  startPriceComparing,
 } from '#/api/srm/tender';
 import { $t } from '#/locales';
 
 import { useGridColumns, useGridFormSchema } from './data';
 import TenderForm from './modules/form.vue';
-import { TENDER_STATUS } from '../enums';
+import { BIDDING_MODE, TENDER_STATUS } from '../enums';
 
 defineOptions({ name: 'SrmTender' });
 
@@ -88,6 +92,64 @@
   handleRefresh();
 }
 
+async function handleStartPriceComparing(row: SrmTenderApi.TenderProjectVO) {
+  await startPriceComparing(row.id!);
+  message.success('宸插紑濮嬫瘮浠�');
+  handleRefresh();
+}
+
+// 纭涓爣鐩稿叧
+const confirmTarget = ref<SrmTenderApi.TenderProjectVO | null>(null);
+const confirmBidId = ref<number>();
+const confirmBidOptions = ref<{ label: string; value: number }[]>([]);
+const confirmLoading = ref(false);
+
+async function openConfirmModal(row: SrmTenderApi.TenderProjectVO) {
+  confirmTarget.value = row;
+  confirmBidId.value = undefined;
+  confirmLoading.value = true;
+  try {
+    const bids = await getBidListByProject(row.id!);
+    confirmBidOptions.value = (bids || []).map((b: any) => ({
+      label: `${b.supplierName} - ${b.bidNo} (楼${b.bidTotalAmount ?? 0})`,
+      value: b.id!,
+    }));
+  } finally {
+    confirmLoading.value = false;
+  }
+  confirmModalApi.open();
+}
+
+async function handleConfirmBid() {
+  if (!confirmTarget.value || !confirmBidId.value) {
+    message.warning('璇烽�夋嫨涓爣渚涘簲鍟嗙殑鎶曟爣璁板綍');
+    return;
+  }
+  confirmLoading.value = true;
+  try {
+    await confirmTenderProject({
+      id: confirmTarget.value.id!,
+      bidId: confirmBidId.value,
+    });
+    message.success('纭涓爣鎴愬姛锛岄噰璐鍗曞凡鑷姩鐢熸垚');
+    confirmModalApi.close();
+    handleRefresh();
+  } finally {
+    confirmLoading.value = false;
+  }
+}
+
+const [ConfirmModal, confirmModalApi] = useVbenModal({
+  title: '纭涓爣',
+  onConfirm: handleConfirmBid,
+  onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      confirmTarget.value = null;
+      confirmBidId.value = undefined;
+    }
+  },
+});
+
 const [FormModal, formModalApi] = useVbenModal({
   connectedComponent: TenderForm,
   destroyOnClose: true,
@@ -121,6 +183,18 @@
 <template>
   <Page auto-content-height>
     <FormModal @success="handleRefresh" />
+    <ConfirmModal>
+      <div class="flex items-center gap-2">
+        <span class="font-medium whitespace-nowrap">涓爣鎶曟爣璁板綍锛�</span>
+        <Select
+          v-model:value="confirmBidId"
+          :options="confirmBidOptions"
+          :loading="confirmLoading"
+          placeholder="璇烽�夋嫨涓爣鐨勬姇鏍囪褰�"
+          class="flex-1"
+        />
+      </div>
+    </ConfirmModal>
     <Grid table-title="鎷涙爣椤圭洰鍒楄〃">
       <template #toolbar-tools>
         <TableAction
@@ -148,7 +222,7 @@
               label: '鍙戝竷',
               type: 'link',
               auth: ['srm:tender-project:update'],
-              ifShow: row.tenderStatus === TENDER_STATUS.DRAFT,
+              ifShow: row.tenderStatus === TENDER_STATUS.DRAFT && row.biddingMode !== BIDDING_MODE.SIMPLE,
               popConfirm: {
                 title: '纭瑕佸彂甯冭椤圭洰鍚楋紵鍙戝竷鍚庢棤娉曚慨鏀广��',
                 confirm: handlePublish.bind(null, row),
@@ -167,7 +241,7 @@
               type: 'link',
               icon: ACTION_ICON.EDIT,
               auth: ['srm:tender-project:update'],
-              ifShow: row.tenderStatus === TENDER_STATUS.PUBLISHED || row.tenderStatus === TENDER_STATUS.BIDDING,
+              ifShow: (row.tenderStatus === TENDER_STATUS.PUBLISHED || row.tenderStatus === TENDER_STATUS.BIDDING) && row.biddingMode !== BIDDING_MODE.SIMPLE,
               onClick: handleEdit.bind(null, row),
             },
             {
@@ -186,7 +260,7 @@
               label: '寮�濮嬫姇鏍�',
               type: 'link',
               auth: ['srm:tender-project:update'],
-              ifShow: row.tenderStatus === TENDER_STATUS.PUBLISHED,
+              ifShow: row.tenderStatus === TENDER_STATUS.PUBLISHED && row.biddingMode !== BIDDING_MODE.SIMPLE,
               popConfirm: {
                 title: '纭瑕佸紑濮嬫姇鏍囧悧锛�',
                 confirm: handleStartBidding.bind(null, row),
@@ -197,7 +271,7 @@
               type: 'link',
               danger: true,
               auth: ['srm:tender-project:update'],
-              ifShow: row.tenderStatus === TENDER_STATUS.PUBLISHED || row.tenderStatus === TENDER_STATUS.BIDDING || row.tenderStatus === TENDER_STATUS.EVALUATING,
+              ifShow: row.tenderStatus === TENDER_STATUS.PUBLISHED || row.tenderStatus === TENDER_STATUS.BIDDING || row.tenderStatus === TENDER_STATUS.EVALUATING || row.tenderStatus === TENDER_STATUS.PRICE_COMPARING,
               popConfirm: {
                 title: '纭瑕佸叧闂椤圭洰鍚楋紵',
                 confirm: handleClose.bind(null, row),
@@ -207,7 +281,7 @@
               label: '寮�濮嬭瘎鏍�',
               type: 'link',
               auth: ['srm:tender-project:update'],
-              ifShow: row.tenderStatus === TENDER_STATUS.BIDDING,
+              ifShow: row.tenderStatus === TENDER_STATUS.BIDDING && row.biddingMode !== BIDDING_MODE.SIMPLE,
               popConfirm: {
                 title: '纭瑕佸紑濮嬭瘎鏍囧悧锛�',
                 confirm: handleStartEvaluation.bind(null, row),
@@ -217,12 +291,29 @@
               label: '鐢熸垚閲囪喘璁㈠崟',
               type: 'link',
               auth: ['srm:bid-award:update'],
-              ifShow: !!row.awardId && !row.purchaseOrderNo,
+              ifShow: !!row.awardId && !row.purchaseOrderNo && row.biddingMode !== BIDDING_MODE.SIMPLE,
               popConfirm: {
                 title: '纭瑕佺敓鎴愰噰璐鍗曞悧锛�',
                 confirm: handleGeneratePurchaseOrder.bind(null, row),
               },
             },
+            {
+              label: '寮�濮嬫瘮浠�',
+              type: 'link',
+              auth: ['srm:tender-project:update'],
+              ifShow: row.biddingMode === BIDDING_MODE.SIMPLE && row.tenderStatus === TENDER_STATUS.DRAFT,
+              popConfirm: {
+                title: '纭瑕佸紑濮嬫瘮浠峰悧锛�',
+                confirm: handleStartPriceComparing.bind(null, row),
+              },
+            },
+            {
+              label: '纭涓爣',
+              type: 'link',
+              auth: ['srm:tender-project:update'],
+              ifShow: row.biddingMode === BIDDING_MODE.SIMPLE && row.tenderStatus === TENDER_STATUS.PRICE_COMPARING,
+              onClick: openConfirmModal.bind(null, row),
+            },
           ]"
         />
       </template>
diff --git a/src/views/srm/tender/modules/form.vue b/src/views/srm/tender/modules/form.vue
index 5812d05..bcdc8d2 100644
--- a/src/views/srm/tender/modules/form.vue
+++ b/src/views/srm/tender/modules/form.vue
@@ -14,6 +14,7 @@
   updateTenderProject,
   updateTenderProjectPartial,
 } from '#/api/srm/tender';
+import { getTenderConfig } from '#/api/srm/tender-config';
 import { $t } from '#/locales';
 
 import { TENDER_STATUS } from '../../enums';
@@ -77,6 +78,15 @@
     }
     const data = modalApi.getData<SrmTenderApi.TenderProjectVO>();
     if (!data || !data.id) {
+      // 鏂板缓妯″紡锛氬姞杞介厤缃幏鍙栭粯璁ゆ嫑鎶曟爣妯″紡
+      try {
+        const config = await getTenderConfig();
+        if (config?.defaultBiddingMode) {
+          await formApi.setFieldValue('biddingMode', config.defaultBiddingMode);
+        }
+      } catch {
+        // 閰嶇疆鍔犺浇澶辫触锛屼娇鐢ㄩ粯璁ゅ��
+      }
       return;
     }
     modalApi.lock();
diff --git a/src/views/wls/stocktaking/plan/components/condition-value-input.vue b/src/views/wls/stocktaking/plan/components/condition-value-input.vue
index d38c9de..1ad1dfc 100644
--- a/src/views/wls/stocktaking/plan/components/condition-value-input.vue
+++ b/src/views/wls/stocktaking/plan/components/condition-value-input.vue
@@ -1,7 +1,7 @@
 <script lang="ts" setup>
 import type { NumberDictDataType } from '@vben/hooks';
 
-import { ref, watch } from 'vue';
+import { computed, ref, watch } from 'vue';
 
 import { DICT_TYPE, WlsStockTakingParamTypeEnum } from '@vben/constants';
 import { getDictOptions } from '@vben/hooks';
@@ -24,16 +24,19 @@
   defineProps<{
     modelValue?: number;
     type?: number;
+    value?: number;
     valueCode?: string;
   }>(),
   {
     modelValue: undefined,
     type: undefined,
+    value: undefined,
     valueCode: undefined,
   },
 );
 const emit = defineEmits<{
   'update:modelValue': [value?: number];
+  'update:value': [value?: number];
   valueChange: [
     payload: { valueCode?: string; valueId?: number; valueName?: string },
   ];
@@ -44,6 +47,9 @@
   'number',
 ) as NumberDictDataType[];
 
+/** 鍏煎 VbenForm 榛樿 v-model prop ('value') 鍜� Vue 3 鏍囧噯 ('modelValue') */
+const effectiveValue = computed(() => props.value ?? props.modelValue);
+
 const locationWarehouseId = ref<number>(); // 搴撳尯閫夋嫨鍣ㄤ复鏃舵暟鎹細閫変粨搴撳悗浼犵粰搴撳尯閫夋嫨鍣�
 const areaWarehouseId = ref<number>(); // 搴撲綅閫夋嫨鍣ㄤ复鏃舵暟鎹細閫変粨搴撳悗浼犵粰搴撳尯閫夋嫨鍣�
 const areaLocationId = ref<number>(); // 搴撲綅閫夋嫨鍣ㄤ复鏃舵暟鎹細閫夊簱鍖哄悗浼犵粰搴撲綅閫夋嫨鍣�
@@ -53,6 +59,7 @@
 /** 閫氱敤閫夋嫨鍣ㄥ彉鍖栵細鍥炲~鏉′欢鍊肩紪鐮併�佸悕绉� */
 function handleSelectorChange(item?: any) {
   emit('update:modelValue', item?.id);
+  emit('update:value', item?.id);
   emit('valueChange', {
     valueId: item?.id,
     valueCode: item?.code || '',
@@ -63,6 +70,7 @@
 /** 鎵规閫夋嫨鍣ㄥ彉鍖� */
 function handleBatchChange(batch?: any) {
   emit('update:modelValue', batch?.id);
+  emit('update:value', batch?.id);
   emit('valueChange', {
     valueId: batch?.id,
     valueCode: batch?.code || '',
@@ -75,6 +83,7 @@
   qualityStatusValue.value = value;
   const selected = qualityStatusOptions.find((item) => item.value === value);
   emit('update:modelValue', undefined);
+  emit('update:value', undefined);
   emit('valueChange', {
     valueId: undefined,
     valueCode: value === null ? '' : String(value),
@@ -85,6 +94,7 @@
 /** 搴撳尯浠撳簱閫夋嫨鍥炶皟锛氭竻绌哄簱鍖� */
 function handleLocationWarehouseChange() {
   emit('update:modelValue', undefined);
+  emit('update:value', undefined);
   emit('valueChange', { valueId: undefined, valueCode: '', valueName: '' });
 }
 
@@ -92,12 +102,14 @@
 function handleAreaWarehouseChange() {
   areaLocationId.value = undefined;
   emit('update:modelValue', undefined);
+  emit('update:value', undefined);
   emit('valueChange', { valueId: undefined, valueCode: '', valueName: '' });
 }
 
 /** 搴撲綅搴撳尯閫夋嫨鍥炶皟锛氭竻绌哄簱浣� */
 function handleAreaLocationChange() {
   emit('update:modelValue', undefined);
+  emit('update:value', undefined);
   emit('valueChange', { valueId: undefined, valueCode: '', valueName: '' });
 }
 
@@ -109,14 +121,14 @@
       : undefined;
     return;
   }
-  if (!props.modelValue) {
+  if (!effectiveValue.value) {
     return;
   }
   if (props.type === WlsStockTakingParamTypeEnum.LOCATION) {
-    const location = await getWarehouseLocation(props.modelValue);
+    const location = await getWarehouseLocation(effectiveValue.value);
     locationWarehouseId.value = location?.warehouseId;
   } else if (props.type === WlsStockTakingParamTypeEnum.AREA) {
-    const area = await getWarehouseArea(props.modelValue);
+    const area = await getWarehouseArea(effectiveValue.value);
     areaWarehouseId.value = area?.warehouseId;
     areaLocationId.value = area?.locationId;
   }
@@ -139,7 +151,7 @@
 <template>
   <WmWarehouseSelect
     v-if="type === WlsStockTakingParamTypeEnum.WAREHOUSE"
-    :model-value="modelValue"
+    :model-value="effectiveValue"
     @change="handleSelectorChange"
   />
   <div
@@ -153,7 +165,7 @@
     />
     <WmWarehouseLocationSelect
       v-if="locationWarehouseId"
-      :model-value="modelValue"
+      :model-value="effectiveValue"
       :warehouse-id="locationWarehouseId"
       placeholder="璇烽�夋嫨搴撳尯"
       @change="handleSelectorChange"
@@ -177,7 +189,7 @@
     />
     <WmWarehouseAreaSelect
       v-if="areaLocationId"
-      :model-value="modelValue"
+      :model-value="effectiveValue"
       :location-id="areaLocationId"
       placeholder="璇烽�夋嫨搴撲綅"
       @change="handleSelectorChange"
@@ -185,12 +197,12 @@
   </div>
   <MdItemSelect
     v-else-if="type === WlsStockTakingParamTypeEnum.ITEM"
-    :model-value="modelValue"
+    :model-value="effectiveValue"
     @change="handleSelectorChange"
   />
   <WmBatchSelect
     v-else-if="type === WlsStockTakingParamTypeEnum.BATCH"
-    :model-value="modelValue"
+    :model-value="effectiveValue"
     @change="handleBatchChange"
   />
   <Select
diff --git a/src/views/wls/stocktaking/plan/data.ts b/src/views/wls/stocktaking/plan/data.ts
index a8b2d91..263e3fa 100644
--- a/src/views/wls/stocktaking/plan/data.ts
+++ b/src/views/wls/stocktaking/plan/data.ts
@@ -326,8 +326,8 @@
       component: markRaw(StockTakingPlanConditionValueInput),
       // 鏉′欢鍊兼帶浠跺唴閮ㄦ寜鏉′欢绫诲瀷鍒囨崲閫夋嫨鍣紝浠呴�夋嫨绫诲瀷鍚庡睍绀�
       dependencies: {
-        triggerFields: ['type'],
-        if: (values) => values.type !== null,
+        triggerFields: ['type', 'valueCode'],
+        show: (values) => values.type != null,
         componentProps: (values) => ({
           type: values.type,
           valueCode: values.valueCode,

--
Gitblit v1.9.3