feat(M08-B): 补联调场次信息

This commit is contained in:
Codex
2026-07-05 16:07:29 +08:00
parent fda768d4ce
commit b5e7121c35
7 changed files with 111 additions and 9 deletions
@@ -58,6 +58,38 @@
show-icon
:closable="false"
/>
<section class="handoff-session">
<el-form label-position="top">
<el-form-item label="联调批次">
<el-input
v-model="sessionMeta.batchNo"
placeholder="例如 2026-07-现场一轮"
@change="persistSessionMeta"
/>
</el-form-item>
<el-form-item label="现场/门店">
<el-input
v-model="sessionMeta.site"
placeholder="门店或现场位置"
@change="persistSessionMeta"
/>
</el-form-item>
<el-form-item label="负责人">
<el-input
v-model="sessionMeta.coordinator"
placeholder="现场负责人"
@change="persistSessionMeta"
/>
</el-form-item>
<el-form-item label="执行时间">
<el-input
v-model="sessionMeta.executedAt"
placeholder="YYYY-MM-DD HH:mm"
@change="persistSessionMeta"
/>
</el-form-item>
</el-form>
</section>
<section v-if="blockedItems.length" class="handoff-blockers">
<header>
<div>
@@ -145,7 +177,15 @@ interface HandoffDraft {
note: string;
}
interface SessionMeta {
batchNo: string;
site: string;
coordinator: string;
executedAt: string;
}
const storageKey = 'qipai.cleaning.field-handoff';
const sessionStorageKey = 'qipai.cleaning.field-handoff.session';
const category = ref<'全部' | HandoffItem['category']>('全部');
const statusFilter = ref<'ALL' | FieldStatus>('ALL');
const bulkForm = reactive<{ status: FieldStatus; owner: string; note: string }>({
@@ -213,6 +253,7 @@ const checklist: HandoffItem[] = [
];
const categoryOptions = ['全部', '商户', '微信', '转账', '硬件'];
const draft = reactive<Record<string, HandoffDraft>>(createInitialDraft());
const sessionMeta = reactive<SessionMeta>(readSessionMeta());
const filteredChecklist = computed(() => checklist.filter((item) => {
const categoryMatched = category.value === '全部' || item.category === category.value;
@@ -244,6 +285,24 @@ function persistDraft() {
localStorage.setItem(storageKey, JSON.stringify(draft));
}
function readSessionMeta(): SessionMeta {
try {
const saved = JSON.parse(localStorage.getItem(sessionStorageKey) || '{}') as Partial<SessionMeta>;
return {
batchNo: saved.batchNo || '',
site: saved.site || '',
coordinator: saved.coordinator || '',
executedAt: saved.executedAt || ''
};
} catch {
return { batchNo: '', site: '', coordinator: '', executedAt: '' };
}
}
function persistSessionMeta() {
localStorage.setItem(sessionStorageKey, JSON.stringify(sessionMeta));
}
function resetDraft() {
for (const item of checklist) {
draft[item.id] = { status: 'PENDING', owner: '', note: '' };
@@ -261,12 +320,18 @@ function applyBulkMark() {
}
async function copyBlockedSummary() {
const header = [
`联调批次:${sessionMeta.batchNo || '未填写'}`,
`现场/门店:${sessionMeta.site || '未填写'}`,
`负责人:${sessionMeta.coordinator || '未填写'}`,
`执行时间:${sessionMeta.executedAt || '未填写'}`
].join('\n');
const content = blockedItems.value.map((item) => {
const itemDraft = draft[item.id];
return `[${item.category}] ${item.title} - ${itemDraft.owner || '未分派'} - ${itemDraft.note || item.expected}`;
}).join('\n');
try {
await navigator.clipboard.writeText(content);
await navigator.clipboard.writeText(`${header}\n\n${content}`);
ElMessage.success('阻断摘要已复制');
} catch {
ElMessage.warning('当前浏览器不允许自动复制,请使用导出 CSV 留存');
@@ -275,6 +340,11 @@ async function copyBlockedSummary() {
function exportChecklist() {
downloadCsv(`cleaning-field-handoff-${Date.now()}.csv`, [
['类别', '字段', '值', '', '', '', '', ''],
['session', '联调批次', sessionMeta.batchNo, '', '', '', '', ''],
['session', '现场/门店', sessionMeta.site, '', '', '', '', ''],
['session', '负责人', sessionMeta.coordinator, '', '', '', '', ''],
['session', '执行时间', sessionMeta.executedAt, '', '', '', '', ''],
['ID', '类别', '检查项', '期望', '状态', '负责人', '结果记录', '证据'],
...checklist.map((item) => [
item.id,