@lark-apaas/coding-steering 0.1.6-alpha.0 → 0.1.6-alpha.1
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/.gitkeep +0 -0
- package/steering/nestjs-react-fullstack/tech.md +21 -0
- package/steering/nestjs-react-fullstack/skills/authn-guide/SKILL.md +0 -122
- package/steering/nestjs-react-fullstack/skills/client-add-aily-web-chat/SKILL.md +0 -139
- package/steering/nestjs-react-fullstack/skills/client-builtins-user-service/SKILL.md +0 -628
- package/steering/nestjs-react-fullstack/skills/code-fix/SKILL.md +0 -246
- package/steering/nestjs-react-fullstack/skills/coding-guide/SKILL.md +0 -707
- package/steering/nestjs-react-fullstack/skills/feishu/SKILL.md +0 -270
- package/steering/nestjs-react-fullstack/skills/feishu/references/approval.md +0 -214
- package/steering/nestjs-react-fullstack/skills/feishu/references/attendance.md +0 -163
- package/steering/nestjs-react-fullstack/skills/feishu/references/bitable.md +0 -309
- package/steering/nestjs-react-fullstack/skills/feishu/references/calendar.md +0 -190
- package/steering/nestjs-react-fullstack/skills/feishu/references/contacts.md +0 -160
- package/steering/nestjs-react-fullstack/skills/feishu/references/doc.md +0 -256
- package/steering/nestjs-react-fullstack/skills/feishu/references/drive.md +0 -103
- package/steering/nestjs-react-fullstack/skills/feishu/references/events.md +0 -198
- package/steering/nestjs-react-fullstack/skills/feishu/references/id-convert.md +0 -128
- package/steering/nestjs-react-fullstack/skills/feishu/references/messaging.md +0 -207
- package/steering/nestjs-react-fullstack/skills/feishu/references/oauth.md +0 -164
- package/steering/nestjs-react-fullstack/skills/feishu/references/perm.md +0 -90
- package/steering/nestjs-react-fullstack/skills/feishu/references/wiki.md +0 -164
- package/steering/nestjs-react-fullstack/skills/openapi-guide/SKILL.md +0 -267
- package/steering/nestjs-react-fullstack/skills/trigger-guide/SKILL.md +0 -452
- package/steering/nestjs-react-fullstack/skills/user-identity/SKILL.md +0 -300
|
@@ -1,707 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: coding-guide
|
|
3
|
-
description: "项目全局编码规范,必须在任何代码编写、阅读、修改、排查前加载。覆盖:项目结构、目录组织、文件命名、NestJS 后端(MVCS/Drizzle ORM/API 规范/异常处理/用户上下文)、React 19 前端(shadcn/ui/Tailwind/路由/样式/组件规范)、前后端联调(shared 类型/axiosForBackend)、数据库操作、插件能力、质量保障流程、日志规范。Use when: 编写任意前端或后端代码、新建页面或模块、修改接口或数据库操作、排查编译错误或运行时问题、代码审查、理解项目架构。"
|
|
4
|
-
steering: true
|
|
5
|
-
steering-inclusion: always
|
|
6
|
-
steering-topic: coding_guide
|
|
7
|
-
match-template-name: nestjs-react-fullstack
|
|
8
|
-
control-by-feature-ab: true
|
|
9
|
-
---
|
|
10
|
-
# 项目结构
|
|
11
|
-
|
|
12
|
-
## 根目录组织
|
|
13
|
-
|
|
14
|
-
```
|
|
15
|
-
├── client/ # React 前端
|
|
16
|
-
├── server/ # NestJS 后端
|
|
17
|
-
├── shared/ # 共享类型定义
|
|
18
|
-
└── package.json # 根配置
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
任何需要被代码引用的文件均需在 `client`、`server`、`shared` 目录中,你需要将文件复制到正确位置。
|
|
22
|
-
|
|
23
|
-
## 后端结构 (`server/`)
|
|
24
|
-
|
|
25
|
-
基于 MVCS 架构,**禁止新增一级目录**。
|
|
26
|
-
|
|
27
|
-
```
|
|
28
|
-
server/ # 符合 NestJS 项目基本规范
|
|
29
|
-
├── main.ts # 应用程序入口点,内容不可改动
|
|
30
|
-
├── app.module.ts # 根模块,模块需要在该文件中导入
|
|
31
|
-
├── config/ # 配置文件
|
|
32
|
-
│ └── app.config.ts # 主要应用配置
|
|
33
|
-
├── modules/ # 功能模块(领域驱动)。新 module 在 app.module.ts 中的引入必须在 ViewModule 之前!编写代码前请 glob `server/modules/**` 目录,检查已有的文件。注意,module 修改或编写完成后,必须检查 app.module.ts 中是否已经引入该模块。
|
|
34
|
-
│ ├── view/ # 模板渲染模块
|
|
35
|
-
│ └── hello/ # 演示 hello world 端点(参考该结构即可)。遵循 Nest.js 最佳实践进行文件组织
|
|
36
|
-
│ ├── hello.controller.ts # controller 示例
|
|
37
|
-
│ ├── hello.module.ts # module 示例
|
|
38
|
-
│ └── hello.service.ts # service 示例(可选)
|
|
39
|
-
├── database/ # Drizzle ORM 数据库相关。应用仅能进行 DML 操作。若用户需求设计表结构变更,DDL 相关操作需要使用 `ddl_sql` 工具进行。
|
|
40
|
-
│ ├── schema.ts # Drizzle ORM 数据库 Schema 定义。会在执行完 DDL 操作后自动生成,必须从该文件导入数据库类型,禁止自行编写 schema 文件。如有需要可调用 CodeGen 工具手动生成。
|
|
41
|
-
└── common/ # 共享工具和接口
|
|
42
|
-
│ ├── filters/ # 通用错误处理。
|
|
43
|
-
│ ├── constants/ # 通用常量。
|
|
44
|
-
│ └── utils/ # 通用工具方法。
|
|
45
|
-
shared/ # 前后端共享的目录
|
|
46
|
-
│ └── api.interface.ts # 前后端共享的类型定义
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## 前端结构 (`client/`)
|
|
50
|
-
|
|
51
|
-
```
|
|
52
|
-
client/
|
|
53
|
-
├── index.html # HTML 模板
|
|
54
|
-
├── public/ # 静态资源
|
|
55
|
-
├── src/
|
|
56
|
-
│ ├── index.tsx # React 应用入口点,内容不可改动
|
|
57
|
-
│ ├── index.css # 全局样式
|
|
58
|
-
│ ├── tailwind-theme.css # tailwindcss 全局css主题变量定制
|
|
59
|
-
│ ├── app.tsx # 主应用组件(包含路由定义)
|
|
60
|
-
│ ├── api/
|
|
61
|
-
│ │ └── index.ts # 所有请求后端的 API 的逻辑均应聚合在该文件内。请先读取该文件再修改,禁止直接编写该文件
|
|
62
|
-
│ ├── pages/ # 页面目录
|
|
63
|
-
│ │ ├── HomePage # 单个页面目录
|
|
64
|
-
│ │ | ├── HomePage.tsx # 页面文件
|
|
65
|
-
│ │ | └── HomeComponentA.tsx # 页面依赖的组件(非必须,除非页面文件大于 500 行,否则组件应该写在页面文件内)
|
|
66
|
-
│ │ └── NotFound # 404 页面所在目录
|
|
67
|
-
| │ | └── NotFound.tsx # 404 页面文件
|
|
68
|
-
│ ├── components/ # 可复用的 UI 组件
|
|
69
|
-
│ │ ├── ui/ # shadcn/ui 组件[Use the components for functionality, but heavily style them.]
|
|
70
|
-
│ │ │ ├── README.md # shadcn/ui 组件库使用说明 [请务必参考该文档进行组件使用]
|
|
71
|
-
{% if projectMeta['flags']['supportBusinessUser'] %}
|
|
72
|
-
│ │ ├── business-ui/ # 封装好的用户,部门选择组件以及展示组件
|
|
73
|
-
│ │ │ ├── README.md # 用户展示,用户头像展示,部门选择展示逻辑必须使用 [请务必参考该文档进行组件使用],禁止直接使用avatar组件等方式自行实现
|
|
74
|
-
{% endif %}
|
|
75
|
-
│ │ ├── Layout.tsx # 布局包装器
|
|
76
|
-
│ ├── hooks/ # 自定义 React hooks
|
|
77
|
-
│ └── utils/ # 工具函数
|
|
78
|
-
│ │ └── config.ts # 应用初始化执行的配置
|
|
79
|
-
shared/ # 前后端共享的目录
|
|
80
|
-
│ └── api.interface.ts # 前后端共享的类型定义
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
---
|
|
84
|
-
|
|
85
|
-
# 全局编码约定
|
|
86
|
-
|
|
87
|
-
## TypeScript 规范
|
|
88
|
-
|
|
89
|
-
- **TypeScript 优先**:所有代码都用 TypeScript 编写,具有适当的类型
|
|
90
|
-
- **路径别名**:`@client/` → `client/`;`@server/` → `server/`;`@shared/` → `shared/`。优先使用别名
|
|
91
|
-
- **环境配置**:不支持配置文件,写在代码中即可
|
|
92
|
-
- **命名约定**:Class → PascalCase,函数/变量 → camelCase,常量 → UPPER_CASE
|
|
93
|
-
- **跨子组件共享常量加语义前缀**:父级桶导出聚合多子组件时,子组件中相同语义的常量必须前缀化(如 `USER_DEFAULTS` / `ORDER_DEFAULTS`),禁止跨子组件复用裸通用名(`DEFAULT_CONFIG` / `STYLES` / `OPTIONS`),避免 barrel re-export 触发 `no-redeclare`
|
|
94
|
-
- **类型约束**:
|
|
95
|
-
- 使用 interface 定义类型,type 用于复杂类型。禁止 `any`
|
|
96
|
-
- 禁止自定义与 TypeScript 内置工具类型同名的类型(如 `Record`, `Omit`)
|
|
97
|
-
- 类型转换必须显式(如 `String(num)`),优先让 TS 自动推断泛型
|
|
98
|
-
- **禁止将 interface/type 作为值使用**(如 `instanceof`),运行时判断用 enum/const 对象
|
|
99
|
-
- **禁止 `{} as T` 和 `as any` 类型断言**:用 `useState<T | null>(null)` + 空值检查代替。修复类型错误时必须找到根因,禁止用 `as any` 绕过
|
|
100
|
-
- **`import type` 只能导入类型**:不能导入 const/enum/function(编译后被擦除→undefined)
|
|
101
|
-
- **变量和函数参数必须显式声明类型**(禁止隐式 `any`):所有变量声明必须有明确类型注解;`.map()/.filter()/.forEach()/.reduce()` 等回调的参数必须显式标注类型,否则触发 TS7006
|
|
102
|
-
|
|
103
|
-
```typescript
|
|
104
|
-
// ❌ 错误:变量无类型注解 → 回调参数隐式 any → TS7006
|
|
105
|
-
const items = await this.service.getItems();
|
|
106
|
-
items.map(item => item.name); // TS7006: item 隐式具有 any 类型
|
|
107
|
-
|
|
108
|
-
// ✅ 正确:变量显式类型 + 回调参数显式类型
|
|
109
|
-
const items: Item[] = await this.service.getItems();
|
|
110
|
-
items.map((item: Item) => item.name);
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
## shared/api.interface.ts 规范(CRITICAL)
|
|
114
|
-
|
|
115
|
-
- 新建/修改后端接口时,**必须先完成 shared/api.interface.ts 中的类型定义,再编写后端实现**
|
|
116
|
-
- 编写前后端代码前,**必须先读取该文件**,严格按照定义实现。禁止凭记忆编写
|
|
117
|
-
- **严格使用已定义的属性名和类型**,禁止使用不存在的属性名。需要新增字段必须先在此文件添加
|
|
118
|
-
- **import 类型前必须确认实际导出**:先读取文件确认类型名称存在且拼写一致,禁止臆造类型名
|
|
119
|
-
- 属性统一 camelCase(禁止 snake_case),server 端 schema 的 snake_case 不应泄漏到接口类型
|
|
120
|
-
- shared 目录是前后端共享文件,**禁止反向引用** `@server/*`、`@client/*` 等路径别名
|
|
121
|
-
|
|
122
|
-
## 代码质量约束
|
|
123
|
-
|
|
124
|
-
- **代码行长度**:单行不超过 80-120 字符,import 成员多时及时换行
|
|
125
|
-
- **嵌套层级**:不超过 4 层,用 early return 减少嵌套
|
|
126
|
-
- **单文件长度**:前端页面和服务端模块业务逻辑优先写在一个文件中。超过 500 行时必须按功能模块拆分,避免"上帝文件"。**自定义 Hook 超过 100 行时应拆分为多个 Hook**
|
|
127
|
-
- **大型闭包风险**:组件内大型回调/处理函数(如 handleApplyResults)应拆到独立文件(如 `apply-results.ts`),避免 Vite React Refresh 循环引用导致白屏
|
|
128
|
-
- **将风格问题视为编码错误**:项目使用 `@eslint/js` + `typescript-eslint` 推荐配置
|
|
129
|
-
- **ESLint 规范**(`@eslint/js` + `typescript-eslint`):
|
|
130
|
-
- 正则控制字符用 unicode 标志:`/\x1b\[/u`
|
|
131
|
-
- 避免无意义转义:字符串 `"'"` 非 `"\'"`, 正则 `[.]` 非 `[\.] `
|
|
132
|
-
- switch-case 中声明变量用 `{}` 包裹作用域
|
|
133
|
-
|
|
134
|
-
## 依赖使用规范
|
|
135
|
-
|
|
136
|
-
1. 优先使用项目已有依赖,仅在无法实现时安装新依赖
|
|
137
|
-
2. 使用前先查看 `package.json` 确保依赖已存在
|
|
138
|
-
3. **子包完整性检查**:部分库有多个子包(如 `@dnd-kit/core` + `@dnd-kit/sortable` + `@dnd-kit/utilities`),添加 import 后必须确认 package.json 中包含所有需要的子包
|
|
139
|
-
4. 用法不清时查看 readme,可进一步搜索或网页访问获取信息
|
|
140
|
-
|
|
141
|
-
## 文件命名约定
|
|
142
|
-
|
|
143
|
-
- **语言**:文件/文件夹命名全部英文
|
|
144
|
-
- **组件**:PascalCase(`HelloWorld.tsx`)
|
|
145
|
-
- **模块**:kebab-case 文件夹,PascalCase 文件(`hello/hello.controller.ts`)
|
|
146
|
-
- **配置**:dot.case(`app.config.ts`)
|
|
147
|
-
- **导入导出**:使用桶导出,优先命名导出,使用路径别名避免相对导入;**同一符号 `export const X` 与底部 `export { X }` 二选一**,优先底部桶导出作为单一来源,禁止行内 + 桶导出双轨
|
|
148
|
-
|
|
149
|
-
## 开发环境与内置服务
|
|
150
|
-
|
|
151
|
-
- **登录/注册/用户系统**:内置,禁止自行实现
|
|
152
|
-
- 项目依赖已安装完整,前后端 devServer 已启动并自动重启(无需怀疑),文件变更自动热重载
|
|
153
|
-
- `/api` 和 `/openapi` 代理已配置,前端 TS 严格模式已禁用,后端已启用
|
|
154
|
-
- Rspack 用于构建,已配置好,**禁止修改**
|
|
155
|
-
|
|
156
|
-
## 质量保障流程
|
|
157
|
-
|
|
158
|
-
修改代码后:
|
|
159
|
-
1. 运行代码检查工具检查语法错误
|
|
160
|
-
2. 改动服务端代码 → 进行接口测试,确保新增接口测试通过
|
|
161
|
-
3. 提交前 **必须** 读取相关日志确认无错误(服务端: server/server-devserver 日志;客户端: client-devserver 日志)
|
|
162
|
-
|
|
163
|
-
如果用户反馈编译失败、服务无法启动:
|
|
164
|
-
1. 调用代码检查工具
|
|
165
|
-
2. 读取 server-devserver 和 client-devserver 日志
|
|
166
|
-
3. dev 服务无响应时可 `pkill -f "dev:server"` / `pkill -f "dev:client"` 触发重启
|
|
167
|
-
|
|
168
|
-
---
|
|
169
|
-
|
|
170
|
-
# 后端开发指南
|
|
171
|
-
|
|
172
|
-
- **运行时**: Node.js >=22.0.0
|
|
173
|
-
- **框架**: NestJS 10.x + TypeScript,每个功能组织为 NestJS 模块,利用内置 DI 容器
|
|
174
|
-
- **控制器-服务模式**:Controller 处理 HTTP 请求响应,Service 负责业务逻辑和数据层交互
|
|
175
|
-
- **通信协议**:仅支持标准 HTTP(POST/GET/PUT/PATCH/DELETE),不支持 SSE/WebSocket。流式输出改为一次性返回;状态同步用短轮询
|
|
176
|
-
- **模板引擎**: Nunjucks
|
|
177
|
-
- **数据库**:Drizzle ORM + Postgres
|
|
178
|
-
- **验证(可选)**: class-validator + class-transformer
|
|
179
|
-
- **一方插件**:服务端内置 AI 和飞书相关插件,PluginInstance 属于平台插件调用链路,不等同于自建 SSE/WS。涉及插件调用**必须先阅读 Plugin 集成指南**。**调用侧选择原则**:结果不需存储(即时展示/发消息)→ Client 侧调用;结果需要持久化到数据库 → Server 侧调用并落库,或 Client 侧调用后通过 CRUD 接口保存
|
|
180
|
-
- **聚合文件与模块边界(CRITICAL — 启动失败 + 并发编辑冲突 Top 原因)**:
|
|
181
|
-
|
|
182
|
-
项目有三个**聚合文件**:`server/app.module.ts`、`client/src/app.tsx`、`client/src/api/index.ts`。它们是所有业务模块的汇聚入口,**必须由主 agent 在派发任何业务模块任务之前一次性串行预填**,业务模块任务(可能并行执行)**不得编辑**这三个文件。多方并发编辑同一聚合文件会触发 `old_string` mismatch 和 LLM 兜底重写,显著拖慢生成。
|
|
183
|
-
|
|
184
|
-
**主 agent 预填职责**(规划阶段产出**业务模块清单**后立即执行,任务派发前完成):
|
|
185
|
-
|
|
186
|
-
- 清单命名约定:目录名 / 路由 / namespace / `api_prefix` 末段统一 `kebab-case`;`module_class` / 页面组件名统一 `PascalCase`;目录名与模块 `name` 一致
|
|
187
|
-
- **严格串行**编辑以下三个文件(一次一个 tool 调用):
|
|
188
|
-
- `server/app.module.ts`:在文件顶部追加 `import { UsersModule } from './modules/users/users.module';`,在 `@Module` 的 `imports` 数组里加入 `UsersModule`;`ViewModule` 必须保持 `imports` 数组最后一项(fallback 路由)
|
|
189
|
-
- `client/src/app.tsx`:顶部追加 `import UsersPage from './pages/users';`,在 `<Routes>` 内保留 `<Route index element={<Welcome />} />`,并加 `<Route path="users" element={<UsersPage />} />`
|
|
190
|
-
- `client/src/api/index.ts`:每行 `export * as <namespace> from './<name>';`(用命名空间导出避免跨模块 export 名冲突)
|
|
191
|
-
- 预填完成后才派发业务模块任务;聚合文件引用的符号(`UsersModule` / `UsersPage` / `./users`)在任务产出前暂时悬空,任务完成后自然对齐
|
|
192
|
-
|
|
193
|
-
**业务模块任务的工作边界**:
|
|
194
|
-
|
|
195
|
-
- 产出只能落在以下目录之一:`server/modules/<name>/`(Module / Controller / Service / DTO)、`client/src/pages/<name>/`、`client/src/api/<name>/`
|
|
196
|
-
- **禁止编辑** `server/app.module.ts` / `client/src/app.tsx` / `client/src/api/index.ts`(已由主 agent 预填)
|
|
197
|
-
- 跨模块依赖(不涉及聚合文件):在源 Module 的 `exports` 中导出即可
|
|
198
|
-
- 若开发中发现规划外的跨模块需求(罕见,如发现需要一个主 agent 未规划的 shared helper 模块),**不要擅自改聚合文件**,在完成摘要里声明:
|
|
199
|
-
|
|
200
|
-
```yaml
|
|
201
|
-
cross_module_needs:
|
|
202
|
-
- type: extra_module # 或 extra_route / extra_provider
|
|
203
|
-
reason: "需要 SharedAuthModule 让 UsersController 使用 @Auth"
|
|
204
|
-
module_class: SharedAuthModule
|
|
205
|
-
module_file: server/modules/shared-auth/shared-auth.module.ts
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
主 agent 在所有模块任务完成后统一串行处理 `cross_module_needs`(补丁阶段,仍是单 agent 编辑聚合文件,无并发冲突)
|
|
209
|
-
|
|
210
|
-
- **@Injectable() Service 注册**:创建 `@Injectable()` Service → 在对应 Module 的 `providers` 中注册(这条在模块目录内部完成,与聚合文件无关)
|
|
211
|
-
- **三方集成**:调用第三方 API 需在后端实现,使用 @nestjs/axios
|
|
212
|
-
- **能力边界**:服务端不支持文件上传(FaaS 限制),前端用 dataloom SDK 上传,服务端仅保存元信息
|
|
213
|
-
- **环境判断**:`process.env.NODE_ENV === "production"` 表示生产环境
|
|
214
|
-
- **文件系统**:**严禁使用 `fs` 写入非 `/tmp` 路径**(无状态 FaaS 容器)
|
|
215
|
-
|
|
216
|
-
## 日志约定
|
|
217
|
-
|
|
218
|
-
- 后端禁止 console,**必须总使用** `@nestjs/common` 的 Logger(无 info 方法,用 `logger.log` 代替)
|
|
219
|
-
- Logger 参数必须为 string,对象需 `JSON.stringify`
|
|
220
|
-
- 输出完整错误堆栈
|
|
221
|
-
|
|
222
|
-
## Database
|
|
223
|
-
|
|
224
|
-
**必须使用注入的 Drizzle 实例**,禁止自建连接:
|
|
225
|
-
|
|
226
|
-
```typescript
|
|
227
|
-
import { DRIZZLE_DATABASE, type PostgresJsDatabase } from "@lark-apaas/fullstack-nestjs-core";
|
|
228
|
-
|
|
229
|
-
@Injectable()
|
|
230
|
-
export class TestService {
|
|
231
|
-
constructor(@Inject(DRIZZLE_DATABASE) private readonly db: PostgresJsDatabase) {}
|
|
232
|
-
}
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
- **每次编写或修改数据库操作代码前,必须重新读取 `server/database/schema.ts`**——该文件由系统在 DDL 执行后自动重新生成,内容随时可能变化。禁止凭记忆或之前读取的版本编写字段名和类型,否则会引用不存在的字段导致 TS2339 批量报错
|
|
236
|
-
- **禁止事务**(`db.transaction`),每个操作独立原子
|
|
237
|
-
- 条件查询用三元分支,禁止 `let query` 重赋值(类型不兼容):
|
|
238
|
-
|
|
239
|
-
```typescript
|
|
240
|
-
// ✅ 条件查询
|
|
241
|
-
const conditions = [];
|
|
242
|
-
if (status) conditions.push(eq(users.status, status));
|
|
243
|
-
const query = conditions.length > 0
|
|
244
|
-
? db.select().from(users).where(and(...conditions))
|
|
245
|
-
: db.select().from(users);
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
- Count 查询:
|
|
249
|
-
```typescript
|
|
250
|
-
// 简单 count
|
|
251
|
-
const result = await this.db.select({ count: count() }).from(users).where(eq(users.status, "active"));
|
|
252
|
-
// 子查询 count(关联计数)
|
|
253
|
-
const users = await this.db.select({
|
|
254
|
-
...users,
|
|
255
|
-
postsCount: this.db.$count(posts, eq(posts.authorId, users.id)),
|
|
256
|
-
}).from(users);
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
### userProfile 自定义类型
|
|
260
|
-
|
|
261
|
-
`userProfile` 是 Drizzle ORM custom_type,TypeScript 中对应 `string`。所有相关变量**必须显式类型注解**,禁止隐式推断。
|
|
262
|
-
|
|
263
|
-
```typescript
|
|
264
|
-
// schema 示例
|
|
265
|
-
export const mockTable = pgTable("mock_table", {
|
|
266
|
-
adminUser: userProfile("admin_user"), // custom_type, TS 中为 string
|
|
267
|
-
});
|
|
268
|
-
// 使用示例
|
|
269
|
-
const userId: string = "user123"; // ✅ 显式注解
|
|
270
|
-
await db.insert(users).values({ adminUser: userId });
|
|
271
|
-
await db.select().from(users).where(eq(users.adminUser, userId));
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
### 使用侧边界陷阱
|
|
275
|
-
|
|
276
|
-
- **`count()` 返回 string**(PostgreSQL bigint)。
|
|
277
|
-
|
|
278
|
-
- **UUID 列 `inArray` 必须显式 `::uuid[]`**:标准 `inArray(col, ids)` 会运行时报 `42809: op ANY/ALL (array) requires array on right side`。
|
|
279
|
-
|
|
280
|
-
```typescript
|
|
281
|
-
where: sql`${tasks.id} = ANY(${ids}::uuid[])`
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
- **`customTimestamptz` 跨网络后是 string**:service 内查询返回 `Date`,但 API 响应经 JSON 序列化后前端拿到 ISO string。`shared/api.interface.ts` 中 timestamptz 字段声明为 `string`,前端需要 Date 时手动 `new Date(value)`。
|
|
285
|
-
|
|
286
|
-
### 数据库使用强约束
|
|
287
|
-
|
|
288
|
-
- 仅通过 schema.ts 暴露的客户端和类型读写,禁止手写 SQL/临时类型
|
|
289
|
-
- **变更流程(CRITICAL)**:`execute_sql` 执行 DDL → 系统自动 codegen → **立即重新读取 schema.ts** → 再编写业务代码。跳过重新读取直接编码是 TS2339 批量错误的主要根因
|
|
290
|
-
- 连接失败/SSL 错误:立即停机并提示用户联系技术支持
|
|
291
|
-
|
|
292
|
-
## API 规范
|
|
293
|
-
|
|
294
|
-
1. **必须遵循 JSON API 规范**:GET=读、POST=创建、PUT=全量更新、PATCH=部分更新、DELETE=删除
|
|
295
|
-
2. **路径前缀约定**:
|
|
296
|
-
- `/api` — 内部业务接口(默认):`@Controller('api/hello')`
|
|
297
|
-
- `/openapi` — 对外开放接口(鉴权/用户身份规则不同,详见 `openapi-guide` skill):`@Controller('openapi/hello')`
|
|
298
|
-
3. **路由设计最佳实践**:
|
|
299
|
-
- 静态路由在前,动态路由在后(`/search` 必须在 `/:id` 之前)
|
|
300
|
-
- 使用描述性路径如 `/meetings/detail/:id` 避免冲突
|
|
301
|
-
4. **前后端一致性**(CRITICAL):
|
|
302
|
-
- 前端 API 调用的 HTTP method 和 path **必须**与后端 Controller 装饰器完全匹配(`GET≠POST` → 405)
|
|
303
|
-
- 后端 Service 返回值结构**必须**与 `shared/api.interface.ts` 中定义的响应类型完全匹配(禁止后端直接返回数组而 shared 定义为 `{items: T[]}`)
|
|
304
|
-
- 编写前端 API 时必须先读取对应 Controller 确认 method 和路径
|
|
305
|
-
5. **路由注册验证**:创建新 Controller 后必须用接口测试工具验证路由是否生效。**遇到 404 排查路径**:① 检查 `@Controller(...)` 是否以 `api/` 或 `openapi/` 开头 ② 检查 Module 是否在 `app.module.ts` 注册 ③ 检查静态路由是否在动态路由 `/:id` 之前
|
|
306
|
-
6. **@Query/@Param 类型转换**:默认 string,必须在 controller 层手动转换(如 `parseInt(limit, 10)`)
|
|
307
|
-
7. **写操作加 @NeedLogin**:POST/PUT/PATCH/DELETE 接口显式加 `@NeedLogin()` 装饰器:
|
|
308
|
-
```typescript
|
|
309
|
-
import { NeedLogin } from "@lark-apaas/fullstack-nestjs-core";
|
|
310
|
-
@NeedLogin()
|
|
311
|
-
@Post()
|
|
312
|
-
async createItem(@Req() req, @Body() dto) { ... }
|
|
313
|
-
```
|
|
314
|
-
8. **OpenAPI 文档同步**:改动 `*.openapi.controller.ts` 或其引用的 interface / service 返回值 / schema 字段时,加载 `openapi-guide` skill,同步更新 `docs/openapi.json`
|
|
315
|
-
|
|
316
|
-
## 异常处理
|
|
317
|
-
|
|
318
|
-
| 分层 | 目标 |
|
|
319
|
-
|------|------|
|
|
320
|
-
| service | 抛出业务异常 |
|
|
321
|
-
| controller | 不处理异常,交全局 Error Filter |
|
|
322
|
-
|
|
323
|
-
## 当前用户信息
|
|
324
|
-
|
|
325
|
-
- **必须从 `req.userContext` 获取**,禁止从前端传递,禁止硬编码
|
|
326
|
-
- 依赖用户信息的接口不要用 API 测试工具测试,用 `think` 工具思考
|
|
327
|
-
|
|
328
|
-
```typescript
|
|
329
|
-
// req.userContext 字段(由身份认证中间件挂载):
|
|
330
|
-
// - userId/tenantId/appId: string
|
|
331
|
-
// - env: 'preview' | 'runtime' (preview=预览态, runtime=发布运行态)
|
|
332
|
-
// - userName/userNameEn: string
|
|
333
|
-
// - userNameI18n: string (多语名字, 如 {zh_cn: '用户', en_us: 'user'})
|
|
334
|
-
|
|
335
|
-
@Post('articles')
|
|
336
|
-
async createArticle(@Req() req: Request, @Body() dto: CreateArticleDto) {
|
|
337
|
-
const { userId } = req.userContext;
|
|
338
|
-
return this.articleService.create({ ...dto, author: userId });
|
|
339
|
-
}
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
## 插件能力(Plugin)
|
|
343
|
-
|
|
344
|
-
### 召回规则(强制执行)
|
|
345
|
-
|
|
346
|
-
当用户需求涉及以下关键词时,**必须查询相应 Skill 获取开发指南**:
|
|
347
|
-
|
|
348
|
-
| 关键词 | 对应能力 |
|
|
349
|
-
|-------|---------|
|
|
350
|
-
| 多维表格、飞书表格、Base、bitable | 飞书多维表格增删改查 |
|
|
351
|
-
| 飞书消息、发送通知、消息推送 | 发送飞书消息 |
|
|
352
|
-
| 飞书群、创建群组、群聊 | 创建飞书群组 |
|
|
353
|
-
| AI生文、AI写作、文本生成 | AI智能生文 |
|
|
354
|
-
| AI总结、文本摘要、内容提炼 | AI文本总结 |
|
|
355
|
-
| AI翻译、多语言翻译、语言互译 | AI智能翻译 |
|
|
356
|
-
| AI分类、文本分类、自动归类 | AI智能分类 |
|
|
357
|
-
| 文本转JSON、结构化提取、文本解析 | AI文本转JSON |
|
|
358
|
-
| AI生图、图片生成、智能配图 | AI智能生图 |
|
|
359
|
-
| 图生图、图片编辑、图片修改 | AI图片编辑 |
|
|
360
|
-
| 图片理解、图片识别、图片分析 | AI图片理解 |
|
|
361
|
-
| 图片转JSON、图片提取、图片结构化 | AI图片转JSON |
|
|
362
|
-
| 图片对比、图片比较、图片差异 | AI图片对比 |
|
|
363
|
-
| 抠图、去背景、去水印、去文字 | AI智能抠图 |
|
|
364
|
-
| 背景替换、换背景、背景合成 | AI背景替换 |
|
|
365
|
-
| 文档解析、PDF解析、文件提取 | AI文档解析 |
|
|
366
|
-
| 搜索总结、网页搜索、搜索摘要 | AI搜索总结 |
|
|
367
|
-
| 语音合成、文本转语音、TTS | AI语音合成 |
|
|
368
|
-
| 语音识别、音频转文字、STT | AI语音转文字 |
|
|
369
|
-
| 插件实例、PluginInstance、Capability | 通用插件调用 |
|
|
370
|
-
|
|
371
|
-
### 插件配置完整性(CRITICAL)
|
|
372
|
-
|
|
373
|
-
调用 `capabilityClient.load(pluginInstanceId)` 前**必须确认**:
|
|
374
|
-
1. `server/capabilities/` 下存在对应 `.json` 配置文件
|
|
375
|
-
2. 配置 `id` 与代码中 `pluginInstanceId` 完全匹配
|
|
376
|
-
3. 不存在则**必须先用 `plugin_instance` 工具创建配置**
|
|
377
|
-
|
|
378
|
-
```typescript
|
|
379
|
-
// ❌ 调用不存在的插件(CapabilityNotFoundError — 开发态 Top 错误)
|
|
380
|
-
capabilityClient.load('feishu_monthly_demand'); // server/capabilities/ 下无此配置
|
|
381
|
-
|
|
382
|
-
// ✅ load 失败时停止后续请求
|
|
383
|
-
try {
|
|
384
|
-
const result = await plugin.call('read_records', input);
|
|
385
|
-
} catch (err) {
|
|
386
|
-
if (err.name === 'CapabilityNotFoundError') { setConfigError(true); return; }
|
|
387
|
-
throw err;
|
|
388
|
-
}
|
|
389
|
-
```
|
|
390
|
-
**缓存 load 结果**,避免重复 load 放大错误请求(单应用可达每页 4-6 次失败)。
|
|
391
|
-
|
|
392
|
-
### 关键区分:多维表格 vs 数据库表
|
|
393
|
-
|
|
394
|
-
| 场景 | 判断 | 操作 |
|
|
395
|
-
|-----|------|-----|
|
|
396
|
-
| 明确提到"多维表格/飞书/Base/bitable" | 飞书平台外部服务 | 召回 `plugin_guide` |
|
|
397
|
-
| "建表/DDL/schema变更" | 应用内数据库结构 | 使用 `ddl_sql` 工具 |
|
|
398
|
-
| "往XX表插入数据"(无明确来源) | 检查 `schema.ts` | 有定义→Drizzle ORM;无定义→询问用户确认 |
|
|
399
|
-
|
|
400
|
-
### 多维表格应用的数据架构选择
|
|
401
|
-
|
|
402
|
-
当应用需要读取飞书多维表格数据时,根据**查询模式**选择架构:
|
|
403
|
-
|
|
404
|
-
| 查询模式 | 架构选择 | 实现方式 |
|
|
405
|
-
|---------|---------|---------|
|
|
406
|
-
| 展示/编辑单条记录 | 纯插件 | `getRecord` / `batchUpdateRecords` |
|
|
407
|
-
| 列表分页浏览 | 纯插件 | `searchRecords` + `pageToken` 游标分页 |
|
|
408
|
-
| 统计聚合(计数/求和/平均) | **纯插件 + aggregateQuery** | 禁止 searchRecords 全量拉取后内存计算 |
|
|
409
|
-
| 统计 + 明细下钻 | 纯插件 | 聚合用 `aggregateQuery`,下钻用 `searchRecords` + filter |
|
|
410
|
-
| 排行榜 / TOP N | 纯插件 | `searchRecords` + sort + pageSize=N |
|
|
411
|
-
| 复杂排序/多表关联/全文搜索 | 插件同步 + 本地数据库 | 定时/Webhook 同步到 postgres,复杂查询走数据库 |
|
|
412
|
-
| 高频写入 + 读取 | 本地数据库为主 | 多维表格仅作展示/备份 |
|
|
413
|
-
|
|
414
|
-
**快捷判断:**
|
|
415
|
-
- 用户说"仪表盘/dashboard/驾驶舱/看板/统计" → **必须用 `aggregateQuery`**
|
|
416
|
-
- 用户说"列表/明细/详情" → `searchRecords` 分页
|
|
417
|
-
- 用户说"排行榜/TOP N" → `searchRecords` + sort + pageSize=N
|
|
418
|
-
- 需要跨表关联或复杂计算 → 建本地数据库表 + 同步
|
|
419
|
-
|
|
420
|
-
### 使用方式
|
|
421
|
-
1. **复用优先**:先查询插件的 Skills,查看已有的候选插件实例 `available_plugin_instances`。
|
|
422
|
-
2. **查 Runtime Spec**:对候选插件实例调用 `get_plugin_ai_json(pluginInstanceId)`,以返回的 `actions[].key / inputSchema / outputSchema / outputMode` 作为**唯一权威**。
|
|
423
|
-
3. **不能复用才创建/更新**:通过 `plugin_instance` 工具 CREATE/UPDATE 生成或调整单文件 PluginInstance 配置。**绝对禁止**使用 `multi_edit` 工具和 `write` 工具来直接修改 `server/capabilities/` 目录下的内容。
|
|
424
|
-
4. **生成调用代码**:必须先调用 `get_plugin_ai_json(pluginInstanceId)` 获取输入输出要求,严格按 schema 与 outputMode 生成调用逻辑。
|
|
425
|
-
> 详细用法、配置格式、代码示例请参阅 `plugin_guide` 文档。
|
|
426
|
-
|
|
427
|
-
### 插件调用 API 签名
|
|
428
|
-
|
|
429
|
-
```typescript
|
|
430
|
-
// Client 侧
|
|
431
|
-
capabilityClient.load(pluginInstanceId).call(actionKey, input) // 非流式
|
|
432
|
-
capabilityClient.load(pluginInstanceId).callStream(actionKey, input) // 流式
|
|
433
|
-
// Server 侧
|
|
434
|
-
capabilityService.load(pluginInstanceId).call(actionKey, input)
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
| 参数 | 说明 |
|
|
438
|
-
|------|------|
|
|
439
|
-
| `pluginInstanceId` | 插件实例 ID,如 `'booking_notification_feishu'` |
|
|
440
|
-
| `actionKey` | 来自 `get_plugin_ai_json` 返回的 `actions[].key` |
|
|
441
|
-
| `input` | 结构必须符合 `actions[].inputSchema` |
|
|
442
|
-
|
|
443
|
-
```typescript
|
|
444
|
-
// ❌ 错误:把参数对象作为 actionKey 传入
|
|
445
|
-
plugin.call(JSON.stringify({ meeting_title: '...' }));
|
|
446
|
-
// ❌ 错误:漏掉 actionKey,直接传参数对象
|
|
447
|
-
plugin.call({ meeting_title: '...' });
|
|
448
|
-
// ✅ 正确:actionKey + input 分开传
|
|
449
|
-
plugin.call('send_feishu_message', { meeting_title: '...' });
|
|
450
|
-
```
|
|
451
|
-
|
|
452
|
-
使用方式:复用优先 → 查 Runtime Spec(`get_plugin_ai_json`)→ 不能复用才创建 → 严格按 schema 生成调用代码。**绝对禁止**用 `multi_edit`/`write` 工具直接修改 `server/capabilities/` 目录下的内容。
|
|
453
|
-
|
|
454
|
-
## 自动化任务
|
|
455
|
-
|
|
456
|
-
需要设计自动化任务配置与开发时,请调用文档工具召回自动化任务配置与代码编写文档
|
|
457
|
-
|
|
458
|
-
## 分页最佳实践
|
|
459
|
-
|
|
460
|
-
### 传统分页(后台管理表格、跳页场景)
|
|
461
|
-
- 1-indexed page,`@Max()` 限制 pageSize
|
|
462
|
-
- 响应含 items/total/page/pageSize,page 超出返回空数组
|
|
463
|
-
- 深分页性能差,大数据集考虑游标分页
|
|
464
|
-
|
|
465
|
-
### 游标分页(移动端/无限滚动,优先推荐)
|
|
466
|
-
- 使用「唯一 + 可排序」字段作为游标排序依据,默认 创建时间+id 降序
|
|
467
|
-
- cursor(首次空) + limit(默认12, @Max(50))
|
|
468
|
-
- 响应仅 items/nextCursor/hasMore,不返回 total
|
|
469
|
-
- **最后一页时 nextCursor 必须为 undefined**
|
|
470
|
-
- DESC 降序配 LessThan(游标),ASC 升序配 GreaterThan(游标)
|
|
471
|
-
|
|
472
|
-
## 问题排查指引
|
|
473
|
-
|
|
474
|
-
1. 使用 API 测试工具测试不依赖用户信息的接口
|
|
475
|
-
2. 积极使用 `read_logs` 工具,如无有效日志可增加 logger 打印
|
|
476
|
-
3. DevServer 停止响应时 `pkill` 对应进程触发重启
|
|
477
|
-
4. API 测试返回 HTML 内容时:检查模块注册顺序、路由顺序、请求路径。禁止修改内置 ViewController
|
|
478
|
-
|
|
479
|
-
---
|
|
480
|
-
|
|
481
|
-
# 前端开发指南
|
|
482
|
-
|
|
483
|
-
## 技术栈
|
|
484
|
-
|
|
485
|
-
- **框架**: React 19 + TypeScript
|
|
486
|
-
- **路由**: React Router DOM v6
|
|
487
|
-
- **样式**: styled-jsx + tailwindcss(语义化 token)。styled-jsx 使用前提见下方"样式开发"
|
|
488
|
-
- **UI 组件库**: shadcn/ui — Use components for functionality, heavily style them
|
|
489
|
-
- **图表**: ReactECharts,**开发前必须调用 `/charts-skill`**
|
|
490
|
-
- **图标**: Lucide React(唯一图标库,禁止 Emoji 和其他图标库)
|
|
491
|
-
- **表格/表单/图表**: 见下方"组件 Skill 召回规则",开发前必须先调用对应 Skill
|
|
492
|
-
{% if projectMeta['flags']['supportBusinessUser'] %}
|
|
493
|
-
- **用户**: 用户信息展示/选择必须用 `business-ui` 组件(阅读 README.md),禁止直接展示 userId
|
|
494
|
-
{% endif %}
|
|
495
|
-
{% if projectMeta['flags']['supportTiptapAndStreamdown'] %}
|
|
496
|
-
- **富文本**: `business-ui/tiptap-editor`(阅读 README.md)
|
|
497
|
-
- **Markdown 渲染**: `components/ui/streamdown`(内置 prose 排版)
|
|
498
|
-
{% endif %}
|
|
499
|
-
|
|
500
|
-
## API 请求
|
|
501
|
-
|
|
502
|
-
**禁止** `fetch`,必须使用 `axiosForBackend` **函数调用方式**(不用会报 `Tenant not found`):
|
|
503
|
-
|
|
504
|
-
```typescript
|
|
505
|
-
import { axiosForBackend } from '@lark-apaas/client-toolkit/utils/getAxiosForBackend';
|
|
506
|
-
// ✅ axiosForBackend({ url: '/api/users', method: 'GET' })
|
|
507
|
-
// ❌ axiosForBackend.get(...) / .post(...) / .put(...) / .delete(...) ← 不是 axios 实例,所有实例方法都会 TypeError
|
|
508
|
-
```
|
|
509
|
-
|
|
510
|
-
- **前后端联调**:编写前端 API 对接代码前**必须先读取后端接口定义**,禁止对后端接口请求进行兜底和过度封装
|
|
511
|
-
- **全栈项目禁止 mock 数据**:只要有服务端接口就当全栈对待,严禁 mock。纯前端项目(静态展示、无后端)可以使用 mock
|
|
512
|
-
- **提交前检查 mock 残留**:禁止硬编码用户 ID、项目 ID 等 mock 数据。提交前确认无 `"user_xxx"`、`"project_1"` 等占位值
|
|
513
|
-
|
|
514
|
-
## 日志与错误处理
|
|
515
|
-
|
|
516
|
-
- **日志**: 必须用 `logger` from `@lark-apaas/client-toolkit/logger`,**禁止所有 console 方法**
|
|
517
|
-
- **禁止静默处理异常**:显示明确错误信息,禁止掩盖问题
|
|
518
|
-
- **无内置多语言/深浅色切换**:如需要需自行实现
|
|
519
|
-
|
|
520
|
-
## 工程规范
|
|
521
|
-
|
|
522
|
-
- **glob 文件查找**:目录存在嵌套,glob 前端页面时必须使用 `client/src/pages/**`
|
|
523
|
-
- **先看后写**:修改文件前先理解已有代码风格,模仿现有 pattern 和库的使用方式
|
|
524
|
-
|
|
525
|
-
## 官方内置组件
|
|
526
|
-
|
|
527
|
-
| 组件 | 来源 | 用途 |
|
|
528
|
-
|------|------|------|
|
|
529
|
-
| Table | `@lark-apaas/client-toolkit/antd-table` | 数据表格,**先调 `/table-skill`** |
|
|
530
|
-
{% if projectMeta['flags']['supportBusinessUser'] %}
|
|
531
|
-
| UserSelect/UserDisplay/UserProfile/DepartmentSelect | `business-ui/*` | 用户/部门选择展示 |
|
|
532
|
-
{% endif %}
|
|
533
|
-
{% if projectMeta['flags']['supportTiptapAndStreamdown'] %}
|
|
534
|
-
| TiptapEditorComplete | `business-ui/tiptap-editor` | 富文本编辑器 |
|
|
535
|
-
| Streamdown | `components/ui/streamdown` | Markdown/流式渲染 |
|
|
536
|
-
{% endif %}
|
|
537
|
-
|
|
538
|
-
### 组件 Skill 召回规则(强制执行)
|
|
539
|
-
|
|
540
|
-
| 场景 | Skill | 说明 |
|
|
541
|
-
|------|-------|------|
|
|
542
|
-
| 表单、Form、Zod | `/forms-skill` | Shadcn Form + React Hook Form + Zod |
|
|
543
|
-
| 图表、Chart、ECharts | `/charts-skill` | shadcn/ui + ReactECharts |
|
|
544
|
-
| 表格、Table | `/table-skill` | antd-table |
|
|
545
|
-
|
|
546
|
-
禁止未调用 Skill 直接编写表单/图表/表格代码。
|
|
547
|
-
|
|
548
|
-
### 组件使用规范
|
|
549
|
-
|
|
550
|
-
- 优先使用 `client/src/components` 下已有组件(Card/Button/Badge 等)
|
|
551
|
-
- 使用前查看 `ui/README.md` 或组件源码
|
|
552
|
-
- 禁止 Card 嵌套 Card
|
|
553
|
-
- shadcn props 值必须从实际联合类型中选取(如 Button variant),不确定时看源码
|
|
554
|
-
- 输入组件用 shadcn,**禁止原生 `<input>`/`<textarea>`/`<select>`/`input[type="date"]`**
|
|
555
|
-
- SelectItem 的 value 禁止空值,占位用 `<SelectValue placeholder="..." />`
|
|
556
|
-
- 版本锁定、按需导入、唯一图标库 lucide-react
|
|
557
|
-
|
|
558
|
-
## @lark-apaas/client-toolkit
|
|
559
|
-
|
|
560
|
-
**零假设原则**:绝不基于假设使用任何子模块。使用任何 `@lark-apaas/client-toolkit` 子模块前**必须**:① 查询 Skills 或 `steering_doc_search` → ② 等待响应获取文档 → ③ 严格按文档编码。**禁止直接调用 `@lark-apaas/client-toolkit` 任何函数/方法**(不基于查询到的文档)。该库**仅可在前端代码中使用,禁止在后端代码中引用**。
|
|
561
|
-
|
|
562
|
-
| 功能 | 导入路径 |
|
|
563
|
-
|------|----------|
|
|
564
|
-
| 插件调用(Client) | `import { capabilityClient } from "@lark-apaas/client-toolkit"` |
|
|
565
|
-
| 日志 | `import { logger } from "@lark-apaas/client-toolkit/logger"` |
|
|
566
|
-
| 文件上传下载 | `import { getDataloom } from "@lark-apaas/client-toolkit/dataloom"` |
|
|
567
|
-
| 应用信息 | `import { useAppInfo } from "@lark-apaas/client-toolkit/hooks/useAppInfo"` |
|
|
568
|
-
| 当前用户 | `import { useCurrentUserProfile } from "@lark-apaas/client-toolkit/hooks/useCurrentUserProfile"` |
|
|
569
|
-
| URL 解析 | `import { resolveAppUrl } from "@lark-apaas/client-toolkit/utils/resolveAppUrl"` |
|
|
570
|
-
| 批量用户信息 | `import { UserService } from "@lark-apaas/client-toolkit/tools/services"` |
|
|
571
|
-
|
|
572
|
-
**useCurrentUserProfile** 返回 `Partial<IUserProfile>`,初始为空对象:访问字段用 `?.`,判断加载完成用 `if (!userInfo?.user_id)`。
|
|
573
|
-
|
|
574
|
-
**UserService.listUsersByIds**: 根据用户 ID 获取用户详细信息(姓名/头像/邮箱/部门)时**必须使用**,**禁止自行实现用户信息查询逻辑**。非组件上下文需要批量获取用户信息时使用。返回 `result.data.userInfoMap: Record<string, UserInfo>`,UserInfo 含 `userID: string`, `name: I18nText { zh_cn, en_us?, ja_jp? }`, `avatar: string`, `email?: string`, `userType: "_employee"|"_externalUser"|"_anonymousUser"`, `department: { departmentID, name: I18nText }`。{% if projectMeta['flags']['supportBusinessUser'] %}展示场景仍优先用 UserDisplay/UserProfile 组件。{% endif %}
|
|
575
|
-
|
|
576
|
-
> ⚠️ `UserInfo` 不含任何飞书 ID 字段(`lark_user_id` / `open_id` / `union_id` 都没有)。需要飞书 ID 一律见 `user-identity` skill(当前用户、任意用户 user_id、open_id、union_id 各种场景都在那里)。
|
|
577
|
-
|
|
578
|
-
## 样式开发
|
|
579
|
-
|
|
580
|
-
**设计规范遵从(最高优先级)**:必须严格遵循 `AGENTS.md` 中定义的设计规范,代码实现必须是对设计文档的精准还原,**严禁随意简化**。所有设计必须响应式。项目使用单套色彩系统,无需支持深色/浅色模式切换。
|
|
581
|
-
|
|
582
|
-
### 样式技术选型
|
|
583
|
-
|
|
584
|
-
```
|
|
585
|
-
需要写样式?
|
|
586
|
-
├─ 基础布局/间距/颜色 → Tailwind ✅
|
|
587
|
-
├─ 复杂动画/伪元素/高级CSS → styled-jsx ✅
|
|
588
|
-
└─ JS动态计算值 → 行内 style ✅
|
|
589
|
-
```
|
|
590
|
-
|
|
591
|
-
### Tailwind 规范
|
|
592
|
-
|
|
593
|
-
- `justify-between/around/evenly` 必须配合 `space-x-*` 或 `gap`
|
|
594
|
-
- `justify-start/end` 的水平 flex 行应加 `flex-wrap`
|
|
595
|
-
- shadcn Button 不要显式控制 padding/height,用 sizes/variants API
|
|
596
|
-
- 颜色优先级:语义化 token(`bg-primary`)> 自定义 token > Tailwind 预设。禁止 `bg-[--primary]`(Tailwind 4 限制)
|
|
597
|
-
- **arbitrary values 中空格用下划线**:`from-[hsl(215_60%_18%)]` 非 `from-[hsl(215 60% 18%)]`
|
|
598
|
-
- `tailwind-theme.css` 自定义属性用 `hsl(H, S%, L%)` 格式(非 `23 10% 23%`)
|
|
599
|
-
|
|
600
|
-
### styled-jsx 规范
|
|
601
|
-
|
|
602
|
-
- **技术栈一致性**:仅在已配置 styled-jsx 插件的项目中使用。`package.json` 无 `styled-jsx` 依赖则**禁用**,否则运行时 SyntaxError
|
|
603
|
-
- **禁止动态插值**:`<style jsx>` 内禁止 `${...}` 等表达式(会卡死)。动态值放 CSS 变量,用 `var(--xxx)` 引用
|
|
604
|
-
|
|
605
|
-
### 布局/排版
|
|
606
|
-
|
|
607
|
-
- Spacing 保持一致(small/medium/large 三级),Panel 风格统一
|
|
608
|
-
- 文本溢出:用户输入/URL 用 `break-words`,标题用 `truncate`
|
|
609
|
-
|
|
610
|
-
## 页面与路由
|
|
611
|
-
|
|
612
|
-
- **新增页面流程**:判断是否需要隔离布局 → 创建 `pages/` 下组件 → `app.tsx` 配置路由 → 更新导航
|
|
613
|
-
- **数据获取**:页面级数据获取在页面组件顶层处理,通过 props 向下传递
|
|
614
|
-
- **禁止 React.lazy**:严禁异步加载路由组件
|
|
615
|
-
- **页面跳转**:必须用 `NavLink`/`Link`/`useNavigate`,**严禁 `window.location.href`**
|
|
616
|
-
- **分享链接/二维码**(CRITICAL):应用部署后 URL 与本地路由不同,**必须**用 `resolveAppUrl` 转换:
|
|
617
|
-
```typescript
|
|
618
|
-
import { resolveAppUrl } from '@lark-apaas/client-toolkit/utils/resolveAppUrl';
|
|
619
|
-
const shareUrl = resolveAppUrl(`/detail/${id}`); // ✅ 路由路径→完整 URL
|
|
620
|
-
const fixedUrl = resolveAppUrl(`${origin}/detail/${id}`); // ✅ 自动修正
|
|
621
|
-
resolveAppUrl('https://other-site.com/page'); // ✅ 外部链接原样返回
|
|
622
|
-
// ❌ 严禁自行拼接:`${window.location.origin}/detail/${id}`
|
|
623
|
-
```
|
|
624
|
-
- **首页路由**:必须含指向 `/` 的 index 路由
|
|
625
|
-
- **路由唯一性**:每页一个唯一路由
|
|
626
|
-
- **导航**:NavLink + active 高亮,路径必须与 `app.tsx` 路由精确对应。全局 Layout 中使用 `useAppInfo`/`useCurrentUserProfile` 获取站点和用户信息
|
|
627
|
-
- **路由动态调整**:更新路由时检查现有路由表,及时将 index 路由指向最合适的页面
|
|
628
|
-
- **禁止混用页面跳转和页内锚点**
|
|
629
|
-
|
|
630
|
-
### 组件代码风格
|
|
631
|
-
|
|
632
|
-
- 箭头函数 + React.FC,Props 接口用 `组件名Props`
|
|
633
|
-
- 编写顺序:① Hook 声明 → ② useEffect → ③ 事件处理 → ④ JSX
|
|
634
|
-
- 导入顺序:React → 三方库 → 内置工具 → 相对路径
|
|
635
|
-
- 交互对话框用 Dialog 组件
|
|
636
|
-
- 弹窗禁止 `alert`/`confirm`/`prompt`
|
|
637
|
-
|
|
638
|
-
### 数据安全渲染
|
|
639
|
-
|
|
640
|
-
- **渲染前必须检查数据是否存在**
|
|
641
|
-
- **使用条件渲染处理 loading/error 状态**
|
|
642
|
-
- **为可能 undefined 的数据提供 fallback UI**
|
|
643
|
-
|
|
644
|
-
```jsx
|
|
645
|
-
if (loading) return <div>加载中...</div>;
|
|
646
|
-
if (!data) return <div>暂无数据</div>;
|
|
647
|
-
return <h1>{data?.title || '未知标题'}</h1>;
|
|
648
|
-
```
|
|
649
|
-
|
|
650
|
-
## 组件区块类型标记
|
|
651
|
-
|
|
652
|
-
为特定功能的顶层容器添加 `data-ai-section-type` 属性:
|
|
653
|
-
|
|
654
|
-
| 值 | 场景 |
|
|
655
|
-
|----|------|
|
|
656
|
-
| `card-stat` | 横向指标卡容器 |
|
|
657
|
-
| `card-list` | Card 组件的 flex 列表 |
|
|
658
|
-
| `button` | 任意 Button |
|
|
659
|
-
| `card-menu` | Card 组件的 grid 网格菜单 |
|
|
660
|
-
|
|
661
|
-
## 图片规范
|
|
662
|
-
|
|
663
|
-
- **展示**:必须用 `@client/src/components/ui/image` 组件(非原生 `<img>`),响应式设 `sizes`,固定宽设 `width`
|
|
664
|
-
{% if projectMeta['flags']['supportBusinessUser'] %}
|
|
665
|
-
- 用户头像用 business-ui 组件
|
|
666
|
-
{% endif %}
|
|
667
|
-
|
|
668
|
-
### 图片资源优先级
|
|
669
|
-
|
|
670
|
-
1. 用户上传图片(最高)
|
|
671
|
-
2. `generate_image`(主要方案)— 每个位置独立图片,禁止重复。提示词格式:`[主体] + [环境] + [风格] + [色彩] + [无文字说明]`
|
|
672
|
-
- **必须包含**:「无文字、无字母、无符号」明确说明
|
|
673
|
-
- **禁止出现**:① 使用场景("适合移动端")② 功能描述("适合/用于/作为")③ 目标用途("品牌展示/课程封面")④ 观众定位("吸引年轻人")⑤ 预期效果("视觉冲击力/引人注目")⑥ 媒介格式("移动端/印刷品")
|
|
674
|
-
3. 内置 Avatar(仅头像)— `@client/src/utils/img-resources/avatar-placeholders.ts`
|
|
675
|
-
4. Picsum(临时)— `https://picsum.photos/seed/${seed}/${w}/${h}`(必须带 seed)
|
|
676
|
-
|
|
677
|
-
## 核心功能依赖
|
|
678
|
-
|
|
679
|
-
| 类别 | 库 |
|
|
680
|
-
|------|-----|
|
|
681
|
-
| 动画 | Framer Motion, GSAP |
|
|
682
|
-
| 日期 | dayjs |
|
|
683
|
-
| 日期选择器 | shadcn Calendar + Popover(禁止 input[type="date"]) |
|
|
684
|
-
| 验证 | zod |
|
|
685
|
-
| 工具函数 | lodash |
|
|
686
|
-
| 样式 | clsx |
|
|
687
|
-
| Excel | xlsx(**仅前端实现,禁止服务端实现**。解析后将结构化数据传到服务端保存) |
|
|
688
|
-
| PDF 导出 | jspdf + html2canvas(**仅前端实现,禁止服务端实现**) |
|
|
689
|
-
| 文件上传 | react-dropzone |
|
|
690
|
-
| 二维码 | qrcode.react |
|
|
691
|
-
| 用户反馈 | sonner |
|
|
692
|
-
| 拖拽 | @dnd-kit/core |
|
|
693
|
-
| 数字动画 | react-countup |
|
|
694
|
-
| Base64 | js-base64 — `import { encode, decode } from 'js-base64'` |
|
|
695
|
-
| 3D 场景 | cobe |
|
|
696
|
-
|
|
697
|
-
## 滚动分页最佳实践
|
|
698
|
-
|
|
699
|
-
- 使用 `useInfiniteQuery` 管理数据
|
|
700
|
-
- `IntersectionObserver` 实现自动加载(防抖 200ms)
|
|
701
|
-
- 调用 `fetchNextPage()` 前检查 `!isFetchingNextPage && hasNextPage`
|
|
702
|
-
- 底部状态:loading → 加载中;无更多 → "没有更多";空 → 空状态
|
|
703
|
-
|
|
704
|
-
## 数据筛选器
|
|
705
|
-
|
|
706
|
-
- 用 shadcn 组件(Date Picker/Input),禁止原生 input
|
|
707
|
-
- 筛选项合理宽度分布
|