@fe-free/core 1.3.0 → 1.3.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @fe-free/core
2
2
 
3
+ ## 1.3.2
4
+
5
+ ### Patch Changes
6
+
7
+ - feat: crud
8
+ - @fe-free/tool@1.3.2
9
+
10
+ ## 1.3.1
11
+
12
+ ### Patch Changes
13
+
14
+ - feat: crud
15
+ - @fe-free/tool@1.3.1
16
+
3
17
  ## 1.3.0
4
18
 
5
19
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fe-free/core",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "",
5
5
  "main": "./src/index.ts",
6
6
  "author": "",
@@ -29,7 +29,7 @@
29
29
  "react-syntax-highlighter": "^15.5.0",
30
30
  "vanilla-jsoneditor": "^0.23.1",
31
31
  "zustand": "^4.5.4",
32
- "@fe-free/tool": "1.3.0"
32
+ "@fe-free/tool": "1.3.2"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "@ant-design/pro-components": "^2.8.7",
@@ -109,8 +109,78 @@ export const ReadDetail: Story = {
109
109
  },
110
110
  };
111
111
 
112
+ export const MoreCustom: Story = {
113
+ render: () => {
114
+ const columns = [
115
+ {
116
+ title: 'id',
117
+ dataIndex: 'id',
118
+ search: true,
119
+ },
120
+ {
121
+ title: '名字(省略)',
122
+ dataIndex: 'name',
123
+ search: true,
124
+ ellipsis: true,
125
+ },
126
+ {
127
+ title: 'city',
128
+ dataIndex: 'city',
129
+ },
130
+ {
131
+ title: 'area',
132
+ dataIndex: 'area',
133
+ },
134
+ ];
135
+
136
+ return (
137
+ <CRUD
138
+ actions={['create', 'read', 'delete', 'update']}
139
+ tableProps={{
140
+ columns,
141
+ request: fakeRequest,
142
+ toolBarRender: () => {
143
+ return [<div key="custom1">自定义1</div>, <div key="custom2">自定义2</div>];
144
+ },
145
+ }}
146
+ operateColumnProps={{
147
+ // 自定义宽度
148
+ width: 300,
149
+ // 自定义操作列
150
+ moreOperator: () => {
151
+ return <div>自定义</div>;
152
+ },
153
+ // 自定义操作列之后
154
+ moreOperatorAfter: () => {
155
+ return <div>自定义</div>;
156
+ },
157
+ }}
158
+ requestDeleteByRecord={fakeDeleteByRecord}
159
+ deleteProps={{
160
+ nameIndex: 'name',
161
+ }}
162
+ detailForm={(formProps) => (
163
+ <>
164
+ <ProFormText
165
+ {...formProps}
166
+ name="name"
167
+ label="名字"
168
+ required
169
+ rules={[{ required: true }]}
170
+ extra="extra extra extra extra"
171
+ />
172
+ </>
173
+ )}
174
+ requestGetByRecord={fakeGetByRecord}
175
+ requestCreateByValues={fakeCreate}
176
+ requestUpdateById={fakeUpdateById}
177
+ />
178
+ );
179
+ },
180
+ };
181
+
112
182
  // 表格表单和详情表单 ref
113
- const RefComponent = () => {
183
+ const FormRefComponent = () => {
114
184
  const formRef = useRef<any>();
115
185
  const [detailFormInstance] = ProForm.useForm();
116
186
 
@@ -156,8 +226,8 @@ const RefComponent = () => {
156
226
  );
157
227
  };
158
228
 
159
- export const Ref: Story = {
160
- render: () => <RefComponent />,
229
+ export const FormRef: Story = {
230
+ render: () => <FormRefComponent />,
161
231
  };
162
232
 
163
233
  // 通过 ref 获取 actionRef
@@ -348,3 +418,37 @@ export const CustomText: Story = {
348
418
  );
349
419
  },
350
420
  };
421
+
422
+ export const RowSelection: Story = {
423
+ render: () => {
424
+ return (
425
+ <CRUD
426
+ actions={[]}
427
+ tableProps={{
428
+ columns: [
429
+ {
430
+ title: 'id',
431
+ dataIndex: 'id',
432
+ search: true,
433
+ },
434
+ {
435
+ title: '名字',
436
+ dataIndex: 'name',
437
+ search: true,
438
+ },
439
+ ],
440
+ request: fakeRequest,
441
+ }}
442
+ batchActions={[
443
+ {
444
+ btnText: '批量删除',
445
+ danger: true,
446
+ onClick: async (_, { selectedRowKeys }) => {
447
+ console.log(selectedRowKeys);
448
+ },
449
+ },
450
+ ]}
451
+ />
452
+ );
453
+ },
454
+ };
package/src/crud/crud.tsx CHANGED
@@ -1,91 +1,19 @@
1
- import type { ActionType, ProFormInstance } from '@ant-design/pro-components';
1
+ import type { ActionType } from '@ant-design/pro-components';
2
2
  import { Button, Space } from 'antd';
