@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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # @fe-free/core
2
2
 
3
+ ## 4.1.41
4
+
5
+ ### Patch Changes
6
+
7
+ - feat: some
8
+ - @fe-free/icons@4.1.41
9
+ - @fe-free/tool@4.1.41
10
+
3
11
  ## 4.1.40
4
12
 
5
13
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fe-free/core",
3
- "version": "4.1.40",
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.40",
54
- "@fe-free/tool": "4.1.40"
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 tableProps;
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}
@@ -15,7 +15,7 @@ function OperateBtn({
15
15
  }) {
16
16
  if (disabled) {
17
17
  if (operateText) {
18
- return <span className="cursor-not-allowed text-lg text-03">{operateText || icon}</span>;
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-lg text-primary" onClick={onClick}>
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
- {operateText || icon}
39
+ {icon}
40
40
  </span>
41
41
  </Tooltip>
42
42
  );
@@ -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
- extends ProTableProps<DataSource, Params, ValueType> {
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
  }
@@ -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 SpecialToolbar2: Story = {
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, <div key="fake" style={{ height: '32px' }} />];
123
+ return [...originRender];
74
124
  },
75
125
  }}
76
126
  />
@@ -69,6 +69,10 @@
69
69
  z-index: 10;
70
70
  pointer-events: none;
71
71
 
72
+ .ant-pro-table-list-toolbar-left {
73
+ margin-block-end: 0;
74
+ }
75
+
72
76
  .ant-pro-table-list-toolbar-right > div {
73
77
  pointer-events: auto;
74
78
  }
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';
@@ -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 };
@@ -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 };