@fe-free/core 2.0.3 → 2.0.5
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/CHANGELOG.md +14 -0
- package/package.json +3 -3
- package/src/markdown/chart.tsx +261 -0
- package/src/markdown/code.tsx +6 -0
- package/src/markdown/markdown.stories.tsx +157 -0
- package/src/markdown/style.scss +13 -0
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fe-free/core",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.5",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"author": "",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@ant-design/icons": "^5.2.6",
|
|
14
|
-
"@ant-design/plots": "^2.
|
|
14
|
+
"@ant-design/plots": "^2.5.0",
|
|
15
15
|
"@codemirror/lang-html": "^6.4.9",
|
|
16
16
|
"@codemirror/lang-javascript": "^6.2.4",
|
|
17
17
|
"@codemirror/lang-json": "^6.0.1",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"remark-gfm": "^4.0.1",
|
|
39
39
|
"vanilla-jsoneditor": "^0.23.1",
|
|
40
40
|
"zustand": "^4.5.4",
|
|
41
|
-
"@fe-free/tool": "2.0.
|
|
41
|
+
"@fe-free/tool": "2.0.5"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"@ant-design/pro-components": "^2.8.7",
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { Column, Line, Pie, Scatter } from '@ant-design/plots';
|
|
2
|
+
import React, { useMemo } from 'react';
|
|
3
|
+
|
|
4
|
+
// 类型定义
|
|
5
|
+
interface ChartData {
|
|
6
|
+
columns: string[];
|
|
7
|
+
rows: (string | number)[][];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface ChartConfig {
|
|
11
|
+
chart_type: 'line' | 'bar' | 'pie' | 'table' | 'scatter';
|
|
12
|
+
x_field?: string;
|
|
13
|
+
y_field?: string;
|
|
14
|
+
angle_field?: string;
|
|
15
|
+
color_field?: string;
|
|
16
|
+
title: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 错误处理组件
|
|
20
|
+
function ChartError(props: { children?: React.ReactNode }) {
|
|
21
|
+
const { children } = props;
|
|
22
|
+
return (
|
|
23
|
+
<div className="markdown-body-chart-block">
|
|
24
|
+
<div style={{ textAlign: 'center', padding: '20px' }}>{children || '图表发生错误'}</div>
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class ErrorBoundary extends React.Component {
|
|
30
|
+
state = { hasError: false };
|
|
31
|
+
|
|
32
|
+
static getDerivedStateFromError() {
|
|
33
|
+
return { hasError: true };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
componentDidCatch(error, info) {
|
|
37
|
+
console.error('Error caught:', error, info);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
render() {
|
|
41
|
+
if (this.state.hasError) {
|
|
42
|
+
return <ChartError />;
|
|
43
|
+
}
|
|
44
|
+
return this.props.children;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 图表容器组件
|
|
49
|
+
function ChartContainer(props: { title: string; children: React.ReactNode }) {
|
|
50
|
+
const { title, children } = props;
|
|
51
|
+
return (
|
|
52
|
+
<div className="markdown-body-chart-block">
|
|
53
|
+
<div className="markdown-body-chart-block-title">{title}</div>
|
|
54
|
+
{children}
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 饼图组件
|
|
60
|
+
function PieChart(props: { data: ChartData; chart: ChartConfig }) {
|
|
61
|
+
const { data, chart } = props;
|
|
62
|
+
const { columns, rows } = data;
|
|
63
|
+
const { angle_field, color_field } = chart;
|
|
64
|
+
|
|
65
|
+
if (!angle_field || !color_field) {
|
|
66
|
+
return <ChartError />;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const angleIndex = columns.indexOf(angle_field);
|
|
70
|
+
const colorIndex = columns.indexOf(color_field);
|
|
71
|
+
|
|
72
|
+
if (angleIndex === -1 || colorIndex === -1) {
|
|
73
|
+
return <ChartError />;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 转换数据格式为 Ant Design Charts 需要的格式
|
|
77
|
+
const chartData = rows.map((row) => ({
|
|
78
|
+
[color_field]: row[colorIndex],
|
|
79
|
+
[angle_field]: Number(row[angleIndex]),
|
|
80
|
+
}));
|
|
81
|
+
|
|
82
|
+
const config = {
|
|
83
|
+
data: chartData,
|
|
84
|
+
angleField: angle_field,
|
|
85
|
+
colorField: color_field,
|
|
86
|
+
label: {
|
|
87
|
+
text: angle_field,
|
|
88
|
+
style: {
|
|
89
|
+
fontWeight: 'bold',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
legend: {
|
|
93
|
+
color: {
|
|
94
|
+
title: false,
|
|
95
|
+
position: 'right',
|
|
96
|
+
rowPadding: 5,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return <Pie {...config} />;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 折线图组件
|
|
105
|
+
function LineChart(props: { data: ChartData; chart: ChartConfig }) {
|
|
106
|
+
const { data, chart } = props;
|
|
107
|
+
const { columns, rows } = data;
|
|
108
|
+
const { x_field, y_field } = chart;
|
|
109
|
+
|
|
110
|
+
if (!x_field || !y_field) {
|
|
111
|
+
return <ChartError />;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const xIndex = columns.indexOf(x_field);
|
|
115
|
+
const yIndex = columns.indexOf(y_field);
|
|
116
|
+
|
|
117
|
+
if (xIndex === -1 || yIndex === -1) {
|
|
118
|
+
return <ChartError />;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 转换数据格式为 Ant Design Charts 需要的格式
|
|
122
|
+
const chartData = rows.map((row) => ({
|
|
123
|
+
[x_field]: row[xIndex],
|
|
124
|
+
[y_field]: Number(row[yIndex]),
|
|
125
|
+
}));
|
|
126
|
+
|
|
127
|
+
const config = {
|
|
128
|
+
data: chartData,
|
|
129
|
+
xField: x_field,
|
|
130
|
+
yField: y_field,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
return <Line {...config} />;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 柱状图组件
|
|
137
|
+
function BarChart(props: { data: ChartData; chart: ChartConfig }) {
|
|
138
|
+
const { data, chart } = props;
|
|
139
|
+
const { columns, rows } = data;
|
|
140
|
+
const { x_field, y_field } = chart;
|
|
141
|
+
|
|
142
|
+
if (!x_field || !y_field) {
|
|
143
|
+
return <ChartError />;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const xIndex = columns.indexOf(x_field);
|
|
147
|
+
const yIndex = columns.indexOf(y_field);
|
|
148
|
+
|
|
149
|
+
if (xIndex === -1 || yIndex === -1) {
|
|
150
|
+
return <ChartError />;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 转换数据格式为 Ant Design Charts 需要的格式
|
|
154
|
+
const chartData = rows.map((row) => ({
|
|
155
|
+
[x_field]: row[xIndex],
|
|
156
|
+
[y_field]: Number(row[yIndex]),
|
|
157
|
+
}));
|
|
158
|
+
|
|
159
|
+
const config = {
|
|
160
|
+
data: chartData,
|
|
161
|
+
xField: x_field,
|
|
162
|
+
yField: y_field,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
return <Column {...config} />;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function ScatterChart(props: { data: ChartData; chart: ChartConfig }) {
|
|
169
|
+
const { data, chart } = props;
|
|
170
|
+
const { columns, rows } = data;
|
|
171
|
+
const { x_field, y_field } = chart;
|
|
172
|
+
|
|
173
|
+
if (!x_field || !y_field) {
|
|
174
|
+
return <ChartError />;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const xIndex = columns.indexOf(x_field);
|
|
178
|
+
const yIndex = columns.indexOf(y_field);
|
|
179
|
+
|
|
180
|
+
if (xIndex === -1 || yIndex === -1) {
|
|
181
|
+
return <ChartError />;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const chartData = rows.map((row) => ({
|
|
185
|
+
[x_field]: row[xIndex],
|
|
186
|
+
[y_field]: row[yIndex],
|
|
187
|
+
}));
|
|
188
|
+
|
|
189
|
+
const config = {
|
|
190
|
+
data: chartData,
|
|
191
|
+
xField: x_field,
|
|
192
|
+
yField: y_field,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
return <Scatter {...config} />;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// 主 ChartBlock 组件
|
|
199
|
+
function ChartBlockBase(props: any) {
|
|
200
|
+
const { children } = props;
|
|
201
|
+
|
|
202
|
+
const chartData = useMemo(() => {
|
|
203
|
+
try {
|
|
204
|
+
return JSON.parse(children);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error('Failed to parse chart data:', error);
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}, [children]);
|
|
210
|
+
|
|
211
|
+
if (!chartData) {
|
|
212
|
+
return <ChartError />;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const { data, chart } = chartData;
|
|
216
|
+
const { chart_type, title } = chart;
|
|
217
|
+
|
|
218
|
+
switch (chart_type) {
|
|
219
|
+
case 'pie':
|
|
220
|
+
return (
|
|
221
|
+
<ChartContainer title={title}>
|
|
222
|
+
<PieChart data={data} chart={chart} />
|
|
223
|
+
</ChartContainer>
|
|
224
|
+
);
|
|
225
|
+
case 'line':
|
|
226
|
+
return (
|
|
227
|
+
<ChartContainer title={title}>
|
|
228
|
+
<LineChart data={data} chart={chart} />
|
|
229
|
+
</ChartContainer>
|
|
230
|
+
);
|
|
231
|
+
case 'bar':
|
|
232
|
+
return (
|
|
233
|
+
<ChartContainer title={title}>
|
|
234
|
+
<BarChart data={data} chart={chart} />
|
|
235
|
+
</ChartContainer>
|
|
236
|
+
);
|
|
237
|
+
case 'scatter':
|
|
238
|
+
return (
|
|
239
|
+
<ChartContainer title={title}>
|
|
240
|
+
<ScatterChart data={data} chart={chart} />
|
|
241
|
+
</ChartContainer>
|
|
242
|
+
);
|
|
243
|
+
case 'table':
|
|
244
|
+
// 表格类型暂不处理
|
|
245
|
+
return null;
|
|
246
|
+
default:
|
|
247
|
+
return <ChartError>不支持的图表类型:{chart_type}</ChartError>;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function ChartBlock(props: any) {
|
|
252
|
+
const { children } = props;
|
|
253
|
+
|
|
254
|
+
return (
|
|
255
|
+
<ErrorBoundary>
|
|
256
|
+
<ChartBlockBase>{children}</ChartBlockBase>
|
|
257
|
+
</ErrorBoundary>
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export { ChartBlock };
|
package/src/markdown/code.tsx
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
2
2
|
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
|
3
|
+
import { ChartBlock } from './chart';
|
|
3
4
|
|
|
4
5
|
function CodeBlock(props: any) {
|
|
5
6
|
const { children, className, ...rest } = props;
|
|
6
7
|
const match = /language-(\w+)/.exec(props.className || '');
|
|
7
8
|
|
|
9
|
+
// 如果是 chart 类型的代码块,使用 ChartBlock 组件
|
|
10
|
+
if (match && match[1] === 'chart') {
|
|
11
|
+
return <ChartBlock>{children}</ChartBlock>;
|
|
12
|
+
}
|
|
13
|
+
|
|
8
14
|
return (
|
|
9
15
|
<div className="markdown-body-code-block">
|
|
10
16
|
{match ? (
|
|
@@ -138,3 +138,160 @@ export const DeepSeekPending: Story = {
|
|
|
138
138
|
`,
|
|
139
139
|
},
|
|
140
140
|
};
|
|
141
|
+
|
|
142
|
+
export const Chart: Story = {
|
|
143
|
+
args: {
|
|
144
|
+
children: `这是一个使用 @ant-design/plots 的 chart 示例
|
|
145
|
+
|
|
146
|
+
## 折线图示例
|
|
147
|
+
|
|
148
|
+
\`\`\`chart
|
|
149
|
+
{
|
|
150
|
+
"data": {
|
|
151
|
+
"columns": ["time", "amount"],
|
|
152
|
+
"rows": [
|
|
153
|
+
["2025-06-03", 18.23],
|
|
154
|
+
["2025-06-10", 112.2],
|
|
155
|
+
["2025-06-23", 10],
|
|
156
|
+
["2025-07-01", 85.5],
|
|
157
|
+
["2025-07-15", 156.8]
|
|
158
|
+
]
|
|
159
|
+
},
|
|
160
|
+
"chart": {
|
|
161
|
+
"chart_type": "line",
|
|
162
|
+
"x_field": "time",
|
|
163
|
+
"y_field": "amount",
|
|
164
|
+
"title": "张三金额变化趋势图"
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
\`\`\`
|
|
168
|
+
|
|
169
|
+
## 柱状图示例
|
|
170
|
+
|
|
171
|
+
\`\`\`chart
|
|
172
|
+
{
|
|
173
|
+
"data": {
|
|
174
|
+
"columns": ["time", "amount"],
|
|
175
|
+
"rows": [
|
|
176
|
+
["2025-06-03", 18.23],
|
|
177
|
+
["2025-06-10", 112.2],
|
|
178
|
+
["2025-06-23", 10],
|
|
179
|
+
["2025-07-01", 85.5],
|
|
180
|
+
["2025-07-15", 156.8]
|
|
181
|
+
]
|
|
182
|
+
},
|
|
183
|
+
"chart": {
|
|
184
|
+
"chart_type": "bar",
|
|
185
|
+
"x_field": "time",
|
|
186
|
+
"y_field": "amount",
|
|
187
|
+
"title": "张三金额变化趋势图"
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
\`\`\`
|
|
191
|
+
|
|
192
|
+
## 饼图示例
|
|
193
|
+
|
|
194
|
+
\`\`\`chart
|
|
195
|
+
{
|
|
196
|
+
"data": {
|
|
197
|
+
"columns": ["category", "sales"],
|
|
198
|
+
"rows": [
|
|
199
|
+
["电子产品", 45],
|
|
200
|
+
["服装", 30],
|
|
201
|
+
["食品", 15],
|
|
202
|
+
["家居", 10]
|
|
203
|
+
]
|
|
204
|
+
},
|
|
205
|
+
"chart": {
|
|
206
|
+
"chart_type": "pie",
|
|
207
|
+
"angle_field": "sales",
|
|
208
|
+
"color_field": "category",
|
|
209
|
+
"title": "销售品类分布"
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
\`\`\`
|
|
213
|
+
|
|
214
|
+
## 散点图示例
|
|
215
|
+
|
|
216
|
+
\`\`\`chart
|
|
217
|
+
{
|
|
218
|
+
"data": {
|
|
219
|
+
"columns": ["width", "height"],
|
|
220
|
+
"rows": [
|
|
221
|
+
[100, 200],
|
|
222
|
+
[300, 400],
|
|
223
|
+
[500, 600]
|
|
224
|
+
]
|
|
225
|
+
},
|
|
226
|
+
"chart": {
|
|
227
|
+
"chart_type": "scatter",
|
|
228
|
+
"x_field": "width",
|
|
229
|
+
"y_field": "height",
|
|
230
|
+
"title": "散点图示例"
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
\`\`\`
|
|
234
|
+
|
|
235
|
+
## 错误处理示例
|
|
236
|
+
|
|
237
|
+
### JSON 解析错误
|
|
238
|
+
|
|
239
|
+
\`\`\`chart
|
|
240
|
+
{
|
|
241
|
+
"data": {
|
|
242
|
+
"columns": ["time", "amount"],
|
|
243
|
+
"rows": [
|
|
244
|
+
["2025-06-03", 18.23],
|
|
245
|
+
["2025-06-10", 112.2]
|
|
246
|
+
]
|
|
247
|
+
},
|
|
248
|
+
"chart": {
|
|
249
|
+
"chart_type": "line",
|
|
250
|
+
"x_field": "time",
|
|
251
|
+
"y_field": "amount",
|
|
252
|
+
"title": "张三金额变化趋势图"
|
|
253
|
+
}
|
|
254
|
+
\`\`\`
|
|
255
|
+
|
|
256
|
+
### 字段不存在错误
|
|
257
|
+
|
|
258
|
+
\`\`\`chart
|
|
259
|
+
{
|
|
260
|
+
"data": {
|
|
261
|
+
"columns": ["time", "amount"],
|
|
262
|
+
"rows": [
|
|
263
|
+
["2025-06-03", 18.23],
|
|
264
|
+
["2025-06-10", 112.2]
|
|
265
|
+
]
|
|
266
|
+
},
|
|
267
|
+
"chart": {
|
|
268
|
+
"chart_type": "line",
|
|
269
|
+
"x_field": "nonexistent_field",
|
|
270
|
+
"y_field": "amount",
|
|
271
|
+
"title": "张三金额变化趋势图"
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
\`\`\`
|
|
275
|
+
|
|
276
|
+
### 不支持的图表类型
|
|
277
|
+
|
|
278
|
+
\`\`\`chart
|
|
279
|
+
{
|
|
280
|
+
"data": {
|
|
281
|
+
"columns": ["time", "amount"],
|
|
282
|
+
"rows": [
|
|
283
|
+
["2025-06-03", 18.23],
|
|
284
|
+
["2025-06-10", 112.2]
|
|
285
|
+
]
|
|
286
|
+
},
|
|
287
|
+
"chart": {
|
|
288
|
+
"chart_type": "some_chart_type",
|
|
289
|
+
"x_field": "time",
|
|
290
|
+
"y_field": "amount",
|
|
291
|
+
"title": "张三金额变化趋势图"
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
\`\`\`
|
|
295
|
+
`,
|
|
296
|
+
},
|
|
297
|
+
};
|
package/src/markdown/style.scss
CHANGED
|
@@ -4,8 +4,21 @@
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
.markdown-body-code-block {
|
|
7
|
+
margin: 0.5rem 0;
|
|
8
|
+
|
|
7
9
|
& > div {
|
|
8
10
|
margin: 0 !important;
|
|
9
11
|
}
|
|
10
12
|
}
|
|
13
|
+
|
|
14
|
+
.markdown-body-chart-block {
|
|
15
|
+
background: white;
|
|
16
|
+
border-radius: 6px;
|
|
17
|
+
|
|
18
|
+
.markdown-body-chart-block-title {
|
|
19
|
+
font-size: 16px;
|
|
20
|
+
font-weight: bold;
|
|
21
|
+
margin-bottom: 10px;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
11
24
|
}
|