@deepstorm/cli 0.1.0 → 0.2.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.
- package/dist/config-schema.json +1 -1
- package/dist/registry.json +65 -5
- package/dist/skills/reef-gen-frontend/variants/react/steps.md +31 -0
- package/dist/skills/reef-gen-frontend/variants/vue/steps.md +34 -0
- package/dist/skills/reef-style-frontend/fragments/test/vitest-react/examples/testing.md +283 -0
- package/dist/skills/reef-style-frontend/fragments/test/vitest-react/quick-reference.md +152 -0
- package/dist/skills/reef-style-frontend/fragments/test/vitest-vue/examples/testing.md +457 -0
- package/dist/skills/reef-style-frontend/fragments/test/vitest-vue/quick-reference.md +186 -0
- package/dist/skills/reef-style-frontend/fragments/ui-lib/antd/examples/ui-components.md +257 -0
- package/dist/skills/reef-style-frontend/fragments/ui-lib/antd/quick-reference.md +166 -0
- package/dist/skills/reef-style-frontend/fragments/ui-lib/antd-vue/examples/ui-components.md +353 -0
- package/dist/skills/reef-style-frontend/fragments/ui-lib/antd-vue/quick-reference.md +267 -0
- package/dist/skills/reef-style-frontend/variants/react/examples/code-wrapping.md +124 -0
- package/dist/skills/reef-style-frontend/variants/react/examples/component-types-pipes.md +195 -0
- package/dist/skills/reef-style-frontend/variants/react/examples/entity-types.md +127 -0
- package/dist/skills/reef-style-frontend/variants/react/examples/forms-layer.md +251 -0
- package/dist/skills/reef-style-frontend/variants/react/examples/service-routing.md +226 -0
- package/dist/skills/reef-style-frontend/variants/react/quick-reference.md +118 -0
- package/dist/skills/reef-style-frontend/variants/vue/examples/code-wrapping.md +162 -0
- package/dist/skills/reef-style-frontend/variants/vue/examples/component-types-pipes.md +270 -0
- package/dist/skills/reef-style-frontend/variants/vue/examples/entity-types.md +145 -0
- package/dist/skills/reef-style-frontend/variants/vue/examples/forms-layer.md +231 -0
- package/dist/skills/reef-style-frontend/variants/vue/examples/service-routing.md +224 -0
- package/dist/skills/reef-style-frontend/variants/vue/quick-reference.md +145 -0
- package/package.json +3 -1
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# React/JSX 代码折行规则
|
|
2
|
+
|
|
3
|
+
遵循 90 列限制,以下是 React 专有的折行场景。
|
|
4
|
+
|
|
5
|
+
## JSX 属性换行
|
|
6
|
+
|
|
7
|
+
单个属性保持在行内;3 个及以上属性每个占一行:
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
// ✅ 单属性行内
|
|
11
|
+
<Button type="primary">提交</Button>
|
|
12
|
+
|
|
13
|
+
// ✅ 2 个属性行内
|
|
14
|
+
<Button type="primary" loading={submitting}>提交</Button>
|
|
15
|
+
|
|
16
|
+
// ✅ 3+ 属性换行排列
|
|
17
|
+
<Table
|
|
18
|
+
dataSource={users}
|
|
19
|
+
columns={columns}
|
|
20
|
+
rowKey="id"
|
|
21
|
+
loading={loading}
|
|
22
|
+
pagination={{ pageSize: 20 }}
|
|
23
|
+
onChange={handlePageChange}
|
|
24
|
+
/>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 自闭合标签
|
|
28
|
+
|
|
29
|
+
无子元素时使用自闭合标签,属性换行规则同上:
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
// ✅ 无属性
|
|
33
|
+
<Divider />
|
|
34
|
+
|
|
35
|
+
// ✅ 多属性
|
|
36
|
+
<DatePicker
|
|
37
|
+
value={date}
|
|
38
|
+
onChange={setDate}
|
|
39
|
+
format="YYYY-MM-DD"
|
|
40
|
+
/>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 条件渲染换行
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
// ✅ 简单的三元表达式保持行内
|
|
47
|
+
return <div>{loading ? <Spin /> : <UserTable users={users} />}</div>
|
|
48
|
+
|
|
49
|
+
// ✅ 复杂条件使用 && 或三元换行
|
|
50
|
+
return (
|
|
51
|
+
<div>
|
|
52
|
+
{error && (
|
|
53
|
+
<Alert
|
|
54
|
+
type="error"
|
|
55
|
+
message={error.message}
|
|
56
|
+
closable
|
|
57
|
+
onClose={clearError}
|
|
58
|
+
/>
|
|
59
|
+
)}
|
|
60
|
+
{!loading && !error && <UserTable users={users} />}
|
|
61
|
+
</div>
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Hook 声明
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
// ✅ Hook 声明连续排列,按逻辑分组
|
|
69
|
+
const [users, setUsers] = useState<User[]>([])
|
|
70
|
+
const [loading, setLoading] = useState(true)
|
|
71
|
+
const [search, setSearch] = useState('')
|
|
72
|
+
|
|
73
|
+
// useEffect 依赖较长时换行
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
fetchUsers(search)
|
|
76
|
+
}, [search]) // 短依赖数组行内
|
|
77
|
+
|
|
78
|
+
// ✅ 长依赖数组换行
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
fetchFilteredUsers({ search, role, status, page, size })
|
|
81
|
+
}, [
|
|
82
|
+
search,
|
|
83
|
+
role,
|
|
84
|
+
status,
|
|
85
|
+
page,
|
|
86
|
+
size,
|
|
87
|
+
])
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## 函数 Props 换行
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
// ✅ 短函数表达式行内
|
|
94
|
+
<Button onClick={() => setIsOpen(true)}>打开</Button>
|
|
95
|
+
|
|
96
|
+
// ✅ 多行函数体换行
|
|
97
|
+
<Modal
|
|
98
|
+
title="确认删除"
|
|
99
|
+
open={isOpen}
|
|
100
|
+
onOk={async () => {
|
|
101
|
+
await deleteUser(id)
|
|
102
|
+
message.success('删除成功')
|
|
103
|
+
setIsOpen(false)
|
|
104
|
+
onDeleted?.()
|
|
105
|
+
}}
|
|
106
|
+
onCancel={() => setIsOpen(false)}
|
|
107
|
+
>
|
|
108
|
+
<p>确定要删除该用户吗?</p>
|
|
109
|
+
</Modal>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## 类型定义换行
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
// ✅ Props 类型每个属性一行
|
|
116
|
+
interface UserFormProps {
|
|
117
|
+
user?: User
|
|
118
|
+
onSubmit: (values: CreateUserRequest) => Promise<void>
|
|
119
|
+
onCancel: () => void
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ✅ 联合类型换行
|
|
123
|
+
type UserStatus = 'active' | 'inactive' | 'locked' | 'pending'
|
|
124
|
+
```
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# React 组件模式示例
|
|
2
|
+
|
|
3
|
+
React 中 Pipe 的概念通过自定义 Hook、工具函数或组件组合实现。
|
|
4
|
+
|
|
5
|
+
## 自定义 Hook 替代 Pipe(数据转换)
|
|
6
|
+
|
|
7
|
+
Angular Pipe 在 React 中用自定义 Hook 或纯函数替代:
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
// ✅ 方案1: 纯函数(无状态转换)
|
|
11
|
+
function formatDate(date: string | Date, format: 'short' | 'long' = 'short'): string {
|
|
12
|
+
const d = typeof date === 'string' ? new Date(date) : date
|
|
13
|
+
if (format === 'short') {
|
|
14
|
+
return d.toLocaleDateString('zh-CN')
|
|
15
|
+
}
|
|
16
|
+
return d.toLocaleDateString('zh-CN', {
|
|
17
|
+
year: 'numeric', month: 'long', day: 'numeric',
|
|
18
|
+
hour: '2-digit', minute: '2-digit',
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function formatCurrency(amount: number, currency = 'CNY'): string {
|
|
23
|
+
return new Intl.NumberFormat('zh-CN', {
|
|
24
|
+
style: 'currency',
|
|
25
|
+
currency,
|
|
26
|
+
}).format(amount)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 使用
|
|
30
|
+
const UserList: React.FC = () => {
|
|
31
|
+
const { users } = useUsers()
|
|
32
|
+
return (
|
|
33
|
+
<Table
|
|
34
|
+
dataSource={users}
|
|
35
|
+
columns={[
|
|
36
|
+
{ title: '创建时间', dataIndex: 'createdAt', render: (v: string) => formatDate(v, 'short') },
|
|
37
|
+
{ title: '金额', dataIndex: 'amount', render: (v: number) => formatCurrency(v) },
|
|
38
|
+
]}
|
|
39
|
+
/>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ✅ 方案2: 自定义 Hook(有状态转换 — 如搜索/过滤)
|
|
44
|
+
function useUserFilter(users: User[], search: string): User[] {
|
|
45
|
+
return useMemo(() => {
|
|
46
|
+
if (!search.trim()) return users
|
|
47
|
+
const q = search.toLowerCase()
|
|
48
|
+
return users.filter(
|
|
49
|
+
u => u.username.toLowerCase().includes(q) || u.email.toLowerCase().includes(q)
|
|
50
|
+
)
|
|
51
|
+
}, [users, search])
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 高阶组件(HOC)— 权限控制
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
// hoc/withAuth.tsx
|
|
59
|
+
interface WithAuthProps {
|
|
60
|
+
requiredRole?: UserRole
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function withAuth<P extends object>(
|
|
64
|
+
Component: React.ComponentType<P>,
|
|
65
|
+
options?: WithAuthProps
|
|
66
|
+
) {
|
|
67
|
+
const AuthenticatedComponent: React.FC<P> = (props) => {
|
|
68
|
+
const { user, loading } = useAuth()
|
|
69
|
+
|
|
70
|
+
if (loading) return <Spin />
|
|
71
|
+
if (!user) return <Navigate to="/login" replace />
|
|
72
|
+
|
|
73
|
+
if (options?.requiredRole && user.role !== options.requiredRole) {
|
|
74
|
+
return <Result status="403" title="无权限访问" />
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return <Component {...props} />
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
AuthenticatedComponent.displayName = `withAuth(${Component.displayName || Component.name})`
|
|
81
|
+
return AuthenticatedComponent
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 使用
|
|
85
|
+
const AdminDashboard = withAuth(Dashboard, { requiredRole: UserRole.Admin })
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## 组件组合模式
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
// ✅ 复合组件模式(Compound Component)
|
|
92
|
+
interface CardComposition {
|
|
93
|
+
Header: React.FC<{ title: string }>
|
|
94
|
+
Body: React.FC<{ children: React.ReactNode }>
|
|
95
|
+
Footer: React.FC<{ children: React.ReactNode }>
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const Card: React.FC<{ children: React.ReactNode }> & CardComposition = ({ children }) => (
|
|
99
|
+
<Card className="rounded-lg border shadow-sm">{children}</Card>
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
Card.Header = ({ title }) => (
|
|
103
|
+
<div className="border-b px-4 py-3 font-medium">{title}</div>
|
|
104
|
+
)
|
|
105
|
+
Card.Body = ({ children }) => (
|
|
106
|
+
<div className="px-4 py-3">{children}</div>
|
|
107
|
+
)
|
|
108
|
+
Card.Footer = ({ children }) => (
|
|
109
|
+
<div className="border-t px-4 py-3 flex justify-end gap-2">{children}</div>
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
// 使用
|
|
113
|
+
<Card>
|
|
114
|
+
<Card.Header title="用户信息" />
|
|
115
|
+
<Card.Body>
|
|
116
|
+
<p>用户名: {user.username}</p>
|
|
117
|
+
<p>邮箱: {user.email}</p>
|
|
118
|
+
</Card.Body>
|
|
119
|
+
<Card.Footer>
|
|
120
|
+
<Button onClick={onCancel}>取消</Button>
|
|
121
|
+
<Button type="primary" onClick={onSave}>保存</Button>
|
|
122
|
+
</Card.Footer>
|
|
123
|
+
</Card>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Render Props 模式
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
// 数据获取 Render Props(复用数据加载逻辑)
|
|
130
|
+
interface DataLoaderProps<T> {
|
|
131
|
+
fetchFn: () => Promise<T>
|
|
132
|
+
children: (result: { data: T | null; loading: boolean; error: Error | null }) => React.ReactNode
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function DataLoader<T>({ fetchFn, children }: DataLoaderProps<T>) {
|
|
136
|
+
const [data, setData] = useState<T | null>(null)
|
|
137
|
+
const [loading, setLoading] = useState(true)
|
|
138
|
+
const [error, setError] = useState<Error | null>(null)
|
|
139
|
+
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
fetchFn()
|
|
142
|
+
.then(setData)
|
|
143
|
+
.catch(setError)
|
|
144
|
+
.finally(() => setLoading(false))
|
|
145
|
+
}, [])
|
|
146
|
+
|
|
147
|
+
return <>{children({ data, loading, error })}</>
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 使用
|
|
151
|
+
<DataLoader fetchFn={() => userService.getById(id)}>
|
|
152
|
+
{({ data: user, loading, error }) => {
|
|
153
|
+
if (loading) return <Skeleton active />
|
|
154
|
+
if (error) return <Alert type="error" message={error.message} />
|
|
155
|
+
return <UserForm user={user} />
|
|
156
|
+
}}
|
|
157
|
+
</DataLoader>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Discriminated Union 组件模式
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
// 多形态组件 - 根据 type 渲染不同 UI
|
|
164
|
+
type NotificationCardProps =
|
|
165
|
+
| { type: 'success'; message: string }
|
|
166
|
+
| { type: 'error'; message: string; errorCode?: string }
|
|
167
|
+
| { type: 'confirm'; message: string; onConfirm: () => void; onCancel: () => void }
|
|
168
|
+
|
|
169
|
+
const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
|
170
|
+
switch (props.type) {
|
|
171
|
+
case 'success':
|
|
172
|
+
return <Alert type="success" message={props.message} showIcon />
|
|
173
|
+
case 'error':
|
|
174
|
+
return (
|
|
175
|
+
<Alert
|
|
176
|
+
type="error"
|
|
177
|
+
message={props.message}
|
|
178
|
+
description={props.errorCode ? `错误码: ${props.errorCode}` : undefined}
|
|
179
|
+
showIcon
|
|
180
|
+
/>
|
|
181
|
+
)
|
|
182
|
+
case 'confirm':
|
|
183
|
+
return (
|
|
184
|
+
<Modal
|
|
185
|
+
open
|
|
186
|
+
title="确认"
|
|
187
|
+
onOk={props.onConfirm}
|
|
188
|
+
onCancel={props.onCancel}
|
|
189
|
+
>
|
|
190
|
+
<p>{props.message}</p>
|
|
191
|
+
</Modal>
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# React 实体类型定义示例
|
|
2
|
+
|
|
3
|
+
## 基础实体层次
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
// shared/types/entity.ts
|
|
7
|
+
export interface ImmutableEntity {
|
|
8
|
+
readonly id: string
|
|
9
|
+
readonly createdAt: string
|
|
10
|
+
readonly updatedAt: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface Entity extends ImmutableEntity {
|
|
14
|
+
version: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface Auditable<T> extends Entity {
|
|
18
|
+
createdBy: T
|
|
19
|
+
updatedBy: T
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ImmutableAuditable<T> extends ImmutableEntity {
|
|
23
|
+
createdBy: T
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## API 响应类型
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
// shared/types/api.ts
|
|
31
|
+
export interface PageResponse<T> {
|
|
32
|
+
content: T[]
|
|
33
|
+
totalElements: number
|
|
34
|
+
totalPages: number
|
|
35
|
+
number: number
|
|
36
|
+
size: number
|
|
37
|
+
first: boolean
|
|
38
|
+
last: boolean
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ApiError {
|
|
42
|
+
code: string
|
|
43
|
+
message: string
|
|
44
|
+
details?: Record<string, string[]>
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 业务实体示例
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
// shared/types/user.ts
|
|
52
|
+
export interface User extends Auditable<string> {
|
|
53
|
+
username: string
|
|
54
|
+
email: string
|
|
55
|
+
displayName: string
|
|
56
|
+
avatar?: string
|
|
57
|
+
role: UserRole
|
|
58
|
+
active: boolean
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export enum UserRole {
|
|
62
|
+
Admin = 'ADMIN',
|
|
63
|
+
Editor = 'EDITOR',
|
|
64
|
+
Viewer = 'VIEWER',
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface CreateUserRequest {
|
|
68
|
+
username: string
|
|
69
|
+
email: string
|
|
70
|
+
displayName: string
|
|
71
|
+
role: UserRole
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface UpdateUserRequest {
|
|
75
|
+
email?: string
|
|
76
|
+
displayName?: string
|
|
77
|
+
role?: UserRole
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## 组件 Props 类型
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
// 通用组件:具名导出 + interface Props
|
|
85
|
+
export interface UserAvatarProps {
|
|
86
|
+
user: Pick<User, 'username' | 'avatar'>
|
|
87
|
+
size?: 'sm' | 'md' | 'lg'
|
|
88
|
+
onClick?: () => void
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const UserAvatar: React.FC<UserAvatarProps> = ({ user, size = 'md', onClick }) => {
|
|
92
|
+
return (
|
|
93
|
+
<Avatar
|
|
94
|
+
src={user.avatar}
|
|
95
|
+
alt={user.username}
|
|
96
|
+
size={size === 'sm' ? 24 : size === 'md' ? 32 : 48}
|
|
97
|
+
onClick={onClick}
|
|
98
|
+
style={{ cursor: onClick ? 'pointer' : 'default' }}
|
|
99
|
+
/>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 页面组件:默认导出 + Props 从路由参数推断
|
|
104
|
+
export interface UserListPageProps {
|
|
105
|
+
// 可由 React Router loader 数据注入
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const UserListPage: React.FC<UserListPageProps> = () => {
|
|
109
|
+
const { users, loading } = useUsers()
|
|
110
|
+
return <UserTable users={users} loading={loading} />
|
|
111
|
+
}
|
|
112
|
+
export default UserListPage
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Discriminated Union 类型
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
// 用于多状态组件
|
|
119
|
+
export type NotificationType = 'success' | 'error' | 'warning' | 'info'
|
|
120
|
+
|
|
121
|
+
export interface NotificationConfig {
|
|
122
|
+
type: NotificationType
|
|
123
|
+
title: string
|
|
124
|
+
message: string
|
|
125
|
+
duration?: number
|
|
126
|
+
}
|
|
127
|
+
```
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# React 表单模式(Ant Design Form)
|
|
2
|
+
|
|
3
|
+
## 基础表单
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
interface CreateUserFormValues {
|
|
7
|
+
username: string
|
|
8
|
+
email: string
|
|
9
|
+
displayName: string
|
|
10
|
+
role: UserRole
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const CreateUserForm: React.FC = () => {
|
|
14
|
+
const [form] = Form.useForm<CreateUserFormValues>()
|
|
15
|
+
const [loading, setLoading] = useState(false)
|
|
16
|
+
|
|
17
|
+
const handleSubmit = async (values: CreateUserFormValues) => {
|
|
18
|
+
setLoading(true)
|
|
19
|
+
try {
|
|
20
|
+
await createUser(values)
|
|
21
|
+
message.success('用户创建成功')
|
|
22
|
+
form.resetFields()
|
|
23
|
+
} catch (err) {
|
|
24
|
+
// Ant Design Form 会自动捕获校验错误
|
|
25
|
+
message.error('创建失败,请重试')
|
|
26
|
+
} finally {
|
|
27
|
+
setLoading(false)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Form
|
|
33
|
+
form={form}
|
|
34
|
+
layout="vertical"
|
|
35
|
+
onFinish={handleSubmit}
|
|
36
|
+
autoComplete="off"
|
|
37
|
+
>
|
|
38
|
+
<Form.Item
|
|
39
|
+
name="username"
|
|
40
|
+
label="用户名"
|
|
41
|
+
rules={[
|
|
42
|
+
{ required: true, message: '请输入用户名' },
|
|
43
|
+
{ min: 3, max: 32, message: '用户名长度 3-32 个字符' },
|
|
44
|
+
]}
|
|
45
|
+
>
|
|
46
|
+
<Input placeholder="请输入用户名" />
|
|
47
|
+
</Form.Item>
|
|
48
|
+
|
|
49
|
+
<Form.Item
|
|
50
|
+
name="email"
|
|
51
|
+
label="邮箱"
|
|
52
|
+
rules={[
|
|
53
|
+
{ required: true, message: '请输入邮箱' },
|
|
54
|
+
{ type: 'email', message: '请输入有效的邮箱地址' },
|
|
55
|
+
]}
|
|
56
|
+
>
|
|
57
|
+
<Input placeholder="请输入邮箱" />
|
|
58
|
+
</Form.Item>
|
|
59
|
+
|
|
60
|
+
<Form.Item
|
|
61
|
+
name="displayName"
|
|
62
|
+
label="显示名称"
|
|
63
|
+
rules={[{ required: true, message: '请输入显示名称' }]}
|
|
64
|
+
>
|
|
65
|
+
<Input placeholder="请输入显示名称" />
|
|
66
|
+
</Form.Item>
|
|
67
|
+
|
|
68
|
+
<Form.Item
|
|
69
|
+
name="role"
|
|
70
|
+
label="角色"
|
|
71
|
+
rules={[{ required: true, message: '请选择角色' }]}
|
|
72
|
+
>
|
|
73
|
+
<Select placeholder="请选择角色">
|
|
74
|
+
<Select.Option value="ADMIN">管理员</Select.Option>
|
|
75
|
+
<Select.Option value="EDITOR">编辑</Select.Option>
|
|
76
|
+
<Select.Option value="VIEWER">查看者</Select.Option>
|
|
77
|
+
</Select>
|
|
78
|
+
</Form.Item>
|
|
79
|
+
|
|
80
|
+
<Form.Item>
|
|
81
|
+
<Button type="primary" htmlType="submit" loading={loading}>
|
|
82
|
+
创建
|
|
83
|
+
</Button>
|
|
84
|
+
</Form.Item>
|
|
85
|
+
</Form>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## 编辑表单(初始值)
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
interface EditUserFormProps {
|
|
94
|
+
user: User
|
|
95
|
+
onSuccess: () => void
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const EditUserForm: React.FC<EditUserFormProps> = ({ user, onSuccess }) => {
|
|
99
|
+
const [form] = Form.useForm()
|
|
100
|
+
const [loading, setLoading] = useState(false)
|
|
101
|
+
|
|
102
|
+
// 初始值填充
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
form.setFieldsValue({
|
|
105
|
+
displayName: user.displayName,
|
|
106
|
+
email: user.email,
|
|
107
|
+
role: user.role,
|
|
108
|
+
})
|
|
109
|
+
}, [user, form])
|
|
110
|
+
|
|
111
|
+
const handleSubmit = async (values: Partial<CreateUserRequest>) => {
|
|
112
|
+
setLoading(true)
|
|
113
|
+
try {
|
|
114
|
+
await updateUser(user.id, values)
|
|
115
|
+
message.success('用户信息已更新')
|
|
116
|
+
onSuccess()
|
|
117
|
+
} catch {
|
|
118
|
+
message.error('更新失败')
|
|
119
|
+
} finally {
|
|
120
|
+
setLoading(false)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<Form
|
|
126
|
+
form={form}
|
|
127
|
+
layout="vertical"
|
|
128
|
+
onFinish={handleSubmit}
|
|
129
|
+
>
|
|
130
|
+
{/* 用户名只读展示 */}
|
|
131
|
+
<Form.Item label="用户名">
|
|
132
|
+
<Input value={user.username} disabled />
|
|
133
|
+
</Form.Item>
|
|
134
|
+
|
|
135
|
+
<Form.Item
|
|
136
|
+
name="email"
|
|
137
|
+
label="邮箱"
|
|
138
|
+
rules={[{ type: 'email' }]}
|
|
139
|
+
>
|
|
140
|
+
<Input />
|
|
141
|
+
</Form.Item>
|
|
142
|
+
|
|
143
|
+
<Form.Item
|
|
144
|
+
name="displayName"
|
|
145
|
+
label="显示名称"
|
|
146
|
+
>
|
|
147
|
+
<Input />
|
|
148
|
+
</Form.Item>
|
|
149
|
+
|
|
150
|
+
<Form.Item
|
|
151
|
+
name="role"
|
|
152
|
+
label="角色"
|
|
153
|
+
>
|
|
154
|
+
<Select>
|
|
155
|
+
<Select.Option value="ADMIN">管理员</Select.Option>
|
|
156
|
+
<Select.Option value="EDITOR">编辑</Select.Option>
|
|
157
|
+
<Select.Option value="VIEWER">查看者</Select.Option>
|
|
158
|
+
</Select>
|
|
159
|
+
</Form.Item>
|
|
160
|
+
|
|
161
|
+
<Form.Item>
|
|
162
|
+
<Button type="primary" htmlType="submit" loading={loading}>
|
|
163
|
+
保存
|
|
164
|
+
</Button>
|
|
165
|
+
</Form.Item>
|
|
166
|
+
</Form>
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## 搜索表单(非受控 + 联动 Table)
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
const UserListPage: React.FC = () => {
|
|
175
|
+
const [searchForm] = Form.useForm()
|
|
176
|
+
const [query, setQuery] = useState<Record<string, string>>({})
|
|
177
|
+
|
|
178
|
+
const handleSearch = (values: Record<string, string>) => {
|
|
179
|
+
// 过滤空值
|
|
180
|
+
const filtered = Object.fromEntries(
|
|
181
|
+
Object.entries(values).filter(([, v]) => v !== undefined && v !== '')
|
|
182
|
+
)
|
|
183
|
+
setQuery(filtered)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const handleReset = () => {
|
|
187
|
+
searchForm.resetFields()
|
|
188
|
+
setQuery({})
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<div>
|
|
193
|
+
<Form
|
|
194
|
+
form={searchForm}
|
|
195
|
+
layout="inline"
|
|
196
|
+
onFinish={handleSearch}
|
|
197
|
+
>
|
|
198
|
+
<Form.Item name="keyword">
|
|
199
|
+
<Input.Search placeholder="搜索用户名/邮箱" onSearch={() => searchForm.submit()} />
|
|
200
|
+
</Form.Item>
|
|
201
|
+
<Form.Item name="role">
|
|
202
|
+
<Select placeholder="角色" allowClear style={{ width: 120 }}>
|
|
203
|
+
<Select.Option value="ADMIN">管理员</Select.Option>
|
|
204
|
+
<Select.Option value="EDITOR">编辑</Select.Option>
|
|
205
|
+
</Select>
|
|
206
|
+
</Form.Item>
|
|
207
|
+
<Form.Item name="status">
|
|
208
|
+
<Select placeholder="状态" allowClear style={{ width: 120 }}>
|
|
209
|
+
<Select.Option value="active">活跃</Select.Option>
|
|
210
|
+
<Select.Option value="inactive">停用</Select.Option>
|
|
211
|
+
</Select>
|
|
212
|
+
</Form.Item>
|
|
213
|
+
<Form.Item>
|
|
214
|
+
<Button type="primary" htmlType="submit">查询</Button>
|
|
215
|
+
<Button onClick={handleReset} style={{ marginLeft: 8 }}>重置</Button>
|
|
216
|
+
</Form.Item>
|
|
217
|
+
</Form>
|
|
218
|
+
|
|
219
|
+
<UserTable query={query} />
|
|
220
|
+
</div>
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## 全局校验信息配置
|
|
226
|
+
|
|
227
|
+
```tsx
|
|
228
|
+
// 在应用入口统一配置校验信息
|
|
229
|
+
import { ConfigProvider } from 'antd'
|
|
230
|
+
|
|
231
|
+
const App: React.FC = () => (
|
|
232
|
+
<ConfigProvider
|
|
233
|
+
form={{
|
|
234
|
+
validateMessages: {
|
|
235
|
+
required: "'${label}' 是必填项",
|
|
236
|
+
types: {
|
|
237
|
+
email: "'${label}' 不是有效的邮箱地址",
|
|
238
|
+
number: "'${label}' 不是有效的数字",
|
|
239
|
+
},
|
|
240
|
+
string: {
|
|
241
|
+
min: "'${label}' 最少 ${min} 个字符",
|
|
242
|
+
max: "'${label}' 最多 ${max} 个字符",
|
|
243
|
+
range: "'${label}' 长度在 ${min} 到 ${max} 之间",
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
}}
|
|
247
|
+
>
|
|
248
|
+
{/* Router 和页面内容 */}
|
|
249
|
+
</ConfigProvider>
|
|
250
|
+
)
|
|
251
|
+
```
|