3
- import type { ReactNode } from 'react';
4
3
  import { forwardRef, useCallback, useImperativeHandle, useMemo, useRef } from 'react';
5
4
  import type { TableProps } from '../table';
6
5
  import { Table } from '../table';
7
6
  import { OperateDelete } from './crud_delete';
8
7
  import { CRUDDetail } from './crud_detail';
9
8
  import './style.scss';
10
-
11
- /**
12
- * create 创建
13
- * read 查看
14
- * read_detail 详情页查看
15
- * update 编辑
16
- * delete 删除
17
- */
18
- type CrudAction = 'create' | 'read' | 'read_detail' | 'update' | 'delete';
19
-
20
- interface CRUDProps {
21
- actions: CrudAction[];
22
-
23
- /** 新建按钮,默认新建 */
24
- createButton?: ReactNode;
25
-
26
- /** 表格相关 */
27
- tableProps: TableProps;
28
- operateColumnProps?: {
29
- width?: number;
30
- /** 扩展操作区域,再其他操作之前 */
31
- moreOperator?: (record) => ReactNode;
32
- /** 扩展操作区域,在其他操作之后 */
33
- moreOperatorAfter?: (record) => ReactNode;
34
- };
35
- readProps?: {
36
- /** 文本 */
37
- operateText?: string;
38
- /** 打开方式, action 为 read_detail 有效 */
39
- target?: '_blank';
40
- };
41
-
42
- /** 删除接口 */
43
- requestDeleteByRecord?: (record) => Promise<any>;
44
- /** 删除相关 */
45
- deleteProps?: {
46
- /** 显示名称索引 */
47
- nameIndex: string;
48
- /** 删除确认描述 */
49
- desc?: string;
50
- /** 文本 */
51
- operateText?: string;
52
- };
53
-
54
- /** 弹窗表单 */
55
- detailForm?: (formProps: { readonly: boolean }, info: { action: CrudAction }) => ReactNode;
56
- /** detailForm 的 formRef */
57
- detailFormInstance?: ProFormInstance;
58
-
59
- /** 新增接口 */
60
- requestCreateByValues?: (values) => Promise<any>;
61
- createProps?: {
62
- /** 成功文案 */
63
- successText?: string | (() => string);
64
- };
65
-
66
- /** 更新接口 */
67
- requestUpdateByValues?: (values) => Promise<any>;
68
- /** @deprecated 请使用 requestUpdateByValues 替代 */
69
- requestUpdateById?: (values) => Promise<any>;
70
- updateProps?: {
71
- /** 文本 */
72
- operateText?: string;
73
- /** 成功文案 */
74
- successText?: string | (() => string);
75
- };
76
-
77
- /** 获取详情接口 */
78
- requestGetByRecord?: (record) => Promise<any>;
79
-
80
- /** 跳转到详情的索引 ,默认 id */
81
- detailIdIndex?: string;
82
- }
83
-
84
- interface CRUDMethods {
85
- getActionRef: () => React.MutableRefObject<ActionType | undefined>;
86
- }
87
-
88
- const CRUD = forwardRef<CRUDMethods, CRUDProps>(function CRUD(props, ref) {
9
+ import type { CRUDMethods, CRUDProps } from './types';
10
+ import { useRowSelection } from './use_row_selection';
11
+ import { useTips } from './use_tips';
12
+
13
+ function CRUDComponent<
14
+ DataSource extends Record<string, any> = any,
15
+ Key extends string | number = string,
16
+ >(props: CRUDProps<DataSource, Key>, ref: React.ForwardedRef<CRUDMethods>) {
89
17
  const {
90
18
  actions,
91
19
  tableProps,
@@ -103,11 +31,14 @@ const CRUD = forwardRef<CRUDMethods, CRUDProps>(function CRUD(props, ref) {
103
31
  requestUpdateById: originalRequestUpdateById,
104
32
  requestUpdateByValues: originalRequestUpdateByValues,
105
33
  detailFormInstance,
34
+ batchActions,
106
35
  } = props;
107
36
 
37
+ useTips(props);
38
+
108
39
  const requestUpdateById = originalRequestUpdateByValues || originalRequestUpdateById;
109
40
 
110
- const actionRef = useRef<ActionType>();
41
+ const actionRef = useRef<ActionType | undefined>(undefined);
111
42
 
112
43
  useImperativeHandle(ref, () => {
113
44
  return {
@@ -248,18 +179,35 @@ const CRUD = forwardRef<CRUDMethods, CRUDProps>(function CRUD(props, ref) {
248
179
  [actions, createButton, detailProps, handleReload, tableProps],
249
180
  );
250
181
 
182
+ const { rowSelection, tableAlertRender, tableAlertOptionRender } = useRowSelection<
183
+ DataSource,
184
+ Key
185
+ >({
186
+ batchActions,
187
+ actionRef,
188
+ });
189
+
251
190
  return (
252
191
  <div className="crud-table">
253
- <Table
192
+ <Table<DataSource>
254
193
  rowKey="id"
255
194
  {...tableProps}
256
195
  actionRef={actionRef}
257
196
  toolBarRender={toolBarRender}
258
197
  columns={newColumns}
198
+ rowSelection={rowSelection}
199
+ tableAlertRender={tableAlertRender}
200
+ tableAlertOptionRender={tableAlertOptionRender}
259
201
  />
260
202
  </div>
261
203
  );
262
- });
204
+ }
205
+
206
+ const CRUD = forwardRef(CRUDComponent) as <
207
+ DataSource extends Record<string, any> = any,
208
+ Key extends string | number = string,
209
+ >(
210
+ props: CRUDProps<DataSource, Key> & { ref?: React.ForwardedRef<CRUDMethods> },
211
+ ) => JSX.Element;
263
212
 
264
213
  export { CRUD };
265
- export type { CRUDMethods, CRUDProps };
@@ -11,15 +11,20 @@ interface Params {
11
11
  function useDelete(params: Params) {
12
12
  const { name, desc, onDelete } = params;
13
13
 
14
- const doDelete = useCallback(() => {
15
- Modal.confirm({
16
- title: `确认删除 “${name}” 吗?`,
17
- content: desc || '删除后不可恢复,请谨慎操作',
18
- okText: '确定',
19
- cancelText: '取消',
20
- onOk: () => {
21
- onDelete();
22
- },
14
+ const doDelete = useCallback(async () => {
15
+ await new Promise((resolve) => {
16
+ Modal.confirm({
17
+ title: `确认删除 “${name}” 吗?`,
18
+ content: desc || '删除后不可恢复,请谨慎操作',
19
+ okText: '确定',
20
+ cancelText: '取消',
21
+ onOk: () => {
22
+ resolve(onDelete());
23
+ },
24
+ onCancel: () => {
25
+ resolve(undefined);
26
+ },
27
+ });
23
28
  });
24
29
  }, [name, desc, onDelete]);
25
30
 
@@ -30,20 +35,10 @@ function useDelete(params: Params) {
30
35
 
31
36
  function OperateDelete(props: Params) {
32
37
  const { name, desc, onDelete, operateText } = props;
33
- const handleClick = useCallback(() => {
34
- Modal.confirm({
35
- title: `确认删除 “${name}” 吗?`,
36
- content: desc || '删除后不可恢复,请谨慎操作',
37
- okText: '确定',
38
- cancelText: '取消',
39
- onOk: () => {
40
- onDelete();
41
- },
42
- });
43
- }, [name, desc, onDelete]);
38
+ const { doDelete } = useDelete({ name, desc, onDelete, operateText });
44
39
 
45
40
  return (
46
- <a style={{ color: 'red' }} onClick={handleClick}>
41
+ <a style={{ color: 'red' }} onClick={doDelete}>
47
42
  {operateText || '删除'}
48
43
  </a>
49
44
  );
@@ -1,5 +1,5 @@
1
1
  export { CRUD } from './crud';
2
- export type { CRUDProps, CRUDMethods } from './crud';
2
+ export { OperateDelete, useDelete } from './crud_delete';
3
3
  export { CRUDDetail } from './crud_detail';
4
4
  export type { CRUDDetailProps } from './crud_detail';
5
- export { useDelete, OperateDelete } from './crud_delete';
5
+ export type { CRUDMethods, CRUDProps } from './types';
@@ -0,0 +1,110 @@
1
+ import type { ActionType, ProFormInstance } from '@ant-design/pro-components';
2
+ import type { ReactNode } from 'react';
3
+ import type { TableProps } from '../table';
4
+
5
+ /**
6
+ * create 创建
7
+ * read 查看
8
+ * read_detail 详情页查看
9
+ * update 编辑
10
+ * delete 删除
11
+ */
12
+ type CrudAction = 'create' | 'read' | 'read_detail' | 'update' | 'delete';
13
+
14
+ interface CRUDProps<DataSource = any, Key = string> {
15
+ actions: CrudAction[];
16
+
17
+ // *** 表单 ***
18
+
19
+ /** 弹窗表单 */
20
+ detailForm?: (formProps: { readonly: boolean }, info: { action: CrudAction }) => ReactNode;
21
+ /** detailForm 的 formRef */
22
+ detailFormInstance?: ProFormInstance;
23
+
24
+ // *** Create 新建 ***
25
+
26
+ /** 新增接口 */
27
+ requestCreateByValues?: (values: Partial<DataSource>) => Promise<any>;
28
+ /** 新建按钮,默认新建 */
29
+ createButton?: ReactNode;
30
+ /** create 更多设置 */
31
+ createProps?: {
32
+ /** 成功文案 */
33
+ successText?: string | (() => string);
34
+ };
35
+
36
+ // *** Read 表格 ***
37
+
38
+ /** 表格相关 */
39
+ tableProps: TableProps<DataSource>;
40
+ /** 表格操作列相关 */
41
+ operateColumnProps?: {
42
+ width?: number;
43
+ /** 扩展操作区域,再其他操作之前 */
44
+ moreOperator?: (record: DataSource) => ReactNode;
45
+ /** 扩展操作区域,在其他操作之后 */
46
+ moreOperatorAfter?: (record: DataSource) => ReactNode;
47
+ };
48
+
49
+ // *** Read 详情 ***
50
+
51
+ /** 获取详情接口 */
52
+ requestGetByRecord?: (record: DataSource) => Promise<any>;
53
+ /** 跳转到详情的索引 ,默认 id */
54
+ detailIdIndex?: string;
55
+ /** read 更多设置 */
56
+ readProps?: {
57
+ /** 文本 */
58
+ operateText?: string;
59
+ /** 打开方式, action 为 read_detail 有效 */
60
+ target?: '_blank';
61
+ };
62
+
63
+ // *** Update 更新 ***
64
+
65
+ /** 更新接口 */
66
+ requestUpdateByValues?: (values: Partial<DataSource>) => Promise<any>;
67
+ /** @deprecated 请使用 requestUpdateByValues 替代 */
68
+ requestUpdateById?: (values: Partial<DataSource>) => Promise<any>;
69
+ updateProps?: {
70
+ /** 文本 */
71
+ operateText?: string;
72
+ /** 成功文案 */
73
+ successText?: string | (() => string);
74
+ };
75
+
76
+ // *** Delete 删除 ***
77
+
78
+ /** 删除接口 */
79
+ requestDeleteByRecord?: (record: DataSource) => Promise<any>;
80
+ /** 删除相关 */
81
+ deleteProps?: {
82
+ /** 显示名称索引 */
83
+ nameIndex: keyof DataSource;
84
+ /** 删除确认描述 */
85
+ desc?: string;
86
+ /** 文本 */
87
+ operateText?: string;
88
+ };
89
+
90
+ // *** 批量操作 ***
91
+
92
+ /** 批量操作 */
93
+ batchActions?: {
94
+ /** 按钮文本 */
95
+ btnText: string;
96
+ /** 红色,且有确认框 */
97
+ danger?: boolean;
98
+ /** 批量操作接口 */
99
+ onClick: (
100
+ event: React.MouseEvent<HTMLElement>,
101
+ options: { selectedRowKeys: Key[]; selectedRows: DataSource[] },
102
+ ) => Promise<any>;
103
+ }[];
104
+ }
105
+
106
+ interface CRUDMethods {
107
+ getActionRef: () => React.MutableRefObject<ActionType | undefined>;
108
+ }
109
+
110
+ export type { CRUDMethods, CRUDProps };
@@ -0,0 +1,91 @@
1
+ import type { ActionType } from '@ant-design/pro-components';
2
+ import { Modal } from 'antd';
3
+ import type { MutableRefObject } from 'react';
4
+ import { useCallback, useMemo } from 'react';
5
+ import { LoadingButton } from '../button';
6
+ import type { CRUDProps } from './types';
7
+
8
+ function useRowSelection<DataSource, Key>({
9
+ batchActions,
10
+ actionRef,
11
+ }: {
12
+ batchActions?: CRUDProps<DataSource, Key>['batchActions'];
13
+ actionRef?: MutableRefObject<ActionType | undefined>;
14
+ }) {
15
+ const rowSelection = useMemo(() => ({}), []);
16
+
17
+ const tableAlertRender = useCallback(({ selectedRowKeys, onCleanSelected }) => {
18
+ return (
19
+ <div>
20
+ <span>
21
+ 已选 {selectedRowKeys.length} 项
22
+ <a style={{ marginInlineStart: 8 }} onClick={onCleanSelected}>
23
+ 取消选择
24
+ </a>
25
+ </span>
26
+ </div>
27
+ );
28
+ }, []);
29
+
30
+ const tableAlertOptionRender = useCallback(
31
+ ({ selectedRowKeys, selectedRows }) => {
32
+ return (
33
+ <div className="flex gap-2 items-center">
34
+ {batchActions?.map((action) => (
35
+ <LoadingButton
36
+ key={action.btnText}
37
+ type="link"
38
+ danger={action.danger}
39
+ onClick={async (event) => {
40
+ if (action.danger) {
41
+ await new Promise((resolve) => {
42
+ Modal.confirm({
43
+ title: `确定要执行 ${action.btnText} 吗?`,
44
+ onOk: () => {
45
+ resolve(
46
+ action.onClick(event, {
47
+ selectedRowKeys,
48
+ selectedRows,
49
+ }),
50
+ );
51
+ },
52
+ onCancel: () => {
53
+ resolve(undefined);
54
+ },
55
+ });
56
+ });
57
+ } else {
58
+ await action.onClick(event, {
59
+ selectedRowKeys,
60
+ selectedRows,
61
+ });
62
+ }
63
+
64
+ actionRef?.current?.reload();
65
+ }}
66
+ >
67
+ {action.btnText}
68
+ </LoadingButton>
69
+ ))}
70
+ </div>
71
+ );
72
+ },
73
+ [actionRef, batchActions],
74
+ );
75
+
76
+ if (!batchActions || batchActions.length === 0) {
77
+ return {
78
+ rowSelection: undefined,
79
+ tableAlertRender: undefined,
80
+ tableAlertOptionRender: undefined,
81
+ };
82
+ }
83
+
84
+ return {
85
+ rowSelection,
86
+ tableAlertRender,
87
+ tableAlertOptionRender,
88
+ };
89
+ }
90
+
91
+ export { useRowSelection };
@@ -0,0 +1,34 @@
1
+ import { useEffect } from 'react';
2
+ import type { CRUDProps } from './types';
3
+
4
+ function useTips<DataSource, Key>(props: CRUDProps<DataSource, Key>) {
5
+ useEffect(() => {
6
+ if (props.actions.includes('create')) {
7
+ if (!props.requestCreateByValues) {
8
+ console.warn('actions 包含 create 时,需要传递 requestCreateByValues');
9
+ }
10
+ }
11
+
12
+ if (props.actions.includes('read')) {
13
+ if (!props.requestGetByRecord) {
14
+ console.warn('actions 包含 read 时,需要传递 requestGetByRecord');
15
+ }
16
+ }
17
+
18
+ if (props.actions.includes('update')) {
19
+ if (!props.requestGetByRecord || !props.requestUpdateByValues) {
20
+ console.warn(
21
+ 'actions 包含 update 时,需要传递 requestGetByRecord 和 requestUpdateByValues',
22
+ );
23
+ }
24
+ }
25
+
26
+ if (props.actions.includes('delete')) {
27
+ if (!props.deleteProps?.nameIndex || !props.requestDeleteByRecord) {
28
+ console.warn('actions 包含 delete 时,需要传递 deleteProps 和 requestDeleteByRecord');
29
+ }
30
+ }
31
+ }, []);
32
+ }
33
+
34
+ export { useTips };
@@ -1,5 +1,5 @@
1
+ import type { ParamsType, ProTableProps } from '@ant-design/pro-components';
1
2
  import { ProTable } from '@ant-design/pro-components';
2
- import type { ProTableProps, ParamsType } from '@ant-design/pro-components';
3
3
  import { useMemo } from 'react';
4
4
 
5
5
  interface TableProps<DataSource = any, Params = any, ValueType = 'text'>
@@ -10,7 +10,7 @@ interface TableProps<DataSource = any, Params = any, ValueType = 'text'>
10
10
 
11
11
  function Table<
12
12
  DataSource extends Record<string, any> = any,
13
- Params extends ParamsType = ParamsType
13
+ Params extends ParamsType = ParamsType,
14
14
  >(props: TableProps<DataSource, Params>) {
15
15
  const { columns, rowKey = 'id', search, ...rest } = props;
16
16
 
@@ -48,7 +48,7 @@ function Table<
48
48
  scroll={getTableScroll(newColumns)}
49
49
  search={
50
50
  hasSearch && {
51
- layout: 'vertical',
51
+ labelWidth: 'auto',
52
52
  defaultCollapsed: false,
53
53
  ...search,
54
54
  }