@lark-apaas/coding-steering 0.1.13 → 0.1.15-alpha.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.
Files changed (29) hide show
  1. package/README.md +19 -0
  2. package/package.json +2 -2
  3. package/steering/design-html/skills/animated-video/SKILL.md +26 -0
  4. package/steering/design-html/skills/charts/SKILL.md +155 -0
  5. package/steering/design-html/skills/frontend-design/SKILL.md +56 -0
  6. package/steering/design-html/skills/hi-fi-design/SKILL.md +29 -0
  7. package/steering/design-html/skills/interactive-prototype/SKILL.md +8 -0
  8. package/steering/design-html/skills/make-a-deck/SKILL.md +64 -0
  9. package/steering/design-html/skills/wireframe/SKILL.md +10 -0
  10. package/steering/nestjs-react-fullstack/skills/anycross-forward/SKILL.md +180 -0
  11. package/steering/nestjs-react-fullstack/skills/app-init-feasibility-guide/SKILL.md +124 -0
  12. package/steering/nestjs-react-fullstack/skills/authn-guide/SKILL.md +3 -0
  13. package/steering/nestjs-react-fullstack/skills/authz-guide/SKILL.md +11 -2
  14. package/steering/nestjs-react-fullstack/skills/authz-guide/references/dynamic-permission-guide.md +0 -6
  15. package/steering/nestjs-react-fullstack/skills/authz-guide/references/management-page-spec.md +1 -4
  16. package/steering/nestjs-react-fullstack/skills/authz-guide/references/sdk-examples.md +5 -3
  17. package/steering/nestjs-react-fullstack/skills/authz-guide/references/sdk-types.md +17 -4
  18. package/steering/nestjs-react-fullstack/skills/client-builtins-file-storage-service/SKILL.md +4 -31
  19. package/steering/nestjs-react-fullstack/skills/client-builtins-user-service/SKILL.md +226 -116
  20. package/steering/nestjs-react-fullstack/skills/code-fix/SKILL.md +319 -0
  21. package/steering/nestjs-react-fullstack/skills/coding-guide/SKILL.md +666 -0
  22. package/steering/nestjs-react-fullstack/skills/contacts-service/SKILL.md +222 -0
  23. package/steering/nestjs-react-fullstack/skills/openapi-guide/SKILL.md +0 -1
  24. package/steering/nestjs-react-fullstack/skills/plugin-guide/SKILL.md +87 -36
  25. package/steering/nestjs-react-fullstack/skills/plugin-guide/references/plugin-coding-guide.md +1 -4
  26. package/steering/nestjs-react-fullstack/skills/plugin-guide/references/table.md +5 -6
  27. package/steering/nestjs-react-fullstack/skills/react-hook-best-practices/SKILL.md +5 -4
  28. package/steering/nestjs-react-fullstack/skills/trigger-guide/SKILL.md +0 -1
  29. package/steering/nestjs-react-fullstack/skills/user-identity/SKILL.md +3 -2
@@ -1,19 +1,18 @@
1
1
  ---
2
2
  name: client-builtins-user-service
3
- description: 前端用户鉴权服务与组件指南,提供 Dataloom SDK 用户认证 API 和 React 用户组件。Use when 需要:(1) 实现登录/登出功能,(2) 获取当前用户信息或展示用户头像姓名,(3) 使用用户选择器或部门选择器组件,(4) 处理 401 未授权错误,或其他用户身份认证相关开发
3
+ description: 前端用户鉴权服务与组件指南,提供 authClient(来自 @lark-apaas/client-toolkit/auth)用户认证 API 和 React 用户组件。Use when 需要:(1) 实现登录/登出功能,(2) 获取当前用户信息或展示用户头像姓名,(3) 使用用户/部门选择器组件的 props 与挂载(选完拿哪个 ID/传飞书/字段获取见 contacts-service),(4) 处理 401 未授权错误
4
4
  steering: true
5
5
  steering-topic: client_builtins_user_service
6
6
  match-template-name: nestjs-react-fullstack
7
7
  ---
8
8
 
9
-
10
- # Dataloom 用户信息与鉴权 SDK
9
+ # 账户/会话 SDK (authClient)
11
10
 
