import { diffJson } from 'diff' /** 统一键名大小写,避免预览(camelCase)与落库(PascalCase)误判差异 */ function canonicalKey(key: string): string { if (!key) return key return key.charAt(0).toLowerCase() + key.slice(1) } /** 递归规范化快照对象,便于语义对比 */ export function normalizeSnapshotForDiff(input: unknown): unknown { if (input === null || input === undefined) return input if (Array.isArray(input)) { return input.map((item) => normalizeSnapshotForDiff(item)) } if (typeof input === 'object') { const record = input as Record const keys = Object.keys(record).sort((a, b) => canonicalKey(a).localeCompare(canonicalKey(b)) ) const out: Record = {} for (const key of keys) { out[canonicalKey(key)] = normalizeSnapshotForDiff(record[key]) } return out } return input } export function parseSnapshotJson(raw: unknown): unknown { if (raw == null || raw === '') return {} if (typeof raw === 'string') { try { return JSON.parse(raw) } catch { return {} } } return raw } export function formatSnapshotJson(raw: unknown): string { try { const parsed = parseSnapshotJson(raw) return JSON.stringify(parsed, null, 2) } catch { return typeof raw === 'string' ? raw : '{}' } } export type SnapshotDiffRow = | { type: 'add' | 'remove' | 'same'; text: string } | { type: 'change'; oldText: string; newText: string } function flattenDiffParts(diff: ReturnType) { const flat: Array<{ text: string; status: 'add' | 'remove' | 'same' }> = [] diff.forEach((part) => { const status = part.added ? 'add' : part.removed ? 'remove' : 'same' const lines = (part.value || '').split('\n') lines.forEach((line, idx) => { if (idx === lines.length - 1 && line === '') return flat.push({ text: line, status }) }) }) const merged: SnapshotDiffRow[] = [] let i = 0 while (i < flat.length) { const curr = flat[i] const next = flat[i + 1] if (curr?.status === 'remove' && next?.status === 'add') { merged.push({ type: 'change', oldText: curr.text, newText: next.text }) i += 2 } else if (curr) { merged.push({ type: curr.status, text: curr.text }) i += 1 } else { i += 1 } } return merged } export function computeSnapshotDiff( originalRaw: unknown, modifiedRaw: unknown ): { length: number; rows: SnapshotDiffRow[] } { try { const original = normalizeSnapshotForDiff(parseSnapshotJson(originalRaw)) const modified = normalizeSnapshotForDiff(parseSnapshotJson(modifiedRaw)) const diff = diffJson(original, modified) const rows = flattenDiffParts(diff) const length = diff.filter((item) => item.added || item.removed).length return { length, rows } } catch { return { length: 0, rows: [] } } } export function filterDiffRows(rows: SnapshotDiffRow[], showOnlyDiff: boolean) { if (!showOnlyDiff) return rows return rows.filter((row) => row.type !== 'same') }