@lark-apaas/coding-steering 0.1.6-alpha.1 → 0.1.6-alpha.10

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.
Files changed (39) hide show
  1. package/README.md +11 -2
  2. package/package.json +1 -1
  3. package/steering/design-stack/skills/.gitkeep +0 -0
  4. package/steering/nestjs-react-fullstack/skills/authn-guide/SKILL.md +122 -0
  5. package/steering/nestjs-react-fullstack/skills/authz-guide/SKILL.md +174 -0
  6. package/steering/nestjs-react-fullstack/skills/authz-guide/references/dynamic-permission-guide.md +621 -0
  7. package/steering/nestjs-react-fullstack/skills/authz-guide/references/management-page-spec.md +505 -0
  8. package/steering/nestjs-react-fullstack/skills/authz-guide/references/runtime-role-controller-spec.md +203 -0
  9. package/steering/nestjs-react-fullstack/skills/authz-guide/references/sdk-examples.md +90 -0
  10. package/steering/nestjs-react-fullstack/skills/authz-guide/references/sdk-types.md +216 -0
  11. package/steering/nestjs-react-fullstack/skills/client-add-aily-web-chat/SKILL.md +139 -0
  12. package/steering/nestjs-react-fullstack/skills/client-builtins-file-storage-service/SKILL.md +405 -0
  13. package/steering/nestjs-react-fullstack/skills/client-builtins-user-service/SKILL.md +628 -0
  14. package/steering/nestjs-react-fullstack/skills/devops-guide/SKILL.md +119 -0
  15. package/steering/nestjs-react-fullstack/skills/feishu/SKILL.md +270 -0
  16. package/steering/nestjs-react-fullstack/skills/feishu/references/approval.md +214 -0
  17. package/steering/nestjs-react-fullstack/skills/feishu/references/attendance.md +163 -0
  18. package/steering/nestjs-react-fullstack/skills/feishu/references/bitable.md +309 -0
  19. package/steering/nestjs-react-fullstack/skills/feishu/references/calendar.md +190 -0
  20. package/steering/nestjs-react-fullstack/skills/feishu/references/contacts.md +160 -0
  21. package/steering/nestjs-react-fullstack/skills/feishu/references/doc.md +256 -0
  22. package/steering/nestjs-react-fullstack/skills/feishu/references/drive.md +103 -0
  23. package/steering/nestjs-react-fullstack/skills/feishu/references/events.md +198 -0
  24. package/steering/nestjs-react-fullstack/skills/feishu/references/id-convert.md +128 -0
  25. package/steering/nestjs-react-fullstack/skills/feishu/references/messaging.md +207 -0
  26. package/steering/nestjs-react-fullstack/skills/feishu/references/oauth.md +164 -0
  27. package/steering/nestjs-react-fullstack/skills/feishu/references/perm.md +90 -0
  28. package/steering/nestjs-react-fullstack/skills/feishu/references/wiki.md +164 -0
  29. package/steering/nestjs-react-fullstack/skills/openapi-guide/SKILL.md +267 -0
  30. package/steering/nestjs-react-fullstack/skills/plugin-guide/SKILL.md +582 -0
  31. package/steering/nestjs-react-fullstack/skills/plugin-guide/references/plugin-coding-guide.md +357 -0
  32. package/steering/nestjs-react-fullstack/skills/plugin-guide/references/table.md +513 -0
  33. package/steering/nestjs-react-fullstack/skills/react-hook-best-practices/SKILL.md +118 -0
  34. package/steering/nestjs-react-fullstack/skills/server-builtins-file-storage-service/SKILL.md +177 -0
  35. package/steering/nestjs-react-fullstack/skills/trigger-guide/SKILL.md +452 -0
  36. package/steering/nestjs-react-fullstack/skills/user-identity/SKILL.md +300 -0
  37. package/steering/nestjs-react-fullstack/skills/user-management-best-practices/SKILL.md +142 -0
  38. package/steering/nestjs-react-fullstack/skills_local/code-fix/SKILL.md +232 -0
  39. package/steering/nestjs-react-fullstack/skills_local/coding-guide/SKILL.md +585 -0
