From 346a988f537228ae44731cd31007f1412b482e83 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期一, 19 十月 2026 16:32:10 +0800
Subject: [PATCH] refactor(mdm): 移除物料主数据中废弃的字段配置

---
 src/views/mes/qc/seedRange/index.vue             |   91 +++
 src/views/basicData/mdm/modules/form.vue         |    9 
 src/views/mes/qc/seedQuality/record.vue          |  155 ++++-
 src/views/mobile/quality-records.vue             |   72 ++
 src/views/mobile/quality.vue                     |   24 
 src/api/mes/qc/seedQuality/index.ts              |   45 +
 src/views/mes/pro/batch/modules/form.vue         |   45 +
 src/views/mes/qc/approverConfig/data.ts          |  199 +++++++
 src/views/mes/qc/oqc/index.vue                   |  129 ----
 src/views/mes/qc/oqc/modules/form.vue            |    4 
 /dev/null                                        |   20 
 src/api/mes/pro/batch/index.ts                   |    4 
 src/api/mes/qc/approverConfig/index.ts           |   53 ++
 src/views/mes/qc/approverConfig/index.vue        |  141 +++++
 src/api/mes/qc/oqc/index.ts                      |   11 
 src/views/basicData/mdm/data.ts                  |  276 -----------
 src/api/mes/pro/bagCode/index.ts                 |    5 
 src/views/mes/qc/seedQuality/index.vue           |   37 +
 src/views/mes/pro/batch/data.ts                  |   43 +
 src/views/mes/qc/approverConfig/modules/form.vue |   92 +++
 20 files changed, 956 insertions(+), 499 deletions(-)

diff --git a/src/api/mes/pro/bagCode/index.ts b/src/api/mes/pro/bagCode/index.ts
index 7ea1122..9d6e141 100644
--- a/src/api/mes/pro/bagCode/index.ts
+++ b/src/api/mes/pro/bagCode/index.ts
@@ -222,6 +222,11 @@
   );
 }
 
+/** 鍙粦瀹氭壒娆$殑鍚ㄨ锛堟棤鎵樼洏涓旀湭缁戝畾鎵规锛� */
+export function getUnboundTonBags() {
+  return requestClient.get<MesProBagCodeApi.BagCode[]>('/mes/pro/bag-code/unbound-ton-bags');
+}
+
 /** 鎵归噺瀵煎嚭琚嬬爜 Excel锛堜氦浠樺嵃鍒凤級 */
 export function exportBagCodeExcel(params: MesProBagCodeApi.PageParams) {
   return requestClient.download('/mes/pro/bag-code/export-excel', { params });
diff --git a/src/api/mes/pro/batch/index.ts b/src/api/mes/pro/batch/index.ts
index d681c62..327d14c 100644
--- a/src/api/mes/pro/batch/index.ts
+++ b/src/api/mes/pro/batch/index.ts
@@ -18,9 +18,10 @@
   /** 鐢熶骇鎵规 */
   export interface Batch {
     id?: number; // 缂栧彿
-    itemId?: number; // 鎵樼洏甯﹀叆鐨勫搧绉嶇紪鍙�
+    itemId?: number; // 鎵樼洏鎴栧惃琚嬪甫鍏ョ殑鍝佺缂栧彿
     itemCode?: string;
     itemName?: string;
+    bindingMode?: 'pallet' | 'tonBag'; // 鎵规宸叉湁缁戝畾鏂瑰紡
     businessCode?: string; // 鍐呴儴鍑哄叆搴撲笟鍔$爜
     code?: string; // 鎵规鍙凤紙琛ュ綍姝e紡淇℃伅鍚庤嚜鍔ㄧ敓鎴愶級
     tempCode?: string; // 涓存椂鎵规鐮侊紙棰勭敓鎴愪簩缁寸爜闃舵浣跨敤锛屾壂鐮佽拷婧吋瀹癸級
@@ -39,6 +40,7 @@
     createTime?: string; // 鍒涘缓鏃堕棿
     workers?: BatchWorker[]; // 鐢熶骇浜哄憳閰嶇疆鍒楄〃
     palletIds?: number[]; // 缁戝畾鎵樼洏缂栧彿鍒楄〃锛堜粎鏂板/琛ュ綍鎻愪氦鏃朵紶鍏ワ紝缁戝畾鏃舵壒娆$洿鎺ヨ繘鍏ョ敓浜т腑锛�
+    tonBagIds?: number[]; // 缁戝畾鍚ㄨ缂栧彿鍒楄〃锛涗笌鎵樼洏浜掓枼
   }
 
   /** 鐢熶骇鎵规鎵爜杩芥函 */
diff --git a/src/api/mes/qc/approverConfig/index.ts b/src/api/mes/qc/approverConfig/index.ts
new file mode 100644
index 0000000..28922e6
--- /dev/null
+++ b/src/api/mes/qc/approverConfig/index.ts
@@ -0,0 +1,53 @@
+import type { PageParam, PageResult } from '@vben/request';
+
+import { requestClient } from '#/api/request';
+
+export namespace MesQcApproverConfigApi {
+  /** MES 璐ㄦ瀹℃牳浜洪厤缃� */
+  export interface ApproverConfig {
+    id?: number; // 缂栧彿
+    qcType?: number; // 璐ㄦ绫诲瀷锛�1绉嶅瓙璐ㄦ
+    approveNode?: number; // 瀹℃牳鑺傜偣锛�1鍔犲伐閮ㄥ鎵� 3鍏徃璐ㄦ瀹℃壒
+    userId?: number; // 瀹℃牳浜虹敤鎴� ID
+    userNickname?: string; // 瀹℃牳浜烘樀绉帮紙鍏宠仈鏌ヨ锛�
+    status?: number; // 鐘舵�侊細0鍚敤 1鍋滅敤
+    remark?: string; // 澶囨敞
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ璐ㄦ瀹℃牳浜洪厤缃垎椤� */
+export function getApproverConfigPage(params: PageParam) {
+  return requestClient.get<PageResult<MesQcApproverConfigApi.ApproverConfig>>(
+    '/mes/qc/approver-config/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ璐ㄦ瀹℃牳浜洪厤缃鎯� */
+export function getApproverConfig(id: number) {
+  return requestClient.get<MesQcApproverConfigApi.ApproverConfig>(
+    `/mes/qc/approver-config/get?id=${id}`,
+  );
+}
+
+/** 鏂板璐ㄦ瀹℃牳浜洪厤缃� */
+export function createApproverConfig(
+  data: MesQcApproverConfigApi.ApproverConfig,
+) {
+  return requestClient.post('/mes/qc/approver-config/create', data);
+}
+
+/** 淇敼璐ㄦ瀹℃牳浜洪厤缃� */
+export function updateApproverConfig(
+  data: MesQcApproverConfigApi.ApproverConfig,
+) {
+  return requestClient.put('/mes/qc/approver-config/update', data);
+}
+
+/** 鍒犻櫎璐ㄦ瀹℃牳浜洪厤缃� */
+export function deleteApproverConfig(id: number) {
+  return requestClient.delete(
+    `/mes/qc/approver-config/delete?id=${id}`,
+  );
+}
diff --git a/src/api/mes/qc/oqc/index.ts b/src/api/mes/qc/oqc/index.ts
index c6ee5c0..793f6db 100644
--- a/src/api/mes/qc/oqc/index.ts
+++ b/src/api/mes/qc/oqc/index.ts
@@ -39,7 +39,6 @@
     inspectorUserId?: number; // 妫�娴嬩汉鍛樼敤鎴� ID
     inspectorNickname?: string; // 妫�娴嬩汉鍛樻樀绉帮紙鍏宠仈鏌ヨ锛�
     status?: number; // 鐘舵��
-    workflowStatus?: number; // 閿�鍞川妫�澶嶆鍜屽鎵圭姸鎬�
     remark?: string; // 澶囨敞
   }
 }
@@ -69,16 +68,6 @@
 /** 瀹屾垚鍑鸿揣妫�楠屽崟 */
 export function finishOqc(id: number) {
   return requestClient.put(`/mes/qc/oqc/finish?id=${id}`);
-}
-export function retestOqc(data: { id: number; checkQuantity: number; qualifiedQuantity: number; unqualifiedQuantity: number; checkResult: number; opinion?: string }) {
-  return requestClient.post('/mes/qc/oqc/retest', data);
-}
-export function approveOqc(stage: 'sales' | 'company', data: { id: number; approved: boolean; opinion: string }) {
-  return requestClient.post(`/mes/qc/oqc/${stage}-approve`, data);
-}
-export interface OqcAction { id: number; action: string; result?: number; opinion?: string; quantitiesJson?: string; operatorId?: number; createTime?: string }
-export function getOqcActions(id: number) {
-  return requestClient.get<OqcAction[]>('/mes/qc/oqc/actions', { params: { id } });
 }
 
 /** 鍒犻櫎鍑鸿揣妫�楠屽崟 */
diff --git a/src/api/mes/qc/seedQuality/index.ts b/src/api/mes/qc/seedQuality/index.ts
index 415c043..3012db0 100644
--- a/src/api/mes/qc/seedQuality/index.ts
+++ b/src/api/mes/qc/seedQuality/index.ts
@@ -1,5 +1,20 @@
 import { requestClient } from '#/api/request';
 
+export interface SeedIndicatorRange {
+  indicatorCode: string;
+  minValue: number;
+  maxValue: number;
+  updateTime?: string;
+}
+
+export function getSeedIndicatorRanges() {
+  return requestClient.get<SeedIndicatorRange[]>('/mes/qc/seed-quality/ranges');
+}
+
+export function saveSeedIndicatorRanges(ranges: SeedIndicatorRange[]) {
+  return requestClient.put('/mes/qc/seed-quality/ranges', { ranges });
+}
+
 export interface SeedQualityPageParams {
   pageNo?: number;
   pageSize?: number;
@@ -12,6 +27,7 @@
 export interface SeedQualityRecord {
   id: number;
   code: string;
+  palletCode?: string;
   batchCode?: string;
   result: number;
   workflowStatus: number;
@@ -23,7 +39,7 @@
 }
 export interface SeedQualityIndicator {
   indicatorCode: string;
-  value: number;
+  value?: number;
   minValue: number;
   maxValue: number;
 }
@@ -45,6 +61,33 @@
 export function retestSeedQuality(id: number, data: Omit<MesQcSeedQualityApi.SubmitParams, 'qualifiedWarehouseId'>) {
   return requestClient.post('/mes/qc/seed-quality/retest', data, { params: { id } });
 }
+
+export function retestSeedQualityBatch(data: {
+  ids: number[];
+  reason?: string;
+  indicators: MesQcSeedQualityApi.Indicator[];
+}) {
+  return requestClient.post('/mes/qc/seed-quality/retest-batch', data);
+}
+export interface SeedRetestScope {
+  code: string;
+  scanType: 'PALLET' | 'BATCH';
+  batchCode?: string;
+  totalCount: number;
+  retestIds: number[];
+  initialRecheckIds: number[];
+  pendingProcessCount: number;
+  pendingCompanyCount: number;
+}
+export function resolveSeedRetestScope(code: string, scanType: 'PALLET' | 'BATCH') {
+  return requestClient.get<SeedRetestScope>('/mes/qc/seed-quality/retest-scope/resolve', { params: { code, scanType } });
+}
+export function submitSeedRetestScope(data: {
+  code: string; scanType: 'PALLET' | 'BATCH'; workflowStatus: 10 | 50;
+  qualityIds: number[]; indicators: MesQcSeedQualityApi.Indicator[]; reason?: string;
+}) {
+  return requestClient.post<{ processedCount: number }>('/mes/qc/seed-quality/retest-scope', data);
+}
 export function approveSeedQuality(stage: 'process' | 'company', data: { id: number; approved: boolean; opinion: string }) {
   return requestClient.post(`/mes/qc/seed-quality/${stage}-approve`, data);
 }
diff --git a/src/views/basicData/mdm/data.ts b/src/views/basicData/mdm/data.ts
index 7604cc1..490572c 100644
--- a/src/views/basicData/mdm/data.ts
+++ b/src/views/basicData/mdm/data.ts
@@ -129,56 +129,6 @@
       },
     },
     {
-      component: "ApiSelect",
-      fieldName: "unitMeasureId2",
-      label: "杈呭崟浣�1",
-      formItemClass: "col-span-1",
-      componentProps: {
-        placeholder: "璇烽�夋嫨杈呭崟浣�1",
-        allowClear: true,
-        api: getUnitListCached,
-        labelField: "name",
-        valueField: "id",
-      },
-    },
-    {
-      component: "InputNumber",
-      fieldName: "unitMeasureRate1",
-      label: "杈呭崟浣�1鎹㈢畻姣旂巼",
-      formItemClass: "col-span-1",
-      componentProps: {
-        class: "!w-full",
-        min: 0,
-        precision: 6,
-        placeholder: "璇疯緭鍏ユ崲绠楁瘮鐜�",
-      },
-    },
-    {
-      component: "ApiSelect",
-      fieldName: "unitMeasureId3",
-      label: "杈呭崟浣�2",
-      formItemClass: "col-span-1",
-      componentProps: {
-        placeholder: "璇烽�夋嫨杈呭崟浣�2",
-        allowClear: true,
-        api: getUnitListCached,
-        labelField: "name",
-        valueField: "id",
-      },
-    },
-    {
-      component: "InputNumber",
-      fieldName: "unitMeasureRate2",
-      label: "杈呭崟浣�2鎹㈢畻姣旂巼",
-      formItemClass: "col-span-1",
-      componentProps: {
-        class: "!w-full",
-        min: 0,
-        precision: 6,
-        placeholder: "璇疯緭鍏ユ崲绠楁瘮鐜�",
-      },
-    },
-    {
       component: 'ApiSelect',
       fieldName: 'warehouseId',
       label: '榛樿浠撳簱',
@@ -205,102 +155,6 @@
       },
     },
     {
-      component: "Input",
-      fieldName: "barCode",
-      label: "鏉$爜",
-      componentProps: {
-        placeholder: "璇疯緭鍏ユ潯鐮�",
-      },
-    },
-    {
-      component: "InputNumber",
-      fieldName: "salesPrice",
-      label: "閿�鍞环",
-      componentProps: {
-        class: "!w-full",
-        min: 0,
-        precision: 2,
-        placeholder: "璇疯緭鍏ラ攢鍞环",
-      },
-    },
-    {
-      component: "InputNumber",
-      fieldName: "costPrice",
-      label: "鎴愭湰浠�",
-      componentProps: {
-        class: "!w-full",
-        min: 0,
-        precision: 2,
-        placeholder: "璇疯緭鍏ユ垚鏈环",
-      },
-    },
-    {
-      component: "InputNumber",
-      fieldName: "safetyStock",
-      label: "瀹夊叏搴撳瓨",
-      componentProps: {
-        class: "!w-full",
-        min: 0,
-        precision: 2,
-        placeholder: "璇疯緭鍏ュ畨鍏ㄥ簱瀛�",
-      },
-    },
-    {
-      component: "InputNumber",
-      fieldName: "minStock",
-      label: "鏈�浣庡簱瀛�",
-      componentProps: {
-        class: "!w-full",
-        min: 0,
-        precision: 2,
-        placeholder: "璇疯緭鍏ユ渶浣庡簱瀛�",
-      },
-    },
-    {
-      component: "InputNumber",
-      fieldName: "maxStock",
-      label: "鏈�楂樺簱瀛�",
-      componentProps: {
-        class: "!w-full",
-        min: 0,
-        precision: 2,
-        placeholder: "璇疯緭鍏ユ渶楂樺簱瀛�",
-      },
-    },
-    {
-      component: 'Switch',
-      fieldName: 'isBatchManaged',
-      label: '鎵规绠$悊',
-      defaultValue: false,
-      componentProps: {
-        class: "w-auto",
-        checkedChildren: "鏄�",
-        unCheckedChildren: "鍚�",
-      },
-      rules: z.boolean().default(false),
-    },
-    {
-      component: "InputNumber",
-      fieldName: "expiryDay",
-      label: "鏈夋晥鏈�(澶�)",
-      componentProps: {
-        class: "!w-full",
-        min: 0,
-        placeholder: "璇疯緭鍏ユ湁鏁堟湡",
-      },
-    },
-    {
-      component: 'Switch',
-      fieldName: 'syncMes',
-      label: '鍚屾鍒癕ES',
-      defaultValue: true,
-      componentProps: {
-        checkedChildren: '鏄�',
-        unCheckedChildren: '鍚�',
-      },
-      rules: z.boolean().default(true),
-    },
-    {
       component: "RadioGroup",
       fieldName: "status",
       label: "鐘舵��",
@@ -310,136 +164,6 @@
         optionType: "button",
       },
       rules: z.number().default(CommonStatusEnum.ENABLE),
-    },
-    {
-      component: "Textarea",
-      fieldName: "remark",
-      label: "澶囨敞",
-      formItemClass: "col-span-2",
-      componentProps: {
-        placeholder: "璇疯緭鍏ュ娉�",
-        rows: 3,
-      },
-    },
-    // ===== 鎵规灞炴�ч厤缃紙isBatchManaged = true 鏃舵樉绀猴級 =====
-    {
-      component: "Divider",
-      fieldName: "batchConfigDivider",
-      label: "鎵规灞炴�ч厤缃�",
-      formItemClass: "col-span-2",
-      componentProps: {
-        orientation: "left",
-        plain: true,
-      },
-      dependencies: {
-        triggerFields: ["isBatchManaged"],
-        show: (values) => values.isBatchManaged === true,
-      },
-    },
-    {
-      component: "Switch",
-      fieldName: "produceDateFlag",
-      label: "鐢熶骇鏃ユ湡蹇呭~",
-      defaultValue: false,
-      componentProps: {
-        class: "w-auto",
-        checkedChildren: "鏄�",
-        unCheckedChildren: "鍚�",
-      },
-      dependencies: {
-        triggerFields: ["isBatchManaged"],
-        show: (values) => values.isBatchManaged === true,
-      },
-    },
-    {
-      component: "Switch",
-      fieldName: "expireDateFlag",
-      label: "鏈夋晥鏈熷繀濉�",
-      defaultValue: false,
-      componentProps: {
-        class: "w-auto",
-        checkedChildren: "鏄�",
-        unCheckedChildren: "鍚�",
-      },
-      dependencies: {
-        triggerFields: ["isBatchManaged"],
-        show: (values) => values.isBatchManaged === true,
-      },
-    },
-    {
-      component: "Switch",
-      fieldName: "receiptDateFlag",
-      label: "鍏ュ簱鏃ユ湡蹇呭~",
-      defaultValue: false,
-      componentProps: {
-        class: "w-auto",
-        checkedChildren: "鏄�",
-        unCheckedChildren: "鍚�",
-      },
-      dependencies: {
-        triggerFields: ["isBatchManaged"],
-        show: (values) => values.isBatchManaged === true,
-      },
-    },
-    {
-      component: "Switch",
-      fieldName: "vendorFlag",
-      label: "渚涘簲鍟嗗繀濉�",
-      defaultValue: false,
-      componentProps: {
-        class: "w-auto",
-        checkedChildren: "鏄�",
-        unCheckedChildren: "鍚�",
-      },
-      dependencies: {
-        triggerFields: ["isBatchManaged"],
-        show: (values) => values.isBatchManaged === true,
-      },
-    },
-    {
-      component: "Switch",
-      fieldName: "purchaseOrderCodeFlag",
-      label: "閲囪喘璁㈠崟鍙峰繀濉�",
-      defaultValue: false,
-      componentProps: {
-        class: "w-auto",
-        checkedChildren: "鏄�",
-        unCheckedChildren: "鍚�",
-      },
-      dependencies: {
-        triggerFields: ["isBatchManaged"],
-        show: (values) => values.isBatchManaged === true,
-      },
-    },
-    {
-      component: "Switch",
-      fieldName: "lotNumberFlag",
-      label: "鐢熶骇鎵瑰彿蹇呭~",
-      defaultValue: false,
-      componentProps: {
-        class: "w-auto",
-        checkedChildren: "鏄�",
-        unCheckedChildren: "鍚�",
-      },
-      dependencies: {
-        triggerFields: ["isBatchManaged"],
-        show: (values) => values.isBatchManaged === true,
-      },
-    },
-    {
-      component: "Switch",
-      fieldName: "qualityStatusFlag",
-      label: "璐ㄩ噺鐘舵�佸繀濉�",
-      defaultValue: false,
-      componentProps: {
-        class: "w-auto",
-        checkedChildren: "鏄�",
-        unCheckedChildren: "鍚�",
-      },
-      dependencies: {
-        triggerFields: ["isBatchManaged"],
-        show: (values) => values.isBatchManaged === true,
-      },
     },
   ];
 }
