refactor: 全面重构 JsonTools 页面,采用声明式响应架构并统一 shadcn 样式
This commit is contained in:
@@ -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 } 声明
|
||||
|
||||
Reference in New Issue
Block a user