From 27f904344349c51e720be96e4afd9ca033a33676 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期五, 16 十月 2026 20:15:51 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_新疆_塔河种业' into dev_新疆_塔河种业

---
 src/views/mes/qc/seedQuality/index.vue |  230 +++++++++++++++++++++++++++++++++++++++++++++------------
 1 files changed, 181 insertions(+), 49 deletions(-)

diff --git a/src/views/mes/qc/seedQuality/index.vue b/src/views/mes/qc/seedQuality/index.vue
index ca55a98..6e2bc93 100644
--- a/src/views/mes/qc/seedQuality/index.vue
+++ b/src/views/mes/qc/seedQuality/index.vue
@@ -6,9 +6,27 @@
 import { MesWmWarehouseTypeEnum } from '@vben/constants';
 import { gsap } from 'gsap';
 
+import type { SeedQualityScan } from '#/api/mes/qc/seedQuality';
 import type { MesWmWarehouseApi } from '#/api/mes/wm/warehouse';
-import { submitSeedQuality } from '#/api/mes/qc/seedQuality';
+import {
+  resolveSeedQuality,
+  submitSeedQualityGroup,
+} from '#/api/mes/qc/seedQuality';
 import { getWarehouseSimpleList } from '#/api/mes/wm/warehouse';
+
+/** 鎵爜绫诲瀷锛氳鐮� / 鎵樼洏鐮� / 鎵规鐮侊紙涓庣敓浜х嚎鎵爜鍏ュ簱淇濇寔涓�鑷达級 */
+type ScanType = 'BAG' | 'PALLET' | 'BATCH';
+
+const SCAN_TYPE_LABEL: Record<ScanType, string> = {
+  BAG: '琚嬬爜',
+  PALLET: '鎵樼洏鐮�',
+  BATCH: '鎵规鐮�',
+};
+const SCAN_TYPE_OPTIONS = [
+  { label: '琚嬬爜', value: 'BAG' },
+  { label: '鎵樼洏鐮�', value: 'PALLET' },
+  { label: '鎵规鐮�', value: 'BATCH' },
+] as const;
 
 interface IndicatorForm {
   code: string;
@@ -20,11 +38,15 @@
 }
 
 const pageRoot = ref<HTMLElement>();
+const scanType = ref<ScanType>('BAG');
 const code = ref('');
-const batchCode = ref('');
+/** 宸茶瘑鍒殑璐ㄦ瀵硅薄锛氬甫鍑烘湁鏁堣鏁般�佸凡妫�琚嬫暟涓庢湰娆″緟妫�琚嬫竻鍗� */
+const target = ref<SeedQualityScan>();
 const reason = ref('');
+const resolving = ref(false);
 const loading = ref(false);
-const submittedId = ref<number>();
+/** 鏈�杩戜竴娆℃垚鍔熸彁浜ょ殑琚嬫暟锛堢敤浜庣粨鏋滃弽棣堬級 */
+const submittedCount = ref<number>();
 let animationContext: gsap.Context | undefined;
 
 /** 鍙�夊悎鏍间粨搴擄紙浠撳簱绫诲瀷=20 鍚堟牸搴擄級 */
@@ -38,10 +60,19 @@
   { code: 'moisture', label: '姘村垎', unit: '%', minValue: 0, maxValue: 100 },
 ]);
 
+const pendingCount = computed(() => target.value?.pendingBagIds?.length ?? 0);
+const completedCount = computed(() => target.value?.completedBagIds?.length ?? 0);
 const enteredCount = computed(() => indicators.value.filter((item) => item.value !== undefined && item.value !== null).length);
 const passedCount = computed(() => indicators.value.filter(isPassed).length);
 const allEntered = computed(() => enteredCount.value === indicators.value.length);
 const overallPassed = computed(() => allEntered.value && passedCount.value === indicators.value.length);
