@fe-free/core 1.0.1

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 ADDED
@@ -0,0 +1,9 @@
1
+ # @fe-free/core
2
+
3
+ ## 1.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - feat: first commit
8
+ - Updated dependencies
9
+ - @fe-free/tool@1.0.1
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@fe-free/core",
3
+ "version": "1.0.1",
4
+ "description": "",
5
+ "main": "./src/index.ts",
6
+ "author": "",
7
+ "license": "ISC",
8
+ "publishConfig": {
9
+ "access": "public",
10
+ "registry": "https://registry.npmjs.org/"
11
+ },
12
+ "dependencies": {
13
+ "@ant-design/icons": "^5.2.6",
14
+ "@ant-design/plots": "^2.2.5",
15
+ "@codemirror/language": "^6.10.3",
16
+ "@codemirror/view": "^6.33.0",
17
+ "@lezer/highlight": "^1.2.1",
18
+ "@uiw/codemirror-themes": "^4.23.2",
19
+ "@uiw/react-codemirror": "^4.23.2",
20
+ "ace-builds": "^1.33.1",
21
+ "ahooks": "^3.7.8",
22
+ "axios": "^1.6.5",
23
+ "classnames": "^2.5.1",
24
+ "dayjs": "~1.11.10",
25
+ "lodash-es": "^4.17.21",
26
+ "react-ace": "^11.0.1",
27
+ "react-markdown": "^9.0.1",
28
+ "react-router-dom": "^6.16.0",
29
+ "react-syntax-highlighter": "^15.5.0",
30
+ "vanilla-jsoneditor": "^0.23.1",
31
+ "zustand": "^4.5.4",
32
+ "@fe-free/tool": "1.0.1"
33
+ },
34
+ "peerDependencies": {
35
+ "@ant-design/pro-components": "^2.7.15",
36
+ "antd": "^5.20.0",
37
+ "react": "^18.2.0"
38
+ },
39
+ "scripts": {
40
+ "test": "echo \"Error: no test specified\" && exit 1"
41
+ }
42
+ }
@@ -0,0 +1,46 @@
1
+ ---
2
+ group: 'core'
3
+ toc: content
4
+ ---
5
+
6
+ # Button
7
+
8
+ ## LoadingButton
9
+
10
+ ```tsx
11
+ import { LoadingButton } from '@fe-free/core';
12
+
13
+ export default () => {
14
+ return (
15
+ <div>
16
+ <LoadingButton
17
+ onClick={() => {
18
+ return;
19
+ }}
20
+ >
21
+ 点击
22
+ </LoadingButton>
23
+
24
+ <LoadingButton
25
+ onClick={() =>
26
+ new Promise((resolve) => {
27
+ setTimeout(resolve, 1000);
28
+ })
29
+ }
30
+ >
31
+ 点击 1000ms resolve
32
+ </LoadingButton>
33
+
34
+ <LoadingButton
35
+ onClick={() =>
36
+ new Promise((_, reject) => {
37
+ setTimeout(reject, 1000);
38
+ })
39
+ }
40
+ >
41
+ 点击 1000ms reject
42
+ </LoadingButton>
43
+ </div>
44
+ );
45
+ };
46
+ ```
@@ -0,0 +1,22 @@
1
+ import type { ButtonProps } from 'antd';
2
+ import { Button } from 'antd';
3
+ import { useCallback, useState } from 'react';
4
+
5
+ function LoadingButton({ onClick, ...rest }: ButtonProps) {
6
+ const [loading, setLoading] = useState(false);
7
+
8
+ const handleClick = useCallback(
9
+ (event) => {
10
+ setLoading(true);
11
+
12
+ Promise.resolve(onClick && onClick(event)).finally(() => {
13
+ setLoading(false);
14
+ });
15
+ },
16
+ [onClick]
17
+ );
18
+
19
+ return <Button loading={loading} {...rest} onClick={handleClick} />;
20
+ }
21
+
22
+ export { LoadingButton };
@@ -0,0 +1,258 @@
1
+ import type { ProFormInstance, ActionType } from '@ant-design/pro-components';
2
+ import { Space, Button } from 'antd';
3
+ import type { ReactNode } from 'react';
4
+ import { forwardRef, useCallback, useImperativeHandle, useMemo, useRef } from 'react';
5
+ import { Link, useLocation } from 'react-router-dom';
6
+ import type { TableProps } from '../table';
7
+ import { Table } from '../table';
8
+ import { OperateDelete } from './crud_delete';
9
+ import { CRUDDetail } from './crud_detail';
10
+ import './style.scss';
11
+
12
+ /**
13
+ * create 创建
14
+ * read 查看
15
+ * read_detail 详情页查看
16
+ * update 编辑
17
+ * delete 删除
18
+ */
19
+ type CrudAction = 'create' | 'read' | 'read_detail' | 'update' | 'delete';
20
+
21
+ interface CRUDProps {
22
+ actions: CrudAction[];
23
+
24
+ /** 新建按钮,默认新建 */
25
+ createButton?: ReactNode;
26
+
27
+ /** 表格相关 */
28
+ tableProps: TableProps;
29
+ operateColumnProps?: {
30
+ width?: number;
31
+ /** 扩展操作区域 */
32
+ moreOperator?: (record) => ReactNode;
33
+ };
34
+ readProps?: {
35
+ /** 文本 */
36
+ operateText?: string;
37
+ };
38
+
39
+ /** 删除接口 */
40
+ deleteByRecord?: (record) => Promise<any>;
41
+ /** 删除相关 */
42
+ deleteProps?: {
43
+ /** 显示名称索引 */
44
+ nameIndex: string;
45
+ /** 删除确认描述 */
46
+ desc?: string;
47
+ /** 文本 */
48
+ operateText?: string;
49
+ };
50
+
51
+ /** 弹窗表单 */
52
+ detailForm?: (formProps: { readonly: boolean }, info: { action: CrudAction }) => ReactNode;
53
+ /** detailForm 的 formRef */
54
+ detailFormInstance?: ProFormInstance;
55
+
56
+ /** 新增接口 */
57
+ requestCreate?: (values) => Promise<any>;
58
+ createProps?: {
59
+ /** 成功文案 */
60
+ successText?: string | (() => string);
61
+ };
62
+
63
+ /** 更新接口 */
64
+ requestUpdateById?: (values) => Promise<any>;
65
+ updateProps?: {
66
+ /** 文本 */
67
+ operateText?: string;
68
+ /** 成功文案 */
69
+ successText?: string | (() => string);
70
+ };
71
+
72
+ /** 获取详情接口 */
73
+ requestGetByRecord?: (record) => Promise<any>;
74
+
75
+ /** 跳转到详情的索引 ,默认 id */
76
+ detailIdIndex?: string;
77
+ }
78
+
79
+ interface CRUDMethods {
80
+ getActionRef: () => React.MutableRefObject<ActionType | undefined>;
81
+ }
82
+
83
+ const CRUD = forwardRef<CRUDMethods, CRUDProps>(function CRUD(props, ref) {
84
+ const {
85
+ actions,
86
+ tableProps,
87
+ createButton,
88
+ operateColumnProps,
89
+ readProps,
90
+ deleteByRecord,
91
+ deleteProps,
92
+ detailIdIndex,
93
+ detailForm,
94
+ requestGetByRecord,
95
+ createProps,
96
+ requestCreate,
97
+ updateProps,
98
+ requestUpdateById,
99
+ detailFormInstance,
100
+ } = props;
101
+
102
+ const actionRef = useRef<ActionType>();
103
+ const location = useLocation();
104
+
105
+ useImperativeHandle(
106
+ ref,
107
+ () => {
108
+ return {
109
+ getActionRef: () => actionRef,
110
+ };
111
+ },
112
+ [actionRef]
113
+ );
114
+
115
+ const detailProps = useMemo(
116
+ () => ({
117
+ detailForm,
118
+ requestGetByRecord,
119
+ requestCreate,
120
+ requestUpdateById,
121
+ detailFormInstance,
122
+ createProps,
123
+ updateProps,
124
+ }),
125
+ [
126
+ detailForm,
127
+ requestGetByRecord,
128
+ requestCreate,
129
+ requestUpdateById,
130
+ detailFormInstance,
131
+ createProps,
132
+ updateProps,
133
+ ]
134
+ );
135
+
136
+ const getHandleDelete = useCallback(
137
+ (record) => {
138
+ return () => {
139
+ if (deleteByRecord) {
140
+ return deleteByRecord(record).then(() => {
141
+ actionRef.current?.reload();
142
+ });
143
+ }
144
+
145
+ throw new Error('没有传 deleteByRecord');
146
+ };
147
+ },
148
+ [deleteByRecord]
149
+ );
150
+
151
+ const handleReload = useCallback(() => {
152
+ actionRef.current?.reload();
153
+ }, []);
154
+
155
+ const newColumns = useMemo(() => {
156
+ const operateColumn = {
157
+ title: '操作',
158
+ fixed: 'right',
159
+ width: operateColumnProps?.width || 120,
160
+ render: function (_, record) {
161
+ return (
162
+ <Space>
163
+ {operateColumnProps?.moreOperator && operateColumnProps.moreOperator(record)}
164
+ {actions.includes('read') && (
165
+ <CRUDDetail
166
+ id={record.id}
167
+ record={record}
168
+ onSuccess={handleReload}
169
+ trigger={<a>{readProps?.operateText || '查看'}</a>}
170
+ action="read"
171
+ {...detailProps}
172
+ />
173
+ )}
174
+ {actions.includes('read_detail') && (
175
+ <Link to={`${location.pathname}/detail/${record[detailIdIndex || 'id']}`}>
176
+ {readProps?.operateText || '查看'}
177
+ </Link>
178
+ )}
179
+ {actions.includes('update') && (
180
+ <CRUDDetail
181
+ id={record.id}
182
+ record={record}
183
+ onSuccess={handleReload}
184
+ trigger={<a>{updateProps?.operateText || '编辑'}</a>}
185
+ action="update"
186
+ {...detailProps}
187
+ />
188
+ )}
189
+ {actions.includes('delete') && deleteProps && (
190
+ <OperateDelete
191
+ name={record[deleteProps.nameIndex]}
192
+ desc={deleteProps.desc}
193
+ operateText={deleteProps.operateText}
194
+ onDelete={getHandleDelete(record)}
195
+ />
196
+ )}
197
+ </Space>
198
+ );
199
+ },
200
+ };
201
+
202
+ if (
203
+ actions.includes('read') ||
204
+ actions.includes('read_detail') ||
205
+ actions.includes('update') ||
206
+ actions.includes('delete')
207
+ ) {
208
+ return [
209
+ ...(tableProps.columns || tableProps.columns || []),
210
+ operateColumn,
211
+ ] as TableProps['columns'];
212
+ }
213
+
214
+ return tableProps.columns as TableProps['columns'];
215
+ }, [
216
+ actions,
217
+ readProps?.operateText,
218
+ deleteProps,
219
+ detailIdIndex,
220
+ detailProps,
221
+ getHandleDelete,
222
+ handleReload,
223
+ tableProps.columns,
224
+ location.pathname,
225
+ operateColumnProps,
226
+ updateProps?.operateText,
227
+ ]);
228
+
229
+ const toolBarRender = useCallback(
230
+ (...args) => [
231
+ ...(tableProps.toolBarRender ? tableProps.toolBarRender(...args) : []),
232
+ actions.includes('create') && (
233
+ <CRUDDetail
234
+ onSuccess={handleReload}
235
+ trigger={createButton || <Button type="primary">新建</Button>}
236
+ action="create"
237
+ {...detailProps}
238
+ />
239
+ ),
240
+ ],
241
+ [actions, createButton, detailProps, handleReload, tableProps]
242
+ );
243
+
244
+ return (
245
+ <div className="crud-table">
246
+ <Table
247
+ rowKey="id"
248
+ {...tableProps}
249
+ actionRef={actionRef}
250
+ toolBarRender={toolBarRender}
251
+ columns={newColumns}
252
+ />
253
+ </div>
254
+ );
255
+ });
256
+
257
+ export { CRUD };
258
+ export type { CRUDProps, CRUDMethods };
@@ -0,0 +1,51 @@
1
+ import { useCallback } from 'react';
2
+ import { Modal } from 'antd';
3
+
4
+ interface Params {
5
+ name: string;
6
+ desc?: string;
7
+ operateText?: string;
8
+ onDelete: () => Promise<any>;
9
+ }
10
+ function useDelete(params: Params) {
11
+ const { name, desc, onDelete } = params;
12
+
13
+ const doDelete = useCallback(() => {
14
+ Modal.confirm({
15
+ title: `确认删除 “${name}” 吗?`,
16
+ content: desc || '删除后不可恢复,请谨慎操作',
17
+ okText: '确定',
18
+ cancelText: '取消',
19
+ onOk: () => {
20
+ onDelete();
21
+ },
22
+ });
23
+ }, [name, desc, onDelete]);
24
+
25
+ return {
26
+ doDelete,
27
+ };
28
+ }
29
+
30
+ function OperateDelete(props: Params) {
31
+ const { name, desc, onDelete, operateText } = props;
32
+ const handleClick = useCallback(() => {
33
+ Modal.confirm({
34
+ title: `确认删除 “${name}” 吗?`,
35
+ content: desc || '删除后不可恢复,请谨慎操作',
36
+ okText: '确定',
37
+ cancelText: '取消',
38
+ onOk: () => {
39
+ onDelete();
40
+ },
41
+ });
42
+ }, [name, desc, onDelete]);
43
+
44
+ return (
45
+ <a style={{ color: 'red' }} onClick={handleClick}>
46
+ {operateText || '删除'}
47
+ </a>
48
+ );
49
+ }
50
+
51
+ export { useDelete, OperateDelete };
@@ -0,0 +1,193 @@
1
+ import { message, Spin } from 'antd';
2
+ import { DrawerForm, ProForm } from '@ant-design/pro-components';
3
+ import { useCallback, useMemo, useState } from 'react';
4
+ import type { CRUDProps } from './crud';
5
+ import { isString } from 'lodash-es';
6
+ import classNames from 'classnames';
7
+
8
+ /**
9
+ * create 创建
10
+ * read 查看
11
+ * read_detail 详情页查看
12
+ * update 编辑
13
+ * delete 删除
14
+ */
15
+ type action = 'create' | 'read' | 'read_detail' | 'update' | 'delete';
16
+
17
+ // 先不管类型
18
+ interface CRUDDetailProps
19
+ extends Pick<
20
+ CRUDProps,
21
+ | 'requestGetByRecord'
22
+ | 'createProps'
23
+ | 'requestCreate'
24
+ | 'updateProps'
25
+ | 'requestUpdateById'
26
+ | 'detailForm'
27
+ | 'detailFormInstance'
28
+ > {
29
+ action: action;
30
+ id?: string;
31
+ record?: any;
32
+ trigger: any;
33
+ /** 添加 or 修改 成功 */
34
+ onSuccess?: () => void;
35
+ }
36
+
37
+ function CRUDDetail(props: CRUDDetailProps) {
38
+ const {
39
+ action,
40
+ id,
41
+ record,
42
+ trigger,
43
+ onSuccess,
44
+ detailForm,
45
+ requestGetByRecord,
46
+ createProps,
47
+ requestCreate,
48
+ updateProps,
49
+ requestUpdateById,
50
+ detailFormInstance,
51
+ } = props;
52
+ const [isOpen, setIsOpen] = useState(false);
53
+ const [loading, setLoading] = useState(id ? true : false);
54
+ const [form] = ProForm.useForm(detailFormInstance);
55
+
56
+ const handleFinish = useCallback(
57
+ async (values) => {
58
+ try {
59
+ let result;
60
+ if (action === 'create' && requestCreate) {
61
+ result = await requestCreate(values);
62
+
63
+ let content = '新建成功';
64
+ if (createProps?.successText) {
65
+ content = isString(createProps.successText)
66
+ ? createProps.successText
67
+ : createProps.successText();
68
+ }
69
+
70
+ message.open({
71
+ type: 'success',
72
+ content,
73
+ });
74
+ }
75
+ if (action === 'update' && requestUpdateById) {
76
+ result = await requestUpdateById({
77
+ ...values,
78
+ id,
79
+ });
80
+
81
+ let content = '更新成功';
82
+ if (updateProps?.successText) {
83
+ content = isString(updateProps.successText)
84
+ ? updateProps.successText
85
+ : updateProps.successText();
86
+ }
87
+
88
+ message.open({
89
+ type: 'success',
90
+ content,
91
+ });
92
+ }
93
+
94
+ // 刷新
95
+ onSuccess?.();
96
+
97
+ // false 则取消默认行为
98
+ if (result !== false) {
99
+ // 关闭弹窗
100
+ return true;
101
+ }
102
+ } catch (e) {
103
+ // 由于 onFinish 吃掉了 error,所以这里自行抛出
104
+ setTimeout(() => {
105
+ throw e;
106
+ }, 10);
107
+ }
108
+ },
109
+ [action, requestCreate, requestUpdateById, onSuccess, createProps, id, updateProps]
110
+ );
111
+
112
+ const handleOpenChange = useCallback(
113
+ async (open) => {
114
+ if (!open) {
115
+ // 关闭重置
116
+ form?.resetFields();
117
+ setIsOpen(open);
118
+ return;
119
+ }
120
+
121
+ setIsOpen(open);
122
+
123
+ if (id) {
124
+ setLoading(true);
125
+
126
+ let res;
127
+ if (requestGetByRecord) {
128
+ res = await requestGetByRecord(record);
129
+ }
130
+
131
+ form?.setFieldsValue(res.data.data);
132
+
133
+ setLoading(false);
134
+ }
135
+
136
+ return;
137
+ },
138
+ [form, id, requestGetByRecord, record]
139
+ );
140
+
141
+ const children = useMemo(() => {
142
+ if (!detailForm) {
143
+ return null;
144
+ }
145
+
146
+ // 关闭的时候不需要 children
147
+ if (!isOpen) {
148
+ return null;
149
+ }
150
+
151
+ if (loading) {
152
+ return (
153
+ <div className="pt-[100px] flex justify-center">
154
+ <Spin />
155
+ </div>
156
+ );
157
+ }
158
+
159
+ return (
160
+ <div
161
+ className={classNames('crud-detail', `crud-detail-action-${action}`, {
162
+ 'crud-detail-hide-extra': action === 'read',
163
+ })}
164
+ >
165
+ {detailForm({ readonly: action === 'read' && !!id }, { action })}
166
+ </div>
167
+ );
168
+ }, [isOpen, loading, detailForm, action, id]);
169
+
170
+ const drawerProps = useMemo(() => {
171
+ return {
172
+ destroyOnClose: true,
173
+ };
174
+ }, []);
175
+
176
+ return (
177
+ <DrawerForm
178
+ form={form}
179
+ trigger={trigger}
180
+ autoFocusFirstInput
181
+ onFinish={handleFinish}
182
+ onOpenChange={handleOpenChange}
183
+ layout="vertical"
184
+ readonly={action === 'read' && !!id}
185
+ // 关闭销毁,否则会有很奇怪的 onFinish 闭包问题,怀疑 pro components bug
186
+ drawerProps={drawerProps}
187
+ >
188
+ {children}
189
+ </DrawerForm>
190
+ );
191
+ }
192
+
193
+ export { CRUDDetail };