@fe-free/core 5.0.0 → 6.0.2

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +391 -5
  2. package/package.json +5 -3
  3. package/src/copy/index.tsx +10 -5
  4. package/src/crud/crud.stories.tsx +13 -1
  5. package/src/crud/crud.tsx +5 -2
  6. package/src/crud/helper.tsx +10 -5
  7. package/src/crud/style.scss +27 -0
  8. package/src/crud/table/index.tsx +7 -2
  9. package/src/crud/table/style.scss +10 -21
  10. package/src/crud/use_operate.tsx +1 -1
  11. package/src/crud_of_list/crud_of_list.stories.tsx +42 -0
  12. package/src/crud_of_list/index.tsx +2 -1
  13. package/src/crud_of_list/style.scss +27 -0
  14. package/src/crud_of_pure/crud_of_pure.stories.tsx +7 -1
  15. package/src/crud_of_pure/index.tsx +52 -2
  16. package/src/crud_of_pure/style.scss +4 -0
  17. package/src/editor/editor.stories.tsx +8 -0
  18. package/src/editor/index.tsx +7 -3
  19. package/src/form/form_list/form_list_modal_helper.tsx +1 -0
  20. package/src/index.ts +4 -1
  21. package/src/infinite_list/index.tsx +5 -2
  22. package/src/markdown/chart.tsx +3 -3
  23. package/src/markdown/code.tsx +19 -32
  24. package/src/markdown/custom_markdown.stories.tsx +485 -0
  25. package/src/markdown/hm_chart.tsx +1 -1
  26. package/src/markdown/index.tsx +93 -34
  27. package/src/markdown/knowledge_ref.tsx +5 -5
  28. package/src/markdown/markdown.stories.tsx +68 -410
  29. package/src/markdown/messages/message_think.tsx +69 -0
  30. package/src/markdown/messages/svgs/think.svg +3 -0
  31. package/src/markdown/think.tsx +55 -0
  32. package/src/page_layout/index.tsx +12 -0
  33. package/src/scroll/helper.tsx +23 -0
  34. package/src/scroll/index.tsx +70 -0
  35. package/src/style.scss +0 -30
  36. package/src/tailwind.css +54 -0
  37. package/src/theme.ts +1 -1
  38. package/src/upload/index.tsx +32 -30
  39. package/src/value_type_map/index.tsx +7 -0
  40. package/src/value_type_map/json_modal.tsx +17 -15
  41. package/src/value_type_map/markdown_modal.tsx +45 -0
  42. package/src/value_type_map/value_type_map.stories.tsx +8 -0
  43. package/src/markdown/deep_seek.tsx +0 -53
  44. package/src/markdown/style.scss +0 -46
  45. package/src/tailwind.config.ts +0 -14
@@ -5,25 +5,14 @@
5
5
  }
6
6
  }
7
7
 