diff --git a/src/views/basicData/mdm/modules/form.vue b/src/views/basicData/mdm/modules/form.vue
index 00191f6..12fff0f 100644
--- a/src/views/basicData/mdm/modules/form.vue
+++ b/src/views/basicData/mdm/modules/form.vue
@@ -38,7 +38,12 @@
       return;
     }
     modalApi.lock();
-    const data = (await formApi.getValues()) as MdmItemApi.Item;
+    // 缂栬緫鏃朵繚鐣欏垪琛ㄦ湭灞曠ず鐨勫巻鍙插瓧娈碉紝閬垮厤淇濆瓨绮剧畝琛ㄥ崟鏃舵竻绌哄畠浠��
+    const data = {
+      ...formData.value,
+      ...(await formApi.getValues()),
+      syncMes: formData.value?.syncedMes ?? true,
+    } as MdmItemApi.Item;
     try {
       await (formData.value?.id ? updateItem(data) : createItem(data));
       await modalApi.close();
@@ -63,8 +68,6 @@
       modalApi.lock();
       try {
         formData.value = await getItem(data.id);
-        // 鍚屾鐘舵�佹槧灏勶細璇︽儏鎺ュ彛杩斿洖 syncedMes锛岃〃鍗曞瓧娈典负 syncMes
-        formData.value.syncMes = formData.value.syncedMes ?? true;
         await formApi.setValues(formData.value);
       } finally {
         modalApi.unlock();
diff --git a/src/views/mes/pro/batch/data.ts b/src/views/mes/pro/batch/data.ts
index 408d018..579f7ba 100644
--- a/src/views/mes/pro/batch/data.ts
+++ b/src/views/mes/pro/batch/data.ts
@@ -23,11 +23,16 @@
   itemCode?: string;
 }
 
+export type TonBagOption = PalletOption;
+
 /** 鏂板/淇敼鐨勮〃鍗� */
 export function useFormSchema(
   formType: FormType,
   palletOptions: PalletOption[] = [],
   onPalletChange?: (ids: number[]) => void,
+  tonBagOptions: TonBagOption[] = [],
+  onTonBagChange?: (ids: number[]) => void,
+  existingBinding?: 'pallet' | 'tonBag',
 ): VbenFormSchema[] {
   const disabled = formType === 'detail';
   return [
@@ -141,11 +146,11 @@
     },
     {
       fieldName: 'itemName', label: '鍝佺鍚嶇О', component: 'Input',
-      componentProps: { disabled: true, placeholder: '閫夋嫨鎵樼洏鍚庤嚜鍔ㄥ甫鍏�' },
+      componentProps: { disabled: true, placeholder: '閫夋嫨鎵樼洏鎴栧惃琚嬪悗鑷姩甯﹀叆' },
     },
     {
       fieldName: 'itemCode', label: '鍝佺缂栫爜', component: 'Input',
-      componentProps: { disabled: true, placeholder: '閫夋嫨鎵樼洏鍚庤嚜鍔ㄥ甫鍏�' },
+      componentProps: { disabled: true, placeholder: '閫夋嫨鎵樼洏鎴栧惃琚嬪悗鑷姩甯﹀叆' },
     },
     {
       fieldName: 'palletIds',
@@ -165,17 +170,47 @@
         showSearch: true,
       },
       dependencies: {
-        triggerFields: ['itemId'],
+        triggerFields: ['itemId', 'tonBagIds'],
         show: () => formType !== 'detail',
         componentProps: (values) => ({
           options: palletOptions.map(option => ({ ...option,
-            disabled: !option.itemId || (!!values.itemId && option.itemId !== values.itemId),
+            disabled: existingBinding === 'tonBag' || !!values.tonBagIds?.length || !option.itemId || (!!values.itemId && option.itemId !== values.itemId),
           })),
+          disabled: disabled || existingBinding === 'tonBag' || !!values.tonBagIds?.length,
         }),
       },
       help: '鍙兘缁戝畾鐩稿悓鍝佺鐨勬墭鐩橈紝鍝佺淇℃伅鑷姩甯﹀叆锛涚己灏戝搧绉嶇殑鎵樼洏璇峰厛瑁呰銆傜粦瀹氬悗鎵规鐩存帴杩涘叆鐢熶骇涓細鎵樼洏鐢熸垚姝e紡鎵樼洏鐮侊紝鎵樼洏涓嬫湭褰曟壒娆$殑琚嬬爜鑷姩绾ц仈褰曞叆鏈壒娆★紙琚嬬爜鍝佺椤讳竴鑷达級锛涚紪杈戞椂浠呴渶鍕鹃�夋柊澧炴墭鐩橈紝宸茬粦瀹氱殑涓嶅彲瑙g粦',
     },
     {
+      fieldName: 'tonBagIds',
+      label: '缁戝畾鍚ㄨ',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        disabled,
+        maxTagCount: 3,
+        dropdownMatchSelectWidth: false,
+        dropdownStyle: { minWidth: '380px', maxWidth: 'calc(100vw - 32px)' },
+        mode: 'multiple',
+        optionFilterProp: 'label',
+        options: tonBagOptions,
+        onChange: onTonBagChange,
+        placeholder: '璇烽�夋嫨鐩稿悓鍝佺鐨勫惃琚�',
+        showSearch: true,
+      },
+      dependencies: {
+        triggerFields: ['itemId', 'palletIds'],
+        show: () => formType !== 'detail',
+        componentProps: (values) => ({
+          options: tonBagOptions.map(option => ({ ...option,
+            disabled: existingBinding === 'pallet' || !!values.palletIds?.length || (!!values.itemId && option.itemId !== values.itemId),
+          })),
+          disabled: disabled || existingBinding === 'pallet' || !!values.palletIds?.length,
+        }),
+      },
+      help: '浠呭彲缁戝畾璁¢噺鍗曚綅涓哄惃銆佹棤鎵樼洏涓旀湭褰曞叆鎵规鐨勮鐮侊紱涓庣粦瀹氭墭鐩樹簰鏂ャ�傜紪杈戞椂浠呴�夋嫨鏂板鍚ㄨ锛屽凡缁戝畾鐨勪笉鍙В缁�',
+    },
+    {
       fieldName: 'remark',
       label: '澶囨敞',
       component: 'Textarea',
diff --git a/src/views/mes/pro/batch/modules/form.vue b/src/views/mes/pro/batch/modules/form.vue
index b909f02..f27d9bb 100644
--- a/src/views/mes/pro/batch/modules/form.vue
+++ b/src/views/mes/pro/batch/modules/form.vue
@@ -1,5 +1,5 @@
 <script lang="ts" setup>
-  import type { FormType, PalletOption } from "../data";
+  import type { FormType, PalletOption, TonBagOption } from "../data";
 
   import type { VxeTableGridOptions } from "#/adapter/vxe-table";
   import type { MesProBatchApi } from "#/api/mes/pro/batch";
@@ -22,6 +22,7 @@
     updateBatch,
   } from "#/api/mes/pro/batch";
   import { getUnboundPalletList } from "#/api/mes/pro/pallet";
+  import { getUnboundTonBags } from "#/api/mes/pro/bagCode";
   import { $t } from "#/locales";
   import { UserSelect } from "#/views/system/user/components";
 
@@ -38,12 +39,37 @@
   const formType = ref<FormType>("create");
   const formData = ref<MesProBatchApi.Batch>();
   let palletOptions: PalletOption[] = [];
