@lark-apaas/coding-steering 0.1.17-alpha.0 → 0.1.17

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.17-alpha.0",
3
+ "version": "0.1.17",
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"
@@ -26,9 +26,7 @@ available-agents:
26
26
 
27
27
  - 先写完整标题序列放进 `scratchpad.md`。只读标题就应能看懂整份 deck 的逻辑(像书的目录)。检查是否形成清楚路径:背景 → 问题 → 洞察 → 方案 → 证据 → 下一步。
28
28
  - 选定一种标题语法并全程一致:要么名词短语("市场机会""产品架构"),要么简短判断句("新用户增长主要来自自然流量")。
29
- - 大字号标题需要换行时,按语义短语断行,避免单字成行、标点出现在行首或拆开固定短语;可通过调整文本区、字号或断行位置修正失衡。
30
29
  - 每页正文只服务本页标题,不塞旁支。封面、章节页、转场页、结尾页也要服务故事,不做纯装饰。
31
- - 文字要有明确功能:保留承担信息、导航、出处或真实品牌识别作用的文字;不为营造风格或填补空间而编造、重复无信息价值的辅助文字。
32
30
 
33
31
  避免这些"AI 味"标题(它们会暴露 deck 是 AI 生成的)——标题的任务是**定位页面、推进叙事**,不是替演讲者甩结论 / 喊 punchline:
34
32
 
@@ -81,14 +79,13 @@ deck 每页先判断它要让观众完成什么阅读动作:抓结论、看证
81
79
 
82
80
  视觉服务演示场景,不是网页首页。
83
81
 
84
- - **主题与风格**:根据主题、受众和演示场景提炼视觉关键词,用它们决定配色、字体、图片类型和页面节奏;不把“干净、专业”默认等同于大量留白或通用企业风。
85
82
  - **明暗按需求选择**:根据品牌 / 主题 / 图片素材 / 演示场景选择浅色、暗色或混合背景。无论选择哪种,都要保证投影、截图和后排阅读的对比度。明暗切换应落在叙事节点上(章节转换、关键强调),不要无来由地跳变。
86
83
  - 字体克制(1-2 套):展示字体可有个性,正文必须稳定可读;整体对比清楚、信息块边界明确。
87
84
  - 图片先判断内容和用途:摄影 / 氛围图可满版裁切;截图、图表、产品界面、架构图必须完整展示(aspect-fit),不能裁掉关键边界或文字;透明图 / 细线图放到有对比的底色上。图上压字用品牌常见方式保护可读性(遮罩、渐变、模糊或文字容器)。
88
85
  - 视觉要有节奏变化:全图页、大数字页、表格页、引用页、流程页、文字页交替出现;不要整套都是同一种卡片。也不要每页硬加描边卡片、固定结论框或装饰分割线——只有内容需要分组时才用容器。
89
86
  - 扁平基线:好的 deck 不依赖阴影和悬浮卡片堆叠。优先用全页背景、色带、分隔线、编号、表格网格、图片裁切、对比色块和尺度差建立层次。只有在需要表达真实物件、票券、照片或舞台层次时才少量使用阴影。
90
87
  - 不用 emoji、不临时手绘复杂假图;优先用用户素材、品牌资产、图标库或真实图片。
91
- - **空间重心**:页面元素应形成完整构图:留白有明确作用,独立元素与主体有可感知的关系,多栏内容在视觉重量和内容关系上协调。不预设内容必须集中在上方或占据固定比例。内容不足以独立成页时,合并、重构或升格为确有价值的观点页,不单纯放大数字、卡片或增加留白撑页。
88
+ - **空间重心**:内容集中在上方 2/3、底部留白,通常是**正确**的 slide 构图。看到 `align-items: flex-start` 加底部留空就想改成 `center`——那是网页设计的条件反射,忍住,留白是有意的。但留白有下界:内容页的主内容应占版心高度约 2/3 以上,撑不满就升格版式——放大成大数字、大卡片,或重排为居中的宣言页,而不是把原内容原样居中或任其悬浮在顶部。空本身不是缺陷,不均才是:同一页一处大空、一处拥挤,要重排。双栏页两列视觉重量要对等,悬殊时改 7:5 / 8:4 不对称栅格或并回单栏。
92
89
 
93
90
  ## HTML 实现(deck-stage 外壳)
94
91
 
@@ -117,8 +114,7 @@ deck 每页先判断它要让观众完成什么阅读动作:抓结论、看证
117
114
  ## 交付前自检
118
115
 
119
116
  - 每页 16:9,无溢出 / 重叠 / 裁切;字号符合投影阅读(正文没有小到像网页)。
120
- - 构图完整:留白与主体关系明确,没有孤立漂浮、局部拥挤或失衡的多栏布局。
121
- - 页码、来源和固定页脚不与正文争用空间;逐页检查文字、图片和固定元素没有遮挡或碰撞。
117
+ - 留白均匀:同一页没有一处大空、一处拥挤;内容页主内容占版心高度约 2/3 以上;双栏视觉重量对等。
122
118
  - 只读标题能讲通故事;标题语法全程一致,没有 punchline / takeaway 盒子。
123
119
  - 已建立 page-type map,并通过页型轮换避免单一上下布局。
124
120
  - 章节分隔页推动叙事,而不是只做装饰。
@@ -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
  ## 使用注意事项