fix(web): 录入卡在等人时,工作区摆占位页,不摆件

建档那一步(「客户 123 不在客户档案里,请核对建档」)要人做的事全在对话区那张录入卡上,
而工作区里躺着的是**上一拍**推的 HTML 件 —— 摆着只会让人以为要在那份工单上办点什么。
`pendingCardsOf`(纯函数,5 测)认出未确认的录入卡 → `ViewRegion` 在 running 之后、viewHtml 之前
插一支占位 `EmptyState`(点名那张表的人读名,指向对话区)。
决策卡**不走**这一支 —— 人要决定的对象常常就是件上那份稿,件得留着给他看。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0167qWjeyBwvfdGovd7FWbCu
This commit is contained in:
xcs
2026-08-28 01:13:07 +08:00
co-authored by Claude Fable 5
parent 282584d26e
commit e3479e0b9b
3 changed files with 122 additions and 3 deletions
@@ -0,0 +1,68 @@
/**
* 工作区看对话区的脸色 —— 录入卡在等人时摆占位页,不摆件(2026-08-28 用户定)。
*/
import { describe, expect, it } from 'vitest';
import { dataEntryCardSchema, decisionCardSchema } from '@contracts';
import type { TaskDetail } from '@contracts';
import { pendingCardsOf } from '../internal/pending-cards.ts';
const AT = '2026-08-28T00:55:30.054Z';
function entry(id: string, extra: Record<string, unknown> = {}) {
return dataEntryCardSchema.parse({
k: 'entry',
id,
at: AT,
ask: '客户 123 不在客户档案里,请核对建档信息后确认',
ledger: 'customer.profile',
ledgerLabel: '客户档案',
key: { customer_code: '123' },
fields: [],
...extra,
});
}
function decision(id: string, extra: Record<string, unknown> = {}) {
return decisionCardSchema.parse({
k: 'decision',
id,
at: AT,
ask: '经营计划是否通过?',
basis: [{ k: '依据', v: '计划表 4 行' }],
options: [
{ value: 'pass', label: '通过' },
{ value: 'reject', label: '驳回' },
],
...extra,
});
}
const chat = (...items: TaskDetail['chat']): TaskDetail['chat'] => items;
describe('pendingCardsOf', () => {
it('未确认的录入卡在场 → entry,并带上那张表的人读名', () => {
expect(pendingCardsOf(chat(entry('c-1')))).toEqual({ decision: false, entry: true, entryLedger: '客户档案' });
});
it('录入卡已确认(done 落了)→ 不再算在等', () => {
const done = { at: AT, by: 'S-1', values: { name: '测试公司' } };
expect(pendingCardsOf(chat(entry('c-1', { done })))).toEqual({ decision: false, entry: false });
});
it('登记里没有人读名时退回对象地址 —— 宁可笼统,不可编', () => {
expect(pendingCardsOf(chat(entry('c-1', { ledgerLabel: undefined }))).entryLedger).toBe('customer.profile');
});
it('未选的决策卡 → decision;它不触发占位页(人要决定的常常就是件上那份稿)', () => {
expect(pendingCardsOf(chat(decision('d-1')))).toEqual({ decision: true, entry: false });
expect(pendingCardsOf(chat(decision('d-1', { chosen: { at: AT, by: 'S-1', value: 'pass' } })))).toEqual({
decision: false,
entry: false,
});
});
it('两张未回的录入卡:点名最早那一张', () => {
const later = entry('c-2', { at: '2026-08-28T01:00:00.000Z', ledgerLabel: '联系人档案' });
expect(pendingCardsOf(chat(entry('c-1'), later)).entryLedger).toBe('客户档案');
});
});