+  let tonBagOptions: TonBagOption[] = [];
   async function handlePalletChange(ids: number[] = []) {
+    const values = await formApi.getValues();
+    if (ids.length && (values.tonBagIds?.length || formData.value?.bindingMode === 'tonBag')) {
+      message.warning('缁戝畾鎵樼洏鍜岀粦瀹氬惃琚嬪彧鑳介�夋嫨涓�绉�');
+      await formApi.setFieldValue('palletIds', []);
+      return;
+    }
     const selected = palletOptions.filter(option => ids.includes(option.value));
     const source = formData.value?.itemId ? formData.value : selected[0];
     if (selected.some(option => !option.itemId || (source?.itemId && option.itemId !== source.itemId))) {
       message.warning('鐢熶骇鎵规鍙兘缁戝畾鐩稿悓鍝佺鐨勬墭鐩�');
       await formApi.setFieldValue('palletIds', selected.filter(option => option.itemId === source?.itemId).map(option => option.value));
+    }
+    await formApi.setFieldValue('itemId', source?.itemId);
+    await formApi.setFieldValue('itemName', source?.itemName);
+    await formApi.setFieldValue('itemCode', source?.itemCode);
+  }
+
+  async function handleTonBagChange(ids: number[] = []) {
+    const values = await formApi.getValues();
+    if (ids.length && (values.palletIds?.length || formData.value?.bindingMode === 'pallet')) {
+      message.warning('缁戝畾鎵樼洏鍜岀粦瀹氬惃琚嬪彧鑳介�夋嫨涓�绉�');
+      await formApi.setFieldValue('tonBagIds', []);
+      return;
+    }
+    const selected = tonBagOptions.filter(option => ids.includes(option.value));
+    const source = formData.value?.itemId ? formData.value : selected[0];
+    if (selected.some(option => option.itemId !== source?.itemId)) {
+      message.warning('鐢熶骇鎵规鍙兘缁戝畾鐩稿悓鍝佺鐨勫惃琚�');
+      await formApi.setFieldValue('tonBagIds', selected.filter(option => option.itemId === source?.itemId).map(option => option.value));
     }
     await formApi.setFieldValue('itemId', source?.itemId);
     await formApi.setFieldValue('itemName', source?.itemName);
@@ -198,6 +224,10 @@
       modalApi.lock();
       try {
         const data = (await formApi.getValues()) as MesProBatchApi.Batch;
+        if (data.palletIds?.length && data.tonBagIds?.length) {
+          message.warning('缁戝畾鎵樼洏鍜岀粦瀹氬惃琚嬪彧鑳介�夋嫨涓�绉�');
+          return;
+        }
         if (formType.value === "fill") {
           await fillBatchInfo({ ...data, id: formData.value!.id, workers });
         } else if (formData.value?.id) {
@@ -221,18 +251,25 @@
       formType.value = data.formType;
       // 鏂板/琛ュ綍/缂栬緫鎬佸姞杞芥湭缁戝畾鎵规鐨勬墭鐩橈紝渚涖�岀粦瀹氭墭鐩樸�嶅閫変笅鎷変娇鐢�
       palletOptions = [];
+      tonBagOptions = [];
       if (formType.value !== "detail") {
-        const pallets = await getUnboundPalletList();
+        const [pallets, tonBags] = await Promise.all([getUnboundPalletList(), getUnboundTonBags()]);
         palletOptions = pallets.map((pallet) => ({
           label: `${pallet.tempCode ?? `鎵樼洏${pallet.id}`} 路 ${pallet.itemName || "寰呭畬鍠勫搧绉�"}锛堝凡瑁� ${pallet.bagCount ?? 0} 琚嬶級`,
           value: pallet.id as number,
           itemId: pallet.itemId, itemName: pallet.itemName, itemCode: pallet.itemCode,
         }));
+        tonBagOptions = tonBags.map((bag) => ({
+          label: `${bag.code ?? `鍚ㄨ${bag.id}`} 路 ${bag.itemName || '寰呭畬鍠勫搧绉�'}`,
+          value: bag.id as number,
+          itemId: bag.itemId, itemName: bag.itemName, itemCode: bag.itemCode,
+        }));
       }
-      formApi.setState({ schema: useFormSchema(formType.value, palletOptions, handlePalletChange) });
+      formApi.setState({ schema: useFormSchema(formType.value, palletOptions, handlePalletChange, tonBagOptions, handleTonBagChange) });
       if (formType.value !== "detail") {
         // 娓呯┖涓婃鎵撳紑鏃跺嬀閫夌殑鎵樼洏锛岄伩鍏嶆畫鐣欏�艰瑙﹀彂銆屾壒娆¤繘鍏ョ敓浜т腑銆�
         await formApi.setFieldValue("palletIds", undefined);
+        await formApi.setFieldValue("tonBagIds", undefined);
       }
       // 鏍规嵁琛ㄥ崟鎬佸垏鎹汉鍛樺瓙琛ㄥ垪
       workerGridApi.setGridOptions({
@@ -244,6 +281,8 @@
         modalApi.lock();
         try {
           formData.value = await getBatch(data.id);
+          const existingBinding = formData.value.bindingMode;
+          formApi.setState({ schema: useFormSchema(formType.value, palletOptions, handlePalletChange, tonBagOptions, handleTonBagChange, existingBinding) });
           await formApi.setValues(formData.value);
           const workers = (formData.value.workers ?? []).map(
             (worker, index) => ({ ...worker, _rowKey: worker.id ?? index })
diff --git a/src/views/mes/qc/approverConfig/data.ts b/src/views/mes/qc/approverConfig/data.ts
new file mode 100644
index 0000000..be42021
--- /dev/null
+++ b/src/views/mes/qc/approverConfig/data.ts
@@ -0,0 +1,199 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesQcApproverConfigApi } from '#/api/mes/qc/approverConfig';
+
+import { DICT_TYPE } from '@vben/constants';
+import { getDictOptions } from '@vben/hooks';
+
+import { getSimpleUserList } from '#/api/system/user';
+
+/** 璐ㄦ绫诲瀷 */
+export const QC_TYPE_OPTIONS = [{ label: '绉嶅瓙璐ㄦ', value: 1 }];
+
+/** 瀹℃牳鑺傜偣 */
+export const APPROVE_NODE_OPTIONS = [
+  { label: '鍒濇瀹℃牳', value: 1 },
+  { label: '澶嶆瀹℃牳', value: 3 },
+];
+
+/** 鎸夎川妫�绫诲瀷杩斿洖鍙�夌殑瀹℃牳鑺傜偣 */
+export function getNodeOptions(_qcType?: number) {
+  return APPROVE_NODE_OPTIONS;
+}
+
+export function getQcTypeLabel(qcType?: number): string {
+  return QC_TYPE_OPTIONS.find((item) => item.value === qcType)?.label || '鈥�';
+}
+
+export function getApproveNodeLabel(approveNode?: number): string {
+  return (
+    APPROVE_NODE_OPTIONS.find((item) => item.value === approveNode)?.label ||
+    '鈥�'
+  );
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'qcType',
+      label: '璐ㄦ绫诲瀷',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: QC_TYPE_OPTIONS,
+        placeholder: '璇烽�夋嫨璐ㄦ绫诲瀷',
+        onChange: async () => {
+          await formApi?.setFieldValue('approveNode', undefined);
+        },
+      },
+      defaultValue: 1,
+      rules: 'required',
+    },
+    {
+      fieldName: 'approveNode',
+      label: '瀹℃牳鑺傜偣',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: APPROVE_NODE_OPTIONS,
+        placeholder: '璇烽�夋嫨瀹℃牳鑺傜偣',
+      },
+      dependencies: {
+        triggerFields: ['qcType'],
+        componentProps: (values) => ({
+          allowClear: true,
+          options: getNodeOptions(values.qcType),
+          placeholder: '璇烽�夋嫨瀹℃牳鑺傜偣',
+        }),
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'userId',
+      label: '瀹℃牳浜�',
+      component: 'ApiSelect',
+      componentProps: {
+        allowClear: true,
+        api: getSimpleUserList,
+        labelField: 'nickname',
+        placeholder: '璇烽�夋嫨瀹℃牳浜�',
+        valueField: 'id',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'status',
+      label: '鐘舵��',
+      component: 'Select',
+      componentProps: {
+        options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
+        placeholder: '璇烽�夋嫨鐘舵��',
+      },
+      defaultValue: 0,
+      rules: 'required',
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'qcType',
+      label: '璐ㄦ绫诲瀷',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: QC_TYPE_OPTIONS,
+        placeholder: '璇烽�夋嫨璐ㄦ绫诲瀷',
+      },
+    },
+    {
+      fieldName: 'approveNode',
+      label: '瀹℃牳鑺傜偣',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: APPROVE_NODE_OPTIONS,
+        placeholder: '璇烽�夋嫨瀹℃牳鑺傜偣',
+      },
+    },
+    {
+      fieldName: 'status',
+      label: '鐘舵��',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
+        placeholder: '璇烽�夋嫨鐘舵��',
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesQcApproverConfigApi.ApproverConfig>['columns'] {
+  return [
+    {
+      field: 'qcType',
+      title: '璐ㄦ绫诲瀷',
+      width: 140,
+      slots: { default: 'qcType' },
+    },
+    {
+      field: 'approveNode',
+      title: '瀹℃牳鑺傜偣',
+      width: 160,
+      slots: { default: 'approveNode' },
+    },
+    {
+      field: 'userNickname',
+      title: '瀹℃牳浜�',
+      width: 140,
+    },
+    {
+      field: 'status',
+      title: '鐘舵��',
+      width: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.COMMON_STATUS },
+      },
+    },
+    {
+      field: 'remark',
+      title: '澶囨敞',
+      minWidth: 180,
+    },
+    {
+      field: 'createTime',
+      title: '鍒涘缓鏃堕棿',
+      width: 180,
+      formatter: 'formatDateTime',
+    },
+    {
+      title: '鎿嶄綔',
+      width: 160,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
diff --git a/src/views/mes/qc/approverConfig/index.vue b/src/views/mes/qc/approverConfig/index.vue
new file mode 100644
index 0000000..feabdcb
--- /dev/null
+++ b/src/views/mes/qc/approverConfig/index.vue
@@ -0,0 +1,141 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesQcApproverConfigApi } from '#/api/mes/qc/approverConfig';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  deleteApproverConfig,
+  getApproverConfigPage,
+} from '#/api/mes/qc/approverConfig';
+import { $t } from '#/locales';
+
+import {
+  getApproveNodeLabel,
+  getQcTypeLabel,
+  useGridColumns,
+  useGridFormSchema,
+} from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓瀹℃牳浜洪厤缃� */
+function handleCreate() {
+  formModalApi.setData(null).open();
+}
+
+/** 缂栬緫瀹℃牳浜洪厤缃� */
+function handleEdit(row: MesQcApproverConfigApi.ApproverConfig) {
+  formModalApi.setData(row).open();
+}
+
+/** 鍒犻櫎瀹℃牳浜洪厤缃� */
+async function handleDelete(row: MesQcApproverConfigApi.ApproverConfig) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.userNickname || '']),
+    duration: 0,
+  });
+  try {
+    await deleteApproverConfig(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.userNickname || '']));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getApproverConfigPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesQcApproverConfigApi.ApproverConfig>,
+});
+</script>
+
+<template>
+  <Page auto-content-height><FormModal @success="handleRefresh" />
+
+    <Grid table-title="璐ㄦ瀹℃牳浜洪厤缃�">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['瀹℃牳浜洪厤缃�']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:qc-approver:create'],
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
+      <template #qcType="{ row }">
+        <span>{{ getQcTypeLabel(row.qcType) }}</span>
+      </template>
+      <template #approveNode="{ row }">
+        <span>{{ getApproveNodeLabel(row.approveNode) }}</span>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:qc-approver:update'],
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mes:qc-approver:delete'],
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [
+                  row.userNickname || '',
+                ]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/mes/qc/approverConfig/modules/form.vue b/src/views/mes/qc/approverConfig/modules/form.vue
new file mode 100644
index 0000000..36a6b7f
--- /dev/null
+++ b/src/views/mes/qc/approverConfig/modules/form.vue
@@ -0,0 +1,92 @@
+<script lang="ts" setup>
+import type { MesQcApproverConfigApi } from '#/api/mes/qc/approverConfig';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createApproverConfig,
+  getApproverConfig,
+  updateApproverConfig,
+} from '#/api/mes/qc/approverConfig';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesQcApproverConfigApi.ApproverConfig>();
+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 MesQcApproverConfigApi.ApproverConfig;
+    try {
+      await (formData.value?.id
+        ? updateApproverConfig(data)
+        : createApproverConfig(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<MesQcApproverConfigApi.ApproverConfig>();
+    if (!data || !data.id) {
+      await formApi.resetForm();
+      await formApi.setValues({ qcType: 1, status: 0 });
+      return;
+    }
+    modalApi.lock();
+    try {
+      formData.value = await getApproverConfig(data.id);
+      // 璁剧疆鍒� values
+      await formApi.setValues(formData.value);
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-2/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/mes/qc/oqc/index.vue b/src/views/mes/qc/oqc/index.vue
index 92d4cea..965cfda 100644
--- a/src/views/mes/qc/oqc/index.vue
+++ b/src/views/mes/qc/oqc/index.vue
@@ -1,95 +1,19 @@
 <script lang="ts" setup>
 import type { VxeTableGridOptions } from '#/adapter/vxe-table';
 import type { MesQcOqcApi } from '#/api/mes/qc/oqc';
-import type { OqcAction } from '#/api/mes/qc/oqc';
-
-import { ref } from 'vue';
-import { useAccess } from '@vben/access';
 
 import { Page, useVbenModal } from '@vben/common-ui';
-import { MesBizTypeEnum, MesQcStatusEnum } from '@vben/constants';
+import { MesQcStatusEnum } from '@vben/constants';
 import { downloadFileFromBlobPart } from '@vben/utils';
 
 import { Button, message } from 'ant-design-vue';
 
 import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { approveOqc, deleteOqc, exportOqc, getOqcActions, getOqcPage, retestOqc } from '#/api/mes/qc/oqc';
+import { deleteOqc, exportOqc, getOqcPage } from '#/api/mes/qc/oqc';
 import { $t } from '#/locales';
 
 import { useGridColumns, useGridFormSchema } from './data';
 import Form from './modules/form.vue';
-
-const props = withDefaults(defineProps<{ salesMode?: boolean }>(), { salesMode: false });
-const { hasAccessByCodes } = useAccess();
-const workflowLabels: Record<number, string> = {
-  0: '鑽夌', 10: '寰呭妫�', 20: '寰呴攢鍞儴瀹℃壒', 30: '寰呭叕鍙歌川妫�瀹℃壒',
-  40: '宸叉斁琛�', 50: '寰呴攢鍞儴涓嶅悎鏍艰瘎瀹�', 60: '寰呭叕鍙镐笉鍚堟牸瀹℃壒', 70: '宸叉嫆缁濆嚭璐�',
-};
-const active = ref<MesQcOqcApi.Oqc>();
-const retestOpen = ref(false);
-const retestBusy = ref(false);
-const retestForm = ref({ checkQuantity: 0, qualifiedQuantity: 0, unqualifiedQuantity: 0, opinion: '' });
-function openRetest(row: MesQcOqcApi.Oqc) {
-  active.value = row;
-  retestForm.value = {
-    checkQuantity: Number(row.checkQuantity ?? 0), qualifiedQuantity: Number(row.qualifiedQuantity ?? 0),
-    unqualifiedQuantity: Number(row.unqualifiedQuantity ?? 0), opinion: '',
-  };
-  retestOpen.value = true;
-}
-async function saveRetest() {
-  if (!active.value?.id) return;
-  const form = retestForm.value;
-  if (form.checkQuantity < 0 || form.qualifiedQuantity < 0 || form.unqualifiedQuantity < 0
-    || form.checkQuantity !== form.qualifiedQuantity + form.unqualifiedQuantity) {
-    message.warning('妫�娴嬫暟閲忓繀椤荤瓑浜庡悎鏍间笌涓嶅悎鏍兼暟閲忎箣鍜�');
-    return;
-  }
-  if (form.unqualifiedQuantity > 0 && !form.opinion.trim()) {
-    message.warning('澶嶆涓嶅悎鏍艰濉啓鍘熷洜');
-    return;
-  }
-  retestBusy.value = true;
-  try {
-    await retestOqc({ id: active.value.id, ...form, checkResult: form.unqualifiedQuantity === 0 ? 1 : 2 });
-    message.success('澶嶆缁撴灉宸叉彁浜わ紝寰呴攢鍞儴璇勫');
-    retestOpen.value = false;
-    handleRefresh();
-  } catch (error) {
-    message.error((error as Error).message || '澶嶆澶辫触');
-  } finally { retestBusy.value = false; }
-}
-const reviewOpen = ref(false);
-const reviewBusy = ref(false);
-const reviewStage = ref<'sales' | 'company'>('sales');
-const reviewApproved = ref(true);
-const reviewOpinion = ref('');
-function openReview(row: MesQcOqcApi.Oqc, stage: 'sales' | 'company') {
-  active.value = row; reviewStage.value = stage; reviewApproved.value = true;
-  reviewOpinion.value = ''; reviewOpen.value = true;
-}
-async function saveReview() {
-  if (!active.value?.id) return;
-  if (!reviewOpinion.value.trim()) { message.warning('璇峰~鍐欏鎵规剰瑙�'); return; }
-  reviewBusy.value = true;
-  try {
-    await approveOqc(reviewStage.value, { id: active.value.id, approved: reviewApproved.value, opinion: reviewOpinion.value.trim() });
-    message.success('瀹℃壒瀹屾垚'); reviewOpen.value = false; handleRefresh();
-  } catch (error) { message.error((error as Error).message || '瀹℃壒澶辫触'); }
-  finally { reviewBusy.value = false; }
-}
-const historyOpen = ref(false);
-const historyActions = ref<OqcAction[]>([]);
-const actionLabels: Record<string, string> = {
-  INITIAL: '鍒濇', RETEST: '澶嶆', SALES_APPROVE: '閿�鍞儴閫氳繃', SALES_REJECT: '閿�鍞儴椹冲洖',
-  COMPANY_APPROVE: '鍏徃璐ㄦ閫氳繃', COMPANY_REJECT: '鍏徃璐ㄦ椹冲洖',
-};
-async function openHistory(row: MesQcOqcApi.Oqc) {
-  if (!row.id) return;
-  active.value = row;
-  try { historyActions.value = await getOqcActions(row.id); historyOpen.value = true; }
-  catch (error) { message.error((error as Error).message || '璇诲彇娴佺▼璁板綍澶辫触'); }
-}
 
 const [FormModal, formModalApi] = useVbenModal({
   connectedComponent: Form,
@@ -134,23 +58,15 @@
 /** 瀵煎嚭琛ㄦ牸 */
 async function handleExport() {
   const data = await exportOqc(await gridApi.formApi.getValues());
-  downloadFileFromBlobPart({ fileName: props.salesMode ? '閿�鍞川妫�鍗�.xls' : '鍑鸿揣妫�楠屽崟.xls', source: data });
+  downloadFileFromBlobPart({ fileName: '鍑鸿揣妫�楠屽崟.xls', source: data });
 }
 
-const columns = useGridColumns();
-if (props.salesMode && columns) {
-  columns.splice(columns.length - 1, 0, {
-    field: 'workflowStatus', title: '閿�鍞川妫�娴佺▼', width: 170, slots: { default: 'workflow' },
-  });
-  const actions = columns.at(-1);
-  if (actions) actions.width = 320;
-}
 const [Grid, gridApi] = useVbenVxeGrid({
   formOptions: {
     schema: useGridFormSchema(),
   },
   gridOptions: {
-    columns,
+    columns: useGridColumns(),
     height: 'auto',
     keepSource: true,
     proxyConfig: {
@@ -160,7 +76,6 @@
             pageNo: page.currentPage,
             pageSize: page.pageSize,
             ...formValues,
-            sourceDocType: props.salesMode ? MesBizTypeEnum.WM_PRODUCT_SALES : undefined,
           });
         },
       },
@@ -180,7 +95,7 @@
 <template>
   <Page auto-content-height><FormModal @success="handleRefresh" />
 
-    <Grid :table-title="props.salesMode ? '閿�鍞川妫�鍗曞垪琛�' : '鍑鸿揣妫�楠屽崟鍒楄〃'">
+    <Grid table-title="鍑鸿揣妫�楠屽崟鍒楄〃">
       <template #toolbar-tools>
         <TableAction
           :actions="[
@@ -205,9 +120,6 @@
         <Button type="link" @click="handleDetail(row)">
           {{ row.code }}
         </Button>
-      </template>
-      <template #workflow="{ row }">
-        <span>{{ workflowLabels[row.workflowStatus ?? 0] || '鏈煡鐘舵��' }}</span>
       </template>
       <template #actions="{ row }">
         <TableAction
@@ -241,38 +153,7 @@
             },
           ]"
         />
-        <Button v-if="props.salesMode && row.workflowStatus === 10 && hasAccessByCodes(['mes:qc-oqc:retest'])" type="link" @click="openRetest(row)">澶嶆</Button>
-        <Button v-if="props.salesMode && [20, 50].includes(row.workflowStatus ?? 0) && hasAccessByCodes(['mes:qc-oqc:sales-approve'])" type="link" @click="openReview(row, 'sales')">閿�鍞儴瀹℃壒</Button>
-        <Button v-if="props.salesMode && [30, 60].includes(row.workflowStatus ?? 0) && hasAccessByCodes(['mes:qc-oqc:company-approve'])" type="link" @click="openReview(row, 'company')">鍏徃璐ㄦ瀹℃壒</Button>
-        <Button v-if="props.salesMode && hasAccessByCodes(['mes:qc-oqc:query'])" type="link" @click="openHistory(row)">娴佺▼璁板綍</Button>
       </template>
     </Grid>
-    <a-modal v-if="props.salesMode" v-model:open="retestOpen" title="閿�鍞川妫�澶嶆" :confirm-loading="retestBusy" ok-text="鎻愪氦澶嶆" @ok="saveRetest">
-      <p>妫�楠屽崟锛歿{ active?.code }}</p>
-      <a-form layout="vertical">
-        <a-form-item label="鏈妫�娴嬫暟閲�"><a-input-number v-model:value="retestForm.checkQuantity" :min="0" style="width: 100%" /></a-form-item>
-        <a-form-item label="鍚堟牸鏁伴噺"><a-input-number v-model:value="retestForm.qualifiedQuantity" :min="0" style="width: 100%" /></a-form-item>
-        <a-form-item label="涓嶅悎鏍兼暟閲�"><a-input-number v-model:value="retestForm.unqualifiedQuantity" :min="0" style="width: 100%" /></a-form-item>
-        <a-form-item label="澶嶆鎰忚"><a-textarea v-model:value="retestForm.opinion" :rows="3" :maxlength="500" /></a-form-item>
-      </a-form>
-    </a-modal>
-    <a-modal v-if="props.salesMode" v-model:open="reviewOpen" :title="reviewStage === 'sales' ? '閿�鍞儴瀹℃壒' : '鍏徃璐ㄦ瀹℃壒'" :confirm-loading="reviewBusy" ok-text="鎻愪氦瀹℃壒" @ok="saveReview">
-      <p>{{ active?.code }} 路 {{ workflowLabels[active?.workflowStatus ?? 0] }}</p>
-      <a-radio-group v-model:value="reviewApproved" style="display: flex; gap: 16px; margin: 12px 0 18px">
-        <a-radio :value="true">鍚屾剰{{ reviewStage === 'company' ? active?.workflowStatus === 30 ? '鍑鸿揣鏀捐' : '涓嶅悎鏍煎缃�' : '涓婃姤鍏徃璐ㄦ' }}</a-radio>
-        <a-radio :value="false">椹冲洖閲嶆</a-radio>
-      </a-radio-group>
-      <a-textarea v-model:value="reviewOpinion" :rows="3" :maxlength="500" placeholder="璇峰~鍐欏鎵规剰瑙�" />
-    </a-modal>
-    <a-modal v-if="props.salesMode" v-model:open="historyOpen" :title="`娴佺▼璁板綍 路 ${active?.code || ''}`" :footer="null">
-      <a-timeline v-if="historyActions.length">
-        <a-timeline-item v-for="action in historyActions" :key="action.id">
-          <strong>{{ actionLabels[action.action] || action.action }}</strong>
-          <span> 路 {{ action.createTime || '鈥�' }} 路 鎿嶄綔浜� {{ action.operatorId || '鈥�' }}</span>
-          <div v-if="action.opinion">{{ action.opinion }}</div>
-        </a-timeline-item>
-      </a-timeline>
-      <a-empty v-else description="鏆傛棤娴佺▼璁板綍" />
-    </a-modal>
   </Page>
 </template>
diff --git a/src/views/mes/qc/oqc/modules/form.vue b/src/views/mes/qc/oqc/modules/form.vue
index 6dcd7cc..2207b24 100644
--- a/src/views/mes/qc/oqc/modules/form.vue
+++ b/src/views/mes/qc/oqc/modules/form.vue
@@ -105,7 +105,7 @@
     return;
   }
   try {
-    await confirm('鏄惁鎻愪氦鍑鸿揣妫�楠岀粨璁猴紵鎻愪氦鍚庤繘鍏ュ鎵规祦绋嬶紝鍒濇涓嶅悎鏍奸渶鍏堝妫�銆�');
+    await confirm('鏄惁鎻愪氦鍑鸿揣妫�楠岀粨璁猴紵');
   } catch {
     return;
   }
@@ -126,7 +126,7 @@
       originalSnapshot.value = current;
     }
     await finishOqc(id);
-    message.success('妫�楠屽凡鎻愪氦锛岃鍦ㄩ攢鍞川妫�鑿滃崟缁х画澶嶆鎴栧鎵�');
+    message.success('妫�楠屽凡鎻愪氦');
     await modalApi.close();
     emit('success');
   } finally {
diff --git a/src/views/mes/qc/salesQuality/index.vue b/src/views/mes/qc/salesQuality/index.vue
deleted file mode 100644
index 4b8733a..0000000
--- a/src/views/mes/qc/salesQuality/index.vue
+++ /dev/null
@@ -1,20 +0,0 @@
-<script setup lang="ts">
-import OqcIndex from '../oqc/index.vue';
-</script>
-
-<template>
-  <div class="sales-quality-page">
-    <div class="flow-notice">
-      <strong>閿�鍞川妫�</strong>
-      <span>閿�鍞嚭搴撳崟鍒濇銆佷笉鍚堟牸澶嶆銆侀攢鍞儴涓庡叕鍙歌川妫�瀹℃壒锛涘鎵瑰畬鎴愬悗鍥炲啓鍑哄簱璐ㄩ噺鐘舵�併��</span>
-    </div>
-    <OqcIndex sales-mode />
-  </div>
-</template>
-
-<style scoped>
-.sales-quality-page { height: 100%; }
-.flow-notice { display: flex; align-items: center; gap: 12px; padding: 12px 20px; margin: 0 0 8px; border: 1px solid #d6e6fa; border-radius: 8px; background: #f2f7fe; color: #385071; }
-.flow-notice strong { flex: none; color: #1557a0; }
-@media (max-width: 720px) { .flow-notice { align-items: flex-start; flex-direction: column; } }
-</style>
diff --git a/src/views/mes/qc/seedQuality/index.vue b/src/views/mes/qc/seedQuality/index.vue
index a034bc7..e69dfe3 100644
--- a/src/views/mes/qc/seedQuality/index.vue
+++ b/src/views/mes/qc/seedQuality/index.vue
@@ -11,6 +11,7 @@
 import {
   resolveSeedQuality,
   submitSeedQualityGroup,
+  getSeedIndicatorRanges,
 } from '#/api/mes/qc/seedQuality';
 import { getWarehouseSimpleList } from '#/api/mes/wm/warehouse';
 
@@ -59,6 +60,17 @@
   { code: 'germination_rate', label: '鍙戣娊鐜�', unit: '%' },
   { code: 'moisture', label: '姘村垎', unit: '%' },
 ]);
+const rangesReady = computed(() => indicators.value.every((item) =>
+  item.minValue !== undefined && item.maxValue !== undefined));
+
+async function loadRanges() {
+  const ranges = await getSeedIndicatorRanges();
+  for (const item of indicators.value) {
+    const range = ranges.find((row) => row.indicatorCode === item.code);
+    item.minValue = range?.minValue === undefined ? undefined : Number(range.minValue);
+    item.maxValue = range?.maxValue === undefined ? undefined : Number(range.maxValue);
+  }
+}
 
 const pendingCount = computed(() => target.value?.pendingBagIds?.length ?? 0);
 const completedCount = computed(() => target.value?.completedBagIds?.length ?? 0);
@@ -102,8 +114,6 @@
   reason.value = '';
   indicators.value.forEach((item) => {
     item.value = undefined;
-    item.minValue = undefined;
-    item.maxValue = undefined;
   });
 }
 
