diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 3bb12f3..2749f33 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -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", diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 7e095d2..6f535c8 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -381,6 +381,8 @@ const zh = { failFields: "失败项", failFieldsEmpty: "—", failWholeRow: "整行", + pairBefore: "前", + pairAfter: "后", sheetAllPass: "全部一致", filterFailField: "失败比对项", filterFailFieldAll: "全部失败项", diff --git a/web/src/index.css b/web/src/index.css index 877a26e..d24de33 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -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 { diff --git a/web/src/pages/network/BizComparePage.tsx b/web/src/pages/network/BizComparePage.tsx index 2a945c1..22549da 100644 --- a/web/src/pages/network/BizComparePage.tsx +++ b/web/src/pages/network/BizComparePage.tsx @@ -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 ( +