@fe-free/core 1.2.4 → 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,23 @@
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
+
10
+ ## 1.3.0
11
+
12
+ ### Minor Changes
13
+
14
+ - feat: some
15
+
16
+ ### Patch Changes
17
+
18
+ - Updated dependencies
19
+ - @fe-free/tool@1.3.0
20
+
3
21
  ## 1.2.4
4
22
 
5
23
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fe-free/core",
3
- "version": "1.2.4",
3
+ "version": "1.3.1",
4
4
  "description": "",
5
5
  "main": "./src/index.ts",
6
6
  "author": "",
@@ -29,11 +29,11 @@
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.2.4"
32
+ "@fe-free/tool": "1.3.1"
33
33
  },
34
34
  "peerDependencies": {
35
- "@ant-design/pro-components": "^2.7.15",
36
- "antd": "^5.20.0",
35
+ "@ant-design/pro-components": "^2.8.7",
36
+ "antd": "^5.25.1",
37
37
  "react": "^18.2.0"
38
38
  },
39
39
  "scripts": {
@@ -1,12 +1,17 @@
1
1
  import { LoadingButton } from '@fe-free/core';
2
+ import type { Meta, StoryObj } from '@storybook/react';
2
3
 
3
- export default {
4
+ const meta: Meta<typeof LoadingButton> = {
4
5
  title: '@fe-free/core/LoadingButton',
5
6
  component: LoadingButton,
6
7
  tags: ['autodocs'],
7
8
  };
8
9
 
9
- export const Default = {
10
+ export default meta;
11
+
12
+ type Story = StoryObj<typeof LoadingButton>;
13
+
14
+ export const Resolve: Story = {
10
15
  args: {
11
16
  children: 'click and resolve',
12
17
  onClick: () => {
@@ -13,7 +13,7 @@ function LoadingButton({ onClick, ...rest }: ButtonProps) {
13
13
  setLoading(false);
14
14
  });
15
15
  },
16
- [onClick]
16
+ [onClick],
17
17
  );
18
18
 
19
19
  return <Button loading={loading} {...rest} onClick={handleClick} />;
@@ -0,0 +1,384 @@
1
+ import type { ProColumns } from '@ant-design/pro-components';
2
+ import { ProForm, ProFormSwitch, ProFormText } from '@ant-design/pro-components';
3
+ import { CRUD, proFormSelectSearchProps } from '@fe-free/core';
4
+ import type { Meta, StoryObj } from '@storybook/react';
5
+ import { Button } from 'antd';
6
+ import { useRef } from 'react';
7
+ import {
8
+ fakeCreate,
9
+ fakeDeleteByRecord,
10
+ fakeGetByRecord,
11
+ fakeRequest,
12
+ fakeRequestArea,
13
+ fakeRequestCity,
14
+ fakeRequestSchool,
15
+ fakeUpdateById,
16
+ levels,
17
+ } from './demo/data';
18
+
19
+ const meta: Meta<typeof CRUD> = {
20
+ title: '@fe-free/core/CRUD',
21
+ component: CRUD,
22
+ tags: ['autodocs'],
23
+ };
24
+
25
+ export default meta;
26
+ type Story = StoryObj<typeof CRUD>;
27
+
28
+ // 基础用法
29
+ export const Normal: Story = {
30
+ render: () => {
31
+ const columns = [
32
+ {
33
+ title: 'id',
34
+ dataIndex: 'id',
35
+ search: true,
36
+ },
37
+ {
38
+ title: '名字(省略)',
39
+ dataIndex: 'name',
40
+ search: true,
41
+ ellipsis: true,
42
+ },
43
+ {
44
+ title: 'city',
45
+ dataIndex: 'city',
46
+ },
47
+ {
48
+ title: 'area',
49
+ dataIndex: 'area',
50
+ },
51
+ ];
52
+
53
+ return (
54
+ <CRUD
55
+ actions={['create', 'read', 'delete', 'update']}
56
+ tableProps={{
57
+ columns,
58
+ request: fakeRequest,
59
+ }}
60
+ requestDeleteByRecord={fakeDeleteByRecord}
61
+ deleteProps={{
62
+ nameIndex: 'name',
63
+ }}
64
+ detailForm={(formProps) => (
65
+ <>
66
+ <ProFormText
67
+ {...formProps}
68
+ name="name"
69
+ label="名字"
70
+ required
71
+ rules={[{ required: true }]}
72
+ extra="extra extra extra extra"
73
+ />
74
+ </>
75
+ )}
76
+ requestGetByRecord={fakeGetByRecord}
77
+ requestCreateByValues={fakeCreate}
78
+ requestUpdateById={fakeUpdateById}
79
+ />
80
+ );
81
+ },
82
+ };
83
+
84
+ // 详情页查看
85
+ export const ReadDetail: Story = {
86
+ render: () => {
87
+ const columns = [
88
+ {
89
+ title: 'id',
90
+ dataIndex: 'id',
91
+ search: true,
92
+ },
93
+ {
94
+ title: '名字',
95
+ dataIndex: 'name',
96
+ search: true,
97
+ },
98
+ ];
99
+
100
+ return (
101
+ <CRUD
102
+ actions={['read_detail']}
103
+ tableProps={{
104
+ columns,
105
+ request: fakeRequest,
106
+ }}
107
+ />
108
+ );
109
+ },
110
+ };
111
+
112
+ // 表格表单和详情表单 ref
113
+ const RefComponent = () => {
114
+ const formRef = useRef<any>();
115
+ const [detailFormInstance] = ProForm.useForm();
116
+
117
+ const columns = [
118
+ {
119
+ title: 'id',
120
+ dataIndex: 'id',
121
+ search: true,
122
+ },
123
+ {
124
+ title: '名字',
125
+ dataIndex: 'name',
126
+ search: true,
127
+ },
128
+ ];
129
+
130
+ return (
131
+ <CRUD
132
+ actions={['create', 'read', 'update']}
133
+ tableProps={{
134
+ formRef,
135
+ columns,
136
+ request: fakeRequest,
137
+ }}
138
+ detailFormInstance={detailFormInstance}
139
+ detailForm={(formProps) => (
140
+ <>
141
+ <ProFormText
142
+ {...formProps}
143
+ name="name"
144
+ label="名字"
145
+ required
146
+ rules={[{ required: true }]}
147
+ initialValue={'default'}
148
+ />
149
+ <ProFormSwitch {...formProps} name="status" label="开启" initialValue={false} />
150
+ </>
151
+ )}
152
+ requestGetByRecord={fakeGetByRecord}
153
+ requestCreateByValues={fakeCreate}
154
+ requestUpdateByValues={fakeUpdateById}
155
+ />
156
+ );
157
+ };
158
+
159
+ export const Ref: Story = {
160
+ render: () => <RefComponent />,
161
+ };
162
+
163
+ // 通过 ref 获取 actionRef
164
+ const ActionRefComponent = () => {
165
+ const ref = useRef<any>();
166
+
167
+ const columns = [
168
+ {
169
+ title: 'id',
170
+ dataIndex: 'id',
171
+ search: true,
172
+ },
173
+ {
174
+ title: '名字',
175
+ dataIndex: 'name',
176
+ search: true,
177
+ },
178
+ ];
179
+
180
+ return (
181
+ <>
182
+ <Button onClick={() => ref.current.getActionRef().current?.reload()}>reload</Button>
183
+ <CRUD
184
+ ref={ref}
185
+ actions={[]}
186
+ tableProps={{
187
+ columns,
188
+ request: fakeRequest,
189
+ }}
190
+ />
191
+ </>
192
+ );
193
+ };
194
+
195
+ export const ActionRef: Story = {
196
+ render: () => <ActionRefComponent />,
197
+ };
198
+
199
+ // 数据 本地&远程&依赖
200
+ export const RemoteData: Story = {
201
+ render: () => {
202
+ const columns: ProColumns<any>[] = [
203
+ {
204
+ title: 'id',
205
+ dataIndex: 'id',
206
+ search: true,
207
+ },
208
+ {
209
+ title: '名字',
210
+ dataIndex: 'name',
211
+ search: true,
212
+ },
213
+ {
214
+ title: '等级(本地数据)',
215
+ dataIndex: 'level',
216
+ search: true,
217
+ valueEnum: levels,
218
+ ...proFormSelectSearchProps,
219
+ },
220
+ {
221
+ title: 'city(远端数据)',
222
+ dataIndex: 'city',
223
+ search: true,
224
+ request: async () => {
225
+ const res = await fakeRequestCity();
226
+ return res.map((item) => ({
227
+ label: item,
228
+ value: item,
229
+ }));
230
+ },
231
+ ...proFormSelectSearchProps,
232
+ },
233
+ {
234
+ title: 'area(联动 city)',
235
+ dataIndex: 'area',
236
+ search: true,
237
+ request: async (params) => {
238
+ const res = await fakeRequestArea(params);
239
+ return res.map((item) => ({
240
+ label: item,
241
+ value: item,
242
+ }));
243
+ },
244
+ dependencies: ['city'],
245
+ ...proFormSelectSearchProps,
246
+ },
247
+ {
248
+ title: '学校(远端数据 label value)',
249
+ dataIndex: 'school',
250
+ search: true,
251
+ valueType: 'select' as const,
252
+ request: () => fakeRequestSchool(),
253
+ ...proFormSelectSearchProps,
254
+ },
255
+ ];
256
+
257
+ return (
258
+ <CRUD
259
+ actions={[]}
260
+ tableProps={{
261
+ columns,
262
+ request: fakeRequest,
263
+ }}
264
+ />
265
+ );
266
+ },
267
+ };
268
+
269
+ // 没有搜索
270
+ export const NoSearch: Story = {
271
+ render: () => {
272
+ const columns = [
273
+ {
274
+ title: 'id',
275
+ dataIndex: 'id',
276
+ },
277
+ {
278
+ title: '名字',
279
+ dataIndex: 'name',
280
+ },
281
+ ];
282
+
283
+ return (
284
+ <CRUD
285
+ actions={[]}
286
+ tableProps={{
287
+ columns,
288
+ request: fakeRequest,
289
+ search: false,
290
+ }}
291
+ />
292
+ );
293
+ },
294
+ };
295
+
296
+ // 自定义文案
297
+ export const CustomText: Story = {
298
+ render: () => {
299
+ const columns = [
300
+ {
301
+ title: 'id',
302
+ dataIndex: 'id',
303
+ search: true,
304
+ },
305
+ {
306
+ title: '名字',
307
+ dataIndex: 'name',
308
+ search: true,
309
+ },
310
+ ];
311
+
312
+ return (
313
+ <CRUD
314
+ actions={['create', 'read', 'delete', 'update']}
315
+ tableProps={{
316
+ columns,
317
+ request: fakeRequest,
318
+ }}
319
+ createButton={<Button type="primary">新建</Button>}
320
+ readProps={{
321
+ operateText: '查看',
322
+ }}
323
+ deleteProps={{
324
+ nameIndex: 'name',
325
+ operateText: '删除',
326
+ desc: '确定要删除吗?',
327
+ }}
328
+ updateProps={{
329
+ operateText: '编辑',
330
+ successText: '编辑成功',
331
+ }}
332
+ requestDeleteByRecord={fakeDeleteByRecord}
333
+ detailForm={(formProps) => (
334
+ <>
335
+ <ProFormText
336
+ {...formProps}
337
+ name="name"
338
+ label="名字"
339
+ required
340
+ rules={[{ required: true }]}
341
+ />
342
+ </>
343
+ )}
344
+ requestGetByRecord={fakeGetByRecord}
345
+ requestCreateByValues={fakeCreate}
346
+ requestUpdateByValues={fakeUpdateById}
347
+ />
348
+ );
349
+ },
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,90 +1,18 @@
1
- import type { ProFormInstance, ActionType } from '@ant-design/pro-components';
2
- import { Space, Button } from 'antd';
3
- import type { ReactNode } from 'react';
1
+ import type { ActionType } from '@ant-design/pro-components';
2
+ import { Button, Space } from 'antd';
4
3
  import { forwardRef, useCallback, useImperativeHandle, useMemo, useRef } from 'react';
5
- import { Link, useLocation } from 'react-router-dom';
6
4
  import type { TableProps } from '../table';
7
5
  import { Table } from '../table';
8
6
  import { OperateDelete } from './crud_delete';
9
7
  import { CRUDDetail } from './crud_detail';
10
8
  import './style.scss';
9
+ import type { CRUDMethods, CRUDProps } from './types';
10
+ import { useRowSelection } from './use_row_selection';
11
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
- moreOperatorAfter?: (record) => ReactNode;
35
- };
36
- readProps?: {
37
- /** 文本 */
38
- operateText?: string;
39
- /** 打开方式, action 为 read_detail 有效 */
40
- target?: '_blank';
41
- };
42
-
43
- /** 删除接口 */
44
- requestDeleteByRecord?: (record) => Promise<any>;
45
- /** 删除相关 */
46
- deleteProps?: {
47
- /** 显示名称索引 */
48
- nameIndex: string;
49
- /** 删除确认描述 */
50
- desc?: string;
51
- /** 文本 */
52
- operateText?: string;
53
- };
54
-
55
- /** 弹窗表单 */
56
- detailForm?: (formProps: { readonly: boolean }, info: { action: CrudAction }) => ReactNode;
57
- /** detailForm 的 formRef */
58
- detailFormInstance?: ProFormInstance;
59
-
60
- /** 新增接口 */
61
- requestCreateByValues?: (values) => Promise<any>;
62
- createProps?: {
63
- /** 成功文案 */
64
- successText?: string | (() => string);
65
- };
66
-
67
- /** 更新接口 */
68
- requestUpdateById?: (values) => Promise<any>;
69
- updateProps?: {
70
- /** 文本 */
71
- operateText?: string;
72
- /** 成功文案 */
73
- successText?: string | (() => string);
74
- };
75
-
76
- /** 获取详情接口 */
77
- requestGetByRecord?: (record) => Promise<any>;
78
-
79
- /** 跳转到详情的索引 ,默认 id */
80
- detailIdIndex?: string;
81
- }
82
-
83
- interface CRUDMethods {
84
- getActionRef: () => React.MutableRefObject<ActionType | undefined>;
85
- }
86
-
87
- 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>) {
88
16
  const {
89
17
  actions,
90
18
  tableProps,
@@ -99,22 +27,21 @@ const CRUD = forwardRef<CRUDMethods, CRUDProps>(function CRUD(props, ref) {
99
27
  createProps,
100
28
  requestCreateByValues,
101
29
  updateProps,
102
- requestUpdateById,
30
+ requestUpdateById: originalRequestUpdateById,
31
+ requestUpdateByValues: originalRequestUpdateByValues,
103
32
  detailFormInstance,
33
+ batchActions,
104
34
  } = props;
105
35
 
36
+ const requestUpdateById = originalRequestUpdateByValues || originalRequestUpdateById;
37
+
106
38
  const actionRef = useRef<ActionType>();
107
- const location = useLocation();
108
39
 
109
- useImperativeHandle(
110
- ref,
111
- () => {
112
- return {
113
- getActionRef: () => actionRef,
114
- };
115
- },
116
- [actionRef]
117
- );
40
+ useImperativeHandle(ref, () => {
41
+ return {
42
+ getActionRef: () => actionRef,
43
+ };
44
+ }, [actionRef]);
118
45
 
119
46
  const detailProps = useMemo(
120
47
  () => ({
@@ -134,7 +61,7 @@ const CRUD = forwardRef<CRUDMethods, CRUDProps>(function CRUD(props, ref) {
134
61
  detailFormInstance,
135
62
  createProps,
136
63
  updateProps,
137
- ]
64
+ ],
138
65
  );
139
66
 
140
67
  const getHandleDelete = useCallback(
@@ -149,7 +76,7 @@ const CRUD = forwardRef<CRUDMethods, CRUDProps>(function CRUD(props, ref) {
149
76
  throw new Error('没有传 requestDeleteByRecord');
150
77
  };
151
78
  },
152
- [requestDeleteByRecord]
79
+ [requestDeleteByRecord],
153
80
  );
154
81
 
155
82
  const handleReload = useCallback(() => {
@@ -176,12 +103,9 @@ const CRUD = forwardRef<CRUDMethods, CRUDProps>(function CRUD(props, ref) {
176
103
  />
177
104
  )}
178
105
  {actions.includes('read_detail') && (
179
- <Link
180
- to={`${location.pathname}/detail/${record[detailIdIndex || 'id']}`}
181
- target={readProps?.target}
182
- >
106
+ <a href={`./detail/${record[detailIdIndex || 'id']}`} target={readProps?.target}>
183
107
  {readProps?.operateText || '查看'}
184
- </Link>
108
+ </a>
185
109
  )}
186
110
  {actions.includes('update') && (
187
111
  <CRUDDetail
@@ -230,7 +154,6 @@ const CRUD = forwardRef<CRUDMethods, CRUDProps>(function CRUD(props, ref) {
230
154
  readProps?.operateText,
231
155
  readProps?.target,
232
156
  detailProps,
233
- location.pathname,
234
157
  detailIdIndex,
235
158
  updateProps?.operateText,
236
159
  deleteProps,
@@ -239,6 +162,7 @@ const CRUD = forwardRef<CRUDMethods, CRUDProps>(function CRUD(props, ref) {
239
162
 
240
163
  const toolBarRender = useCallback(
241
164
  (...args) => [
165
+ // @ts-ignore
242
166
  ...(tableProps.toolBarRender ? tableProps.toolBarRender(...args) : []),
243
167
  actions.includes('create') && (
244
168
  <CRUDDetail
@@ -249,9 +173,16 @@ const CRUD = forwardRef<CRUDMethods, CRUDProps>(function CRUD(props, ref) {
249
173
  />
250
174
  ),
251
175
  ],
252
- [actions, createButton, detailProps, handleReload, tableProps]
176
+ [actions, createButton, detailProps, handleReload, tableProps],
253
177
  );
254
178
 
179
+ const { rowSelection, tableAlertRender, tableAlertOptionRender } = useRowSelection<
180
+ DataSource,
181
+ Key
182
+ >({
183
+ batchActions,
184
+ });
185
+
255
186
  return (
256
187
  <div className="crud-table">
257
188
  <Table
@@ -260,10 +191,19 @@ const CRUD = forwardRef<CRUDMethods, CRUDProps>(function CRUD(props, ref) {
260
191
  actionRef={actionRef}
261
192
  toolBarRender={toolBarRender}
262
193
  columns={newColumns}
194
+ rowSelection={rowSelection}
195
+ tableAlertRender={tableAlertRender}
196
+ tableAlertOptionRender={tableAlertOptionRender}
263
197
  />
264
198
  </div>
265
199
  );
266
- });
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;
267
208
 
268
209
  export { CRUD };
269
- export type { CRUDProps, CRUDMethods };