Develop (#74)
* feat: 生成成功改为 toast 提示 * refactor: 数据预览改为显示示例数据,简化组件逻辑 * fix: 非必填字段预览显示 null * fix: 修复空值率与必填状态的逻辑 * fix: 切换为非必填时默认空值率 100% * feat: 字段编辑弹窗添加「完成」按钮 去除关闭按钮,添加取消按钮 * refactor: 删除未使用的 defaultNullRate 功能 * feat: 优化规则管理功能 * fix: 更新 features 测试断言以匹配新增的 testDataGenerator * refactor: 将 formatFileSize 重命名为 formatBytes 并移至通用工具模块 * refactor: 使用 Label 组件替换原生 label 元素,并在 DecodeResultPaper 中引入 Input 组件 * refactor: 规范化页面组件目录结构 * refactor(JsonTools): 规范化页面组件目录结构 * refactor(StorageCleaner): 规范化页面组件目录结构 * refactor(Timestamp): 规范化页面组件目录结构 * refactor(i18n): 移除 chrome.i18n 国际化,统一使用中文硬编码 * refactor(AppRoot): 移除 RouterProvider 组件,直接渲染子组件 * fix(RouterProvider): 将 goBack 重命名为 goHome,修复跨窗同步 merge 不一致 * fix(ThemeModeProvider): 修复主题快照同步与首屏闪烁问题 * refactor: 将 UI 文案改为中文并优化相关逻辑 * refactor: 清理代码注释并优化组件结构 * feat(Dashboard): 重构仪表板功能组件和逻辑 * refactor: 更新功能键名称并移除 README 文件 * refactor: 重构 TopBar 组件并更新项目结构 * feat(TopBar): 添加搜索功能组件并优化结构 * refactor: 统一使用 Button 组件替换原有 button 元素 * feat: 添加 EmptyPlaceholder 组件并更新相关页面 * feat(TextStatistics): 引入 StatCard 组件以优化统计信息展示 * feat(Timestamp): 重构时间戳转换功能,新增 ConverterForm 组件 * feat(Jwt): 重构 JWT 组件,新增常量和结果视图 * refactor(RightClickRestorer): 用状态配置表简化页面 UI 结构 * fix(business): 修复 skewedRandom 函数中的数学计算错误 * refactor(ImageUploader): 移除预览 URL 相关逻辑并简化粘贴事件处理 * feat(TestDataGenerator): 更新规则编辑提示和警告显示逻辑 * refactor(CopyButton.test): 移除冗余测试用例 * refactor(RouterProvider, chromeStorage, useContextMenuData): 更新存储获取逻辑以移除默认值参数 * refactor(代码重复): 抽取共享工具函数与 UI 组件,消除多处重复实现 * fix(RouterProvider): 修复初始加载状态逻辑,确保在加载前不覆盖 chrome.storage * feat(TopBar): 添加搜索框快捷键提示功能及相关测试 * refactor(StorageCleaner): 移动 isRestrictedUrl 函数到独立模块并更新相关引用 * fix(StorageCleaner): 修复 IndexedDB 清理竞态、部分成功计数与刷新后状态同步 * fix: 修复路由初始化竞态、存储状态静默覆盖与存储清理误删 * refactor: 统一中文文案并简化组件结构,提升代码可读性 * refactor: 更新 Worker 消息类型以支持生成任务 ID * fix: 修复 ruleStorage 写入失败处理与 CopyButton 成功态样式
This commit is contained in:
@@ -1,38 +0,0 @@
|
||||
import { CopyButton } from '@/components/CopyButton';
|
||||
import { useI18n } from '@/utils/chromeI18n';
|
||||
import { stringifyJson } from '@/utils/jwt';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface JwtSectionProps {
|
||||
title: string;
|
||||
content: unknown;
|
||||
colorClass: string;
|
||||
bgClass: string;
|
||||
borderClass: string;
|
||||
}
|
||||
|
||||
export default function JwtSection({
|
||||
title,
|
||||
content,
|
||||
colorClass,
|
||||
bgClass,
|
||||
borderClass,
|
||||
}: JwtSectionProps) {
|
||||
const { t } = useI18n('jwt');
|
||||
return (
|
||||
<div className={cn('p-4 rounded-xl border border-solid', bgClass, borderClass)}>
|
||||
<div className="flex justify-between items-center mb-2 select-none">
|
||||
<span className={cn('text-xs font-bold tracking-wider uppercase', colorClass)}>
|
||||
{title}
|
||||
</span>
|
||||
<CopyButton
|
||||
text={JSON.stringify(content)}
|
||||
className="h-6 w-6 rounded-md border text-muted-foreground"
|
||||
/>
|
||||
</div>
|
||||
<pre className="m-0 p-3 bg-muted/30 dark:bg-muted/10 rounded-lg text-xs font-mono overflow-x-auto whitespace-pre-wrap break-all border border-border/50 text-foreground/90 leading-relaxed select-text">
|
||||
{content ? stringifyJson(content) : t('jwt:invalidFormat')}
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { CopyButton } from '@/components/CopyButton';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface JwtCopyBlockProps {
|
||||
title: string;
|
||||
copyText: string;
|
||||
titleClassName?: string;
|
||||
containerClassName?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function JwtCopyBlock({
|
||||
title,
|
||||
copyText,
|
||||
titleClassName,
|
||||
containerClassName,
|
||||
children,
|
||||
}: JwtCopyBlockProps) {
|
||||
return (
|
||||
<div className={cn('p-4 rounded-xl border shadow-sm', containerClassName)}>
|
||||
<div className="flex justify-between items-center mb-2 select-none">
|
||||
<span className={cn('text-xs font-bold tracking-wider uppercase', titleClassName)}>
|
||||
{title}
|
||||
</span>
|
||||
<CopyButton text={copyText} className="h-6 w-6 rounded-md border text-muted-foreground" />
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { stringifyJson } from '@/utils/jwt';
|
||||
import type { JwtResult } from '@/utils/jwt';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
JWT_JSON_CONTENT_CLASS,
|
||||
JWT_SECTION_CONFIG,
|
||||
JWT_SIGNATURE_SECTION,
|
||||
JWT_TEXT_CONTENT_CLASS,
|
||||
} from '../constants';
|
||||
import JwtCopyBlock from './JwtCopyBlock';
|
||||
|
||||
interface JwtResultViewProps {
|
||||
result: JwtResult;
|
||||
}
|
||||
|
||||
export default function JwtResultView({ result }: JwtResultViewProps) {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
{JWT_SECTION_CONFIG.map(({ contentKey, title, colorClass, bgClass, borderClass }) => {
|
||||
const content = result[contentKey];
|
||||
|
||||
return (
|
||||
<JwtCopyBlock
|
||||
key={contentKey}
|
||||
title={title}
|
||||
copyText={JSON.stringify(content)}
|
||||
titleClassName={colorClass}
|
||||
containerClassName={cn('border-solid', bgClass, borderClass)}
|
||||
>
|
||||
<pre className={JWT_JSON_CONTENT_CLASS}>
|
||||
{content ? stringifyJson(content) : '无法解析'}
|
||||
</pre>
|
||||
</JwtCopyBlock>
|
||||
);
|
||||
})}
|
||||
|
||||
<JwtCopyBlock
|
||||
title={JWT_SIGNATURE_SECTION.title}
|
||||
copyText={result.signature || ''}
|
||||
titleClassName={JWT_SIGNATURE_SECTION.titleClassName}
|
||||
containerClassName={JWT_SIGNATURE_SECTION.containerClassName}
|
||||
>
|
||||
<span className={JWT_TEXT_CONTENT_CLASS}>
|
||||
{result.signature || JWT_SIGNATURE_SECTION.emptyLabel}
|
||||
</span>
|
||||
</JwtCopyBlock>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
export const JWT_INPUT_PLACEHOLDER = '在此粘贴 JWT 令牌 (Encoded JWT)...';
|
||||
|
||||
export type JwtSectionContentKey = 'header' | 'payload';
|
||||
|
||||
export interface JwtSectionConfig {
|
||||
contentKey: JwtSectionContentKey;
|
||||
title: string;
|
||||
colorClass: string;
|
||||
borderClass: string;
|
||||
bgClass: string;
|
||||
}
|
||||
|
||||
export const JWT_SECTION_CONFIG: JwtSectionConfig[] = [
|
||||
{
|
||||
contentKey: 'header',
|
||||
title: 'HEADER: 算法 & 令牌类型',
|
||||
colorClass: 'text-[#fb015b] dark:text-rose-400',
|
||||
borderClass: 'border-[#fb015b]/20 dark:border-rose-500/20',
|
||||
bgClass: 'bg-[#fb015b]/5 dark:bg-rose-500/5',
|
||||
},
|
||||
{
|
||||
contentKey: 'payload',
|
||||
title: 'PAYLOAD: 数据',
|
||||
colorClass: 'text-[#a03aff] dark:text-purple-400',
|
||||
borderClass: 'border-[#a03aff]/20 dark:border-purple-500/20',
|
||||
bgClass: 'bg-[#a03aff]/5 dark:bg-purple-500/5',
|
||||
},
|
||||
];
|
||||
|
||||
export const JWT_SIGNATURE_SECTION = {
|
||||
title: '签名',
|
||||
titleClassName: 'text-muted-foreground/90',
|
||||
containerClassName: 'border border-border bg-secondary/40',
|
||||
emptyLabel: '无签名',
|
||||
} as const;
|
||||
|
||||
export const JWT_JSON_CONTENT_CLASS =
|
||||
'm-0 p-3 bg-muted/30 dark:bg-muted/10 rounded-lg text-xs font-mono overflow-x-auto whitespace-pre-wrap break-all border border-border/50 text-foreground/90 leading-relaxed select-text';
|
||||
|
||||
export const JWT_TEXT_CONTENT_CLASS =
|
||||
'block text-xs font-mono break-all text-foreground/80 bg-muted/30 dark:bg-muted/10 p-3 rounded-lg border border-border/50 leading-relaxed select-text';
|
||||
+15
-60
@@ -1,71 +1,26 @@
|
||||
import TextInputArea from '@/components/TextInputArea';
|
||||
import { CopyButton } from '@/components/CopyButton';
|
||||
import JwtSection from './JwtSection';
|
||||
import JwtResultView from './components/JwtResultView';
|
||||
import { JWT_INPUT_PLACEHOLDER } from './constants';
|
||||
import { useJwt } from './useJwt';
|
||||
import { useI18n } from '@/utils/chromeI18n';
|
||||
|
||||
export default function Index() {
|
||||
const { t } = useI18n(['jwt', 'jsonFormat']);
|
||||
const { jwtInput, result, handleChange, handleClear } = useJwt();
|
||||
|
||||
return (
|
||||
<div className="p-4 w-full flex flex-col space-y-4 select-none">
|
||||
<div className="flex flex-col gap-4">
|
||||
<TextInputArea
|
||||
minRows={5}
|
||||
maxRows={10}
|
||||
placeholder={t('jwt_placeholder')}
|
||||
value={jwtInput}
|
||||
onChange={handleChange}
|
||||
allowCopy={true}
|
||||
showClear={true}
|
||||
externalError={result?.error || undefined}
|
||||
onClear={handleClear}
|
||||
/>
|
||||
<div className="p-4 w-full flex flex-col gap-4 select-none">
|
||||
<TextInputArea
|
||||
minRows={5}
|
||||
maxRows={10}
|
||||
placeholder={JWT_INPUT_PLACEHOLDER}
|
||||
value={jwtInput}
|
||||
onChange={handleChange}
|
||||
allowCopy={true}
|
||||
showClear={true}
|
||||
externalError={result?.error || undefined}
|
||||
onClear={handleClear}
|
||||
/>
|
||||
|
||||
{result && !result.error && (
|
||||
<div className="flex flex-col gap-4">
|
||||
<JwtSection
|
||||
title={t('jwt:headerTitle')}
|
||||
content={result.header}
|
||||
colorClass="text-[#fb015b] dark:text-rose-400"
|
||||
borderClass="border-[#fb015b]/20 dark:border-rose-500/20"
|
||||
bgClass="bg-[#fb015b]/5 dark:bg-rose-500/5"
|
||||
/>
|
||||
|
||||
<JwtSection
|
||||
title={t('jwt:payloadTitle')}
|
||||
content={result.payload}
|
||||
colorClass="text-[#a03aff] dark:text-purple-400"
|
||||
borderClass="border-[#a03aff]/20 dark:border-purple-500/20"
|
||||
bgClass="bg-[#a03aff]/5 dark:bg-purple-500/5"
|
||||
/>
|
||||
|
||||
<div className="p-4 rounded-xl border border-border bg-secondary/40 shadow-sm">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<span className="text-xs font-bold tracking-wider text-muted-foreground/90 uppercase">
|
||||
{t('jwt:signatureTitle')}
|
||||
</span>
|
||||
<CopyButton
|
||||
text={result.signature || ''}
|
||||
className="h-6 w-6 rounded-md border text-muted-foreground"
|
||||
/>
|
||||
</div>
|
||||
<span className="block text-xs font-mono break-all text-foreground/80 bg-muted/30 dark:bg-muted/10 p-3 rounded-lg border border-border/50 leading-relaxed select-text">
|
||||
{result.signature || t('jwt:noSignature')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{result?.error && (
|
||||
<div className="p-6 rounded-xl bg-muted/30 border border-dashed border-border text-center">
|
||||
<p className="text-xs font-semibold text-muted-foreground/80">
|
||||
{t('jsonFormat:invalidJson')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{result && !result.error && <JwtResultView result={result} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user