@@ -132,6 +142,7 @@
   resolving.value = true;
   submittedCount.value = undefined;
   try {
+    await loadRanges().catch(() => undefined);
     const data = await resolveSeedQuality(value, scanType.value);
     if (data.canceled) {
       target.value = undefined;
@@ -166,7 +177,7 @@
     return;
   }
   if (!allEntered.value) {
-    message.warning('璇峰~鍐欏洓椤规寚鏍囩殑瀹炴祴鍊煎強鍚堟牸涓婁笅闄�');
+    message.warning('璇峰~鍐欏洓椤规寚鏍囩殑瀹炴祴鍊煎拰鏈鍚堟牸涓婁笅闄�');
     return;
   }
   if (indicators.value.some((item) => item.minValue === undefined || item.maxValue === undefined || item.minValue > item.maxValue)) {
@@ -190,8 +201,8 @@
     Modal.confirm({
       title: overallPassed.value ? '纭鎻愪氦鍚堟牸缁撴灉锛�' : '纭鎻愪氦涓嶅悎鏍肩粨鏋滐紵',
       content: overallPassed.value
-        ? `鏈 ${count} 琚嬪皢缁熶竴璁板綍涓哄垵妫�鍚堟牸锛屽緟鍔犲伐閮ㄥ拰鍏徃璐ㄦ瀹℃壒鍚庢墠杞叆鍚堟牸搴撱��${scopeNote}`
-        : `鏈 ${count} 琚嬪皢璁板綍涓哄垵妫�涓嶅悎鏍硷紝淇濈暀鏆傚瓨搴撳瓨骞剁瓑寰呭妫�銆�${scopeNote}`,
+        ? `鏈 ${count} 琚嬪皢缁熶竴璁板綍涓哄垵妫�鍚堟牸锛屽緟鍒濇瀹℃牳閫氳繃鍚庡妫�锛涘妫�瀹℃牳閫氳繃鍚庢墠杞叆鍚堟牸搴撱��${scopeNote}`
+        : `鏈 ${count} 琚嬪皢璁板綍涓哄垵妫�涓嶅悎鏍硷紝淇濈暀鏆傚瓨搴撳瓨锛涘垵妫�瀹℃牳鍚庡喅瀹氭槸鍚﹁繘鍏ュ妫�銆�${scopeNote}`,
       okText: '纭鎻愪氦',
       cancelText: '杩斿洖淇敼',
       okButtonProps: { danger: !overallPassed.value },
@@ -219,7 +230,7 @@
       })),
     });
     submittedCount.value = result.processedCount;
-    message.success(`鏈 ${result.processedCount} 琚嬪凡鎻愪氦锛�${passed ? '鍒濇鍚堟牸锛屽緟涓ょ骇瀹℃壒' : '鍒濇涓嶅悎鏍硷紝寰呭妫�'}`);
+    message.success(`鏈 ${result.processedCount} 琚嬪凡鎻愪氦鍒濇锛屽緟鍒濇瀹℃牳`);
     if (pageRoot.value && !window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
       gsap.fromTo('.result-card', { scale: 0.97, autoAlpha: 0.5 }, { scale: 1, autoAlpha: 1, duration: 0.35, ease: 'power2.out' });
     }
