ginskill-init 1.0.2 → 2.4.0

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.

Potentially problematic release.


This version of ginskill-init might be problematic. Click here for more details.

@@ -0,0 +1,543 @@
1
+ # Ant Design Pro Components (ProTable, ProForm, ProLayout)
2
+
3
+ ```bash
4
+ npm install @ant-design/pro-components
5
+ ```
6
+
7
+ > Pro Components build on top of antd for enterprise admin UIs. All three share the same package.
8
+
9
+ ---
10
+
11
+ ## ProTable
12
+
13
+ ```typescript
14
+ import { ProTable } from '@ant-design/pro-components';
15
+ import type { ProColumns, ActionType } from '@ant-design/pro-components';
16
+ ```
17
+
18
+ ### Core Concept
19
+
20
+ Every column in `columns` automatically generates a filter form field. The `request` prop handles data fetching with pagination, sorting, and filtering — no manual state management needed.
21
+
22
+ ### ProTableProps (key props)
23
+
24
+ | Prop | Type | Description |
25
+ |------|------|-------------|
26
+ | `columns` | `ProColumns<T>[]` | Column + filter form definitions |
27
+ | `request` | `(params, sort, filter) => Promise<RequestData<T>>` | Data fetcher — the primary API |
28
+ | `actionRef` | `Ref<ActionType>` | Imperative table control (reload, reset, etc.) |
29
+ | `dataSource` | `T[]` | Static data (alternative to `request`) |
30
+ | `rowKey` | `string \| (r) => string` | Unique row key (default `'id'`) |
31
+ | `headerTitle` | `ReactNode` | Title above table |
32
+ | `toolBarRender` | `(action, { selectedRows }) => ReactNode[]` | Toolbar elements |
33
+ | `search` | `SearchConfig \| false` | Filter form config; `false` = hide |
34
+ | `pagination` | `PaginationConfig` | Pagination; default `{ pageSize: 20 }` |
35
+ | `rowSelection` | `TableRowSelection<T>` | Row selection config |
36
+ | `params` | `Record<string, any>` | Extra params always sent to `request` |
37
+ | `options` | `OptionsConfig \| false` | Toolbar icons (reload, density, settings) |
38
+ | `dateFormatter` | `'string' \| 'number' \| false` | Auto-format date params in request |
39
+
40
+ ### request function signature
41
+
42
+ ```typescript
43
+ type RequestFn<T> = (
44
+ params: {
45
+ pageSize?: number; // current page size
46
+ current?: number; // 1-indexed page number
47
+ [filterKey: string]: any; // filter form values
48
+ },
49
+ sort: Record<string, 'ascend' | 'descend'>, // column sort state
50
+ filter: Record<string, (string | number)[]>, // column filter state
51
+ ) => Promise<{
52
+ data: T[];
53
+ success: boolean; // REQUIRED — table checks this
54
+ total?: number; // REQUIRED for pagination
55
+ }>
56
+ ```
57
+
58
+ ### ProColumns
59
+
60
+ ```typescript
61
+ type ProColumns<T> = {
62
+ // Standard antd column props
63
+ dataIndex?: string | string[];
64
+ title?: string | ReactNode;
65
+ key?: string;
66
+ width?: number | string;
67
+ fixed?: 'left' | 'right';
68
+ align?: 'left' | 'center' | 'right';
69
+ sorter?: boolean | ((a: T, b: T) => number);
70
+ render?: (text, record: T, index, action: ActionType) => ReactNode;
71
+
72
+ // Pro-specific
73
+ valueType?: ProColumnValueType; // determines filter field and display
74
+ valueEnum?: ValueEnumMap; // for select/status display
75
+ hideInSearch?: boolean; // hide from filter form
76
+ hideInTable?: boolean; // hide from table columns
77
+ copyable?: boolean; // show copy icon
78
+ ellipsis?: boolean; // truncate text
79
+ tooltip?: string; // column header tooltip
80
+ fieldProps?: Record<string, any>; // props for the filter field component
81
+ formItemProps?: Record<string, any>; // props for Form.Item in filter
82
+ initialValue?: any; // initial filter value
83
+ request?: () => Promise<{ label, value }[]>; // async options
84
+ };
85
+ ```
86
+
87
+ ### valueType options
88
+
89
+ | valueType | Table display | Filter field |
90
+ |-----------|--------------|--------------|
91
+ | `'text'` | plain text | Input |
92
+ | `'select'` | badge/label | Select |
93
+ | `'date'` | formatted date | DatePicker |
94
+ | `'dateTime'` | date + time | DatePicker (time) |
95
+ | `'dateRange'` | — | RangePicker |
96
+ | `'money'` | `¥1,234.00` | InputNumber |
97
+ | `'percent'` | `12%` | InputNumber |
98
+ | `'progress'` | ProgressBar | Slider |
99
+ | `'status'` | colored badge | Select |
100
+ | `'avatar'` | Avatar | — |
101
+ | `'image'` | image | — |
102
+ | `'switch'` | — | Switch |
103
+ | `'option'` | action links | hidden |
104
+
105
+ ### valueEnum format
106
+
107
+ ```typescript
108
+ // Object form (key = value, status for badge color)
109
+ valueEnum: {
110
+ active: { text: 'Active', status: 'success' },
111
+ inactive: { text: 'Inactive', status: 'error' },
112
+ pending: { text: 'Pending', status: 'processing' },
113
+ }
114
+ // status: 'success' | 'error' | 'processing' | 'warning' | 'default'
115
+
116
+ // Array form
117
+ valueEnum: [
118
+ { label: 'Active', value: 'active' },
119
+ { label: 'Inactive', value: 'inactive' },
120
+ ]
121
+ ```
122
+
123
+ ### ActionType methods
124
+
125
+ ```typescript
126
+ const actionRef = useRef<ActionType>(null);
127
+
128
+ actionRef.current?.reload(); // reload current page
129
+ actionRef.current?.reloadAndRest(); // reload + reset to page 1
130
+ actionRef.current?.reset(); // reset filter form only
131
+ actionRef.current?.clearSelected(); // clear row selection
132
+ ```
133
+
134
+ ### Complete example
135
+
136
+ ```tsx
137
+ import { ProTable, ActionType } from '@ant-design/pro-components';
138
+ import type { ProColumns } from '@ant-design/pro-components';
139
+ import { useRef } from 'react';
140
+
141
+ interface User { id: string; name: string; status: string; createdAt: string; }
142
+
143
+ const UserTable = () => {
144
+ const actionRef = useRef<ActionType>(null);
145
+
146
+ const columns: ProColumns<User>[] = [
147
+ { dataIndex: 'name', title: 'Name' },
148
+ {
149
+ dataIndex: 'status', title: 'Status', valueType: 'select',
150
+ valueEnum: {
151
+ active: { text: 'Active', status: 'success' },
152
+ inactive: { text: 'Inactive', status: 'error' },
153
+ },
154
+ },
155
+ { dataIndex: 'createdAt', title: 'Created', valueType: 'dateTime', hideInSearch: true },
156
+ {
157
+ title: 'Actions', valueType: 'option', fixed: 'right', width: 120,
158
+ render: (_, record) => [
159
+ <a key="edit" onClick={() => openEditModal(record)}>Edit</a>,
160
+ <a key="delete" onClick={() => confirmDelete(record.id)}>Delete</a>,
161
+ ],
162
+ },
163
+ ];
164
+
165
+ return (
166
+ <ProTable<User>
167
+ actionRef={actionRef}
168
+ columns={columns}
169
+ rowKey="id"
170
+ headerTitle="Users"
171
+ request={async ({ current, pageSize, name, status }) => {
172
+ const res = await api.getUsers({ page: current, size: pageSize, name, status });
173
+ return { data: res.items, success: true, total: res.total };
174
+ }}
175
+ toolBarRender={() => [
176
+ <Button key="add" type="primary" onClick={openCreateModal}>+ Add</Button>,
177
+ <Button key="reload" onClick={() => actionRef.current?.reload()}>Reload</Button>,
178
+ ]}
179
+ search={{ labelWidth: 'auto' }}
180
+ pagination={{ pageSize: 20, showSizeChanger: true }}
181
+ />
182
+ );
183
+ };
184
+ ```
185
+
186
+ ### Common Mistakes
187
+ - **Missing `success: true`** in request response — table stays loading forever
188
+ - **Missing `total`** — pagination breaks without it
189
+ - **`dataIndex` mismatch** — must match your API response field names exactly
190
+ - **Filter params are primitives** — complex objects may not serialize correctly in requests
191
+
192
+ ---
193
+
194
+ ## ProForm
195
+
196
+ ```typescript
197
+ import {
198
+ ProForm, ProFormText, ProFormSelect, ProFormTextArea,
199
+ ProFormDatePicker, ProFormDateRangePicker, ProFormSwitch,
200
+ ProFormCheckbox, ProFormRadio, ProFormMoney, ProFormUploadButton,
201
+ ProFormDependencies, ModalForm, DrawerForm, QueryFilter, StepsForm,
202
+ } from '@ant-design/pro-components';
203
+ ```
204
+
205
+ ### Core Concept
206
+
207
+ `onFinish` returns a `Promise` — ProForm automatically manages button loading state. Fields use `name` to bind to form state; `fieldProps` passes props to the underlying component.
208
+
209
+ ### ProFormProps (key props)
210
+
211
+ | Prop | Type | Default | Description |
212
+ |------|------|---------|-------------|
213
+ | `onFinish` | `(values) => Promise<boolean \| void>` | — | Submit handler — **must return a Promise** |
214
+ | `initialValues` | `Record<string, any>` | — | Initial form values |
215
+ | `layout` | `'horizontal' \| 'vertical' \| 'inline'` | `'vertical'` | Form layout |
216
+ | `grid` | `boolean` | `false` | Enable grid layout for fields |
217
+ | `colProps` | `ColProps` | — | Default column props for all fields |
218
+ | `submitter` | `SubmitterProps \| false` | — | Submit/reset button config; `false` = hide |
219
+ | `readonly` | `boolean` | `false` | Read-only display mode |
220
+ | `disabled` | `boolean` | `false` | Disable all fields |
221
+ | `scrollToFirstError` | `boolean` | — | Scroll to first error on submit |
222
+
223
+ ### Field Width Presets
224
+
225
+ ```typescript
226
+ // width prop on any ProFormXxx field
227
+ 'xs' // 104px — short (ID, code)
228
+ 's' // 216px — shorter (name, phone)
229
+ 'm' // 328px — standard (default)
230
+ 'lg' // 440px — long (URL, tag list)
231
+ 'xl' // 552px — very long (description)
232
+ ```
233
+
234
+ ### ProForm Field Components
235
+
236
+ All fields share common props:
237
+ ```typescript
238
+ {
239
+ name: string | string[]; // field name (required)
240
+ label?: ReactNode; // field label
241
+ rules?: Rule[]; // validation rules
242
+ required?: boolean; // mark as required
243
+ tooltip?: string | ReactNode; // help tooltip
244
+ placeholder?: string;
245
+ width?: 'xs' | 's' | 'm' | 'lg' | 'xl' | number;
246
+ initialValue?: any;
247
+ disabled?: boolean;
248
+ readonly?: boolean;
249
+ fieldProps?: Record<string, any>; // props for underlying component
250
+ formItemProps?: Record<string, any>; // props for Form.Item
251
+ colProps?: ColProps; // column props for grid layout
252
+ }
253
+ ```
254
+
255
+ ### Key Fields
256
+
257
+ ```tsx
258
+ // Text input
259
+ <ProFormText name="name" label="Name" placeholder="Enter name"
260
+ rules={[{ required: true }, { min: 2 }]} width="m" />
261
+
262
+ // Password
263
+ <ProFormText name="pwd" label="Password" fieldProps={{ type: 'password' }} />
264
+
265
+ // Textarea
266
+ <ProFormTextArea name="bio" label="Bio" fieldProps={{ rows: 4, showCount: true, maxLength: 500 }} />
267
+
268
+ // Select from options
269
+ <ProFormSelect name="role" label="Role"
270
+ options={[{ label: 'Admin', value: 'admin' }, { label: 'User', value: 'user' }]} />
271
+
272
+ // Select from valueEnum
273
+ <ProFormSelect name="status" label="Status"
274
+ valueEnum={{ active: { text: 'Active' }, inactive: { text: 'Inactive' } }} />
275
+
276
+ // Async select options
277
+ <ProFormSelect name="category" label="Category"
278
+ request={async () => {
279
+ const res = await api.getCategories();
280
+ return res.map(c => ({ label: c.name, value: c.id }));
281
+ }} />
282
+
283
+ // Date picker
284
+ <ProFormDatePicker name="date" label="Date" fieldProps={{ format: 'YYYY-MM-DD' }} />
285
+
286
+ // Date range
287
+ <ProFormDateRangePicker name="dateRange" label="Date Range" />
288
+
289
+ // Switch (boolean)
290
+ <ProFormSwitch name="active" label="Active"
291
+ fieldProps={{ checkedChildren: 'On', unCheckedChildren: 'Off' }} />
292
+
293
+ // Checkbox group
294
+ <ProFormCheckbox.Group name="perms" label="Permissions"
295
+ options={[{ label: 'Read', value: 'r' }, { label: 'Write', value: 'w' }]} />
296
+
297
+ // Radio group
298
+ <ProFormRadio.Group name="size" label="Size"
299
+ options={[{ label: 'S', value: 's' }, { label: 'M', value: 'm' }]} />
300
+
301
+ // Money
302
+ <ProFormMoney name="price" label="Price" fieldProps={{ locale: 'en-US' }} />
303
+
304
+ // Upload
305
+ <ProFormUploadButton name="avatar" label="Avatar"
306
+ fieldProps={{ action: '/api/upload', accept: 'image/*', maxCount: 1, listType: 'picture-card' }} />
307
+
308
+ // Conditional fields (dependencies)
309
+ <ProFormDependencies name={['accountType']}>
310
+ {({ accountType }) =>
311
+ accountType === 'business' ? (
312
+ <ProFormText name="companyName" label="Company Name" rules={[{ required: true }]} />
313
+ ) : null
314
+ }
315
+ </ProFormDependencies>
316
+ ```
317
+
318
+ ### ProForm Variants
319
+
320
+ ```tsx
321
+ // Modal form (trigger opens a modal with the form)
322
+ <ModalForm title="Create User" trigger={<Button type="primary">Add User</Button>}
323
+ onFinish={async (values) => {
324
+ await createUser(values);
325
+ return true; // true closes the modal
326
+ }}>
327
+ <ProFormText name="name" label="Name" rules={[{ required: true }]} />
328
+ <ProFormText name="email" label="Email" rules={[{ type: 'email' }]} />
329
+ </ModalForm>
330
+
331
+ // Drawer form
332
+ <DrawerForm title="Edit Record" trigger={<a>Edit</a>}
333
+ drawerProps={{ destroyOnClose: true }}
334
+ onFinish={async (values) => {
335
+ await updateRecord(values);
336
+ return true;
337
+ }}>
338
+ <ProFormText name="title" label="Title" />
339
+ </DrawerForm>
340
+
341
+ // Query filter (horizontal filter bar)
342
+ <QueryFilter onFinish={async (filters) => setFilters(filters)} span={8} labelWidth="auto">
343
+ <ProFormText name="keyword" label="Keyword" />
344
+ <ProFormSelect name="status" label="Status" options={statusOptions} />
345
+ </QueryFilter>
346
+
347
+ // Step form
348
+ <StepsForm onFinish={async (allValues) => { await submit(allValues); }}>
349
+ <StepsForm.StepForm title="Basic Info">
350
+ <ProFormText name="name" label="Name" rules={[{ required: true }]} />
351
+ </StepsForm.StepForm>
352
+ <StepsForm.StepForm title="Contact">
353
+ <ProFormText name="email" label="Email" rules={[{ required: true, type: 'email' }]} />
354
+ </StepsForm.StepForm>
355
+ </StepsForm>
356
+ ```
357
+
358
+ ### Complete ProForm example
359
+
360
+ ```tsx
361
+ const CreateUserForm = () => (
362
+ <ProForm
363
+ layout="vertical"
364
+ grid
365
+ colProps={{ span: 12 }}
366
+ onFinish={async (values) => {
367
+ await api.createUser(values);
368
+ message.success('Created!');
369
+ return true;
370
+ }}
371
+ submitter={{ submitButtonProps: { block: true } }}
372
+ >
373
+ <ProFormText name="firstName" label="First Name" rules={[{ required: true }]} />
374
+ <ProFormText name="lastName" label="Last Name" rules={[{ required: true }]} />
375
+ <ProFormText name="email" label="Email" colProps={{ span: 24 }}
376
+ rules={[{ required: true }, { type: 'email' }]} />
377
+ <ProFormSelect name="role" label="Role" colProps={{ span: 24 }}
378
+ options={[{ label: 'Admin', value: 'admin' }, { label: 'User', value: 'user' }]} />
379
+ <ProFormSwitch name="active" label="Active" initialValue={true} />
380
+ </ProForm>
381
+ );
382
+ ```
383
+
384
+ ### Common Mistakes
385
+ - **`onFinish` must return a Promise** — without it, the button loading state doesn't work
386
+ - **Return `true` from `onFinish`** in ModalForm/DrawerForm to close the overlay; `false` or throwing keeps it open
387
+ - **Component props go in `fieldProps`** — `<ProFormText fieldProps={{ maxLength: 20 }}>`, not as root props
388
+ - **Password field**: use `fieldProps={{ type: 'password' }}` on `ProFormText`
389
+
390
+ ---
391
+
392
+ ## ProLayout
393
+
394
+ ```typescript
395
+ import { ProLayout, PageContainer } from '@ant-design/pro-components';
396
+ ```
397
+
398
+ ### Core Concept
399
+
400
+ ProLayout generates a sidebar menu from a `route` config object. Use `PageContainer` inside to get automatic breadcrumbs, page title, and footer toolbar.
401
+
402
+ ### ProLayoutProps (key props)
403
+
404
+ | Prop | Type | Default | Description |
405
+ |------|------|---------|-------------|
406
+ | `title` | `string \| ReactNode` | `'Ant Design Pro'` | Brand name |
407
+ | `logo` | `string \| ReactNode` | — | Logo in sidebar header |
408
+ | `layout` | `'side' \| 'top' \| 'mix'` | `'side'` | Menu placement |
409
+ | `navTheme` | `'light' \| 'dark' \| 'realDark'` | `'light'` | Sidebar theme |
410
+ | `collapsed` | `boolean` | — | Controlled sidebar collapse |
411
+ | `onCollapse` | `(collapsed) => void` | — | Collapse state change |
412
+ | `siderWidth` | `number` | `256` | Sidebar width (px) |
413
+ | `route` | `{ routes: MenuDataItem[] }` | — | Menu tree definition |
414
+ | `menuProps` | `MenuProps` | — | Passed to the Menu component |
415
+ | `menuDataRender` | `(menuData) => MenuDataItem[]` | — | Transform menu items (e.g., filter by permissions) |
416
+ | `rightContentRender` | `(props) => ReactNode` | — | Right side of header (user dropdown, etc.) |
417
+ | `headerRender` | `(props) => ReactNode` | — | Completely custom header |
418
+ | `footerRender` | `(props) => ReactNode` | — | Footer below content |
419
+ | `fixedHeader` | `boolean` | `false` | Sticky header |
420
+ | `fixSiderbar` | `boolean` | `false` | Sticky sidebar |
421
+ | `token` | `object` | — | Design token overrides |
422
+ | `onPageChange` | `(pathname) => void` | — | Navigation change callback |
423
+
424
+ ### MenuDataItem
425
+
426
+ ```typescript
427
+ type MenuDataItem = {
428
+ name: string; // REQUIRED — label text (without this, item is hidden!)
429
+ path?: string; // URL path
430
+ icon?: ReactNode | string; // icon (string = antd icon name)
431
+ children?: MenuDataItem[];
432
+ hideInMenu?: boolean; // hide from menu but keep route
433
+ hideChildrenInMenu?: boolean; // show parent, hide children
434
+ hideInBreadcrumb?: boolean;
435
+ disabled?: boolean;
436
+ access?: string; // permission key
437
+ };
438
+ ```
439
+
440
+ ### PageContainer Props
441
+
442
+ | Prop | Type | Description |
443
+ |------|------|-------------|
444
+ | `header.title` | `ReactNode` | Page title (also shown in browser tab) |
445
+ | `header.subTitle` | `ReactNode` | Subtitle |
446
+ | `header.extra` | `ReactNode` | Right side of header (action buttons) |
447
+ | `header.breadcrumb` | `BreadcrumbProps` | Custom breadcrumb config |
448
+ | `footer` | `ReactNode[]` | Footer toolbar (array of buttons) |
449
+ | `loading` | `boolean` | Loading state for content |
450
+ | `ghost` | `boolean` | Transparent background |
451
+ | `fixedHeader` | `boolean` | Sticky page header |
452
+
453
+ ### Patterns
454
+
455
+ ```tsx
456
+ // Basic layout with router
457
+ const App = () => {
458
+ const [collapsed, setCollapsed] = useState(false);
459
+
460
+ return (
461
+ <ProLayout
462
+ title="My App"
463
+ collapsed={collapsed}
464
+ onCollapse={setCollapsed}
465
+ layout="side"
466
+ navTheme="dark"
467
+ route={{
468
+ routes: [
469
+ { path: '/dashboard', name: 'Dashboard', icon: 'dashboard' },
470
+ {
471
+ path: '/users', name: 'Users', icon: 'team',
472
+ children: [
473
+ { path: '/users/list', name: 'All Users' },
474
+ { path: '/users/roles', name: 'Roles' },
475
+ ],
476
+ },
477
+ { path: '/settings', name: 'Settings', icon: 'setting' },
478
+ ],
479
+ }}
480
+ rightContentRender={() => (
481
+ <Space>
482
+ <Avatar src={user.avatar} />
483
+ <Dropdown menu={{ items: userMenuItems }}>
484
+ <span>{user.name}</span>
485
+ </Dropdown>
486
+ </Space>
487
+ )}
488
+ footerRender={() => <Footer style={{ textAlign: 'center' }}>My App © 2024</Footer>}
489
+ >
490
+ <PageContainer header={{ title: 'Dashboard' }}>
491
+ <Content />
492
+ </PageContainer>
493
+ </ProLayout>
494
+ );
495
+ };
496
+
497
+ // Page with action buttons and footer toolbar
498
+ <PageContainer
499
+ header={{
500
+ title: 'User Details',
501
+ subTitle: 'View and edit user info',
502
+ extra: [
503
+ <Button key="edit" type="primary">Edit</Button>,
504
+ <Button key="delete" danger>Delete</Button>,
505
+ ],
506
+ }}
507
+ footer={[
508
+ <Button key="cancel">Cancel</Button>,
509
+ <Button key="save" type="primary">Save</Button>,
510
+ ]}
511
+ >
512
+ <Descriptions bordered items={userFields} />
513
+ </PageContainer>
514
+
515
+ // Permission-based menu filtering
516
+ <ProLayout
517
+ menuDataRender={(menuData) =>
518
+ menuData.filter(item => hasPermission(item.access))
519
+ }
520
+ route={{ routes: allRoutes }}
521
+ >
522
+ ...
523
+ </ProLayout>
524
+ ```
525
+
526
+ ### Common Mistakes
527
+ - **Menu item without `name` is invisible** — always set `name` on every route
528
+ - **Icons as strings**: `icon: 'dashboard'` not `icon: <DashboardOutlined />` (for route configs)
529
+ - **`hideInMenu` vs `hideChildrenInMenu`**: former hides the item completely; latter shows parent but hides its sub-items
530
+ - **Breadcrumbs come from route `path` hierarchy** — missing intermediate routes creates breadcrumb gaps
531
+
532
+ ---
533
+
534
+ ## When to Use Pro Components
535
+
536
+ | Use Case | Component |
537
+ |----------|-----------|
538
+ | Admin table with server-side filter/sort/pagination | `ProTable` |
539
+ | Create/edit form in modal or drawer | `ModalForm` / `DrawerForm` |
540
+ | Multi-step wizard form | `StepsForm` |
541
+ | Horizontal search/filter bar | `QueryFilter` |
542
+ | Admin dashboard shell (sidebar + header) | `ProLayout` |
543
+ | Page content with breadcrumbs + action buttons | `PageContainer` |