feat(M08-B): 补联调逾期清单
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user