数据工单有了自己的面孔;件上四处样式对齐;缺库不再问人;5173 反代改回本机

用户在工作台上连报六条(2026-08-27):
- 对话区把数据工单摆成「执行工单」→ 契约 ExecutionWorkOrder.variant:'data'(引擎侧与执行工单无异,
  只换面孔),归一层从卡上带下来,web 新增 DataForm(同一套办法、另一副面孔:色/图标/「数据工单」/「确认写入」),
  办结卡与治理台人类工具目录同步;活动的两张数据工单卡骨架标 variant:"data"。
- 表显示不全不能右滑 → 目标件把 overflow-x:auto 写在 .tbl-wrap,平台包装是 .jc-tbl-wrap,谁都没管到;补一条。
- 表头只留中文 → 三张数据工单表去英文列名与 .thd 小字。
- 编辑态内部料号「只读字 + 输入框」并排 → 目标件 #tbl1 .jc-vcell{display:inline}(id 特异度)盖过
  table.on 的隐藏规则,表 1 每格其实都并排,PN 那格恰好有字;用 id 特异度覆盖回去。
- input 与 select 没对齐 → 徽标从前包 .ro 编辑态留着、压在下拉框头上把那格顶低 13px,改成随只读格一起藏;
  input/select 钉同一高度。
- 第一步「派数据工单还是放弃候选」不该问人 → 决策卡退役(覆盖规格「候选 ≥2 可放弃」那一条,用户定),
  查无记录一律自动派;剧本/说明书/测试/e2e 同步(e2e 新增 expectAsks.max 断言「不该问的没问」)。
顺带:5173 的 /api 反代默认指向一条公网隧道(150.158.83.131:46100 → nps → npc → 本机 :8787),
浏览器每次请求先出公网再绕回来;默认改回本机,要远端的显式给 API_PROXY_TARGET。

