Show before/after values for all non-key compare result columns.

Key stays single-value; other fields always render a paired 前/后 cell so cutover diffs are readable at a glance.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-18 09:27:30 +08:00
parent 157188bd3e
commit 97796478b0
4 changed files with 102 additions and 50 deletions

View file

@ -381,6 +381,8 @@ const en = {
failFields: "Failed fields",
failFieldsEmpty: "—",
failWholeRow: "Whole row",
pairBefore: "Before",
pairAfter: "After",
sheetAllPass: "All match",
filterFailField: "Failed compare field",
filterFailFieldAll: "Any failed field",

View file

@ -381,6 +381,8 @@ const zh = {
failFields: "失败项",
failFieldsEmpty: "—",
failWholeRow: "整行",
pairBefore: "前",
pairAfter: "后",
sheetAllPass: "全部一致",
filterFailField: "失败比对项",
filterFailFieldAll: "全部失败项",

View file

@ -10735,8 +10735,47 @@ html.login-page--paused .login-page__flare {
background: rgba(245, 158, 11, 0.08);
}
.bs-cmp-pair {
display: flex;
flex-direction: column;
gap: 3px;
min-width: 4.5rem;
}
.bs-cmp-pair__row {
display: grid;
grid-template-columns: 1.6em minmax(0, 1fr);
gap: 6px;
align-items: baseline;
}
.bs-cmp-pair__tag {
flex: 0 0 auto;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.02em;
color: #64748b;
line-height: 1.3;
}
.bs-cmp-val-cell--diff .bs-cmp-pair__row:first-child .bs-cmp-pair__tag {
color: #fca5a5;
}
.bs-cmp-val-cell--diff .bs-cmp-pair__row:last-child .bs-cmp-pair__tag {
color: #86efac;
}
.bs-cmp-val {
font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
color: #e2e8f0;
word-break: break-all;
line-height: 1.35;
}
.bs-cmp-val.is-empty {
color: #64748b;
font-style: italic;
}
.bs-cmp-val--pre {

View file

@ -138,6 +138,51 @@ function failFieldNames(d: DiffRow): string[] {
return Object.keys(d.changes || {});
}
/** Non-key fields: always show before/after pair for cutover review. */
function PairCell(props: {
beforeText: string;
afterText: string;
mismatch: boolean;
reason?: string;
beforeLabel: string;
afterLabel: string;
}) {
const { beforeText, afterText, mismatch, reason, beforeLabel, afterLabel } = props;
const pre = beforeText || "—";
const post = afterText || "—";
return (
<td
className={`bs-cmp-val-cell bs-cmp-val-cell--pair${
mismatch ? " bs-cmp-val-cell--diff" : ""
}`}
>
<div className="bs-cmp-pair">
<div className="bs-cmp-pair__row">
<span className="bs-cmp-pair__tag">{beforeLabel}</span>
<span
className={`bs-cmp-val${mismatch ? " bs-cmp-val--pre" : ""}${
!beforeText ? " is-empty" : ""
}`}
>
{pre}
</span>
</div>
<div className="bs-cmp-pair__row">
<span className="bs-cmp-pair__tag">{afterLabel}</span>
<span
className={`bs-cmp-val${mismatch ? " bs-cmp-val--post" : ""}${
!afterText ? " is-empty" : ""
}`}
>
{post}
</span>
</div>
</div>
{reason ? <div className="bs-cmp-val-reason muted">{reason}</div> : null}
</td>
);
}
function toggleInList(list: string[], name: string, on: boolean): string[] {
if (on) return list.includes(name) ? list : [...list, name];
return list.filter((x) => x !== name);
@ -2263,58 +2308,22 @@ export function BizComparePage() {
{resultColumns.extras.map((f) => {
const pv = cellText(pre[f]);
const av = cellText(post[f]);
const isCmp = resultColumns.compareSet.has(f);
const ch = d.changes?.[f];
if (!isCmp) {
const show =
d.kind === "removed" ? pv || "—" : av || pv || "—";
return (
<td key={f} className="bs-cmp-val-cell bs-cmp-val-cell--ctx">
<span className="bs-cmp-val">{show}</span>
</td>
);
}
if (d.kind === "added") {
return (
<td key={f} className="bs-cmp-val-cell">
<span className="bs-cmp-val bs-cmp-val--post">
{av || "—"}
</span>
</td>
);
}
if (d.kind === "removed") {
return (
<td key={f} className="bs-cmp-val-cell">
<span className="bs-cmp-val bs-cmp-val--pre">
{pv || "—"}
</span>
</td>
);
}
const mismatch = Boolean(ch);
if (!mismatch) {
return (
<td key={f} className="bs-cmp-val-cell">
<span className="bs-cmp-val">{pv || av || "—"}</span>
</td>
);
}
const isCmp = resultColumns.compareSet.has(f);
// Compare fields: engine mismatch; display fields: value differ / side missing
const mismatch = isCmp
? Boolean(ch) || d.kind === "added" || d.kind === "removed"
: pv !== av;
return (
<td key={f} className="bs-cmp-val-cell bs-cmp-val-cell--diff">
<span className="bs-cmp-val bs-cmp-val--pre">
{pv || "—"}
</span>
<span className="bs-cmp-val-arrow" aria-hidden>
→
</span>
<span className="bs-cmp-val bs-cmp-val--post">
{av || "—"}
</span>
{ch?.reason ? (
<div className="bs-cmp-val-reason muted">{ch.reason}</div>
) : null}
</td>
<PairCell
key={f}
beforeText={pv}
afterText={av}
mismatch={mismatch}
reason={ch?.reason}
beforeLabel={t("bizCompare.pairBefore")}
afterLabel={t("bizCompare.pairAfter")}
/>
);
})}
</tr>