8
- .ant-table-container {
9
- border-inline-start-color: transparent !important;
10
-
11
- .ant-table-thead {
12
- & > tr > th:last-child {
13
- border-inline-end-color: transparent !important;
14
- }
15
- }
16
-
17
- .ant-table-row {
18
- & > td:last-child {
19
- border-inline-end-color: transparent !important;
20
- }
21
- }
22
- }
23
-
24
8
  .ant-pagination {
25
9
  // gap: 8px;
26
10
 
11
+ // 奇怪的问题。 full page 的时候会有滚动条。 需要 overflow: hidden;
12
+ & > li {
13
+ overflow: hidden;
14
+ }
15
+
27
16
  .ant-pagination-options {
28
17
  order: -1;
29
18
  margin-right: 8px;
@@ -32,17 +21,17 @@
32
21
  .ant-pagination-prev,
33
22
  .ant-pagination-next {
34
23
  .ant-pagination-item-link {
35
- border: 1px solid #e2e7f0;
24
+ border: 1px solid var(--ant-color-border);
36
25
  }
37
26
  }
38
27
 
39
28
  .ant-pagination-item {
40
- border: 1px solid #e2e7f0;
29
+ border: 1px solid var(--ant-color-border);
41
30
 
42
31
  &.ant-pagination-item-active {
43
- color: theme('colors.theme08');
44
- background-color: theme('colors.theme03');
45
- border-color: theme('colors.theme05');
32
+ color: var(--color-theme08);
33
+ background-color: var(--color-theme03);
34
+ border-color: var(--color-theme05);
46
35
  }
47
36
  }
48
37
  }
@@ -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)}
@@ -54,6 +54,7 @@ export const Basic: Story = {
54
54
  ref={ref}
55
55
  actions={[]}
56
56
  tableProps={{
57
+ rowKey: 'id',
57
58
  columns,
58
59
  request: fakeRequest,
59
60
  }}
@@ -88,6 +89,45 @@ export const WithSearch: Story = {
88
89
  <CRUDOfList
89
90
  actions={['delete']}
90
91
  tableProps={{
92
+ rowKey: 'id',
93
+ columns,
94
+ request: fakeRequest,
95
+ }}
96
+ requestDeleteByRecord={fakeDeleteByRecord}
97
+ deleteProps={{
98
+ nameIndex: 'name',
99
+ operateIsHidden: (record) => {
100
+ return record.id === '1';
101
+ },
102
+ }}
103
+ detailForm={() => (
104
+ <>
105
+ <ProFormText name="name" label="名字" required rules={[{ required: true }]} />
106
+ </>
107
+ )}
108
+ requestCreateByValues={fakeCreate}
109
+ />
110
+ );
111
+ },
112
+ };
113
+
114
+ export const WithToolbarSticky: Story = {
115
+ render: () => {
116
+ const columns = [
117
+ {
118
+ title: '名字(省略)',
119
+ dataIndex: 'name',
120
+ search: true,
121
+ ellipsis: true,
122
+ },
123
+ ];
124
+
125
+ return (
126
+ <CRUDOfList
127
+ toolbarSticky
128
+ actions={['delete']}
129
+ tableProps={{
130
+ rowKey: 'id',
91
131
  columns,
92
132
  request: fakeRequest,
93
133
  }}
@@ -121,6 +161,7 @@ export const WithCreateDelete: Story = {
121
161
  <CRUDOfList
122
162
  actions={['create', 'delete']}
123
163
  tableProps={{
164
+ rowKey: 'id',
124
165
  columns,
125
166
  request: fakeRequest,
126
167
  }}
@@ -153,6 +194,7 @@ export const NoSearch: Story = {
153
194
  <CRUDOfList
154
195
  actions={['create', 'delete']}
155
196
  tableProps={{
197
+ rowKey: 'id',
156
198
  columns,
157
199
  request: fakeRequest,
158
200
  }}
@@ -12,7 +12,7 @@ interface CRUDOfListProps<
12
12
  DataSource extends Record<string, any> = any,
13
13
  Key extends string | number = string,
14
14
  > extends CRUDProps<DataSource, Key> {
15
- // nothing
15
+ toolbarSticky?: boolean;
16
16
  }
17
17
 
18
18
  function useTips(props) {
@@ -104,6 +104,7 @@ function CRUDOfList(props: CRUDOfListProps) {
104
104
  {
105
105
  // 先这样实现
106
106
  'fec-crud-of-list-no-toolbar': !searchDataIndex && !props.actions.includes('create'),
107
+ 'fec-crud-of-list-toolbar-sticky': props.toolbarSticky,
107
108
  },
108
109
  props.className,
109
110
  )}
@@ -12,6 +12,11 @@
12
12
  &:hover {
13
13
  .ant-table-cell-fix-right {
14
14
  display: block;
15
+
16
+ // 如果操作列没有内容,则隐藏操作列
17
+ &:has(.fec-crud-operate-column:empty) {
18
+ display: none;
19
+ }
15
20
  }
16
21
  }
17
22
  }
@@ -34,4 +39,26 @@
34
39
  padding-block: 0;
35
40
  }
36
41
  }
42
+
43
+ &.fec-crud-of-list-toolbar-sticky {
44
+ .ant-pro-table-list-toolbar {
45
+ position: sticky;
46
+ top: 0;
47
+ z-index: 10;
48
+ background-color: #fff;
49
+ }
50
+ }
51
+
52
+ .ant-table-bordered {
53
+ .ant-table-container {
54
+ border-top: none !important;
55
+ border-left: none !important;
56
+
57
+ .ant-table-row {
58
+ .ant-table-cell {
59
+ border-inline-end: none !important;
60
+ }
61
+ }
62
+ }
63
+ }
37
64
  }
@@ -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
  }