静态渲染实证:900px 宽的框里横向能滑 200px;编辑态同一行 3 input + 1 select 全部 32px 同一 y;
只读 PN 格 display:none。活动包 1.4.0;2097 测 / typecheck / lint / depcruise 绿。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJcX8A6TWp4GRs41TYryEh
This commit is contained in:
xcs
2026-08-27 12:36:13 +08:00
co-authored by Claude Fable 5
parent 3171d8a864
commit 51c87af583
15 changed files with 197 additions and 116 deletions
+3
View File
@@ -1694,6 +1694,8 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring {
const gateLedgers = Array.isArray(input.card['gateLedgers'])
? input.card['gateLedgers'].map((x) => String(x).trim()).filter((x) => /^[a-z0-9_]+\.[a-z0-9_]+$/i.test(x))
: [];
/* 数据工单 = 执行工单的变体(契约 `variant`):活动在卡上写 `"variant":"data"`,对话区据此换一副面孔 */
const variant = input.card['variant'] === 'data' ? ('data' as const) : undefined;
const issued = await deps.humanTool.issueExecution(context, {
title,
basis: basis.length > 0 ? basis : [{ k: '来源', v: '案例模型当下局面' }],
@@ -1703,6 +1705,7 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring {
...(standard === '' ? {} : { standard }),
...(blocks === undefined ? {} : { blocks: blocks as ExecutionWorkOrderDraft['blocks'] }),
...(gateLedgers.length === 0 ? {} : { gateLedgers }),
...(variant === undefined ? {} : { variant }),
});
// 打扰率的计量对象:每发一张卡就是调了一次人 —— 不计就等于没有上限
await taskStore.addConsumption(input.taskId, { humanCalls: 1 });
@@ -32,3 +32,4 @@
- **调人预算环外强制**:超过契约声明的打扰上限即 `HumanCallBudgetExceededError`,
活动代码无从自查自守
- **落地失败卡是执行工单的 preset**,不构成第五种卡
- **数据工单是执行工单的 `variant:'data'`**,同样不构成新的 mode:发卡 / 闸 / 回传全走执行工单那条路,只是对话区换一副面孔(2026-08-27)
+2
View File
@@ -44,6 +44,7 @@
--st-execute: #e07a00; --st-execute-soft: #fff4e5;
--st-decision: #7f3bf5; --st-decision-soft: #f5f0fe;
--st-collab: #0aa294; --st-collab-soft: #e4f8f5;
--st-data: #1f6fe0; --st-data-soft: #eaf2fd;
--st-wait: #6a7590; --st-wait-soft: #eef0f5;
/* —— 字体 / 投影 / 动效 —— */
--mono: ui-monospace, "SF Mono", SFMono-Regular, "JetBrains Mono", Menlo, Consolas, monospace;
@@ -2387,6 +2388,7 @@
.proto-root .jc-fc { --fc: var(--brand); --fc-soft: var(--brand-wash); width: 100%; max-width: 100%; background: #fff; border: 1px solid color-mix(in srgb, var(--fc) 22%, var(--line-soft)); border-radius: 13px; overflow: hidden; box-shadow: 0 1px 2px rgba(31,35,41,.04), 0 4px 14px rgba(31,35,41,.06); }
.proto-root .jc-fc.t-exe { --fc: var(--st-execute); --fc-soft: var(--st-execute-soft); }
.proto-root .jc-fc.t-acc { --fc: var(--st-collab); --fc-soft: var(--st-collab-soft); }
.proto-root .jc-fc.t-data { --fc: var(--st-data); --fc-soft: var(--st-data-soft); }
.proto-root .jc-fc.t-dec { --fc: var(--st-decision); --fc-soft: var(--st-decision-soft); }
.proto-root .jc-fc.t-fill { --fc: var(--brand); --fc-soft: var(--brand-wash); }
/* 题头:色染带 + 计数章 */
@@ -357,6 +357,15 @@ function Msg({
onLocate={onLocate}
{...(orderValues === undefined ? {} : { orderValues })}
/>
) : order.mode === 'execution' && order.variant === 'data' && !settled ? (
/* 数据工单 = 执行工单的变体(契约 `variant`):同一套办法,另一副面孔 */
<DataForm
order={order}
onReplyOrder={onReplyOrder}
onLocate={onLocate}
onField={onField}
{...(orderValues === undefined ? {} : { orderValues })}
/>
) : order.mode === 'execution' && !settled ? (
/* 没办结的执行单 = 一张能就地办的卡:指导书 + 依据 + 表单 + 提交 */
<ExecForm
@@ -582,11 +591,12 @@ function WoCard({ order, onLocate }: { order: WorkOrder; onLocate: (id: string)
const result = order.done?.result;
const bad = result !== undefined && isRejection(result);
const exe = order.mode === 'execution';
const data = order.mode === 'execution' && order.variant === 'data';
return (
<div className={'jc-fc ' + (exe ? 't-exe' : 't-acc')}>
<div className={'jc-fc ' + (data ? 't-data' : exe ? 't-exe' : 't-acc')}>
<div className="jc-fc-h">
<Ico name={exe ? 'file-text' : 'shield-check'} s={13} />
<b>工单 · {exe ? '执行' : '验收'}</b>
<Ico name={data ? 'database' : exe ? 'file-text' : 'shield-check'} s={13} />
<b>工单 · {data ? '数据' : exe ? '执行' : '验收'}</b>
<em className={result === undefined ? '' : bad ? 'bad' : 'ok'}>
{result === undefined ? '待处理' : bad ? '已退回' : '已办结'}
</em>
@@ -777,19 +787,54 @@ function Guide({ steps, standard }: { steps?: readonly string[] | undefined; sta
* 表格那一份值不在这儿收 —— 人是在工作区的件上改的,由外层缓冲带过来
* (与验收裁决同一条路子:编辑与提交分在两个区,缓冲得由壳层持有)。
*/
function ExecForm({
order,
onReplyOrder,
onLocate,
orderValues,
onField,
}: {
/** 执行工单与数据工单是同一套办法、两副面孔 —— 面孔在这儿定,办法只写一份 */
type FormFace = {
readonly klass: 't-exe' | 't-data';
readonly headIcon: 'clipboard-check' | 'database';
readonly head: string;
readonly docIcon: 'file-text' | 'table';
readonly docTitle: string;
readonly docHint: string;
readonly submit: string;
};
const EXEC_FACE: FormFace = {
klass: 't-exe',
headIcon: 'clipboard-check',
head: '工单 · 执行',
docIcon: 'file-text',
docTitle: '执行工单',
docHint: '整份清单在工作区,每一格都能就地改;改完回这里提交',
submit: '提交',
};
/**
* 数据工单(2026-08-27 用户:「工单类型新加入了一类数据工单…照着这个大概的样式为数据工单单独做一个组件」):
* 让人**从零建一张主数据表** —— 件上的表就是要建的记录,填齐、保存,回这里确认即入库,活动从「等待数据」唤醒。
* 引擎那一侧与执行工单无异(契约 `ExecutionWorkOrder.variant = 'data'`),所以只换面孔:色、图标、四字、按钮字。
*/
const DATA_FACE: FormFace = {
klass: 't-data',
headIcon: 'database',
head: '工单 · 数据',
docIcon: 'table',
docTitle: '数据工单',
docHint: '件上的表就是要建的记录:每格就地填、保存;填齐后回这里确认,确认即入库',
submit: '确认写入',
};
type ExecFormProps = {
order: Extract<WorkOrder, { mode: 'execution' }>;
onReplyOrder: (reply: WorkOrderReply) => void;
onLocate: (id: string) => void;
orderValues?: Readonly<Record<string, string>> | undefined;
onField: (key: string, value: string) => void;
}) {
};
/** 数据工单卡 —— 执行工单的另一副面孔(见 DATA_FACE) */
function DataForm(props: ExecFormProps) {
return <ExecForm {...props} face={DATA_FACE} />;
}
function ExecForm({ order, onReplyOrder, onLocate, orderValues, onField, face = EXEC_FACE }: ExecFormProps & { face?: FormFace }) {
const busy = useReplyBusy();
const val = (f: FormField) => orderValues?.[f.key] ?? f.ai ?? '';
/*
@@ -805,10 +850,10 @@ function ExecForm({
const gate = order.gate;
const ok = order.fields.every((f) => f.required !== true || val(f).trim() !== '') && gate?.ready !== false;
return (
<div className="jc-fc t-exe">
<div className={'jc-fc ' + face.klass}>
<div className="jc-fc-h">
<Ico name="clipboard-check" s={13} />
<b>工单 · 执行</b>
<Ico name={face.headIcon} s={13} />
<b>{face.head}</b>
{order.steps !== undefined && order.steps.length > 0 && <em>{order.steps.length} 步</em>}
</div>
<div className="jc-fc-b">
@@ -821,11 +866,11 @@ function ExecForm({
*/}
<button className="jc-fc-doc" onClick={() => onLocate(order.id)}>
<span className="jc-fc-doc-ico">
<Ico name="file-text" s={15} />
<Ico name={face.docIcon} s={15} />
</span>
<span className="jc-fc-doc-bd">
<b>执行工单</b>
<em>整份清单在工作区,每一格都能就地改;改完回这里提交</em>
<b>{face.docTitle}</b>
<em>{face.docHint}</em>
</span>
<Ico name="chevron-right" s={14} />
</button>
@@ -878,7 +923,7 @@ function ExecForm({
})
}
>
<Ico name="check" s={13} /> {order.submitLabel ?? '提交'}
<Ico name="check" s={13} /> {order.submitLabel ?? face.submit}
</Btn>
</div>
</div>
@@ -48,6 +48,12 @@ const HUMAN_TOOLS = [
ico: 'clipboard-check',
desc: '作业指导书 + 空表单,请人把一步做完;落地失败卡是它的系统预置形态,不算第五种卡',
},
{
name: '数据工单',
mode: 'work-order · execution · data',
ico: 'database',
desc: '执行工单的变体:件上的表就是要建的主数据记录,人填齐、保存、确认即入库,活动从「等待数据」唤醒',
},
{
name: '验收工单',
mode: 'work-order · acceptance',
+8 -2
View File
@@ -58,8 +58,14 @@ export default defineConfig({
*/
host: true,
proxy: {
// 默认连测试服务器;本地起 API 时用 API_PROXY_TARGET=http://127.0.0.1:8787 覆盖
'/api': { target: process.env.API_PROXY_TARGET ?? 'http://150.158.83.131:46100', changeOrigin: true },
/*
* **默认连本机 API(:8787)**;要连测试服务器就显式给 `API_PROXY_TARGET=http://150.158.83.131:46100`。
* 从前反过来(默认远端、本地要覆盖):实测那条远端是一条**隧道回到本机 :8787**,
* 浏览器的每一次请求都先出公网再绕回来 —— 能用,但慢,且隧道一断本地开发就整个瘫掉,
* 而人只会看到「前端调的后端不太对」(2026-08-27)。CLAUDE.md 写的本地工作流是
* `pnpm dev:api` + `pnpm dev:web`,默认值就该服务于这条路;要远端的人自己说出来。
*/
'/api': { target: process.env.API_PROXY_TARGET ?? 'http://127.0.0.1:8787', changeOrigin: true },
},
},
});