| | |
| | | <script setup lang="ts"> |
| | | 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 { MesWmWarehouseTypeEnum } from '@vben/constants'; |
| | | |
| | | import { |
| | | approveSeedQualityBatch, |
| | | getSeedIndicatorRanges, |
| | | getSeedQualityPage, |
| | | resolveSeedQuality, |
| | | resolveSeedRetestScope, |
| | | submitSeedQualityGroup, |
| | | submitSeedRetestScope, |
| | | type SeedQualityRecord, |
| | | type SeedQualityScan, |
| | | type SeedRetestScope, |
| | | } from '#/api/mes/qc/seedQuality'; |
| | | import { getWarehouseSimpleList } from '#/api/mes/wm/warehouse'; |
| | | import { errorText } from '#/mobile/api'; |
| | | import { extractCode } from '#/mobile/access'; |
| | | |
| | | import Scanner from './scanner.vue'; |
| | | |
| | | const rows = ref<SeedQualityRecord[]>([]), busy = ref(false), error = ref(''); |
| | | type ReviewGroup = { ids: number[]; stage: 'company' | 'process'; status: number }; |
| | | |
| | | const { hasAccessByCodes } = useAccess(); |
| | | const canInitial = computed(() => hasAccessByCodes(['mes:qc-seed-quality:submit'])); |
| | | 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 canProcessReview = computed(() => hasAccessByCodes(['mes:qc-seed-quality:process-approve'])); |
| | | const canCompanyReview = computed(() => hasAccessByCodes(['mes:qc-seed-quality:company-approve'])); |
| | | const canBatchAction = computed(() => canInitial.value || canRetest.value || canProcessReview.value || canCompanyReview.value); |
| | | |
| | | const rows = ref<SeedQualityRecord[]>([]), busy = ref(false), error = ref(''), notice = ref(''); |
| | | const scanType = ref<'BATCH' | 'PALLET'>('PALLET'), scannedCode = ref(''); |
| | | const scope = ref<SeedRetestScope>(), initialScope = ref<SeedQualityScan>(); |
| | | const workflowStatus = ref<10 | 50>(10), reason = ref(''); |
| | | const warehouseId = ref<number>(); |
| | | const warehouseOptions = ref<Array<{ label: string; value: number }>>([]); |
| | | const reviewGroup = ref<ReviewGroup>(), reviewApproved = ref(true), reviewOpinion = 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 initialIds = computed(() => initialScope.value?.pendingBagIds || []); |
| | | 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; } |
| | | const complete = computed(() => indicators.value.every((item) => |
| | | [item.value, item.minValue, item.maxValue].every((value) => String(value).trim() !== '' && Number.isFinite(Number(value))) |
| | | && Number(item.value) >= 0 && Number(item.value) <= 100 && Number(item.minValue) >= 0 |
| | | && Number(item.maxValue) <= 100 && Number(item.minValue) <= Number(item.maxValue))); |
| | | const passed = computed(() => complete.value && indicators.value.every((item) => |
| | | Number(item.value) >= Number(item.minValue) && Number(item.value) <= Number(item.maxValue))); |
| | | const reviewGroups = computed<ReviewGroup[]>(() => { |
| | | if (!scope.value) return []; |
| | | return [ |
| | | { status: 20, stage: 'process' as const, ids: scope.value.pendingProcessIds || [] }, |
| | | { status: 25, stage: 'process' as const, ids: scope.value.pendingInitialFailIds || [] }, |
| | | { status: 30, stage: 'company' as const, ids: scope.value.pendingCompanyIds || [] }, |
| | | { status: 60, stage: 'company' as const, ids: scope.value.pendingCompanyFailIds || [] }, |
| | | ].filter((group) => group.ids.length && (group.stage === 'process' ? canProcessReview.value : canCompanyReview.value)); |
| | | }); |
| | | const statusNames: Record<number, string> = { |
| | | 10: '待复检', 20: '待初检审核', 25: '待初检不合格处理', 30: '待复检审核', |
| | | 40: '已放行', 50: '待重新初检', 60: '待复检不合格处理', 70: '已报废', |
| | | }; |
| | | |
| | | function resetIndicators() { |
| | | indicators.value.forEach((item) => { item.value = ''; }); |
| | | reason.value = ''; |
| | | } |
| | | 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; |
| | | |
| | | async function hydrateReviewIds(current: SeedRetestScope, rawCode: string) { |
| | | const loaded = (current.pendingProcessIds?.length || 0) + (current.pendingInitialFailIds?.length || 0) |
| | | + (current.pendingCompanyIds?.length || 0) + (current.pendingCompanyFailIds?.length || 0); |
| | | if (loaded >= current.pendingProcessCount + current.pendingCompanyCount) return; |
| | | const batch = current.batchCode || (current.scanType === 'BATCH' ? current.code : undefined); |
| | | if (!batch) return; |
| | | const statuses = [20, 25, 30, 60] as const; |
| | | const pages = await Promise.all(statuses.map((workflow) => getSeedQualityPage({ |
| | | batchCode: batch, pageNo: 1, pageSize: 500, workflowStatus: workflow, |
| | | }).catch(() => ({ list: [] as SeedQualityRecord[], total: 0 })))); |
| | | const scopeCodes = new Set([current.code, rawCode]); |
| | | const ids = pages.map(({ list }) => list |
| | | .filter((row) => current.scanType === 'BATCH' || (!!row.palletCode && scopeCodes.has(row.palletCode))) |
| | | .map((row) => row.id)); |
| | | current.pendingProcessIds = ids[0]; |
| | | current.pendingInitialFailIds = ids[1]; |
| | | current.pendingCompanyIds = ids[2]; |
| | | current.pendingCompanyFailIds = ids[3]; |
| | | } |
| | | |
| | | async function identifyScope(raw: string) { |
| | | if (busy.value || !canBatchAction.value) return; |
| | | busy.value = true; error.value = ''; notice.value = ''; |
| | | scope.value = undefined; initialScope.value = undefined; reviewGroup.value = undefined; |
| | | try { |
| | | const codeValue = extractCode(raw); |
| | | scannedCode.value = codeValue; |
| | | const current = await resolveSeedRetestScope(codeValue, scanType.value); |
| | | await hydrateReviewIds(current, codeValue); |
| | | scope.value = current; |
| | | scanType.value = current.scanType; |
| | | workflowStatus.value = current.retestIds.length ? 10 : 50; |
| | | initialScope.value = await resolveSeedQuality(codeValue, current.scanType); |
| | | resetIndicators(); |
| | | if (!initialIds.value.length && !targetIds.value.length && !reviewGroups.value.length) { |
| | | notice.value = '当前托盘或生产批次没有可处理的质检记录。'; |
| | | } |
| | | } catch (exception) { error.value = errorText(exception); } |
| | | finally { busy.value = false; } |
| | | } |
| | | |
| | | function indicatorPayload() { |
| | | return indicators.value.map((item) => ({ code: item.code, value: Number(item.value), minValue: Number(item.minValue), maxValue: Number(item.maxValue) })); |
| | | } |
| | | |
| | | async function refreshScopeAndList(success: string) { |
| | | await identifyScope(scannedCode.value); |
| | | await load(); |
| | | notice.value = success; |
| | | } |
| | | |
| | | async function submitInitial() { |
| | | const ids = initialIds.value.slice(0, 500); |
| | | if (busy.value || !canInitial.value || !initialScope.value || !ids.length || !complete.value) return; |
| | | if (!warehouseId.value) { error.value = '请选择检验合格后的目标合格库'; return; } |
| | | if (!passed.value && !reason.value.trim()) { error.value = '初检不合格请填写原因'; return; } |
| | | if (!window.confirm(`确认批量提交 ${ids.length} 袋初检?`)) 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 result = await submitSeedQualityGroup({ code: initialScope.value.code, batchCode: initialScope.value.batchCode, |
| | | scanType: initialScope.value.scanType || scanType.value, bagIds: ids, qualifiedWarehouseId: warehouseId.value, |
| | | reason: reason.value.trim() || undefined, indicators: indicatorPayload() }); |
| | | busy.value = false; |
| | | await refreshScopeAndList(`已完成 ${result.processedCount} 袋初检。`); |
| | | } catch (exception) { error.value = errorText(exception) + '。请重新识别后核对待检清单。'; } |
| | | finally { busy.value = false; } |
| | | } |
| | | |
| | | async function submitRetest() { |
| | | const ids = targetIds.value.slice(0, 500); |
| | | if (busy.value || !canRetest.value || !scope.value || !ids.length || !complete.value) return; |
| | | if (!passed.value && !reason.value.trim()) { error.value = '检验不合格请填写原因'; return; } |
| | | if (!window.confirm(`确认将本次指标用于 ${ids.length} 袋${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: ids, reason: reason.value.trim() || undefined, indicators: indicatorPayload() }); |
| | | busy.value = false; |
| | | await refreshScopeAndList(`已完成 ${result.processedCount} 袋检验。`); |
| | | } catch (exception) { error.value = errorText(exception) + '。请重新识别后核对待检清单。'; } |
| | | finally { busy.value = false; } |
| | | } |
| | | |
| | | function selectReview(group: ReviewGroup) { |
| | | reviewGroup.value = group; reviewApproved.value = true; reviewOpinion.value = ''; error.value = ''; |
| | | } |
| | | |
| | | async function submitReview() { |
| | | if (busy.value || !reviewGroup.value?.ids.length) return; |
| | | if (!reviewApproved.value && !reviewOpinion.value.trim()) { error.value = '驳回时请填写审核意见'; return; } |
| | | const ids = reviewGroup.value.ids.slice(0, 500); |
| | | if (!window.confirm(`确认${reviewApproved.value ? '通过' : '驳回'}本次 ${ids.length} 条记录?`)) return; |
| | | busy.value = true; error.value = ''; |
| | | try { |
| | | await approveSeedQualityBatch(reviewGroup.value.stage, { ids, approved: reviewApproved.value, opinion: reviewOpinion.value.trim() }); |
| | | busy.value = false; |
| | | await refreshScopeAndList(`已完成 ${ids.length} 条审核。`); |
| | | } catch (exception) { error.value = errorText(exception) + '。请重新识别后核对待审核清单。'; } |
| | | finally { busy.value = false; } |
| | | } |
| | | |
| | | 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 } = {}; |
| | | let filters: { batchCode?: string; code?: string; result?: number } = {}; |
| | | async function load(reset = true) { |
| | | if (busy.value) return; |
| | | busy.value = true; error.value = ''; |
| | | const next = reset ? 1 : page.value + 1; |
| | | const query = reset ? { code: code.value.trim() || undefined, batchCode: batchCode.value.trim() || undefined, result: result.value ? Number(result.value) : undefined } : filters; |
| | | const query = reset ? { code: code.value.trim() || undefined, batchCode: batchCode.value.trim() || undefined, |
| | | result: result.value ? Number(result.value) : undefined } : filters; |
| | | try { |
| | | const data = await getSeedQualityPage({ ...query, pageNo: next, pageSize: 20 }); |
| | | rows.value = reset ? data.list : [...rows.value, ...data.list]; |
| | | page.value = next; total.value = data.total; filters = query; |
| | | } catch (e) { error.value = errorText(e); } finally { busy.value = false; } |
| | | } catch (exception) { error.value = errorText(exception); } |
| | | finally { busy.value = false; } |
| | | } |
| | | onMounted(() => load()); |
| | | |
| | | onMounted(async () => { |
| | | if (canInitial.value) { |
| | | try { |
| | | const warehouses = await getWarehouseSimpleList(); |
| | | warehouseOptions.value = warehouses.filter((item) => item.type === MesWmWarehouseTypeEnum.QUALIFIED && !item.frozen && item.id) |
| | | .map((item) => ({ label: item.code ? `${item.name}(${item.code})` : item.name || '', value: item.id! })); |
| | | if (warehouseOptions.value.length === 1) warehouseId.value = warehouseOptions.value[0]?.value; |
| | | } catch { warehouseOptions.value = []; } |
| | | } |
| | | if (canInitial.value || canRetest.value) { |
| | | try { |
| | | const ranges = await getSeedIndicatorRanges(); |
| | | indicators.value.forEach((item) => { const range = ranges.find((row) => row.indicatorCode === item.code); |
| | | if (range) { item.minValue = String(range.minValue); item.maxValue = String(range.maxValue); } }); |
| | | } catch { /* 合格范围仍可手工填写 */ } |
| | | } |
| | | await 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 ? '正在提交…' : `提交 ${targetIds.length} 袋质检`}}</button> |
| | | </form> |
| | | <section v-if="canBatchAction" 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; initialScope = undefined"><option value="PALLET">托盘码</option><option value="BATCH">生产批次码</option></select></label> |
| | | <Scanner :disabled="busy" @code="identifyScope" /> |
| | | |
| | | <template v-if="scope"> |
| | | <div class="m-panel"><h3 class="m-title">已识别范围</h3><dl><div class="m-row"><dt>码值</dt><dd>{{ scope.code }}</dd></div><div class="m-row"><dt>生产批次</dt><dd>{{ scope.batchCode || '—' }}</dd></div><div class="m-row"><dt>有效袋数</dt><dd>{{ scope.totalCount }}</dd></div></dl></div> |
| | | |
| | | <form v-if="canInitial && initialIds.length" class="m-panel" @submit.prevent="submitInitial"> |
| | | <h3 class="m-title">批量初检({{ Math.min(initialIds.length, 500) }} 袋)</h3> |
| | | <p v-if="!initialScope?.inbound" class="m-warning">该范围的有效袋码尚未全部入库,暂不能提交初检。</p> |
| | | <template v-else> |
| | | <label class="m-field"><span>目标合格库</span><select v-model="warehouseId" required :disabled="busy"><option :value="undefined" disabled>请选择合格库</option><option v-for="item in warehouseOptions" :key="item.value" :value="item.value">{{ item.label }}</option></select></label> |
| | | <fieldset v-for="item in indicators" :key="`initial-${item.code}`" class="m-panel m-quality-indicator" :disabled="busy"><legend>{{ item.name }}(%)</legend><label class="m-field"><span>实测值</span><input v-model="item.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="item.minValue" type="number" inputmode="decimal" min="0" max="100" step="any" required /></label><label class="m-field"><span>合格上限</span><input v-model="item.maxValue" type="number" inputmode="decimal" min="0" max="100" step="any" required /></label></div></fieldset> |
| | | <label class="m-field"><span>{{ complete && !passed ? '不合格原因(必填)' : '检验备注' }}</span><textarea v-model="reason" rows="3" maxlength="500" /></label> |
| | | <button class="m-primary" :disabled="busy || !complete || !warehouseId">{{ busy ? '正在提交…' : `提交 ${Math.min(initialIds.length, 500)} 袋初检` }}</button> |
| | | </template> |
| | | </form> |
| | | |
| | | <section v-if="reviewGroups.length" class="m-panel"> |
| | | <h3 class="m-title">批量审核</h3> |
| | | <button v-for="group in reviewGroups" :key="group.status" type="button" class="m-secondary" :disabled="busy" @click="selectReview(group)">{{ statusNames[group.status] }} · {{ Math.min(group.ids.length, 500) }} 条</button> |
| | | <form v-if="reviewGroup" @submit.prevent="submitReview"> |
| | | <label class="m-field"><span>审核结论</span><select v-model="reviewApproved" :disabled="busy"><option :value="true">通过 / 确认处理</option><option :value="false">驳回重新检验</option></select></label> |
| | | <label class="m-field"><span>{{ reviewApproved ? '审核意见(选填)' : '驳回意见(必填)' }}</span><textarea v-model="reviewOpinion" rows="3" maxlength="500" /></label> |
| | | <button class="m-primary" :disabled="busy">{{ busy ? '正在审核…' : `确认审核 ${Math.min(reviewGroup.ids.length, 500)} 条` }}</button> |
| | | </form> |
| | | </section> |
| | | |
| | | <form v-if="canRetest && (scope.retestIds.length || scope.initialRecheckIds.length)" class="m-panel" @submit.prevent="submitRetest"> |
| | | <h3 class="m-title">批量复检 / 重新初检</h3> |
| | | <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">同一组指标将用于本次 {{ Math.min(targetIds.length, 500) }} 袋,请核对抽样结果。</p> |
| | | <fieldset v-for="item in indicators" :key="`retest-${item.code}`" class="m-panel m-quality-indicator" :disabled="busy"><legend>{{ item.name }}(%)</legend><label class="m-field"><span>实测值</span><input v-model="item.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="item.minValue" type="number" inputmode="decimal" min="0" max="100" step="any" required /></label><label class="m-field"><span>合格上限</span><input v-model="item.maxValue" type="number" inputmode="decimal" min="0" max="100" step="any" required /></label></div></fieldset> |
| | | <label class="m-field"><span>{{ complete && !passed ? '不合格原因(必填)' : '检验备注' }}</span><textarea v-model="reason" rows="3" maxlength="500" /></label> |
| | | <button class="m-primary" :disabled="busy || !complete || !targetIds.length">{{ busy ? '正在提交…' : `提交 ${Math.min(targetIds.length, 500)} 袋检验` }}</button> |
| | | </form> |
| | | </template> |
| | | </section> |
| | | <p v-if="notice" class="m-success" role="status">{{notice}}</p> |
| | | <section v-else class="m-panel"><h2 class="m-title">批量检验与审核</h2><p class="m-muted">当前账号只有质检记录查看权限。如需批量操作,请联系管理员配置初检、复检或审核权限。</p></section> |
| | | |
| | | <p v-if="notice" class="m-success" role="status">{{ notice }}</p> |
| | | <p v-if="error" class="m-error" role="alert">{{ error }}</p> |
| | | <p class="m-muted">查看质检结果、批次与不合格原因。</p> |
| | | <Scanner :disabled="busy" @code="value => { code = value; load(); }" /> |
| | | <form class="m-panel" @submit.prevent="load()"> |
| | | <label class="m-field"><span>业务码 / 追溯码</span><input v-model="code" :disabled="busy" placeholder="输入袋码" /></label> |
| | | <label class="m-field"><span>生产批次</span><input v-model="batchCode" :disabled="busy" placeholder="输入批次号" /></label> |
| | | <label class="m-field"><span>质检结果</span><select v-model="result" :disabled="busy"><option value="">全部结果</option><option value="1">合格</option><option value="2">不合格</option></select></label> |
| | | <button class="m-secondary" :disabled="busy">{{busy ? '正在查询…' : '查询记录'}}</button> |
| | | <button class="m-secondary" :disabled="busy">{{ busy ? '正在查询…' : '查询记录' }}</button> |
| | | </form> |
| | | <p v-if="error" class="m-error" role="alert">{{error}}</p> |
| | | <p class="m-muted" role="status">共 {{total}} 条记录</p> |
| | | <p class="m-muted" role="status">共 {{ total }} 条记录</p> |
| | | <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>{{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> |
| | | <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>{{ 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 ? '正在加载…' : '加载更多'}}</button> |
| | | <button v-if="rows.length < total" class="m-secondary" :disabled="busy" @click="load(false)">{{ busy ? '正在加载…' : '加载更多' }}</button> |
| | | </template> |