| | |
| | | |
| | | import { message, Modal } from 'ant-design-vue'; |
| | | import { IconifyIcon } from '@vben/icons'; |
| | | import { MesWmWarehouseTypeEnum } from '@vben/constants'; |
| | | import { gsap } from 'gsap'; |
| | | |
| | | import MdItemSelect from '#/views/mes/md/item/components/select.vue'; |
| | | |
| | | import { |
| | | submitSeedQuality, |
| | | type MesQcSeedQualityApi, |
| | | } from '#/api/mes/qc/seedQuality'; |
| | | import type { MesWmWarehouseApi } from '#/api/mes/wm/warehouse'; |
| | | import { submitSeedQuality } from '#/api/mes/qc/seedQuality'; |
| | | import { getWarehouseSimpleList } from '#/api/mes/wm/warehouse'; |
| | | |
| | | interface IndicatorForm { |
| | | code: string; |
| | |
| | | |
| | | const pageRoot = ref<HTMLElement>(); |
| | | const code = ref(''); |
| | | const itemId = ref<number>(); |
| | | const batchCode = ref(''); |
| | | const reason = ref(''); |
| | | const loading = ref(false); |
| | | const submittedId = ref<number>(); |
| | | let animationContext: gsap.Context | undefined; |
| | | |
| | | /** 可选合格仓库(仓库类型=20 合格库) */ |
| | | const qualifiedWarehouses = ref<MesWmWarehouseApi.Warehouse[]>([]); |
| | | const qualifiedWarehouseId = ref<number>(); |
| | | |
| | | const indicators = ref<IndicatorForm[]>([ |
| | | { code: 'purity', label: '纯度', unit: '%', minValue: 0, maxValue: 100 }, |
| | |
| | | |
| | | function resetForm(clearResult = true) { |
| | | code.value = ''; |
| | | itemId.value = undefined; |
| | | batchCode.value = ''; |
| | | reason.value = ''; |
| | | if (clearResult) submittedId.value = undefined; |
| | |
| | | } |
| | | |
| | | async function submit() { |
| | | if (!code.value.trim() || !itemId.value) { |
| | | message.warning('请填写袋码并选择物料'); |
| | | if (!code.value.trim()) { |
| | | message.warning('请填写袋码'); |
| | | 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)) { |
| | |
| | | try { |
| | | submittedId.value = await submitSeedQuality({ |
| | | code: code.value.trim(), |
| | | itemId: itemId.value, |
| | | batchCode: batchCode.value.trim() || undefined, |
| | | indicators: indicators.value.map(({ code: indicatorCode, value, minValue, maxValue }) => ({ |
| | | code: indicatorCode, |
| | |
| | | maxValue: maxValue as number, |
| | | })), |
| | | reason: reason.value.trim() || undefined, |
| | | qualifiedWarehouseId: qualifiedWarehouseId.value, |
| | | }); |
| | | message.success(`质检提交成功,质检编号:${submittedId.value}`); |
| | | if (pageRoot.value && !window.matchMedia('(prefers-reduced-motion: reduce)').matches) { |
| | |
| | | } |
| | | |
| | | onMounted(async () => { |
| | | try { |
| | | const warehouses = await getWarehouseSimpleList(); |
| | | qualifiedWarehouses.value = (warehouses ?? []).filter( |
| | | (item) => item.type === MesWmWarehouseTypeEnum.QUALIFIED, |
| | | ); |
| | | } catch { |
| | | qualifiedWarehouses.value = []; |
| | | } |
| | | await nextTick(); |
| | | if (!pageRoot.value) return; |
| | | animationContext = gsap.context(() => { |
| | |
| | | <a-form layout="vertical"> |
| | | <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="物料" required> |
| | | <MdItemSelect v-model="itemId" :disabled="loading" size="large" 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-select |
| | | v-model:value="qualifiedWarehouseId" |
| | | size="large" |
| | | :disabled="loading" |
| | | placeholder="请选择质检合格后入库的合格库" |
| | | :options="qualifiedWarehouses.map((item) => ({ label: `${item.name}(${item.code})`, value: item.id }))" |
| | | /> |
| | | </a-form-item> |
| | | </div> |
| | | |
| | | <a-divider orientation="left">指标结果</a-divider> |
| | |
| | | <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 /> |
| | | <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> |
| | | </a-card> |
| | | </section> |
| | |
| | | <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 1fr 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 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%; } |
| | | .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(3, 1fr); } } |
| | | @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; } } |
| | | </style> |