feat(Jwt): 重构 JWT 组件,新增常量和结果视图

- 新增 constants.ts 文件,定义 JWT 输入占位符和配置常量,提升代码可读性。
- 删除 JwtSection 组件,重构为 JwtResultView 组件,简化结果展示逻辑。
- 更新 index.tsx,使用 JwtResultView 组件替代原有的 JWT 结果展示方式,优化页面结构。
This commit is contained in:
雨霖铃
2026-06-19 10:31:40 +08:00
parent 0906d1f425
commit 06b9d29270
5 changed files with 135 additions and 91 deletions
-36
View File
@@ -1,36 +0,0 @@
import { CopyButton } from '@/components/CopyButton';
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) {
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) : '无法解析'}
</pre>
</div>
);
}
+30
View File
@@ -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>
);
}
+41
View File
@@ -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 -55
View File
@@ -1,66 +1,26 @@
import TextInputArea from '@/components/TextInputArea'; import TextInputArea from '@/components/TextInputArea';
import { CopyButton } from '@/components/CopyButton'; import JwtResultView from './components/JwtResultView';
import EmptyPlaceholder from '@/components/EmptyPlaceholder'; import { JWT_INPUT_PLACEHOLDER } from './constants';
import JwtSection from './JwtSection';
import { useJwt } from './useJwt'; import { useJwt } from './useJwt';
export default function Index() { export default function Index() {
const { jwtInput, result, handleChange, handleClear } = useJwt(); const { jwtInput, result, handleChange, handleClear } = useJwt();
return ( return (
<div className="p-4 w-full flex flex-col space-y-4 select-none"> <div className="p-4 w-full flex flex-col gap-4 select-none">
<div className="flex flex-col gap-4"> <TextInputArea
<TextInputArea minRows={5}
minRows={5} maxRows={10}
maxRows={10} placeholder={JWT_INPUT_PLACEHOLDER}
placeholder={'在此粘贴 JWT 令牌 (Encoded JWT)...'} value={jwtInput}
value={jwtInput} onChange={handleChange}
onChange={handleChange} allowCopy={true}
allowCopy={true} showClear={true}
showClear={true} externalError={result?.error || undefined}
externalError={result?.error || undefined} onClear={handleClear}
onClear={handleClear} />
/>
{result && !result.error && ( {result && !result.error && <JwtResultView result={result} />}
<div className="flex flex-col gap-4">
<JwtSection
title={'HEADER: 算法 & 令牌类型'}
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={'PAYLOAD: 数据'}
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">
{'签名'}
</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 || '无签名'}
</span>
</div>
</div>
)}
{result?.error && (
<EmptyPlaceholder className="p-6 min-h-0">{'无效的 JSON 格式'}</EmptyPlaceholder>
)}
</div>
</div> </div>
); );
} }