@lovrabet/cli 1.2.4 → 1.2.5-beta.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/lib/add-page/input-page-router.js +1 -1
- package/lib/add-page/main.js +1 -1
- package/lib/add-page/select-page-template.js +1 -1
- package/lib/api/api-doc-ui.js +1 -1
- package/lib/api/api-doc.js +1 -1
- package/lib/api/api-pull-ui.js +1 -1
- package/lib/api/fetch-model-list.js +1 -1
- package/lib/api/generate-api-file.js +1 -1
- package/lib/api/main.js +1 -1
- package/lib/api/pull-silent.js +1 -1
- package/lib/app-menu/app-menu-sync-ui.js +1 -1
- package/lib/app-menu/create-menu.js +1 -1
- package/lib/app-menu/get-local-pages.js +1 -1
- package/lib/app-menu/get-online-menu-list.js +1 -1
- package/lib/app-menu/use-get-online-menu-list.js +1 -1
- package/lib/app-menu/utils.js +1 -1
- package/lib/app-menu/valid-url.js +1 -1
- package/lib/app-menu-update-cdn/current-content.js +1 -1
- package/lib/app-menu-update-cdn/input-cdn-asset.js +1 -1
- package/lib/app-menu-update-cdn/main.js +1 -1
- package/lib/app-menu-update-cdn/update-menu-cdn-url.js +1 -1
- package/lib/auth/auth-server-ui.js +1 -1
- package/lib/auth/auth-server.js +1 -1
- package/lib/auth/constant.js +1 -1
- package/lib/auth/get-cookie.js +1 -1
- package/lib/auth/is-session-valid.js +1 -1
- package/lib/auth/logout.js +1 -1
- package/lib/cli.js +1 -1
- package/lib/cmd/build-watch.js +1 -1
- package/lib/cmd/build.js +1 -1
- package/lib/cmd/logs.js +1 -1
- package/lib/cmd/preview.js +1 -1
- package/lib/cmd/start.js +1 -1
- package/lib/config/config-help.js +1 -1
- package/lib/config/main.js +1 -1
- package/lib/constant/domain.js +1 -1
- package/lib/constant/env.js +1 -1
- package/lib/create-app/enhanced-guided-create.js +1 -1
- package/lib/create-app/format-elapsed.js +1 -1
- package/lib/create-app/main.js +1 -1
- package/lib/create-app/task-finished.js +1 -1
- package/lib/create-app/task-loading.js +1 -1
- package/lib/create-app/task-running.js +1 -1
- package/lib/create-app/task-time.js +1 -1
- package/lib/create-app/use-copy-project-template.js +1 -1
- package/lib/create-app/use-format-code.js +1 -1
- package/lib/create-app/use-install-dependencies.js +1 -1
- package/lib/help.js +1 -1
- package/lib/init/main.js +1 -1
- package/lib/mcp/claude.js +1 -0
- package/lib/mcp/cursor.js +1 -1
- package/lib/mcp/main.js +1 -1
- package/lib/skills/main.js +1 -0
- package/lib/utils/check-sdk-version.js +1 -1
- package/lib/utils/config.js +1 -1
- package/lib/utils/copy-directory.js +1 -1
- package/lib/utils/http-client.js +1 -1
- package/lib/utils/logger.js +1 -1
- package/lib/utils/router-updater.js +1 -1
- package/lib/utils/sleep.js +1 -1
- package/lib/utils/template-replacer.js +1 -1
- package/package.json +1 -1
- package/templates/projects/sub-app-react-demo/index.html +22 -34
- package/templates/projects/sub-app-react-demo/public/logo.svg +1 -0
- package/templates/projects/sub-app-react-demo/src/api/api.ts +1 -1
- package/templates/projects/sub-app-react-demo/src/api/client.ts +1 -1
- package/templates/projects/sub-app-react-demo/src/layouts/MainLayout.tsx +44 -71
- package/templates/projects/sub-app-react-demo/src/pages/index.tsx +387 -927
- package/templates/projects/sub-app-react-demo/src/pages/sdk-demo/index.tsx +1 -1
- package/templates/projects/sub-app-react-demo/src/pages/workbench/index.module.css +293 -0
- package/templates/projects/sub-app-react-demo/src/pages/workbench/index.tsx +100 -414
- package/templates/projects/sub-app-react-demo/src/style.css +21 -15
- package/templates/projects/sub-app-react-demo/vite.config.ts +18 -13
- package/templates/rules/lovrabet_rules.mdc.tpl +636 -43
- package/templates/skills/.claude/skills/lovrabet/SKILL.md +257 -0
- package/templates/skills/.cursor/commands/lovrabet.md +247 -0
- package/templates/skills/.cursorrules +109 -0
- package/templates/skills/.shared/README.md +45 -0
- package/templates/skills/.shared/guides/01-filter-query/guide.md +300 -0
- package/templates/skills/.shared/guides/02-mcp-sql-workflow/guide.md +272 -0
- package/templates/skills/.shared/guides/03-antd-style/guide.md +227 -0
- package/templates/skills/.shared/guides/04-troubleshooting/guide.md +426 -0
- package/templates/skills/.shared/guides/05-api-integration/guide.md +327 -0
- package/templates/skills/.shared/guides/06-menu-management/guide.md +305 -0
- package/templates/skills/.windsurf/workflows/lovrabet.md +256 -0
- package/templates/projects/sub-app-react-demo/.vscode/extensions.json +0 -3
- package/templates/projects/sub-app-react-demo/.vscode/settings.json +0 -57
- package/templates/projects/sub-app-react-demo/src/pages/intro/index.tsx +0 -560
|
@@ -153,57 +153,141 @@ const result = await client.models.projectMembers.filter({...});
|
|
|
153
153
|
|
|
154
154
|
**编写代码前,请先检查用户的 `createClient` 和 `registerModels` 代码,确认使用的是用户定义的 alias。**
|
|
155
155
|
|
|
156
|
-
### 4.
|
|
156
|
+
### 4. SDK Filter 查询构建规范(强制)
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
**重要**:这是 AI 最容易出错的地方,必须严格遵守以下规范。
|
|
159
159
|
|
|
160
|
-
|
|
161
|
-
- 强大的查询能力(支持复杂条件、范围查询、模糊匹配)
|
|
162
|
-
- 高性能(通过 `select` 参数只返回需要的字段)
|
|
163
|
-
- 简洁的返回结构(直接返回 `{ tableData, total }`)
|
|
164
|
-
- 支持 11 种条件操作符和 `$and`/`$or` 逻辑组合
|
|
165
|
-
- 支持的操作符(11 种):
|
|
166
|
-
- $eq, $ne, $gte ($gteq), $lte ($lteq), $in, $contain, $startWith, $endWith
|
|
167
|
-
- 逻辑操作符:$and, $or(支持嵌套)
|
|
160
|
+
#### Filter 基本结构
|
|
168
161
|
|
|
169
162
|
```typescript
|
|
163
|
+
// 完整的 filter 调用结构
|
|
170
164
|
const result = await client.models.<modelName>.filter({
|
|
171
|
-
where: {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
{ country: { $in: ['中国', '美国', '日本'] } }, // 国家在集合内
|
|
175
|
-
{ vip: { $ne: null } }, // VIP 字段非空
|
|
176
|
-
{ name: { $contain: 'hello' } }, // 名称模糊匹配
|
|
177
|
-
],
|
|
178
|
-
},
|
|
179
|
-
select: ['id', 'name', 'age', 'country'], // 注意:是 select,不是 fields
|
|
180
|
-
orderBy: [
|
|
181
|
-
{ lastLoginAt: SortOrder.DESC }, // 先按登录时间倒序
|
|
182
|
-
{ name: SortOrder.ASC }, // 再按名称升序
|
|
183
|
-
], // 注意:是 orderBy,不是 sortList
|
|
165
|
+
where: { /* 查询条件 */ },
|
|
166
|
+
select: ['<field1>', '<field2>'], // 注意:是 select,不是 fields
|
|
167
|
+
orderBy: [{ <field>: 'desc' }], // 注意:是 orderBy,不是 sortList
|
|
184
168
|
currentPage: 1,
|
|
185
169
|
pageSize: 20,
|
|
186
170
|
});
|
|
187
171
|
|
|
172
|
+
// 返回结构
|
|
188
173
|
console.log(result.tableData); // 数据列表
|
|
189
|
-
console.log(result.total);
|
|
174
|
+
console.log(result.total); // 总数
|
|
175
|
+
```
|
|
190
176
|
|
|
191
|
-
|
|
192
|
-
|
|
177
|
+
#### 支持的条件操作符
|
|
178
|
+
|
|
179
|
+
| 操作符 | 说明 | 示例 |
|
|
180
|
+
|--------|------|------|
|
|
181
|
+
| `$eq` | 等于 | `{ status: { $eq: 'active' } }` |
|
|
182
|
+
| `$ne` | 不等于 | `{ status: { $ne: 'deleted' } }` |
|
|
183
|
+
| `$gte` | 大于等于 | `{ age: { $gte: 18 } }` |
|
|
184
|
+
| `$lte` | 小于等于 | `{ age: { $lte: 65 } }` |
|
|
185
|
+
| `$gt` | 大于 | `{ price: { $gt: 100 } }` |
|
|
186
|
+
| `$lt` | 小于 | `{ price: { $lt: 1000 } }` |
|
|
187
|
+
| `$in` | 包含于 | `{ type: { $in: ['A', 'B', 'C'] } }` |
|
|
188
|
+
| `$contain` | 包含(字符串) | `{ name: { $contain: 'keyword' } }` |
|
|
189
|
+
| `$startWith` | 开头匹配 | `{ email: { $startWith: 'admin' } }` |
|
|
190
|
+
| `$endWith` | 结尾匹配 | `{ domain: { $endWith: '.com' } }` |
|
|
191
|
+
|
|
192
|
+
#### 逻辑操作符
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
// $and - 所有条件都满足
|
|
196
|
+
where: {
|
|
197
|
+
$and: [
|
|
198
|
+
{ age: { $gte: 18 } },
|
|
199
|
+
{ status: { $eq: 'active' } }
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// $or - 任一条件满足
|
|
204
|
+
where: {
|
|
205
|
+
$or: [
|
|
206
|
+
{ status: { $eq: 'pending' } },
|
|
207
|
+
{ status: { $eq: 'processing' } }
|
|
208
|
+
]
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 嵌套组合
|
|
212
|
+
where: {
|
|
213
|
+
$and: [
|
|
214
|
+
{ age: { $gte: 18 } },
|
|
215
|
+
{
|
|
216
|
+
$or: [
|
|
217
|
+
{ country: { $eq: '中国' } },
|
|
218
|
+
{ country: { $eq: '美国' } }
|
|
219
|
+
]
|
|
220
|
+
}
|
|
221
|
+
]
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
#### 常见错误 vs 正确写法
|
|
226
|
+
|
|
227
|
+
| 错误写法 | 正确写法 | 错误原因 |
|
|
228
|
+
|---------|---------|---------|
|
|
229
|
+
| `where: { status: 'active' }` | `where: { status: { $eq: 'active' } }` | 缺少操作符 |
|
|
230
|
+
| `fields: ['id', 'name']` | `select: ['id', 'name']` | 参数名错误 |
|
|
231
|
+
| `sort: [{ createdAt: 'desc' }]` | `orderBy: [{ createdAt: 'desc' }]` | 参数名错误 |
|
|
232
|
+
| `where: { name: 'like %keyword%' }` | `where: { name: { $contain: 'keyword' } }` | 不支持 SQL LIKE 语法 |
|
|
233
|
+
| `where: { age: '>= 18' }` | `where: { age: { $gte: 18 } }` | 不支持字符串比较 |
|
|
234
|
+
| `where: { age: { $gt: 18, $lt: 65 } }` | `where: { age: { $gte: 18, $lte: 65 } }` | 区间查询用 gte/lte |
|
|
235
|
+
| `orderBy: 'createdAt desc'` | `orderBy: [{ createdAt: 'desc' }]` | 必须是数组格式 |
|
|
236
|
+
| `select: 'id,name,status'` | `select: ['id', 'name', 'status']` | 必须是数组格式 |
|
|
237
|
+
| `where: { $or: { status: 'A', type: 'X' } }` | `where: { $or: [{ status: { $eq: 'A' } }, { type: { $eq: 'X' } }] }` | $or 值必须是数组 |
|
|
238
|
+
| `page: 1, limit: 20` | `currentPage: 1, pageSize: 20` | 参数名错误 |
|
|
239
|
+
|
|
240
|
+
#### 正确示例
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
// 数据集: 用户信息 | 数据表: users
|
|
244
|
+
const result = await client.models.users.filter({
|
|
193
245
|
where: {
|
|
194
246
|
$and: [
|
|
195
|
-
{
|
|
247
|
+
{ status: { $eq: 'active' } },
|
|
248
|
+
{ age: { $gte: 18, $lte: 65 } },
|
|
196
249
|
{
|
|
197
250
|
$or: [
|
|
198
|
-
{
|
|
199
|
-
{
|
|
251
|
+
{ name: { $contain: keyword } },
|
|
252
|
+
{ email: { $contain: keyword } }
|
|
200
253
|
]
|
|
201
254
|
}
|
|
202
255
|
]
|
|
203
|
-
}
|
|
256
|
+
},
|
|
257
|
+
select: ['id', 'name', 'email', 'status', 'createdAt'],
|
|
258
|
+
orderBy: [{ createdAt: 'desc' }, { name: 'asc' }],
|
|
259
|
+
currentPage: 1,
|
|
260
|
+
pageSize: 20,
|
|
204
261
|
});
|
|
262
|
+
|
|
263
|
+
// 使用返回数据
|
|
264
|
+
result.tableData.forEach(user => {
|
|
265
|
+
console.log(user.name);
|
|
266
|
+
});
|
|
267
|
+
console.log('总计:', result.total);
|
|
205
268
|
```
|
|
206
269
|
|
|
270
|
+
#### 返回值结构
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
// filter 返回结构
|
|
274
|
+
interface FilterResult<T> {
|
|
275
|
+
tableData: T[]; // 数据列表
|
|
276
|
+
total: number; // 总数(用于分页)
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### 5. 数据模型操作
|
|
281
|
+
|
|
282
|
+
#### 列表查询:必须使用 `filter()` 方法
|
|
283
|
+
|
|
284
|
+
**禁止使用 `getList()` 进行列表查询**,必须使用 `filter()`。
|
|
285
|
+
|
|
286
|
+
`filter()` 方法支持 **OpenAPI 和 WebAPI 两种模式**:
|
|
287
|
+
- 强大的查询能力(支持复杂条件、范围查询、模糊匹配)
|
|
288
|
+
- 高性能(通过 `select` 参数只返回需要的字段)
|
|
289
|
+
- 简洁的返回结构(直接返回 `{ tableData, total }`)
|
|
290
|
+
|
|
207
291
|
#### SQL API(v1.1.19+)
|
|
208
292
|
|
|
209
293
|
**注意事项**:
|
|
@@ -217,24 +301,191 @@ const result = await client.models.<modelName>.filter({
|
|
|
217
301
|
|
|
218
302
|
```typescript
|
|
219
303
|
// 基础查询(无参数)
|
|
220
|
-
const data = await client.api.executeSql(
|
|
304
|
+
const data = await client.api.executeSql("fc8e7777-06e3847d");
|
|
221
305
|
|
|
222
306
|
if (data.execSuccess && data.execResult) {
|
|
223
|
-
data.execResult.forEach(row => console.log(row));
|
|
307
|
+
data.execResult.forEach((row) => console.log(row));
|
|
224
308
|
}
|
|
225
309
|
|
|
226
310
|
// 参数化查询(推荐)
|
|
227
|
-
const data = await client.api.executeSql(
|
|
228
|
-
userId:
|
|
229
|
-
startDate:
|
|
230
|
-
endDate:
|
|
311
|
+
const data = await client.api.executeSql("fc8e7777-06e3847d", {
|
|
312
|
+
userId: "123", // SQL 中使用 #{userId}
|
|
313
|
+
startDate: "2025-01-01", // SQL 中使用 #{startDate}
|
|
314
|
+
endDate: "2025-12-31", // SQL 中使用 #{endDate}
|
|
231
315
|
});
|
|
232
316
|
|
|
233
317
|
if (data.execSuccess && data.execResult) {
|
|
234
|
-
console.log(
|
|
318
|
+
console.log("查询成功", data.execResult);
|
|
235
319
|
}
|
|
236
320
|
```
|
|
237
321
|
|
|
322
|
+
### 6. MCP SQL 创建工作流(强制顺序)
|
|
323
|
+
|
|
324
|
+
**⚠️ 关键原则**:创建自定义 SQL 时,**必须严格按照以下顺序执行**,禁止跳过任何步骤!
|
|
325
|
+
|
|
326
|
+
#### 完整工作流程
|
|
327
|
+
|
|
328
|
+
```
|
|
329
|
+
第1步 → 第2步 → 第3步 → 第4步 → 第5步
|
|
330
|
+
↓ ↓ ↓ ↓ ↓
|
|
331
|
+
查询 生成 验证 保存 测试
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
#### 第1步:查询现有 SQL
|
|
335
|
+
|
|
336
|
+
**目标**:确认 SQL 是否已存在,避免重复创建。
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
// 使用 MCP 工具:list_sql_queries
|
|
340
|
+
const sqlList = await list_sql_queries({
|
|
341
|
+
keyword: '<keyword>', // 可选:按关键词搜索
|
|
342
|
+
sqlCode: '<sql-code>', // 可选:按 code 精确查询
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// 检查是否已存在
|
|
346
|
+
const existingSql = sqlList.sqls.find(s => s.sqlCode === '<sql-code>');
|
|
347
|
+
if (existingSql) {
|
|
348
|
+
console.log('SQL 已存在:', existingSql.sqlName);
|
|
349
|
+
// 进入第3步验证现有 SQL
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
#### 第2步:生成 SQL 代码
|
|
354
|
+
|
|
355
|
+
**目标**:基于数据集结构生成正确的 SQL。
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
// 使用 MCP 工具:get_dataset_detail
|
|
359
|
+
// 先获取表结构,确保字段名正确
|
|
360
|
+
const detail = await get_dataset_detail({
|
|
361
|
+
datasetCode: '<dataset-code>',
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// 确认字段存在
|
|
365
|
+
const fieldCodes = detail.fields.map(f => f.code);
|
|
366
|
+
if (!fieldCodes.includes('<field-name>')) {
|
|
367
|
+
throw new Error(`字段 ${<field-name>} 不存在`);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// 编写 SQL(必须先确认表名和字段名)
|
|
371
|
+
const sqlContent = `
|
|
372
|
+
SELECT id, <field1>, <field2>
|
|
373
|
+
FROM ${detail.basic.tableName}
|
|
374
|
+
WHERE <condition>
|
|
375
|
+
`;
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
#### 第3步:验证 SQL 内容
|
|
379
|
+
|
|
380
|
+
**目标**:在保存前验证 SQL 语法和结构正确性。
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
// 使用 MCP 工具:validate_sql_content
|
|
384
|
+
const validation = await validate_sql_content({
|
|
385
|
+
sqlContent: sqlContent,
|
|
386
|
+
dbId: detail.basic.database.dbId,
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
if (!validation.validation.valid) {
|
|
390
|
+
console.error('SQL 验证失败:', validation.validation.errors);
|
|
391
|
+
// 修复后重新验证
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
#### 第4步:保存 SQL
|
|
397
|
+
|
|
398
|
+
**目标**:将验证通过的 SQL 保存到平台。
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
// 使用 MCP 工具:save_or_update_custom_sql
|
|
402
|
+
const result = await save_or_update_custom_sql({
|
|
403
|
+
sqlName: '<sql-name>',
|
|
404
|
+
sqlCode: '<sql-code>',
|
|
405
|
+
sqlContent: sqlContent,
|
|
406
|
+
dbId: detail.basic.database.dbId,
|
|
407
|
+
verifyAfterSave: true, // 保存后自动验证
|
|
408
|
+
});
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
#### 第5步:测试执行
|
|
412
|
+
|
|
413
|
+
**目标**:验证 SQL 能正确执行并返回预期数据。
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
// 使用 MCP 工具:execute_custom_sql
|
|
417
|
+
const execResult = await execute_custom_sql({
|
|
418
|
+
sqlCode: '<sql-code>',
|
|
419
|
+
params: { /* 参数 */ },
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
if (!execResult.execSuccess) {
|
|
423
|
+
console.error('SQL 执行失败:', execResult.execError);
|
|
424
|
+
} else {
|
|
425
|
+
console.log('SQL 执行成功,返回', execResult.rowCount, '行');
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
#### 禁止行为
|
|
430
|
+
|
|
431
|
+
| 禁止行为 | 正确做法 |
|
|
432
|
+
|---------|---------|
|
|
433
|
+
| 跳过第1步,直接创建 SQL | 先查询,避免重复 |
|
|
434
|
+
| 跳过第2步,直接写 SQL | 先获取表结构,确认字段 |
|
|
435
|
+
| 跳过第3步,直接保存 SQL | 先验证,确保语法正确 |
|
|
436
|
+
| 保存后不测试执行 | 必须测试,确保运行正确 |
|
|
437
|
+
| 假设 SQL code 存在 = SQL 正确 | 必须验证 SQL 内容 |
|
|
438
|
+
|
|
439
|
+
#### SQL 内容验证检查清单
|
|
440
|
+
|
|
441
|
+
创建或修改 SQL 时,必须确认:
|
|
442
|
+
|
|
443
|
+
- [ ] FROM 子句的表名正确(与 `datasetDetail.basic.tableName` 一致)
|
|
444
|
+
- [ ] SELECT 的字段名正确(与 `datasetDetail.fields[].code` 一致)
|
|
445
|
+
- [ ] WHERE 条件的字段名正确
|
|
446
|
+
- [ ] JOIN 条件的字段名在两个表中都存在
|
|
447
|
+
- [ ] 主键字段名正确(特别是 COUNT(DISTINCT xxx) 中的字段)
|
|
448
|
+
- [ ] 只包含 SELECT 查询(禁止 INSERT/UPDATE/DELETE)
|
|
449
|
+
- [ ] 参数占位符格式正确(使用 #{paramName})
|
|
450
|
+
|
|
451
|
+
#### 错误示例
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
// ❌ 错误:跳过验证步骤
|
|
455
|
+
await save_or_update_custom_sql({
|
|
456
|
+
sqlCode: 'my-query',
|
|
457
|
+
sqlContent: 'SELECT wrong_field FROM wrong_table', // 未验证
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
// ❌ 错误:只检查 SQL code 是否存在
|
|
461
|
+
const sqlList = await list_sql_queries();
|
|
462
|
+
if (sqlList.sqls.find(s => s.sqlCode === 'my-query')) {
|
|
463
|
+
// 假设 SQL 正确,但内容可能有问题
|
|
464
|
+
await client.api.executeSql('my-query');
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// ✅ 正确:完整的工作流程
|
|
468
|
+
// 1. 查询现有 SQL
|
|
469
|
+
const sqlList = await list_sql_queries({ sqlCode: 'my-query' });
|
|
470
|
+
|
|
471
|
+
// 2. 获取表结构
|
|
472
|
+
const detail = await get_dataset_detail({ datasetCode: 'xxx' });
|
|
473
|
+
|
|
474
|
+
// 3. 验证 SQL 内容
|
|
475
|
+
const validation = await validate_sql_content({
|
|
476
|
+
sqlContent: detail.sqlContent,
|
|
477
|
+
dbId: detail.basic.database.dbId,
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// 4. 保存 SQL
|
|
481
|
+
if (validation.validation.valid) {
|
|
482
|
+
await save_or_update_custom_sql({ /* ... */ });
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// 5. 测试执行
|
|
486
|
+
const result = await execute_custom_sql({ sqlCode: 'my-query' });
|
|
487
|
+
```
|
|
488
|
+
|
|
238
489
|
#### 用户列表 API(v1.1.19+)
|
|
239
490
|
|
|
240
491
|
**说明**:获取平台用户列表,内置 30 秒缓存。
|
|
@@ -469,8 +720,289 @@ result.tableData.forEach(item => {
|
|
|
469
720
|
|
|
470
721
|
|
|
471
722
|
|
|
723
|
+
## AntD UI 开发规范(禁止 AI 风格)
|
|
724
|
+
|
|
725
|
+
**重要**:ToB 企业应用需要专业、简洁的界面风格,严禁使用 AI 典型的花哨风格。
|
|
726
|
+
|
|
727
|
+
### 禁止事项(强制)
|
|
728
|
+
|
|
729
|
+
| 禁止项 | 说明 |
|
|
730
|
+
|-------|------|
|
|
731
|
+
| ❌ **禁止使用 emoji** | 包括但不限于:✨ 🚀 💡 ⚡ 🎯 📊 🔥 🎉 等 |
|
|
732
|
+
| ❌ **禁止使用 AI 味道的文案** | 如:"让我们开始吧!"、"太棒了!"、"哇!"、"试试看!" |
|
|
733
|
+
| ❌ **禁止使用花哨的颜色** | 只使用 AntD token 颜色,禁止自定义霓虹色、渐变色 |
|
|
734
|
+
| ❌ **禁止过度动画** | 只使用 AntD 内置动画,禁止自定义花哨动画效果 |
|
|
735
|
+
| ❌ **禁止使用非专业称呼** | 如:"小伙伴"、"亲"、"宝宝" 等 |
|
|
736
|
+
| ❌ **禁止使用可爱的图标** | 如:🎈 🎁 🍭 🌈 等非业务相关图标 |
|
|
737
|
+
| ❌ **禁止使用感叹号** | 标题和按钮文案中禁止使用 "!" |
|
|
738
|
+
|
|
739
|
+
### 文案规范
|
|
740
|
+
|
|
741
|
+
#### 正确的文案风格
|
|
742
|
+
|
|
743
|
+
| 场景 | 正确 ✅ | 错误 ❌ |
|
|
744
|
+
|------|--------|--------|
|
|
745
|
+
| 按钮 | 保存 / 取消 / 确认 | 保存吧! / 再想想 / 好的! |
|
|
746
|
+
| 提示 | 操作成功 / 加载中 / 操作失败 | 太棒了!/ 马上就好~ / 哎呀出错了 |
|
|
747
|
+
| 标题 | 用户管理 / 数据列表 | 🎯 用户管理 / 📊 数据列表 |
|
|
748
|
+
| 空状态 | 暂无数据 | 还没有数据哦~ / 空空如也 |
|
|
749
|
+
| 确认弹窗 | 确定要删除吗? | 你真的要删除吗?😱 |
|
|
750
|
+
|
|
751
|
+
#### 文案示例
|
|
752
|
+
|
|
753
|
+
```typescript
|
|
754
|
+
// ✅ 正确:简洁专业
|
|
755
|
+
const messages = {
|
|
756
|
+
success: '操作成功',
|
|
757
|
+
error: '操作失败',
|
|
758
|
+
confirm: '确定要删除这条记录吗?',
|
|
759
|
+
cancel: '取消',
|
|
760
|
+
submit: '提交',
|
|
761
|
+
save: '保存',
|
|
762
|
+
loading: '加载中...',
|
|
763
|
+
noData: '暂无数据',
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
// ❌ 错误:AI 味道
|
|
767
|
+
const messages = {
|
|
768
|
+
success: '🎉 太棒了!操作成功!',
|
|
769
|
+
error: '哎呀,出错了 😢',
|
|
770
|
+
confirm: '你真的要删除吗?确定吗?',
|
|
771
|
+
cancel: '再想想吧~',
|
|
772
|
+
submit: '提交表单 ✨',
|
|
773
|
+
save: '保存更改 💾',
|
|
774
|
+
loading: '马上就好~请稍等 ⏳',
|
|
775
|
+
noData: '空空如也~还没有数据哦',
|
|
776
|
+
};
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
### 颜色规范
|
|
780
|
+
|
|
781
|
+
#### 使用 AntD Token
|
|
782
|
+
|
|
783
|
+
```typescript
|
|
784
|
+
// ✅ 正确:使用 AntD token
|
|
785
|
+
import { theme } from 'antd';
|
|
786
|
+
|
|
787
|
+
const { token } = theme.useToken();
|
|
788
|
+
|
|
789
|
+
const styles = {
|
|
790
|
+
primary: token.colorPrimary,
|
|
791
|
+
success: token.colorSuccess,
|
|
792
|
+
error: token.colorError,
|
|
793
|
+
warning: token.colorWarning,
|
|
794
|
+
text: token.colorText,
|
|
795
|
+
border: token.colorBorder,
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
// ❌ 错误:自定义花哨颜色
|
|
799
|
+
const styles = {
|
|
800
|
+
primary: '#FF00FF', // 霓虹粉
|
|
801
|
+
success: '#00FF00', // 亮绿色
|
|
802
|
+
accent: '#FFD700', // 金色
|
|
803
|
+
gradient: 'linear-gradient(45deg, #ff6b6b, #feca57)', // 渐变
|
|
804
|
+
};
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
### 组件使用规范
|
|
808
|
+
|
|
809
|
+
#### 按钮
|
|
810
|
+
|
|
811
|
+
```typescript
|
|
812
|
+
// ✅ 正确:简洁明了
|
|
813
|
+
<Button type="primary" onClick={handleSave}>
|
|
814
|
+
保存
|
|
815
|
+
</Button>
|
|
816
|
+
<Button danger onClick={handleDelete}>
|
|
817
|
+
删除
|
|
818
|
+
</Button>
|
|
819
|
+
|
|
820
|
+
// ❌ 错误:花哨风格
|
|
821
|
+
<Button type="primary" onClick={handleSave}>
|
|
822
|
+
💾 保存更改
|
|
823
|
+
</Button>
|
|
824
|
+
<Button danger onClick={handleDelete}>
|
|
825
|
+
🗑️ 哎呀,要删除吗?
|
|
826
|
+
</Button>
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
#### 表格
|
|
830
|
+
|
|
831
|
+
```typescript
|
|
832
|
+
// ✅ 正确:使用 ProTable 或 Table + Pagination
|
|
833
|
+
import { Table } from 'antd';
|
|
834
|
+
|
|
835
|
+
<Table
|
|
836
|
+
columns={columns}
|
|
837
|
+
dataSource={data}
|
|
838
|
+
pagination={{
|
|
839
|
+
current: currentPage,
|
|
840
|
+
pageSize: pageSize,
|
|
841
|
+
total: total,
|
|
842
|
+
showSizeChanger: true,
|
|
843
|
+
showQuickJumper: true,
|
|
844
|
+
showTotal: (total) => `共 ${total} 条`,
|
|
845
|
+
}}
|
|
846
|
+
/>
|
|
847
|
+
|
|
848
|
+
// ❌ 错误:花哨的空状态
|
|
849
|
+
<Table
|
|
850
|
+
columns={columns}
|
|
851
|
+
dataSource={data}
|
|
852
|
+
locale={{
|
|
853
|
+
emptyText: (
|
|
854
|
+
<div>
|
|
855
|
+
<span>🎭 </span>
|
|
856
|
+
<span>还没有数据哦~</span>
|
|
857
|
+
</div>
|
|
858
|
+
),
|
|
859
|
+
}}
|
|
860
|
+
/>
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
#### 消息提示
|
|
864
|
+
|
|
865
|
+
```typescript
|
|
866
|
+
// ✅ 正确:简洁专业
|
|
867
|
+
import { message } from 'antd';
|
|
868
|
+
|
|
869
|
+
message.success('保存成功');
|
|
870
|
+
message.error('操作失败,请重试');
|
|
871
|
+
message.warning('请填写必填项');
|
|
872
|
+
message.info('正在处理...');
|
|
873
|
+
|
|
874
|
+
// ❌ 错误:AI 味道
|
|
875
|
+
message.success('🎉 太棒了!保存成功!');
|
|
876
|
+
message.error('😱 哎呀出错了!请再试一次');
|
|
877
|
+
message.warning('⚠️ 嘿!别忘了填写必填项哦');
|
|
878
|
+
message.info('🔔 正在为你处理中,请稍等~');
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
#### Modal 确认
|
|
882
|
+
|
|
883
|
+
```typescript
|
|
884
|
+
// ✅ 正确:专业简洁
|
|
885
|
+
import { Modal } from 'antd';
|
|
886
|
+
|
|
887
|
+
Modal.confirm({
|
|
888
|
+
title: '确认删除',
|
|
889
|
+
content: '确定要删除这条记录吗?',
|
|
890
|
+
okText: '确定',
|
|
891
|
+
cancelText: '取消',
|
|
892
|
+
onOk: handleDelete,
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
// ❌ 错误:AI 味道
|
|
896
|
+
Modal.confirm({
|
|
897
|
+
title: '🤔 确认要删除吗?',
|
|
898
|
+
content: '删除后可就找不回来了哦!你确定要删除这条记录吗?',
|
|
899
|
+
okText: '是的,删除!',
|
|
900
|
+
cancelText: '再想想~',
|
|
901
|
+
icon: <ExclamationCircleOutlined style={{ color: '#FFD700' }} />,
|
|
902
|
+
});
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
### 表单规范
|
|
906
|
+
|
|
907
|
+
```typescript
|
|
908
|
+
// ✅ 正确:简洁清晰的错误提示
|
|
909
|
+
<Form>
|
|
910
|
+
<Form.Item
|
|
911
|
+
name="username"
|
|
912
|
+
label="用户名"
|
|
913
|
+
rules={[
|
|
914
|
+
{ required: true, message: '请输入用户名' },
|
|
915
|
+
{ min: 3, message: '用户名至少3个字符' },
|
|
916
|
+
]}
|
|
917
|
+
>
|
|
918
|
+
<Input placeholder="请输入用户名" />
|
|
919
|
+
</Form.Item>
|
|
920
|
+
</Form>
|
|
921
|
+
|
|
922
|
+
// ❌ 错误:花哨的错误提示
|
|
923
|
+
<Form>
|
|
924
|
+
<Form.Item
|
|
925
|
+
name="username"
|
|
926
|
+
label="👤 用户名"
|
|
927
|
+
rules={[
|
|
928
|
+
{ required: true, message: '哎呀!别忘了填写用户名哦~' },
|
|
929
|
+
{ min: 3, message: '用户名太短啦,至少要3个字符呢!' },
|
|
930
|
+
]}
|
|
931
|
+
>
|
|
932
|
+
<Input placeholder="输入用户名试试看~ ✨" />
|
|
933
|
+
</Form.Item>
|
|
934
|
+
</Form>
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
### 空状态规范
|
|
938
|
+
|
|
939
|
+
```typescript
|
|
940
|
+
// ✅ 正确:简洁专业
|
|
941
|
+
import { Empty } from 'antd';
|
|
942
|
+
|
|
943
|
+
<Empty
|
|
944
|
+
description="暂无数据"
|
|
945
|
+
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
|
946
|
+
/>
|
|
947
|
+
|
|
948
|
+
// 带操作按钮
|
|
949
|
+
<Empty
|
|
950
|
+
description="暂无数据"
|
|
951
|
+
image={Empty.PRESENTED_IMAGE_SIMPLE}>
|
|
952
|
+
<Button type="primary">创建数据</Button>
|
|
953
|
+
</Empty>
|
|
954
|
+
|
|
955
|
+
// ❌ 错误:AI 味道
|
|
956
|
+
<Empty
|
|
957
|
+
description={
|
|
958
|
+
<div>
|
|
959
|
+
<div>🎭 还没有数据哦~</div>
|
|
960
|
+
<div style={{ fontSize: 12, color: '#999' }}>
|
|
961
|
+
快来创建第一条数据吧!✨
|
|
962
|
+
</div>
|
|
963
|
+
</div>
|
|
964
|
+
}
|
|
965
|
+
/>
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
### 图标使用规范
|
|
969
|
+
|
|
970
|
+
```typescript
|
|
971
|
+
// ✅ 正确:使用 @ant-design/icons
|
|
972
|
+
import {
|
|
973
|
+
UserOutlined,
|
|
974
|
+
DeleteOutlined,
|
|
975
|
+
EditOutlined,
|
|
976
|
+
SearchOutlined,
|
|
977
|
+
} from '@ant-design/icons';
|
|
978
|
+
|
|
979
|
+
// 按钮图标(可选,保持简洁)
|
|
980
|
+
<Button icon={<PlusOutlined />}>新建</Button>
|
|
981
|
+
|
|
982
|
+
// ❌ 错误:使用 emoji 作为图标
|
|
983
|
+
<Button>🆕 新建</Button>
|
|
984
|
+
<Button>✏️ 编辑</Button>
|
|
985
|
+
<Button>🗑️ 删除</Button>
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
### 检查清单
|
|
989
|
+
|
|
990
|
+
在编写 UI 代码时,确保:
|
|
991
|
+
|
|
992
|
+
- [ ] 没有 emoji
|
|
993
|
+
- [ ] 没有感叹号
|
|
994
|
+
- [ ] 文案简洁专业
|
|
995
|
+
- [ ] 使用 AntD token 颜色
|
|
996
|
+
- [ ] 使用 @ant-design/icons 而非 emoji
|
|
997
|
+
- [ ] 按钮文案为动词(保存/删除/编辑)
|
|
998
|
+
- [ ] 提示文案中性(操作成功/操作失败)
|
|
999
|
+
- [ ] 没有过度动画效果
|
|
1000
|
+
|
|
1001
|
+
|
|
1002
|
+
|
|
472
1003
|
## 禁止事项
|
|
473
1004
|
|
|
1005
|
+
### SDK 相关
|
|
474
1006
|
1. **不要臆测 API** - 使用不存在的方法或参数
|
|
475
1007
|
2. **不要臆测字段** - 未通过 MCP 确认字段是否存在
|
|
476
1008
|
3. **不要混淆认证模式** - 在错误的模式使用功能(如 OpenAPI 模式使用 delete)
|
|
@@ -478,8 +1010,29 @@ result.tableData.forEach(item => {
|
|
|
478
1010
|
5. **不要假设返回值结构** - 注意不同方法返回值的差异
|
|
479
1011
|
6. **不要在循环中调用 API** - 使用 Promise.all 批量处理
|
|
480
1012
|
7. **不要在每次渲染时调用 API** - 使用 useEffect 控制
|
|
481
|
-
8.
|
|
482
|
-
|
|
1013
|
+
8. **不要使用 getList()** - 列表查询必须使用 filter()
|
|
1014
|
+
|
|
1015
|
+
### Filter 查询相关
|
|
1016
|
+
9. **不要忘记使用操作符** - where 条件必须使用 `$eq`、`$gte` 等操作符
|
|
1017
|
+
10. **不要使用错误的参数名** - 是 `select` 不是 `fields`,是 `orderBy` 不是 `sortList`
|
|
1018
|
+
11. **不要使用 SQL 语法** - 不支持 `LIKE`、`>=` 等,必须用 `$contain`、`$gte` 等
|
|
1019
|
+
12. **不要忽略参数格式** - `orderBy` 和 `select` 必须是数组,不能是字符串
|
|
1020
|
+
|
|
1021
|
+
### SQL 相关
|
|
1022
|
+
13. **不要跳过 MCP SQL 工作流步骤** - 必须按顺序:查询→生成→验证→保存→测试
|
|
1023
|
+
14. **不要忘记检查 execSuccess** - executeSql 返回的数据必须检查 `execSuccess` 状态
|
|
1024
|
+
15. **不要假设 executeSql 返回数组** - 返回的是 `{ execSuccess, execResult }` 对象
|
|
1025
|
+
16. **不要假设SQL code存在 = SQL正确** - 必须验证SQL内容中的表名和字段名与实际数据库结构匹配
|
|
1026
|
+
17. **不要仅检查SQL code是否存在** - 必须获取SQL内容,对比表名和字段名,修复SQL内容本身
|
|
1027
|
+
18. **不要在自定义 SQL 中使用 INSERT/UPDATE/DELETE** - 只允许 SELECT 查询
|
|
1028
|
+
|
|
1029
|
+
### UI 相关
|
|
1030
|
+
19. **不要使用 emoji** - 包括按钮、标题、提示文案等所有地方
|
|
1031
|
+
20. **不要使用 AI 味道的文案** - 如"太棒了!"、"让我们开始吧!"、"试试看!"
|
|
1032
|
+
21. **不要使用花哨的颜色** - 只使用 AntD token 颜色
|
|
1033
|
+
22. **不要使用感叹号** - 标题和按钮文案中禁止使用 "!"
|
|
1034
|
+
23. **不要使用自定义动画** - 只使用 AntD 内置动画
|
|
1035
|
+
24. **不要使用非专业称呼** - 如"小伙伴"、"亲"、"宝宝" 等
|
|
483
1036
|
|
|
484
1037
|
|
|
485
1038
|
|
|
@@ -499,14 +1052,28 @@ result.tableData.forEach(item => {
|
|
|
499
1052
|
|
|
500
1053
|
### 记住
|
|
501
1054
|
|
|
1055
|
+
#### SDK 核心
|
|
502
1056
|
- **成功**:SDK 返回 API 响应的 `data` 字段内容
|
|
503
1057
|
- **失败**:抛出 `LovrabetError` 异常(不是返回错误)
|
|
504
1058
|
- **所有调用**:必须用 `try-catch` 包裹
|
|
505
1059
|
- **先获取元数据**:使用 MCP 确认字段存在
|
|
506
|
-
|
|
507
|
-
|
|
1060
|
+
|
|
1061
|
+
#### Filter 查询
|
|
1062
|
+
- **列表查询用 filter**:必须使用 `filter()` 方法,禁止使用 `getList()`
|
|
1063
|
+
- **注意参数名**:`select`(不是 `fields`),`orderBy`(不是 `sortList`),`currentPage`/`pageSize`(不是 `page`/`limit`)
|
|
1064
|
+
- **必须使用操作符**:`where: { status: { $eq: 'active' } }`,不是 `where: { status: 'active' }`
|
|
1065
|
+
- **参数必须是数组**:`select: ['id', 'name']`,不是 `select: 'id,name'`
|
|
1066
|
+
|
|
1067
|
+
#### MCP SQL 流程
|
|
1068
|
+
- **严格执行 5 步流程**:查询→生成→验证→保存→测试
|
|
508
1069
|
- **executeSql 检查状态**:应用层必须检查 `execSuccess` 状态
|
|
509
|
-
-
|
|
1070
|
+
- **SQL验证必须系统化**:创建或使用SQL时,必须对比SQL内容与实际表结构,验证表名和字段名
|
|
1071
|
+
- **SQL code存在 ≠ SQL正确**:必须获取SQL内容,验证表名、字段名、JOIN条件是否正确
|
|
1072
|
+
|
|
1073
|
+
#### UI 规范
|
|
1074
|
+
- **禁止 emoji**:所有地方都不使用 emoji
|
|
1075
|
+
- **文案简洁专业**:"保存"而不是"保存吧!","操作成功"而不是"太棒了!"
|
|
1076
|
+
- **使用 AntD token**:颜色使用 `token.colorPrimary` 等,不自定义花哨颜色
|
|
510
1077
|
|
|
511
1078
|
|
|
512
1079
|
|
|
@@ -520,12 +1087,17 @@ result.tableData.forEach(item => {
|
|
|
520
1087
|
- `<modelName>` - 模型名称,如 `users`、`orders` 等
|
|
521
1088
|
- `<dataset-code>` - 数据集代码
|
|
522
1089
|
- `<table-name>` - 表名
|
|
1090
|
+
- `<table-alias>` - 表别名(SQL 中使用)
|
|
523
1091
|
- `<id>` - 记录 ID
|
|
524
1092
|
- `<field>`, `<field1>`, `<field2>` - 字段名称
|
|
525
1093
|
- `<fieldName>` - 字段名称(用于排序等)
|
|
1094
|
+
- `<field-name>` - 具体字段名
|
|
1095
|
+
- `<wrong-field>`, `<correct-field>` - 错误/正确的字段名(SQL 验证示例)
|
|
1096
|
+
- `<wrong-pk>`, `<correct-pk>` - 错误/正确的主键字段名
|
|
1097
|
+
- `<wrong-table>`, `<correct-table>` - 错误/正确的表名
|
|
526
1098
|
- `<value>`, `<value1>`, `<value2>` - 字段值
|
|
527
1099
|
- `<sql-code>` - SQL 代码
|
|
528
|
-
- `<
|
|
1100
|
+
- `<sql-name>` - SQL 名称
|
|
529
1101
|
- `<value-field>` - 用作选项值的字段
|
|
530
1102
|
- `<label-field>` - 用作显示文本的字段
|
|
531
1103
|
|
|
@@ -545,3 +1117,24 @@ result.tableData.forEach(item => {
|
|
|
545
1117
|
- [官方文档](https://open.lovrabet.com/docs/lovrabet-sdk/intro)
|
|
546
1118
|
- [API 参考](https://open.lovrabet.com/docs/lovrabet-sdk/api-reference)
|
|
547
1119
|
- [Filter API](https://open.lovrabet.com/docs/lovrabet-sdk/filter-api)
|
|
1120
|
+
|
|
1121
|
+
---
|
|
1122
|
+
|
|
1123
|
+
## 更新日志
|
|
1124
|
+
|
|
1125
|
+
### v2.0.0 (2025-01-17)
|
|
1126
|
+
- ✨ **新增 SDK Filter 查询构建规范(强制)** - 详细说明 filter 的正确用法,列举 10 种常见错误
|
|
1127
|
+
- ✨ **新增 MCP SQL 创建工作流(强制顺序)** - 明确 5 步流程顺序:查询→生成→验证→保存→测试
|
|
1128
|
+
- ✨ **新增 AntD UI 开发规范(禁止 AI 风格)** - 禁止 emoji、AI 味道文案、花哨颜色等
|
|
1129
|
+
- 📝 重组禁止事项为 4 大类(SDK / Filter / SQL / UI),共 24 条
|
|
1130
|
+
- 📝 更新"记住"部分,分 4 大类总结关键要点
|
|
1131
|
+
|
|
1132
|
+
### v1.0.0 (2025-11-15)
|
|
1133
|
+
- 📋 完整的 SDK 使用规范
|
|
1134
|
+
- 🔧 MCP Tools 使用指南
|
|
1135
|
+
- ⚡ 性能优化建议
|
|
1136
|
+
- 🛡️ 错误处理最佳实践
|
|
1137
|
+
|
|
1138
|
+
---
|
|
1139
|
+
|
|
1140
|
+
> update:2025-01-17
|