@xtalpi/agentic-lab-skills 0.0.10 → 0.0.11

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.
Files changed (68) hide show
  1. package/README.md +1 -1
  2. package/package.json +14 -14
  3. package/skills/lab-flow-designer/SKILL.md +612 -612
  4. package/skills/lab-flow-designer/embedded-template/SKILL.md +103 -103
  5. package/skills/lab-flow-designer/embedded-template/pools//345/205/245/345/217/243/346/261/240.md +21 -21
  6. package/skills/lab-flow-designer/embedded-template/pools//345/207/272/345/217/243/346/261/240.md +21 -21
  7. package/skills/lab-flow-designer/embedded-template/scripts//347/244/272/344/276/213/346/225/260/346/215/256/344/270/216/346/240/241/351/252/214/351/227/250/346/216/247.js +142 -142
  8. package/skills/lab-flow-designer/embedded-template/valves//347/244/272/344/276/213/346/225/260/346/215/256/344/270/216/346/240/241/351/252/214/351/227/250/346/216/247.md +114 -114
  9. package/skills/lab-flow-designer/references/agentic-lab-processer.md +122 -122
  10. package/skills/lab-flow-designer/references/agentic-lab-sdk.md +534 -361
  11. package/skills/lab-flow-designer/references/rhea-api/README.md +7 -7
  12. package/skills/lab-flow-designer/references/rhea-api/execute_process_batch.md +58 -58
  13. package/skills/lab-flow-designer/references/skill-package-layout.md +268 -268
  14. package/skills/lab-flow-designer/references//344/270/232/345/212/241/346/265/201/347/250/213/346/226/207/346/241/243/346/240/207/345/207/206.md +216 -216
  15. package/skills/lab-flow-designer/templates//344/270/232/345/212/241/346/265/201/347/250/213/346/226/207/346/241/243/346/250/241/346/235/277.md +192 -192
  16. package/skills/lab-flow-designer/templates//344/270/232/345/212/241/346/265/201/347/250/213/346/226/207/346/241/243/347/244/272/344/276/213.md +207 -207
  17. package/skills/lab-flow-designer/testing/test-processer.mjs +1240 -1240
  18. package/skills/lab-nocobase-flow-generator/SKILL.md +164 -164
  19. package/skills/lab-nocobase-flow-generator/examples/setting/350/241/250/350/216/267/345/217/226/345/244/226/351/203/250/346/234/215/345/212/241.js +70 -70
  20. package/skills/lab-nocobase-flow-generator/examples//346/237/245/350/257/242/345/214/226/345/255/246/345/223/201/344/277/241/346/201/257.js +30 -30
  21. package/skills/lab-nocobase-flow-generator/references/doc-standard.md +84 -84
  22. package/skills/lab-nocobase-flow-generator/references/runtime-api.md +224 -224
  23. package/skills/lab-nocobase-flow-generator/templates//350/204/232/346/234/254/351/200/273/350/276/221/346/226/207/346/241/243/346/250/241/346/235/277.md +121 -121
  24. package/skills/lab-nocobase-flow-generator/templates//350/204/232/346/234/254/351/200/273/350/276/221/346/226/207/346/241/243/347/244/272/344/276/213.md +67 -67
  25. package/skills/lab-orbit-component-builder/SKILL.md +353 -353
  26. package/skills/lab-orbit-component-builder/examples/xnb-component-template/.env.local.example +27 -27
  27. package/skills/lab-orbit-component-builder/examples/xnb-component-template/.eslintignore +7 -7
  28. package/skills/lab-orbit-component-builder/examples/xnb-component-template/.eslintrc.cjs +88 -88
  29. package/skills/lab-orbit-component-builder/examples/xnb-component-template/.nvmrc +1 -1
  30. package/skills/lab-orbit-component-builder/examples/xnb-component-template/AgenticAppAPI.md +268 -268
  31. package/skills/lab-orbit-component-builder/examples/xnb-component-template/Jenkinsfile +106 -106
  32. package/skills/lab-orbit-component-builder/examples/xnb-component-template/OrbitAPI.md +453 -453
  33. package/skills/lab-orbit-component-builder/examples/xnb-component-template/README.md +176 -176
  34. package/skills/lab-orbit-component-builder/examples/xnb-component-template/dev/public/index.html +12 -12
  35. package/skills/lab-orbit-component-builder/examples/xnb-component-template/dev/src/App.vue +151 -151
  36. package/skills/lab-orbit-component-builder/examples/xnb-component-template/dev/src/components/DevOpenerLauncher.vue +143 -143
  37. package/skills/lab-orbit-component-builder/examples/xnb-component-template/dev/src/global.d.ts +77 -77
  38. package/skills/lab-orbit-component-builder/examples/xnb-component-template/dev/src/main.ts +308 -308
  39. package/skills/lab-orbit-component-builder/examples/xnb-component-template/dev/src/mockXNBBitable.ts +119 -119
  40. package/skills/lab-orbit-component-builder/examples/xnb-component-template/dev/src/shims-vue.d.ts +6 -6
  41. package/skills/lab-orbit-component-builder/examples/xnb-component-template/dev/src/utils/devOpenerHost.ts +75 -75
  42. package/skills/lab-orbit-component-builder/examples/xnb-component-template/index.html +13 -13
  43. package/skills/lab-orbit-component-builder/examples/xnb-component-template/package.json +60 -60
  44. package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/api/agenticlabTickets.ts +110 -110
  45. package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/entries/bitable.ts +4 -4
  46. package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/entries/custom-page.ts +4 -4
  47. package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/index.ts +1 -1
  48. package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/styles/orbit-quasar-host.scss +19 -19
  49. package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/types/context.ts +15 -15
  50. package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/types/xnb-context.ts +70 -70
  51. package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/use/useBitablePage.ts +189 -189
  52. package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/use/useSuperCellDemo.ts +257 -257
  53. package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/use/useSuperTableBitableLifecycle.ts +555 -555
  54. package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/utils/openerInitParams.ts +158 -158
  55. package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/utils/openerTicketIds.ts +32 -32
  56. package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/utils/orbitHttpClient.ts +110 -110
  57. package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/utils/request.ts +92 -92
  58. package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/views/bitable.vue +67 -67
  59. package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/views/custom-page.vue +140 -140
  60. package/skills/lab-orbit-component-builder/examples/xnb-component-template/tsconfig.json +45 -45
  61. package/skills/lab-orbit-component-builder/examples/xnb-component-template/vite.config.ts +170 -170
  62. package/skills/lab-orbit-component-builder/examples/xnb-component-template/vite.dev.config.ts +58 -58
  63. package/skills/lab-orbit-component-builder/references/flow-document-human-ui.md +65 -65
  64. package/skills/lab-orbit-component-builder/references/orbit-vue-conventions.md +133 -133
  65. package/skills/lab-orbit-component-builder/references/pool-schema-to-columns.md +67 -67
  66. package/skills/lab-orbit-component-builder/references/vue-template-checklist.md +179 -179
  67. package/skills/lab-orbit-component-builder/references/xnb-context-vue-props.md +49 -49
  68. package/skills/lab-orbit-component-builder/references/xnbitable-vue-parity.md +32 -32
