@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
@@ -12,9 +12,13 @@ interface PageLayoutProps {
12
12
  /** beta equalParts */
13
13
  equalParts?: boolean;
14
14
  className?: string;
15
+ style?: React.CSSProperties;
15
16
  startClassName?: string;
17
+ startStyle?: React.CSSProperties;
16
18
  childrenClassName?: string;
19
+ childrenStyle?: React.CSSProperties;
17
20
  endClassName?: string;
21
+ endStyle?: React.CSSProperties;
18
22
  }
19
23
 
20
24
  function PageLayout({
@@ -24,9 +28,13 @@ function PageLayout({
24
28
  end,
25
29
  equalParts,
26
30
  className,
31
+ style,
27
32
  startClassName,
33
+ startStyle,
28
34
  childrenClassName,
35
+ childrenStyle,
29
36
  endClassName,
37
+ endStyle,
30
38
  }: PageLayoutProps) {
31
39
  return (
32
40
  <div
@@ -38,6 +46,7 @@ function PageLayout({
38
46
  },
39
47
  className,
40
48
  )}
49
+ style={style}
41
50
  >
42
51
  {start && (
43
52
  <div
@@ -48,6 +57,7 @@ function PageLayout({
48
57
  },
49
58
  startClassName,
50
59
  )}
60
+ style={startStyle}
51
61
  >
52
62
  {start}
53
63
  </div>
@@ -60,6 +70,7 @@ function PageLayout({
60
70
  },
61
71
  childrenClassName,
62
72
  )}
73
+ style={childrenStyle}
63
74
  >
64
75
  {children}
65
76
  </div>
@@ -72,6 +83,7 @@ function PageLayout({
72
83
  },
73
84
  endClassName,
74
85
  )}
86
+ style={endStyle}
75
87
  >
76
88
  {end}
77
89
  </div>
@@ -0,0 +1,23 @@
1
+ function getScrollbarWidth() {
2
+ const target = document.body;
3
+
4
+ // 创建一个不可见的 div 元素
5
+ const outer = document.createElement('div');
6
+ outer.style.visibility = 'hidden';
7
+ outer.style.overflow = 'scroll'; // 强制显示滚动条
8
+ target.appendChild(outer);
9
+
10
+ // 创建一个内部 div,宽度为 100%
11
+ const inner = document.createElement('div');
12
+ outer.appendChild(inner);
13
+
14
+ // 滚动条宽度 = 外部容器的宽度 - 内部内容的宽度
15
+ const scrollbarWidth = outer.offsetWidth - inner.offsetWidth;
16
+
17
+ // 清理 DOM
18
+ target.removeChild(outer);
19
+
20
+ return scrollbarWidth;
21
+ }
22
+
23
+ export { getScrollbarWidth };
@@ -0,0 +1,70 @@
1
+ import classNames from 'classnames';
2
+ import { useEffect, useMemo, useRef, useState } from 'react';
3
+ import { getScrollbarWidth } from './helper';
4
+
5
+ function useScrollFixed({
6
+ ref,
7
+ disabled,
8
+ }: {
9
+ ref: React.RefObject<HTMLElement | null>;
10
+ disabled?: boolean;
11
+ }) {
12
+ const [marginRight, setMarginRight] = useState(0);
13
+
14
+ const scrollbarWidth = useMemo(() => {
15
+ return getScrollbarWidth();
16
+ }, []);
17
+
18
+ useEffect(() => {
19
+ if (disabled) {
20
+ return;
21
+ }
22
+
23
+ const el = ref.current;
24
+ if (!el) return;
25
+
26
+ const updateMargin = () => {
27
+ const hasVerticalScrollbar = el.scrollHeight > el.clientHeight;
28
+ setMarginRight(hasVerticalScrollbar ? scrollbarWidth : 0);
29
+ };
30
+
31
+ updateMargin();
32
+
33
+ const resizeObserver = new ResizeObserver(updateMargin);
34
+ resizeObserver.observe(el);
35
+ el.addEventListener('scroll', updateMargin);
36
+
37
+ return () => {
38
+ resizeObserver.disconnect();
39
+ el.removeEventListener('scroll', updateMargin);
40
+ };
41
+ }, [ref, scrollbarWidth, disabled]);
42
+
43
+ return { marginRight };
44
+ }
45
+
46
+ interface ScrollFixedProps {
47
+ refScroll?: React.RefObject<HTMLDivElement | null>;
48
+ disabled?: boolean;
49
+ className?: string;
50
+ style?: React.CSSProperties;
51
+ children: React.ReactNode;
52
+ }
53
+
54
+ function ScrollFixed({ refScroll, className, children, disabled, ...rest }: ScrollFixedProps) {
55
+ const innerRef = useRef<HTMLDivElement>(null);
56
+ const ref = refScroll || innerRef;
57
+ const { marginRight } = useScrollFixed({ ref, disabled });
58
+
59
+ return (
60
+ <div
61
+ ref={ref}
62
+ {...rest}
63
+ className={classNames('h-full w-full overflow-y-auto overflow-x-hidden', className)}
64
+ >
65
+ <div style={{ marginRight: `-${marginRight || 0}px` }}>{children}</div>
66
+ </div>
67
+ );
68
+ }
69
+
70
+ export { getScrollbarWidth, ScrollFixed, useScrollFixed };
package/src/style.scss CHANGED
@@ -1,33 +1,3 @@
1
- /** 和 antd 冲突,不开启 */
2
- /* stylelint-disable-next-line at-rule-no-unknown */
3
- // @tailwind base;
4
-
5
- // base
6
- * {
7
- border: 0 solid;
8
- box-sizing: border-box;
9
- }
10
-
11
- /* stylelint-disable-next-line at-rule-no-unknown */
12
- @tailwind components;
13
-
14
- /* stylelint-disable-next-line at-rule-no-unknown */
15
- @tailwind utilities;
16
-
17
- ::-webkit-scrollbar {
18
- width: 8px;
19
- height: 8px;
20
- }
21
-
22
- ::-webkit-scrollbar-thumb {
23
- background: #ccc;
24
- border-radius: 2px;
25
- }
26
-
27
- ::-webkit-scrollbar-track {
28
- background: transparent;
29
- }
30
-
31
1
  .fec-app-hidden-form-item-label-colon {
32
2
  // 隐藏 label 的冒号
33
3
  .ant-form-item .ant-form-item-label > label::after {
@@ -0,0 +1,54 @@
1
+ @import 'tailwindcss';
2
+
3
+ /* stylelint-disable-next-line */
4
+ @theme {
5
+ --color-primary: #0374e9;
6
+ --color-theme09: #0368d2;
7
+ --color-theme08: #0374e9;
8
+ --color-theme05: #a2cbf7;
9
+ --color-theme03: #e6f1fd;
10
+ --color-theme02: #f0f7fe;
11
+ --color-red09: #e64547;
12
+ --color-red08: #ff4d4f;
13
+ --color-red05: #ffb8b9;
14
+ --color-red03: #ffeded;
15
+ --color-green09: #01a468;
16
+ --color-green08: #01b673;
17
+ --color-green05: #9be5c8;
18
+ --color-green03: #ddf9ec;
19
+ --color-yellow09: #bf7a05;
20
+ --color-yellow08: #faad14;
21
+ --color-yellow05: #eecf9b;
22
+ --color-yellow03: #f6e7cd;
23
+ --text-color-01: #15191e;
24
+ --text-color-02: #444;
25
+ --text-color-03: #777;
26
+ --text-color-04: #bfbfbf;
27
+ --border-color-01: #e2e7f0;
28
+ --border-color-02: #d5dde9;
29
+ --border-color-03: #c0c7d2;
30
+ --background-color-01: #f1f3f5;
31
+ --background-color-02: #ececec;
32
+ --background-color-03: #d9d9d9;
33
+ --background-color-04: #c0c0c0;
34
+ }
35
+
36
+ @layer base {
37
+ * {
38
+ -webkit-font-smoothing: antialiased;
39
+ }
40
+
41
+ ::-webkit-scrollbar {
42
+ width: 8px;
43
+ height: 8px;
44
+ }
45
+
46
+ ::-webkit-scrollbar-thumb {
47
+ background: #ccc;
48
+ border-radius: 2px;
49
+ }
50
+
51
+ ::-webkit-scrollbar-track {
52
+ background: transparent;
53
+ }
54
+ }
package/src/theme.ts CHANGED
@@ -28,7 +28,7 @@ const themeVariables = {
28
28
  '01': '#15191e', // 主要
29
29
  '02': '#444444', // 次要
30
30
  '03': '#777777', // 描述
31
- '04': '#94999f', // placeholder
31
+ '04': '#bfbfbf', // placeholder
32
32
  },
33
33
  borderColor: {
34
34
  '01': '#e2e7f0', // 主要
@@ -1,4 +1,4 @@
1
- import { DeleteOutlined, InboxOutlined, PlusOutlined, UploadOutlined } from '@fe-free/icons';
1
+ import { CloseOutlined, InboxOutlined, PlusOutlined, UploadOutlined } from '@fe-free/icons';
2
2
  import type { UploadProps as AntdUploadProps, UploadFile } from 'antd';
3
3
  import { Upload as AntdUpload, App, Avatar, Button } from 'antd';
4
4
  import type { UploadChangeParam } from 'antd/es/upload';
@@ -272,37 +272,39 @@ interface AvatarImageUploadProps {
272
272
  }
273
273
  function AvatarImageUpload(props: AvatarImageUploadProps) {
274
274
  const { value, onChange, action, customRequest, accept = 'image/*', headers } = props;
275
- const { t } = useTranslation();
276
275
 
277
276
  return (
278
- <div className="flex gap-2">
279
- <Avatar size={80} src={value} shape="square" className="bg-01 shadow" />
280
-
281
- <div className="flex flex-1 flex-col justify-between">
282
- <div className="text-03">{t('@fe-free/core.upload.pleaseSelect', '请选择')}</div>
283
- <div className="flex gap-2">
284
- <AntdUpload
285
- action={action}
286
- customRequest={customRequest}
287
- onChange={(info) => {
288
- const url = info.file.response?.data?.url;
289
- if (url) {
290
- onChange?.(url);
291
- }
292
- }}
293
- itemRender={() => null}
294
- accept={accept}
295
- headers={headers}
296
- >
297
- <Button icon={<UploadOutlined />}>
298
- {t('@fe-free/core.upload.localUpload', '本地上传')}
299
- </Button>
300
- </AntdUpload>
301
- <Button icon={<DeleteOutlined />} danger onClick={() => onChange?.()}>
302
- {t('@fe-free/core.upload.delete', '删除')}
303
- </Button>
304
- </div>
305
- </div>
277
+ <div>
278
+ <AntdUpload
279
+ action={action}
280
+ customRequest={customRequest}
281
+ onChange={(info) => {
282
+ const url = info.file.response?.data?.url;
283
+ if (url) {
284
+ onChange?.(url);
285
+ }
286
+ }}
287
+ accept={accept}
288
+ headers={headers}
289
+ itemRender={() => null}
290
+ >
291
+ {value ? (
292
+ <div className="group relative h-20 w-20 cursor-pointer">
293
+ <Avatar size={80} src={value} shape="square" className="bg-01 shadow" />
294
+ <CloseOutlined
295
+ className="absolute right-1 top-1 cursor-pointer rounded-full bg-black/50 p-1 text-[10px] text-white opacity-0 transition-opacity group-hover:opacity-100"
296
+ onClick={(e) => {
297
+ e.stopPropagation();
298
+ onChange?.();
299
+ }}
300
+ />
301
+ </div>
302
+ ) : (
303
+ <div className="flex h-20 w-20 cursor-pointer items-center justify-center rounded bg-01 shadow transition-colors hover:bg-02">
304
+ <PlusOutlined className="text-xl text-02" />
305
+ </div>
306
+ )}
307
+ </AntdUpload>
306
308
  </div>
307
309
  );
308
310
  }
@@ -2,6 +2,7 @@ import type { ProRenderFieldPropsType } from '@ant-design/pro-components';
2
2
  import { dateRender } from './date';
3
3
  import { jsonRender } from './json';
4
4
  import { jsonModalRender } from './json_modal';
5
+ import { markdownModalRender } from './markdown_modal';
5
6
  import { switchNumberRender, switchOptionsRender } from './switch';
6
7
 
7
8
  enum CustomValueTypeEnum {
@@ -13,6 +14,8 @@ enum CustomValueTypeEnum {
13
14
  CustomJSON = 'CustomJSON',
14
15
  /** JSON Modal */
15
16
  CustomJSONModal = 'CustomJSONModal',
17
+ /** Markdown Modal */
18
+ CustomMarkdownModal = 'CustomMarkdownModal',
16
19
  /** 渲染开关 */
17
20
  CustomSwitchNumber = 'CustomSwitchNumber',
18
21
  CustomSwitchOptions = 'CustomSwitchOptions',
@@ -37,6 +40,10 @@ const customValueTypeMap: Record<string, ProRenderFieldPropsType> = {
37
40
  render: jsonModalRender.render,
38
41
  renderFormItem: jsonModalRender.renderFormItem,
39
42
  },
43
+ [CustomValueTypeEnum.CustomMarkdownModal]: {
44
+ render: markdownModalRender.render,
45
+ renderFormItem: markdownModalRender.renderFormItem,
46
+ },
40
47
  [CustomValueTypeEnum.CustomSwitchNumber]: {
41
48
  render: switchNumberRender.render,
42
49
  renderFormItem: switchNumberRender.renderFormItem,
@@ -31,21 +31,23 @@ function Render(text, props: ProFormItemProps<JSONModalProps>) {
31
31
  return (
32
32
  <>
33
33
  <a onClick={() => setShow(true)}>{title}</a>
34
- <Modal
35
- title={title}
36
- open={show}
37
- onCancel={() => setShow(false)}
38
- onOk={() => setShow(false)}
39
- cancelButtonProps={{
40
- style: {
41
- display: 'none',
42
- },
43
- }}
44
- >
45
- <div className="h-[500px]">
46
- <EditorJSON value={jsonText} readonly />
47
- </div>
48
- </Modal>
34
+ {show && (
35
+ <Modal
36
+ title={title}
37
+ open
38
+ onCancel={() => setShow(false)}
39
+ onOk={() => setShow(false)}
40
+ cancelButtonProps={{
41
+ style: {
42
+ display: 'none',
43
+ },
44
+ }}
45
+ >
46
+ <div>
47
+ <EditorJSON value={jsonText} readonly />
48
+ </div>
49
+ </Modal>
50
+ )}
49
51
  </>
50
52
  );
51
53
  }
@@ -0,0 +1,45 @@
1
+ import { Modal } from 'antd';
2
+ import { useState } from 'react';
3
+ import { Markdown } from '../markdown';
4
+
5
+ function Render(text) {
6
+ const [show, setShow] = useState(false);
7
+
8
+ if (!text) {
9
+ return <div>-</div>;
10
+ }
11
+
12
+ return (
13
+ <>
14
+ <div onClick={() => setShow(true)} className="flex cursor-pointer items-center">
15
+ <div className="flex-1 truncate">{text}</div>
16
+ <span className="min-w-0 text-primary">查看</span>
17
+ </div>
18
+ {show && (
19
+ <Modal
20
+ title="Markdown"
21
+ open
22
+ onCancel={() => setShow(false)}
23
+ onOk={() => setShow(false)}
24
+ cancelButtonProps={{
25
+ style: {
26
+ display: 'none',
27
+ },
28
+ }}
29
+ width={900}
30
+ >
31
+ <div>
32
+ <Markdown content={text} />
33
+ </div>
34
+ </Modal>
35
+ )}
36
+ </>
37
+ );
38
+ }
39
+
40
+ const markdownModalRender = {
41
+ render: Render,
42
+ renderFormItem: () => <></>,
43
+ };
44
+
45
+ export { markdownModalRender };
@@ -25,6 +25,7 @@ async function fakeRequest() {
25
25
  dateNumber: +dayjs('2024-10-01'),
26
26
  seconds: Math.abs(+dayjs('2024-10-01') / 1000),
27
27
  jsonText: JSON.stringify({ name: 'hello world hello world hello world' }),
28
+ markdownText: `# Hello World\n\nThis is a markdown text.`,
28
29
  switchNumber: Math.random() > 0.5 ? 1 : 0,
29
30
  switchOptions: Math.random() > 0.5 ? 'ON' : 'OFF',
30
31
  }));
@@ -84,6 +85,12 @@ const Table = () => {
84
85
  ellipsis: true,
85
86
  valueType: CustomValueTypeEnum.CustomJSONModal,
86
87
  },
88
+ {
89
+ title: 'markdownModal',
90
+ dataIndex: 'markdownText',
91
+ ellipsis: true,
92
+ valueType: CustomValueTypeEnum.CustomMarkdownModal,
93
+ },
87
94
  {
88
95
  title: '开关 number',
89
96
  dataIndex: 'switchNumber',
@@ -104,6 +111,7 @@ const Table = () => {
104
111
  <CRUD
105
112
  actions={[]}
106
113
  tableProps={{
114
+ rowKey: 'id',
107
115
  columns,
108
116
  request: fakeRequest,
109
117
  }}
@@ -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 };
@@ -1,46 +0,0 @@
1
- .markdown-body {
2
- font-size: 14px;
3
-
4
- pre {
5
- padding: 0;
6
- }
7
-
8
- .markdown-body-block-code {
9
- margin: 0.5rem 0;
10
-
11
- & > div {
12
- margin: 0 !important;
13
- }
14
- }
15
-
16
- .markdown-body-block-chart {
17
- background: white;
18
- border-radius: 6px;
19
- margin: 0.5rem;
20
-
21
- .markdown-body-block-chart-title {
22
- font-size: 16px;
23
- font-weight: bold;
24
- padding: 10px;
25
- }
26
- }
27
-
28
- .markdown-body-block-knowledge-ref[data-id] {
29
- height: 16px;
30
- min-width: 16px;
31
- cursor: pointer;
32
- display: inline-flex;
33
- align-items: center;
34
- justify-content: center;
35
- border-radius: 999px;
36
- border: 1px solid #0374e9;
37
- color: #0374e9;
38
- font-size: 12px;
39
- padding: 0 4px;
40
- }
41
-
42
- .markdown-body-block-knowledge-ref-source {
43
- color: #0374e9;
44
- cursor: pointer;
45
- }
46
- }
@@ -1,14 +0,0 @@
1
- /** @type {import('tailwindcss').Config} */
2
- import { themeVariables } from './theme';
3
-
4
- module.exports = {
5
- theme: {
6
- extend: {
7
- colors: themeVariables.color,
8
- textColor: themeVariables.textColor,
9
- borderColor: themeVariables.borderColor,
10
- backgroundColor: themeVariables.backgroundColor,
11
- },
12
- },
13
- plugins: [],
14
- };