name: json-ui description: | 关键:用于 json-ui 组件的渲染和开发。触发词包括: json-ui, json 渲染, 组件目录, 报告渲染, HTML 报告, I18nString, i18n, 双语, 语言切换, 双语言, PaperHeader, AuthorList, Abstract, MetricsGrid, Section, Highlight, Zod schema, catalog.ts, cli.ts, components/index.tsx, “如何添加组件”, “如何渲染 JSON”, JSON 渲染, 组件目录, 报告渲染, 多语言, 中英文切换
json-ui
版本: 1.0.0 | 最后更新: 2026-01-29
您是 json-ui 包的专家——这是一个支持 React 组件、双语 i18n 和 CLI 工具的 JSON 到 HTML 报告渲染器。通过以下方式帮助用户:
- 编写组件:按照现有模式添加新的组件类型
- 渲染报告:从 JSON 报告定义生成 HTML
- 调试:修复渲染、构建或 i18n 问题
- 回答问题:解释架构、组件目录、数据流
快速参考
| 任务 | 文件 | 模式 |
|---|---|---|
| 定义组件模式 | src/catalog.ts |
添加 Zod 模式 + 在 catalog 对象中导出 |
| 渲染组件 (HTML) | src/cli.ts |
在 renderNode() 的 switch 语句中添加 case |
| 渲染组件 (React) | src/components/index.tsx |
使用目录类型导出 React FC |
| 添加 i18n 文本 | 任何 JSON | { "en": "Hello", "zh": "你好" } 或纯文本 "Hello" |
| 构建 | 终端 | pnpm build (使用 tsup,输出 ESM + DTS) |
| 渲染报告 | 终端 | json-ui render report.json [-o out.html] [--no-open] |
文档
请参考本地源文件获取详细文档:
packages/json-ui/src/catalog.ts- 所有 Zod 模式和类型定义packages/json-ui/src/cli.ts- HTML 渲染器和 CLI 入口点packages/json-ui/src/components/index.tsx- React 组件实现
重要:文档完整性检查
在回答问题之前,Claude 必须:
- 阅读上面列出的相关源文件
- 如果文件读取失败:通知用户“本地文档不完整,建议更新”
- 仍然基于 SKILL.md 模式和内置知识进行回答
架构
JSON 报告格式
报告是节点的树状结构:
{
"type": "Report",
"props": { "title": "我的报告", "theme": "auto" },
"children": [
{
"type": "Section",
"props": { "title": "概述", "icon": "bulb" },
"children": [
{ "type": "Abstract", "props": { "text": "..." } }
]
}
]
}
三层渲染
| 层 | 文件 | 输出 | 使用场景 |
|---|---|---|---|
| Zod 模式 | catalog.ts |
类型定义 | 验证、类型安全 |
| HTML 渲染器 | cli.ts |
静态 HTML 字符串 | CLI render 命令 |
| React 组件 | components/index.tsx |
React 元素 | 嵌入式使用 |
数据流
JSON 文件 → CLI 解析 → renderNode() 递归 → HTML 字符串 → 文件写入 → 浏览器打开
组件目录 (38 种类型)
布局
| 组件 | 关键属性 | 描述 |
|---|---|---|
Report |
title?, theme |
根包装器,800px 最大宽度 |
Section |
title, icon?, collapsible? |
带标题的可折叠部分 |
Grid |
cols, gap |
CSS 网格布局 |
Card |
variant, padding, shadow |
卡片容器 |
论文信息
| 组件 | 关键属性 | 描述 |
|---|---|---|
PaperHeader |
title, arxivId, date, categories? |
论文标题 + 元数据 |
AuthorList |
authors, layout?, maxVisible? |
作者姓名 + 所属机构 |
Abstract |
text, highlights?, maxLength? |
带关键词高亮的摘要 |
TagList |
tags, variant |
标签/类别药丸 |
内容
| 组件 | 关键属性 | 描述 |
|---|---|---|
ContributionList |
items, numbered? |
带徽章的编号贡献列表 |
MethodOverview |
steps, showConnectors? |
分步方法流程 |
Highlight |
text, type, source? |
引用块 (引用/重要/警告/代码) |
KeyPoint |
icon, title, description |
图标 + 标题 + 描述 |
CodeBlock |
code, language, showLineNumbers? |
语法高亮代码 |
Prose |
content |
Markdown 内容块 |
Callout |
type, title?, content |
信息/提示/警告/重要/注意框 |
富内容
| 组件 | 关键属性 | 描述 |
|---|---|---|
Image |
src, alt?, caption?, width? |
单张图片 |
Figure |
images, caption?, label? |
多图组合 |
Formula |
latex, block?, label? |
LaTeX 公式 |
DefinitionList |
items |
术语-定义对 |
Theorem |
type, number?, title?, content |
定理/引理/命题 |
Algorithm |
title, steps, caption? |
算法伪代码 |
ResultsTable |
columns, rows, highlights? |
带最佳单元格高亮的结果表 |
数据显示
| 组件 | 关键属性 | 描述 |
|---|---|---|
Metric |
label, value, trend?, icon? |
单个指标卡片 |
MetricsGrid |
metrics, cols? |
指标卡片网格 |
Table |
columns, rows, striped?, caption? |
数据表 |
交互式
| 组件 | 关键属性 | 描述 |
|---|---|---|
LinkButton |
href, label, icon?, external? |
样式化链接按钮 |
LinkGroup |
links, layout? |
链接按钮组 |
品牌
| 组件 | 关键属性 | 描述 |
|---|---|---|
BrandHeader |
badge?, poweredBy?, showBadge? |
AI 生成徽章页眉 |
BrandFooter |
timestamp, attribution?, disclaimer? |
带归属信息的页脚 |
I18n 系统
向后兼容的双语字符串
I18nString 类型接受纯字符串和双语对象:
// catalog.ts
export const I18nString = z.union([
z.string(),
z.object({ en: z.string(), zh: z.string() }),
]);
JSON 用法
// 纯字符串 (向后兼容)
{ "title": "Hello World" }
// 双语对象
{ "title": { "en": "Hello World", "zh": "你好世界" } }
HTML 渲染 (cli.ts)
对于 HTML 输出,i18n 字符串渲染为双 span:
// renderI18n() 输出:
<span class="i18n-en">Hello</span><span class="i18n-zh">你好</span>
// CSS 控制可见性:
html[lang="en"] .i18n-zh { display: none; }
html[lang="zh"] .i18n-en { display: none; }
对于仅支持纯字符串的 HTML 属性 (alt, title):
// resolveI18n() 选择一种语言:
const alt = resolveI18n(props.alt, 'en'); // 返回纯字符串
React 渲染 (components/index.tsx)
// 在 JSX 中使用 <I18nText> 组件:
<I18nText value={props.title} />
// 在纯字符串上下文中使用 resolveI18nStr():
const altText = resolveI18nStr(props.alt, 'en');
语言切换器
- 固定右上角按钮:EN | 中文
- 切换
<html lang="en|zh">属性 - 通过
localStorage.getItem('json-ui-lang')持久化选择
关键模式
模式 1:添加新组件
- 在
catalog.ts中定义模式:
export const MyWidgetSchema = z.object({
label: I18nString, // 对用户可见的文本使用 I18nString
count: z.number(), // 对数据使用 z.string()/z.number()
variant: VariantType.default('default'),
});
// 添加到 catalog 对象:
export const catalog = {
// ...existing...
MyWidget: MyWidgetSchema,
} as const;
// 导出类型:
export type MyWidgetProps = z.infer<typeof MyWidgetSchema>;
- 在
cli.ts的renderNode()switch 语句中添加 HTML 渲染器:
case 'MyWidget': {
const { label, count, variant } = props;
return `<div class="my-widget ${variant}">
<span>${renderI18n(label)}</span>
<strong>${escapeHtml(String(count))}</strong>
</div>`;
}
- 在
components/index.tsx中添加 React 组件:
export const MyWidget: React.FC<MyWidgetProps> = ({ label, count, variant = 'default' }) => (
<div className={`my-widget ${variant}`}>
<span><I18nText value={label} /></span>
<strong>{count}</strong>
</div>
);
模式 2:处理特殊情况下的 I18n
对于需要处理的文本(例如,Abstract 高亮):
// HTML (cli.ts) - 分别处理每种语言:
if (isI18n(text)) {
return `<span class="i18n-en">${processText(text.en)}</span>
<span class="i18n-zh">${processText(text.zh)}</span>`;
} else {
return processText(String(text));
}
// React (components/index.tsx):
if (typeof text === 'object' && 'en' in text && 'zh' in text) {
return (
<>
<span className="i18n-en" dangerouslySetInnerHTML={{ __html: processText(text.en) }} />
<span className="i18n-zh" dangerouslySetInnerHTML={{ __html: processText(text.zh) }} />
</>
);
}
常见错误
| 错误 | 原因 | 解决方案 |
|---|---|---|
Type 'I18nStringType' is not assignable to 'ReactNode' |
将 i18n 对象直接传递给 JSX | 用 <I18nText value={...} /> 包装 |
Property 'length' does not exist on type 'I18nStringType' |
在 i18n 值上调用字符串方法 | 使用类型守卫:typeof text === 'string' ? text : text.en |
| 来自 arxiv 的图片无法加载 | <img> 标签上的 crossorigin="anonymous" |
移除 crossorigin;仅保留 referrerpolicy="no-referrer" |
| 语言切换器不工作 | 缺少 CSS 规则或 JS | 确保模板中包含 html[lang] .i18n-* CSS 规则和切换 JS |
| 构建失败并出现类型错误 | 模式已更改但组件未更新 | 更新所有三个文件:catalog, cli, components |
关键:图片处理
请勿在 <img> 标签上使用 crossorigin="anonymous"。
像 arxiv.org 这样的网站不发送 CORS 头。添加 crossorigin="anonymous" 会导致浏览器要求 CORS,这会失败并阻止图片加载。
<!-- 错误 - 破坏来自 arxiv 和类似网站的图片 -->
<img src="..." referrerpolicy="no-referrer" crossorigin="anonymous" />
<!-- 正确 -->
<img src="..." referrerpolicy="no-referrer" />
中文翻译指南
为 ML/AI 论文撰写中文翻译时:
| 错误 | 正确 | 原因 |
|---|---|---|
| 评论器 | 价值函数(critic) | 标准 ML 术语 |
| 运行估计 | 滑动估计 | Running estimate = 滑动估计 |
| 重加权因子 | 加权系数 | 更自然的中文 |
| 不断演化的 | 动态更新的 | 含义更清晰 |
| 简单修复 | 改动小 | 学术语气 |
构建 & CLI
# 构建 (通过 tsup 输出 ESM + DTS)
cd packages/json-ui && pnpm build
# 将报告渲染为 HTML
node dist/cli.js render example-report-rich.json
# 带选项
node dist/cli.js render report.json -o output.html --no-open
编写代码时
- 在模式中始终对用户可见的文本属性使用
I18nString - 在渲染器中始终处理字符串和
{en, zh}两种形式 - 切勿在 img 标签上使用
crossorigin="anonymous" - 在 img 标签上保留
referrerpolicy="no-referrer"以保护隐私 - 任何模式或组件更改后,使用
pnpm build进行测试 - 添加组件时更新所有三层 (catalog, cli, components)