@@ -1,453 +1,453 @@
1
- # Orbit JsCode 运行时 API 参考(Vue Cell 对照)
2
-
3
- > **Vue 组件(`<script setup>` + `props.xnbContext`)**
4
- > - HTTP:使用本仓库 **`src/utils/orbitHttpClient.ts`** → **`orbitRequestJson`**;宿主内优先 **`window.xnb.http.client.request`**(`ResponseType.JSON`、`Body.json`,与 Book 代理及鉴权一致),无注入时回退 **`fetch`**。
5
- > - Token:优先 **`await window.xnb.choreo.getUserToken()`**(无参;与本仓库工单请求一致);本地直连 AgenticLab 见 **`AgenticAppAPI.md`** 与 `.env.local`。JsCode 场景若环境仍要求传入 **`bookPath`**,见下文 **§6.2** 原文。
6
- > - 超级表格行数据:本模板 **`src/use/useSuperTableBitableLifecycle.ts`**;列与 `items` 约定见 **§8**(尤其 **§8.5.2**)。
7
- > - **§7 / §9**(Book `Sheet` / Materialize 表单)在 Vue 中多由 SFC + Quasar 实现,JsCode 原文仍保留供对照。
8
- >
9
- > 原文档路径:**`orbit-skills/orbit-write-js-cell/references/API.md`**(本仓库为完整拷贝)。
10
-
11
- 下文整理自多团队线上 JsCode 实践,并与 Orbit / XNB 在 **Cell、Book** 侧的常见产品约定对照。**以实际运行时注入为准**:不同 Orbit 版本可能增减字段,编写前可在同环境已有 JsCode 中对照。
12
-
13
- ---
14
-
15
- ## 1. 注入:`xnbContext`
16
-
17
- 脚本**无需**自己声明 `xnbContext`,由运行时注入。常见用法是在文件顶部解构:
18
-
19
- ```js
20
- const cellUid = xnbContext.cellUid
21
- const bookPath = xnbContext.bookPath
22
- const notifySuccess = xnbContext.notifySuccess
23
- const notifyError = xnbContext.notifyError
24
- const loadingShow = xnbContext.loadingShow
25
- const loadingHide = xnbContext.loadingHide
26
- ```
27
-
28
- ### 1.1 高频成员(场景中出现次数多)
29
-
30
- | 成员 | 用途 |
31
- |------|------|
32
- | `cellUid` | 当前 JsCode Cell 的 uid;事件名、`cell.register`、DOM id 前缀 |
33
- | `bookPath` | 当前 Book 路径;传给 `window.xnb.book.*`、`ChoreoTool`、`XNBBitable` 等 |
34
- | `notifySuccess` / `notifyError` | 轻量提示 |
35
- | `loadingShow` / `loadingHide` | 全局 loading |
36
- | `getUserData` / (若存在)`setUserData` | 读写 Cell 用户数据(持久化字段由 Book/Cell 配置决定) |
37
- | `getInstanceValueById` / `setInstanceValueById` | 按 DOM 控件 id 读写实例值(与渲染 HTML 中的 `id` 对应) |
38
- | `stepDone(stepIndex, ...)` | 多步向导中标记某步完成(参数以环境为准) |
39
- | `reconfirm` | 二次确认对话框(若环境提供) |
40
-
41
- ### 1.2 类/工具构造函数(按业务选用)
42
-
43
- 从 `xnbContext` 取出构造函数后 **自己 `new`**,例如:
44
-
45
- - `XNBBitable`:`new XNBBitable({ bookPath, cellUid })`
46
- - `ChoreoTool`:`new ChoreoTool({ bookPath, cellUid, feishuTable: { feishuUrl, tableNames } })`
47
- - `XNBUtil`、`G2Service` 等:按业务需要解构
48
-
49
- ### 1.3 常见错误对照(代码无法运行时的首要排查)
50
-
51
- | 错误(易由通用 LLM 臆造) | 正确 |
52
- |---------------------------|------|
53
- | 使用 `cellUid` / `bookPath` 但未从 **`xnbContext`** 解构 | 文件**最顶部**:`const cellUid = xnbContext.cellUid` 等 |
54
- | `window.xnb.getUserData()` | **`xnbContext.getUserData()`** |
55
- | `window.xnb.getUserToken()` | **`await window.xnb.choreo.getUserToken(bookPath)`**(需已有 `bookPath`) |
56
- | `window.xnb.cell.loadingShow` / `notify` / `notifyError` | **`xnbContext.loadingShow` / `notifySuccess` / `notifyError`**(名称以注入为准) |
57
- | `const { cell } = window.xnb` 再 `cell.register({ ... })` | **`window.xnb.cell.register(cellUid, '方法名字符串', async () => {})`**,每个方法单独注册 |
58
- | `document.body.appendChild`、`document.head`、`DOMContentLoaded` 驱动主界面 | 主 UI 用脚本**末尾 `return \`...\``** 返回 HTML;交互用 **`onclick="window.xnb.cell.run('${cellUid}', '方法名')"`** |
59
- | `return \`...\`` 里嵌 **`<script>`** 再 `cell.run` | 优先 **按钮 onclick**;内联 script 在 Cell 沙箱里常**不执行或不可靠** |
60
- | 裸 **`fetch`** 调业务域 | 优先 **`window.xnb.http.client.request`** + `ResponseType` / `Body.json`(与现有 Book 代理、鉴权一致) |
61
- | `const { book, cell, http } = window.xnb` 当解构入口 | **`window.xnb.book` / `window.xnb.cell` / `window.xnb.http`** 分命名空间访问;`http` 一般为 **`window.xnb.http.client`** |
62
- | 已有 **子 Sheet / XNBBitable**,再在 `return` 里放 **整页 `<table>`** 用 `innerHTML` 刷接口数据 | **列表只写一处**:`items`+`updateSubBitable` **或** `celldata`+`updateCellData`;`return` 只放工具栏/筛选/弹窗(见 **7.2**、**8.5**) |
63
- | **内嵌 XNBBitable** 已显示,却用 **`createCell('Sheet')` + `celldata`** 当主列表 | 主列表走 **`resolveBitableBySub` + `items` + `updateSubBitable`**(见 **7.0**、**8.5–8.6**、`examples/bitable-list-lifecycle.js`) |
64
- | **有列头、items 已赋值,格内仍全空** | **`dataColumns[].field` 与 `fields` 键不一致**;按 **8.4.1** **`rebuildSubBitableColumns`** 对齐列后再写 `items` |
65
- | 每次点「刷新」都跑 **`createSubBitable` / 清空列** | **§8.5.1**:**`resolve` → 拉数 → 写 `items`(全量 list 须整表替换,见 **§8.5.2**)→ `updateSubBitable({ isMerge:true })`**;仅首次或 **`dataColumnsMismatch`** 时重建列 |
66
- | **全量 list** 仍用「有可见行则只按 id 合并、从不删行」 | 若接口返回**当前全集**,每次刷新应 **`bitable.items = mappedRows`**;否则删空或变少时界面仍留旧行,**`[]`** 时常见 **toast「0 条」表格不变**(**§8.5.2**) |
67
- | 接口行直接赋给 **`items`**,`fields` 键与 **`dataColumns[].field`** 不一致 | **先映射**再 `updateSubBitable`,否则有表头无格内文字 |
68
- | Sheet **`celldata`** 只有 **`v.v`** 无 **`m`/`ct`** | 使用 **`{ v, m, ct: { fa: 'General', t: 'g' } }`**,否则网格常空白(见 **7.1**) |
69
- | **`datetime-local`** 仍用 **`input-field`** + **`getInstanceValueById` 读值** | 块级 **`label` + 边框 `input`**;读 **`document.getElementById`**(见 **9.2**);**仅日期**优先 **`type="date"`** 且模板写 **`value="YYYY-MM-DD"`**(见 **9.1**) |
70
- | **`<select>` 与周围控件风格脱节或双影** | **与 `date` 同款线框 + label/select 横向 flex**;双影时再试 **`browser-default`**(见 **9.1.1**) |
71
- | 每次刷新都 **`createCell` / `refresh-book`** 再提示成功 | **缓存子表 uid**;刷新只 **`updateCellData` + `reload`**;**toast 在写回与短延迟之后**(见 **7.3**) |
72
- | NocoBase **`POST …/api/{collection}:create`** 使用 **`Body.json({ values: payload })`**(误套 `values`) | **官方 `:create` 体为字段扁平 JSON**:**`Body.json(payload)`**;误包一层会导致服务端读不到字段、**落库空记录**(见 **§6.3.1**) |
73
-
74
- 部分线上 Cell 使用 **`return () => ({ html: () => \`...\` })`** 工厂形式渲染;**同一 Book 内请与已有 JsCode 保持一致**,勿在不确定运行时版本时混用两种 `return` 形态。
75
-
76
- ---
77
-
78
- ## 2. 全局:`window.xnb`
79
-
80
- ### 2.1 `window.xnb.book`
81
-
82
- | 方法 | 说明 |
83
- |------|------|
84
- | `getBook(bookPath)` | 返回含 `metadata` 等;`metadata.cells` 列出 Cell |
85
- | `getBookMeta(bookPath)` | 仅元数据场景可用(部分脚本使用) |
86
- | `getCell(bookPath, cellUid)` | 读取某 Cell 的 `data`(如 Sheet 的 `input`/`output`) |
87
- | **`getUserConfig(bookPath)`** | 异步读取 Book **`userConfig`**(与 `xnb_metadata.json` 中配置对应)。JsCode 常用键:**`hide_project_config`**、**`hide_table_config`**、**`hide_params_config`**(是否隐藏各配置面板)。见 **`examples/book-user-config.js`** |
88
- | `createCell(bookPath, type, options)` | 创建 Cell;如 `type === 'Sheet'` 且带 `parentCellUid` 创建子表 |
89
- | `updateCellData(...)` | 更新 Cell 数据(一般由上层或 `save` 封装调用) |
90
-
91
- ### 2.2 `window.xnb.cell`
92
-
93
- | 方法 | 说明 |
94
- |------|------|
95
- | `register(cellUid, methodName, fn)` | 将异步/同步函数暴露给渲染 HTML |
96
- | `run(cellUid, methodName, ...args)` | 在 HTML 字符串的 `onclick` 中调用 |
97
-
98
- ### 2.3 `window.xnb.event`
99
-
100
- 跨 Cell 或通知视图层的通道。
101
-
102
- **常见模式(请求-响应)**:`emit` 传入回调,由监听方调用 `resolve`/`reject`:
103
-
104
- ```js
105
- await new Promise((resolve, reject) => {
106
- window.xnb.event.emit(`${otherCellUid}::get-sheet`, resolve, reject, nodata)
107
- })
108
- ```
109
-
110
- **运行/编辑状态**(与 Book 层协同;`running` / `status` 等语义见第 4 节):
111
-
112
- ```js
113
- window.xnb.event.emit(`${cellUid}::running`, 'start') // 'stop' | 'error'
114
- window.xnb.event.emit(`${cellUid}::edited`, true)
115
- ```
116
-
117
- **视图**(监听 Cell 视图变化):
118
-
119
- ```js
120
- window.xnb.event.on(`${cellUid}::cell-view`, handler)
121
- ```
122
-
123
- ### 2.4 `window.xnb.choreo`
124
-
125
- 例如:`getDbInfo(bookPath)`、**`getUserToken(bookPath)`**(与当前 Book 会话绑定的 Bearer Token,常用于业务 HTTP)、`delay(ms)` 等(以当前环境为准)。详见下文 **6.2 节**。
126
-
127
- ### 2.5 `window.xnb.http`
128
-
129
- ```js
130
- const { client, Body, ResponseType } = window.xnb.http
131
- // client.request({ method, url, body: Body.json({...}) })
132
- ```
133
-
134
- ### 2.6 `window.xnb.file` / `window.xnb.store`
135
-
136
- - `file.joinPath`、`readJson` 等:读写工作区文件(权限受环境限制)。
137
- - `store.get(key)` / `store.set`:键值存储(如布局 `layout-store`,与 Book 布局功能配套)。
138
-
139
- ### 2.7 `window.xnb.utils`
140
-
141
- 例如 `window.xnb.utils.uuid.v4()`、`window.xnb.utils.dayjs`(若环境挂载)。
142
-
143
- ---
144
-
145
- ## 3. Book / 布局层事件(摘要)
146
-
147
- 以下为 Orbit **Book 菜单**与 **GoldenLayout 布局** 侧常见事件(产品实现可能迭代,以当前前端为准)。**完整菜单事件名**(如 `save`、`refresh-book`、`lock-book`、`copy-id` 等)以你环境 **Orbit 前端或产品文档**为准;下表仅列 JsCode 常用子集。
148
-
149
- - 菜单类:部分文档为 `window.xnb.event.emit('xnb-book-menu', /* 事件名 */)`(如 `'change-book-view-type'`、`'reset-tabs-view'`);另有 `window.webb.event.emit('xnb-book-menu', BookEvents)` 写法,以当前 Orbit 前端版本为准。
150
- - 聚焦 Cell:`window.xnb.event.emit('xnb-focus-cell', { bookPath, cellUid })`。
151
- - Tabs 布局:**`layout-tabs-add-cell`**(载荷 `{ type, uid }`;在指定 Tab 组内新增时可传第三参 **`{ currentUid }`**)、**`${uid}::layout-tabs-focus`**、**`${uid}::layout-tabs-remove`**。布局模式:**`window.xnb.store.get('layout-store')?.layoutType`** 常见为 **`'tabs'`**(二分)或 **`'default'`**。
152
- - 刷新 Book:`window.xnb.event.emit('refresh-book', { bookPath })`(场景中有使用)。
153
-
154
- ---
155
-
156
- ## 4. XNB Cell 通用模型(约定摘要)
157
-
158
- - **permission**:`writable` / `readable` / `executable`,由外层传入,Cell 内实现交互限制。
159
- - **status**:`running` / `focus` / `error`;运行态由 **`${cellUid}::running`** 与 Book 层协同;**`error` 后需再发 `stop`** 以便外层清错误态。
160
- - **mode**:`Normal` | `Render`(编辑 vs 渲染)。
161
- - **view**:`Normal` | `Fullscreen` | **`Maximum`** | `Abstract` | `Empty` 等,由 Book 控制、Cell 内响应;监听 **`window.xnb.event.on(\`${cellUid}::cell-view\`, handler)`** 时以实际枚举为准。
162
- - **CellParams.isCellWindow**:是否独立窗口。
163
- - **焦点**:若 Cell 内 **`mousedown` 被拦截导致无法冒泡**,可 **`window.xnb.event.emit(\`${cellUid}::mousedown\`)`** 通知外层更新高亮。
164
-
165
- JsCode 侧通常通过 **事件** 与 **`window.xnb.cell.run`** 驱动逻辑。若 Cell 实例暴露 **`save(isNotify?, clearUserData?)`**、**`setPermission`**、**`setStatus`**、**`setView`** 等,**参数与语义以你方 Cell 封装或 Orbit 文档为准**。
166
-
167
- ---
168
-
169
- ## 5. 编写习惯(精简)
170
-
171
- 1. 顶部解构 `cellUid`、`bookPath`、通知与 loading;**先对照第 1.3 节**。
172
- 2. 用户操作 → **`register(cellUid, name, fn)` + `onclick` 里 `run(cellUid, name)`**;表格类数据优先 **XNBBitable / ChoreoTool**(见示例),勿用 `document.body` 拼整页 DOM。
173
- 3. 含表单时:第 9 节 + `examples/form-controls-materialize.js`。
174
-
175
- ---
176
-
177
- ## 6. 用户数据、Choreo Token 与 HTTP(常见生产模式)
178
-
179
- ### 6.1 获取配置:`getUserData` + `projectURL`
180
-
181
- Cell 的 **用户数据**(在 Book/Cell 配置里由运营或实验员填写)通过 `xnbContext.getUserData()` 读取。常见做法是把 **JSON 字符串** 放在 `projectURL`(命名历史原因,内容不一定是 URL)里,解析后得到后端基址、多环境路由等:
182
-
183
- ```js
184
- const userData = xnbContext.getUserData()
185
- const { projectURL } = userData
186
- let {
187
- request_url = 'http://127.0.0.1:13000'
188
- } = JSON.parse(projectURL || '{}')
189
- ```
190
-
191
- - **`JSON.parse(projectURL || '{}')`**:避免空值抛错;缺省字段用解构默认值。
192
- - **约定**:与业务方书面固定 JSON 字段名(如 `request_url`),并在技能/交付说明中写清示例 JSON,避免现场填错导致静默连错环境。
193
-
194
- ### 6.2 获取用户 Token:`window.xnb.choreo.getUserToken`
195
-
196
- 与 Orbit/Choreo 登录态绑定的 Token,用于请求受保护的业务 API:
197
-
198
- ```js
199
- const getUserToken = async () => {
200
- try {
201
- return await window.xnb.choreo.getUserToken(bookPath)
202
- } catch (error) {
203
- console.error('获取用户 Token 失败:', error)
204
- throw error // 或按策略 return null / 仅开发环境备用(勿把长期 Token 提交仓库)
205
- }
206
- }
207
- ```
208
-
209
- - **必须传入 `bookPath`**,与当前 Book 一致。
210
- - 捕获失败时是否在开发机回退到固定 Token:**仅作本地调试**,生产应提示用户重新登录或检查 Choreo。
211
-
212
- ### 6.3 调用 HTTP:`window.xnb.http.client.request`
213
-
214
- 典型解构:
215
-
216
- ```js
217
- const { client, Body, ResponseType } = window.xnb.http
218
- ```
219
-
220
- GET 列表示例(Bearer + `query` + JSON 响应类型):
221
-
222
- ```js
223
- const token = await getUserToken()
224
- const res = await client.request({
225
- method: 'get',
226
- url: `${request_url}/api/your_resource:list`,
227
- headers: {
228
- 'Content-Type': 'application/json; charset=utf-8',
229
- Authorization: `Bearer ${token}`
230
- },
231
- responseType: ResponseType.JSON,
232
- query: { pageSize: '99999' }
233
- })
234
- // 业务体位置因网关而异,常见为 res?.data?.data
235
- ```
236
-
237
- POST 时常用 `body: Body.json({ ... })`。具体 `client.request` 的完整参数以当前 `window.xnb.http` 实现为准。
238
-
239
- ### 6.3.1 NocoBase:`{collection}:create` / `:update` 与 `values` 包裹(易踩坑)
240
-
241
- NocoBase **资源风格**接口(常见于 **`POST ${request_url}/api/your_collection:create`**,与 **`…:list`** 同源)在官方文档与 API 文档插件中的约定是:**请求体 JSON 的根级即业务字段**,与集合字段名(如 `title`、`barcode`)一一对应,**不要**再包一层 `values` / `data`(除非你们网关或另一套 REST 明确另有约定)。
242
-
243
- ```js
244
- // ✅ 正确:扁平字段(与 NocoBase API 文档示例一致)
245
- await client.request({
246
- method: 'post',
247
- url: `${request_url}/api/compound:create`,
248
- headers: {
249
- 'Content-Type': 'application/json; charset=utf-8',
250
- Authorization: `Bearer ${token}`
251
- },
252
- responseType: ResponseType.JSON,
253
- body: Body.json({ barcode: 'B001', compound_id: 'C-01', status: 'Available' })
254
- })
255
-
256
- // ❌ 常见误生成:多包一层 values → 服务端不读嵌套键,易出现「创建成功但记录全空」
257
- // body: Body.json({ values: { barcode: 'B001', ... } })
258
- ```
259
-
260
- - **`:update`** 同理:更新字段一般也在 **根级** 与 **`filterByTk`(或团队约定的 filter query)** 配合使用;仍以环境内 **API 文档插件**为准。
261
- - **另一套路由**(如 **`POST /api/collections/{name}/records`**)可能要求 **`{ values: { … } }`** 或其它形状;**路径不同则体不同**,生成代码前用 **第 0 步** 与业务方对齐 **URL + 示例 JSON**,勿把「collections 记录」的 `values` 习惯套到 **`…:create`** 上。
262
-
263
- ### 6.4 `try/catch`、`loadingShow`/`loadingHide` 与 `notify*`
264
-
265
- 长任务或可能失败的操作:**先 `loadingShow()`,在 `finally` 里 `loadingHide()`**,避免异常路径漏关 loading:
266
-
267
- ```js
268
- try {
269
- loadingShow()
270
- await createEmptyBitable()
271
- notifySuccess('表格已就绪')
272
- } catch (error) {
273
- notifyError(error, '', '创建表格失败')
274
- throw error
275
- } finally {
276
- loadingHide()
277
- }
278
- ```
279
-
280
- - **`notifySuccess(message)`**:成功轻提示。
281
- - **`notifyError(error, detail, title)`**(参数个数与语义以运行环境为准):第三个参数常用于 **简短标题**,便于用户区分失败场景。
282
- - 是否在 `catch` 里 **`throw error`**:若希望上层或 `cell.run` 统一感知失败,可继续抛出;若仅提示即可则不必再抛。
283
-
284
- ---
285
-
286
- ## 7. 创建 **Book 级** Sheet Cell(用户意图:「创建 Sheet Cell」「新建表格 Cell」等)
287
-
288
- 当用户明确要求在 Book 里新增一个 **类型为 `Sheet` 的独立 Cell**(出现在 Book 元数据的 `cells` 列表中)时,使用 **`window.xnb.book.createCell`**,而不是仅用 `XNBBitable` 在 JsCode 内画图(见第 8 节)。
289
-
290
- 典型参数:
291
-
292
- - `type`:`'Sheet'`
293
- - `data`:`{ input: [{ celldata }], output: null }`(可从已有 Sheet `getCell` 拷贝结构再裁剪)
294
- - `parentCellUid`:可选;挂在当前 JsCode 下时用当前 `cellUid`
295
-
296
- 创建成功后通常 **`window.xnb.event.emit('refresh-book', { bookPath })`** 或向目标 Sheet 的 uid **`emit(\`${sheetUid}::reload\`)`** 刷新视图。完整流程见 **`examples/create-sheet.js`**。
297
-
298
- **与第 8 节区分**:第 8 节是 **JsCode 内 XNBBitable**(列头来自 `dataColumns`,行来自 `items`);本节是 **Book 里独立的 Sheet Cell**(格点来自 `celldata`)。需要「与业务脚本相同的列类型/下拉/着色列头」时,**优先第 8 节**,不要假设 `celldata` 能等价替代。
299
-
300
- ### 7.0 先判「用户看到的是哪张表」(避免整表空白)
301
-
302
- - **典型现象**:Cell 下方已是 **带格式工具栏、列字母 A/B/… 的表格区域**(即编辑器里为 JsCode 配置的 **XNBBitable / 子表**),但 **第 1 行不出现业务列头**、刷新后仍空;脚本却在 **`book.createCell(..., 'Sheet')` + `updateCellData(celldata)`** 上写数。
303
- - **原因**:**Book 元数据里的独立 `Sheet` Cell** 与 **当前 Cell 内嵌的 XNBBitable** 是 **两套存储**;内嵌表的列头与行数据只认 **`dataColumns` + `items` + `updateSubBitable`**,**不会**自动读取你为「子 Sheet Cell」写入的 `celldata`。
304
- - **正确做法**:主列表若画在内嵌 Bitable 上,用 **`getAllSubBitable` / `getSubBitable` → 解析到目标 `bitable` → 赋 `items` → `await updateSubBitable`**(见 **8.5、8.6** 与 **`examples/bitable-list-lifecycle.js`**)。只有需求明确为「在 Book 里再挂一个 **独立 Sheet Cell**」时才用 **本节 `createCell`**。
305
- - **首屏无列头**:若 **`!(await xnbBitable.getSubCell())`**,须先 **`createSubBitable` + `addColumns`**(与现网 **`if (!await xnbBitable.getSubCell()) await createEmptyBitable()`** 同类);若已有子 Cell 但 **`data_columns` 为空或与脚本不一致**,走 **`rebuildSubBitableColumns` / `dataColumnsMismatch`** 再 **`updateSubBitable`**,然后再拉接口写 **`items`**。
306
-
307
- ### 7.1 子 Sheet 已创建但网格无列头 / 无数据行(`celldata` 路径)
308
-
309
- **`Cell.data.input` / `output_bitable`(Sheet Cell)与 XNBBitable**:独立 **Sheet** Cell 的 **`data.input`** 为 Luckysheet 系 **`celldata`**;**`output_bitable`** 为引擎/导出侧的多维表结构,**与** JsCode 里 **`getSubBitable(true)` 得到的内存 `items`/`data_columns` 不是同一套对象**。格点 **`v`** 内建议同时带 **`m`**、**`ct`**(见下)。**`celldata` / `output_bitable` 字段级类型**以同 Book 内 **`getCell` 样例**或 **Luckysheet 初始化文档**为准。
310
-
311
- 常见原因:
312
-
313
- 1. **`updateCellData` / `updateCell` 不存在或签名不同**:运行时若无对应 API,写入不会生效;应用 **`getCell`** 对照同 Book 内**已能正常显示**的 Sheet 的 `data.input[0].celldata` 结构再生成。
314
- 2. **`celldata` 与引擎约定不一致**:每个格点建议 **`{ r, c, v: { v, m, ct: { fa: 'General', t: 'g' } } }`**(`m` 为展示文本,`ct` 为类型);仅有 **`{ r, c, v: { v } }`** 时 Luckysheet 系内核**常整表空白**。`input[0]` 可带 **`row` / `column` / `status`** 等与 `getCell` 对齐。
315
- 3. **只 `reload` 未 `refresh-book`**:创建后元数据未稳定时,可先 **`refresh-book`** 再对子 uid **`emit(\`${uid}::reload\`)`**(顺序以实测为准)。
316
- 4. **需求其实是「可配置子表」**:应改用 **第 8 节 XNBBitable**,而不是继续调 `celldata`。
317
-
318
- ### 7.2 禁止「双轨列表」:子 Sheet / XNBBitable + 一整张 HTML `<table>`
319
-
320
- 常见误生成:已创建 **Book 子 Sheet** 或 **XNBBitable**,又在 `return \`...\`` 里放 **`<table><thead>…<tbody id=…>`**,用 `innerHTML` 把接口数据画进 tbody。
321
-
322
- - **结果**:用户看到 **两套表**(子 Sheet 有表头但格子里没数据 + HTML 表有数据),或误以为数据已进子表。
323
- - **正确**:**列表数据只写到一个载体**——要么 **`bitable.items` + `updateSubBitable`**(XNBBitable),要么 **`celldata` + `updateCellData`(若存在)+ `reload`**(Book Sheet);`return` 里通常 **只保留工具栏、筛选、弹窗**,**不要**再堆主数据 `<table>`。
324
-
325
- ### 7.3 刷新时机:缓存子表、避免「先提示后空表」
326
-
327
- **Book 子 Sheet(`celldata` + `updateCellData`)**
328
-
329
- - **缓存子 Sheet 的 `uid`**(模块级变量):仅在首次不存在时 **`createCell` + `refresh-book`**;之后每次刷新只做 **`updateCellData` + `emit(\`${uid}::reload\`)`**,**不要**重复创建或反复全量 `refresh-book`。
330
- - **`notifySuccess('已刷新 N 条')`** 建议放在 **`updateCellData` 与 `reload`(及可选的短 `setTimeout`)之后**,减少「提示已更新但网格 1~2 秒仍空」的错觉。
331
-
332
- **XNBBitable(`items` + `updateSubBitable`)**
333
-
334
- - 现网常见:**`getAllSubBitable` → 按 `index` 找到目标表 →(必要时先 `updateSubBitable` 切换当前子表)→ 赋 `items` → 再 `await updateSubBitable`**;**成功提示放在最后一次 `updateSubBitable` 的 `await` 之后**。
335
- - **不要**在每次拉数时重复 **`createSubBitable`**;刷新 = **改数据 API + 更新内存 `items` + `updateSubBitable`**。
336
-
337
- ---
338
-
339
- ## 8. XNBBitable:列头(`dataColumns`)与数据行(`items`)
340
-
341
- 在 JsCode 的 **子表区域**(编辑器里已为该 Cell 配置子 Sheet / bitable)中,**列标题与列编辑器类型**来自 **`dataColumns` + `addColumns`**。**不要**再用 `return` 里的整页 **`<table>`** 展示同一批业务行(见 **7.2**)。Book 子 Sheet 的 `celldata` **不会**自动出现在 XNBBitable 里。
342
-
343
- **子表列表实现**:**`examples/bitable-list-lifecycle.js`**。**摘录**:同目录 **`bitable-toolbar-forms.md`**。
344
-
345
- ### 8.1 必须先对齐 Book 配置(否则只有空壳、无列头)
346
-
347
- - **`subBitables[i].index`**:必须与编辑器里该子表的 **index 字符串** 完全一致(常见如 `sheet-xxx`)。写错则 **`handleBitable`** 绑错对象,列不渲染。
348
- - **`createSubBitable` / `updateSubBitable` 的 `name` 参数**:须与 Book 配置一致;常见写法为 **`subBitables[k].name`**,也有项目写死 **`'sheet'`**;**与 `subBitables[0].index` 写错一样会导致表头在但行不刷新或更新无效**。
349
-
350
- ### 8.2 `dataColumns`(列模型)
351
-
352
- - 每项至少含 **`field`(列名)**、**`dv`**(如 `text_length`、`number`、`xnb_select`、`xnb_checkbox`、`xnb_time`)、**`readonly` / `visible` / `dbRelated`** 等;`xnb_select` 常用 **`value1`(逗号分隔选项)**、**`value2`(可选颜色)**。
353
- - 定义完成后必须:**`dataColumns.forEach((dc, i) => { dc.c = i })`**。
354
- - **`initDataColumns`** 可为同步函数;列依赖接口选项时,先拉选项再赋值再 `forEach`。
355
-
356
- ### 8.3 `handleBitable(bitable, options)`
357
-
358
- - **`options`** 通常即 **`subBitables[k]`**(含 `name`、`index`、`row` 等)。**`index` 若为空字符串或未传,勿覆盖 `bitable` 已有 `index`**(单主表回退取到的实例上已有真实 index)。
359
- - 设置 **`bitable.xnb_show_sheetbar`**、**`xnb_height`**(常按 `window.innerHeight` 比例)、**`config`**(如 `columnlen` / `rowlen`)等;可按业务加 **`frozen`**。
360
-
361
- ### 8.4 `createEmptyBitable(options, data_columns)`(推荐签名)
362
-
363
- 与多表业务脚本一致:**第二个参数传入当前表用到的列数组**(如 `dataColumns` / `outDataColumns`),避免写死全局变量。
364
-
365
- - **已存在子 Cell**(`await xnbBitable.getSubCell()` 为真):`getSubBitable(true)` → 清空 **`items`、`data_columns`** → **`addColumns(bitable, data_columns, [])`** → **`handleBitable(bitable, options)`** → **`updateSubBitable([bitable], { isMerge, name: sheetName })`** → 按需 **`deleteSubSheets`**。
366
- - **尚未创建**:`const bitable = { items: [], data_columns: [] }` → **`handleBitable(bitable, options)`** → **`addColumns(bitable, data_columns, [])`** → **`createSubBitable([bitable], sheetName)`**。
367
-
368
- 注意两分支中 **`handleBitable` 与 `addColumns` 的先后顺序** 可与线上已有 Cell 保持一致;上表为常见一种。
369
-
370
- ### 8.4.1 Book 已占位但「有格子无列头 / 有列头无格内字」:列 `field` 与 `items.fields` 须一致
371
-
372
- - **有工具栏、网格空、无业务列头**:常为 **子 Cell 已存在** 但 **`data_columns` 仍为空** 或 **从未成功 `addColumns` + `updateSubBitable`**;按 **8.4** 第一分支或 **`createSubBitable`** 第二分支补全。
373
- - **有列头、接口已拉取、单元格仍全空**:多为 **Book 预置或历史 `data_columns` 的 `field` 字符串** 与脚本里 **`items[].fields` 的键** 不一致(含中英文、空格)。此时 **仅 `items = mappedRows` 不够**,须先 **`bitable.items = []`、清空 `bitable.data_columns`**,再 **`addColumns(bitable, 当前 dataColumns, [])`** → **`handleBitable`** → **`updateSubBitable(..., { isMerge: false, name })`**,与 **8.4** 已存在子 Cell 分支一致;**刷新接口列表时不要每次整表 `isMerge:false` 除非列结构变更**——仅在 **检测到列 `field` 与脚本列定义逐列不一致**时重建列。
374
- - **可复用**:**`examples/bitable-list-lifecycle.js`** 中的 **`dataColumnsMismatch`**、**`rebuildSubBitableColumns`**。
375
-
376
- ### 8.5 数据行(`items`)
377
-
378
- - 行数据写在 **`bitable.items`**:元素形如 **`{ id: '...', fields: { [列 field 名]: 值 } }`**,`fields` 的键必须与 **`dataColumns[].field` 字符串完全一致**(含空格、大小写)。
379
- - 接口返回 **`snake_case`** 或嵌套对象时,**必须先映射**到 `fields`,不能直接 `fields: apiRow`,否则列有表头、**单元格全空**。
380
- - 每次拉取列表后:先 **`resolveBitableBySub(subBitables[k])`**(或等价:`getAllSubBitable` → `find` → 单表回退,见 **8.6**)→ 写回行:见 **8.5.2** 区分 **全量 list(整表 `items = mappedRows`)** 与 **增量/询价式(按 `id` 合并 `fields` + 新行 `push`)** → **`updateSubBitable([bitable], { isMerge: true, name: subTableName })`**;`name` 与 **8.1** 中 `createSubBitable` 所用一致。单主表且仅一块子表时也可用 **`getSubBitable(true)`**,但仍须保证 **`fields` 键与列 `field` 一致**。
381
- - **提示语**:与 **7.3** 一致,**在 `updateSubBitable` 完成后再 `notifySuccess`**,避免表格尚未提交渲染就弹出「已 N 条」。
382
-
383
- 实现见 **`examples/bitable-list-lifecycle.js`**。
384
-
385
- ### 8.5.1 刷新列表:勿与「初次建表」绑死
386
-
387
- - **流程**:**`resolve` → `fetch` → 写 `items`(策略见 **8.5.2**)→ `updateSubBitable({ isMerge:true, name })`**。勿每次刷新 **`createEmptyBitable`**;仅 **无子 Cell / `dataColumnsMismatch`** 时重建列。
388
- - **完整实现**:**`examples/bitable-list-lifecycle.js`**(`refreshTable`、`applyRowsToBitable`、`ensureBitableStructure`)。
389
-
390
- ### 8.5.2 全量 `list` 与按 `id` 合并:必选其一
391
-
392
- **`examples/bitable-list-lifecycle.js`** 里 **`applyRowsToBitable`** 的典型行为是:
393
-
394
- - **无可见行**:**`bitable.items = mapped`**(首载 / 空表后整表写入)。
395
- - **已有可见行**:对 **`mapped`** 建 **`Map(id → row)`**,对**现有** `bitable.items` **只合并同 `id` 的 `fields`**,再把 **`mapped` 里尚未出现的 `id`** **`push`** 进去。
396
-
397
- 该「合并」路径**不会删除**响应里已不存在的旧行。因此当 **`GET …/list`(或团队约定的全量查询)代表服务端当前全集**时,刷新逻辑**必须**在拉数后执行 **`bitable.items = mapped`**(或等价:先清空再赋新数组),再 **`await updateSubBitable(...)`**。否则:
398
-
399
- 1. 后台删光或筛掉部分记录后,表格仍显示**已删行**;
400
- 2. 极端情况:接口返回 **`[]`** 时 **`mapped` 为空**,合并分支**不改任何现有 `item`**,用户会看到 **toast「已刷新,共 0 条」但网格仍是旧数据**。
401
-
402
- **何时保留合并**:产品明确要求**增量补丁**、**询价式只更新变动行**、或需**保留本地未同步行**且与接口语义一致时,再在脚本注释中写清策略;若仍用合并,须**额外**处理「服务端已删除的 `id`」在客户端如何移除。
403
-
404
- **生成代码自检**:「若下一帧接口返回**更少行**或**空数组**,当前写回是否会从 `bitable.items` **去掉**多余行?」若否且 list 为全量语义,改为整表替换。
405
-
406
- ### 8.6 多子表与 `getAllSubBitable`:按 `index` 取表再写 `items`
407
-
408
- 与现网「查询表 / 编辑表 / 备注表」等多块子区域一致时,常用:
409
-
410
- 1. **配置数组**:`subBitables = [{ index: '…', name: '…', isMain?: true }, …]`。**`name`(`updateSubBitable` 最后一参)必须与 Book 一致**。**`index` 必须从 Book 编辑器配置抄写**;禁止臆造占位字符串(否则 `find` 永远失败)。
411
- 2. **取全部内存实例**:`const all = await xnbBitable.getAllSubBitable(true)`。
412
- 3. **定位子表(推荐封装)**:`all.find((b) => b.index === sub.index)`;若未找到且 **`all.length === 1`**,可回退 **`all[0]`**(仅「Book 只有一块子表」的迁移/演示);若仍失败且 **`getSubCell()`** 为真,再试 **`getSubBitable(true)`** 且校验 **`bitable.index === sub.index`**(或单主表场景下接受主表实例)。**多子表时禁止依赖回退**,否则可能写到错误的表。
413
- 4. **首次建表**:仅当 **`getSubCell()`** 为假(或确认不存在目标子表)时 **`createSubBitable` + `addColumns`**(见 **8.4**)。**已存在子 Cell 时** `find` 失败 **不要**再 `createSubBitable`,应先修正 **index/name**。
414
- 5. **写行**:`one.items = mappedRows`(元素 **`{ id, fields }`**);**`await xnbBitable.updateSubBitable([one], { isMerge: true 或 false, name: sub.name })`**。**每次刷新列表**只做本步 + 第 2~3 步,**不要**重复 `createSubBitable`。
415
- 6. **切换当前展示子表**:可对目标表 **再调用一次** `updateSubBitable`(参数以线上为准)。
416
- 7. **列样式**:`dataColumns` 中 **`headerColor`、`xnb_select`、`xnb_time`、`number`+`ct`** 等;**勿照抄业务列名**。
417
-
418
- 解析与刷新:**`examples/bitable-list-lifecycle.js`**。
419
-
420
- ---
421
-
422
- ## 9. 表单录入(Materialize 要点)
423
-
424
- 见 **`examples/form-controls-materialize.js`**、**`examples/filter-toolbar-line.js`**。要点:**`id` 一律 `${cellUid}-...`**;**`input-field` 尽量只用于 `text` / `textarea`**;**`date` / `datetime-local` 用 `document.getElementById` 读值**。
425
-
426
- ### 9.1 仅日期(`type="date"`)
427
-
428
- - **`value` 与 `input.value`** 均为 **`YYYY-MM-DD`**。推荐与线上一致写三个小函数:**`getDateString(date)`** → **`getTodayDate()`**、**`getSixMonthsAgoDate()`**(`setMonth(getMonth()-6)`)等,在 **`return \`...\``** 里写 **`value="${getTodayDate()}"`**,避免手拼错格式。
429
- - **日期区间**:两个 **`<input type="date">`**,中间文案 **「至」**;样式可与其它筛选控件统一(如 `padding:8px 12px;border:1px solid #ddd;border-radius:4px;min-width:150px;font-size:14px;line-height:1.5;box-sizing:border-box;`)。
430
- - **读取**(在 `register` 的方法内):**`document.getElementById('…')?.value || ''`**;起、止各读一次再拼查询参数。`type="date"` 常用此读法;其它控件可再用 **`getInstanceValueById`**。
431
- - **`id`**:同页多 JsCode 实例时用 **`${cellUid}-entry-time-start`** 等与 **`getElementById`** 同一字符串;若整页仅单实例,也可使用固定 id(读写必须同名)。
432
-
433
- 见 **`examples/filter-date-native.js`**(含起止默认值与读区间示例)。
434
-
435
- ### 9.1.1 表单里的 `<select>`:线框 + 与筛选区一致的横向排布
436
-
437
- - **线框**:与 **`input[type=date]`** 一致,例如 **`padding:8px 12px;border:1px solid #ddd;border-radius:4px;min-width:180px;font-size:14px;line-height:1.5;background-color:#fff;box-sizing:border-box;`**(与现网 **Project code** 行同款即可)。
438
- - **默认不加 `browser-default`**,与线框 `date` / `input` 视觉统一;**Materialize 双影**(上方一行字 + 下方又一个箭头)时再对该 **`<select>`** 试加 **`class="browser-default"`**,并采用 **`display:flex; align-items:center; gap:8px`**:**左侧 `label`(可加 `font-weight:bold`、`min-width`)+ 右侧 `select`(`flex:1; min-width:0`)**(见 **`examples/form-controls-materialize.js`**)。
439
- - **表格内枚举列**:优先 **`dataColumns` 的 `xnb_select`**;表单筛选区才用 HTML `<select>`。
440
-
441
- ### 9.2 `datetime-local` 与 Materialize「浮动 label」冲突
442
-
443
- - **`input-field` + `label` 浮动** 与 **`type="datetime-local"`** 叠在一起时,易出现 **label 与值重叠**。做法:**不要用 `input-field` 包 datetime**;改为 **`label` 普通块级(`display:block;margin-bottom:6px;font-size:12px;color:rgba(0,0,0,0.6)`)+ `input` 自带边框**(见 **`examples/form-controls-materialize.js`**),**`label` 默认与其它字段一样左对齐**;除非设计明确要求,**不要**为时间单独做整列 `flex` 居中导致与 Barcode 等列标题错位。
444
- - **框内文字垂直居中**:Chromium 系下 **`datetime-local`** 易出现日期时间偏下留白;可对 **`#${cellUid}-…` 作用域** 内增加 **`height:40px; line-height:40px; padding:0 12px; box-sizing:border-box`**,并配合 **`::-webkit-datetime-edit-fields-wrapper { padding:0 }`**、**`::-webkit-datetime-edit { height:100%; margin:0; vertical-align:middle }`**(见示例内嵌 `<style>`);Firefox 以 **`height` + 对称 `padding`** 为主即可。
445
- - **读值**:**`datetime-local` 与 `type="date"` 优先 `document.getElementById(id).value`**;`xnbContext.getInstanceValueById` 对部分原生控件可能始终为空,导致提交 JSON 里时间字段为 **`""`**。
446
-
447
- ### 9.3 按钮与语义色(Materialize)
448
-
449
- - 同类操作里用 **不同颜色** 表达动作含义,避免全部默认灰/蓝:**`green`**(查询/提交/确认)、**`blue`**(导入/次要正向)、**`orange`**(警告类)、**`red`**(清除/危险)等;**不要**给主业务按钮加 **`grey`** 除非明确禁用态。
450
-
451
- ### 9.4 提交 JSON 与字段类型
452
-
453
- - 表单读入多为 **字符串**;提交前按接口约定 **转换**:整数 **`parseInt`**、小数 **`Number`**、空串 **`undefined` 并 `delete` 键**;**`datetime-local`** 可转 **`new Date(value).toISOString()`**(或团队约定的时区格式),**勿把未解析的空串提交为 `""` 当无值**。
1
+ # Orbit JsCode 运行时 API 参考(Vue Cell 对照)
2
+
3
+ > **Vue 组件(`<script setup>` + `props.xnbContext`)**
4
+ > - HTTP:使用本仓库 **`src/utils/orbitHttpClient.ts`** → **`orbitRequestJson`**;宿主内优先 **`window.xnb.http.client.request`**(`ResponseType.JSON`、`Body.json`,与 Book 代理及鉴权一致),无注入时回退 **`fetch`**。
5
+ > - Token:优先 **`await window.xnb.choreo.getUserToken()`**(无参;与本仓库工单请求一致);本地直连 AgenticLab 见 **`AgenticAppAPI.md`** 与 `.env.local`。JsCode 场景若环境仍要求传入 **`bookPath`**,见下文 **§6.2** 原文。
6
+ > - 超级表格行数据:本模板 **`src/use/useSuperTableBitableLifecycle.ts`**;列与 `items` 约定见 **§8**(尤其 **§8.5.2**)。
7
+ > - **§7 / §9**(Book `Sheet` / Materialize 表单)在 Vue 中多由 SFC + Quasar 实现,JsCode 原文仍保留供对照。
8
+ >
9
+ > 原文档路径:**`orbit-skills/orbit-write-js-cell/references/API.md`**(本仓库为完整拷贝)。
10
+
11
+ 下文整理自多团队线上 JsCode 实践,并与 Orbit / XNB 在 **Cell、Book** 侧的常见产品约定对照。**以实际运行时注入为准**:不同 Orbit 版本可能增减字段,编写前可在同环境已有 JsCode 中对照。
12
+
13
+ ---
14
+
15
+ ## 1. 注入:`xnbContext`
16
+
17
+ 脚本**无需**自己声明 `xnbContext`,由运行时注入。常见用法是在文件顶部解构:
18
+
19
+ ```js
20
+ const cellUid = xnbContext.cellUid
21
+ const bookPath = xnbContext.bookPath
22
+ const notifySuccess = xnbContext.notifySuccess
23
+ const notifyError = xnbContext.notifyError
24
+ const loadingShow = xnbContext.loadingShow
25
+ const loadingHide = xnbContext.loadingHide
26
+ ```
27
+
28
+ ### 1.1 高频成员(场景中出现次数多)
29
+
30
+ | 成员 | 用途 |
31
+ |------|------|
32
+ | `cellUid` | 当前 JsCode Cell 的 uid;事件名、`cell.register`、DOM id 前缀 |
33
+ | `bookPath` | 当前 Book 路径;传给 `window.xnb.book.*`、`ChoreoTool`、`XNBBitable` 等 |
34
+ | `notifySuccess` / `notifyError` | 轻量提示 |
35
+ | `loadingShow` / `loadingHide` | 全局 loading |
36
+ | `getUserData` / (若存在)`setUserData` | 读写 Cell 用户数据(持久化字段由 Book/Cell 配置决定) |
37
+ | `getInstanceValueById` / `setInstanceValueById` | 按 DOM 控件 id 读写实例值(与渲染 HTML 中的 `id` 对应) |
38
+ | `stepDone(stepIndex, ...)` | 多步向导中标记某步完成(参数以环境为准) |
39
+ | `reconfirm` | 二次确认对话框(若环境提供) |
40
+
41
+ ### 1.2 类/工具构造函数(按业务选用)
42
+
43
+ 从 `xnbContext` 取出构造函数后 **自己 `new`**,例如:
44
+
45
+ - `XNBBitable`:`new XNBBitable({ bookPath, cellUid })`
46
+ - `ChoreoTool`:`new ChoreoTool({ bookPath, cellUid, feishuTable: { feishuUrl, tableNames } })`
47
+ - `XNBUtil`、`G2Service` 等:按业务需要解构
48
+
49
+ ### 1.3 常见错误对照(代码无法运行时的首要排查)
50
+
51
+ | 错误(易由通用 LLM 臆造) | 正确 |
52
+ |---------------------------|------|
53
+ | 使用 `cellUid` / `bookPath` 但未从 **`xnbContext`** 解构 | 文件**最顶部**:`const cellUid = xnbContext.cellUid` 等 |
54
+ | `window.xnb.getUserData()` | **`xnbContext.getUserData()`** |
55
+ | `window.xnb.getUserToken()` | **`await window.xnb.choreo.getUserToken(bookPath)`**(需已有 `bookPath`) |
56
+ | `window.xnb.cell.loadingShow` / `notify` / `notifyError` | **`xnbContext.loadingShow` / `notifySuccess` / `notifyError`**(名称以注入为准) |
57
+ | `const { cell } = window.xnb` 再 `cell.register({ ... })` | **`window.xnb.cell.register(cellUid, '方法名字符串', async () => {})`**,每个方法单独注册 |
58
+ | `document.body.appendChild`、`document.head`、`DOMContentLoaded` 驱动主界面 | 主 UI 用脚本**末尾 `return \`...\``** 返回 HTML;交互用 **`onclick="window.xnb.cell.run('${cellUid}', '方法名')"`** |
59
+ | `return \`...\`` 里嵌 **`<script>`** 再 `cell.run` | 优先 **按钮 onclick**;内联 script 在 Cell 沙箱里常**不执行或不可靠** |
60
+ | 裸 **`fetch`** 调业务域 | 优先 **`window.xnb.http.client.request`** + `ResponseType` / `Body.json`(与现有 Book 代理、鉴权一致) |
61
+ | `const { book, cell, http } = window.xnb` 当解构入口 | **`window.xnb.book` / `window.xnb.cell` / `window.xnb.http`** 分命名空间访问;`http` 一般为 **`window.xnb.http.client`** |
62
+ | 已有 **子 Sheet / XNBBitable**,再在 `return` 里放 **整页 `<table>`** 用 `innerHTML` 刷接口数据 | **列表只写一处**:`items`+`updateSubBitable` **或** `celldata`+`updateCellData`;`return` 只放工具栏/筛选/弹窗(见 **7.2**、**8.5**) |
63
+ | **内嵌 XNBBitable** 已显示,却用 **`createCell('Sheet')` + `celldata`** 当主列表 | 主列表走 **`resolveBitableBySub` + `items` + `updateSubBitable`**(见 **7.0**、**8.5–8.6**、`examples/bitable-list-lifecycle.js`) |
64
+ | **有列头、items 已赋值,格内仍全空** | **`dataColumns[].field` 与 `fields` 键不一致**;按 **8.4.1** **`rebuildSubBitableColumns`** 对齐列后再写 `items` |
65
+ | 每次点「刷新」都跑 **`createSubBitable` / 清空列** | **§8.5.1**:**`resolve` → 拉数 → 写 `items`(全量 list 须整表替换,见 **§8.5.2**)→ `updateSubBitable({ isMerge:true })`**;仅首次或 **`dataColumnsMismatch`** 时重建列 |
66
+ | **全量 list** 仍用「有可见行则只按 id 合并、从不删行」 | 若接口返回**当前全集**,每次刷新应 **`bitable.items = mappedRows`**;否则删空或变少时界面仍留旧行,**`[]`** 时常见 **toast「0 条」表格不变**(**§8.5.2**) |
67
+ | 接口行直接赋给 **`items`**,`fields` 键与 **`dataColumns[].field`** 不一致 | **先映射**再 `updateSubBitable`,否则有表头无格内文字 |
68
+ | Sheet **`celldata`** 只有 **`v.v`** 无 **`m`/`ct`** | 使用 **`{ v, m, ct: { fa: 'General', t: 'g' } }`**,否则网格常空白(见 **7.1**) |
69
+ | **`datetime-local`** 仍用 **`input-field`** + **`getInstanceValueById` 读值** | 块级 **`label` + 边框 `input`**;读 **`document.getElementById`**(见 **9.2**);**仅日期**优先 **`type="date"`** 且模板写 **`value="YYYY-MM-DD"`**(见 **9.1**) |
70
+ | **`<select>` 与周围控件风格脱节或双影** | **与 `date` 同款线框 + label/select 横向 flex**;双影时再试 **`browser-default`**(见 **9.1.1**) |
71
+ | 每次刷新都 **`createCell` / `refresh-book`** 再提示成功 | **缓存子表 uid**;刷新只 **`updateCellData` + `reload`**;**toast 在写回与短延迟之后**(见 **7.3**) |
72
+ | NocoBase **`POST …/api/{collection}:create`** 使用 **`Body.json({ values: payload })`**(误套 `values`) | **官方 `:create` 体为字段扁平 JSON**:**`Body.json(payload)`**;误包一层会导致服务端读不到字段、**落库空记录**(见 **§6.3.1**) |
73
+
74
+ 部分线上 Cell 使用 **`return () => ({ html: () => \`...\` })`** 工厂形式渲染;**同一 Book 内请与已有 JsCode 保持一致**,勿在不确定运行时版本时混用两种 `return` 形态。
75
+
76
+ ---
77
+
78
+ ## 2. 全局:`window.xnb`
79
+
80
+ ### 2.1 `window.xnb.book`
81
+
82
+ | 方法 | 说明 |
83
+ |------|------|
84
+ | `getBook(bookPath)` | 返回含 `metadata` 等;`metadata.cells` 列出 Cell |
85
+ | `getBookMeta(bookPath)` | 仅元数据场景可用(部分脚本使用) |
86
+ | `getCell(bookPath, cellUid)` | 读取某 Cell 的 `data`(如 Sheet 的 `input`/`output`) |
87
+ | **`getUserConfig(bookPath)`** | 异步读取 Book **`userConfig`**(与 `xnb_metadata.json` 中配置对应)。JsCode 常用键:**`hide_project_config`**、**`hide_table_config`**、**`hide_params_config`**(是否隐藏各配置面板)。见 **`examples/book-user-config.js`** |
88
+ | `createCell(bookPath, type, options)` | 创建 Cell;如 `type === 'Sheet'` 且带 `parentCellUid` 创建子表 |
89
+ | `updateCellData(...)` | 更新 Cell 数据(一般由上层或 `save` 封装调用) |
90
+
91
+ ### 2.2 `window.xnb.cell`
92
+
93
+ | 方法 | 说明 |
94
+ |------|------|
95
+ | `register(cellUid, methodName, fn)` | 将异步/同步函数暴露给渲染 HTML |
96
+ | `run(cellUid, methodName, ...args)` | 在 HTML 字符串的 `onclick` 中调用 |
97
+
98
+ ### 2.3 `window.xnb.event`
99
+
100
+ 跨 Cell 或通知视图层的通道。
101
+
102
+ **常见模式(请求-响应)**:`emit` 传入回调,由监听方调用 `resolve`/`reject`:
103
+
104
+ ```js
105
+ await new Promise((resolve, reject) => {
106
+ window.xnb.event.emit(`${otherCellUid}::get-sheet`, resolve, reject, nodata)
107
+ })
108
+ ```
109
+
110
+ **运行/编辑状态**(与 Book 层协同;`running` / `status` 等语义见第 4 节):
111
+
112
+ ```js
113
+ window.xnb.event.emit(`${cellUid}::running`, 'start') // 'stop' | 'error'
114
+ window.xnb.event.emit(`${cellUid}::edited`, true)
115
+ ```
116
+
117
+ **视图**(监听 Cell 视图变化):
118
+
119
+ ```js
120
+ window.xnb.event.on(`${cellUid}::cell-view`, handler)
121
+ ```
122
+
123
+ ### 2.4 `window.xnb.choreo`
124
+
125
+ 例如:`getDbInfo(bookPath)`、**`getUserToken(bookPath)`**(与当前 Book 会话绑定的 Bearer Token,常用于业务 HTTP)、`delay(ms)` 等(以当前环境为准)。详见下文 **6.2 节**。
126
+
127
+ ### 2.5 `window.xnb.http`
128
+
129
+ ```js
130
+ const { client, Body, ResponseType } = window.xnb.http
131
+ // client.request({ method, url, body: Body.json({...}) })
132
+ ```
133
+
134
+ ### 2.6 `window.xnb.file` / `window.xnb.store`
135
+
136
+ - `file.joinPath`、`readJson` 等:读写工作区文件(权限受环境限制)。
137
+ - `store.get(key)` / `store.set`:键值存储(如布局 `layout-store`,与 Book 布局功能配套)。
138
+
139
+ ### 2.7 `window.xnb.utils`
140
+
141
+ 例如 `window.xnb.utils.uuid.v4()`、`window.xnb.utils.dayjs`(若环境挂载)。
142
+
143
+ ---
144
+
145
+ ## 3. Book / 布局层事件(摘要)
146
+
147
+ 以下为 Orbit **Book 菜单**与 **GoldenLayout 布局** 侧常见事件(产品实现可能迭代,以当前前端为准)。**完整菜单事件名**(如 `save`、`refresh-book`、`lock-book`、`copy-id` 等)以你环境 **Orbit 前端或产品文档**为准;下表仅列 JsCode 常用子集。
148
+
149
+ - 菜单类:部分文档为 `window.xnb.event.emit('xnb-book-menu', /* 事件名 */)`(如 `'change-book-view-type'`、`'reset-tabs-view'`);另有 `window.webb.event.emit('xnb-book-menu', BookEvents)` 写法,以当前 Orbit 前端版本为准。
150
+ - 聚焦 Cell:`window.xnb.event.emit('xnb-focus-cell', { bookPath, cellUid })`。
151
+ - Tabs 布局:**`layout-tabs-add-cell`**(载荷 `{ type, uid }`;在指定 Tab 组内新增时可传第三参 **`{ currentUid }`**)、**`${uid}::layout-tabs-focus`**、**`${uid}::layout-tabs-remove`**。布局模式:**`window.xnb.store.get('layout-store')?.layoutType`** 常见为 **`'tabs'`**(二分)或 **`'default'`**。
152
+ - 刷新 Book:`window.xnb.event.emit('refresh-book', { bookPath })`(场景中有使用)。
153
+
154
+ ---
155
+
156
+ ## 4. XNB Cell 通用模型(约定摘要)
157
+
158
+ - **permission**:`writable` / `readable` / `executable`,由外层传入,Cell 内实现交互限制。
159
+ - **status**:`running` / `focus` / `error`;运行态由 **`${cellUid}::running`** 与 Book 层协同;**`error` 后需再发 `stop`** 以便外层清错误态。
160
+ - **mode**:`Normal` | `Render`(编辑 vs 渲染)。
161
+ - **view**:`Normal` | `Fullscreen` | **`Maximum`** | `Abstract` | `Empty` 等,由 Book 控制、Cell 内响应;监听 **`window.xnb.event.on(\`${cellUid}::cell-view\`, handler)`** 时以实际枚举为准。
162
+ - **CellParams.isCellWindow**:是否独立窗口。
163
+ - **焦点**:若 Cell 内 **`mousedown` 被拦截导致无法冒泡**,可 **`window.xnb.event.emit(\`${cellUid}::mousedown\`)`** 通知外层更新高亮。
164
+
165
+ JsCode 侧通常通过 **事件** 与 **`window.xnb.cell.run`** 驱动逻辑。若 Cell 实例暴露 **`save(isNotify?, clearUserData?)`**、**`setPermission`**、**`setStatus`**、**`setView`** 等,**参数与语义以你方 Cell 封装或 Orbit 文档为准**。
166
+
167
+ ---
168
+
169
+ ## 5. 编写习惯(精简)
170
+
171
+ 1. 顶部解构 `cellUid`、`bookPath`、通知与 loading;**先对照第 1.3 节**。
172
+ 2. 用户操作 → **`register(cellUid, name, fn)` + `onclick` 里 `run(cellUid, name)`**;表格类数据优先 **XNBBitable / ChoreoTool**(见示例),勿用 `document.body` 拼整页 DOM。
173
+ 3. 含表单时:第 9 节 + `examples/form-controls-materialize.js`。
174
+
175
+ ---
176
+
177
+ ## 6. 用户数据、Choreo Token 与 HTTP(常见生产模式)
178
+
179
+ ### 6.1 获取配置:`getUserData` + `projectURL`
180
+
181
+ Cell 的 **用户数据**(在 Book/Cell 配置里由运营或实验员填写)通过 `xnbContext.getUserData()` 读取。常见做法是把 **JSON 字符串** 放在 `projectURL`(命名历史原因,内容不一定是 URL)里,解析后得到后端基址、多环境路由等:
182
+
183
+ ```js
184
+ const userData = xnbContext.getUserData()
185
+ const { projectURL } = userData
186
+ let {
187
+ request_url = 'http://127.0.0.1:13000'
188
+ } = JSON.parse(projectURL || '{}')
189
+ ```
190
+
191
+ - **`JSON.parse(projectURL || '{}')`**:避免空值抛错;缺省字段用解构默认值。
192
+ - **约定**:与业务方书面固定 JSON 字段名(如 `request_url`),并在技能/交付说明中写清示例 JSON,避免现场填错导致静默连错环境。
193
+
194
+ ### 6.2 获取用户 Token:`window.xnb.choreo.getUserToken`
195
+
196
+ 与 Orbit/Choreo 登录态绑定的 Token,用于请求受保护的业务 API:
197
+
198
+ ```js
199
+ const getUserToken = async () => {
200
+ try {
201
+ return await window.xnb.choreo.getUserToken(bookPath)
202
+ } catch (error) {
203
+ console.error('获取用户 Token 失败:', error)
204
+ throw error // 或按策略 return null / 仅开发环境备用(勿把长期 Token 提交仓库)
205
+ }
206
+ }
207
+ ```
208
+
209
+ - **必须传入 `bookPath`**,与当前 Book 一致。
210
+ - 捕获失败时是否在开发机回退到固定 Token:**仅作本地调试**,生产应提示用户重新登录或检查 Choreo。
211
+
212
+ ### 6.3 调用 HTTP:`window.xnb.http.client.request`
213
+
214
+ 典型解构:
215
+
216
+ ```js
217
+ const { client, Body, ResponseType } = window.xnb.http
218
+ ```
219
+
220
+ GET 列表示例(Bearer + `query` + JSON 响应类型):
221
+
222
+ ```js
223
+ const token = await getUserToken()
224
+ const res = await client.request({
225
+ method: 'get',
226
+ url: `${request_url}/api/your_resource:list`,
227
+ headers: {
228
+ 'Content-Type': 'application/json; charset=utf-8',
229
+ Authorization: `Bearer ${token}`
230
+ },
231
+ responseType: ResponseType.JSON,
232
+ query: { pageSize: '99999' }
233
+ })
234
+ // 业务体位置因网关而异,常见为 res?.data?.data
235
+ ```
236
+
237
+ POST 时常用 `body: Body.json({ ... })`。具体 `client.request` 的完整参数以当前 `window.xnb.http` 实现为准。
238
+
239
+ ### 6.3.1 NocoBase:`{collection}:create` / `:update` 与 `values` 包裹(易踩坑)
240
+
241
+ NocoBase **资源风格**接口(常见于 **`POST ${request_url}/api/your_collection:create`**,与 **`…:list`** 同源)在官方文档与 API 文档插件中的约定是:**请求体 JSON 的根级即业务字段**,与集合字段名(如 `title`、`barcode`)一一对应,**不要**再包一层 `values` / `data`(除非你们网关或另一套 REST 明确另有约定)。
242
+
243
+ ```js
244
+ // ✅ 正确:扁平字段(与 NocoBase API 文档示例一致)
245
+ await client.request({
246
+ method: 'post',
247
+ url: `${request_url}/api/compound:create`,
248
+ headers: {
249
+ 'Content-Type': 'application/json; charset=utf-8',
250
+ Authorization: `Bearer ${token}`
251
+ },
252
+ responseType: ResponseType.JSON,
253
+ body: Body.json({ barcode: 'B001', compound_id: 'C-01', status: 'Available' })
254
+ })
255
+
256
+ // ❌ 常见误生成:多包一层 values → 服务端不读嵌套键,易出现「创建成功但记录全空」
257
+ // body: Body.json({ values: { barcode: 'B001', ... } })
258
+ ```
259
+
260
+ - **`:update`** 同理:更新字段一般也在 **根级** 与 **`filterByTk`(或团队约定的 filter query)** 配合使用;仍以环境内 **API 文档插件**为准。
261
+ - **另一套路由**(如 **`POST /api/collections/{name}/records`**)可能要求 **`{ values: { … } }`** 或其它形状;**路径不同则体不同**,生成代码前用 **第 0 步** 与业务方对齐 **URL + 示例 JSON**,勿把「collections 记录」的 `values` 习惯套到 **`…:create`** 上。
262
+
263
+ ### 6.4 `try/catch`、`loadingShow`/`loadingHide` 与 `notify*`
264
+
265
+ 长任务或可能失败的操作:**先 `loadingShow()`,在 `finally` 里 `loadingHide()`**,避免异常路径漏关 loading:
266
+
267
+ ```js
268
+ try {
269
+ loadingShow()
270
+ await createEmptyBitable()
271
+ notifySuccess('表格已就绪')
272
+ } catch (error) {
273
+ notifyError(error, '', '创建表格失败')
274
+ throw error
275
+ } finally {
276
+ loadingHide()
277
+ }
278
+ ```
279
+
280
+ - **`notifySuccess(message)`**:成功轻提示。
281
+ - **`notifyError(error, detail, title)`**(参数个数与语义以运行环境为准):第三个参数常用于 **简短标题**,便于用户区分失败场景。
282
+ - 是否在 `catch` 里 **`throw error`**:若希望上层或 `cell.run` 统一感知失败,可继续抛出;若仅提示即可则不必再抛。
283
+
284
+ ---
285
+
286
+ ## 7. 创建 **Book 级** Sheet Cell(用户意图:「创建 Sheet Cell」「新建表格 Cell」等)
287
+
288
+ 当用户明确要求在 Book 里新增一个 **类型为 `Sheet` 的独立 Cell**(出现在 Book 元数据的 `cells` 列表中)时,使用 **`window.xnb.book.createCell`**,而不是仅用 `XNBBitable` 在 JsCode 内画图(见第 8 节)。
289
+
290
+ 典型参数:
291
+
292
+ - `type`:`'Sheet'`
293
+ - `data`:`{ input: [{ celldata }], output: null }`(可从已有 Sheet `getCell` 拷贝结构再裁剪)
294
+ - `parentCellUid`:可选;挂在当前 JsCode 下时用当前 `cellUid`
295
+
296
+ 创建成功后通常 **`window.xnb.event.emit('refresh-book', { bookPath })`** 或向目标 Sheet 的 uid **`emit(\`${sheetUid}::reload\`)`** 刷新视图。完整流程见 **`examples/create-sheet.js`**。
297
+
298
+ **与第 8 节区分**:第 8 节是 **JsCode 内 XNBBitable**(列头来自 `dataColumns`,行来自 `items`);本节是 **Book 里独立的 Sheet Cell**(格点来自 `celldata`)。需要「与业务脚本相同的列类型/下拉/着色列头」时,**优先第 8 节**,不要假设 `celldata` 能等价替代。
299
+
300
+ ### 7.0 先判「用户看到的是哪张表」(避免整表空白)
301
+
302
+ - **典型现象**:Cell 下方已是 **带格式工具栏、列字母 A/B/… 的表格区域**(即编辑器里为 JsCode 配置的 **XNBBitable / 子表**),但 **第 1 行不出现业务列头**、刷新后仍空;脚本却在 **`book.createCell(..., 'Sheet')` + `updateCellData(celldata)`** 上写数。
303
+ - **原因**:**Book 元数据里的独立 `Sheet` Cell** 与 **当前 Cell 内嵌的 XNBBitable** 是 **两套存储**;内嵌表的列头与行数据只认 **`dataColumns` + `items` + `updateSubBitable`**,**不会**自动读取你为「子 Sheet Cell」写入的 `celldata`。
304
+ - **正确做法**:主列表若画在内嵌 Bitable 上,用 **`getAllSubBitable` / `getSubBitable` → 解析到目标 `bitable` → 赋 `items` → `await updateSubBitable`**(见 **8.5、8.6** 与 **`examples/bitable-list-lifecycle.js`**)。只有需求明确为「在 Book 里再挂一个 **独立 Sheet Cell**」时才用 **本节 `createCell`**。
305
+ - **首屏无列头**:若 **`!(await xnbBitable.getSubCell())`**,须先 **`createSubBitable` + `addColumns`**(与现网 **`if (!await xnbBitable.getSubCell()) await createEmptyBitable()`** 同类);若已有子 Cell 但 **`data_columns` 为空或与脚本不一致**,走 **`rebuildSubBitableColumns` / `dataColumnsMismatch`** 再 **`updateSubBitable`**,然后再拉接口写 **`items`**。
306
+
307
+ ### 7.1 子 Sheet 已创建但网格无列头 / 无数据行(`celldata` 路径)
308
+
309
+ **`Cell.data.input` / `output_bitable`(Sheet Cell)与 XNBBitable**:独立 **Sheet** Cell 的 **`data.input`** 为 Luckysheet 系 **`celldata`**;**`output_bitable`** 为引擎/导出侧的多维表结构,**与** JsCode 里 **`getSubBitable(true)` 得到的内存 `items`/`data_columns` 不是同一套对象**。格点 **`v`** 内建议同时带 **`m`**、**`ct`**(见下)。**`celldata` / `output_bitable` 字段级类型**以同 Book 内 **`getCell` 样例**或 **Luckysheet 初始化文档**为准。
310
+
311
+ 常见原因:
312
+
313
+ 1. **`updateCellData` / `updateCell` 不存在或签名不同**:运行时若无对应 API,写入不会生效;应用 **`getCell`** 对照同 Book 内**已能正常显示**的 Sheet 的 `data.input[0].celldata` 结构再生成。
314
+ 2. **`celldata` 与引擎约定不一致**:每个格点建议 **`{ r, c, v: { v, m, ct: { fa: 'General', t: 'g' } } }`**(`m` 为展示文本,`ct` 为类型);仅有 **`{ r, c, v: { v } }`** 时 Luckysheet 系内核**常整表空白**。`input[0]` 可带 **`row` / `column` / `status`** 等与 `getCell` 对齐。
315
+ 3. **只 `reload` 未 `refresh-book`**:创建后元数据未稳定时,可先 **`refresh-book`** 再对子 uid **`emit(\`${uid}::reload\`)`**(顺序以实测为准)。
316
+ 4. **需求其实是「可配置子表」**:应改用 **第 8 节 XNBBitable**,而不是继续调 `celldata`。
317
+
318
+ ### 7.2 禁止「双轨列表」:子 Sheet / XNBBitable + 一整张 HTML `<table>`
319
+
320
+ 常见误生成:已创建 **Book 子 Sheet** 或 **XNBBitable**,又在 `return \`...\`` 里放 **`<table><thead>…<tbody id=…>`**,用 `innerHTML` 把接口数据画进 tbody。
321
+
322
+ - **结果**:用户看到 **两套表**(子 Sheet 有表头但格子里没数据 + HTML 表有数据),或误以为数据已进子表。
323
+ - **正确**:**列表数据只写到一个载体**——要么 **`bitable.items` + `updateSubBitable`**(XNBBitable),要么 **`celldata` + `updateCellData`(若存在)+ `reload`**(Book Sheet);`return` 里通常 **只保留工具栏、筛选、弹窗**,**不要**再堆主数据 `<table>`。
324
+
325
+ ### 7.3 刷新时机:缓存子表、避免「先提示后空表」
326
+
327
+ **Book 子 Sheet(`celldata` + `updateCellData`)**
328
+
329
+ - **缓存子 Sheet 的 `uid`**(模块级变量):仅在首次不存在时 **`createCell` + `refresh-book`**;之后每次刷新只做 **`updateCellData` + `emit(\`${uid}::reload\`)`**,**不要**重复创建或反复全量 `refresh-book`。
330
+ - **`notifySuccess('已刷新 N 条')`** 建议放在 **`updateCellData` 与 `reload`(及可选的短 `setTimeout`)之后**,减少「提示已更新但网格 1~2 秒仍空」的错觉。
331
+
332
+ **XNBBitable(`items` + `updateSubBitable`)**
333
+
334
+ - 现网常见:**`getAllSubBitable` → 按 `index` 找到目标表 →(必要时先 `updateSubBitable` 切换当前子表)→ 赋 `items` → 再 `await updateSubBitable`**;**成功提示放在最后一次 `updateSubBitable` 的 `await` 之后**。
335
+ - **不要**在每次拉数时重复 **`createSubBitable`**;刷新 = **改数据 API + 更新内存 `items` + `updateSubBitable`**。
336
+
337
+ ---
338
+
339
+ ## 8. XNBBitable:列头(`dataColumns`)与数据行(`items`)
340
+
341
+ 在 JsCode 的 **子表区域**(编辑器里已为该 Cell 配置子 Sheet / bitable)中,**列标题与列编辑器类型**来自 **`dataColumns` + `addColumns`**。**不要**再用 `return` 里的整页 **`<table>`** 展示同一批业务行(见 **7.2**)。Book 子 Sheet 的 `celldata` **不会**自动出现在 XNBBitable 里。
342
+
343
+ **子表列表实现**:**`examples/bitable-list-lifecycle.js`**。**摘录**:同目录 **`bitable-toolbar-forms.md`**。
344
+
345
+ ### 8.1 必须先对齐 Book 配置(否则只有空壳、无列头)
346
+
347
+ - **`subBitables[i].index`**:必须与编辑器里该子表的 **index 字符串** 完全一致(常见如 `sheet-xxx`)。写错则 **`handleBitable`** 绑错对象,列不渲染。
348
+ - **`createSubBitable` / `updateSubBitable` 的 `name` 参数**:须与 Book 配置一致;常见写法为 **`subBitables[k].name`**,也有项目写死 **`'sheet'`**;**与 `subBitables[0].index` 写错一样会导致表头在但行不刷新或更新无效**。
349
+
350
+ ### 8.2 `dataColumns`(列模型)
351
+
352
+ - 每项至少含 **`field`(列名)**、**`dv`**(如 `text_length`、`number`、`xnb_select`、`xnb_checkbox`、`xnb_time`)、**`readonly` / `visible` / `dbRelated`** 等;`xnb_select` 常用 **`value1`(逗号分隔选项)**、**`value2`(可选颜色)**。
353
+ - 定义完成后必须:**`dataColumns.forEach((dc, i) => { dc.c = i })`**。
354
+ - **`initDataColumns`** 可为同步函数;列依赖接口选项时,先拉选项再赋值再 `forEach`。
355
+
356
+ ### 8.3 `handleBitable(bitable, options)`
357
+
358
+ - **`options`** 通常即 **`subBitables[k]`**(含 `name`、`index`、`row` 等)。**`index` 若为空字符串或未传,勿覆盖 `bitable` 已有 `index`**(单主表回退取到的实例上已有真实 index)。
359
+ - 设置 **`bitable.xnb_show_sheetbar`**、**`xnb_height`**(常按 `window.innerHeight` 比例)、**`config`**(如 `columnlen` / `rowlen`)等;可按业务加 **`frozen`**。
360
+
361
+ ### 8.4 `createEmptyBitable(options, data_columns)`(推荐签名)
362
+
363
+ 与多表业务脚本一致:**第二个参数传入当前表用到的列数组**(如 `dataColumns` / `outDataColumns`),避免写死全局变量。
364
+
365
+ - **已存在子 Cell**(`await xnbBitable.getSubCell()` 为真):`getSubBitable(true)` → 清空 **`items`、`data_columns`** → **`addColumns(bitable, data_columns, [])`** → **`handleBitable(bitable, options)`** → **`updateSubBitable([bitable], { isMerge, name: sheetName })`** → 按需 **`deleteSubSheets`**。
366
+ - **尚未创建**:`const bitable = { items: [], data_columns: [] }` → **`handleBitable(bitable, options)`** → **`addColumns(bitable, data_columns, [])`** → **`createSubBitable([bitable], sheetName)`**。
367
+
368
+ 注意两分支中 **`handleBitable` 与 `addColumns` 的先后顺序** 可与线上已有 Cell 保持一致;上表为常见一种。
369
+
370
+ ### 8.4.1 Book 已占位但「有格子无列头 / 有列头无格内字」:列 `field` 与 `items.fields` 须一致
371
+
372
+ - **有工具栏、网格空、无业务列头**:常为 **子 Cell 已存在** 但 **`data_columns` 仍为空** 或 **从未成功 `addColumns` + `updateSubBitable`**;按 **8.4** 第一分支或 **`createSubBitable`** 第二分支补全。
373
+ - **有列头、接口已拉取、单元格仍全空**:多为 **Book 预置或历史 `data_columns` 的 `field` 字符串** 与脚本里 **`items[].fields` 的键** 不一致(含中英文、空格)。此时 **仅 `items = mappedRows` 不够**,须先 **`bitable.items = []`、清空 `bitable.data_columns`**,再 **`addColumns(bitable, 当前 dataColumns, [])`** → **`handleBitable`** → **`updateSubBitable(..., { isMerge: false, name })`**,与 **8.4** 已存在子 Cell 分支一致;**刷新接口列表时不要每次整表 `isMerge:false` 除非列结构变更**——仅在 **检测到列 `field` 与脚本列定义逐列不一致**时重建列。
374
+ - **可复用**:**`examples/bitable-list-lifecycle.js`** 中的 **`dataColumnsMismatch`**、**`rebuildSubBitableColumns`**。
375
+
376
+ ### 8.5 数据行(`items`)
377
+
378
+ - 行数据写在 **`bitable.items`**:元素形如 **`{ id: '...', fields: { [列 field 名]: 值 } }`**,`fields` 的键必须与 **`dataColumns[].field` 字符串完全一致**(含空格、大小写)。
379
+ - 接口返回 **`snake_case`** 或嵌套对象时,**必须先映射**到 `fields`,不能直接 `fields: apiRow`,否则列有表头、**单元格全空**。
380
+ - 每次拉取列表后:先 **`resolveBitableBySub(subBitables[k])`**(或等价:`getAllSubBitable` → `find` → 单表回退,见 **8.6**)→ 写回行:见 **8.5.2** 区分 **全量 list(整表 `items = mappedRows`)** 与 **增量/询价式(按 `id` 合并 `fields` + 新行 `push`)** → **`updateSubBitable([bitable], { isMerge: true, name: subTableName })`**;`name` 与 **8.1** 中 `createSubBitable` 所用一致。单主表且仅一块子表时也可用 **`getSubBitable(true)`**,但仍须保证 **`fields` 键与列 `field` 一致**。
381
+ - **提示语**:与 **7.3** 一致,**在 `updateSubBitable` 完成后再 `notifySuccess`**,避免表格尚未提交渲染就弹出「已 N 条」。
382
+
383
+ 实现见 **`examples/bitable-list-lifecycle.js`**。
384
+
385
+ ### 8.5.1 刷新列表:勿与「初次建表」绑死
386
+
387
+ - **流程**:**`resolve` → `fetch` → 写 `items`(策略见 **8.5.2**)→ `updateSubBitable({ isMerge:true, name })`**。勿每次刷新 **`createEmptyBitable`**;仅 **无子 Cell / `dataColumnsMismatch`** 时重建列。
388
+ - **完整实现**:**`examples/bitable-list-lifecycle.js`**(`refreshTable`、`applyRowsToBitable`、`ensureBitableStructure`)。
389
+
390
+ ### 8.5.2 全量 `list` 与按 `id` 合并:必选其一
391
+
392
+ **`examples/bitable-list-lifecycle.js`** 里 **`applyRowsToBitable`** 的典型行为是:
393
+
394
+ - **无可见行**:**`bitable.items = mapped`**(首载 / 空表后整表写入)。
395
+ - **已有可见行**:对 **`mapped`** 建 **`Map(id → row)`**,对**现有** `bitable.items` **只合并同 `id` 的 `fields`**,再把 **`mapped` 里尚未出现的 `id`** **`push`** 进去。
396
+
397
+ 该「合并」路径**不会删除**响应里已不存在的旧行。因此当 **`GET …/list`(或团队约定的全量查询)代表服务端当前全集**时,刷新逻辑**必须**在拉数后执行 **`bitable.items = mapped`**(或等价:先清空再赋新数组),再 **`await updateSubBitable(...)`**。否则:
398
+
399
+ 1. 后台删光或筛掉部分记录后,表格仍显示**已删行**;
400
+ 2. 极端情况:接口返回 **`[]`** 时 **`mapped` 为空**,合并分支**不改任何现有 `item`**,用户会看到 **toast「已刷新,共 0 条」但网格仍是旧数据**。
401
+
402
+ **何时保留合并**:产品明确要求**增量补丁**、**询价式只更新变动行**、或需**保留本地未同步行**且与接口语义一致时,再在脚本注释中写清策略;若仍用合并,须**额外**处理「服务端已删除的 `id`」在客户端如何移除。
403
+
404
+ **生成代码自检**:「若下一帧接口返回**更少行**或**空数组**,当前写回是否会从 `bitable.items` **去掉**多余行?」若否且 list 为全量语义,改为整表替换。
405
+
406
+ ### 8.6 多子表与 `getAllSubBitable`:按 `index` 取表再写 `items`
407
+
408
+ 与现网「查询表 / 编辑表 / 备注表」等多块子区域一致时,常用:
409
+
410
+ 1. **配置数组**:`subBitables = [{ index: '…', name: '…', isMain?: true }, …]`。**`name`(`updateSubBitable` 最后一参)必须与 Book 一致**。**`index` 必须从 Book 编辑器配置抄写**;禁止臆造占位字符串(否则 `find` 永远失败)。
411
+ 2. **取全部内存实例**:`const all = await xnbBitable.getAllSubBitable(true)`。
412
+ 3. **定位子表(推荐封装)**:`all.find((b) => b.index === sub.index)`;若未找到且 **`all.length === 1`**,可回退 **`all[0]`**(仅「Book 只有一块子表」的迁移/演示);若仍失败且 **`getSubCell()`** 为真,再试 **`getSubBitable(true)`** 且校验 **`bitable.index === sub.index`**(或单主表场景下接受主表实例)。**多子表时禁止依赖回退**,否则可能写到错误的表。
413
+ 4. **首次建表**:仅当 **`getSubCell()`** 为假(或确认不存在目标子表)时 **`createSubBitable` + `addColumns`**(见 **8.4**)。**已存在子 Cell 时** `find` 失败 **不要**再 `createSubBitable`,应先修正 **index/name**。
414
+ 5. **写行**:`one.items = mappedRows`(元素 **`{ id, fields }`**);**`await xnbBitable.updateSubBitable([one], { isMerge: true 或 false, name: sub.name })`**。**每次刷新列表**只做本步 + 第 2~3 步,**不要**重复 `createSubBitable`。
415
+ 6. **切换当前展示子表**:可对目标表 **再调用一次** `updateSubBitable`(参数以线上为准)。
416
+ 7. **列样式**:`dataColumns` 中 **`headerColor`、`xnb_select`、`xnb_time`、`number`+`ct`** 等;**勿照抄业务列名**。
417
+
418
+ 解析与刷新:**`examples/bitable-list-lifecycle.js`**。
419
+
420
+ ---
421
+
422
+ ## 9. 表单录入(Materialize 要点)
423
+
424
+ 见 **`examples/form-controls-materialize.js`**、**`examples/filter-toolbar-line.js`**。要点:**`id` 一律 `${cellUid}-...`**;**`input-field` 尽量只用于 `text` / `textarea`**;**`date` / `datetime-local` 用 `document.getElementById` 读值**。
425
+
426
+ ### 9.1 仅日期(`type="date"`)
427
+
428
+ - **`value` 与 `input.value`** 均为 **`YYYY-MM-DD`**。推荐与线上一致写三个小函数:**`getDateString(date)`** → **`getTodayDate()`**、**`getSixMonthsAgoDate()`**(`setMonth(getMonth()-6)`)等,在 **`return \`...\``** 里写 **`value="${getTodayDate()}"`**,避免手拼错格式。
429
+ - **日期区间**:两个 **`<input type="date">`**,中间文案 **「至」**;样式可与其它筛选控件统一(如 `padding:8px 12px;border:1px solid #ddd;border-radius:4px;min-width:150px;font-size:14px;line-height:1.5;box-sizing:border-box;`)。
430
+ - **读取**(在 `register` 的方法内):**`document.getElementById('…')?.value || ''`**;起、止各读一次再拼查询参数。`type="date"` 常用此读法;其它控件可再用 **`getInstanceValueById`**。
431
+ - **`id`**:同页多 JsCode 实例时用 **`${cellUid}-entry-time-start`** 等与 **`getElementById`** 同一字符串;若整页仅单实例,也可使用固定 id(读写必须同名)。
432
+
433
+ 见 **`examples/filter-date-native.js`**(含起止默认值与读区间示例)。
434
+
435
+ ### 9.1.1 表单里的 `<select>`:线框 + 与筛选区一致的横向排布
436
+
437
+ - **线框**:与 **`input[type=date]`** 一致,例如 **`padding:8px 12px;border:1px solid #ddd;border-radius:4px;min-width:180px;font-size:14px;line-height:1.5;background-color:#fff;box-sizing:border-box;`**(与现网 **Project code** 行同款即可)。
438
+ - **默认不加 `browser-default`**,与线框 `date` / `input` 视觉统一;**Materialize 双影**(上方一行字 + 下方又一个箭头)时再对该 **`<select>`** 试加 **`class="browser-default"`**,并采用 **`display:flex; align-items:center; gap:8px`**:**左侧 `label`(可加 `font-weight:bold`、`min-width`)+ 右侧 `select`(`flex:1; min-width:0`)**(见 **`examples/form-controls-materialize.js`**)。
439
+ - **表格内枚举列**:优先 **`dataColumns` 的 `xnb_select`**;表单筛选区才用 HTML `<select>`。
440
+
441
+ ### 9.2 `datetime-local` 与 Materialize「浮动 label」冲突
442
+
443
+ - **`input-field` + `label` 浮动** 与 **`type="datetime-local"`** 叠在一起时,易出现 **label 与值重叠**。做法:**不要用 `input-field` 包 datetime**;改为 **`label` 普通块级(`display:block;margin-bottom:6px;font-size:12px;color:rgba(0,0,0,0.6)`)+ `input` 自带边框**(见 **`examples/form-controls-materialize.js`**),**`label` 默认与其它字段一样左对齐**;除非设计明确要求,**不要**为时间单独做整列 `flex` 居中导致与 Barcode 等列标题错位。
444
+ - **框内文字垂直居中**:Chromium 系下 **`datetime-local`** 易出现日期时间偏下留白;可对 **`#${cellUid}-…` 作用域** 内增加 **`height:40px; line-height:40px; padding:0 12px; box-sizing:border-box`**,并配合 **`::-webkit-datetime-edit-fields-wrapper { padding:0 }`**、**`::-webkit-datetime-edit { height:100%; margin:0; vertical-align:middle }`**(见示例内嵌 `<style>`);Firefox 以 **`height` + 对称 `padding`** 为主即可。
445
+ - **读值**:**`datetime-local` 与 `type="date"` 优先 `document.getElementById(id).value`**;`xnbContext.getInstanceValueById` 对部分原生控件可能始终为空,导致提交 JSON 里时间字段为 **`""`**。
446
+
447
+ ### 9.3 按钮与语义色(Materialize)
448
+
449
+ - 同类操作里用 **不同颜色** 表达动作含义,避免全部默认灰/蓝:**`green`**(查询/提交/确认)、**`blue`**(导入/次要正向)、**`orange`**(警告类)、**`red`**(清除/危险)等;**不要**给主业务按钮加 **`grey`** 除非明确禁用态。
450
+
451
+ ### 9.4 提交 JSON 与字段类型
452
+
453
+ - 表单读入多为 **字符串**;提交前按接口约定 **转换**:整数 **`parseInt`**、小数 **`Number`**、空串 **`undefined` 并 `delete` 键**;**`datetime-local`** 可转 **`new Date(value).toISOString()`**(或团队约定的时区格式),**勿把未解析的空串提交为 `""` 当无值**。