R107/A1-A6 修复网页视觉巡检问题

这个提交包含在:
2026-05-07 20:05:48 +08:00
父节点 78b07ad895
当前提交 230d8f53db
共修改 8 个文件,包含 77 行新增和 101 行删除
+9 -57
查看文件
@@ -1,5 +1,5 @@
{
"generatedAt": "2026-05-07T11:36:29.101Z",
"generatedAt": "2026-05-07T12:03:41.249Z",
"checks": [
{
"label": "PC Portal",
@@ -37,16 +37,16 @@
{
"label": "PC 报销详情",
"type": "button-compare",
"status": "diff",
"status": "same-layout",
"labelA": "明细编辑",
"labelB": "明细删除",
"data": {
"a": {
"text": "编辑",
"rect": {
"width": 50,
"width": 64,
"height": 40,
"x": 853,
"x": 805,
"y": 1060
},
"style": {
@@ -62,10 +62,10 @@
"b": {
"text": "删除",
"rect": {
"width": 50,
"width": 64,
"height": 40,
"x": 853,
"y": 1108
"x": 877,
"y": 1060
},
"style": {
"backgroundColor": "rgba(255, 255, 255, 0.9)",
@@ -133,7 +133,7 @@
"height": 960,
"scrollWidth": 1440,
"tableCount": 1,
"uuidCount": 1
"uuidCount": 0
},
{
"label": "PC 请假",
@@ -235,55 +235,7 @@
"uuidCount": 0
}
],
"findings": [
{
"severity": "P2",
"page": "PC 报销详情",
"category": "文案",
"title": "页面仍有偏解释型/啰嗦文案",
"detail": "可在当前页面内直接绑定已有发票,或上传新发票后等待 OCR 完成。",
"evidence": "/home/shunli/Projects/hyb-oa/output/playwright/r106/pc-reimbursement-detail.png"
},
{
"severity": "P2",
"page": "PC 报销详情",
"category": "按钮一致性",
"title": "明细编辑 与 明细删除 视觉规格不一致",
"detail": "纵向位置 1060/1108"
},
{
"severity": "P1",
"page": "PC 票据中心",
"category": "业务展示",
"title": "页面向用户暴露内部 UUID",
"detail": "1 个 UUID",
"evidence": "/home/shunli/Projects/hyb-oa/output/playwright/r106/pc-invoice-pool.png"
},
{
"severity": "P2",
"page": "PC 票据中心",
"category": "业务展示",
"title": "票据详情仍把内部票据 ID 放在主信息区",
"detail": "票据详情存在“票据 ID”字段,应改为票据号码/销售方/金额等业务信息。",
"evidence": "/home/shunli/Projects/hyb-oa/output/playwright/r106/pc-invoice-pool.png"
},
{
"severity": "P2",
"page": "PC 票据中心",
"category": "按钮样式",
"title": "危险/终态操作出现主色背景叠红色文字",
"detail": "[{\"text\":\"手工归档\",\"testid\":\"invoice-archive\",\"color\":\"rgb(220, 38, 38)\",\"backgroundColor\":\"rgb(0, 57, 138)\",\"visible\":true,\"redTextOnPrimary\":true}]",
"evidence": "/home/shunli/Projects/hyb-oa/output/playwright/r106/pc-invoice-pool.png"
},
{
"severity": "P2",
"page": "PC 差旅",
"category": "视觉",
"title": "存在疑似文字/控件裁切",
"detail": "[{\"tag\":\"input\",\"testid\":\"\",\"text\":\"\",\"client\":\"189x42\",\"scroll\":\"224x42\"},{\"tag\":\"input\",\"testid\":\"\",\"text\":\"\",\"client\":\"189x42\",\"scroll\":\"224x42\"}]",
"evidence": "/home/shunli/Projects/hyb-oa/output/playwright/r106/pc-travel.png"
}
],
"findings": [],
"seedData": {
"reimbursementId": "[redacted-local-id]",
"reimbursementItemId": "[redacted-local-id]",
+4 -20
查看文件
@@ -1,34 +1,18 @@
# R106 自动视觉巡检报告
生成时间:2026-05-07T11:36:29.101Z
生成时间:2026-05-07T12:03:41.249Z
## 结论
- 巡检记录数:15
- 发现问题数:6
- 发现问题数:0
- 截图目录:/home/shunli/Projects/hyb-oa/output/playwright/r106
## 发现的问题
1. [P2] PC 报销详情 / 文案:页面仍有偏解释型/啰嗦文案
- 详情:可在当前页面内直接绑定已有发票,或上传新发票后等待 OCR 完成。
- 截图:/home/shunli/Projects/hyb-oa/output/playwright/r106/pc-reimbursement-detail.png
2. [P2] PC 报销详情 / 按钮一致性:明细编辑 与 明细删除 视觉规格不一致
- 详情:纵向位置 1060/1108
3. [P1] PC 票据中心 / 业务展示:页面向用户暴露内部 UUID
- 详情:1 个 UUID
- 截图:/home/shunli/Projects/hyb-oa/output/playwright/r106/pc-invoice-pool.png
4. [P2] PC 票据中心 / 业务展示:票据详情仍把内部票据 ID 放在主信息区
- 详情:票据详情存在“票据 ID”字段,应改为票据号码/销售方/金额等业务信息。
- 截图:/home/shunli/Projects/hyb-oa/output/playwright/r106/pc-invoice-pool.png
5. [P2] PC 票据中心 / 按钮样式:危险/终态操作出现主色背景叠红色文字
- 详情:[{"text":"手工归档","testid":"invoice-archive","color":"rgb(220, 38, 38)","backgroundColor":"rgb(0, 57, 138)","visible":true,"redTextOnPrimary":true}]
- 截图:/home/shunli/Projects/hyb-oa/output/playwright/r106/pc-invoice-pool.png
6. [P2] PC 差旅 / 视觉:存在疑似文字/控件裁切
- 详情:[{"tag":"input","testid":"","text":"","client":"189x42","scroll":"224x42"},{"tag":"input","testid":"","text":"","client":"189x42","scroll":"224x42"}]
- 截图:/home/shunli/Projects/hyb-oa/output/playwright/r106/pc-travel.png
- 未发现规则命中的视觉/文案/展示问题。
## 按钮对比
- PC 报销详情:明细编辑 vs 明细删除 => diff
- PC 报销详情:明细编辑 vs 明细删除 => same-layout
- PC 报销详情:返回列表 vs 删除草稿 => same-layout
+4 -3
查看文件
@@ -1034,7 +1034,7 @@ export function ReimbursementDetailPage() {
<TableHead>发生日期</TableHead>
<TableHead>金额</TableHead>
<TableHead>说明</TableHead>
<TableHead className="w-32">操作</TableHead>
<TableHead className="w-44">操作</TableHead>
</tr>
</TableHeader>
<TableBody>
@@ -1087,9 +1087,10 @@ export function ReimbursementDetailPage() {
</TableCell>
<TableCell>
{canEditItems ? (
<div className="flex flex-wrap gap-2">
<div className="flex min-w-36 items-center gap-2">
<Button
data-testid="reimbursement-item-edit"
className="min-w-16"
size="sm"
type="button"
variant="secondary"
@@ -1099,6 +1100,7 @@ export function ReimbursementDetailPage() {
</Button>
<Button
data-testid="reimbursement-item-delete"
className="min-w-16"
size="sm"
type="button"
variant="secondary"
@@ -1142,7 +1144,6 @@ export function ReimbursementDetailPage() {
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<div className="space-y-1">
<h3 className="text-base font-semibold text-zinc-900">关联票据</h3>
<p className="text-xs text-zinc-500">可在当前页面内直接绑定已有发票,或上传新发票后等待 OCR 完成。</p>
</div>
{canOpenInvoiceBindDialog ? (
<Button
+1 -1
查看文件
@@ -1072,7 +1072,7 @@ function TravelRequestForm(props: TravelRequestFormProps) {
</Button>
) : null}
</div>
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
<div className="grid gap-4 md:grid-cols-2">
<div>
<Label>角色</Label>
<select
@@ -45,6 +45,18 @@ export type InvoiceDetailPanelProps = {
setConfirmAction: (action: { type: string; invoiceId: string } | null) => void;
};
function invoiceStatusLabel(status: string) {
const labels: Record<string, string> = {
pending_ocr: "识别中",
available: "可用",
bound: "已绑定",
archived: "已归档",
ocr_failed: "识别失败",
duplicate_suspected: "疑似重复",
};
return labels[status] ?? status;
}
export function InvoiceDetailPanel({
detail,
canEditDetail,
@@ -84,6 +96,17 @@ export function InvoiceDetailPanel({
confirmAction,
setConfirmAction,
}: InvoiceDetailPanelProps) {
const invoiceNo = detail
? [detail.invoiceCode, detail.invoiceNumber].filter(Boolean).join("-") || detail.originalName || "待识别"
: "";
const bindingStatus = detail?.linkedReimbursementId
? "已绑定报销单"
: detail?.linkedTravelOrderId
? "已绑定差旅订单"
: detail?.linkedItemId
? "已绑定明细"
: "未绑定";
return (
<>
<div className="card-std space-y-4 p-4 md:p-5" data-testid="invoice-detail-panel">
@@ -91,12 +114,12 @@ export function InvoiceDetailPanel({
{detail ? (
<>
<div className="grid grid-cols-2 gap-y-3 pt-2 text-sm text-zinc-700">
<div><span className="text-zinc-500">票据 ID:</span>{detail.id}</div>
<div><span className="text-zinc-500">状态:</span>{detail.status}</div>
<div className="col-span-2">
<span className="text-zinc-500">发票号:</span>
{detail.invoiceCode || "-"}-{detail.invoiceNumber || "-"}
<span className="text-zinc-500">发票号码:</span>
{invoiceNo}
</div>
<div><span className="text-zinc-500">状态:</span>{invoiceStatusLabel(detail.status)}</div>
<div><span className="text-zinc-500">绑定状态:</span>{bindingStatus}</div>
<div><span className="text-zinc-500">销售方:</span>{detail.sellerName || "-"}</div>
<div><span className="text-zinc-500">购买方:</span>{detail.buyerName || "-"}</div>
<div className="col-span-2">
@@ -104,7 +127,7 @@ export function InvoiceDetailPanel({
{detail.totalAmount ? new Intl.NumberFormat("zh-CN", { style: "currency", currency: "CNY" }).format(Number(detail.totalAmount)) : "-"}
</div>
<div><span className="text-zinc-500">开票日期:</span>{detail.invoiceDate || "-"}</div>
<div><span className="text-zinc-500">关联报销:</span>{detail.linkedReimbursementId || "-"}</div>
<div><span className="text-zinc-500">文件名:</span>{detail.originalName || "-"}</div>
<div className="col-span-2 flex flex-col gap-1">
<div>
<span className="text-zinc-500">验真状态:</span>
@@ -144,7 +167,7 @@ export function InvoiceDetailPanel({
{canVerifyInvoice ? (
<Button
data-testid="invoice-trigger-verification"
className="btn-secondary"
variant="secondary"
type="button"
disabled={triggerVerificationMutation.isPending || detail.status === "pending_ocr"}
onClick={() => triggerVerificationMutation.mutate(detail.id)}
@@ -158,7 +181,7 @@ export function InvoiceDetailPanel({
["failed", "error"].includes(detail.verification.status) ? (
<Button
data-testid="invoice-retry-verification"
className="btn-secondary"
variant="secondary"
type="button"
disabled={retryVerificationMutation.isPending}
onClick={() => retryVerificationMutation.mutate(detail.id)}
@@ -170,7 +193,7 @@ export function InvoiceDetailPanel({
{canEditDetail ? (
<Button
data-testid="invoice-edit-open"
className="btn-secondary"
variant="secondary"
type="button"
disabled={patchMutation.isPending}
onClick={() => setEditOpen(!editOpen)}
@@ -182,7 +205,7 @@ export function InvoiceDetailPanel({
{canEditDetail && detail.status === "ocr_failed" ? (
<Button
data-testid="invoice-retry-ocr"
className="btn-secondary"
variant="secondary"
type="button"
disabled={retryOcrMutation.isPending}
onClick={() => setConfirmAction({ type: "retryOcr", invoiceId: detail.id })}
@@ -194,7 +217,7 @@ export function InvoiceDetailPanel({
{canEditDetail && canUploadInvoice && detail.status === "bound" ? (
<Button
data-testid="invoice-unbind"
className="btn-secondary"
variant="secondary"
type="button"
disabled={unbindMutation.isPending}
onClick={() => setConfirmAction({ type: "unbind", invoiceId: detail.id })}
@@ -206,7 +229,7 @@ export function InvoiceDetailPanel({
{canEditDetail && detail.status !== "bound" && detail.status !== "archived" ? (
<Button
data-testid="invoice-archive"
className="btn-secondary text-red-600 hover:text-red-700 hover:bg-red-50"
variant="destructive"
type="button"
disabled={archiveMutation.isPending}
onClick={() => setConfirmAction({ type: "archive", invoiceId: detail.id })}
@@ -220,7 +243,7 @@ export function InvoiceDetailPanel({
["available", "ocr_failed"].includes(detail.status) ? (
<Button
data-testid="invoice-override-duplicate"
className="btn-secondary"
variant="secondary"
type="button"
disabled={overrideDuplicateMutation.isPending}
onClick={() => {
@@ -246,10 +269,10 @@ export function InvoiceDetailPanel({
<p className="mb-3 text-sm font-semibold text-zinc-900 border-b border-zinc-200/60 pb-2">验真记录</p>
{(verificationQuery.data?.items ?? []).length ? (
<div className="space-y-3 text-xs">
{(verificationQuery.data?.items ?? []).map((record) => (
{(verificationQuery.data?.items ?? []).map((record, index) => (
<div key={record.id} className="rounded-md border border-zinc-200 bg-white px-3 py-2 shadow-sm">
<div className="flex flex-wrap items-center justify-between gap-2 mb-1">
<span className="font-mono text-zinc-500">{record.id}</span>
<span className="font-medium text-zinc-700">记录 {index + 1}</span>
<span className={`font-semibold ${record.status === "failed" || record.status === "error" ? "text-red-600" : "text-emerald-600"}`}>
{record.status}
</span>
@@ -403,7 +426,6 @@ export function InvoiceDetailPanel({
<div className="flex flex-wrap gap-2">
<Button
data-testid="invoice-edit-save"
className="btn-primary"
disabled={patchMutation.isPending}
onClick={() => patchMutation.mutate()}
type="button"
@@ -412,7 +434,7 @@ export function InvoiceDetailPanel({
</Button>
<Button
data-testid="invoice-edit-cancel"
className="btn-secondary"
variant="secondary"
disabled={patchMutation.isPending}
onClick={() => setEditOpen(false)}
type="button"
@@ -21,6 +21,11 @@ export type InvoiceListCardProps = {
signedUrlMutation: UseMutationResult<{ signedUrl: string; expiresAt: string }, Error, string>;
};
function invoiceDisplayName(item: InvoicePoolListResponse["items"][number]) {
const invoiceNo = [item.invoiceCode, item.invoiceNumber].filter(Boolean).join("-");
return item.originalName || invoiceNo || "待识别票据";
}
export function InvoiceListCard({
listQuery,
status,
@@ -79,8 +84,8 @@ export function InvoiceListCard({
<article className="rounded-xl border border-zinc-200 bg-white p-4 shadow-sm" data-testid="invoice-mobile-row" key={item.id}>
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<div className="truncate text-sm font-semibold text-zinc-900">{item.originalName || item.id}</div>
<div className="mt-1 font-mono text-xs text-zinc-500">{item.id.slice(0, 8)}</div>
<div className="truncate text-sm font-semibold text-zinc-900">{invoiceDisplayName(item)}</div>
<div className="mt-1 text-xs text-zinc-500">{item.invoiceDate || "待识别"}</div>
</div>
<StatusBadge className="shrink-0" status={item.status} />
</div>
@@ -141,7 +146,7 @@ export function InvoiceListCard({
<table className="w-full text-left text-sm text-zinc-600" data-testid="invoice-pool-table">
<thead className="bg-zinc-50/50">
<tr>
<th className="table-th w-[280px]">票据流水号</th>
<th className="table-th w-[280px]">票据名称</th>
<th className="table-th w-[120px]">状态</th>
<th className="table-th w-[200px]">发票号码</th>
<th className="table-th">识别状态</th>
@@ -153,7 +158,8 @@ export function InvoiceListCard({
listQuery.data?.items.map((item) => (
<tr className="table-row" data-testid="invoice-row" key={item.id}>
<td className="table-td">
<div className="font-mono text-xs text-zinc-500">{item.id}</div>
<div className="max-w-[260px] truncate font-medium text-zinc-900">{invoiceDisplayName(item)}</div>
<div className="mt-1 text-xs text-zinc-500">{item.invoiceDate || "待识别"}</div>
</td>
<td className="table-td"><StatusBadge status={item.status} /></td>
<td className="table-td font-medium text-zinc-900">
@@ -566,6 +566,10 @@ test.describe.serial("R106 自动视觉巡检", () => {
await adminMobileContext.close();
writeReport(seedData);
if (process.env.R106_VISUAL_FAIL_ON_FINDINGS === "1") {
const summary = findings.map((finding) => `${finding.severity} ${finding.page} / ${finding.category}: ${finding.title}`).join("\n");
expect(findings, summary || "R106 visual findings should be empty").toEqual([]);
}
await expect.poll(() => findings.length, { message: "R106 report generated" }).toBeGreaterThanOrEqual(0);
});
});
@@ -0,0 +1,7 @@
ID,Title,Description,Acceptance,Test_Method,Tools,Dev_Status,Review1_Status,Regression_Status,Files,Dependencies,Notes
A1,R107 UI 修复执行合同,"基于 R106 自动视觉巡检报告修复报销、票据、差旅的展示与视觉问题;不修改权限、审批状态机、Prisma schema。","CSV 校验通过;范围清晰;后续验证命令可复现。","python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-07_19-51-41-r107-ui-visual-fixes.csv;业务流程:把 R106 发现转为 R107 可验收修复项。","python|manual",DONE,DONE,DONE,"issues/2026-05-07_19-51-41-r107-ui-visual-fixes.csv","[]","回滚路径:删除本批前端改动和 R107 CSV,不涉及数据迁移。"
A2,报销详情文案与明细按钮一致性,"精简关联票据说明文案;修复明细编辑/删除按钮上下堆叠和视觉规格不一致。","R106 报告不再出现 PC 报销详情啰嗦文案和明细按钮 diff。","cd client && npm run typecheck && npm run build;QA_SKIP_CLIENT_BUILD=1 QA_PORT=3199 QA_DB_NAME=hyb_oa_r107_ui bash scripts/qa/run-web-gates.sh r106-visual-local;业务流程:员工查看报销草稿、编辑/删除明细、添加发票入口。","typescript|playwright",DONE,DONE,DONE,"client/src/pages/ReimbursementDetailPage.tsx","[""A1""]","只改展示和布局。"
A3,票据详情业务化展示,"票据详情主信息不展示内部 UUID;用发票号、销售方、购买方、金额、开票日期、绑定状态替代内部 ID。","R106 报告不再出现 PC 票据中心 UUID 暴露和票据 ID 主展示问题。","cd client && npm run typecheck && npm run build;运行 R106 视觉巡检并检查 artifacts/r106-visual-audit/report.md;业务流程:员工进入票据中心查看票据详情。","typescript|playwright",DONE,DONE,DONE,"client/src/pages/invoice-pool/InvoiceDetailPanel.tsx|client/src/pages/invoice-pool/InvoiceListCard.tsx","[""A1""]","若 API 暂无业务单号,则只显示绑定状态,不显示 UUID。"
A4,票据归档按钮语义样式,"修复手工归档蓝底红字问题,使用统一危险操作样式。","R106 报告不再出现危险按钮红字叠主色背景。","cd client && npm run typecheck && npm run build;运行 R106 视觉巡检;业务流程:票据中心对未绑定票据执行归档前确认。","typescript|playwright",DONE,DONE,DONE,"client/src/pages/invoice-pool/InvoiceDetailPanel.tsx","[""A3""]","只改按钮样式与文案,不改归档逻辑。"
A5,差旅行程表单裁切修复,"调整差旅行程段表单列宽,避免时间输入控件裁切。","R106 报告不再出现 PC 差旅控件裁切。","cd client && npm run typecheck && npm run build;运行 R106 视觉巡检;业务流程:员工新建差旅草稿、填写行程段出发/到达时间。","typescript|playwright",DONE,DONE,DONE,"client/src/pages/TravelRequestsPage.tsx","[""A1""]","只改布局,不改差旅保存/提交逻辑。"
A6,R107 严格视觉回归与提交,"增加或使用严格视觉验证模式,确认 R107 修复项归零,回填状态并中文提交。","R107 相关命令通过;CSV 状态回填 DONE;中文 commit 引用 R107/A1-A6。","python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-07_19-51-41-r107-ui-visual-fixes.csv;cd client && npm run typecheck && npm run build;R106_VISUAL_FAIL_ON_FINDINGS=1 QA_SKIP_CLIENT_BUILD=1 QA_PORT=3199 QA_DB_NAME=hyb_oa_r107_ui bash scripts/qa/run-web-gates.sh r106-visual-local;git diff --check;业务流程:PC 报销、票据、差旅页面视觉回归。","python|typescript|playwright|git",DONE,DONE,DONE,"client/tests/e2e/r106-visual-audit.spec.ts|artifacts/r106-visual-audit","[""A2"",""A3"",""A4"",""A5""]","报告发现问题数为 0;已检查不包含密码、token、cookie、secret 和 UUID 原文;不提交 output/playwright 截图。"
1 ID Title Description Acceptance Test_Method Tools Dev_Status Review1_Status Regression_Status Files Dependencies Notes
2 A1 R107 UI 修复执行合同 基于 R106 自动视觉巡检报告修复报销、票据、差旅的展示与视觉问题;不修改权限、审批状态机、Prisma schema。 CSV 校验通过;范围清晰;后续验证命令可复现。 python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-07_19-51-41-r107-ui-visual-fixes.csv;业务流程:把 R106 发现转为 R107 可验收修复项。 python|manual DONE DONE DONE issues/2026-05-07_19-51-41-r107-ui-visual-fixes.csv [] 回滚路径:删除本批前端改动和 R107 CSV,不涉及数据迁移。
3 A2 报销详情文案与明细按钮一致性 精简关联票据说明文案;修复明细编辑/删除按钮上下堆叠和视觉规格不一致。 R106 报告不再出现 PC 报销详情啰嗦文案和明细按钮 diff。 cd client && npm run typecheck && npm run build;QA_SKIP_CLIENT_BUILD=1 QA_PORT=3199 QA_DB_NAME=hyb_oa_r107_ui bash scripts/qa/run-web-gates.sh r106-visual-local;业务流程:员工查看报销草稿、编辑/删除明细、添加发票入口。 typescript|playwright DONE DONE DONE client/src/pages/ReimbursementDetailPage.tsx ["A1"] 只改展示和布局。
4 A3 票据详情业务化展示 票据详情主信息不展示内部 UUID;用发票号、销售方、购买方、金额、开票日期、绑定状态替代内部 ID。 R106 报告不再出现 PC 票据中心 UUID 暴露和票据 ID 主展示问题。 cd client && npm run typecheck && npm run build;运行 R106 视觉巡检并检查 artifacts/r106-visual-audit/report.md;业务流程:员工进入票据中心查看票据详情。 typescript|playwright DONE DONE DONE client/src/pages/invoice-pool/InvoiceDetailPanel.tsx|client/src/pages/invoice-pool/InvoiceListCard.tsx ["A1"] 若 API 暂无业务单号,则只显示绑定状态,不显示 UUID。
5 A4 票据归档按钮语义样式 修复手工归档蓝底红字问题,使用统一危险操作样式。 R106 报告不再出现危险按钮红字叠主色背景。 cd client && npm run typecheck && npm run build;运行 R106 视觉巡检;业务流程:票据中心对未绑定票据执行归档前确认。 typescript|playwright DONE DONE DONE client/src/pages/invoice-pool/InvoiceDetailPanel.tsx ["A3"] 只改按钮样式与文案,不改归档逻辑。
6 A5 差旅行程表单裁切修复 调整差旅行程段表单列宽,避免时间输入控件裁切。 R106 报告不再出现 PC 差旅控件裁切。 cd client && npm run typecheck && npm run build;运行 R106 视觉巡检;业务流程:员工新建差旅草稿、填写行程段出发/到达时间。 typescript|playwright DONE DONE DONE client/src/pages/TravelRequestsPage.tsx ["A1"] 只改布局,不改差旅保存/提交逻辑。
7 A6 R107 严格视觉回归与提交 增加或使用严格视觉验证模式,确认 R107 修复项归零,回填状态并中文提交。 R107 相关命令通过;CSV 状态回填 DONE;中文 commit 引用 R107/A1-A6。 python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-07_19-51-41-r107-ui-visual-fixes.csv;cd client && npm run typecheck && npm run build;R106_VISUAL_FAIL_ON_FINDINGS=1 QA_SKIP_CLIENT_BUILD=1 QA_PORT=3199 QA_DB_NAME=hyb_oa_r107_ui bash scripts/qa/run-web-gates.sh r106-visual-local;git diff --check;业务流程:PC 报销、票据、差旅页面视觉回归。 python|typescript|playwright|git DONE DONE DONE client/tests/e2e/r106-visual-audit.spec.ts|artifacts/r106-visual-audit ["A2","A3","A4","A5"] 报告发现问题数为 0;已检查不包含密码、token、cookie、secret 和 UUID 原文;不提交 output/playwright 截图。