| | |
| | | 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; |
| | |
| | | } |
| | | |
| | | 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 åæ ¼åºï¼ */ |
| | |
| | | { 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 |
| | |
| | | && 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; |
| | |
| | | }); |
| | | } |
| | | |
| | | /** 忢æ«ç ç±»åï¼ç å¼ä¸å·²è¯å«å¯¹è±¡åæ¶å¤±æï¼é¿å
ç¨éç±»åçç å¼æäº¤ */ |
| | | 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()) { |
| | |
| | | return; |
| | | } |
| | | |
| | | if (!overallPassed.value) { |
| | | const count = current.pendingBagIds?.length ?? 0; |
| | | const scopeNote = isGroupScan.value |
| | | ? `ææ ä¸æ£éªç»è®ºå°åºç¨äºæ¬æ¬¡å
¨é¨ ${count} è¢ï¼è¯·ç¡®è®¤æ½æ ·ç»æéç¨äºè¯¥èå´ã` |
| | | : ''; |
| | | const confirmed = await new Promise<boolean>((resolve) => { |
| | | Modal.confirm({ |
| | | title: '确认æäº¤ä¸åæ ¼ç»æï¼', |
| | | content: 'ä¸åæ ¼åå°ç±åç«¯ä»æååºå䏿¸
é¤ï¼å¹¶ä¿çä¸åæ ¼åå ç¨äºè´¨éåæã', |
| | | title: overallPassed.value ? '确认æäº¤åæ ¼ç»æï¼' : '确认æäº¤ä¸åæ ¼ç»æï¼', |
| | | content: overallPassed.value |
| | | ? `æ¬æ¬¡ ${count} è¢å°ç»ä¸å¤å®åæ ¼ï¼å¹¶ä»æååºå转å
¥æéåæ ¼åºã${scopeNote}` |
| | | : `æ¬æ¬¡ ${count} è¢å°ä»æååºå䏿¸
é¤å¹¶æ è®°ä¸åæ ¼ï¼åæ¶ä¿çä¸åæ ¼åå ç¨äºè´¨éåæã${scopeNote}`, |
| | | okText: '确认æäº¤', |
| | | cancelText: 'è¿åä¿®æ¹', |
| | | okButtonProps: { danger: true }, |
| | | okButtonProps: { danger: !overallPassed.value }, |
| | | onOk: () => resolve(true), |
| | | onCancel: () => resolve(false), |
| | | }); |
| | | }); |
| | | 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; |
| | | } |
| | |
| | | try { |
| | | const warehouses = await getWarehouseSimpleList(); |
| | | qualifiedWarehouses.value = (warehouses ?? []).filter( |
| | | (item) => item.type === MesWmWarehouseTypeEnum.QUALIFIED, |
| | | (item) => item.type === MesWmWarehouseTypeEnum.QUALIFIED && !item.frozen, |
| | | ); |
| | | } catch { |
| | | qualifiedWarehouses.value = []; |
| | |
| | | <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> |
| | | |
| | |
| | | <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"> |
| | |
| | | </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> |
| | |
| | | <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> |