<script setup lang="ts">
|
import { computed, onMounted, ref } from 'vue';
|
import { onBeforeRouteLeave } from 'vue-router';
|
import { useUserStore } from '@vben/stores';
|
import { resolveSeedQuality, submitSeedQualityGroup, type SeedQualityScan } from '#/api/mes/qc/seedQuality';
|
import { getWarehouseSimpleList, type MesWmWarehouseApi } from '#/api/mes/wm/warehouse';
|
import { allowedMobilePath, extractCode } from '#/mobile/access';
|
import { errorText } from '#/mobile/api';
|
import Scanner from './scanner.vue';
|
import Picker from './picker.vue';
|
|
const busy = ref(false), error = ref(''), notice = ref(''), bag = ref<SeedQualityScan>();
|
const warehouses = ref<MesWmWarehouseApi.Warehouse[]>([]), warehouseId = ref<number>();
|
const reason = ref(''), pendingCode = ref('');
|
type ScanType = 'BAG' | 'PALLET' | 'BATCH';
|
const scanType = ref<ScanType>('BAG');
|
const typeNames = { BAG: '袋码', PALLET: '托盘码', BATCH: '生产批次码' };
|
const pendingType = ref<ScanType>('BAG'), pendingIds = ref<number[]>([]);
|
const storageKey = 'mom-mobile-quality-' + useUserStore().userInfo?.id;
|
const indicators = ref([
|
{ code: 'purity', name: '纯度', value: '', minValue: '0', maxValue: '100' },
|
{ code: 'cleanliness', name: '净度', value: '', minValue: '0', maxValue: '100' },
|
{ code: 'germination_rate', name: '发芽率', value: '', minValue: '0', maxValue: '100' },
|
{ code: 'moisture', name: '水分', value: '', minValue: '0', maxValue: '100' },
|
]);
|
const complete = computed(() => indicators.value.every(i => [i.value, i.minValue, i.maxValue].every(v => String(v).trim() !== '' && Number.isFinite(Number(v))) && Number(i.minValue) >= 0 && Number(i.maxValue) <= 100 && Number(i.value) >= 0 && Number(i.value) <= 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)));
|
function clearPending() { pendingCode.value = ''; pendingIds.value = []; sessionStorage.removeItem(storageKey); }
|
async function identify(raw: string) {
|
if (busy.value || pendingCode.value) return;
|
busy.value = true; error.value = ''; notice.value = ''; bag.value = undefined;
|
try {
|
const data = await resolveSeedQuality(extractCode(raw), scanType.value);
|
if (data.canceled || !data.inbound) throw new Error('该对象已作废或有效袋码尚未全部入库,不能质检');
|
if (!data.pendingBagIds?.length) throw new Error('该对象没有待质检袋码,请在质检记录中查看已有结果');
|
bag.value = data; indicators.value.forEach(i => i.value = ''); reason.value = '';
|
} catch (e) { error.value = errorText(e); } finally { busy.value = false; }
|
}
|
async function submit() {
|
if (busy.value || pendingCode.value || !bag.value || !complete.value || !warehouseId.value) return;
|
if (!passed.value && !reason.value.trim()) { error.value = '请填写不合格原因'; return; }
|
const count = bag.value.pendingBagIds!.length;
|
if (!window.confirm(passed.value ? `确认对本次 ${count} 袋统一判定合格?全部将转入所选合格库。` : `确认对本次 ${count} 袋统一判定不合格?全部将从暂存库存移除并标记报废。`)) return;
|
error.value = ''; notice.value = '';
|
pendingCode.value = bag.value.code; pendingType.value = scanType.value; pendingIds.value = [...bag.value.pendingBagIds!];
|
sessionStorage.setItem(storageKey, JSON.stringify({ code: pendingCode.value, scanType: pendingType.value, bagIds: pendingIds.value })); busy.value = true;
|
try {
|
const result = await submitSeedQualityGroup({ code: bag.value.code, scanType: scanType.value, bagIds: pendingIds.value, qualifiedWarehouseId: warehouseId.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) })) });
|
clearPending(); notice.value = `本次 ${result.processedCount} 袋` + (passed.value ? '质检合格,已转入合格库。' : '质检不合格,已记录原因并移除暂存库存。');
|
bag.value = undefined; reason.value = ''; indicators.value.forEach(i => i.value = '');
|
} catch (e) { error.value = '提交结果待确认:' + errorText(e) + '。请核实后再操作下一袋。'; } finally { busy.value = false; }
|
}
|
async function verify() {
|
if (busy.value || !pendingCode.value) return;
|
busy.value = true; error.value = '';
|
try {
|
const data = await resolveSeedQuality(pendingCode.value, pendingType.value);
|
const completed = pendingIds.value.filter(id => data.completedBagIds?.includes(id));
|
if (pendingIds.value.length ? completed.length === pendingIds.value.length : !!data.qualityId) { notice.value = '已核实:本次袋码均已有质检记录,请在质检记录中核对结果。'; bag.value = undefined; clearPending(); }
|
else if (completed.length) error.value = '本次清单仅部分袋码已有质检记录,请核对质检记录后联系负责人处理,当前保留待确认操作。';
|
else if (window.confirm('当前未查到本次袋码质检记录。确认原请求已结束后,可重新扫描并核对指标。继续?')) { clearPending(); bag.value = undefined; notice.value = '请重新扫描并录入指标;后端会拦截重复质检。'; }
|
} catch (e) { error.value = errorText(e); } finally { busy.value = false; }
|
}
|
onMounted(async () => {
|
const saved = sessionStorage.getItem(storageKey);
|
if (saved) {
|
try { const pending = JSON.parse(saved); if (pending && typeof pending === 'object' && pending.code) { pendingCode.value = pending.code; pendingType.value = pending.scanType; pendingIds.value = pending.bagIds || []; } else pendingCode.value = saved; }
|
catch { pendingCode.value = saved; } // 兼容升级前保存的单袋待确认码值。
|
}
|
busy.value = true;
|
try { warehouses.value = (await getWarehouseSimpleList()).filter(w => w.type === 20 && !w.frozen); }
|
catch (e) { error.value = errorText(e); } finally { busy.value = false; }
|
});
|
onBeforeRouteLeave(() => !busy.value && (!bag.value || !!pendingCode.value || window.confirm('离开将放弃未提交的质检指标,是否继续?')));
|
</script>
|
<template>
|
<p v-if="notice" class="m-success" role="status">{{notice}}</p>
|
<p v-if="error" class="m-error" role="alert">{{error}}</p>
|
<div v-if="pendingCode" class="m-panel"><h2 class="m-title">质检结果待确认</h2><p>{{pendingCode}}</p><button class="m-primary" :disabled="busy" @click="verify">核实质检结果</button></div>
|
<template v-else>
|
<label class="m-field"><span>质检扫码类型</span><select v-model="scanType" :disabled="busy" @change="bag = undefined; notice = ''; error = ''"><option value="BAG">袋码</option><option value="PALLET">托盘码</option><option value="BATCH">生产批次码</option></select></label>
|
<p class="m-muted">扫描{{typeNames[scanType]}},核对待检袋数,统一录入四项指标。已检、作废袋码不会重复处理。</p>
|
<Scanner :disabled="busy" @code="identify" />
|
<form v-if="bag" @submit.prevent="submit">
|
<div class="m-panel"><h2 class="m-title">本次检验对象</h2><dl><div class="m-row"><dt>对象类型</dt><dd>{{typeNames[scanType]}}</dd></div><div class="m-row"><dt>对象码值</dt><dd>{{bag.code}}</dd></div><div class="m-row"><dt>业务码</dt><dd>{{bag.businessCode || '—'}}</dd></div><div class="m-row"><dt>生产批次</dt><dd>{{bag.batchCode || '—'}}</dd></div><div class="m-row"><dt>有效袋数</dt><dd>{{bag.totalCount}}</dd></div><div class="m-row"><dt>已检袋数</dt><dd>{{bag.completedBagIds?.length || 0}}</dd></div><div class="m-row"><dt>本次待检</dt><dd>{{bag.pendingBagIds?.length || 0}} 袋</dd></div></dl><p v-if="scanType !== 'BAG'" class="m-warning">以下指标和检验结论将应用于本次全部待检袋码,请确认抽样结果适用于这{{bag.pendingBagIds?.length}}袋。</p></div>
|
<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 placeholder="输入实测百分比" /></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>
|
<div class="m-panel"><p class="m-muted">上下限沿用现有质检规则录入方式,请按该品种检验标准核对。</p><Picker v-model="warehouseId" label="目标合格库" :options="warehouses" :disabled="busy" /><p v-if="!warehouses.length && !busy" class="m-warning">暂无可用合格库,请联系管理员配置。</p><label class="m-field"><span>{{complete && !passed ? '不合格原因(必填)' : '检验备注'}}</span><textarea v-model="reason" :disabled="busy" :required="complete && !passed" rows="3" maxlength="255" /></label></div>
|
<div class="m-action-bar"><p v-if="complete" :class="passed ? 'm-success' : 'm-warning'">预判:{{passed ? '合格,提交后转入合格库' : '不合格,提交后移除暂存库存'}}。最终以服务端结果为准。</p><button class="m-primary" :disabled="busy || !complete || !warehouseId">{{busy ? '正在提交…' : '确认提交质检'}}</button></div>
|
</form>
|
</template>
|
<RouterLink v-if="allowedMobilePath('/mobile/quality-records')" to="/mobile/quality-records" class="m-link">查看质检记录</RouterLink>
|
</template>
|