draftgo-cli 2.0.5 → 3.0.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/README.md +70 -15
- package/package.json +1 -1
- package/resources/skill/SKILL.md +74 -1333
- package/resources/skill/core/architecture.md +91 -0
- package/resources/skill/core/modules.md +54 -0
- package/resources/skill/practices/anti-patterns.md +70 -0
- package/resources/skill/practices/best-practices.md +41 -0
- package/resources/skill/practices/dev-declaration.md +94 -0
- package/resources/skill/push/SKILL.md +39 -5
- package/resources/skill/quickref/api-endpoints.md +130 -0
- package/resources/skill/quickref/api.json +17675 -0
- package/resources/skill/quickref/app-api.md +110 -0
- package/resources/skill/quickref/dg-components.md +198 -0
- package/resources/skill/rules/dev-workflow.md +70 -70
- package/resources/skill/rules/frontend.md +129 -594
- package/resources/skill/scripts/draftgo_delete.py +151 -0
- package/resources/skill/scripts/draftgo_pull.py +1 -1
- package/resources/skill/scripts/draftgo_push.py +165 -22
- package/resources/skill/specs/data.md +108 -0
- package/resources/skill/specs/runtime.md +98 -0
- package/resources/skill/specs/security.md +74 -0
- package/resources/skill/specs/ui-protocol.md +68 -0
- package/src/commands/check.js +1 -1
- package/src/commands/delete.js +86 -0
- package/src/commands/help.js +7 -0
- package/src/commands/map.js +37 -1
- package/src/commands/new.js +183 -0
- package/src/commands/sync.js +4 -1
- package/src/index.js +6 -0
- package/src/localdev/compose.js +19 -0
- package/src/projectMap.js +142 -2
|
@@ -1,675 +1,210 @@
|
|
|
1
|
-
---
|
|
1
|
+
---
|
|
2
2
|
name: draftgo-frontend-rules
|
|
3
3
|
description: DraftGo frontend page development rules.
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# DraftGo 前端开发规范
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- 页面通过 `window.parent.App` 调用平台能力
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## 前端 UI 能力调用声明
|
|
20
|
-
|
|
21
|
-
平台前端开发默认采用 React + shadcn/ui + Tailwind CSS;新增壳层管理界面、组件、弹窗、表单、表格和设置页时,优先复用 `frontend/src/components/ui` 下的 shadcn 组件与 Tailwind token。若本地 Agent 环境存在前端 UI 相关 Skills,前端界面开发时优先调用;本文件同时规定 DraftGo 前端运行时、资源、数据、路由、入口绑定和验证方法。
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## dg-* = shadcn(最高优先级)
|
|
26
|
-
|
|
27
|
-
在 DraftGo 中,`dg-*` 就意味着 shadcn。
|
|
28
|
-
|
|
29
|
-
- `dg-*` 是 shadcn/ui 在数据库 HTML 页面机制里的协议化表达,不是一套自研 UI 库。
|
|
30
|
-
- 数据库页面仍然写 HTML,不进入 Vite/React 编译链;因此不能把 TSX 版 shadcn 组件直接写进 `page.value.html`。
|
|
31
|
-
- 需要 shadcn 组件能力时,优先写对应 `dg-*` 标签,例如 `dg-button`、`dg-card`、`dg-form`、`dg-table`、`dg-tabs`、`dg-dialog`、`dg-dropdown-menu`、`dg-sheet`。
|
|
32
|
-
- 若 shadcn 有组件,DraftGo 必须有对应 `dg-*` 映射。当前 runtime 已按映射表提供基础协议覆盖;复杂组件在 iframe 中以 HTML runtime 方式复用 shadcn 语义,而不是直接运行 React/Radix 组件。
|
|
33
|
-
- `dg-*` 的属性、状态、交互预期和 token 命名应跟随 shadcn:例如 `variant`、`size`、`disabled`、`default-value`、`data-state`、`aria-*`。
|
|
34
|
-
- `dg-dialog`、`dg-sheet`、`dg-dropdown-menu`、`dg-popover`、`dg-tooltip` 等复杂组件使用 trigger/content/open-state 协议;`dg-table[source]` 支持基础数据拉取和 `data-dg-props.columns` 渲染。
|
|
35
|
-
|
|
36
|
-
AI 开发时的判断句:**看到 `dg-*`,必须读作 shadcn;要用 shadcn,数据库页面就写 `dg-*`。**
|
|
37
|
-
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
## 运行时机制(Runtime)
|
|
41
|
-
|
|
42
|
-
### 壳层启动流程
|
|
43
|
-
|
|
44
|
-
壳层入口由 Vite/React 工程加载,当前兼容 runtime 仍通过 `frontend/src/core/runtime.js` 的 `createAppRuntime()` 完成以下初始化:
|
|
45
|
-
|
|
46
|
-
1. `reloadSystemConfig()` — 拉取 `/api/system/config`,填充 `state.config`
|
|
47
|
-
2. `validateToken()` — 验证 `localStorage.dg_access_token`,成功后设置 `currentUser`
|
|
48
|
-
3. `loadSetupStatus()` — 检查系统是否已初始化(`/api/system/setup/status`)
|
|
49
|
-
4. `loadPage()` — 根据当前路由拉取页面 HTML,注入 iframe
|
|
50
|
-
|
|
51
|
-
### iframe 注入机制
|
|
52
|
-
|
|
53
|
-
壳层通过 `decorateFrameHtml(html, routeContext, theme)` 处理数据库页面 HTML,注入:
|
|
54
|
-
- `window.__DG_ROUTE_CONTEXT__` — 当前路由上下文(含 `query` 参数)
|
|
55
|
-
- 主题 CSS 变量
|
|
56
|
-
- 静态资源(数据库页面使用的本地 Tailwind runtime、FontAwesome、GSAP 等)
|
|
57
|
-
|
|
58
|
-
页面 HTML 以 `iframe.srcdoc` 方式渲染,**不是独立 URL**,因此:
|
|
59
|
-
- `window.location` 指向壳层地址,不可用于读取路由参数
|
|
60
|
-
- `window.parent.App` 是壳层暴露的能力对象
|
|
61
|
-
|
|
62
|
-
### App 对象来源
|
|
63
|
-
|
|
64
|
-
壳层将 `app` 对象赋值给 `window.App`(通过 `Object.assign(app, ...)`),页面内通过 `window.parent.App` 访问。
|
|
65
|
-
|
|
66
|
-
`app` 对象包含以下模块(均已合并到顶层):
|
|
67
|
-
|
|
68
|
-
| 属性/方法 | 来源模块 | 说明 |
|
|
69
|
-
|---|---|---|
|
|
70
|
-
| `get/post/put/patch/delete` | `api.js` | HTTP 请求,自动携带 token |
|
|
71
|
-
| `uploadFile(file, onProgress)` | `api.js` | 文件上传 |
|
|
72
|
-
| `toast(msg, type?, duration?)` | `feedback.js` | Toast 通知(type: success/error/warning/info,默认 info) |
|
|
73
|
-
| `showSuccess(msg)` | `feedback.js` | 成功 Toast 3000ms ✓ |
|
|
74
|
-
| `showError(msg)` | `feedback.js` | 错误 Toast 4000ms ✕ |
|
|
75
|
-
| `showWarning(msg)` | `feedback.js` | 警告 Toast 3000ms ⚠ |
|
|
76
|
-
| `showInfo(msg)` | `feedback.js` | 信息 Toast 3000ms ℹ |
|
|
77
|
-
| `confirm(msg, title?)` | `feedback.js` | 确认弹窗,返回 Promise\<boolean\> |
|
|
78
|
-
| `showModal(msg, title?)` | `feedback.js` | 信息模态框,带确定按钮 |
|
|
79
|
-
| `showLoading() / hideLoading()` | `feedback.js` | 全局 loading 蒙层 |
|
|
80
|
-
| `navigate(route)` | `runtime.js` | 路由跳转(pushState) |
|
|
81
|
-
| `getCurrentRoute()` | `runtime.js` | 当前路径字符串 |
|
|
82
|
-
| `getCurrentRouteContext()` | `runtime.js` | 完整路由上下文(含 query) |
|
|
83
|
-
| `reloadGlobalLayer()` | `runtime.js` | 重载前端全局层 |
|
|
84
|
-
| `openGlobalWidget(name)` / `closeGlobalWidget(name)` | `runtime.js` | 触发全局挂件打开/关闭事件 |
|
|
85
|
-
| `logout()` | `runtime.js` | 登出并跳转登录页 |
|
|
86
|
-
| `setAuthTokens({access_token, refresh_token})` | `runtime.js` | 登录后写入 token |
|
|
87
|
-
| `applyTheme(theme)` | `runtime.js` | 切换主题(light/dark),同步写 localStorage + iframe |
|
|
88
|
-
| `t(key, fallback?)` | `i18n.js` | 国际化文本 |
|
|
89
|
-
| `currentUser` | state | 当前用户对象(未登录为 null) |
|
|
90
|
-
| `isAdmin` | state | 是否管理员 |
|
|
91
|
-
| `isAuthenticated` | state | 是否已认证 |
|
|
92
|
-
| `hasToken` | state | 是否有 token(含未验证) |
|
|
93
|
-
| `config` | state | 系统配置键值对 |
|
|
94
|
-
| `theme` | state | 当前主题名(`'light'` \| `'dark'`) |
|
|
95
|
-
|
|
96
|
-
### Token 存储
|
|
97
|
-
|
|
98
|
-
| key | 说明 |
|
|
99
|
-
|---|---|
|
|
100
|
-
| `localStorage.dg_access_token` | 访问 token |
|
|
101
|
-
| `localStorage.dg_refresh_token` | 刷新 token |
|
|
102
|
-
|
|
103
|
-
登录成功后调用 `App.setAuthTokens({ access_token, refresh_token })` 写入并触发验证。
|
|
104
|
-
|
|
105
|
-
### 禁止使用浏览器默认弹窗(强制)
|
|
106
|
-
|
|
107
|
-
**禁止**在页面中使用 `alert()`、`confirm()`、`prompt()`。它们会阻塞主线程、与平台交互协议不一致,且在 iframe 环境中行为不可预期。
|
|
108
|
-
|
|
109
|
-
使用 App 提供的替代方案:
|
|
110
|
-
|
|
111
|
-
| 浏览器原生 | DraftGo 替代 | 说明 |
|
|
112
|
-
|---|---|---|
|
|
113
|
-
| `alert(msg)` | `App.showModal(msg, title?)` | 信息展示,带确定按钮 |
|
|
114
|
-
| `confirm(msg)` | `await App.confirm(msg, title?)` | 二次确认,返回 `Promise<boolean>`,注意必须 `await` |
|
|
115
|
-
| `prompt(msg)` | 自行构建输入弹窗 | 平台无内置 prompt,需在页面内自建 modal + input,并保持与当前页面和平台交互一致 |
|
|
116
|
-
|
|
117
|
-
```javascript
|
|
118
|
-
// ❌ 禁止
|
|
119
|
-
if (confirm('确认删除?')) { doDelete(); }
|
|
120
|
-
|
|
121
|
-
// ✅ 正确
|
|
122
|
-
const ok = await App.confirm('确认删除?', '删除确认');
|
|
123
|
-
if (ok) { doDelete(); }
|
|
124
|
-
|
|
125
|
-
// ❌ 禁止
|
|
126
|
-
const name = prompt('请输入名称');
|
|
127
|
-
|
|
128
|
-
// ✅ 正确:自建输入弹窗
|
|
129
|
-
showInputModal('请输入名称', async (value) => {
|
|
130
|
-
if (value) { await save(value); }
|
|
131
|
-
});
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### 前端全局层(强制)
|
|
135
|
-
|
|
136
|
-
全局浮窗、全局客服/反馈入口、统计监控脚本、Toast 二开、全站挂件等,属于前端全局层,不属于单个业务页面。全局层通过 `sys_config.category = frontend_global` 存储,并在页面管理的「全局」分类编辑固定槽位。
|
|
137
|
-
|
|
138
|
-
规则:
|
|
139
|
-
|
|
140
|
-
- 不要在业务页面内重复实现全局浮窗、客服、反馈入口或统计脚本。
|
|
141
|
-
- 不允许新增全局项;只能编辑系统内置固定槽位。
|
|
142
|
-
- 如果不了解用途,不要修改全局层;它会影响所有页面。
|
|
143
|
-
- Toast 调用协议保持稳定:继续用 `App.toast()` / `App.showSuccess()` / `App.showError()`;默认时长可通过全局项二开。
|
|
144
|
-
- 保存全局层后调用 `App.reloadSystemConfig()` 或 `App.reloadGlobalLayer()` 让运行时立即重载。
|
|
145
|
-
|
|
146
|
-
常见槽位:
|
|
147
|
-
|
|
148
|
-
| config_key | 用途 |
|
|
149
|
-
|---|---|
|
|
150
|
-
| `frontend_global_head_html` | 注入父级壳层 head |
|
|
151
|
-
| `frontend_global_body_html` | 注入父级壳层 body 末尾 |
|
|
152
|
-
| `frontend_global_css` / `frontend_global_js` | 父级壳层全局 CSS/JS |
|
|
153
|
-
| `frontend_global_iframe_head_html` | 注入每个业务页面 iframe head |
|
|
154
|
-
| `frontend_global_widget_html/css/js` | 全局挂件层 |
|
|
155
|
-
| `frontend_global_toast_css/position/duration_*` | Toast 二开 |
|
|
156
|
-
|
|
157
|
-
### 全局事件
|
|
158
|
-
|
|
159
|
-
| 事件名 | 触发时机 |
|
|
160
|
-
|---|---|
|
|
161
|
-
| `dg:auth-ready` | token 验证完成(成功或失败) |
|
|
162
|
-
| `dg:auth-changed` | token 变更(登录/登出) |
|
|
163
|
-
|
|
164
|
-
页面内监听:`window.parent.addEventListener('dg:auth-ready', handler)`
|
|
9
|
+
> 本文件只规定前端**操作性规则**,已剥离的内容见:
|
|
10
|
+
> 架构原理 → `{{SKILL_DIR}}/core/architecture.md`
|
|
11
|
+
> App API / Token / 路由 / 运行时 → `{{SKILL_DIR}}/specs/runtime.md` · `{{SKILL_DIR}}/quickref/app-api.md`
|
|
12
|
+
> dg-* 映射 → `{{SKILL_DIR}}/specs/ui-protocol.md` · `{{SKILL_DIR}}/quickref/dg-components.md`
|
|
13
|
+
> 动态 DB / filters → `{{SKILL_DIR}}/specs/data.md`
|
|
14
|
+
> 开发禁区 → `{{SKILL_DIR}}/specs/security.md`
|
|
165
15
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
`App.get/post/...` 的路径参数解析(来自 `api.js`):
|
|
169
|
-
|
|
170
|
-
| 传入值 | 实际请求 |
|
|
171
|
-
|---|---|
|
|
172
|
-
| `'pages'` | `{apiBase}/api/pages` |
|
|
173
|
-
| `'pages/123'` | `{apiBase}/api/pages/123` |
|
|
174
|
-
| `'/api/pages'` | `{apiBase}/api/pages` |
|
|
175
|
-
| `'https://...'` | 直接请求(绝对 URL) |
|
|
176
|
-
|
|
177
|
-
### GET 请求筛选范式
|
|
178
|
-
|
|
179
|
-
**分页**(绝大多数列表接口):
|
|
180
|
-
|
|
181
|
-
```javascript
|
|
182
|
-
App.get('users', { page: 1, page_size: 20 })
|
|
183
|
-
App.get('pages/', { page: 1, page_size: 20 })
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
> 分页参数统一使用 `page` 与 `page_size`。GET 列表请求若不携带这两个参数,则返回全量数据;当数据量超过 10000 条时后端会拒绝并返回 400,此时必须传入 `page` 与 `page_size`。
|
|
187
|
-
|
|
188
|
-
**通用筛选参数**:
|
|
189
|
-
|
|
190
|
-
| 参数 | 类型 | 说明 |
|
|
191
|
-
|---|---|---|
|
|
192
|
-
| `page` | int | 页码,从 1 开始 |
|
|
193
|
-
| `page_size` | int | 每页条数 |
|
|
194
|
-
| `search` | string | 全文搜索(users/roles/pages/navigations/feedback —— **注意:动态 DB 不用 search,见下方**) |
|
|
195
|
-
| `status` | string | 状态过滤(`active`/`inactive`/`published` 等,按资源而定) |
|
|
196
|
-
| `type` | string | 类型过滤(notices/feedback/aihub) |
|
|
197
|
-
| `tag` | string | 标签过滤(pages/aihub) |
|
|
198
|
-
|
|
199
|
-
**典型示例**:
|
|
200
|
-
|
|
201
|
-
```javascript
|
|
202
|
-
// 用户列表,带搜索+状态+角色过滤
|
|
203
|
-
App.get('users', { page: 1, page_size: 20, search: 'alice', status: 'active', role_id: 3 })
|
|
204
|
-
|
|
205
|
-
// 页面列表,带标签+状态过滤
|
|
206
|
-
App.get('pages/', { page: 1, page_size: 20, search: 'home', tag: 'blog', status: 'published' })
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
### 动态 DB(`/api/db/{type}`)检索范式
|
|
210
|
-
|
|
211
|
-
动态 DB 的检索**不用 `search` 参数**,改用 **`filters` 结构化条件**,直接对记录的 `data` JSON 字段做查询。
|
|
212
|
-
|
|
213
|
-
```javascript
|
|
214
|
-
// 动态 DB 列表查询:filters 每项形如 "字段:操作符:值",可传多个(数组),多个为 AND
|
|
215
|
-
App.get(`db/${type}`, {
|
|
216
|
-
page: 1, page_size: 20,
|
|
217
|
-
filters: ['title:like:公告', 'views:gte:100', 'status:eq:paid'],
|
|
218
|
-
order_by: 'views', order: 'desc',
|
|
219
|
-
})
|
|
220
|
-
|
|
221
|
-
// 动态 DB 创建:业务字段必须放在 data 包裹里,不要平铺到 body 顶层
|
|
222
|
-
App.post(`db/${type}`, { data: { name: '张三', age: 30 }, status: 1 })
|
|
223
|
-
|
|
224
|
-
// 动态 DB 更新:同样用 data 包裹
|
|
225
|
-
App.put(`db/${type}/${id}`, { data: { age: 31 } })
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
**filters 操作符**(字段必须在 db_meta schema 里标 `searchable`,且操作符要匹配字段的检索模式):
|
|
229
|
-
|
|
230
|
-
| 操作符 | 含义 | 适用 searchable 模式 |
|
|
231
|
-
|---|---|---|
|
|
232
|
-
| `eq` | 精确等于 | exact / fuzzy / range |
|
|
233
|
-
| `like` | 模糊包含(`%`/`_` 会被自动转义,按字面匹配) | fuzzy |
|
|
234
|
-
| `gte` / `lte` / `gt` / `lt` | 数值范围 | range |
|
|
235
|
-
| `in` | 枚举命中(值逗号分隔:`status:in:paid,pending`) | exact / fuzzy / range |
|
|
236
|
-
| `contains` | 数组字段包含某值 | contains |
|
|
237
|
-
|
|
238
|
-
- 省略操作符时(`filters: ['title:公告']`)默认 `like`。
|
|
239
|
-
- 字段间互相隔离,按 `data.字段` 精确定位,不存在跨字段串扰;中文可正常检索。
|
|
240
|
-
- `order_by` 只能用 searchable 字段;`order` 取 `asc`/`desc`,默认 `desc`。
|
|
241
|
-
- 字段不可检索、或操作符与字段模式不匹配时,后端返回 400。
|
|
242
|
-
|
|
243
|
-
**DB Meta 说明**:涉及动态数据库操作前,先读取 `db_meta/index.json` 了解当前项目有哪些 DB 类型(`type`)以及各字段的 `searchable` 模式,再调用 `/api/db/{type}`。不要硬编码 type 值,也不要对未标 searchable 的字段做 filters。
|
|
244
|
-
|
|
245
|
-
---
|
|
16
|
+
若本地 Agent 存在前端 UI Skills,前端界面开发时优先调用。
|
|
246
17
|
|
|
247
18
|
---
|
|
248
19
|
|
|
249
|
-
##
|
|
20
|
+
## 页面开发强制规则
|
|
250
21
|
|
|
251
|
-
###
|
|
22
|
+
### 真实闭环(强制)
|
|
252
23
|
|
|
253
|
-
|
|
254
|
-
-
|
|
255
|
-
-
|
|
256
|
-
-
|
|
257
|
-
-
|
|
258
|
-
- 若页面展示可维护内容(案例、新闻、产品、招聘、资料等),主动判断是否需要后台管理页和同一份真实数据;不要只写死 HTML 或前端数组。
|
|
259
|
-
- 若 DraftGo 动态 DB、已有 db_meta、custom_scripts、外部 API、AIHub 等能力都无法完成真实闭环,不要写伪功能交差;停止开发,向用户说明阻塞点,并按 dev-workflow 写入 `.draftgo/lessons/`。
|
|
24
|
+
- 页面必须有可访问 route,从真实入口可达(导航/首页/后台菜单/相关按钮至少一处)
|
|
25
|
+
- 按钮、表单、搜索、筛选、分页、提交、删除等交互默认真实有效
|
|
26
|
+
- 必须有加载态/空态/错误态/成功态,不能只堆静态卡片
|
|
27
|
+
- 展示可维护内容时主动判断是否需要后台管理页 + 同一份真实数据
|
|
28
|
+
- 无法实现真实闭环时停止开发,向用户说明阻塞点,写入 `.draftgo/lessons/`
|
|
260
29
|
|
|
261
|
-
###
|
|
30
|
+
### 操作型页面布局(强制)
|
|
262
31
|
|
|
263
|
-
|
|
264
|
-
- 页面根容器应占满可用视口 / iframe 内容区,使用 `display:flex; flex-direction:column; min-height:100vh` 或等效结构。
|
|
265
|
-
- 顶部筛选、搜索、批量入口、标题操作区保持稳定高度,不应随数据量漂移。
|
|
266
|
-
- 主体数据区使用 `flex:1; min-height:0; overflow:auto` 承接剩余空间,数据少时保留工作区空白,不让页面高度随行数塌陷。
|
|
267
|
-
- 分页、批量操作栏、保存栏、流程按钮等控制区应固定在当前工作区底部或保持稳定位置,不能跟随 1-2 条数据上浮。
|
|
268
|
-
- 内部滚动优先发生在数据区 / 表格区,避免整个后台页面因为少量内容显得松散,也避免大量内容把操作区挤出视野。
|
|
269
|
-
|
|
270
|
-
参考结构:
|
|
32
|
+
后台管理、表格、列表等操作型页面用**工作台布局**,不用文档流堆叠:
|
|
271
33
|
|
|
272
34
|
```css
|
|
273
|
-
.workbench-page {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
.workbench-toolbar {
|
|
280
|
-
flex-shrink: 0;
|
|
281
|
-
}
|
|
282
|
-
.workbench-body {
|
|
283
|
-
flex: 1;
|
|
284
|
-
min-height: 0;
|
|
285
|
-
display: flex;
|
|
286
|
-
flex-direction: column;
|
|
287
|
-
}
|
|
288
|
-
.workbench-data {
|
|
289
|
-
flex: 1;
|
|
290
|
-
min-height: 0;
|
|
291
|
-
overflow: auto;
|
|
292
|
-
}
|
|
293
|
-
.workbench-footer {
|
|
294
|
-
flex-shrink: 0;
|
|
295
|
-
}
|
|
35
|
+
.workbench-page { min-height:100vh; display:flex; flex-direction:column; overflow:hidden; }
|
|
36
|
+
.workbench-toolbar { flex-shrink:0; }
|
|
37
|
+
.workbench-body { flex:1; min-height:0; display:flex; flex-direction:column; }
|
|
38
|
+
.workbench-data { flex:1; min-height:0; overflow:auto; }
|
|
39
|
+
.workbench-footer { flex-shrink:0; }
|
|
296
40
|
```
|
|
297
41
|
|
|
298
|
-
|
|
42
|
+
- 分页/操作栏/保存栏固定在工作区底部,不随数据量上浮
|
|
43
|
+
- 滚动发生在数据区,不是整页
|
|
299
44
|
|
|
300
45
|
### 新增页面绑定(强制)
|
|
301
46
|
|
|
302
|
-
|
|
303
|
-
-
|
|
304
|
-
-
|
|
305
|
-
-
|
|
306
|
-
-
|
|
307
|
-
- 推送时同时推送页面和被修改的导航栏 / 入口页面。
|
|
308
|
-
- 验证时必须从真实入口点击进入目标页面;只手动输入 route 打开不算完整验证。
|
|
309
|
-
|
|
310
|
-
如果用户明确要求创建“暂不公开页面 / 隐藏页 / 草稿页”,可以不绑定导航,但必须在 Task / changelog / 完成说明里写明“暂不绑定入口”的原因。
|
|
47
|
+
- 公开页 → 顶部/侧边导航、首页入口、相关按钮之一
|
|
48
|
+
- 后台页 → 后台导航、管理菜单或现有后台入口
|
|
49
|
+
- 多页面功能:列表/详情/新建/编辑/管理必须互相走通
|
|
50
|
+
- 导航链接必须用 `data-page-route`:`<a href="/orders" data-page-route="/orders">订单</a>`
|
|
51
|
+
- 用户明确要求"隐藏页/草稿页"才可不绑定,但必须在 changelog / Task 说明原因
|
|
311
52
|
|
|
312
53
|
### `page_1_root.html` 首页特例(强制)
|
|
313
54
|
|
|
314
|
-
|
|
55
|
+
- 仍是默认内置首页 → 按用户需求和本地 UI Skills 重新实现,不沿用内置文案和结构
|
|
56
|
+
- 已被用户改过 → 只做要求范围内的修改,不强制重做
|
|
57
|
+
- 判断依据:看 HTML 内容是否保留 DraftGo 默认文案/系统介绍/未定制品牌,不要只凭文件名判断
|
|
315
58
|
|
|
316
|
-
|
|
317
|
-
- 如果仍是默认内置首页,把它视为项目自己的首页资源,按用户需求、Story 和本地 UI Skills 重新实现,不沿用内置页的文案和业务结构。
|
|
318
|
-
- 如果首页已经被用户改过,只做用户要求范围内的修改,不强制重做整页。
|
|
319
|
-
- 判断是否仍为默认内置首页时,优先看 HTML 内容是否明显保留 DraftGo 默认首页的文案、结构、系统介绍、默认模块和未定制品牌;不要只凭文件名或 `id=1` 下结论。
|
|
320
|
-
- 修改前先读取 `.draftgo/story.yaml`(如存在)和用户需求;若 Story 不存在且用户只要求“重做首页/设计首页”,按高风险任务处理,先确认方向再做。
|
|
321
|
-
- 无论首页如何实现,都必须遵守本文件的资源、主题、弹窗、路由和响应格式规则。
|
|
322
|
-
- 验证时除常规 console / push 外,还要检查桌面和移动宽度下无明显布局破损,且用户路径入口可达。
|
|
59
|
+
---
|
|
323
60
|
|
|
324
|
-
|
|
61
|
+
## 必须 / 禁止
|
|
325
62
|
|
|
326
63
|
### 必须
|
|
327
64
|
- 完整 HTML 文档结构(`<html><head><body>`)
|
|
328
|
-
-
|
|
329
|
-
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
```
|
|
334
|
-
- 颜色全部用语义 token(见下方"颜色 Token")
|
|
335
|
-
- 弹窗用 `App.confirm()` / `App.toast()`
|
|
336
|
-
- 页面初始渲染必须展示默认状态,不能因网络请求延迟导致空白
|
|
337
|
-
- 需要 shadcn 组件能力时优先使用对应 `dg-*` 标签;`dg-*` 必须映射 shadcn 组件语义
|
|
65
|
+
- 静态资源用本地路径(见下方资源清单)
|
|
66
|
+
- 颜色全用语义 token `var(--dg-*)`
|
|
67
|
+
- 弹窗用 `App.confirm()` / `App.toast()`,禁止 `window.alert/confirm/prompt`
|
|
68
|
+
- 页面初始渲染展示默认状态(骨架屏),不能因请求延迟空白
|
|
69
|
+
- 需要组件能力时用对应 `dg-*` 标签
|
|
338
70
|
|
|
339
71
|
### 禁止
|
|
340
|
-
-
|
|
341
|
-
-
|
|
342
|
-
-
|
|
343
|
-
-
|
|
344
|
-
-
|
|
345
|
-
-
|
|
346
|
-
-
|
|
347
|
-
-
|
|
72
|
+
- ❌ 境外 CDN(googleapis / jsdelivr / cdnjs / unpkg)→ ✅ 国内镜像或本地资源
|
|
73
|
+
- ❌ 外部 CDN 引入图标 → ✅ `/assets/icons/{name}.svg`
|
|
74
|
+
- ❌ `window.alert/confirm/prompt`
|
|
75
|
+
- ❌ `App()` 写法 → ✅ `const App = window.parent?.App`
|
|
76
|
+
- ❌ `window.location.search` 读参数 → ✅ `window.__DG_ROUTE_CONTEXT__.query`
|
|
77
|
+
- ❌ 页面内 `window.location.href=...` 跳转 → ✅ `window.parent.location.href=...`
|
|
78
|
+
- ❌ TSX/React 版 shadcn 组件写进数据库 HTML
|
|
79
|
+
- ❌ `dg-*` 当 daisyUI / Bootstrap / Ant Design
|
|
80
|
+
- ❌ 硬编码颜色值 → ✅ `var(--dg-*)`
|
|
81
|
+
- ❌ 在业务页面实现全局浮窗/客服/统计脚本 → ✅ `frontend_global_*` 固定槽位
|
|
82
|
+
|
|
83
|
+
---
|
|
348
84
|
|
|
349
|
-
|
|
85
|
+
## 本地静态资源清单
|
|
350
86
|
|
|
351
87
|
| 路径 | 说明 |
|
|
352
88
|
|---|---|
|
|
353
89
|
| `/assets/tailwindcss.js` | Tailwind CSS 运行时 |
|
|
354
|
-
| `/assets/fontawesome/css/all.min.css` | FontAwesome 6
|
|
90
|
+
| `/assets/fontawesome/css/all.min.css` | FontAwesome 6 |
|
|
91
|
+
| `/assets/icons/{name}.svg` | 内置精选 SVG 图标库(kebab-case 命名) |
|
|
92
|
+
| `/assets/icons/manifest.json` | 图标库映射清单 |
|
|
355
93
|
| `/assets/fonts/inter.css` | Inter 字体 |
|
|
356
94
|
| `/assets/fonts/lexend.css` | Lexend 字体 |
|
|
357
|
-
| `/assets/fonts/plus-jakarta-sans.css` | Plus Jakarta Sans
|
|
358
|
-
| `/assets/fonts/plus-jakarta-sans-jetbrains-mono.css` | Plus Jakarta Sans + JetBrains Mono
|
|
95
|
+
| `/assets/fonts/plus-jakarta-sans.css` | Plus Jakarta Sans |
|
|
96
|
+
| `/assets/fonts/plus-jakarta-sans-jetbrains-mono.css` | Plus Jakarta Sans + JetBrains Mono |
|
|
359
97
|
| `/assets/vendor/marked/marked.min.js` | Markdown 解析 |
|
|
360
|
-
| `/assets/vendor/prism/prism.min.js` |
|
|
361
|
-
| `/assets/vendor/
|
|
362
|
-
| `/assets/vendor/
|
|
363
|
-
| `/assets/vendor/
|
|
364
|
-
| `/assets/vendor/
|
|
365
|
-
| `/assets/vendor/gsap/
|
|
366
|
-
| `/assets/vendor/gsap/
|
|
367
|
-
| `/assets/vendor/gsap/
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
| `/assets/vendor/gsap/EasePack.min.js` | GSAP 扩展缓动集合(Bounce/Elastic 等) |
|
|
376
|
-
| `/assets/vendor/gsap/DrawSVGPlugin.min.js` | GSAP SVG 路径描边动画插件 |
|
|
377
|
-
| `/assets/vendor/gsap/MorphSVGPlugin.min.js` | GSAP SVG 形变动画插件 |
|
|
378
|
-
|
|
379
|
-
> **GSAP 使用规范**:如需使用 GSAP,禁止引用任何境外 CDN(gsap.com/cdn.jsdelivr.net 等),必须使用上表本地路径。按需引入所需插件,注册插件后方可使用:`gsap.registerPlugin(ScrollTrigger, CustomEase)`。
|
|
380
|
-
|
|
381
|
-
---
|
|
382
|
-
|
|
383
|
-
## URL 参数读取
|
|
384
|
-
|
|
385
|
-
页面运行在 `iframe.srcdoc` 中,`window.location.search` 不可靠——它指向壳层地址,不是页面路由。
|
|
386
|
-
|
|
387
|
-
### 注入机制
|
|
388
|
-
|
|
389
|
-
壳层通过 `decorateFrameHtml()` 向每个页面注入 `<script data-dg-route-bridge>`,该脚本将路由上下文写入 iframe 的 `window`:
|
|
390
|
-
|
|
391
|
-
- `window.__DG_ROUTE_CONTEXT__` — 完整路由上下文(冻结对象),含 `query`、`route`、`params` 等
|
|
392
|
-
- `window.__DG_QUERY__` — `routeContext.query` 的快捷方式
|
|
393
|
-
- `window.__DG_GET_ROUTE_CONTEXT__()` — 函数形式的兜底读取
|
|
394
|
-
|
|
395
|
-
### 标准读取方式(三阶回落)
|
|
396
|
-
|
|
397
|
-
```javascript
|
|
398
|
-
const routeContext =
|
|
399
|
-
window.__DG_ROUTE_CONTEXT__ // 首选:bridge 注入的主变量
|
|
400
|
-
|| window.__DG_GET_ROUTE_CONTEXT__?.() // 备选:函数兜底
|
|
401
|
-
|| window.parent?.App?.getCurrentRouteContext?.() // 三选:壳层 API
|
|
402
|
-
|| { query: {} }; // 兜底:空对象防崩溃
|
|
403
|
-
const query = routeContext.query || {};
|
|
404
|
-
|
|
405
|
-
// 使用示例
|
|
406
|
-
// 路由 /doctor/prescription-estimate?patientId=42&visitId=7
|
|
407
|
-
const patientId = query.patientId; // "42"
|
|
408
|
-
const visitId = query.visitId; // "7"
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
### 禁止
|
|
412
|
-
|
|
413
|
-
- `new URLSearchParams(window.location.search)` — iframe 中取不到壳层 URL
|
|
414
|
-
- `window.location.search` 直接解析 — 同上
|
|
415
|
-
|
|
416
|
-
### 常见陷阱
|
|
417
|
-
|
|
418
|
-
页面 JS 中引用 `window.__DG_ROUTE_CONTEXT__` 是**读取**操作。框架注入 bridge 的检测基于 `<script data-dg-route-bridge>` 标记,不会因页面代码读取该变量而误判跳过注入。
|
|
419
|
-
|
|
420
|
-
---
|
|
421
|
-
|
|
422
|
-
## 平台能力调用(App API)
|
|
423
|
-
|
|
424
|
-
```javascript
|
|
425
|
-
const App = window.parent?.App;
|
|
426
|
-
|
|
427
|
-
// ─── 响应格式 ───
|
|
428
|
-
// App.get/post/put/patch/delete 原样返回后端 JSON,不做任何剥壳。
|
|
429
|
-
// 后端统一信封:{ code: 200, message: "success", data: ... }
|
|
430
|
-
//
|
|
431
|
-
// 统一消费模式:
|
|
432
|
-
// const res = await App.get('some/endpoint');
|
|
433
|
-
// if (res.code !== 200) { App.showError(res.message || '操作失败'); return; }
|
|
434
|
-
// const payload = res.data; // 业务数据
|
|
435
|
-
// // 分页列表:payload.items / payload.total / payload.page / payload.page_size
|
|
436
|
-
// // 非分页列表:payload 本身是数组
|
|
437
|
-
|
|
438
|
-
// GET(第二参数为 query params)
|
|
439
|
-
const res = await App.get('pages', { page: 1, page_size: 20 });
|
|
440
|
-
const pages = res.data.items; // Array
|
|
441
|
-
|
|
442
|
-
// POST / PUT / PATCH / DELETE
|
|
443
|
-
await App.post('feedback', { type: 'bug', title: '标题' });
|
|
444
|
-
await App.put('pages/123', { title: '新标题' });
|
|
445
|
-
await App.delete('pages/123');
|
|
446
|
-
|
|
447
|
-
// Toast(推荐用便捷方法)
|
|
448
|
-
App.toast('操作成功', 'success'); // 通用方法,type 默认 'info'
|
|
449
|
-
App.showSuccess('操作成功'); // 成功提示
|
|
450
|
-
App.showError('操作失败'); // 错误提示
|
|
451
|
-
App.showWarning('请注意'); // 警告提示
|
|
452
|
-
App.showInfo('提示信息'); // 信息提示
|
|
453
|
-
|
|
454
|
-
// 确认弹窗
|
|
455
|
-
const ok = await App.confirm('确认删除?', '删除确认');
|
|
456
|
-
if (!ok) return;
|
|
457
|
-
// App.confirm(msg, title?) — 返回 Promise<boolean>,取消/点击蒙层返回 false
|
|
458
|
-
|
|
459
|
-
// 信息模态框
|
|
460
|
-
App.showModal('Token 详情内容', 'Token 明细');
|
|
461
|
-
// App.showModal(msg, title?) — 替代 window.alert()
|
|
462
|
-
|
|
463
|
-
// Loading 蒙层
|
|
464
|
-
App.showLoading(); await someAsyncOp(); App.hideLoading();
|
|
465
|
-
|
|
466
|
-
// 文件上传(返回格式同标准信封)
|
|
467
|
-
const res = await App.uploadFile(file, progress => console.log(progress));
|
|
468
|
-
const url = res.data?.url || res.data?.path;
|
|
469
|
-
|
|
470
|
-
// 外部 API 调用(已注册到「外部 API 接入」)
|
|
471
|
-
// 认证由后端注入;不要在页面里写 API Key / Bearer
|
|
472
|
-
const r = await App.callApi('weather-now', {
|
|
473
|
-
path_params: { city: 'beijing' }, // 替换 path 模板里的 {city}
|
|
474
|
-
query_params: { unit: 'metric' },
|
|
475
|
-
// body: { ... }, // POST/PUT/PATCH 才生效
|
|
476
|
-
// headers: { 'X-Trace': 't1' }, // 不会覆盖后端注入的认证头
|
|
477
|
-
});
|
|
478
|
-
// 返回:{ status_code, headers, body, duration_ms, error }
|
|
479
|
-
// body 已按 Content-Type 自动解析;上游业务错误看 status_code,不是 error
|
|
480
|
-
|
|
481
|
-
// 列出当前用户可调用的外部 API(含 code / name / 各 JSON Schema)
|
|
482
|
-
const apis = await App.listApis();
|
|
483
|
-
```
|
|
484
|
-
|
|
485
|
-
路径解析:`https://...` 直接请求 · `/api/...` 拼接 origin · 其他相对路径拼接 apiBase
|
|
486
|
-
|
|
487
|
-
### AIHub 页面 SDK
|
|
488
|
-
|
|
489
|
-
```javascript
|
|
490
|
-
// 聊天型 Agent
|
|
491
|
-
const text = await DraftGoAI.chat(agentId, '你好', (delta, full) => {
|
|
492
|
-
render(full);
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
// 图片生成型 Agent
|
|
496
|
-
const imageResult = await DraftGoAI.images(agentId, '生成一张产品主图', {
|
|
497
|
-
size: '1024x1024',
|
|
498
|
-
quality: 'auto',
|
|
499
|
-
response_format: 'url',
|
|
500
|
-
n: 1,
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
// 用户自主选择模型
|
|
504
|
-
const selectable = await DraftGoAI.getSelectableModels(agentId);
|
|
505
|
-
if (selectable.user_selectable && selectable.models.length) {
|
|
506
|
-
renderModelPicker(selectable.models);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
await DraftGoAI.chat(agentId, '你好', (delta, full) => {
|
|
510
|
-
render(full);
|
|
511
|
-
}, {
|
|
512
|
-
model: getSelectedModel(),
|
|
513
|
-
});
|
|
98
|
+
| `/assets/vendor/prism/prism.min.js` | 代码高亮 |
|
|
99
|
+
| `/assets/vendor/gsap/gsap.min.js` | GSAP 核心 |
|
|
100
|
+
| `/assets/vendor/gsap/ScrollTrigger.min.js` | GSAP ScrollTrigger |
|
|
101
|
+
| `/assets/vendor/gsap/ScrollToPlugin.min.js` | GSAP ScrollTo |
|
|
102
|
+
| `/assets/vendor/gsap/Draggable.min.js` | GSAP Draggable |
|
|
103
|
+
| `/assets/vendor/gsap/Flip.min.js` | GSAP Flip |
|
|
104
|
+
| `/assets/vendor/gsap/SplitText.min.js` | GSAP SplitText |
|
|
105
|
+
| `/assets/vendor/gsap/CustomEase.min.js` | GSAP CustomEase |
|
|
106
|
+
|
|
107
|
+
**图标用法**:
|
|
108
|
+
```html
|
|
109
|
+
<!-- 跟随文字颜色(推荐) -->
|
|
110
|
+
<span aria-hidden="true" style="width:16px;height:16px;display:inline-block;background:currentColor;-webkit-mask:url('/assets/icons/fingerprint.svg') center/contain no-repeat;mask:url('/assets/icons/fingerprint.svg') center/contain no-repeat;"></span>
|
|
111
|
+
<!-- 保留原色 -->
|
|
112
|
+
<img src="/assets/icons/fingerprint.svg" alt="" width="16" height="16">
|
|
514
113
|
```
|
|
515
114
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
`DraftGoAI.getSelectableModels(agentId)` 返回 `{ user_selectable, models }`。`models` 是 Agent 主模型 + 备用模型白名单,不是供应商全量模型列表;只有管理端开启“用户选模型”后才会返回非空列表。用户选择值通过 `options.model` 传给 `DraftGoAI.chat/images`,后端仍会按 Agent 白名单校验。
|
|
519
|
-
|
|
520
|
-
**外部 API 注意事项:**
|
|
521
|
-
- code 不知道时先 `await App.listApis()` 查询
|
|
522
|
-
- `r.error` 仅在代理层错误(超时 / 网络 / 校验失败)时非空
|
|
523
|
-
- API 未注册 / 已禁用 / 无权限 → 后端以 4xx 抛错,会被 `App.callApi` 当成异常 throw
|
|
115
|
+
GSAP:禁止引用任何境外 CDN,必须用本地路径,注册插件后才能使用:`gsap.registerPlugin(ScrollTrigger, CustomEase)`
|
|
524
116
|
|
|
525
117
|
---
|
|
526
118
|
|
|
527
119
|
## 颜色 Token(强制)
|
|
528
120
|
|
|
529
|
-
禁止硬编码任何 hex / rgb / rgba / hsl 值。
|
|
530
|
-
|
|
531
121
|
| Token | 用途 |
|
|
532
122
|
|---|---|
|
|
533
|
-
| `--dg-bg-base` |
|
|
534
|
-
| `--dg-
|
|
535
|
-
| `--dg-
|
|
536
|
-
| `--dg-
|
|
537
|
-
| `--dg-text-secondary` | 次要文字 |
|
|
538
|
-
| `--dg-text-muted` | 弱化文字 |
|
|
539
|
-
| `--dg-accent` | 主题色 |
|
|
540
|
-
| `--dg-accent-hover` | 主题色 hover |
|
|
541
|
-
| `--dg-border` | 边框 |
|
|
542
|
-
| `--dg-success/error/warning/info` | 状态色 |
|
|
543
|
-
|
|
544
|
-
例外:SVG 的 `fill`/`stroke` 可用 `currentColor` 或 `var(--dg-accent)`。
|
|
545
|
-
|
|
546
|
-
---
|
|
123
|
+
| `--dg-bg-base` / `--dg-bg-page` / `--dg-bg-surface` | 背景层级 |
|
|
124
|
+
| `--dg-text-primary` / `--dg-text-secondary` / `--dg-text-muted` | 文字层级 |
|
|
125
|
+
| `--dg-accent` / `--dg-accent-hover` / `--dg-accent-subtle` | 主题色 |
|
|
126
|
+
| `--dg-border` / `--dg-success` / `--dg-error` / `--dg-warning` | 功能色 |
|
|
547
127
|
|
|
548
|
-
|
|
128
|
+
例外:品牌色、图表色。硬编码时必须同时提供 `[data-theme="dark"]` 覆盖,对比度达 WCAG AA(文字 4.5:1)。
|
|
549
129
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
| 概念 | 键 | 取值 | 说明 |
|
|
553
|
-
|---|---|---|---|
|
|
554
|
-
| 显示模式 | `App.theme` / `dg_theme` | `'light'` \| `'dark'` | 亮色/暗色 |
|
|
555
|
-
| 配色方案 | `App.colorScheme` / `dg_color_scheme` | `'dark-gray-white'` \| `'deep-blue-white'` \| `'orange-white'` \| `'custom'` | light 模式下的色彩搭配 |
|
|
556
|
-
|
|
557
|
-
### 内置配色方案
|
|
558
|
-
|
|
559
|
-
| 方案 | 键 | 主题色 | 特点 |
|
|
560
|
-
|---|---|---|---|
|
|
561
|
-
| 深灰白(默认) | `dark-gray-white` | #27272a |
|
|
562
|
-
| 深蓝白 | `deep-blue-white` | #1e3a5f |
|
|
563
|
-
| 橙白 | `orange-white` | #ea580c |
|
|
564
|
-
| 自定义 | `custom` | 用户自选 |
|
|
565
|
-
|
|
566
|
-
### 入场主题
|
|
567
|
-
|
|
568
|
-
页面以 `iframe.srcdoc` 渲染,壳层在注入 HTML 时已将当前主题的 CSS 变量写入 `<head>` 最顶部。
|
|
569
|
-
|
|
570
|
-
**页面无需任何初始化代码**,CSS 变量在 DOM 解析时已生效,直接用 `var(--dg-*)` 即可。
|
|
571
|
-
|
|
572
|
-
### 读取当前主题
|
|
130
|
+
主题机制:壳层注入时已把 CSS 变量写入 `<head>`,页面无需初始化,直接用 `var(--dg-*)` 即可。
|
|
573
131
|
|
|
574
132
|
```javascript
|
|
575
|
-
|
|
133
|
+
// 读取(仅需 JS 分支时才读)
|
|
576
134
|
const theme = App?.theme; // 'light' | 'dark'
|
|
577
|
-
const scheme = App?.colorScheme; // 'dark-gray-white' | 'deep-blue-white' |
|
|
578
|
-
```
|
|
135
|
+
const scheme = App?.colorScheme; // 'dark-gray-white' | 'deep-blue-white' | ...
|
|
579
136
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
```javascript
|
|
585
|
-
App.applyTheme('dark'); // 切换显示模式
|
|
586
|
-
App.setColorScheme('deep-blue-white'); // 切换为预设方案
|
|
587
|
-
App.setColorScheme('custom', customVarsObject); // 应用自定义配色
|
|
588
|
-
```
|
|
589
|
-
|
|
590
|
-
调用后:壳层更新 CSS 变量 → 写入 localStorage → 同步注入当前 iframe。
|
|
591
|
-
|
|
592
|
-
### 获取方案详情
|
|
593
|
-
|
|
594
|
-
```javascript
|
|
595
|
-
const info = App.getColorScheme();
|
|
596
|
-
// { name: 'dark-gray-white', vars: { ... }, schemes: { ... } }
|
|
137
|
+
// 切换
|
|
138
|
+
App.applyTheme('dark');
|
|
139
|
+
App.setColorScheme('deep-blue-white');
|
|
597
140
|
```
|
|
598
141
|
|
|
599
|
-
### 监听主题变化
|
|
600
|
-
|
|
601
|
-
壳层不广播主题变更事件。如果页面需要响应用户切换主题,用 `MutationObserver` 监听根元素 CSS 变量变化,或在切换按钮的回调里手动处理。
|
|
602
|
-
|
|
603
|
-
### 禁止
|
|
604
|
-
|
|
605
|
-
- 禁止在页面内读取 `localStorage.dg_theme`(通过 `App.theme` 获取)
|
|
606
|
-
- 禁止在页面内读取 `localStorage.dg_color_scheme`(通过 `App.colorScheme` 获取)
|
|
607
|
-
- 禁止在页面内调用 `document.documentElement.style.setProperty` 设置主题变量
|
|
608
|
-
- 禁止硬编码任何颜色值作为主题分支的输出
|
|
609
|
-
|
|
610
142
|
---
|
|
611
143
|
|
|
612
|
-
##
|
|
144
|
+
## 导航栏开发
|
|
613
145
|
|
|
614
|
-
|
|
146
|
+
| 属性 | 说明 |
|
|
147
|
+
|---|---|
|
|
148
|
+
| `data-nav-position="top\|side"` | 根元素必填 |
|
|
149
|
+
| `data-nav-width="220px"` | 侧边栏展开宽度(可选,默认 260px) |
|
|
150
|
+
| `data-nav-collapsed-width="64px"` | 收起宽度(可选,默认 72px) |
|
|
615
151
|
|
|
616
|
-
|
|
152
|
+
```html
|
|
153
|
+
<aside data-nav-position="side" data-nav-width="220px">
|
|
154
|
+
<a href="/dashboard" data-page-route="/dashboard">仪表盘</a>
|
|
155
|
+
</aside>
|
|
156
|
+
```
|
|
617
157
|
|
|
618
|
-
|
|
158
|
+
所有导航链接必须用 `data-page-route`,不要硬编 `onclick` 跳转。
|
|
619
159
|
|
|
620
|
-
|
|
160
|
+
---
|
|
621
161
|
|
|
622
|
-
|
|
162
|
+
## AIHub 页面 SDK
|
|
623
163
|
|
|
624
|
-
|
|
164
|
+
```javascript
|
|
165
|
+
// 聊天
|
|
166
|
+
const text = await DraftGoAI.chat(agentId, '你好', (delta, full) => render(full));
|
|
625
167
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
| 顶栏 | `<header>` | `data-nav-position="top"` | 顶部 |
|
|
629
|
-
| 侧边 | `<aside>` | `data-nav-position="side"` | 左侧,可声明自定义宽度 |
|
|
168
|
+
// 图片生成(必须用 /images 接口,不要用 /chat)
|
|
169
|
+
const result = await DraftGoAI.images(agentId, '生成主图', { size: '1024x1024', n: 1 });
|
|
630
170
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
<a href="/dashboard" data-page-route="/dashboard">仪表盘</a>
|
|
636
|
-
```
|
|
171
|
+
// 用户选模型
|
|
172
|
+
const { user_selectable, models } = await DraftGoAI.getSelectableModels(agentId);
|
|
173
|
+
await DraftGoAI.chat(agentId, '你好', handler, { model: selectedModel });
|
|
174
|
+
```
|
|
637
175
|
|
|
638
|
-
|
|
176
|
+
---
|
|
639
177
|
|
|
640
|
-
|
|
178
|
+
## 退出登录
|
|
641
179
|
|
|
642
|
-
```
|
|
643
|
-
|
|
180
|
+
```javascript
|
|
181
|
+
window.parent.location.href = '/login'; // ✅ 完整刷新,导航栏重新加载
|
|
182
|
+
// App.navigate('/login') ❌ 导航栏不会更新
|
|
644
183
|
```
|
|
645
184
|
|
|
646
|
-
|
|
647
|
-
- `data-nav-collapsed-width`:收起宽度,可选;不写默认 `72px`
|
|
648
|
-
- 支持纯数字(按 px 处理)或合法 CSS 长度,如 `220px`、`14rem`、`clamp(200px,20vw,260px)`
|
|
649
|
-
- 不要在导航脚本里硬编码覆盖 `#nav-container` / `#page-container` 的 `260px`,优先用上述属性让 runtime 同步布局
|
|
185
|
+
---
|
|
650
186
|
|
|
651
|
-
|
|
187
|
+
## 加载体验
|
|
652
188
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
| 链接点击无效 | 缺少 `data-page-route` | 在 `<a>` 上加该属性 |
|
|
658
|
-
| 主题切换后颜色异常 | 使用了硬编码颜色 | 改用 `var(--dg-*)` 变量 |
|
|
189
|
+
- 初始渲染必须先展示骨架屏,再异步填数据
|
|
190
|
+
- 禁止因请求失败导致整页空白
|
|
191
|
+
- 空态必须有明确文案 + 后续操作入口,不能留空或只有 loading
|
|
192
|
+
- 若父级是 flex 工作区,空态容器应 `flex:1` 承接剩余空间,避免高度塌陷
|
|
659
193
|
|
|
660
194
|
---
|
|
661
195
|
|
|
662
|
-
##
|
|
196
|
+
## 平台能力调用快速参考
|
|
663
197
|
|
|
664
198
|
```javascript
|
|
665
|
-
|
|
666
|
-
// 错误:navigate('/login') — 导航栏不会更新
|
|
667
|
-
```
|
|
199
|
+
const App = window.parent?.App;
|
|
668
200
|
|
|
669
|
-
|
|
201
|
+
// 响应信封:{ code: 200, data: <载荷>, message: "success" }
|
|
202
|
+
const res = await App.get('pages', { page: 1, page_size: 20 });
|
|
203
|
+
if (res.code !== 200) { App.showError(res.message); return; }
|
|
204
|
+
const items = res.data.items;
|
|
670
205
|
|
|
671
|
-
|
|
206
|
+
// URL 参数(三阶回落)
|
|
207
|
+
const q = (window.__DG_ROUTE_CONTEXT__ || window.__DG_GET_ROUTE_CONTEXT__?.() || App?.getCurrentRouteContext?.() || { query: {} }).query;
|
|
208
|
+
```
|
|
672
209
|
|
|
673
|
-
-
|
|
674
|
-
- 页面初始化必须先渲染默认状态,再异步填充数据
|
|
675
|
-
- 推荐使用 shadcn Skeleton 对应的 `dg-skeleton`,或使用与 shadcn token 对齐的骨架样式。
|
|
210
|
+
完整 API 表 → `{{SKILL_DIR}}/quickref/app-api.md`
|