refactor: 清理代码注释并优化组件结构
- 在 useQrCode.ts 中移除多余的注释,简化代码逻辑。 - 在 DataPreview.tsx 和 FieldEditor.tsx 中删除不必要的注释,提升可读性。 - 在 format.ts 和 jsonToToml.ts 中更新注释内容,确保准确性。
This commit is contained in:
@@ -6,10 +6,8 @@ import { useContextMenuData } from '@/utils/useContextMenuData';
|
|||||||
import type { QrCodeContextValue } from '../contexts/QrCodeContext';
|
import type { QrCodeContextValue } from '../contexts/QrCodeContext';
|
||||||
import type { QrCodeGeneratorState, QrCodeMode, QrCodeParserState } from '../types';
|
import type { QrCodeGeneratorState, QrCodeMode, QrCodeParserState } from '../types';
|
||||||
|
|
||||||
/** 防抖延迟时间(毫秒) */
|
|
||||||
const DEBOUNCE_DELAY = 500;
|
const DEBOUNCE_DELAY = 500;
|
||||||
|
|
||||||
/** 检测文本是否为URL格式 */
|
|
||||||
function isUrl(text: string): boolean {
|
function isUrl(text: string): boolean {
|
||||||
const trimmed = text.trim();
|
const trimmed = text.trim();
|
||||||
if (!trimmed) return false;
|
if (!trimmed) return false;
|
||||||
@@ -99,7 +97,6 @@ export function useQrCode(): QrCodeContextValue {
|
|||||||
|
|
||||||
const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
|
||||||
// 清除防抖定时器
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
if (debounceTimerRef.current) {
|
if (debounceTimerRef.current) {
|
||||||
@@ -108,7 +105,6 @@ export function useQrCode(): QrCodeContextValue {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
/** 自动检测URL并生成二维码 */
|
|
||||||
const autoGenerateIfUrl = useCallback((text: string) => {
|
const autoGenerateIfUrl = useCallback((text: string) => {
|
||||||
if (debounceTimerRef.current) {
|
if (debounceTimerRef.current) {
|
||||||
clearTimeout(debounceTimerRef.current);
|
clearTimeout(debounceTimerRef.current);
|
||||||
@@ -149,7 +145,6 @@ export function useQrCode(): QrCodeContextValue {
|
|||||||
[autoGenerateIfUrl],
|
[autoGenerateIfUrl],
|
||||||
);
|
);
|
||||||
|
|
||||||
/** 手动触发生成二维码(用于非URL文本) */
|
|
||||||
const confirmGenerate = useCallback(() => {
|
const confirmGenerate = useCallback(() => {
|
||||||
const text = generatorState.textToEncode.trim();
|
const text = generatorState.textToEncode.trim();
|
||||||
|
|
||||||
@@ -159,10 +154,8 @@ export function useQrCode(): QrCodeContextValue {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 先设置 loading 状态,让 React 渲染加载动画
|
|
||||||
setGeneratorState((prev) => ({ ...prev, generating: true, inputError: '' }));
|
setGeneratorState((prev) => ({ ...prev, generating: true, inputError: '' }));
|
||||||
|
|
||||||
// 延迟到下一帧生成,确保 loading 状态先被渲染显示
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const qrCodeDataUrl = generateQrCodeDataUrl(text);
|
const qrCodeDataUrl = generateQrCodeDataUrl(text);
|
||||||
|
|
||||||
@@ -186,7 +179,6 @@ export function useQrCode(): QrCodeContextValue {
|
|||||||
}, 0);
|
}, 0);
|
||||||
}, [generatorState.textToEncode]);
|
}, [generatorState.textToEncode]);
|
||||||
|
|
||||||
/** 返回编辑态,保留上次输入内容 */
|
|
||||||
const backToEdit = useCallback(() => {
|
const backToEdit = useCallback(() => {
|
||||||
if (debounceTimerRef.current) {
|
if (debounceTimerRef.current) {
|
||||||
clearTimeout(debounceTimerRef.current);
|
clearTimeout(debounceTimerRef.current);
|
||||||
@@ -200,7 +192,6 @@ export function useQrCode(): QrCodeContextValue {
|
|||||||
}));
|
}));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
/** 从图片URL解析二维码 */
|
|
||||||
const parseQrCodeFromUrl = useCallback(async (imageUrl: string) => {
|
const parseQrCodeFromUrl = useCallback(async (imageUrl: string) => {
|
||||||
try {
|
try {
|
||||||
setParserState((prev) => ({
|
setParserState((prev) => ({
|
||||||
@@ -212,7 +203,6 @@ export function useQrCode(): QrCodeContextValue {
|
|||||||
selectedFile: null,
|
selectedFile: null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 从URL获取图片并转换为File对象
|
|
||||||
const response = await fetch(imageUrl);
|
const response = await fetch(imageUrl);
|
||||||
const blob = await response.blob();
|
const blob = await response.blob();
|
||||||
const file = new File([blob], 'qrcode-image.png', { type: blob.type });
|
const file = new File([blob], 'qrcode-image.png', { type: blob.type });
|
||||||
@@ -239,35 +229,19 @@ export function useQrCode(): QrCodeContextValue {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
/** 右键菜单传入URL时,自动生成二维码或解析图片 */
|
|
||||||
const handleContextMenuData = useCallback(
|
const handleContextMenuData = useCallback(
|
||||||
(payload: string) => {
|
(payload: string) => {
|
||||||
// 检测是否为图片URL,如果是则切换到解析模式
|
|
||||||
if (isImageUrl(payload)) {
|
if (isImageUrl(payload)) {
|
||||||
setMode('parse');
|
setMode('parse');
|
||||||
void parseQrCodeFromUrl(payload);
|
void parseQrCodeFromUrl(payload);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 非图片URL,生成二维码
|
|
||||||
setMode('generate');
|
setMode('generate');
|
||||||
|
|
||||||
// 直接生成二维码,无需等待
|
|
||||||
const qrCodeDataUrl = generateQrCodeDataUrl(payload);
|
const qrCodeDataUrl = generateQrCodeDataUrl(payload);
|
||||||
|
|
||||||
if (qrCodeDataUrl) {
|
if (!qrCodeDataUrl) {
|
||||||
// 生成成功,直接跳转到预览态
|
|
||||||
setGeneratorState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
step: 'preview',
|
|
||||||
textToEncode: payload,
|
|
||||||
savedText: payload.trim(),
|
|
||||||
qrCodeDataUrl,
|
|
||||||
generating: false,
|
|
||||||
inputError: '',
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
// 生成失败,停留在输入态,显示文本供用户编辑
|
|
||||||
setGeneratorState((prev) => ({
|
setGeneratorState((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
step: 'input',
|
step: 'input',
|
||||||
@@ -284,7 +258,6 @@ export function useQrCode(): QrCodeContextValue {
|
|||||||
|
|
||||||
useContextMenuData({ featureKey: 'qrCode', onData: handleContextMenuData });
|
useContextMenuData({ featureKey: 'qrCode', onData: handleContextMenuData });
|
||||||
|
|
||||||
// 反向活态解析二维码算法
|
|
||||||
const parseQrCode = useCallback(async (file: File) => {
|
const parseQrCode = useCallback(async (file: File) => {
|
||||||
try {
|
try {
|
||||||
setParserState((prev) => ({ ...prev, parsing: true, parseError: '', decodedResult: '' }));
|
setParserState((prev) => ({ ...prev, parsing: true, parseError: '', decodedResult: '' }));
|
||||||
@@ -354,7 +327,6 @@ export function useQrCode(): QrCodeContextValue {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// 触发解析安全的后台 Promise
|
|
||||||
parseQrCode(file).catch((err) => {
|
parseQrCode(file).catch((err) => {
|
||||||
console.error('Parser standalone task thread exploded:', err);
|
console.error('Parser standalone task thread exploded:', err);
|
||||||
});
|
});
|
||||||
@@ -362,7 +334,6 @@ export function useQrCode(): QrCodeContextValue {
|
|||||||
[parseQrCode],
|
[parseQrCode],
|
||||||
);
|
);
|
||||||
|
|
||||||
// 清除解析受控文件
|
|
||||||
const handleClearFile = useCallback(() => {
|
const handleClearFile = useCallback(() => {
|
||||||
setParserState((prev) => {
|
setParserState((prev) => {
|
||||||
if (prev.previewUrl) {
|
if (prev.previewUrl) {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* 数据预览组件
|
* 数据预览组件
|
||||||
* 展示一条示例数据,展示数据结构
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
@@ -12,7 +11,6 @@ interface DataPreviewProps {
|
|||||||
fields: FieldConfig[];
|
fields: FieldConfig[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** JSON 语法高亮渲染 */
|
|
||||||
function JsonHighlight({ data }: { data: Record<string, unknown> }) {
|
function JsonHighlight({ data }: { data: Record<string, unknown> }) {
|
||||||
const formatted = useMemo(() => {
|
const formatted = useMemo(() => {
|
||||||
const lines: { indent: string; key?: string; value: string; isLast: boolean }[] = [];
|
const lines: { indent: string; key?: string; value: string; isLast: boolean }[] = [];
|
||||||
@@ -45,7 +43,6 @@ function JsonHighlight({ data }: { data: Record<string, unknown> }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 根据值类型返回颜色类名 */
|
|
||||||
function getValueColor(value: string): string {
|
function getValueColor(value: string): string {
|
||||||
if (value === 'null') return 'text-muted-foreground';
|
if (value === 'null') return 'text-muted-foreground';
|
||||||
if (value.startsWith('"')) return 'text-emerald-600 dark:text-emerald-400';
|
if (value.startsWith('"')) return 'text-emerald-600 dark:text-emerald-400';
|
||||||
@@ -55,13 +52,11 @@ function getValueColor(value: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function DataPreview({ fields }: DataPreviewProps) {
|
export default function DataPreview({ fields }: DataPreviewProps) {
|
||||||
// 生成一条示例数据
|
|
||||||
const sampleData = useMemo(() => {
|
const sampleData = useMemo(() => {
|
||||||
if (fields.length === 0) return null;
|
if (fields.length === 0) return null;
|
||||||
|
|
||||||
const record: Record<string, unknown> = {};
|
const record: Record<string, unknown> = {};
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
// 非必填字段预览显示 null
|
|
||||||
if (!field.required) {
|
if (!field.required) {
|
||||||
record[field.name] = null;
|
record[field.name] = null;
|
||||||
continue;
|
continue;
|
||||||
@@ -87,7 +82,6 @@ export default function DataPreview({ fields }: DataPreviewProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
{/* 示例标签 */}
|
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="h-5 w-5 rounded bg-primary/10 flex items-center justify-center">
|
<div className="h-5 w-5 rounded bg-primary/10 flex items-center justify-center">
|
||||||
@@ -100,7 +94,6 @@ export default function DataPreview({ fields }: DataPreviewProps) {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 示例数据展示 */}
|
|
||||||
<div className="flex-1 min-h-0 overflow-auto rounded-lg bg-zinc-950 dark:bg-zinc-900 p-4">
|
<div className="flex-1 min-h-0 overflow-auto rounded-lg bg-zinc-950 dark:bg-zinc-900 p-4">
|
||||||
<JsonHighlight data={sampleData} />
|
<JsonHighlight data={sampleData} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* 字段编辑器组件
|
* 字段编辑器组件
|
||||||
* 编辑单个字段的详细配置
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useCallback } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
@@ -16,7 +15,6 @@ import GeneratorConfig from './GeneratorConfig';
|
|||||||
interface FieldEditorProps {
|
interface FieldEditorProps {
|
||||||
field: FieldConfig;
|
field: FieldConfig;
|
||||||
onChange: (field: FieldConfig) => void;
|
onChange: (field: FieldConfig) => void;
|
||||||
/** 所有字段名列表,用于检测重复 */
|
|
||||||
allFieldNames?: string[];
|
allFieldNames?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +44,6 @@ export default function FieldEditor({ field, onChange, allFieldNames = [] }: Fie
|
|||||||
|
|
||||||
const handleNameChange = (name: string) => {
|
const handleNameChange = (name: string) => {
|
||||||
onChange({ ...field, name });
|
onChange({ ...field, name });
|
||||||
// 实时校验
|
|
||||||
const error = validateFieldName(name);
|
const error = validateFieldName(name);
|
||||||
setNameError(error);
|
setNameError(error);
|
||||||
};
|
};
|
||||||
@@ -59,15 +56,12 @@ export default function FieldEditor({ field, onChange, allFieldNames = [] }: Fie
|
|||||||
onChange({
|
onChange({
|
||||||
...field,
|
...field,
|
||||||
required,
|
required,
|
||||||
// 切换为必填时清零,切换为非必填时默认 100%
|
|
||||||
nullRate: required ? 0 : 100,
|
nullRate: required ? 0 : 100,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNullRateChange = (nullRate: number) => {
|
const handleNullRateChange = (nullRate: number) => {
|
||||||
// 限制范围 0-100
|
|
||||||
const clampedRate = Math.max(0, Math.min(100, nullRate));
|
const clampedRate = Math.max(0, Math.min(100, nullRate));
|
||||||
// 空值率为 0 时自动设为必填
|
|
||||||
if (clampedRate === 0) {
|
if (clampedRate === 0) {
|
||||||
onChange({ ...field, nullRate: clampedRate, required: true });
|
onChange({ ...field, nullRate: clampedRate, required: true });
|
||||||
} else {
|
} else {
|
||||||
@@ -105,7 +99,6 @@ export default function FieldEditor({ field, onChange, allFieldNames = [] }: Fie
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
{/* 基础配置 */}
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
@@ -139,7 +132,6 @@ export default function FieldEditor({ field, onChange, allFieldNames = [] }: Fie
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 必填/选填配置 */}
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Label className="text-sm font-medium text-foreground">{'必填'}</Label>
|
<Label className="text-sm font-medium text-foreground">{'必填'}</Label>
|
||||||
@@ -187,19 +179,16 @@ export default function FieldEditor({ field, onChange, allFieldNames = [] }: Fie
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 唯一性约束 */}
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Label className="text-sm font-medium text-foreground">{'唯一性约束'}</Label>
|
<Label className="text-sm font-medium text-foreground">{'唯一性约束'}</Label>
|
||||||
<Switch checked={field.unique} onCheckedChange={handleUniqueChange} />
|
<Switch checked={field.unique} onCheckedChange={handleUniqueChange} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 生成器选择 */}
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-sm font-medium text-foreground">{'数据生成器'}</Label>
|
<Label className="text-sm font-medium text-foreground">{'数据生成器'}</Label>
|
||||||
<GeneratorSelector selectedId={field.generatorId} onChange={handleGeneratorChange} />
|
<GeneratorSelector selectedId={field.generatorId} onChange={handleGeneratorChange} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 生成器参数配置 */}
|
|
||||||
{generator && (
|
{generator && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-sm font-medium text-foreground">{'生成器参数'}</Label>
|
<Label className="text-sm font-medium text-foreground">{'生成器参数'}</Label>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { getTextStats, type TextStats } from '@/utils/textStatistics';
|
import { getTextStats, type TextStats } from '@/utils/textStatistics';
|
||||||
import { useContextMenuData } from '@/utils/useContextMenuData';
|
import { useContextMenuData } from '@/utils/useContextMenuData';
|
||||||
|
|
||||||
@@ -10,6 +10,12 @@ export interface UseTextStatisticsReturn {
|
|||||||
|
|
||||||
export function useTextStatistics(): UseTextStatisticsReturn {
|
export function useTextStatistics(): UseTextStatisticsReturn {
|
||||||
const [text, setText] = useState('');
|
const [text, setText] = useState('');
|
||||||
|
const [debouncedText, setDebouncedText] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handle = setTimeout(() => setDebouncedText(text), 200);
|
||||||
|
return () => clearTimeout(handle);
|
||||||
|
}, [text]);
|
||||||
|
|
||||||
const handleContextMenuData = useCallback((payload: string) => {
|
const handleContextMenuData = useCallback((payload: string) => {
|
||||||
setText(payload);
|
setText(payload);
|
||||||
@@ -17,7 +23,7 @@ export function useTextStatistics(): UseTextStatisticsReturn {
|
|||||||
|
|
||||||
useContextMenuData({ featureKey: 'textStatistics', onData: handleContextMenuData });
|
useContextMenuData({ featureKey: 'textStatistics', onData: handleContextMenuData });
|
||||||
|
|
||||||
const stats = getTextStats(text);
|
const stats = useMemo(() => getTextStats(debouncedText), [debouncedText]);
|
||||||
|
|
||||||
return { text, stats, setText };
|
return { text, stats, setText };
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-11
@@ -11,7 +11,7 @@ const isObject = (v: unknown): v is Record<string, unknown> =>
|
|||||||
const isArray = (v: unknown): v is unknown[] => Array.isArray(v);
|
const isArray = (v: unknown): v is unknown[] => Array.isArray(v);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 健壮的 JSONPath 生成器:支持针对包含点号、空格或特殊字符的键名进行括号转义拦截
|
* JSONPath 生成器
|
||||||
*/
|
*/
|
||||||
const buildPath = (parent: string, key: string, isArrayChild: boolean): string => {
|
const buildPath = (parent: string, key: string, isArrayChild: boolean): string => {
|
||||||
if (isArrayChild) {
|
if (isArrayChild) {
|
||||||
@@ -47,7 +47,7 @@ const diffNode = (
|
|||||||
newValue: right,
|
newValue: right,
|
||||||
path,
|
path,
|
||||||
isLeaf: !isObject(right) && !isArray(right),
|
isLeaf: !isObject(right) && !isArray(right),
|
||||||
hasDiffInChildren: false, // 自身即是新增,子树无需向下检索
|
hasDiffInChildren: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ const diffNode = (
|
|||||||
newValue: undefined,
|
newValue: undefined,
|
||||||
path,
|
path,
|
||||||
isLeaf: !isObject(left) && !isArray(left),
|
isLeaf: !isObject(left) && !isArray(left),
|
||||||
hasDiffInChildren: false, // 自身即是删除,子树无需向下检索
|
hasDiffInChildren: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +70,6 @@ const diffNode = (
|
|||||||
const leftArr = isArray(left);
|
const leftArr = isArray(left);
|
||||||
const rightArr = isArray(right);
|
const rightArr = isArray(right);
|
||||||
|
|
||||||
// 分支 3:双对象深层递归 (容器状态)
|
|
||||||
if (leftObj && rightObj) {
|
if (leftObj && rightObj) {
|
||||||
const keySet = new Set<string>();
|
const keySet = new Set<string>();
|
||||||
const leftKeys = Object.keys(left);
|
const leftKeys = Object.keys(left);
|
||||||
@@ -97,11 +96,10 @@ const diffNode = (
|
|||||||
children,
|
children,
|
||||||
path,
|
path,
|
||||||
isLeaf: false,
|
isLeaf: false,
|
||||||
hasDiffInChildren, // 完美注入预计算衍生状态
|
hasDiffInChildren,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分支 4:双数组深层按序递归 (容器状态)
|
|
||||||
if (leftArr && rightArr) {
|
if (leftArr && rightArr) {
|
||||||
const len = Math.max(left.length, right.length);
|
const len = Math.max(left.length, right.length);
|
||||||
const children: DiffNode[] = new Array(len);
|
const children: DiffNode[] = new Array(len);
|
||||||
@@ -124,11 +122,10 @@ const diffNode = (
|
|||||||
children,
|
children,
|
||||||
path,
|
path,
|
||||||
isLeaf: false,
|
isLeaf: false,
|
||||||
hasDiffInChildren, // 完美注入预计算衍生状态
|
hasDiffInChildren,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分支 5:绝对类型安全防护大闸 (双基本基元比对)
|
|
||||||
const leftIsContainer = leftObj || leftArr;
|
const leftIsContainer = leftObj || leftArr;
|
||||||
const rightIsContainer = rightObj || rightArr;
|
const rightIsContainer = rightObj || rightArr;
|
||||||
|
|
||||||
@@ -160,7 +157,6 @@ const diffNode = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 类型完全发生突变错配,或者基本数值不相等
|
|
||||||
diffPaths.push(path);
|
diffPaths.push(path);
|
||||||
return {
|
return {
|
||||||
key,
|
key,
|
||||||
@@ -169,12 +165,12 @@ const diffNode = (
|
|||||||
newValue: right,
|
newValue: right,
|
||||||
path,
|
path,
|
||||||
isLeaf: !leftIsContainer && !rightIsContainer,
|
isLeaf: !leftIsContainer && !rightIsContainer,
|
||||||
hasDiffInChildren: false, // 变动在自身,后代无子树变动
|
hasDiffInChildren: false,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 比较两个 JSON 值的差异,返回安全的差异树及高精度差异路径列表。
|
* 比较两个 JSON 值的差异
|
||||||
*/
|
*/
|
||||||
export const diffJson = (left: unknown, right: unknown): DiffResult => {
|
export const diffJson = (left: unknown, right: unknown): DiffResult => {
|
||||||
const diffPaths: string[] = [];
|
const diffPaths: string[] = [];
|
||||||
|
|||||||
+1
-2
@@ -2,7 +2,7 @@
|
|||||||
* 格式化字节大小为易读字符串
|
* 格式化字节大小为易读字符串
|
||||||
*
|
*
|
||||||
* @param bytes 字节数
|
* @param bytes 字节数
|
||||||
* @returns 格式化后的字符串,例如 "1.5 KB" 或 "100 B"
|
* @returns 格式化后的字符串
|
||||||
*/
|
*/
|
||||||
export function formatBytes(bytes: number): string {
|
export function formatBytes(bytes: number): string {
|
||||||
if (bytes === 0) return '0 B';
|
if (bytes === 0) return '0 B';
|
||||||
@@ -17,7 +17,6 @@ export function formatBytes(bytes: number): string {
|
|||||||
unitIndex++;
|
unitIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// KB uses 1 decimal, MB/GB/TB use 2 decimals
|
|
||||||
const decimals = unitIndex === 0 ? 1 : 2;
|
const decimals = unitIndex === 0 ? 1 : 2;
|
||||||
|
|
||||||
return `${size.toFixed(decimals)} ${units[unitIndex]}`;
|
return `${size.toFixed(decimals)} ${units[unitIndex]}`;
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ export function jsonToToml(text: string): JsonToTomlResult {
|
|||||||
|
|
||||||
// TOML 要求顶层是表
|
// TOML 要求顶层是表
|
||||||
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||||||
throw new Error('TOML requires the top-level value to be an object');
|
throw new Error('TOML 要求顶层值必须是一个对象');
|
||||||
}
|
}
|
||||||
|
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
|
|||||||
Reference in New Issue
Block a user