refactor: 全面重构 JsonTools 页面,采用声明式响应架构并统一 shadcn 样式

This commit is contained in:
雨霖铃
2026-05-22 21:45:13 +08:00
parent 336b62256d
commit d2e5e6b45d
9 changed files with 797 additions and 601 deletions
+81 -32
View File
@@ -1,19 +1,31 @@
import React from 'react';
import { useLazyTranslation } from '@/utils/useLazyTranslation';
import { cn } from '@/lib/utils';
import JsonTree from './JsonTree';
import type { DiffNode, DiffResult as DiffResultType, DiffType, ViewMode } from './types';
interface DiffResultProps {
// 💡 顶层 Interface 继承原生 HTML 容器属性,扩展灵活性
export interface DiffResultProps extends React.HTMLAttributes<HTMLDivElement> {
result: DiffResultType;
viewMode: ViewMode;
activePath?: string;
}
export default function DiffResult({ result, viewMode, activePath }: DiffResultProps) {
export default function DiffResult({
result,
viewMode,
activePath,
className,
...props
}: DiffResultProps) {
const { t } = useLazyTranslation('jsonDiff');
if (viewMode === 'sideBySide') {
return (
<div className="flex flex-col md:flex-row gap-4 items-stretch">
<div
className={cn('flex flex-col md:flex-row gap-4 items-stretch w-full', className)}
{...props}
>
<div className="flex-1 min-w-0">
<SectionLabel text={t('jsonDiff:leftLabel')} />
<JsonTree node={result.root} side="left" activePath={activePath} />
@@ -27,14 +39,24 @@ export default function DiffResult({ result, viewMode, activePath }: DiffResultP
}
return (
<div className="p-3 rounded-lg bg-background border border-border font-mono text-sm overflow-x-auto min-h-[200px] max-h-[480px] overflow-y-auto">
/* 1. 单栏拍平视图容器:
- 对齐 shadcn 规范,使用 bg-card、border-border 隔离。
- 注入 tabular-nums 配合 font-mono,消灭任何行高和字符抖动。
*/
<div
className={cn(
'rounded-xl border border-border bg-card font-mono text-xs shadow-sm overflow-x-auto min-h-[200px] max-h-[520px] overflow-y-auto p-1.5',
className,
)}
{...props}
>
<UnifiedView node={result.root} depth={0} activePath={activePath} />
</div>
);
}
const SectionLabel = ({ text }: { text: string }) => (
<span className="block mb-1.5 text-[11px] font-extrabold tracking-wider text-muted-foreground uppercase">
<span className="block mb-2 text-[10px] font-bold tracking-wider text-muted-foreground/80 uppercase px-0.5 select-none">
{text}
</span>
);
@@ -51,24 +73,31 @@ const isContainerType = (v: unknown): boolean =>
(typeof v === 'object' && v !== null) || Array.isArray(v);
const prefixForType = (type: DiffType): string => {
if (type === 'added') return '+ ';
if (type === 'removed') return '- ';
if (type === 'modified') return '~ ';
return ' ';
if (type === 'added') return '+';
if (type === 'removed') return '-';
if (type === 'modified') return '~';
return ' ';
};
const colorForType = (type: DiffType): string => {
if (type === 'added') return 'text-green-600';
if (type === 'removed') return 'text-red-600';
if (type === 'modified') return 'text-amber-600';
return 'text-foreground';
};
const bgForType = (type: DiffType): string | undefined => {
if (type === 'added') return 'bg-green-50';
if (type === 'removed') return 'bg-red-50';
if (type === 'modified') return 'bg-amber-50';
return undefined;
// 2. 状态色彩超进化:
// 拒绝硬编码实色系,全部换用高度安全的语义色变体与暗黑模式自适应。
const typeThemeMap = {
added: {
text: 'text-emerald-600 dark:text-emerald-400',
bg: 'bg-emerald-500/5 dark:bg-emerald-500/10',
},
removed: {
text: 'text-destructive',
bg: 'bg-destructive/5 dark:bg-destructive/10',
},
modified: {
text: 'text-amber-600 dark:text-amber-400',
bg: 'bg-amber-500/5 dark:bg-amber-500/10',
},
unchanged: {
text: 'text-foreground/80',
bg: 'bg-transparent',
},
};
interface UnifiedViewProps {
@@ -85,7 +114,6 @@ const UnifiedView = ({ node, depth, activePath }: UnifiedViewProps) => {
const keyLabel = isRoot ? '' : `${node.key}: `;
if (!isContainer) {
// 叶子节点
if (node.type === 'modified') {
return (
<>
@@ -115,7 +143,7 @@ const UnifiedView = ({ node, depth, activePath }: UnifiedViewProps) => {
);
}
// 容器节点added/removed 整块呈现
// 容器节点整块渲染处理
if (node.type === 'added') {
return (
<UnifiedRow
@@ -163,17 +191,38 @@ interface UnifiedRowProps {
}
const UnifiedRow = ({ depth, type, text, active, multiline }: UnifiedRowProps) => {
const color = colorForType(type);
const bg = bgForType(type);
// 3. 高精度提取状态样式映射
const currentTheme = typeThemeMap[type] || typeThemeMap.unchanged;
return (
<div
className={`${bg ?? ''} ${color} ${active ? 'ring-2 ring-blue-500 rounded' : ''} ${
multiline ? 'whitespace-pre' : 'whitespace-nowrap'
} font-mono`}
style={{ paddingLeft: `${depth * 1.5}rem`, paddingRight: '0.25rem', paddingBlock: '0.2rem' }}
className={cn(
'flex items-start w-full font-mono py-0.5 select-text group transition-colors',
currentTheme.bg,
currentTheme.text,
// 4. 高亮定位条:不再使用生硬的蓝圆环,改为现代编辑器的“侧边左高亮带”设计,质感直接拉满
active &&
'bg-primary/10 relative before:absolute before:left-0 before:top-0 before:bottom-0 before:w-1 before:bg-blue-500',
)}
style={{
// 维持高精度的 Padding 基线缩进
paddingLeft: `${Math.max(0.5, depth * 1.25)}rem`,
paddingRight: '0.5rem',
}}
>
<span className="font-extrabold">{prefixForType(type)}</span>
<span>{text}</span>
{/* 5. 前缀标识:等宽锁定,强行占据 w-5 并让符号居中对齐,达成 VSCode 般的整洁排版 */}
<span className="font-bold w-5 shrink-0 text-center select-none opacity-70 tabular-nums">
{prefixForType(type)}
</span>
<span
className={cn(
'flex-1 break-all tracking-tight leading-normal',
multiline ? 'whitespace-pre' : 'whitespace-nowrap',
)}
>
{text}
</span>
</div>
);
};
@@ -192,4 +241,4 @@ const stringifyMultiline = (v: unknown, depth: number): string => {
}
};
export type { DiffResultProps };
// 💡 彻底移除了文件底部引发 TS2484 冲突的 export type { DiffResultProps } 声明