feat: 新增各渠道收入占比

This commit is contained in:
R524809
2026-09-07 17:42:45 +08:00
parent 9605228c3f
commit 44c6437b64
9 changed files with 234 additions and 3 deletions
+8
View File
@@ -0,0 +1,8 @@
period,retail,online,robot_store,wholesale,total_revenue
2024H1,57.4,25.9,8.1,8.6,45.59
2024H2,49.0,35.2,5.4,10.4,84.78
2024FY,51.9,31.9,6.4,9.8,130.37
2025H1,49.8,39.6,5.9,4.7,138.75
2025H2,44.5,47.0,3.7,4.7,232.45
2025FY,46.5,44.3,4.6,4.7,371.20
2026H1,53.1,36.2,5.5,5.2,171.74
1 period retail online robot_store wholesale total_revenue
2 2024H1 57.4 25.9 8.1 8.6 45.59
3 2024H2 49.0 35.2 5.4 10.4 84.78
4 2024FY 51.9 31.9 6.4 9.8 130.37
5 2025H1 49.8 39.6 5.9 4.7 138.75
6 2025H2 44.5 47.0 3.7 4.7 232.45
7 2025FY 46.5 44.3 4.6 4.7 371.20
8 2026H1 53.1 36.2 5.5 5.2 171.74
+3
View File
@@ -0,0 +1,3 @@
period,retail,online,robot_store,wholesale,total_revenue
2024FY,51.9,31.9,6.4,9.8,130.37
2025FY,46.5,44.3,4.6,4.7,371.20
1 period retail online robot_store wholesale total_revenue
2 2024FY 51.9 31.9 6.4 9.8 130.37
3 2025FY 46.5 44.3 4.6 4.7 371.20
@@ -0,0 +1,6 @@
period,retail,online,robot_store,wholesale,total_revenue
2024H1,57.4,25.9,8.1,8.6,45.59
2024H2,49.0,35.2,5.4,10.4,84.78
2025H1,49.8,39.6,5.9,4.7,138.75
2025H2,44.5,47.0,3.7,4.7,232.45
2026H1,53.1,36.2,5.5,5.2,171.74
1 period retail online robot_store wholesale total_revenue
2 2024H1 57.4 25.9 8.1 8.6 45.59
3 2024H2 49.0 35.2 5.4 10.4 84.78
4 2025H1 49.8 39.6 5.9 4.7 138.75
5 2025H2 44.5 47.0 3.7 4.7 232.45
6 2026H1 53.1 36.2 5.5 5.2 171.74
+81
View File
@@ -0,0 +1,81 @@
region,channel,period,revenue
中国,零售店,2024H1,20.04
中国,零售店,2024FY,46.01
中国,零售店,2025H1,44.06
中国,零售店,2025FY,100.75
中国,零售店,2026H1,60.90
中国,机器人商店,2024H1,3.38
中国,机器人商店,2024FY,7.51
中国,机器人商店,2025H1,6.78
中国,机器人商店,2025FY,13.46
中国,机器人商店,2026H1,7.79
中国,线上渠道,2024H1,9.41
中国,线上渠道,2024FY,27.72
中国,线上渠道,2025H1,29.37
中国,线上渠道,2025FY,85.22
中国,线上渠道,2026H1,47.79
中国,批发及其他,2024H1,2.39
中国,批发及其他,2024FY,7.63
中国,批发及其他,2025H1,2.61
中国,批发及其他,2025FY,9.08
中国,批发及其他,2026H1,5.53
亚太,零售店,2024H1,4.85
亚太,零售店,2024FY,16.68
亚太,零售店,2025H1,14.93
亚太,零售店,2025FY,43.19
亚太,零售店,2026H1,17.16
亚太,机器人商店,2024H1,0.17
亚太,机器人商店,2024FY,0.40
亚太,机器人商店,2025H1,0.33
亚太,机器人商店,2025FY,0.95
亚太,机器人商店,2026H1,0.57
亚太,线上渠道,2024H1,1.65
亚太,线上渠道,2024FY,9.72
亚太,线上渠道,2025H1,10.69
亚太,线上渠道,2025FY,30.53
亚太,线上渠道,2026H1,6.44
亚太,批发及其他,2024H1,1.29
亚太,批发及其他,2024FY,4.30
亚太,批发及其他,2025H1,2.56
亚太,批发及其他,2025FY,5.44
亚太,批发及其他,2026H1,1.59
美洲,零售店,2024H1,0.87
美洲,零售店,2024FY,3.41
美洲,零售店,2025H1,7.37
美洲,零售店,2025FY,20.04
美洲,零售店,2026H1,9.03
美洲,机器人商店,2024H1,0.13
美洲,机器人商店,2024FY,0.38
美洲,机器人商店,2025H1,1.05
美洲,机器人商店,2025FY,2.32
美洲,机器人商店,2026H1,1.04
美洲,线上渠道,2024H1,0.64
美洲,线上渠道,2024FY,3.64
美洲,线上渠道,2025H1,13.27
美洲,线上渠道,2025FY,43.54
美洲,线上渠道,2026H1,7.22
美洲,批发及其他,2024H1,0.19
美洲,批发及其他,2024FY,0.58
美洲,批发及其他,2025H1,0.96
美洲,批发及其他,2025FY,2.17
美洲,批发及其他,2026H1,1.62
欧洲及其他,零售店,2024H1,0.41
欧洲及其他,零售店,2024FY,1.57
欧洲及其他,零售店,2025H1,2.72
欧洲及其他,零售店,2025FY,8.56
欧洲及其他,零售店,2026H1,4.03
欧洲及其他,机器人商店,2024H1,0.01
欧洲及其他,机器人商店,2024FY,0.02
欧洲及其他,机器人商店,2025H1,0.04
欧洲及其他,机器人商店,2025FY,0.17
欧洲及其他,机器人商店,2026H1,0.11
欧洲及其他,线上渠道,2024H1,0.11
欧洲及其他,线上渠道,2024FY,0.55
欧洲及其他,线上渠道,2025H1,1.65
欧洲及其他,线上渠道,2025FY,5.05
欧洲及其他,线上渠道,2026H1,0.68
欧洲及其他,批发及其他,2024H1,0.05
欧洲及其他,批发及其他,2024FY,0.25
欧洲及其他,批发及其他,2025H1,0.36
欧洲及其他,批发及其他,2025FY,0.73
欧洲及其他,批发及其他,2026H1,0.24
1 region channel period revenue
2 中国 零售店 2024H1 20.04
3 中国 零售店 2024FY 46.01
4 中国 零售店 2025H1 44.06
5 中国 零售店 2025FY 100.75
6 中国 零售店 2026H1 60.90
7 中国 机器人商店 2024H1 3.38
8 中国 机器人商店 2024FY 7.51
9 中国 机器人商店 2025H1 6.78
10 中国 机器人商店 2025FY 13.46
11 中国 机器人商店 2026H1 7.79
12 中国 线上渠道 2024H1 9.41
13 中国 线上渠道 2024FY 27.72
14 中国 线上渠道 2025H1 29.37
15 中国 线上渠道 2025FY 85.22
16 中国 线上渠道 2026H1 47.79
17 中国 批发及其他 2024H1 2.39
18 中国 批发及其他 2024FY 7.63
19 中国 批发及其他 2025H1 2.61
20 中国 批发及其他 2025FY 9.08
21 中国 批发及其他 2026H1 5.53
22 亚太 零售店 2024H1 4.85
23 亚太 零售店 2024FY 16.68
24 亚太 零售店 2025H1 14.93
25 亚太 零售店 2025FY 43.19
26 亚太 零售店 2026H1 17.16
27 亚太 机器人商店 2024H1 0.17
28 亚太 机器人商店 2024FY 0.40
29 亚太 机器人商店 2025H1 0.33
30 亚太 机器人商店 2025FY 0.95
31 亚太 机器人商店 2026H1 0.57
32 亚太 线上渠道 2024H1 1.65
33 亚太 线上渠道 2024FY 9.72
34 亚太 线上渠道 2025H1 10.69
35 亚太 线上渠道 2025FY 30.53
36 亚太 线上渠道 2026H1 6.44
37 亚太 批发及其他 2024H1 1.29
38 亚太 批发及其他 2024FY 4.30
39 亚太 批发及其他 2025H1 2.56
40 亚太 批发及其他 2025FY 5.44
41 亚太 批发及其他 2026H1 1.59
42 美洲 零售店 2024H1 0.87
43 美洲 零售店 2024FY 3.41
44 美洲 零售店 2025H1 7.37
45 美洲 零售店 2025FY 20.04
46 美洲 零售店 2026H1 9.03
47 美洲 机器人商店 2024H1 0.13
48 美洲 机器人商店 2024FY 0.38
49 美洲 机器人商店 2025H1 1.05
50 美洲 机器人商店 2025FY 2.32
51 美洲 机器人商店 2026H1 1.04
52 美洲 线上渠道 2024H1 0.64
53 美洲 线上渠道 2024FY 3.64
54 美洲 线上渠道 2025H1 13.27
55 美洲 线上渠道 2025FY 43.54
56 美洲 线上渠道 2026H1 7.22
57 美洲 批发及其他 2024H1 0.19
58 美洲 批发及其他 2024FY 0.58
59 美洲 批发及其他 2025H1 0.96
60 美洲 批发及其他 2025FY 2.17
61 美洲 批发及其他 2026H1 1.62
62 欧洲及其他 零售店 2024H1 0.41
63 欧洲及其他 零售店 2024FY 1.57
64 欧洲及其他 零售店 2025H1 2.72
65 欧洲及其他 零售店 2025FY 8.56
66 欧洲及其他 零售店 2026H1 4.03
67 欧洲及其他 机器人商店 2024H1 0.01
68 欧洲及其他 机器人商店 2024FY 0.02
69 欧洲及其他 机器人商店 2025H1 0.04
70 欧洲及其他 机器人商店 2025FY 0.17
71 欧洲及其他 机器人商店 2026H1 0.11
72 欧洲及其他 线上渠道 2024H1 0.11
73 欧洲及其他 线上渠道 2024FY 0.55
74 欧洲及其他 线上渠道 2025H1 1.65
75 欧洲及其他 线上渠道 2025FY 5.05
76 欧洲及其他 线上渠道 2026H1 0.68
77 欧洲及其他 批发及其他 2024H1 0.05
78 欧洲及其他 批发及其他 2024FY 0.25
79 欧洲及其他 批发及其他 2025H1 0.36
80 欧洲及其他 批发及其他 2025FY 0.73
81 欧洲及其他 批发及其他 2026H1 0.24
Binary file not shown.
+2
View File
@@ -25,6 +25,7 @@ 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';
import { channelShareChart } from './ppmt_channel_share.js';
import { storesRetailChart } from './ppmt_stores_retail.js';
import { storesRobotChart } from './ppmt_stores_robot.js';
import { regionShareChart } from './ppmt_region_share.js';
@@ -62,6 +63,7 @@ export const chartRegistry = {
'ppmt-working-capital-days': workingCapitalDaysChart,
'ppmt-expense-structure': expenseStructureChart,
'ppmt-channel-revenue': channelRevenueChart,
'ppmt-channel-share': channelShareChart,
'ppmt-stores-retail': storesRetailChart,
'ppmt-stores-robot': storesRobotChart,
'ppmt-region-share': regionShareChart,
+83
View File
@@ -0,0 +1,83 @@
import { col } from '../csv.js';
import {
CHANNEL_COLORS, PALETTE, FONT, axisCommon, standalonePeriodAxis, titleStyle, standardLegend,
financialViewLabel, barSeries, footnote, watermarks, LAYOUT,
} from '../theme.js';
const CHANNELS = [
{ key: 'retail', name: '零售店', color: CHANNEL_COLORS.retail, labelColor: '#fff' },
{ key: 'online', name: '线上渠道', color: CHANNEL_COLORS.online, labelColor: '#fff' },
{ key: 'robot_store', name: '机器人商店', color: CHANNEL_COLORS.robotStore, labelColor: '#fff' },
{ key: 'wholesale', name: '批发及其他', color: CHANNEL_COLORS.wholesale, labelColor: PALETTE.ink },
];
function sharePoint(value, labelColor) {
if (!Number.isFinite(value) || value === 0) return null;
// 3.5% 以下的段放不下 11px 段内标签;当前数据最小为 3.7%(2025H2 机器人商店)
return {
value,
label: value >= 3.5
? { position: 'inside', color: labelColor }
: { show: false },
};
}
/** 泡泡玛特全渠道收入占比:全部地区合并,每根柱按当期总收入归一化为 100%。 */
export function channelShareChart(rows, { width, height, view = 'all' }) {
const periods = col(rows, 'period');
const barWidth = view === 'all' ? 48 : 80;
return {
backgroundColor: '#fff',
title: {
...titleStyle,
text: `泡泡玛特渠道收入占比(${financialViewLabel(view)})`,
subtext: '全部地区合并;归一化堆叠:每根柱为当期总收入的 100%',
},
legend: standardLegend(CHANNELS.map((item) => item.name)),
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
textStyle: { fontFamily: FONT },
formatter: (params) => {
const row = rows[periods.indexOf(params[0].axisValue)];
return `<b>${params[0].axisValue}</b> 总收入 ${row.total_revenue.toFixed(2)} 亿元<br/>${params
.filter((item) => item.value !== null)
.slice()
.reverse()
.map((item) => `${item.marker}${item.seriesName}: ${item.value.toFixed(1)}%`)
.join('<br/>')}`;
},
},
grid: { left: 78, right: 42, top: LAYOUT.gridTop, bottom: 100 },
xAxis: { ...standalonePeriodAxis, data: periods },
yAxis: {
...axisCommon,
type: 'value',
min: 0,
max: 100,
interval: 20,
axisLabel: { ...axisCommon.axisLabel, formatter: '{value}%' },
},
series: CHANNELS.map((item) => barSeries({
name: item.name,
data: rows.map((row) => sharePoint(row[item.key], item.labelColor)),
color: item.color,
barWidth,
stack: 'channel-share',
label: {
show: true,
position: 'inside',
fontSize: 11,
fontFamily: FONT,
fontWeight: 500,
formatter: (point) => `${point.value.toFixed(1)}%`,
},
emphasis: { focus: 'series' },
})),
graphic: [
...watermarks(width, height),
footnote('数据来源:泡泡玛特分地区渠道收入披露(中国含港澳台);H2为单半年(全年−H1)\n线上渠道含抽盒机、天猫、抖音、自研APP及官网等 | @思考的Joey'),
],
};
}
+2 -1
View File
@@ -27,6 +27,7 @@
"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": "费用结构与费用率", "defaultView": "all", "views": { "all": "data/ppmt/ppmt_expense_structure.csv", "annual": "data/ppmt/ppmt_expense_structure_annual.csv", "halfyear": "data/ppmt/ppmt_expense_structure_halfyear.csv" }, "height": 700, "filename": "泡泡玛特费用结构与费用率" },
"ppmt-channel-revenue": { "key": "ppmt-channel-revenue", "title": "渠道收入结构与增速", "defaultView": "all", "views": { "all": "data/ppmt/ppmt_channel_revenue.csv", "annual": "data/ppmt/ppmt_channel_revenue_annual.csv", "halfyear": "data/ppmt/ppmt_channel_revenue_halfyear.csv" }, "height": 720, "filename": "泡泡玛特渠道收入结构与增速" },
"ppmt-channel-share": { "key": "ppmt-channel-share", "title": "渠道收入占比", "defaultView": "all", "views": { "all": "data/ppmt/ppmt_channel_share.csv", "annual": "data/ppmt/ppmt_channel_share_annual.csv", "halfyear": "data/ppmt/ppmt_channel_share_halfyear.csv" }, "height": 680, "filename": "泡泡玛特渠道收入占比" },
"ppmt-stores-retail": { "key": "ppmt-stores-retail", "title": "分地区零售门店数量与同比增速", "defaultView": "all", "views": { "all": "data/ppmt/ppmt_stores_retail.csv", "annual": "data/ppmt/ppmt_stores_retail_annual.csv", "halfyear": "data/ppmt/ppmt_stores_retail_halfyear.csv" }, "height": 680, "filename": "泡泡玛特分地区零售门店数量与同比增速" },
"ppmt-stores-retail-share": { "key": "ppmt-stores-retail-share", "title": "分地区零售门店数量占比", "defaultView": "all", "views": { "all": "data/ppmt/ppmt_stores_retail_share.csv", "annual": "data/ppmt/ppmt_stores_retail_share_annual.csv", "halfyear": "data/ppmt/ppmt_stores_retail_share_halfyear.csv" }, "height": 680, "filename": "泡泡玛特分地区零售门店数量占比" },
"ppmt-retail-store-revenue": { "key": "ppmt-retail-store-revenue", "title": "各地区零售店单店收入", "defaultView": "all", "views": { "all": "data/ppmt/ppmt_retail_store_revenue.csv", "annual": "data/ppmt/ppmt_retail_store_revenue_annual.csv", "halfyear": "data/ppmt/ppmt_retail_store_revenue_halfyear.csv" }, "height": 680, "filename": "泡泡玛特各地区零售店单店收入" },
@@ -40,7 +41,7 @@
{ "id": "expense", "name": "费用率", "chartRefs": ["ppmt-margins", "ppmt-expense-structure"] },
{ "id": "balance-sheet", "name": "资产负债", "chartRefs": ["ppmt-assets", "ppmt-inventory", "ppmt-liquidity-working-capital", "ppmt-working-capital-days"] },
{ "id": "ip-category", "name": "IP 与品类", "chartRefs": ["ppmt-ip", "ppmt-ip-share", "ppmt-ip-share-history", "ppmt-ip-ex-monsters", "ppmt-category"] },
{ "id": "region-channel", "name": "区域渠道", "chartRefs": ["ppmt-region-channel", "ppmt-region-yoy", "ppmt-region-yoy-timeseries", "ppmt-channel-revenue", "ppmt-stores-retail", "ppmt-stores-retail-share", "ppmt-stores-robot", "ppmt-region-share"] },
{ "id": "region-channel", "name": "区域渠道", "chartRefs": ["ppmt-region-channel", "ppmt-region-yoy", "ppmt-region-yoy-timeseries", "ppmt-channel-revenue", "ppmt-channel-share", "ppmt-stores-retail", "ppmt-stores-retail-share", "ppmt-stores-robot", "ppmt-region-share"] },
{ "id": "store-revenue", "name": "单店收入", "chartRefs": ["ppmt-retail-store-revenue", "ppmt-robot-store-revenue"] }
]
},
+49 -2
View File
@@ -4,6 +4,7 @@ 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 RAWDATA_DIR = path.join(ROOT, 'data/ppmt/rawData');
const OUTPUT_DIR = path.join(ROOT, 'data/ppmt');
const FILES = {
@@ -565,8 +566,8 @@ writeViewFiles('ppmt_expense_structure', ['period', 'sales_expense', 'admin_expe
sales_expense: 2, admin_expense: 2, financial_expense: 2, four_expense_rate: 1,
});
function readRuntimeRows(filename) {
const [header, ...lines] = fs.readFileSync(path.join(OUTPUT_DIR, filename), 'utf8').trim().split(/\r?\n/);
function readCsvRows(dir, filename) {
const [header, ...lines] = fs.readFileSync(path.join(dir, filename), 'utf8').trim().split(/\r?\n/);
const headers = header.split(',');
return lines.map((line) => Object.fromEntries(line.split(',').map((cell, index) => {
const value = Number(cell);
@@ -574,6 +575,10 @@ function readRuntimeRows(filename) {
})));
}
function readRuntimeRows(filename) {
return readCsvRows(OUTPUT_DIR, filename);
}
function writeRuntimeViews(base, headers, views, digits = {}) {
writeViewFiles(base, headers, views, digits);
}
@@ -698,3 +703,45 @@ writeViewFiles('ppmt_inventory', ['period', 'inventory', 'yoy'], inventoryViews,
}, 2017);
console.log('已生成第二批连续图表的全部、年度、半年 CSV。');
// 渠道收入占比:底表 data/ppmt/rawData/地区渠道收入.csv 整理自
// 泡泡玛特-rawdata.xlsx「各地区渠道收入」工作表(中国含港澳台的四地区 × 四渠道明细)。
// 全球合计 = 四地区加总;披露口径只有 H1 与全年,H2 单半年 = 全年 − H1;
// 占比在金额视图之后计算,按当期四渠道合计归一化,不可用占比直接相减。
const CHANNEL_NAME_TO_KEY = {
零售店: 'retail',
机器人商店: 'robot_store',
线上渠道: 'online',
批发及其他: 'wholesale',
};
const channelRevenueByPeriod = new Map();
readCsvRows(RAWDATA_DIR, '地区渠道收入.csv').forEach(({ region, channel, period, revenue }) => {
const key = CHANNEL_NAME_TO_KEY[channel];
if (!key) throw new Error(`地区渠道收入底表存在未知渠道:${channel}`);
if (!period || !Number.isFinite(revenue)) throw new Error(`地区渠道收入底表行不完整:${region} ${channel} ${period} ${revenue}`);
if (!channelRevenueByPeriod.has(period)) {
channelRevenueByPeriod.set(period, { retail: 0, online: 0, robot_store: 0, wholesale: 0 });
}
channelRevenueByPeriod.get(period)[key] += revenue;
});
const channelAmountViews = buildViews((year, type) => {
const bucket = channelRevenueByPeriod.get(type === 'H1' ? `${year}H1` : `${year}FY`);
return bucket ?? null;
});
const channelShareViews = Object.fromEntries(Object.entries(channelAmountViews).map(([view, rows]) => [
view,
rows.map(({ period, ...amounts }) => {
const total = Object.values(amounts).reduce((sum, value) => sum + value, 0);
return {
period,
...Object.fromEntries(Object.entries(amounts).map(([key, value]) => [key, value / total * 100])),
total_revenue: total,
};
}),
]));
writeRuntimeViews('ppmt_channel_share', ['period', 'retail', 'online', 'robot_store', 'wholesale', 'total_revenue'], channelShareViews, {
retail: 1, online: 1, robot_store: 1, wholesale: 1, total_revenue: 2,
});
console.log('已生成渠道收入占比的全部、年度、半年 CSV。');