@lark-apaas/coding-steering 0.1.6-alpha.8 → 0.1.6-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 +1 -1
- package/steering/design-stack/skills/.gitkeep +0 -0
- package/steering/nestjs-react-fullstack/skills/authz-guide/SKILL.md +174 -0
- package/steering/nestjs-react-fullstack/skills/authz-guide/references/dynamic-permission-guide.md +627 -0
- package/steering/nestjs-react-fullstack/skills/authz-guide/references/management-page-spec.md +508 -0
- package/steering/nestjs-react-fullstack/skills/authz-guide/references/runtime-role-controller-spec.md +203 -0
- package/steering/nestjs-react-fullstack/skills/authz-guide/references/sdk-examples.md +90 -0
- package/steering/nestjs-react-fullstack/skills/authz-guide/references/sdk-types.md +216 -0
- package/steering/nestjs-react-fullstack/skills/client-add-aily-web-chat/SKILL.md +1 -1
- package/steering/nestjs-react-fullstack/skills/client-builtins-file-storage-service/SKILL.md +431 -0
- package/steering/nestjs-react-fullstack/skills/client-builtins-user-service/SKILL.md +39 -127
- package/steering/nestjs-react-fullstack/skills/devops-guide/SKILL.md +119 -0
- package/steering/nestjs-react-fullstack/skills/feishu/SKILL.md +0 -1
- package/steering/nestjs-react-fullstack/skills/feishu/references/approval.md +1 -1
- package/steering/nestjs-react-fullstack/skills/feishu/references/attendance.md +1 -1
- package/steering/nestjs-react-fullstack/skills/feishu/references/bitable.md +3 -1
- package/steering/nestjs-react-fullstack/skills/feishu/references/calendar.md +1 -1
- package/steering/nestjs-react-fullstack/skills/feishu/references/contacts.md +1 -1
- package/steering/nestjs-react-fullstack/skills/feishu/references/doc.md +2 -1
- package/steering/nestjs-react-fullstack/skills/feishu/references/drive.md +2 -1
- package/steering/nestjs-react-fullstack/skills/feishu/references/events.md +2 -1
- package/steering/nestjs-react-fullstack/skills/feishu/references/id-convert.md +2 -2
- package/steering/nestjs-react-fullstack/skills/feishu/references/messaging.md +1 -1
- package/steering/nestjs-react-fullstack/skills/feishu/references/oauth.md +2 -1
- package/steering/nestjs-react-fullstack/skills/feishu/references/perm.md +2 -1
- package/steering/nestjs-react-fullstack/skills/feishu/references/wiki.md +3 -2
- package/steering/nestjs-react-fullstack/skills/openapi-guide/SKILL.md +1 -0
- package/steering/nestjs-react-fullstack/skills/plugin-guide/SKILL.md +601 -0
- package/steering/nestjs-react-fullstack/skills/plugin-guide/references/plugin-coding-guide.md +360 -0
- package/steering/nestjs-react-fullstack/skills/plugin-guide/references/table.md +515 -0
- package/steering/nestjs-react-fullstack/skills/react-hook-best-practices/SKILL.md +118 -0
- package/steering/nestjs-react-fullstack/skills/server-builtins-file-storage-service/SKILL.md +177 -0
- package/steering/nestjs-react-fullstack/skills/trigger-guide/SKILL.md +1 -0
- package/steering/nestjs-react-fullstack/skills/user-identity/SKILL.md +1 -0
- package/steering/nestjs-react-fullstack/skills/user-management-best-practices/SKILL.md +142 -0
- package/steering/nestjs-react-fullstack/skills_local/code-fix/SKILL.md +42 -28
- package/steering/nestjs-react-fullstack/skills_local/coding-guide/SKILL.md +37 -149
- package/steering/design-stack/skills/client-add-aily-web-chat/SKILL.md +0 -139
- package/steering/design-stack/skills/client-builtins-user-service/SKILL.md +0 -628
- package/steering/design-stack/skills/code-fix/SKILL.md +0 -246
- package/steering/design-stack/skills/feishu/SKILL.md +0 -270
- package/steering/design-stack/skills/feishu/references/approval.md +0 -214
- package/steering/design-stack/skills/feishu/references/attendance.md +0 -163
- package/steering/design-stack/skills/feishu/references/bitable.md +0 -309
- package/steering/design-stack/skills/feishu/references/calendar.md +0 -190
- package/steering/design-stack/skills/feishu/references/contacts.md +0 -160
- package/steering/design-stack/skills/feishu/references/doc.md +0 -256
- package/steering/design-stack/skills/feishu/references/drive.md +0 -103
- package/steering/design-stack/skills/feishu/references/events.md +0 -198
- package/steering/design-stack/skills/feishu/references/id-convert.md +0 -128
- package/steering/design-stack/skills/feishu/references/messaging.md +0 -207
- package/steering/design-stack/skills/feishu/references/oauth.md +0 -164
- package/steering/design-stack/skills/feishu/references/perm.md +0 -90
- package/steering/design-stack/skills/feishu/references/wiki.md +0 -164
- package/steering/design-stack/skills/user-identity/SKILL.md +0 -300
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
# 审批 (Approval)
|
|
2
|
-
|
|
3
|
-
> 开放平台文档(Markdown 版):https://open.larkoffice.com/document/server-docs/approval-v4/approval-overview.md
|
|
4
|
-
|
|
5
|
-
使用 `@larksuiteoapi/node-sdk` 在 NestJS 中管理飞书审批流程。
|
|
6
|
-
|
|
7
|
-
## 所需权限
|
|
8
|
-
|
|
9
|
-
| 权限标识 | 说明 |
|
|
10
|
-
|----------|------|
|
|
11
|
-
| `approval:approval` | 读写审批信息 |
|
|
12
|
-
| `approval:approval.list:readonly` | 查询审批实例列表 |
|
|
13
|
-
| `approval:task` | 审批人操作(同意/拒绝/转交、查询任务) |
|
|
14
|
-
|
|
15
|
-
## 获取 Approval Code
|
|
16
|
-
|
|
17
|
-
飞书不提供「列出所有审批定义」的 API,需从管理后台手动获取:
|
|
18
|
-
|
|
19
|
-
1. 打开 [飞书审批管理后台(开发者模式)](https://www.feishu.cn/approval/admin/approvalList?devMode=on)
|
|
20
|
-
2. 找到目标审批 → 点击 **编辑**
|
|
21
|
-
3. 从浏览器地址栏复制 `definitionCode=` 后面的值
|
|
22
|
-
- 示例:`https://www.feishu.cn/approval/admin/edit?definitionCode=48D49517-C979-447E-AD93-4BAE0FBC57EA`
|
|
23
|
-
4. 获取到的 Code 即为 `approval_code`
|
|
24
|
-
|
|
25
|
-
## 获取审批定义
|
|
26
|
-
|
|
27
|
-
查看审批表单结构,了解需要填写哪些字段:
|
|
28
|
-
|
|
29
|
-
```typescript
|
|
30
|
-
const definition = await client.approval.approval.get({
|
|
31
|
-
path: { approval_code: '48D49517-C979-447E-AD93-4BAE0FBC57EA' },
|
|
32
|
-
});
|
|
33
|
-
// definition.data?.form — 表单控件 JSON 字符串
|
|
34
|
-
// definition.data?.node_list — 审批节点列表
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## 创建审批实例
|
|
38
|
-
|
|
39
|
-
```typescript
|
|
40
|
-
const instance = await client.approval.instance.create({
|
|
41
|
-
data: {
|
|
42
|
-
approval_code: '48D49517-C979-447E-AD93-4BAE0FBC57EA',
|
|
43
|
-
open_id: 'ou_xxx', // 发起人
|
|
44
|
-
form: JSON.stringify([
|
|
45
|
-
{
|
|
46
|
-
id: 'widget001',
|
|
47
|
-
type: 'input',
|
|
48
|
-
value: '出差事由:参加客户交流会',
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
id: 'widget002',
|
|
52
|
-
type: 'date',
|
|
53
|
-
value: '2026-03-01T09:00:00+08:00',
|
|
54
|
-
},
|
|
55
|
-
]),
|
|
56
|
-
// department_id: 'od_xxx', // 多部门用户需填写
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
const instanceCode = instance.data?.instance_code;
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
## 获取审批详情
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
const detail = await client.approval.instance.get({
|
|
66
|
-
path: { instance_id: instanceCode },
|
|
67
|
-
});
|
|
68
|
-
// detail.data?.status — PENDING / APPROVED / REJECTED / CANCELED
|
|
69
|
-
// detail.data?.form — 表单数据
|
|
70
|
-
// detail.data?.task_list — 任务列表
|
|
71
|
-
// detail.data?.timeline — 审批动态
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
## 查询审批实例列表
|
|
75
|
-
|
|
76
|
-
```typescript
|
|
77
|
-
const list = await client.approval.instance.query({
|
|
78
|
-
data: {
|
|
79
|
-
approval_code: '48D49517-...',
|
|
80
|
-
instance_status: 'PENDING', // PENDING / APPROVED / REJECT / RECALL / ALL
|
|
81
|
-
// user_id: 'ou_xxx', // 按发起人过滤
|
|
82
|
-
// start_time: '1708300800000', // Unix 毫秒时间戳
|
|
83
|
-
// end_time: '1708387200000',
|
|
84
|
-
page_size: 20,
|
|
85
|
-
},
|
|
86
|
-
});
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
## 撤回审批
|
|
90
|
-
|
|
91
|
-
```typescript
|
|
92
|
-
await client.approval.instance.cancel({
|
|
93
|
-
data: {
|
|
94
|
-
approval_code: '48D49517-...',
|
|
95
|
-
instance_code: instanceCode,
|
|
96
|
-
user_id: 'ou_xxx', // 审批提交人
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
> 撤回需要在审批后台对应审批定义中勾选「允许撤销审批中的申请」或「允许撤销 x 天内通过的审批」。
|
|
102
|
-
|
|
103
|
-
## 查询审批人待办任务
|
|
104
|
-
|
|
105
|
-
```typescript
|
|
106
|
-
const tasks = await client.approval.task.search({
|
|
107
|
-
data: {
|
|
108
|
-
user_id: 'ou_xxx', // 审批人 open_id
|
|
109
|
-
approval_code: '48D49517-...', // 可选
|
|
110
|
-
task_status: 'PENDING', // PENDING / APPROVED / REJECTED / TRANSFERRED
|
|
111
|
-
page_size: 10,
|
|
112
|
-
},
|
|
113
|
-
params: { user_id_type: 'open_id' },
|
|
114
|
-
});
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
也可以通过 `query` 方法查询任务:
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
const taskQuery = await client.approval.task.query({
|
|
121
|
-
params: {
|
|
122
|
-
page_size: 20,
|
|
123
|
-
// page_token: '...',
|
|
124
|
-
},
|
|
125
|
-
data: {
|
|
126
|
-
topic: 'pending', // pending / approved / rejected
|
|
127
|
-
user_id: 'ou_xxx',
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
## 同意审批
|
|
133
|
-
|
|
134
|
-
```typescript
|
|
135
|
-
await client.approval.task.approve({
|
|
136
|
-
data: {
|
|
137
|
-
approval_code: '48D49517-...',
|
|
138
|
-
instance_code: instanceCode,
|
|
139
|
-
user_id: 'ou_xxx', // 审批人
|
|
140
|
-
task_id: '7605931414537653476',
|
|
141
|
-
comment: '同意,请注意安全',
|
|
142
|
-
// form: '...', // 部分审批需要补充表单
|
|
143
|
-
},
|
|
144
|
-
});
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
## 拒绝审批
|
|
148
|
-
|
|
149
|
-
```typescript
|
|
150
|
-
await client.approval.task.reject({
|
|
151
|
-
data: {
|
|
152
|
-
approval_code: '48D49517-...',
|
|
153
|
-
instance_code: instanceCode,
|
|
154
|
-
user_id: 'ou_xxx',
|
|
155
|
-
task_id: '7605931414537653476',
|
|
156
|
-
comment: '时间冲突,建议改期',
|
|
157
|
-
},
|
|
158
|
-
});
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
## 转交审批
|
|
162
|
-
|
|
163
|
-
```typescript
|
|
164
|
-
await client.approval.task.transfer({
|
|
165
|
-
data: {
|
|
166
|
-
approval_code: '48D49517-...',
|
|
167
|
-
instance_code: instanceCode,
|
|
168
|
-
user_id: 'ou_xxx', // 当前审批人
|
|
169
|
-
task_id: '7605931414537653476',
|
|
170
|
-
comment: '转交给主管处理',
|
|
171
|
-
transfer_user_id: 'ou_yyy', // 转交目标
|
|
172
|
-
},
|
|
173
|
-
});
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
## 常见表单控件类型
|
|
177
|
-
|
|
178
|
-
| 控件类型 | 说明 | value 格式 |
|
|
179
|
-
|----------|------|------------|
|
|
180
|
-
| `input` | 单行文本 | `"文本内容"` |
|
|
181
|
-
| `textarea` | 多行文本 | `"文本内容"` |
|
|
182
|
-
| `number` | 数字 | `123.45` |
|
|
183
|
-
| `date` | 日期 | `"2026-02-12T09:00:00+08:00"` (RFC3339) |
|
|
184
|
-
| `leaveGroup` | 请假控件组 | `{"name":"年假","start":"...","end":"...","interval":2.0}` |
|
|
185
|
-
| `remedyGroupV2` | 补卡控件组 | `[{"date":"2026-02-12","remedy_time":"...","reason":"..."}]` |
|
|
186
|
-
| `tripGroup` | 出差控件组 | `{"schedule":[...],"interval":2.0,"reason":"..."}` |
|
|
187
|
-
| `radioV2` | 单选 | `"选项名称"` |
|
|
188
|
-
| `checkboxV2` | 多选 | `["选项1","选项2"]` |
|
|
189
|
-
| `attachmentV2` | 附件 | 附件 token 列表 |
|
|
190
|
-
|
|
191
|
-
## 典型工作流
|
|
192
|
-
|
|
193
|
-
### 发起审批(发起人视角)
|
|
194
|
-
|
|
195
|
-
1. 获取 approval_code(管理后台或预配置)
|
|
196
|
-
2. `client.approval.approval.get()` → 查看表单结构
|
|
197
|
-
3. 组装 form JSON → `client.approval.instance.create()` 发起审批
|
|
198
|
-
4. `client.approval.instance.get()` → 查询审批状态
|
|
199
|
-
|
|
200
|
-
### 处理审批(审批人视角)
|
|
201
|
-
|
|
202
|
-
1. `client.approval.task.search({ data: { user_id, task_status: 'PENDING' } })` → 获取待办
|
|
203
|
-
2. 根据标题/申请人匹配目标任务 → 拿到 `task_id` 和 `instance_code`
|
|
204
|
-
3. `client.approval.task.approve()` 同意 / `reject()` 拒绝 / `transfer()` 转交
|
|
205
|
-
|
|
206
|
-
## Common Mistakes
|
|
207
|
-
|
|
208
|
-
| 错误 | 正确做法 |
|
|
209
|
-
|------|----------|
|
|
210
|
-
| 想用 API 列出所有审批定义 | 无此 API,从管理后台获取 approval_code |
|
|
211
|
-
| form 传对象而非 JSON 字符串 | `form: JSON.stringify([{id, type, value}])` |
|
|
212
|
-
| 忘记传 `task_id` 执行同意/拒绝 | 先通过 `task.search()` 获取 task_id |
|
|
213
|
-
| 撤回失败 | 检查审批定义是否允许撤回 |
|
|
214
|
-
| `instance_status` 拼写 | `REJECT`(不是 `REJECTED`),`RECALL`(不是 `CANCELED`) |
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
# 考勤 (Attendance)
|
|
2
|
-
|
|
3
|
-
> 开放平台文档(Markdown 版):https://open.larkoffice.com/document/server-docs/attendance-v1/overview.md
|
|
4
|
-
|
|
5
|
-
使用 `@larksuiteoapi/node-sdk` 在 NestJS 中查询飞书考勤数据。
|
|
6
|
-
|
|
7
|
-
## 所需权限
|
|
8
|
-
|
|
9
|
-
| 权限标识 | 说明 |
|
|
10
|
-
|----------|------|
|
|
11
|
-
| `attendance:task:readonly` | 导出打卡数据 |
|
|
12
|
-
| `attendance:rule:readonly` | 导出打卡管理规则(查询考勤组时需要) |
|
|
13
|
-
| `contact:user.employee_id:readonly` | 获取用户 employee_id(open_id 转换必需) |
|
|
14
|
-
|
|
15
|
-
> **重要**:考勤 API 使用 `employee_id` 而非 `open_id`。需先通过通讯录 API 将 `open_id` 转换为 `employee_id`。
|
|
16
|
-
|
|
17
|
-
## open_id 转 employee_id
|
|
18
|
-
|
|
19
|
-
```typescript
|
|
20
|
-
// 通过通讯录 API 获取用户的 employee_id
|
|
21
|
-
const user = await client.contact.user.get({
|
|
22
|
-
path: { user_id: 'ou_xxx' },
|
|
23
|
-
params: { user_id_type: 'open_id' },
|
|
24
|
-
});
|
|
25
|
-
const employeeId = user.data?.user?.employee_id; // 如 'abd754f7'
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## 查询打卡结果
|
|
29
|
-
|
|
30
|
-
```typescript
|
|
31
|
-
const tasks = await client.attendance.userTask.query({
|
|
32
|
-
params: { employee_type: 'employee_id' },
|
|
33
|
-
data: {
|
|
34
|
-
user_ids: ['abd754f7'], // employee_id 列表,最多 50 个
|
|
35
|
-
check_date_from: 20260209, // yyyyMMdd 格式整数
|
|
36
|
-
check_date_to: 20260213,
|
|
37
|
-
},
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// 遍历结果
|
|
41
|
-
for (const task of tasks.data?.user_task_results || []) {
|
|
42
|
-
console.log(`${task.employee_name} - ${task.day}`);
|
|
43
|
-
for (const record of task.records || []) {
|
|
44
|
-
console.log(` 上班: ${record.check_in_result}, 下班: ${record.check_out_result}`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### 日期格式
|
|
50
|
-
|
|
51
|
-
考勤 API 的日期格式是 `yyyyMMdd` **整数**(不是字符串,不是时间戳):
|
|
52
|
-
|
|
53
|
-
```typescript
|
|
54
|
-
// 正确
|
|
55
|
-
check_date_from: 20260209
|
|
56
|
-
|
|
57
|
-
// 错误
|
|
58
|
-
check_date_from: '2026-02-09' // 不是字符串
|
|
59
|
-
check_date_from: 1739059200 // 不是 Unix 时间戳
|
|
60
|
-
check_date_from: '20260209' // 不是字符串
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### 打卡状态码
|
|
64
|
-
|
|
65
|
-
| 值 | 含义 |
|
|
66
|
-
|----|------|
|
|
67
|
-
| `Normal` | 正常 |
|
|
68
|
-
| `Late` | 迟到 |
|
|
69
|
-
| `Early` | 早退 |
|
|
70
|
-
| `Lack` | 缺卡 |
|
|
71
|
-
| `Todo` | 未打卡 |
|
|
72
|
-
| `NoNeedCheck` | 无需打卡 |
|
|
73
|
-
|
|
74
|
-
## 查询补卡记录
|
|
75
|
-
|
|
76
|
-
```typescript
|
|
77
|
-
const remedys = await client.attendance.userTaskRemedy.query({
|
|
78
|
-
params: { employee_type: 'employee_id' },
|
|
79
|
-
data: {
|
|
80
|
-
user_ids: ['abd754f7'],
|
|
81
|
-
check_time_from: '1738800000', // Unix 秒时间戳字符串
|
|
82
|
-
check_time_to: '1739404800',
|
|
83
|
-
status: 2, // 0=待审批, 1=未通过, 2=已通过, 3=已取消, 4=已撤回
|
|
84
|
-
},
|
|
85
|
-
});
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### 补卡状态码
|
|
89
|
-
|
|
90
|
-
| 值 | 含义 |
|
|
91
|
-
|----|------|
|
|
92
|
-
| 0 | 待审批 |
|
|
93
|
-
| 1 | 未通过 |
|
|
94
|
-
| 2 | 已通过 |
|
|
95
|
-
| 3 | 已取消 |
|
|
96
|
-
| 4 | 已撤回 |
|
|
97
|
-
|
|
98
|
-
## 查询考勤组
|
|
99
|
-
|
|
100
|
-
```typescript
|
|
101
|
-
// group_id 从打卡结果中获取
|
|
102
|
-
const group = await client.attendance.group.get({
|
|
103
|
-
path: { group_id: '6737202939523236110' },
|
|
104
|
-
params: { employee_type: 'employee_id' },
|
|
105
|
-
});
|
|
106
|
-
// group.data — { group_name, time_zone, group_type, locations, ... }
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### 考勤组类型
|
|
110
|
-
|
|
111
|
-
| 值 | 含义 |
|
|
112
|
-
|----|------|
|
|
113
|
-
| 0 | 固定班制 |
|
|
114
|
-
| 2 | 排班制 |
|
|
115
|
-
| 3 | 自由班制 |
|
|
116
|
-
|
|
117
|
-
## 列出考勤组
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
const groups = await client.attendance.group.list({
|
|
121
|
-
params: { page_size: 20 },
|
|
122
|
-
});
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
## 搜索考勤组
|
|
126
|
-
|
|
127
|
-
```typescript
|
|
128
|
-
const search = await client.attendance.group.search({
|
|
129
|
-
data: { group_name: '产品部' },
|
|
130
|
-
});
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
## 典型工作流
|
|
134
|
-
|
|
135
|
-
### 查看本周考勤
|
|
136
|
-
|
|
137
|
-
1. **获取 employee_id** → `client.contact.user.get()` 转换 open_id
|
|
138
|
-
2. **查询打卡结果** → `client.attendance.userTask.query({ data: { user_ids, check_date_from, check_date_to } })`
|
|
139
|
-
3. 汇总每天上下班打卡状态
|
|
140
|
-
4. 标记异常(Late/Early/Lack)
|
|
141
|
-
|
|
142
|
-
### 查看考勤规则
|
|
143
|
-
|
|
144
|
-
1. **查询打卡结果** → 获取 `group_id`
|
|
145
|
-
2. **查询考勤组** → `client.attendance.group.get({ path: { group_id } })`
|
|
146
|
-
3. 查看打卡地点、考勤时间、补卡策略等配置
|
|
147
|
-
|
|
148
|
-
### 批量查询团队考勤
|
|
149
|
-
|
|
150
|
-
1. 获取部门成员 → `client.contact.user.findByDepartment()`
|
|
151
|
-
2. 提取所有成员的 `employee_id`
|
|
152
|
-
3. 分批查询(每批最多 50 人)→ `client.attendance.userTask.query()`
|
|
153
|
-
4. 汇总统计异常情况
|
|
154
|
-
|
|
155
|
-
## Common Mistakes
|
|
156
|
-
|
|
157
|
-
| 错误 | 正确做法 |
|
|
158
|
-
|------|----------|
|
|
159
|
-
| 用 open_id 调考勤 API | 先转换为 employee_id |
|
|
160
|
-
| 日期格式传字符串或时间戳 | 必须是 yyyyMMdd 整数如 `20260209` |
|
|
161
|
-
| 补卡查询时间传整数 | 补卡时间是 Unix 秒时间戳**字符串** |
|
|
162
|
-
| 一次查超过 50 人 | `user_ids` 最多 50 个,需分批 |
|
|
163
|
-
| 未申请 `contact:user.employee_id:readonly` | open_id 转 employee_id 必需此权限 |
|
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
# 多维表格 (Bitable)
|
|
2
|
-
|
|
3
|
-
> 开放平台文档(Markdown 版):https://open.larkoffice.com/document/server-docs/docs/bitable-v1/bitable-overview.md
|
|
4
|
-
|
|
5
|
-
使用 `@larksuiteoapi/node-sdk` 在 NestJS 中操作飞书多维表格。
|
|
6
|
-
|
|
7
|
-
## 所需权限
|
|
8
|
-
|
|
9
|
-
| 权限标识 | 说明 |
|
|
10
|
-
|----------|------|
|
|
11
|
-
| `bitable:app` | 读写多维表格 |
|
|
12
|
-
| `drive:drive` | 访问云空间(创建/列出多维表格时需要) |
|
|
13
|
-
|
|
14
|
-
> 对于已有的多维表格,需确保应用已被添加为协作者:云文档右上角「...」→「更多」→「添加文档应用」。
|
|
15
|
-
|
|
16
|
-
## 获取 app_token
|
|
17
|
-
|
|
18
|
-
多维表格的 `app_token` 有三种获取方式:
|
|
19
|
-
1. **通过 API 创建** → 响应中包含 `app_token`
|
|
20
|
-
2. **通过 API 列出** → 从文件列表中获取
|
|
21
|
-
3. **从 URL 提取** → 见下方 URL 解析
|
|
22
|
-
|
|
23
|
-
### URL 解析
|
|
24
|
-
|
|
25
|
-
多维表格有两种 URL 格式:
|
|
26
|
-
|
|
27
|
-
| 格式 | URL 示例 | 说明 |
|
|
28
|
-
|------|----------|------|
|
|
29
|
-
| `/base/` | `https://xxx.feishu.cn/base/{app_token}?table=tblXXX` | 直接提取 `app_token` |
|
|
30
|
-
| `/wiki/` | `https://xxx.feishu.cn/wiki/{node_token}?table=tblXXX` | 需先通过 Wiki API 获取 `obj_token` |
|
|
31
|
-
|
|
32
|
-
```typescript
|
|
33
|
-
// /base/ 格式:直接提取
|
|
34
|
-
const baseUrl = 'https://xxx.feishu.cn/base/ABC123?table=tblXXX';
|
|
35
|
-
const u = new URL(baseUrl);
|
|
36
|
-
const appToken = u.pathname.match(/\/base\/([A-Za-z0-9]+)/)?.[1]; // 'ABC123'
|
|
37
|
-
const tableId = u.searchParams.get('table'); // 'tblXXX'
|
|
38
|
-
|
|
39
|
-
// /wiki/ 格式:需先获取 obj_token
|
|
40
|
-
const wikiUrl = 'https://xxx.feishu.cn/wiki/XYZ789?table=tblXXX';
|
|
41
|
-
const nodeToken = new URL(wikiUrl).pathname.match(/\/wiki\/([A-Za-z0-9]+)/)?.[1];
|
|
42
|
-
const nodeRes = await client.wiki.space.getNode({ params: { token: nodeToken } });
|
|
43
|
-
const appTokenFromWiki = nodeRes.data?.node?.obj_token; // 这才是真正的 app_token
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
## 创建多维表格
|
|
47
|
-
|
|
48
|
-
```typescript
|
|
49
|
-
const res = await client.bitable.app.create({
|
|
50
|
-
data: {
|
|
51
|
-
name: '项目跟踪表',
|
|
52
|
-
// folder_token: 'fldcnxxxxxx', // 可选:目标文件夹
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
const appToken = res.data?.app?.app_token;
|
|
56
|
-
const defaultTableId = res.data?.app?.table_id;
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
## 获取多维表格信息
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
const info = await client.bitable.app.get({
|
|
63
|
-
path: { app_token: appToken },
|
|
64
|
-
});
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
## 列出数据表
|
|
68
|
-
|
|
69
|
-
```typescript
|
|
70
|
-
const tables = await client.bitable.appTable.list({
|
|
71
|
-
path: { app_token: appToken },
|
|
72
|
-
params: { page_size: 20 },
|
|
73
|
-
});
|
|
74
|
-
// tables.data?.items — [{table_id, name, revision}, ...]
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## 创建数据表
|
|
78
|
-
|
|
79
|
-
```typescript
|
|
80
|
-
const newTable = await client.bitable.appTable.create({
|
|
81
|
-
path: { app_token: appToken },
|
|
82
|
-
data: {
|
|
83
|
-
table: {
|
|
84
|
-
name: '新数据表',
|
|
85
|
-
default_view_name: '默认视图',
|
|
86
|
-
fields: [
|
|
87
|
-
{ field_name: '任务名称', type: 1 }, // 1 = 多行文本
|
|
88
|
-
{ field_name: '状态', type: 3 }, // 3 = 单选
|
|
89
|
-
{ field_name: '截止日期', type: 5 }, // 5 = 日期
|
|
90
|
-
],
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
});
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## 列出字段
|
|
97
|
-
|
|
98
|
-
```typescript
|
|
99
|
-
const fields = await client.bitable.appTableField.list({
|
|
100
|
-
path: { app_token: appToken, table_id: tableId },
|
|
101
|
-
params: { page_size: 100 },
|
|
102
|
-
});
|
|
103
|
-
// fields.data?.items — [{field_id, field_name, type, is_primary, property}, ...]
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
## 查询记录
|
|
107
|
-
|
|
108
|
-
```typescript
|
|
109
|
-
// 使用 search 方法支持 filter 和 sort
|
|
110
|
-
const records = await client.bitable.appTableRecord.search({
|
|
111
|
-
path: { app_token: appToken, table_id: tableId },
|
|
112
|
-
data: {
|
|
113
|
-
field_names: ['任务名称', '负责人', '状态'],
|
|
114
|
-
filter: {
|
|
115
|
-
conjunction: 'and',
|
|
116
|
-
conditions: [
|
|
117
|
-
{ field_name: '状态', operator: 'is', value: ['进行中'] },
|
|
118
|
-
],
|
|
119
|
-
},
|
|
120
|
-
sort: [
|
|
121
|
-
{ field_name: '截止日期', desc: false },
|
|
122
|
-
],
|
|
123
|
-
page_size: 20,
|
|
124
|
-
},
|
|
125
|
-
});
|
|
126
|
-
// records.data?.items — [{record_id, fields: {...}}, ...]
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### filter 语法
|
|
130
|
-
|
|
131
|
-
```typescript
|
|
132
|
-
// conjunction: 'and' | 'or'
|
|
133
|
-
// operator: 'is' | 'isNot' | 'contains' | 'doesNotContain' | 'isEmpty' | 'isNotEmpty' | 'isGreater' | 'isLess'
|
|
134
|
-
{
|
|
135
|
-
conjunction: 'and',
|
|
136
|
-
conditions: [
|
|
137
|
-
{ field_name: '状态', operator: 'is', value: ['进行中'] },
|
|
138
|
-
{ field_name: '优先级', operator: 'is', value: ['高'] },
|
|
139
|
-
],
|
|
140
|
-
}
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
## 获取单条记录
|
|
144
|
-
|
|
145
|
-
```typescript
|
|
146
|
-
const record = await client.bitable.appTableRecord.get({
|
|
147
|
-
path: { app_token: appToken, table_id: tableId, record_id: 'recXXX' },
|
|
148
|
-
});
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
## 列出记录(简单查询)
|
|
152
|
-
|
|
153
|
-
```typescript
|
|
154
|
-
const list = await client.bitable.appTableRecord.list({
|
|
155
|
-
path: { app_token: appToken, table_id: tableId },
|
|
156
|
-
params: {
|
|
157
|
-
page_size: 100,
|
|
158
|
-
// filter: '...', // URL filter 表达式
|
|
159
|
-
// sort: '...', // URL sort 表达式
|
|
160
|
-
},
|
|
161
|
-
});
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
## 新增记录
|
|
165
|
-
|
|
166
|
-
```typescript
|
|
167
|
-
const created = await client.bitable.appTableRecord.create({
|
|
168
|
-
path: { app_token: appToken, table_id: tableId },
|
|
169
|
-
data: {
|
|
170
|
-
fields: {
|
|
171
|
-
'任务名称': '设计数据库表结构',
|
|
172
|
-
'状态': '待开始',
|
|
173
|
-
'优先级': '高',
|
|
174
|
-
'截止日期': 1708300800000, // 毫秒时间戳
|
|
175
|
-
},
|
|
176
|
-
},
|
|
177
|
-
});
|
|
178
|
-
const recordId = created.data?.record?.record_id;
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
## 批量新增记录
|
|
182
|
-
|
|
183
|
-
```typescript
|
|
184
|
-
await client.bitable.appTableRecord.batchCreate({
|
|
185
|
-
path: { app_token: appToken, table_id: tableId },
|
|
186
|
-
data: {
|
|
187
|
-
records: [
|
|
188
|
-
{ fields: { '任务名称': '任务1', '状态': '待开始' } },
|
|
189
|
-
{ fields: { '任务名称': '任务2', '状态': '待开始' } },
|
|
190
|
-
],
|
|
191
|
-
},
|
|
192
|
-
});
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
## 更新记录
|
|
196
|
-
|
|
197
|
-
```typescript
|
|
198
|
-
await client.bitable.appTableRecord.update({
|
|
199
|
-
path: { app_token: appToken, table_id: tableId, record_id: recordId },
|
|
200
|
-
data: {
|
|
201
|
-
fields: {
|
|
202
|
-
'状态': '已完成',
|
|
203
|
-
'完成日期': Date.now(), // 毫秒时间戳
|
|
204
|
-
},
|
|
205
|
-
},
|
|
206
|
-
});
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
## 删除记录
|
|
210
|
-
|
|
211
|
-
```typescript
|
|
212
|
-
// 单条删除
|
|
213
|
-
await client.bitable.appTableRecord.delete({
|
|
214
|
-
path: { app_token: appToken, table_id: tableId, record_id: 'recXXX' },
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
// 批量删除
|
|
218
|
-
await client.bitable.appTableRecord.batchDelete({
|
|
219
|
-
path: { app_token: appToken, table_id: tableId },
|
|
220
|
-
data: {
|
|
221
|
-
records: ['recXXX', 'recYYY', 'recZZZ'],
|
|
222
|
-
},
|
|
223
|
-
});
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
## 字段 CRUD
|
|
227
|
-
|
|
228
|
-
### 创建字段
|
|
229
|
-
|
|
230
|
-
```typescript
|
|
231
|
-
const res = await client.bitable.appTableField.create({
|
|
232
|
-
path: { app_token: appToken, table_id: tableId },
|
|
233
|
-
data: {
|
|
234
|
-
field_name: '优先级',
|
|
235
|
-
type: 3, // SingleSelect
|
|
236
|
-
// property: { options: [{name: '高'}, {name: '中'}, {name: '低'}] }, // 可选
|
|
237
|
-
},
|
|
238
|
-
});
|
|
239
|
-
const fieldId = res.data?.field?.field_id;
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
### 更新字段
|
|
243
|
-
|
|
244
|
-
```typescript
|
|
245
|
-
await client.bitable.appTableField.update({
|
|
246
|
-
path: { app_token: appToken, table_id: tableId, field_id: fieldId },
|
|
247
|
-
data: {
|
|
248
|
-
field_name: '新字段名',
|
|
249
|
-
type: 1, // 字段类型
|
|
250
|
-
},
|
|
251
|
-
});
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
### 删除字段
|
|
255
|
-
|
|
256
|
-
```typescript
|
|
257
|
-
await client.bitable.appTableField.delete({
|
|
258
|
-
path: { app_token: appToken, table_id: tableId, field_id: fieldId },
|
|
259
|
-
});
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
> **注意**:主字段(`is_primary: true`)不能删除,只能重命名。
|
|
263
|
-
|
|
264
|
-
## 新建多维表格清理建议
|
|
265
|
-
|
|
266
|
-
新建的多维表格会自动创建默认字段(单选、日期、附件)和空行。建议创建后清理:
|
|
267
|
-
1. 删除不需要的默认字段(类型 3/5/17)
|
|
268
|
-
2. 重命名主字段为有意义的名称
|
|
269
|
-
3. 批量删除空的占位行
|
|
270
|
-
|
|
271
|
-
## 字段类型与写入格式
|
|
272
|
-
|
|
273
|
-
| 类型编号 | 字段类型 | 写入格式 | 示例 |
|
|
274
|
-
|----------|----------|----------|------|
|
|
275
|
-
| 1 | 多行文本 | string 或 text 对象数组 | `"Hello"` 或 `[{"text":"Hello","type":"text"}]` |
|
|
276
|
-
| 2 | 数字 | number | `2323.23` |
|
|
277
|
-
| 3 | 单选 | string | `"选项1"` |
|
|
278
|
-
| 4 | 多选 | string[] | `["选项1", "选项2"]` |
|
|
279
|
-
| 5 | 日期 | number(毫秒时间戳) | `1690992000000` |
|
|
280
|
-
| 7 | 复选框 | boolean | `true` |
|
|
281
|
-
| 11 | 人员 | object[] | `[{"id": "ou_xxx"}]` |
|
|
282
|
-
| 13 | 电话号码 | string | `"13800138000"` |
|
|
283
|
-
| 15 | 超链接 | object | `{"text": "链接", "link": "https://..."}` |
|
|
284
|
-
| 17 | 附件 | object[] | `[{"file_token": "xxx"}]` |
|
|
285
|
-
| 18 | 单向关联 | string[] | `["recXXX"]` |
|
|
286
|
-
| 19 | 查找引用 | — | 只读,由关联字段自动计算 |
|
|
287
|
-
| 20 | 公式 | — | 只读,由公式自动计算 |
|
|
288
|
-
| 21 | 双向关联 | string[] | `["recXXX"]` |
|
|
289
|
-
| 22 | 地理位置 | object | `{"location": "北京市朝阳区", "pname": "北京市"}` |
|
|
290
|
-
| 23 | 群组 | string[] | `["oc_xxx"]` |
|
|
291
|
-
| 1001 | 创建时间 | — | 只读,系统自动生成 |
|
|
292
|
-
| 1002 | 修改时间 | — | 只读,系统自动生成 |
|
|
293
|
-
| 1003 | 创建人 | — | 只读,系统自动生成 |
|
|
294
|
-
| 1004 | 修改人 | — | 只读,系统自动生成 |
|
|
295
|
-
| 1005 | 自动编号 | — | 只读,系统自动生成 |
|
|
296
|
-
|
|
297
|
-
## Common Mistakes
|
|
298
|
-
|
|
299
|
-
| 错误 | 正确做法 |
|
|
300
|
-
|------|----------|
|
|
301
|
-
| 应用无法访问已有多维表格 | 需先添加应用为协作者 |
|
|
302
|
-
| 日期字段传 ISO 字符串 | 必须传毫秒时间戳数字 |
|
|
303
|
-
| 人员字段传字符串 | 必须传 `[{id: 'ou_xxx'}]` 数组格式 |
|
|
304
|
-
| 单选/多选传不存在的选项 | 选项会自动创建,但注意拼写一致 |
|
|
305
|
-
| `app_token` 和 `table_id` 搞混 | `app_token` 是多维表格级别,`table_id` 是数据表级别 |
|
|
306
|
-
| 查询用 `list` 不支持复杂筛选 | 使用 `search` 方法支持 filter/sort |
|
|
307
|
-
| `/wiki/` URL 直接当 app_token 用 | Wiki URL 的 token 是 node_token,需先 `wiki.space.getNode()` 获取 `obj_token` |
|
|
308
|
-
| 删除主字段 | 主字段(`is_primary: true`)不能删除,只能重命名 |
|
|
309
|
-
| 写入只读字段(公式/创建时间等) | 类型 19/20/1001-1005 为只读,不能通过 API 写入 |
|