antd-crud-table 0.0.0 → 0.0.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/.github/workflows/toghpage.yml +33 -0
- package/.github/workflows/tonpm.yml +31 -0
- package/helper/post-build.js +22 -0
- package/index.html +9 -0
- package/lib/CrudTable/CrudTable.css +3 -0
- package/lib/CrudTable/CrudTable.tsx +314 -0
- package/lib/CrudTable/CrudTableLazy.tsx +27 -0
- package/lib/main.ts +5 -0
- package/package.json +23 -18
- package/src/App.tsx +103 -31
- package/src/index.css +1 -1
- package/tsconfig.app.json +14 -6
- package/tsconfig.build.json +27 -0
- package/tsconfig.node.json +7 -5
- package/vite.config.lib.ts +26 -0
- package/vite.config.ts +2 -1
- package/public/vite.svg +0 -1
- package/src/CrudTable.tsx +0 -166
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Build and Publish to GH Pages
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main ]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
node-version: [18.x]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v1
|
|
18
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
|
19
|
+
uses: actions/setup-node@v1
|
|
20
|
+
with:
|
|
21
|
+
node-version: ${{ matrix.node-version }}
|
|
22
|
+
- name: 'Installing build deps'
|
|
23
|
+
run: npm i -g pnpm
|
|
24
|
+
- name: 'Installing deps'
|
|
25
|
+
run: pnpm i
|
|
26
|
+
- name: 'Build'
|
|
27
|
+
run: pnpm build:static
|
|
28
|
+
- name: 'Deploy'
|
|
29
|
+
uses: peaceiris/actions-gh-pages@v4
|
|
30
|
+
with:
|
|
31
|
+
deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }}
|
|
32
|
+
publish_dir: ./dist
|
|
33
|
+
allow_empty_commit: true
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Publish to NPM
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [created]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
node-version: [18.x]
|
|
14
|
+
registry-url: ['https://registry.npmjs.org']
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v2
|
|
18
|
+
- uses: actions/setup-node@v2
|
|
19
|
+
with:
|
|
20
|
+
node-version: ${{ matrix.node-version }}
|
|
21
|
+
registry-url: ${{ matrix.registry-url }}
|
|
22
|
+
- name: 'Installing build deps'
|
|
23
|
+
run: npm i -g pnpm
|
|
24
|
+
- name: 'Installing deps'
|
|
25
|
+
run: pnpm i
|
|
26
|
+
- name: 'Build'
|
|
27
|
+
run: pnpm build:lib
|
|
28
|
+
- name: 'Publish'
|
|
29
|
+
run: pnpm publish --no-git-checks
|
|
30
|
+
env:
|
|
31
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import packageJson from '../package.json' assert { type: 'json' };
|
|
4
|
+
import { exit } from 'process';
|
|
5
|
+
|
|
6
|
+
const distPath = path.resolve('dist/lib');
|
|
7
|
+
const packageJsonPath = path.join(distPath, 'package.json');
|
|
8
|
+
|
|
9
|
+
(async () => {
|
|
10
|
+
try {
|
|
11
|
+
// Ensure the dist/lib directory exists
|
|
12
|
+
await fs.mkdir(distPath, { recursive: true });
|
|
13
|
+
|
|
14
|
+
// Write the package.json file to the `dist/lib` directory
|
|
15
|
+
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf-8');
|
|
16
|
+
|
|
17
|
+
console.log(`package.json has been copied to ${packageJsonPath}`);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.error('Error copying package.json:', error);
|
|
20
|
+
exit(1);
|
|
21
|
+
}
|
|
22
|
+
})();
|
package/index.html
CHANGED
|
@@ -5,6 +5,15 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>Vite + React + TS</title>
|
|
8
|
+
|
|
9
|
+
<script async src="https://www.googletagmanager.com/gtag/js?id=G-3G1RNC5YLT"></script>
|
|
10
|
+
<script>
|
|
11
|
+
window.dataLayer = window.dataLayer || [];
|
|
12
|
+
function gtag(){dataLayer.push(arguments);}
|
|
13
|
+
gtag('js', new Date());
|
|
14
|
+
|
|
15
|
+
gtag('config', 'G-3G1RNC5YLT');
|
|
16
|
+
</script>
|
|
8
17
|
</head>
|
|
9
18
|
<body>
|
|
10
19
|
<div id="root"></div>
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { PlusOutlined, EllipsisOutlined } from '@ant-design/icons';
|
|
2
|
+
import type { ActionType, ProColumns } from '@ant-design/pro-components';
|
|
3
|
+
import { ProTable, ProConfigProvider } from '@ant-design/pro-components';
|
|
4
|
+
import { Button, Dropdown, Tag, message, Modal, Form, Input, InputNumber, Select, Switch, DatePicker } from 'antd';
|
|
5
|
+
import { useRef, useState } from 'react';
|
|
6
|
+
import type { SortOrder } from 'antd/es/table/interface';
|
|
7
|
+
import { format, parseISO, formatISO } from 'date-fns';
|
|
8
|
+
import dayjs from 'dayjs';
|
|
9
|
+
|
|
10
|
+
import './CrudTable.css';
|
|
11
|
+
|
|
12
|
+
type DataType = Record<string, any>;
|
|
13
|
+
type FieldType = 'string' | 'number' | 'date' | 'boolean' | 'enum' | 'custom';
|
|
14
|
+
|
|
15
|
+
interface CrudColumn<T extends DataType> extends ProColumns<T> {
|
|
16
|
+
fieldType?: FieldType;
|
|
17
|
+
enumOptions?: Record<string, { text: string;[key: string]: any }>;
|
|
18
|
+
customRender?: (value: any, record: T) => React.ReactNode;
|
|
19
|
+
formConfig?: {
|
|
20
|
+
required?: boolean;
|
|
21
|
+
component?: React.ReactNode;
|
|
22
|
+
transform?: (value: any) => any;
|
|
23
|
+
};
|
|
24
|
+
fieldEditable?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface CrudTableConfig<T extends DataType> {
|
|
28
|
+
columns: CrudColumn<T>[];
|
|
29
|
+
service: {
|
|
30
|
+
getList: (params: any) => Promise<{ data: T[]; total: number }>;
|
|
31
|
+
create: (data: Partial<T>) => Promise<T>;
|
|
32
|
+
update: (id: any, data: Partial<T>) => Promise<T>;
|
|
33
|
+
delete: (id: any) => Promise<void>;
|
|
34
|
+
};
|
|
35
|
+
rowKey: keyof T;
|
|
36
|
+
title: string;
|
|
37
|
+
defaultPageSize?: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const CrudTable = <T extends DataType>(config: CrudTableConfig<T>) => {
|
|
41
|
+
const actionRef = useRef<ActionType>(null);
|
|
42
|
+
const { columns, service, rowKey, title, defaultPageSize = 5 } = config;
|
|
43
|
+
const [modalVisible, setModalVisible] = useState(false);
|
|
44
|
+
const [currentRecord, setCurrentRecord] = useState<Partial<T> | null>(null);
|
|
45
|
+
const [form] = Form.useForm();
|
|
46
|
+
|
|
47
|
+
let enhancedColumns: ProColumns<T>[] = (columns || []).map((col) => {
|
|
48
|
+
const baseColumn: ProColumns<T> = {
|
|
49
|
+
...col,
|
|
50
|
+
dataIndex: col.dataIndex as string,
|
|
51
|
+
title: col.title,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
switch (col.fieldType) {
|
|
55
|
+
case 'date':
|
|
56
|
+
return {
|
|
57
|
+
...baseColumn,
|
|
58
|
+
valueType: 'dateTime',
|
|
59
|
+
render: (_, record) => (
|
|
60
|
+
<span>
|
|
61
|
+
{format(parseISO(record[col.dataIndex as string]), 'yyyy-MM-dd HH:mm')}
|
|
62
|
+
</span>
|
|
63
|
+
),
|
|
64
|
+
};
|
|
65
|
+
case 'enum':
|
|
66
|
+
return {
|
|
67
|
+
...baseColumn,
|
|
68
|
+
valueType: 'select',
|
|
69
|
+
valueEnum: col.enumOptions,
|
|
70
|
+
};
|
|
71
|
+
case 'number':
|
|
72
|
+
return {
|
|
73
|
+
...baseColumn,
|
|
74
|
+
valueType: 'digit',
|
|
75
|
+
};
|
|
76
|
+
case 'boolean':
|
|
77
|
+
return {
|
|
78
|
+
...baseColumn,
|
|
79
|
+
valueType: 'switch',
|
|
80
|
+
render: (_, record) => (
|
|
81
|
+
<Tag color={record[col.dataIndex as string] ? 'green' : 'red'}>
|
|
82
|
+
{record[col.dataIndex as string] ? 'Yes' : 'No'}
|
|
83
|
+
</Tag>
|
|
84
|
+
),
|
|
85
|
+
};
|
|
86
|
+
case 'custom':
|
|
87
|
+
return {
|
|
88
|
+
...baseColumn,
|
|
89
|
+
render: (_, record) => col.customRender?.(record[col.dataIndex as string], record),
|
|
90
|
+
};
|
|
91
|
+
default:
|
|
92
|
+
return baseColumn;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
enhancedColumns.push({
|
|
96
|
+
title: 'Actions',
|
|
97
|
+
valueType: 'option',
|
|
98
|
+
render: (_, record: T) => [
|
|
99
|
+
<Button key="edit" type="primary" onClick={() => openModal(record)}>
|
|
100
|
+
Edit
|
|
101
|
+
</Button>,
|
|
102
|
+
<Button key="delete" type="primary" danger onClick={async () => {
|
|
103
|
+
await service.delete(record[rowKey]);
|
|
104
|
+
actionRef.current?.reload();
|
|
105
|
+
}}>
|
|
106
|
+
Delete
|
|
107
|
+
</Button>,
|
|
108
|
+
],
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const handleRequest = async (
|
|
112
|
+
params: Record<string, any>,
|
|
113
|
+
sort: Record<string, SortOrder>,
|
|
114
|
+
filter: Record<string, any>,
|
|
115
|
+
) => {
|
|
116
|
+
try {
|
|
117
|
+
const query = {
|
|
118
|
+
...params,
|
|
119
|
+
sortBy: Object.keys(sort)[0],
|
|
120
|
+
sortOrder: Object.values(sort)[0],
|
|
121
|
+
...filter,
|
|
122
|
+
};
|
|
123
|
+
const { data, total } = await service.getList(query);
|
|
124
|
+
return { data, success: true, total };
|
|
125
|
+
} catch (error) {
|
|
126
|
+
message.error('Failed to fetch data');
|
|
127
|
+
return { data: [], success: false, total: 0 };
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const openModal = (record?: Partial<T>) => {
|
|
132
|
+
setCurrentRecord(record || null);
|
|
133
|
+
if (record) {
|
|
134
|
+
const values = { ...record };
|
|
135
|
+
columns.forEach((col) => {
|
|
136
|
+
const field = col.dataIndex as string;
|
|
137
|
+
if (col.fieldType === 'date' && values[field]) {
|
|
138
|
+
// @ts-ignore
|
|
139
|
+
values[field] = dayjs(values[field]);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
form.setFieldsValue(values);
|
|
143
|
+
} else {
|
|
144
|
+
form.resetFields();
|
|
145
|
+
}
|
|
146
|
+
setModalVisible(true);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const handleOk = async () => {
|
|
150
|
+
try {
|
|
151
|
+
const values = await form.validateFields();
|
|
152
|
+
const transformedValues = { ...values };
|
|
153
|
+
|
|
154
|
+
// Handle any transformations like date formatting
|
|
155
|
+
columns.forEach((col) => {
|
|
156
|
+
const field = col.dataIndex as string;
|
|
157
|
+
if (col.fieldType === 'date' && values[field]) {
|
|
158
|
+
transformedValues[field] = formatISO(values[field]);
|
|
159
|
+
}
|
|
160
|
+
if (col.formConfig?.transform) {
|
|
161
|
+
transformedValues[field] = col.formConfig.transform(values[field]);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
if (currentRecord && currentRecord[rowKey]) {
|
|
166
|
+
await service.update(currentRecord[rowKey], transformedValues);
|
|
167
|
+
message.success('Updated successfully');
|
|
168
|
+
} else {
|
|
169
|
+
await service.create(transformedValues);
|
|
170
|
+
message.success('Created successfully');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
setModalVisible(false);
|
|
174
|
+
actionRef.current?.reload();
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.error(error);
|
|
177
|
+
message.error('Submit failed');
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const handleCancel = () => {
|
|
182
|
+
setModalVisible(false);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<ProConfigProvider needDeps>
|
|
187
|
+
<ProTable<T>
|
|
188
|
+
headerTitle={title}
|
|
189
|
+
rowKey={rowKey as string}
|
|
190
|
+
// row classname to gray and white
|
|
191
|
+
rowClassName={(_, index) => (index % 2 === 0 && 'row-differentiator' || '')}
|
|
192
|
+
actionRef={actionRef}
|
|
193
|
+
columns={enhancedColumns}
|
|
194
|
+
request={handleRequest}
|
|
195
|
+
search={{ labelWidth: 'auto' }}
|
|
196
|
+
pagination={{ pageSize: defaultPageSize }}
|
|
197
|
+
toolBarRender={() => [
|
|
198
|
+
<Button
|
|
199
|
+
key="add"
|
|
200
|
+
type="primary"
|
|
201
|
+
icon={<PlusOutlined />}
|
|
202
|
+
onClick={() => openModal()}
|
|
203
|
+
>
|
|
204
|
+
New
|
|
205
|
+
</Button>,
|
|
206
|
+
<Dropdown
|
|
207
|
+
key="menu"
|
|
208
|
+
menu={{
|
|
209
|
+
items: [
|
|
210
|
+
{ key: 'export', label: 'Export' },
|
|
211
|
+
{ key: 'refresh', label: 'Refresh', onClick: () => actionRef.current?.reload() },
|
|
212
|
+
],
|
|
213
|
+
}}
|
|
214
|
+
>
|
|
215
|
+
<Button>
|
|
216
|
+
<EllipsisOutlined />
|
|
217
|
+
</Button>
|
|
218
|
+
</Dropdown>,
|
|
219
|
+
]}
|
|
220
|
+
options={{
|
|
221
|
+
setting: { listsHeight: 400 },
|
|
222
|
+
reload: () => actionRef.current?.reload(),
|
|
223
|
+
}}
|
|
224
|
+
dateFormatter="string"
|
|
225
|
+
/>
|
|
226
|
+
|
|
227
|
+
<Modal
|
|
228
|
+
forceRender
|
|
229
|
+
title={currentRecord ? 'Edit Item' : 'Create Item'}
|
|
230
|
+
open={modalVisible}
|
|
231
|
+
onOk={handleOk}
|
|
232
|
+
onCancel={handleCancel}
|
|
233
|
+
destroyOnClose
|
|
234
|
+
>
|
|
235
|
+
<Form form={form} layout="vertical">
|
|
236
|
+
{columns.map((col) => {
|
|
237
|
+
if (!col.dataIndex) return null;
|
|
238
|
+
const name = col.dataIndex as string;
|
|
239
|
+
const label = col.title as string;
|
|
240
|
+
const fieldDisabled = !(col.fieldEditable ?? true);
|
|
241
|
+
|
|
242
|
+
switch (col.fieldType) {
|
|
243
|
+
case 'string':
|
|
244
|
+
return (
|
|
245
|
+
<Form.Item
|
|
246
|
+
key={name}
|
|
247
|
+
name={name}
|
|
248
|
+
label={label}
|
|
249
|
+
rules={[{ required: col.formConfig?.required, message: `${label} is required` }]}
|
|
250
|
+
>
|
|
251
|
+
<Input disabled={fieldDisabled} />
|
|
252
|
+
</Form.Item>
|
|
253
|
+
);
|
|
254
|
+
case 'number':
|
|
255
|
+
return (
|
|
256
|
+
<Form.Item
|
|
257
|
+
key={name}
|
|
258
|
+
name={name}
|
|
259
|
+
label={label}
|
|
260
|
+
rules={[{ required: col.formConfig?.required, message: `${label} is required` }]}
|
|
261
|
+
>
|
|
262
|
+
<InputNumber style={{ width: '100%' }} disabled={fieldDisabled} />
|
|
263
|
+
</Form.Item>
|
|
264
|
+
);
|
|
265
|
+
case 'date':
|
|
266
|
+
return (
|
|
267
|
+
<Form.Item
|
|
268
|
+
key={name}
|
|
269
|
+
name={name}
|
|
270
|
+
label={label}
|
|
271
|
+
rules={[{ required: col.formConfig?.required, message: `${label} is required` }]}
|
|
272
|
+
>
|
|
273
|
+
<DatePicker style={{ width: '100%' }} showTime disabled={fieldDisabled} />
|
|
274
|
+
</Form.Item>
|
|
275
|
+
);
|
|
276
|
+
case 'boolean':
|
|
277
|
+
return (
|
|
278
|
+
<Form.Item
|
|
279
|
+
key={name}
|
|
280
|
+
name={name}
|
|
281
|
+
label={label}
|
|
282
|
+
valuePropName="checked"
|
|
283
|
+
>
|
|
284
|
+
<Switch disabled={fieldDisabled} />
|
|
285
|
+
</Form.Item>
|
|
286
|
+
);
|
|
287
|
+
case 'enum':
|
|
288
|
+
return (
|
|
289
|
+
<Form.Item
|
|
290
|
+
key={name}
|
|
291
|
+
name={name}
|
|
292
|
+
label={label}
|
|
293
|
+
rules={[{ required: col.formConfig?.required, message: `${label} is required` }]}
|
|
294
|
+
>
|
|
295
|
+
<Select
|
|
296
|
+
disabled={fieldDisabled}
|
|
297
|
+
options={Object.entries(col.enumOptions || {}).map(([value, option]) => ({
|
|
298
|
+
label: option.text,
|
|
299
|
+
value,
|
|
300
|
+
}))} />
|
|
301
|
+
</Form.Item>
|
|
302
|
+
);
|
|
303
|
+
default:
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
})}
|
|
307
|
+
</Form>
|
|
308
|
+
</Modal>
|
|
309
|
+
</ProConfigProvider>
|
|
310
|
+
);
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
export default CrudTable;
|
|
314
|
+
export type { CrudTableConfig, CrudColumn, DataType };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Suspense, lazy } from 'react';
|
|
2
|
+
import type { CrudTableConfig, DataType } from './CrudTable';
|
|
3
|
+
|
|
4
|
+
const CrudTable = lazy(() => import('./CrudTable'));
|
|
5
|
+
|
|
6
|
+
const CrudTableLazy = <T extends DataType>(props: CrudTableConfig<T>) => {
|
|
7
|
+
const { columns, service, rowKey, title, defaultPageSize = 5 } = props;
|
|
8
|
+
return (
|
|
9
|
+
<Suspense
|
|
10
|
+
fallback={
|
|
11
|
+
<div style={{ textAlign: 'center', padding: '2rem' }}>
|
|
12
|
+
Loading curd table w antd
|
|
13
|
+
</div>
|
|
14
|
+
}
|
|
15
|
+
>
|
|
16
|
+
<CrudTable
|
|
17
|
+
columns={columns as any}
|
|
18
|
+
service={service as any}
|
|
19
|
+
rowKey={rowKey as string}
|
|
20
|
+
title={title}
|
|
21
|
+
defaultPageSize={defaultPageSize}
|
|
22
|
+
/>
|
|
23
|
+
</Suspense>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default CrudTableLazy;
|
package/lib/main.ts
ADDED
package/package.json
CHANGED
|
@@ -1,39 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "antd-crud-table",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.3",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"
|
|
7
|
-
"dev": "vite",
|
|
8
|
-
"build": "tsc -b && vite build",
|
|
9
|
-
"lint": "eslint .",
|
|
10
|
-
"preview": "vite preview"
|
|
11
|
-
},
|
|
12
|
-
"dependencies": {
|
|
13
|
-
"react": "^19.0.0",
|
|
14
|
-
"react-dom": "^19.0.0"
|
|
15
|
-
},
|
|
6
|
+
"homepage": ".",
|
|
16
7
|
"devDependencies": {
|
|
8
|
+
"@ant-design/icons": "^6.0.0",
|
|
9
|
+
"@ant-design/pro-components": "^2.8.7",
|
|
17
10
|
"@eslint/js": "^9.22.0",
|
|
11
|
+
"@types/node": "^22.15.2",
|
|
18
12
|
"@types/react": "^19.0.10",
|
|
19
13
|
"@types/react-dom": "^19.0.4",
|
|
20
14
|
"@vitejs/plugin-react-swc": "^3.8.0",
|
|
15
|
+
"antd": "^5.24.8",
|
|
16
|
+
"date-fns": "^4.1.0",
|
|
17
|
+
"dayjs": "^1.11.13",
|
|
21
18
|
"eslint": "^9.22.0",
|
|
22
19
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
23
20
|
"eslint-plugin-react-refresh": "^0.4.19",
|
|
24
21
|
"globals": "^16.0.0",
|
|
22
|
+
"react": "^19.0.0",
|
|
23
|
+
"react-dom": "^19.0.0",
|
|
25
24
|
"typescript": "~5.7.2",
|
|
26
25
|
"typescript-eslint": "^8.26.1",
|
|
27
|
-
"vite": "^6.3.1"
|
|
28
|
-
"@ant-design/icons": "^6.0.0",
|
|
29
|
-
"@ant-design/pro-components": "^2.8.7",
|
|
30
|
-
"antd": "^5.24.8",
|
|
31
|
-
"date-fns": "^4.1.0"
|
|
26
|
+
"vite": "^6.3.1"
|
|
32
27
|
},
|
|
33
28
|
"peerDependencies": {
|
|
34
29
|
"@ant-design/icons": "^6.0.0",
|
|
35
30
|
"@ant-design/pro-components": "^2.8.7",
|
|
36
31
|
"antd": "^5.24.8",
|
|
37
|
-
"date-fns": "^4.1.0"
|
|
32
|
+
"date-fns": "^4.1.0",
|
|
33
|
+
"dayjs": "^1.11.13",
|
|
34
|
+
"react": "^19.0.0",
|
|
35
|
+
"react-dom": "^19.0.0"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"dev": "vite",
|
|
39
|
+
"build:static": "tsc -b && vite build",
|
|
40
|
+
"build:lib": "tsc --p ./tsconfig.build.json && vite build --config vite.config.lib.ts && node helper/post-build.js",
|
|
41
|
+
"lint": "eslint .",
|
|
42
|
+
"preview": "vite preview"
|
|
38
43
|
}
|
|
39
|
-
}
|
|
44
|
+
}
|
package/src/App.tsx
CHANGED
|
@@ -1,35 +1,107 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
2
|
-
import reactLogo from './assets/react.svg'
|
|
3
|
-
import viteLogo from '/vite.svg'
|
|
4
1
|
import './App.css'
|
|
2
|
+
import CrudTableLazy from '../lib/CrudTable/CrudTableLazy';
|
|
3
|
+
// import CrudTable from '../lib/CrudTable/CrudTable';
|
|
4
|
+
import { ConfigProvider } from 'antd';
|
|
5
|
+
import enUS from 'antd/locale/en_US';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
<img src={viteLogo} className="logo" alt="Vite logo" />
|
|
14
|
-
</a>
|
|
15
|
-
<a href="https://react.dev" target="_blank">
|
|
16
|
-
<img src={reactLogo} className="logo react" alt="React logo" />
|
|
17
|
-
</a>
|
|
18
|
-
</div>
|
|
19
|
-
<h1>Vite + React</h1>
|
|
20
|
-
<div className="card">
|
|
21
|
-
<button onClick={() => setCount((count) => count + 1)}>
|
|
22
|
-
count is {count}
|
|
23
|
-
</button>
|
|
24
|
-
<p>
|
|
25
|
-
Edit <code>src/App.tsx</code> and save to test HMR
|
|
26
|
-
</p>
|
|
27
|
-
</div>
|
|
28
|
-
<p className="read-the-docs">
|
|
29
|
-
Click on the Vite and React logos to learn more
|
|
30
|
-
</p>
|
|
31
|
-
</>
|
|
32
|
-
)
|
|
7
|
+
interface User {
|
|
8
|
+
id: number;
|
|
9
|
+
name: string;
|
|
10
|
+
age: number;
|
|
11
|
+
createdAt: string;
|
|
12
|
+
status: 'active' | 'inactive';
|
|
13
|
+
isAdmin: boolean;
|
|
33
14
|
}
|
|
34
15
|
|
|
35
|
-
|
|
16
|
+
class UserService {
|
|
17
|
+
// @ts-ignore
|
|
18
|
+
async getList(params: any): Promise<{ data: User[]; total: number }> {
|
|
19
|
+
// todo
|
|
20
|
+
return {
|
|
21
|
+
data: [
|
|
22
|
+
{ id: 1, name: 'Jane Smith 1', age: 30, createdAt: '2023-01-01', status: 'active', isAdmin: true },
|
|
23
|
+
{ id: 2, name: 'Jane Smith 2', age: 25, createdAt: '2023-02-01', status: 'inactive', isAdmin: false },
|
|
24
|
+
{ id: 3, name: 'Jane Smith 3', age: 25, createdAt: '2023-02-01', status: 'inactive', isAdmin: false },
|
|
25
|
+
{ id: 4, name: 'Jane Smith 4', age: 25, createdAt: '2023-02-01', status: 'inactive', isAdmin: false },
|
|
26
|
+
{ id: 5, name: 'Jane Smith 5', age: 25, createdAt: '2023-02-01', status: 'inactive', isAdmin: false },
|
|
27
|
+
{ id: 6, name: 'Jane Smith 6', age: 25, createdAt: '2023-02-01', status: 'inactive', isAdmin: false },
|
|
28
|
+
{ id: 7, name: 'Jane Smith 7', age: 25, createdAt: '2023-02-01', status: 'inactive', isAdmin: false },
|
|
29
|
+
{ id: 8, name: 'Jane Smith 8', age: 25, createdAt: '2023-02-01', status: 'inactive', isAdmin: false },
|
|
30
|
+
{ id: 9, name: 'Jane Smith 9', age: 25, createdAt: '2023-02-01', status: 'inactive', isAdmin: false },
|
|
31
|
+
{ id: 10, name: 'Jane Smith 10', age: 25, createdAt: '2023-02-01', status: 'inactive', isAdmin: false },
|
|
32
|
+
{ id: 11, name: 'Jane Smith 11', age: 25, createdAt: '2023-02-01', status: 'inactive', isAdmin: false },
|
|
33
|
+
{ id: 12, name: 'Jane Smith 12', age: 25, createdAt: '2023-02-01', status: 'inactive', isAdmin: false },
|
|
34
|
+
],
|
|
35
|
+
total: 2,
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async create(data: Partial<User>) {
|
|
40
|
+
// todo
|
|
41
|
+
return data as User;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async update(id: number, data: Partial<User>) {
|
|
45
|
+
// todo
|
|
46
|
+
return { id, ...data } as User;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// @ts-ignore
|
|
50
|
+
async delete(id: number) {
|
|
51
|
+
// todo
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const UserTable = () => (
|
|
56
|
+
<CrudTableLazy<User>
|
|
57
|
+
title="User Management"
|
|
58
|
+
rowKey="id"
|
|
59
|
+
// defaultPageSize={10}
|
|
60
|
+
service={new UserService()}
|
|
61
|
+
columns={[
|
|
62
|
+
{
|
|
63
|
+
dataIndex: 'name',
|
|
64
|
+
title: 'Name',
|
|
65
|
+
fieldType: 'string',
|
|
66
|
+
formConfig: { required: true },
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
dataIndex: 'age',
|
|
70
|
+
title: 'Age',
|
|
71
|
+
fieldType: 'number',
|
|
72
|
+
fieldEditable: false,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
dataIndex: 'age2',
|
|
76
|
+
title: 'Age2',
|
|
77
|
+
fieldType: 'number',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
dataIndex: 'createdAt',
|
|
81
|
+
title: 'Created At',
|
|
82
|
+
fieldType: 'date',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
dataIndex: 'status',
|
|
86
|
+
title: 'Status',
|
|
87
|
+
fieldType: 'enum',
|
|
88
|
+
enumOptions: {
|
|
89
|
+
active: { text: 'Active' },
|
|
90
|
+
inactive: { text: 'Inactive' },
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
dataIndex: 'isAdmin',
|
|
95
|
+
title: 'Administrator',
|
|
96
|
+
fieldType: 'boolean',
|
|
97
|
+
},
|
|
98
|
+
]}
|
|
99
|
+
/>
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const App = () => <ConfigProvider locale={enUS}>
|
|
103
|
+
<UserTable />
|
|
104
|
+
</ConfigProvider>
|
|
105
|
+
|
|
106
|
+
// eslint-disable-next-line import/no-default-export
|
|
107
|
+
export default App;
|
package/src/index.css
CHANGED
package/tsconfig.app.json
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
4
4
|
"target": "ES2020",
|
|
5
5
|
"useDefineForClassFields": true,
|
|
6
|
-
"lib": [
|
|
6
|
+
"lib": [
|
|
7
|
+
"ES2020",
|
|
8
|
+
"DOM",
|
|
9
|
+
"DOM.Iterable"
|
|
10
|
+
],
|
|
7
11
|
"module": "ESNext",
|
|
8
12
|
"skipLibCheck": true,
|
|
9
|
-
|
|
10
13
|
/* Bundler mode */
|
|
11
14
|
"moduleResolution": "bundler",
|
|
12
15
|
"allowImportingTsExtensions": true,
|
|
@@ -14,13 +17,18 @@
|
|
|
14
17
|
"moduleDetection": "force",
|
|
15
18
|
"noEmit": true,
|
|
16
19
|
"jsx": "react-jsx",
|
|
17
|
-
|
|
18
20
|
/* Linting */
|
|
19
21
|
"strict": true,
|
|
20
22
|
"noUnusedLocals": true,
|
|
21
23
|
"noUnusedParameters": true,
|
|
22
24
|
"noFallthroughCasesInSwitch": true,
|
|
23
|
-
"noUncheckedSideEffectImports": true
|
|
25
|
+
"noUncheckedSideEffectImports": true,
|
|
26
|
+
"types": [
|
|
27
|
+
"node"
|
|
28
|
+
]
|
|
24
29
|
},
|
|
25
|
-
"include": [
|
|
26
|
-
|
|
30
|
+
"include": [
|
|
31
|
+
"src",
|
|
32
|
+
"lib"
|
|
33
|
+
]
|
|
34
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": [
|
|
6
|
+
"DOM",
|
|
7
|
+
"DOM.Iterable",
|
|
8
|
+
"ESNext"
|
|
9
|
+
],
|
|
10
|
+
"moduleResolution": "Node",
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"allowSyntheticDefaultImports": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"isolatedModules": true,
|
|
15
|
+
"skipLibCheck": true,
|
|
16
|
+
"declaration": true,
|
|
17
|
+
"declarationDir": "./dist/lib/types",
|
|
18
|
+
"emitDeclarationOnly": false,
|
|
19
|
+
"outDir": "./dist/lib",
|
|
20
|
+
"jsx": "react-jsx",
|
|
21
|
+
"strict": true,
|
|
22
|
+
"forceConsistentCasingInFileNames": true
|
|
23
|
+
},
|
|
24
|
+
"include": [
|
|
25
|
+
"lib"
|
|
26
|
+
]
|
|
27
|
+
}
|
package/tsconfig.node.json
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
4
4
|
"target": "ES2022",
|
|
5
|
-
"lib": [
|
|
5
|
+
"lib": [
|
|
6
|
+
"ES2023"
|
|
7
|
+
],
|
|
6
8
|
"module": "ESNext",
|
|
7
9
|
"skipLibCheck": true,
|
|
8
|
-
|
|
9
10
|
/* Bundler mode */
|
|
10
11
|
"moduleResolution": "bundler",
|
|
11
12
|
"allowImportingTsExtensions": true,
|
|
12
13
|
"isolatedModules": true,
|
|
13
14
|
"moduleDetection": "force",
|
|
14
15
|
"noEmit": true,
|
|
15
|
-
|
|
16
16
|
/* Linting */
|
|
17
17
|
"strict": true,
|
|
18
18
|
"noUnusedLocals": true,
|
|
@@ -20,5 +20,7 @@
|
|
|
20
20
|
"noFallthroughCasesInSwitch": true,
|
|
21
21
|
"noUncheckedSideEffectImports": true
|
|
22
22
|
},
|
|
23
|
-
"include": [
|
|
24
|
-
|
|
23
|
+
"include": [
|
|
24
|
+
"vite.config.ts"
|
|
25
|
+
]
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import react from '@vitejs/plugin-react-swc';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [react()],
|
|
7
|
+
build: {
|
|
8
|
+
lib: {
|
|
9
|
+
entry: path.resolve(__dirname, 'lib/main.ts'),
|
|
10
|
+
name: 'AntdCrudTable',
|
|
11
|
+
fileName: (format) => `antd-crud-table.${format}.js`,
|
|
12
|
+
formats: ['es', 'cjs', 'umd'],
|
|
13
|
+
},
|
|
14
|
+
outDir: 'dist/lib',
|
|
15
|
+
rollupOptions: {
|
|
16
|
+
external: ['react', 'react-dom', 'antd', '@ant-design/icons', '@ant-design/pro-components', 'date-fns', 'dayjs'],
|
|
17
|
+
output: {
|
|
18
|
+
globals: {
|
|
19
|
+
react: 'React',
|
|
20
|
+
'react-dom': 'ReactDOM',
|
|
21
|
+
antd: 'antd',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
});
|
package/vite.config.ts
CHANGED
package/public/vite.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
package/src/CrudTable.tsx
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import { PlusOutlined, EllipsisOutlined } from '@ant-design/icons';
|
|
2
|
-
import type { ActionType, ProColumns } from '@ant-design/pro-components';
|
|
3
|
-
import { ProTable, ProConfigProvider } from '@ant-design/pro-components';
|
|
4
|
-
import { Button, Dropdown, Tag, message } from 'antd';
|
|
5
|
-
import { useRef } from 'react';
|
|
6
|
-
import type { SortOrder } from 'antd/es/table/interface';
|
|
7
|
-
import { format, parseISO } from 'date-fns';
|
|
8
|
-
|
|
9
|
-
type DataType = Record<string, any>;
|
|
10
|
-
type FieldType = 'string' | 'number' | 'date' | 'boolean' | 'enum' | 'custom';
|
|
11
|
-
|
|
12
|
-
interface CrudColumn<T extends DataType> extends ProColumns<T> {
|
|
13
|
-
fieldType?: FieldType;
|
|
14
|
-
enumOptions?: Record<string, { text: string;[key: string]: any }>;
|
|
15
|
-
customRender?: (value: any, record: T) => React.ReactNode;
|
|
16
|
-
formConfig?: {
|
|
17
|
-
required?: boolean;
|
|
18
|
-
component?: React.ReactNode;
|
|
19
|
-
transform?: (value: any) => any;
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
interface CrudTableConfig<T extends DataType> {
|
|
24
|
-
columns: CrudColumn<T>[];
|
|
25
|
-
service: {
|
|
26
|
-
getList: (params: any) => Promise<{ data: T[]; total: number }>;
|
|
27
|
-
create: (data: Partial<T>) => Promise<T>;
|
|
28
|
-
update: (id: any, data: Partial<T>) => Promise<T>;
|
|
29
|
-
delete: (id: any) => Promise<void>;
|
|
30
|
-
};
|
|
31
|
-
rowKey: keyof T;
|
|
32
|
-
title: string;
|
|
33
|
-
defaultPageSize?: number;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const CrudTable = <T extends DataType>(config: CrudTableConfig<T>) => {
|
|
37
|
-
const actionRef = useRef<ActionType>(null);
|
|
38
|
-
const { columns, service, rowKey, title, defaultPageSize = 5 } = config;
|
|
39
|
-
|
|
40
|
-
const enhancedColumns: ProColumns<T>[] = (columns || []).map((col) => {
|
|
41
|
-
const baseColumn: ProColumns<T> = {
|
|
42
|
-
...col,
|
|
43
|
-
dataIndex: col.dataIndex as string,
|
|
44
|
-
title: col.title,
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
switch (col.fieldType) {
|
|
48
|
-
case 'date':
|
|
49
|
-
return {
|
|
50
|
-
...baseColumn,
|
|
51
|
-
valueType: 'dateTime',
|
|
52
|
-
render: (_, record) => (
|
|
53
|
-
<span>
|
|
54
|
-
{format(parseISO(record[col.dataIndex as string]), 'yyyy-MM-dd HH:mm')}
|
|
55
|
-
</span>
|
|
56
|
-
),
|
|
57
|
-
};
|
|
58
|
-
case 'enum':
|
|
59
|
-
return {
|
|
60
|
-
...baseColumn,
|
|
61
|
-
valueType: 'select',
|
|
62
|
-
valueEnum: col.enumOptions,
|
|
63
|
-
};
|
|
64
|
-
case 'number':
|
|
65
|
-
return {
|
|
66
|
-
...baseColumn,
|
|
67
|
-
valueType: 'digit',
|
|
68
|
-
};
|
|
69
|
-
case 'boolean':
|
|
70
|
-
return {
|
|
71
|
-
...baseColumn,
|
|
72
|
-
valueType: 'switch',
|
|
73
|
-
render: (_, record) => (
|
|
74
|
-
<Tag color={record[col.dataIndex as string] ? 'green' : 'red'}>
|
|
75
|
-
{record[col.dataIndex as string] ? 'Yes' : 'No'}
|
|
76
|
-
</Tag>
|
|
77
|
-
),
|
|
78
|
-
};
|
|
79
|
-
case 'custom':
|
|
80
|
-
return {
|
|
81
|
-
...baseColumn,
|
|
82
|
-
render: (_, record) => col.customRender?.(record[col.dataIndex as string], record),
|
|
83
|
-
};
|
|
84
|
-
default:
|
|
85
|
-
return baseColumn;
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
const handleRequest = async (
|
|
90
|
-
params: Record<string, any>,
|
|
91
|
-
sort: Record<string, SortOrder>,
|
|
92
|
-
filter: Record<string, any>,
|
|
93
|
-
) => {
|
|
94
|
-
try {
|
|
95
|
-
const query = {
|
|
96
|
-
...params,
|
|
97
|
-
sortBy: Object.keys(sort)[0],
|
|
98
|
-
sortOrder: Object.values(sort)[0],
|
|
99
|
-
...filter,
|
|
100
|
-
};
|
|
101
|
-
const { data, total } = await service.getList(query);
|
|
102
|
-
return { data, success: true, total };
|
|
103
|
-
} catch (error) {
|
|
104
|
-
message.error('Failed to fetch data');
|
|
105
|
-
return { data: [], success: false, total: 0 };
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
return (
|
|
110
|
-
<ProConfigProvider needDeps>
|
|
111
|
-
<ProTable<T>
|
|
112
|
-
headerTitle={title}
|
|
113
|
-
rowKey={rowKey as string}
|
|
114
|
-
actionRef={actionRef}
|
|
115
|
-
columns={enhancedColumns}
|
|
116
|
-
request={handleRequest}
|
|
117
|
-
editable={{
|
|
118
|
-
type: 'multiple',
|
|
119
|
-
onSave: async (key, row) => {
|
|
120
|
-
await service.update(key, row);
|
|
121
|
-
actionRef.current?.reload();
|
|
122
|
-
},
|
|
123
|
-
onDelete: async (key) => {
|
|
124
|
-
await service.delete(key);
|
|
125
|
-
actionRef.current?.reload();
|
|
126
|
-
},
|
|
127
|
-
}}
|
|
128
|
-
search={{ labelWidth: 'auto' }}
|
|
129
|
-
pagination={{ pageSize: defaultPageSize }}
|
|
130
|
-
toolBarRender={() => [
|
|
131
|
-
<Button
|
|
132
|
-
key="add"
|
|
133
|
-
type="primary"
|
|
134
|
-
icon={<PlusOutlined />}
|
|
135
|
-
onClick={() => {
|
|
136
|
-
// todo: create logic here
|
|
137
|
-
actionRef.current?.reload();
|
|
138
|
-
}}
|
|
139
|
-
>
|
|
140
|
-
New
|
|
141
|
-
</Button>,
|
|
142
|
-
<Dropdown
|
|
143
|
-
key="menu"
|
|
144
|
-
menu={{
|
|
145
|
-
items: [
|
|
146
|
-
{ key: 'export', label: 'Export' },
|
|
147
|
-
{ key: 'refresh', label: 'Refresh' },
|
|
148
|
-
],
|
|
149
|
-
}}
|
|
150
|
-
>
|
|
151
|
-
<Button>
|
|
152
|
-
<EllipsisOutlined />
|
|
153
|
-
</Button>
|
|
154
|
-
</Dropdown>,
|
|
155
|
-
]}
|
|
156
|
-
options={{
|
|
157
|
-
setting: { listsHeight: 400 },
|
|
158
|
-
reload: () => actionRef.current?.reload(),
|
|
159
|
-
}}
|
|
160
|
-
dateFormatter="string"
|
|
161
|
-
/>
|
|
162
|
-
</ProConfigProvider>
|
|
163
|
-
);
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
export {CrudTable}
|