lan-low-code-cli 2.5.0 → 2.6.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.
@@ -0,0 +1,220 @@
1
+ # 业务操作脚本(JS 模式)context API 参考
2
+
3
+ > 适用:表单业务操作 `fdType=3` + `fdViewType=5`(平台「脚本模式」)。
4
+ > 结论来源:sys-modeling 前端源码(MK-PaaS,`frontend/sys-modeling`)。
5
+ > **重要**:除 `fdOprId` / `fdIds` / `fdBtnCfg` / `destroyLoading` 是显式注入外,
6
+ > 其余字段来自组件内部 props 的浅拷贝,**不是公开契约**,平台升级可能变化——使用前请做 `typeof` 兜底。
7
+
8
+ ## 1. 执行机制
9
+
10
+ 点击「脚本模式」按钮后,前端先调 `sysModelingOperation` 相关接口拿到操作配置,再执行脚本:
11
+
12
+ ```js
13
+ // desktop/operation/other.ts(移动端 mobile/operation/other.ts 同构)
14
+ new Function('context', model.fdCfg?.fdJavaScript)({
15
+ ...context, // 列表页传入的上下文(浅拷贝,见第 3 节)
16
+ fdOprId: operation.id,
17
+ fdIds: params['fdIds'],
18
+ fdBtnCfg: model.fdCfg,
19
+ destroyLoading, // 关闭「执行中...」提示
20
+ })
21
+ ```
22
+
23
+ 关键点:
24
+
25
+ 1. 脚本在**全局作用域**求值,可直接用 `window.mk`、`window.MKXFORM`、`fetch` 等页面全局对象;
26
+ 2. 脚本抛错会被前端 `try/catch` 吞掉(仅 `console.error`),页面只关闭 loading——**错误不会提示用户**,
27
+ 脚本内部必须自己 try/catch 并给出可见反馈;
28
+ 3. 平台**不会**在脚本执行后自动刷新列表(只有 AI 能力分支会 `refresh()`),需要刷新就自己调;
29
+ 4. 脚本里调用 `context.destroyLoading()` 后,前端检测到即不再重复关闭;不调用则由前端关闭(无参数执行完也关)。
30
+
31
+ ## 2. 显式注入字段
32
+
33
+ | 字段 | 类型 | 说明 |
34
+ |---|---|---|
35
+ | `context.fdOprId` | `string` | 当前业务操作 id |
36
+ | `context.fdIds` | `string[] \| undefined` | 选中/当前行 id。**仅** `operation.selected === true` 时赋值:行内按钮、标准行操作;工具栏按钮通常为 `undefined` |
37
+ | `context.fdBtnCfg` | `object` | 操作配置(后端返回的 `fdCfg` 解析结果,含 `fdViewType` / `fdJavaScript` 等) |
38
+ | `context.destroyLoading` | `() => void` | 关闭「执行中...」提示并复位按钮 loading |
39
+
40
+ ## 3. 列表页上下文(desktop)
41
+
42
+ `context` 由列表页 `useListOperations` 组装(`components/list-core/hooks/useListOperations.ts`),
43
+ 是 `content.tsx` 传入 props 的浅拷贝 + 若干选中态字段。
44
+
45
+ ### 3.1 选中数据
46
+
47
+ | 字段 | 类型 | 说明 |
48
+ |---|---|---|
49
+ | `selectedId` | `string[]` | 勾选行的 `fd_id` 数组(组件 state,工具栏按钮场景) |
50
+ | `docId` | `string \| undefined` | 行内按钮:当前行 id(= `operation.selectedIds[0]`) |
51
+ | `fdIds` | `string[] \| undefined` | 见第 2 节;行内按钮/标准行操作才有 |
52
+ | `setSelectedId` | `(ids: string[]) => void` | 设置勾选行 |
53
+ | `setSelectedDocs` | `(docs: any[]) => void` | 设置勾选行的完整数据 |
54
+
55
+ **取值优先级建议**:`fdIds`(行内按钮)→ `selectedId`(工具栏勾选)→ `docId`(兜底)。
56
+
57
+ > 坑:`selectedId: selectedId || selectedIds`,空数组 `[]` 是 truthy——行内按钮点击时
58
+ > `selectedId` 往往就是 `[]`,此时必须用 `docId` / `fdIds`。
59
+
60
+ ### 3.2 列表数据
61
+
62
+ | 字段 | 类型 | 说明 |
63
+ |---|---|---|
64
+ | `data` | `any[]` | 当前页数据数组(= `$$contentviewProps.data`,行字段 `fd_id`) |
65
+ | `$$contentviewProps.data` | `any[]` | 同上;`$$contentviewProps` 还含 `refresh` / `query` / `queryChange` / `history` |
66
+ | `xlistRuntimeRef.current.getData()` | `() => { content: any[] }` | 列表运行时数据(**类型未声明**,运行时存在,需兜底) |
67
+
68
+ ### 3.3 刷新与查询
69
+
70
+ | 入口 | 说明 |
71
+ |---|---|
72
+ | `context.refresh(query?)` | 推荐。等价 `xlistRuntimeRef.current.loadData(query)` |
73
+ | `context.$$contentviewProps.refresh(query?)` | 同上(列表页透传的同一个函数) |
74
+ | `context.cacheListRef.current.refresh(query?)` | 列表页把 `refresh` 缓存在这里,供跨组件/iframe 使用 |
75
+ | `context.xlistRuntimeRef.current.loadData(query?)` | 最底层;支持传对象或函数式 updater |
76
+ | `context.$$contentviewProps.queryChange(newQuery)` | 在现有 query 上合并后重载:`loadData(q => ({...q, ...newQuery}))` |
77
+ | `context.cacheListRef.current.queryChange` | 同上(缓存副本) |
78
+
79
+ ### 3.4 视图 / 权限 / 路由上下文
80
+
81
+ | 字段 | 说明 |
82
+ |---|---|
83
+ | `formId` | 表单 id |
84
+ | `listviewId` | 列表视图 id |
85
+ | `fdRightGroupId` | 当前权限组 id |
86
+ | `appId` / `appCode` | 应用 id / 编码 |
87
+ | `fdCategoryModelCode` / `fdCategoryType` / `fdCategoryId` | 分类表单相关 |
88
+ | `menuId` / `baseUrl` | 菜单与路由前缀 |
89
+ | `fdOpenMode` / `drawerOptions` | 打开方式(`self`/`drawer`/`_blank`)与抽屉配置 |
90
+ | `isInDrawer` | 当前是否在抽屉内 |
91
+ | `templateId` / `commonIncParams` / `localQuery` / `mainTable` | 模板、公共入参、本地查询、主表标识 |
92
+ | `params` | 外部调用 `operations.execute(op, params)` 时传入的第二个参数 |
93
+ | `dispatch` | 通用弹窗 reducer:`dispatch({type:'modalProps', payload:{...}})`(内部状态,不建议依赖) |
94
+ | `setDoingOperation` | `(v: boolean) => void`,切换按钮「执行中」状态 |
95
+
96
+ ### 3.5 工具栏按钮 vs 行内按钮
97
+
98
+ | 场景 | `selected` | `fdIds` | `docId` | `selectedId` |
99
+ |---|---|---|---|---|
100
+ | 列表工具栏按钮 | 通常 `undefined` | `undefined` | `undefined` | 勾选行数组(可能 `[]`) |
101
+ | 行内右侧按钮 | `true` | `[当前行 id]` | 当前行 id | 通常 `[]` |
102
+ | 外部 `operations.execute` | 取决于调用方 | 需自己传 | 需自己传 | 取决于调用方 |
103
+
104
+ > 平台配置的「勾选数据」(`fdConfig.fdListViewIdValid`)在脚本模式下**不会**转成 `selected`,
105
+ > 也不会触发「请选择数据」校验——脚本必须自己判断并提示。
106
+
107
+ ## 4. 全局能力(window)
108
+
109
+ | API | 说明 |
110
+ |---|---|
111
+ | `mk.openLink({ url, target, drawerOptions, forceTarget })` | 打开页面:`target` 取 `self` / `drawer` / `_blank` |
112
+ | `mk.normalizeUrl('sys://user/sys-modeling/<path>')` | 内部路由转可访问地址(业务操作的 `goto` 用它) |
113
+ | `mk.getSysConfig(key)` | `modulesUrlPrefix` / `apiUrlPrefix` / `currentUser` / `platform` / `standalone` 等 |
114
+ | `mk.emit(event, payload)` / `mk.on` / `mk.off` | 事件总线(如 `mk.emit('CHANGETAGSS')`、`mk.emit(MkModelingEventTypes.PageListStateChange, {})`) |
115
+ | `mk.userStore` | 全局 key-value(`mk.userStore.set({ key, value })` / `get`) |
116
+ | `window.MKXFORM` | 列表页已初始化(`components/list-core/components/content-area.tsx`):`tableModal` / `modal` / `HTMLModal`;表单上下文还有 `setValue`/`getValue` 等(见 `mkxform-api.md`) |
117
+ | `fetch` / `Blob` / `navigator.clipboard` / `window.open` | 标准浏览器能力,可自行调接口、导出文件 |
118
+
119
+ ### 4.1 跨窗口 / iframe
120
+
121
+ 列表页监听 `message`(`window` 与 `window.parent`):
122
+
123
+ | 事件名 | 效果 |
124
+ |---|---|
125
+ | `mk-refresh-opener`(`ModelingDrawerViewEvents.RefreshPage`) | 触发列表刷新(`cacheListRef.current.refresh()`) |
126
+ | `PageInfo` / `Clear` / `TurnPage` | 分页信息交换 / 清缓存 / 翻页(列表页与抽屉 iframe 之间) |
127
+
128
+ ```js
129
+ window.parent.postMessage({ eventType: 'mk-refresh-opener' }, '*')
130
+ ```
131
+
132
+ ## 5. 常用片段
133
+
134
+ ### 5.1 读取选中数据 + 兜底
135
+
136
+ ```js
137
+ function resolveRows(ctx) {
138
+ var ids = (Array.isArray(ctx.fdIds) && ctx.fdIds.length ? ctx.fdIds : null) ||
139
+ (Array.isArray(ctx.selectedId) && ctx.selectedId.length ? ctx.selectedId : null) ||
140
+ (ctx.docId ? [ctx.docId] : [])
141
+ var list = (ctx.$$contentviewProps && ctx.$$contentviewProps.data) || ctx.data || []
142
+ if (!list.length && ctx.xlistRuntimeRef && ctx.xlistRuntimeRef.current) {
143
+ try { list = (ctx.xlistRuntimeRef.current.getData() || {}).content || [] } catch (e) { list = [] }
144
+ }
145
+ return list.filter(function (row) { return ids.indexOf(row.fd_id || row.fdId) > -1 })
146
+ }
147
+ ```
148
+
149
+ ### 5.2 刷新列表(多级兜底)
150
+
151
+ ```js
152
+ function refreshList(query) {
153
+ var c = context
154
+ try {
155
+ if (typeof c.refresh === 'function') return c.refresh(query)
156
+ if (c.$$contentviewProps && typeof c.$$contentviewProps.refresh === 'function')
157
+ return c.$$contentviewProps.refresh(query)
158
+ if (c.cacheListRef && c.cacheListRef.current && typeof c.cacheListRef.current.refresh === 'function')
159
+ return c.cacheListRef.current.refresh(query)
160
+ if (c.xlistRuntimeRef && c.xlistRuntimeRef.current && typeof c.xlistRuntimeRef.current.loadData === 'function')
161
+ return c.xlistRuntimeRef.current.loadData(query)
162
+ } catch (e) {
163
+ console.warn('[script] refresh failed', e)
164
+ }
165
+ }
166
+ ```
167
+
168
+ ### 5.3 打开页面 / 抽屉
169
+
170
+ ```js
171
+ window.mk.openLink({
172
+ url: window.mk.normalizeUrl('sys://user/sys-modeling/add/' + context.formId),
173
+ target: context.fdOpenMode || '_blank',
174
+ drawerOptions: context.drawerOptions,
175
+ })
176
+ ```
177
+
178
+ ### 5.4 批量调接口后刷新
179
+
180
+ ```js
181
+ fetch(window.mk.getSysConfig('apiUrlPrefix') + '/sys-modeling/<接口路径>', {
182
+ method: 'POST',
183
+ headers: { 'Content-Type': 'application/json' },
184
+ credentials: 'include',
185
+ body: JSON.stringify({ fdIds: context.selectedId }),
186
+ })
187
+ .then(function (r) { return r.json() })
188
+ .then(function () { refreshList(); context.setSelectedId([]); context.destroyLoading() })
189
+ .catch(function (e) { console.error(e); context.destroyLoading() })
190
+ ```
191
+
192
+ ## 6. 移动端差异
193
+
194
+ `mobile/operation/other.ts` 同样是 `new Function('context', script)({ ...context, fdIds, destroyLoading })`:
195
+
196
+ - 可靠可用的:`fdOprId`、`fdIds`、`fdBtnCfg`、`destroyLoading`、`refresh`(查看/编辑页传的是整页 `refreshPage`);
197
+ - **不要依赖** `xlistRuntimeRef` / `$$contentviewProps` / `cacheListRef`(PC 列表专有),移动端列表是 `m-list-view`;
198
+ - 移动端 `refresh()` 是整页重渲染,不是 `loadData(query)`。
199
+
200
+ ## 7. 已知边界与坑
201
+
202
+ 1. **没有** `context.selectRow`、`context.requestData`、`context.showAddPopup`、`context.queryChange`(后者只在 `$$contentviewProps` 里)。
203
+ 2. `callback` / `lcodeIncParams` / `defaultTemplateData` / `detailDataRowFilterOrder` 在列表页点击场景为 `undefined`(列表页未传)。
204
+ 3. `getData()` 未出现在 `IXListRuntimeRef` 类型声明中,仅运行时存在。
205
+ 4. 脚本异常被静默吞掉,务必自己 try/catch + 提示 + `destroyLoading()`。
206
+ 5. 刷新后如需清空勾选:`context.setSelectedId([])`。
207
+ 6. 不要直接修改 `$$contentviewProps.data`(不持久化,且与组件 state 不一致),写操作走后端接口。
208
+ 7. 调试:脚本里 `console.log(context)` 后在浏览器控制台查看完整字段;`localStorage.getItem('mk-debugger') === 'true'` 时平台会打印部分调试信息。
209
+
210
+ ## 8. 源码索引(核对版本用)
211
+
212
+ | 内容 | 位置(sys-modeling/src) |
213
+ |---|---|
214
+ | 脚本执行入口(PC) | `desktop/operation/other.ts` |
215
+ | 脚本执行入口(移动) | `mobile/operation/other.ts` |
216
+ | context 组装 | `components/list-core/hooks/useListOperations.ts` |
217
+ | 列表页 props / refresh | `desktop/pages/list/index.ts` |
218
+ | 列表内容区 / window.MKXFORM | `components/list-core/components/content.tsx`、`content-area.tsx` |
219
+ | 按钮点击 / 行内按钮 | `components/view-config/utils/operationsFun.tsx` |
220
+ | 统一执行入口 | `desktop/operation/execute.ts` |