@@ -115,3 +115,11 @@ This is a markdown file.
115
115
  },
116
116
  render: (props) => <BasicDemo {...props} />,
117
117
  };
118
+
119
+ export const HTML: Story = {
120
+ args: {
121
+ language: 'html',
122
+ value: '<html><body><h1>Hello, World!</h1></body></html>',
123
+ },
124
+ render: (props) => <BasicDemo {...props} />,
125
+ };
@@ -1,3 +1,4 @@
1
+ import { html } from '@codemirror/lang-html';
1
2
  import { javascript } from '@codemirror/lang-javascript';
2
3
  import { json } from '@codemirror/lang-json';
3
4
  import { markdown } from '@codemirror/lang-markdown';
@@ -9,7 +10,7 @@ import CodeMirror from '@uiw/react-codemirror';
9
10
  import { useCallback, useMemo } from 'react';
10
11
 
11
12
  interface EditorProps {
12
- language?: 'javascript' | 'python' | 'json' | 'markdown';
13
+ language?: 'javascript' | 'python' | 'json' | 'markdown' | 'html';
13
14
  value?: string;
14
15
  onChange?: (value: string) => void;
15
16
  autoFocus?: boolean;
@@ -73,12 +74,15 @@ function Editor(props: EditorProps) {
73
74
  case 'markdown':
74
75
  result.push(markdown());
75
76
  break;
77
+ case 'html':
78
+ result.push(html());
79
+ break;
76
80
  default:
77
81
  break;
78
82
  }
79
83
 
80
84
  return result;
81
- }, [language, originExtensions]);
85
+ }, [language, originExtensions, lineWrapping]);
82
86
 
