@loom-framework/core 0.1.0-alpha.90 → 0.1.0-alpha.92
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/builtin-skills/loom/SKILL.md +18 -12
- package/builtin-skills/loom/references/README.md +146 -89
- package/builtin-skills/loom/references/dashboard.md +101 -82
- package/builtin-skills/loom/references/data-model.md +38 -0
- package/dist/backend/index.js +1 -1
- package/dist/backend/index.js.map +1 -1
- package/dist/backend/routes/data.d.ts +8 -7
- package/dist/backend/routes/data.d.ts.map +1 -1
- package/dist/backend/routes/data.js +32 -6
- package/dist/backend/routes/data.js.map +1 -1
- package/dist/cli/commands/generate-dashboard.d.ts +2 -2
- package/dist/cli/commands/generate-dashboard.js +19 -19
- package/dist/cli/commands/generate-dashboard.js.map +1 -1
- package/dist/cli/commands/generate-page.d.ts.map +1 -1
- package/dist/cli/commands/generate-page.js +28 -34
- package/dist/cli/commands/generate-page.js.map +1 -1
- package/dist/cli/helpers/app-tsx-wiring.js +1 -1
- package/dist/cli/helpers/app-tsx-wiring.js.map +1 -1
- package/dist/cli/helpers/column-template.d.ts +1 -1
- package/dist/cli/helpers/column-template.d.ts.map +1 -1
- package/dist/cli/helpers/column-template.js +13 -6
- package/dist/cli/helpers/column-template.js.map +1 -1
- package/dist/cli/helpers/field-template.d.ts +1 -1
- package/dist/cli/helpers/field-template.d.ts.map +1 -1
- package/dist/cli/helpers/field-template.js +6 -1
- package/dist/cli/helpers/field-template.js.map +1 -1
- package/dist/cli/templates/frontend-entry.d.ts.map +1 -1
- package/dist/cli/templates/frontend-entry.js +4 -11
- package/dist/cli/templates/frontend-entry.js.map +1 -1
- package/dist/config.d.ts +151 -9
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -1
- package/dist/config.js.map +1 -1
- package/dist/dashboard-config.d.ts +1 -7
- package/dist/dashboard-config.d.ts.map +1 -1
- package/dist/dashboard-config.js +1 -30
- package/dist/dashboard-config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/types/config.d.ts +2 -0
- package/dist/types/config.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -53,19 +53,13 @@ loom generate page <Name> --model <model-name>
|
|
|
53
53
|
|
|
54
54
|
### 4. 生成 Dashboard(数据概览)
|
|
55
55
|
|
|
56
|
-
Dashboard
|
|
57
|
-
|
|
58
|
-
**阶段 1:生成 dashboard.config.json**
|
|
59
|
-
|
|
60
|
-
根据 `loom.config.ts` 中的数据模型,站在用户角度设计 Dashboard,生成 `dashboard.config.json` 到项目根目录。设计原则和配置 Schema 详见 `references/dashboard.md`。
|
|
61
|
-
|
|
62
|
-
**阶段 2:执行生成命令**
|
|
56
|
+
Dashboard 配置在 `loom.config.ts` 的 `dashboards` 字段中定义。设计原则和配置 Schema 详见 `references/dashboard.md`。
|
|
63
57
|
|
|
64
58
|
```bash
|
|
65
59
|
loom generate dashboard <name>
|
|
66
60
|
```
|
|
67
61
|
|
|
68
|
-
|
|
62
|
+
此命令从 `loom.config.ts` 的 `dashboards` 字段读取配置,自动生成 Dashboard 页面并接线 App.tsx。
|
|
69
63
|
|
|
70
64
|
### 5. 完善 AI Skill(必须)
|
|
71
65
|
|
|
@@ -94,7 +88,11 @@ loom dev
|
|
|
94
88
|
|
|
95
89
|
### 迭代:修改配置 → 重新生成
|
|
96
90
|
|
|
97
|
-
编辑 `loom.config.ts`(添加/修改模型或 AI 按钮)→ 重新执行步骤 3
|
|
91
|
+
编辑 `loom.config.ts`(添加/修改模型或 AI 按钮)→ 重新执行步骤 3,加 `--force` 覆盖已有页面:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
loom generate page <Name> --model <model-name> --force
|
|
95
|
+
```
|
|
98
96
|
|
|
99
97
|
### 内置 Skill 使用原则
|
|
100
98
|
|
|
@@ -109,7 +107,9 @@ loom dev
|
|
|
109
107
|
| `loom dev` | 启动开发环境(`--skip-generate` `--backend-only` `--frontend-only`) |
|
|
110
108
|
| `loom build` | 生产构建 |
|
|
111
109
|
| `loom generate page <name> --model <model>` | 生成 CRUD 页面 + 应用 Skill(含 aiButtons 则自动集成) |
|
|
112
|
-
| `loom generate
|
|
110
|
+
| `loom generate page <name> --model <model> --force` | 覆盖已有页面 |
|
|
111
|
+
| `loom generate dashboard <name>` | 从 loom.config.ts dashboards 生成 Dashboard 页面(fallback dashboard.config.json) |
|
|
112
|
+
| `loom generate dashboard <name> --force` | 覆盖已有 Dashboard 页面 |
|
|
113
113
|
| `loom generate capabilities` | 仅重新生成应用 Skill 的 `references/models.md` |
|
|
114
114
|
| `loom generate skill <name>` | 生成自定义 Skill 脚架 |
|
|
115
115
|
|
|
@@ -149,6 +149,12 @@ export default defineConfig({
|
|
|
149
149
|
prompt: '分析{{questionContent}},错误答案:{{wrongAnswer}}',
|
|
150
150
|
placement: 'wrong_questions', // 可选:限制按钮只出现在指定模型页面,逗号分隔多个模型名;省略则出现在所有页面
|
|
151
151
|
}],
|
|
152
|
+
dashboards: [{ // 可选:Dashboard 定义(不再使用独立的 dashboard.config.json)
|
|
153
|
+
name: 'overview', // 必填:唯一标识,同命名规则
|
|
154
|
+
description: '数据概览', // 可选
|
|
155
|
+
models: ['items'], // 必填:关联的数据模型
|
|
156
|
+
layout: [{ row: [{ type: 'stat', title: '总数', model: 'items', aggregate: 'count', span: 6 }] }],
|
|
157
|
+
}],
|
|
152
158
|
});
|
|
153
159
|
```
|
|
154
160
|
|
|
@@ -156,9 +162,9 @@ export default defineConfig({
|
|
|
156
162
|
|
|
157
163
|
## API 路由格式
|
|
158
164
|
|
|
159
|
-
后端 REST API 路径为 `/api/v1/data/<model-name>`,支持:`GET`(列表)、`GET /:id`(单条)、`POST`(创建)、`PUT /:id`(更新)、`DELETE /:id
|
|
165
|
+
后端 REST API 路径为 `/api/v1/data/<model-name>`,支持:`GET`(列表)、`GET /:id`(单条)、`POST`(创建)、`PUT /:id`(更新)、`DELETE /:id`(删除)、`GET /schema`(模型 Schema + AI 按钮)。
|
|
160
166
|
AI 相关:`POST /api/v1/chat`(SSE 流式对话)、`GET /api/v1/sessions`(会话列表)。
|
|
161
|
-
前端通过 `useData('<model-name>')`
|
|
167
|
+
前端通过 `useData('<model-name>')` 自动访问,无需手动拼接。`useSchema('<model-name>')` 获取模型字段和 AI 按钮定义,`filterOptions(schema, fieldName)` 和 `selectOptions(schema, fieldName)` 将 enum 值转为 antd 组件格式。
|
|
162
168
|
|
|
163
169
|
**注意:** `sqlite` 适配器在 Node.js ≥ 25 时可能因 `better-sqlite3` 原生模块未编译而报错,改用 `filesystem` 即可。
|
|
164
170
|
|
|
@@ -1,128 +1,185 @@
|
|
|
1
|
-
# Loom
|
|
1
|
+
# 用 Loom 开发 AI 平台
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## 30 秒理解 Loom
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
你写一个配置文件描述你的数据模型和 AI 按钮,Loom 生成一个可运行的平台。然后你和 AI 一起迭代它。
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
```
|
|
8
|
+
你写的 Loom 生成的 你和 AI 继续做的
|
|
9
|
+
loom.config.ts → 页面 + API + AI 对话 → 改布局、加功能、调样式
|
|
10
|
+
```
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
- `@loom-framework/frontend-antd` — 前端组件库(项目运行时依赖,`loom init` 自动添加)
|
|
12
|
+
## 开发方式
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
npm install -g @loom-framework/core
|
|
16
|
-
```
|
|
14
|
+
Loom 项目的主要交互方式是**和 AI 对话**。你告诉 AI 想要什么,AI 通过 Loom Skill 知道怎么用 Loom 实现它。
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
两个入口,同一个 AI:
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
- **终端 Claude Code** — 适合搭建阶段、大范围代码修改
|
|
19
|
+
- **页面内对话框** — 适合日常操作数据、小范围页面调整
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
# 从全局安装复制
|
|
24
|
-
cp -r $(npm root -g)/@loom-framework/core/builtin-skills/loom ~/.claude/skills/loom
|
|
21
|
+
两个入口背后是同一个 Claude Code 引擎、同一套项目上下文。在哪边改都行。
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
cp -r node_modules/@loom-framework/core/builtin-skills/loom ~/.claude/skills/loom
|
|
28
|
-
```
|
|
23
|
+
## 第一个项目
|
|
29
24
|
|
|
30
|
-
|
|
25
|
+
假设你要做一个**错题管理平台**。
|
|
31
26
|
|
|
32
|
-
|
|
27
|
+
### 第 1 步:告诉 AI 你的想法
|
|
33
28
|
|
|
34
|
-
|
|
29
|
+
在 Claude Code 中说:
|
|
35
30
|
|
|
36
|
-
|
|
31
|
+
> 用 loom 创建一个错题管理平台,项目名 wrong-questions,filesystem 适配器。
|
|
32
|
+
> 需要两个模型:错题记录(科目、题目内容、错误答案、正确答案、错误类型、是否掌握)和复习计划(错题ID、计划日期、状态)。
|
|
33
|
+
> AI 按钮要能分析错因和出类似题。
|
|
37
34
|
|
|
38
|
-
|
|
35
|
+
AI 会读取 Loom Skill,自动执行:
|
|
39
36
|
|
|
40
|
-
|
|
37
|
+
1. `loom init wrong-questions --adapter filesystem` — 创建项目
|
|
38
|
+
2. 编写 `loom.config.ts` — 把你的需求转成配置
|
|
39
|
+
3. `loom generate page` — 生成页面
|
|
40
|
+
4. `loom generate dashboard` — 生成数据概览
|
|
41
|
+
5. 补充项目级 AI Skill — 让 AI 理解你的业务语义
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
- "创建一个 loom 项目"
|
|
44
|
-
- "为系统添加页面"
|
|
45
|
-
- "配置 AI 按钮"
|
|
43
|
+
你也可以自己跑命令,但通过 Loom Skill 让 AI 来做更省事 — 它知道每一步该做什么。
|
|
46
44
|
|
|
47
|
-
|
|
45
|
+
### 第 2 步:看看效果
|
|
48
46
|
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
Claude: [读取 SKILL.md] → 运行 loom init → 编辑 config →
|
|
53
|
-
loom generate page → loom dev
|
|
47
|
+
```bash
|
|
48
|
+
cd wrong-questions
|
|
49
|
+
loom dev
|
|
54
50
|
```
|
|
55
51
|
|
|
56
|
-
|
|
52
|
+
打开 http://localhost:5173 ,你的平台已经能用了:
|
|
53
|
+
- 左侧导航有「错题记录」和「复习计划」页面
|
|
54
|
+
- 每个页面有新增、编辑、删除、筛选
|
|
55
|
+
- 错题记录每行有 AI 按钮:分析错因、出类似题
|
|
56
|
+
- 右下角有 AI 对话框,可以自然语言操作数据
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
2. **编辑配置**:在 `loom.config.ts` 中定义数据模型和 AI 按钮
|
|
60
|
-
3. **生成页面**:`loom generate page <Name> --model <model>`
|
|
61
|
-
4. **启动开发**:`loom dev` → http://localhost:5173
|
|
58
|
+
### 第 3 步:继续和 AI 聊
|
|
62
59
|
|
|
63
|
-
|
|
60
|
+
平台跑起来后,你主要的工作方式是对 AI 说需求:
|
|
64
61
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
| 你说的 | AI 做的 |
|
|
63
|
+
|-------|--------|
|
|
64
|
+
| "帮我记一道数学错题,3+5=7,正确答案是8" | 用 `loom data write` 创建记录 |
|
|
65
|
+
| "看看哪些错题还没掌握" | 查数据并展示 |
|
|
66
|
+
| "把这个页面改成卡片布局" | 修改页面 TSX |
|
|
67
|
+
| "加一个学期字段" | 改 loom.config.ts + 改页面 + 更新 Skill references |
|
|
68
|
+
| "加一个科目分布的饼图" | 修改 Dashboard 页面 |
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
这些操作在终端 Claude Code 和页面对话框里都能做。
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
|------|------|---------|-----------|
|
|
73
|
-
| AI Chat | 自然语言对话 | AppShell 右下角浮动按钮 | 开箱即用,无需配置 |
|
|
74
|
-
| AI Buttons | 一键 AI 动作 | CRUD 页面操作列的 AI 下拉菜单 | 配置 aiButtons + generate page,自动集成 |
|
|
75
|
-
| CRUD | 结构化数据管理 | 导航菜单页面 | 配置 model + generate page,自动生成 |
|
|
72
|
+
## 配置变更
|
|
76
73
|
|
|
77
|
-
|
|
74
|
+
改了 `loom.config.ts`(加了字段、改了 enum),页面不会自动更新。告诉 AI 你改了什么,AI 会同步更新页面。
|
|
78
75
|
|
|
76
|
+
如果你想重来一个干净的页面:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
loom generate page WrongQuestions --model wrong_questions --force
|
|
79
80
|
```
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
81
|
+
|
|
82
|
+
`--force` 会用最新配置全量重建页面。页面不存在时直接 `loom generate page` 即可,不需要 `--force`。
|
|
83
|
+
|
|
84
|
+
## 配置写法
|
|
85
|
+
|
|
86
|
+
`loom.config.ts` 是唯一配置源,包含数据模型、AI 按钮和 Dashboard 定义。完整的字段类型和高级选项见 `references/data-model.md`。
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { defineConfig } from '@loom-framework/core';
|
|
90
|
+
|
|
91
|
+
export default defineConfig({
|
|
92
|
+
project: { name: 'my-app', description: '我的应用' },
|
|
93
|
+
data: {
|
|
94
|
+
defaultAdapter: 'filesystem',
|
|
95
|
+
models: [{
|
|
96
|
+
name: 'tasks',
|
|
97
|
+
description: '任务管理',
|
|
98
|
+
fields: [
|
|
99
|
+
{ name: 'title', type: 'string', required: true, description: '任务标题' },
|
|
100
|
+
{ name: 'status', type: 'string', enum: ['待办', '进行中', '已完成'], default: '待办' },
|
|
101
|
+
{ name: 'priority', type: 'string', enum: ['低', '中', '高'] },
|
|
102
|
+
],
|
|
103
|
+
}],
|
|
104
|
+
},
|
|
105
|
+
aiButtons: [{
|
|
106
|
+
id: 'breakdown',
|
|
107
|
+
label: '拆解任务',
|
|
108
|
+
prompt: '请将这个任务拆解为可执行的子步骤:{{title}}',
|
|
109
|
+
placement: 'tasks',
|
|
110
|
+
}],
|
|
111
|
+
dashboards: [{
|
|
112
|
+
name: 'overview',
|
|
113
|
+
description: '数据概览',
|
|
114
|
+
models: ['tasks'],
|
|
115
|
+
layout: [
|
|
116
|
+
{ row: [
|
|
117
|
+
{ type: 'stat', title: '任务总数', model: 'tasks', aggregate: 'count', span: 6 },
|
|
118
|
+
{ type: 'pie', title: '状态分布', model: 'tasks', groupBy: 'status', span: 8 },
|
|
119
|
+
{ type: 'bar', title: '优先级分布', model: 'tasks', groupBy: 'priority', span: 8 },
|
|
120
|
+
]}
|
|
121
|
+
],
|
|
122
|
+
}],
|
|
123
|
+
});
|
|
89
124
|
```
|
|
90
125
|
|
|
91
|
-
|
|
126
|
+
- AI 按钮的 `prompt` 支持 `{{fieldName}}` 插值,运行时替换为当前行的字段值
|
|
127
|
+
- `placement` 限制按钮只出现在指定模型页面,省略则全局显示
|
|
128
|
+
- Dashboard 的 17 种图表类型和配置 Schema 见 `references/dashboard.md`
|
|
92
129
|
|
|
93
|
-
|
|
94
|
-
2. ChatDrawer 打开并自动提交 prompt
|
|
95
|
-
3. AI 可能通过工具调用进行 CRUD 操作
|
|
96
|
-
4. done 事件触发 `loom:data-changed` → useData 自动 refresh
|
|
130
|
+
## 项目级 AI Skill
|
|
97
131
|
|
|
98
|
-
|
|
132
|
+
每个项目的 `.claude/skills/<项目名>/SKILL.md` 是 AI 理解你业务的入口。`loom generate page` 会自动生成骨架,但需要你补充三个地方:
|
|
99
133
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
| `ChatDrawer` | AI 对话抽屉(多会话、流式、thinking/tool_call) |
|
|
104
|
-
| `AIBubble` | AI 浮动按钮(imperativeHandle: openWithQuery) |
|
|
105
|
-
| `AIActionButton` | AI 动作按钮(通过 AIContext 触发 ChatDrawer) |
|
|
106
|
-
| `AIContext` | AI 上下文(AppShell 提供 triggerAI) |
|
|
107
|
-
| `useData` | 数据 CRUD hook(自动响应 loom:data-changed 事件) |
|
|
134
|
+
1. **description**:写用户会说的话,如 `"记录错题", "查看错题", "分析错因"`
|
|
135
|
+
2. **Overview**:2-3 句话描述平台做什么
|
|
136
|
+
3. **Usage Scenarios**:5-10 个典型用户请求及对应的 `loom data` 命令
|
|
108
137
|
|
|
109
|
-
|
|
138
|
+
补充后,AI 在终端和对话框里都能理解"帮我查数学错题"这类业务请求。
|
|
110
139
|
|
|
140
|
+
## 数据操作
|
|
141
|
+
|
|
142
|
+
不打开页面也能操作数据。你可以自己跑命令,也可以让 AI 跑:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
loom data read wrong_questions --filter '{"subject":"数学"}' --limit 10
|
|
146
|
+
loom data write wrong_questions --data '{"subject":"数学","questionContent":"3+5=?","wrongAnswer":"7","correctAnswer":"8"}'
|
|
147
|
+
loom data update wrong_questions --id rec_xxx --data '{"isMastered":true}'
|
|
148
|
+
loom data delete wrong_questions --id rec_xxx
|
|
149
|
+
loom data schema wrong_questions # 查看字段结构
|
|
111
150
|
```
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
151
|
+
|
|
152
|
+
## 后端扩展
|
|
153
|
+
|
|
154
|
+
需要自定义 API 路由时,编辑 `backend/src/index.ts`:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { LoomServer } from '@loom-framework/core';
|
|
158
|
+
|
|
159
|
+
const server = new LoomServer({
|
|
160
|
+
projectRoot,
|
|
161
|
+
hooks: {
|
|
162
|
+
afterInit({ app, adapter }) {
|
|
163
|
+
app.get('/api/v1/stats/trend', async (req, reply) => { ... });
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
await server.initialize();
|
|
168
|
+
await server.start();
|
|
121
169
|
```
|
|
122
170
|
|
|
123
|
-
|
|
171
|
+
不需要时就是原来的 3 行。
|
|
172
|
+
|
|
173
|
+
## 常见问题
|
|
174
|
+
|
|
175
|
+
**loom 命令报错 "No loom.config.ts found"**
|
|
176
|
+
在项目根目录下运行。
|
|
177
|
+
|
|
178
|
+
**better-sqlite3 加载失败**
|
|
179
|
+
`defaultAdapter` 改为 `'filesystem'`,或者 `cd node_modules/better-sqlite3 && npx node-gyp rebuild`。
|
|
180
|
+
|
|
181
|
+
**改了 loom.config.ts 页面没变**
|
|
182
|
+
告诉 AI 你改了什么,AI 会同步更新页面。或者用 `--force` 重建。
|
|
124
183
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
| `@loom-framework/core` | DataAdapter、LoomServer、CLI(loom / loom-server) |
|
|
128
|
-
| `@loom-framework/frontend-antd` | 前端组件库:AppShell、ChatDrawer、AIActionButton、useData、AIContext |
|
|
184
|
+
**对话框里的 AI 能改代码吗**
|
|
185
|
+
能。和终端 Claude Code 是同一个引擎,在哪边改都行。
|
|
@@ -1,10 +1,27 @@
|
|
|
1
1
|
# 生成 Dashboard(数据概览页面)
|
|
2
2
|
|
|
3
|
-
Dashboard
|
|
4
|
-
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
Dashboard 在 `loom.config.ts` 的 `dashboards` 字段中定义,然后通过 CLI 生成页面。
|
|
4
|
+
|
|
5
|
+
## 配置 Dashboard
|
|
6
|
+
|
|
7
|
+
在 `loom.config.ts` 中添加 `dashboards` 字段:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { defineConfig } from '@loom-framework/core';
|
|
11
|
+
|
|
12
|
+
export default defineConfig({
|
|
13
|
+
project: { name: 'my-app' },
|
|
14
|
+
data: { /* ... */ },
|
|
15
|
+
dashboards: [{
|
|
16
|
+
name: 'overview',
|
|
17
|
+
description: '数据概览',
|
|
18
|
+
models: ['items'],
|
|
19
|
+
layout: [
|
|
20
|
+
{ row: [{ type: 'stat', title: '总数', model: 'items', aggregate: 'count', span: 6 }] },
|
|
21
|
+
],
|
|
22
|
+
}],
|
|
23
|
+
});
|
|
24
|
+
```
|
|
8
25
|
|
|
9
26
|
**设计原则:**
|
|
10
27
|
- 统计卡片放第一行(一眼看到关键数字)
|
|
@@ -15,29 +32,25 @@ Dashboard 采用**两阶段**工作流:AI 设计配置 → 代码生成页面
|
|
|
15
32
|
|
|
16
33
|
### 配置 Schema
|
|
17
34
|
|
|
18
|
-
```
|
|
35
|
+
```typescript
|
|
19
36
|
{
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
{
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
]
|
|
39
|
-
}
|
|
40
|
-
]
|
|
37
|
+
name: 'overview', // 必填:Dashboard 标识(同模型命名规则)
|
|
38
|
+
description: '数据概览', // 可选:侧边栏展示名
|
|
39
|
+
models: ['model_a'], // 必填:关联的数据模型名列表
|
|
40
|
+
layout: [{ // 必填:布局定义,数组每项代表一行
|
|
41
|
+
row: [{ // 每行包含一组 widget
|
|
42
|
+
type: 'stat', // 图表类型,见下方图表类型表
|
|
43
|
+
title: '总数', // 业务语言标题
|
|
44
|
+
model: 'model_a', // 数据模型名
|
|
45
|
+
span: 6, // 可选:Col 宽度(1-24),省略则自动均分
|
|
46
|
+
aggregate: 'count', // 可选:count|sum|avg|min|max|ratio(默认 count)
|
|
47
|
+
field: 'score', // aggregate 为 sum/avg/min/max 时必填:数值字段名
|
|
48
|
+
filter: { status: 'active' }, // 可选:聚合前筛选条件
|
|
49
|
+
groupBy: 'category', // 可选:分组字段(string/enum/date/boolean)
|
|
50
|
+
crossGroupBy: 'grade', // 可选:第二分组字段,用于二维图表
|
|
51
|
+
interval: 'month' // 可选:date 字段分组粒度 day|week|month(默认 month)
|
|
52
|
+
}]
|
|
53
|
+
}]
|
|
41
54
|
}
|
|
42
55
|
```
|
|
43
56
|
|
|
@@ -47,7 +60,7 @@ Dashboard 采用**两阶段**工作流:AI 设计配置 → 代码生成页面
|
|
|
47
60
|
|------|------|------|
|
|
48
61
|
| `aggregate` | `count\|sum\|avg\|min\|max\|ratio` | 聚合方式。`ratio` = 符合 filter 条件的记录数/总记录数,用于 gauge/liquid/stat 百分比 |
|
|
49
62
|
| `field` | string | 聚合目标字段。仅 aggregate 为 sum/avg/min/max 时需要 |
|
|
50
|
-
| `filter` | object | 筛选条件,键值对。如 `{
|
|
63
|
+
| `filter` | object | 筛选条件,键值对。如 `{isMastered: true}` 筛选已掌握的记录 |
|
|
51
64
|
| `groupBy` | string | 主分组字段。string/enum 字段按值分组计数;boolean 字段自动映射为"是/否";date 字段按 interval 分段 |
|
|
52
65
|
| `crossGroupBy` | string | 第二分组维度。配合 groupBy 使用,产生交叉分组数据,用于二维图表 |
|
|
53
66
|
| `interval` | `day\|week\|month` | date 字段的分组粒度,仅 groupBy 指向 date 字段时有效 |
|
|
@@ -96,20 +109,20 @@ Dashboard 采用**两阶段**工作流:AI 设计配置 → 代码生成页面
|
|
|
96
109
|
| `scatter` | 散点图 | `groupBy` | `{ groupBy: "subject" }` | G2 散点图(相关性/分布) |
|
|
97
110
|
| `heatmap` | 热力图 | `groupBy` + `crossGroupBy` | `{ groupBy: "subject", crossGroupBy: "grade" }` | G2 热力图(密度/交叉分析,颜色深浅表示数量) |
|
|
98
111
|
|
|
99
|
-
##
|
|
112
|
+
## 执行 generate dashboard
|
|
100
113
|
|
|
101
|
-
|
|
114
|
+
配置好 `dashboards` 字段后,运行命令生成页面:
|
|
102
115
|
|
|
103
116
|
```bash
|
|
104
117
|
loom generate dashboard <name>
|
|
105
118
|
```
|
|
106
119
|
|
|
107
120
|
此命令会:
|
|
108
|
-
1.
|
|
109
|
-
2.
|
|
121
|
+
1. 从 `loom.config.ts` 的 `dashboards` 字段读取配置
|
|
122
|
+
2. 验证引用的模型是否存在
|
|
110
123
|
3. 生成 Dashboard TSX 页面(含 `useData` + `useChartData` + `DashboardChart`)
|
|
111
124
|
4. 自动接线 App.tsx(添加 import、navItem、switch case)
|
|
112
|
-
5. 检测 `@antv/g2`
|
|
125
|
+
5. 检测 `@antv/g2` 是否已安装,未安装则自动安装
|
|
113
126
|
|
|
114
127
|
**前置依赖:** Dashboard 使用 @antv/g2 渲染图表,需在 frontend 目录安装:
|
|
115
128
|
|
|
@@ -121,55 +134,61 @@ cd frontend && pnpm add @antv/g2
|
|
|
121
134
|
|
|
122
135
|
以小学生错题管理平台为例,展示所有 15 种图表的配置:
|
|
123
136
|
|
|
124
|
-
```
|
|
125
|
-
{
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
137
|
+
```typescript
|
|
138
|
+
import { defineConfig } from '@loom-framework/core';
|
|
139
|
+
|
|
140
|
+
export default defineConfig({
|
|
141
|
+
project: { name: 'wrong-questions' },
|
|
142
|
+
data: { /* ... */ },
|
|
143
|
+
dashboards: [{
|
|
144
|
+
name: 'overview',
|
|
145
|
+
description: '错题数据概览',
|
|
146
|
+
models: ['wrong_questions', 'review_plans'],
|
|
147
|
+
layout: [
|
|
148
|
+
{
|
|
149
|
+
row: [
|
|
150
|
+
{ type: 'stat', title: '错题总数', model: 'wrong_questions', aggregate: 'count', span: 4 },
|
|
151
|
+
{ type: 'stat', title: '掌握率', model: 'wrong_questions', aggregate: 'ratio', filter: { isMastered: true }, span: 4 },
|
|
152
|
+
{ type: 'gauge', title: '掌握率仪表盘', model: 'wrong_questions', aggregate: 'ratio', filter: { isMastered: true }, span: 6 },
|
|
153
|
+
{ type: 'liquid', title: '掌握率水波图', model: 'wrong_questions', aggregate: 'ratio', filter: { isMastered: true }, span: 5 },
|
|
154
|
+
{ type: 'stat', title: '平均复习次数', model: 'wrong_questions', aggregate: 'avg', field: 'reviewCount', span: 5 }
|
|
155
|
+
]
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
row: [
|
|
159
|
+
{ type: 'pie', title: '科目分布', model: 'wrong_questions', groupBy: 'subject', span: 8 },
|
|
160
|
+
{ type: 'ring', title: '难度分布', model: 'wrong_questions', groupBy: 'difficulty', span: 8 },
|
|
161
|
+
{ type: 'bar', title: '错误类型分布', model: 'wrong_questions', groupBy: 'errorType', span: 8 }
|
|
162
|
+
]
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
row: [
|
|
166
|
+
{ type: 'line', title: '错题录入趋势', model: 'wrong_questions', groupBy: 'createdAt', interval: 'month', span: 12 },
|
|
167
|
+
{ type: 'area', title: '复习计划趋势', model: 'review_plans', groupBy: 'createdAt', interval: 'month', span: 12 }
|
|
168
|
+
]
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
row: [
|
|
172
|
+
{ type: 'radar', title: '各科目概览', model: 'wrong_questions', groupBy: 'subject', span: 8 },
|
|
173
|
+
{ type: 'stacked_bar', title: '科目×错误类型', model: 'wrong_questions', groupBy: 'subject', crossGroupBy: 'errorType', span: 8 },
|
|
174
|
+
{ type: 'grouped_bar', title: '科目×难度', model: 'wrong_questions', groupBy: 'subject', crossGroupBy: 'difficulty', span: 8 }
|
|
175
|
+
]
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
row: [
|
|
179
|
+
{ type: 'scatter', title: '科目分布散点', model: 'wrong_questions', groupBy: 'subject', span: 8 },
|
|
180
|
+
{ type: 'funnel', title: '错误类型漏斗', model: 'wrong_questions', groupBy: 'errorType', span: 8 },
|
|
181
|
+
{ type: 'treemap', title: '科目×难度树图', model: 'wrong_questions', groupBy: 'subject', crossGroupBy: 'difficulty', span: 8 }
|
|
182
|
+
]
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
row: [
|
|
186
|
+
{ type: 'heatmap', title: '科目×年级热力图', model: 'wrong_questions', groupBy: 'subject', crossGroupBy: 'grade', span: 24 }
|
|
187
|
+
]
|
|
188
|
+
}
|
|
189
|
+
]
|
|
190
|
+
}],
|
|
191
|
+
});
|
|
173
192
|
```
|
|
174
193
|
|
|
175
194
|
## 追加自定义图表
|
|
@@ -75,4 +75,42 @@ data: {
|
|
|
75
75
|
models: [...],
|
|
76
76
|
sqlite: { filename: 'loom.db' }, // optional, default 'loom.db'
|
|
77
77
|
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## DashboardConfig
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
dashboards: [{
|
|
84
|
+
name: 'overview', // ^[a-zA-Z][a-zA-Z0-9_]*$, same naming rules as model
|
|
85
|
+
description: '数据概览', // optional: shown in sidebar navigation
|
|
86
|
+
models: ['items'], // required: data models included in this dashboard
|
|
87
|
+
layout: [{ // required: at least one row
|
|
88
|
+
row: [{ // at least one widget per row
|
|
89
|
+
type: 'stat' | 'pie' | 'ring' | 'bar' | 'line' | 'area' | 'scatter' | 'radar' | 'funnel' | 'treemap' | 'gauge' | 'liquid' | 'heatmap' | 'stacked_bar' | 'grouped_bar',
|
|
90
|
+
title: '任务总数', // required: display title
|
|
91
|
+
model: 'items', // required: data model name
|
|
92
|
+
span: 6, // optional: Ant Design Grid Col span (1-24), auto-equal-split if omitted
|
|
93
|
+
groupBy: 'status', // optional: group by field for aggregation
|
|
94
|
+
crossGroupBy: 'priority', // optional: second group-by for 2D charts (heatmap, stacked_bar, grouped_bar)
|
|
95
|
+
aggregate: 'count', // optional: 'count' | 'sum' | 'avg' | 'min' | 'max' | 'ratio', default count
|
|
96
|
+
field: 'score', // optional: numeric field for sum/avg/min/max
|
|
97
|
+
filter: { status: 'active' }, // optional: pre-aggregation filter
|
|
98
|
+
interval: 'month', // optional: 'day' | 'week' | 'month' for date groupBy
|
|
99
|
+
}],
|
|
100
|
+
}],
|
|
101
|
+
}]
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## useSchema Runtime API
|
|
105
|
+
|
|
106
|
+
Generated pages use `useSchema(model)` to fetch model schema and AI buttons at runtime:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
const { schema, loading, error } = useSchema('items');
|
|
110
|
+
// schema.fields — field definitions with enum values
|
|
111
|
+
// schema.aiButtons — AI buttons filtered by placement
|
|
112
|
+
|
|
113
|
+
// Convert schema enum to antd formats:
|
|
114
|
+
filterOptions(schema, 'status') // → [{ text: 'active', value: 'active' }, ...]
|
|
115
|
+
selectOptions(schema, 'status') // → [{ label: 'active', value: 'active' }, ...]
|
|
78
116
|
```
|
package/dist/backend/index.js
CHANGED
|
@@ -86,7 +86,7 @@ export class LoomServer {
|
|
|
86
86
|
}
|
|
87
87
|
// Register routes
|
|
88
88
|
registerHealthRoute(this.fastify, this.adapter);
|
|
89
|
-
registerDataRoutes(this.fastify, this.adapter);
|
|
89
|
+
registerDataRoutes(this.fastify, this.adapter, this.config);
|
|
90
90
|
registerUploadRoutes(this.fastify, { projectRoot: this.projectRoot });
|
|
91
91
|
registerChatRoutes(this.fastify, {
|
|
92
92
|
engine: this.engine,
|