|
|
@@ -0,0 +1,592 @@
|
|
|
+<template>
|
|
|
+ <AidopDemoShell :title="pageTitle" :subtitle="detail?.billNo || ''">
|
|
|
+ <div class="detail-topbar">
|
|
|
+ <el-button @click="goBack">← 返回</el-button>
|
|
|
+ <span class="bill-no">{{ detail?.billNo || (billId ? '加载中…' : '') }}</span>
|
|
|
+ <el-tag v-if="flowState?.flowStatus" type="info" size="small">{{ flowState.flowStatus }}</el-tag>
|
|
|
+ <el-tag v-if="flowState?.currentNodeName" size="small">{{ flowState.currentNodeName }}</el-tag>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-loading="loading" class="detail-body">
|
|
|
+ <template v-if="detail">
|
|
|
+ <el-descriptions title="基本信息" :column="2" border size="small">
|
|
|
+ <el-descriptions-item label="单据编号">{{ dash(detail.billNo) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="检验时间">{{ displayInspectTime }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="来源单据编号">{{ dash(detail.sourceBillNo) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="检验人">{{ displayInspector }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="检验数量">{{ dashNum(detail.inspectQty) }}</el-descriptions-item>
|
|
|
+ <!-- 判定/合格数量/不合格数量:检验员(N1)节点尚未产生,隐藏;主管/QE/只读节点展示 -->
|
|
|
+ <el-descriptions-item v-if="!flowState?.canSubmitResult" label="判定">{{ dash(detail.judgment) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item v-if="!flowState?.canSubmitResult" label="合格数量">{{ dashNum(detail.qualifiedQty) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item v-if="!flowState?.canSubmitResult" label="不合格数量">{{ dashNum(detail.unqualifiedQty) }}</el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+
|
|
|
+ <el-descriptions title="成品信息" :column="2" border size="small" class="mt8">
|
|
|
+ <el-descriptions-item label="物料编码">{{ dash(detail.materialCode) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="物料名称">{{ dash(detail.materialName) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="规格型号">{{ dash(detail.spec) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="生产批号">{{ dash(detail.productionBatchNo) }}</el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+
|
|
|
+ <el-descriptions title="生产信息" :column="2" border size="small" class="mt8">
|
|
|
+ <el-descriptions-item label="生产指令单">{{ dash(detail.productionOrderNo) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="指令单数量">{{ dashNum(detail.orderQty) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="SAP工单号">{{ dash(detail.sapWorkOrderNo) }}</el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+
|
|
|
+ <el-descriptions title="检规信息" :column="2" border size="small" class="mt8">
|
|
|
+ <el-descriptions-item label="击发编号">{{ dash(detail.fireNo) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="检规编号">{{ dash(detail.inspectSpecNo) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="检规版本">{{ dash(detail.inspectSpecVersion) }}</el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+
|
|
|
+ <div class="items-title mt8">品检项目明细</div>
|
|
|
+
|
|
|
+ <!-- N1 检验员录入:老风格逐项录入(类型/样本/合格数/自动判定/保存)-->
|
|
|
+ <template v-if="flowState?.canSubmitResult">
|
|
|
+ <div class="insp-toolbar">
|
|
|
+ <el-button type="warning" :loading="judging" @click="onAutoJudge">自动判定</el-button>
|
|
|
+ <el-button :loading="saving" @click="onSaveLines">保存</el-button>
|
|
|
+ <span class="insp-tip">数值型展开录样本值,自动判定按快照上下限判;非数值型录合格/不合格数,无规则由人工确认判定。</span>
|
|
|
+ </div>
|
|
|
+ <el-table :data="inspLines" v-loading="inspLoading" border stripe size="small" row-key="id" class="mt8">
|
|
|
+ <el-table-column type="expand">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="expand" v-if="row.lrlx === 'NUMERIC'">
|
|
|
+ <span class="lbl">样本值(上限 {{ row.sx || '-' }} / 下限 {{ row.xx || '-' }}):</span>
|
|
|
+ <el-input v-for="(s, i) in sampleSlots(row)" :key="i" v-model="row.samples[i]" size="small" class="sample" :placeholder="'样本' + (i + 1)" />
|
|
|
+ <el-button link type="primary" size="small" @click="addSample(row)">+样本</el-button>
|
|
|
+ </div>
|
|
|
+ <div class="expand" v-else-if="row.lrlx === 'NON_NUMERIC'">非数值型项目:请在行内填「合格数 / 不合格数」,无需逐样本录入。</div>
|
|
|
+ <div class="expand muted" v-else>请先在「类型」列选择 数值型 或 非数值型,再录入。</div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="xh" label="序号" width="56" />
|
|
|
+ <el-table-column prop="jyxm" label="检验项目" min-width="130" class-name="wrap-cell" />
|
|
|
+ <el-table-column label="类型" width="110">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-select v-model="row.lrlx" size="small" placeholder="选择类型" style="width: 96px">
|
|
|
+ <el-option label="数值型" value="NUMERIC" />
|
|
|
+ <el-option label="非数值型" value="NON_NUMERIC" />
|
|
|
+ </el-select>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="jsbz" label="接收标准" min-width="150" class-name="wrap-cell" />
|
|
|
+ <el-table-column prop="jsyq" label="技术要求" min-width="150" class-name="wrap-cell" />
|
|
|
+ <el-table-column prop="cyfa" label="抽样方案" min-width="110" class-name="wrap-cell" />
|
|
|
+ <el-table-column label="合格数" width="90">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-input-number v-if="row.lrlx === 'NON_NUMERIC'" v-model="row.nonNumericalTypeOK" :controls="false" :min="0" size="small" style="width: 78px" />
|
|
|
+ <span v-else class="muted">—</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="不合格数" width="90">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-input-number v-if="row.lrlx === 'NON_NUMERIC'" v-model="row.nonNumericalTypeNG" :controls="false" :min="0" size="small" style="width: 78px" />
|
|
|
+ <span v-else class="muted">—</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="判定" width="120">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-select v-model="row.pd" size="small" placeholder="待判定" clearable style="width: 100px">
|
|
|
+ <el-option label="合格" value="合格" />
|
|
|
+ <el-option label="不合格" value="不合格" />
|
|
|
+ </el-select>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <template #empty><el-empty description="该检验单无明细" :image-size="60" /></template>
|
|
|
+ </el-table>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <!-- 非 N1(审核/处置/只读):只读明细,文本自动换行 -->
|
|
|
+ <el-table v-else :data="detail.lines || []" border stripe size="small" row-key="id">
|
|
|
+ <el-table-column type="expand">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="samples-wrap">
|
|
|
+ <template v-if="nonEmptySamples(row).length">
|
|
|
+ <span class="samples-label">样品值:</span>
|
|
|
+ <el-tag v-for="s in nonEmptySamples(row)" :key="s.idx" class="sample-tag" type="info" disable-transitions>样品{{ s.idx }}: {{ s.val }}</el-tag>
|
|
|
+ </template>
|
|
|
+ <span v-else class="samples-empty">无样品数据</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="seqNo" label="序号" min-width="70" resizable />
|
|
|
+ <el-table-column prop="inspectionItem" label="检验项目" min-width="140" class-name="wrap-cell" resizable />
|
|
|
+ <el-table-column prop="acceptStandard" label="接收标准" min-width="150" class-name="wrap-cell" resizable />
|
|
|
+ <el-table-column prop="techRequirement" label="技术要求" min-width="150" class-name="wrap-cell" resizable />
|
|
|
+ <el-table-column prop="samplingPlan" label="抽样方案" min-width="120" class-name="wrap-cell" resizable />
|
|
|
+ <el-table-column prop="sampleQty" label="样本量" min-width="90" resizable />
|
|
|
+ <el-table-column prop="upperLimit" label="上限" min-width="90" resizable />
|
|
|
+ <el-table-column prop="lowerLimit" label="下限" min-width="90" resizable />
|
|
|
+ <el-table-column prop="sizeSpecification" label="规格标准" min-width="120" class-name="wrap-cell" resizable />
|
|
|
+ <el-table-column prop="judgment" label="判定" min-width="90" resizable />
|
|
|
+ <el-table-column prop="nonNumericOk" label="非数值OK" min-width="100" resizable />
|
|
|
+ <el-table-column prop="nonNumericNg" label="非数值NG" min-width="100" resizable />
|
|
|
+ <el-table-column prop="remark" label="备注" min-width="140" class-name="wrap-cell" resizable />
|
|
|
+ <template #empty>
|
|
|
+ <el-empty description="暂无明细" :image-size="60" />
|
|
|
+ </template>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <el-descriptions title="流程信息" :column="2" border size="small" class="mt8">
|
|
|
+ <el-descriptions-item label="流程状态">{{ dash(flowState?.flowStatus) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="当前节点">{{ dash(flowState?.currentNodeName) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="当前角色">{{ dash(flowState?.currentAssigneeRole) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="说明">{{ dash(flowState?.message) }}</el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+
|
|
|
+ <!-- N1 检验员提交(先存逐项明细再提交转主管)-->
|
|
|
+ <div v-if="flowState?.canSubmitResult" class="flow-op mt12">
|
|
|
+ <div class="flow-op-title">检验员提交检验结果(转检验主管审核)</div>
|
|
|
+ <el-form :model="resultForm" label-width="96px" size="small">
|
|
|
+ <el-form-item label="判定">
|
|
|
+ <el-select v-model="resultForm.pd" style="width: 180px" @change="onPdChange">
|
|
|
+ <el-option :value="0" label="合格" />
|
|
|
+ <el-option :value="1" label="不合格" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="合格数量">
|
|
|
+ <el-input-number v-model="resultForm.hgsl" :min="0" :controls="false" style="width: 180px" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="不合格数量">
|
|
|
+ <el-input-number v-model="resultForm.bhgsl" :min="0" :controls="false" :disabled="resultForm.pd === 0" style="width: 180px" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="处理方式" v-if="resultForm.pd === 1">
|
|
|
+ <el-select v-model="resultForm.clfs" style="width: 180px" clearable>
|
|
|
+ <el-option :value="0" label="让步接收" />
|
|
|
+ <el-option :value="1" label="挑选" />
|
|
|
+ <el-option :value="2" label="报废" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="备注">
|
|
|
+ <el-input v-model="resultForm.comment" type="textarea" :rows="2" style="width: 320px" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button :loading="saving" @click="onSaveLines">保存明细</el-button>
|
|
|
+ <el-button type="primary" :loading="submitting" @click="onSubmitResult">提交检验结果</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- N2 检验主管审核 -->
|
|
|
+ <div v-if="flowState?.canSupervisorApprove || flowState?.canSupervisorReject" class="flow-op mt12">
|
|
|
+ <div class="flow-op-title">检验主管审核</div>
|
|
|
+ <el-button type="success" :loading="submitting" @click="onApprove">通过</el-button>
|
|
|
+ <el-button type="warning" :loading="submitting" @click="onReject">退回</el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- N3 QE 处置 -->
|
|
|
+ <div v-if="flowState?.canSubmitDisposition" class="flow-op mt12">
|
|
|
+ <div class="flow-op-title">QE 质量处置(不合格)</div>
|
|
|
+ <el-form :model="qeForm" label-width="96px" size="small">
|
|
|
+ <el-form-item label="处置类型">
|
|
|
+ <el-select v-model="qeForm.dispositionType" style="width: 180px">
|
|
|
+ <el-option value="REWORK" label="返工" />
|
|
|
+ <el-option value="CONCESSION" label="让步放行" />
|
|
|
+ <el-option value="SCRAP" label="报废" />
|
|
|
+ <el-option value="OTHER" label="其它" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="处置数量"><el-input-number v-model="qeForm.dispositionQty" :min="0" :controls="false" style="width: 180px" /></el-form-item>
|
|
|
+ <el-form-item label="返工数量"><el-input-number v-model="qeForm.reworkQty" :min="0" :controls="false" style="width: 180px" /></el-form-item>
|
|
|
+ <el-form-item label="让步数量"><el-input-number v-model="qeForm.concessionQty" :min="0" :controls="false" style="width: 180px" /></el-form-item>
|
|
|
+ <el-form-item label="报废数量"><el-input-number v-model="qeForm.scrapQty" :min="0" :controls="false" style="width: 180px" /></el-form-item>
|
|
|
+ <el-form-item label="责任部门"><el-input v-model="qeForm.responsibilityDept" style="width: 240px" /></el-form-item>
|
|
|
+ <el-form-item label="责任人"><el-input v-model="qeForm.responsibleUser" style="width: 180px" /></el-form-item>
|
|
|
+ <el-form-item label="处置意见"><el-input v-model="qeForm.dispositionOpinion" type="textarea" :rows="2" style="width: 320px" /></el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" :loading="submitting" @click="onSubmitQe">提交 QE 处置</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 已提交 QE 处置(只读展示)-->
|
|
|
+ <el-descriptions v-if="flowState?.hasDisposition" title="QE 处置结论(仅展示)" :column="2" border size="small" class="mt12">
|
|
|
+ <el-descriptions-item label="处置类型">{{ dash(flowState?.dispositionType) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="处置意见"><span class="pre">{{ dash(flowState?.dispositionOpinion) }}</span></el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+ </template>
|
|
|
+ <el-empty v-else-if="!loading" :description="emptyText" />
|
|
|
+ </div>
|
|
|
+ </AidopDemoShell>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts" name="aidopS7FqcResultDetail">
|
|
|
+import { computed, onMounted, reactive, ref } from 'vue';
|
|
|
+import { useRoute, useRouter } from 'vue-router';
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus';
|
|
|
+import AidopDemoShell from '/@/views/aidop/components/AidopDemoShell.vue';
|
|
|
+import { fetchFqcDetail, type FqcDetail, type FqcDetailLine } from '../api/fqcInspBill';
|
|
|
+import { fetchInspectionDetail, saveInspectionDetail, autoJudgeInspection, type FqcInspLine, type FqcInspHead } from '../api/fqcInspection';
|
|
|
+import {
|
|
|
+ fetchFqcFlowState,
|
|
|
+ submitFqcResult,
|
|
|
+ fqcSupervisorApprove,
|
|
|
+ fqcSupervisorReject,
|
|
|
+ submitFqcQeDisposition,
|
|
|
+ type FqcFlowState,
|
|
|
+} from '../api/fqcInspBillFlow';
|
|
|
+
|
|
|
+const route = useRoute();
|
|
|
+const router = useRouter();
|
|
|
+const pageTitle = computed(() => (route.meta?.title as string) || 'FQC检验单详情');
|
|
|
+
|
|
|
+const billId = computed(() => Number(route.query.id) || 0);
|
|
|
+const loading = ref(false);
|
|
|
+const submitting = ref(false);
|
|
|
+const detail = ref<FqcDetail | null>(null);
|
|
|
+const flowState = ref<FqcFlowState | null>(null);
|
|
|
+const emptyText = ref('暂无详情数据');
|
|
|
+
|
|
|
+// N1 逐项录入(老风格):活单明细 qms_qcpp_inspbillst + 活单头(取负责人/检验开始时间)
|
|
|
+const inspLines = ref<FqcInspLine[]>([]);
|
|
|
+const inspHead = ref<FqcInspHead | null>(null);
|
|
|
+const inspLoading = ref(false);
|
|
|
+
|
|
|
+// 检验人/检验时间:N1 录入节点取活单头(负责人 + 检验开始时间);否则取只读详情(提交后写入的实际检验人/完成时间)
|
|
|
+const displayInspector = computed(() => {
|
|
|
+ if (flowState.value?.canSubmitResult && inspHead.value?.ownerName) return inspHead.value.ownerName;
|
|
|
+ return dash(detail.value?.inspector);
|
|
|
+});
|
|
|
+const displayInspectTime = computed(() => {
|
|
|
+ if (flowState.value?.canSubmitResult && inspHead.value?.inspectStartTime) return fmtDateTime(inspHead.value.inspectStartTime);
|
|
|
+ return fmtDateTime(detail.value?.inspectTime);
|
|
|
+});
|
|
|
+const saving = ref(false);
|
|
|
+const judging = ref(false);
|
|
|
+
|
|
|
+// 检验员聚合提交表单
|
|
|
+const resultForm = reactive<{ pd: number; hgsl: number; bhgsl: number; clfs: number | null; comment: string }>({ pd: 0, hgsl: 0, bhgsl: 0, clfs: null, comment: '' });
|
|
|
+// QE 处置表单
|
|
|
+const qeForm = reactive<{
|
|
|
+ dispositionType: string;
|
|
|
+ dispositionQty: number | null;
|
|
|
+ reworkQty: number | null;
|
|
|
+ concessionQty: number | null;
|
|
|
+ scrapQty: number | null;
|
|
|
+ dispositionOpinion: string;
|
|
|
+ responsibilityDept: string;
|
|
|
+ responsibleUser: string;
|
|
|
+}>({ dispositionType: 'REWORK', dispositionQty: null, reworkQty: null, concessionQty: null, scrapQty: null, dispositionOpinion: '', responsibilityDept: '', responsibleUser: '' });
|
|
|
+
|
|
|
+function resetForms() {
|
|
|
+ resultForm.pd = 0;
|
|
|
+ resultForm.hgsl = 0;
|
|
|
+ resultForm.bhgsl = 0;
|
|
|
+ resultForm.clfs = null;
|
|
|
+ resultForm.comment = '';
|
|
|
+ qeForm.dispositionType = 'REWORK';
|
|
|
+ qeForm.dispositionQty = null;
|
|
|
+ qeForm.reworkQty = null;
|
|
|
+ qeForm.concessionQty = null;
|
|
|
+ qeForm.scrapQty = null;
|
|
|
+ qeForm.dispositionOpinion = '';
|
|
|
+ qeForm.responsibilityDept = '';
|
|
|
+ qeForm.responsibleUser = '';
|
|
|
+}
|
|
|
+
|
|
|
+// 数值型样本槽位:至少 5 个空槽或已录入数+1
|
|
|
+function sampleSlots(row: FqcInspLine): number {
|
|
|
+ if (!row.samples) row.samples = [];
|
|
|
+ const filled = row.samples.filter((s) => s != null && String(s).trim() !== '').length;
|
|
|
+ const n = Math.max(5, filled + 1);
|
|
|
+ while (row.samples.length < n) row.samples.push(null);
|
|
|
+ return row.samples.length;
|
|
|
+}
|
|
|
+function addSample(row: FqcInspLine) {
|
|
|
+ if (!row.samples) row.samples = [];
|
|
|
+ row.samples.push(null);
|
|
|
+}
|
|
|
+
|
|
|
+async function loadInspLines() {
|
|
|
+ inspLoading.value = true;
|
|
|
+ try {
|
|
|
+ const d = await fetchInspectionDetail(billId.value);
|
|
|
+ inspHead.value = d.head;
|
|
|
+ inspLines.value = (d.lines || []).map((l) => ({ ...l, samples: l.samples ? [...l.samples] : [] }));
|
|
|
+ } catch (e: any) {
|
|
|
+ inspLines.value = [];
|
|
|
+ inspHead.value = null;
|
|
|
+ ElMessage.error(e?.message || '加载检验单录入明细失败');
|
|
|
+ } finally {
|
|
|
+ inspLoading.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function loadDetailAndState() {
|
|
|
+ const id = billId.value;
|
|
|
+ if (!id || id <= 0) {
|
|
|
+ detail.value = null;
|
|
|
+ flowState.value = null;
|
|
|
+ emptyText.value = '缺少检验单 id,无法加载';
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ loading.value = true;
|
|
|
+ try {
|
|
|
+ const [d, s] = await Promise.all([fetchFqcDetail(id), fetchFqcFlowState(id)]);
|
|
|
+ detail.value = d;
|
|
|
+ flowState.value = s;
|
|
|
+ if (!d) emptyText.value = '检验单不存在或无权访问';
|
|
|
+ resetForms();
|
|
|
+ // N1 录入模式:另拉活单逐项明细(含快照上下限/已录样本),供老风格录入
|
|
|
+ if (s?.canSubmitResult) await loadInspLines();
|
|
|
+ else {
|
|
|
+ inspLines.value = [];
|
|
|
+ inspHead.value = null;
|
|
|
+ }
|
|
|
+ } catch (e: any) {
|
|
|
+ detail.value = null;
|
|
|
+ flowState.value = null;
|
|
|
+ emptyText.value = e?.message || '加载FQC检验单详情失败';
|
|
|
+ ElMessage.error(e?.message || '加载FQC检验单详情失败');
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 逐项保存(草稿,不起流)
|
|
|
+async function onSaveLines() {
|
|
|
+ saving.value = true;
|
|
|
+ try {
|
|
|
+ await saveInspectionDetail({
|
|
|
+ inspBillId: billId.value,
|
|
|
+ lines: inspLines.value.map((l) => ({ id: l.id, lrlx: l.lrlx ?? null, samples: l.samples, nonNumericalTypeOK: l.nonNumericalTypeOK ?? null, nonNumericalTypeNG: l.nonNumericalTypeNG ?? null, ybl: l.ybl ?? null, pd: l.pd ?? null })),
|
|
|
+ });
|
|
|
+ ElMessage.success('已保存');
|
|
|
+ } catch (e: any) {
|
|
|
+ ElMessage.error(e?.message || '保存失败');
|
|
|
+ } finally {
|
|
|
+ saving.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 自动判定:先存样本再判(数值型按上下限,非数值型/无规则留人工)
|
|
|
+async function onAutoJudge() {
|
|
|
+ judging.value = true;
|
|
|
+ try {
|
|
|
+ await onSaveLines();
|
|
|
+ const res = await autoJudgeInspection(billId.value);
|
|
|
+ ElMessage.success(res.message);
|
|
|
+ await loadInspLines();
|
|
|
+ } catch (e: any) {
|
|
|
+ ElMessage.error(e?.message || '自动判定失败');
|
|
|
+ } finally {
|
|
|
+ judging.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function onSubmitResult() {
|
|
|
+ if (resultForm.pd !== 0 && resultForm.pd !== 1) return ElMessage.error('请选择判定');
|
|
|
+ if (resultForm.hgsl < 0) return ElMessage.error('合格数量不能为负');
|
|
|
+ if (resultForm.bhgsl < 0) return ElMessage.error('不合格数量不能为负');
|
|
|
+ if (resultForm.pd === 0 && resultForm.bhgsl !== 0) return ElMessage.error('判定合格时不合格数量必须为 0');
|
|
|
+ if (resultForm.pd === 1 && resultForm.bhgsl <= 0) return ElMessage.error('判定不合格时不合格数量必须大于 0');
|
|
|
+ if (resultForm.pd === 1 && (resultForm.clfs === null || resultForm.clfs === undefined)) return ElMessage.error('判定不合格时请选择处理方式');
|
|
|
+ submitting.value = true;
|
|
|
+ try {
|
|
|
+ await onSaveLines(); // 提交前先存逐项明细(与老风格一致)
|
|
|
+ await submitFqcResult({ id: billId.value, pd: resultForm.pd, hgsl: resultForm.hgsl, bhgsl: resultForm.bhgsl, clfs: resultForm.pd === 1 ? resultForm.clfs : null, comment: resultForm.comment });
|
|
|
+ ElMessage.success('已提交,转检验主管审核');
|
|
|
+ await loadDetailAndState();
|
|
|
+ } catch (e: any) {
|
|
|
+ ElMessage.error(e?.message || '提交检验结果失败');
|
|
|
+ } finally {
|
|
|
+ submitting.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function onPdChange() {
|
|
|
+ if (resultForm.pd === 0) {
|
|
|
+ resultForm.bhgsl = 0;
|
|
|
+ resultForm.clfs = null;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function onApprove() {
|
|
|
+ submitting.value = true;
|
|
|
+ try {
|
|
|
+ await fqcSupervisorApprove(billId.value);
|
|
|
+ ElMessage.success('已通过');
|
|
|
+ await loadDetailAndState();
|
|
|
+ } catch (e: any) {
|
|
|
+ ElMessage.error(e?.message || '审核通过失败');
|
|
|
+ } finally {
|
|
|
+ submitting.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function onReject() {
|
|
|
+ let comment = '';
|
|
|
+ try {
|
|
|
+ const { value } = await ElMessageBox.prompt('请填写退回意见', '退回', {
|
|
|
+ confirmButtonText: '确定退回',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ inputType: 'textarea',
|
|
|
+ inputValidator: (v: string) => (v && v.trim() ? true : '退回意见不能为空'),
|
|
|
+ });
|
|
|
+ comment = value.trim();
|
|
|
+ } catch {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ submitting.value = true;
|
|
|
+ try {
|
|
|
+ await fqcSupervisorReject(billId.value, comment);
|
|
|
+ ElMessage.success('已退回');
|
|
|
+ await loadDetailAndState();
|
|
|
+ } catch (e: any) {
|
|
|
+ ElMessage.error(e?.message || '退回失败');
|
|
|
+ } finally {
|
|
|
+ submitting.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function onSubmitQe() {
|
|
|
+ if (!['REWORK', 'CONCESSION', 'SCRAP', 'OTHER'].includes(qeForm.dispositionType)) return ElMessage.error('请选择处置类型');
|
|
|
+ if (!qeForm.dispositionOpinion || !qeForm.dispositionOpinion.trim()) return ElMessage.error('处置意见必填');
|
|
|
+ for (const q of [qeForm.dispositionQty, qeForm.reworkQty, qeForm.concessionQty, qeForm.scrapQty]) {
|
|
|
+ if (q !== null && q !== undefined && q < 0) return ElMessage.error('处置数量不能为负');
|
|
|
+ }
|
|
|
+ submitting.value = true;
|
|
|
+ try {
|
|
|
+ await submitFqcQeDisposition({
|
|
|
+ id: billId.value,
|
|
|
+ dispositionType: qeForm.dispositionType,
|
|
|
+ dispositionQty: qeForm.dispositionQty,
|
|
|
+ reworkQty: qeForm.reworkQty,
|
|
|
+ concessionQty: qeForm.concessionQty,
|
|
|
+ scrapQty: qeForm.scrapQty,
|
|
|
+ dispositionOpinion: qeForm.dispositionOpinion.trim(),
|
|
|
+ responsibilityDept: qeForm.responsibilityDept || undefined,
|
|
|
+ responsibleUser: qeForm.responsibleUser || undefined,
|
|
|
+ });
|
|
|
+ ElMessage.success('QE 处置已提交');
|
|
|
+ await loadDetailAndState();
|
|
|
+ } catch (e: any) {
|
|
|
+ ElMessage.error(e?.message || '提交 QE 处置失败');
|
|
|
+ } finally {
|
|
|
+ submitting.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function nonEmptySamples(row: FqcDetailLine) {
|
|
|
+ const arr = row?.samples || [];
|
|
|
+ const out: { idx: number; val: string }[] = [];
|
|
|
+ for (let i = 0; i < arr.length; i++) {
|
|
|
+ const v = arr[i];
|
|
|
+ if (v !== null && v !== undefined && String(v).trim() !== '') out.push({ idx: i + 1, val: String(v) });
|
|
|
+ }
|
|
|
+ return out;
|
|
|
+}
|
|
|
+
|
|
|
+function dash(v: unknown) {
|
|
|
+ if (v === null || v === undefined || v === '') return '-';
|
|
|
+ return String(v);
|
|
|
+}
|
|
|
+function dashNum(v?: number | null) {
|
|
|
+ if (v === null || v === undefined) return '-';
|
|
|
+ return String(v);
|
|
|
+}
|
|
|
+function fmtDateTime(v?: string | null) {
|
|
|
+ if (!v) return '-';
|
|
|
+ return String(v).replace('T', ' ').slice(0, 19);
|
|
|
+}
|
|
|
+function goBack() {
|
|
|
+ if (window.history.length > 1) router.back();
|
|
|
+ else router.push('/aidop/s7/fqc/result-list');
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(loadDetailAndState);
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.detail-topbar {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ .bill-no {
|
|
|
+ font-weight: bold;
|
|
|
+ font-size: 15px;
|
|
|
+ }
|
|
|
+}
|
|
|
+.detail-body {
|
|
|
+ min-height: 120px;
|
|
|
+}
|
|
|
+.mt8 {
|
|
|
+ margin-top: 8px;
|
|
|
+}
|
|
|
+.mt12 {
|
|
|
+ margin-top: 12px;
|
|
|
+}
|
|
|
+.items-title {
|
|
|
+ font-weight: bold;
|
|
|
+ font-size: 14px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+}
|
|
|
+.insp-toolbar {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ .insp-tip {
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+}
|
|
|
+.expand {
|
|
|
+ padding: 6px 12px;
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ .lbl {
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+ .sample {
|
|
|
+ width: 96px;
|
|
|
+ }
|
|
|
+}
|
|
|
+.muted {
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+}
|
|
|
+.samples-wrap {
|
|
|
+ padding: 6px 12px;
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+}
|
|
|
+.samples-label {
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+.sample-tag {
|
|
|
+ margin: 0;
|
|
|
+}
|
|
|
+.samples-empty {
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+.flow-op {
|
|
|
+ padding: 12px;
|
|
|
+ border: 1px solid var(--el-border-color-lighter);
|
|
|
+ border-radius: 4px;
|
|
|
+ background: var(--el-fill-color-light);
|
|
|
+}
|
|
|
+.flow-op-title {
|
|
|
+ font-weight: bold;
|
|
|
+ margin-bottom: 10px;
|
|
|
+}
|
|
|
+.pre {
|
|
|
+ white-space: pre-wrap;
|
|
|
+ word-break: break-word;
|
|
|
+}
|
|
|
+// 文本列自动换行(替代 show-overflow-tooltip 的省略号截断)
|
|
|
+:deep(.wrap-cell .cell) {
|
|
|
+ white-space: pre-wrap;
|
|
|
+ word-break: break-word;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+</style>
|