feat(M08-B): 补联调逾期清单

This commit is contained in:
Codex
2026-07-07 09:52:28 +08:00
parent e490637179
commit 85d65068e7
6 changed files with 97 additions and 11 deletions
@@ -8,6 +8,7 @@
<span>待处理 <strong>{{ pendingCount }}</strong></span>
<span>证据 <strong>{{ evidenceRecordedCount }}</strong></span>
<span>待补证据 <strong>{{ evidenceMissingCount }}</strong></span>
<span>逾期 <strong>{{ overdueItems.length }}</strong></span>
<span>收口 <strong>{{ closeoutReady ? '可归档' : '待补齐' }}</strong></span>
<span>更新 <strong>{{ sessionMeta.updatedAt || '未记录' }}</strong></span>
</div>
@@ -210,6 +211,21 @@
</li>
</ul>
</section>
<section v-if="overdueItems.length" class="handoff-blockers handoff-overdue-gaps">
<header>
<div>
<h3>逾期检查项</h3>
<span>{{ overdueItems.length }} 个现场检查项已超过处理期限</span>
</div>
<el-button :icon="Copy" @click="copyOverdueSummary">复制逾期清单</el-button>
</header>
<ul>
<li v-for="item in overdueItems" :key="item.id">
<strong>{{ item.category }} · {{ item.title }}</strong>
<span>{{ draft[item.id].owner || '未分派' }} / 期限 {{ draft[item.id].deadline }}</span>
</li>
</ul>
</section>
<el-table :data="filteredChecklist" class="data-table" row-key="id">
<el-table-column prop="category" label="类别" width="120" />
@@ -410,6 +426,7 @@ const evidenceMissingItems = computed(() => checklist.filter((item) => (
)));
const evidenceMissingCount = computed(() => evidenceMissingItems.value.length);
const blockedItems = computed(() => checklist.filter((item) => draft[item.id].status === 'BLOCKED'));
const overdueItems = computed(() => checklist.filter((item) => isOverdueDraft(draft[item.id])));
const riskItems = computed(() => checklist.filter((item) => {
const itemDraft = draft[item.id];
return itemDraft.status === 'BLOCKED'
@@ -422,6 +439,7 @@ const missingCloseoutFields = computed(() => {
const fields: string[] = [];
if (!handoffReady.value) fields.push('检查项状态');
if (evidenceMissingCount.value > 0) fields.push('现场证据');
if (overdueItems.value.length > 0) fields.push('逾期检查项');
if (!sessionMeta.acceptor.trim()) fields.push('验收人');
if (!sessionMeta.closeoutVerdict.trim()) fields.push('收口结论');
return fields;
@@ -508,6 +526,30 @@ function touchSessionMeta() {
sessionMeta.updatedAt = formatLocalMinute(new Date());
}
function isOverdueDraft(itemDraft: HandoffDraft) {
if (itemDraft.status === 'PASS' || itemDraft.status === 'SKIP') return false;
const deadline = parseDeadline(itemDraft.deadline);
if (!deadline) return false;
return deadline.getTime() < Date.now();
}
function parseDeadline(value: string) {
const normalized = value.trim().replace(/\//g, '-');
if (!normalized) return null;
const match = normalized.match(/^(\d{4})-(\d{1,2})-(\d{1,2})(?:\s+(\d{1,2}):(\d{1,2}))?$/);
if (!match) return null;
const [, year, month, day, hour = '23', minute = '59'] = match;
const parsed = new Date(
Number(year),
Number(month) - 1,
Number(day),
Number(hour),
Number(minute),
59
);
return Number.isNaN(parsed.getTime()) ? null : parsed;
}
function resetDraft() {
for (const item of checklist) {
draft[item.id] = normalizeDraft();
@@ -577,6 +619,27 @@ async function copyEvidenceSummary() {
}
}
async function copyOverdueSummary() {
const header = [
`联调批次:${sessionMeta.batchNo || '未填写'}`,
`现场/门店:${sessionMeta.site || '未填写'}`,
`负责人:${sessionMeta.coordinator || '未填写'}`,
`执行时间:${sessionMeta.executedAt || '未填写'}`,
`最后更新:${sessionMeta.updatedAt || '未记录'}`,
`逾期检查项:${overdueItems.value.length}`
].join('\n');
const content = overdueItems.value.map((item) => {
const itemDraft = draft[item.id];
return `[${item.category}] ${item.title} - ${statusText(itemDraft.status)} - ${itemDraft.owner || '未分派'} - 期限:${itemDraft.deadline} - ${itemDraft.note || item.expected}`;
}).join('\n');
try {
await navigator.clipboard.writeText(`${header}\n\n${content}`);
ElMessage.success('逾期清单已复制');
} catch {
ElMessage.warning('当前浏览器不允许自动复制,请使用风险导出或归档包留存');
}
}
async function copyVerdict() {
const verdict = [
`联调批次:${sessionMeta.batchNo || '未填写'}`,
@@ -659,11 +722,13 @@ function buildCloseoutArchive() {
blocked: blockedCount.value,
pending: pendingCount.value,
evidenceRecorded: evidenceRecordedCount.value,
evidenceMissing: evidenceMissingCount.value
evidenceMissing: evidenceMissingCount.value,
overdue: overdueItems.value.length
},
categoryProgress: categoryProgress.value,
riskItems: riskItems.value.map((item) => serializeHandoffItem(item)),
evidenceMissingItems: evidenceMissingItems.value.map((item) => serializeHandoffItem(item)),
overdueItems: overdueItems.value.map((item) => serializeHandoffItem(item)),
checklist: checklist.map((item) => serializeHandoffItem(item)),
report: buildHandoffReport()
};
@@ -699,7 +764,7 @@ function buildHandoffReport() {
`最后更新:${sessionMeta.updatedAt || '未记录'}`,
`结论:${handoffReady.value ? '可进入现场验收收口' : '仍不可收口'}`,
`归档门禁:${closeoutReady.value ? '可归档' : `待补齐 ${missingCloseoutFields.value.join('、')}`}`,
`汇总:总项 ${checklist.length},通过 ${passedCount.value},阻断 ${blockedCount.value},待处理 ${pendingCount.value},证据 ${evidenceRecordedCount.value}/${checklist.length},待补证据 ${evidenceMissingCount.value}`
`汇总:总项 ${checklist.length},通过 ${passedCount.value},阻断 ${blockedCount.value},待处理 ${pendingCount.value},证据 ${evidenceRecordedCount.value}/${checklist.length},待补证据 ${evidenceMissingCount.value},逾期 ${overdueItems.value.length}`
];
const progress = categoryProgress.value.map((item) => (
`- ${item.category}${item.passed}/${item.total} 通过,阻断 ${item.blocked},待处理 ${item.pending},不适用 ${item.skipped}`
@@ -710,6 +775,9 @@ function buildHandoffReport() {
const evidenceGaps = evidenceMissingItems.value.length
? evidenceMissingItems.value.map((item) => `- [${item.category}] ${item.title}${item.evidence}`)
: ['- 无'];
const overdueGaps = overdueItems.value.length
? overdueItems.value.map((item) => `- [${item.category}] ${item.title}${draft[item.id].owner || '未分派'} / ${draft[item.id].deadline}`)
: ['- 无'];
const details = checklist.map((item) => formatReportItem(item));
return [
...header,
@@ -723,6 +791,9 @@ function buildHandoffReport() {
'## 待补证据',
...evidenceGaps,
'',
'## 逾期检查项',
...overdueGaps,
'',
'## 明细',
...details
].join('\n');
@@ -765,6 +836,7 @@ function exportChecklist() {
['session', '归档门禁', closeoutReady.value ? '可归档' : `待补齐 ${missingCloseoutFields.value.join('、')}`, '', '', '', '', '', ''],
['session', '证据记录数', `${evidenceRecordedCount.value}/${checklist.length}`, '', '', '', '', '', ''],
['session', '待补证据数', String(evidenceMissingCount.value), '', '', '', '', '', ''],
['session', '逾期检查项', String(overdueItems.value.length), '', '', '', '', '', ''],
['ID', '类别', '检查项', '期望', '状态', '负责人', '期限', '结果记录', '应留证据', '证据记录'],
...checklist.map((item) => [
item.id,