@fe-free/core 4.1.40 → 4.1.41
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 +8 -0
- package/package.json +3 -3
- package/src/crud/crud.tsx +5 -2
- package/src/crud/helper.tsx +3 -3
- package/src/crud/table/index.tsx +7 -2
- package/src/crud/use_operate.tsx +1 -1
- package/src/crud_of_pure/crud_of_pure.stories.tsx +7 -1
- package/src/crud_of_pure/index.tsx +52 -2
- package/src/crud_of_pure/style.scss +4 -0
- package/src/index.ts +0 -1
- package/src/markdown/chart.tsx +0 -318
- package/src/markdown/code.tsx +0 -42
- package/src/markdown/deep_seek.tsx +0 -53
- package/src/markdown/hm_chart.tsx +0 -176
- package/src/markdown/index.tsx +0 -53
- package/src/markdown/knowledge_ref.tsx +0 -54
- package/src/markdown/markdown.stories.tsx +0 -526
- package/src/markdown/style.scss +0 -46
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fe-free/core",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.41",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"author": "",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"i18next-icu": "^2.4.1",
|
|
51
51
|
"react": "^19.2.0",
|
|
52
52
|
"react-i18next": "^16.4.0",
|
|
53
|
-
"@fe-free/icons": "4.1.
|
|
54
|
-
"@fe-free/tool": "4.1.
|
|
53
|
+
"@fe-free/icons": "4.1.41",
|
|
54
|
+
"@fe-free/tool": "4.1.41"
|
|
55
55
|
},
|
|
56
56
|
"scripts": {
|
|
57
57
|
"i18n-extract": "rm -rf ./src/locales/zh-CN && npx i18next-cli extract"
|
package/src/crud/crud.tsx
CHANGED
|
@@ -129,6 +129,7 @@ function CRUD<DataSource extends Record<string, any> = any, Key extends string |
|
|
|
129
129
|
if (fullPage) {
|
|
130
130
|
return {
|
|
131
131
|
...tableProps,
|
|
132
|
+
rowKey: tableProps.rowKey || 'id',
|
|
132
133
|
scroll: {
|
|
133
134
|
...getTableScroll(newColumns),
|
|
134
135
|
y: '100%',
|
|
@@ -136,7 +137,10 @@ function CRUD<DataSource extends Record<string, any> = any, Key extends string |
|
|
|
136
137
|
};
|
|
137
138
|
}
|
|
138
139
|
|
|
139
|
-
return
|
|
140
|
+
return {
|
|
141
|
+
...tableProps,
|
|
142
|
+
rowKey: tableProps.rowKey || 'id',
|
|
143
|
+
};
|
|
140
144
|
}, [fullPage, tableProps, newColumns]);
|
|
141
145
|
|
|
142
146
|
return (
|
|
@@ -150,7 +154,6 @@ function CRUD<DataSource extends Record<string, any> = any, Key extends string |
|
|
|
150
154
|
)}
|
|
151
155
|
>
|
|
152
156
|
<Table<DataSource>
|
|
153
|
-
rowKey="id"
|
|
154
157
|
{...newTableProps}
|
|
155
158
|
actionRef={actionRef}
|
|
156
159
|
toolBarRender={toolBarRender}
|
package/src/crud/helper.tsx
CHANGED
|
@@ -15,7 +15,7 @@ function OperateBtn({
|
|
|
15
15
|
}) {
|
|
16
16
|
if (disabled) {
|
|
17
17
|
if (operateText) {
|
|
18
|
-
return <span className="cursor-not-allowed text-
|
|
18
|
+
return <span className="cursor-not-allowed text-03">{operateText}</span>;
|
|
19
19
|
} else {
|
|
20
20
|
return (
|
|
21
21
|
<Tooltip title={title}>
|
|
@@ -27,7 +27,7 @@ function OperateBtn({
|
|
|
27
27
|
|
|
28
28
|
if (operateText) {
|
|
29
29
|
return (
|
|
30
|
-
<span className="cursor-pointer text-
|
|
30
|
+
<span className="cursor-pointer text-primary" onClick={onClick}>
|
|
31
31
|
{operateText}
|
|
32
32
|
</span>
|
|
33
33
|
);
|
|
@@ -36,7 +36,7 @@ function OperateBtn({
|
|
|
36
36
|
return (
|
|
37
37
|
<Tooltip title={title}>
|
|
38
38
|
<span className="cursor-pointer text-lg text-primary" onClick={onClick}>
|
|
39
|
-
{
|
|
39
|
+
{icon}
|
|
40
40
|
</span>
|
|
41
41
|
</Tooltip>
|
|
42
42
|
);
|
package/src/crud/table/index.tsx
CHANGED
|
@@ -4,8 +4,13 @@ import classNames from 'classnames';
|
|
|
4
4
|
import { useMemo } from 'react';
|
|
5
5
|
import './style.scss';
|
|
6
6
|
|
|
7
|
-
interface TableProps<DataSource = any, Params = any, ValueType = 'text'>
|
|
8
|
-
|
|
7
|
+
interface TableProps<DataSource = any, Params = any, ValueType = 'text'> extends ProTableProps<
|
|
8
|
+
DataSource,
|
|
9
|
+
Params,
|
|
10
|
+
ValueType
|
|
11
|
+
> {
|
|
12
|
+
// 要求 rowKey 必须存在
|
|
13
|
+
rowKey: string;
|
|
9
14
|
/** 区别 ProTable columns,默认 search: false */
|
|
10
15
|
columns?: ProTableProps<DataSource, Params, ValueType>['columns'];
|
|
11
16
|
}
|
package/src/crud/use_operate.tsx
CHANGED
|
@@ -224,7 +224,7 @@ function useOperate(props, detailProps, actionRef) {
|
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
return (
|
|
227
|
-
<div className="fec-crud-operate-column flex justify-center gap-4">
|
|
227
|
+
<div className="fec-crud-operate-column flex items-center justify-center gap-4">
|
|
228
228
|
{operateColumnProps?.moreOperator && operateColumnProps.moreOperator(record)}
|
|
229
229
|
{btns}
|
|
230
230
|
{operateColumnProps?.moreOperatorAfter && operateColumnProps.moreOperatorAfter(record)}
|
|
@@ -56,6 +56,7 @@ export const Basic: Story = {
|
|
|
56
56
|
<CRUDOfPure
|
|
57
57
|
actions={['create', 'delete']}
|
|
58
58
|
tableProps={{
|
|
59
|
+
rowKey: 'id',
|
|
59
60
|
columns,
|
|
60
61
|
request: fakeRequest,
|
|
61
62
|
search: {
|
|
@@ -118,6 +119,7 @@ export const WithCreate: Story = {
|
|
|
118
119
|
<CRUDOfPure
|
|
119
120
|
actions={['create', 'delete']}
|
|
120
121
|
tableProps={{
|
|
122
|
+
rowKey: 'id',
|
|
121
123
|
columns,
|
|
122
124
|
request: fakeRequest,
|
|
123
125
|
pagination: false,
|
|
@@ -179,6 +181,7 @@ export const NoSearch: Story = {
|
|
|
179
181
|
<CRUDOfPure
|
|
180
182
|
actions={['create', 'delete']}
|
|
181
183
|
tableProps={{
|
|
184
|
+
rowKey: 'id',
|
|
182
185
|
columns,
|
|
183
186
|
request: fakeRequest,
|
|
184
187
|
pagination: false,
|
|
@@ -233,6 +236,7 @@ export const SpecialToolbar: Story = {
|
|
|
233
236
|
specialToolbar
|
|
234
237
|
actions={['create', 'delete']}
|
|
235
238
|
tableProps={{
|
|
239
|
+
rowKey: 'id',
|
|
236
240
|
columns,
|
|
237
241
|
request: fakeRequest,
|
|
238
242
|
pagination: false,
|
|
@@ -258,7 +262,7 @@ export const SpecialToolbar: Story = {
|
|
|
258
262
|
},
|
|
259
263
|
};
|
|
260
264
|
|
|
261
|
-
export const
|
|
265
|
+
export const SpecialToolbarWithToolBarRender: Story = {
|
|
262
266
|
render: () => {
|
|
263
267
|
const columns = [
|
|
264
268
|
{
|
|
@@ -287,6 +291,7 @@ export const SpecialToolbar2: Story = {
|
|
|
287
291
|
specialToolbar
|
|
288
292
|
actions={['delete']}
|
|
289
293
|
tableProps={{
|
|
294
|
+
rowKey: 'id',
|
|
290
295
|
columns,
|
|
291
296
|
request: fakeRequest,
|
|
292
297
|
pagination: false,
|
|
@@ -346,6 +351,7 @@ export const FullPage: Story = {
|
|
|
346
351
|
specialToolbar
|
|
347
352
|
actions={['create', 'delete']}
|
|
348
353
|
tableProps={{
|
|
354
|
+
rowKey: 'id',
|
|
349
355
|
columns,
|
|
350
356
|
request: fakeRequest,
|
|
351
357
|
search: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
|
-
import { useMemo } from 'react';
|
|
2
|
+
import { useEffect, useId, useMemo } from 'react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import type { CRUDProps } from '../crud';
|
|
5
5
|
import { CRUD } from '../crud';
|
|
@@ -14,11 +14,60 @@ interface CRUDOfPureProps<
|
|
|
14
14
|
specialToolbar?: boolean;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
function useSpecialToolbar({ specialToolbar, id }: { specialToolbar?: boolean; id: string }) {
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (!specialToolbar) return;
|
|
20
|
+
|
|
21
|
+
const container = document.querySelector(
|
|
22
|
+
`.fec-crud-of-pure-${id}.fec-crud-of-pure-special-toolbar`,
|
|
23
|
+
);
|
|
24
|
+
if (!container) return;
|
|
25
|
+
|
|
26
|
+
const connect = () => {
|
|
27
|
+
const toolbarRightDiv = container.querySelector('.ant-pro-table-list-toolbar-right > div');
|
|
28
|
+
const queryFilter = container.querySelector('.ant-pro-query-filter') as HTMLElement | null;
|
|
29
|
+
if (!toolbarRightDiv || !queryFilter) return null;
|
|
30
|
+
|
|
31
|
+
const applyPadding = (width: number) => {
|
|
32
|
+
// 16 是原本的 padding-right。 20 是预留间隔
|
|
33
|
+
queryFilter.style.paddingRight = `${width + 16 + 20}px`;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const ro = new ResizeObserver((entries) => {
|
|
37
|
+
for (const entry of entries) {
|
|
38
|
+
applyPadding(entry.contentRect.width);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
ro.observe(toolbarRightDiv);
|
|
42
|
+
|
|
43
|
+
applyPadding((toolbarRightDiv as HTMLElement).getBoundingClientRect().width);
|
|
44
|
+
return () => ro.disconnect();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
let disconnect: (() => void) | null = connect();
|
|
48
|
+
if (!disconnect) {
|
|
49
|
+
const mo = new MutationObserver(() => {
|
|
50
|
+
disconnect = connect();
|
|
51
|
+
if (disconnect) mo.disconnect();
|
|
52
|
+
});
|
|
53
|
+
mo.observe(container, { childList: true, subtree: true });
|
|
54
|
+
return () => {
|
|
55
|
+
mo.disconnect();
|
|
56
|
+
disconnect?.();
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return disconnect;
|
|
60
|
+
}, [id, specialToolbar]);
|
|
61
|
+
}
|
|
62
|
+
|
|
17
63
|
function CRUDOfPure<
|
|
18
64
|
DataSource extends Record<string, any> = any,
|
|
19
65
|
Key extends string | number = string,
|
|
20
66
|
>(props: CRUDOfPureProps<DataSource, Key>) {
|
|
67
|
+
const id = useId();
|
|
68
|
+
|
|
21
69
|
const { t } = useTranslation();
|
|
70
|
+
useSpecialToolbar({ specialToolbar: props.specialToolbar, id });
|
|
22
71
|
const newColumns = props.tableProps.columns?.map((column) => {
|
|
23
72
|
if (column.search) {
|
|
24
73
|
return {
|
|
@@ -54,6 +103,7 @@ function CRUDOfPure<
|
|
|
54
103
|
{...props}
|
|
55
104
|
className={classNames(
|
|
56
105
|
'fec-crud-of-pure',
|
|
106
|
+
`fec-crud-of-pure-${id}`,
|
|
57
107
|
{
|
|
58
108
|
'fec-crud-of-pure-no-search': noSearch,
|
|
59
109
|
'fec-crud-of-pure-special-toolbar': props.specialToolbar,
|
|
@@ -70,7 +120,7 @@ function CRUDOfPure<
|
|
|
70
120
|
if (typeof props.tableProps.toolBarRender === 'function') {
|
|
71
121
|
originRender = props.tableProps.toolBarRender(...args);
|
|
72
122
|
}
|
|
73
|
-
return [...originRender
|
|
123
|
+
return [...originRender];
|
|
74
124
|
},
|
|
75
125
|
}}
|
|
76
126
|
/>
|
package/src/index.ts
CHANGED
|
@@ -52,7 +52,6 @@ export { RequestError, initErrorHandle } from './global/error';
|
|
|
52
52
|
export { downloadInterceptor } from './global/interceptors';
|
|
53
53
|
export { InfiniteList } from './infinite_list';
|
|
54
54
|
export type { InfiniteListProps } from './infinite_list';
|
|
55
|
-
export { Markdown } from './markdown';
|
|
56
55
|
export { PageLayout, PageLayoutTabs } from './page_layout';
|
|
57
56
|
export type { PageLayoutProps, PageLayoutTabsProps } from './page_layout';
|
|
58
57
|
export { Record, RecordArray } from './record';
|
package/src/markdown/chart.tsx
DELETED
|
@@ -1,318 +0,0 @@
|
|
|
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 ChartConfigBase {
|
|
11
|
-
chart_type: '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
|
-
interface LineChartConfig {
|
|
20
|
-
chart_type: 'line';
|
|
21
|
-
x_field?: string;
|
|
22
|
-
y_field: string | string[];
|
|
23
|
-
angle_field?: string;
|
|
24
|
-
color_field?: string;
|
|
25
|
-
title: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// @ts-ignore
|
|
29
|
-
interface ChartConfig extends ChartConfigBase, ChartConfigLine {}
|
|
30
|
-
|
|
31
|
-
// 错误处理组件
|
|
32
|
-
function ChartError(props: { children?: React.ReactNode }) {
|
|
33
|
-
const { children } = props;
|
|
34
|
-
return (
|
|
35
|
-
<div className="markdown-body-block-chart">
|
|
36
|
-
<div style={{ textAlign: 'center', padding: '20px' }}>{children || '图表发生错误'}</div>
|
|
37
|
-
</div>
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
class ErrorBoundary extends React.Component {
|
|
42
|
-
state = { hasError: false };
|
|
43
|
-
|
|
44
|
-
static getDerivedStateFromError(error) {
|
|
45
|
-
console.error('ErrorBoundary:', error);
|
|
46
|
-
return { hasError: true };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
componentDidCatch(error, info) {
|
|
50
|
-
console.error('Error caught:', error, info);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
render() {
|
|
54
|
-
if (this.state.hasError) {
|
|
55
|
-
return <ChartError />;
|
|
56
|
-
}
|
|
57
|
-
return this.props.children;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// 图表容器组件
|
|
62
|
-
function ChartContainer(props: { title: string; children: React.ReactNode }) {
|
|
63
|
-
const { title, children } = props;
|
|
64
|
-
return (
|
|
65
|
-
<div className="markdown-body-block-chart">
|
|
66
|
-
<div className="markdown-body-block-chart-title">{title}</div>
|
|
67
|
-
{children}
|
|
68
|
-
</div>
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// 饼图组件
|
|
73
|
-
function PieChart(props: { data: ChartData; chart: ChartConfig }) {
|
|
74
|
-
const { data, chart } = props;
|
|
75
|
-
const { columns, rows } = data;
|
|
76
|
-
const { angle_field, color_field } = chart;
|
|
77
|
-
|
|
78
|
-
if (!angle_field || !color_field) {
|
|
79
|
-
return <ChartError />;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const angleIndex = columns.indexOf(angle_field);
|
|
83
|
-
const colorIndex = columns.indexOf(color_field);
|
|
84
|
-
|
|
85
|
-
if (angleIndex === -1 || colorIndex === -1) {
|
|
86
|
-
return <ChartError />;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// 转换数据格式为 Ant Design Charts 需要的格式
|
|
90
|
-
const chartData = rows.map((row) => ({
|
|
91
|
-
[color_field]: row[colorIndex],
|
|
92
|
-
[angle_field]: Number(row[angleIndex]),
|
|
93
|
-
}));
|
|
94
|
-
|
|
95
|
-
const config = {
|
|
96
|
-
data: chartData,
|
|
97
|
-
angleField: angle_field,
|
|
98
|
-
colorField: color_field,
|
|
99
|
-
label: {
|
|
100
|
-
text: angle_field,
|
|
101
|
-
style: {
|
|
102
|
-
fontWeight: 'bold',
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
legend: {
|
|
106
|
-
color: {
|
|
107
|
-
title: false,
|
|
108
|
-
position: 'right',
|
|
109
|
-
rowPadding: 5,
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
return <Pie {...config} />;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// 折线图组件
|
|
118
|
-
function LineChart(props: { data: ChartData; chart: LineChartConfig }) {
|
|
119
|
-
const { data, chart } = props;
|
|
120
|
-
const { columns, rows } = data;
|
|
121
|
-
const { x_field, y_field } = chart;
|
|
122
|
-
|
|
123
|
-
if (!x_field || !y_field) {
|
|
124
|
-
return <ChartError />;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const xIndex = columns.indexOf(x_field);
|
|
128
|
-
if (xIndex === -1) {
|
|
129
|
-
return <ChartError />;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// 处理 y_field 为数组的情况
|
|
133
|
-
if (Array.isArray(y_field)) {
|
|
134
|
-
// 验证所有 y_field 是否存在于 columns 中
|
|
135
|
-
const yIndices = y_field.map((field) => columns.indexOf(field));
|
|
136
|
-
if (yIndices.some((index) => index === -1)) {
|
|
137
|
-
return <ChartError />;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// 转换数据格式为 Ant Design Charts 需要的格式(长数据格式)
|
|
141
|
-
const chartData: any[] = [];
|
|
142
|
-
rows.forEach((row) => {
|
|
143
|
-
y_field.forEach((field, index) => {
|
|
144
|
-
const value = row[yIndices[index]];
|
|
145
|
-
if (value !== null && value !== undefined) {
|
|
146
|
-
chartData.push({
|
|
147
|
-
[x_field]: row[xIndex],
|
|
148
|
-
type: field,
|
|
149
|
-
value: Number(value),
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
const config = {
|
|
156
|
-
data: chartData,
|
|
157
|
-
xField: x_field,
|
|
158
|
-
yField: 'value',
|
|
159
|
-
seriesField: 'type',
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
console.log('config', config);
|
|
163
|
-
|
|
164
|
-
return <Line {...config} />;
|
|
165
|
-
} else {
|
|
166
|
-
// 处理单个 y_field 的情况(保持向后兼容)
|
|
167
|
-
const yIndex = columns.indexOf(y_field);
|
|
168
|
-
if (yIndex === -1) {
|
|
169
|
-
return <ChartError />;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// 转换数据格式为 Ant Design Charts 需要的格式
|
|
173
|
-
const chartData = rows.map((row) => ({
|
|
174
|
-
[x_field]: row[xIndex],
|
|
175
|
-
[y_field]: Number(row[yIndex]),
|
|
176
|
-
}));
|
|
177
|
-
|
|
178
|
-
const config = {
|
|
179
|
-
data: chartData,
|
|
180
|
-
xField: x_field,
|
|
181
|
-
yField: y_field,
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
return <Line {...config} />;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// 柱状图组件
|
|
189
|
-
function BarChart(props: { data: ChartData; chart: ChartConfig }) {
|
|
190
|
-
const { data, chart } = props;
|
|
191
|
-
const { columns, rows } = data;
|
|
192
|
-
const { x_field, y_field } = chart;
|
|
193
|
-
|
|
194
|
-
if (!x_field || !y_field) {
|
|
195
|
-
return <ChartError />;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const xIndex = columns.indexOf(x_field);
|
|
199
|
-
const yIndex = columns.indexOf(y_field);
|
|
200
|
-
|
|
201
|
-
if (xIndex === -1 || yIndex === -1) {
|
|
202
|
-
return <ChartError />;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// 转换数据格式为 Ant Design Charts 需要的格式
|
|
206
|
-
const chartData = rows.map((row) => ({
|
|
207
|
-
[x_field]: row[xIndex],
|
|
208
|
-
[y_field]: Number(row[yIndex]),
|
|
209
|
-
}));
|
|
210
|
-
|
|
211
|
-
const config = {
|
|
212
|
-
data: chartData,
|
|
213
|
-
xField: x_field,
|
|
214
|
-
yField: y_field,
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
return <Column {...config} />;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function ScatterChart(props: { data: ChartData; chart: ChartConfig }) {
|
|
221
|
-
const { data, chart } = props;
|
|
222
|
-
const { columns, rows } = data;
|
|
223
|
-
const { x_field, y_field } = chart;
|
|
224
|
-
|
|
225
|
-
if (!x_field || !y_field) {
|
|
226
|
-
return <ChartError />;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const xIndex = columns.indexOf(x_field);
|
|
230
|
-
const yIndex = columns.indexOf(y_field);
|
|
231
|
-
|
|
232
|
-
if (xIndex === -1 || yIndex === -1) {
|
|
233
|
-
return <ChartError />;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const chartData = rows.map((row) => ({
|
|
237
|
-
[x_field]: row[xIndex],
|
|
238
|
-
[y_field]: row[yIndex],
|
|
239
|
-
}));
|
|
240
|
-
|
|
241
|
-
const config = {
|
|
242
|
-
data: chartData,
|
|
243
|
-
xField: x_field,
|
|
244
|
-
yField: y_field,
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
return <Scatter {...config} />;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// 主 ChartBlock 组件
|
|
251
|
-
function ChartBlockBase(props: any) {
|
|
252
|
-
const { children } = props;
|
|
253
|
-
|
|
254
|
-
const chartData = useMemo(() => {
|
|
255
|
-
try {
|
|
256
|
-
return JSON.parse(children);
|
|
257
|
-
} catch (error) {
|
|
258
|
-
console.error('Failed to parse chart data:', error);
|
|
259
|
-
return null;
|
|
260
|
-
}
|
|
261
|
-
}, [children]);
|
|
262
|
-
|
|
263
|
-
if (!chartData) {
|
|
264
|
-
return <ChartError />;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const { data, chart } = chartData;
|
|
268
|
-
const { chart_type, title } = chart;
|
|
269
|
-
|
|
270
|
-
switch (chart_type) {
|
|
271
|
-
case 'pie':
|
|
272
|
-
return (
|
|
273
|
-
<ChartContainer title={title}>
|
|
274
|
-
<PieChart data={data} chart={chart} />
|
|
275
|
-
</ChartContainer>
|
|
276
|
-
);
|
|
277
|
-
case 'line':
|
|
278
|
-
return (
|
|
279
|
-
<ChartContainer title={title}>
|
|
280
|
-
<LineChart data={data} chart={chart} />
|
|
281
|
-
</ChartContainer>
|
|
282
|
-
);
|
|
283
|
-
case 'bar':
|
|
284
|
-
return (
|
|
285
|
-
<ChartContainer title={title}>
|
|
286
|
-
<BarChart data={data} chart={chart} />
|
|
287
|
-
</ChartContainer>
|
|
288
|
-
);
|
|
289
|
-
case 'scatter':
|
|
290
|
-
return (
|
|
291
|
-
<ChartContainer title={title}>
|
|
292
|
-
<ScatterChart data={data} chart={chart} />
|
|
293
|
-
</ChartContainer>
|
|
294
|
-
);
|
|
295
|
-
case 'table':
|
|
296
|
-
// 表格类型暂不处理
|
|
297
|
-
return null;
|
|
298
|
-
default:
|
|
299
|
-
return <ChartError>不支持的图表类型:{chart_type}</ChartError>;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function ChartBlock(props: any) {
|
|
304
|
-
const { children } = props;
|
|
305
|
-
|
|
306
|
-
// 大模型会返回一些奇怪字符,需要去掉
|
|
307
|
-
// 不间断空格
|
|
308
|
-
// eslint-disable-next-line no-irregular-whitespace
|
|
309
|
-
const content = children?.replace(/ /g, '');
|
|
310
|
-
|
|
311
|
-
return (
|
|
312
|
-
<ErrorBoundary>
|
|
313
|
-
<ChartBlockBase>{content}</ChartBlockBase>
|
|
314
|
-
</ErrorBoundary>
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
export { ChartBlock };
|
package/src/markdown/code.tsx
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
2
|
-
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
|
3
|
-
import { ChartBlock } from './chart';
|
|
4
|
-
import { HMChartBlock } from './hm_chart';
|
|
5
|
-
|
|
6
|
-
function CodeBlock(props: any) {
|
|
7
|
-
const { children, className, ...rest } = props;
|
|
8
|
-
const match = /language-(\w+)/.exec(props.className || '');
|
|
9
|
-
|
|
10
|
-
// 如果是 chart 类型的代码块,使用 ChartBlock 组件
|
|
11
|
-
if (match && match[1] === 'chart') {
|
|
12
|
-
return <ChartBlock>{children}</ChartBlock>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// 如果是 hmchart 类型的代码块,使用 HMChartBlock 组件
|
|
16
|
-
if (match && match[1] === 'hmchart') {
|
|
17
|
-
return <HMChartBlock>{children}</HMChartBlock>;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return (
|
|
21
|
-
<div className="markdown-body-block-code">
|
|
22
|
-
{match ? (
|
|
23
|
-
<SyntaxHighlighter
|
|
24
|
-
{...rest}
|
|
25
|
-
style={vscDarkPlus}
|
|
26
|
-
language={match?.[1]}
|
|
27
|
-
showLineNumbers
|
|
28
|
-
PreTag="div"
|
|
29
|
-
wrapLongLines
|
|
30
|
-
>
|
|
31
|
-
{children}
|
|
32
|
-
</SyntaxHighlighter>
|
|
33
|
-
) : (
|
|
34
|
-
<code {...rest} className={className}>
|
|
35
|
-
{children}
|
|
36
|
-
</code>
|
|
37
|
-
)}
|
|
38
|
-
</div>
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export { CodeBlock };
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { DownOutlined, UpOutlined } from '@fe-free/icons';
|
|
2
|
-
import { useState } from 'react';
|
|
3
|
-
|
|
4
|
-
function DeepSeekBlock(props: { children: string }) {
|
|
5
|
-
const [show, setShow] = useState(true);
|
|
6
|
-
|
|
7
|
-
return (
|
|
8
|
-
<div className="markdown-body-block-deep-seek mb-3 flex flex-col gap-2 text-[12px] text-03">
|
|
9
|
-
<div
|
|
10
|
-
className="cursor-pointer"
|
|
11
|
-
onClick={() => {
|
|
12
|
-
setShow((v) => !v);
|
|
13
|
-
}}
|
|
14
|
-
>
|
|
15
|
-
深度思考 {show ? <UpOutlined /> : <DownOutlined />}
|
|
16
|
-
</div>
|
|
17
|
-
{show && (
|
|
18
|
-
<div className="relative pl-[15px]">
|
|
19
|
-
<div className="top=0 absolute left-0 h-full w-[2px] bg-[#00000014]" />
|
|
20
|
-
{props.children === '<br/>' ? undefined : props.children}
|
|
21
|
-
</div>
|
|
22
|
-
)}
|
|
23
|
-
</div>
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function processWithDeepSeek(text: string) {
|
|
28
|
-
// 开始 <think> 才算开始
|
|
29
|
-
if (!text.startsWith('<think>')) {
|
|
30
|
-
return text;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const [left, right] = text.split('</think>');
|
|
34
|
-
|
|
35
|
-
let newText = text;
|
|
36
|
-
|
|
37
|
-
// 如果 think 部分是 <think>\n\n</think>,相当于没有,则直接返回 right
|
|
38
|
-
if (text.startsWith('<think>\n\n</think>')) {
|
|
39
|
-
newText = right;
|
|
40
|
-
}
|
|
41
|
-
// 否则做一些处理
|
|
42
|
-
else {
|
|
43
|
-
newText =
|
|
44
|
-
left
|
|
45
|
-
.replace('<think>\n', '<think>')
|
|
46
|
-
.replace('\n</think>', '</think>')
|
|
47
|
-
.replace(/\n/g, '<br/>') + (right || '');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return newText;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export { DeepSeekBlock, processWithDeepSeek };
|