@lark-apaas/coding-steering 0.1.16 → 0.1.17-beta.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/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@lark-apaas/coding-steering",
3
- "version": "0.1.16",
3
+ "version": "0.1.17-beta.0",
4
4
  "description": "Stack-specific steering content for miaoda-coding templates",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "steering"
8
8
  ],
9
9
  "scripts": {
10
- "lint:md": "markdownlint 'steering/**/*.md' --ignore 'steering/**/skills/**'"
10
+ "lint:md": "markdownlint 'steering/**/*.md' --ignore 'steering/**/skills/**' --ignore 'steering/**/skills_common/**' --ignore 'steering/**/skills_local/**'"
11
11
  },
12
12
  "devDependencies": {
13
13
  "markdownlint-cli": "^0.47.0"
@@ -13,27 +13,20 @@ match-template-name: nestjs-react-fullstack
13
13
  项目通过 `authClient`(来自 `@lark-apaas/client-toolkit/auth`)提供用户信息与鉴权服务,用于用户登录、登出、获取用户信息等身份认证相关功能。
14
14
 
15
15
  > **边界说明**:`authClient.session` 仅用于用户登录/登出/获取用户信息等鉴权操作。**插件调用(capability)不属于账户 SDK**,须使用独立的 `capabilityClient`(参见 plugin-guide)。
16
+ >
17
+ > **运行时边界**:本 skill 所有能力(`authClient`、`useCurrentUserProfile`、UserSelect/UserDisplay 等)仅限前端代码使用,**严禁在 `server/**` 中 import**。服务端获取用户身份用 `req.userContext` / `AuthNPaasService`(见 `user-identity` skill),完整边界规则见 coding-guide。
18
+
19
+ ## 怎么选(决策指引)
20
+
21
+ - **React 组件内展示当前用户**(名称/头像/邮箱/飞书 ID)→ 用 `useCurrentUserProfile()`(见下文),不要手动调 `getUserInfo`
22
+ - **登录/登出/跳转用户详情页,或非 React 上下文取用户信息** → `authClient.session.*`(本节)
23
+ - **选人/选部门/展示任意用户** → UserSelect / DepartmentSelect / UserDisplay 组件(见下文)
16
24
 
17
25
  ## 统一响应结构
18
26
 
19
- ### 响应类型定义
27
+ 所有 `authClient.session.*` 接口返回统一的 `DataloomServiceResponse<T>` 结构。在非浏览器环境调用会返回失败结构(`status: 400`,`error.message: 'Incompatible runtime environment'`)。
20
28
 
21
29
  ```typescript
22
- /**
23
- * 统一结果返回结构
24
- * 在非浏览器环境调用时返回示例:
25
- * {
26
- * data: null,
27
- * error: {
28
- * code: 400,
29
- * message: 'Incompatible runtime environment',
30
- * hint: 'Please check if the current environment is browser.',
31
- * details: 'This method can only be invoked in browser environment.',
32
- * },
33
- * status: 400,
34
- * statusText: 'Bad Request',
35
- * }
36
- */
37
30
  interface DataloomServiceBase {
38
31
  status: number;
39
32
  statusText: string;
@@ -64,202 +57,32 @@ import { authClient } from "@lark-apaas/client-toolkit/auth";
64
57
  // authClient 是 SDK 内置的 singleton,零参可用,不需要异步初始化
65
58
  ```
66
59
 
67
- ## 鉴权服务接口
68
-
69
- #### 1. 登录跳转接口 (redirectToLogin)
70
-
71
- ##### 用途
60
+ ## 接口速查表
72
61
 
73
- 跳转至 Dataloom 登录页面,适用于:用户身份认证、单点登录、权限验证
62
+ | 方法 | 入参 | 成功时 data 类型 | 说明 |
63
+ | ----------------------------------------- | ------------------------------ | -------------------------- | ---------------------------------------------- |
64
+ | `session.redirectToLogin(options?)` | `SignInRedirectionOptions` | `'success'` | 跳转至 Dataloom 登录页(身份认证/单点登录) |
65
+ | `session.signOut()` | 无 | `null`(异步) | 退出登录,删除 cookie 中的登录态 |
66
+ | `session.navigateToUserProfile(options?)` | `NavigateToUserProfileOptions` | `'success'` | 跳转至当前登录用户详情页(头像/姓名点击进入) |
67
+ | `session.getUserInfo()` | 无 | `UserInfoResponse`(异步) | 获取当前登录用户信息,未登录返回 `status: 401` |
74
68
 
75
- ##### 适用场景
76
-
77
- 需要用户进行身份认证的场景
78
-
79
- ##### 入参说明
69
+ ### 入参类型定义
80
70
 
81
71
  ```typescript
82
72
  interface SignInRedirectionOptions {
83
- /**
84
- * 选填,登录成功后跳转回的页面。省略会默认用调用接口时页面的url。
85
- */
73
+ /** 选填,登录成功后跳转回的页面。省略默认用当前页面 URL */
86
74
  returnUrl?: string;
87
- /**
88
- * 选填,是否在新浏览器tab上打开登录页。默认为false
89
- */
75
+ /** 选填,是否在新浏览器 tab 打开登录页。默认 false */
90
76
  newTab?: boolean;
91
77
  }
92
- ```
93
-
94
- | 属性名 | 类型 | 必填 | 默认值 | 说明 |
95
- | ----------- | --------- | ---- | ----------- | ------------------------------ |
96
- | `returnUrl` | `string` | ❌ | 当前页面URL | 登录成功后跳转回的页面 |
97
- | `newTab` | `boolean` | ❌ | `false` | 是否在新浏览器标签页打开登录页 |
98
-
99
- ##### 出参说明
100
-
101
- | 字段名 | 类型 | 说明 |
102
- | ------------ | ----------- | ---------------------- |
103
- | `data` | `'success'` | 成功标识 |
104
- | `error` | `null` | 错误信息,成功时为null |
105
- | `status` | `200` | HTTP状态码 |
106
- | `statusText` | `'OK'` | HTTP状态文本 |
107
-
108
- ##### 使用示例
109
-
110
- ```typescript
111
- /**
112
- * 跳转至dataloom登录页
113
- * @param {string} brand - 品牌id 例如:妙搭为 1.
114
- * @param {string} appId - 运行态应用的id,由dataloom authn 服务下发。
115
- * @return 成功返回示例:
116
- * {
117
- * data: 'success',
118
- * error: null,
119
- * status: 200,
120
- * statusText: 'OK',
121
- * }
122
- */
123
- const res: DataloomServiceResponse<'success'> = authClient
124
- .session
125
- .redirectToLogin(options: SignInRedirectionOptions);
126
-
127
- // 基本使用
128
- const loginResult = authClient
129
- .session
130
- .redirectToLogin();
131
-
132
- // 带参数使用
133
- const loginResult = authClient
134
- .session
135
- .redirectToLogin({
136
- returnUrl: 'https://example.com/dashboard',
137
- newTab: true
138
- });
139
- ```
140
-
141
- #### 2. 退出登录接口 (signOut)
142
-
143
- ##### 用途
144
-
145
- 退出登录,删除cookie中的登录态,适用于:用户主动登出、会话清理、安全退出
146
-
147
- ##### 适用场景
148
-
149
- 需要清除用户登录状态的场景
150
-
151
- ##### 入参说明
152
-
153
- 无需传入参数
154
-
155
- ##### 出参说明
156
-
157
- | 字段名 | 类型 | 说明 |
158
- | ------------ | ------ | ---------------------- |
159
- | `data` | `null` | 数据为空 |
160
- | `error` | `null` | 错误信息,成功时为null |
161
- | `status` | `200` | HTTP状态码 |
162
- | `statusText` | `'OK'` | HTTP状态文本 |
163
-
164
- ##### 使用示例
165
-
166
- ```typescript
167
- /**
168
- * 退出登录,删除cookie中的登陆态
169
- * @return 成功返回示例:
170
- * {
171
- * data: null,
172
- * error: null,
173
- * status: 200,
174
- * statusText: 'OK',
175
- * }
176
- */
177
- const res: Promise<DataloomServiceResponse<null>> = await authClient.session.signOut();
178
-
179
- // 使用示例
180
- import { logger } from "@lark-apaas/client-toolkit/logger";
181
-
182
- try {
183
- const result = await authClient.session.signOut();
184
-
185
- if (result.error) {
186
- logger.error("退出登录失败:", result.error.message);
187
- } else {
188
- logger.info("退出登录成功");
189
- // 跳转到登录页或首页
190
- authClient.session.redirectToLogin();
191
- }
192
- } catch (error) {
193
- logger.error("退出登录异常:", error);
194
- }
195
- ```
196
-
197
- #### 3. 跳转用户详情页接口 (navigateToUserProfile)
198
-
199
- ##### 用途
200
-
201
- 跳转至当前登录用户的详情页,适用于:查看个人资料、从用户头像/姓名点击进入详情页面
202
-
203
- ##### 适用场景
204
-
205
- 需要在应用中快速打开用户详情页面的场景
206
78
 
207
- ##### 入参说明
208
-
209
- ```typescript
210
79
  interface NavigateToUserProfileOptions {
211
- /**
212
- * 选填,是否在新浏览器tab上打开详情页。默认为false
213
- */
80
+ /** 选填,是否在新浏览器 tab 打开详情页。默认 false */
214
81
  newTab?: boolean;
215
82
  }
216
83
  ```
217
84
 
218
- | 属性名 | 类型 | 必填 | 默认值 | 说明 |
219
- | -------- | --------- | ---- | ------- | ------------------------------ |
220
- | `newTab` | `boolean` | ❌ | `false` | 是否在新浏览器标签页打开详情页 |
221
-
222
- ##### 出参说明
223
-
224
- | 字段名 | 类型 | 说明 |
225
- | ------------ | ----------- | ---------------------- |
226
- | `data` | `'success'` | 成功标识 |
227
- | `error` | `null` | 错误信息,成功时为null |
228
- | `status` | `200` | HTTP状态码 |
229
- | `statusText` | `'OK'` | HTTP状态文本 |
230
-
231
- ##### 使用示例
232
-
233
- ```typescript
234
- /**
235
- * 跳转至用户详情页
236
- * @return 成功返回示例:
237
- * {
238
- * data: 'success',
239
- * error: null,
240
- * status: 200,
241
- * statusText: 'OK',
242
- * }
243
- */
244
- const res: DataloomServiceResponse<"success"> = authClient.session.navigateToUserProfile();
245
-
246
- // 在新标签页打开
247
- const resNewTab = authClient.session.navigateToUserProfile({ newTab: true });
248
- ```
249
-
250
- ## 用户信息服务
251
-
252
- #### 4. 获取用户信息接口 (getUserInfo)
253
-
254
- ##### 用途
255
-
256
- 根据当前登录态获取已登录的用户信息,适用于:用户资料展示、权限判断、个性化配置
257
-
258
- ##### 适用场景
259
-
260
- 需要获取当前登录用户详细信息的场景
261
-
262
- ##### 数据类型定义
85
+ ### getUserInfo 返回类型定义
263
86
 
264
87
  ```typescript
265
88
  interface I18n {
@@ -291,75 +114,39 @@ interface UserInfoResponse {
291
114
  }
292
115
  ```
293
116
 
294
- ##### 入参说明
295
-
296
- 无需传入参数
297
-
298
- ##### 出参说明
299
-
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 |
310
-
311
- ##### 使用示例
117
+ ### 综合示例
312
118
 
313
119
  ```typescript
314
- /**
315
- * 根据当前登陆态获取已登录的用户信息。
316
- * @param {string} brand - 品牌id 例如:妙搭为 1.
317
- * @param {string} appId - 运行态应用的id,由dataloom authn 服务下发。
318
- * @return
319
- */
320
- const res: Promise<DataloomServiceResponse<UserInfoResponse>> = await authClient.session.getUserInfo();
321
-
322
- // 使用示例
120
+ import { authClient } from "@lark-apaas/client-toolkit/auth";
323
121
  import { logger } from "@lark-apaas/client-toolkit/logger";
324
122
 
325
- try {
326
- const result = await authClient.session.getUserInfo();
327
-
328
- if (result.error) {
329
- logger.error("获取用户信息失败:", result.error.message);
330
- // 可能需要重新登录
331
- if (result.status === 401) {
332
- // 跳转到登录页
333
- authClient.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
- }
123
+ // 获取用户信息(React 组件内展示当前用户请优先用 useCurrentUserProfile,见下文)
124
+ const result = await authClient.session.getUserInfo();
125
+ if (result.error) {
126
+ logger.error("获取用户信息失败:", result.error.message);
127
+ if (result.status === 401) {
128
+ // 未登录:跳转登录页(可传 returnUrl / newTab,默认回到当前页)
129
+ authClient.session.redirectToLogin();
353
130
  }
354
- } catch (error) {
355
- logger.error("获取用户信息异常:", error);
131
+ } else if (result.data?.user_info) {
132
+ const info = result.data.user_info;
133
+ const userName = info.name?.[0]?.text || "未知用户"; // 名称是多语言数组
134
+ const avatarUrl = info.avatar?.image?.large; // 头像 URL 可能不返回
135
+ // React 中通过 state 渲染上述字段,禁止 document.getElementById 等直接 DOM 操作
136
+ }
137
+
138
+ // 退出登录后回到登录页
139
+ const signOutResult = await authClient.session.signOut();
140
+ if (!signOutResult.error) {
141
+ authClient.session.redirectToLogin();
356
142
  }
143
+
144
+ // 跳转当前用户详情页(新标签页打开)
145
+ authClient.session.navigateToUserProfile({ newTab: true });
357
146
  ```
358
147
 
359
148
  ## 错误处理
360
149
 
361
- ### 常见错误类型
362
-
363
150
  | 错误码 | 说明 | 处理建议 |
364
151
  | ------ | -------------- | ---------------------- |
365
152
  | `400` | 请求参数错误 | 检查传入参数是否正确 |
@@ -368,60 +155,19 @@ try {
368
155
  | `404` | 资源不存在 | 检查请求的资源是否存在 |
369
156
  | `500` | 服务器内部错误 | 稍后重试或联系技术支持 |
370
157
 
371
- ### 错误处理最佳实践
372
-
373
- ```typescript
374
- import { toast } from "sonner";
375
-
376
- // 统一错误处理函数
377
- function handleDataloomError(response: DataloomServiceResponse<any>) {
378
- if (response.error) {
379
- switch (response.status) {
380
- case 401:
381
- // 未授权,跳转登录
382
- authClient.session.redirectToLogin();
383
- break;
384
- case 403:
385
- // 权限不足
386
- toast.error("权限不足,请联系管理员");
387
- break;
388
- case 500:
389
- // 服务器错误
390
- toast.error("服务器错误,请稍后重试");
391
- break;
392
- default:
393
- toast.error(`操作失败: ${response.error.message}`);
394
- }
395
- return false;
396
- }
397
- return true;
398
- }
399
-
400
- // 使用示例
401
- import { logger } from "@lark-apaas/client-toolkit/logger";
402
-
403
- const userInfoResult = await authClient.session.getUserInfo();
404
- if (handleDataloomError(userInfoResult)) {
405
- // 处理成功逻辑
406
- logger.info("用户信息:", userInfoResult.data);
407
- }
408
- ```
158
+ 统一处理建议:`401` 调用 `authClient.session.redirectToLogin()`(见综合示例);`403`/`500` 用 toast(如 `sonner`)提示用户;其余情况展示 `error.message`。
409
159
 
410
160
  ## 注意事项
411
161
 
412
- 1. **环境限制**:部分接口只能在浏览器环境中使用,服务端调用会返回环境不兼容错误
413
- 2. **登录状态**:获取用户信息前需要确保用户已登录,否则会返回401错误
414
- 3. **跨域配置**:确保应用域名已在 Dataloom 后台配置白名单
415
- 4. **安全性**:不要在客户端代码中暴露敏感的配置信息
416
- 5. **错误处理**:建议对所有接口调用进行统一的错误处理
162
+ 1. **环境限制**:本 skill 涉及的 `@lark-apaas/client-toolkit/**` 能力只能在前端/浏览器环境中使用,服务端调用会返回环境不兼容错误;更重要的是,服务端代码中禁止 import 这些前端 SDK
163
+ 2. **跨域配置**:确保应用域名已在 Dataloom 后台配置白名单
164
+ 3. **安全性**:不要在客户端代码中暴露敏感的配置信息
417
165
 
418
166
  # 用户系统前端相关规范
419
167
 
420
168
  ## 概述
421
169
 
422
- 内置的用户前端组件规范,提供了 UserSelect(支持单选/多选的用户选择器)和 UserDisplay(用户信息展示组件)两个核心 React 组件,基于统一的userid数据,并且用户选择组件中会通过onchange返回User类型数据,专门用于处理用户相关的表单输入和数据展示场景。
423
-
424
- 同时提供 DepartmentSelect(部门选择组件),用于部门字段的单选/多选选择,交互与受控规范与 UserSelect 保持一致。
170
+ 内置用户前端组件:UserSelect(单选/多选用户选择器,onChange 返回用户对象)、DepartmentSelect(部门选择组件,交互与受控规范与 UserSelect 一致)、UserDisplay(用户信息展示组件)。均基于统一的 userid 数据,用于用户相关的表单输入和数据展示场景。
425
171
 
426
172
  ## 类型定义
427
173
 
@@ -448,17 +194,8 @@ export type User = {
448
194
 
449
195
  ## 当前用户信息的获取方案
450
196
 
451
- ### 常用场景
452
-
453
- - **用户信息展示**:在界面中显示当前用户名称和头像
454
- - **权限验证**:基于用户ID进行权限检查和控制
455
- - **数据关联**:在数据操作时关联当前用户信息
456
- - **日志记录**:记录用户操作日志时获取用户标识
457
-
458
197
  ### Hooks 方法: `useCurrentUserProfile` - 在 React 中获取当前用户信息
459
198
 
460
- ### 基本信息
461
-
462
199
  - **文件路径**:`@lark-apaas/client-toolkit/hooks/useCurrentUserProfile`
463
200
  - **功能**:获取当前登录用户的个人信息(含飞书 user_id)
464
201
  - **返回值**:`Partial<IUserProfile>`(初始为空对象 `{}`,异步获取后填充完整字段)
@@ -473,29 +210,18 @@ export type User = {
473
210
  | `avatar` | `string` | 用户头像 URL |
474
211
  | `lark_user_id` | `string` | 飞书 user_id,通过额外异步请求获取,可能晚于其他字段就绪 |
475
212
 
476
- > ⚠️ **空值处理(CRITICAL)**:Hook 初始返回空对象 `{}`(truthy)、字段均 `undefined`。**MUST** 用可选链,并以 `if (!userInfo?.user_id)` 判加载态(不是 `!userInfo`);`lark_user_id` 异步获取、可能为 `undefined`,使用前条件渲染。
477
-
478
- ### 使用方法
213
+ > ⚠️ **空值处理(CRITICAL)**:Hook 初始返回空对象 `{}`(truthy),**MUST** `if (!userInfo?.user_id)` 判加载态(不是 `!userInfo`);`lark_user_id` 可能为 `undefined`,使用前条件渲染。完整禁止行为清单与飞书 ID 转换指南(后端 `AuthNPaasService` 等)参见 `user-identity` skill。
479
214
 
480
215
  ```typescript
481
216
  import { useCurrentUserProfile } from "@lark-apaas/client-toolkit/hooks/useCurrentUserProfile";
482
217
 
483
218
  const MyComponent = () => {
484
219
  const userInfo = useCurrentUserProfile();
485
-
486
- // 正确:安全访问 + 加载态处理
487
220
  if (!userInfo?.user_id) return <div>加载中...</div>;
488
- return (
489
- <div>
490
- <p>{userInfo.name}</p>
491
- {userInfo.lark_user_id && <p>飞书 ID: {userInfo.lark_user_id}</p>}
492
- </div>
493
- );
221
+ return <p>{userInfo.name}</p>;
494
222
  };
495
223
  ```
496
224
 
497
- > **飞书 ID 转换详细指南**(后端 `AuthNPaasService` 用法、自定义转换接口等)参见 `user-identity` skill。
498
-
499
225
  ## 用户展示与选择方案
500
226
 
501
227
  {% if projectMeta['flags']['supportBusinessUser'] %}
@@ -509,7 +235,7 @@ const MyComponent = () => {
509
235
 
510
236
  {% else %}
511
237
 
512
- ##
238
+ ## UserSelect - 用户选择组件
513
239
 
514
240
  ### 基本信息
515
241
 
@@ -529,14 +255,9 @@ interface UserSelectProps {
529
255
  }
530
256
  ```
531
257
 
532
- ### 值类型说明
533
-
534
- - **单选模式** (`mode="single"`):值为 userid,返回为`IUserProfile` 对象
535
- - **多选模式** (`mode="multiple"`):值为 userid数组,返回为`IUserProfile` 数组
258
+ 值类型:单选模式(`mode="single"`)值为 userid、onChange 返回 `IUserProfile` 对象;多选模式(`mode="multiple"`)值为 userid 数组、onChange 返回 `IUserProfile` 数组。
536
259
 
537
- ### 使用示例
538
-
539
- #### 表单集成
260
+ ### 使用示例(表单集成)
540
261
 
541
262
  ```typescript
542
263
  import { useForm } from "react-hook-form";
@@ -545,10 +266,8 @@ import * as z from "zod";
545
266
  import { Form, FormControl, FormField, FormItem, FormLabel } from "@/components/ui/form";
546
267
  import { UserSelect } from "@lark-apaas/client-toolkit/components/User";
547
268
 
548
- // 定义表单验证schema
549
269
  const formSchema = z.object({
550
270
  assignee: z.string(),
551
- participants: z.array(z.string()).optional(),
552
271
  });
553
272
 
554
273
  const form = useForm<z.infer<typeof formSchema>>({
@@ -574,24 +293,8 @@ const form = useForm<z.infer<typeof formSchema>>({
574
293
  </FormItem>
575
294
  )}
576
295
  />
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
- />
296
+ {/* 多选字段差异仅:schema 用 z.array(z.string()),mode="multiple",
297
+ onChange={(users) => field.onChange(users.map((u) => u.user_id))} */}
595
298
  </form>
596
299
  </Form>
597
300
  ```
@@ -604,41 +307,24 @@ const form = useForm<z.infer<typeof formSchema>>({
604
307
  - **功能**:用于所有用户信息的展示场景
605
308
  - **特性**:显示用户**头像**和**姓名**,支持多用户展示
606
309
 
607
- IMPORTANT:当不传递showLabel时,组件会同时展示用户头像和姓名,如果只需要头像,则需要传递showLabel的值为false
608
-
609
310
  ### 属性定义
610
311
 
611
312
  ```typescript
612
313
  interface UserDisplayProps {
613
- users: string[]; // 用户id数组(必需)
314
+ users: string[]; // 用户id数组(必需),单个用户传 [userId]
614
315
  size?: "small" | "medium" | "large"; // 头像尺寸
615
316
  className?: string; // 自定义样式类名
616
- showLabel?: boolean; // 默认为true,会展示用户姓名,如果只需要展示头像则需要设置为false
317
+ showLabel?: boolean; // 默认为true,同时展示头像和姓名;只需要头像时必须显式传 false
617
318
  }
618
319
  ```
619
320
 
620
321
  ### 使用示例
621
322
 
622
- #### 基础用法
623
-
624
323
  ```jsx
625
324
  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
- />
325
+
326
+ // 单个或多个用户展示:users 传用户 id 数组
327
+ <UserDisplay users={project.participants} size="medium" className="project-members" />
642
328
  ```
643
329
 
644
330
  ## 使用注意事项
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: user-identity
3
- description: "Use when getting current user info/profile, displaying user name/avatar/email, converting miaoda userId to lark_user_id, or reading req.userContext fields (userId/roles/tenantId): useCurrentUserProfile, AuthNPaasService, FeishuID conversion. 触发词:用户身份, 用户信息, 用户资料, 当前用户, userProfile, useCurrentUserProfile, 飞书ID, FeishuID, 飞书用户ID, lark_user_id, 用户ID转换, AuthNPaasService, 用户上下文, userContext, userContext.roles, 用户角色, 当前用户角色, 获取请求者角色, 展示用户, 显示用户, 用户面板, 我是谁, 获取用户"
3
+ description: "Use when getting current user info/profile, displaying user name/avatar/email, converting miaoda userId lark_user_id (both directions via AuthNPaasService), or reading req.userContext fields (userId/roles/tenantId): useCurrentUserProfile, AuthNPaasService, FeishuID conversion. 触发词:用户身份, 用户信息, 用户资料, 当前用户, userProfile, useCurrentUserProfile, 飞书ID, FeishuID, 飞书用户ID, lark_user_id, 用户ID转换, AuthNPaasService, getBatchMiaodaUserIds, 飞书ID转妙搭, employee_id 转 userId, 用户上下文, userContext, userContext.roles, 用户角色, 当前用户角色, 获取请求者角色, 展示用户, 显示用户, 用户面板, 我是谁, 获取用户"
4
4
  steering: true
5
5
  steering-topic: user_identity
6
6
  match-template-name: nestjs-react-fullstack
@@ -77,7 +77,7 @@ match-template-name: nestjs-react-fullstack
77
77
  │ └─ 跳到 `feishu` skill 的 `references/id-convert.md`(spark id_convert type 20/21)
78
78
 
79
79
  ├─ 需要把 飞书 user_id(employee_id)反查成 妙搭 userId?
80
- │ └─ 无单步方案 ──→ `feishu` skill `references/id-convert.md` "反向两步走"
80
+ │ └─ 后端 ──→ `AuthNPaasService.getBatchMiaodaUserIds()`(第三节,SDK 一步,convertType 31);非模板项目 ──→ `feishu` skill 两步兜底
81
81
 
82
82
  └─ 需要自定义飞书 ID 转换接口?
83
83
  └─ 是 ──→ 注入 AuthNPaasService 编写 Controller(第四节,仅适用于 user_id)
@@ -102,7 +102,7 @@ match-template-name: nestjs-react-fullstack
102
102
  | `appId` | `string` | 应用 ID |
103
103
  | `loginUrl` | `string` | 登录跳转 URL |
104
104
  | `userType` | `string` | 用户类型(如 `_employee`) |
105
- | `env` | `string` | 环境(如 `preview`、`online`) |
105
+ | `env` | `string` | 环境(`preview` 预览态、`runtime` 发布运行态) |
106
106
  | `userName` | `string` | 用户名 |
107
107
  | `userNameI18n` | `{ zh_cn, en_us, ja_jp }` | 多语言用户名 |
108
108
  | `isSystemAccount` | `boolean` | 是否系统账号 |
@@ -146,9 +146,9 @@ export class TasksController {
146
146
 
147
147
  妙搭平台的用户 ID(`userId`)与飞书用户 ID 是两套独立体系。调用飞书 OpenAPI 时需要传入某种飞书侧 ID(`open_id` / `union_id` / `user_id` 任选其一,具体通过哪个参数指定取决于 API:消息 API 用 `receive_id_type`,多数其他 API 用 `user_id_type`,文档协作者用 `member_id_type`)。
148
148
 
149
- `AuthNPaasService` 暴露的飞书 ID 是 **`user_id`**(即 `employee_id`,飞书企业内的用户标识)这一种,且**只支持 妙搭 userId 飞书 user_id 单向转换**。
149
+ `AuthNPaasService` 暴露的飞书 ID 是 **`user_id`**(即 `employee_id`,飞书企业内的用户标识)这一种,支持 **妙搭 userId 飞书 user_id 双向**转换(正向 `getBatchLarkUserIds`/`getCurrentUserLarkUserId`,反向 `getBatchMiaodaUserIds`)。
150
150
 
151
- > 如果需要 `open_id` / `union_id`,或者需要"飞书 ID → 妙搭 userId"反向,请改用飞书开放平台 `spark id_convert` 接口,参见 `feishu` skill 的 `references/id-convert.md`。
151
+ > 如果需要 `open_id` / `union_id`(无论正反向),请改用飞书开放平台 `spark id_convert` 接口,参见 `feishu` skill 的 `references/id-convert.md`。`employee_id` ↔ 妙搭 userId 双向都在本 SDK 内(见下方方法表)。
152
152
 
153
153
  ### 后端 API
154
154
 
@@ -168,6 +168,10 @@ export class MyService {
168
168
  // 批量转换(最多 100 个)
169
169
  const larkUserIds = await this.authnService.getBatchLarkUserIds(['uid1', 'uid2']);
170
170
  // => ['<飞书 user_id>', null] 顺序与输入对应,失败项为 null
171
+
172
+ // 反向:飞书 user_id(employee_id) → 妙搭 userId
173
+ const miaodaUserIds = await this.authnService.getBatchMiaodaUserIds(['emp1', 'emp2']);
174
+ // => ['<妙搭 userId>', null] 顺序与输入对应,失败项为 null
171
175
  }
172
176
  }
173
177
  ```
@@ -176,6 +180,7 @@ export class MyService {
176
180
  |------|------|------|
177
181
  | `getCurrentUserLarkUserId` | `() → Promise<string \| null>` | 从请求上下文获取当前用户的飞书 ID |
178
182
  | `getBatchLarkUserIds` | `(userIds: string[]) → Promise<(string \| null)[]>` | 批量转换,最多 100 个,与输入顺序一一对应 |
183
+ | `getBatchMiaodaUserIds` | `(employeeIds: string[]) → Promise<(string \| null)[]>` | 反向:批量把飞书 user_id(employee_id)转为妙搭 userId,最多 100 个,顺序一一对应,失败项 `null`(底层 convertType 31) |
179
184
 
180
185
  ### 内置接口
181
186
 
@@ -249,6 +254,8 @@ export class FeishuIdController {
249
254
  }
250
255
  ```
251
256
 
257
+ > 反向转换(employee_id → 妙搭 userId)同理,把 `getBatchLarkUserIds` 换成 `getBatchMiaodaUserIds` 即可,无需新增 Controller。
258
+
252
259
  前端调用示例:
253
260
 
254
261
  ```typescript