Compare commits
4 Commits
f0a785f8f8
...
7aab3d7134
| Author | SHA1 | Date | |
|---|---|---|---|
| 7aab3d7134 | |||
| 5ecc554aaa | |||
| 2fe0f245e3 | |||
| 25781f2ee6 |
@@ -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` 数组加一行,否则不会被测到。
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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; }
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
stage,mid_price,onshore_spot
|
||||||
|
年初,7.0288,6.9890
|
||||||
|
2026-06-30,6.8109,6.7852
|
||||||
|
@@ -0,0 +1,10 @@
|
|||||||
|
period,non_gaap_net_profit,gross_profit,revenue,cost
|
||||||
|
22H1,-13.5,22.7,33.9,50.8
|
||||||
|
22年,-42.7,4.3,2.8,13.5
|
||||||
|
23H1,42.3,19.3,12.9,10.6
|
||||||
|
23年,107.7,45.5,36.5,24.2
|
||||||
|
24H1,90.2,71.9,62.0,46.9
|
||||||
|
24年,185.7,125.4,106.9,77.6
|
||||||
|
25H1,362.8,234.4,204.4,151.1
|
||||||
|
25年,284.5,207.4,184.7,139.0
|
||||||
|
26H1,9.2,26.5,20.5,26.5
|
||||||
|
@@ -0,0 +1,10 @@
|
|||||||
|
period,inventory,revenue,turnover_days
|
||||||
|
22H1,9.6,23.6,160
|
||||||
|
22年,8.7,46.2,154
|
||||||
|
23H1,7.6,28.1,132
|
||||||
|
23年,9.1,63.0,133
|
||||||
|
24H1,9.2,45.6,101
|
||||||
|
24年,15.2,130.4,102
|
||||||
|
25H1,22.7,138.8,84
|
||||||
|
25年,54.7,371.2,123
|
||||||
|
26H1,61.0,371.7,201
|
||||||
|
@@ -0,0 +1,10 @@
|
|||||||
|
period,gross_margin,sales_expense_rate,admin_expense_rate,non_gaap_net_margin
|
||||||
|
22H1,58.1,29.4,13.7,15.9
|
||||||
|
22年,57.5,31.9,14.9,12.4
|
||||||
|
23H1,60.4,31.2,11.8,19.0
|
||||||
|
23年,61.3,31.8,11.2,18.9
|
||||||
|
24H1,64.0,29.7,9.5,22.3
|
||||||
|
24年,66.8,28.0,7.3,26.1
|
||||||
|
25H1,70.3,23.0,5.6,33.9
|
||||||
|
25年,72.1,21.8,4.8,35.2
|
||||||
|
26H1,69.7,22.9,5.4,30.0
|
||||||
|
@@ -0,0 +1,13 @@
|
|||||||
|
period,total_assets,cash_like,receivables,investment,operating_assets
|
||||||
|
2020H1,17.89,8.22,0.41,0.46,8.8
|
||||||
|
2020H2,69.71,56.8,0.78,0.67,11.45
|
||||||
|
2021H1,76.41,57.67,1.35,1.78,15.6
|
||||||
|
2021H2,83.24,52.65,1.71,4.11,24.77
|
||||||
|
2022H1,85.9,51.63,1.32,5.53,27.42
|
||||||
|
2022H2,85.8,50.42,1.94,5.55,27.89
|
||||||
|
2023H1,90.65,55.4,2.09,5.75,27.42
|
||||||
|
2023H2,99.69,59.63,3.21,5.87,30.97
|
||||||
|
2024H1,110.17,70.1,2.64,5.5,31.93
|
||||||
|
2024H2,148.71,96.2,4.78,5.6,42.13
|
||||||
|
2025H1,213.57,137.66,9.72,5.4,60.79
|
||||||
|
2025H2,321.01,172.25,9.21,4.95,132.04
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
period,plush,figure,mega,other
|
||||||
|
品类,毛绒,手办,MEGA,衍生品及其他
|
||||||
|
2024H1,4.46105,26.56781,5.8613,8.68815
|
||||||
|
2024H2,23.85973,42.79306,10.9765,7.16989
|
||||||
|
2025H1,61.39236,51.75875,10.07147,15.54018
|
||||||
|
2025H2,125.68838,68.46694,9.08935,29.19309
|
||||||
|
@@ -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
|
||||||
|
@@ -0,0 +1,14 @@
|
|||||||
|
period,core_margin
|
||||||
|
2020H1,22.6
|
||||||
|
2020H2,27.2
|
||||||
|
2021H1,25.8
|
||||||
|
2021H2,24.4
|
||||||
|
2022H1,15.0
|
||||||
|
2022H2,10.8
|
||||||
|
2023H1,17.4
|
||||||
|
2023H2,18.3
|
||||||
|
2024H1,24.8
|
||||||
|
2024H2,31.5
|
||||||
|
2025H1,41.8
|
||||||
|
2025H2,45.6
|
||||||
|
2026H1,41.4
|
||||||
|
@@ -0,0 +1,14 @@
|
|||||||
|
period,core_profit,yoy
|
||||||
|
2020H1,1.9,25.0
|
||||||
|
2020H2,6.8,17.1
|
||||||
|
2021H1,4.6,147.0
|
||||||
|
2021H2,11.0,60.1
|
||||||
|
2022H1,3.5,-22.5
|
||||||
|
2022H2,5.0,-54.5
|
||||||
|
2023H1,4.9,38.1
|
||||||
|
2023H2,11.5,131.3
|
||||||
|
2024H1,11.3,131.5
|
||||||
|
2024H2,41.1,256.9
|
||||||
|
2025H1,58.0,412.2
|
||||||
|
2025H2,169.1,311.4
|
||||||
|
2026H1,71.1,22.6
|
||||||
|
@@ -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
|
||||||
|
@@ -0,0 +1,14 @@
|
|||||||
|
period,non_gaap_net_profit,gross_profit,revenue,cost
|
||||||
|
2020H1,23.7,61.0,50.6,33.3
|
||||||
|
2020H2,16.2,46.2,49.3,55.0
|
||||||
|
2021H1,154.6,109.6,116.7,131.0
|
||||||
|
2021H2,69.5,73.1,78.7,88.5
|
||||||
|
2022H1,-13.6,22.6,33.1,50.6
|
||||||
|
2022H2,-42.7,-3.8,2.8,13.3
|
||||||
|
2023H1,42.3,23.9,19.3,12.9
|
||||||
|
2023H2,107.5,45.5,36.5,24.1
|
||||||
|
2024H1,90.3,71.9,62.0,47.0
|
||||||
|
2024H2,185.7,125.4,106.9,77.7
|
||||||
|
2025H1,362.7,234.4,204.4,151.1
|
||||||
|
2025H2,284.5,207.4,184.7,139.1
|
||||||
|
2026H1,9.5,22.6,23.8,26.5
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
ip,2021H1,2021H2,2022H1,2022H2,2023H1,2023H2,2024H1,2024H2,2025H1,2025H2
|
||||||
|
THE MONSTERS,1.45789,1.58642,1.34887,1.28115,1.59829,2.08042,6.26806,24.13858,48.14017,93.47047
|
||||||
|
MOLLY,2.039,5.01198,4.04347,3.97808,4.11433,6.08872,7.8216,13.11012,13.57248,15.39836
|
||||||
|
SKULLPANDA,1.83042,4.12214,4.61766,3.89845,5.26383,4.98405,5.74562,7.337,12.20504,23.19038
|
||||||
|
CRYBABY,0,0,0,0,0.26603,0.44546,3.49382,8.15503,12.1817,17.10836
|
||||||
|
DIMOO,2.04993,3.61742,2.98442,2.79506,3.62032,3.75634,3.77838,5.31723,11.05135,16.7156
|
||||||
|
HIRONO-小野,0,0,0.37538,1.0323,1.09352,2.41849,2.45247,4.81278,7.2842,10.06815
|
||||||
|
HACIPUPU,0,0,0,0.42797,0.69171,0.82587,0.94991,1.29297,3.32122,5.15899
|
||||||
|
星星人,0,0,0,0,0,0,0,0,3.89363,16.66217
|
||||||
|
@@ -0,0 +1,12 @@
|
|||||||
|
period,revenue,yoy
|
||||||
|
2021H1,16.27,
|
||||||
|
2021H2,25.59,
|
||||||
|
2022H1,22.24,36.7
|
||||||
|
2022H2,21.30,-16.8
|
||||||
|
2023H1,26.54,19.3
|
||||||
|
2023H2,32.79,53.9
|
||||||
|
2024H1,39.31,48.1
|
||||||
|
2024H2,60.66,85.0
|
||||||
|
2025H1,90.62,130.5
|
||||||
|
2025H2,138.97,129.1
|
||||||
|
2026H1,127.19,40.4
|
||||||
|
@@ -0,0 +1,12 @@
|
|||||||
|
ip,revenue
|
||||||
|
THE MONSTERS,44.5
|
||||||
|
星星人,26.5
|
||||||
|
授权IP(非独家),17.7
|
||||||
|
CRYBABY,16.3
|
||||||
|
DIMOO,16.2
|
||||||
|
SKULLPANDA,15.5
|
||||||
|
其他艺术家IP,11.7
|
||||||
|
HIRONO-小野,10.1
|
||||||
|
MOLLY,9.0
|
||||||
|
Zsiga,3.0
|
||||||
|
外采及其他,1.1
|
||||||
|
@@ -0,0 +1,12 @@
|
|||||||
|
ip,2021H1,2021H2,2022H1,2022H2,2023H1,2023H2,2024H1,2024H2,2025H1,2025H2,2026H1
|
||||||
|
THE MONSTERS,1.46,1.59,1.35,1.28,1.60,2.08,6.27,24.14,48.14,93.47,44.54
|
||||||
|
星星人,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,3.89,16.66,26.50
|
||||||
|
授权IP(非独家),2.88,3.33,3.01,2.50,4.22,6.14,6.95,9.06,15.25,18.57,17.71
|
||||||
|
CRYBABY,0.00,0.00,0.00,0.00,0.27,0.45,3.49,8.16,12.18,17.11,16.33
|
||||||
|
DIMOO,2.05,3.62,2.98,2.80,3.62,3.76,3.78,5.32,11.05,16.72,16.19
|
||||||
|
SKULLPANDA,1.83,4.12,4.62,3.90,5.26,4.98,5.75,7.34,12.21,23.19,15.51
|
||||||
|
其他艺术家IP,1.43,2.47,4.37,3.00,4.54,5.44,6.37,7.72,10.64,14.00,11.74
|
||||||
|
HIRONO-小野,0.00,0.00,0.38,1.03,1.09,2.42,2.45,4.81,7.28,10.07,10.09
|
||||||
|
MOLLY,2.04,5.01,4.04,3.98,4.11,6.09,7.82,13.11,13.57,15.40,9.01
|
||||||
|
其他IP(含已停单列),4.14,4.51,0.81,1.86,1.15,1.37,0.95,3.74,3.32,5.16,2.98
|
||||||
|
外采及其他,1.90,2.53,2.03,2.24,2.28,2.15,1.75,1.41,1.23,2.09,1.12
|
||||||
|
@@ -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
|
||||||
|
@@ -0,0 +1,13 @@
|
|||||||
|
item,base,value,type
|
||||||
|
2025H1净利率,0,33.74,total
|
||||||
|
毛利率变化,33.23,0.51,neg
|
||||||
|
销售费用率优化,33.23,0.09,pos
|
||||||
|
管理费用率优化,33.33,0.11,pos
|
||||||
|
财务费用恶化,33.03,0.41,neg
|
||||||
|
投资收益减少,32.9,0.13,neg
|
||||||
|
公允价值变动,32.9,0.16,pos
|
||||||
|
资产减值,33.07,0.01,pos
|
||||||
|
其他业务净额,33.07,0.01,neg
|
||||||
|
其他收益净额(主要汇兑),29.96,3.11,neg
|
||||||
|
税率上升,29.70,0.26,neg
|
||||||
|
2026H1净利率,0,29.7,total
|
||||||
|
@@ -0,0 +1,14 @@
|
|||||||
|
period,gross_margin,sales_expense_rate,admin_expense_rate,non_gaap_net_margin
|
||||||
|
2020H1,65.23,27.27,15.33,17.24
|
||||||
|
2020H2,63.42,25.07,11.14,23.52
|
||||||
|
2021H1,63.02,23.68,13.52,24.53
|
||||||
|
2021H2,61.43,24.63,12.41,22.31
|
||||||
|
2022H1,58.1,29.39,13.68,15.94
|
||||||
|
2022H2,57.49,31.85,14.86,12.43
|
||||||
|
2023H1,60.36,31.21,11.77,19.01
|
||||||
|
2023H2,61.32,31.82,11.23,18.9
|
||||||
|
2024H1,64.05,29.69,9.53,22.33
|
||||||
|
2024H2,66.79,28.0,7.26,26.1
|
||||||
|
2025H1,70.34,23.01,5.55,33.94
|
||||||
|
2025H2,72.1,21.77,4.77,35.25
|
||||||
|
2026H1,69.68,22.89,5.4,30.02
|
||||||
|
@@ -0,0 +1,18 @@
|
|||||||
|
period,revenue,gross_profit,gross_margin,net_profit
|
||||||
|
2026H1,171.73,119.66,69.67999999999999,50.38
|
||||||
|
2025H2,371.2,267.65,72.1,127.76
|
||||||
|
2025H1,138.76,97.61,70.34,45.74
|
||||||
|
2024H2,130.38,87.08,66.79,31.25
|
||||||
|
2024H1,45.58,29.19,64.05,9.21
|
||||||
|
2023H2,63.01,38.64,61.31999999999999,10.82
|
||||||
|
2023H1,28.14,16.98,60.36,4.77
|
||||||
|
2022H2,46.17,26.55,57.489999999999995,4.76
|
||||||
|
2022H1,23.59,13.7,58.099999999999994,3.33
|
||||||
|
2021H2,44.91,27.59,61.42999999999999,8.54
|
||||||
|
2021H1,17.73,11.17,63.019999999999996,3.59
|
||||||
|
2020H2,25.13,15.94,63.42,5.24
|
||||||
|
2020H1,8.18,5.33,65.23,1.41
|
||||||
|
2019H2,16.83,10.9,64.77000000000001,4.51
|
||||||
|
2019H1,5.43,3.31,60.84,1.14
|
||||||
|
2018H2,5.15,2.98,57.92,1
|
||||||
|
2017H2,1.58,0.75,47.61,0.02
|
||||||
|
@@ -0,0 +1,16 @@
|
|||||||
|
region,channel,value
|
||||||
|
中国,线下渠道,68.69
|
||||||
|
中国,线上渠道,47.79
|
||||||
|
中国,批发及其他,5.53
|
||||||
|
海外,线下渠道,31.94
|
||||||
|
海外,线上渠道,14.34
|
||||||
|
海外,批发及其他,3.45
|
||||||
|
亚太,线下渠道,17.73
|
||||||
|
亚太,线上渠道,6.44
|
||||||
|
亚太,批发及其他,1.59
|
||||||
|
美洲,线下渠道,10.07
|
||||||
|
美洲,线上渠道,7.22
|
||||||
|
美洲,批发及其他,1.62
|
||||||
|
欧洲及其他,线下渠道,4.14
|
||||||
|
欧洲及其他,线上渠道,0.68
|
||||||
|
欧洲及其他,批发及其他,0.24
|
||||||
|
@@ -0,0 +1,16 @@
|
|||||||
|
region,channel,yoy
|
||||||
|
中国,线下渠道,35.1
|
||||||
|
中国,线上渠道,62.7
|
||||||
|
中国,批发及其他,111.6
|
||||||
|
海外,线下渠道,20.8
|
||||||
|
海外,线上渠道,-44.0
|
||||||
|
海外,批发及其他,-11.1
|
||||||
|
亚太,线下渠道,16.2
|
||||||
|
亚太,线上渠道,-39.8
|
||||||
|
亚太,批发及其他,-37.9
|
||||||
|
美洲,线下渠道,19.5
|
||||||
|
美洲,线上渠道,-45.6
|
||||||
|
美洲,批发及其他,68.9
|
||||||
|
欧洲及其他,线下渠道,49.7
|
||||||
|
欧洲及其他,线上渠道,-58.8
|
||||||
|
欧洲及其他,批发及其他,-33.7
|
||||||
|
@@ -0,0 +1,82 @@
|
|||||||
|
period,region,channel,yoy
|
||||||
|
2022H1,中国,线下渠道,21.8
|
||||||
|
2022H1,中国,线上渠道,46.8
|
||||||
|
2022H1,中国,批发及其他,-12.5
|
||||||
|
2022H1,海外,线下渠道,1065.1
|
||||||
|
2022H1,海外,线上渠道,190.5
|
||||||
|
2022H1,海外,批发及其他,86.7
|
||||||
|
2022H2,中国,线下渠道,-21.3
|
||||||
|
2022H2,中国,线上渠道,-28.7
|
||||||
|
2022H2,中国,批发及其他,-15.9
|
||||||
|
2022H2,海外,线下渠道,5857.0
|
||||||
|
2022H2,海外,线上渠道,89.9
|
||||||
|
2022H2,海外,批发及其他,39.8
|
||||||
|
2023H1,中国,线下渠道,32.4
|
||||||
|
2023H1,中国,线上渠道,-16.4
|
||||||
|
2023H1,中国,批发及其他,32.3
|
||||||
|
2023H1,海外,线下渠道,392.4
|
||||||
|
2023H1,海外,线上渠道,63.1
|
||||||
|
2023H1,海外,批发及其他,54.6
|
||||||
|
2023H2,中国,线下渠道,61.9
|
||||||
|
2023H2,中国,线上渠道,4.8
|
||||||
|
2023H2,中国,批发及其他,141.7
|
||||||
|
2023H2,海外,线下渠道,301.3
|
||||||
|
2023H2,海外,线上渠道,78.7
|
||||||
|
2023H2,海外,批发及其他,9.0
|
||||||
|
2024H1,中国,线下渠道,61.4
|
||||||
|
2024H1,中国,线上渠道,15.1
|
||||||
|
2024H1,中国,批发及其他,40.5
|
||||||
|
2024H1,海外,线下渠道,238.1
|
||||||
|
2024H1,海外,线上渠道,323.5
|
||||||
|
2024H1,海外,批发及其他,18.5
|
||||||
|
2024H2,中国,线下渠道,90.8
|
||||||
|
2024H2,中国,线上渠道,105.1
|
||||||
|
2024H2,中国,批发及其他,60.5
|
||||||
|
2024H2,海外,线下渠道,256.3
|
||||||
|
2024H2,海外,线上渠道,1062.1
|
||||||
|
2024H2,海外,批发及其他,155.4
|
||||||
|
2025H1,中国,线下渠道,117.1
|
||||||
|
2025H1,中国,线上渠道,212.2
|
||||||
|
2025H1,中国,批发及其他,9.4
|
||||||
|
2025H1,海外,线下渠道,310.9
|
||||||
|
2025H1,海外,线上渠道,964.8
|
||||||
|
2025H1,海外,批发及其他,154.7
|
||||||
|
2025H1,亚太,线下渠道,203.5
|
||||||
|
2025H1,亚太,线上渠道,546.7
|
||||||
|
2025H1,亚太,批发及其他,98.9
|
||||||
|
2025H1,美洲,线下渠道,744.3
|
||||||
|
2025H1,美洲,线上渠道,1977.4
|
||||||
|
2025H1,美洲,批发及其他,413.5
|
||||||
|
2025H1,欧洲及其他,线下渠道,569.6
|
||||||
|
2025H1,欧洲及其他,线上渠道,1358.7
|
||||||
|
2025H1,欧洲及其他,批发及其他,624.4
|
||||||
|
2025H2,中国,线下渠道,110.5
|
||||||
|
2025H2,中国,线上渠道,205.0
|
||||||
|
2025H2,中国,批发及其他,23.4
|
||||||
|
2025H2,海外,线下渠道,204.3
|
||||||
|
2025H2,海外,线上渠道,365.0
|
||||||
|
2025H2,海外,批发及其他,23.6
|
||||||
|
2025H2,亚太,线下渠道,139.7
|
||||||
|
2025H2,亚太,线上渠道,146.0
|
||||||
|
2025H2,亚太,批发及其他,-4.5
|
||||||
|
2025H2,美洲,线下渠道,397.9
|
||||||
|
2025H2,美洲,线上渠道,907.4
|
||||||
|
2025H2,美洲,批发及其他,205.8
|
||||||
|
2025H2,欧洲及其他,线下渠道,405.4
|
||||||
|
2025H2,欧洲及其他,线上渠道,675.4
|
||||||
|
2025H2,欧洲及其他,批发及其他,86.7
|
||||||
|
2026H1,中国,线下渠道,35.1
|
||||||
|
2026H1,中国,线上渠道,62.7
|
||||||
|
2026H1,中国,批发及其他,111.6
|
||||||
|
2026H1,海外,线下渠道,20.8
|
||||||
|
2026H1,海外,线上渠道,-44.0
|
||||||
|
2026H1,海外,批发及其他,-11.1
|
||||||
|
2026H1,亚太,线下渠道,16.2
|
||||||
|
2026H1,亚太,线上渠道,-39.8
|
||||||
|
2026H1,亚太,批发及其他,-37.9
|
||||||
|
2026H1,美洲,线下渠道,19.5
|
||||||
|
2026H1,美洲,线上渠道,-45.6
|
||||||
|
2026H1,美洲,批发及其他,68.9
|
||||||
|
2026H1,欧洲及其他,线下渠道,49.7
|
||||||
|
2026H1,欧洲及其他,线上渠道,-58.8
|
||||||
|
2026H1,欧洲及其他,批发及其他,-33.7
|
||||||
|
@@ -0,0 +1,17 @@
|
|||||||
|
source,target,value
|
||||||
|
营业收入,营业成本,52.07
|
||||||
|
营业收入,毛利,119.66
|
||||||
|
毛利,销售费用,39.31
|
||||||
|
毛利,管理费用,9.27
|
||||||
|
毛利,财务费用,0.11
|
||||||
|
毛利,营业利润,70.97
|
||||||
|
营业利润,利润总额,70.97
|
||||||
|
投资收益,利润总额,0.31
|
||||||
|
公允价值变动,利润总额,0.37
|
||||||
|
其他收入,利润总额,3.03
|
||||||
|
利润总额,所得税费用,16.67
|
||||||
|
利润总额,净利润,51
|
||||||
|
净利润,少数股东损益,0.62
|
||||||
|
净利润,归母净利润,50.38
|
||||||
|
归母净利润,非GAAP归母净利润,50.38
|
||||||
|
非GAAP调整项,非GAAP归母净利润,1.1799999999999997
|
||||||
|
@@ -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
|
||||||
|
Binary file not shown.
Binary file not shown.
@@ -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)
|
||||||
|
@@ -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)
|
||||||
|
@@ -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
@@ -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
@@ -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: {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
@@ -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: {
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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'),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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',
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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
@@ -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',
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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',
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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 },
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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
@@ -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
@@ -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 自动隐藏
|
||||||
|
|||||||
@@ -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
@@ -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": "人民币汇率两种口径" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
Generated
-44
@@ -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
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+463
@@ -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
|
||||||
@@ -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
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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()],
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user