Refactor financial chart implementation

This commit is contained in:
R524809
2026-08-28 16:32:10 +08:00
parent 5ecc554aaa
commit 7aab3d7134
73 changed files with 2340 additions and 594 deletions
+2 -2
View File
@@ -22,10 +22,10 @@ import {
```bash ```bash
node test/regress.mjs ppmt_ip_share # 改了一张,就只测这一张 node test/regress.mjs ppmt_ip_share # 改了一张,就只测这一张
node test/regress.mjs ppmt_ip_share ppmt_core_margin # 改了多张,列出来 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` 数组加一行,否则不会被测到。 它会断言 `legend.top`、`grid.top`、折线 `symbolSize`/`borderWidth`,并检查 SVG 字体引号。加新图记得同步往 `regress.mjs` 顶部的 `all` 数组加一行,否则不会被测到。
+28 -11
View File
@@ -8,24 +8,38 @@
## 快速开始 ## 快速开始
### 1. 启动本地服务器 首次使用时,在项目根目录安装依赖:
```bash ```bash
# 方案 A:Python 3(Mac/Linux 自带) pnpm install
python3 -m http.server 8899 ```
# 方案 B:Node.js(需要先安装 Node) ### 1. 启动开发服务器(支持热更新)
npx http-server -p 8899
# 方案 C:PHP(Mac 自带) ```bash
php -S localhost:8899 # 方案 A:Vite 开发服务器
pnpm dev
# 方案 B:直接双击项目根目录的 `.start.command`
# 脚本会启动 Vite 并自动打开浏览器
``` ```
### 2. 打开浏览器 ### 2. 打开浏览器
访问 **http://localhost:8899**。页面左侧选择公司,中间用 Tab 切换分析维度,右侧可跳转到当前分类的图表。 访问 **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 轴 1. **存货、营收与存货周转天数** — 双柱 + 折线,双 Y 轴
@@ -49,6 +63,8 @@ php -S localhost:8899
17. **主要 IP 收入占比(2026H1)** — 饼图,右侧图例带收入与占比 17. **主要 IP 收入占比(2026H1)** — 饼图,右侧图例带收入与占比
18. **各 IP 半年收入占比(21H1-26H1)** — 归一化堆叠柱状图,看收入结构变迁 18. **各 IP 半年收入占比(21H1-26H1)** — 归一化堆叠柱状图,看收入结构变迁
19. **除 THE MONSTERS 外的 IP 收入及同比** — 柱 + 折线双轴,剔掉爆款看其余盘子 19. **除 THE MONSTERS 外的 IP 收入及同比** — 柱 + 折线双轴,剔掉爆款看其余盘子
20. **区域渠道分布(2026H1)** — 左右堆叠条形图,对比各区域线上与线下渠道收入
21. **区域渠道同比增速趋势** — 热力图查看区域 × 渠道的历史同比变化
### 3. 修改数据 ### 3. 修改数据
@@ -77,6 +93,7 @@ smart-fin-chart/
├── data/ # 数据文件(按公司划分) ├── data/ # 数据文件(按公司划分)
│ ├── demo/ │ ├── demo/
│ └── ppmt/ │ └── ppmt/
│ └── raw/ # 用户提供的原始 xlsx / PDF
├── js/ ├── js/
│ ├── main.js # 入口:读取 manifest、渲染、导出 PNG │ ├── main.js # 入口:读取 manifest、渲染、导出 PNG
│ ├── ui.js # 三栏 UI 构建 │ ├── ui.js # 三栏 UI 构建
@@ -92,7 +109,7 @@ smart-fin-chart/
## 测试 ## 测试
```bash ```bash
npm test pnpm test
``` ```
用 ECharts 的 SSR 模式遍历 manifest 中的图表并渲染成 SVG,断言: 用 ECharts 的 SSR 模式遍历 manifest 中的图表并渲染成 SVG,断言:
@@ -139,7 +156,7 @@ A:
1. 在 `data/` 下创建新 CSV 1. 在 `data/` 下创建新 CSV
2. 在 `js/charts/` 下创建新 `.js`,从 `theme.js` 取共享配置(`lineSeries()`、`barSeries()`、`standardLegend()`、`periodAxis`),不要手写等价值 2. 在 `js/charts/` 下创建新 `.js`,从 `theme.js` 取共享配置(`lineSeries()`、`barSeries()`、`standardLegend()`、`periodAxis`),不要手写等价值
3. 在 `js/charts/index.js` 注册 chart key 3. 在 `js/charts/index.js` 注册 chart key
4. 在 `manifest.json` 对应公司与 Tab 中添加图表配置 4. 在 `manifest.json` 对应公司的 `chartCatalog` 中登记图表配置,再在需要展示的 Tab 的 `chartRefs` 中引用它;概览可直接引用同一图表 ID,无需复制配置
5. 跑 `node test/regress.mjs <chart-id>`;浏览器检查标签、颜色和脚注 5. 跑 `node test/regress.mjs <chart-id>`;浏览器检查标签、颜色和脚注
**Q:能改水印文字吗?** **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**:用原生 `<script type="module">`,无构建步骤 - **ES6 Modules**:用原生 `<script type="module">`,无构建步骤
- **Canvas 渲染**:页面用 canvas(性能好),导出时用 ECharts 自带的 `getDataURL` 生成 PNG - **Canvas 渲染**:页面用 canvas(性能好),导出时用 ECharts 自带的 `getDataURL` 生成 PNG
- **响应式**:window resize 时重绘(水印数量跟着画布尺寸变化) - **响应式**:window resize 时重绘(水印数量跟着画布尺寸变化)
+15 -21
View File
@@ -24,25 +24,9 @@ button {
cursor: pointer; cursor: pointer;
} }
.app-header {
min-height: 76px;
padding: 16px 28px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
background: var(--surface);
border-bottom: 1px solid var(--line);
}
.app-brand h1,
.app-brand p,
.card-title, .card-title,
.sidebar-title { margin: 0; } .sidebar-title { margin: 0; }
.app-brand h1 { font-size: 21px; font-weight: 600; }
.app-brand p { margin-top: 4px; color: var(--muted); font-size: 13px; }
.reload-button, .reload-button,
.export-button { .export-button {
padding: 7px 13px; padding: 7px 13px;
@@ -53,12 +37,24 @@ button {
} }
.reload-button { .reload-button {
position: fixed;
top: 16px;
right: 18px;
z-index: 10;
width: 36px;
height: 36px;
padding: 0;
display: grid;
place-items: center;
color: #fff; color: #fff;
background: var(--accent); background: var(--accent);
border-color: var(--accent); border-color: var(--accent);
border-radius: 50%;
font-size: 22px;
line-height: 1;
} }
.reload-button:hover { opacity: .88; } .reload-button:hover { opacity: .88; transform: rotate(-45deg); }
.export-button:hover { color: var(--accent); border-color: var(--accent); } .export-button:hover { color: var(--accent); border-color: var(--accent); }
.app-layout { .app-layout {
@@ -67,7 +63,7 @@ button {
gap: 24px; gap: 24px;
max-width: 1600px; max-width: 1600px;
margin: 0 auto; margin: 0 auto;
padding: 28px 24px 64px; padding: 20px 24px 64px;
} }
.sidebar { .sidebar {
@@ -201,9 +197,7 @@ button {
.chart-sidebar { display: none; } .chart-sidebar { display: none; }
} }
@media (max-width: 720px) { @media (max-width: 880px) {
.app-header { padding: 14px 16px; }
.app-brand p { display: none; }
.app-layout { display: block; padding: 16px 12px 40px; } .app-layout { display: block; padding: 16px 12px 40px; }
.sidebar { position: static; margin-bottom: 12px; } .sidebar { position: static; margin-bottom: 12px; }
.company-sidebar { padding: 7px; } .company-sidebar { padding: 7px; }
View File
+7
View File
@@ -0,0 +1,7 @@
period,operating_cash_flow,net_profit,cash_conversion
2020FY,7.03,5.23,134.4
2021FY,7.79,8.55,91.2
2022FY,8.91,4.76,187.3
2023FY,19.91,10.89,182.8
2024FY,49.54,33.08,149.7
2025FY,108.65,130.12,83.5
1 period operating_cash_flow net_profit cash_conversion
2 2020FY 7.03 5.23 134.4
3 2021FY 7.79 8.55 91.2
4 2022FY 8.91 4.76 187.3
5 2023FY 19.91 10.89 182.8
6 2024FY 49.54 33.08 149.7
7 2025FY 108.65 130.12 83.5
+7
View File
@@ -0,0 +1,7 @@
period,operating_cash_flow,investing_cash_flow,financing_cash_flow,net_cash_increase
2020FY,7.03,-2.17,48.70,53.57
2021FY,7.79,-6.48,-4.36,-3.05
2022FY,8.91,-46.98,-11.77,-49.85
2023FY,19.91,2.34,-8.42,13.83
2024FY,49.54,0.09,-9.59,40.05
2025FY,108.65,-9.89,-17.67,81.09
1 period operating_cash_flow investing_cash_flow financing_cash_flow net_cash_increase
2 2020FY 7.03 -2.17 48.70 53.57
3 2021FY 7.79 -6.48 -4.36 -3.05
4 2022FY 8.91 -46.98 -11.77 -49.85
5 2023FY 19.91 2.34 -8.42 13.83
6 2024FY 49.54 0.09 -9.59 40.05
7 2025FY 108.65 -9.89 -17.67 81.09
+14
View File
@@ -0,0 +1,14 @@
period,retail,online,robot_store,wholesale,total_revenue,revenue_yoy
2020H1,3.13,3.34,1.05,0.64,8.18,50.5
2020FY,10.02,9.52,3.29,2.07,25.13,49.3
2021H1,6.75,6.78,2.28,1.91,17.73,116.8
2021FY,16.73,18.79,4.70,4.45,44.91,78.7
2022H1,9.26,10.13,2.08,2.12,23.59,33.1
2022FY,18.27,19.20,3.94,4.77,46.17,2.8
2023H1,13.44,8.74,2.96,2.99,28.14,19.3
2023FY,30.62,18.66,6.06,7.67,63.01,36.5
2024H1,23.65,13.43,3.68,4.82,45.58,62.0
2024FY,67.66,41.53,8.31,12.88,130.38,106.9
2025H1,69.08,54.98,8.21,6.50,138.76,204.4
2025FY,172.54,164.34,16.89,17.42,371.20,184.7
2026H1,91.12,62.12,9.51,8.98,171.73,23.8
1 period retail online robot_store wholesale total_revenue revenue_yoy
2 2020H1 3.13 3.34 1.05 0.64 8.18 50.5
3 2020FY 10.02 9.52 3.29 2.07 25.13 49.3
4 2021H1 6.75 6.78 2.28 1.91 17.73 116.8
5 2021FY 16.73 18.79 4.70 4.45 44.91 78.7
6 2022H1 9.26 10.13 2.08 2.12 23.59 33.1
7 2022FY 18.27 19.20 3.94 4.77 46.17 2.8
8 2023H1 13.44 8.74 2.96 2.99 28.14 19.3
9 2023FY 30.62 18.66 6.06 7.67 63.01 36.5
10 2024H1 23.65 13.43 3.68 4.82 45.58 62.0
11 2024FY 67.66 41.53 8.31 12.88 130.38 106.9
12 2025H1 69.08 54.98 8.21 6.50 138.76 204.4
13 2025FY 172.54 164.34 16.89 17.42 371.20 184.7
14 2026H1 91.12 62.12 9.51 8.98 171.73 23.8
+7
View File
@@ -0,0 +1,7 @@
period,sales_expense,admin_expense,financial_expense,four_expense_rate
2020FY,6.30,2.80,0.09,36.6
2021FY,11.06,5.58,-0.07,36.9
2022FY,14.71,6.86,-0.29,46.1
2023FY,20.05,7.07,-1.52,40.6
2024FY,36.50,9.47,-1.63,34.0
2025FY,80.82,17.70,-0.76,26.3
1 period sales_expense admin_expense financial_expense four_expense_rate
2 2020FY 6.30 2.80 0.09 36.6
3 2021FY 11.06 5.58 -0.07 36.9
4 2022FY 14.71 6.86 -0.29 46.1
5 2023FY 20.05 7.07 -1.52 40.6
6 2024FY 36.50 9.47 -1.63 34.0
7 2025FY 80.82 17.70 -0.76 26.3
@@ -0,0 +1,14 @@
period,cash,total_liabilities,net_working_capital
2020H1,8.22,5.47,8.66
2020FY,56.80,8.40,55.91
2021H1,57.67,13.26,55.92
2021FY,52.65,15.04,56.65
2022H1,51.63,14.76,58.16
2022FY,50.42,16.15,54.85
2023H1,55.40,17.59,57.33
2023FY,59.63,21.88,59.50
2024H1,70.10,25.65,66.47
2024FY,96.20,39.86,88.66
2025H1,137.66,69.27,119.27
2025FY,172.25,94.49,177.46
2026H1,139.62,77.40,174.20
1 period cash total_liabilities net_working_capital
2 2020H1 8.22 5.47 8.66
3 2020FY 56.80 8.40 55.91
4 2021H1 57.67 13.26 55.92
5 2021FY 52.65 15.04 56.65
6 2022H1 51.63 14.76 58.16
7 2022FY 50.42 16.15 54.85
8 2023H1 55.40 17.59 57.33
9 2023FY 59.63 21.88 59.50
10 2024H1 70.10 25.65 66.47
11 2024FY 96.20 39.86 88.66
12 2025H1 137.66 69.27 119.27
13 2025FY 172.25 94.49 177.46
14 2026H1 139.62 77.40 174.20
+14
View File
@@ -0,0 +1,14 @@
period,inventory_days,receivable_days,payable_days,cash_conversion_cycle
2020H1,101.4,9.6,27.6,83.3
2020FY,63.0,8.9,28.4,43.5
2021H1,74.3,10.9,40.9,44.3
2021FY,105.4,10.0,29.9,85.5
2022H1,159.0,11.6,44.7,125.9
2022FY,151.8,14.3,46.3,119.8
2023H1,131.2,12.9,54.4,89.7
2023FY,130.9,14.7,51.2,94.4
2024H1,100.0,11.6,54.5,57.1
2024FY,101.0,11.0,52.9,59.1
2025H1,83.1,9.4,48.8,43.7
2025FY,121.6,6.8,36.1,92.3
2026H1,200.0,8.1,42.6,165.5
1 period inventory_days receivable_days payable_days cash_conversion_cycle
2 2020H1 101.4 9.6 27.6 83.3
3 2020FY 63.0 8.9 28.4 43.5
4 2021H1 74.3 10.9 40.9 44.3
5 2021FY 105.4 10.0 29.9 85.5
6 2022H1 159.0 11.6 44.7 125.9
7 2022FY 151.8 14.3 46.3 119.8
8 2023H1 131.2 12.9 54.4 89.7
9 2023FY 130.9 14.7 51.2 94.4
10 2024H1 100.0 11.6 54.5 57.1
11 2024FY 101.0 11.0 52.9 59.1
12 2025H1 83.1 9.4 48.8 43.7
13 2025FY 121.6 6.8 36.1 92.3
14 2026H1 200.0 8.1 42.6 165.5
@@ -0,0 +1,13 @@
日期,="归属于母公司普通股股东的净利润(单位: 元)",="总分红金额",单位,="分红率"
2026-06-30,=5038384000,=0,港币,=0
2025-12-31,=12775689000,=3193962470.3550,元,=0.2500
2024-12-31,=3125473000,=1093961489.9900,元,=0.3500
2023-12-31,=1082344000,=378844262.6150,元,=0.3500
2022-12-31,=475660000,=120107027.8500,元,=0.2525
2021-12-31,=854339000,=213655282.6200,元,=0.2501
2020-12-31,=523505000,=209449469.9700,元,=0.4001
2019-12-31,=451118000,=0,港币,=0
2018-12-31,=99521000,=0,港币,=0
2017-12-31,=1569000,=0,港币,=0
数据来源于:理杏仁网站(lixinger.com)
Can't render this file because it contains an unexpected character in line 1 and column 9.
@@ -0,0 +1,67 @@
财报类型,股票代码,股票名称,中报,年报,中报,年报,中报,年报,中报,年报,中报,年报,中报,年报,中报,年报,中报,年报,年报
日期,="09992",="泡泡玛特",2026-06-30,2025-12-31,2025-06-30,2024-12-31,2024-06-30,2023-12-31,2023-06-30,2022-12-31,2022-06-30,2021-12-31,2021-06-30,2020-12-31,2020-06-30,2019-12-31,2019-06-30,2018-12-31,2017-12-31
货币,="09992",="泡泡玛特",元,元,元,元,元,元,元,元,元,元,元,元,元,元,元,元,元
审计意见,="09992",="泡泡玛特",,,,,,,,,,,,,,,,,
会计师事务所,="09992",="泡泡玛特",,,,,,,,,,,,,,,,,
一、营业总收入,="09992",="泡泡玛特",=17172921000,=37120052000,=13876276000,=13037749000,=4557831000,=6301002000,=2813812000,=4617324000,=2358818000,=4490651000,=1772577000,=2513471000,=817791000,=1683434000,=543396000,=514511000,=158074000
营业收入,="09992",="泡泡玛特",=17172921000,=37120052000,=13876276000,=13037749000,=4557831000,=6301002000,=2813812000,=4617324000,=2358818000,=4490651000,=1772577000,=2513471000,=817791000,=1683434000,=543396000,=514511000,=158074000
利息收入,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
手续费及佣金收入,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
其他收入,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
营业成本,="09992",="泡泡玛特",=5207181000,=10355136000,=4115212000,=4329984000,=1638726000,=2436931000,=1115452000,=1962781000,=988411000,=1732027000,=655512000,=919363000,=284352000,=593100000,=212804000,=216486000,=82820000
利息支出,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
手续费及佣金支出,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
毛利,="09992",="泡泡玛特",=11965740000,=26764916000,=9761064000,=8707765000,=2919105000,=3864071000,=1698360000,=2654543000,=1370407000,=2758624000,=1117065000,=1594108000,=533439000,=1090334000,=330592000,=298025000,=75254000
毛利率(GM),="09992",="泡泡玛特",=0.6968,=0.7210,=0.7034,=0.6679,=0.6405,=0.6132,=0.6036,=0.5749,=0.5810,=0.6143,=0.6302,=0.6342,=0.6523,=0.6477,=0.6084,=0.5792,=0.4761
减:销售费用,="09992",="泡泡玛特",=3931123000,=8082433000,=3192590000,=3650464000,=1353206000,=2004706000,=878319000,=1470753000,=693230000,=1106078000,=419780000,=630069000,=223030000,=363819000,=118731000,=125721000,=51047000
管理费用,="09992",="泡泡玛特",=926526000,=1770114000,=770405000,=947093000,=434410000,=707300000,=331252000,=686280000,=322679000,=557509000,=239673000,=279967000,=125397000,=142468000,=64132000,=43599000,=20897000
财务费用,="09992",="泡泡玛特",=-11479000,=-76495000,=-65506000,=-163352000,=-83775000,=-151880000,=-63439000,=-29103000,=-7011000,=-7363000,=-5728000,=8993000,=3925000,=5389000,=1926000,=2313000,=1755000
(其中)融资成本,="09992",="泡泡玛特",=81173000,=82471000,=28364000,=48983000,=22218000,=32337000,=16174000,=38579000,=15981000,=21246000,=8160000,=10946000,=4624000,=5813000,=2010000,=2455000,=1764000
(其中)利息收入,="09992",="泡泡玛特",=92652000,=158966000,=93870000,=212335000,=105993000,=184217000,=79613000,=67682000,=22992000,=28609000,=13888000,=1953000,=699000,=424000,=84000,=142000,=9000
(其中)外币兑换净收益,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
研发费用,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
股份酬金成本,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
销售费用率,="09992",="泡泡玛特",=0.2289,=0.2177,=0.2301,=0.2800,=0.2969,=0.3182,=0.3121,=0.3185,=0.2939,=0.2463,=0.2368,=0.2507,=0.2727,=0.2161,=0.2185,=0.2444,=0.3229
管理费用率,="09992",="泡泡玛特",=0.0540,=0.0477,=0.0555,=0.0726,=0.0953,=0.1123,=0.1177,=0.1486,=0.1368,=0.1241,=0.1352,=0.1114,=0.1533,=0.0846,=0.1180,=0.0847,=0.1322
营业费用率,="09992",="泡泡玛特",=0.2829,=0.2654,=0.2856,=0.3526,=0.3922,=0.4304,=0.4299,=0.4672,=0.4307,=0.3705,=0.3720,=0.3621,=0.4261,=0.3007,=0.3365,=0.3291,=0.4551
财务费用率,="09992",="泡泡玛特",=-0.0007,=-0.0021,=-0.0047,=-0.0125,=-0.0184,=-0.0241,=-0.0225,=-0.0063,=-0.0030,=-0.0016,=-0.0032,=0.0036,=0.0048,=0.0032,=0.0035,=0.0045,=0.0111
研发费用率,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
四项费用率,="09992",="泡泡玛特",=0.2822,=0.2634,=0.2809,=0.3401,=0.3738,=0.4063,=0.4073,=0.4609,=0.4277,=0.3688,=0.3688,=0.3656,=0.4309,=0.3039,=0.3401,=0.3336,=0.4662
加:对联营企业及合营企业的投资收益,="09992",="泡泡玛特",=30633000,=69653000,=47625000,=48188000,=18246000,=33229000,=17346000,=27046000,=4868000,=14016000,=4140000,=3873000,=-1125000,=4970000,=1974000,=959000,=-351000
(其中)对联营公司的投资收益,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
(其中)对合营企业的投资收益,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
投资收益,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,
公允价值变动收益,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=-6260000,=-6436000,=0,=0,=0,=0
投资物业公允值变动,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
资产减值损失,="09992",="泡泡玛特",=0,=11805000,=746000,=3446000,=2589000,=745000,=95000,=4500000,=-80000,=1435000,=3296000,=-398000,=0,=0,=0,=0,=0
信用减值损失,="09992",="泡泡玛特",=-2651000,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
其他业务收入,="09992",="泡泡玛特",=302837000,=149921000,=246418000,=84288000,=31586000,=79326000,=49068000,=90370000,=91479000,=56210000,=32371000,=45420000,=22379000,=17833000,=4168000,=5179000,=1413000
其他业务成本,="09992",="泡泡玛特",=688833000,=160011000,=0,=36778000,=34069000,=0,=0,=0,=0,=0,=0,=11107000,=0,=0,=0,=0,=0
二、利润总额,="09992",="泡泡玛特",=6766858000,=17036622000,=6156872000,=4365812000,=1228438000,=1415755000,=618547000,=639529000,=457936000,=1171191000,=496555000,=707403000,=196882000,=598375000,=151044000,=132260000,=2273000
研发费占利润总额比值,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
减:所得税费用,="09992",="泡泡玛特",=1666548000,=4024580000,=1475159000,=1057467000,=264296000,=326984000,=141305000,=163728000,=124991000,=316624000,=137757000,=184091000,=55598000,=147257000,=37431000,=32739000,=704000
有效税率,="09992",="泡泡玛特",=0.2463,=0.2362,=0.2396,=0.2422,=0.2151,=0.2310,=0.2284,=0.2560,=0.2729,=0.2703,=0.2774,=0.2602,=0.2824,=0.2461,=0.2478,=0.2475,=0.3097
三、净利润,="09992",="泡泡玛特",=5100310000,=13012042000,=4681713000,=3308345000,=964142000,=1088771000,=477242000,=475801000,=332945000,=854567000,=358798000,=523312000,=141284000,=451118000,=113613000,=99521000,=1569000
净利润率,="09992",="泡泡玛特",=0.2970,=0.3505,=0.3374,=0.2538,=0.2115,=0.1728,=0.1696,=0.1030,=0.1411,=0.1903,=0.2024,=0.2082,=0.1728,=0.2680,=0.2091,=0.1934,=0.0099
归属于母公司股东及其他权益持有者的净利润,="09992",="泡泡玛特",=5038384000,=12775689000,=4574368000,=3125473000,=921333000,=1082344000,=476575000,=475660000,=332820000,=854339000,=358742000,=523505000,=141358000,=451118000,=113613000,=99521000,=1569000
归属于母公司普通股股东的净利润,="09992",="泡泡玛特",=5038384000,=12775689000,=4574368000,=3125473000,=921333000,=1082344000,=476575000,=475660000,=332820000,=854339000,=358742000,=523505000,=141358000,=451118000,=113613000,=99521000,=1569000
少数股东损益,="09992",="泡泡玛特",=61926000,=236353000,=107345000,=182872000,=42809000,=6427000,=667000,=141000,=125000,=228000,=56000,=-193000,=-74000,=0,=0,=0,=0
可赎回少数股东权益利息及回购视同股利分配,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
非国际公认会计准则净利润,="09992",="泡泡玛特",=5155777000,=13083646000,=4709630000,=3403162000,=1017625000,=1190519000,=535365000,=573540000,=376285000,=1001635000,=435189000,=590506000,,,,,
非国际公认会计准则归属于母公司股东净利润,="09992",="泡泡玛特",,,,,,,,,,,,,,,,,
四、基本每股收益,="09992",="泡泡玛特",=3.8000,=9.6100,=3.4417,=2.3600,=0.6949,=0.8100,=0.3546,=0.3500,=0.2418,=0.6200,=0.2604,=0.4400,=1.2200,=3.9100,=0.9800,=0.8600,=0.0100
稀释每股收益,="09992",="泡泡玛特",=3.7900,=9.5800,=3.4295,=2.3500,=0.6922,=0.8100,=0.3542,=0.3500,=0.2417,=0.6200,=0.2602,=0.4400,=1.2200,=3.9100,=0.9800,=0.8600,=0.0100
五、综合收益总额,="09992",="泡泡玛特",=5460017000,=12832458000,=4658377000,=3460496000,=1069848000,=1163877000,=646723000,=886846000,=575801000,=739012000,=304325000,=484536000,=126233000,=452021000,=113613000,=99521000,=1569000
归属于母公司股东及其他权益持有者的综合收益总额,="09992",="泡泡玛特",=5420889000,=12590104000,=4545123000,=3269817000,=1026682000,=1157468000,=646192000,=886633000,=575590000,=738816000,=304336000,=484604000,=126101000,=452021000,=113613000,=99521000,=1569000
归属于母公司普通股股东的综合收益总额,="09992",="泡泡玛特",=5420889000,=12590104000,=4545123000,=3269817000,=1026682000,=1157468000,=646192000,=886633000,=575590000,=738816000,=304336000,=484604000,=126101000,=452021000,=113613000,=99521000,=1569000
归属于少数股东的综合收益总额,="09992",="泡泡玛特",=39128000,=242354000,=113254000,=190679000,=43166000,=6409000,=531000,=213000,=211000,=196000,=-11000,=-68000,=132000,=0,=0,=0,=0
可赎回少数股东权益的综合收益,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
其他综合收益的税后净额,="09992",="泡泡玛特",=359707000,=-179584000,=-23336000,=152151000,=105706000,=75106000,=169481000,=411045000,=242856000,=-115555000,=-54473000,=-38776000,=-15051000,=903000,=0,=0,=0
分红、回购及涨跌幅,,,,,,,,,,,,,,,,,,,
分红金额,="09992",="泡泡玛特",,=3193962470.3550,,=1093961489.9900,,=378844262.6150,,=120107027.8500,,=213655282.6200,,=209449469.9700,,=0,,=0,=0
H股分红金额,="09992",="泡泡玛特",,=3193962470.3550,,=1093961489.9900,,=378844262.6150,,=120107027.8500,,=213655282.6200,,=209449469.9700,,=0,,=0,=0
分红率,="09992",="泡泡玛特",,=0.2500,,=0.3500,,=0.3500,,=0.2525,,=0.2501,,=0.4001,,=0,,=0,=0
回购金额,="09992",="泡泡玛特",,=0,,=85826488,,=370687990,,=735363682,,=0,,=0,,=0,,=0,=0
年度涨跌幅,="09992",="泡泡玛特",,=1.1017,,=3.4625,,=0.0273,,=-0.5543,,=-0.4492,,,,,,,
数据来源于:理杏仁网站(lixinger.com)
Can't render this file because it contains an unexpected character in line 2 and column 9.
@@ -0,0 +1,44 @@
日期,2025-12-31,2024-12-31,2023-12-31,2022-12-31,2021-12-31,2020-12-31
人均营业总收入,=4443652.6007,=2333795.5786,=1388191.6722,=1121935.1233,=1409273.8114,
人均净利润,=1557675.4654,=592203.5264,=239870.2357,=115611.9548,=268183.5870,
员工总数,=10434,=6273,=4900,=4178,=4053,=2320
按性别分,,,,,,
男,=1918,=1783,=1369,,,
女,=4780,=4490,=3531,,,
按僱傭類型分,,,,,,
全職,=8663,=5692,=4696,,,
兼職,=1771,=581,=204,,,
按年龄分,,,,,,
30歲(不含)以下,=4186,=4024,=3333,,,
30歲至50歲(不含),=2506,=2242,=1565,,,
50歲(含)以上,=0.0600,=0.0700,=0.0200,,,
按地區分,,,,,,
中國境內,=6698,=4947,=4496,,,
港澳台及海外地區,=3736,,,,,
港澳台,,=1326,=404,,,
总流失率,=0.3569,=0.3260,=0.3828,=0.3226,=0.2296,
流失率按性别分 ,,,,,,
男,=0.3160,=0.2836,=0.3512,=0.3489,=0.2203,
女,=0.3291,=0.3426,=0.3959,=0.3105,=0.2291,
流失率按年齡分,,,,,,
30歲(不含)以下,=0.4449,=0.4004,=0.4890,=0.3457,=0.2495,
30歲至50歲(不含),=0.1285,=0.1818,=0.1504,=0.2554,=0.1984,
50歲(含)以上,,=0.4444,,,,
流失率按地區分,,,,,,
中國境內,=0.3292,=0.3230,=0.3734,=0.3248,=0.2296,
港澳台及海外,=0.4204,,,,,
港澳台,,=0.3422,=0.6058,=0.1443,,
按性别分 (正式員工),,,,,,
男,,,,=1279,=1243,
女,,,,=2860,=2602,
按僱傭類型分(正式員工),,,,,,
全職,,,,=3937,=3716,=2320
兼職,,,,=202,=129,
按年龄分 (正式員工),,,,,,
30歲(不含)以下,,,,=2872,=3076,
30歲至50歲(不含),,,,=1267,=769,
按地區分(正式員工),,,,,,
中國境內,,,,=4042,=3845,
港澳台,,,,=97,,
数据来源于:理杏仁网站(lixinger.com)
1 日期 2025-12-31 2024-12-31 2023-12-31 2022-12-31 2021-12-31 2020-12-31
2 人均营业总收入 =4443652.6007 =2333795.5786 =1388191.6722 =1121935.1233 =1409273.8114
3 人均净利润 =1557675.4654 =592203.5264 =239870.2357 =115611.9548 =268183.5870
4 员工总数 =10434 =6273 =4900 =4178 =4053 =2320
5 按性别分
6 男 =1918 =1783 =1369
7 女 =4780 =4490 =3531
8 按僱傭類型分
9 全職 =8663 =5692 =4696
10 兼職 =1771 =581 =204
11 按年龄分
12 30歲(不含)以下 =4186 =4024 =3333
13 30歲至50歲(不含) =2506 =2242 =1565
14 50歲(含)以上 =0.0600 =0.0700 =0.0200
15 按地區分
16 中國境內 =6698 =4947 =4496
17 港澳台及海外地區 =3736
18 港澳台 =1326 =404
19 总流失率 =0.3569 =0.3260 =0.3828 =0.3226 =0.2296
20 流失率按性别分
21 男 =0.3160 =0.2836 =0.3512 =0.3489 =0.2203
22 女 =0.3291 =0.3426 =0.3959 =0.3105 =0.2291
23 流失率按年齡分
24 30歲(不含)以下 =0.4449 =0.4004 =0.4890 =0.3457 =0.2495
25 30歲至50歲(不含) =0.1285 =0.1818 =0.1504 =0.2554 =0.1984
26 50歲(含)以上 =0.4444
27 流失率按地區分
28 中國境內 =0.3292 =0.3230 =0.3734 =0.3248 =0.2296
29 港澳台及海外 =0.4204
30 港澳台 =0.3422 =0.6058 =0.1443
31 按性别分 (正式員工)
32 男 =1279 =1243
33 女 =2860 =2602
34 按僱傭類型分(正式員工)
35 全職 =3937 =3716 =2320
36 兼職 =202 =129
37 按年龄分 (正式員工)
38 30歲(不含)以下 =2872 =3076
39 30歲至50歲(不含) =1267 =769
40 按地區分(正式員工)
41 中國境內 =4042 =3845
42 港澳台 =97
43 数据来源于:理杏仁网站(lixinger.com)
@@ -0,0 +1,14 @@
财报类型,股票代码,股票名称,中报,年报,中报,年报,中报,年报,中报,年报,中报,年报,中报,年报,中报,年报,中报,年报,年报
日期,="09992",="泡泡玛特",2026-06-30,2025-12-31,2025-06-30,2024-12-31,2024-06-30,2023-12-31,2023-06-30,2022-12-31,2022-06-30,2021-12-31,2021-06-30,2020-12-31,2020-06-30,2019-12-31,2019-06-30,2018-12-31,2017-12-31
货币,="09992",="泡泡玛特",元,元,元,元,元,元,元,元,元,元,元,元,元,元,元,元,元
审计意见,="09992",="泡泡玛特",,,,,,,,,,,,,,,,,
会计师事务所,="09992",="泡泡玛特",,,,,,,,,,,,,,,,,
经营活动产生的现金流量净额,="09992",="泡泡玛特",,=10865152000,=5977473000,=4954220000,=1905271000,=1990596000,=1092337000,=891022000,=373578000,=779097000,=441657000,=703358000,=122191000,=502889000,=145388000,=175000000,=15507000
投资活动产生的现金流量净额,="09992",="泡泡玛特",,=-988896000,=1234090000,=8957000,=333591000,=233940000,=82034000,=-4698408000,=-4209902000,=-648002000,=-4498407000,=-216935000,=-48259000,=-118267000,=12553000,=-100510000,=-8884000
筹资活动产生的现金流量净额,="09992",="泡泡玛特",,=-1767352000,=-1404951000,=-958503000,=-715974000,=-841557000,=-558287000,=-1177437000,=-480561000,=-435827000,=-120998000,=4870374000,=423756000,=-155584000,=-106273000,=8718000,=-17602000
现金及现金等价物的净增加额,="09992",="泡泡玛特",,=8108904000,=5806612000,=4004674000,=1522888000,=1382979000,=616084000,=-4984823000,=-4316885000,=-304732000,=-4177748000,=5356797000,=497688000,=229038000,=51668000,=83208000,=-10979000
期初现金及现金等价物的余额,="09992",="泡泡玛特",,=6109017000,=6109017000,=2077927000,=2077927000,=685314000,=685314000,=5264710000,=5264710000,=5680235000,=5680235000,=324614000,=324614000,=96802000,=96802000,=13592000,=24571000
汇率变动对现金及现金等价物的影响,="09992",="泡泡玛特",,=-442834000,=7065000,=26416000,=7859000,=9634000,=171984000,=405427000,=127555000,=-110793000,=1135000,=-1176000,=-616000,=-1226000,=-10000,=2000,=0
期末现金及现金等价物净余额,="09992",="泡泡玛特",,=13775087000,=11922694000,=6109017000,=3608674000,=2077927000,=1473382000,=685314000,=1075380000,=5264710000,=1503622000,=5680235000,=821686000,=324614000,=148460000,=96802000,=13592000
数据来源于:理杏仁网站(lixinger.com)
Can't render this file because it contains an unexpected character in line 2 and column 9.
@@ -0,0 +1,18 @@
日期,="总股数(单位: 股)"
2026-07-20,=1331779203
2026-05-31,=1331723150
2026-02-28,=1341043150
2026-01-31,=1341543150
2024-02-27,=1342943150
2023-12-29,=1348243150
2023-07-25,=1351373150
2023-06-30,=1356323150
2023-03-31,=1367590150
2022-08-31,=1380540550
2022-06-30,=1397572150
2022-03-31,=1400527350
2020-12-18,=1401937550
2020-12-17,=1401940000
2020-12-11,=1381580000
数据来源于:理杏仁网站(lixinger.com)
Can't render this file because it contains an unexpected character in line 1 and column 9.
@@ -0,0 +1,141 @@
营收构成表( 元),日期,2026-Q2,2025-Q4,2025-Q2,2024-Q4,2024-Q2,2023-Q4,2023-Q2,2022-Q4,2022-Q2,2021-Q4,2021-Q2,2020-Q4,2020-Q2,2019-Q4,2019-Q2,2018-Q4,2017-Q4
按服務類型分-零售店銷售收益
,收入,=9111987000,=17254326000,=6908010000,=6765771000,=2364781000,=3062152000,=1344492000,=1826671000,=926495000,=1673386000,=675286000,=1002054000,=313296000,=739690000,=232954000,=248257000,=101005000
,收入同比,31.90%,155.02%,192.12%,120.95%,75.89%,67.64%,45.12%,9.16%,37.20%,67.00%,115.54%,35.47%,34.49%,197.95%,,145.79%,
,成本,,,,,,,,,,,,,=121908000,=254176000,=99637000,=96233000,=45501000
,成本同比,,,,,,,,,,,,,22.35%,164.13%,,111.50%,
,毛利率,,,,,,,,,,,,,61.09%,65.64%,57.23%,61.24%,54.95%
按服務類型分-線上銷售收益
,收入,=6212071000,=16433829000,=5497997000,=4152781000,=1342913000,=1866274000,=874209000,=1920033000,=1012762000,=1879189000,=678281000,=951875000,=334303000,=539201000,=163545000,=102886000,=14854000
,收入同比,12.99%,295.73%,309.41%,122.52%,53.61%,-2.80%,-13.68%,2.17%,49.31%,97.42%,102.89%,76.53%,104.41%,424.08%,,592.65%,
,成本,,,,,,,,,,,,,=98798000,=167482000,=47064000,=38247000,=6528000
,成本同比,,,,,,,,,,,,,109.92%,337.90%,,485.89%,
,毛利率,,,,,,,,,,,,,70.45%,68.94%,71.22%,62.83%,56.05%
按服務類型分-機器人商店銷售收益
,收入,=950525000,=1689446000,=820741000,=831477000,=368157000,=605950000,=296434000,=394039000,=207820000,=469803000,=227561000,=328679000,=105496000,=248554000,=82473000,=86431000,=5568000
,收入同比,15.81%,103.19%,122.93%,37.22%,24.20%,53.78%,42.64%,-16.13%,-8.68%,42.94%,115.71%,32.24%,27.92%,187.58%,,1452.28%,
,成本,,,,,,,,,,,,,=28592000,=72776000,=23323000,=24674000,=2051000
,成本同比,,,,,,,,,,,,,22.59%,194.95%,,1103.02%,
,毛利率,,,,,,,,,,,,,72.90%,70.72%,71.72%,71.45%,63.16%
按服務類型分-批發收益
,收入,=898338000,=1742451000,=649528000,=1287720000,=481980000,=766626000,=298677000,=476581000,=211741000,=444817000,=191449000,=207386000,=63500000,=110467000,=39887000,=51329000,=29884000
,收入同比,38.31%,35.31%,34.76%,67.97%,61.37%,60.86%,41.06%,7.14%,10.60%,114.49%,201.49%,87.74%,59.20%,115.21%,,71.76%,
,成本,,,,,,,,,,,,,=34004000,=69908000,=26567000,=38209000,=23996000
,成本同比,,,,,,,,,,,,,27.99%,82.96%,,59.23%,
,毛利率,,,,,,,,,,,,,46.45%,36.72%,33.39%,25.56%,19.70%
按服務類型分-展會收益
,收入,,,,,,,,,,=23456000,,=23477000,=1196000,=45522000,=24537000,=25608000,=6763000
,收入同比,,,,,,,,,,-0.09%,,-48.43%,-95.13%,77.76%,,278.65%,
,成本,,,,,,,,,,,,,=1050000,=28758000,=16213000,=19123000,=4744000
,成本同比,,,,,,,,,,,,,-93.52%,50.38%,,303.10%,
,毛利率,,,,,,,,,,,,,12.21%,36.83%,33.92%,25.32%,29.85%
按地區分-中國
,收入,=12200509000,=20851717000,,,,,,,,,,,=797119000,=1656544000,=536854000,=510149000,=157414000
,收入同比,,,,,,,,,,,,,48.48%,224.72%,,224.08%,
,成本,,,,,,,,,,,,,=273637000,=578280000,=209837000,=214495000,=82536000
,成本同比,,,,,,,,,,,,,30.40%,169.60%,,159.88%,
,毛利率,,,,,,,,,,,,,65.67%,65.09%,60.91%,57.95%,47.57%
按地區分-亞太
,收入,=2575045000,=8011027000,,,,,,,,,,,,,,,
,收入同比,,,,,,,,,,,,,,,,,
,成本,,,,,,,,,,,,,,,,,
,成本同比,,,,,,,,,,,,,,,,,
,毛利率,,,,,,,,,,,,,,,,,
按地區分-美洲
,收入,=1891637000,=6806189000,,,,,,,,,,,,,,,
,收入同比,,,,,,,,,,,,,,,,,
,成本,,,,,,,,,,,,,,,,,
,成本同比,,,,,,,,,,,,,,,,,
,毛利率,,,,,,,,,,,,,,,,,
按地區分-海外
,收入,=505730000,,,,,,,,,,,,=20672000,=26890000,=6542000,=4362000,=660000
,收入同比,,,,,,,,,,,,,215.99%,516.46%,,560.91%,
,成本,,,,,,,,,,,,,=10715000,=14820000,=2967000,=1991000,=284000
,成本同比,,,,,,,,,,,,,261.14%,644.35%,,601.06%,
,毛利率,,,,,,,,,,,,,48.17%,44.89%,54.65%,54.36%,56.97%
按地區分-歐洲及其他地區
,收入,,=1451119000,,,,,,,,,,,,,,,
,收入同比,,,,,,,,,,,,,,,,,
,成本,,,,,,,,,,,,,,,,,
,成本同比,,,,,,,,,,,,,,,,,
,毛利率,,,,,,,,,,,,,,,,,
按地區分-中國大陸
,收入,,,=8282812000,=7972074000,=3206354000,=5234901000,=2438032000,=4163386000,=2202128000,,,,,,,,
,收入同比,,,158.32%,52.29%,31.51%,25.74%,10.71%,,,,,,,,,,
,成本,,,,,,=2062904000,=976166000,,,,,,,,,,
,成本同比,,,,,,,,,,,,,,,,,
,毛利率,,,,,,60.59%,59.96%,,,,,,,,,,
按地區分-港澳台地區及海外
,收入,,,=5593464000,=5065675000,=1351477000,=1066101000,=375780000,=453938000,=156690000,,,,,,,,
,收入同比,,,313.88%,375.16%,259.65%,134.86%,139.82%,,,,,,,,,,
,成本,,,,,,=374027000,=139286000,,,,,,,,,,
,成本同比,,,,,,,,,,,,,,,,,
,毛利率,,,,,,64.92%,62.93%,,,,,,,,,,
按產品類型分-藝術家IP
,收入,=15289469000,,,,,,,,,,,,,,,,
,收入同比,,,,,,,,,,,,,,,,,
,成本,,,,,,,,,,,,,,,,,
,成本同比,,,,,,,,,,,,,,,,,
,毛利率,,,,,,,,,,,,,,,,,
按產品類型分-授權IP
,收入,=1771199000,,,,,,,,,,,,,,,,
,收入同比,,,,,,,,,,,,,,,,,
,成本,,,,,,,,,,,,,,,,,
,成本同比,,,,,,,,,,,,,,,,,
,毛利率,,,,,,,,,,,,,,,,,
按產品類型分-外採及其他
,收入,=112253000,,,,,,,,,,,,,,,,
,收入同比,,,,,,,,,,,,,,,,,
,成本,,,,,,,,,,,,,,,,,
,成本同比,,,,,,,,,,,,,,,,,
,毛利率,,,,,,,,,,,,,,,,,
按產品類型分-手辦
,收入,,=12022569000,=5175875000,=6936087000,=2656781000,,,,,,,,,,,,
,收入同比,,73.33%,94.82%,,,,,,,,,,,,,,
,成本,,,,,,,,,,,,,,,,,
,成本同比,,,,,,,,,,,,,,,,,
,毛利率,,,,,,,,,,,,,,,,,
按產品類型分-MEGA
,收入,,=1916082000,=1007147000,=1683780000,=586130000,,,,,,,,,,,,
,收入同比,,13.80%,71.83%,,,,,,,,,,,,,,
,成本,,,,,,,,,,,,,,,,,
,成本同比,,,,,,,,,,,,,,,,,
,毛利率,,,,,,,,,,,,,,,,,
按產品類型分-毛絨玩具
,收入,,=18708074000,=6139236000,=2832078000,=446105000,,,,,,,,,,,,
,收入同比,,560.58%,1276.19%,,,,,,,,,,,,,,
,成本,,,,,,,,,,,,,,,,,
,成本同比,,,,,,,,,,,,,,,,,
,毛利率,,,,,,,,,,,,,,,,,
按產品類型分-衍生品及其他
,收入,,=4473327000,=1554018000,=1585804000,=868815000,,,,,,,,,,,,
,收入同比,,182.09%,78.87%,,,,,,,,,,,,,,
,成本,,,,,,,,,,,,,,,,,
,成本同比,,,,,,,,,,,,,,,,,
,毛利率,,,,,,,,,,,,,,,,,
按產品類型分-泡泡瑪特品牌產品
,收入,,,,,,,,,,,,,=686915000,=1384209000,=395168000,=340149000,=45864000
,收入同比,,,,,,,,,,,,,73.83%,306.94%,,641.65%,
,成本,,,,,,,,,,,,,=198718000,=397963000,=113254000,=98519000,=16501000
,成本同比,,,,,,,,,,,,,75.46%,303.95%,,497.05%,
,毛利率,,,,,,,,,,,,,71.07%,71.25%,71.34%,71.04%,64.02%
按產品類型分-第三方產品
,收入,,,,,,,,,,,,,=130876000,=279986000,=138743000,=162277000,=110262000
,收入同比,,,,,,,,,,,,,-5.67%,72.54%,,47.17%,
,成本,,,,,,,,,,,,,=85634000,=176230000,=89623000,=105699000,=63959000
,成本同比,,,,,,,,,,,,,-4.45%,66.73%,,65.26%,
,毛利率,,,,,,,,,,,,,34.57%,37.06%,35.40%,34.87%,41.99%
按產品類型分-其他
,收入,,,,,,,,,,,,,,=19239000,=9485000,=12085000,=1948000
,收入同比,,,,,,,,,,,,,,59.20%,,520.38%,
,成本,,,,,,,,,,,,,,=18907000,=9927000,=12268000,=2360000
,成本同比,,,,,,,,,,,,,,54.12%,,419.83%,
,毛利率,,,,,,,,,,,,,,1.73%,-4.66%,-1.51%,-21.15%
合計
,收入,=17172921000,=37120052000,=13876276000,=13037749000,=4557831000,=6301002000,=2813812000,=4617324000,=2358818000,=4490651000,=1772577000,=2513471000,=817791000,=1683434000,=543396000,=514511000,=158074000
,收入同比,23.76%,184.71%,204.45%,106.92%,61.98%,36.46%,19.29%,2.82%,33.07%,78.66%,116.75%,49.31%,50.50%,227.19%,,225.49%,
,成本,,,,,,=2436931000,=1115452000,,,,,,=284352000,=593100000,=212804000,=216486000,=82820000
,成本同比,,,,,,,,,,,,,33.62%,173.97%,,161.39%,
,毛利率,,,,,,61.32%,60.36%,,,,,,65.23%,64.77%,60.84%,57.92%,47.61%
数据来源于:理杏仁网站(lixinger.com)
1 营收构成表( 元),日期,2026-Q2,2025-Q4,2025-Q2,2024-Q4,2024-Q2,2023-Q4,2023-Q2,2022-Q4,2022-Q2,2021-Q4,2021-Q2,2020-Q4,2020-Q2,2019-Q4,2019-Q2,2018-Q4,2017-Q4
2 按服務類型分-零售店銷售收益
3 ,收入,=9111987000,=17254326000,=6908010000,=6765771000,=2364781000,=3062152000,=1344492000,=1826671000,=926495000,=1673386000,=675286000,=1002054000,=313296000,=739690000,=232954000,=248257000,=101005000
4 ,收入同比,31.90%,155.02%,192.12%,120.95%,75.89%,67.64%,45.12%,9.16%,37.20%,67.00%,115.54%,35.47%,34.49%,197.95%,,145.79%,
5 ,成本,,,,,,,,,,,,,=121908000,=254176000,=99637000,=96233000,=45501000
6 ,成本同比,,,,,,,,,,,,,22.35%,164.13%,,111.50%,
7 ,毛利率,,,,,,,,,,,,,61.09%,65.64%,57.23%,61.24%,54.95%
8 按服務類型分-線上銷售收益
9 ,收入,=6212071000,=16433829000,=5497997000,=4152781000,=1342913000,=1866274000,=874209000,=1920033000,=1012762000,=1879189000,=678281000,=951875000,=334303000,=539201000,=163545000,=102886000,=14854000
10 ,收入同比,12.99%,295.73%,309.41%,122.52%,53.61%,-2.80%,-13.68%,2.17%,49.31%,97.42%,102.89%,76.53%,104.41%,424.08%,,592.65%,
11 ,成本,,,,,,,,,,,,,=98798000,=167482000,=47064000,=38247000,=6528000
12 ,成本同比,,,,,,,,,,,,,109.92%,337.90%,,485.89%,
13 ,毛利率,,,,,,,,,,,,,70.45%,68.94%,71.22%,62.83%,56.05%
14 按服務類型分-機器人商店銷售收益
15 ,收入,=950525000,=1689446000,=820741000,=831477000,=368157000,=605950000,=296434000,=394039000,=207820000,=469803000,=227561000,=328679000,=105496000,=248554000,=82473000,=86431000,=5568000
16 ,收入同比,15.81%,103.19%,122.93%,37.22%,24.20%,53.78%,42.64%,-16.13%,-8.68%,42.94%,115.71%,32.24%,27.92%,187.58%,,1452.28%,
17 ,成本,,,,,,,,,,,,,=28592000,=72776000,=23323000,=24674000,=2051000
18 ,成本同比,,,,,,,,,,,,,22.59%,194.95%,,1103.02%,
19 ,毛利率,,,,,,,,,,,,,72.90%,70.72%,71.72%,71.45%,63.16%
20 按服務類型分-批發收益
21 ,收入,=898338000,=1742451000,=649528000,=1287720000,=481980000,=766626000,=298677000,=476581000,=211741000,=444817000,=191449000,=207386000,=63500000,=110467000,=39887000,=51329000,=29884000
22 ,收入同比,38.31%,35.31%,34.76%,67.97%,61.37%,60.86%,41.06%,7.14%,10.60%,114.49%,201.49%,87.74%,59.20%,115.21%,,71.76%,
23 ,成本,,,,,,,,,,,,,=34004000,=69908000,=26567000,=38209000,=23996000
24 ,成本同比,,,,,,,,,,,,,27.99%,82.96%,,59.23%,
25 ,毛利率,,,,,,,,,,,,,46.45%,36.72%,33.39%,25.56%,19.70%
26 按服務類型分-展會收益
27 ,收入,,,,,,,,,,=23456000,,=23477000,=1196000,=45522000,=24537000,=25608000,=6763000
28 ,收入同比,,,,,,,,,,-0.09%,,-48.43%,-95.13%,77.76%,,278.65%,
29 ,成本,,,,,,,,,,,,,=1050000,=28758000,=16213000,=19123000,=4744000
30 ,成本同比,,,,,,,,,,,,,-93.52%,50.38%,,303.10%,
31 ,毛利率,,,,,,,,,,,,,12.21%,36.83%,33.92%,25.32%,29.85%
32 按地區分-中國
33 ,收入,=12200509000,=20851717000,,,,,,,,,,,=797119000,=1656544000,=536854000,=510149000,=157414000
34 ,收入同比,,,,,,,,,,,,,48.48%,224.72%,,224.08%,
35 ,成本,,,,,,,,,,,,,=273637000,=578280000,=209837000,=214495000,=82536000
36 ,成本同比,,,,,,,,,,,,,30.40%,169.60%,,159.88%,
37 ,毛利率,,,,,,,,,,,,,65.67%,65.09%,60.91%,57.95%,47.57%
38 按地區分-亞太
39 ,收入,=2575045000,=8011027000,,,,,,,,,,,,,,,
40 ,收入同比,,,,,,,,,,,,,,,,,
41 ,成本,,,,,,,,,,,,,,,,,
42 ,成本同比,,,,,,,,,,,,,,,,,
43 ,毛利率,,,,,,,,,,,,,,,,,
44 按地區分-美洲
45 ,收入,=1891637000,=6806189000,,,,,,,,,,,,,,,
46 ,收入同比,,,,,,,,,,,,,,,,,
47 ,成本,,,,,,,,,,,,,,,,,
48 ,成本同比,,,,,,,,,,,,,,,,,
49 ,毛利率,,,,,,,,,,,,,,,,,
50 按地區分-海外
51 ,收入,=505730000,,,,,,,,,,,,=20672000,=26890000,=6542000,=4362000,=660000
52 ,收入同比,,,,,,,,,,,,,215.99%,516.46%,,560.91%,
53 ,成本,,,,,,,,,,,,,=10715000,=14820000,=2967000,=1991000,=284000
54 ,成本同比,,,,,,,,,,,,,261.14%,644.35%,,601.06%,
55 ,毛利率,,,,,,,,,,,,,48.17%,44.89%,54.65%,54.36%,56.97%
56 按地區分-歐洲及其他地區
57 ,收入,,=1451119000,,,,,,,,,,,,,,,
58 ,收入同比,,,,,,,,,,,,,,,,,
59 ,成本,,,,,,,,,,,,,,,,,
60 ,成本同比,,,,,,,,,,,,,,,,,
61 ,毛利率,,,,,,,,,,,,,,,,,
62 按地區分-中國大陸
63 ,收入,,,=8282812000,=7972074000,=3206354000,=5234901000,=2438032000,=4163386000,=2202128000,,,,,,,,
64 ,收入同比,,,158.32%,52.29%,31.51%,25.74%,10.71%,,,,,,,,,,
65 ,成本,,,,,,=2062904000,=976166000,,,,,,,,,,
66 ,成本同比,,,,,,,,,,,,,,,,,
67 ,毛利率,,,,,,60.59%,59.96%,,,,,,,,,,
68 按地區分-港澳台地區及海外
69 ,收入,,,=5593464000,=5065675000,=1351477000,=1066101000,=375780000,=453938000,=156690000,,,,,,,,
70 ,收入同比,,,313.88%,375.16%,259.65%,134.86%,139.82%,,,,,,,,,,
71 ,成本,,,,,,=374027000,=139286000,,,,,,,,,,
72 ,成本同比,,,,,,,,,,,,,,,,,
73 ,毛利率,,,,,,64.92%,62.93%,,,,,,,,,,
74 按產品類型分-藝術家IP
75 ,收入,=15289469000,,,,,,,,,,,,,,,,
76 ,收入同比,,,,,,,,,,,,,,,,,
77 ,成本,,,,,,,,,,,,,,,,,
78 ,成本同比,,,,,,,,,,,,,,,,,
79 ,毛利率,,,,,,,,,,,,,,,,,
80 按產品類型分-授權IP
81 ,收入,=1771199000,,,,,,,,,,,,,,,,
82 ,收入同比,,,,,,,,,,,,,,,,,
83 ,成本,,,,,,,,,,,,,,,,,
84 ,成本同比,,,,,,,,,,,,,,,,,
85 ,毛利率,,,,,,,,,,,,,,,,,
86 按產品類型分-外採及其他
87 ,收入,=112253000,,,,,,,,,,,,,,,,
88 ,收入同比,,,,,,,,,,,,,,,,,
89 ,成本,,,,,,,,,,,,,,,,,
90 ,成本同比,,,,,,,,,,,,,,,,,
91 ,毛利率,,,,,,,,,,,,,,,,,
92 按產品類型分-手辦
93 ,收入,,=12022569000,=5175875000,=6936087000,=2656781000,,,,,,,,,,,,
94 ,收入同比,,73.33%,94.82%,,,,,,,,,,,,,,
95 ,成本,,,,,,,,,,,,,,,,,
96 ,成本同比,,,,,,,,,,,,,,,,,
97 ,毛利率,,,,,,,,,,,,,,,,,
98 按產品類型分-MEGA
99 ,收入,,=1916082000,=1007147000,=1683780000,=586130000,,,,,,,,,,,,
100 ,收入同比,,13.80%,71.83%,,,,,,,,,,,,,,
101 ,成本,,,,,,,,,,,,,,,,,
102 ,成本同比,,,,,,,,,,,,,,,,,
103 ,毛利率,,,,,,,,,,,,,,,,,
104 按產品類型分-毛絨玩具
105 ,收入,,=18708074000,=6139236000,=2832078000,=446105000,,,,,,,,,,,,
106 ,收入同比,,560.58%,1276.19%,,,,,,,,,,,,,,
107 ,成本,,,,,,,,,,,,,,,,,
108 ,成本同比,,,,,,,,,,,,,,,,,
109 ,毛利率,,,,,,,,,,,,,,,,,
110 按產品類型分-衍生品及其他
111 ,收入,,=4473327000,=1554018000,=1585804000,=868815000,,,,,,,,,,,,
112 ,收入同比,,182.09%,78.87%,,,,,,,,,,,,,,
113 ,成本,,,,,,,,,,,,,,,,,
114 ,成本同比,,,,,,,,,,,,,,,,,
115 ,毛利率,,,,,,,,,,,,,,,,,
116 按產品類型分-泡泡瑪特品牌產品
117 ,收入,,,,,,,,,,,,,=686915000,=1384209000,=395168000,=340149000,=45864000
118 ,收入同比,,,,,,,,,,,,,73.83%,306.94%,,641.65%,
119 ,成本,,,,,,,,,,,,,=198718000,=397963000,=113254000,=98519000,=16501000
120 ,成本同比,,,,,,,,,,,,,75.46%,303.95%,,497.05%,
121 ,毛利率,,,,,,,,,,,,,71.07%,71.25%,71.34%,71.04%,64.02%
122 按產品類型分-第三方產品
123 ,收入,,,,,,,,,,,,,=130876000,=279986000,=138743000,=162277000,=110262000
124 ,收入同比,,,,,,,,,,,,,-5.67%,72.54%,,47.17%,
125 ,成本,,,,,,,,,,,,,=85634000,=176230000,=89623000,=105699000,=63959000
126 ,成本同比,,,,,,,,,,,,,-4.45%,66.73%,,65.26%,
127 ,毛利率,,,,,,,,,,,,,34.57%,37.06%,35.40%,34.87%,41.99%
128 按產品類型分-其他
129 ,收入,,,,,,,,,,,,,,=19239000,=9485000,=12085000,=1948000
130 ,收入同比,,,,,,,,,,,,,,59.20%,,520.38%,
131 ,成本,,,,,,,,,,,,,,=18907000,=9927000,=12268000,=2360000
132 ,成本同比,,,,,,,,,,,,,,54.12%,,419.83%,
133 ,毛利率,,,,,,,,,,,,,,1.73%,-4.66%,-1.51%,-21.15%
134 合計
135 ,收入,=17172921000,=37120052000,=13876276000,=13037749000,=4557831000,=6301002000,=2813812000,=4617324000,=2358818000,=4490651000,=1772577000,=2513471000,=817791000,=1683434000,=543396000,=514511000,=158074000
136 ,收入同比,23.76%,184.71%,204.45%,106.92%,61.98%,36.46%,19.29%,2.82%,33.07%,78.66%,116.75%,49.31%,50.50%,227.19%,,225.49%,
137 ,成本,,,,,,=2436931000,=1115452000,,,,,,=284352000,=593100000,=212804000,=216486000,=82820000
138 ,成本同比,,,,,,,,,,,,,33.62%,173.97%,,161.39%,
139 ,毛利率,,,,,,61.32%,60.36%,,,,,,65.23%,64.77%,60.84%,57.92%,47.61%
140 数据来源于:理杏仁网站(lixinger.com)
@@ -0,0 +1,57 @@
财报类型,股票代码,股票名称,中报,年报,中报,年报,中报,年报,中报,年报,中报,年报,中报,年报,中报,年报,中报,年报,年报
日期,="09992",="泡泡玛特",2026-06-30,2025-12-31,2025-06-30,2024-12-31,2024-06-30,2023-12-31,2023-06-30,2022-12-31,2022-06-30,2021-12-31,2021-06-30,2020-12-31,2020-06-30,2019-12-31,2019-06-30,2018-12-31,2017-12-31
货币,="09992",="泡泡玛特",元,元,元,元,元,元,元,元,元,元,元,元,元,元,元,元,元
审计意见,="09992",="泡泡玛特",,,,,,,,,,,,,,,,,
会计师事务所,="09992",="泡泡玛特",,,,,,,,,,,,,,,,,
人均指标,,,,,,,,,,,,,,,,,,,
员工人数,="09992",="泡泡玛特",,=10434,,=6273,,=4900,,=4178,,=4053,,=2320,,,,,
人均营业总收入,="09992",="泡泡玛特",,=4443652.6007,,=2333795.5786,,=1388191.6722,,=1121935.1233,,=1409273.8114,,,,,,,
人均净利润,="09992",="泡泡玛特",,=1557675.4654,,=592203.5264,,=239870.2357,,=115611.9548,,=268183.5870,,,,,,,
每股指标,,,,,,,,,,,,,,,,,,,
总股数,="09992",="泡泡玛特",=1331723150,=1342943150,=1342943150,=1342943150,=1342943150,=1348243150,=1356323150,=1380540550,=1397572150,=1401937550,=1401937550,=1401937550,=0,=0,=0,=0,=0
归属于母公司普通股股东的每股收益,="09992",="泡泡玛特",=3.7834,=9.5132,=3.4062,=2.3273,=0.6861,=0.8028,=0.3514,=0.3445,=0.2381,=0.6094,=0.2559,=0.3734,,,,,
归属于母公司普通股股东的每股股东权益,="09992",="泡泡玛特",=17.3152,=16.5887,=10.5619,=7.9553,=6.2542,=5.7630,=5.3847,=5.0439,=5.0889,=4.8634,=4.5031,=4.3721,,,,,
每股经营活动产生的现金流量净额,="09992",="泡泡玛特",,=8.0906,=4.4510,=3.6891,=1.4187,=1.4764,=0.8054,=0.6454,=0.2673,=0.5557,=0.3150,=0.5017,,,,,
每股资本公积,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,,,,,
每股未分配利润,="09992",="泡泡玛特",=18.1642,=14.2626,=8.1681,=4.7672,=3.1566,=2.4703,=2.0245,=1.6438,=1.5216,=1.2794,=0.9259,=0.6700,,,,,
每股分红,="09992",="泡泡玛特",,=2.3783,,=0.8146,,=0.2810,,=0.0870,,=0.1524,,=0.1494,,,,,
盈利能力,,,,,,,,,,,,,,,,,,,
归属于少数股股东的ROE,="09992",="泡泡玛特",=0.2050,=0.8210,=0.4806,=1.7286,=1.3362,=1.0290,=0.2897,=0.0730,=0.0648,=0.1321,=0.0345,=-0.2371,=-0.0810,,,,
归属于母公司普通股股东的ROE,="09992",="泡泡玛特",=0.2223,=0.7752,=0.3679,=0.3387,=0.1140,=0.1469,=0.0668,=0.0690,=0.0478,=0.1320,=0.0577,=0.1558,=0.1543,=1.1117,,=0.6687,
净资产收益率(ROE),="09992",="泡泡玛特",=0.2220,=0.7760,=0.3699,=0.3545,=0.1188,=0.1477,=0.0669,=0.0690,=0.0478,=0.1320,=0.0577,=0.1557,=0.1540,=1.1117,,=0.6687,
杠杆倍数,="09992",="泡泡玛特",=1.3323,=1.4171,=1.4801,=1.3662,=1.3034,=1.2813,=1.2408,=1.2318,=1.2074,=1.2205,=1.2100,=1.1371,=1.4408,=1.7991,,=1.8407,=1.8050
总资产收益率(ROA),="09992",="泡泡玛特",=0.1616,=0.5540,=0.2585,=0.2664,=0.0919,=0.1174,=0.0541,=0.0563,=0.0394,=0.1117,=0.0491,=0.1302,=0.0990,=0.6141,,=0.3652,
总资产周转率,="09992",="泡泡玛特",=0.5441,=1.5805,=0.7661,=1.0498,=0.4344,=0.6794,=0.3189,=0.5463,=0.2789,=0.5872,=0.2426,=0.6254,=0.5728,=2.2915,,=1.8879,
净利润率,="09992",="泡泡玛特",=0.2970,=0.3505,=0.3374,=0.2538,=0.2115,=0.1728,=0.1696,=0.1030,=0.1411,=0.1903,=0.2024,=0.2082,=0.1728,=0.2680,=0.2091,=0.1934,=0.0099
毛利率(GM),="09992",="泡泡玛特",=0.6968,=0.7210,=0.7034,=0.6679,=0.6405,=0.6132,=0.6036,=0.5749,=0.5810,=0.6143,=0.6302,=0.6342,=0.6523,=0.6477,=0.6084,=0.5792,=0.4761
营运能力(周转率),,,,,,,,,,,,,,,,,,,
存货周转率,="09992",="泡泡玛特",=0.8998,=2.9597,=2.1669,=3.5649,=1.7994,=2.7510,=1.3722,=2.3708,=1.1320,=3.4156,=2.4219,=5.7162,=1.7752,=9.4621,=14.6453,=9.7077,
应收票据和应收账款周转率,="09992",="泡泡玛特",=22.1794,=53.0680,=19.1489,=32.6327,=15.5808,=24.4364,=13.9546,=25.2518,=15.5300,=35.9730,=16.5875,=40.5497,=18.7976,=56.1791,=76.0260,=52.0128,
应收票据周转率,="09992",="泡泡玛特",,,,,,,,,,,,,,,,,
应收账款周转率,="09992",="泡泡玛特",=22.1794,=53.0680,=19.1489,=32.6327,=15.5808,=24.4364,=13.9546,=25.2518,=15.5300,=35.9730,=16.5875,=40.5497,=18.7976,=56.1791,=76.0260,=52.0128,
应付票据和应付账款周转率,="09992",="泡泡玛特",=4.2237,=9.9734,=3.6882,=6.8036,=3.3011,=7.0308,=3.3107,=7.7735,=4.0275,=12.0213,=4.4052,=12.6921,=6.5104,=16.7893,=12.5610,=12.9163,
应付票据周转率,="09992",="泡泡玛特",,,,,,,,,,,,,,,,,
应付账款周转率,="09992",="泡泡玛特",=4.2237,=9.9734,=3.6882,=6.8036,=3.3011,=7.0308,=3.3107,=7.7735,=4.0275,=12.0213,=4.4052,=12.6921,=6.5104,=16.7893,=12.5610,=12.9163,
不动产、厂房及设备周转率,="09992",="泡泡玛特",=10.7743,=34.4193,=16.5100,=18.7236,=7.1196,=11.4339,=5.9319,=11.3286,=6.1247,=14.8548,=6.9712,=14.7036,=6.8497,=24.1469,,=21.4514,
营运能力(周转天数),,,,,,,,,,,,,,,,,,,
存货周转天数,="09992",="泡泡玛特",=200.0494,=121.6329,=83.0672,=100.9845,=100.0323,=130.8633,=131.1740,=151.8491,=159.0077,=105.4000,=74.3233,=62.9792,=101.3943,=38.0464,=12.2906,=37.0841,
应收票据和应收账款周转天数,="09992",="泡泡玛特",=8.1156,=6.7838,=9.4000,=11.0319,=11.5527,=14.7321,=12.8989,=14.2564,=11.5905,=10.0075,=10.8515,=8.8780,=9.5757,=6.4081,=2.3676,=6.9214,
应收票据周转天数,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,
应收账款周转天数,="09992",="泡泡玛特",=8.1156,=6.7838,=9.4000,=11.0319,=11.5527,=14.7321,=12.8989,=14.2564,=11.5905,=10.0075,=10.8515,=8.8780,=9.5757,=6.4081,=2.3676,=6.9214,
合同资产周转天数,="09992",="泡泡玛特",=0,=0,=0,=0,=0,,,,,,,,,,,,
应付票据和应付账款周转天数,="09992",="泡泡玛特",=42.6162,=36.0961,=48.8036,=52.9132,=54.5266,=51.2035,=54.3698,=46.3114,=44.6930,=29.9467,=40.8609,=28.3641,=27.6480,=21.4422,=14.3300,=27.8718,
应付票据周转天数,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,
应付账款周转天数,="09992",="泡泡玛特",=42.6162,=36.0961,=48.8036,=52.9132,=54.5266,=51.2035,=54.3698,=46.3114,=44.6930,=29.9467,=40.8609,=28.3641,=27.6480,=21.4422,=14.3300,=27.8718,
不动产、厂房及设备周转天数,="09992",="泡泡玛特",=16.7065,=10.4593,=10.9025,=19.2271,=25.2823,=31.4853,=30.3442,=31.7781,=29.3893,=24.2346,=25.8207,=24.4837,=26.2785,=14.9088,,=16.7821,
流动资产周转天数,="09992",="泡泡玛特",=246.3546,=180.1487,=195.1388,=275.0106,=323.8147,=408.4780,=438.0225,=521.3211,=518.8750,=521.6112,=657.0020,=499.3295,=219.4274,=103.8874,,=112.2301,
股东权益周转天数,="09992",="泡泡玛特",=240.7672,=162.6253,=164.1852,=257.6905,=320.5414,=421.2389,=456.4688,=537.4021,=531.6548,=519.1188,=631.9111,=481.5178,=201.8980,=86.7809,,=104.1333,
总资产周转天数,="09992",="泡泡玛特",=330.8496,=227.7735,=234.9666,=342.9362,=414.4012,=529.8850,=564.3895,=658.9669,=645.3296,=613.0707,=741.9173,=575.6185,=314.2722,=157.1013,,=190.6924,
偿债及资本结构,,,,,,,,,,,,,,,,,,,
货币资金占流动负债比率,="09992",="泡泡玛特",=2.9881,=2.4030,=2.3240,=2.8546,=3.3892,=3.4404,=4.1165,=4.4586,=5.0312,=4.8180,=5.2806,=8.5377,=1.9986,=0.8507,,=0.6682,=0.3088
资产负债率,="09992",="泡泡玛特",=0.2494,=0.2943,=0.3244,=0.2680,=0.2328,=0.2195,=0.1941,=0.1882,=0.1718,=0.1806,=0.1736,=0.1205,=0.3059,=0.4442,,=0.4567,=0.4460
流动比率,="09992",="泡泡玛特",=4.7281,=3.4757,=3.0137,=3.6309,=4.2137,=4.4329,=5.2605,=5.8506,=6.6678,=6.1840,=6.1199,=9.4029,=3.1067,=1.8780,,=1.7601,=1.4948
现金流量,,,,,,,,,,,,,,,,,,,
经投融产生的现金流量净额,="09992",="泡泡玛特",,=8108904000,=5806612000,=4004674000,=1522888000,=1382979000,=616084000,=-4984823000,=-4316885000,=-304732000,=-4177748000,=5356797000,=497688000,=229038000,=51668000,=83208000,=-10979000
经营活动产生的现金流量净额对净利润的比率,="09992",="泡泡玛特",,=0.8350,=1.2768,=1.4975,=1.9761,=1.8283,=2.2889,=1.8727,=1.1220,=0.9117,=1.2309,=1.3441,=0.8649,=1.1148,=1.2797,=1.7584,=9.8834
数据来源于:理杏仁网站(lixinger.com)
Can't render this file because it contains an unexpected character in line 2 and column 9.
@@ -0,0 +1,128 @@
财报类型,股票代码,股票名称,中报,年报,中报,年报,中报,年报,中报,年报,中报,年报,中报,年报,中报,年报,中报,年报,年报
日期,="09992",="泡泡玛特",2026-06-30,2025-12-31,2025-06-30,2024-12-31,2024-06-30,2023-12-31,2023-06-30,2022-12-31,2022-06-30,2021-12-31,2021-06-30,2020-12-31,2020-06-30,2019-12-31,2019-06-30,2018-12-31,2017-12-31
货币,="09992",="泡泡玛特",元,元,元,元,元,元,元,元,元,元,元,元,元,元,元,元,元
审计意见,="09992",="泡泡玛特",,,,,,,,,,,,,,,,,
会计师事务所,="09992",="泡泡玛特",,,,,,,,,,,,,,,,,
一、资产总计,="09992",="泡泡玛特",=31028136000,=32101354000,=21356679000,=14870672000,=11017479000,=9968863000,=9065337000,=8580062000,=8589874000,=8323626000,=7640984000,=6971299000,=1789174000,=1066481000,,=402795000,=142279000
流动资产合计,="09992",="泡泡玛特",=22092339000,=24914643000,=17850582000,=12236081000,=8715341000,=7683469000,=7079054000,=6615535000,=6841953000,=6757289000,=6683951000,=6255900000,=1277241000,=716601000,,=254997000,=65801000
流动资产占比,="09992",="泡泡玛特",=0.7120,=0.7761,=0.8358,=0.8228,=0.7910,=0.7707,=0.7809,=0.7710,=0.7965,=0.8118,=0.8748,=0.8974,=0.7139,=0.6719,,=0.6331,=0.4625
货币资金,="09992",="泡泡玛特",=13962252000,=17225009000,=13765711000,=9620160000,=7009949000,=5963289000,=5539604000,=5041534000,=5162623000,=5264710000,=5767288000,=5680235000,=821686000,=324614000,=0,=96802000,=13592000
货币资金占比,="09992",="泡泡玛特",=0.4500,=0.5366,=0.6446,=0.6469,=0.6363,=0.5982,=0.6111,=0.5876,=0.6010,=0.6325,=0.7548,=0.8148,=0.4593,=0.3044,,=0.2403,=0.0955
发放贷款及垫款(流动),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
受限制现金及已抵押存款,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
受限制货币资金,="09992",="泡泡玛特",=3384000,=2658000,=79994000,=25649000,=19954000,=18159000,=22011000,=13265000,=3494000,=3353000,=3230000,=3263000,=3548000,=0,=0,=0,=0
已抵押银行存款(流动),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
存货,="09992",="泡泡玛特",=6101530000,=5472839000,=2273691000,=1524521000,=916686000,=904708000,=758774000,=866985000,=957448000,=788829000,=315962000,=225369000,=224050000,=96302000,=0,=29061000,=15540000
应收票据及应收账款,="09992",="泡泡玛特",=627306000,=921240000,=971579000,=477723000,=263722000,=321337000,=208911000,=194369000,=132442000,=171334000,=135390000,=78334000,=41374000,=45636000,=0,=14295000,=5489000
(其中)应收票据,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
(其中)应收账款,="09992",="泡泡玛特",=627306000,=921240000,=971579000,=477723000,=263722000,=321337000,=208911000,=194369000,=132442000,=171334000,=135390000,=78334000,=41374000,=45636000,=0,=14295000,=5489000
应收款项融资,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,
可收回之税项,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
合同资产(流动),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
租赁应收款项(流动),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
持有待售资产(流动),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
衍生金融资产(流动),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
结构性银行存款,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,
短期投资,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
其他短期投资,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,
以摊销成本计量的金融投资(流动),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
以公允价值计量且其变动计入损益的金融投资(流动),="09992",="泡泡玛特",=9662000,=9743000,=11393000,=11434000,=7409000,=8415000,=10701000,=12829000,=13926000,=20544000,=39959000,=0,=0,=50000000,=0,=50303000,=0
以公允价值计量且其变动计入其他综合收益的金融投资(流动),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
预付款项、按金、其他应收款及其他资产(流动),="09992",="泡泡玛特",=1388205000,=1283154000,=748214000,=576594000,=497621000,=467561000,=539053000,=486553000,=572020000,=508519000,=422122000,=268699000,=186583000,=200049000,=0,=64536000,=31180000
非流动资产合计,="09992",="泡泡玛特",=8935797000,=7186711000,=3506097000,=2634591000,=2302138000,=2285394000,=1986283000,=1964527000,=1747921000,=1566337000,=957033000,=715399000,=511933000,=349880000,,=147798000,=76478000
非流动资产占比,="09992",="泡泡玛特",=0.2880,=0.2239,=0.1642,=0.1772,=0.2090,=0.2293,=0.2191,=0.2290,=0.2035,=0.1882,=0.1252,=0.1026,=0.2861,=0.3281,,=0.3669,=0.5375
定期存款,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
发放贷款及垫款(非流动),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
已抵押银行存款(非流动),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
物业、厂房及设备,="09992",="泡泡玛特",=1770206000,=1417556000,=941575000,=739378000,=627083000,=653278000,=499814000,=448884000,=403986000,=366281000,=270221000,=238325000,=135222000,=103559000,,=35874000,=12096000
不动产、厂房及设备占总资产比率,="09992",="泡泡玛特",=0.0571,=0.0442,=0.0441,=0.0497,=0.0569,=0.0655,=0.0551,=0.0523,=0.0470,=0.0440,=0.0354,=0.0342,=0.0756,=0.0971,,=0.0891,=0.0850
土地使用权,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
使用权资产,="09992",="泡泡玛特",=3602656000,=2791171000,=1366773000,=927558000,=789368000,=726053000,=630452000,=701627000,=640044000,=609517000,=380204000,=287799000,=209420000,=178938000,=0,=70816000,=35078000
生物资产,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
油气资产,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,
在建工程,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
投资物业,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
于联营及合资公司之权益,="09992",="泡泡玛特",=159359000,=128124000,=107024000,=136783000,=112928000,=107001000,=102225000,=83333000,=67969000,=61539000,=54205000,=50380000,=45979000,=22101000,=0,=11798000,=10839000
(其中)联营公司投资,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
(其中)合营公司投资,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
商誉及无形资产,="09992",="泡泡玛特",=236368000,=208665000,=207473000,=135400000,=123135000,=115888000,=122354000,=146507000,=119378000,=134032000,=90206000,=92731000,=85271000,=18620000,=0,=17641000,=2580000
(其中)商誉,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
(其中)无形资产,="09992",="泡泡玛特",=236368000,=208665000,=207473000,=135400000,=123135000,=115888000,=122354000,=146507000,=119378000,=134032000,=90206000,=92731000,=85271000,=18620000,=0,=17641000,=2580000
其他长期投资,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
递延所得税资产,="09992",="泡泡玛特",=1813681000,=1753551000,=333432000,=147029000,=96906000,=83416000,=89971000,=80977000,=30633000,=35553000,=25881000,=23087000,=22344000,=16219000,=0,=7766000,=15569000
合同资产(非流动),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
租赁应收款项(非流动),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
持有待售资产(非流动),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
衍生金融资产(非流动),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
以摊销成本计量的金融投资(非流动),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
以公允价值计量且其变动计入损益的金融投资(非流动),="09992",="泡泡玛特",=620762000,=356906000,=421934000,=411880000,=429972000,=471769000,=462037000,=459034000,=471235000,=328688000,=83974000,=16900000,=0,=0,=0,=0,=0
以公允价值计量且其变动计入其他综合收益的金融投资(非流动),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
长期应收款,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
预付款项、按金、其他应收款及其他资产(非流动),="09992",="泡泡玛特",=306974000,=274473000,=127886000,=136563000,=122746000,=127989000,=79430000,=44165000,=14676000,=30727000,=52342000,=6177000,=13697000,=10443000,=0,=3903000,=316000
二、负债合计,="09992",="泡泡玛特",=7739661000,=9448987000,=6927105000,=3986033000,=2564822000,=2188481000,=1759359000,=1614737000,=1475691000,=1503620000,=1326307000,=840297000,=547395000,=473700000,,=183965000,=63455000
资产负债率,="09992",="泡泡玛特",=0.2494,=0.2943,=0.3244,=0.2680,=0.2328,=0.2195,=0.1941,=0.1882,=0.1718,=0.1806,=0.1736,=0.1205,=0.3059,=0.4442,,=0.4567,=0.4460
流动负债合计,="09992",="泡泡玛特",=4672550000,=7168161000,=5923198000,=3370028000,=2068343000,=1733301000,=1345695000,=1130747000,=1026122000,=1092706000,=1092166000,=665313000,=411129000,=381570000,,=144874000,=44019000
流动负债占比,="09992",="泡泡玛特",=0.6037,=0.7586,=0.8551,=0.8455,=0.8064,=0.7920,=0.7649,=0.7003,=0.6954,=0.7267,=0.8235,=0.7918,=0.7511,=0.8055,,=0.7875,=0.6937
短期借款,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=15058000,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
有抵押银行借款(短期),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
短期应付债券,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
应交税费,="09992",="泡泡玛特",=622992000,=2115988000,=1034077000,=562141000,=196997000,=115123000,=99423000,=47069000,=65616000,=97169000,=73281000,=59667000,=35862000,=67184000,=0,=12445000,=297000
其他税项负债,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
应付票据及应付账款,="09992",="泡泡玛特",=905150000,=1858216000,=1627664000,=1010109000,=555137000,=444944000,=349477000,=259006000,=308470000,=266098000,=222935000,=115804000,=77191000,=49406000,=0,=29256000,=6359000
(其中)应付票据,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
(其中)应付账款,="09992",="泡泡玛特",=905150000,=1858216000,=1627664000,=1010109000,=555137000,=444944000,=349477000,=259006000,=308470000,=266098000,=222935000,=115804000,=77191000,=49406000,=0,=29256000,=6359000
一年内到期的递延收益,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
可转换债券(短期),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
合同负债(短期),="09992",="泡泡玛特",=513419000,=393119000,=855216000,=188577000,=151536000,=112143000,=92531000,=88797000,=93331000,=119624000,=156428000,=83941000,=58343000,=35167000,=0,=10039000,=695000
租赁负债(短期),="09992",="泡泡玛特",=665736000,=586274000,=414939000,=363092000,=356175000,=351799000,=292116000,=293567000,=256051000,=256909000,=181412000,=144724000,=111052000,=92586000,=0,=40011000,=19296000
衍生金融负债(短期),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
优先票据(短期),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,,,,,,,,,,,
以公允价值计量且其变动计入损益的金融负债(短期),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
预收款项、已收按金、应计费用、其他应付款及其他负债(短期),="09992",="泡泡玛特",=1451670000,=1777317000,=1445057000,=904274000,=535472000,=514841000,=351438000,=308791000,=196189000,=266902000,=399279000,=202297000,=105982000,=122050000,=0,=49746000,=16599000
非流动负债合计,="09992",="泡泡玛特",=3067111000,=2280826000,=1003907000,=616005000,=496479000,=455180000,=413664000,=483990000,=449569000,=410914000,=234141000,=174984000,=136266000,=92130000,=0,=39091000,=19436000
非流动负债占比,="09992",="泡泡玛特",=0.3963,=0.2414,=0.1449,=0.1545,=0.1936,=0.2080,=0.2351,=0.2997,=0.3046,=0.2733,=0.1765,=0.2082,=0.2489,=0.1945,,=0.2125,=0.3063
长期借款,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
有抵押银行借款(长期),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
递延所得税负债,="09992",="泡泡玛特",=0,=0,=0,=0,=16290000,=14419000,=15667000,=15120000,=0,=0,=0,=0,=0,=0,=0,=0,=0
长期递延收益,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
优先票据,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
应付债券,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
可赎回可换股优先股负债部分,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
可转换债券(长期),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
合同负债(长期),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
租赁负债(长期),="09992",="泡泡玛特",=665736000,=2275301000,=996260000,=601469000,=466001000,=425954000,=385774000,=447564000,=421409000,=364543000,=209447000,=147050000,=100134000,=90812000,=0,=35287000,=19436000
衍生金融负债(长期),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
以公允价值计量且其变动计入损益的金融负债(长期),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
预收款项、已收按金、应计费用、其他应付款及其他负债(长期),="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
三、所有者权益合计,="09992",="泡泡玛特",=23288475000,=22652367000,=14429574000,=10884639000,=8452657000,=7780382000,=7305978000,=6965325000,=7114183000,=6820006000,=6314677000,=6131002000,=1241779000,=592781000,,=218830000,=78824000
股东权益占比,="09992",="泡泡玛特",=0.7506,=0.7057,=0.6756,=0.7320,=0.7672,=0.7805,=0.8059,=0.8118,=0.8282,=0.8194,=0.8264,=0.8795,=0.6941,=0.5558,,=0.5433,=0.5540
股本,="09992",="泡泡玛特",=875000,=882000,=882000,=882000,=882000,=885000,=891000,=908000,=920000,=923000,=923000,=923000,=86000,=82000,=0,=0,=0
股本溢价,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
其他权益工具,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
(其中)优先股,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
(其中)永续债,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
资本公积,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
减:库存股,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
储备,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
专项储备,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
其他储备,="09992",="泡泡玛特",=-1131526000,=3123058000,=3213856000,=4280527000,=4159062000,=4438448000,=4556606000,=4693043000,=4984732000,=5023583000,=5014059000,=5189115000,=675439000,=169631000,=0,=126800000,=75889000
盈余公积,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
未分配利润,="09992",="泡泡玛特",=24189681000,=19153802000,=10969312000,=6402105000,=4239103000,=3330606000,=2745926000,=2269351000,=2126511000,=1793691000,=1298094000,=939352000,=564426000,=423068000,=0,=92030000,=2935000
股份奖励计划所持股份,="09992",="泡泡玛特",=-6000,=-7000,=-8000,=-9000,=-11000,=-12000,=-13000,=-14000,=-15000,=-15000,=-16000,=-16000,=0,=0,=0,=0,=0
可换股债券权益部分,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
归属于母公司股东及其他权益持有者的权益合计,="09992",="泡泡玛特",=23059024000,=22277735000,=14184042000,=10683505000,=8399036000,=7769927000,=7303410000,=6963288000,=7112148000,=6818182000,=6313060000,=6129374000,=1239951000,=592781000,,=218830000,=78824000
归属于母公司普通股股东权益合计,="09992",="泡泡玛特",=23059024000,=22277735000,=14184042000,=10683505000,=8399036000,=7769927000,=7303410000,=6963288000,=7112148000,=6818182000,=6313060000,=6129374000,=1239951000,=592781000,,=218830000,=78824000
少数股东权益,="09992",="泡泡玛特",=229451000,=374632000,=245532000,=201134000,=53621000,=10455000,=2568000,=2037000,=2035000,=1824000,=1617000,=1628000,=1828000,=0,=0,=0,=0
可赎回少数股东权益,="09992",="泡泡玛特",=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0,=0
员工情况,,,,,,,,,,,,,,,,,,,
员工人数,="09992",="泡泡玛特",,=10434,,=6273,,=4900,,=4178,,=4053,,=2320,,,,,
股本、股东以及估值,,,,,,,,,,,,,,,,,,,
总市值,="09992",="泡泡玛特",=206017571305,=252070429255,=358028643790.0001,=120394853397.5000,=51367575487.5000,=27301923787.5000,=23627149273.0000,=27362313701,=52967984485,=62666608485.0000,=107949191350,=114047619692.5000,=0,=0,=0,=0,=0
总股数,="09992",="泡泡玛特",=1331723150,=1342943150,=1342943150,=1342943150,=1342943150,=1348243150,=1356323150,=1380540550,=1397572150,=1401937550,=1401937550,=1401937550,=0,=0,=0,=0,=0
PE-TTM,="09992",="泡泡玛特",=14.4931,=34.3219,=107.7526,=72.2425,=43.1137,=41.4927,=44.3107,=27.2826,=50.7023,=69.8216,=173.8510,=215.0773,=0,=0,=0,=0,=0
PB,="09992",="泡泡玛特",=8.3114,=16.2222,=31.5232,=13.3455,=6.0057,=3.4626,=3.0269,=3.2855,=6.3532,=8.2528,=14.8485,=83.9364,=0,=0,=0,=0,=0
PS-TTM,="09992",="泡泡玛特",=4.9881,=10.4306,=25.8310,=13.6907,=7.4058,=5.0080,=4.5647,=4.4700,=9.6460,=14.8599,=36.2096,=52.7741,=0,=0,=0,=0,=0
PCF-TTM,="09992",="泡泡玛特",=17.0416,=25.8039,=67.9780,=39.3651,=23.4422,=15.6848,=23.6547,=31.8568,=55.5989,=50.8831,=129.3962,=214.7674,=0,=0,=0,=0,=0
股息率,="09992",="泡泡玛特",=0.0176,=0.0046,=0.0033,=0.0034,=0.0080,=0.0050,=0.0058,=0.0096,=0.0050,=0.0040,=0.0023,=0,,,,,
数据来源于:理杏仁网站(lixinger.com)
Can't render this file because it contains an unexpected character in line 2 and column 9.
+155 -311
View File
@@ -1,353 +1,197 @@
# 智能财报分析平台 · 方案设计 # 智能财报分析平台 · 方案设计
> 版本:v0.2(MVP 草案) > 版本:v0.3(P0 已落地)
> 状态:待评审,尚未动代码 >
> 范围:本仓库 `smart-fin-chart` 只负责「规范化 CSV → ECharts 展示」这一层。 > 状态:与当前代码同步
> 原始财报 CSV 由使用者自行提供(外部数据处理流程不在本仓库范围内)。 > 范围:本仓库负责「规范化 CSV → ECharts 图表展示」。原始财报的抽取、整理与复核在仓库外完成。
--- ---
## 1. 目标与愿景 ## 1. 当前目标
把当前「单公司、硬编码」的财报图表项目,升级成**多公司、可声明式扩展**的个人财报分析平台。第一版以“够用、易维护”为目标,不预建复杂的数据平台: 这是一个以“够用、易维护”为优先级的多公司财报图表工具,而不是一个预先建设完整数据平台的项目。
1. **多公司**:左栏是公司列表,选中后展示该公司的图表体系。 1. **多公司浏览**:左侧选择公司,中间按分析主题查看图表,右侧可跳转当前栏目的图表。
2. **通用图表 + 专属图表**: 2. **概览优先**:概览只展示最值得先看的跨主题图表;它是精选入口,不是新的数据或图表副本。
- 通用图表(资产负债堆积、营收/净利增速、费用率、人效、现金流…)用**模板**,任何公司都能套。 3. **确定性渲染**:CSV 相同,图表结果相同;AI 只协助将原始财报整理成 CSV,或一次性生成经 review 后固化的新增图表代码。
- 专属图表(泡泡玛特各 IP 收入、茅台基酒产能、分渠道销售…)**一图一文件**,逐步沟通搭建。 4. **低维护成本**:更新已有图表时只替换 CSV;新增专属图时才增加图表构建文件与注册项。
3. **AI 协作**:继续以 Claude Code 对话方式协作,AI 负责「把原始 CSV 整理成规范化 CSV」+「偶发生成新图代码」,不内嵌运行期聊天机器人。
4. **数据更新闭环**:图表体系搭好后,已有图表的数据更新只需替换 CSV;只有新增专属图才需要新增图表代码。 第一阶段不建设自动累计转单季、通用指标平台、完整财务勾稽或运行期聊天机器人。这些只在第二家公司接入后,确实出现重复劳动时再考虑。
--- ---
## 2. 总体判断(这个想法是否成立) ## 2. 当前架构
**结论:方向正确、可行,且单公司路径已经走通 60%。**
现有项目就是「单公司完整实现」:`theme.js` 统一视觉 + 一文件一图 + `csv.js` 解析 + ECharts 渲染 + 导出 PNG + SSR 回归测试,这套已经成熟。要做的不是推倒重来,而是把「单公司、硬编码」抽象成「多公司、可扩展」。
真正的风险不在图表,而在三件事:
1. **数据契约与口径治理** —— 跨多家公司后,单位、累计/单期、非 GAAP、衍生指标这些坑会被放大。
2. **AI 出数的可靠性** —— AI 生成的 CSV 必须可校验、可 diff、可回放,否则幻觉直接污染所有图表。
3. **通用模板的边界** —— 别把「通用」抽象过头,模板要能容忍字段缺失、要按公司可配置。
**一个关键认知**:AI 的职责是「把原始财报整理成图表可用的 CSV」,而不是「绘制图表」。图表由确定性代码渲染,AI 只在需要全新图型时一次性生成 chart 代码(人工 review 后固化)。这样数据更新 = 只改 CSV,图表结果可回放、可回归测试。
> MVP 不先建设通用指标库、自动累计转单季引擎或完整财务勾稽系统。先用清晰的 CSV 口径和脚注保证正确性;出现重复劳动、且第二家公司接入后,再升级数据层。
---
## 3. 架构设计
### 3.1 分层
``` ```
原始财报 CSV(用户提供) 原始财报 / PDF / XLSX(仓库外保存)
│ AI / 脚本:抽取 + 归一化 + 口径对齐 │ 人工、AI 或脚本:抽取、归一化、口径确认
▼ ▼
data/{company}/*.csv ← 图表使用的规范化数据(唯一运行时事实来源;是否入 git 按数据策略决定) data/{company}/*.csv ← 运行时数据
│ P0:读取失败直接提示;P3:再加列名/类型/单位/勾稽/期间校验
▼ ▼
渲染层(确定性,无 AI 参与) manifest.json ← 展示编排与图表配置的唯一事实来源
├─ manifest.json ← 公司 × tab × 图表 的注册表(唯一配置事实来源) ├─ chartCatalog ← 每张图只定义一次
├─ js/charts/index.js ← chart key → build 函数的映射 └─ tabs[].chartRefs ← 各栏目仅引用图表并决定顺序
├─ js/charts/ ← P0 保持现有图表文件;后续再按公司/模板整理
└─ js/main.js + js/ui.js ← 三栏壳:读 manifest,懒加载渲染
```
### 3.2 核心原则
1. **单一事实来源**:`manifest.json` 是「公司 / tab / 图表 / CSV / 高度 / 导出名」的唯一真相。左栏公司、顶部 tab、中间卡片、右侧锚点、回归测试**全部由它生成**,消除现在 `index.html` / `main.js CHARTS` / `regress.mjs all` 三处手工同步的维护债。
2. **先清晰、后自动化**:MVP 的 CSV 只要求列名和图表口径明确;缺列或读取失败时显示卡片错误。完整 schema、勾稽和数值校验留到后续有真实需要时再做。
3. **图表注册与实例分离**:每张卡片有全局唯一的 `id`(DOM、URL、ECharts 实例使用);`key` 只表示图表构建函数。相同 `key` 可以在不同 tab 或周期视图复用。
4. **模板渲染后置**:专属图延续一文件一图。确认跨公司重复出现的图型后,再抽取纯函数模板。
### 3.3 AI 管道边界(最重要的决策)
```
原始财报 CSV
│ AI:抽取 + 归一化 + 口径对齐
▼ ▼
data/{company}/*.csv ← AI 的输出,也是唯一与 AI 耦合的点 js/charts/index.js ← chart key → ECharts option 构建函数
│ 校验器 js/main.js + js/ui.js ← 解析引用、渲染三栏、导出和刷新
▼ ✅ 通过 → 图表自动渲染;❌ 不通过 → 拦下人工 review
图表渲染(确定性模板,无 AI 参与)
``` ```
- **AI 不直接生成 ECharts option**。option 由确定性的图表代码生成,保证「同一份 CSV → 同一张图」,可回放、可回归、可 diff。 ### 2.1 图表定义与页面编排分离
- **校验器是 P3 的命门**,把 AI 幻觉挡在数据层之外。届时至少校验:列名符合契约、数值列无 NaN、单位一致、勾稽关系(营收 = 分部加总、资产 = 负债 + 权益)、累计/单期口径与期数连续性。
- **PDF 比 CSV 难一个量级**。优先人工/半自动把 PDF 落成结构化 CSV 再进管道;AI 从 PDF 出的只能是「初稿 CSV」,必须过校验 + 人工确认,不指望零人工全自动。
- **第一版不内嵌运行期聊天机器人**。「对话式开发 → 落到文件和 CSV → git 可追溯」已经是最稳的 AI 形态。等模板 + 契约成熟后再考虑轻量「对话入口」只做「触发数据更新」。
--- 每家公司拥有一份 `chartCatalog`。一张图的 `key`、CSV、标题、高度与导出名只在这里维护一次;Tab 只使用 `chartRefs` 引用它。
## 4. 前端三栏布局与交互
```
┌──────────┬───────────────────────────────┬──────────┐
│ 公司列表 │ [概览][成长性][盈利能力][…] │ 图表锚点 │
│ 泡泡玛特 │ ┌────────────────────────┐ │ · 盈利能力 │
│ 贵州茅台 │ │ 图表卡片(标题+导出) │ │ · 利润流向 │
│ … │ └────────────────────────┘ │ … │
└──────────┴───────────────────────────────┴──────────┘
```
- **左栏**:公司列表,点击选中 → 渲染该公司第一个 tab。
- **中上**:tab 栏(财务类型),点击 → 只渲染该 tab 的图表卡片。
- **中主**:图表卡片堆叠(标题 + csv 路径 + 导出按钮 + canvas),纵向滚动。
- **右栏**:当前 tab 的图表锚点列表,点击 `scrollIntoView({behavior:'smooth'})` 滑动定位。
- **懒加载**:只渲染「当前公司 + 当前 tab」的图,切走 `dispose()`,避免多公司全量 canvas 拖垮性能。
- **状态**:URL hash `#/ppmt/growth` 保留公司 + tab(锚点可选 `#/ppmt/growth/ppmt-bridge`),刷新可还原。
- 保留:全局「重新加载数据」、每卡「导出 PNG」、resize 重绘。
### 4.1 时间维度切换(MVP)
部分图表(如“净利润与同比”)可在卡片标题区显示 `[年度] [半年] [季度]`。用户切换时,页面读取该视图对应的 CSV,并复用同一个 chart build 函数重新渲染。
- 只有 manifest 声明了多个 `views` 的图表才显示切换按钮;没有季度数据就不显示“季度”。
- 每个视图文件已包含图表需要的数值和同比,不在前端自动计算同比,也不从累计值推导单季/下半年。
- URL hash 可选记录视图,例如 `#/ppmt/profitability/ppmt-net-profit?view=quarter`;未声明或无效时回退到默认视图。
- 若未来多个公司反复需要同一套转换逻辑,再将这一层抽成通用数据处理;MVP 不预建。
---
## 5. 数据层:目录与契约
### 5.1 目录
```
data/
├── {company}/ # 每公司一个目录,company 名 = manifest 里的 id
│ ├── {chart}_annual.csv # 年度视图(仅在该图需要时提供)
│ ├── {chart}_halfyear.csv # 半年视图(仅在该图需要时提供)
│ └── {chart}_quarter.csv # 季度视图(仅在该图需要时提供)
```
MVP 继续使用“每图一份 CSV”;同一图有多个时间维度时,用多份 CSV 表达,**不在浏览器内自动把累计值拆成单季/下半年**。原始财报可由使用者在仓库外保存;是否将原始文件或规范化 CSV 纳入 git,按数据来源、授权和体积另行决定。
### 5.2 CSV 契约(口径约定)
> 这些是从现有项目踩坑经验里提炼的硬约束,跨公司时必须显式标注,不能靠人肉记忆。
| 项 | 约定 |
|---|---|
| 期间格式 | CSV 存四位年份。`2026FY` = 全年,`2026H1` / `2026H2` = 上/下半年单期,`2026Q1`…`2026Q4` = 单季;简写只在显示层做 |
| **一份文件一种口径** | 年度、半年、季度分别存文件;同一份 CSV 不混写累计和单期含义。若源数据为累计值,先在整理 CSV 时人工确认并转换 |
| 必需列 | 每个 CSV 至少有 `period` 和该图需要的指标列;含同比的图直接提供 `yoy` 列,不在第一版临时计算 |
| 单位 | 每个 CSV 在图表脚注注明单位(常用“亿元”);百分比、天数、人数等按自身量纲标注,禁止隐式混用 |
| 数值精度 | 默认保留一位小数;确需更高精度(如归一化占比)须在脚注说明 |
| 衍生指标 | 非 GAAP、核心利润率等「算出来的」行,必须标注计算公式与替代来源 |
### 5.3 校验(按需后置)
P0 只做 manifest 完整性检查:图表 `id` 唯一、`key` 在 registry 中存在、CSV 能读取。财务 schema(列名、类型、单位、累计口径、期数连续性)和勾稽校验留到 P3;届时再把 AI 出数错误挡在数据层之外。
---
## 6. 通用财务分析类型清单
按分析视角分 9 类,作为「通用骨架」:
1. **成长性**:营收及增速(同比/环比)、净利/归母净利及增速、毛利及增速、分部/业务线增速、量价拆分。
2. **盈利能力**:毛利率、净利率(含非 GAAP)、核心利润率、EBITDA margin、ROE / ROIC / ROA、杜邦分解、利润率同比桥。
3. **费用率**:销售/管理/研发/财务费用率、期间费用率合计、费用结构占比。
4. **营运能力**:存货周转天数/率、应收/应付周转天数、现金转化周期 CCC、总资产/固定资产周转率、存货结构与库龄。
5. **偿债与资本结构**:资产负债率、流动/速动比率、有息负债、净负债、负债期限结构、资产结构堆积。
6. **现金流**:经营/投资/筹资净现金流、自由现金流 FCF、净现比、收现比、现金流组合形态。
7. **人效**:人均营收、人均利润、人均薪酬/人力成本、人均创收/创利增速、人效 vs 薪酬剪刀差。
8. **结构/分部**:收入结构(产品/渠道/区域/客户/IP)、分部毛利率、收入占比变迁、分部利润贡献。
9. **估值/市场(可选)**:股息回购、股本变化、资本开支 vs 折旧、PE/PS(需股价,可后置)。
---
## 7. 财务 Tab 分类
**两级:通用 Tab(骨架,所有公司尽量覆盖)+ 专属 Tab(manifest 里按公司声明)。**
### 7.1 通用 Tab(推荐 9 个,顺序即 manifest 顺序)
| Tab | 内容 |
|---|---|
| 概览 | 落地页:核心指标卡片 + 最关键的几张图(营收净利、ROE/杜邦、现金流) |
| 成长性 | 营收/净利/毛利增速、分部增速 |
| 盈利能力 | 毛利率、净利率、核心利润率、ROE/杜邦、利润桥、利润流向桑基 |
| 费用率 | 销售/管理/研发/财务费用率 |
| 营运能力 | 存货/应收/应付周转、现金周期 |
| 现金流 | 三流、自由现金流、净现比 |
| 资产负债 | 资产结构堆积、资产负债率、流动/速动、负债结构 |
| 人效 | 人均营收/利润/薪酬 |
| 分部结构 | 收入结构、分部毛利率、占比变迁 |
### 7.2 专属 Tab(示例)
- 泡泡玛特:IP 收入 / 品类结构 / 区域渠道
- 贵州茅台:基酒产能 / 渠道(直销 vs 批发)/ 库存与投放
### 7.3 设计原则
1. Tab 由 manifest 声明,顺序 = manifest 顺序;通用 tab 用固定 id(`growth` / `profitability` / `expense` / …),专属 tab 自由命名。
2. 公司没数据 → 该通用 tab 隐藏或置灰,不硬凑。
3. 「概览」承担汇总,放跨 tab 的核心指标。
4. Tab 总数控制在 7~10 个,多了靠「概览 + 专属」消化。
---
## 8. 通用模板设计(后续,非 P0 前置条件)
- 模板 = 纯函数 `template(rows, meta, {width, height}) => option`,`meta` 携带 `{title, subtext, fields, unit, ...}`。
- **字段映射显式化**:manifest 里写「模板字段 ↔ CSV 列名」的映射,列名不写死在模板里。
- **容错**:字段缺失 = 跳过该图并提示,而不是报错。
- 触发条件:至少两家公司出现相同图型,并且字段映射与口径已稳定;在此之前宁可保留专属图,避免“为了通用而通用”。
- 第一批候选模板:现金流、费用率、人效、偿债(当前泡泡玛特缺的四块)。
### 8.1 视觉一致性(P0 保持,轻量治理)
现有 `theme.js`、`CLAUDE.md` 和 SSR 样式回归已经形成成熟视觉语言。新增公司或图表时,目标是复用它,而不是另建设计系统。
1. **主题组件是硬约束**:新图必须从 `theme.js` 使用 `lineSeries()`、`barSeries()`、`standardLegend()`、`periodAxis()`、`footnote()`、`watermarks()`、`LAYOUT`、`FONT` 与 `PALETTE`。图表文件不重复手写等价的字体、网格、图例位置、水印和基础折线/柱状样式。
2. **颜色分两类集中管理**:跨公司仍有相同财务含义的系列使用语义色,例如营收、毛利、净利润、现金流、费用各自固定颜色;IP、区域、产品等大量分类项使用统一分类色板。若确有专属业务色板,在 `theme.js` 以命名色板新增,不在单个图表中散落十六进制颜色值。
3. **职责边界明确**:manifest 只定义“图表在哪里展示、读取哪个 CSV”;`theme.js` 定义“所有图怎样长得一致”;图表文件只处理业务字段和该图独有的表达。
4. **加图最小检查清单**:使用主题组件 → 运行该图的单图 SSR 回归 → 在浏览器人工检查标签重叠、颜色可辨性和脚注 → 再登记进 manifest。无需在 P0 引入成本更高的截图像素基线。
语义色可在后续按需要以类似 `SERIES_COLORS.revenue`、`SERIES_COLORS.netProfit` 的命名映射集中补入 `theme.js`;P0 不要求为现有图表批量重构。
---
## 9. P0 实施计划(本次:展示层壳)
### 9.1 目标与验收
1. 用现有图表时,加一家公司 = 改 `manifest.json` + 放 CSV,**不碰 HTML**;新增专属图时才增加一个图表文件和 registry 项。
2. 三栏布局跑通:左公司列表、中「顶部 tab + 图表卡片」、右锚点导航。
3. 现有 19 张图(5 示例 + 14 泡泡玛特)原样迁移,视觉、导出 PNG、回归测试全部保持。
4. `npm test` 用相对路径 + 遍历 manifest,不再硬编码。
5. 用一张图验证年度 / 半年 / 季度 CSV 切换;不做自动累计拆分、指标平台和财务勾稽。
### 9.2 目标文件结构
```
smart-fin-chart/
├── index.html # 改成空壳:三栏骨架 + CDN echarts + <script type=module>
├── manifest.json # ★ 唯一事实来源:公司 × tab × 图表
├── data/ # 使用者提供的图表 CSV;是否入 git 按数据策略决定
│ ├── ppmt/
│ └── demo/
├── js/
│ ├── main.js # 编排:读 manifest → 渲染三栏 → 懒加载 → 导出/刷新/resize
│ ├── ui.js # DOM 构建:左栏 / tab / 卡片 / 右锚点(新增)
│ ├── csv.js # 不变
│ ├── theme.js # 不变
│ └── charts/
│ ├── index.js # ★ 注册表:chart key → build 函数
│ └── *.js # P0 保持现有平铺文件,避免只为整理目录批量改 import
├── test/regress.mjs # 改:相对路径 + 遍历 manifest
└── css/style.css # 增:三栏布局样式
```
### 9.3 manifest schema(JSON,纯数据,不引用函数)
```json ```json
{ {
"companies": [ "id": "ppmt",
{ "chartCatalog": {
"id": "ppmt", "name": "泡泡玛特", "ppmt-growth": {
"tabs": [ "key": "ppmt-growth",
{ "title": "营收到利润的增速对比",
"id": "overview", "name": "概览", "csv": "data/ppmt/ppmt_growth.csv",
"charts": [ "height": 700,
{ "filename": "泡泡玛特营收到利润增速对比"
"id": "ppmt-net-profit",
"key": "ppmt-profit",
"title": "净利润与同比",
"height": 680,
"filename": "泡泡玛特净利润",
"defaultView": "annual",
"views": {
"annual": "data/ppmt/net_profit_annual.csv",
"halfyear": "data/ppmt/net_profit_halfyear.csv",
"quarter": "data/ppmt/net_profit_quarter.csv"
} }
}, },
{ "id": "ppmt-sankey", "key": "ppmt-sankey", "title": "利润流向", "csv": "data/ppmt/ppmt_sankey_2026h1.csv", "height": 800, "filename": "泡泡玛特利润流向2026H1" } "tabs": [
] { "id": "overview", "name": "概览", "chartRefs": ["ppmt-growth"] },
} { "id": "profitability", "name": "盈利能力", "chartRefs": ["ppmt-growth"] }
]
}
] ]
} }
``` ```
`id` 是卡片的唯一标识,供 DOM、锚点、URL 和 ECharts 实例使用;`key` 只做 `chart key → build 函数` 映射(`'ppmt-profit': profitChart, …`)。`csv` 用于单视图图表,`views` 用于可切换周期的图表;两者二选一。JSON 不引用函数、JS 不描述布局,各管一半。 这保证概览与原分类使用**同一数据源、同一图表构建函数、同一份展示配置**。修改图表只需改 `chartCatalog` 一处。
### 9.4 现有图表迁移表(tab 映射) 页面在每次展示时生成 `公司 + Tab + 图表 ID` 的实例 ID,供 DOM 和 ECharts 使用。因此,同一图既能出现在概览,也能出现在原分类,不会发生画布或导出实例冲突。URL 中仍使用稳定的图表 ID,例如:`#/ppmt/profitability/ppmt-growth`。
| Tab | 图表(chart key) | ### 2.2 图表代码职责
|---|---|
| 概览 | ppmt-profit、ppmt-sankey |
| 成长性 | ppmt-growth |
| 盈利能力 | ppmt-core-profit、ppmt-core-margin、ppmt-bridge |
| 费用率 | ppmt-margins |
| 资产负债 | ppmt-assets |
| IP 与品类(专属) | ppmt-ip、ppmt-ip-share、ppmt-ip-share-history、ppmt-ip-ex-monsters、ppmt-category |
| 区域渠道(专属) | ppmt-region-yoy |
demo(示例)公司:`demo-inventory`(营运)、`demo-margin`/`demo-twoline`(盈利)、`demo-growth`(成长)、`demo-fx`(汇率)。 - `manifest.json`:图表定义在哪里展示、读哪个 CSV、卡片尺寸和导出名。
- `js/charts/index.js`:注册 `key → build 函数`。
### 9.5 regress.mjs 重写 - `js/charts/*.js`:只处理该图的业务字段与 ECharts 表达。
- `js/theme.js`:字体、配色、图例、坐标轴、脚注和水印等统一视觉规范。
- 去掉硬编码绝对路径 `/Users/joey/sites/vest-tools/echart-demo/`(已过期),改 `process.cwd()` / `import.meta.url` 相对定位。 - `data/{company}/`:图表需要的规范化 CSV。
- 固定 `all` 数组 → 读 manifest + 遍历 registry;有 `views` 的卡片逐个视图测试。
- 保留按 key 只测单图(`node test/regress.mjs ppmt-assets`)与 STYLE_RULES 断言(含已知的右侧纵向图例例外);新增图还须按 §8.1 做一次浏览器视觉检查。
### 9.6 .gitignore 变更
- 本次不强制变更。恢复数据时再决定:若 CSV 可公开且体积合适,按公司目录精确取消忽略并纳入 git;否则保留忽略,并提供不含敏感数据的测试 fixture。不要不加区分地把原始财报全部提交。
### 9.7 实施步骤(顺序,每步可验证)
1. 恢复 `data/` 目录(由使用者提供图表 CSV,本仓库不负责生成),安装依赖,先让现有图表回归可运行,建立迁移基线。
2. 新建 `manifest.json` + `js/charts/index.js`(注册表),保持现有 19 个图表文件路径不变。
3. 重写 `index.html`(空壳三栏)+ 补 `css/style.css` 布局 + 新增 `js/ui.js`。
4. 重写 `js/main.js` 为 manifest 驱动的懒加载渲染,支持单视图 `csv` 和可选 `views`。
5. 用净利润图接入年度 / 半年 / 季度三份 CSV,验证切换、导出、hash 恢复。
6. 重写 `regress.mjs`,跑对应图表回归,浏览器手测三栏交互 + 导出 PNG。
7. 最后再按数据来源决定 `.gitignore` 与数据提交策略;无关的文件整理和删除死文件独立处理,不混入 P0。
### 9.8 明确不变的部分
`theme.js`、`csv.js` 核心解析、全部 19 个 chart 构建函数、PNG 导出逻辑、水印/脚注/配色规范、`CLAUDE.md` 全部约定——**一概不动**。P0 不为目录美观迁移图表文件;新增 registry 不改变既有图表函数。
--- ---
## 10. 风险与注意事项 ## 3. 页面与交互
1. **周期口径必须在文件层隔离**:`FY`、`H1/H2`、`Q1-Q4` 分文件;不让一列或一个标签同时承担“累计”和“单期”两种意思。复杂的自动转换后置。 ```
2. **单位和衍生指标必须写进脚注**:第一版无需全库换算,但每张图要说明单位、非 GAAP 替代和计算口径。 ┌──────────┬────────────────────────────────────┬──────────┐
3. **不要过早抽模板**:一个公司的一张图先写专属实现;第二家公司复用且字段稳定,才抽取模板。 │ 公司列表 │ [概览][盈利能力][费用率][资产负债]… │ 当前图表 │
4. **性能与状态**:只渲染当前公司 + tab;切换时 dispose 并删除实例记录。刷新、切换和 resize 的异步请求不能让旧结果覆盖新视图。 │ 泡泡玛特 │ ┌────────────────────────────────┐ │ · 图表 A │
5. **三处手工同步合并成 manifest**,否则每加公司都要同步改 HTML / CHARTS / regress,迟早漏。 │ 示例 │ │ 图表卡片:标题、数据路径、导出 │ │ · 图表 B │
6. **硬编码路径清零**(`regress.mjs` 的绝对路径已过期),全相对路径;有多周期视图时,每个视图都要能被回归测试读取。 │ │ └────────────────────────────────┘ │ │
7. **数据纳入版本控制前先确认边界**:规范化 CSV 有利于 diff 和复现,但原始文件可能涉及授权、体积或敏感性,不能默认全部提交。 └──────────┴────────────────────────────────────┴──────────┘
8. **PDF 抽取是最大不确定性**:优先半自动落 CSV 再进管道;MVP 中人工确认优先于自动化。 ```
- 左栏切换公司;顶部 Tab 切换该公司的分析栏目;仅渲染当前栏目,切走后释放 ECharts 实例。
- 右栏显示当前栏目锚点,点击后平滑定位到相应图表。
- 右上角固定刷新按钮重新读取 manifest 与 CSV;导出按钮按当前卡片导出 PNG。
- 宽度 **≤1040px** 时隐藏右侧锚点;宽度 **≤880px** 时公司栏改为页面顶部横向菜单,图表卡片允许横向滚动以保持可读性。
- Vite 开发环境中,JS/CSS 修改热更新;`manifest.json` 或 CSV 修改会自动整页刷新。
--- ---
## 11. 后续路线 ## 4. 泡泡玛特当前栏目设计
| 阶段 | 内容 | ### 4.1 概览:精选入口
|---|---|
| P0 | 可用展示层:manifest + 三栏 + tab + 锚点 + 回归重写;以一张图验证年度/半年/季度 CSV 切换 | 概览用四张图快速回答“公司是否仍在增长、增长是否转化为利润、增长来自哪里、海外/渠道结构如何”。它们同时保留在原分析栏目中。
| P1 | 接入第二家公司,验证 manifest、目录和周期切换是否够用;只在重复处抽少量模板 |
| P2 | 通用模板:现金流/费用率/人效/偿债,先回填已有公司 | 1. **营收到利润的增速对比**:经营增长和利润兑现。
| P3 | AI 数据管道 + schema/勾稽校验:原始财报 → 规范化 CSV → 校验 → 渲染 | 2. **盈利能力**:主要利润率趋势。
3. **主要 IP 收入占比**:IP 集中度与核心 IP 贡献。
4. **区域渠道分布**:区域、渠道的收入结构。
### 4.2 详细栏目
| Tab | 当前内容 | 设计意图 |
|---|---|---|
| 概览 | 上述 4 张精选图 | 进入公司后的快速判断 |
| 盈利能力 | 营收到利润的增速对比、核心利润及同比、核心利润率、净利率同比拆解、利润流向、经营现金流与净利润(年度)、三大现金流与现金净增加(年度) | 将“增长是否兑现为利润”与利润质量放在同一叙事中 |
| 费用率 | 利润率与费用率拆解、费用结构与费用率(年度) | 从费率和金额两个角度拆解利润率变化 |
| 资产负债 | 资产结构演变、现金、负债与净营运资本、营运资本效率 | 观察资本与资产结构、流动性、资金占用和现金转换周期 |
| IP 与品类 | IP 收入趋势/占比、非 THE MONSTERS IP、品类结构 | 观察 IP 集中度与品类演变 |
| 区域渠道 | 区域渠道分布、同比增速、同比增速趋势、渠道收入结构与增速 | 观察全球化与渠道驱动 |
**成长性不是泡泡玛特的独立 Tab。** 目前它只有“营收到利润的增速对比”一张图,且目标是解释利润增长,因此归入“盈利能力”。未来若有多张独立增长主题图(如收入、门店、海外、分部增速),再恢复单独的“成长性”Tab。
“利润流向”是单期的利润结构细节,放在“盈利能力”而非概览。
--- ---
## 12. 待决策项 ## 5. 数据与时间维度
1. **示例图表处理**:inventory/margin/twoline/growth/fx 收进「示例」公司(默认),或删掉只留泡泡玛特。 ### 5.1 CSV 约定
2. **泡泡玛特 tab 映射**:见 9.4,是否认可,不认可直接改归类。
3. **周期试点图**:默认以“净利润与同比”验证年度 / 半年 / 季度切换;每个视图由使用者提供对应 CSV。 - 每图一份 CSV;同一图的年度、半年、季度视图使用各自 CSV。
4. **`data/` 恢复来源与提交策略**:当前 checkout 里 `data/` 为空(gitignored),19 个既有图表 CSV 及周期试点 CSV 需由使用者提供;确认哪些可入 git。 - 一份 CSV 只表达一种期间口径,禁止混合累计和单期含义。
5. **是否开工 P0**。 - CSV 保留四位年份;显示层可简写。`H2` 的含义必须以数据口径为准:若为下半年单期,不得显示成全年累计。
- 同比、环比、核心利润等需要计算的指标,应在 CSV 整理阶段明确生成;前端不自动从累计值推导单季或下半年。
- CSV 至少包含 `period` 和该图需要的指标列;单位、口径与重要计算方式通过图表脚注说明。
### 5.2 可选周期切换
需要切换年度、半年、季度的图,可在 `chartCatalog` 中使用 `views` 替代单个 `csv`:
```json
{
"key": "some-chart",
"title": "净利润与同比",
"defaultView": "annual",
"views": {
"annual": "data/company/net_profit_annual.csv",
"halfyear": "data/company/net_profit_halfyear.csv",
"quarter": "data/company/net_profit_quarter.csv"
}
}
```
页面仅在声明多个视图时显示切换按钮,并将选择写入 URL query。当前泡泡玛特图表没有强制启用这一功能;基础能力已保留,等真实数据需要时再登记。
---
## 6. 视觉一致性与新增图表
新增图表必须复用 `theme.js` 的 `FONT`、`PALETTE`、`LAYOUT`、`periodAxis()`、`standardLegend()`、`lineSeries()`、`barSeries()`、`footnote()` 与 `watermarks()`,而不是在单个图表中重复手写基础样式。
- 财务含义相同的系列应使用固定语义色;IP、区域、品类等多分类数据使用统一分类色板。
- 新图的业务差异由 chart 文件表达;统一视觉由主题文件表达;展示位置与 CSV 由 manifest 表达。
- 新增或调整图表后,必须检查标签、图例、脚注和控制器是否重叠,以及颜色与文字是否有足够对比度。
通用图表模板仍然后置。只有至少两家公司需要相同图型、字段映射与口径已稳定时,才抽取 `template(rows, meta, { width, height }) => option`;在此之前,保留一图一文件更低风险。
---
## 7. 校验、发布与维护
### 7.1 当前校验
`pnpm test` 会遍历每家公司的 `chartCatalog`,以 ECharts SSR 渲染所有图表并检查:
- 图表 key 已在 registry 注册;
- CSV 可读取;
- Tab 的 `chartRefs` 都存在,且同一 Tab 不重复;
- 图例、网格、折线圆点等共享视觉规范;
- SVG 导出没有字体引号导致的非法属性。
截至当前,仓库登记 **27 张**图表(泡泡玛特 22 张、示例 5 张)。概览中的重复展示不增加图表定义或测试数量。
### 7.2 日常工作流
1. 更新已有数据:替换对应 CSV,检查口径与脚注,运行 `pnpm test`。
2. 新增图表:新增 CSV → 新增图表构建文件 → 在 registry 注册 key → 在公司的 `chartCatalog` 登记一次 → 在需要的 Tab 增加 `chartRefs`。
3. 调整首页:只调整 `overview.chartRefs` 的选择与顺序,不复制任何图表配置。
4. 发布静态文件:运行 `pnpm build`;产物只包含运行需要的 manifest 和 CSV,不包含 `data/*/raw/` 中的原始文件。
---
## 8. 明确后置的能力
- 原始 PDF 自动抽取与全自动财务勾稽;
- 自动将累计半年/季度数据拆分为单期;
- 全公司统一指标库与 schema 强校验;
- 现金流、人效、偿债等通用模板;
- 截图像素级视觉回归。
这些能力并非不需要,而是在当前“先把现有公司稳定用起来”的阶段不应抢占开发成本。出现第二家公司、稳定重复的数据与图型后,再按实际痛点逐项引入。
+1 -4
View File
@@ -18,10 +18,7 @@ export function growthChart(rows, { width, height }) {
...legendStyle, ...legendStyle,
top: LAYOUT.legendTop, top: LAYOUT.legendTop,
itemGap: 34, itemGap: 34,
data: ['非国际归母净利', '毛利润', '营业收入', '营业成本'].map(name => ({ data: ['非国际归母净利', '毛利润', '营业收入', '营业成本'],
name,
icon: 'emptyCircle', // 全部折线图
})),
}, },
grid: { left: 84, right: 56, top: LAYOUT.gridTop, bottom: 64 }, grid: { left: 84, right: 56, top: LAYOUT.gridTop, bottom: 64 },
tooltip: { tooltip: {
+16
View File
@@ -8,7 +8,9 @@ import { profitChart } from './ppmt_profit.js';
import { ipChart } from './ppmt_ip.js'; import { ipChart } from './ppmt_ip.js';
import { categoryChart } from './ppmt_category.js'; import { categoryChart } from './ppmt_category.js';
import { sankeyChart } from './ppmt_sankey.js'; import { sankeyChart } from './ppmt_sankey.js';
import { regionChannelChart } from './ppmt_region_channel.js';
import { regionChannelYoyChart } from './ppmt_region_yoy.js'; import { regionChannelYoyChart } from './ppmt_region_yoy.js';
import { regionChannelYoyTimeseriesChart } from './ppmt_region_channel_yoy_timeseries.js';
import { ppmtGrowthChart } from './ppmt_growth.js'; import { ppmtGrowthChart } from './ppmt_growth.js';
import { ppmtMarginsChart } from './ppmt_margins.js'; import { ppmtMarginsChart } from './ppmt_margins.js';
import { ppmtBridgeChart } from './ppmt_bridge.js'; import { ppmtBridgeChart } from './ppmt_bridge.js';
@@ -17,6 +19,12 @@ import { ppmtCoreMarginChart } from './ppmt_core_margin.js';
import { ppmtIpShareChart } from './ppmt_ip_share.js'; import { ppmtIpShareChart } from './ppmt_ip_share.js';
import { ppmtIpShareHistoryChart } from './ppmt_ip_share_history.js'; import { ppmtIpShareHistoryChart } from './ppmt_ip_share_history.js';
import { ppmtIpExMonstersChart } from './ppmt_ip_ex_monsters.js'; import { ppmtIpExMonstersChart } from './ppmt_ip_ex_monsters.js';
import { cashConversionChart } from './ppmt_cash_conversion.js';
import { liquidityWorkingCapitalChart } from './ppmt_liquidity_working_capital.js';
import { cashflowCompositionChart } from './ppmt_cashflow_composition.js';
import { workingCapitalDaysChart } from './ppmt_working_capital_days.js';
import { expenseStructureChart } from './ppmt_expense_structure.js';
import { channelRevenueChart } from './ppmt_channel_revenue.js';
/** 图表构建函数注册表。manifest 只引用这里的 key,不直接引用 JS 函数。 */ /** 图表构建函数注册表。manifest 只引用这里的 key,不直接引用 JS 函数。 */
export const chartRegistry = { export const chartRegistry = {
@@ -30,7 +38,9 @@ export const chartRegistry = {
'ppmt-ip': ipChart, 'ppmt-ip': ipChart,
'ppmt-category': categoryChart, 'ppmt-category': categoryChart,
'ppmt-sankey': sankeyChart, 'ppmt-sankey': sankeyChart,
'ppmt-region-channel': regionChannelChart,
'ppmt-region-yoy': regionChannelYoyChart, 'ppmt-region-yoy': regionChannelYoyChart,
'ppmt-region-yoy-timeseries': regionChannelYoyTimeseriesChart,
'ppmt-growth': ppmtGrowthChart, 'ppmt-growth': ppmtGrowthChart,
'ppmt-margins': ppmtMarginsChart, 'ppmt-margins': ppmtMarginsChart,
'ppmt-bridge': ppmtBridgeChart, 'ppmt-bridge': ppmtBridgeChart,
@@ -39,6 +49,12 @@ export const chartRegistry = {
'ppmt-ip-share': ppmtIpShareChart, 'ppmt-ip-share': ppmtIpShareChart,
'ppmt-ip-share-history': ppmtIpShareHistoryChart, 'ppmt-ip-share-history': ppmtIpShareHistoryChart,
'ppmt-ip-ex-monsters': ppmtIpExMonstersChart, 'ppmt-ip-ex-monsters': ppmtIpExMonstersChart,
'ppmt-cash-conversion': cashConversionChart,
'ppmt-liquidity-working-capital': liquidityWorkingCapitalChart,
'ppmt-cashflow-composition': cashflowCompositionChart,
'ppmt-working-capital-days': workingCapitalDaysChart,
'ppmt-expense-structure': expenseStructureChart,
'ppmt-channel-revenue': channelRevenueChart,
}; };
export function getChartBuilder(key) { export function getChartBuilder(key) {
+1 -5
View File
@@ -17,11 +17,7 @@ export function inventoryChart(rows, { width, height }) {
legend: { legend: {
...legendStyle, ...legendStyle,
top: LAYOUT.legendTop, top: LAYOUT.legendTop,
data: [ data: ['存货', '营收', '存货周转天数'],
{ name: '存货', icon: 'rect' },
{ name: '营收', icon: 'rect' },
{ name: '存货周转天数', icon: 'emptyCircle' }, // 折线
],
}, },
grid: { left: 78, right: 82, top: LAYOUT.gridTop, bottom: 90 }, grid: { left: 78, right: 82, top: LAYOUT.gridTop, bottom: 90 },
tooltip: { tooltip: {
+1 -4
View File
@@ -21,10 +21,7 @@ export function marginChart(rows, { width, height }) {
legend: { legend: {
...legendStyle, ...legendStyle,
top: LAYOUT.legendTop, top: LAYOUT.legendTop,
data: ['毛利率', '销售费用率', '管理费用率', '非国际归母净利率'].map(name => ({ data: ['毛利率', '销售费用率', '管理费用率', '非国际归母净利率'],
name,
icon: 'emptyCircle', // 全部折线图
})),
}, },
grid: { left: 72, right: 48, top: LAYOUT.gridTop, bottom: 62 }, grid: { left: 72, right: 48, top: LAYOUT.gridTop, bottom: 62 },
tooltip: { tooltip: {
+1 -4
View File
@@ -21,10 +21,7 @@ export function assetsChart(rows, { width, height }) {
legend: { legend: {
...legendStyle, ...legendStyle,
top: LAYOUT.legendTop, top: LAYOUT.legendTop,
data: ['类现金', '应收账款', '投资资产', '经营类资产', '资产总计'].map(name => ({ data: ['类现金', '应收账款', '投资资产', '经营类资产', '资产总计'],
name,
icon: name === '资产总计' ? 'emptyCircle' : 'rect', // 总计是折线,其他是柱
})),
}, },
grid: { left: 78, right: 82, top: LAYOUT.gridTop, bottom: 90 }, grid: { left: 78, right: 82, top: LAYOUT.gridTop, bottom: 90 },
tooltip: { tooltip: {
+91
View File
@@ -0,0 +1,91 @@
import { col } from '../csv.js';
import {
FINANCIAL_COLORS, PALETTE, FONT, axisCommon, periodAxis, titleStyle, legendStyle,
lineSeries, footnote, watermarks, LAYOUT,
} from '../theme.js';
/** 泡泡玛特经营现金流与净利润:观察利润的现金兑现能力。 */
export function cashConversionChart(rows, { width, height }) {
const periods = col(rows, 'period');
return {
backgroundColor: '#fff',
title: {
...titleStyle,
text: '泡泡玛特经营现金流与净利润(年度)',
subtext: '现金转换率 = 经营活动现金流净额 / 净利润;单位:亿元',
},
legend: {
...legendStyle,
top: LAYOUT.legendTop,
data: ['经营活动现金流净额', '净利润', '现金转换率'],
},
grid: { left: 78, right: 82, top: LAYOUT.gridTop, bottom: 90 },
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
textStyle: { fontFamily: FONT },
valueFormatter: (value) => `${value.toFixed(1)}`,
},
xAxis: { ...periodAxis, data: periods },
yAxis: [
{
...axisCommon,
type: 'value',
name: '金额(亿元)',
nameLocation: 'middle',
nameGap: 52,
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
min: 0,
splitLine: { show: true, lineStyle: { color: PALETTE.gridLine } },
},
{
...axisCommon,
type: 'value',
name: '现金转换率(%)',
nameLocation: 'middle',
nameGap: 58,
nameRotate: -90,
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
min: 0,
max: 200,
interval: 50,
axisLabel: { ...axisCommon.axisLabel, formatter: '{value}%' },
splitLine: { show: false },
},
],
series: [
{
name: '经营活动现金流净额',
type: 'bar',
data: col(rows, 'operating_cash_flow'),
barWidth: 31.2,
itemStyle: { color: FINANCIAL_COLORS.operatingCashFlow },
label: { show: true, position: 'top', color: PALETTE.ink, fontFamily: FONT, fontSize: 11, formatter: (p) => p.value.toFixed(1) },
},
{
name: '净利润',
type: 'bar',
data: col(rows, 'net_profit'),
barWidth: 31.2,
itemStyle: { color: FINANCIAL_COLORS.netProfit },
label: { show: true, position: 'top', color: PALETTE.ink, fontFamily: FONT, fontSize: 11, formatter: (p) => p.value.toFixed(1) },
},
{
...lineSeries({
name: '现金转换率',
data: col(rows, 'cash_conversion'),
color: FINANCIAL_COLORS.cashConversion,
formatter: (p) => `${p.value.toFixed(0)}%`,
width: 3,
}),
yAxisIndex: 1,
z: 10,
},
],
graphic: [
...watermarks(width, height),
footnote('数据来源:泡泡玛特合并现金流量表、利润表(年度口径)| @思考的Joey'),
],
};
}
+61
View File
@@ -0,0 +1,61 @@
import { col } from '../csv.js';
import {
FINANCIAL_COLORS, PALETTE, FONT, axisCommon, periodAxis, titleStyle, standardLegend,
lineDataWithSafeLabels,
lineSeries, footnote, watermarks, LAYOUT,
} from '../theme.js';
/** 泡泡玛特三大现金流:展示经营造血、投资投入与筹资变化。 */
export function cashflowCompositionChart(rows, { width, height }) {
const netCashIncrease = lineDataWithSafeLabels(rows, {
lineKey: 'net_cash_increase',
barKeys: ['operating_cash_flow', 'investing_cash_flow', 'financing_cash_flow'],
});
return {
backgroundColor: '#fff',
title: {
...titleStyle,
text: '泡泡玛特三大现金流与现金净增加(年度)',
subtext: '单位:亿元;正值为流入,负值为流出',
},
// 不手工指定 icon,让 ECharts 按系列类型生成:柱为矩形、折线为短线 + 空心圆。
legend: standardLegend(['经营活动现金流', '投资活动现金流', '筹资活动现金流', '现金净增加']),
grid: { left: 78, right: 42, top: LAYOUT.gridTop, bottom: 90 },
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
textStyle: { fontFamily: FONT },
valueFormatter: (value) => `${value.toFixed(1)} 亿元`,
},
xAxis: { ...periodAxis, data: col(rows, 'period') },
yAxis: {
...axisCommon,
type: 'value',
name: '金额(亿元)',
nameLocation: 'middle',
nameGap: 52,
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
},
series: [
{ name: '经营活动现金流', type: 'bar', data: col(rows, 'operating_cash_flow'), barWidth: 26, itemStyle: { color: FINANCIAL_COLORS.operatingCashFlow } },
{ name: '投资活动现金流', type: 'bar', data: col(rows, 'investing_cash_flow'), barWidth: 26, itemStyle: { color: FINANCIAL_COLORS.investingCashFlow } },
{ name: '筹资活动现金流', type: 'bar', data: col(rows, 'financing_cash_flow'), barWidth: 26, itemStyle: { color: FINANCIAL_COLORS.financingCashFlow } },
{
...lineSeries({
name: '现金净增加',
data: netCashIncrease,
color: FINANCIAL_COLORS.netCashIncrease,
formatter: (p) => p.value.toFixed(1),
width: 3,
}),
labelLayout: { moveOverlap: 'shiftY', hideOverlap: true },
z: 10,
},
],
graphic: [
...watermarks(width, height),
footnote('数据来源:泡泡玛特合并现金流量表(年度口径)| @思考的Joey'),
],
};
}
+10 -6
View File
@@ -1,6 +1,6 @@
import { col } from '../csv.js'; import { col } from '../csv.js';
import { import {
PALETTE, FONT, axisCommon, periodAxis, titleStyle, legendStyle, PALETTE, FONT, axisCommon, categoryAxis, titleStyle, legendStyle,
footnote, watermarks, LAYOUT, footnote, watermarks, LAYOUT,
} from '../theme.js'; } from '../theme.js';
@@ -23,10 +23,7 @@ export function categoryChart(rows, { width, height }) {
legend: { legend: {
...legendStyle, ...legendStyle,
top: LAYOUT.legendTop, top: LAYOUT.legendTop,
data: ['毛绒', '手办', 'MEGA', '衍生品及其他'].map(name => ({ data: ['毛绒', '手办', 'MEGA', '衍生品及其他'],
name,
icon: 'rect', // 全部是堆叠柱状图
})),
}, },
grid: { left: 78, right: 56, top: LAYOUT.gridTop, bottom: 90 }, grid: { left: 78, right: 56, top: LAYOUT.gridTop, bottom: 90 },
tooltip: { tooltip: {
@@ -44,7 +41,14 @@ export function categoryChart(rows, { width, height }) {
return result; return result;
}, },
}, },
xAxis: { ...periodAxis, data: periods }, xAxis: {
...categoryAxis,
data: periods,
axisLabel: {
...categoryAxis.axisLabel,
formatter: (value) => String(value).replace(/^20/, ''),
},
},
yAxis: { yAxis: {
...axisCommon, ...axisCommon,
type: 'value', type: 'value',
+87
View File
@@ -0,0 +1,87 @@
import { col } from '../csv.js';
import {
CHANNEL_COLORS, PALETTE, FONT, axisCommon, periodAxis, titleStyle, legendStyle,
lineSeries, footnote, watermarks, LAYOUT,
} from '../theme.js';
/** 泡泡玛特渠道收入结构与增速。 */
export function channelRevenueChart(rows, { width, height }) {
const channels = [
{ key: 'retail', name: '零售店', color: CHANNEL_COLORS.retail },
{ key: 'online', name: '线上', color: CHANNEL_COLORS.online },
{ key: 'robot_store', name: '机器人商店', color: CHANNEL_COLORS.robotStore },
{ key: 'wholesale', name: '批发', color: CHANNEL_COLORS.wholesale },
];
return {
backgroundColor: '#fff',
title: {
...titleStyle,
text: '泡泡玛特渠道收入结构与增速',
subtext: '渠道收入为堆叠柱;折线为营业收入同比增速',
},
legend: {
...legendStyle,
top: LAYOUT.legendTop,
data: [...channels.map((item) => item.name), '营业收入同比'],
},
grid: { left: 78, right: 82, top: LAYOUT.gridTop, bottom: 90 },
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
textStyle: { fontFamily: FONT },
valueFormatter: (value) => value.toFixed(1),
},
xAxis: { ...periodAxis, data: col(rows, 'period') },
yAxis: [
{
...axisCommon,
type: 'value',
name: '渠道收入(亿元)',
nameLocation: 'middle',
nameGap: 56,
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
min: 0,
},
{
...axisCommon,
type: 'value',
name: '营业收入同比(%)',
nameLocation: 'middle',
nameGap: 60,
nameRotate: -90,
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
min: 0,
max: 250,
interval: 50,
axisLabel: { ...axisCommon.axisLabel, formatter: '{value}%' },
splitLine: { show: false },
},
],
series: [
...channels.map((item) => ({
name: item.name,
type: 'bar',
stack: 'channel-revenue',
data: col(rows, item.key),
itemStyle: { color: item.color },
emphasis: { focus: 'series' },
})),
{
...lineSeries({
name: '营业收入同比',
data: col(rows, 'revenue_yoy'),
color: CHANNEL_COLORS.revenueGrowth,
formatter: (p) => `${p.value.toFixed(0)}%`,
width: 3,
}),
yAxisIndex: 1,
z: 10,
},
],
graphic: [
...watermarks(width, height),
footnote('数据来源:泡泡玛特营收构成表;渠道口径按财报披露 | @思考的Joey'),
],
};
}
+1
View File
@@ -22,6 +22,7 @@ export function ppmtCoreProfitChart(rows, { width, height }) {
subtext: '核心利润 = 毛利 - 销售费用 - 管理费用,单位:亿元', subtext: '核心利润 = 毛利 - 销售费用 - 管理费用,单位:亿元',
}, },
legend: { legend: {
...legendStyle,
top: LAYOUT.legendTop, top: LAYOUT.legendTop,
itemGap: 32, itemGap: 32,
textStyle: { textStyle: {
+74
View File
@@ -0,0 +1,74 @@
import { col } from '../csv.js';
import {
PALETTE, FONT, axisCommon, periodAxis, titleStyle, legendStyle,
lineSeries, footnote, watermarks, LAYOUT,
} from '../theme.js';
/** 泡泡玛特期间费用结构:观察销售投入、管理成本与费用率的规模效应。 */
export function expenseStructureChart(rows, { width, height }) {
return {
backgroundColor: '#fff',
title: {
...titleStyle,
text: '泡泡玛特费用结构与费用率(年度)',
subtext: '销售、管理、财务费用;折线为四项费用率',
},
legend: {
...legendStyle,
top: LAYOUT.legendTop,
data: ['销售费用', '管理费用', '财务费用(净额)', '四项费用率'],
},
grid: { left: 78, right: 82, top: LAYOUT.gridTop, bottom: 90 },
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
textStyle: { fontFamily: FONT },
valueFormatter: (value) => value.toFixed(1),
},
xAxis: { ...periodAxis, data: col(rows, 'period') },
yAxis: [
{
...axisCommon,
type: 'value',
name: '金额(亿元)',
nameLocation: 'middle',
nameGap: 52,
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
},
{
...axisCommon,
type: 'value',
name: '四项费用率(%)',
nameLocation: 'middle',
nameGap: 58,
nameRotate: -90,
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
min: 0,
max: 50,
interval: 10,
axisLabel: { ...axisCommon.axisLabel, formatter: '{value}%' },
splitLine: { show: false },
},
],
series: [
{ name: '销售费用', type: 'bar', stack: 'expenses', data: col(rows, 'sales_expense'), itemStyle: { color: PALETTE.orange } },
{ name: '管理费用', type: 'bar', stack: 'expenses', data: col(rows, 'admin_expense'), itemStyle: { color: PALETTE.purple } },
{ name: '财务费用(净额)', type: 'bar', stack: 'expenses', data: col(rows, 'financial_expense'), itemStyle: { color: PALETTE.blue } },
{
...lineSeries({
name: '四项费用率',
data: col(rows, 'four_expense_rate'),
color: PALETTE.red,
formatter: (p) => `${p.value.toFixed(1)}%`,
width: 3,
}),
yAxisIndex: 1,
z: 10,
},
],
graphic: [
...watermarks(width, height),
footnote('数据来源:泡泡玛特合并利润表(年度口径)| @思考的Joey'),
],
};
}
+2 -12
View File
@@ -1,6 +1,6 @@
import { col } from '../csv.js'; import { col } from '../csv.js';
import { import {
PALETTE, FONT, axisCommon, periodAxis, titleStyle, legendStyle, PALETTE, FONT, axisCommon, periodAxis, titleStyle, standardLegend,
lineSeries, footnote, watermarks, LAYOUT, lineSeries, footnote, watermarks, LAYOUT,
} from '../theme.js'; } from '../theme.js';
@@ -53,17 +53,7 @@ export function ppmtGrowthChart(rows, { width, height }) {
text: '泡泡玛特营收到利润的增速对比(2020H1-2026H1)', text: '泡泡玛特营收到利润的增速对比(2020H1-2026H1)',
subtext: '同比增速(%),净利增速显著高于营收增速体现利润弹性', subtext: '同比增速(%),净利增速显著高于营收增速体现利润弹性',
}, },
legend: { legend: standardLegend(['非GAAP归母净利', '毛利润', '营业收入', '营业成本'], { itemGap: 34 }),
// 不继承 legendStyle,让 ECharts 按系列自动生成「短线+空心圆」图例
top: LAYOUT.legendTop,
itemGap: 34,
textStyle: {
color: PALETTE.ink,
fontSize: 14,
fontFamily: FONT,
},
data: ['非GAAP归母净利', '毛利润', '营业收入', '营业成本'],
},
grid: { left: 84, right: 56, top: LAYOUT.gridTop, bottom: 80 }, grid: { left: 84, right: 56, top: LAYOUT.gridTop, bottom: 80 },
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
+10 -6
View File
@@ -1,6 +1,6 @@
import { col } from '../csv.js'; import { col } from '../csv.js';
import { import {
PALETTE, FONT, axisCommon, periodAxis, titleStyle, legendStyle, PALETTE, FONT, axisCommon, categoryAxis, titleStyle, legendStyle,
lineSeries, footnote, watermarks, LAYOUT, lineSeries, footnote, watermarks, LAYOUT,
} from '../theme.js'; } from '../theme.js';
@@ -49,10 +49,7 @@ export function ipChart(rows, { width, height }) {
...legendStyle, ...legendStyle,
top: LAYOUT.legendTop, top: LAYOUT.legendTop,
itemGap: 20, itemGap: 20,
data: rows.map(r => ({ data: rows.map((row) => row.ip),
name: r.ip,
icon: 'emptyCircle', // 折线图:空心圆圈
})),
}, },
grid: { left: 78, right: 56, top: LAYOUT.gridTop, bottom: 70 }, grid: { left: 78, right: 56, top: LAYOUT.gridTop, bottom: 70 },
tooltip: { tooltip: {
@@ -60,7 +57,14 @@ export function ipChart(rows, { width, height }) {
valueFormatter: (v) => `${v.toFixed(2)} 亿元`, valueFormatter: (v) => `${v.toFixed(2)} 亿元`,
textStyle: { fontFamily: FONT }, textStyle: { fontFamily: FONT },
}, },
xAxis: { ...periodAxis, data: periods }, xAxis: {
...categoryAxis,
data: periods,
axisLabel: {
...categoryAxis.axisLabel,
formatter: (value) => String(value).replace(/^20/, ''),
},
},
yAxis: { yAxis: {
...axisCommon, ...axisCommon,
type: 'value', type: 'value',
+2 -2
View File
@@ -1,5 +1,5 @@
import { import {
PALETTE, FONT, axisCommon, categoryAxis, titleStyle, PALETTE, FONT, axisCommon, categoryAxis, titleStyle, LEGEND_ITEM_WIDTH,
barSeries, footnote, watermarks, LAYOUT, barSeries, footnote, watermarks, LAYOUT,
} from '../theme.js'; } from '../theme.js';
@@ -58,7 +58,7 @@ export function ppmtIpShareHistoryChart(rows, { width, height }) {
width: LEGEND_WIDTH, width: LEGEND_WIDTH,
top: 'middle', top: 'middle',
itemGap: 12, itemGap: 12,
itemWidth: 14, itemWidth: LEGEND_ITEM_WIDTH,
itemHeight: 14, itemHeight: 14,
data: names, data: names,
textStyle: { color: PALETTE.ink, fontSize: 12, fontFamily: FONT }, textStyle: { color: PALETTE.ink, fontSize: 12, fontFamily: FONT },
@@ -0,0 +1,73 @@
import { col } from '../csv.js';
import {
PALETTE, FONT, axisCommon, periodAxis, titleStyle, legendStyle,
lineSeries, footnote, watermarks, LAYOUT,
} from '../theme.js';
/** 泡泡玛特现金、负债与净营运资本:观察流动性与资金占用。 */
export function liquidityWorkingCapitalChart(rows, { width, height }) {
const periods = col(rows, 'period');
return {
backgroundColor: '#fff',
title: {
...titleStyle,
text: '泡泡玛特现金、负债与净营运资本',
subtext: '净营运资本 = 流动资产 − 流动负债;单位:亿元',
},
legend: {
...legendStyle,
top: LAYOUT.legendTop,
data: ['货币资金', '负债合计', '净营运资本'],
},
grid: { left: 78, right: 42, top: LAYOUT.gridTop, bottom: 90 },
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
textStyle: { fontFamily: FONT },
valueFormatter: (value) => `${value.toFixed(1)} 亿元`,
},
xAxis: { ...periodAxis, data: periods },
yAxis: {
...axisCommon,
type: 'value',
name: '金额(亿元)',
nameLocation: 'middle',
nameGap: 52,
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
min: 0,
},
series: [
{
name: '货币资金',
type: 'bar',
data: col(rows, 'cash'),
barWidth: 18,
itemStyle: { color: PALETTE.blue },
emphasis: { focus: 'series' },
},
{
name: '负债合计',
type: 'bar',
data: col(rows, 'total_liabilities'),
barWidth: 18,
itemStyle: { color: PALETTE.orange },
emphasis: { focus: 'series' },
},
{
...lineSeries({
name: '净营运资本',
data: col(rows, 'net_working_capital'),
color: PALETTE.red,
formatter: (p) => p.value.toFixed(1),
width: 3,
}),
z: 10,
},
],
graphic: [
...watermarks(width, height),
footnote('数据来源:泡泡玛特合并资产负债表 | @思考的Joey'),
],
};
}
+2 -13
View File
@@ -1,6 +1,6 @@
import { col } from '../csv.js'; import { col } from '../csv.js';
import { import {
PALETTE, FONT, axisCommon, periodAxis, titleStyle, legendStyle, PALETTE, FONT, axisCommon, periodAxis, titleStyle, standardLegend,
lineSeries, footnote, watermarks, LAYOUT, lineSeries, footnote, watermarks, LAYOUT,
} from '../theme.js'; } from '../theme.js';
@@ -64,18 +64,7 @@ export function ppmtMarginsChart(rows, { width, height }) {
text: '泡泡玛特利润率与费用率拆解(2020H1-2026H1)', text: '泡泡玛特利润率与费用率拆解(2020H1-2026H1)',
subtext: '毛利率抬升叠加费用率下降,共同推动净利率走高(H1为半年,H2为全年累计)', subtext: '毛利率抬升叠加费用率下降,共同推动净利率走高(H1为半年,H2为全年累计)',
}, },
legend: { legend: standardLegend(SERIES.map((series) => series.name)),
// 不继承 legendStyle:它带 icon:'roundRect',会盖掉 ECharts 按系列
// 自动生成的「短线+空心圆」图例
top: LAYOUT.legendTop,
itemGap: 28,
textStyle: {
color: PALETTE.ink,
fontSize: 14,
fontFamily: FONT,
},
data: SERIES.map((s) => s.name),
},
grid: { left: 78, right: 56, top: LAYOUT.gridTop, bottom: 80 }, grid: { left: 78, right: 56, top: LAYOUT.gridTop, bottom: 80 },
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
+1 -6
View File
@@ -25,12 +25,7 @@ export function profitChart(rows, { width, height }) {
legend: { legend: {
...legendStyle, ...legendStyle,
top: LAYOUT.legendTop, top: LAYOUT.legendTop,
data: [ data: ['营业收入', '毛利润', '归母净利润', '毛利率'],
{ name: '营业收入', icon: 'rect' },
{ name: '毛利润', icon: 'rect' },
{ name: '归母净利润', icon: 'rect' },
{ name: '毛利率', icon: 'emptyCircle' },
],
}, },
grid: { left: 78, right: 82, top: LAYOUT.gridTop, bottom: 90 }, grid: { left: 78, right: 82, top: LAYOUT.gridTop, bottom: 90 },
tooltip: { tooltip: {
+3 -2
View File
@@ -1,5 +1,5 @@
import { import {
PALETTE, FONT, titleStyle, footnote, watermarks, PALETTE, FONT, titleStyle, footnote, watermarks, legendStyle,
LAYOUT, } from '../theme.js'; LAYOUT, } from '../theme.js';
/** /**
@@ -44,7 +44,8 @@ export function regionChannelChart(rows, { width, height }) {
top: 18, top: 18,
}, },
legend: { legend: {
data: channels.map(name => ({ name, icon: 'rect' })), // 全部堆叠柱状图 ...legendStyle,
data: channels,
top: LAYOUT.legendTop, top: LAYOUT.legendTop,
itemGap: 32, itemGap: 32,
textStyle: { fontFamily: FONT, fontSize: 13 }, textStyle: { fontFamily: FONT, fontSize: 13 },
@@ -0,0 +1,117 @@
import {
FONT, PALETTE, categoryAxis, footnote, titleStyle, watermarks,
} from '../theme.js';
const REGION_ORDER = ['中国', '海外', '亚太', '美洲', '欧洲及其他'];
const CHANNEL_ORDER = ['线下渠道', '线上渠道', '批发及其他'];
function orderedValues(values, order) {
return [...values].sort((a, b) => {
const aIndex = order.indexOf(a);
const bIndex = order.indexOf(b);
return (aIndex === -1 ? order.length : aIndex) - (bIndex === -1 ? order.length : bIndex)
|| a.localeCompare(b, 'zh-CN');
});
}
function orderedPeriods(rows) {
return [...new Set(rows.map((row) => row.period))].sort((a, b) => {
const [yearA, halfA] = String(a).match(/(\d{4})H(\d)/).slice(1).map(Number);
const [yearB, halfB] = String(b).match(/(\d{4})H(\d)/).slice(1).map(Number);
return yearA - yearB || halfA - halfB;
});
}
/** 泡泡玛特区域 × 渠道同比增速热力图。 */
export function regionChannelYoyTimeseriesChart(rows, { width, height }) {
const periods = orderedPeriods(rows);
const regions = orderedValues(new Set(rows.map((row) => row.region)), REGION_ORDER);
const channels = orderedValues(new Set(rows.map((row) => row.channel)), CHANNEL_ORDER);
const dimensions = regions.flatMap((region) => channels.map((channel) => ({ region, channel })));
const periodIndex = new Map(periods.map((period, index) => [period, index]));
const dimensionIndex = new Map(dimensions.map(({ region, channel }, index) => [`${region}_${channel}`, index]));
const data = rows.map((row) => [
periodIndex.get(row.period),
dimensionIndex.get(`${row.region}_${row.channel}`),
row.yoy,
]);
return {
backgroundColor: '#fff',
title: {
...titleStyle,
text: '泡泡玛特区域渠道同比增速趋势',
subtext: '颜色越深代表同比变化幅度越大;空白表示当期未单列披露',
},
tooltip: {
position: 'top',
textStyle: { fontFamily: FONT },
formatter: (params) => {
const [periodIndexValue, dimensionIndexValue, value] = params.value;
const { region, channel } = dimensions[dimensionIndexValue];
const sign = value >= 0 ? '+' : '';
return `<b>${periods[periodIndexValue]}</b><br/>${region} · ${channel}<br/>同比:${sign}${value.toFixed(1)}%`;
},
},
grid: {
left: 178,
right: 42,
top: 112,
// 底部依次预留:横轴标签 → visualMap 控制器 → 数据来源。
bottom: 155,
},
xAxis: {
...categoryAxis,
data: periods,
axisLabel: {
...categoryAxis.axisLabel,
formatter: (value) => String(value).replace(/^20/, ''),
},
},
yAxis: {
...categoryAxis,
type: 'category',
inverse: true,
data: dimensions.map(({ region, channel }) => `${region} · ${channel}`),
axisLabel: {
...categoryAxis.axisLabel,
fontSize: 12,
margin: 12,
},
},
visualMap: {
min: -100,
max: 200,
calculable: true,
orient: 'horizontal',
left: 'center',
bottom: 64,
text: ['较高增长', '较低增长'],
textStyle: { color: PALETTE.grey, fontFamily: FONT, fontSize: 12 },
// 红色系代表较高增长;两端均采用浅色,保证数值标签清晰。
inRange: { color: ['#9cc4d0', '#f3eee6', PALETTE.coral] },
},
series: [{
name: '同比增速',
type: 'heatmap',
data,
label: {
show: true,
fontFamily: FONT,
fontSize: 11,
color: PALETTE.ink,
formatter: (params) => {
const value = params.value[2];
return `${value >= 0 ? '+' : ''}${value.toFixed(0)}%`;
},
},
emphasis: {
itemStyle: { borderColor: PALETTE.ink, borderWidth: 1 },
},
}],
graphic: [
...watermarks(width, height),
footnote('数据来源:泡泡玛特财报;早期海外为合并口径 | @思考的Joey'),
],
};
}
+3 -2
View File
@@ -1,5 +1,5 @@
import { import {
PALETTE, FONT, titleStyle, footnote, watermarks, PALETTE, FONT, titleStyle, footnote, watermarks, legendStyle,
LAYOUT, } from '../theme.js'; LAYOUT, } from '../theme.js';
/** /**
@@ -42,7 +42,8 @@ export function regionChannelYoyChart(rows, { width, height }) {
top: 18, top: 18,
}, },
legend: { legend: {
data: channels.map(name => ({ name, icon: 'rect' })), // 全部柱状图 ...legendStyle,
data: channels,
top: LAYOUT.legendTop, top: LAYOUT.legendTop,
itemGap: 32, itemGap: 32,
textStyle: { fontFamily: FONT, fontSize: 13 }, textStyle: { fontFamily: FONT, fontSize: 13 },
+58
View File
@@ -0,0 +1,58 @@
import { col } from '../csv.js';
import {
PALETTE, FONT, axisCommon, periodAxis, titleStyle, legendStyle,
lineSeries, footnote, watermarks, LAYOUT,
} from '../theme.js';
/** 泡泡玛特营运资本效率:存货、应收、应付与现金转换周期。 */
export function workingCapitalDaysChart(rows, { width, height }) {
const series = [
{ key: 'inventory_days', name: '存货周转天数', color: PALETTE.orange, width: 2.4 },
{ key: 'receivable_days', name: '应收周转天数', color: PALETTE.blue, width: 2.2 },
{ key: 'payable_days', name: '应付周转天数', color: PALETTE.purple, width: 2.2 },
{ key: 'cash_conversion_cycle', name: '现金转换周期', color: PALETTE.red, width: 3.2 },
];
return {
backgroundColor: '#fff',
title: {
...titleStyle,
text: '泡泡玛特营运资本效率',
subtext: '现金转换周期 = 存货周转天数 + 应收周转天数 − 应付周转天数',
},
legend: { ...legendStyle, top: LAYOUT.legendTop, data: series.map((item) => item.name) },
grid: { left: 78, right: 42, top: LAYOUT.gridTop, bottom: 90 },
tooltip: {
trigger: 'axis',
textStyle: { fontFamily: FONT },
valueFormatter: (value) => `${value.toFixed(1)} 天`,
},
xAxis: { ...periodAxis, data: col(rows, 'period') },
yAxis: {
...axisCommon,
type: 'value',
name: '天数',
nameLocation: 'middle',
nameGap: 42,
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
min: 0,
},
series: series.map((item) => ({
...lineSeries({
name: item.name,
data: col(rows, item.key),
color: item.color,
formatter: (p) => p.value.toFixed(0),
width: item.width,
}),
label: item.key === 'cash_conversion_cycle'
? { show: true, color: item.color, fontFamily: FONT, fontSize: 11, formatter: (p) => `${p.value.toFixed(0)}天` }
: { show: false },
z: item.key === 'cash_conversion_cycle' ? 10 : 1,
})),
graphic: [
...watermarks(width, height),
footnote('数据来源:泡泡玛特财务指标(合并报表)| @思考的Joey'),
],
};
}
+1 -4
View File
@@ -15,10 +15,7 @@ export function twoLineChart(rows, { width, height }) {
legend: { legend: {
...legendStyle, ...legendStyle,
top: LAYOUT.legendTop, top: LAYOUT.legendTop,
data: ['毛利率', '非国际归母净利率'].map(name => ({ data: ['毛利率', '非国际归母净利率'],
name,
icon: 'emptyCircle', // 全部折线图
})),
}, },
grid: { left: 72, right: 48, top: LAYOUT.gridTop, bottom: 62 }, grid: { left: 72, right: 48, top: LAYOUT.gridTop, bottom: 62 },
tooltip: { tooltip: {
+45 -25
View File
@@ -22,21 +22,31 @@ function validateManifest(value) {
throw new Error('manifest 缺少 companies'); throw new Error('manifest 缺少 companies');
} }
const chartIds = new Set();
value.companies.forEach((company) => { value.companies.forEach((company) => {
if (!company.id || !Array.isArray(company.tabs)) throw new Error('manifest 中存在无效公司配置'); if (!company.id || !Array.isArray(company.tabs) || !company.chartCatalog) {
throw new Error('manifest 中存在无效公司配置');
}
Object.entries(company.chartCatalog).forEach(([chartId, chart]) => {
if (!chartId || !getChartBuilder(chart.key)) {
throw new Error(`图表 ${chartId || '(空)'} 引用了未注册 key:${chart.key}`);
}
if (Boolean(chart.csv) === Boolean(chart.views)) {
throw new Error(`图表 ${chartId} 必须声明 csv 或 views 之一`);
}
if (chart.views && (!chart.defaultView || !chart.views[chart.defaultView])) {
throw new Error(`图表 ${chartId} 的 defaultView 无效`);
}
});
const tabIds = new Set(); const tabIds = new Set();
company.tabs.forEach((tab) => { company.tabs.forEach((tab) => {
if (!tab.id || tabIds.has(tab.id)) throw new Error(`公司 ${company.id} 的 tab id 重复或为空`); if (!tab.id || tabIds.has(tab.id)) throw new Error(`公司 ${company.id} 的 tab id 重复或为空`);
if (!Array.isArray(tab.chartRefs)) throw new Error(`公司 ${company.id} 的 ${tab.id} 缺少 chartRefs`);
tabIds.add(tab.id); tabIds.add(tab.id);
(tab.charts || []).forEach((chart) => { const refs = new Set();
if (!chart.id || chartIds.has(chart.id)) throw new Error(`图表 id 重复或为空:${chart.id || '(空)'}`); tab.chartRefs.forEach((chartId) => {
if (!getChartBuilder(chart.key)) throw new Error(`图表 ${chart.id} 引用了未注册 key:${chart.key}`); if (!company.chartCatalog[chartId]) throw new Error(`栏目 ${tab.id} 引用了不存在的图表:${chartId}`);
if (Boolean(chart.csv) === Boolean(chart.views)) throw new Error(`图表 ${chart.id} 必须声明 csv 或 views 之一`); if (refs.has(chartId)) throw new Error(`栏目 ${tab.id} 重复引用图表:${chartId}`);
if (chart.views && (!chart.defaultView || !chart.views[chart.defaultView])) { refs.add(chartId);
throw new Error(`图表 ${chart.id} 的 defaultView 无效`);
}
chartIds.add(chart.id);
}); });
}); });
}); });
@@ -50,6 +60,15 @@ function getTab(company, tabId) {
return company.tabs.find((tab) => tab.id === tabId) || company.tabs[0]; return company.tabs.find((tab) => tab.id === tabId) || company.tabs[0];
} }
function chartsForTab(company, tab) {
return tab.chartRefs.map((chartId) => ({
...company.chartCatalog[chartId],
id: chartId,
instanceId: `${company.id}--${tab.id}--${chartId}`,
viewKey: `${company.id}--${chartId}`,
}));
}
function parseHash() { function parseHash() {
const [path = '', query = ''] = window.location.hash.replace(/^#\/?/, '').split('?'); const [path = '', query = ''] = window.location.hash.replace(/^#\/?/, '').split('?');
const [companyId, tabId, chartId] = path.split('/').filter(Boolean).map(decodeURIComponent); const [companyId, tabId, chartId] = path.split('/').filter(Boolean).map(decodeURIComponent);
@@ -60,11 +79,11 @@ function parseHash() {
function normalizeState(nextState) { function normalizeState(nextState) {
const company = getCompany(nextState.companyId); const company = getCompany(nextState.companyId);
const tab = getTab(company, nextState.tabId); const tab = getTab(company, nextState.tabId);
const chart = tab.charts.find((item) => item.id === nextState.chartId); const chart = chartsForTab(company, tab).find((item) => item.id === nextState.chartId);
const view = chart?.views && chart.views[nextState.view] const view = chart?.views && chart.views[nextState.view]
? nextState.view ? nextState.view
: chart?.defaultView; : chart?.defaultView;
if (chart?.views && view) activeViews.set(chart.id, view); if (chart?.views && view) activeViews.set(chart.viewKey, view);
return { companyId: company.id, tabId: tab.id, chartId: chart?.id, view }; return { companyId: company.id, tabId: tab.id, chartId: chart?.id, view };
} }
@@ -83,12 +102,12 @@ function disposeAll() {
function chartSource(chart) { function chartSource(chart) {
if (!chart.views) return chart.csv; if (!chart.views) return chart.csv;
const view = activeViews.get(chart.id) || chart.defaultView; const view = activeViews.get(chart.viewKey) || chart.defaultView;
return chart.views[view]; return chart.views[view];
} }
async function renderChart(chart, version) { async function renderChart(chart, version) {
const el = document.getElementById(`chart-${chart.id}`); const el = document.getElementById(`chart-${chart.instanceId}`);
if (!el) return; if (!el) return;
const source = chartSource(chart); const source = chartSource(chart);
@@ -97,11 +116,11 @@ async function renderChart(chart, version) {
if (version !== renderVersion || !document.body.contains(el)) return; if (version !== renderVersion || !document.body.contains(el)) return;
const instance = echarts.init(el, null, { renderer: 'canvas' }); const instance = echarts.init(el, null, { renderer: 'canvas' });
instances.set(chart.id, instance); instances.set(chart.instanceId, instance);
const { width, height } = el.getBoundingClientRect(); const { width, height } = el.getBoundingClientRect();
instance.setOption(getChartBuilder(chart.key)(rows, { width, height }), true); instance.setOption(getChartBuilder(chart.key)(rows, { width, height }), true);
} catch (error) { } catch (error) {
if (version === renderVersion) showChartError(chart.id, error.message); if (version === renderVersion) showChartError(chart.instanceId, error.message);
console.error(`渲染图表失败:${chart.id}`, error); console.error(`渲染图表失败:${chart.id}`, error);
} }
} }
@@ -125,16 +144,17 @@ function updateHash(nextState) {
window.location.hash = hash; window.location.hash = hash;
} }
function selectAnchor(chartId) { function selectAnchor(chart) {
state = { ...state, chartId, view: activeViews.get(chartId) }; state = { ...state, chartId: chart.id, view: activeViews.get(chart.viewKey) };
history.replaceState(null, '', serializeHash(state)); history.replaceState(null, '', serializeHash(state));
document.getElementById(`card-${chartId}`)?.scrollIntoView({ behavior: 'smooth', block: 'start' }); document.getElementById(`card-${chart.instanceId}`)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
} }
function renderCurrent() { function renderCurrent() {
state = normalizeState(state); state = normalizeState(state);
const company = getCompany(state.companyId); const company = getCompany(state.companyId);
const tab = getTab(company, state.tabId); const tab = getTab(company, state.tabId);
const charts = chartsForTab(company, tab);
const version = ++renderVersion; const version = ++renderVersion;
disposeAll(); disposeAll();
@@ -144,15 +164,15 @@ function renderCurrent() {
renderTabs(shell.tabs, company.tabs, tab.id, (tabId) => { renderTabs(shell.tabs, company.tabs, tab.id, (tabId) => {
updateHash({ companyId: company.id, tabId }); updateHash({ companyId: company.id, tabId });
}); });
renderCards(shell.cards, tab.charts || [], activeViews, { renderCards(shell.cards, charts, activeViews, {
onExport: exportPNG, onExport: exportPNG,
onSelectView: (chartId, view) => { onSelectView: (chart, view) => {
activeViews.set(chartId, view); activeViews.set(chart.viewKey, view);
updateHash({ companyId: company.id, tabId: tab.id, chartId, view }); updateHash({ companyId: company.id, tabId: tab.id, chartId: chart.id, view });
}, },
}); });
renderAnchors(shell.chartNav, tab.charts || [], selectAnchor); renderAnchors(shell.chartNav, charts, selectAnchor);
Promise.all((tab.charts || []).map((chart) => renderChart(chart, version))); Promise.all(charts.map((chart) => renderChart(chart, version)));
} }
async function start() { async function start() {
+52 -4
View File
@@ -27,6 +27,27 @@ export const PALETTE = {
axisLine: '#d0d0d0', axisLine: '#d0d0d0',
}; };
/**
* 试行语义色:柱线组合图中,柱采用低饱和辅助色,折线保留单一强调色。
* 待页面视觉确认后再固化为新增图表的正式规范。
*/
export const FINANCIAL_COLORS = {
operatingCashFlow: '#6E9A83',
investingCashFlow: '#7897B2',
financingCashFlow: '#B18A66',
netCashIncrease: '#A8465C',
netProfit: '#C9885C',
cashConversion: '#6B4A6C',
};
export const CHANNEL_COLORS = {
retail: '#597A9B',
online: '#C87368',
robotStore: '#98A77A',
wholesale: '#BB9A5C',
revenueGrowth: '#775D8B',
};
/** 坐标轴通用样式:只留浅色横向网格,去掉轴线的存在感 */ /** 坐标轴通用样式:只留浅色横向网格,去掉轴线的存在感 */
export const axisCommon = { export const axisCommon = {
axisLine: { show: false }, axisLine: { show: false },
@@ -104,13 +125,14 @@ export const LAYOUT = {
gridTop: 145, // 绘图区顶部边距(图例到绘图区约 60px) gridTop: 145, // 绘图区顶部边距(图例到绘图区约 60px)
}; };
// 统一图例图标宽度,同时保留 ECharts 按系列类型生成的形状。
export const LEGEND_ITEM_WIDTH = 20;
export const legendStyle = { export const legendStyle = {
top: 62, // ⚠️ 已废弃:请改用 LAYOUT.legendTop 或 standardLegend() top: 62, // ⚠️ 已废弃:请改用 LAYOUT.legendTop 或 standardLegend()
itemGap: 26, itemGap: 26,
itemWidth: 18, itemWidth: LEGEND_ITEM_WIDTH,
// ❌ 删除 icon: 'roundRect' // 不设置 icon:柱状图自动使用矩形,折线图自动使用“短线 + 空心圆”。
// 原因:它会覆盖 ECharts 按系列类型自动生成的图例样式(折线图的"短线+空心圆")
// 现在让每个图表按需设置,或使用 standardLegend() 获得正确默认值
textStyle: { textStyle: {
color: PALETTE.ink, color: PALETTE.ink,
fontSize: 14, fontSize: 14,
@@ -139,6 +161,7 @@ export function labelStyle(color, position = 'top') {
export function standardLegend(data, options = {}) { export function standardLegend(data, options = {}) {
return { return {
top: LAYOUT.legendTop, top: LAYOUT.legendTop,
itemWidth: LEGEND_ITEM_WIDTH,
itemGap: options.itemGap ?? 28, itemGap: options.itemGap ?? 28,
textStyle: { textStyle: {
color: PALETTE.ink, color: PALETTE.ink,
@@ -176,6 +199,31 @@ export function lineSeries({ name, data, color, labelPosition = 'top', formatter
}; };
} }
/**
* 柱线组合图的折线标签:正值放在点上方、负值放在点下方;
* 若与同期间柱值接近,则加大距离,避免文字压在柱体端点附近。
*/
export function lineDataWithSafeLabels(rows, { lineKey, barKeys, distance = 8, closeDistance = 14 }) {
const values = rows.map((row) => row[lineKey]).filter(Number.isFinite);
const range = Math.max(...values) - Math.min(...values) || 1;
const threshold = range * 0.06;
return rows.map((row) => {
const value = row[lineKey];
const nearestBarGap = Math.min(...barKeys
.map((key) => row[key])
.filter(Number.isFinite)
.map((barValue) => Math.abs(value - barValue)));
return {
value,
label: {
position: value < 0 ? 'bottom' : 'top',
distance: nearestBarGap <= threshold ? closeDistance : distance,
},
};
});
}
/** /**
* 柱状图系列标准配置 * 柱状图系列标准配置
* 强制显示所有标签(overflow: 'none'),避免小柱子标签被 ECharts 自动隐藏 * 强制显示所有标签(overflow: 'none'),避免小柱子标签被 ECharts 自动隐藏
+9 -10
View File
@@ -16,10 +16,9 @@ function button(label, { active = false, onClick, className = '' } = {}) {
export function createAppShell(root, { onReload }) { export function createAppShell(root, { onReload }) {
root.replaceChildren(); root.replaceChildren();
const header = element('header', 'app-header'); const reloadButton = button('↻', { onClick: onReload, className: 'reload-button' });
const brand = element('div', 'app-brand'); reloadButton.title = '重新加载数据';
brand.append(element('h1', '', '财报图表'), element('p', '', '选择公司与分析维度,数据更新后可重新加载。')); reloadButton.setAttribute('aria-label', '重新加载数据');
header.append(brand, button('重新加载数据', { onClick: onReload, className: 'reload-button' }));
const layout = element('div', 'app-layout'); const layout = element('div', 'app-layout');
const companyNav = element('aside', 'sidebar company-sidebar'); const companyNav = element('aside', 'sidebar company-sidebar');
@@ -29,7 +28,7 @@ export function createAppShell(root, { onReload }) {
content.append(tabs, cards); content.append(tabs, cards);
const chartNav = element('aside', 'sidebar chart-sidebar'); const chartNav = element('aside', 'sidebar chart-sidebar');
layout.append(companyNav, content, chartNav); layout.append(companyNav, content, chartNav);
root.append(header, layout); root.append(layout, reloadButton);
return { companyNav, tabs, cards, chartNav }; return { companyNav, tabs, cards, chartNav };
} }
@@ -67,13 +66,13 @@ export function renderCards(container, charts, activeViews, { onExport, onSelect
charts.forEach((chart) => { charts.forEach((chart) => {
const card = element('section', 'card'); const card = element('section', 'card');
card.id = `card-${chart.id}`; card.id = `card-${chart.instanceId}`;
const bar = element('div', 'card-bar'); const bar = element('div', 'card-bar');
const heading = element('div', 'card-heading'); const heading = element('div', 'card-heading');
heading.append(element('h2', 'card-title', chart.title)); heading.append(element('h2', 'card-title', chart.title));
const viewNames = Object.keys(chart.views || {}); const viewNames = Object.keys(chart.views || {});
const activeView = activeViews.get(chart.id) || chart.defaultView || viewNames[0]; const activeView = activeViews.get(chart.viewKey) || chart.defaultView || viewNames[0];
const source = chart.views ? chart.views[activeView] : chart.csv; const source = chart.views ? chart.views[activeView] : chart.csv;
heading.append(element('span', 'src', source)); heading.append(element('span', 'src', source));
bar.append(heading); bar.append(heading);
@@ -84,7 +83,7 @@ export function renderCards(container, charts, activeViews, { onExport, onSelect
viewNames.forEach((view) => { viewNames.forEach((view) => {
switcher.append(button(labels[view] || view, { switcher.append(button(labels[view] || view, {
active: view === activeView, active: view === activeView,
onClick: () => onSelectView(chart.id, view), onClick: () => onSelectView(chart, view),
className: 'view-button', className: 'view-button',
})); }));
}); });
@@ -92,11 +91,11 @@ export function renderCards(container, charts, activeViews, { onExport, onSelect
} }
bar.append(button('导出 PNG', { bar.append(button('导出 PNG', {
onClick: () => onExport(chart.id, chart.filename || chart.title), onClick: () => onExport(chart.instanceId, chart.filename || chart.title),
className: 'export-button', className: 'export-button',
})); }));
const chartEl = element('div', 'chart'); const chartEl = element('div', 'chart');
chartEl.id = `chart-${chart.id}`; chartEl.id = `chart-${chart.instanceId}`;
chartEl.style.height = `${chart.height || 680}px`; chartEl.style.height = `${chart.height || 680}px`;
card.append(bar, chartEl); card.append(bar, chartEl);
container.append(card); container.append(card);
+41 -85
View File
@@ -3,98 +3,54 @@
{ {
"id": "ppmt", "id": "ppmt",
"name": "泡泡玛特", "name": "泡泡玛特",
"chartCatalog": {
"ppmt-profit": { "key": "ppmt-profit", "title": "盈利能力", "csv": "data/ppmt/ppmt_profit.csv", "height": 680, "filename": "泡泡玛特盈利能力" },
"ppmt-sankey": { "key": "ppmt-sankey", "title": "利润流向", "csv": "data/ppmt/ppmt_sankey_2026h1.csv", "height": 800, "filename": "泡泡玛特利润流向2026H1" },
"ppmt-growth": { "key": "ppmt-growth", "title": "营收到利润的增速对比", "csv": "data/ppmt/ppmt_growth.csv", "height": 700, "filename": "泡泡玛特营收到利润增速对比" },
"ppmt-core-profit": { "key": "ppmt-core-profit", "title": "核心利润及同比增速", "csv": "data/ppmt/ppmt_core_profit.csv", "height": 720, "filename": "泡泡玛特核心利润及同比增速" },
"ppmt-core-margin": { "key": "ppmt-core-margin", "title": "核心利润率", "csv": "data/ppmt/ppmt_core_margin.csv", "height": 680, "filename": "泡泡玛特核心利润率" },
"ppmt-bridge": { "key": "ppmt-bridge", "title": "净利率同比拆解", "csv": "data/ppmt/ppmt_margin_bridge.csv", "height": 700, "filename": "泡泡玛特净利率同比拆解" },
"ppmt-margins": { "key": "ppmt-margins", "title": "利润率与费用率拆解", "csv": "data/ppmt/ppmt_margins.csv", "height": 680, "filename": "泡泡玛特利润率与费用率拆解" },
"ppmt-assets": { "key": "ppmt-assets", "title": "资产结构演变", "csv": "data/ppmt/ppmt_assets.csv", "height": 680, "filename": "泡泡玛特资产结构" },
"ppmt-ip": { "key": "ppmt-ip", "title": "主要 IP 收入趋势", "csv": "data/ppmt/ppmt_ip.csv", "height": 720, "filename": "泡泡玛特IP收入趋势" },
"ppmt-ip-share": { "key": "ppmt-ip-share", "title": "主要 IP 收入占比", "csv": "data/ppmt/ppmt_ip_share_2026h1.csv", "height": 720, "filename": "泡泡玛特IP收入占比2026H1" },
"ppmt-ip-share-history": { "key": "ppmt-ip-share-history", "title": "各 IP 半年收入占比", "csv": "data/ppmt/ppmt_ip_share_history.csv", "height": 700, "filename": "泡泡玛特各IP半年收入占比" },
"ppmt-ip-ex-monsters": { "key": "ppmt-ip-ex-monsters", "title": "除 THE MONSTERS 外的 IP 收入及同比", "csv": "data/ppmt/ppmt_ip_ex_monsters.csv", "height": 700, "filename": "泡泡玛特除MONSTERS外IP收入及同比" },
"ppmt-category": { "key": "ppmt-category", "title": "品类收入结构", "csv": "data/ppmt/ppmt_category.csv", "height": 680, "filename": "泡泡玛特品类结构" },
"ppmt-region-channel": { "key": "ppmt-region-channel", "title": "区域渠道分布", "csv": "data/ppmt/ppmt_region_channel_2026h1.csv", "height": 680, "filename": "泡泡玛特区域渠道分布2026H1" },
"ppmt-region-yoy": { "key": "ppmt-region-yoy", "title": "区域渠道同比增速", "csv": "data/ppmt/ppmt_region_channel_yoy.csv", "height": 620, "filename": "泡泡玛特区域渠道同比增速" },
"ppmt-region-yoy-timeseries": { "key": "ppmt-region-yoy-timeseries", "title": "区域渠道同比增速趋势", "csv": "data/ppmt/ppmt_region_channel_yoy_timeseries.csv", "height": 760, "filename": "泡泡玛特区域渠道同比增速趋势" },
"ppmt-cash-conversion": { "key": "ppmt-cash-conversion", "title": "经营现金流与净利润(年度)", "csv": "data/ppmt/ppmt_cash_conversion.csv", "height": 700, "filename": "泡泡玛特经营现金流与净利润" },
"ppmt-liquidity-working-capital": { "key": "ppmt-liquidity-working-capital", "title": "现金、负债与净营运资本", "csv": "data/ppmt/ppmt_liquidity_working_capital.csv", "height": 720, "filename": "泡泡玛特现金负债与净营运资本" },
"ppmt-cashflow-composition": { "key": "ppmt-cashflow-composition", "title": "三大现金流与现金净增加(年度)", "csv": "data/ppmt/ppmt_cashflow_composition.csv", "height": 700, "filename": "泡泡玛特三大现金流与现金净增加" },
"ppmt-working-capital-days": { "key": "ppmt-working-capital-days", "title": "营运资本效率", "csv": "data/ppmt/ppmt_working_capital_days.csv", "height": 700, "filename": "泡泡玛特营运资本效率" },
"ppmt-expense-structure": { "key": "ppmt-expense-structure", "title": "费用结构与费用率(年度)", "csv": "data/ppmt/ppmt_expense_structure.csv", "height": 700, "filename": "泡泡玛特费用结构与费用率" },
"ppmt-channel-revenue": { "key": "ppmt-channel-revenue", "title": "渠道收入结构与增速", "csv": "data/ppmt/ppmt_channel_revenue.csv", "height": 720, "filename": "泡泡玛特渠道收入结构与增速" }
},
"tabs": [ "tabs": [
{ { "id": "overview", "name": "概览", "chartRefs": ["ppmt-growth", "ppmt-profit", "ppmt-ip-share", "ppmt-region-channel"] },
"id": "overview", { "id": "profitability", "name": "盈利能力", "chartRefs": ["ppmt-growth", "ppmt-core-profit", "ppmt-core-margin", "ppmt-bridge", "ppmt-sankey", "ppmt-cash-conversion", "ppmt-cashflow-composition"] },
"name": "概览", { "id": "expense", "name": "费用率", "chartRefs": ["ppmt-margins", "ppmt-expense-structure"] },
"charts": [ { "id": "balance-sheet", "name": "资产负债", "chartRefs": ["ppmt-assets", "ppmt-liquidity-working-capital", "ppmt-working-capital-days"] },
{ "id": "ppmt-profit", "key": "ppmt-profit", "title": "盈利能力", "csv": "data/ppmt/ppmt_profit.csv", "height": 680, "filename": "泡泡玛特盈利能力" }, { "id": "ip-category", "name": "IP 与品类", "chartRefs": ["ppmt-ip", "ppmt-ip-share", "ppmt-ip-share-history", "ppmt-ip-ex-monsters", "ppmt-category"] },
{ "id": "ppmt-sankey", "key": "ppmt-sankey", "title": "利润流向", "csv": "data/ppmt/ppmt_sankey_2026h1.csv", "height": 800, "filename": "泡泡玛特利润流向2026H1" } { "id": "region-channel", "name": "区域渠道", "chartRefs": ["ppmt-region-channel", "ppmt-region-yoy", "ppmt-region-yoy-timeseries", "ppmt-channel-revenue"] }
]
},
{
"id": "growth",
"name": "成长性",
"charts": [
{ "id": "ppmt-growth", "key": "ppmt-growth", "title": "营收到利润的增速对比", "csv": "data/ppmt/ppmt_growth.csv", "height": 700, "filename": "泡泡玛特营收到利润增速对比" }
]
},
{
"id": "profitability",
"name": "盈利能力",
"charts": [
{ "id": "ppmt-core-profit", "key": "ppmt-core-profit", "title": "核心利润及同比增速", "csv": "data/ppmt/ppmt_core_profit.csv", "height": 720, "filename": "泡泡玛特核心利润及同比增速" },
{ "id": "ppmt-core-margin", "key": "ppmt-core-margin", "title": "核心利润率", "csv": "data/ppmt/ppmt_core_margin.csv", "height": 680, "filename": "泡泡玛特核心利润率" },
{ "id": "ppmt-bridge", "key": "ppmt-bridge", "title": "净利率同比拆解", "csv": "data/ppmt/ppmt_margin_bridge.csv", "height": 700, "filename": "泡泡玛特净利率同比拆解" }
]
},
{
"id": "expense",
"name": "费用率",
"charts": [
{ "id": "ppmt-margins", "key": "ppmt-margins", "title": "利润率与费用率拆解", "csv": "data/ppmt/ppmt_margins.csv", "height": 680, "filename": "泡泡玛特利润率与费用率拆解" }
]
},
{
"id": "balance-sheet",
"name": "资产负债",
"charts": [
{ "id": "ppmt-assets", "key": "ppmt-assets", "title": "资产结构演变", "csv": "data/ppmt/ppmt_assets.csv", "height": 680, "filename": "泡泡玛特资产结构" }
]
},
{
"id": "ip-category",
"name": "IP 与品类",
"charts": [
{ "id": "ppmt-ip", "key": "ppmt-ip", "title": "主要 IP 收入趋势", "csv": "data/ppmt/ppmt_ip.csv", "height": 720, "filename": "泡泡玛特IP收入趋势" },
{ "id": "ppmt-ip-share", "key": "ppmt-ip-share", "title": "主要 IP 收入占比", "csv": "data/ppmt/ppmt_ip_share_2026h1.csv", "height": 720, "filename": "泡泡玛特IP收入占比2026H1" },
{ "id": "ppmt-ip-share-history", "key": "ppmt-ip-share-history", "title": "各 IP 半年收入占比", "csv": "data/ppmt/ppmt_ip_share_history.csv", "height": 700, "filename": "泡泡玛特各IP半年收入占比" },
{ "id": "ppmt-ip-ex-monsters", "key": "ppmt-ip-ex-monsters", "title": "除 THE MONSTERS 外的 IP 收入及同比", "csv": "data/ppmt/ppmt_ip_ex_monsters.csv", "height": 700, "filename": "泡泡玛特除MONSTERS外IP收入及同比" },
{ "id": "ppmt-category", "key": "ppmt-category", "title": "品类收入结构", "csv": "data/ppmt/ppmt_category.csv", "height": 680, "filename": "泡泡玛特品类结构" }
]
},
{
"id": "region-channel",
"name": "区域渠道",
"charts": [
{ "id": "ppmt-region-yoy", "key": "ppmt-region-yoy", "title": "区域渠道同比增速", "csv": "data/ppmt/ppmt_region_channel_yoy.csv", "height": 620, "filename": "泡泡玛特区域渠道同比增速" }
]
}
] ]
}, },
{ {
"id": "demo", "id": "demo",
"name": "示例", "name": "示例",
"chartCatalog": {
"demo-inventory": { "key": "demo-inventory", "title": "存货、营收与存货周转天数", "csv": "data/demo/inventory.csv", "height": 620, "filename": "存货与周转天数" },
"demo-margin": { "key": "demo-margin", "title": "利润率与费用率拆解", "csv": "data/demo/margin.csv", "height": 620, "filename": "利润率与费用率拆解" },
"demo-twoline": { "key": "demo-twoline", "title": "毛利率与非国际净利率", "csv": "data/demo/margin.csv", "height": 620, "filename": "毛利率与非国际净利率" },
"demo-growth": { "key": "demo-growth", "title": "营收到利润的增速对比", "csv": "data/demo/growth.csv", "height": 700, "filename": "营收到利润的增速对比" },
"demo-fx": { "key": "demo-fx", "title": "人民币汇率两种口径", "csv": "data/demo/fx.csv", "height": 780, "filename": "人民币汇率两种口径" }
},
"tabs": [ "tabs": [
{ { "id": "operations", "name": "营运", "chartRefs": ["demo-inventory"] },
"id": "operations", { "id": "profitability", "name": "盈利能力", "chartRefs": ["demo-margin", "demo-twoline"] },
"name": "营运", { "id": "growth", "name": "成长性", "chartRefs": ["demo-growth"] },
"charts": [ { "id": "market", "name": "汇率", "chartRefs": ["demo-fx"] }
{ "id": "demo-inventory", "key": "demo-inventory", "title": "存货、营收与存货周转天数", "csv": "data/demo/inventory.csv", "height": 620, "filename": "存货与周转天数" }
]
},
{
"id": "profitability",
"name": "盈利能力",
"charts": [
{ "id": "demo-margin", "key": "demo-margin", "title": "利润率与费用率拆解", "csv": "data/demo/margin.csv", "height": 620, "filename": "利润率与费用率拆解" },
{ "id": "demo-twoline", "key": "demo-twoline", "title": "毛利率与非国际净利率", "csv": "data/demo/margin.csv", "height": 620, "filename": "毛利率与非国际净利率" }
]
},
{
"id": "growth",
"name": "成长性",
"charts": [
{ "id": "demo-growth", "key": "demo-growth", "title": "营收到利润的增速对比", "csv": "data/demo/growth.csv", "height": 700, "filename": "营收到利润的增速对比" }
]
},
{
"id": "market",
"name": "汇率",
"charts": [
{ "id": "demo-fx", "key": "demo-fx", "title": "人民币汇率两种口径", "csv": "data/demo/fx.csv", "height": 780, "filename": "人民币汇率两种口径" }
]
}
] ]
} }
] ]
-44
View File
@@ -1,44 +0,0 @@
{
"name": "echart-demo",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "echart-demo",
"version": "1.0.0",
"license": "MIT",
"devDependencies": {
"echarts": "^5.5.1"
}
},
"node_modules/echarts": {
"version": "5.5.1",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.5.1.tgz",
"integrity": "sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"tslib": "2.3.0",
"zrender": "5.6.0"
}
},
"node_modules/tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
"dev": true,
"license": "0BSD"
},
"node_modules/zrender": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.0.tgz",
"integrity": "sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"tslib": "2.3.0"
}
}
}
}
+8 -4
View File
@@ -5,8 +5,10 @@
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "python3 -m http.server 8899", "start": "vite",
"dev": "python3 -m http.server 8899", "dev": "vite",
"build": "vite build",
"preview": "vite preview",
"test": "node test/regress.mjs" "test": "node test/regress.mjs"
}, },
"keywords": [ "keywords": [
@@ -18,6 +20,8 @@
"author": "Joey", "author": "Joey",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"echarts": "^5.5.1" "echarts": "5.5.1",
} "vite": "^8.2.2"
},
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be"
} }
+463
View File
@@ -0,0 +1,463 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
devDependencies:
echarts:
specifier: 5.5.1
version: 5.5.1
vite:
specifier: ^8.2.2
version: 8.2.2
packages:
'@oxc-project/types@0.147.0':
resolution: {integrity: sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==}
'@rolldown/binding-android-arm-eabi@1.2.6':
resolution: {integrity: sha512-b+jTcARdTiFLI6jB4a5XjTm0RWd6KcRfQj/I2356fxUZemiho9zQLxo0RtCuMDAyKcLo6cEltkgbQp6d1+sjjQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [android]
'@rolldown/binding-android-arm64@1.2.6':
resolution: {integrity: sha512-lkWU8ZJaRk9q3CIEY1Tc7vIFALp3Xw5NfGJo2hQg5oIqNgxWi1zI+IiDEK3r70BF5Dzol1tcXsnzsRc8NLhG+Q==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [android]
'@rolldown/binding-darwin-arm64@1.2.6':
resolution: {integrity: sha512-dgR56NYnvAszm7Ob1B2/Vn0e8bUQYZH2UjVaMMtMVOCKFSfjhfLmuA/9+O+F+ajUdG6B/bSssrKW6JJYASa8jA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [darwin]
'@rolldown/binding-darwin-x64@1.2.6':
resolution: {integrity: sha512-vpVxFvUCFioJqug7OTvqptkc4yb8UX0AwfDmJpaR/0sWz+BUmqSVAf7c8JkUgnN8YLspb4a/N6NhTyMAmdyQ7Q==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [darwin]
'@rolldown/binding-freebsd-x64@1.2.6':
resolution: {integrity: sha512-h1wG6Y6K3JlRswxsI64qQJqBAy4vrLuHgRbc8CZMGSWTOFRY6ghMApM1NKzB2I0n5xV1fjkE18SuVl2QpLeNpA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [freebsd]
'@rolldown/binding-linux-arm-gnueabihf@1.2.6':
resolution: {integrity: sha512-tbCiqub0q2MVWJKgF5PoAlNWCtQydiOYSLIkd8sByqK/6MMYLJRcSXSYodqYtd0O+Fw7QaVmKKlS4oL94YRZ0w==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
'@rolldown/binding-linux-arm64-gnu@1.2.6':
resolution: {integrity: sha512-oxK9+baEBPhZG5HB4URY+uU04zJWeZlH6Tb9rB5DK4DF9XR1uXNLXt5Q5ZsugTKayNCNLhkcwz/ye74hRI98dg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-arm64-musl@1.2.6':
resolution: {integrity: sha512-muWCk27FVBEZtv0MsK8gnfSmgczA8KQ0uRVJbTABKhkRfQc38aUrcb7fhi3BNiyseFmgcRsoMfQsSNJ+DbZdSw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rolldown/binding-linux-ppc64-gnu@1.2.6':
resolution: {integrity: sha512-eWDoSfU7Co2qj3vgB3Dt4lj1mG6CoWbcJQkRMP3XJplyCMtuaq3LHvPFjS9QIPvMGWVadJC04Xiy0IdcVPtnwQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-s390x-gnu@1.2.6':
resolution: {integrity: sha512-2bWNjRSIayvupRKxXUY2tWG9fYdoUlTqWywHRvE8Eq3GvuQ+f2HeIkve697fIt+IQs/PV8yFsdWuhp1aJ1PdnA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-x64-gnu@1.2.6':
resolution: {integrity: sha512-KekI0gS0wLxe1UBSQSjenBVwou/JkcQPDzBPICGZjxUv9k3RteHDPBQaiOicZUFKRIH2wKEimGwVpnJsbPzu7w==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-x64-musl@1.2.6':
resolution: {integrity: sha512-TvtPnfVr+HtyGiDmPK4VWmlNm7QhNNAcK5Q9A7aOXsI8545yCyaoMaicXrFZ72JzeYjaUVk7yT243zT0jzjFKQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@rolldown/binding-openharmony-arm64@1.2.6':
resolution: {integrity: sha512-iOo0VEay2XFhaCcH0sps5XIimkSuOnNaZrf6+ZkoSOQBJPKNU48RkmJv0/lSpipexu5P+ouFgafe5IGr/DiQfg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [openharmony]
'@rolldown/binding-win32-arm64-msvc@1.2.6':
resolution: {integrity: sha512-y5NTmmasMS455JlOCO4ZM9krIchv3Mvm1crL1iUPGOPgEzSkves9n0SdC5Sjz6+qWDFhd8/JpfWMH8NSWNHe+A==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [win32]
'@rolldown/binding-win32-x64-msvc@1.2.6':
resolution: {integrity: sha512-np8iZSLfXlAD4kWhiyq/u0Yt8oZDtRQ8lGhQaCXo2rl37KNjeU0GjJuwr4P3oeZ++ROfofsKNBqR5LTO8aXyWQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [win32]
'@rolldown/pluginutils@1.0.1':
resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==}
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
echarts@5.5.1:
resolution: {integrity: sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==}
fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
peerDependencies:
picomatch: ^3 || ^4
peerDependenciesMeta:
picomatch:
optional: true
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
lightningcss-android-arm64@1.33.0:
resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [android]
lightningcss-darwin-arm64@1.33.0:
resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [darwin]
lightningcss-darwin-x64@1.33.0:
resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [darwin]
lightningcss-freebsd-x64@1.33.0:
resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [freebsd]
lightningcss-linux-arm-gnueabihf@1.33.0:
resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm]
os: [linux]
lightningcss-linux-arm64-gnu@1.33.0:
resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
lightningcss-linux-arm64-musl@1.33.0:
resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
lightningcss-linux-x64-gnu@1.33.0:
resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
lightningcss-linux-x64-musl@1.33.0:
resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
lightningcss-win32-arm64-msvc@1.33.0:
resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [win32]
lightningcss-win32-x64-msvc@1.33.0:
resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [win32]
lightningcss@1.33.0:
resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==}
engines: {node: '>= 12.0.0'}
nanoid@3.3.18:
resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
picomatch@4.0.7:
resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==}
engines: {node: '>=12'}
postcss@8.5.26:
resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==}
engines: {node: ^10 || ^12 || >=14}
rolldown@1.2.6:
resolution: {integrity: sha512-vMM4q3aixf46GiF1Kok8jDPFsEpXgFWGjUHXNkNHNm+Y2adXAG2dbX91jkti3i0ZRsOlcmbuzAz1poObSHCmUA==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
tinyglobby@0.2.17:
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
engines: {node: '>=12.0.0'}
tslib@2.3.0:
resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
vite@8.2.2:
resolution: {integrity: sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
'@types/node': ^20.19.0 || >=22.12.0
'@vitejs/devtools': ^0.4.0 || ^0.5.0
esbuild: ^0.27.0 || ^0.28.0
jiti: '>=1.21.0'
less: ^4.0.0
sass: ^1.70.0
sass-embedded: ^1.70.0
stylus: '>=0.54.8'
sugarss: ^5.0.0
terser: ^5.16.0
tsx: ^4.8.1
yaml: ^2.4.2
peerDependenciesMeta:
'@types/node':
optional: true
'@vitejs/devtools':
optional: true
esbuild:
optional: true
jiti:
optional: true
less:
optional: true
sass:
optional: true
sass-embedded:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
tsx:
optional: true
yaml:
optional: true
zrender@5.6.0:
resolution: {integrity: sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==}
snapshots:
'@oxc-project/types@0.147.0': {}
'@rolldown/binding-android-arm-eabi@1.2.6':
optional: true
'@rolldown/binding-android-arm64@1.2.6':
optional: true
'@rolldown/binding-darwin-arm64@1.2.6':
optional: true
'@rolldown/binding-darwin-x64@1.2.6':
optional: true
'@rolldown/binding-freebsd-x64@1.2.6':
optional: true
'@rolldown/binding-linux-arm-gnueabihf@1.2.6':
optional: true
'@rolldown/binding-linux-arm64-gnu@1.2.6':
optional: true
'@rolldown/binding-linux-arm64-musl@1.2.6':
optional: true
'@rolldown/binding-linux-ppc64-gnu@1.2.6':
optional: true
'@rolldown/binding-linux-s390x-gnu@1.2.6':
optional: true
'@rolldown/binding-linux-x64-gnu@1.2.6':
optional: true
'@rolldown/binding-linux-x64-musl@1.2.6':
optional: true
'@rolldown/binding-openharmony-arm64@1.2.6':
optional: true
'@rolldown/binding-win32-arm64-msvc@1.2.6':
optional: true
'@rolldown/binding-win32-x64-msvc@1.2.6':
optional: true
'@rolldown/pluginutils@1.0.1': {}
detect-libc@2.1.2: {}
echarts@5.5.1:
dependencies:
tslib: 2.3.0
zrender: 5.6.0
fdir@6.5.0(picomatch@4.0.7):
optionalDependencies:
picomatch: 4.0.7
fsevents@2.3.3:
optional: true
lightningcss-android-arm64@1.33.0:
optional: true
lightningcss-darwin-arm64@1.33.0:
optional: true
lightningcss-darwin-x64@1.33.0:
optional: true
lightningcss-freebsd-x64@1.33.0:
optional: true
lightningcss-linux-arm-gnueabihf@1.33.0:
optional: true
lightningcss-linux-arm64-gnu@1.33.0:
optional: true
lightningcss-linux-arm64-musl@1.33.0:
optional: true
lightningcss-linux-x64-gnu@1.33.0:
optional: true
lightningcss-linux-x64-musl@1.33.0:
optional: true
lightningcss-win32-arm64-msvc@1.33.0:
optional: true
lightningcss-win32-x64-msvc@1.33.0:
optional: true
lightningcss@1.33.0:
dependencies:
detect-libc: 2.1.2
optionalDependencies:
lightningcss-android-arm64: 1.33.0
lightningcss-darwin-arm64: 1.33.0
lightningcss-darwin-x64: 1.33.0
lightningcss-freebsd-x64: 1.33.0
lightningcss-linux-arm-gnueabihf: 1.33.0
lightningcss-linux-arm64-gnu: 1.33.0
lightningcss-linux-arm64-musl: 1.33.0
lightningcss-linux-x64-gnu: 1.33.0
lightningcss-linux-x64-musl: 1.33.0
lightningcss-win32-arm64-msvc: 1.33.0
lightningcss-win32-x64-msvc: 1.33.0
nanoid@3.3.18: {}
picocolors@1.1.1: {}
picomatch@4.0.7: {}
postcss@8.5.26:
dependencies:
nanoid: 3.3.18
picocolors: 1.1.1
source-map-js: 1.2.1
rolldown@1.2.6:
dependencies:
'@oxc-project/types': 0.147.0
'@rolldown/pluginutils': 1.0.1
optionalDependencies:
'@rolldown/binding-android-arm-eabi': 1.2.6
'@rolldown/binding-android-arm64': 1.2.6
'@rolldown/binding-darwin-arm64': 1.2.6
'@rolldown/binding-darwin-x64': 1.2.6
'@rolldown/binding-freebsd-x64': 1.2.6
'@rolldown/binding-linux-arm-gnueabihf': 1.2.6
'@rolldown/binding-linux-arm64-gnu': 1.2.6
'@rolldown/binding-linux-arm64-musl': 1.2.6
'@rolldown/binding-linux-ppc64-gnu': 1.2.6
'@rolldown/binding-linux-s390x-gnu': 1.2.6
'@rolldown/binding-linux-x64-gnu': 1.2.6
'@rolldown/binding-linux-x64-musl': 1.2.6
'@rolldown/binding-openharmony-arm64': 1.2.6
'@rolldown/binding-win32-arm64-msvc': 1.2.6
'@rolldown/binding-win32-x64-msvc': 1.2.6
source-map-js@1.2.1: {}
tinyglobby@0.2.17:
dependencies:
fdir: 6.5.0(picomatch@4.0.7)
picomatch: 4.0.7
tslib@2.3.0: {}
vite@8.2.2:
dependencies:
lightningcss: 1.33.0
picomatch: 4.0.7
postcss: 8.5.26
rolldown: 1.2.6
tinyglobby: 0.2.17
optionalDependencies:
fsevents: 2.3.3
zrender@5.6.0:
dependencies:
tslib: 2.3.0
+260
View File
@@ -0,0 +1,260 @@
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const RAW_DIR = path.join(ROOT, 'data/ppmt/raw');
const OUTPUT_DIR = path.join(ROOT, 'data/ppmt');
const FILES = {
cashflow: '泡泡玛特_现金流量表_合并报表_20260828_134646.csv',
income: '泡泡玛特_利润表_合并报表_20260828_134510.csv',
balance: '泡泡玛特_资产负债表_合并报表_20260828_134503.csv',
indicators: '泡泡玛特_财务指标_合并报表_20260828_134712.csv',
revenueComposition: '泡泡玛特_营收构成_20260828_134749.csv',
};
function clean(cell = '') {
return cell.trim().replace(/^\uFEFF/, '').replace(/^="(.*)"$/, '$1');
}
function toNumber(cell) {
const value = clean(cell).replace(/^=/, '');
if (!value) return null;
if (value.endsWith('%')) {
const percentage = Number(value.slice(0, -1));
return Number.isFinite(percentage) ? percentage / 100 : null;
}
const number = Number(value);
return Number.isFinite(number) ? number : null;
}
function readStatement(filename) {
const lines = fs.readFileSync(path.join(RAW_DIR, filename), 'utf8')
.trim()
.split(/\r?\n/)
.map((line) => line.split(','));
const dates = lines[1].slice(3).map(clean);
const values = new Map(lines.slice(2).map((cells) => [clean(cells[0]), cells.slice(3)]));
return { dates, values };
}
function row(statement, name) {
const values = statement.values.get(name);
if (!values) throw new Error(`原始报表缺少行:${name}`);
return values.map(toNumber);
}
function periodFor(date) {
const [year, month] = date.split('-');
if (month === '06') return `${year}H1`;
if (month === '12') return `${year}FY`;
throw new Error(`不支持的报表日期:${date}`);
}
function periodForRevenue(date) {
const [year, quarter] = date.split('-Q');
if (quarter === '2') return `${year}H1`;
if (quarter === '4') return `${year}FY`;
throw new Error(`不支持的营收构成期间:${date}`);
}
function readRevenueComposition(filename) {
const lines = fs.readFileSync(path.join(RAW_DIR, filename), 'utf8')
.trim()
.split(/\r?\n/)
.map((line) => line.split(','));
const dates = lines[0].slice(2).map(clean);
const channels = new Map();
let section = '';
let isTotal = false;
lines.slice(1).forEach((cells) => {
const first = clean(cells[0]);
const second = clean(cells[1]);
if (first.startsWith('按服務類型分-')) {
section = first.replace('按服務類型分-', '');
isTotal = false;
return;
}
if (first === '合計') {
section = '合計';
isTotal = true;
return;
}
if (second !== '收入' && second !== '收入同比') return;
const values = cells.slice(2).map(toNumber);
if (isTotal) channels.set(second === '收入' ? 'totalRevenue' : 'revenueYoy', values);
else if (section) channels.set(`${section}:${second}`, values);
});
return { dates, channels };
}
function writeCSV(filename, headers, rows) {
const text = [headers.join(','), ...rows.map((values) => values.join(','))].join('\n').concat('\n');
fs.writeFileSync(path.join(OUTPUT_DIR, filename), text);
}
const cashflow = readStatement(FILES.cashflow);
const income = readStatement(FILES.income);
const balance = readStatement(FILES.balance);
const indicators = readStatement(FILES.indicators);
const revenueComposition = readRevenueComposition(FILES.revenueComposition);
const operatingCashFlow = row(cashflow, '经营活动产生的现金流量净额');
const netProfit = row(income, '三、净利润');
const cashConversionRows = cashflow.dates.map((date, index) => ({
date,
operatingCashFlow: operatingCashFlow[index],
netProfit: netProfit[index],
})).filter(({ date, operatingCashFlow: cfo, netProfit: profit }) => (
date >= '2020-01-01' && date.endsWith('-12-31') && cfo !== null && profit !== null
)).map(({ date, operatingCashFlow: cfo, netProfit: profit }) => [
periodFor(date),
(cfo / 100000000).toFixed(2),
(profit / 100000000).toFixed(2),
(cfo / profit * 100).toFixed(1),
]).reverse();
writeCSV('ppmt_cash_conversion.csv', [
'period', 'operating_cash_flow', 'net_profit', 'cash_conversion',
], cashConversionRows);
const cash = row(balance, '货币资金');
const liabilities = row(balance, '二、负债合计');
const currentAssets = row(balance, '流动资产合计');
const currentLiabilities = row(balance, '流动负债合计');
const liquidityRows = balance.dates.map((date, index) => ({
date,
cash: cash[index],
liabilities: liabilities[index],
currentAssets: currentAssets[index],
currentLiabilities: currentLiabilities[index],
})).filter((item) => (
item.date >= '2020-01-01'
&& item.cash !== null
&& item.liabilities !== null
&& item.currentAssets !== null
&& item.currentLiabilities !== null
)).map((item) => [
periodFor(item.date),
(item.cash / 100000000).toFixed(2),
(item.liabilities / 100000000).toFixed(2),
((item.currentAssets - item.currentLiabilities) / 100000000).toFixed(2),
]).reverse();
writeCSV('ppmt_liquidity_working_capital.csv', [
'period', 'cash', 'total_liabilities', 'net_working_capital',
], liquidityRows);
const investingCashFlow = row(cashflow, '投资活动产生的现金流量净额');
const financingCashFlow = row(cashflow, '筹资活动产生的现金流量净额');
const netCashIncrease = row(cashflow, '现金及现金等价物的净增加额');
const cashflowCompositionRows = cashflow.dates.map((date, index) => ({
date,
operating: operatingCashFlow[index],
investing: investingCashFlow[index],
financing: financingCashFlow[index],
netIncrease: netCashIncrease[index],
})).filter((item) => (
item.date >= '2020-01-01' && item.date.endsWith('-12-31')
&& Object.values(item).every((value) => value !== null)
)).map((item) => [
periodFor(item.date),
(item.operating / 100000000).toFixed(2),
(item.investing / 100000000).toFixed(2),
(item.financing / 100000000).toFixed(2),
(item.netIncrease / 100000000).toFixed(2),
]).reverse();
writeCSV('ppmt_cashflow_composition.csv', [
'period', 'operating_cash_flow', 'investing_cash_flow', 'financing_cash_flow', 'net_cash_increase',
], cashflowCompositionRows);
const inventoryDays = row(indicators, '存货周转天数');
const receivableDays = row(indicators, '应收票据和应收账款周转天数');
const payableDays = row(indicators, '应付票据和应付账款周转天数');
const workingCapitalDaysRows = indicators.dates.map((date, index) => ({
date,
inventory: inventoryDays[index],
receivable: receivableDays[index],
payable: payableDays[index],
})).filter((item) => (
item.date >= '2020-01-01'
&& item.inventory !== null && item.receivable !== null && item.payable !== null
)).map((item) => [
periodFor(item.date),
item.inventory.toFixed(1),
item.receivable.toFixed(1),
item.payable.toFixed(1),
(item.inventory + item.receivable - item.payable).toFixed(1),
]).reverse();
writeCSV('ppmt_working_capital_days.csv', [
'period', 'inventory_days', 'receivable_days', 'payable_days', 'cash_conversion_cycle',
], workingCapitalDaysRows);
const revenue = row(income, '一、营业总收入');
const salesExpense = row(income, '减:销售费用');
const adminExpense = row(income, '管理费用');
const financialExpense = row(income, '财务费用');
const fourExpenseRate = row(income, '四项费用率');
const expenseRows = income.dates.map((date, index) => ({
date,
revenue: revenue[index],
sales: salesExpense[index],
admin: adminExpense[index],
financial: financialExpense[index],
rate: fourExpenseRate[index],
})).filter((item) => (
item.date >= '2020-01-01' && item.date.endsWith('-12-31')
&& Object.values(item).every((value) => value !== null)
)).map((item) => [
periodFor(item.date),
(item.sales / 100000000).toFixed(2),
(item.admin / 100000000).toFixed(2),
(item.financial / 100000000).toFixed(2),
(item.rate * 100).toFixed(1),
]).reverse();
writeCSV('ppmt_expense_structure.csv', [
'period', 'sales_expense', 'admin_expense', 'financial_expense', 'four_expense_rate',
], expenseRows);
const channelKeys = [
['零售店銷售收益', 'retail'],
['線上銷售收益', 'online'],
['機器人商店銷售收益', 'robot_store'],
['批發收益', 'wholesale'],
];
const channelRows = revenueComposition.dates.map((date, index) => {
const values = Object.fromEntries(channelKeys.map(([name, key]) => [
key,
revenueComposition.channels.get(`${name}:收入`)?.[index] ?? null,
]));
return {
date,
...values,
totalRevenue: revenueComposition.channels.get('totalRevenue')?.[index] ?? null,
revenueYoy: revenueComposition.channels.get('revenueYoy')?.[index] ?? null,
};
}).filter((item) => (
item.date >= '2020-Q2'
&& Object.values(item).every((value) => value !== null)
)).map((item) => [
periodForRevenue(item.date),
(item.retail / 100000000).toFixed(2),
(item.online / 100000000).toFixed(2),
(item.robot_store / 100000000).toFixed(2),
(item.wholesale / 100000000).toFixed(2),
(item.totalRevenue / 100000000).toFixed(2),
(item.revenueYoy * 100).toFixed(1),
]).reverse();
writeCSV('ppmt_channel_revenue.csv', [
'period', 'retail', 'online', 'robot_store', 'wholesale', 'total_revenue', 'revenue_yoy',
], channelRows);
console.log(`已生成现金转换 ${cashConversionRows.length}、流动性 ${liquidityRows.length}、现金流 ${cashflowCompositionRows.length}、营运资本 ${workingCapitalDaysRows.length}、费用 ${expenseRows.length}、渠道 ${channelRows.length} 行数据。`);
Executable
+27
View File
@@ -0,0 +1,27 @@
#!/bin/zsh
set -e
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
PORT=8899
LOG_PATH="/tmp/smart-fin-chart-server.log"
URL="http://localhost:${PORT}/"
cd "$PROJECT_DIR"
if ! command -v pnpm >/dev/null 2>&1; then
echo "未找到 pnpm,请先安装 pnpm 后重试。"
exit 1
fi
if lsof -n -P -iTCP:"$PORT" -sTCP:LISTEN >/dev/null 2>&1; then
echo "端口 ${PORT} 已有服务,直接打开现有页面。"
else
nohup pnpm start >"$LOG_PATH" 2>&1 &
SERVER_PID=$!
echo "服务已启动(PID: ${SERVER_PID}),日志:${LOG_PATH}"
sleep 1
fi
open "$URL"
echo "已打开:${URL}"
+19 -6
View File
@@ -14,11 +14,23 @@ const STYLE_RULES = {
}; };
function entriesFromManifest(value) { function entriesFromManifest(value) {
const ids = new Set();
const entries = []; const entries = [];
value.companies.forEach((company) => company.tabs.forEach((tab) => (tab.charts || []).forEach((chart) => { value.companies.forEach((company) => {
if (!chart.id || ids.has(chart.id)) throw new Error(`manifest 图表 id 重复或为空:${chart.id || '(空)'}`); if (!company.chartCatalog || !Array.isArray(company.tabs)) {
ids.add(chart.id); throw new Error(`公司 ${company.id || '(空)'} 缺少 chartCatalog 或 tabs`);
}
const chartIds = Object.keys(company.chartCatalog);
company.tabs.forEach((tab) => {
if (!Array.isArray(tab.chartRefs)) throw new Error(`栏目 ${tab.id} 缺少 chartRefs`);
const refs = new Set();
tab.chartRefs.forEach((chartId) => {
if (!company.chartCatalog[chartId]) throw new Error(`栏目 ${tab.id} 引用了不存在的图表:${chartId}`);
if (refs.has(chartId)) throw new Error(`栏目 ${tab.id} 重复引用图表:${chartId}`);
refs.add(chartId);
});
});
chartIds.forEach((id) => {
const chart = company.chartCatalog[id];
if (!getChartBuilder(chart.key)) throw new Error(`manifest 图表 ${chart.id} 引用了未注册 key:${chart.key}`); if (!getChartBuilder(chart.key)) throw new Error(`manifest 图表 ${chart.id} 引用了未注册 key:${chart.key}`);
if (Boolean(chart.csv) === Boolean(chart.views)) { if (Boolean(chart.csv) === Boolean(chart.views)) {
throw new Error(`manifest 图表 ${chart.id} 必须声明 csv 或 views 之一`); throw new Error(`manifest 图表 ${chart.id} 必须声明 csv 或 views 之一`);
@@ -32,8 +44,9 @@ function entriesFromManifest(value) {
if (!sources.every(({ csv }) => typeof csv === 'string' && csv.length > 0)) { if (!sources.every(({ csv }) => typeof csv === 'string' && csv.length > 0)) {
throw new Error(`manifest 图表 ${chart.id} 缺少 CSV`); throw new Error(`manifest 图表 ${chart.id} 缺少 CSV`);
} }
sources.forEach(({ view, csv }) => entries.push({ ...chart, company, tab, view, csv })); sources.forEach(({ view, csv }) => entries.push({ ...chart, id, company, view, csv }));
}))); });
});
return entries; return entries;
} }
+54
View File
@@ -0,0 +1,54 @@
import { defineConfig } from 'vite';
import { copyFileSync, cpSync } from 'node:fs';
import { extname, relative, resolve } from 'node:path';
function reloadFinancialData() {
return {
name: 'reload-financial-data',
configureServer(server) {
server.watcher.add(['manifest.json', 'data/**/*.csv']);
server.watcher.on('change', (file) => {
if (file.endsWith('.csv') || file.endsWith('/manifest.json')) {
server.ws.send({ type: 'full-reload', path: '*' });
}
});
},
};
}
function copyRuntimeData() {
let outputDirectory;
const dataDirectory = resolve('data');
return {
name: 'copy-runtime-data',
apply: 'build',
configResolved(config) {
outputDirectory = resolve(config.build.outDir);
},
closeBundle() {
copyFileSync('manifest.json', resolve(outputDirectory, 'manifest.json'));
cpSync(dataDirectory, resolve(outputDirectory, 'data'), {
recursive: true,
filter: (source) => {
const path = relative(dataDirectory, source);
return path === '' || extname(path) === '' || path.endsWith('.csv');
},
});
},
};
}
export default defineConfig({
server: {
host: '127.0.0.1',
port: 8899,
strictPort: true,
},
preview: {
host: '127.0.0.1',
port: 8899,
strictPort: true,
},
plugins: [reloadFinancialData(), copyRuntimeData()],
});