@lark-apaas/coding-steering 0.1.14 → 0.1.15
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/package.json +1 -1
- package/steering/nestjs-react-fullstack/skills/anycross-forward/SKILL.md +180 -0
- package/steering/nestjs-react-fullstack/skills/app-init-feasibility-guide/SKILL.md +124 -0
- package/steering/nestjs-react-fullstack/skills/authn-guide/SKILL.md +3 -0
- package/steering/nestjs-react-fullstack/skills/authz-guide/SKILL.md +11 -2
- package/steering/nestjs-react-fullstack/skills/authz-guide/references/dynamic-permission-guide.md +0 -6
- package/steering/nestjs-react-fullstack/skills/authz-guide/references/management-page-spec.md +1 -4
- package/steering/nestjs-react-fullstack/skills/authz-guide/references/sdk-examples.md +5 -3
- package/steering/nestjs-react-fullstack/skills/authz-guide/references/sdk-types.md +17 -4
- package/steering/nestjs-react-fullstack/skills/client-builtins-file-storage-service/SKILL.md +4 -31
- package/steering/nestjs-react-fullstack/skills/client-builtins-user-service/SKILL.md +226 -116
- package/steering/nestjs-react-fullstack/skills/code-fix/SKILL.md +319 -0
- package/steering/nestjs-react-fullstack/skills/coding-guide/SKILL.md +666 -0
- package/steering/nestjs-react-fullstack/skills/contacts-service/SKILL.md +222 -0
- package/steering/nestjs-react-fullstack/skills/openapi-guide/SKILL.md +0 -1
- package/steering/nestjs-react-fullstack/skills/plugin-guide/SKILL.md +87 -36
- package/steering/nestjs-react-fullstack/skills/plugin-guide/references/plugin-coding-guide.md +1 -4
- package/steering/nestjs-react-fullstack/skills/plugin-guide/references/table.md +5 -6
- package/steering/nestjs-react-fullstack/skills/react-hook-best-practices/SKILL.md +5 -4
- package/steering/nestjs-react-fullstack/skills/trigger-guide/SKILL.md +0 -1
- package/steering/nestjs-react-fullstack/skills/user-identity/SKILL.md +3 -2
- package/steering/nestjs-react-fullstack/skills_local/coding-guide/SKILL.md +86 -87
- package/steering/nestjs-react-fullstack/skills_local/openapi-guide/SKILL.md +262 -0
- package/steering/nestjs-react-fullstack/skills_local/plugin-guide/SKILL.md +580 -0
- package/steering/nestjs-react-fullstack/skills_local/plugin-guide/references/plugin-coding-guide.md +322 -0
- package/steering/nestjs-react-fullstack/skills_local/plugin-guide/scripts/plugin-hydrate.js +152 -0
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: client-builtins-user-service
|
|
3
|
-
description: 前端用户鉴权服务与组件指南,提供
|
|
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
|
-
|
|
13
|
+
项目通过 `authClient`(来自 `@lark-apaas/client-toolkit/auth`)提供用户信息与鉴权服务,用于用户登录、登出、获取用户信息等身份认证相关功能。
|
|
15
14
|
|
|
16
|
-
> **边界说明**:`
|
|
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
|
-
##
|
|
60
|
+
## authClient 引入方式
|
|
62
61
|
|
|
63
62
|
```typescript
|
|
64
|
-
import {
|
|
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`
|
|
100
|
-
| `newTab`
|
|
94
|
+
| 属性名 | 类型 | 必填 | 默认值 | 说明 |
|
|
95
|
+
| ----------- | --------- | ---- | ----------- | ------------------------------ |
|
|
96
|
+
| `returnUrl` | `string` | ❌ | 当前页面URL | 登录成功后跳转回的页面 |
|
|
97
|
+
| `newTab` | `boolean` | ❌ | `false` | 是否在新浏览器标签页打开登录页 |
|
|
101
98
|
|
|
102
99
|
##### 出参说明
|
|
103
100
|
|
|
104
|
-
| 字段名
|
|
105
|
-
|
|
106
|
-
| `data`
|
|
107
|
-
| `error`
|
|
108
|
-
| `status`
|
|
109
|
-
| `statusText` | `'OK'`
|
|
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'> =
|
|
127
|
-
.service
|
|
123
|
+
const res: DataloomServiceResponse<'success'> = authClient
|
|
128
124
|
.session
|
|
129
125
|
.redirectToLogin(options: SignInRedirectionOptions);
|
|
130
126
|
|
|
131
127
|
// 基本使用
|
|
132
|
-
const loginResult =
|
|
133
|
-
.service
|
|
128
|
+
const loginResult = authClient
|
|
134
129
|
.session
|
|
135
130
|
.redirectToLogin();
|
|
136
131
|
|
|
137
132
|
// 带参数使用
|
|
138
|
-
const loginResult =
|
|
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`
|
|
166
|
-
| `error`
|
|
167
|
-
| `status`
|
|
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
|
|
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
|
|
193
|
-
.service
|
|
194
|
-
.session
|
|
195
|
-
.signOut();
|
|
183
|
+
const result = await authClient.session.signOut();
|
|
196
184
|
|
|
197
185
|
if (result.error) {
|
|
198
|
-
logger.error(
|
|
186
|
+
logger.error("退出登录失败:", result.error.message);
|
|
199
187
|
} else {
|
|
200
|
-
logger.info(
|
|
188
|
+
logger.info("退出登录成功");
|
|
201
189
|
// 跳转到登录页或首页
|
|
202
|
-
|
|
190
|
+
authClient.session.redirectToLogin();
|
|
203
191
|
}
|
|
204
192
|
} catch (error) {
|
|
205
|
-
logger.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` | ❌
|
|
218
|
+
| 属性名 | 类型 | 必填 | 默认值 | 说明 |
|
|
219
|
+
| -------- | --------- | ---- | ------- | ------------------------------ |
|
|
220
|
+
| `newTab` | `boolean` | ❌ | `false` | 是否在新浏览器标签页打开详情页 |
|
|
233
221
|
|
|
234
222
|
##### 出参说明
|
|
235
223
|
|
|
236
|
-
| 字段名
|
|
237
|
-
|
|
238
|
-
| `data`
|
|
239
|
-
| `error`
|
|
240
|
-
| `status`
|
|
241
|
-
| `statusText` | `'OK'`
|
|
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<
|
|
257
|
-
.service
|
|
258
|
-
.session
|
|
259
|
-
.navigateToUserProfile();
|
|
244
|
+
const res: DataloomServiceResponse<"success"> = authClient.session.navigateToUserProfile();
|
|
260
245
|
|
|
261
246
|
// 在新标签页打开
|
|
262
|
-
const resNewTab =
|
|
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`
|
|
321
|
-
| `data.user_info.user_id`
|
|
322
|
-
| `data.user_info.name`
|
|
323
|
-
| `data.user_info.avatar`
|
|
324
|
-
| `data.user_info.email`
|
|
325
|
-
| `data.user_info.phone_number` | `string`
|
|
326
|
-
| `data.user_info.tenant_name`
|
|
327
|
-
| `error`
|
|
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
|
|
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
|
|
348
|
-
.service
|
|
349
|
-
.session
|
|
350
|
-
.getUserInfo();
|
|
326
|
+
const result = await authClient.session.getUserInfo();
|
|
351
327
|
|
|
352
328
|
if (result.error) {
|
|
353
|
-
logger.error(
|
|
329
|
+
logger.error("获取用户信息失败:", result.error.message);
|
|
354
330
|
// 可能需要重新登录
|
|
355
331
|
if (result.status === 401) {
|
|
356
332
|
// 跳转到登录页
|
|
357
|
-
|
|
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(
|
|
337
|
+
logger.info("用户信息:", userInfo);
|
|
362
338
|
|
|
363
339
|
// 显示用户名
|
|
364
|
-
const userName = userInfo.name?.[0]?.text ||
|
|
365
|
-
document.getElementById(
|
|
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(
|
|
346
|
+
document.getElementById("avatar").src = avatarUrl;
|
|
371
347
|
}
|
|
372
348
|
|
|
373
349
|
// 显示用户邮箱
|
|
374
350
|
if (userInfo.email) {
|
|
375
|
-
document.getElementById(
|
|
351
|
+
document.getElementById("email").textContent = userInfo.email;
|
|
376
352
|
}
|
|
377
353
|
}
|
|
378
354
|
} catch (error) {
|
|
379
|
-
logger.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
|
-
|
|
382
|
+
authClient.session.redirectToLogin();
|
|
405
383
|
break;
|
|
406
384
|
case 403:
|
|
407
385
|
// 权限不足
|
|
408
|
-
|
|
386
|
+
toast.error("权限不足,请联系管理员");
|
|
409
387
|
break;
|
|
410
388
|
case 500:
|
|
411
389
|
// 服务器错误
|
|
412
|
-
|
|
390
|
+
toast.error("服务器错误,请稍后重试");
|
|
413
391
|
break;
|
|
414
392
|
default:
|
|
415
|
-
|
|
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
|
|
403
|
+
const userInfoResult = await authClient.session.getUserInfo();
|
|
426
404
|
if (handleDataloomError(userInfoResult)) {
|
|
427
405
|
// 处理成功逻辑
|
|
428
|
-
logger.info(
|
|
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
|
|
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`
|
|
488
|
-
| `email`
|
|
489
|
-
| `name`
|
|
490
|
-
| `avatar`
|
|
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
|
|
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
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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 %}
|