From 7aab3d713478bf8120ec7084fbc8b85499ecad44 Mon Sep 17 00:00:00 2001 From: R524809 Date: Fri, 28 Aug 2026 16:32:10 +0800 Subject: [PATCH] Refactor financial chart implementation --- CLAUDE.md | 4 +- README.md | 39 +- css/style.css | 36 +- data/{ => demo}/fx.csv | 0 data/{ => demo}/growth.csv | 0 data/{ => demo}/inventory.csv | 0 data/{ => demo}/margin.csv | 0 data/{ => ppmt}/ppmt_assets.csv | 0 data/ppmt/ppmt_cash_conversion.csv | 7 + data/ppmt/ppmt_cashflow_composition.csv | 7 + data/{ => ppmt}/ppmt_category.csv | 0 data/ppmt/ppmt_channel_revenue.csv | 14 + data/{ => ppmt}/ppmt_core_margin.csv | 0 data/{ => ppmt}/ppmt_core_profit.csv | 0 data/ppmt/ppmt_expense_structure.csv | 7 + data/{ => ppmt}/ppmt_growth.csv | 0 data/{ => ppmt}/ppmt_ip.csv | 0 data/{ => ppmt}/ppmt_ip_ex_monsters.csv | 0 data/{ => ppmt}/ppmt_ip_share_2026h1.csv | 0 data/{ => ppmt}/ppmt_ip_share_history.csv | 0 data/ppmt/ppmt_liquidity_working_capital.csv | 14 + data/{ => ppmt}/ppmt_margin_bridge.csv | 0 data/{ => ppmt}/ppmt_margins.csv | 0 data/{ => ppmt}/ppmt_profit.csv | 0 .../{ => ppmt}/ppmt_region_channel_2026h1.csv | 0 data/{ => ppmt}/ppmt_region_channel_yoy.csv | 0 .../ppmt_region_channel_yoy_timeseries.csv | 0 data/{ => ppmt}/ppmt_sankey_2026h1.csv | 0 data/ppmt/ppmt_working_capital_days.csv | 14 + data/{ => ppmt/raw}/2026H1.pdf | Bin data/{ => ppmt/raw}/ppmt.xlsx | Bin .../raw/泡泡玛特_分红分析_20260828_134834.csv | 13 + ...¡泡玛特_利润表_合并报表_20260828_134510.csv | 67 +++ .../raw/泡泡玛特_员工_20260828_134800.csv | 44 ++ ...玛特_现金流量表_合并报表_20260828_134646.csv | 14 + .../raw/泡泡玛特_股本变动_20260828_134823.csv | 18 + .../raw/泡泡玛特_营收构成_20260828_134749.csv | 141 ++++++ ...³¡玛特_财务指标_合并报表_20260828_134712.csv | 57 +++ ...玛特_资产负债表_合并报表_20260828_134503.csv | 128 +++++ docs/方案设计.md | 468 ++++++------------ js/charts/growth.js | 5 +- js/charts/index.js | 16 + js/charts/inventory.js | 6 +- js/charts/margin.js | 5 +- js/charts/ppmt_assets.js | 5 +- js/charts/ppmt_cash_conversion.js | 91 ++++ js/charts/ppmt_cashflow_composition.js | 61 +++ js/charts/ppmt_category.js | 16 +- js/charts/ppmt_channel_revenue.js | 87 ++++ js/charts/ppmt_core_profit.js | 1 + js/charts/ppmt_expense_structure.js | 74 +++ js/charts/ppmt_growth.js | 14 +- js/charts/ppmt_ip.js | 16 +- js/charts/ppmt_ip_share_history.js | 4 +- js/charts/ppmt_liquidity_working_capital.js | 73 +++ js/charts/ppmt_margins.js | 15 +- js/charts/ppmt_profit.js | 7 +- js/charts/ppmt_region_channel.js | 5 +- .../ppmt_region_channel_yoy_timeseries.js | 117 +++++ js/charts/ppmt_region_yoy.js | 5 +- js/charts/ppmt_working_capital_days.js | 58 +++ js/charts/twoline.js | 5 +- js/main.js | 70 ++- js/theme.js | 56 ++- js/ui.js | 19 +- manifest.json | 126 ++--- package-lock.json | 44 -- package.json | 12 +- pnpm-lock.yaml | 463 +++++++++++++++++ scripts/normalize_ppmt_financials.mjs | 260 ++++++++++ start.command | 27 + test/regress.mjs | 25 +- vite.config.js | 54 ++ 73 files changed, 2340 insertions(+), 594 deletions(-) rename data/{ => demo}/fx.csv (100%) rename data/{ => demo}/growth.csv (100%) rename data/{ => demo}/inventory.csv (100%) rename data/{ => demo}/margin.csv (100%) rename data/{ => ppmt}/ppmt_assets.csv (100%) create mode 100644 data/ppmt/ppmt_cash_conversion.csv create mode 100644 data/ppmt/ppmt_cashflow_composition.csv rename data/{ => ppmt}/ppmt_category.csv (100%) create mode 100644 data/ppmt/ppmt_channel_revenue.csv rename data/{ => ppmt}/ppmt_core_margin.csv (100%) rename data/{ => ppmt}/ppmt_core_profit.csv (100%) create mode 100644 data/ppmt/ppmt_expense_structure.csv rename data/{ => ppmt}/ppmt_growth.csv (100%) rename data/{ => ppmt}/ppmt_ip.csv (100%) rename data/{ => ppmt}/ppmt_ip_ex_monsters.csv (100%) rename data/{ => ppmt}/ppmt_ip_share_2026h1.csv (100%) rename data/{ => ppmt}/ppmt_ip_share_history.csv (100%) create mode 100644 data/ppmt/ppmt_liquidity_working_capital.csv rename data/{ => ppmt}/ppmt_margin_bridge.csv (100%) rename data/{ => ppmt}/ppmt_margins.csv (100%) rename data/{ => ppmt}/ppmt_profit.csv (100%) rename data/{ => ppmt}/ppmt_region_channel_2026h1.csv (100%) rename data/{ => ppmt}/ppmt_region_channel_yoy.csv (100%) rename data/{ => ppmt}/ppmt_region_channel_yoy_timeseries.csv (100%) rename data/{ => ppmt}/ppmt_sankey_2026h1.csv (100%) create mode 100644 data/ppmt/ppmt_working_capital_days.csv rename data/{ => ppmt/raw}/2026H1.pdf (100%) rename data/{ => ppmt/raw}/ppmt.xlsx (100%) create mode 100644 data/ppmt/raw/泡泡玛特_分红分析_20260828_134834.csv create mode 100644 data/ppmt/raw/泡泡玛特_利润表_合并报表_20260828_134510.csv create mode 100644 data/ppmt/raw/泡泡玛特_员工_20260828_134800.csv create mode 100644 data/ppmt/raw/泡泡玛特_现金流量表_合并报表_20260828_134646.csv create mode 100644 data/ppmt/raw/泡泡玛特_股本变动_20260828_134823.csv create mode 100644 data/ppmt/raw/泡泡玛特_营收构成_20260828_134749.csv create mode 100644 data/ppmt/raw/泡泡玛特_财务指标_合并报表_20260828_134712.csv create mode 100644 data/ppmt/raw/泡泡玛特_资产负债表_合并报表_20260828_134503.csv create mode 100644 js/charts/ppmt_cash_conversion.js create mode 100644 js/charts/ppmt_cashflow_composition.js create mode 100644 js/charts/ppmt_channel_revenue.js create mode 100644 js/charts/ppmt_expense_structure.js create mode 100644 js/charts/ppmt_liquidity_working_capital.js create mode 100644 js/charts/ppmt_region_channel_yoy_timeseries.js create mode 100644 js/charts/ppmt_working_capital_days.js delete mode 100644 package-lock.json create mode 100644 pnpm-lock.yaml create mode 100644 scripts/normalize_ppmt_financials.mjs create mode 100755 start.command create mode 100644 vite.config.js diff --git a/CLAUDE.md b/CLAUDE.md index 2bf1e2b..925119e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -22,10 +22,10 @@ import { ```bash node test/regress.mjs ppmt_ip_share # 改了一张,就只测这一张 node test/regress.mjs ppmt_ip_share ppmt_core_margin # 改了多张,列出来 -npm test # 全量,只在改了 theme.js / csv.js 等公共模块时才跑 +pnpm test # 全量,只在改了 theme.js / csv.js 等公共模块时才跑 ``` -全量跑纯属浪费,而且十几张无关图表的输出会盖住本次改动的真实问题。不传参数时才是全量,所以 `npm test` 保持向后兼容。 +全量跑纯属浪费,而且十几张无关图表的输出会盖住本次改动的真实问题。不传参数时才是全量,所以 `pnpm test` 保持向后兼容。 它会断言 `legend.top`、`grid.top`、折线 `symbolSize`/`borderWidth`,并检查 SVG 字体引号。加新图记得同步往 `regress.mjs` 顶部的 `all` 数组加一行,否则不会被测到。 diff --git a/README.md b/README.md index d6f1441..3c85ab3 100644 --- a/README.md +++ b/README.md @@ -8,24 +8,38 @@ ## 快速开始 -### 1. 启动本地服务器 +首次使用时,在项目根目录安装依赖: ```bash -# 方案 A:Python 3(Mac/Linux 自带) -python3 -m http.server 8899 +pnpm install +``` -# 方案 B:Node.js(需要先安装 Node) -npx http-server -p 8899 +### 1. 启动开发服务器(支持热更新) -# 方案 C:PHP(Mac 自带) -php -S localhost:8899 +```bash +# 方案 A:Vite 开发服务器 +pnpm dev + +# 方案 B:直接双击项目根目录的 `.start.command` +# 脚本会启动 Vite 并自动打开浏览器 ``` ### 2. 打开浏览器 访问 **http://localhost:8899**。页面左侧选择公司,中间用 Tab 切换分析维度,右侧可跳转到当前分类的图表。 -当前 manifest 内登记了 5 张示例图表和 14 张泡泡玛特财报图表: +修改 `js/` 或 `css/` 后页面会热更新;修改 `manifest.json` 或 `data/**/*.csv` 后页面会自动整页刷新。 + +需要生成静态发布文件时: + +```bash +pnpm build +pnpm preview +``` + +构建结果位于 `dist/`,其中包含运行所需的 manifest 与 CSV,不包含 `data/ppmt/raw/` 中的原始 PDF/XLSX。 + +当前 manifest 内登记了 5 张示例图表和 22 张泡泡玛特财报图表: **示例图表:** 1. **存货、营收与存货周转天数** — 双柱 + 折线,双 Y 轴 @@ -49,6 +63,8 @@ php -S localhost:8899 17. **主要 IP 收入占比(2026H1)** — 饼图,右侧图例带收入与占比 18. **各 IP 半年收入占比(21H1-26H1)** — 归一化堆叠柱状图,看收入结构变迁 19. **除 THE MONSTERS 外的 IP 收入及同比** — 柱 + 折线双轴,剔掉爆款看其余盘子 +20. **区域渠道分布(2026H1)** — 左右堆叠条形图,对比各区域线上与线下渠道收入 +21. **区域渠道同比增速趋势** — 热力图查看区域 × 渠道的历史同比变化 ### 3. 修改数据 @@ -77,6 +93,7 @@ smart-fin-chart/ ├── data/ # 数据文件(按公司划分) │ ├── demo/ │ └── ppmt/ +│ └── raw/ # 用户提供的原始 xlsx / PDF ├── js/ │ ├── main.js # 入口:读取 manifest、渲染、导出 PNG │ ├── ui.js # 三栏 UI 构建 @@ -92,7 +109,7 @@ smart-fin-chart/ ## 测试 ```bash -npm test +pnpm test ``` 用 ECharts 的 SSR 模式遍历 manifest 中的图表并渲染成 SVG,断言: @@ -139,7 +156,7 @@ A: 1. 在 `data/` 下创建新 CSV 2. 在 `js/charts/` 下创建新 `.js`,从 `theme.js` 取共享配置(`lineSeries()`、`barSeries()`、`standardLegend()`、`periodAxis`),不要手写等价值 3. 在 `js/charts/index.js` 注册 chart key -4. 在 `manifest.json` 对应公司与 Tab 中添加图表配置 +4. 在 `manifest.json` 对应公司的 `chartCatalog` 中登记图表配置,再在需要展示的 Tab 的 `chartRefs` 中引用它;概览可直接引用同一图表 ID,无需复制配置 5. 跑 `node test/regress.mjs `;浏览器检查标签、颜色和脚注 **Q:能改水印文字吗?** @@ -152,7 +169,7 @@ A:编辑 `js/theme.js` 的 `PALETTE` 对象,所有图表自动同步。 ## 技术细节 -- **ECharts 5.5.1**:从 jsDelivr CDN 加载,无需 npm install +- **ECharts 5.5.1**:从 jsDelivr CDN 加载,无需额外安装 ECharts - **ES6 Modules**:用原生 `