byteplan-cli 1.0.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -3
- package/skills/byteplan-analysis/SKILL.md +1078 -0
- package/skills/byteplan-api/API_REFERENCE.md +249 -0
- package/skills/byteplan-api/SKILL.md +96 -0
- package/skills/byteplan-api/package.json +16 -0
- package/skills/byteplan-api/scripts/api.js +973 -0
- package/skills/byteplan-excel/SKILL.md +212 -0
- package/skills/byteplan-excel/examples/margin-analysis.json +40 -0
- package/skills/byteplan-excel/package.json +12 -0
- package/skills/byteplan-excel/pnpm-lock.yaml +68 -0
- package/skills/byteplan-excel/scripts/generate_excel.js +156 -0
- package/skills/byteplan-html/SKILL.md +490 -0
- package/skills/byteplan-html/examples/example-output.html +184 -0
- package/skills/byteplan-html/examples/generate-ppt-style-html.js +611 -0
- package/skills/byteplan-html/examples/margin-contribution-analysis.json +152 -0
- package/skills/byteplan-html/package.json +18 -0
- package/skills/byteplan-html/scripts/generate_html.js +517 -0
- package/skills/byteplan-ppt/SKILL.md +394 -0
- package/skills/byteplan-ppt/examples/margin-contribution-analysis.json +152 -0
- package/skills/byteplan-ppt/package.json +16 -0
- package/skills/byteplan-ppt/pnpm-lock.yaml +138 -0
- package/skills/byteplan-ppt/scripts/check_ppt_overlap.js +318 -0
- package/skills/byteplan-ppt/scripts/generate_ppt.js +680 -0
- package/skills/byteplan-video/SKILL.md +606 -0
- package/skills/byteplan-video/examples/sample-video-data.json +82 -0
- package/skills/byteplan-video/remotion-project/package.json +22 -0
- package/skills/byteplan-video/remotion-project/pnpm-lock.yaml +1646 -0
- package/skills/byteplan-video/remotion-project/remotion.config.ts +6 -0
- package/skills/byteplan-video/remotion-project/scene_durations.json +32 -0
- package/skills/byteplan-video/remotion-project/scripts/generate_audio.js +279 -0
- package/skills/byteplan-video/remotion-project/src/DynamicReport.tsx +172 -0
- package/skills/byteplan-video/remotion-project/src/Root.tsx +51 -0
- package/skills/byteplan-video/remotion-project/src/SalesReport.tsx +107 -0
- package/skills/byteplan-video/remotion-project/src/index.tsx +4 -0
- package/skills/byteplan-video/remotion-project/src/scenes/ChartSlide.tsx +201 -0
- package/skills/byteplan-video/remotion-project/src/scenes/CoverSlide.tsx +61 -0
- package/skills/byteplan-video/remotion-project/src/scenes/EndSlide.tsx +60 -0
- package/skills/byteplan-video/remotion-project/src/scenes/InsightSlide.tsx +101 -0
- package/skills/byteplan-video/remotion-project/src/scenes/KpiSlide.tsx +84 -0
- package/skills/byteplan-video/remotion-project/src/scenes/RecommendationSlide.tsx +100 -0
- package/skills/byteplan-video/remotion-project/tsconfig.json +13 -0
- package/skills/byteplan-video/remotion-project/video_data.json +76 -0
- package/skills/byteplan-video/scripts/generate_video.js +270 -0
- package/skills/byteplan-video/templates/package.json +31 -0
- package/skills/byteplan-video/templates/pnpm-lock.yaml +2200 -0
- package/skills/byteplan-video/templates/remotion.config.ts +9 -0
- package/skills/byteplan-video/templates/scripts/generate-audio.ts +55 -0
- package/skills/byteplan-video/templates/src/components/BarChartScene.tsx +153 -0
- package/skills/byteplan-video/templates/src/components/InsightScene.tsx +135 -0
- package/skills/byteplan-video/templates/src/components/LineChartScene.tsx +214 -0
- package/skills/byteplan-video/templates/src/components/SceneFactory.tsx +34 -0
- package/skills/byteplan-video/templates/src/components/SummaryScene.tsx +155 -0
- package/skills/byteplan-video/templates/src/components/TitleScene.tsx +130 -0
- package/skills/byteplan-video/templates/src/compositions/AnalysisVideo.tsx +39 -0
- package/skills/byteplan-video/templates/src/index.tsx +28 -0
- package/skills/byteplan-video/templates/src/register-root.tsx +4 -0
- package/skills/byteplan-video/templates/src/storyboard/types.ts +46 -0
- package/skills/byteplan-video/templates/tsconfig.json +17 -0
- package/skills/byteplan-video/templates/tsconfig.scripts.json +13 -0
- package/skills/byteplan-word/SKILL.md +233 -0
- package/skills/byteplan-word/package.json +12 -0
- package/skills/byteplan-word/pnpm-lock.yaml +120 -0
- package/skills/byteplan-word/scripts/generate_word.js +548 -0
- package/src/cli.js +4 -0
- package/src/commands/skills.js +279 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AbsoluteFill,
|
|
3
|
+
useCurrentFrame,
|
|
4
|
+
interpolate,
|
|
5
|
+
} from "remotion";
|
|
6
|
+
|
|
7
|
+
interface ChartSlideProps {
|
|
8
|
+
title: string;
|
|
9
|
+
chartType: string;
|
|
10
|
+
data: Array<{ name: string; value: number }>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const COLORS = ["#667eea", "#4ade80", "#f472b6", "#fbbf24", "#06B6D4"];
|
|
14
|
+
|
|
15
|
+
export const ChartSlide: React.FC<ChartSlideProps> = ({
|
|
16
|
+
title,
|
|
17
|
+
chartType,
|
|
18
|
+
data,
|
|
19
|
+
}) => {
|
|
20
|
+
const frame = useCurrentFrame();
|
|
21
|
+
|
|
22
|
+
const titleOpacity = interpolate(frame, [0, 20], [0, 1], { extrapolateRight: "clamp" });
|
|
23
|
+
|
|
24
|
+
if (chartType === "pie") {
|
|
25
|
+
// 饼图
|
|
26
|
+
const total = data.reduce((sum, d) => sum + d.value, 0);
|
|
27
|
+
const radius = 180;
|
|
28
|
+
const svgSize = 400;
|
|
29
|
+
const cx = svgSize / 2;
|
|
30
|
+
const cy = svgSize / 2;
|
|
31
|
+
|
|
32
|
+
let cumulativeAngle = 0;
|
|
33
|
+
const slices = data.map((d, i) => {
|
|
34
|
+
const angle = (d.value / total) * 360;
|
|
35
|
+
const startAngle = cumulativeAngle;
|
|
36
|
+
cumulativeAngle += angle;
|
|
37
|
+
return { ...d, startAngle, angle, color: COLORS[i % COLORS.length] };
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const pieOpacity = interpolate(frame, [20, 50], [0, 1], { extrapolateRight: "clamp" });
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<AbsoluteFill
|
|
44
|
+
style={{
|
|
45
|
+
background: "linear-gradient(135deg, #16213e 0%, #1a1a2e 100%)",
|
|
46
|
+
justifyContent: "center",
|
|
47
|
+
alignItems: "center",
|
|
48
|
+
flexDirection: "column",
|
|
49
|
+
}}
|
|
50
|
+
>
|
|
51
|
+
<div
|
|
52
|
+
style={{
|
|
53
|
+
fontSize: 44,
|
|
54
|
+
fontWeight: "bold",
|
|
55
|
+
color: "#fff",
|
|
56
|
+
marginBottom: 50,
|
|
57
|
+
opacity: titleOpacity,
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
📦 {title}
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div style={{ display: "flex", flexDirection: "row", gap: 80, alignItems: "center", opacity: pieOpacity }}>
|
|
64
|
+
{/* 饼图 SVG */}
|
|
65
|
+
<svg width={svgSize} height={svgSize}>
|
|
66
|
+
{slices.map((slice, i) => {
|
|
67
|
+
const startRad = (slice.startAngle - 90) * (Math.PI / 180);
|
|
68
|
+
const endRad = (slice.startAngle + slice.angle - 90) * (Math.PI / 180);
|
|
69
|
+
const x1 = cx + radius * Math.cos(startRad);
|
|
70
|
+
const y1 = cy + radius * Math.sin(startRad);
|
|
71
|
+
const x2 = cx + radius * Math.cos(endRad);
|
|
72
|
+
const y2 = cy + radius * Math.sin(endRad);
|
|
73
|
+
const largeArc = slice.angle > 180 ? 1 : 0;
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<path
|
|
77
|
+
key={i}
|
|
78
|
+
d={`M ${cx} ${cy} L ${x1} ${y1} A ${radius} ${radius} 0 ${largeArc} 1 ${x2} ${y2} Z`}
|
|
79
|
+
fill={slice.color}
|
|
80
|
+
stroke="#fff"
|
|
81
|
+
strokeWidth={2}
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
})}
|
|
85
|
+
</svg>
|
|
86
|
+
|
|
87
|
+
{/* 图例 */}
|
|
88
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
|
89
|
+
{data.map((d, i) => (
|
|
90
|
+
<div key={i} style={{ display: "flex", alignItems: "center", gap: 16 }}>
|
|
91
|
+
<div
|
|
92
|
+
style={{
|
|
93
|
+
width: 24,
|
|
94
|
+
height: 24,
|
|
95
|
+
borderRadius: 4,
|
|
96
|
+
background: COLORS[i % COLORS.length],
|
|
97
|
+
}}
|
|
98
|
+
/>
|
|
99
|
+
<div style={{ fontSize: 20, color: "#fff", width: 100 }}>{d.name}</div>
|
|
100
|
+
<div style={{ fontSize: 18, color: "#4ade80" }}>
|
|
101
|
+
¥{d.value.toLocaleString()}
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
))}
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</AbsoluteFill>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 柱状图
|
|
112
|
+
const maxValue = Math.max(...data.map((d) => d.value));
|
|
113
|
+
const barWidth = 140;
|
|
114
|
+
const barGap = 40;
|
|
115
|
+
const chartHeight = 400;
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<AbsoluteFill
|
|
119
|
+
style={{
|
|
120
|
+
background: "linear-gradient(135deg, #1a1a2e 0%, #16213e 100%)",
|
|
121
|
+
justifyContent: "center",
|
|
122
|
+
alignItems: "center",
|
|
123
|
+
flexDirection: "column",
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
<div
|
|
127
|
+
style={{
|
|
128
|
+
fontSize: 44,
|
|
129
|
+
fontWeight: "bold",
|
|
130
|
+
color: "#fff",
|
|
131
|
+
marginBottom: 60,
|
|
132
|
+
opacity: titleOpacity,
|
|
133
|
+
}}
|
|
134
|
+
>
|
|
135
|
+
{title.includes("渠道") ? "🛒" : "🗺️"} {title}
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
{/* 图表容器 - 居中 */}
|
|
139
|
+
<div
|
|
140
|
+
style={{
|
|
141
|
+
display: "flex",
|
|
142
|
+
flexDirection: "row",
|
|
143
|
+
alignItems: "flex-end",
|
|
144
|
+
gap: barGap,
|
|
145
|
+
justifyContent: "center",
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
{data.map((d, i) => {
|
|
149
|
+
const barHeight = (d.value / maxValue) * chartHeight;
|
|
150
|
+
const delay = 20 + i * 10;
|
|
151
|
+
const animatedHeight = interpolate(
|
|
152
|
+
frame,
|
|
153
|
+
[delay, delay + 30],
|
|
154
|
+
[0, barHeight],
|
|
155
|
+
{ extrapolateRight: "clamp" }
|
|
156
|
+
);
|
|
157
|
+
const opacity = interpolate(frame, [delay, delay + 20], [0, 1], { extrapolateRight: "clamp" });
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<div
|
|
161
|
+
key={i}
|
|
162
|
+
style={{
|
|
163
|
+
display: "flex",
|
|
164
|
+
flexDirection: "column",
|
|
165
|
+
alignItems: "center",
|
|
166
|
+
opacity,
|
|
167
|
+
width: barWidth,
|
|
168
|
+
}}
|
|
169
|
+
>
|
|
170
|
+
{/* 数值 */}
|
|
171
|
+
<div
|
|
172
|
+
style={{
|
|
173
|
+
fontSize: 24,
|
|
174
|
+
fontWeight: "bold",
|
|
175
|
+
color: "#4ade80",
|
|
176
|
+
marginBottom: 10,
|
|
177
|
+
}}
|
|
178
|
+
>
|
|
179
|
+
{maxValue <= 100 ? `${d.value}%` : `¥${(d.value / 1000).toFixed(0)}k`}
|
|
180
|
+
</div>
|
|
181
|
+
{/* 柱状条 */}
|
|
182
|
+
<div
|
|
183
|
+
style={{
|
|
184
|
+
width: barWidth,
|
|
185
|
+
height: animatedHeight,
|
|
186
|
+
background: `linear-gradient(180deg, ${COLORS[i]} 0%, ${COLORS[i]}80 100%)`,
|
|
187
|
+
borderRadius: 8,
|
|
188
|
+
boxShadow: `0 4px 20px ${COLORS[i]}40`,
|
|
189
|
+
}}
|
|
190
|
+
/>
|
|
191
|
+
{/* 标签 */}
|
|
192
|
+
<div style={{ fontSize: 22, color: "#fff", marginTop: 16 }}>
|
|
193
|
+
{d.name}
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
);
|
|
197
|
+
})}
|
|
198
|
+
</div>
|
|
199
|
+
</AbsoluteFill>
|
|
200
|
+
);
|
|
201
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AbsoluteFill,
|
|
3
|
+
useCurrentFrame,
|
|
4
|
+
interpolate,
|
|
5
|
+
} from "remotion";
|
|
6
|
+
|
|
7
|
+
interface CoverSlideProps {
|
|
8
|
+
title: string;
|
|
9
|
+
subtitle: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const CoverSlide: React.FC<CoverSlideProps> = ({ title, subtitle }) => {
|
|
13
|
+
const frame = useCurrentFrame();
|
|
14
|
+
|
|
15
|
+
const titleOpacity = interpolate(frame, [0, 30], [0, 1], { extrapolateRight: "clamp" });
|
|
16
|
+
const subtitleOpacity = interpolate(frame, [30, 60], [0, 1], { extrapolateRight: "clamp" });
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<AbsoluteFill
|
|
20
|
+
style={{
|
|
21
|
+
background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
|
|
22
|
+
justifyContent: "center",
|
|
23
|
+
alignItems: "center",
|
|
24
|
+
flexDirection: "column",
|
|
25
|
+
}}
|
|
26
|
+
>
|
|
27
|
+
<div
|
|
28
|
+
style={{
|
|
29
|
+
fontSize: 64,
|
|
30
|
+
fontWeight: "bold",
|
|
31
|
+
color: "#fff",
|
|
32
|
+
textAlign: "center",
|
|
33
|
+
opacity: titleOpacity,
|
|
34
|
+
marginBottom: 40,
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
📊 {title}
|
|
38
|
+
</div>
|
|
39
|
+
<div
|
|
40
|
+
style={{
|
|
41
|
+
fontSize: 32,
|
|
42
|
+
color: "rgba(255,255,255,0.9)",
|
|
43
|
+
textAlign: "center",
|
|
44
|
+
opacity: subtitleOpacity,
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
{subtitle}
|
|
48
|
+
</div>
|
|
49
|
+
<div
|
|
50
|
+
style={{
|
|
51
|
+
fontSize: 18,
|
|
52
|
+
color: "rgba(255,255,255,0.6)",
|
|
53
|
+
marginTop: 60,
|
|
54
|
+
opacity: subtitleOpacity,
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
2026年4月1日 · 跨境电商租户
|
|
58
|
+
</div>
|
|
59
|
+
</AbsoluteFill>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AbsoluteFill,
|
|
3
|
+
useCurrentFrame,
|
|
4
|
+
interpolate,
|
|
5
|
+
} from "remotion";
|
|
6
|
+
|
|
7
|
+
interface EndSlideProps {
|
|
8
|
+
title: string;
|
|
9
|
+
subtitle: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const EndSlide: React.FC<EndSlideProps> = ({ title, subtitle }) => {
|
|
13
|
+
const frame = useCurrentFrame();
|
|
14
|
+
|
|
15
|
+
const titleOpacity = interpolate(frame, [0, 30], [0, 1], { extrapolateRight: "clamp" });
|
|
16
|
+
const subtitleOpacity = interpolate(frame, [30, 60], [0, 1], { extrapolateRight: "clamp" });
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<AbsoluteFill
|
|
20
|
+
style={{
|
|
21
|
+
background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
|
|
22
|
+
justifyContent: "center",
|
|
23
|
+
alignItems: "center",
|
|
24
|
+
flexDirection: "column",
|
|
25
|
+
}}
|
|
26
|
+
>
|
|
27
|
+
<div
|
|
28
|
+
style={{
|
|
29
|
+
fontSize: 52,
|
|
30
|
+
fontWeight: "bold",
|
|
31
|
+
color: "#fff",
|
|
32
|
+
textAlign: "center",
|
|
33
|
+
opacity: titleOpacity,
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
📊 {title}
|
|
37
|
+
</div>
|
|
38
|
+
<div
|
|
39
|
+
style={{
|
|
40
|
+
fontSize: 28,
|
|
41
|
+
color: "rgba(255,255,255,0.8)",
|
|
42
|
+
marginTop: 30,
|
|
43
|
+
opacity: subtitleOpacity,
|
|
44
|
+
}}
|
|
45
|
+
>
|
|
46
|
+
{subtitle}
|
|
47
|
+
</div>
|
|
48
|
+
<div
|
|
49
|
+
style={{
|
|
50
|
+
fontSize: 18,
|
|
51
|
+
color: "rgba(255,255,255,0.6)",
|
|
52
|
+
marginTop: 50,
|
|
53
|
+
opacity: subtitleOpacity,
|
|
54
|
+
}}
|
|
55
|
+
>
|
|
56
|
+
BytePlan 数据平台
|
|
57
|
+
</div>
|
|
58
|
+
</AbsoluteFill>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AbsoluteFill,
|
|
3
|
+
useCurrentFrame,
|
|
4
|
+
interpolate,
|
|
5
|
+
spring,
|
|
6
|
+
} from "remotion";
|
|
7
|
+
|
|
8
|
+
interface InsightSlideProps {
|
|
9
|
+
title: string;
|
|
10
|
+
insights: Array<{ title: string; content: string; warning?: boolean }>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const InsightSlide: React.FC<InsightSlideProps> = ({ title, insights }) => {
|
|
14
|
+
const frame = useCurrentFrame();
|
|
15
|
+
|
|
16
|
+
const titleOpacity = interpolate(frame, [0, 20], [0, 1], { extrapolateRight: "clamp" });
|
|
17
|
+
|
|
18
|
+
// 根据 insights 数量决定布局
|
|
19
|
+
const gridCols = insights.length <= 3 ? 1 : 2;
|
|
20
|
+
const gridRows = Math.ceil(insights.length / gridCols);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<AbsoluteFill
|
|
24
|
+
style={{
|
|
25
|
+
background: "linear-gradient(135deg, #1a1a2e 0%, #16213e 100%)",
|
|
26
|
+
justifyContent: "center",
|
|
27
|
+
alignItems: "center",
|
|
28
|
+
flexDirection: "column",
|
|
29
|
+
padding: 60,
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
<div
|
|
33
|
+
style={{
|
|
34
|
+
fontSize: 48,
|
|
35
|
+
fontWeight: "bold",
|
|
36
|
+
color: "#fff",
|
|
37
|
+
marginBottom: 50,
|
|
38
|
+
opacity: titleOpacity,
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
💡 {title}
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div
|
|
45
|
+
style={{
|
|
46
|
+
display: "grid",
|
|
47
|
+
gridTemplateColumns: gridCols === 1 ? "1fr" : "1fr 1fr",
|
|
48
|
+
gap: 25,
|
|
49
|
+
width: gridCols === 1 ? 800 : 1200,
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
{insights.map((insight, index) => {
|
|
53
|
+
const delay = 20 + index * 12;
|
|
54
|
+
const scale = spring({
|
|
55
|
+
frame: frame - delay,
|
|
56
|
+
fps: 30,
|
|
57
|
+
config: { damping: 100, stiffness: 200 },
|
|
58
|
+
});
|
|
59
|
+
const opacity = interpolate(frame, [delay, delay + 20], [0, 1], { extrapolateRight: "clamp" });
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div
|
|
63
|
+
key={index}
|
|
64
|
+
style={{
|
|
65
|
+
background: insight.warning
|
|
66
|
+
? "rgba(251,191,36,0.15)"
|
|
67
|
+
: "rgba(255,255,255,0.1)",
|
|
68
|
+
borderRadius: 18,
|
|
69
|
+
padding: 30,
|
|
70
|
+
borderLeft: `5px solid ${insight.warning ? "#fbbf24" : "#667eea"}`,
|
|
71
|
+
opacity,
|
|
72
|
+
transform: `scale(${scale})`,
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
{insight.title && (
|
|
76
|
+
<div
|
|
77
|
+
style={{
|
|
78
|
+
fontSize: 26,
|
|
79
|
+
fontWeight: "bold",
|
|
80
|
+
color: insight.warning ? "#fbbf24" : "#fff",
|
|
81
|
+
marginBottom: 12,
|
|
82
|
+
}}
|
|
83
|
+
>
|
|
84
|
+
{insight.title}
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
<div style={{
|
|
88
|
+
fontSize: 20,
|
|
89
|
+
color: "#CBD5E1",
|
|
90
|
+
lineHeight: 1.6,
|
|
91
|
+
fontWeight: insight.title ? "normal" : "bold"
|
|
92
|
+
}}>
|
|
93
|
+
{insight.content}
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
})}
|
|
98
|
+
</div>
|
|
99
|
+
</AbsoluteFill>
|
|
100
|
+
);
|
|
101
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AbsoluteFill,
|
|
3
|
+
useCurrentFrame,
|
|
4
|
+
interpolate,
|
|
5
|
+
spring,
|
|
6
|
+
} from "remotion";
|
|
7
|
+
|
|
8
|
+
interface KpiSlideProps {
|
|
9
|
+
title: string;
|
|
10
|
+
kpis: Array<{ icon: string; label: string; value: string }>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const KpiSlide: React.FC<KpiSlideProps> = ({ title, kpis }) => {
|
|
14
|
+
const frame = useCurrentFrame();
|
|
15
|
+
|
|
16
|
+
const titleOpacity = interpolate(frame, [0, 20], [0, 1], { extrapolateRight: "clamp" });
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<AbsoluteFill
|
|
20
|
+
style={{
|
|
21
|
+
background: "linear-gradient(135deg, #0f3460 0%, #16213e 100%)",
|
|
22
|
+
justifyContent: "center",
|
|
23
|
+
alignItems: "center",
|
|
24
|
+
flexDirection: "column",
|
|
25
|
+
padding: 60,
|
|
26
|
+
}}
|
|
27
|
+
>
|
|
28
|
+
<div
|
|
29
|
+
style={{
|
|
30
|
+
fontSize: 44,
|
|
31
|
+
fontWeight: "bold",
|
|
32
|
+
color: "#fff",
|
|
33
|
+
marginBottom: 60,
|
|
34
|
+
opacity: titleOpacity,
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
📈 {title}
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div
|
|
41
|
+
style={{
|
|
42
|
+
display: "flex",
|
|
43
|
+
flexDirection: "row",
|
|
44
|
+
gap: 40,
|
|
45
|
+
justifyContent: "center",
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
48
|
+
{kpis.map((kpi, index) => {
|
|
49
|
+
const delay = 20 + index * 15;
|
|
50
|
+
const scale = spring({
|
|
51
|
+
frame: frame - delay,
|
|
52
|
+
fps: 30,
|
|
53
|
+
config: { damping: 100, stiffness: 200 },
|
|
54
|
+
});
|
|
55
|
+
const opacity = interpolate(frame, [delay, delay + 20], [0, 1], { extrapolateRight: "clamp" });
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div
|
|
59
|
+
key={index}
|
|
60
|
+
style={{
|
|
61
|
+
background: "rgba(255,255,255,0.1)",
|
|
62
|
+
borderRadius: 20,
|
|
63
|
+
padding: 40,
|
|
64
|
+
textAlign: "center",
|
|
65
|
+
border: "2px solid rgba(255,255,255,0.2)",
|
|
66
|
+
width: 280,
|
|
67
|
+
opacity,
|
|
68
|
+
transform: `scale(${scale})`,
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<div style={{ fontSize: 48, marginBottom: 20 }}>{kpi.icon}</div>
|
|
72
|
+
<div style={{ fontSize: 18, color: "#94A3B8", marginBottom: 12 }}>
|
|
73
|
+
{kpi.label}
|
|
74
|
+
</div>
|
|
75
|
+
<div style={{ fontSize: 36, fontWeight: "bold", color: "#4ade80" }}>
|
|
76
|
+
{kpi.value}
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
})}
|
|
81
|
+
</div>
|
|
82
|
+
</AbsoluteFill>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AbsoluteFill,
|
|
3
|
+
useCurrentFrame,
|
|
4
|
+
interpolate,
|
|
5
|
+
spring,
|
|
6
|
+
} from "remotion";
|
|
7
|
+
|
|
8
|
+
interface RecommendationSlideProps {
|
|
9
|
+
title: string;
|
|
10
|
+
recommendations: string[];
|
|
11
|
+
source?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const RecommendationSlide: React.FC<RecommendationSlideProps> = ({
|
|
15
|
+
title,
|
|
16
|
+
recommendations,
|
|
17
|
+
source,
|
|
18
|
+
}) => {
|
|
19
|
+
const frame = useCurrentFrame();
|
|
20
|
+
|
|
21
|
+
const titleOpacity = interpolate(frame, [0, 20], [0, 1], { extrapolateRight: "clamp" });
|
|
22
|
+
|
|
23
|
+
// 根据建议数量决定布局
|
|
24
|
+
const gridCols = recommendations.length <= 2 ? 1 : Math.min(recommendations.length, 2);
|
|
25
|
+
const gridRows = Math.ceil(recommendations.length / gridCols);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<AbsoluteFill
|
|
29
|
+
style={{
|
|
30
|
+
background: "linear-gradient(135deg, #0f3460 0%, #16213e 100%)",
|
|
31
|
+
justifyContent: "center",
|
|
32
|
+
alignItems: "center",
|
|
33
|
+
flexDirection: "column",
|
|
34
|
+
padding: 60,
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
<div
|
|
38
|
+
style={{
|
|
39
|
+
fontSize: 48,
|
|
40
|
+
fontWeight: "bold",
|
|
41
|
+
color: "#fff",
|
|
42
|
+
marginBottom: 50,
|
|
43
|
+
opacity: titleOpacity,
|
|
44
|
+
}}
|
|
45
|
+
>
|
|
46
|
+
🎯 {title}
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div
|
|
50
|
+
style={{
|
|
51
|
+
display: "grid",
|
|
52
|
+
gridTemplateColumns: gridCols === 1 ? "1fr" : "1fr 1fr",
|
|
53
|
+
gap: 25,
|
|
54
|
+
width: gridCols === 1 ? 800 : 1200,
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
{recommendations.map((rec, index) => {
|
|
58
|
+
const delay = 20 + index * 12;
|
|
59
|
+
const scale = spring({
|
|
60
|
+
frame: frame - delay,
|
|
61
|
+
fps: 30,
|
|
62
|
+
config: { damping: 100, stiffness: 200 },
|
|
63
|
+
});
|
|
64
|
+
const opacity = interpolate(frame, [delay, delay + 20], [0, 1], { extrapolateRight: "clamp" });
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<div
|
|
68
|
+
key={index}
|
|
69
|
+
style={{
|
|
70
|
+
background: "rgba(74,222,128,0.15)",
|
|
71
|
+
borderRadius: 18,
|
|
72
|
+
padding: 30,
|
|
73
|
+
borderLeft: "5px solid #4ade80",
|
|
74
|
+
opacity,
|
|
75
|
+
transform: `scale(${scale})`,
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
<div style={{ fontSize: 22, color: "#fff", lineHeight: 1.6 }}>
|
|
79
|
+
{rec}
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
})}
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
{source && (
|
|
87
|
+
<div
|
|
88
|
+
style={{
|
|
89
|
+
fontSize: 16,
|
|
90
|
+
color: "#94A3B8",
|
|
91
|
+
marginTop: 40,
|
|
92
|
+
opacity: interpolate(frame, [60, 80], [0, 1], { extrapolateRight: "clamp" }),
|
|
93
|
+
}}
|
|
94
|
+
>
|
|
95
|
+
数据来源:{source}
|
|
96
|
+
</div>
|
|
97
|
+
)}
|
|
98
|
+
</AbsoluteFill>
|
|
99
|
+
);
|
|
100
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"resolveJsonModule": true
|
|
11
|
+
},
|
|
12
|
+
"include": ["src/**/*"]
|
|
13
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "边际贡献分析报告",
|
|
3
|
+
"subtitle": "找出贡献最大的三个要素",
|
|
4
|
+
"period": "2026年4月2日",
|
|
5
|
+
"scenes": [
|
|
6
|
+
{
|
|
7
|
+
"id": "scene_1",
|
|
8
|
+
"type": "title",
|
|
9
|
+
"title": "边际贡献分析报告",
|
|
10
|
+
"subtitle": "找出贡献最大的三个要素",
|
|
11
|
+
"narration": "本视频将为您分析边际贡献情况,找出贡献最大的三个要素。",
|
|
12
|
+
"duration": 5
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "scene_2",
|
|
16
|
+
"type": "insight",
|
|
17
|
+
"title": "核心发现",
|
|
18
|
+
"narration": "经过分析,天猫渠道以1627万元边际贡献位居第一,CD120P产品贡献560万元排名第二,美国市场贡献498万元排名第三。",
|
|
19
|
+
"highlights": [
|
|
20
|
+
"🥇 天猫渠道:边际贡献 1,627万元,贡献率 68%",
|
|
21
|
+
"🥈 CD120P产品:边际贡献 563万元,贡献率 74%",
|
|
22
|
+
"🥉 美国市场:边际贡献 498万元,贡献率 65%"
|
|
23
|
+
],
|
|
24
|
+
"duration": 10
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "scene_3",
|
|
28
|
+
"type": "bar_chart",
|
|
29
|
+
"title": "边际贡献对比(TOP5)",
|
|
30
|
+
"narration": "从边际贡献对比图可以看出,天猫渠道遥遥领先,是公司最核心的销售渠道。",
|
|
31
|
+
"duration": 10
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "scene_4",
|
|
35
|
+
"type": "insight",
|
|
36
|
+
"title": "关键洞察",
|
|
37
|
+
"narration": "天猫渠道贡献占比68%,是最大收入来源。CD120P产品贡献率高达74%,盈利能力最强。美国市场收入规模最大,但贡献率65%,仍有优化空间。天猫渠道变动成本偏高,需排查运营成本构成。",
|
|
38
|
+
"highlights": [
|
|
39
|
+
"天猫渠道:贡献占比68%,是最大收入来源",
|
|
40
|
+
"CD120P产品:贡献率74%,盈利效率最高",
|
|
41
|
+
"美国市场:收入最大,贡献率65%仍有优化空间",
|
|
42
|
+
"⚠️ 天猫变动成本偏高:需排查运营成本构成"
|
|
43
|
+
],
|
|
44
|
+
"duration": 10
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"id": "scene_5",
|
|
48
|
+
"type": "summary",
|
|
49
|
+
"title": "行动建议",
|
|
50
|
+
"narration": "建议加大CD120P产品产能,优化天猫运营成本,并建立月度边际贡献监控机制。",
|
|
51
|
+
"points": [
|
|
52
|
+
"加大CD120P产能,贡献率74%的产品应获得更多资源",
|
|
53
|
+
"优化天猫运营成本,降低变动成本提升贡献率",
|
|
54
|
+
"建立月度边际贡献监控机制,及时发现问题"
|
|
55
|
+
],
|
|
56
|
+
"source": "BytePlan 数据平台 · 跨境电商租户",
|
|
57
|
+
"duration": 8
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "scene_6",
|
|
61
|
+
"type": "end",
|
|
62
|
+
"title": "边际贡献分析报告",
|
|
63
|
+
"narration": "谢谢观看。",
|
|
64
|
+
"duration": 3
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"chartData": {
|
|
68
|
+
"channelContribution": [
|
|
69
|
+
{ "name": "天猫", "value": 1627 },
|
|
70
|
+
{ "name": "CD120P", "value": 563 },
|
|
71
|
+
{ "name": "美国", "value": 498 },
|
|
72
|
+
{ "name": "Amazon", "value": 279 },
|
|
73
|
+
{ "name": "京东", "value": 156 }
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
}
|