83
87
  const handleChange = useCallback(
84
88
  (value: string) => {
@@ -89,7 +93,7 @@ function Editor(props: EditorProps) {
89
93
 
90
94
  return (
91
95
  <CodeMirror
92
- className="w-full h-full"
96
+ className="h-full w-full"
93
97
  height={height || '100%'}
94
98
  width={width || '100%'}
95
99
  value={value}
@@ -65,6 +65,7 @@ function Edit<T>(props: {
65
65
 
66
66
  function ProFormListModalHelper<T = any>(props: ProFormListModalHelperProps<T>) {
67
67
  const options = props.value || emptyArr;
68
+ const { t } = useTranslation();
68
69
 
69
70
  return (
70
71
  <div className="flex flex-col gap-2">
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import './style.scss';
2
+ import './tailwind.css';
2
3
 
3
4
  export { useGlobalInfiniteScroll } from './ahooks/use_global_infinite_scroll';
4
5
  export { useGlobalRequest } from './ahooks/use_global_request';
@@ -52,12 +53,14 @@ export { RequestError, initErrorHandle } from './global/error';
52
53
  export { downloadInterceptor } from './global/interceptors';
53
54
  export { InfiniteList } from './infinite_list';
54
55
  export type { InfiniteListProps } from './infinite_list';
55
- export { Markdown } from './markdown';
56
+ export { CustomMarkdown, Markdown } from './markdown';
57
+ export type { CustomMarkdownProps, MarkdownProps } from './markdown';
56
58
  export { PageLayout, PageLayoutTabs } from './page_layout';
57
59
  export type { PageLayoutProps, PageLayoutTabsProps } from './page_layout';
58
60
  export { Record, RecordArray } from './record';
59
61
  export type { RecordArrayProps, RecordProps } from './record';
60
62
  export { routeTool } from './route';
63
+ export { ScrollFixed, getScrollbarWidth, useScrollFixed } from './scroll';
61
64
  export { NumberSlider, PercentageSlider } from './slider';
62
65
  export type { NumberSliderProps, PercentageSliderProps } from './slider';
63
66
  export { Tabs } from './tabs';
@@ -5,6 +5,7 @@ import { useImperativeHandle, useRef, useState } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
  import stringify from 'safe-stable-stringify';
7
7
  import { useGlobalInfiniteScroll } from '../ahooks/use_global_infinite_scroll';
8
+ import { ScrollFixed } from '../scroll';
8
9
 
9
10
  interface ActionType {
10
11
  reload: () => void;
@@ -22,6 +23,7 @@ interface InfiniteListProps<D, P> {
22
23
  gridClassName: string;
23
24
  className?: string;
24
25
  actionRef?: React.Ref<ActionType | undefined>;
26
+ scrollFixed?: boolean;
25
27
  }
26
28
 
27
29
  const emptyParams = {};
@@ -34,6 +36,7 @@ const InfiniteListBase = <D, P>({
34
36
  pageSize,
35
37
  gridClassName,
36
38
  className,
39
+ scrollFixed = false,
37
40
  }: InfiniteListProps<D, P>) => {
38
41
  const { t } = useTranslation();
39
42
  const ref = useRef<HTMLDivElement>(null);
@@ -91,7 +94,7 @@ const InfiniteListBase = <D, P>({
91
94
  }));
92
95
 
93
96
  return (
94
- <div ref={ref} className={classNames('h-full overflow-y-auto', className)}>
97
+ <ScrollFixed refScroll={ref} className={className} disabled={!scrollFixed}>
95
98
  {loading && (
96
99
  <div className="flex h-full w-full items-center justify-center">
97
100
  <Spin />
@@ -116,7 +119,7 @@ const InfiniteListBase = <D, P>({
116
119
  </div>
117
120
  )}
118
121
  </div>
119
- </div>
122
+ </ScrollFixed>
120
123
  );
121
124
  };
122
125
 
@@ -32,7 +32,7 @@ interface ChartConfig extends ChartConfigBase, ChartConfigLine {}
32
32
  function ChartError(props: { children?: React.ReactNode }) {
33
33
  const { children } = props;
34
34
  return (
35
- <div className="markdown-body-block-chart">
35
+ <div className="fea-markdown-body-block-chart">
36
36
  <div style={{ textAlign: 'center', padding: '20px' }}>{children || '图表发生错误'}</div>
37
37
  </div>
38
38
  );
@@ -62,8 +62,8 @@ class ErrorBoundary extends React.Component {
62
62
  function ChartContainer(props: { title: string; children: React.ReactNode }) {
63
63
  const { title, children } = props;
64
64
  return (
65
- <div className="markdown-body-block-chart">
66
- <div className="markdown-body-block-chart-title">{title}</div>
65
+ <div className="fea-markdown-body-block-chart">
66
+ <div className="fea-markdown-body-block-chart-title">{title}</div>
67
67
  {children}
68
68
  </div>
69
69
  );
@@ -1,42 +1,29 @@
1
- import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
2
- import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
1
+ import { CodeHighlighter, Mermaid } from '@ant-design/x';
2
+ import type { ComponentProps } from '@ant-design/x-markdown';
3
+ import { memo } from 'react';
3
4
  import { ChartBlock } from './chart';
4
5
  import { HMChartBlock } from './hm_chart';
5
6
 
6
- function CodeBlock(props: any) {
7
- const { children, className, ...rest } = props;
8
- const match = /language-(\w+)/.exec(props.className || '');
7
+ const CodeComponent = memo((props: ComponentProps) => {
8
+ const { className, children } = props;
9
9
 
10
- // 如果是 chart 类型的代码块,使用 ChartBlock 组件
11
- if (match && match[1] === 'chart') {
10
+ const lang = className?.match(/language-(\w+)/)?.[1] || '';
11
+
12
+ if (typeof children !== 'string') return null;
13
+
14
+ if (lang === 'mermaid') {
15
+ return <Mermaid>{children}</Mermaid>;
16
+ }
17
+
18
+ if (lang === 'chart') {
12
19
  return <ChartBlock>{children}</ChartBlock>;
13
20
  }
14
21
 
15
- // 如果是 hmchart 类型的代码块,使用 HMChartBlock 组件
16
- if (match && match[1] === 'hmchart') {
22
+ if (lang === 'hmchart') {
17
23
  return <HMChartBlock>{children}</HMChartBlock>;
18
24
  }
19
25
 
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 };
26
+ return <CodeHighlighter lang={lang}>{children}</CodeHighlighter>;
27
+ });
28
+
29
+ export { CodeComponent };