<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 { errorText } from '#/mobile/api';
|
import { extractCode } from '#/mobile/access';
|
import Scanner from './scanner.vue';
|
|
const rows = ref<SeedQualityRecord[]>([]), busy = ref(false), error = ref('');
|
const { hasAccessByCodes } = useAccess();
|
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 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 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; }
|
}
|
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;
|
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 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 } = {};
|
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;
|
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; }
|
}
|
onMounted(() => 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>
|
<p v-if="notice" class="m-success" role="status">{{notice}}</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>
|
</form>
|
<p v-if="error" class="m-error" role="alert">{{error}}</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>
|
</article>
|
<button v-if="rows.length < total" class="m-secondary" :disabled="busy" @click="load(false)">{{busy ? '正在加载…' : '加载更多'}}</button>
|
</template>
|