@fe-free/core 1.3.0 → 1.3.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 CHANGED
@@ -1,5 +1,12 @@
1
1
  # @fe-free/core
2
2
 
3
+ ## 1.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - feat: crud
8
+ - @fe-free/tool@1.3.1
9
+
3
10
  ## 1.3.0
4
11
 
5
12
  ### 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.1",
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.1"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "@ant-design/pro-components": "^2.8.7",
@@ -348,3 +348,37 @@ export const CustomText: Story = {
348
348
  );
349
349
  },
350
350
  };
351
+
352
+ export const RowSelection: Story = {
353
+ render: () => {
354
+ return (
355
+ <CRUD
356
+ actions={[]}
357
+ tableProps={{
358
+ columns: [
359
+ {
360
+ title: 'id',
361
+ dataIndex: 'id',
362
+ search: true,
363
+ },
364
+ {
365
+ title: '名字',
366
+ dataIndex: 'name',
367
+ search: true,
368
+ },
369
+ ],
370
+ request: fakeRequest,
371
+ }}
372
+ batchActions={[
373
+ {
374
+ btnText: '批量删除',
375
+ danger: true,
376
+ onClick: async (_, { selectedRowKeys }) => {
377
+ console.log(selectedRowKeys);
378
+ },
379
+ },
380
+ ]}
381
+ />
382
+ );
383
+ },
384
+ };
package/src/crud/crud.tsx CHANGED
@@ -1,91 +1,18 @@
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';
9
+ import type { CRUDMethods, CRUDProps } from './types';
10
+ import { useRowSelection } from './use_row_selection';
10
11
 
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) {
12
+ function CRUDComponent<
13
+ DataSource extends Record<string, any> = any,
14
+ Key extends string | number = string,
15
+ >(props: CRUDProps<DataSource, Key>, ref: React.ForwardedRef<CRUDMethods>) {
89
16
  const {
90
17
  actions,
91
18
  tableProps,
@@ -103,6 +30,7 @@ const CRUD = forwardRef<CRUDMethods, CRUDProps>(function CRUD(props, ref) {
103
30
  requestUpdateById: originalRequestUpdateById,
104
31
  requestUpdateByValues: originalRequestUpdateByValues,
105
32
  detailFormInstance,
33
+ batchActions,
106
34
  } = props;
107
35
 
108
36
  const requestUpdateById = originalRequestUpdateByValues || originalRequestUpdateById;
@@ -248,6 +176,13 @@ const CRUD = forwardRef<CRUDMethods, CRUDProps>(function CRUD(props, ref) {
248
176
  [actions, createButton, detailProps, handleReload, tableProps],
249
177
  );
250
178
 
179
+ const { rowSelection, tableAlertRender, tableAlertOptionRender } = useRowSelection<
180
+ DataSource,
181
+ Key
182
+ >({
183
+ batchActions,
184
+ });
185
+
251
186
  return (
252
187
  <div className="crud-table">
253
188
  <Table
@@ -256,10 +191,19 @@ const CRUD = forwardRef<CRUDMethods, CRUDProps>(function CRUD(props, ref) {
256
191
  actionRef={actionRef}
257
192
  toolBarRender={toolBarRender}
258
193
  columns={newColumns}
194
+ rowSelection={rowSelection}
195
+ tableAlertRender={tableAlertRender}
196
+ tableAlertOptionRender={tableAlertOptionRender}
259
197
  />
260
198
  </div>
261
199
  );
262
- });
200
+ }
201
+
202
+ const CRUD = forwardRef(CRUDComponent) as <
203
+ DataSource extends Record<string, any> = any,
204
+ Key extends string | number = string,
205
+ >(
206
+ props: CRUDProps<DataSource, Key> & { ref?: React.ForwardedRef<CRUDMethods> },
207
+ ) => JSX.Element;
263
208
 
264
209
  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,98 @@
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<
15
+ DataSource extends Record<string, any> = any,
16
+ Key extends string | number = string,
17
+ > {
18
+ actions: CrudAction[];
19
+
20
+ /** 新建按钮,默认新建 */
21
+ createButton?: ReactNode;
22
+
23
+ /** 表格相关 */
24
+ tableProps: TableProps<DataSource>;
25
+ operateColumnProps?: {
26
+ width?: number;
27
+ /** 扩展操作区域,再其他操作之前 */
28
+ moreOperator?: (record: DataSource) => ReactNode;
29
+ /** 扩展操作区域,在其他操作之后 */
30
+ moreOperatorAfter?: (record: DataSource) => ReactNode;
31
+ };
32
+ readProps?: {
33
+ /** 文本 */
34
+ operateText?: string;
35
+ /** 打开方式, action 为 read_detail 有效 */
36
+ target?: '_blank';
37
+ };
38
+
39
+ /** 删除接口 */
40
+ requestDeleteByRecord?: (record: DataSource) => Promise<any>;
41
+ /** 删除相关 */
42
+ deleteProps?: {
43
+ /** 显示名称索引 */
44
+ nameIndex: keyof DataSource;
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
+ requestCreateByValues?: (values: Partial<DataSource>) => Promise<any>;
58
+ createProps?: {
59
+ /** 成功文案 */
60
+ successText?: string | (() => string);
61
+ };
62
+
63
+ /** 更新接口 */
64
+ requestUpdateByValues?: (values: Partial<DataSource>) => Promise<any>;
65
+ /** @deprecated 请使用 requestUpdateByValues 替代 */
66
+ requestUpdateById?: (values: Partial<DataSource>) => Promise<any>;
67
+ updateProps?: {
68
+ /** 文本 */
69
+ operateText?: string;
70
+ /** 成功文案 */
71
+ successText?: string | (() => string);
72
+ };
73
+
74
+ /** 获取详情接口 */
75
+ requestGetByRecord?: (record: DataSource) => Promise<any>;
76
+
77
+ /** 跳转到详情的索引 ,默认 id */
78
+ detailIdIndex?: string;
79
+
80
+ /** 批量操作 */
81
+ batchActions?: {
82
+ /** 按钮文本 */
83
+ btnText: string;
84
+ /** 红色,且有确认框 */
85
+ danger?: boolean;
86
+ /** 批量操作接口 */
87
+ onClick: (
88
+ event: React.MouseEvent<HTMLElement>,
89
+ options: { selectedRowKeys: Key[]; selectedRows: DataSource[] },
90
+ ) => Promise<any>;
91
+ }[];
92
+ }
93
+
94
+ interface CRUDMethods {
95
+ getActionRef: () => React.MutableRefObject<ActionType | undefined>;
96
+ }
97
+
98
+ export type { CRUDMethods, CRUDProps };
@@ -0,0 +1,84 @@
1
+ import { Modal } from 'antd';
2
+ import { useCallback, useMemo } from 'react';
3
+ import { LoadingButton } from '../button';
4
+ import type { CRUDProps } from './types';
5
+
6
+ function useRowSelection<
7
+ DataSource extends Record<string, any> = any,
8
+ Key extends string | number = string,
9
+ >({ batchActions }: { batchActions?: CRUDProps<DataSource, Key>['batchActions'] }) {
10
+ const rowSelection = useMemo(() => ({}), []);
11
+
12
+ const tableAlertRender = useCallback(({ selectedRowKeys, onCleanSelected }) => {
13
+ return (
14
+ <div>
15
+ <span>
16
+ 已选 {selectedRowKeys.length} 项
17
+ <a style={{ marginInlineStart: 8 }} onClick={onCleanSelected}>
18
+ 取消选择
19
+ </a>
20
+ </span>
21
+ </div>
22
+ );
23
+ }, []);
24
+
25
+ const tableAlertOptionRender = useCallback(
26
+ ({ selectedRowKeys, selectedRows }) => {
27
+ return (
28
+ <div className="flex gap-2 items-center">
29
+ {batchActions?.map((action) => (
30
+ <LoadingButton
31
+ key={action.btnText}
32
+ type="link"
33
+ danger={action.danger}
34
+ onClick={async (event) => {
35
+ if (action.danger) {
36
+ await new Promise((resolve) => {
37
+ Modal.confirm({
38
+ title: `确定要执行 ${action.btnText} 吗?`,
39
+ onOk: () => {
40
+ resolve(
41
+ action.onClick(event, {
42
+ selectedRowKeys,
43
+ selectedRows,
44
+ }),
45
+ );
46
+ },
47
+ onCancel: () => {
48
+ resolve(undefined);
49
+ },
50
+ });
51
+ });
52
+ } else {
53
+ await action.onClick(event, {
54
+ selectedRowKeys,
55
+ selectedRows,
56
+ });
57
+ }
58
+ }}
59
+ >
60
+ {action.btnText}
61
+ </LoadingButton>
62
+ ))}
63
+ </div>
64
+ );
65
+ },
66
+ [batchActions],
67
+ );
68
+
69
+ if (!batchActions || batchActions.length === 0) {
70
+ return {
71
+ rowSelection: undefined,
72
+ tableAlertRender: undefined,
73
+ tableAlertOptionRender: undefined,
74
+ };
75
+ }
76
+
77
+ return {
78
+ rowSelection,
79
+ tableAlertRender,
80
+ tableAlertOptionRender,
81
+ };
82
+ }
83
+
84
+ export { useRowSelection };
@@ -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
  }