@@ -0,0 +1,628 @@
1
+ ---
2
+ name: client-builtins-user-service
3
+ description: 前端用户鉴权服务与组件指南,提供 Dataloom SDK 用户认证 API 和 React 用户组件。Use when 需要:(1) 实现登录/登出功能,(2) 获取当前用户信息或展示用户头像姓名,(3) 使用用户选择器或部门选择器组件,(4) 处理 401 未授权错误,或其他用户身份认证相关开发
4
+ steering: true
5
+ steering-topic: client_builtins_user_service
6
+ match-template-name: nestjs-react-fullstack
7
+ ---
8
+
9
+
10
+ # Dataloom 用户信息与鉴权 SDK
11
+
12
+ ## 概述
13
+ 项目提供了 Dataloom SDK 的用户信息与鉴权服务,用于用户登录、登出、获取用户信息等身份认证相关功能。
14
+
15
+ > **边界说明**:`dataloom.service.session` 仅用于用户登录/登出/获取用户信息等鉴权操作。**插件调用(capability)不属于 dataloom**,须使用独立的 `capabilityClient`(参见 plugin-guide)。
16
+
17
+ ## 统一响应结构
18
+
19
+ ### 响应类型定义
20
+ ```typescript
21
+ /**
22
+ * 统一结果返回结构
23
+ * 在非浏览器环境调用时返回示例:
24
+ * {
25
+ * data: null,
26
+ * error: {
27
+ * code: 400,
28
+ * message: 'Incompatible runtime environment',
29
+ * hint: 'Please check if the current environment is browser.',
30
+ * details: 'This method can only be invoked in browser environment.',
31
+ * },
32
+ * status: 400,
33
+ * statusText: 'Bad Request',
34
+ * }
35
+ */
36
+ interface DataloomServiceBase {
37
+ status: number;
38
+ statusText: string;
39
+ }
40
+
41
+ interface DataloomServiceSuccess<T> extends DataloomServiceBase {
42
+ error: null;
43
+ data: T;
44
+ }
45
+
46
+ interface DataloomServiceFailure extends DataloomServiceBase {
47
+ error: {
48
+ code: number;
49
+ details: string;
50
+ hint: string | null;
51
+ message: string;
52
+ };
53
+ data: null;
54
+ }
55
+
56
+ type DataloomServiceResponse<T> = DataloomServiceSuccess<T> | DataloomServiceFailure;
57
+ ```
58
+
59
+ ## Dataloom实例获取方式
60
+ ```typescript
61
+ import { getDataloom } from "@lark-apaas/client-toolkit/dataloom";
62
+
63
+ // 异步获取dataloom实例
64
+ const dataloom = await getDataloom();
65
+ ```
66
+
67
+ ## 鉴权服务接口
68
+
69
+ #### 1. 登录跳转接口 (redirectToLogin)
70
+
71
+ ##### 用途
72
+ 跳转至 Dataloom 登录页面,适用于:用户身份认证、单点登录、权限验证
73
+
74
+ ##### 适用场景
75
+ 需要用户进行身份认证的场景
76
+
77
+ ##### 入参说明
78
+ ```typescript
79
+ interface SignInRedirectionOptions {
80
+ /**
81
+ * 选填,登录成功后跳转回的页面。省略会默认用调用接口时页面的url。
82
+ */
83
+ returnUrl?: string;
84
+ /**
85
+ * 选填,是否在新浏览器tab上打开登录页。默认为false
86
+ */
87
+ newTab?: boolean;
88
+ }
89
+ ```
90
+
91
+ | 属性名 | 类型 | 必填 | 默认值 | 说明 |
92
+ |--------|------|------|--------|---------|
93
+ | `returnUrl` | `string` | ❌ | 当前页面URL | 登录成功后跳转回的页面 |
94
+ | `newTab` | `boolean` | ❌ | `false` | 是否在新浏览器标签页打开登录页 |
95
+
96
+ ##### 出参说明
97
+ | 字段名 | 类型 | 说明 |
98
+ |--------|------|---------|
99
+ | `data` | `'success'` | 成功标识 |
100
+ | `error` | `null` | 错误信息,成功时为null |
101
+ | `status` | `200` | HTTP状态码 |
102
+ | `statusText` | `'OK'` | HTTP状态文本 |
103
+
104
+ ##### 使用示例
105
+ ```typescript
106
+ /**
107
+ * 跳转至dataloom登录页
108
+ * @param {string} brand - 品牌id 例如:妙搭为 1.
109
+ * @param {string} appId - 运行态应用的id,由dataloom authn 服务下发。
110
+ * @return 成功返回示例:
111
+ * {
112
+ * data: 'success',
113
+ * error: null,
114
+ * status: 200,
115
+ * statusText: 'OK',
116
+ * }
117
+ */
118
+ const res: DataloomServiceResponse<'success'> = dataloom
119
+ .service
120
+ .session
121
+ .redirectToLogin(options: SignInRedirectionOptions);
122
+
123
+ // 基本使用
124
+ const loginResult = dataloom
125
+ .service
126
+ .session
127
+ .redirectToLogin();
128
+
129
+ // 带参数使用
130
+ const loginResult = dataloom
131
+ .service
132
+ .session
133
+ .redirectToLogin({
134
+ returnUrl: 'https://example.com/dashboard',
135
+ newTab: true
136
+ });
137
+ ```
138
+
139
+ #### 2. 退出登录接口 (signOut)
140
+
141
+ ##### 用途
142
+ 退出登录,删除cookie中的登录态,适用于:用户主动登出、会话清理、安全退出
143
+
144
+ ##### 适用场景
145
+ 需要清除用户登录状态的场景
146
+
147
+ ##### 入参说明
148
+ 无需传入参数
149
+
150
+ ##### 出参说明
151
+ | 字段名 | 类型 | 说明 |
152
+ |--------|------|---------|
153
+ | `data` | `null` | 数据为空 |
154
+ | `error` | `null` | 错误信息,成功时为null |
155
+ | `status` | `200` | HTTP状态码 |
156
+ | `statusText` | `'OK'` | HTTP状态文本 |
157
+
158
+ ##### 使用示例
159
+ ```typescript
160
+ /**
161
+ * 退出登录,删除cookie中的登陆态
162
+ * @return 成功返回示例:
163
+ * {
164
+ * data: null,
165
+ * error: null,
166
+ * status: 200,
167
+ * statusText: 'OK',
168
+ * }
169
+ */
170
+ const res: Promise<DataloomServiceResponse<null>> = await dataloom
171
+ .service
172
+ .session
173
+ .signOut();
174
+
175
+ // 使用示例
176
+ import { logger } from "@lark-apaas/client-toolkit/logger";
177
+
178
+ try {
179
+ const result = await dataloom
180
+ .service
181
+ .session
182
+ .signOut();
183
+
184
+ if (result.error) {
185
+ logger.error('退出登录失败:', result.error.message);
186
+ } else {
187
+ logger.info('退出登录成功');
188
+ // 跳转到登录页或首页
189
+ dataloom.service.session.redirectToLogin();
190
+ }
191
+ } catch (error) {
192
+ logger.error('退出登录异常:', error);
193
+ }
194
+ ```
195
+
196
+ #### 3. 跳转用户详情页接口 (navigateToUserProfile)
197
+
198
+ ##### 用途
199
+ 跳转至当前登录用户的详情页,适用于:查看个人资料、从用户头像/姓名点击进入详情页面
200
+
201
+ ##### 适用场景
202
+ 需要在应用中快速打开用户详情页面的场景
203
+
204
+ ##### 入参说明
205
+ ```typescript
206
+ interface NavigateToUserProfileOptions {
207
+ /**
208
+ * 选填,是否在新浏览器tab上打开详情页。默认为false
209
+ */
210
+ newTab?: boolean;
211
+ }
212
+ ```
213
+
214
+ | 属性名 | 类型 | 必填 | 默认值 | 说明 |
215
+ |--------|------|------|--------|---------|
216
+ | `newTab` | `boolean` | ❌ | `false` | 是否在新浏览器标签页打开详情页 |
217
+
218
+ ##### 出参说明
219
+ | 字段名 | 类型 | 说明 |
220
+ |--------|------|---------|
221
+ | `data` | `'success'` | 成功标识 |
222
+ | `error` | `null` | 错误信息,成功时为null |
223
+ | `status` | `200` | HTTP状态码 |
224
+ | `statusText` | `'OK'` | HTTP状态文本 |
225
+
226
+ ##### 使用示例
227
+ ```typescript
228
+ /**
229
+ * 跳转至用户详情页
230
+ * @return 成功返回示例:
231
+ * {
232
+ * data: 'success',
233
+ * error: null,
234
+ * status: 200,
235
+ * statusText: 'OK',
236
+ * }
237
+ */
238
+ const res: DataloomServiceResponse<'success'> = dataloom
239
+ .service
240
+ .session
241
+ .navigateToUserProfile();
242
+
243
+ // 在新标签页打开
244
+ const resNewTab = dataloom
245
+ .service
246
+ .session
247
+ .navigateToUserProfile({ newTab: true });
248
+ ```
249
+
250
+ ## 用户信息服务
251
+
252
+ #### 4. 获取用户信息接口 (getUserInfo)
253
+
254
+ ##### 用途
255
+ 根据当前登录态获取已登录的用户信息,适用于:用户资料展示、权限判断、个性化配置
256
+
257
+ ##### 适用场景
258
+ 需要获取当前登录用户详细信息的场景
259
+
260
+ ##### 数据类型定义
261
+ ```typescript
262
+ interface I18n {
263
+ language_code: number;
264
+ text: string;
265
+ }
266
+
267
+ type I18ns = I18n[];
268
+
269
+ interface Avatar {
270
+ source?: string;
271
+ image?: {
272
+ large?: string; // 图片url, 可能不返回
273
+ };
274
+ color?: string; // 颜色,可能不返回。
275
+ }
276
+
277
+ interface UserBaseInfo {
278
+ user_id?: number;
279
+ name?: I18ns;
280
+ avatar?: Avatar;
281
+ email?: string;
282
+ phone_number?: string;
283
+ tenant_name?: string;
284
+ }
285
+
286
+ interface UserInfoResponse {
287
+ user_info?: UserBaseInfo;
288
+ }
289
+ ```
290
+
291
+ ##### 入参说明
292
+ 无需传入参数
293
+
294
+ ##### 出参说明
295
+ | 字段名 | 类型 | 说明 |
296
+ |--------|------|---------|
297
+ | `data.user_info` | `UserBaseInfo` | 用户基本信息对象 |
298
+ | `data.user_info.user_id` | `number` | 用户唯一标识符 |
299
+ | `data.user_info.name` | `I18ns` | 用户名称(支持多语言) |
300
+ | `data.user_info.avatar` | `Avatar` | 用户头像信息 |
301
+ | `data.user_info.email` | `string` | 用户邮箱地址 |
302
+ | `data.user_info.phone_number` | `string` | 用户手机号码 |
303
+ | `data.user_info.tenant_name` | `string` | 租户名称 |
304
+ | `error` | `null` | 错误信息,成功时为null |
305
+
306
+ ##### 使用示例
307
+ ```typescript
308
+ /**
309
+ * 根据当前登陆态获取已登录的用户信息。
310
+ * @param {string} brand - 品牌id 例如:妙搭为 1.
311
+ * @param {string} appId - 运行态应用的id,由dataloom authn 服务下发。
312
+ * @return
313
+ */
314
+ const res: Promise<DataloomServiceResponse<UserInfoResponse>> = await dataloom
315
+ .service
316
+ .session
317
+ .getUserInfo();
318
+
319
+ // 使用示例
320
+ import { logger } from "@lark-apaas/client-toolkit/logger";
321
+
322
+ try {
323
+ const result = await dataloom
324
+ .service
325
+ .session
326
+ .getUserInfo();
327
+
328
+ if (result.error) {
329
+ logger.error('获取用户信息失败:', result.error.message);
330
+ // 可能需要重新登录
331
+ if (result.status === 401) {
332
+ // 跳转到登录页
333
+ dataloom.service.session.redirectToLogin();
334
+ }
335
+ } else if (result.data?.user_info) {
336
+ const userInfo = result.data.user_info;
337
+ logger.info('用户信息:', userInfo);
338
+
339
+ // 显示用户名
340
+ const userName = userInfo.name?.[0]?.text || '未知用户';
341
+ document.getElementById('username').textContent = userName;
342
+
343
+ // 显示用户头像
344
+ const avatarUrl = userInfo.avatar?.image?.large;
345
+ if (avatarUrl) {
346
+ document.getElementById('avatar').src = avatarUrl;
347
+ }
348
+
349
+ // 显示用户邮箱
350
+ if (userInfo.email) {
351
+ document.getElementById('email').textContent = userInfo.email;
352
+ }
353
+ }
354
+ } catch (error) {
355
+ logger.error('获取用户信息异常:', error);
356
+ }
357
+ ```
358
+
359
+ ## 错误处理
360
+
361
+ ### 常见错误类型
362
+ | 错误码 | 说明 | 处理建议 |
363
+ |--------|------|---------|
364
+ | `400` | 请求参数错误 | 检查传入参数是否正确 |
365
+ | `401` | 未授权访问 | 需要重新登录 |
366
+ | `403` | 权限不足 | 联系管理员分配权限 |
367
+ | `404` | 资源不存在 | 检查请求的资源是否存在 |
368
+ | `500` | 服务器内部错误 | 稍后重试或联系技术支持 |
369
+
370
+ ### 错误处理最佳实践
371
+ ```typescript
372
+ // 统一错误处理函数
373
+ function handleDataloomError(response: DataloomServiceResponse<any>) {
374
+ if (response.error) {
375
+ switch (response.status) {
376
+ case 401:
377
+ // 未授权,跳转登录
378
+ dataloom.service.session.redirectToLogin();
379
+ break;
380
+ case 403:
381
+ // 权限不足
382
+ alert('权限不足,请联系管理员');
383
+ break;
384
+ case 500:
385
+ // 服务器错误
386
+ alert('服务器错误,请稍后重试');
387
+ break;
388
+ default:
389
+ alert(`操作失败: ${response.error.message}`);
390
+ }
391
+ return false;
392
+ }
393
+ return true;
394
+ }
395
+
396
+ // 使用示例
397
+ import { logger } from "@lark-apaas/client-toolkit/logger";
398
+
399
+ const userInfoResult = await dataloom.service.session.getUserInfo();
400
+ if (handleDataloomError(userInfoResult)) {
401
+ // 处理成功逻辑
402
+ logger.info('用户信息:', userInfoResult.data);
403
+ }
404
+ ```
405
+
406
+ ## 注意事项
407
+
408
+ 1. **环境限制**:部分接口只能在浏览器环境中使用,服务端调用会返回环境不兼容错误
409
+ 2. **登录状态**:获取用户信息前需要确保用户已登录,否则会返回401错误
410
+ 3. **跨域配置**:确保应用域名已在 Dataloom 后台配置白名单
411
+ 4. **安全性**:不要在客户端代码中暴露敏感的配置信息
412
+ 5. **错误处理**:建议对所有接口调用进行统一的错误处理
413
+
414
+ # 用户系统前端相关规范
415
+
416
+ ## 概述
417
+ 内置的用户前端组件规范,提供了 UserSelect(支持单选/多选的用户选择器)和 UserDisplay(用户信息展示组件)两个核心 React 组件,基于统一的userid数据,并且用户选择组件中会通过onchange返回User类型数据,专门用于处理用户相关的表单输入和数据展示场景。
418
+
419
+ 同时提供 DepartmentSelect(部门选择组件),用于部门字段的单选/多选选择,交互与受控规范与 UserSelect 保持一致。
420
+ ## 类型定义
421
+
422
+ ### 用户数据类型
423
+ ```typescript
424
+ import type { User } from '@/types/common';
425
+ ```
426
+
427
+ `User` 接口包含用户的基本信息,如用户 ID、姓名、头像字段。
428
+
429
+ ```typescript
430
+ export type User = {
431
+ user_id: string;
432
+ name: string;
433
+ avatar: string;
434
+ };
435
+ ```
436
+
437
+ ## 当前用户信息的获取方案
438
+
439
+ ### 常用场景
440
+ - **用户信息展示**:在界面中显示当前用户名称和头像
441
+ - **权限验证**:基于用户ID进行权限检查和控制
442
+ - **数据关联**:在数据操作时关联当前用户信息
443
+ - **日志记录**:记录用户操作日志时获取用户标识
444
+
445
+ ### Hooks 方法: `useCurrentUserProfile` - 在 React 中获取当前用户信息
446
+
447
+ ### 基本信息
448
+ - **文件路径**:`@lark-apaas/client-toolkit/hooks/useCurrentUserProfile`
449
+ - **功能**:获取当前登录用户的个人信息(含飞书 user_id)
450
+ - **返回值**:`Partial<IUserProfile>`(初始为空对象 `{}`,异步获取后填充完整字段)
451
+
452
+ **IUserProfile 字段**:
453
+
454
+ | 字段 | 类型 | 说明 |
455
+ |------|------|------|
456
+ | `user_id` | `string` | 妙搭用户 ID |
457
+ | `email` | `string` | 用户邮箱 |
458
+ | `name` | `string` | 用户名称 |
459
+ | `avatar` | `string` | 用户头像 URL |
460
+ | `lark_user_id` | `string` | 飞书 user_id,通过额外异步请求获取,可能晚于其他字段就绪 |
461
+
462
+ > ⚠️ **空值处理(CRITICAL)**:该 Hook 初始返回空对象 `{}`(truthy),所有字段均为 `undefined`。
463
+ > - **MUST** 使用可选链访问:`userInfo?.name`、`userInfo?.user_id`
464
+ > - **MUST** 判断加载状态用 `if (!userInfo?.user_id)` 而非 `if (!userInfo)`(空对象是 truthy)
465
+ > - **MUST** 渲染时提供默认值:`userInfo?.name ?? '加载中...'`
466
+ > - **MUST** `lark_user_id` 通过额外异步请求获取,可能为 `undefined`(加载中/获取失败/无飞书账号),使用时必须条件渲染
467
+
468
+ ### 使用方法
469
+ ```typescript
470
+ import { useCurrentUserProfile } from "@lark-apaas/client-toolkit/hooks/useCurrentUserProfile";
471
+
472
+ const MyComponent = () => {
473
+ const userInfo = useCurrentUserProfile();
474
+
475
+ // 正确:安全访问 + 加载态处理
476
+ if (!userInfo?.user_id) return <div>加载中...</div>;
477
+ return (
478
+ <div>
479
+ <p>{userInfo.name}</p>
480
+ {userInfo.lark_user_id && <p>飞书 ID: {userInfo.lark_user_id}</p>}
481
+ </div>
482
+ );
483
+ };
484
+ ```
485
+
486
+ > **飞书 ID 转换详细指南**(后端 `AuthNPaasService` 用法、自定义转换接口等)参见 `user-identity` skill。
487
+
488
+ ## 用户展示与选择方案
489
+ {% if projectMeta['flags']['supportBusinessUser'] %}
490
+ 这些组件在使用之前必须读取 client/src/components/business-ui/README.md文件来理解用法
491
+
492
+ 目前可用的组件有:
493
+ - UserSelect 用户选择组件(@/components/business-ui/user-select)
494
+ - DepartmentSelect 部门选择组件(@/components/business-ui/department-select)
495
+ - UserDisplay - 用户展示组件(@/components/business-ui/user-display)
496
+
497
+ {% else %}
498
+ ##
499
+
500
+ ### 基本信息
501
+ - **组件路径**:`@lark-apaas/client-toolkit/components/User`
502
+ - **功能**:用于所有用户字段的表单输入
503
+ - **特性**:支持单选/多选模式,内置搜索功能
504
+
505
+ ### 组件 Props 定义
506
+ ```typescript
507
+ interface UserSelectProps {
508
+ mode: 'single' | 'multiple'; // 选择模式
509
+ value?: string | string[]; // 当前userid的值
510
+ onChange?: (value: IUserProfile | IUserProfile[]) => void; // userid值变化回调
511
+ placeholder?: string; // 占位符文本
512
+ disabled?: boolean; // 是否禁用
513
+ }
514
+ ```
515
+
516
+ ### 值类型说明
517
+ - **单选模式** (`mode="single"`):值为 userid,返回为`IUserProfile` 对象
518
+ - **多选模式** (`mode="multiple"`):值为 userid数组,返回为`IUserProfile` 数组
519
+
520
+ ### 使用示例
521
+
522
+ #### 表单集成
523
+ ```typescript
524
+ import { useForm } from "react-hook-form";
525
+ import { zodResolver } from "@hookform/resolvers/zod";
526
+ import * as z from "zod";
527
+ import { Form, FormControl, FormField, FormItem, FormLabel } from "@/components/ui/form";
528
+ import { UserSelect } from "@lark-apaas/client-toolkit/components/User";
529
+
530
+ // 定义表单验证schema
531
+ const formSchema = z.object({
532
+ assignee: z.string(),
533
+ participants: z.array(z.string()).optional(),
534
+ });
535
+
536
+ const form = useForm<z.infer<typeof formSchema>>({
537
+ resolver: zodResolver(formSchema),
538
+ });
539
+
540
+ <Form {...form}>
541
+ <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
542
+ <FormField
543
+ control={form.control}
544
+ name="assignee"
545
+ render={({ field }) => (
546
+ <FormItem>
547
+ <FormLabel>负责人</FormLabel>
548
+ <FormControl>
549
+ <UserSelect
550
+ mode="single"
551
+ placeholder="选择负责人"
552
+ value={field.value}
553
+ onChange={(user) => field.onChange(user.user_id)}
554
+ />
555
+ </FormControl>
556
+ </FormItem>
557
+ )}
558
+ />
559
+
560
+ <FormField
561
+ control={form.control}
562
+ name="participants"
563
+ render={({ field }) => (
564
+ <FormItem>
565
+ <FormLabel>参与人(多选)</FormLabel>
566
+ <FormControl>
567
+ <UserSelect
568
+ mode="multiple"
569
+ placeholder="选择参与人员"
570
+ value={field.value}
571
+ onChange={(users) => field.onChange(users.map(user => user.user_id))}
572
+ />
573
+ </FormControl>
574
+ </FormItem>
575
+ )}
576
+ />
577
+ </form>
578
+ </Form>
579
+ ```
580
+
581
+ ## UserDisplay - 用户展示组件
582
+
583
+ ### 基本信息
584
+ - **组件路径**:`@lark-apaas/client-toolkit/components/User`
585
+ - **功能**:用于所有用户信息的展示场景
586
+ - **特性**:显示用户**头像**和**姓名**,支持多用户展示
587
+
588
+ IMPORTANT:当不传递showLabel时,组件会同时展示用户头像和姓名,如果只需要头像,则需要传递showLabel的值为false
589
+
590
+ ### 属性定义
591
+ ```typescript
592
+ interface UserDisplayProps {
593
+ users: string[]; // 用户id数组(必需)
594
+ size?: 'small' | 'medium' | 'large'; // 头像尺寸
595
+ className?: string; // 自定义样式类名
596
+ showLabel?: boolean; // 默认为true,会展示用户姓名,如果只需要展示头像则需要设置为false
597
+ }
598
+ ```
599
+
600
+ ### 使用示例
601
+
602
+ #### 基础用法
603
+ ```jsx
604
+ import { UserDisplay } from "@lark-apaas/client-toolkit/components/User";
605
+ import { useEmployeeStore } from "@/models/employee";
606
+
607
+ const { employeesId } = useEmployeeStore();
608
+
609
+ // 单个用户展示
610
+ <UserDisplay
611
+ users={[employeesId]}
612
+ size="small"
613
+ />
614
+
615
+ // 多用户展示
616
+ <UserDisplay
617
+ users={project.participants}
618
+ size="medium"
619
+ className="project-members"
620
+ />
621
+ ```
622
+
623
+ ## 使用注意事项
624
+
625
+ ### 最佳实践
626
+ - 根据展示场景选择合适的组件尺寸(`small`、`medium`、`large`)
627
+ - 对于用户信息 userId,禁止直接展示文本,总是使用 UserDisplay 组件展示
628
+ {% endif %}