@fe-free/core 1.3.1 → 1.3.3

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.3
4
+
5
+ ### Patch Changes
6
+
7
+ - feat: add CRUDOfSimple
8
+ - @fe-free/tool@1.3.3
9
+
10
+ ## 1.3.2
11
+
12
+ ### Patch Changes
13
+
14
+ - feat: crud
15
+ - @fe-free/tool@1.3.2
16
+
3
17
  ## 1.3.1
4
18
 
5
19
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fe-free/core",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
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.1"
32
+ "@fe-free/tool": "1.3.3"
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
package/src/crud/crud.tsx CHANGED
@@ -8,6 +8,7 @@ import { CRUDDetail } from './crud_detail';
8
8
  import './style.scss';
9
9
  import type { CRUDMethods, CRUDProps } from './types';
10
10
  import { useRowSelection } from './use_row_selection';
11
+ import { useTips } from './use_tips';
11
12
 
12
13
  function CRUDComponent<
13
14
  DataSource extends Record<string, any> = any,
@@ -33,9 +34,11 @@ function CRUDComponent<
33
34
  batchActions,
34
35
  } = props;
35
36
 
37
+ useTips(props);
38
+
36
39
  const requestUpdateById = originalRequestUpdateByValues || originalRequestUpdateById;
37
40
 
38
- const actionRef = useRef<ActionType>();
41
+ const actionRef = useRef<ActionType | undefined>(undefined);
39
42
 
