- name
- report-expert
- description
- 生成 HTML 报告页面并部署到 Cloudflare Pages 站点。涵盖设计系统、页面结构、索引管理、iframe 内嵌查看、自动部署全流程。触发词:写报告、发布报告、部署报告、生成报告页面、report publisher、报告专家、升级报告专家、更新报告技能、发布技能升级。
报告专家
将调研内容生成符合设计规范的 HTML 报告页面,支持两种内容类型:报告页面(同站部署)和外部页面(独立部署,iframe 内嵌查看)。
技能版本管理
所有远程地址均从 manifest.json 的 repository 字段动态获取,不硬编码任何绝对 URL。
安装方式
技能支持两种安装渠道:
| 渠道 | 安装命令 | 特点 | |
|---|---|---|---|
| ClawHub | clawhub install report-expert | 自动管理版本,安全扫描,推荐 | |
| CF Pages | 将 manifest.json 地址发送给 Agent | 包含 upgrade.py 自更新脚本 |
拉取升级(用户指令触发)
当用户说「升级报告专家」「更新报告技能」「检查技能更新」时,根据安装方式选择升级命令:
ClawHub 安装的技能:
clawhub update report-expertCF Pages 安装的技能:
安全提示: 升级会从 manifest.repository 指定的远端下载文件并覆盖本地技能文件(含 SHA256 校验)。请确认你信任该远端来源。SKILL_DIR="<技能实际目录路径>"
python3 "${SKILL_DIR}/upgrade.py"注意:upgrade.py仅随 CF Pages 渠道分发,ClawHub 渠道通过clawhub update管理,不包含该文件。
### 发布升级(修改技能后触发)
当用户说「发布技能升级」「发布报告专家」「提交技能更新」时,执行发布流程。
> **安全提示:** 发布会将技能文件部署到 Cloudflare Pages,需要 `CLOUDFLARE_API_TOKEN`。请确认 token 权限范围合理(仅需 Pages 编辑权限)。
SKILL_DIR="<技能实际目录路径>"
SKILL_DIR="$SKILL_DIR" python3 << 'PYEOF' import json, hashlib, os, sys from datetime import date
skill_dir = os.environ.get("SKILL_DIR", os.path.dirname(os.path.abspath(__file__))) manifest_path = os.path.join(skill_dir, "manifest.json")
with open(manifest_path, "r", encoding="utf-8") as f: manifest = json.load(f)
1. 版本递增
old_ver = manifest.get("version", "0.0.0") parts = old_ver.lstrip("v").split(".") parts[-1] = str(int(parts[-1]) + 1) new_ver = ".".join(parts) manifest["version"] = new_ver manifest["updated"] = date.today().isoformat()
2. 远程基础地址(从 repository 字段读取)
repo = manifest.get("repository", "").rstrip("/")
3. 重算所有文件哈希
for root, dirs, fnames in os.walk(skill_dir): dirs[:] = [d for d in dirs if d not in (".git", "__pycache__")] for fname in fnames: fpath = os.path.join(root, fname) rel = os.path.relpath(fpath, skill_dir) if rel == "manifest.json": continue with open(fpath, "rb") as f: sha = hashlib.sha256(f.read()).hexdigest() sz = os.path.getsize(fpath) if rel not in manifest.get("files", {}): manifest.setdefault("files", {})[rel] = {} manifest["files"][rel]["sha256"] = sha manifest["files"][rel]["size"] = sz # url 保持相对路径 manifest["files"][rel]["url"] = rel
4. 清理已删除的文件
existing = set() for root, dirs, fnames in os.walk(skill_dir): dirs[:] = [d for d in dirs if d not in (".git", "__pycache__")] for fname in fnames: rel = os.path.relpath(os.path.join(root, fname), skill_dir) if rel != "manifest.json": existing.add(rel) for rel in list(manifest.get("files", {}).keys()): if rel not in existing: del manifest["files"][rel]
5. 写入 manifest
with open(manifest_path, "w", encoding="utf-8") as f: json.dump(manifest, f, ensure_ascii=False, indent=2)
5.5. 同步版本号到技能介绍页面
index_html = os.path.join(skill_dir, "index.html") if os.path.exists(index_html): ih = open(index_html, "r", encoding="utf-8").read() # 替换版本号标记(匹配 v数字.数字.数字 格式) import re ih = re.sub(r'v\d+\.\d+\.\d+', f'v{new_ver}', ih, count=1) open(index_html, "w", encoding="utf-8").write(ih) print(f"📄 介绍页版本已同步: v{new_ver}")
print(f"✅ 版本: v{old_ver} → v{new_ver}") print(f"📦 文件: {len(manifest['files'])} 个") for rel, meta in manifest["files"].items(): print(f" {meta['sha256'][:12]}... {meta['size']:>6}B {rel}") PYEOF
6. 发布到预览环境(自动升级版本号 + 同步所有文件版本)
SKILL_DIR="$SKILL_DIR" python3 "${SKILL_DIR}/deploy.py" publish
7. 验证预览内容(检查输出的预览地址)
- manifest.json 版本号正确
- index.html 版本号正确
- SKILL.md 版本号正确
8. 验证无误后,发布到生产环境
python3 "${SKILL_DIR}/deploy.py" publish-prod
### 验证发布
发布后执行以下命令确认:
SKILL_DIR="<技能实际目录路径>" REPO=$(python3 -c "import json; print(json.load(open('${SKILL_DIR}/manifest.json'))['repository'])")
检查 manifest 可达
curl -sL "${REPO}/manifest.json" | python3 -c "import json,sys; m=json.load(sys.stdin); print(f'v{m[\"version\"]} — {len(m[\"files\"])} files')"
检查各文件可达
curl -sI "${REPO}/SKILL.md" | head -1 curl -sI "${REPO}/deploy.py" | head -1
---
## 前置配置(使用者必须完成)
### 四组部署目标配置
在 TOOLS.md 中按需配置:
Report Expert 技能配置
部署模式: local
REPORT_DEPLOY_MODE=localREPORT_LOCAL_DIR=/path/to/site# 本地站点根目录REPORT_LOCAL_URL=https://example.com/claw# 站点访问地址REPORT_SITE_NAME=传琪
远程报告部署(可选)
REPORT_CF_PROJECT=reports-site# 报告站 CF Pages 项目名
技能 CF 发布(可选)
SKILL_CF_PROJECT=report-expert-skill# 技能站 CF Pages 项目名
技能 ClawHub 发布(可选)
SKILL_CLAWHUB_SLUG=report-expertSKILL_CLAWHUB_NAME=报告专家
### 共用凭据
- `CLOUDFLARE_API_TOKEN` — 环境变量或 TOOLS.md,一个 token 管所有 CF 操作
- 建议使用最小权限 token(仅需 Cloudflare Pages 编辑权限)
- 建议使用环境变量方式配置,不要提交到公共仓库
## 两种内容类型
### 1. 报告页面(同站部署)
完整的调研报告、教程等,部署在报告主站的子目录下。
**流程:**
1. 生成 HTML body 内容(只需 `<style>` + 内容 HTML,不需要完整页面)
2. 运行 `python3 deploy.py deploy <category> <html_file> --title "标题" --desc "描述"`
3. 脚本自动:套模板、注入样式、添加页脚、更新索引
4. 运行 wrangler 部署到 Cloudflare Pages
### 2. 外部页面(独立部署 + iframe 内嵌)
游戏、工具、测试等独立页面,部署到独立的 Cloudflare Pages 项目,同时在报告主站索引中展示。
**流程:**
1. 生成完整的独立 HTML 页面
2. 创建独立的 Cloudflare Pages 项目并部署
3. 运行 `python3 deploy.py add <filename> --title "标题" --desc "描述" --category other --url "https://xxx.pages.dev/"`
4. 更新 dist/index.json 并部署报告主站
**首页行为:**
- 报告页面:正常跳转到报告详情页
- 外部页面:在 iframe 中内嵌打开,保留页头、侧边栏和页脚导航
## 分类体系
### 分类命名规范
1. **双标识格式**:创建报告或新建分类时,必须使用 `英文标识 + 中文名称` 的双标识体系
2. **路径使用英文标识**:文件存储路径、URL 路径、索引过滤字段均使用英文标识(如 `research`、`analysis`)
3. **展示使用中文名称**:索引页面、用户界面展示时使用中文名称(如「深度研究」「数据分析」)
4. **逻辑执行用英文标识**:代码中的分类判断、过滤、路由等逻辑全部基于英文标识
### 现有分类
| key | 名称 | 说明 |
|-----|------|------|
| research | 深度研究 | 系统性调研与分析 |
| analysis | 数据分析 | 数据驱动的分析洞察 |
| summary | 内容摘要 | 信息整理与要点提炼 |
| comparison | 对比评测 | 产品、方案或工具的横向对比 |
| tutorial | 教程指南 | 操作指南与最佳实践 |
| other | 其他 | 游戏、工具、测试等外部页面 |
## CLI 命令
报告部署
python3 deploy.py deploy <category> <file> [--title T] [--desc D] # 自动选本地/远程 python3 deploy.py local-deploy <category> <file> [--title T] [--desc D] python3 deploy.py remote-deploy <category> <file> [--title T] [--desc D]
技能发布
python3 deploy.py skill-cf-publish # 发布技能到 CF Pages 预览 python3 deploy.py skill-cf-publish-prod # 发布技能到 CF Pages 生产 python3 deploy.py skill-clawhub-publish [--changelog "xxx"] # 发布到 ClawHub
索引和备份
python3 deploy.py add <filename> --title T --desc D --category C [--url U] python3 deploy.py rebuild_index python3 deploy.py scan [--ai] python3 deploy.py update python3 deploy.py backup [list|restore <name>]
## 索引管理操作规范
当用户从索引页管理模式生成指令后,执行对应的索引管理操作:
### 移动分类
- **只修改 index.json 中的 category 字段,不要移动实际文件**
- 修改后必须执行 `python3 deploy.py update` 部署更新
修改 category 示例
import json f = 'dist/index.json' d = json.load(open(f)) for p in d['pages']: if p['filename'] == 'xxx.html': p['category'] = 'target-category' json.dump(d, open(f, 'w'), ensure_ascii=False, indent=2)
### 删除页面
- 删除源文件(`REPORT_LOCAL_DIR` 下对应路径)和 dist 中的副本
- 执行 `python3 deploy.py rebuild_index` 重建索引并部署
### 操作后必须部署
- **移动分类** → `python3 deploy.py update`
- **删除页面** → `python3 deploy.py rebuild_index`
- 不部署 = 没生效
---
## 部署前检查清单
每份报告部署前必须逐项确认:
### 🔍 成果物 Review(部署前必须执行)
**生成 HTML 后、部署前,必须对产出物执行自动化 review:**
1. div 闭合校验(必须完全相等)
OPEN=$(grep -c '<div' FILE); CLOSE=$(grep -c '</div>' FILE); echo "div: $OPEN open / $CLOSE close"; [ "$OPEN" = "$CLOSE" ] && echo "✅ PASS" || echo "❌ FAIL"
2. 检查重复嵌套 report-wrap
PATTERN=$(grep -c 'class="report-wrap"' FILE); [ "$PATTERN" -le 1 ] && echo "✅ PASS" || echo "❌ FAIL: $PATTERN report-wrap found"
3. 检查重复 page-body
PATTERN=$(grep -c 'class="page-body"' FILE); [ "$PATTERN" -le 1 ] && echo "✅ PASS" || echo "❌ FAIL: $PATTERN page-body found"
4. 检查重复 scroll-progress / toc-sidebar / toc-toggle
for CLS in scroll-progress toc-sidebar toc-toggle; do PATTERN=$(grep -c "$CLS" FILE); [ "$PATTERN" -le 1 ] && echo "✅ $CLS" || echo "❌ FAIL: $CLS x$PATTERN"; done
5. 检查是否有手写 footer
grep -q 'class="page-footer"' FILE && echo "❌ FAIL: 手写 footer detected" || echo "✅ PASS"
6. 检查标题是否为中文(标题应在 h1.report-header__title 中)
TITLE=$(grep -o 'report-header__title">[^<]*<' FILE | sed 's/.*>\(.*\)</.*/\1/') echo "标题: $TITLE"
7. 检查深色背景
grep -qP 'background[^;]*#0[0-9a-f]|background[^;]*#1[0-9a-f]' FILE && echo "❌ FAIL: 深色背景" || echo "✅ PASS"
8. 检查图表完整性(有容器必须有 data-option)
CHART_DIVS=$(grep -c 'chart-box__canvas' FILE) CHART_OPTS=$(grep -c 'data-option=' FILE) echo "图表容器: $CHART_DIVS / data-option: $CHART_OPTS" if [ "$CHART_DIVS" -gt 0 ] && [ "$CHART_OPTS" -eq 0 ]; then echo "❌ FAIL: 有图表容器但没有 data-option 属性" fi [ "$CHART_DIVS" -eq 0 ] || [ "$CHART_OPTS" -gt 0 ] && echo "✅ 图表完整性 PASS"
9. 检查 page-body 中是否有 script 标签
(允许 body 底部的 script,但 page-body 内容区不应有)
**review 失败时必须修复后再部署,不可跳过此步骤。**
### 部署检查项
- [ ] 使用 base.css + main.js(引用 `../styles/base.css` 和 `../scripts/main.js`)
- [ ] 包含 toc-sidebar + toc-toggle + scroll-progress 元素
- [ ] TOC 标题带 SVG 列表图标,关闭按钮用 SVG X 图标,展开按钮用 SVG 汉堡图标
- [ ] **页头页脚由 main.js 统一注入,HTML 中不要手写 `<footer>` 或面包屑链接**
- [ ] report-header__breadcrumb 只需写结构(a + span),链接和 SVG 图标由 JS 自动填充
- [ ] 分类标签带 SVG 图标,日期带 SVG 日历图标
- [ ] 内容包裹在 report-wrap > report-header + page-body 中
- [ ] h2/h3 有 id 属性供大纲跳转(main.js 会自动补 id,但建议模板中直接写上)
- [ ] **不要手写 `<footer class="page-footer">`**,JS 会自动注入统一页脚(logo + 传琪 + 年份)
- [ ] **没有使用任何 emoji 作为图标或 UI 元素**
- [ ] 配色使用 base.css 变量(`var(--c-xxx)`),没有硬编码颜色
- [ ] **禁止在 body 内联样式中使用深色背景(`#0f`/`#1a`/`#111`/`#222` 等深色值)**
- [ ] **禁止 CSS fallback 使用深色值**,如 `var(--c-bg, #0f0f1e)` 必须改为 `var(--c-bg, #f8fafc)` 等亮色值
- [ ] **禁止深色背景配深色文字**,确保背景和文字的对比度足够
- [ ] 卡片/区块用底色+阴影,没有用线框
- [ ] 表格表头用 `--c-elevated` 深色底
- [ ] 表格主体用 `--c-surface` 白色底,偶数行用 `--c-surface-alt`
- [ ] **🔴 强制校验:`<div>` 和 `</div>` 数量必须完全相等**(`grep -c '<div' file && grep -c '</div>' file`),不等则禁止部署。多余的闭合标签会导致页面白屏、样式错乱、内容消失
- [ ] **🔴 强制校验:只允许一套页面框架**(1个 scroll-progress、1个 toc-sidebar、1个 toc-toggle、1个 report-wrap、1个 page-body),重复会导致布局崩溃
- [ ] **🔴 禁止手写 `<footer>`**(第3次强调,子 agent 最容易犯这个错)
- [ ] **🔴 禁止在 page-body 中间插入 `<script>` 标签**,所有图表初始化脚本必须写在 body 底部、main.js 之后
## 模板文件
- `templates/base.css` — 设计系统样式(v6.1 底色层级)
- `templates/index.html` — 首页模板(含 iframe viewer)
- `scripts/main.js` — 交互系统(TOC、滚动进度、渐入动画、图表放大 Lightbox)
- `deploy.py` — CLI 入口(参数解析与命令分发)
- `lib/config.py` — 配置加载、四组部署目标常量、工具函数
- `lib/page.py` — 页面 HTML 生成与部署
- `lib/index.py` — 索引管理(重建/添加/扫描)
- `lib/backup.py` — 备份恢复与页面模板更新
- `lib/local_deploy.py` — 本地报告部署(写入 dist 目录)
- `lib/remote_deploy.py` — 远程报告部署(Cloudflare Pages via wrangler)
- `lib/skill_cf_publish.py` — 技能发布到 CF Pages(预览/生产)
- `lib/skill_clawhub_publish.py` — 技能发布到 ClawHub registry
- `manifest.json` — 版本信息、文件清单、更新日志
- `upgrade.py` — 独立升级脚本(可选,agent 可直接按 SKILL.md 流程执行)
## 安全说明
### 所需环境变量
- `CLOUDFLARE_API_TOKEN` — Cloudflare API Token,远程报告部署 + 技能 CF 发布共用,local 模式无需配置
### 所需外部工具
- `python3` — 运行部署脚本
- `curl` — 升级流程下载远端文件
- `npx` / `wrangler` — 远程发布到 Cloudflare Pages
- `openclaw` — AI 自动分类功能(`--ai` 模式,可选)
- `clawhub` — ClawHub 发布 CLI(`skill-clawhub-publish` 命令)
### ClawHub 发布注意
- **⚠️ 发布前必须排除 `dist/` 和 `backups/` 目录**,否则超过 20MB 限制会失败
- `skill-clawhub-publish` 已内置自动临时移除逻辑,但需确认生效
### 升级安全
- 升级从 `manifest.json` 的 `repository` 字段指定的远端下载文件
- 所有文件经过 SHA256 校验,校验失败会拒绝写入
- **建议:** 仅在信任远端来源时执行升级
### 凭据管理
- 凭据通过环境变量或 TOOLS.md 配置,**不要将 token 提交到公共仓库**
- 建议使用环境变量方式配置 CLOUDFLARE_API_TOKEN
- 建议使用最小权限 token(仅需 Cloudflare Pages 编辑权限)
## 图表与可视化规范
> **图表放大功能**:main.js 已内置图表放大(Lightbox)功能,支持 chart-box、ECharts div、Mermaid 流程图。手机端按钮始终可见。ECharts 弹层会重新创建实例确保正确渲染。
报告页面内置三种图表引擎,按需选用:
### 1. Mermaid — 流程图/架构图/时序图
适用于:系统架构、流程说明、时序关系、状态机等。
<div class="mermaid-wrap"><pre class="mermaid"> flowchart TD A["节点A"] --> B["节点B"] </pre></div>
**禁止事项:**
- ❌ 不要在 `mermaid-wrap` 外再包额外的 `div`(如 `arch-diagram`),否则会导致 `</div>` 闭合错乱
- ❌ 不要在 mermaid 代码块前后加多余的闭合标签
**支持类型:** `flowchart`、`sequence`、`class`、`state`、`er`、`gantt`、`pie`、`mindmap`、`timeline`
### 2. ECharts — 数据可视化(推荐)
适用于:柱状图、折线图、饼图、散点图、雷达图、热力图、地图、桑基图等复杂数据可视化。
**HTML 结构(写在 page-body 内容区,使用 data-option 属性声明配置):**<div class="chart-box"> <div class="chart-box__title">图表标题</div> <div class="chart-box__canvas" id="chart-xxx" data-option='{"xAxis":{"type":"category","data":["A","B","C"]},"yAxis":{"type":"value"},"series":[{"type":"bar","data":[10,20,15]}]}'></div> <div class="chart-box__caption">数据来源说明</div> </div>
**⚠️ 关键规则:**
- **使用 `data-option` 属性**:将 ECharts option 配置写成 JSON 字符串,放在 `chart-box__canvas` 元素的 `data-option` 属性中
- **不需要手写 `<script>` 标签**:`main.js` 会自动扫描所有 `chart-box__canvas[data-option]` 元素,自动调用 `echarts.init()` + `setOption()`
- **每个图表的 `id` 必须唯一**(建议用 `chart-` 前缀 + 简短描述)
- **`data-option` 的值必须是合法 JSON**(双引号包裹 key,字符串值用双引号)
**data-option 内 JSON 转义规则:**
- 如果 `data-option` 在双引号属性中(`data-option="..."`),内层 JSON 的双引号要写为 `"`
- 推荐用单引号包裹属性值(`data-option='...'`),内层 JSON 用标准双引号即可
- 如果 option 中有 HTML 模板字符串,需要先转义
**完整示例:**<div class="chart-box"> <div class="chart-box__title">市场规模增长趋势</div> <div class="chart-box__canvas" id="chart-growth" data-option='{ "tooltip":{"trigger":"axis"}, "xAxis":{"type":"category","data":["2023","2024","2025","2026"]}, "yAxis":{"type":"value","name":"亿美元"}, "series":[{"type":"line","data":[3.2,5.1,7.2,8.5],"smooth":true,"areaStyle":{"opacity":0.15},"itemStyle":{"color":"#6366f1"}}] }'></div> <div class="chart-box__caption">数据来源:Fortune Business Insights</div> </div>
**配色原则:** 使用报告主题色 `#6366f1`(indigo)为主色,辅以 `#8b5cf6`、`#06b6d4`、`#10b981`、`#f59e0b`、`#ef4444` 等柔和色系。避免使用默认亮色。
**尺寸类:**
- 默认高度 360px:`chart-box__canvas`
- 小图 240px:`chart-box__canvas--sm`
- 大图 480px:`chart-box__canvas--lg`
**注意事项:**
- 每个图表的 `id` 必须唯一(建议用 `chart-` 前缀 + 简短描述)
- 响应式 resize 由 main.js 自动处理,无需手动监听
- 多个图表各自独立,各自写 `data-option` 即可
- ECharts CDN 由 deploy.py 按需自动注入到 `<head>` 中,不需要手动添加 `<script src="echarts">`
### 3. Chart.js — 轻量图表
适用于:简单的柱状图、折线图、饼图、环形图、面积图。比 ECharts 更轻量。
**HTML 结构(写在 page-body 内容区):**<div class="chart-container"> <canvas id="chart-xxx" height="300"></canvas> </div> <p class="chart-caption">图表说明</p>
**初始化脚本(必须写在 body 底部,ECharts 脚本之后):**<script> document.addEventListener('DOMContentLoaded', function() { new Chart(document.getElementById('chart-xxx'), { type: 'bar', data: { labels: ['A', 'B', 'C'], datasets: [{ label: '数据', data: [10, 20, 15], backgroundColor: '#6366f1' }] }, options: { responsive: true, plugins: { legend: { display: false } }, scales: { y: { beginAtZero: true } } } }); }); </script>
### 图表优先原则(必须遵守)
生成报告时,凡是能用图表表达的内容,**必须优先使用图表渲染**,而非纯文字或表格堆砌。
**适用场景:**
- 数据对比 → 柱状图/水平柱状图
- 趋势变化 → 折线图/面积图
- 占比分布 → 饼图/环形图
- 多维评估 → 雷达图
- 流程步骤 → 流程图/甘特图
- 系统关系 → 架构图/ER图
- 转化链路 → 漏斗图
- 状态流转 → 状态机图
- 时序交互 → 时序图
- 项目排期 → 甘特图
- 数据散布 → 散点图
**判断标准:** 如果一段内容包含 3 个以上数据点、多步骤流程、多组件关系,就必须用图表呈现。
---
### 选型指南
| 场景 | 推荐引擎 | 理由 |
|------|----------|------|
| 流程/架构/时序 | Mermaid | 声明式语法,适合关系图 |
| 复杂数据可视化 | ECharts | 交互丰富,图表类型全面 |
| 简单统计图表 | Chart.js | 轻量,上手快 |
| 需要动画效果 | ECharts | 内置丰富动画和交互 |
| 多图表混排 | ECharts | 统一风格,配色一致 |
### 🔴 图表渲染完整性校验(每次生成后必须执行)
**ECharts 使用声明式 `data-option` 属性,不需要手写 `<script>` 标签。**
检查每个 chart-box__canvas 是否有 data-option 属性
CONTAINERS=$(grep -c 'chart-box__canvas' FILE) DATA_OPTS=$(grep -c 'data-option=' FILE) echo "图表容器: $CONTAINERS, data-option: $DATA_OPTS" if [ "$CONTAINERS" -gt 0 ] && [ "$DATA_OPTS" -eq 0 ]; then echo "❌ 致命错误:有图表容器但没有 data-option!图表不会渲染!" echo "必须在 chart-box__canvas 元素上添加 data-option='{json}' 属性" fi
**正确示例(声明式):**<!-- 内容区:图表容器 + data-option 属性,一步到位 --> <div class="chart-box"> <div class="chart-box__title">标题</div> <div class="chart-box__canvas" id="chart-xxx" data-option='{"xAxis":{"type":"category","data":["A","B"]},"series":[{"type":"bar","data":[10,20]}]}'></div> </div> <!-- main.js 自动扫描所有 chart-box__canvas[data-option] 并初始化,无需手写 <script> -->
### ⚠️ 常见错误检查清单(每次生成后必须检查)
| # | 问题 | 正确做法 | 后果 |
|---|------|---------|------|
| 1 | Mermaid 用 `<mermaid>` 标签 | 必须用 `<pre class="mermaid">` | 图表不渲染 |
| 2 | `mermaid.initialize()` 在 CDN 加载前调用 | 必须在 `<script src="mermaid.min.js">` 之后调用 | ReferenceError |
| 3 | Mermaid 外层 div 设置了 background 色 | 外层容器不设背景色,让 Mermaid 自身控制渲染 | 底色污染/白块 |
| 4 | ECharts 容器 div 没设高度 | 必须 `style="width:100%;height:400px"` 或用 CSS 设固定高度 | 图表不渲染(高度为0) |
| 5 | ECharts CDN 放在 body 底部但初始化用 DOMContentLoaded | CDN 放 head 同步加载(deploy.py 自动处理),初始化用 DOMContentLoaded | 加载时序可能出错 |
| 5b | **有 chart 容器但没有 echarts.init 脚本** | **每个 chart-box__canvas 必须有对应的 echarts.init + setOption** | **图表不渲染(最常见错误)** |
| 6 | 多个图表用了相同 id | 每个 ECharts 容器 id 必须唯一 | 只渲染第一个 |
| 7 | 子 agent 输出完整 HTML 页面 | 子 agent 只输出 style + body 内容片段 | report-wrap/page-body 双重嵌套 |
| 8 | style 标签残留到 page-body 内 | style 必须在 head 中,body 中不得有 style | 样式异常/白屏 |
### 脚本加载顺序(严格遵守)
<head> ├── echarts.min.js ← 同步加载(deploy.py 按需注入) └── chart.umd.min.js ← 同步加载(deploy.py 按需注入) <body> ├── page-body 内容区 ← 图表容器用 data-option 声明配置 ├── main.js ← 自动扫描 data-option 并初始化 ECharts └── mermaid.min.js + init ← 按需加载 </body>
**核心规则:** ECharts 图表使用 **声明式 `data-option` 属性**,不需要手写初始化脚本。`main.js` 自动处理所有初始化和 resize。Chart.js 仍使用 `data-chart` 属性(已有自动初始化支持)。Mermaid 使用 `<pre class="mermaid">` 标签。
---
## 生成报告时的关键提醒
> **🔴 图表渲染——data-option 声明式(无需手写 script!)**
>
> ECharts 图表不需要手写 `<script>` 初始化代码。只需在 `chart-box__canvas` 元素上加 `data-option` 属性:
>
> ```html
> <div class="chart-box__canvas" id="chart-xxx" data-option='{"xAxis":...,"series":...}'></div>
> ```
>
> `main.js` 会自动扫描所有 `chart-box__canvas[data-option]` 元素,自动调用 `echarts.init()` + `setOption()` + resize 监听。
>
> **完整三件套变为:**
> 1. **图表容器** + **`data-option` 属性**(内容区 HTML,一步到位)
> 2. **ECharts CDN**(deploy.py 按需自动注入 `<head>`)
> 3. ~~初始化脚本~~(不再需要!main.js 自动处理)
## 注意事项
- **本技能不包含任何 Cloudflare 凭据**,所有 token/account/project 信息由使用者自行配置
- 首页由 index.html 模板 + index.json 数据驱动,index.html 不应手动编辑
- 如果报告 HTML 是完整页面(含 `<html>` 标签),deploy.py 会自动提取 body 和 style
- deploy.py 会自动给文件名加日期前缀
- **外部页面**需要额外创建独立 Cloudflare Pages 项目并部署,然后用 `add` 命令加入索引
- 首页的 iframe viewer 会自动识别外部 URL(`url` 字段以 http 开头且域名不同),用 iframe 内嵌打开
- 管理模式下支持拖拽页面到侧边栏分类进行移动、勾选删除,操作完成后生成组合指令
- 管理模式下按 ESC 退出管理模式,取消所有未提交的操作