From d86ec51d8aa1b4bce02bf72a812e03b4d34a497b Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期五, 16 十月 2026 17:50:46 +0800
Subject: [PATCH] feat(mes): 种子质检支持袋码/托盘码/批次码三种扫码类型
---
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