40
43
  useImperativeHandle(ref, () => {
41
44
  return {
@@ -161,18 +164,19 @@ function CRUDComponent<
161
164
  ]);
162
165
 
163
166
  const toolBarRender = useCallback(
164
- (...args) => [
165
- // @ts-ignore
166
- ...(tableProps.toolBarRender ? tableProps.toolBarRender(...args) : []),
167
- actions.includes('create') && (
168
- <CRUDDetail
169
- onSuccess={handleReload}
170
- trigger={createButton || <Button type="primary">新建</Button>}
171
- action="create"
172
- {...detailProps}
173
- />
174
- ),
175
- ],
167
+ (...args) =>
168
+ [
169
+ // @ts-ignore
170
+ ...(tableProps.toolBarRender ? tableProps.toolBarRender(...args) : []),
171
+ actions.includes('create') && (
172
+ <CRUDDetail
173
+ onSuccess={handleReload}
174
+ trigger={createButton || <Button type="primary">新建</Button>}
175
+ action="create"
176
+ {...detailProps}
177
+ />
178
+ ),
179
+ ].filter(Boolean),
176
180
  [actions, createButton, detailProps, handleReload, tableProps],
177
181
  );
178
182
 
@@ -181,11 +185,12 @@ function CRUDComponent<
181
185
  Key
182
186
  >({
183
187
  batchActions,
188
+ actionRef,
184
189
  });
185
190
 
186
191
  return (
187
192
  <div className="crud-table">
188
- <Table
193
+ <Table<DataSource>
189
194
  rowKey="id"
190
195
  {...tableProps}
191
196
  actionRef={actionRef}
@@ -0,0 +1,109 @@
1
+ import { ProFormText } from '@ant-design/pro-components';
2
+ import { CRUDOfSimple } from '@fe-free/core';
3
+ import type { Meta, StoryObj } from '@storybook/react';
4
+ import { fakeCreate, fakeDeleteByRecord, fakeRequest } from './demo/data';
5
+
6
+ const meta: Meta<typeof CRUDOfSimple> = {
7
+ title: '@fe-free/core/CRUDOfSimple',
8
+ component: CRUDOfSimple,
9
+ tags: ['autodocs'],
10
+ };
11
+
12
+ export default meta;
13
+ type Story = StoryObj<typeof CRUDOfSimple>;
14
+
15
+ // 基础用法
16
+ export const Normal: Story = {
17
+ render: () => {
18
+ const columns = [
19
+ {
20
+ title: 'id',
21
+ dataIndex: 'id',
22
+ search: true,
23
+ },
24
+ {
25
+ title: '名字(省略)',
26
+ dataIndex: 'name',
27
+ search: true,
28
+ ellipsis: true,
29
+ },
30
+ ];
31
+
32
+ return (
33
+ <CRUDOfSimple
34
+ actions={['create', 'delete']}
35
+ tableProps={{
36
+ columns,
37
+ request: fakeRequest,
38
+ pagination: false,
39
+ }}
40
+ requestDeleteByRecord={fakeDeleteByRecord}
41
+ deleteProps={{
42
+ nameIndex: 'name',
43
+ }}
44
+ detailForm={(formProps) => (
45
+ <>
46
+ <ProFormText
47
+ {...formProps}
48
+ name="name"
49
+ label="名字"
50
+ required
51
+ rules={[{ required: true }]}
52
+ extra="extra extra extra extra"
53
+ />
54
+ </>
55
+ )}
56
+ requestCreateByValues={fakeCreate}
57
+ />
58
+ );
59
+ },
60
+ };
61
+
62
+ export const WithSearch: Story = {
63
+ render: () => {
64
+ const columns = [
65
+ {
66
+ title: 'id',
67
+ dataIndex: 'id',
68
+ search: true,
69
+ },
70
+ {
71
+ title: '名字(省略)',
72
+ dataIndex: 'name',
73
+ search: true,
74
+ ellipsis: true,
75
+ },
76
+ ];
77
+
78
+ return (
79
+ <CRUDOfSimple
80
+ actions={['create', 'delete']}
81
+ tableProps={{
82
+ columns,
83
+ request: fakeRequest,
84
+ pagination: false,
85
+ }}
86
+ requestDeleteByRecord={fakeDeleteByRecord}
87
+ deleteProps={{
88
+ nameIndex: 'name',
89
+ }}
90
+ detailForm={(formProps) => (
91
+ <>
92
+ <ProFormText
93
+ {...formProps}
94
+ name="name"
95
+ label="名字"
96
+ required
97
+ rules={[{ required: true }]}
98
+ extra="extra extra extra extra"
99
+ />
100
+ </>
101
+ )}
102
+ requestCreateByValues={fakeCreate}
103
+ simpleSearchProps={{
104
+ name: 'id',
105
+ }}
106
+ />
107
+ );
108
+ },
109
+ };
@@ -0,0 +1,118 @@
1
+ import { useDebounce } from 'ahooks';
2
+ import { Input } from 'antd';
3
+ import { forwardRef, useCallback, useEffect, useMemo, useState } from 'react';
4
+ import { CRUD } from './crud';
5
+ import type { CRUDMethods, CRUDProps } from './types';
6
+
7
+ interface CRUDOfSimpleProps<
8
+ DataSource extends Record<string, any> = any,
9
+ Key extends string | number = string,
10
+ > extends CRUDProps<DataSource, Key> {
11
+ // 传才开启搜索
12
+ simpleSearchProps?: {
13
+ /** 搜索项的名称,默认 keywords */
14
+ name: string;
15
+ /** 搜索项的 placeholder,默认 请输入 */
16
+ placeholder?: string;
17
+ };
18
+ }
19
+
20
+ function useTips(props) {
21
+ const { columns } = props;
22
+
23
+ useEffect(() => {
24
+ const hasSearchColumn = columns?.find((column) => column.search);
25
+ if (hasSearchColumn) {
26
+ console.warn('CRUDOfSimple 的 columns 中不能有 search 为 true 的列');
27
+ }
28
+ }, []);
29
+ }
30
+
31
+ function SearchRender(props: {
32
+ placeholder?: string;
33
+ value?: string;
34
+ onChange: (value: string) => void;
35
+ }) {
36
+ return (
37
+ <Input
38
+ placeholder={props.placeholder ?? '输入搜索'}
39
+ allowClear
40
+ value={props.value}
41
+ onChange={(e) => props.onChange(e.target.value)}
42
+ />
43
+ );
44
+ }
45
+
46
+ function CRUDOfSimpleComponent(props: CRUDOfSimpleProps, ref: React.ForwardedRef<CRUDMethods>) {
47
+ const { simpleSearchProps, tableProps, ...rest } = props;
48
+
49
+ useTips(props);
50
+ const [searchValue, setSearchValue] = useState<string>('');
51
+
52
+ const debouncedSearchValue = useDebounce(searchValue, { wait: 300 });
53
+
54
+ const newColumns = useMemo(() => {
55
+ return (tableProps.columns || []).map((column) => ({
56
+ ...column,
57
+ search: false,
58
+ }));
59
+ }, [tableProps.columns]);
60
+
61
+ const toolBarRender = useCallback(
62
+ (...args) => {
63
+ return [
64
+ <div key="search">
65
+ {simpleSearchProps && (
66
+ <SearchRender
67
+ placeholder={simpleSearchProps.placeholder}
68
+ value={searchValue}
69
+ onChange={(value) => setSearchValue(value)}
70
+ />
71
+ )}
72
+ </div>,
73
+ // @ts-ignore
74
+ ...(tableProps.toolBarRender ? tableProps.toolBarRender(...args) : []),
75
+ ];
76
+ },
77
+ [searchValue, simpleSearchProps, tableProps],
78
+ );
79
+
80
+ const newParams = useMemo(() => {
81
+ if (!simpleSearchProps) {
82
+ return tableProps.params;
83
+ }
84
+
85
+ return {
86
+ ...tableProps.params,
87
+ [simpleSearchProps.name]: debouncedSearchValue,
88
+ };
89
+ }, [debouncedSearchValue, simpleSearchProps, tableProps.params]);
90
+
91
+ return (
92
+ <div className="crud-of-simple">
93
+ <CRUD
94
+ ref={ref}
95
+ {...rest}
96
+ tableProps={{
97
+ ...tableProps,
98
+ params: newParams,
99
+ cardBordered: false,
100
+ showHeader: false,
101
+ ghost: true,
102
+ columns: newColumns,
103
+ toolBarRender,
104
+ }}
105
+ />
106
+ </div>
107
+ );
108
+ }
109
+
110
+ const CRUDOfSimple = forwardRef(CRUDOfSimpleComponent) as <
111
+ DataSource extends Record<string, any> = any,
112
+ Key extends string | number = string,
113
+ >(
114
+ props: CRUDOfSimpleProps<DataSource, Key> & { ref?: React.ForwardedRef<CRUDMethods> },
115
+ ) => JSX.Element;
116
+
117
+ export { CRUDOfSimple };
118
+ export type { CRUDOfSimpleProps };
@@ -58,12 +58,24 @@ let fakeData = makeData(21);
58
58
  async function fakeRequest(params) {
59
59
  console.log('fakeRequest', params);
60
60
 
61
+ let data = fakeData;
62
+ Object.keys(params).forEach((field) => {
63
+ if (params[field] !== undefined && params[field] !== '') {
64
+ data = data.filter((item) => {
65
+ if (typeof item[field] === 'string') {
66
+ return item[field].includes(params[field]);
67
+ }
68
+ return true;
69
+ });
70
+ }
71
+ });
72
+
61
73
  return new Promise((resolve) => {
62
74
  setTimeout(() => {
63
75
  resolve({
64
- data: fakeData,
76
+ data,
65
77
  success: true,
66
- total: fakeData.length,
78
+ total: data.length,
67
79
  });
68
80
  }, 1000);
69
81
  }) as Promise<any>;
@@ -2,4 +2,6 @@ export { CRUD } from './crud';
2
2
  export { OperateDelete, useDelete } from './crud_delete';
3
3
  export { CRUDDetail } from './crud_detail';
4
4
  export type { CRUDDetailProps } from './crud_detail';
5
+ export { CRUDOfSimple } from './crud_of_simple';
6
+ export type { CRUDOfSimpleProps } from './crud_of_simple';
5
7
  export type { CRUDMethods, CRUDProps } from './types';
@@ -11,17 +11,33 @@ import type { TableProps } from '../table';
11
11
  */
12
12
  type CrudAction = 'create' | 'read' | 'read_detail' | 'update' | 'delete';
13
13
 
14
- interface CRUDProps<
15
- DataSource extends Record<string, any> = any,
16
- Key extends string | number = string,
17
- > {
14
+ interface CRUDProps<DataSource = any, Key = string> {
18
15
  actions: CrudAction[];
19
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>;
20
28
  /** 新建按钮,默认新建 */
21
29
  createButton?: ReactNode;
30
+ /** create 更多设置 */
31
+ createProps?: {
32
+ /** 成功文案 */
33
+ successText?: string | (() => string);
34
+ };
35
+
36
+ // *** Read 表格 ***
22
37
 
23
38
  /** 表格相关 */
24
39
  tableProps: TableProps<DataSource>;
40
+ /** 表格操作列相关 */
25
41
  operateColumnProps?: {
26
42
  width?: number;
27
43
  /** 扩展操作区域,再其他操作之前 */
@@ -29,6 +45,14 @@ interface CRUDProps<
29
45
  /** 扩展操作区域,在其他操作之后 */
30
46
  moreOperatorAfter?: (record: DataSource) => ReactNode;
31
47
  };
48
+
49
+ // *** Read 详情 ***
50
+
51
+ /** 获取详情接口 */
52
+ requestGetByRecord?: (record: DataSource) => Promise<any>;
53
+ /** 跳转到详情的索引 ,默认 id */
54
+ detailIdIndex?: string;
55
+ /** read 更多设置 */
32
56
  readProps?: {
33
57
  /** 文本 */
34
58
  operateText?: string;
@@ -36,29 +60,7 @@ interface CRUDProps<
36
60
  target?: '_blank';
37
61
  };
38
62
 
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
- };
63
+ // *** Update 更新 ***
62
64
 
63
65
  /** 更新接口 */
64
66
  requestUpdateByValues?: (values: Partial<DataSource>) => Promise<any>;
@@ -71,11 +73,21 @@ interface CRUDProps<
71
73
  successText?: string | (() => string);
72
74
  };
73
75
 
74
- /** 获取详情接口 */
75
- requestGetByRecord?: (record: DataSource) => Promise<any>;
76
+ // *** Delete 删除 ***
76
77
 
77
- /** 跳转到详情的索引 ,默认 id */
78
- detailIdIndex?: string;
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
+ // *** 批量操作 ***
79
91
 
80
92
  /** 批量操作 */
81
93
  batchActions?: {
@@ -1,12 +1,17 @@
1
+ import type { ActionType } from '@ant-design/pro-components';
1
2
  import { Modal } from 'antd';
3
+ import type { MutableRefObject } from 'react';
2
4
  import { useCallback, useMemo } from 'react';
3
5
  import { LoadingButton } from '../button';
4
6
  import type { CRUDProps } from './types';
5
7
 
6
- function useRowSelection<
7
- DataSource extends Record<string, any> = any,
8
- Key extends string | number = string,
9
- >({ batchActions }: { batchActions?: CRUDProps<DataSource, Key>['batchActions'] }) {
8
+ function useRowSelection<DataSource, Key>({
9
+ batchActions,
10
+ actionRef,
11
+ }: {
12
+ batchActions?: CRUDProps<DataSource, Key>['batchActions'];
13
+ actionRef?: MutableRefObject<ActionType | undefined>;
14
+ }) {
10
15
  const rowSelection = useMemo(() => ({}), []);
11
16
 
12
17
  const tableAlertRender = useCallback(({ selectedRowKeys, onCleanSelected }) => {
@@ -55,6 +60,8 @@ function useRowSelection<
55
60
  selectedRows,
56
61
  });
57
62
  }
63
+
64
+ actionRef?.current?.reload();
58
65
  }}
59
66
  >
60
67
  {action.btnText}
@@ -63,7 +70,7 @@ function useRowSelection<
63
70
  </div>
64
71
  );
65
72
  },
66
- [batchActions],
73
+ [actionRef, batchActions],
67
74
  );
68
75
 
69
76
  if (!batchActions || batchActions.length === 0) {
@@ -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 };
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { LoadingButton } from './button';
2
2
 
3
- export { CRUD, CRUDDetail, OperateDelete, useDelete } from './crud';
4
- export type { CRUDDetailProps, CRUDMethods, CRUDProps } from './crud';
3
+ export { CRUD, CRUDDetail, CRUDOfSimple, OperateDelete, useDelete } from './crud';
4
+ export type { CRUDDetailProps, CRUDMethods, CRUDOfSimpleProps, CRUDProps } from './crud';
5
5
 
6
6
  export { EditorJavascript } from './editor_javascript';
7
7
  export type { EditorJavascriptProps } from './editor_javascript';