12
11
  ## 概述
13
12
 
14
- 项目提供了 Dataloom SDK 的用户信息与鉴权服务,用于用户登录、登出、获取用户信息等身份认证相关功能。
13
+ 项目通过 `authClient`(来自 `@lark-apaas/client-toolkit/auth`)提供用户信息与鉴权服务,用于用户登录、登出、获取用户信息等身份认证相关功能。
15
14
 
16
- > **边界说明**:`dataloom.service.session` 仅用于用户登录/登出/获取用户信息等鉴权操作。**插件调用(capability)不属于 dataloom**,须使用独立的 `capabilityClient`(参见 plugin-guide)。
15
+ > **边界说明**:`authClient.session` 仅用于用户登录/登出/获取用户信息等鉴权操作。**插件调用(capability)不属于账户 SDK**,须使用独立的 `capabilityClient`(参见 plugin-guide)。
17
16
 
18
17
  ## 统一响应结构
19
18
 
@@ -58,13 +57,11 @@ interface DataloomServiceFailure extends DataloomServiceBase {
58
57
  type DataloomServiceResponse<T> = DataloomServiceSuccess<T> | DataloomServiceFailure;
59
58
  ```
60
59
 
61
- ## Dataloom实例获取方式
60
+ ## authClient 引入方式
62
61
 
63
62
  ```typescript
64
- import { getDataloom } from "@lark-apaas/client-toolkit/dataloom";
65
-
66
- // 异步获取dataloom实例
67
- const dataloom = await getDataloom();
63
+ import { authClient } from "@lark-apaas/client-toolkit/auth";
64
+ // authClient 是 SDK 内置的 singleton,零参可用,不需要异步初始化
68
65
  ```
69
66
 
70
67
  ## 鉴权服务接口
@@ -94,19 +91,19 @@ interface SignInRedirectionOptions {
94
91
  }
95
92
  ```
96
93
 
97
- | 属性名 | 类型 | 必填 | 默认值 | 说明 |
98
- |--------|------|------|--------|---------|
99
- | `returnUrl` | `string` | ❌ | 当前页面URL | 登录成功后跳转回的页面 |
100
- | `newTab` | `boolean` | ❌ | `false` | 是否在新浏览器标签页打开登录页 |
94
+ | 属性名 | 类型 | 必填 | 默认值 | 说明 |
95
+ | ----------- | --------- | ---- | ----------- | ------------------------------ |
96
+ | `returnUrl` | `string` | ❌ | 当前页面URL | 登录成功后跳转回的页面 |
97
+ | `newTab` | `boolean` | ❌ | `false` | 是否在新浏览器标签页打开登录页 |
101
98
 
102
99
  ##### 出参说明
103
100
 
104
- | 字段名 | 类型 | 说明 |
105
- |--------|------|---------|
106
- | `data` | `'success'` | 成功标识 |
107
- | `error` | `null` | 错误信息,成功时为null |
108
- | `status` | `200` | HTTP状态码 |
109
- | `statusText` | `'OK'` | HTTP状态文本 |
101
+ | 字段名 | 类型 | 说明 |
102
+ | ------------ | ----------- | ---------------------- |
103
+ | `data` | `'success'` | 成功标识 |
104
+ | `error` | `null` | 错误信息,成功时为null |
105
+ | `status` | `200` | HTTP状态码 |
106
+ | `statusText` | `'OK'` | HTTP状态文本 |
110
107
 
111
108
  ##### 使用示例
112
109
 
@@ -123,20 +120,17 @@ interface SignInRedirectionOptions {
123
120
  * statusText: 'OK',
124
121
  * }
125
122
  */
126
- const res: DataloomServiceResponse<'success'> = dataloom
127
- .service
123
+ const res: DataloomServiceResponse<'success'> = authClient
128
124
  .session
129
125
  .redirectToLogin(options: SignInRedirectionOptions);
130
126
 
131
127
  // 基本使用
132
- const loginResult = dataloom
133
- .service
128
+ const loginResult = authClient
134
129
  .session
135
130
  .redirectToLogin();
136
131
 
137
132
  // 带参数使用
138
- const loginResult = dataloom
139
- .service
133
+ const loginResult = authClient
140
134
  .session
141
135
  .redirectToLogin({
142
136
  returnUrl: 'https://example.com/dashboard',
@@ -160,12 +154,12 @@ const loginResult = dataloom
160
154
 
161
155
  ##### 出参说明
162
156
 
163
- | 字段名 | 类型 | 说明 |
164
- |--------|------|---------|
165
- | `data` | `null` | 数据为空 |
166
- | `error` | `null` | 错误信息,成功时为null |
167
- | `status` | `200` | HTTP状态码 |
168
- | `statusText` | `'OK'` | HTTP状态文本 |
157
+ | 字段名 | 类型 | 说明 |
158
+ | ------------ | ------ | ---------------------- |
159
+ | `data` | `null` | 数据为空 |
160
+ | `error` | `null` | 错误信息,成功时为null |
161
+ | `status` | `200` | HTTP状态码 |
162
+ | `statusText` | `'OK'` | HTTP状态文本 |
169
163
 
170
164
  ##### 使用示例
171
165
 
@@ -180,29 +174,23 @@ const loginResult = dataloom
180
174
  * statusText: 'OK',
181
175
  * }
182
176
  */
183
- const res: Promise<DataloomServiceResponse<null>> = await dataloom
184
- .service
185
- .session
186
- .signOut();
177
+ const res: Promise<DataloomServiceResponse<null>> = await authClient.session.signOut();
187
178
 
188
179
  // 使用示例
189
180
  import { logger } from "@lark-apaas/client-toolkit/logger";
190
181
 
191
182
  try {
192
- const result = await dataloom
193
- .service
194
- .session
195
- .signOut();
183
+ const result = await authClient.session.signOut();
196
184
 
197
185
  if (result.error) {
198
- logger.error('退出登录失败:', result.error.message);
186
+ logger.error("退出登录失败:", result.error.message);
199
187
  } else {
200
- logger.info('退出登录成功');
188
+ logger.info("退出登录成功");
201
189
  // 跳转到登录页或首页
202
- dataloom.service.session.redirectToLogin();
190
+ authClient.session.redirectToLogin();
203
191
  }
204
192
  } catch (error) {
205
- logger.error('退出登录异常:', error);
193
+ logger.error("退出登录异常:", error);
206
194
  }
207
195
  ```
208
196
 
@@ -227,18 +215,18 @@ interface NavigateToUserProfileOptions {
227
215
  }
228
216
  ```
229
217
 
230
- | 属性名 | 类型 | 必填 | 默认值 | 说明 |
231
- |--------|------|------|--------|---------|
232
- | `newTab` | `boolean` | ❌ | `false` | 是否在新浏览器标签页打开详情页 |
218
+ | 属性名 | 类型 | 必填 | 默认值 | 说明 |
219
+ | -------- | --------- | ---- | ------- | ------------------------------ |
220
+ | `newTab` | `boolean` | ❌ | `false` | 是否在新浏览器标签页打开详情页 |
233
221
 
234
222
  ##### 出参说明
235
223
 
236
- | 字段名 | 类型 | 说明 |
237
- |--------|------|---------|
238
- | `data` | `'success'` | 成功标识 |
239
- | `error` | `null` | 错误信息,成功时为null |
240
- | `status` | `200` | HTTP状态码 |
241
- | `statusText` | `'OK'` | HTTP状态文本 |
224
+ | 字段名 | 类型 | 说明 |
225
+ | ------------ | ----------- | ---------------------- |
226
+ | `data` | `'success'` | 成功标识 |
227
+ | `error` | `null` | 错误信息,成功时为null |
228
+ | `status` | `200` | HTTP状态码 |
229
+ | `statusText` | `'OK'` | HTTP状态文本 |
242
230
 
243
231
  ##### 使用示例
244
232
 
@@ -253,16 +241,10 @@ interface NavigateToUserProfileOptions {
253
241
  * statusText: 'OK',
254
242
  * }
255
243
  */
256
- const res: DataloomServiceResponse<'success'> = dataloom
257
- .service
258
- .session
259
- .navigateToUserProfile();
244
+ const res: DataloomServiceResponse<"success"> = authClient.session.navigateToUserProfile();
260
245
 
261
246
  // 在新标签页打开
262
- const resNewTab = dataloom
263
- .service
264
- .session
265
- .navigateToUserProfile({ newTab: true });
247
+ const resNewTab = authClient.session.navigateToUserProfile({ newTab: true });
266
248
  ```
267
249
 
268
250
  ## 用户信息服务
@@ -315,16 +297,16 @@ interface UserInfoResponse {
315
297
 
316
298
  ##### 出参说明
317
299
 
318
- | 字段名 | 类型 | 说明 |
319
- |--------|------|---------|
320
- | `data.user_info` | `UserBaseInfo` | 用户基本信息对象 |
321
- | `data.user_info.user_id` | `number` | 用户唯一标识符 |
322
- | `data.user_info.name` | `I18ns` | 用户名称(支持多语言) |
323
- | `data.user_info.avatar` | `Avatar` | 用户头像信息 |
324
- | `data.user_info.email` | `string` | 用户邮箱地址 |
325
- | `data.user_info.phone_number` | `string` | 用户手机号码 |
326
- | `data.user_info.tenant_name` | `string` | 租户名称 |
327
- | `error` | `null` | 错误信息,成功时为null |
300
+ | 字段名 | 类型 | 说明 |
301
+ | ----------------------------- | -------------- | ---------------------- |
302
+ | `data.user_info` | `UserBaseInfo` | 用户基本信息对象 |
303
+ | `data.user_info.user_id` | `number` | 用户唯一标识符 |
304
+ | `data.user_info.name` | `I18ns` | 用户名称(支持多语言) |
305
+ | `data.user_info.avatar` | `Avatar` | 用户头像信息 |
306
+ | `data.user_info.email` | `string` | 用户邮箱地址 |
307
+ | `data.user_info.phone_number` | `string` | 用户手机号码 |
308
+ | `data.user_info.tenant_name` | `string` | 租户名称 |
309
+ | `error` | `null` | 错误信息,成功时为null |
328
310
 
329
311
  ##### 使用示例
330
312
 
@@ -335,48 +317,42 @@ interface UserInfoResponse {
335
317
  * @param {string} appId - 运行态应用的id,由dataloom authn 服务下发。
336
318
  * @return
337
319
  */
338
- const res: Promise<DataloomServiceResponse<UserInfoResponse>> = await dataloom
339
- .service
340
- .session
341
- .getUserInfo();
320
+ const res: Promise<DataloomServiceResponse<UserInfoResponse>> = await authClient.session.getUserInfo();
342
321
 
343
322
  // 使用示例
344
323
  import { logger } from "@lark-apaas/client-toolkit/logger";
345
324
 
346
325
  try {
347
- const result = await dataloom
348
- .service
349
- .session
350
- .getUserInfo();
326
+ const result = await authClient.session.getUserInfo();
351
327
 
352
328
  if (result.error) {
353
- logger.error('获取用户信息失败:', result.error.message);
329
+ logger.error("获取用户信息失败:", result.error.message);
354
330
  // 可能需要重新登录
355
331
  if (result.status === 401) {
356
332
  // 跳转到登录页
357
- dataloom.service.session.redirectToLogin();
333
+ authClient.session.redirectToLogin();
358
334
  }
359
335
  } else if (result.data?.user_info) {
360
336
  const userInfo = result.data.user_info;
361
- logger.info('用户信息:', userInfo);
337
+ logger.info("用户信息:", userInfo);
362
338
 
363
339
  // 显示用户名
364
- const userName = userInfo.name?.[0]?.text || '未知用户';
365
- document.getElementById('username').textContent = userName;
340
+ const userName = userInfo.name?.[0]?.text || "未知用户";
341
+ document.getElementById("username").textContent = userName;
366
342
 
367
343
  // 显示用户头像
368
344
  const avatarUrl = userInfo.avatar?.image?.large;
369
345
  if (avatarUrl) {
370
- document.getElementById('avatar').src = avatarUrl;
346
+ document.getElementById("avatar").src = avatarUrl;
371
347
  }
372
348
 
373
349
  // 显示用户邮箱
374
350
  if (userInfo.email) {
375
- document.getElementById('email').textContent = userInfo.email;
351
+ document.getElementById("email").textContent = userInfo.email;
376
352
  }
377
353
  }
378
354
  } catch (error) {
379
- logger.error('获取用户信息异常:', error);
355
+ logger.error("获取用户信息异常:", error);
380
356
  }
381
357
  ```
382
358
 
@@ -384,35 +360,37 @@ try {
384
360
 
385
361
  ### 常见错误类型
386
362
 
387
- | 错误码 | 说明 | 处理建议 |
388
- |--------|------|---------|
389
- | `400` | 请求参数错误 | 检查传入参数是否正确 |
390
- | `401` | 未授权访问 | 需要重新登录 |
391
- | `403` | 权限不足 | 联系管理员分配权限 |
392
- | `404` | 资源不存在 | 检查请求的资源是否存在 |
393
- | `500` | 服务器内部错误 | 稍后重试或联系技术支持 |
363
+ | 错误码 | 说明 | 处理建议 |
364
+ | ------ | -------------- | ---------------------- |
365
+ | `400` | 请求参数错误 | 检查传入参数是否正确 |
366
+ | `401` | 未授权访问 | 需要重新登录 |
367
+ | `403` | 权限不足 | 联系管理员分配权限 |
368
+ | `404` | 资源不存在 | 检查请求的资源是否存在 |
369
+ | `500` | 服务器内部错误 | 稍后重试或联系技术支持 |
394
370
 
395
371
  ### 错误处理最佳实践
396
372
 
397
373
  ```typescript
374
+ import { toast } from "sonner";
375
+
398
376
  // 统一错误处理函数
399
377
  function handleDataloomError(response: DataloomServiceResponse<any>) {
400
378
  if (response.error) {
401
379
  switch (response.status) {
402
380
  case 401:
403
381
  // 未授权,跳转登录
404
- dataloom.service.session.redirectToLogin();
382
+ authClient.session.redirectToLogin();
405
383
  break;
406
384
  case 403:
407
385
  // 权限不足
408
- alert('权限不足,请联系管理员');
386
+ toast.error("权限不足,请联系管理员");
409
387
  break;
410
388
  case 500:
411
389
  // 服务器错误
412
- alert('服务器错误,请稍后重试');
390
+ toast.error("服务器错误,请稍后重试");
413
391
  break;
414
392
  default:
415
- alert(`操作失败: ${response.error.message}`);
393
+ toast.error(`操作失败: ${response.error.message}`);
416
394
  }
417
395
  return false;
418
396
  }
@@ -422,10 +400,10 @@ function handleDataloomError(response: DataloomServiceResponse<any>) {
422
400
  // 使用示例
423
401
  import { logger } from "@lark-apaas/client-toolkit/logger";
424
402
 
425
- const userInfoResult = await dataloom.service.session.getUserInfo();
403
+ const userInfoResult = await authClient.session.getUserInfo();
426
404
  if (handleDataloomError(userInfoResult)) {
427
405
  // 处理成功逻辑
428
- logger.info('用户信息:', userInfoResult.data);
406
+ logger.info("用户信息:", userInfoResult.data);
429
407
  }
430
408
  ```
431
409
 
@@ -450,19 +428,24 @@ if (handleDataloomError(userInfoResult)) {
450
428
  ### 用户数据类型
451
429
 
452
430
  ```typescript
453
- import type { User } from '@/types/common';
431
+ import type { User } from "@/types/common";
454
432
  ```
455
433
 
456
434
  `User` 接口包含用户的基本信息,如用户 ID、姓名、头像字段。
457
435
 
458
436
  ```typescript
459
437
  export type User = {
438
+ /** 妙搭用户 ID:入库、传飞书内置插件、跨边界透传一律用它 */
460
439
  user_id: string;
440
+ /** 飞书企业内 user_id(仅内部飞书用户),调飞书开放平台 API 用它 */
441
+ employee_id?: string;
461
442
  name: string;
462
443
  avatar: string;
463
444
  };
464
445
  ```
465
446
 
447
+ > **用户/部门 ID 怎么选、字段获取与权限引导 → 见 [`contacts-service`](../contacts-service/SKILL.md) skill;本 skill 只讲组件 API 用法。**
448
+
466
449
  ## 当前用户信息的获取方案
467
450
 
468
451
  ### 常用场景
@@ -482,20 +465,15 @@ export type User = {
482
465
 
483
466
  **IUserProfile 字段**:
484
467
 
485
- | 字段 | 类型 | 说明 |
486
- |------|------|------|
487
- | `user_id` | `string` | 妙搭用户 ID |
488
- | `email` | `string` | 用户邮箱 |
489
- | `name` | `string` | 用户名称 |
490
- | `avatar` | `string` | 用户头像 URL |
468
+ | 字段 | 类型 | 说明 |
469
+ | -------------- | -------- | -------------------------------------------------------- |
470
+ | `user_id` | `string` | 妙搭用户 ID |
471
+ | `email` | `string` | 用户邮箱 |
472
+ | `name` | `string` | 用户名称 |
473
+ | `avatar` | `string` | 用户头像 URL |
491
474
  | `lark_user_id` | `string` | 飞书 user_id,通过额外异步请求获取,可能晚于其他字段就绪 |
492
475
 
493
- > ⚠️ **空值处理(CRITICAL)**:该 Hook 初始返回空对象 `{}`(truthy),所有字段均为 `undefined`。
494
- >
495
- > - **MUST** 使用可选链访问:`userInfo?.name`、`userInfo?.user_id`
496
- > - **MUST** 判断加载状态用 `if (!userInfo?.user_id)` 而非 `if (!userInfo)`(空对象是 truthy)
497
- > - **MUST** 渲染时提供默认值:`userInfo?.name ?? '加载中...'`
498
- > - **MUST** `lark_user_id` 通过额外异步请求获取,可能为 `undefined`(加载中/获取失败/无飞书账号),使用时必须条件渲染
476
+ > ⚠️ **空值处理(CRITICAL)**:Hook 初始返回空对象 `{}`(truthy)、字段均 `undefined`。**MUST** 用可选链,并以 `if (!userInfo?.user_id)` 判加载态(不是 `!userInfo`);`lark_user_id` 异步获取、可能为 `undefined`,使用前条件渲染。
499
477
 
500
478
  ### 使用方法
501
479
 
@@ -520,6 +498,7 @@ const MyComponent = () => {
520
498
 
521
499
  ## 用户展示与选择方案
522
500
 
501
+ {% if projectMeta['flags']['supportBusinessUser'] %}
523
502
  这些组件在使用之前必须读取 client/src/components/business-ui/README.md文件来理解用法
524
503
 
525
504
  目前可用的组件有:
@@ -528,9 +507,139 @@ const MyComponent = () => {
528
507
  - DepartmentSelect 部门选择组件(@/components/business-ui/department-select)
529
508
  - UserDisplay - 用户展示组件(@/components/business-ui/user-display)
530
509
 
531
- <!-- legacy: client-toolkit User 组件备用方案,已不再推荐;按本地版静态化,仅保留 business-ui 路径 -->
532
- <!-- begin: removed Nunjucks else branch (line 532..672 in upstream) -->
533
- <!-- end -->
510
+ {% else %}
511
+
512
+ ##
513
+
514
+ ### 基本信息
515
+
516
+ - **组件路径**:`@lark-apaas/client-toolkit/components/User`
517
+ - **功能**:用于所有用户字段的表单输入
518
+ - **特性**:支持单选/多选模式,内置搜索功能
519
+
520
+ ### 组件 Props 定义
521
+
522
+ ```typescript
523
+ interface UserSelectProps {
524
+ mode: "single" | "multiple"; // 选择模式
525
+ value?: string | string[]; // 当前userid的值
526
+ onChange?: (value: IUserProfile | IUserProfile[]) => void; // userid值变化回调
527
+ placeholder?: string; // 占位符文本
528
+ disabled?: boolean; // 是否禁用
529
+ }
530
+ ```
531
+
532
+ ### 值类型说明
533
+
534
+ - **单选模式** (`mode="single"`):值为 userid,返回为`IUserProfile` 对象
535
+ - **多选模式** (`mode="multiple"`):值为 userid数组,返回为`IUserProfile` 数组
536
+
537
+ ### 使用示例
538
+
539
+ #### 表单集成
540
+
541
+ ```typescript
542
+ import { useForm } from "react-hook-form";
543
+ import { zodResolver } from "@hookform/resolvers/zod";
544
+ import * as z from "zod";
545
+ import { Form, FormControl, FormField, FormItem, FormLabel } from "@/components/ui/form";
546
+ import { UserSelect } from "@lark-apaas/client-toolkit/components/User";
547
+
548
+ // 定义表单验证schema
549
+ const formSchema = z.object({
550
+ assignee: z.string(),
551
+ participants: z.array(z.string()).optional(),
552
+ });
553
+
554
+ const form = useForm<z.infer<typeof formSchema>>({
555
+ resolver: zodResolver(formSchema),
556
+ });
557
+
558
+ <Form {...form}>
559
+ <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
560
+ <FormField
561
+ control={form.control}
562
+ name="assignee"
563
+ render={({ field }) => (
564
+ <FormItem>
565
+ <FormLabel>负责人</FormLabel>
566
+ <FormControl>
567
+ <UserSelect
568
+ mode="single"
569
+ placeholder="选择负责人"
570
+ value={field.value}
571
+ onChange={(user) => field.onChange(user.user_id)}
572
+ />
573
+ </FormControl>
574
+ </FormItem>
575
+ )}
576
+ />
577
+
578
+ <FormField
579
+ control={form.control}
580
+ name="participants"
581
+ render={({ field }) => (
582
+ <FormItem>
583
+ <FormLabel>参与人(多选)</FormLabel>
584
+ <FormControl>
585
+ <UserSelect
586
+ mode="multiple"
587
+ placeholder="选择参与人员"
588
+ value={field.value}
589
+ onChange={(users) => field.onChange(users.map(user => user.user_id))}
590
+ />
591
+ </FormControl>
592
+ </FormItem>
593
+ )}
594
+ />
595
+ </form>
596
+ </Form>
597
+ ```
598
+
599
+ ## UserDisplay - 用户展示组件
600
+
601
+ ### 基本信息
602
+
603
+ - **组件路径**:`@lark-apaas/client-toolkit/components/User`
604
+ - **功能**:用于所有用户信息的展示场景
605
+ - **特性**:显示用户**头像**和**姓名**,支持多用户展示
606
+
607
+ IMPORTANT:当不传递showLabel时,组件会同时展示用户头像和姓名,如果只需要头像,则需要传递showLabel的值为false
608
+
609
+ ### 属性定义
610
+
611
+ ```typescript
612
+ interface UserDisplayProps {
613
+ users: string[]; // 用户id数组(必需)
614
+ size?: "small" | "medium" | "large"; // 头像尺寸
615
+ className?: string; // 自定义样式类名
616
+ showLabel?: boolean; // 默认为true,会展示用户姓名,如果只需要展示头像则需要设置为false
617
+ }
618
+ ```
619
+
620
+ ### 使用示例
621
+
622
+ #### 基础用法
623
+
624
+ ```jsx
625
+ import { UserDisplay } from "@lark-apaas/client-toolkit/components/User";
626
+ import { useEmployeeStore } from "@/models/employee";
627
+
628
+ const { employeesId } = useEmployeeStore();
629
+
630
+ // 单个用户展示
631
+ <UserDisplay
632
+ users={[employeesId]}
633
+ size="small"
634
+ />
635
+
636
+ // 多用户展示
637
+ <UserDisplay
638
+ users={project.participants}
639
+ size="medium"
640
+ className="project-members"
641
+ />
642
+ ```
534
643
 
535
644
  ## 使用注意事项
536
645
 
@@ -538,3 +647,4 @@ const MyComponent = () => {
538
647
 
539
648
  - 根据展示场景选择合适的组件尺寸(`small`、`medium`、`large`)
540
649
  - 对于用户信息 userId,禁止直接展示文本,总是使用 UserDisplay 组件展示
650
+ {% endif %}