+const isGroupScan = computed(() => scanType.value !== 'BAG');
+const qualifiedWarehouseOptions = computed(() =>
+  qualifiedWarehouses.value.map((item) => ({
+    label: item.code ? `${item.name}锛�${item.code}锛塦 : (item.name ?? ''),
+    value: item.id ?? 0,
+  })),
+);
 
 function isPassed(item: IndicatorForm) {
   return item.value !== undefined && item.value !== null
@@ -49,11 +80,23 @@
     && item.value >= item.minValue && item.value <= item.maxValue;
 }
 
-function resetForm(clearResult = true) {
-  code.value = '';
-  batchCode.value = '';
+/** 缁熶竴鎻愬彇鍚庣涓氬姟閿欒淇℃伅 */
+function errorText(error: unknown) {
+  const response = error as {
+    message?: string;
+    msg?: string;
+    response?: { data?: { message?: string; msg?: string } };
+  };
+  return response.response?.data?.message
+    ?? response.response?.data?.msg
+    ?? response.message
+    ?? response.msg
+    ?? '鎿嶄綔澶辫触锛岃绋嶅悗閲嶈瘯';
+}
+
+/** 娓呯┖鎸囨爣涓庝笉鍚堟牸鍘熷洜 */
+function resetIndicators() {
   reason.value = '';
-  if (clearResult) submittedId.value = undefined;
   indicators.value.forEach((item) => {
     item.value = undefined;
     item.minValue = 0;
@@ -61,21 +104,74 @@
   });
 }
 
+/** 鍒囨崲鎵爜绫诲瀷锛氱爜鍊间笌宸茶瘑鍒璞″悓鏃跺け鏁堬紝閬垮厤鐢ㄩ敊绫诲瀷鐨勭爜鍊兼彁浜� */
+function onScanTypeChange() {
+  code.value = '';
+  target.value = undefined;
+  submittedCount.value = undefined;
+  resetIndicators();
+}
+
+function resetForm() {
+  code.value = '';
+  target.value = undefined;
+  submittedCount.value = undefined;
+  resetIndicators();
+}
+
+/** 璇嗗埆鐮佸�硷細纭畾鏈璐ㄦ鑼冨洿锛堣鐮� 1 琚� / 鎵樼洏宸茶琚� / 鏁存壒琚嬫暟锛� */
+async function identify() {
+  const value = code.value.trim();
+  if (!value) {
+    message.warning(`璇疯緭鍏ユ垨鎵弿${SCAN_TYPE_LABEL[scanType.value]}`);
+    return;
+  }
+  resolving.value = true;
+  submittedCount.value = undefined;
+  try {
+    const data = await resolveSeedQuality(value, scanType.value);
+    if (data.canceled) {
+      target.value = undefined;
+      message.error('璇ヨ川妫�瀵硅薄宸蹭綔搴燂紝涓嶈兘璐ㄦ');
+      return;
+    }
+    if (!data.inbound) {
+      target.value = undefined;
+      message.error('璇ュ璞$殑鏈夋晥琚嬬爜灏氭湭鍏ㄩ儴鍏ュ簱锛屾殏涓嶈兘璐ㄦ');
+      return;
+    }
+    if (!data.pendingBagIds?.length) {
+      target.value = undefined;
+      message.error('璇ュ璞℃病鏈夊緟璐ㄦ琚嬬爜锛岃鍦ㄨ川妫�璁板綍涓煡鐪嬪凡鏈夌粨鏋�');
+      return;
+    }
+    target.value = data;
+    resetIndicators();
+    message.success(`${SCAN_TYPE_LABEL[scanType.value]} ${data.code} 宸茶瘑鍒紝鏈寰呮 ${data.pendingBagIds.length} 琚媊);
+  } catch (error) {
+    target.value = undefined;
+    message.error(errorText(error));
+  } finally {
+    resolving.value = false;
+  }
+}
+
 async function submit() {
-  if (!code.value.trim()) {
-    message.warning('璇峰~鍐欒鐮�');
+  const current = target.value;
+  if (!current) {
+    message.warning(`璇峰厛璇嗗埆${SCAN_TYPE_LABEL[scanType.value]}`);
     return;
   }
   if (!allEntered.value) {
     message.warning('璇峰厛濉啓鍥涢」鎸囨爣鐨勫疄娴嬪��');
     return;
   }
-  if (!qualifiedWarehouseId.value) {
-    message.warning('璇烽�夋嫨鍏ュ簱鍚堟牸搴�');
-    return;
-  }
   if (indicators.value.some((item) => item.minValue === undefined || item.maxValue === undefined || item.minValue > item.maxValue)) {
     message.warning('鎸囨爣涓嬮檺涓嶈兘澶т簬涓婇檺');
+    return;
+  }
+  if (!qualifiedWarehouseId.value) {
+    message.warning('璇烽�夋嫨鍏ュ簱鍚堟牸搴�');
     return;
   }
   if (!overallPassed.value && !reason.value.trim()) {
@@ -83,43 +179,53 @@
     return;
   }
 
-  if (!overallPassed.value) {
-    const confirmed = await new Promise<boolean>((resolve) => {
-      Modal.confirm({
-        title: '纭鎻愪氦涓嶅悎鏍肩粨鏋滐紵',
-        content: '涓嶅悎鏍煎搧灏嗙敱鍚庣浠庢殏瀛樺簱瀛樹腑娓呴櫎锛屽苟淇濈暀涓嶅悎鏍煎師鍥犵敤浜庤川閲忓垎鏋愩��',
-        okText: '纭鎻愪氦',
-        cancelText: '杩斿洖淇敼',
-        okButtonProps: { danger: true },
-        onOk: () => resolve(true),
-        onCancel: () => resolve(false),
-      });
+  const count = current.pendingBagIds?.length ?? 0;
+  const scopeNote = isGroupScan.value
+    ? `鎸囨爣涓庢楠岀粨璁哄皢搴旂敤浜庢湰娆″叏閮� ${count} 琚嬶紝璇风‘璁ゆ娊鏍风粨鏋滈�傜敤浜庤鑼冨洿銆俙
+    : '';
+  const confirmed = await new Promise<boolean>((resolve) => {
+    Modal.confirm({
+      title: overallPassed.value ? '纭鎻愪氦鍚堟牸缁撴灉锛�' : '纭鎻愪氦涓嶅悎鏍肩粨鏋滐紵',
+      content: overallPassed.value
+        ? `鏈 ${count} 琚嬪皢缁熶竴鍒ゅ畾鍚堟牸锛屽苟浠庢殏瀛樺簱瀛樿浆鍏ユ墍閫夊悎鏍煎簱銆�${scopeNote}`
+        : `鏈 ${count} 琚嬪皢浠庢殏瀛樺簱瀛樹腑娓呴櫎骞舵爣璁颁笉鍚堟牸锛屽悓鏃朵繚鐣欎笉鍚堟牸鍘熷洜鐢ㄤ簬璐ㄩ噺鍒嗘瀽銆�${scopeNote}`,
+      okText: '纭鎻愪氦',
+      cancelText: '杩斿洖淇敼',
+      okButtonProps: { danger: !overallPassed.value },
+      onOk: () => resolve(true),
+      onCancel: () => resolve(false),
     });
-    if (!confirmed) return;
-  }
+  });
+  if (!confirmed) return;
 
   loading.value = true;
+  const passed = overallPassed.value;
   try {
-    submittedId.value = await submitSeedQuality({
-      code: code.value.trim(),
-      batchCode: batchCode.value.trim() || undefined,
+    const result = await submitSeedQualityGroup({
+      code: current.code,
+      batchCode: current.batchCode || undefined,
+      scanType: scanType.value,
+      bagIds: current.pendingBagIds ?? [],
+      qualifiedWarehouseId: qualifiedWarehouseId.value,
+      reason: reason.value.trim() || undefined,
       indicators: indicators.value.map(({ code: indicatorCode, value, minValue, maxValue }) => ({
         code: indicatorCode,
         value: value as number,
         minValue: minValue as number,
         maxValue: maxValue as number,
       })),
-      reason: reason.value.trim() || undefined,
-      qualifiedWarehouseId: qualifiedWarehouseId.value,
     });
-    message.success(`璐ㄦ鎻愪氦鎴愬姛锛岃川妫�缂栧彿锛�${submittedId.value}`);
+    submittedCount.value = result.processedCount;
+    message.success(`鏈 ${result.processedCount} 琚嬪凡鎻愪氦锛�${passed ? '璐ㄦ鍚堟牸锛屽凡杞叆鎵�閫夊悎鏍煎簱' : '璐ㄦ涓嶅悎鏍硷紝宸茶褰曞師鍥犲苟绉婚櫎鏆傚瓨搴撳瓨'}`);
     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' });
     }
-    resetForm(false);
+    // 妫�楠岃寖鍥村凡娑堣垂锛岄渶閲嶆柊璇嗗埆鍚庢墠鑳芥彁浜や笅涓�鎵癸紝閬垮厤璇妸涓婁竴鎵圭殑娓呭崟鍐嶆鎻愪氦
+    code.value = '';
+    target.value = undefined;
+    resetIndicators();
   } catch (error) {
-    const response = error as { message?: string; msg?: string };
-    message.error(response.message ?? response.msg ?? '璐ㄦ鎻愪氦澶辫触锛岃妫�鏌ヨ〃鍗曞悗閲嶈瘯');
+    message.error(errorText(error));
   } finally {
     loading.value = false;
   }
@@ -129,7 +235,7 @@
   try {
     const warehouses = await getWarehouseSimpleList();
     qualifiedWarehouses.value = (warehouses ?? []).filter(
-      (item) => item.type === MesWmWarehouseTypeEnum.QUALIFIED,
+      (item) => item.type === MesWmWarehouseTypeEnum.QUALIFIED && !item.frozen,
     );
   } catch {
     qualifiedWarehouses.value = [];
@@ -157,14 +263,14 @@
       <div>
         <div class="eyebrow">MES / 璐ㄩ噺绠$悊</div>
         <h1>绉嶅瓙璐ㄦ</h1>
-        <p>褰曞叆绾害銆佸噣搴︺�佸彂鑺界巼鍜屾按鍒嗭紝绯荤粺鏍规嵁鏍囧噯鑼冨洿鑷姩鍒ゅ畾妫�楠岀粨璁�</p>
+        <p>閫夋嫨琚嬬爜銆佹墭鐩樼爜鎴栨壒娆$爜璇嗗埆寰呮鑼冨洿锛屽綍鍏ョ函搴︺�佸噣搴︺�佸彂鑺界巼鍜屾按鍒嗭紝绯荤粺鑷姩鍒ゅ畾妫�楠岀粨璁�</p>
       </div>
-      <div class="header-badge"><IconifyIcon icon="ep:checked" /> 鍥涢」鎸囨爣妫�楠�</div>
+      <div class="header-badge"><IconifyIcon icon="ep:checked" /> 琚嬬爜 / 鎵樼洏鐮� / 鎵规鐮�</div>
     </header>
 
     <section class="summary-grid" aria-label="璐ㄦ鎽樿">
       <div class="summary-card blue"><IconifyIcon icon="ep:document-checked" /><div><span>妫�楠岄」鐩�</span><strong>4</strong><small>绾害銆佸噣搴︺�佸彂鑺界巼銆佹按鍒�</small></div></div>
-      <div class="summary-card green"><IconifyIcon icon="ep:success-filled" /><div><span>褰撳墠鍚堟牸椤�</span><strong>{{ passedCount }} / 4</strong><small>瀹炴椂渚濇嵁涓婁笅闄愬垽鏂�</small></div></div>
+      <div class="summary-card" :class="target ? 'green' : 'blue'"><IconifyIcon icon="ep:box" /><div><span>鏈寰呮</span><strong>{{ target ? `${pendingCount} 琚媊 : '寰呰瘑鍒�' }}</strong><small>{{ target ? `${SCAN_TYPE_LABEL[scanType]} ${target.code}` : `璇烽�夋嫨绫诲瀷鍚庤瘑鍒�${SCAN_TYPE_LABEL[scanType]}` }}</small></div></div>
       <div class="summary-card" :class="overallPassed ? 'green' : allEntered ? 'red' : 'blue'"><IconifyIcon :icon="overallPassed ? 'ep:circle-check-filled' : allEntered ? 'ep:warning-filled' : 'ep:edit-pen'" /><div><span>鎬讳綋缁撹</span><strong>{{ overallPassed ? '鍚堟牸' : allEntered ? '涓嶅悎鏍�' : '寰呭綍鍏�' }}</strong><small>{{ overallPassed ? '鍙彁浜ゅ悎鏍肩粨鏋�' : allEntered ? '璇峰~鍐欎笉鍚堟牸鍘熷洜' : `宸插綍鍏� ${enteredCount} / 4 椤筦 }}</small></div></div>
     </section>
 
@@ -172,19 +278,43 @@
       <a-card class="form-card" :bordered="false">
         <template #title><div class="card-title"><IconifyIcon icon="ep:edit-pen" />璐ㄦ淇℃伅</div></template>
         <a-form layout="vertical">
+          <a-form-item label="鎵爜绫诲瀷" required>
+            <div class="scan-row">
+              <a-select v-model:value="scanType" class="scan-type" size="large" :options="SCAN_TYPE_OPTIONS" :disabled="loading" @change="onScanTypeChange" />
+              <a-input-search v-model:value="code" class="scan-input" size="large" :disabled="loading" :loading="resolving" enter-button="璇嗗埆" :placeholder="`璇疯緭鍏ユ垨鎵弿${SCAN_TYPE_LABEL[scanType]}`" @search="identify" />
+            </div>
+            <div class="scan-hint"><IconifyIcon icon="ep:info-filled" /> 琚嬬爜鎸� 1 琚嬨�佹墭鐩樼爜鎸夊凡瑁呰鏁般�佹壒娆$爜鎸夋壒娆¤鏁扮‘瀹氭湰娆″緟妫�鑼冨洿锛屽凡妫�涓庝綔搴熻鐮佷笉閲嶅澶勭悊</div>
+          </a-form-item>
+
+          <div v-if="target" class="target-panel">
+            <div class="target-head">
+              <span class="target-type">{{ SCAN_TYPE_LABEL[scanType] }}宸茶瘑鍒�</span>
+              <span class="target-code">{{ target.code }}</span>
+              <a-tag v-if="target.businessCode" color="blue">涓氬姟鐮� {{ target.businessCode }}</a-tag>
+              <a-tag v-if="target.batchCode" color="geekblue">鎵规 {{ target.batchCode }}</a-tag>
+            </div>
+            <div class="target-stats">
+              <div><span>鏈夋晥琚嬫暟</span><strong>{{ target.totalCount ?? 0 }}</strong></div>
+              <div><span>宸叉琚嬫暟</span><strong>{{ completedCount }}</strong></div>
+              <div class="highlight"><span>鏈寰呮</span><strong>{{ pendingCount }} 琚�</strong></div>
+            </div>
+            <p v-if="isGroupScan" class="target-warning">浠ヤ笅鎸囨爣涓庢楠岀粨璁哄皢搴旂敤浜庢湰娆″叏閮� {{ pendingCount }} 琚嬶紝璇风‘璁ゆ娊鏍风粨鏋滈�傜敤浜庤鑼冨洿銆�</p>
+          </div>
+          <a-alert v-else class="target-empty" type="info" show-icon message="灏氭湭璇嗗埆璐ㄦ瀵硅薄" description="鍏堥�夋嫨鎵爜绫诲瀷骞惰瘑鍒爜鍊硷紝绯荤粺浼氬甫鍑鸿瀵硅薄鐨勬湁鏁堣鏁般�佸凡妫�琚嬫暟涓庢湰娆″緟妫�琚嬫暟銆�" />
+
           <div class="base-grid">
-            <a-form-item label="琚嬬爜" required><a-input v-model:value="code" size="large" :disabled="loading" placeholder="璇疯緭鍏ユ垨鎵弿琚嬬爜" /></a-form-item>
-            <a-form-item label="鐢熶骇鎵规"><a-input v-model:value="batchCode" size="large" :disabled="loading" placeholder="璇疯緭鍏ユ壒娆″彿锛堥�夊~锛�" /></a-form-item>
-            <a-form-item label="鍏ュ簱鍚堟牸搴�" required class="full-width">
+            <a-form-item label="鐢熶骇鎵规"><a-input :value="target?.batchCode ?? ''" size="large" disabled placeholder="璇嗗埆鍚庤嚜鍔ㄥ甫鍑�" /></a-form-item>
+            <a-form-item label="鍏ュ簱鍚堟牸搴�" required>
               <a-select
                 v-model:value="qualifiedWarehouseId"
                 size="large"
                 :disabled="loading"
+                :options="qualifiedWarehouseOptions"
                 placeholder="璇烽�夋嫨璐ㄦ鍚堟牸鍚庡叆搴撶殑鍚堟牸搴�"
-                :options="qualifiedWarehouses.map((item) => ({ label: `${item.name}锛�${item.code}锛塦, value: item.id }))"
               />
             </a-form-item>
           </div>
+          <p v-if="!qualifiedWarehouses.length" class="target-warning">鏆傛棤鍙敤鍚堟牸搴擄紝璇疯仈绯荤鐞嗗憳閰嶇疆鍚庡啀鎻愪氦璐ㄦ銆�</p>
 
           <a-divider orientation="left">鎸囨爣缁撴灉</a-divider>
           <div class="indicator-list">
@@ -200,16 +330,16 @@
           </div>
 
           <a-form-item label="涓嶅悎鏍煎師鍥�" :required="!overallPassed" class="reason-item"><a-textarea v-model:value="reason" :disabled="loading" :rows="3" :maxlength="500" show-count :placeholder="overallPassed ? '鍚堟牸缁撴灉鏃犻渶濉啓' : '璇疯鏄庝笉鍚堟牸鍘熷洜'" /></a-form-item>
-          <div class="form-actions"><a-button :disabled="loading" @click="resetForm">閲嶇疆</a-button><a-button type="primary" size="large" :loading="loading" @click="submit"><IconifyIcon icon="ep:upload-filled" />鎻愪氦璐ㄦ</a-button></div>
+          <div class="form-actions"><a-button :disabled="loading" @click="resetForm">閲嶇疆</a-button><a-button type="primary" size="large" :loading="loading" :disabled="!target" @click="submit"><IconifyIcon icon="ep:upload-filled" />鎻愪氦璐ㄦ</a-button></div>
         </a-form>
       </a-card>
 
       <a-card class="result-card" :bordered="false">
         <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>
-        <a-alert message="搴撳瓨娴佽浆鐢卞悗绔鐞�" description="鍚堟牸鍝佸皢鎸夋墍閫夈�屽叆搴撳悎鏍煎簱銆嶄粠鏆傚瓨搴撳瓨鍏ヨ鍚堟牸搴擄紱涓嶅悎鏍煎搧灏嗙洿鎺ユ竻闄ゆ殏瀛樺簱瀛樺苟淇濈暀涓嶅悎鏍煎師鍥犮�傝川妫�鍛樺拰妫�楠屾椂闂寸敱鏈嶅姟绔褰曘��" type="info" show-icon />
-        <div v-if="submittedId" class="success-note"><IconifyIcon icon="ep:success-filled" /><span>鏈�杩戞彁浜よ川妫�缂栧彿锛�<strong>{{ submittedId }}</strong></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 />
+        <div v-if="submittedCount" class="success-note"><IconifyIcon icon="ep:success-filled" /><span>鏈�杩戞彁浜よ川妫�锛�<strong>{{ submittedCount }} 琚�</strong></span></div>
       </a-card>
     </section>
   </div>
@@ -218,9 +348,11 @@
 <style scoped>
 .quality-page { min-height: 100%; padding: 24px; background: #f4f7fb; color: #172033; }
 .page-header { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 22px; }.eyebrow { color: #1677ff; font-size: 12px; font-weight: 700; letter-spacing: .08em; }.page-header h1 { margin: 6px 0; font-size: 28px; }.page-header p { margin: 0; color: #718096; }.header-badge { display: inline-flex; align-items: center; gap: 8px; padding: 9px 13px; border: 1px solid #cfe0fb; border-radius: 7px; color: #1677ff; background: #f0f6ff; font-size: 13px; font-weight: 600; }
-.summary-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 18px; }.summary-card { display: flex; align-items: flex-start; gap: 14px; padding: 17px 19px; border: 1px solid #e8edf5; border-radius: 10px; background: #fff; box-shadow: 0 3px 12px #243b5310; }.summary-card > .iconify { flex: 0 0 38px; width: 38px; height: 38px; padding: 9px; border-radius: 8px; font-size: 20px; }.summary-card.blue > .iconify { color: #1677ff; background: #eaf3ff; }.summary-card.green > .iconify { color: #17a673; background: #e6f8f0; }.summary-card.red > .iconify { color: #e05252; background: #fff0f0; }.summary-card span, .summary-card small { display: block; color: #7b8799; font-size: 12px; }.summary-card strong { display: block; margin: 4px 0; font-size: 23px; }.content-grid { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(310px, .75fr); gap: 18px; }.form-card, .result-card { border-radius: 10px; box-shadow: 0 3px 12px #243b5310; }.card-title { display: flex; align-items: center; gap: 9px; font-weight: 700; }.card-title :deep(.iconify) { color: #1677ff; }.base-grid { display: grid; grid-template-columns: 1.3fr 1.2fr; gap: 16px; }.full-width { width: 100%; }
+.summary-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 18px; }.summary-card { display: flex; align-items: flex-start; gap: 14px; padding: 17px 19px; border: 1px solid #e8edf5; border-radius: 10px; background: #fff; box-shadow: 0 3px 12px #243b5310; }.summary-card > .iconify { flex: 0 0 38px; width: 38px; height: 38px; padding: 9px; border-radius: 8px; font-size: 20px; }.summary-card.blue > .iconify { color: #1677ff; background: #eaf3ff; }.summary-card.green > .iconify { color: #17a673; background: #e6f8f0; }.summary-card.red > .iconify { color: #e05252; background: #fff0f0; }.summary-card span, .summary-card small { display: block; color: #7b8799; font-size: 12px; }.summary-card small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.summary-card strong { display: block; margin: 4px 0; font-size: 23px; }.content-grid { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(310px, .75fr); gap: 18px; }.form-card, .result-card { border-radius: 10px; box-shadow: 0 3px 12px #243b5310; }.card-title { display: flex; align-items: center; gap: 9px; font-weight: 700; }.card-title :deep(.iconify) { color: #1677ff; }.base-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }.full-width { width: 100%; }
+.scan-row { display: flex; align-items: stretch; gap: 10px; }.scan-type { flex: 0 0 140px; }.scan-input { flex: 1 1 auto; min-width: 0; }.scan-hint { display: flex; align-items: center; gap: 8px; margin-top: 10px; color: #8793a5; font-size: 12px; line-height: 1.5; }.scan-hint :deep(.iconify) { flex: 0 0 auto; color: #1677ff; }
+.target-panel { margin-bottom: 18px; padding: 14px 16px; border: 1px solid #d6e8ff; border-radius: 8px; background: #f7fbff; }.target-head { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; }.target-type { color: #1677ff; font-size: 13px; font-weight: 700; }.target-code { font-size: 16px; font-weight: 700; letter-spacing: .02em; }.target-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-top: 12px; }.target-stats div { padding: 9px 12px; border: 1px solid #e8edf5; border-radius: 6px; background: #fff; }.target-stats span { display: block; color: #7b8799; font-size: 12px; }.target-stats strong { display: block; margin-top: 3px; font-size: 19px; }.target-stats .highlight { border-color: #b7ebd2; background: #f2fcf7; }.target-stats .highlight strong { color: #16835b; }.target-warning { margin: 12px 0 0; color: #c07a17; font-size: 12px; line-height: 1.6; }.target-empty { margin-bottom: 18px; }
 .indicator-list { display: flex; flex-direction: column; gap: 10px; }.indicator-row { display: flex; align-items: center; gap: 9px; padding: 11px 12px; border: 1px solid #e8edf5; border-radius: 7px; background: #fbfcfe; }.indicator-row.failed { border-color: #ffd6d6; background: #fffafa; }.indicator-name { display: flex; align-items: center; gap: 8px; min-width: 82px; }.indicator-name small { color: #8793a5; font-size: 12px; }.indicator-dot { width: 8px; height: 8px; border-radius: 50%; }.indicator-dot.pass { background: #20b878; }.indicator-dot.fail { background: #e05252; }.indicator-input { width: 105px; }.range-input { width: 88px; }.range-separator { margin-left: auto; color: #8793a5; font-size: 12px; }.reason-item { margin-top: 18px; }.form-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 5px; }.form-actions :deep(.iconify) { margin-right: 5px; }
 .result-hero { display: flex; flex-direction: column; align-items: center; gap: 8px; margin: 8px 0 22px; padding: 25px 15px; border-radius: 8px; text-align: center; }.result-hero :deep(.iconify) { font-size: 35px; }.result-hero strong { font-size: 18px; }.result-hero span { color: #718096; font-size: 12px; }.result-hero.passed { color: #16835b; background: #effaf5; }.result-hero.failed-result { color: #c53e3e; background: #fff3f3; }.result-hero.pending-result { color: #1677ff; background: #f0f6ff; }.rule-list { display: flex; flex-direction: column; gap: 14px; margin-bottom: 22px; }.rule-list div { display: flex; align-items: center; gap: 9px; color: #59677b; font-size: 13px; }.rule-list :deep(.iconify) { color: #20b878; }.success-note { display: flex; align-items: center; gap: 8px; margin-top: 16px; padding: 11px 12px; border-radius: 7px; color: #16835b; background: #effaf5; font-size: 13px; }
 @media (max-width: 1050px) { .content-grid { grid-template-columns: 1fr; }.base-grid { grid-template-columns: repeat(2, 1fr); } }
-@media (max-width: 760px) { .quality-page { padding: 14px; }.page-header { display: block; }.header-badge { margin-top: 14px; }.summary-grid, .base-grid { grid-template-columns: 1fr; }.indicator-row { flex-wrap: wrap; }.range-separator { margin-left: 0; }.indicator-input { flex: 1; min-width: 105px; }.range-input { flex: 1; min-width: 75px; } }
+@media (max-width: 760px) { .quality-page { padding: 14px; }.page-header { display: block; }.header-badge { margin-top: 14px; }.summary-grid, .base-grid, .target-stats { grid-template-columns: 1fr; }.scan-row { flex-wrap: wrap; }.scan-type { flex-basis: 100%; }.indicator-row { flex-wrap: wrap; }.range-separator { margin-left: 0; }.indicator-input { flex: 1; min-width: 105px; }.range-input { flex: 1; min-width: 75px; } }
 </style>

--
Gitblit v1.9.3