@@ -235,6 +246,11 @@
 }
 
 onMounted(async () => {
+  try {
+    await loadRanges();
+  } catch (error) {
+    message.error(errorText(error));
+  }
   try {
     const warehouses = await getWarehouseSimpleList();
     qualifiedWarehouses.value = (warehouses ?? []).filter(
@@ -266,7 +282,7 @@
       <div>
         <div class="eyebrow">MES / 璐ㄩ噺绠$悊</div>
         <h1>绉嶅瓙璐ㄦ</h1>
-        <p>璇嗗埆寰呮鑼冨洿骞跺綍鍏ュ洓椤规寚鏍囷紱鍚堟牸缁忓姞宸ラ儴涓庡叕鍙歌川妫�瀹℃壒鍚庡叆搴擄紝涓嶅悎鏍煎厛澶嶆</p>
+        <p>璇嗗埆寰呮鑼冨洿骞跺綍鍏ュ洓椤规寚鏍囷紱鍒濇瀹℃牳鍚庡妫�锛屽妫�瀹℃牳鍚庡叆搴撴垨澶勭疆</p>
       </div>
       <div class="header-badge"><IconifyIcon icon="ep:checked" /> 琚嬬爜 / 鎵樼洏鐮� / 鎵规鐮�</div>
     </header>
@@ -318,6 +334,7 @@
             </a-form-item>
           </div>
           <p v-if="!qualifiedWarehouses.length" class="target-warning">鏆傛棤鍙敤鍚堟牸搴擄紝璇疯仈绯荤鐞嗗憳閰嶇疆鍚庡啀鎻愪氦璐ㄦ銆�</p>
+          <a-alert v-if="!rangesReady" class="target-empty" type="info" show-icon message="鏈璇锋墜鍔ㄥ~鍐欏悎鏍艰寖鍥�" description="绠$悊鍛樺皻鏈厤缃畬鏁撮粯璁よ寖鍥达紱浠嶅彲鎵嬪姩濉啓鍥涢」鎸囨爣鐨勫悎鏍间笂涓嬮檺銆�" />
 
           <a-divider orientation="left">鎸囨爣缁撴灉</a-divider>
           <div class="indicator-list">
@@ -325,9 +342,9 @@
               <div class="indicator-name"><span class="indicator-dot" :class="isPassed(item) ? 'pass' : 'fail'" /><strong>{{ item.label }}</strong><small>{{ item.unit }}</small></div>
               <a-input-number v-model:value="item.value" class="indicator-input" :disabled="loading" :min="0" :precision="3" placeholder="瀹炴祴鍊�" />
               <span class="range-separator">鑼冨洿</span>
-              <a-input-number v-model:value="item.minValue" class="range-input" :disabled="loading" :min="0" :precision="3" placeholder="涓嬮檺" />
+              <a-input-number v-model:value="item.minValue" class="range-input" :disabled="loading" :min="0" :max="100" :precision="3" placeholder="涓嬮檺" />
               <span>鈥�</span>
-              <a-input-number v-model:value="item.maxValue" class="range-input" :disabled="loading" :min="0" :precision="3" placeholder="涓婇檺" />
+              <a-input-number v-model:value="item.maxValue" class="range-input" :disabled="loading" :min="0" :max="100" :precision="3" placeholder="涓婇檺" />
               <a-tag :color="!allEntered ? 'default' : isPassed(item) ? 'success' : 'error'">{{ !allEntered ? '寰呭綍鍏�' : isPassed(item) ? '鍚堟牸' : '瓒呭嚭鑼冨洿' }}</a-tag>
             </div>
           </div>
@@ -341,7 +358,7 @@
         <template #title><div class="card-title"><IconifyIcon icon="ep:data-analysis" />鍒ゅ畾璇存槑</div></template>
         <div class="result-hero" :class="overallPassed ? 'passed' : allEntered ? 'failed-result' : 'pending-result'"><IconifyIcon :icon="overallPassed ? 'ep:circle-check-filled' : allEntered ? 'ep:warning-filled' : 'ep:edit-pen'" /><strong>{{ overallPassed ? '褰撳墠缁撴灉鍚堟牸' : allEntered ? '褰撳墠瀛樺湪涓嶅悎鏍奸」' : '绛夊緟褰曞叆鎸囨爣' }}</strong><span>{{ allEntered ? '瀹炴祴鍊奸渶澶勪簬鏈�灏忓�间笌鏈�澶у�间箣闂�' : `宸插綍鍏� ${enteredCount} / 4 椤瑰疄娴嬪�糮 }}</span></div>
         <div class="rule-list"><div><IconifyIcon icon="ep:check" /><span>鍏堣瘑鍒爜鍊煎啀褰曞叆鎸囨爣锛屽洓椤规寚鏍囧繀椤诲叏閮ㄦ彁浜�</span></div><div><IconifyIcon icon="ep:check" /><span>杈圭晫鍊兼寜鍚堟牸澶勭悊</span></div><div><IconifyIcon icon="ep:check" /><span>鎵樼洏鐮併�佹壒娆$爜鐨勭粨璁哄簲鐢ㄤ簬鏈鍏ㄩ儴寰呮琚�</span></div><div><IconifyIcon icon="ep:check" /><span>涓嶅悎鏍肩粨鏋滈渶淇濈暀鍘熷洜</span></div><div><IconifyIcon icon="ep:check" /><span>璐ㄦ鍛樹笌妫�楠屾椂闂寸敱鏈嶅姟绔褰�</span></div></div>
-        <a-alert message="瀹℃壒瀹屾垚鍚庢祦杞簱瀛�" description="鍒濇涓嶆敼鍙樺簱瀛橈紱涓嶅悎鏍煎緟澶嶆銆傚妫�缁撹缁忓姞宸ラ儴鍜屽叕鍙歌川妫�瀹℃壒鍚庯紝鍚堟牸琚嬫墠杞叆鍚堟牸搴擄紝涓嶅悎鏍艰鎵嶆姤搴熴��" type="info" show-icon />
+        <a-alert message="澶嶆瀹℃牳瀹屾垚鍚庢祦杞簱瀛�" description="鍒濇鍙婂妫�鏈熼棿琚嬬爜鐣欏湪鏆傚瓨搴撱�傚垵妫�瀹℃牳閫氳繃鍚庤繘鍏ュ妫�锛涘妫�鍚堟牸骞跺鏍搁�氳繃鎵嶈浆鍏ュ悎鏍煎簱锛屽妫�涓嶅悎鏍肩粡纭鍚庢墠鎶ュ簾銆�" type="info" show-icon />
         <div v-if="submittedCount" class="success-note"><IconifyIcon icon="ep:success-filled" /><span>鏈�杩戞彁浜よ川妫�锛�<strong>{{ submittedCount }} 琚�</strong></span></div>
       </a-card>
     </section>
diff --git a/src/views/mes/qc/seedQuality/record.vue b/src/views/mes/qc/seedQuality/record.vue
index a03ccd8..15396a1 100644
--- a/src/views/mes/qc/seedQuality/record.vue
+++ b/src/views/mes/qc/seedQuality/record.vue
@@ -10,12 +10,16 @@
   getSeedQualityPage,
   getSeedQualityIndicators,
   getSeedQualityActions,
+  getSeedIndicatorRanges,
   retestSeedQuality,
+  resolveSeedRetestScope,
+  submitSeedRetestScope,
   approveSeedQuality,
   approveSeedQualityBatch,
   type SeedQualityRecord,
   type SeedQualityIndicator,
   type SeedQualityAction,
+  type SeedRetestScope,
 } from '#/api/mes/qc/seedQuality';
 
 const records = ref<SeedQualityRecord[]>([]);
@@ -37,13 +41,13 @@
 );
 const { hasAccessByCodes } = useAccess();
 const workflowLabels: Record<number, string> = {
-  10: '寰呭妫�', 20: '寰呭姞宸ラ儴瀹℃壒', 30: '寰呭叕鍙歌川妫�瀹℃壒',
-  40: '宸叉斁琛�', 50: '寰呭姞宸ラ儴涓嶅悎鏍艰瘎瀹�', 60: '寰呭叕鍙镐笉鍚堟牸瀹℃壒', 70: '宸叉姤搴�',
+  10: '寰呭妫�', 20: '寰呭垵妫�瀹℃牳', 25: '寰呭垵妫�涓嶅悎鏍煎鐞�', 30: '寰呭妫�瀹℃牳',
+  40: '宸叉斁琛�', 50: '鍒濇椹冲洖寰呴噸妫�', 60: '寰呭妫�涓嶅悎鏍煎鐞�', 70: '宸叉姤搴�',
 };
 const actionLabels: Record<string, string> = {
-  INITIAL: '鍒濇', RETEST: '澶嶆', PROCESS_APPROVE: '鍔犲伐閮ㄩ�氳繃',
-  PROCESS_REJECT: '鍔犲伐閮ㄩ┏鍥�', COMPANY_APPROVE: '鍏徃璐ㄦ閫氳繃',
-  COMPANY_REJECT: '鍏徃璐ㄦ椹冲洖',
+  INITIAL: '鍒濇', INITIAL_RECHECK: '閲嶆柊鍒濇', RETEST: '澶嶆', PROCESS_APPROVE: '鍒濇瀹℃牳閫氳繃',
+  PROCESS_REJECT: '鍒濇瀹℃牳椹冲洖', COMPANY_APPROVE: '澶嶆瀹℃牳閫氳繃',
+  COMPANY_REJECT: '澶嶆瀹℃牳椹冲洖',
 };
 const indicatorLabels: Record<string, string> = {
   purity: '绾害', cleanliness: '鍑�搴�', germination_rate: '鍙戣娊鐜�', moisture: '姘村垎',
@@ -73,6 +77,13 @@
 const retestBusy = ref(false);
 const retestIndicators = ref<SeedQualityIndicator[]>([]);
 const retestReason = ref('');
+const scopeOpen = ref(false);
+const scopeType = ref<'PALLET' | 'BATCH'>('PALLET');
+const scopeCode = ref('');
+const retestScope = ref<SeedRetestScope>();
+const scopeStatus = ref<10 | 50>(10);
+const scopeBusy = ref(false);
+const scopeIds = computed(() => scopeStatus.value === 10 ? retestScope.value?.retestIds || [] : retestScope.value?.initialRecheckIds || []);
 const reviewOpen = ref(false);
 const reviewBusy = ref(false);
 const reviewStage = ref<'process' | 'company'>('process');
@@ -84,7 +95,7 @@
 const selectableStage = computed(() => {
   if (!selectedRows.value.length) return undefined;
   if (!selectedRows.value.every((record) => record.workflowStatus === selectedRows.value[0]?.workflowStatus)) return undefined;
-  if (selectedRows.value.every((record) => [20, 50].includes(record.workflowStatus))) return 'process';
+  if (selectedRows.value.every((record) => [20, 25].includes(record.workflowStatus))) return 'process';
   if (selectedRows.value.every((record) => [30, 60].includes(record.workflowStatus))) return 'company';
   return undefined;
 });
@@ -92,13 +103,65 @@
 async function openRetest(record: SeedQualityRecord) {
   activeRecord.value = record;
   try {
-    retestIndicators.value = await getSeedQualityIndicators(record.id);
+    const previous = await getSeedQualityIndicators(record.id);
+    const ranges = await getSeedIndicatorRanges().catch(() => []);
+    retestIndicators.value = previous.map((item) => {
+      const range = ranges.find((row) => row.indicatorCode === item.indicatorCode);
+      return { ...item, minValue: range ? Number(range.minValue) : Number(item.minValue), maxValue: range ? Number(range.maxValue) : Number(item.maxValue) };
+    });
     if (retestIndicators.value.length !== 4) throw new Error('璐ㄦ鎸囨爣涓嶅畬鏁�');
+    retestIndicators.value = retestIndicators.value.map((item) => ({ ...item, value: undefined }));
     retestReason.value = '';
     retestOpen.value = true;
   } catch (error) {
     message.error((error as Error).message || '璇诲彇璐ㄦ鎸囨爣澶辫触');
   }
+}
+function openScopeRetest() {
+  scopeOpen.value = true; retestScope.value = undefined; scopeCode.value = ''; retestReason.value = '';
+}
+async function identifyScope() {
+  if (!scopeCode.value.trim()) { message.warning('璇锋壂鎻忔垨杈撳叆鎵樼洏鐮� / 鐢熶骇鎵规鐮�'); return; }
+  scopeBusy.value = true; retestScope.value = undefined;
+  try {
+    const data = await resolveSeedRetestScope(scopeCode.value.trim(), scopeType.value);
+    if (!data.retestIds.length && !data.initialRecheckIds.length) {
+      const pending = [
+        data.pendingProcessCount ? `${data.pendingProcessCount} 鏉″緟鍒濇瀹℃牳鎴栦笉鍚堟牸澶勭悊` : '',
+        data.pendingCompanyCount ? `${data.pendingCompanyCount} 鏉″緟澶嶆瀹℃牳鎴栦笉鍚堟牸澶勭悊` : '',
+      ].filter(Boolean).join('銆�');
+      throw new Error(pending
+        ? `褰撳墠鑼冨洿鏈�${pending}锛岃鍏堝畬鎴愬搴斿鏍告垨澶勭悊`
+        : '褰撳墠鑼冨洿娌℃湁寰呭妫�鎴栧緟閲嶆柊鍒濇鐨勮鐮�');
+    }
+    retestScope.value = data; scopeStatus.value = data.retestIds.length ? 10 : 50;
+    const ranges = await getSeedIndicatorRanges().catch(() => []);
+    retestIndicators.value = Object.entries(indicatorLabels).map(([indicatorCode]) => {
+      const range = ranges.find((row) => row.indicatorCode === indicatorCode);
+      return { indicatorCode, value: undefined, minValue: range ? Number(range.minValue) : undefined as unknown as number,
+        maxValue: range ? Number(range.maxValue) : undefined as unknown as number };
+    });
+    retestReason.value = '';
+  } catch (error) { message.error((error as Error).message || '璇嗗埆澶辫触'); }
+  finally { scopeBusy.value = false; }
+}
+async function saveScopeRetest() {
+  if (!retestScope.value || !scopeIds.value.length) return;
+  if (retestIndicators.value.some(item => item.value === undefined || item.value === null || item.minValue === undefined || item.maxValue === undefined
+    || Number(item.minValue) < 0 || Number(item.maxValue) > 100 || Number(item.minValue) > Number(item.maxValue))) {
+    message.warning('璇峰~鍐欏洓椤规寚鏍囩殑瀹炴祴鍊间笌鏈鍚堟牸鑼冨洿'); return;
+  }
+  const passed = retestIndicators.value.every(item => Number(item.value) >= Number(item.minValue) && Number(item.value) <= Number(item.maxValue));
+  if (!passed && !retestReason.value.trim()) { message.warning('涓嶅悎鏍艰濉啓鍘熷洜'); return; }
+  scopeBusy.value = true;
+  try {
+    const result = await submitSeedRetestScope({ code: retestScope.value.code, scanType: retestScope.value.scanType, workflowStatus: scopeStatus.value,
+      qualityIds: scopeIds.value, reason: retestReason.value.trim(), indicators: retestIndicators.value.map(item => ({
+        code: item.indicatorCode, value: Number(item.value), minValue: Number(item.minValue), maxValue: Number(item.maxValue),
+      })) });
+    message.success(`宸插畬鎴� ${result.processedCount} 琚嬭川妫�`); scopeOpen.value = false; refreshAll();
+  } catch (error) { message.error((error as Error).message || '鎵归噺璐ㄦ澶辫触'); retestScope.value = undefined; }
+  finally { scopeBusy.value = false; }
 }
 async function saveRetest() {
   const record = activeRecord.value;
@@ -106,27 +169,30 @@
   if (retestIndicators.value.some((item) => item.value === null || item.value === undefined
     || item.minValue === null || item.minValue === undefined || item.maxValue === null || item.maxValue === undefined
     || Number(item.minValue) > Number(item.maxValue))) {
-    message.warning('璇峰~鍐欏畬鏁寸殑瀹炴祴鍊煎強鏈夋晥涓婁笅闄�');
+    message.warning('璇峰~鍐欏畬鏁寸殑瀹炴祴鍊�');
     return;
   }
   const passed = retestIndicators.value.every((item) =>
     Number(item.value) >= Number(item.minValue) && Number(item.value) <= Number(item.maxValue));
   if (!passed && !retestReason.value.trim()) {
-    message.warning('澶嶆涓嶅悎鏍艰濉啓鍘熷洜');
+    message.warning('妫�楠屼笉鍚堟牸璇峰~鍐欏師鍥�');
     return;
   }
   retestBusy.value = true;
   try {
-    await retestSeedQuality(record.id, {
-      code: record.code,
+    const payload = {
       reason: retestReason.value.trim(),
       indicators: retestIndicators.value.map((item) => ({
         code: item.indicatorCode, value: Number(item.value),
         minValue: Number(item.minValue), maxValue: Number(item.maxValue),
       })),
-    });
-    message.success(passed ? '澶嶆鍚堟牸锛屽緟鍔犲伐閮ㄥ鎵�' : '澶嶆涓嶅悎鏍硷紝寰呭姞宸ラ儴璇勫');
+    };
+    await retestSeedQuality(record.id, { ...payload, code: record.code });
+    message.success(record.workflowStatus === 50
+      ? passed ? '閲嶆柊鍒濇鍚堟牸锛屽緟鍒濇瀹℃牳' : '閲嶆柊鍒濇涓嶅悎鏍硷紝寰呬笉鍚堟牸澶勭悊'
+      : passed ? '澶嶆鍚堟牸锛屽緟澶嶆瀹℃牳' : '澶嶆涓嶅悎鏍硷紝寰呬笉鍚堟牸澶勭悊');
     retestOpen.value = false;
+    selectedIds.value = [];
     refreshAll();
   } catch (error) {
     message.error((error as Error).message || '澶嶆鎻愪氦澶辫触');
@@ -154,8 +220,8 @@
 }
 async function saveReview() {
   if (!reviewIds.value.length) return;
-  if (!reviewOpinion.value.trim()) {
-    message.warning('璇峰~鍐欏鎵规剰瑙�');
+  if (!reviewApproved.value && !reviewOpinion.value.trim()) {
+    message.warning('椹冲洖鏃惰濉啓瀹℃壒鎰忚');
     return;
   }
   reviewBusy.value = true;
@@ -254,7 +320,7 @@
       <div class="header-copy">
         <div class="eyebrow">MES / 璐ㄩ噺绠$悊</div>
         <h1>璐ㄦ璁板綍</h1>
-        <p>璺熻釜鍒濇銆佸妫�銆佸姞宸ラ儴瀹℃壒鍜屽叕鍙歌川妫�瀹℃壒锛涘畬鎴愬鎵瑰悗鎵嶆祦杞簱瀛�</p>
+        <p>鍒濇瀹℃牳閫氳繃鍚庤繘琛屽妫�锛屽妫�瀹℃牳閫氳繃鍚庢墠鏀捐鍏ュ簱鎴栧缃笉鍚堟牸鍝�</p>
       </div>
       <div class="header-badge">
         <span class="badge-pulse" />
@@ -291,7 +357,8 @@
           <span class="count-chip">{{ total.toLocaleString() }} 鏉�</span>
         </div>
         <div class="filters">
-          <a-button v-if="selectableStage && hasAccessByCodes([`mes:qc-seed-quality:${selectableStage === 'process' ? 'process-approve' : 'company-approve'}`])" @click="openBatchReview">鎵归噺{{ selectableStage === 'process' ? '鍔犲伐閮ㄥ鎵�' : '鍏徃璐ㄦ瀹℃壒' }}锛坽{ selectedRows.length }}锛�</a-button>
+          <a-button v-if="hasAccessByCodes(['mes:qc-seed-quality:retest'])" type="primary" @click="openScopeRetest">鎸夋墭鐩� / 鎵规鎵归噺澶嶆</a-button>
+          <a-button v-if="selectableStage && hasAccessByCodes([`mes:qc-seed-quality:${selectableStage === 'process' ? 'process-approve' : 'company-approve'}`])" @click="openBatchReview">鎵归噺{{ selectableStage === 'process' ? selectedRows[0]?.workflowStatus === 25 ? '鍒濇涓嶅悎鏍煎鐞�' : '鍒濇瀹℃牳' : selectedRows[0]?.workflowStatus === 60 ? '澶嶆涓嶅悎鏍煎鐞�' : '澶嶆瀹℃牳' }}锛坽{ selectedRows.length }}锛�</a-button>
           <a-input v-model:value="code" allow-clear placeholder="琚嬬爜" @press-enter="searchRecords">
             <template #prefix><IconifyIcon icon="ep:box" /></template>
           </a-input>
@@ -320,7 +387,7 @@
         :data-source="records"
         :row-selection="{ selectedRowKeys: selectedIds, onChange: (keys: (string | number)[]) => { selectedIds = keys.map(Number); } }"
         :loading="loading"
-        :scroll="{ x: 1120 }"
+        :scroll="{ x: 1900 }"
         :pagination="{
           current: page,
           pageSize,
@@ -343,6 +410,9 @@
         </a-table-column>
         <a-table-column key="code" title="琚嬬爜" data-index="code" min-width="220">
           <template #default="{ record }"><span class="mono strong">{{ record.code }}</span></template>
+        </a-table-column>
+        <a-table-column key="palletCode" title="鎵樼洏鐮�" data-index="palletCode" min-width="190">
+          <template #default="{ record }"><span class="mono">{{ record.palletCode || '鈥�' }}</span></template>
         </a-table-column>
         <a-table-column key="batchCode" title="鐢熶骇鎵规" data-index="batchCode" min-width="190">
           <template #default="{ record }">{{ record.batchCode || '鈥�' }}</template>
@@ -373,32 +443,53 @@
         <a-table-column key="actions" title="鎿嶄綔" width="260" fixed="right">
           <template #default="{ record }">
             <a-space>
-              <a-button v-if="record.workflowStatus === 10 && hasAccessByCodes(['mes:qc-seed-quality:retest'])" type="link" @click="openRetest(record)">澶嶆</a-button>
-              <a-button v-if="[20, 50].includes(record.workflowStatus) && hasAccessByCodes(['mes:qc-seed-quality:process-approve'])" type="link" @click="openReview(record, 'process')">鍔犲伐閮ㄥ鎵�</a-button>
-              <a-button v-if="[30, 60].includes(record.workflowStatus) && hasAccessByCodes(['mes:qc-seed-quality:company-approve'])" type="link" @click="openReview(record, 'company')">鍏徃璐ㄦ瀹℃壒</a-button>
+              <a-button v-if="[10, 50].includes(record.workflowStatus) && hasAccessByCodes(['mes:qc-seed-quality:retest'])" type="link" @click="openRetest(record)">{{ record.workflowStatus === 50 ? '閲嶆柊鍒濇' : '澶嶆' }}</a-button>
+              <a-button v-if="[20, 25].includes(record.workflowStatus) && hasAccessByCodes(['mes:qc-seed-quality:process-approve'])" type="link" @click="openReview(record, 'process')">{{ record.workflowStatus === 25 ? '涓嶅悎鏍煎鐞�' : '鍒濇瀹℃牳' }}</a-button>
+              <a-button v-if="[30, 60].includes(record.workflowStatus) && hasAccessByCodes(['mes:qc-seed-quality:company-approve'])" type="link" @click="openReview(record, 'company')">{{ record.workflowStatus === 60 ? '涓嶅悎鏍煎鐞�' : '澶嶆瀹℃牳' }}</a-button>
               <a-button v-if="hasAccessByCodes(['mes:qc-seed-quality:query'])" type="link" @click="openHistory(record)">娴佺▼璁板綍</a-button>
             </a-space>
           </template>
         </a-table-column>
       </a-table>
     </section>
-    <a-modal v-model:open="retestOpen" title="绉嶅瓙璐ㄦ澶嶆" :confirm-loading="retestBusy" ok-text="鎻愪氦澶嶆" @ok="saveRetest">
-      <p class="muted">琚嬬爜锛歿{ activeRecord?.code }}銆傚師鎸囨爣鑼冨洿宸插甫鍏ワ紝璇峰綍鍏ユ湰娆″疄娴嬪�煎苟鏍稿涓婁笅闄愩��</p>
+    <a-modal v-model:open="retestOpen" :title="activeRecord?.workflowStatus === 50 ? '閲嶆柊鍒濇' : '绉嶅瓙璐ㄦ澶嶆'" :confirm-loading="retestBusy" :ok-text="activeRecord?.workflowStatus === 50 ? '鎻愪氦鍒濇' : '鎻愪氦澶嶆'" @ok="saveRetest">
+      <p class="muted">琚嬬爜锛歿{ activeRecord?.code }}銆傚洓椤规寚鏍囦笌鍒濇涓�鑷达紱璇峰綍鍏ユ湰娆″疄娴嬪�煎苟鏍稿鍚堟牸鑼冨洿銆�</p>
       <div v-for="item in retestIndicators" :key="item.indicatorCode" class="retest-row">
         <strong>{{ indicatorLabels[item.indicatorCode] || item.indicatorCode }}</strong>
-        <a-input-number v-model:value="item.value" :min="0" :precision="3" placeholder="瀹炴祴鍊�" />
-        <a-input-number v-model:value="item.minValue" :min="0" :precision="3" placeholder="涓嬮檺" />
-        <a-input-number v-model:value="item.maxValue" :min="0" :precision="3" placeholder="涓婇檺" />
+        <a-input-number v-model:value="item.value" :min="0" :precision="3" placeholder="鏈瀹炴祴鍊�" />
+        <a-input-number v-model:value="item.minValue" :min="0" :max="100" :precision="3" placeholder="涓嬮檺" />
+        <a-input-number v-model:value="item.maxValue" :min="0" :max="100" :precision="3" placeholder="涓婇檺" />
       </div>
-      <a-textarea v-model:value="retestReason" :rows="3" :maxlength="500" placeholder="澶嶆涓嶅悎鏍兼椂濉啓鍘熷洜" />
+      <a-textarea v-model:value="retestReason" :rows="3" :maxlength="500" placeholder="鏈妫�楠屼笉鍚堟牸鏃跺~鍐欏師鍥�" />
     </a-modal>
-    <a-modal v-model:open="reviewOpen" :title="reviewStage === 'process' ? '鍔犲伐閮ㄥ鎵�' : '鍏徃璐ㄦ瀹℃壒'" :confirm-loading="reviewBusy" ok-text="鎻愪氦瀹℃壒" @ok="saveReview">
+    <a-modal v-model:open="scopeOpen" title="鎸夋墭鐩� / 鐢熶骇鎵规鎵归噺澶嶆鎴栭噸鏂板垵妫�" :confirm-loading="scopeBusy" ok-text="鎻愪氦鏈樁娈靛叏閮ㄨ鐮�" :ok-button-props="{ disabled: !scopeIds.length }" @ok="saveScopeRetest">
+      <a-space class="scope-scan">
+        <a-select v-model:value="scopeType" style="width: 130px" :options="[{ label: '鎵樼洏鐮�', value: 'PALLET' }, { label: '鐢熶骇鎵规鐮�', value: 'BATCH' }]" @change="retestScope = undefined" />
+        <a-input-search v-model:value="scopeCode" placeholder="鎵弿鎴栬緭鍏ョ爜鍊�" enter-button="璇嗗埆" :loading="scopeBusy" @search="identifyScope" />
+      </a-space>
+      <template v-if="retestScope">
+        <p>宸茶瘑鍒� {{ retestScope.code }} 路 鏈夋晥琚� {{ retestScope.totalCount }} 路 鐢熶骇鎵规 {{ retestScope.batchCode || '鈥�' }}</p>
+        <a-radio-group v-model:value="scopeStatus">
+          <a-radio-button v-if="retestScope.retestIds.length" :value="10">澶嶆 {{ retestScope.retestIds.length }} 琚�</a-radio-button>
+          <a-radio-button v-if="retestScope.initialRecheckIds.length" :value="50">閲嶆柊鍒濇 {{ retestScope.initialRecheckIds.length }} 琚�</a-radio-button>
+        </a-radio-group>
+        <p class="muted">鍚屼竴缁勬寚鏍囧皢搴旂敤浜庢湰闃舵鍏ㄩ儴 {{ scopeIds.length }} 琚嬨�傛ā鏉胯寖鍥村彲浠ヤ慨鏀广��</p>
+        <div v-for="item in retestIndicators" :key="item.indicatorCode" class="retest-row">
+          <strong>{{ indicatorLabels[item.indicatorCode] }}</strong>
+          <a-input-number v-model:value="item.value" :min="0" :max="100" :precision="3" placeholder="瀹炴祴鍊�" />
+          <a-input-number v-model:value="item.minValue" :min="0" :max="100" :precision="3" placeholder="涓嬮檺" />
+          <a-input-number v-model:value="item.maxValue" :min="0" :max="100" :precision="3" placeholder="涓婇檺" />
+        </div>
+        <a-textarea v-model:value="retestReason" :rows="3" :maxlength="500" placeholder="涓嶅悎鏍兼椂濉啓鍘熷洜" />
+      </template>
+    </a-modal>
+    <a-modal v-model:open="reviewOpen" :title="[25, 60].includes(activeRecord?.workflowStatus ?? 0) ? '涓嶅悎鏍煎鐞�' : reviewStage === 'process' ? '鍒濇瀹℃牳' : '澶嶆瀹℃牳'" :confirm-loading="reviewBusy" ok-text="鎻愪氦瀹℃牳" @ok="saveReview">
       <p>{{ reviewIds.length > 1 ? `鍏� ${reviewIds.length} 鏉¤褰昤 : activeRecord?.code }} 路 {{ workflowLabels[activeRecord?.workflowStatus ?? 0] }}</p>
       <a-radio-group v-model:value="reviewApproved" class="review-choice">
-        <a-radio :value="true">鍚屾剰{{ reviewStage === 'company' ? activeRecord?.workflowStatus === 30 ? '鏀捐鍏ュ簱' : '鎶ュ簾澶勭疆' : '涓婃姤鍏徃璐ㄦ' }}</a-radio>
-        <a-radio :value="false">椹冲洖閲嶆</a-radio>
+        <a-radio :value="true">{{ reviewStage === 'process' ? activeRecord?.workflowStatus === 25 ? '纭涓嶅悎鏍硷紝瀹夋帓澶嶆' : '閫氳繃锛岃繘鍏ュ妫�' : activeRecord?.workflowStatus === 30 ? '閫氳繃锛屾斁琛屽叆搴�' : '纭涓嶅悎鏍硷紝鎶ュ簾澶勭疆' }}</a-radio>
+        <a-radio :value="false">{{ reviewStage === 'process' ? '椹冲洖锛岄噸鏂板垵妫�' : '椹冲洖锛岄噸鏂板妫�' }}</a-radio>
       </a-radio-group>
-      <a-textarea v-model:value="reviewOpinion" :rows="3" :maxlength="500" placeholder="璇峰~鍐欏鎵规剰瑙�" />
+      <a-textarea v-model:value="reviewOpinion" :rows="3" :maxlength="500" :placeholder="reviewApproved ? '瀹℃壒鎰忚锛堥�夊~锛�' : '璇峰~鍐欓┏鍥炴剰瑙�'" />
     </a-modal>
     <a-modal v-model:open="historyOpen" :title="`娴佺▼璁板綍 路 ${activeRecord?.code || ''}`" :footer="null">
       <a-timeline v-if="historyActions.length">
@@ -458,6 +549,8 @@
 .filters :deep(.ant-btn .iconify) { font-size: 14px; }
 .retest-row { display: grid; grid-template-columns: 60px repeat(3, 1fr); gap: 8px; align-items: center; margin-bottom: 10px; }
 .retest-row :deep(.ant-input-number) { width: 100%; }
+.scope-scan { display: flex; width: 100%; margin-bottom: 16px; }
+.scope-scan :deep(.ant-space-item:last-child) { flex: 1; }
 .review-choice { display: flex; gap: 16px; margin: 10px 0 18px; }
 
 /* ===== 鍗曞厓鏍� ===== */
diff --git a/src/views/mes/qc/seedRange/index.vue b/src/views/mes/qc/seedRange/index.vue
new file mode 100644
index 0000000..72b6e4b
--- /dev/null
+++ b/src/views/mes/qc/seedRange/index.vue
@@ -0,0 +1,91 @@
+<script setup lang="ts">
+import { computed, onMounted, ref } from 'vue';
+import { message } from 'ant-design-vue';
+import { useAccess } from '@vben/access';
+import { getSeedIndicatorRanges, saveSeedIndicatorRanges } from '#/api/mes/qc/seedQuality';
+
+const definitions = [
+  { indicatorCode: 'purity', name: '绾害' },
+  { indicatorCode: 'cleanliness', name: '鍑�搴�' },
+  { indicatorCode: 'germination_rate', name: '鍙戣娊鐜�' },
+  { indicatorCode: 'moisture', name: '姘村垎' },
+];
+const ranges = ref(definitions.map((item) => ({ ...item, minValue: undefined as number | undefined, maxValue: undefined as number | undefined })));
+const loading = ref(false);
+const saving = ref(false);
+const { hasAccessByCodes } = useAccess();
+const complete = computed(() => ranges.value.every((item) =>
+  item.minValue !== undefined && item.maxValue !== undefined && item.minValue <= item.maxValue));
+
+async function load() {
+  loading.value = true;
+  try {
+    const data = await getSeedIndicatorRanges();
+    ranges.value = definitions.map((item) => {
+      const saved = data.find((row) => row.indicatorCode === item.indicatorCode);
+      return { ...item, minValue: saved ? Number(saved.minValue) : undefined,
+        maxValue: saved ? Number(saved.maxValue) : undefined };
+    });
+  } catch (error) {
+    message.error((error as Error).message || '璇诲彇鍚堟牸鑼冨洿澶辫触');
+  } finally {
+    loading.value = false;
+  }
+}
+
+async function save() {
+  if (!complete.value || ranges.value.some((item) => item.minValue! < 0 || item.maxValue! > 100)) {
+    message.warning('璇蜂负鍥涢」鎸囨爣閰嶇疆 0鈥�100% 鐨勬湁鏁堜笂涓嬮檺');
+    return;
+  }
+  saving.value = true;
+  try {
+    await saveSeedIndicatorRanges(ranges.value.map((item) => ({
+      indicatorCode: item.indicatorCode,
+      minValue: item.minValue!, maxValue: item.maxValue!,
+    })));
+    message.success('榛樿鍚堟牸鑼冨洿宸蹭繚瀛橈紝妫�楠屾椂浠嶅彲鎸夊疄闄呮儏鍐佃皟鏁�');
+    await load();
+  } catch (error) {
+    message.error((error as Error).message || '淇濆瓨鍚堟牸鑼冨洿澶辫触');
+  } finally {
+    saving.value = false;
+  }
+}
+
+onMounted(load);
+</script>
+
+<template>
+  <div class="range-page">
+    <header class="page-header">
+      <div><div class="eyebrow">MES / 璐ㄩ噺绠$悊</div><h1>璐ㄦ椤瑰悎鏍艰寖鍥撮厤缃�</h1>
+        <p>涓哄垵妫�鍜屽妫�鎻愪緵鍥涢」鎸囨爣鐨勯粯璁よ寖鍥淬��</p></div>
+    </header>
+    <a-alert type="info" show-icon message="杩欓噷閰嶇疆鐨勬槸濉啓妯℃澘" description="妫�楠屽憳鍙互淇敼鏈鍚堟牸涓婁笅闄愶紱瀹為檯鎻愪氦鐨勪笂涓嬮檺鍜屽疄娴嬪�间竴鍚屼繚瀛橈紝鍘嗗彶璁板綍涓嶅彈妯℃澘淇敼褰卞搷銆�" />
+    <a-card class="range-card" :loading="loading" :bordered="false">
+      <div class="grid-head"><span>璐ㄦ椤�</span><span>鍚堟牸涓嬮檺</span><span>鍚堟牸涓婇檺</span><span>鍗曚綅</span></div>
+      <div v-for="item in ranges" :key="item.indicatorCode" class="range-row">
+        <strong>{{ item.name }}</strong>
+        <a-input-number v-model:value="item.minValue" :min="0" :max="100" :precision="3" :disabled="!hasAccessByCodes(['mes:qc-seed-range:update'])" placeholder="涓嬮檺" />
+        <a-input-number v-model:value="item.maxValue" :min="0" :max="100" :precision="3" :disabled="!hasAccessByCodes(['mes:qc-seed-range:update'])" placeholder="涓婇檺" />
+        <span>%</span>
+      </div>
+      <div class="actions">
+        <a-button @click="load">鍒锋柊</a-button>
+        <a-button v-if="hasAccessByCodes(['mes:qc-seed-range:update'])" type="primary" :loading="saving" :disabled="!complete" @click="save">淇濆瓨鍚堟牸鑼冨洿</a-button>
+      </div>
+    </a-card>
+  </div>
+</template>
+
+<style scoped>
+.range-page { padding: 24px; min-height: 100%; background: #f4f7fb; }
+.page-header { margin-bottom: 20px; }.eyebrow { color: #1677ff; font-size: 12px; font-weight: 700; letter-spacing: .08em; }
+h1 { margin: 6px 0; color: #172033; font-size: 28px; }.page-header p { margin: 0; color: #718096; }
+.range-card { margin-top: 16px; border-radius: 10px; box-shadow: 0 3px 12px #243b5310; }
+.grid-head, .range-row { display: grid; grid-template-columns: minmax(120px, 1fr) minmax(120px, 1fr) minmax(120px, 1fr) 70px; align-items: center; gap: 16px; padding: 12px 16px; }
+.grid-head { color: #718096; background: #f7f9fc; font-weight: 600; }.range-row { border-bottom: 1px solid #e8edf5; }.range-row :deep(.ant-input-number) { width: 100%; }
+.actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; }
+@media (max-width: 640px) { .range-page { padding: 14px; }.grid-head { display: none; }.range-row { grid-template-columns: 1fr 1fr; }.range-row strong { grid-column: 1 / -1; } }
+</style>
diff --git a/src/views/mobile/quality-records.vue b/src/views/mobile/quality-records.vue
index a25cae6..306e369 100644
--- a/src/views/mobile/quality-records.vue
+++ b/src/views/mobile/quality-records.vue
@@ -1,11 +1,58 @@
 <script setup lang="ts">
-import { onMounted, ref } from 'vue';
-import { getSeedQualityPage, type SeedQualityRecord } from '#/api/mes/qc/seedQuality';
+import { computed, onMounted, ref } from 'vue';
+import { useAccess } from '@vben/access';
+import { getSeedIndicatorRanges, getSeedQualityPage, resolveSeedRetestScope, submitSeedRetestScope, type SeedQualityRecord, type SeedRetestScope } from '#/api/mes/qc/seedQuality';
 import { errorText } from '#/mobile/api';
+import { extractCode } from '#/mobile/access';
 import Scanner from './scanner.vue';
 
 const rows = ref<SeedQualityRecord[]>([]), busy = ref(false), error = ref('');
+const { hasAccessByCodes } = useAccess();
+const canRetest = computed(() => hasAccessByCodes(['mes:qc-seed-quality:retest']));
+const scanType = ref<'PALLET' | 'BATCH'>('PALLET'), scope = ref<SeedRetestScope>();
+const workflowStatus = ref<10 | 50>(10), reason = ref(''), notice = ref('');
+const indicators = ref([
+  { code: 'purity', name: '绾害', value: '', minValue: '', maxValue: '' },
+  { code: 'cleanliness', name: '鍑�搴�', value: '', minValue: '', maxValue: '' },
+  { code: 'germination_rate', name: '鍙戣娊鐜�', value: '', minValue: '', maxValue: '' },
+  { code: 'moisture', name: '姘村垎', value: '', minValue: '', maxValue: '' },
+]);
+const targetIds = computed(() => workflowStatus.value === 10 ? scope.value?.retestIds || [] : scope.value?.initialRecheckIds || []);
+const complete = computed(() => indicators.value.every(i => [i.value, i.minValue, i.maxValue].every(v => v.trim() !== '' && Number.isFinite(Number(v)))
+  && Number(i.value) >= 0 && Number(i.value) <= 100 && Number(i.minValue) >= 0 && Number(i.maxValue) <= 100 && Number(i.minValue) <= Number(i.maxValue)));
+const passed = computed(() => complete.value && indicators.value.every(i => Number(i.value) >= Number(i.minValue) && Number(i.value) <= Number(i.maxValue)));
+async function identifyRetest(raw: string) {
+  if (busy.value) return;
+  busy.value = true; error.value = ''; notice.value = ''; scope.value = undefined;
+  try {
+    const data = await resolveSeedRetestScope(extractCode(raw), scanType.value);
+    if (!data.retestIds.length && !data.initialRecheckIds.length) throw new Error('褰撳墠鎵樼洏鎴栨壒娆℃病鏈夊緟澶嶆鎴栧緟閲嶆柊鍒濇鐨勮褰�');
+    scope.value = data;
+    workflowStatus.value = data.retestIds.length ? 10 : 50;
+    indicators.value.forEach(i => { i.value = ''; i.minValue = ''; i.maxValue = ''; }); reason.value = '';
+    try {
+      const ranges = await getSeedIndicatorRanges();
+      indicators.value.forEach(i => { const range = ranges.find(r => r.indicatorCode === i.code); if (range) { i.minValue = String(range.minValue); i.maxValue = String(range.maxValue); } });
+    } catch { /* 鍙墜鍔ㄥ~鍐欐湰娆¤寖鍥� */ }
+  } catch (e) { error.value = errorText(e); } finally { busy.value = false; }
+}
+async function submitRetest() {
+  if (busy.value || !scope.value || !targetIds.value.length || !complete.value) return;
+  if (!passed.value && !reason.value.trim()) { error.value = '妫�楠屼笉鍚堟牸璇峰~鍐欏師鍥�'; return; }
+  const count = targetIds.value.length;
+  if (!window.confirm(`纭灏嗘湰娆℃寚鏍囩敤浜� ${count} 琚�${workflowStatus.value === 10 ? '澶嶆' : '閲嶆柊鍒濇'}锛焋)) return;
+  busy.value = true; error.value = '';
+  try {
+    const result = await submitSeedRetestScope({ code: scope.value.code, scanType: scope.value.scanType, workflowStatus: workflowStatus.value,
+      qualityIds: targetIds.value, reason: reason.value.trim() || undefined,
+      indicators: indicators.value.map(i => ({ code: i.code, value: Number(i.value), minValue: Number(i.minValue), maxValue: Number(i.maxValue) })) });
+    notice.value = `宸插畬鎴� ${result.processedCount} 琚嬫楠岋紝绛夊緟鍚庣画瀹℃牳鎴栦笉鍚堟牸澶勭悊銆俙;
+    scope.value = undefined;
+  } catch (e) { error.value = errorText(e) + '銆傝閲嶆柊鎵爜鏍稿娓呭崟鍚庡啀鎻愪氦銆�'; scope.value = undefined; }
+  finally { busy.value = false; if (notice.value) void load(); }
+}
 const code = ref(''), batchCode = ref(''), result = ref(''), page = ref(0), total = ref(0);
+const statusNames: Record<number, string> = { 10: '寰呭妫�', 20: '寰呭垵妫�瀹℃牳', 25: '寰呭垵妫�涓嶅悎鏍煎鐞�', 30: '寰呭妫�瀹℃牳', 40: '宸叉斁琛�', 50: '寰呴噸鏂板垵妫�', 60: '寰呭妫�涓嶅悎鏍煎鐞�', 70: '宸叉姤搴�' };
 let filters: { code?: string; batchCode?: string; result?: number } = {};
 async function load(reset = true) {
   if (busy.value) return;
@@ -21,6 +68,25 @@
 onMounted(() => load());
 </script>
 <template>
+  <section v-if="canRetest" class="m-panel">
+    <h2 class="m-title">鎸夋墭鐩� / 鎵规鎵归噺璐ㄦ</h2>
+    <p class="m-muted">鎵弿鎵樼洏鐮佹垨鐢熶骇鎵规鐮侊紝绯荤粺浼氬垪鍑哄緟澶嶆鍜屽緟閲嶆柊鍒濇鐨勮鏁般��</p>
+    <label class="m-field"><span>鎵爜绫诲瀷</span><select v-model="scanType" :disabled="busy" @change="scope = undefined"><option value="PALLET">鎵樼洏鐮�</option><option value="BATCH">鐢熶骇鎵规鐮�</option></select></label>
+    <Scanner :disabled="busy" @code="identifyRetest" />
+    <form v-if="scope" @submit.prevent="submitRetest">
+      <p class="m-muted">{{scope.code}} 路 {{scope.batchCode || '鏈爣娉ㄦ壒娆�'}} 路 鏈夋晥琚� {{scope.totalCount}}</p>
+      <label class="m-field"><span>璐ㄦ闃舵</span><select v-model="workflowStatus" :disabled="busy"><option v-if="scope.retestIds.length" :value="10">澶嶆锛坽{scope.retestIds.length}} 琚嬶級</option><option v-if="scope.initialRecheckIds.length" :value="50">閲嶆柊鍒濇锛坽{scope.initialRecheckIds.length}} 琚嬶級</option></select></label>
+      <p class="m-warning">鍚屼竴缁勬寚鏍囧皢鐢ㄤ簬鏈樁娈靛叏閮� {{targetIds.length}} 琚嬨�傝鏍稿鎶芥牱缁撴灉鏄惁閫傜敤銆�</p>
+      <fieldset v-for="i in indicators" :key="i.code" class="m-panel m-quality-indicator" :disabled="busy"><legend>{{i.name}}锛�%锛�</legend>
+        <label class="m-field"><span>瀹炴祴鍊�</span><input v-model="i.value" type="number" inputmode="decimal" min="0" max="100" step="any" required /></label>
+        <div class="m-grid"><label class="m-field"><span>鍚堟牸涓嬮檺</span><input v-model="i.minValue" type="number" inputmode="decimal" min="0" max="100" step="any" required /></label><label class="m-field"><span>鍚堟牸涓婇檺</span><input v-model="i.maxValue" type="number" inputmode="decimal" min="0" max="100" step="any" required /></label></div>
+      </fieldset>
+      <p class="m-muted">涓婁笅闄愪粠妯℃澘甯﹀叆锛屽彲鎸夋湰娆℃楠屾爣鍑嗚皟鏁淬��</p>
+      <label class="m-field"><span>{{complete && !passed ? '涓嶅悎鏍煎師鍥狅紙蹇呭~锛�' : '妫�楠屽娉�'}}</span><textarea v-model="reason" :required="complete && !passed" maxlength="500" rows="3" /></label>
+      <button class="m-primary" :disabled="busy || !complete || !targetIds.length">{{busy ? '姝e湪鎻愪氦鈥�' : `鎻愪氦 ${targetIds.length} 琚嬭川妫�`}}</button>
+    </form>
+  </section>
+  <p v-if="notice" class="m-success" role="status">{{notice}}</p>
   <p class="m-muted">鏌ョ湅璐ㄦ缁撴灉銆佹壒娆′笌涓嶅悎鏍煎師鍥犮��</p>
   <Scanner :disabled="busy" @code="value => { code = value; load(); }" />
   <form class="m-panel" @submit.prevent="load()">
@@ -34,7 +100,7 @@
   <p v-if="!rows.length && !busy && !error" class="m-empty">鏆傛棤鍖归厤鐨勮川妫�璁板綍</p>
   <article v-for="row in rows" :key="row.id" class="m-panel">
     <strong :class="['m-status', row.result === 1 ? 'success' : row.result === 2 ? 'failed' : '']">{{row.result === 1 ? '鍚堟牸' : row.result === 2 ? '涓嶅悎鏍�' : '澶勭悊涓�'}}</strong>
-    <dl><div class="m-row"><dt>杩芥函鐮�</dt><dd>{{row.code}}</dd></div><div class="m-row"><dt>鐢熶骇鎵规</dt><dd>{{row.batchCode || '鈥�'}}</dd></div><div class="m-row"><dt>妫�楠屾椂闂�</dt><dd>{{row.inspectedAt || row.createTime || '鈥�'}}</dd></div><div v-if="row.reason" class="m-row"><dt>鍘熷洜 / 澶囨敞</dt><dd>{{row.reason}}</dd></div></dl>
+    <dl><div class="m-row"><dt>杩芥函鐮�</dt><dd>{{row.code}}</dd></div><div class="m-row"><dt>鐢熶骇鎵规</dt><dd>{{row.batchCode || '鈥�'}}</dd></div><div class="m-row"><dt>娴佺▼鐘舵��</dt><dd>{{statusNames[row.workflowStatus] || '鏈煡鐘舵��'}}</dd></div><div class="m-row"><dt>妫�楠屾椂闂�</dt><dd>{{row.inspectedAt || row.createTime || '鈥�'}}</dd></div><div v-if="row.reason" class="m-row"><dt>鍘熷洜 / 澶囨敞</dt><dd>{{row.reason}}</dd></div></dl>
   </article>
   <button v-if="rows.length < total" class="m-secondary" :disabled="busy" @click="load(false)">{{busy ? '姝e湪鍔犺浇鈥�' : '鍔犺浇鏇村'}}</button>
 </template>
diff --git a/src/views/mobile/quality.vue b/src/views/mobile/quality.vue
index 4e870f2..81bf336 100644
--- a/src/views/mobile/quality.vue
+++ b/src/views/mobile/quality.vue
@@ -2,7 +2,7 @@
 import { computed, onMounted, ref } from 'vue';
 import { onBeforeRouteLeave } from 'vue-router';
 import { useUserStore } from '@vben/stores';
-import { resolveSeedQuality, submitSeedQualityGroup, type SeedQualityScan } from '#/api/mes/qc/seedQuality';
+import { getSeedIndicatorRanges, resolveSeedQuality, submitSeedQualityGroup, type SeedQualityScan } from '#/api/mes/qc/seedQuality';
 import { getWarehouseSimpleList, type MesWmWarehouseApi } from '#/api/mes/wm/warehouse';
 import { allowedMobilePath, extractCode } from '#/mobile/access';
 import { errorText } from '#/mobile/api';
@@ -18,10 +18,10 @@
 const pendingType = ref<ScanType>('BAG'), pendingIds = ref<number[]>([]);
 const storageKey = 'mom-mobile-quality-' + useUserStore().userInfo?.id;
 const indicators = ref([
-  { code: 'purity', name: '绾害', value: '', minValue: '0', maxValue: '100' },
-  { code: 'cleanliness', name: '鍑�搴�', value: '', minValue: '0', maxValue: '100' },
-  { code: 'germination_rate', name: '鍙戣娊鐜�', value: '', minValue: '0', maxValue: '100' },
-  { code: 'moisture', name: '姘村垎', value: '', minValue: '0', maxValue: '100' },
+  { code: 'purity', name: '绾害', value: '', minValue: '', maxValue: '' },
+  { code: 'cleanliness', name: '鍑�搴�', value: '', minValue: '', maxValue: '' },
+  { code: 'germination_rate', name: '鍙戣娊鐜�', value: '', minValue: '', maxValue: '' },
+  { code: 'moisture', name: '姘村垎', value: '', minValue: '', maxValue: '' },
 ]);
 const complete = computed(() => indicators.value.every(i => [i.value, i.minValue, i.maxValue].every(v => String(v).trim() !== '' && Number.isFinite(Number(v))) && Number(i.minValue) >= 0 && Number(i.maxValue) <= 100 && Number(i.value) >= 0 && Number(i.value) <= 100 && Number(i.minValue) <= Number(i.maxValue)));
 const passed = computed(() => complete.value && indicators.value.every(i => Number(i.value) >= Number(i.minValue) && Number(i.value) <= Number(i.maxValue)));
@@ -33,20 +33,24 @@
     const data = await resolveSeedQuality(extractCode(raw), scanType.value);
     if (data.canceled || !data.inbound) throw new Error('璇ュ璞″凡浣滃簾鎴栨湁鏁堣鐮佸皻鏈叏閮ㄥ叆搴擄紝涓嶈兘璐ㄦ');
     if (!data.pendingBagIds?.length) throw new Error('璇ュ璞℃病鏈夊緟璐ㄦ琚嬬爜锛岃鍦ㄨ川妫�璁板綍涓煡鐪嬪凡鏈夌粨鏋�');
-    bag.value = data; indicators.value.forEach(i => i.value = ''); reason.value = '';
+    bag.value = data; indicators.value.forEach(i => { i.value = ''; i.minValue = ''; i.maxValue = ''; }); reason.value = '';
+    try {
+      const ranges = await getSeedIndicatorRanges();
+      indicators.value.forEach(i => { const range = ranges.find(r => r.indicatorCode === i.code); if (range) { i.minValue = String(range.minValue); i.maxValue = String(range.maxValue); } });
+    } catch { /* 妯℃澘涓嶅彲鐢ㄦ椂浠嶅厑璁告墜鍔ㄥ~鍐欐湰娆¤寖鍥� */ }
   } catch (e) { error.value = errorText(e); } finally { busy.value = false; }
 }
 async function submit() {
   if (busy.value || pendingCode.value || !bag.value || !complete.value || !warehouseId.value) return;
   if (!passed.value && !reason.value.trim()) { error.value = '璇峰~鍐欎笉鍚堟牸鍘熷洜'; return; }
   const count = bag.value.pendingBagIds!.length;
-  if (!window.confirm(passed.value ? `纭瀵规湰娆� ${count} 琚嬬粺涓�鍒ゅ畾鍚堟牸锛熷叏閮ㄥ皢杞叆鎵�閫夊悎鏍煎簱銆俙 : `纭瀵规湰娆� ${count} 琚嬬粺涓�鍒ゅ畾涓嶅悎鏍硷紵鍏ㄩ儴灏嗕粠鏆傚瓨搴撳瓨绉婚櫎骞舵爣璁版姤搴熴�俙)) return;
+  if (!window.confirm(passed.value ? `纭瀵规湰娆� ${count} 琚嬬粺涓�鎻愪氦鍒濇鍚堟牸锛熷鏍搁�氳繃鍚庤繘鍏ュ妫�銆俙 : `纭瀵规湰娆� ${count} 琚嬬粺涓�鎻愪氦鍒濇涓嶅悎鏍硷紵灏嗚繘鍏ヤ笉鍚堟牸澶勭悊銆俙)) return;
   error.value = ''; notice.value = '';
   pendingCode.value = bag.value.code; pendingType.value = scanType.value; pendingIds.value = [...bag.value.pendingBagIds!];
   sessionStorage.setItem(storageKey, JSON.stringify({ code: pendingCode.value, scanType: pendingType.value, bagIds: pendingIds.value })); busy.value = true;
   try {
     const result = await submitSeedQualityGroup({ code: bag.value.code, scanType: scanType.value, bagIds: pendingIds.value, qualifiedWarehouseId: warehouseId.value, reason: reason.value.trim() || undefined, indicators: indicators.value.map(i => ({ code: i.code, value: Number(i.value), minValue: Number(i.minValue), maxValue: Number(i.maxValue) })) });
-    clearPending(); notice.value = `鏈 ${result.processedCount} 琚媊 + (passed.value ? '璐ㄦ鍚堟牸锛屽凡杞叆鍚堟牸搴撱��' : '璐ㄦ涓嶅悎鏍硷紝宸茶褰曞師鍥犲苟绉婚櫎鏆傚瓨搴撳瓨銆�');
+    clearPending(); notice.value = `鏈 ${result.processedCount} 琚媊 + (passed.value ? '鍒濇鍚堟牸锛岀瓑寰呭鏍搞��' : '鍒濇涓嶅悎鏍硷紝绛夊緟涓嶅悎鏍煎鐞嗐��');
     bag.value = undefined; reason.value = ''; indicators.value.forEach(i => i.value = '');
   } catch (e) { error.value = '鎻愪氦缁撴灉寰呯‘璁わ細' + errorText(e) + '銆傝鏍稿疄鍚庡啀鎿嶄綔涓嬩竴琚嬨��'; } finally { busy.value = false; }
 }
@@ -87,8 +91,8 @@
         <label class="m-field"><span>瀹炴祴鍊�</span><input v-model="i.value" type="number" inputmode="decimal" min="0" max="100" step="any" required placeholder="杈撳叆瀹炴祴鐧惧垎姣�" /></label>
         <div class="m-grid"><label class="m-field"><span>鍚堟牸涓嬮檺</span><input v-model="i.minValue" type="number" inputmode="decimal" min="0" max="100" step="any" required /></label><label class="m-field"><span>鍚堟牸涓婇檺</span><input v-model="i.maxValue" type="number" inputmode="decimal" min="0" max="100" step="any" required /></label></div>
       </fieldset>
-      <div class="m-panel"><p class="m-muted">涓婁笅闄愭部鐢ㄧ幇鏈夎川妫�瑙勫垯褰曞叆鏂瑰紡锛岃鎸夎鍝佺妫�楠屾爣鍑嗘牳瀵广��</p><Picker v-model="warehouseId" label="鐩爣鍚堟牸搴�" :options="warehouses" :disabled="busy" /><p v-if="!warehouses.length && !busy" class="m-warning">鏆傛棤鍙敤鍚堟牸搴擄紝璇疯仈绯荤鐞嗗憳閰嶇疆銆�</p><label class="m-field"><span>{{complete && !passed ? '涓嶅悎鏍煎師鍥狅紙蹇呭~锛�' : '妫�楠屽娉�'}}</span><textarea v-model="reason" :disabled="busy" :required="complete && !passed" rows="3" maxlength="255" /></label></div>
-      <div class="m-action-bar"><p v-if="complete" :class="passed ? 'm-success' : 'm-warning'">棰勫垽锛歿{passed ? '鍚堟牸锛屾彁浜ゅ悗杞叆鍚堟牸搴�' : '涓嶅悎鏍硷紝鎻愪氦鍚庣Щ闄ゆ殏瀛樺簱瀛�'}}銆傛渶缁堜互鏈嶅姟绔粨鏋滀负鍑嗐��</p><button class="m-primary" :disabled="busy || !complete || !warehouseId">{{busy ? '姝e湪鎻愪氦鈥�' : '纭鎻愪氦璐ㄦ'}}</button></div>
+      <div class="m-panel"><p class="m-muted">鍚堟牸鑼冨洿浠庢ā鏉垮甫鍏ワ紝鍙湪鏈璐ㄦ涓慨鏀癸紱璇锋寜璇ュ搧绉嶆爣鍑嗘牳瀵广��</p><Picker v-model="warehouseId" label="鐩爣鍚堟牸搴�" :options="warehouses" :disabled="busy" /><p v-if="!warehouses.length && !busy" class="m-warning">鏆傛棤鍙敤鍚堟牸搴擄紝璇疯仈绯荤鐞嗗憳閰嶇疆銆�</p><label class="m-field"><span>{{complete && !passed ? '涓嶅悎鏍煎師鍥狅紙蹇呭~锛�' : '妫�楠屽娉�'}}</span><textarea v-model="reason" :disabled="busy" :required="complete && !passed" rows="3" maxlength="255" /></label></div>
+      <div class="m-action-bar"><p v-if="complete" :class="passed ? 'm-success' : 'm-warning'">棰勫垽锛歿{passed ? '鍒濇鍚堟牸锛岀瓑寰呭垵妫�瀹℃牳' : '鍒濇涓嶅悎鏍硷紝杩涘叆涓嶅悎鏍煎鐞�'}}銆傛渶缁堜互鏈嶅姟绔粨鏋滀负鍑嗐��</p><button class="m-primary" :disabled="busy || !complete || !warehouseId">{{busy ? '姝e湪鎻愪氦鈥�' : '纭鎻愪氦璐ㄦ'}}</button></div>
     </form>
   </template>
   <RouterLink v-if="allowedMobilePath('/mobile/quality-records')" to="/mobile/quality-records" class="m-link">鏌ョ湅璐ㄦ璁板綍</RouterLink>

--
Gitblit v1.9.3