Title: 🌐 V8 函数列表 - 前端
Canonical: https://microi.net/doc/v8-engine/v8-client.html
Page-Modified: 2026-11-09T09:09:51.000Z
Source: published HTML document body; page revision is not a capability verification date.
# 🌐 V8 函数列表 - 前端
前端 V8 引擎支持完整 ES6 语法,集成丰富的前端函数库
---
## 📌 介绍
- 前端 V8 引擎代码与服务器端 V8 的编程语言均为 JavaScript 语法
- 前端 V8 引擎支持完整 ES6 语法
- 前端新 HTTP 代码优先使用与后端参数基本一致的 V8.Http;旧 V8.Post/Get 继续兼容已有代码
- 若前端直接调用服务器端的通用增删改查接口,浏览器内的 SubmitFormV8 等前端事件不会执行;服务器端提交前/后事件仍按客户端调用语义执行
- 后端接口引擎、后端表单 V8 调用 V8.FormEngine 默认不触发表单事件;只有明确传入 _InvokeType:'Client' 才触发客户端式表单事件。_InvokeType 不是授权开关
- 主要用于表单属性的前端 V8 事件、模块引擎 V8 按钮代码等
## 独立应用中的双向实时接口
适用版本:后端 8.6.1 及以上;通过 MCP 配置时使用开发工具 6.0.4 及以上。
数字孪生导播、协同控制等需要“按钮指令和执行回执都走长连接”的场景, 使用接口引擎 ResponseType=SignalR 与 /api-engine-socket。 该通道需要对应后端能力,完整权限、消息格式和容量限制见 后端双向实时通道 (https://microi.net/doc/v8-engine/v8-server.html#%E6%8E%A5%E5%8F%A3%E5%BC%95%E6%93%8E%E5%8F%8C%E5%90%91%E5%AE%9E%E6%97%B6%E9%80%9A%E9%81%93-signalr)。 它不修改现有 V8.Http,也不把 SSE 当成双向连接。
在 Vue/微服务项目中将 @microsoft/signalr 安装并打包进产物,不依赖临时外链脚本:
typescript
```
import { HubConnectionBuilder, HttpTransportType } from '@microsoft/signalr';
const endpoint = new URL('/api-engine-socket', apiBase);
endpoint.searchParams.set('OsClient', osClient);
endpoint.searchParams.set('ApiEngineKey', 'my_realtime_engine');
const connection = new HubConnectionBuilder()
.withUrl(endpoint.href, {
transport: HttpTransportType.WebSockets,
skipNegotiation: true,
accessTokenFactory: () => currentDiyToken // 匿名专用接口可省略
})
.withAutomaticReconnect([0, 1500, 4000, 8000, 15000])
.build();
connection.on('EngineMessage', packet => {
// 按业务 RequestId/Version 匹配回执,不把任意收到的消息视为本次成功。
handleBusinessPacket(packet);
});
connection.onreconnecting(() => showConnectionState('正在重连,待确认指令不可自动重放'));
connection.onreconnected(async () => {
// ConnectionId 已改变;重建业务授权/配对并读取权威状态。
await restoreAuthorizedSession(connection);
});
connection.onclose(() => showConnectionState('已断开'));
await connection.start();
// 这个 invoke 在已经建立的 WebSocket 内发送,不会为每次点击发 HTTP 请求。
const requestId = crypto.randomUUID().replaceAll('-', '');
const accepted = await connection.invoke('Invoke', requestId, {
Action: 'SelectCamera', CameraId: selectedCameraId
});
if (accepted.Code !== 1) showError(accepted.Msg);
// accepted.Code=1 只证明接口成功;目标屏幕实际完成后再收到业务 ACK 才显示控制成功。
```
示例中的动作、restoreAuthorizedSession、心跳和回执处理必须与当前业务接口约定一致。 页面可每 15 秒通过同一 Invoke 发送业务心跳;卸载或停止授权时清理心跳、待回执计时器和 监听,并 await connection.stop()。后台、断网、刷新和服务端拒绝都要显示可恢复状态。 withAutomaticReconnect 不自动重试第一次 start(),首次失败应显示重试入口。
使用四位控制码时,由展示屏点击“允许控制”后才连接和登记;localStorage 可保存随机设备秘密和 偏好的控制码,重新打开页面仍需重新授权,服务端处理码冲突并回传实际分配值。 不能把四位码当平台登录凭据,也不能在 URL、控制台日志或截图里暴露设备秘密。
多台手机共享一个展示屏时,每台手机使用独立随机会话身份,展示屏的控制码保持相同。 后端按接收顺序调度镜头命令,手机分别显示“排队中 / 执行中 / 成功或失败”;只有收到展示屏 实际执行回执才显示成功。单台手机离开只清除自身会话,画面状态同步到所有仍在线的手机。 本地导播操作或远程手动控制应停止自动巡游,明确点击“开始巡游”后才恢复。 手机端将发送中、等待执行、执行成功、拒绝、超时和断线区分显示,支持主动断开及前台停止授权。 配对租约、权限和幂等必须由服务端共享状态判定,localStorage 不是权限事实源。
## 独立前端 SDK 的公有媒体地址选择
以下是复制到独立 Vue / UniApp 项目的 microi.v8.js 能力,不是主后台 V8 事件新增的同名全局方法。它依赖同源接口引擎 mci-file-resource-public-url 和后端 PublicSourceUrlRoutes/1 能力;未部署、未配置或资源尚未就绪时保留原地址。
assetUrl(value) 继续同步返回兼容地址,不会等待 NAS 路由。需要分流的公有图片、视频或模型在发出首个资源请求之前等待 resolvePublicResource;上传元数据明确 Limit:false 的 resolveFileUrl 也使用同一解析器。普通字符串调用 resolveFileUrl(value) 保持旧行为;只有调用方已知道资源是公有时才传 {private:false}。对象显式私有、Limit 非 false、签名 URL 和未知类型不能靠这个选项变成公有资源。
历史租户配置的原始 FileServer 主地址兼容 http:// 和 https://。公有相对路径回退时保留其协议、端口及路径前缀,不强行升级 HTTPS,也不改拼 API Base。此兼容只用于已配置的原始主地址:备用绑定、源地址映射和别名仍要求 HTTPS 及对应的校验,不能因为主地址使用 HTTP 就放宽备用路由。私有路径、带凭据地址和危险协议仍按原安全规则拒绝或鉴权取址。
小程序缺少浏览器的 URL、AbortController 或 queueMicrotask 时,SDK 使用经过校验的原始 FileServer 公有地址,暂停备份路由解析。缺少 URL 时的主地址兼容仅接受普通域名或 IPv4;IPv6、国际域名及无法可靠识别的地址保持空值。私有文件仍走原有鉴权流程。若生成接口返回成功但图片显示“图片暂不可用”,且没有发出图片请求,应先检查解析结果是否为空及运行时是否缺少这些 API;更新 SDK 后重新编译小程序生效。
js
```
// 配置来自当前租户的系统设置;切换租户/API 时同步重新 configure。
V8.configure({ apiBase, osClient, fileServer, fileServerBak });
const original = { Path: 'https://static.example.com/media/model.glb', Limit: false };
const route = await V8.resolvePublicResource(original);
if (route.url) mediaSrc.value = route.url; // 此前保持 src 未设置
// 当前元素确实在加载 NAS 地址且失败时,最多回退一次。
if (route.useBackup && !fallbackUsed) {
fallbackUsed = true;
mediaSrc.value = V8.reportPublicResourceFailure(original).url;
}
```
调用方应绑定本次源值与当前租户,忽略切换或卸载后返回的旧结果。SDK 内部还核对 API 基址、租户与文件服务配置。别名列表只用于发现;后端重新核对当前云源站、公有桶、ACL、不可变副本摘要、当前资源版本及 NAS 健康后,才返回绑定原始 URL 的短期结果,最长 60 秒。GLTF/GLB 等运行资源必须满足完整依赖组校验,不能只复制入口文件。前端不拼 NAS 前缀,不改私有签名 URL。
主后台界面引擎的图片、视频和 WebGL 的 HDR / GLTF 组件复用 DiyCommon.GetPublicResourceRoute,先解析再加载,并在 NAS 加载失败时回退一次。旧 HTML、srcset、CSS 背景图、任意应用自写的 Three.js 以及平台启动脚本没有被全局改写;它们需要在各自拥有的入口显式接入。源码与离线测试通过不等于已经发布、真实请求下降或完整日费用下降。
PageEngine 的 HTML 组件和历史富文本在插入 DOM 前用独立惰性 template 解析 img/video/source 的确切 src,等待同一公有资源解析器后交给原 sanitizer;私有字段、签名、srcset/picture/CSS/脚本不改。显示 NAS 地址时保存仍还原原稳定地址,每元素失败最多回退一次。编辑器 hydration 期间禁用编辑及源码/附件入口,结束后只恢复原可编辑实例;切记录、租户、模式或卸载丢弃迟到结果,源码 Edit→等待 View→Edit 会取消旧轮并释放其锁。离线测试不代替真实浏览器首请求与编辑保存验收。
## V8.Form
- 访问当前表单字段值
js
```
var id = V8.Form.Id;//在新增数据时也能访问到,因为 Id 是提前生成的,以备子表使用
var name = V8.Form.UserName;
//如果是下拉框组件,则获取到的是object,可访问到数据源中的所有字段
var selectId = V8.Form.SelectUser.Id;
```
## V8.OldForm
- 访问当前表单修改前字段值
js
```
var oldName = V8.OldForm.UserName;
```
## V8.FormSet
- 在普通表单 diy-form 中给字段赋值,并触发目标字段的值变更事件
- 下拉框可以赋值对象;对象至少应包含该下拉框配置的存储字段(SelectSaveField)和显示字段(SelectLabel),字段 V8 还会读取其它业务属性时也要一并传入
- 直接使用 V8.Form.UserName = value(包括对象)同样会响应式更新表单,但不会触发该字段的值变更事件,也不会执行 FormSet 的下拉选项注入、修改字段记录和模板通知,适合需要“静默赋值”的场景
- 前端已阻止字段值变更 V8 同步执行期间再次 FormSet 当前字段所造成的直接重入;但异步回写和多字段互相赋值仍可能形成循环,字段自身事件中仍建议直接写 V8.Form.字段名
- 在列表按钮、行内编辑等 diy-table 上下文中,FormSet 只更新当前行并刷新模板结果,不会递归触发目标字段 V8。不要依赖不同上下文的副作用做业务校验
js
```
//给文本框赋值
V8.FormSet('UserName', '张三');
//给下拉框赋值(Id和Name分别对应存储字段、显示字段)
V8.FormSet('SelectUser', { Id : 1, Name : '张三', DeptId : 'dept-1' });
//静默赋值:界面会更新,但不会触发SelectUser的值变更V8
V8.Form.SelectUser = { Id : 1, Name : '张三', DeptId : 'dept-1' };
```
## V8.Field
- 访问当前表单字段属性
js
```
var isReadonly = V8.Field.UserName.Readonly;//UserName字段当前是否是只读
//包含属性:Name、Label、Config、Data(绑定数据源)、Readonly、Visible、Placeholder等等
```
## V8.FieldSet
- 给当前表单字段属性赋值
- 普通表单支持 Config.Button.Loading 这类 Config 点路径;列表和部分全屏按钮上下文目前只保证顶层属性(如 Visible、Readonly、Data)可用。跨上下文代码优先使用顶层属性
js
```
//设置UserName字段为只读
V8.FieldSet('UserName', 'Readonly', true);
//其它可设置属性:Readonly、Visible、Placeholder等等
//给某个下拉框动态设置数据源:
V8.FieldSet('字段名', 'Data', [{Id:1}, {Id:2}]);
```
只读详情可能使用静态展示器,不挂载下拉组件。若当前值已成为 {Key: Id, Value: Id} 占位对象,仅更新 Data 不一定刷新显示名。取得服务端授权的选项后,可静默补齐同Key名称;数据库继续保存原Key。纯名称回显不需要触发 FormSet 的字段联动。
js
```
// result.Data 必须来自服务端当前记录权限范围内的关联选项。
// 异步请求结束后,先核对仍是同一个表单、记录及选择,再执行本段。
var current = V8.Form.Contact;
var key = current && typeof current === 'object' ? current.Key : current;
var selected = result.Data.find(x => x.Key === key);
if (selected) {
V8.FieldSet('Contact', 'Data', result.Data);
V8.Form.Contact = { Key: key, Value: selected.Value };
}
```
关联不存在、停用或没有权限时显示实际原因,不能改选第一项、清空旧关联或凭客户端名称授予权限。新增检索、编辑和只读详情需分别验收,尤其检查首屏之外的已有值及快速切换后的迟到响应。
## V8.FormMode
- 获取当前Form打开的模式,可能的值:Add(新增)、Edit(编辑)、View(预览)
js
```
if(V8.FormMode == 'Add'){
V8.FormSet('ShenqingR', V8.CurrentUser.Name);//默认申请人名称
V8.FormSet('ShenqingRID', V8.CurrentUser.Id);//默认申请人Id
V8.FormSet('Bumen', V8.CurrentUser.DeptName);//默认申请人部门名称
}
```
## V8.FormSubmitAction
- 表单提交类型,可能的值:Insert、Update、Delete
- 在表单进入事件无法访问到值,只能在表单提交前、提交后访问到值
- 在表单进入事件要判断当前表单是新增、还是编辑,请使用V8.FormMode(可能的值:Add(新增)、Edit(编辑)、View(预览))
## V8.FormOutAction
- 获取离开表单的类型,可用于离开表单、提交表单后V8引擎代码中做为判断,可能的值:Update、Insert、Close、Delete
## V8.FormOutAfterAction
- 获取离开表单后的类型,可用于离开表单/提交表单后V8引擎代码,可能的值:Insert、Update、View、Close
## V8.LoadMode
- 当前Form的加载模式,要么为空,要么值为Design(string,设计模式),特别注意一些事件中如果使用了V8.FieldSet更改了字段属性,需要判断V8.LoadMode == 'Design'时不执行,否则保存表单设计后会持久化保存字段属性。
## V8.KeyCode
- 键盘V8事件可获取键盘的code值,如Enter键对应13
javascript
```
if(V8.KeyCode == 13){
V8.Tips('您已经按了Enter键!');
}
//常见KeyCode对照表
8:Backspace(退格键)
9:Tab(表格键)
12:Clear(清除键)
13:Enter(回车键)
16:Shift_L(左Shift)
17:Control_L(左Control)
18:Alt_L(左Alt)
20:Caps_Lock(大小写锁定)
27:Escape(Esc键)
32:Space(空格键)
46:Delete(删除键)
37:Left(左)
38:Up(上)
39:Right(右)
40:Down(下)
```
## V8.TableId、V8.TableName
- 获取当前DIY表的Id、Name
## V8.EventName
- 前端V8事件名称,在全局V8引擎代码中比较好用,可能的值
js
```
FormTemplateEngine:表单模板引擎
TableTemplateEngine:表格模板引擎
OpenTableBefore:弹出表格前事件
OpenTableSubmit:弹出表格提交事件
FieldOnKeyup:表单文本框键盘事件
TableFieldOnKeyup:表格行内文本框键盘事件
FieldSlotButtonClick:单行文本插槽按钮点击事件
FormOut:离开表单事件(指表单提交后)
FormSubmitBefore:表单提交前事件
FormIn:进入表单事件
FieldValueChange:字段值变更事件
BtnFormDetailRun:详情按钮V8按钮
V8BtnLimit:V8按钮是否显示事件
V8BtnRun:V8按钮执行事件
TableRowClick:表格行点击V8事件
PageTab:多Tab页签V8事件
WFNodeEnd:流程节点结束V8事件
WFNodeStart:流程节点开始V8事件
```
日志、评论和数据版本分别支持 Data{Log|Comment|Version}BeforeClientV8 与 Data{Log|Comment|Version}AfterClientV8。V8.EventName 为属性名去掉末尾 V8; 前端提交前可校验白名单草稿,提交后仅针对后端回执中实际新写入的活动执行。 幂等重放、无差异、读取和预览不触发提交后事件。前端 After 中的 Form 是本地合法草稿, 不能视为数据库最终快照,事件失败也不能撤销已提交结果。 后端共享事务与 _ActivityData 白名单见日志、评论与数据版本写入事件 (https://microi.net/doc/v8-engine/v8-server.html#%E6%97%A5%E5%BF%97%E8%AF%84%E8%AE%BA%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%89%88%E6%9C%AC%E5%86%99%E5%85%A5%E4%BA%8B%E4%BB%B6)。
## V8.CurrentToken
- 当前登陆身份token
- Token 可能在每次受保护请求后续签轮换。Microi.Client 以公共 Token 存储为请求发送时的单一事实源,平台请求层收到响应头中的新 authorization/token 后会更新公共存储并同步登录状态;不要再用组件或状态库中的副本决定是否携带 Token,也不要把 Token 写入 URL、日志、V8.Result 或业务数据,或自行长期缓存旧值
## V8.TableModel
- 获取当前表的对象,里面包含了Id、Name等表信息。
## V8.ThisValue
- 当前字段事件的新值。下拉框通常是选中对象,文本/数字等控件可能是原始值;表格行内部分数值控件可能传入 { New, Old }
按钮读取 V8.Form 中的下拉字段时,以 Config.SelectSaveFormat 为准:Text 保存的是文本,旧表单也可能返回对象或 JSON 文本。先提取并校验选中值,再调用接口,避免因读取不存在的对象属性而提交空参数;网络失败时用 finally 恢复按钮 Loading。数据库扩展的“加载为DIY表”修复通过更新“数据库扩展”应用获取。
## V8.OldValue
- 当前字段旧值,仅在表格行内字段值变更上下文中可靠提供。普通表单字段事件应从 V8.OldForm 或业务快照读取,不要假设始终存在
## 常用上下文变量
不同事件只会提供与当前宿主有关的变量,使用前应判空:
变量 | 主要上下文 | 说明 |
V8.DataAppend | 表单、列表、弹窗 | 打开宿主时传入的附加业务数据 |
V8.SysMenuId | 标准菜单表单/列表 | 当前真实菜单 Id;只读使用,不要伪造 |
V8.SysMenuModel | 列表、菜单按钮 | 当前菜单模型 |
V8.TableRowId | 表单、子表 | 当前记录 Id 或父子表关联值 |
V8.CurrentTableData | 表单/列表 | 当前页或当前宿主已加载的数据 |
V8.TableRowSelected / V8.SelectedData | 列表批量按钮 | 当前勾选行数组,两个名称互为兼容别名 |
V8.ClearTableSelection() | 列表 | 清空勾选 |
V8.SearchParam | 列表 | { Keyword, Where } 当前搜索快照 |
V8.Row / V8.Rows / V8.RowIndex | 表格行事件 | 当前行、当前页行数组、行索引 |
V8.Event | 插槽按钮等显式传事件的场景 | 原生浏览器事件;键盘 V8 目前请使用 V8.KeyCode |
V8.Result | 模板、显隐、字段回调 | 事件/模板的显式输出 |
V8.ParentForm / V8.ParentV8 | 子表/嵌套表单 | 父级数据与父级 V8 |
V8.FormWF | 带流程表单 | 当前工作流打开状态 |
V8.ApiReplace | 表单 | 当前表单的接口替换配置 |
## V8.Tips
- 右下角弹出消息提示
js
```
V8.Tips(msgContent, true/false, time)
//msgContent为消息内容
//true为成功消息(1秒后消失),false为错误消息(5秒后消失)
//time可传入提示框多少秒后消失
```
## V8.CurrentUser
- 访问当前登陆用户信息
js
```
var id = V8.CurrentUser.Id;
```
## V8.Http
前端现已支持与后端一致的对象参数:Get/GetResponse、Post/PostResponse、Patch/PatchResponse。浏览器请求是异步的,因此前端必须使用 await;字符串方法返回原始响应文本,完整响应方法返回 { Content, Headers, RawBytes, StatusCode, ErrorMessage }。
javascript
```
var postText = await V8.Http.Post({
Url: '/api/example',
PostParam: { Id: 1 },
ParamType: 'json',
Timeout: 600, // 秒;默认即 600(10 分钟),可按接口覆盖
Headers: { 'X-Trace-Id': 'trace-id' }
});
var postResult = JSON.parse(postText);
var getText = await V8.Http.Get({
Url: '/api/example',
GetParam: { Id: 1 }
});
var patchResp = await V8.Http.PatchResponse({
Url: '/api/example/1',
PatchParamString: JSON.stringify({ profile: { name: '新名字' } }),
ParamType: 'json'
});
if (patchResp.StatusCode >= 200 && patchResp.StatusCode < 300) {
var data = JSON.parse(patchResp.Content);
}
```
参数与后端一致:Url、GetParam、PostParam/PostParamString、PatchParam/PatchParamString、ParamType、Timeout/TimeOut、Headers/Header、FilesByteBase64/FilesByteString/FilesByte。默认超时 600 秒(10 分钟);GetParam 可为 GET、POST、PATCH 附加 URL 查询参数。相对地址或当前 ApiBase 会自动携带吾码登录头并接收续签 Token;外部绝对地址不会自动携带吾码 Token。浏览器请求第三方域名仍需对方允许 CORS。
浏览器不支持后端的 FilesStream;使用 Base64、字符串或字节数组文件参数。更多示例见后端 V8.Http 文档及 v8-http-integration skill。
### 从前端 V8 调用 Microi.AI
#### 工作台全部能力与实时过程
前端从平台 8.8.1 起补齐以下统一入口,须同时部署匹配的 Core、Microi.net、AI、API 和 PC。 普通动作复用已安装 AI助手应用的 platform-ai-runtime,无需把供应商密钥写入 V8。
需求 | 前端 V8 方法 | 结果/过程 |
对话、数据分析、在线建模 | Chat、RecognizeIntent、NL2SQL、NL2V8 | DosResult;建模使用 Chat 的 Mode: 'builder',服务端重新检查管理员权限 |
打字机与执行摘要 | ChatStream、NL2V8Stream | SSE;文本回调 + onEvent(event, data),含 agent/receipt/result/error/done |
SignalR 对话/建模 | ChatSignalR | 当前 Token 连接 /api-engine-realtime,订阅 ChatAIStream,无自动重连重放 |
选择模型 | ListModels、GetMediaModels | 当前租户可读配置/安全媒体目录;目录存在不等于有模型额度 |
AI 生图/编辑 | GenerateMiniMaxImage(别名 GenerateImage、EditImage) | Code=2 + TaskId 受理;用 GetMiniMaxImageTask 查询原任务 |
图片结果恢复 | RecoverMiniMaxImageTask | 恢复已有结果,不重新生成 |
精确图片处理 | ProcessImage | 缩放、黑白、纯色抠图、裁剪、旋转、翻转、格式转换、拼图 |
音乐/语音 | GenerateMiniMaxMusic、GetMiniMaxMusicTask、RecoverMiniMaxMusicTask、GenerateMiniMaxSpeech | 沿用管理员、许可、配额和 HDFS 规则 |
视频 | CreateMiniMaxVideo、GetMiniMaxVideoTask、GetMiniMaxVideoFile、PersistMiniMaxVideoFile | 原任务/文件签名句柄,转存后读取永久结果 |
生成式图片统一传 Operation,覆盖工作台的 21 个工具:
text
```
text-to-image, sketch-to-image, poster, logo, image-to-image,
redraw, upscale, erase, outpaint, remove-watermark, background-replace,
style-transfer, colorize, restore, remove-background, id-photo,
portrait-retouch, avatar, product-scene, multi-composite, relight
```
实际可用操作由所选模型协议校验。去水印/消除等结构编辑不能使用仅支持人物参考的模型。 另外 8 项精确工具使用 ProcessImage.Operation 的 resize/grayscale/remove-solid-background/crop/rotate/flip/convert/merge,参数为 Images 和 Options。
javascript
```
var controller = new AbortController();
var result = await V8.AI.ChatStream({
UserChatMsg: '分析本月已授权的数据', AiModel: '当前启用模型', Mode: 'data'
}, function (chunk) {
// 把真实增量追加到自己的控件,不能把定时动画当服务端进度。
V8.FormSet('Answer', (V8.Form.Answer || '') + chunk);
}, { Signal: controller.signal, onEvent: function (event, data) {
if (event === 'receipt') { /* 消费授权执行摘要,不记录问题或密钥 */ }
}});
// 同样的参数/回调可交给 V8.AI.ChatSignalR。
// result.Code===1 才是成功;DataAppend 保存执行回执。EOF/done 不单独代表成功。
```
图片编辑示例(ReferenceImages 来自本次已授权上传的图片;先通过目录选支持编辑的模型):
javascript
```
var queued = await V8.AI.EditImage({
RequestId: '保持稳定的本次请求号', AiModelId: '目录返回的模型记录Id',
Operation: 'erase', Prompt: '移除画面右侧的指定对象',
ReferenceImages: [{ FileName: 'source.png', DataUrl: imageDataUrl }]
});
// 保存 queued.Data.TaskId;后续用相同 TaskId 查询,不因超时换请求号重发。
var status = await V8.AI.GetMiniMaxImageTask({TaskId: queued.Data.TaskId});
```
取消只停止当前流消费及受支持的模型读取,不回滚已完成的工具写入或供应商消费。 以上方法均提供同名 Async 别名。真实付费模型、输出图像质量与目标账号权限须单独验收。
平台提供第一等 V8.AI。它使用当前 ApiBase,自动携带登录 Token、did、语言头并接收响应 Token 轮换;调用参数不能覆盖当前用户、OsClient、Endpoint、ApiKey 或认证头:
javascript
```
var result = await V8.AI.Chat({
UserChatMsg: '把当前表单内容归纳为三条结论',
AiModel: 'MiniMax-M3',
AiModelId: '当前租户启用的 mic_ai 记录Id'
});
if (result.Code != 1) V8.Tips(result.Msg || 'AI调用失败', false);
else V8.Result = result.Data;
```
V8.AI.Chat 默认 POST;只有纯标量且确认问题适合进入 URL、代理日志和浏览器历史时才使用 V8.AI.ChatGet。其它普通方法包括 RecognizeIntent、NL2SQL 和管理员使用的 NL2V8,均返回标准 DosResult。
打字机效果使用内置 SSE 适配,不需要在按钮事件中重复实现分帧、认证与 Token 轮换:
javascript
```
var controller = new AbortController();
var answer = '';
var result = await V8.AI.ChatStream({
UserChatMsg: '生成一段客户回访建议',
AiModel: 'MiniMax-M3',
AiModelId: '当前租户启用的 mic_ai 记录Id'
}, function (chunk) {
answer += chunk;
V8.Result = answer;
}, {
Signal: controller.signal
});
```
页面关闭时调用 controller.abort()。ChatStream 的回调收到真实 message 增量,最终 Promise 返回标准 DosResult;还提供管理员限定的 NL2V8Stream。完整授权矩阵、后端 V8 与 microi_chat MCP 示例见 AI 引擎与 Microi.AI 中转站 (https://microi.net/doc/system-engine/ai-engine.html)。
## V8.Post
- 历史兼容方法,继续保留。前端新代码应优先使用参数与后端基本一致的 V8.Http;V8.Post/Get 不再作为新功能首选,已有代码无需迁移。
javascript
```
//发起ajax请求,常规用法,自带token,默认Form Data参数格式(非Request Payload)
V8.Post('api url', { Id : 1 }, function(result){
if(result.Code == 1){ ... }
})
//完整用法
V8.Post({
url : '',//接口地址,必传。
data : {}, //接口参数
dataType : 'json', //默认空(Form Data),可选json(Request Payload)
header : {}, //可选参数
success : function(result){ }, //请求成功的回调函数,常用参数。
fail : function(result){ }, //请求失败的回调函数,如接口报错404、500,可选参数,也可传入error,与fail一致。
});
```
## V8.Get
- 历史兼容 GET 方法,继续保留。
js
```
V8.Get('api url', {}, function(result){})
```
## V8.ChineseToPinyin
- 中文转拼音,V8.ChineseToPinyin(chinese, fullPyLen, type)
js
```
//fullPyLen: 前几个字转换为全拼音
//type : 1:驼峰(默认),2:全大写,3:全小写
var pinyin = V8.ChineseToPinyin('你好吾码', 2, 1);//结果:NihaoWM
```
## V8.RefreshTable({ _PageIndex : 1 })
- 刷新表格数据列表,_PageIndex传入-1表示跳转到最后一页。
- 一般用于页面更多按钮、行更多按钮等刷新当前表格。
- V8.RefreshTable 刷新当前 V8 所属的列表;V8.TableRefresh(子表字段, 参数) 用于刷新当前主表单里的指定子表格。
## V8.Router.Push
- 页面跳转,可以在V8按钮上执行
js
```
V8.Router.Push(`/notice`)
```
## V8.Window.Open
- 打开新页面,如:
js
```
V8.Window.Open(`https://microi.net`)
```
## V8.OpenForm(formModel, type)
- 打开表单,type:'View'/'Edit'/'Add',如在[行更多V8按钮]事件中:
js
```
V8.OpenForm(V8.Form, 'Edit')
```
## V8.OpenFormWF(formModel, type)
- 打开带流程信息的表单。(目前是获取此数据对应的最后一个流程)
## V8.SelectedData
- 列表批量按钮中获取已选择的行数组,每行包含当前列表已查询的数据。兼容别名为 V8.TableRowSelected;非列表上下文不保证存在
js
```
//批量删除数据
var selectData = V8.SelectedData;
if(selectData.length == 0){
V8.Tips('请选择要删除的数据!', false);
return;
}
V8.ConfirmTips(`确认批量删除选中的[${selectData.length}]条数据?`, async function(){
var ids = selectData.map(item => { return item.Id });
var result = await V8.FormEngine.DelFormData('diy_order', {
Ids : ids
});
if(result.Code != 1){
V8.Tips('删除失败:' + result.Msg, false);
return;
}
V8.Tips('删除成功!');
V8.RefreshTable({ _PageIndex : 1 })
});
```
## V8.SearchSet
- 列表/PageTabs 替换搜索条件。数组按 _Where 处理;对象会转换为各字段的 Like 条件
js
```
V8.SearchSet([
['Age', '>=', 18],
['Age', '<', 50]
]);
// 或:
V8.SearchSet({ Status: '待办' });
```
## V8.SearchAppend
- 列表/PageTabs 追加搜索条件。数组追加 _Where;对象合并到当前搜索模型
js
```
V8.SearchAppend([
['Age', '>=', 18],
['Age', '<', 50]
]);
// 或:
V8.SearchAppend({ OwnerId: V8.CurrentUser.Id });
```
## V8.AppendSearchChildTable【建议使用V8.OpenTableSetWhere】
- 弹出表格的[弹出前V8事件代码]中为表格指定搜索条件
js
```
V8.AppendSearchChildTable(V8.Field.XuanzeGLSP, { ShangpinLXZ: '1'});
```
## V8.OpenTableSetWhere
- 弹出表格的[弹出前V8事件代码]中为表格指定搜索条件
- 该条件属于弹出表格的固定查询范围。弹出表格内继续使用搜索、高级筛选或切换分页时,固定条件仍会以 AND 方式参与查询,不会被同字段的临时筛选覆盖。
js
```
V8.OpenTableSetWhere(V8.Field.XuanzeGLSP, [
['ShangpinMC', 'Like', '商用直饮机']
]);
```
## V8.IsNull(value)
- 判断某个值是否为空
- 当值为null、undefined、''(空字符串)、'null'(null字符串)、'undefined'(undefined字符串),均返回true
## 父表中对子表操作
javascript
```
V8.TableSearchAppend(V8.Field.子表Name, {FiedlName : value, FieldName2 : value})
V8.TableSearchSet(V8.Field.子表Name, {FiedlName : value, FieldName2 : value})
V8.TableRefresh(V8.Field.子表Name, { _PageIndex : -1 })
_PageIndex传入-1表示跳转到最后一页。(注意与【V8.RefreshTable】不同的是它一般是用于模块引擎中行更多按钮、页面更多按钮刷新当前表格,将来会优化函数命名)。
```
## V8.FormSubmit
- 提交表单,注意:此函数会触发前端表单提交前V8事件,因此不能在前端表单提交前V8事件调用此函数,否则会死循环。
js
```
V8.FormSubmit({
CloseForm: true, //是否关闭Form表单
SavedType:'Insert', //保存表单后的操作Insert/Update/View
Callback: function (result) {
if (result && result.Code == 1) {
V8.Tips('保存成功', true);
}
}
});
```
## V8.FormClose
- 强制关闭表单
js
```
V8.FormClose();
```
## V8.ParentV8
- 子表中访问父表的V8对象,可使用父表V8对象的所有功能
js
```
var parentForm = V8.ParentV8.Form;//访问父级表单所有字段
V8.ParentV8.FormSet('字段名', '值');
```
## V8.AddSysLog
- 新增日志
js
```
V8.AddSysLog({
Title : '库存同步',
Type : 'SyncStock',
Content : '张三调用了库存同步接口,同步后库存为100。'
})
```
## V8.ReloadForm
- 重新加载当前表单
js
```
V8.ReloadForm({ Id : 'xxxx-xxxx-xxxx'}, 'Edit/View' );//以编辑或预览模式重新加载当前表单
```
## V8.HideFormBtn
- 隐藏编辑、删除、新增按钮
js
```
V8.HideFormBtn('Update');
V8.HideFormBtn('Delete');
V8.HideFormBtn('Save');
```
## V8.HideFormTab(tabName)
- 隐藏某个表单Tab标签页
js
```
V8.HideFormTab('tabName(在表单属性中配置的Tab名称)')
```
## V8.ShowFormTab(tabName)
- 显示某个表单Tab标签页
js
```
V8.ShowFormTab('tabName(在表单属性中配置的Tab名称)')
```
## V8.ClickFormTab(tabName)
- 选中某个表单Tab标签页
## V8.GetFormTabs
- 获取表单所有Tab标签页。
## V8.ConfirmTips
- 确认提示框
javascript
```
例:V8.ConfirmTips('确认审批?', okCallback, cancelCallback, option)。
`option` 除了 `Title`、`OkText`、`CancelText`、`Icon` 外,还支持 `CustomClass` 自定义弹窗样式,以及 Element Plus 兼容的 `BeforeClose(action, instance, done)` 关闭前校验。文件上传、表格、Tab、步骤条等复杂交互仍应优先使用 `V8.OpenAppDialog`;`BeforeClose` 主要用于极少量输入或老环境兼容兜底。
//option为可选参数,可配置:{Title:'',OkText:'',CancelText:'',Icon:''}
```
ConfirmTips 内部会按 HTML 渲染 content。只有完全可信或经过 HTML 转义的数据才能拼接进去,严禁直接插入用户输入、接口返回文本、数据库富文本或 URL。复杂交互不要拼 HTML,使用 V8.OpenAppDialog。
- 自定义 HTML 仅用于简单、一次性的可信展示,如下图所示:
[V8 确认提示框默认样式: https://file.microi.net/upload/img/v8-confirm-tips.png] | [V8 确认提示框自定义样式: https://file.microi.net/upload/img/v8-confirm-tips-2.png] |
动态html参考代码
JS
```
// @cham 2026-04-30 快捷报工保存后,完成品弹出确认跳转入库
if (V8.FormOutAction == 'Insert'
&& V8.Form._GongDanLX == '生产工单'
&& V8.Form._LinshiGX != 1) {
// ConfirmTips 按 HTML 渲染,所有业务值必须先转义
var escapeHtml = function (value) {
return String(value == null ? '' : value)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
};
// 查询工位任务,判断是否为完成品(末道工序 ShifouMDGX == 1)
var gwrwRes = await V8.FormEngine.GetFormData('diy_gwrw', {
Id: V8.Form.GongweiRWID
});
if (gwrwRes.Code == 1 && gwrwRes.Data && gwrwRes.Data.ShifouMDGX == 1) {
// 附带查询条件跳转到本次报工的【快捷报工单】
// BaoGongD 为服务端自动生成,OutFormV8 的 V8.Form 中可能为空,需回查
var bgRes = await V8.FormEngine.GetFormData('diy_baogong', {
Id: V8.Form.Id,
_SelectFields: ['Id', 'BaoGongD']
});
var baoGongDan = (bgRes.Code == 1 && bgRes.Data) ? bgRes.Data.BaoGongD : '';
// 查询本次报工生成的箱码列表
var xmRes = await V8.FormEngine.GetTableData('diy_kjbgxm', {
_Where: [['DangqianBGDID', '=', V8.Form.Id], ['IsDeleted', '=', 0]],
_SelectFields: ['Xiangma', 'CunhuoMC', 'Tuhao', 'ZhuangxiangSL', 'RukuZT']
});
var xmList = (xmRes.Code == 1 && xmRes.Data) ? xmRes.Data : [];
// 构建箱码明细表格 HTML
var tdStyle = 'style="padding:4px 8px;border:1px solid #ddd;white-space:nowrap"';
var thStyle = 'style="padding:4px 8px;border:1px solid #ddd;background:#f5f5f5;white-space:nowrap"';
var rows = xmList.map(function(item, idx) {
return '
'
+ '| ' + (idx + 1) + ' | '
+ '' + escapeHtml(item.Xiangma || '') + ' | '
+ '' + escapeHtml(item.CunhuoMC || '') + ' | '
+ '' + escapeHtml(item.Tuhao || '') + ' | '
+ '' + escapeHtml(item.ZhuangxiangSL || 0) + ' | '
+ '' + escapeHtml(item.RukuZT || '-') + ' | '
+ '
';
}).join('');
var html = ''
+ '
报工单号:' + escapeHtml(baoGongDan) + ',共生成 ' + xmList.length + ' 个箱码,是否跳转到快捷报工单进行入库?
'
+ '
'
+ '
'
+ ''
+ '| 序号 | '
+ '箱码 | '
+ '存货名称 | '
+ '图号 | '
+ '装箱数量 | '
+ '入库状态 | '
+ '
'
+ '' + rows + ''
+ '
'
+ '
';
V8.ConfirmTips(
html,
function () {
V8.Router.Push('/baogongdan?Keyword=' + encodeURIComponent(baoGongDan));
},
function () { /* 用户取消 */ },
{
Title: '入库提示',
OkText: '前往入库',
CancelText: '稍后处理',
Icon: 'icon-exclamation-circle'
// Width: '780px'
}
);
}
}
```
## V8.ShowTableChildHideField
- 将子表已隐藏的字段强制显示出来,并且刷新子表。
js
```
V8.ShowTableChildHideField('子表fieldName',['fieldName','fieldName']);
V8.RefreshChildTable(fieldModel, V8.Row);//刷新子表
V8.RefreshChildTable(V8.Field.子表列名, V8.Row);//第二个参数可传入parentFormModel。
```
## V8.GetChildTableData
js
```
var data = V8.GetChildTableData('子表字段名称');
```
## V8.CurrentTableData
- 获取当前表当页的数据
## 表格/表单 V8 模板引擎
- 表格模板逐行同步执行,V8.EventName='TableTemplateEngine',V8.Form/V8.Row 是当前行。输出优先级为 V8.Result → JavaScript return → 原字段值。
- 表单模板异步执行,V8.EventName='FormTemplateEngine',使用 V8.Result 输出;空值会回退到原字段值。
- 模板能访问的字段取决于当前菜单查询列。缺字段应补模块查询列或在后端 DataFilterV8 预处理,不要在逐行模板中调用 FormEngine 造成 N+1。
- 模板结果最终通过 v-safe-html/DOMPurify 净化后渲染。script、iframe、on* 事件属性、javascript: 等危险内容会被移除,因此不要依赖 onclick 等内联事件。
- 输出建议显式转换为字符串,并对业务文本做 HTML 转义。模板只负责展示;权限、校验、金额/状态写入等业务逻辑必须放后端。
js
```
var escapeHtml = function (value) {
return String(value == null ? '' : value)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
};
V8.Result = ''
+ escapeHtml(V8.Form.Status)
+ '';
```
## V8.WF.StartWork
- 发起流程
javascript
```
V8.WF.StartWork({
FlowDesignId:'',//流程图Id,必传
FormData:JSON.stringify({}),//可选,也可以传入{} object类型,内部会自动序列化
TableRowId:'',//关联的数据Id,必传
NoticeFields:JSON.stringify([]),//通知数据,可选,格式:[{Id:'字段Id',Name:'字段名',Label:'字段名称',Value:'值'}],如果是数组类型,内部会自动序列化
//还可以传入选择的下一步审批人、添加的审批人、审批意见 等等
}, function(result){//这是回调函数处理,result返回了Receivers、ToNodeName等
if(result.Code == 1){
V8.Tips('发起流程成功!');
}
});
```
## V8.Notification
PC 前端 V8 提供统一通知方法:
js
```
// 发送:调用 msg_event,由消息策略决定平台内部、公众号、短信或邮件渠道
await V8.Notification.Send('order_wait_approve', {
EventId: 'order-wait-approve-' + V8.Form.Id,
ReceiverUserIds: [V8.Form.ApproverId],
Content: '订单等待审批',
LinkUrl: '/#/orders/detail?id=' + V8.Form.Id
});
// 当前登录用户的持久通知
var result = await V8.Notification.List({ PageIndex: 1, PageSize: 20 });
// 本人单条/全部已读
await V8.Notification.MarkRead(result.Data[0].Id);
await V8.Notification.MarkRead({ All: true });
```
List、MarkRead 分别调用 msg_internal_list、msg_internal_mark_read,服务端只信任 V8.CurrentUser.Id。SignalR 固定事件 ReceivePlatformNotification 只是低延迟刷新提示;启动、打开通知中心和重连后仍会回读列表,不能用实时事件替代数据库事实。
完整配置、表结构、后端调用、幂等和多节点说明见消息通知 (https://microi.net/doc/system-engine/message-notification.html)。
## V8.SendSystemMessage
兼容聊天系统的旧方法。新业务通知使用 V8.Notification,才能获得多渠道策略、持久事件日志、幂等和已读状态。
- 发送系统消息、消息提醒
展开查看 JavaScript 代码(25 行)
js
```
//消息内容
var msgContent = '测试v8发送系统消息!' + new Date().toString();
//内容增加路由跳转
msgContent += '测试页面跳转';
//发送系统消息
V8.SendSystemMessage({
Content: msgContent,
ToUserId: 'c74d669c-a3d4-11e5-b60d-b870f43edd03',//admin //'c19e70d1-b7b3-4eaa-933d-e8f59c85562f' anderson
}, function(result){
V8.Tips(JSON.stringify(result), true, 20);
});
//后端接口引擎可以这样使用:
return V8.Http.Post({
Url : V8.SysConfig.ApiBase + '/api/DiyChat/SendSystemMessage',
PostParam : {
Content : `测试接口引擎通过https发送系统消息!测试页面跳转 发送时间:${V8.Action.GetDateTimeNow()}`,
ToUserId: 'c74d669c-a3d4-11e5-b60d-b870f43edd03'//给admin帐号发送一条消息
},
Headers : {
authorization : 'Bearer ' + V8.Method.GetCurrentToken().Token
}
});
```
## V8.FormWF
- 访问当前是否打开了带流程界面的表单,返回值:
js
```
{
IsWF:true/false, //是否打开了带流程界面的表单
WorkType:'',//StartWork、ViewWork
FlowDesignId:'流程图Id'
}
```
## V8.Base64
- Base64 编码/解码(不是加密,不能用于保护密码或密钥)
js
```
V8.Base64.encode('待编码字符串');//编码
V8.Base64.decode('待解码Base64');//解码
V8.Base64.isValid('待判断字符串');//判断是否是有效 Base64
```
## V8.OpenDialog
- 打开一个定制组件对话框
展开查看 JavaScript 代码(28 行)
javascript
```
V8.OpenDialog({
ComponentName:'NodeColConfig',//必传,其余参数可选。组件名称,二次开发必须提前预注册。
Title: '测试定制组件标题',
OpenType:'',//可传:Drawer
TitleIcon: 'fas fa-plus',//标题左侧的图标
Width: '70%',
DataAppend:{//传入自定义附加数据,DataAppend为固定参数名称
Abc:123,
Name:'张三'
}
});
//在定制组件内props接收V8对象:
props: {
DataAppend:{
type: Object,
default: () => {}
}},
mounted() {
//this.DataAppend.V8包含了绝大部分可使用的V8内置函数,可以使用V8事件一样的写法。
//获取已打开的表单中(或已选中的一条表格数据)的Title字段值。
var title = this.DataAppend.V8.Form.Title;
//访问传过来的自定义数据
var name = this.DataAppend.Name;//张三
//刷新diy表格
this.DataAppend.V8.RefreshTable();
//关闭当前对话框
this.DataAppend.V8.CloseThisDialog();
}
```
- 通用打开iframe
js
```
V8.OpenDialog({
ComponentName:'OpenIframe',//必传,其余参数可选。组件名称,二次开发必须提前预注册。
Title: '打印',
OpenType:'Drawer',//可传:Drawer
TitleIcon: 'fas fa-plus',//标题左侧的图标
Width: '800px',
DataAppend:{//传入自定义附加数据,DataAppend为固定参数名称
Url:'/autoprint/#/doprint',
PrintId:'27833304-caeb-4665-b722-808fd3663bb1',
DataApi: `${V8.SysConfig.ApiBase}/apiengine/print_xm?OsClient=${V8.SysConfig.OsClient}&Id=${ids}`
}
});
```
### 使用 V8.OpenDialog 打开预注册定制组件
平台内置或二次开发的 Vue 组件先在主前端注册,再由模块按钮通过通用 V8.OpenDialog 打开。以下是【系统账号】访问密钥面板的配置示例:
js
```
var user = V8.Form || {};
if (!user.Id) {
V8.Tips("缺少用户Id,无法管理访问密钥。", false);
return;
}
V8.OpenDialog({
ComponentName: "UserAccessKeyPanel",
Title: "访问密钥 - " + (user.Name || user.Account || ""),
TitleIcon: "fas fa-key",
Width: "min(980px, calc(100vw - 32px))",
OpenType: "Dialog",
DataAppend: { User: user }
});
```
入口仍配置在目标模块的 sys_menu.MoreBtns 中;组件用 DataAppend 接收业务数据。不要为单个业务面板再新增一个并不存在的 V8 打开方法,也不要在通用表格/卡片模板中按表名、菜单 Id 或路由写死按钮。若页面需要独立部署、跨应用升级或不应进入主前端,则改用 V8.OpenAppDialog 打开已发布微服务。
按钮显隐只用于界面体验,访问密钥创建、查询和吊销仍由服务端逐次校验普通登录会话以及本人/平台管理员权限。
## V8.OpenAppDialog
- 在表格、表单、页面按钮等前端 V8 代码中,以标准 Dialog/Drawer 打开一个已经发布的在线微服务应用。适合复杂交互页面,可避免把大量 HTML、CSS、JavaScript 直接写进按钮 V8 代码。
### 详情替换与表单初始化的边界
点击详情直接由微服务接管时,将代码配置在实际入口模块的 sys_menu.DetailPageV8,隐藏子表模块同样使用自己的该字段。diy_table.InFormV8 是原生表单已打开后的初始化阶段,用来配置字段、选项、显隐与默认值;在这里另开微服务,原生窗口已经出现。
列表与完整表单入口统一采用“配置即替换默认详情”:配置 DetailPageV8 后会等待脚本(包括 await 的准备工作),由脚本接管打开;返回 undefined 或 { Code: 1 } 都不会再隐式打开原生详情。return false、return { Code: 0, Msg: '取消原因' },或给 V8.Result 赋相同值,表示取消其它 UI。错误会终止本次打开并提示,不以原生表单掩盖错误。
没有详情脚本时,普通详情和 Add/Edit 继续原生。已配置脚本但希望打开原生详情时,调用既有 V8.OpenForm(V8.Form, 'View');平台内部沿用 isDefaultOpen===true 跳过本次替换,避免递归。不要新增继续原生的配置开关,也不要把 undefined 当作继续原生协议。微服务替换代码在 V8.OpenAppDialog(...) 后显式 return false,同时兼容旧 PC。
关闭、切换记录/菜单/API/租户/用户、重新初始化或销毁后,当前事件的迟到开窗会被丢弃。运行时只包装本次 V8,不修改全局或 ParentV8 方法;旧逻辑若通过 ParentV8 开窗,应保留父上下文、明确取消原生打开,并自行检查异步父副作用的当前性。迁移仅移动原 View 接管段,保留 Add/Edit 初始化,不把整段 FormIn 提前执行。实际菜单、子表上下文和后端记录权限始终保留,前端传来的 Id/TaskId 不是授权凭据。
js
```
V8.OpenAppDialog({
AppKey: 'saas_tenant_creator',
RoutePath: '/create',
Title: '创建空数据库 SaaS 租户',
TitleIcon: 'fas fa-database',
Width: 'min(960px, calc(100vw - 32px))',
OpenType: 'Dialog',
Data: {
source: 'osclients',
osClientNetwork: 'Internal'
},
// 可省略:默认继承当前菜单。跨模块操作时传入真实、已授权的模块 Key。
ModuleEngineKey: 'authorized-module-key',
OnSuccess: function (data) {
V8.Tips('创建任务已提交', true);
V8.RefreshTable({ _PageIndex: -1 });
},
OnCancel: function (data) {
console.log('用户取消', data);
},
OnError: function (error) {
V8.Tips(error.message || '应用加载失败', false);
}
});
```
### 参数说明
参数 | 类型 | 必传 | 默认值 | 说明 |
AppKey | string | 是 | - | 在线微服务的唯一标识,对应 sys_microiservice.MsKey。应用必须已经编译发布。 |
RoutePath | string | 否 | / | 微服务内部路由,例如 /create。未以 / 开头时会自动补齐。 |
MicroRoute | string | 否 | / | RoutePath 的兼容别名;两者同时传入时优先使用 RoutePath。 |
Version | string | 否 | 当前发布版本 | 缓存版本标识,例如 v1.0.2。宿主始终访问固定的 /{AppKey}/index.html?v={Version} 入口;不传时自动读取 sys_microiservice.BuildVersion。 |
Title | string | 否 | 应用 | 弹窗或抽屉标题。 |
Header | object | 否 | - | 初始外层标题栏 { title, tags, actions }。标签最多 6 个,按钮最多 8 个;只传可序列化文本、标识和状态。 |
OnHeaderAction | function(actionId, V8) | 否 | - | 可选的调用方前端 V8 按钮逻辑。若未提供,点击动作会传给子微服务处理。 |
TitleIcon | string | 否 | fas fa-window-maximize | 标题左侧图标 class。 |
Width | string | 否 | min(920px, calc(100vw - 32px)) | 弹窗/抽屉宽度,支持 px、%、vw、min(...) 等 CSS 宽度值。 |
OpenType | string | 否 | Dialog | 打开方式:Dialog 或 Drawer。 |
Data | object | 否 | {} | 传给子应用的业务数据。应使用可序列化的普通对象,不要在其中放回调函数。 |
SysMenuId | string | 否 | 当前调用菜单 | 菜单权限上下文;一般无需手工传入。 |
ModuleEngineKey | string | 否 | 当前调用模块 | 目标模块引擎 Key。跨模块调用时应显式使用真实、已授权的 Key,不能用表名代替。 |
DiyTableId | string | 否 | 当前调用表 | 目标表 Id,仅作上下文,不能代替后端权限校验。 |
OnSuccess | function(data) | 否 | - | 子应用提交成功时执行;执行后宿主会自动关闭弹窗。 |
OnCancel | function(data) | 否 | - | 子应用主动取消时执行;执行后宿主会自动关闭弹窗。 |
OnError | function(error) | 否 | - | 应用加载失败或子应用上报错误时执行;错误不会自动关闭弹窗。 |
### 子应用获取宿主参数
宿主会自动向微服务传入当前环境,不需要把 Token 拼接到 URL:
嵌入平台的微服务应复用宿主会话,不再次展示账号密码登录表单。需要确认会话时调用 platform-current-user;任务、订单或内容的业务接口可能因缺少职责、站点范围或配置而拒绝请求,这些错误不能被当作未登录。应显示原始业务原因并提供重试,保留当前会话。只有认证失效响应才进入会话恢复,由吾码主站完成重新登录。子应用收到 SDK 续签 Token 时通过 micro-app:token 回传新 Token 和对应的 requestToken,避免宿主旧快照覆盖新会话;不在 URL、日志或错误提示中输出 Token。
js
```
var hostData = window.microApp.getData();
// 当前环境
console.log(hostData.apiBase);
console.log(hostData.osClient);
console.log(hostData.token);
console.log(hostData.permissionContext);
// OpenAppDialog 参数
console.log(hostData.appKey);
console.log(hostData.version);
console.log(hostData.microRoute);
console.log(hostData.dialog); // true
console.log(hostData.dialogHeaderBridge); // true,宿主支持外层标题栏动态控制
console.log(hostData.dialogData); // 即宿主传入的 Data
```
子应用字段 | 说明 |
apiBase | 当前吾码后端地址。 |
osClient | 当前租户标识。 |
token | 当前登录 Token,供子应用请求吾码接口时使用。 |
menuId | 当前调用菜单 Id。 |
moduleEngineKey | 当前或显式指定的模块引擎 Key。 |
diyTableId | 当前或显式指定的表 Id。 |
permissionContext | { sysMenuId, moduleEngineKey, diyTableId };用于选择正确的模块调用上下文,不是授权凭证。 |
appKey | 当前微服务 AppKey。 |
version | 实际加载的构建版本。 |
microRoute | 实际打开的微服务内部路由。 |
dialog | 固定为 true,用于让应用识别弹窗运行模式。 |
dialogHeaderBridge | 为 true 时,子应用可隐藏内部工具标题栏,将标题、状态标签和操作按钮派发给外层弹窗。 |
dialogData | V8.OpenAppDialog 的 Data 参数。 |
route | 路由兼容对象,包含 microRoute、microRoutePath。 |
### 子应用返回结果
子应用可动态更新外层标题栏;按钮点击由宿主以 host:dialog-action 数据消息回传,包含 actionId 和唯一 requestId。子应用需依据当前数据和权限重新判定可执行性,不能把按钮显隐当成授权。旧版宿主未提供 dialogHeaderBridge 时,应继续显示自身工具栏。
js
```
window.microApp.forceDispatch({
type: 'app-dialog:header',
data: {
title: '加工任务单-2026-001',
tags: [{ text: '待审核', tone: 'warning' }],
actions: [{ id: 'approve', text: '审核', tone: 'success', disabled: false }]
}
});
window.microApp.addDataListener(function (data) {
if (data.type === 'host:dialog-action' && data.actionId === 'approve') {
// 在此执行子应用动作,并再次向可信后端核验权限。
}
});
```
微服务通过 micro-app 的数据通信协议向宿主派发结果:
js
```
// 成功:触发 OnSuccess,并自动关闭
window.microApp.dispatch({
type: 'app-dialog:success',
data: { taskId: '01H...', osClient: 'customer_a' }
});
// 取消:触发 OnCancel,并自动关闭
window.microApp.dispatch({
type: 'app-dialog:cancel',
data: { reason: 'user-cancel' }
});
// 失败:触发 OnError,弹窗保持打开,便于用户修改后重试
window.microApp.dispatch({
type: 'app-dialog:error',
data: { message: 'OsClient 已存在' }
});
```
同时兼容简写类型 success、cancel、error。
### 与 V8.OpenDialog 的区别
API | 适用场景 |
V8.OpenAppDialog | 按 AppKey 动态加载在线 AI 应用/微服务;应用可以独立维护、AI 生成、编译和发布。 |
V8.OpenDialog | 打开 Microi.Client 源码中已经注册的 Vue 组件,需要前端二次开发和重新发布主站。 |
复杂定制界面优先使用 V8.OpenAppDialog;按钮 V8 代码只负责传参、接收结果和刷新页面,数据校验及业务事务仍应放在接口引擎或后端服务中。子应用提示“没权限”时,应先核对登录 Token、OsClient、ModuleEngineKey 和角色授权,禁止删除权限参数、改成匿名接口或把管理员 Token 写入前端。
## V8.OpenImportDialog
在数据表格的页面按钮 V8 中打开平台统一导入弹层。通用【导入】与本 API 共用同一套智能识别、人工校正和预览界面:弹层默认占页面宽度的 80%,支持 .xls、.xlsx、.csv(CSV 自动识别 UTF-8/GBK 和常见分隔符),自动识别工作表、单行或多级合并表头、首条数据行及字段映射;上传后先按每页 15 条预览,不会立即写库。识别可信度低时,用户可人工指定表头起止行、数据起止行和逐列映射,确认后才提交。
数据预览与字段映射解耦:即使未匹配到当前表的任何字段,也会显示识别数据范围内的全部源列和源行,未匹配列头以红色显示,悬停可查看原因。【列映射】之后的【原始工作簿】页签始终保留完整源文件视图,可核对所有工作表、复杂/合并表头、图片、说明文字、样式和原始数据;它不受自动识别结果或字段匹配率影响。
为兼容 OpenPyXL 等工具生成的等价 DrawingML 默认命名空间,前端会在内存中为预览副本规范化图片命名空间;正式提交的仍是原始文件,规范化结果不会进入导入数据或替换上传文件。
js
```
V8.OpenImportDialog({
Title: '项目材料自定义导入',
Description: '读取固定模板并在后台完成校验、匹配和写入。',
ApiEngineKey: 'project_material_custom_import',
TaskTitle: '项目材料自定义导入',
ErrorPolicy: 'RollbackAll', // 初始值;用户仍可在弹层改为 ContinueOnError
Param: {
TemplateVersion: '2026-08'
},
Workbook: {
SheetIndex: 0,
HeaderStartRow: 5,
HeaderEndRow: 6,
Cells: {
ProjectText: 'A4'
},
Columns: [
{ Name: 'LineNo', Column: 'A' },
{ Name: 'Specification', Column: 'C' },
{ Name: 'Material', Column: 'D' },
{ Name: 'Weight', Column: 'G' }
],
DataStartRow: 7,
DataEndRow: 1000,
KeyField: 'LineNo'
},
BackgroundOptions: {
ConcurrencyKey: 'project-material-custom-import',
MaxAttempts: 1
}
});
```
参数 | 类型 | 必传 | 说明 |
ApiEngineKey | string | 是 | 后台执行的接口引擎 Key。 |
Title / Description / TaskTitle | string | 否 | 弹层标题、说明和后台任务标题。 |
ErrorPolicy | string | 否 | 初始错误策略:RollbackAll(默认,任一错误整批回滚)或 ContinueOnError(跳过错误行并继续);用户可在弹层中修改。 |
Param | object | 否 | 追加给接口引擎的固定参数。 |
Workbook.SheetIndex / SheetName | number/string | 否 | 工作表索引或名称,默认第一张;用户仍可在预览中切换。 |
Workbook.Cells | object | 否 | 元数据名称到 A1 单元格地址的映射。 |
Workbook.Columns | array | 否 | 目标字段名到 Excel 列字母或零基列号的优先映射;省略时自动按字段名/标题识别,也可由用户人工映射。 |
HeaderStartRow / HeaderEndRow | number | 否 | 一基表头起止行;可表达多级表头。省略时自动识别。 |
DataStartRow / DataEndRow | number | 否 | 一基数据起止行;省略时自动识别首条与末条数据。 |
KeyField | string | 否 | 为空时跳过该行的业务键字段。 |
MaxFileSizeMB / MaxRows / MaxColumns | number | 否 | 客户端文件、有效数据行和列数上限,后台自定义任务默认 20 MB / 5000 行 / 256 列。 |
BackgroundOptions | object | 否 | 传给 RunBackground 的并发、重试等选项。 |
接口引擎从 V8.Param._ImportRowsJson、_ImportMetaJson、_ImportErrorPolicy、_ImportUniqueRulesJson、_ImportFileName 和 _ImportFileSize 读取数据。_ImportMetaJson 版本为 2.2,除 SheetIndex/SheetName/HeaderStartRow/HeaderEndRow/DataStartRow/DataEndRow/Columns/Cells/RowCount/Confidence/FileType/Encoding/Delimiter 外,还包含:
- ErrorPolicy:用户最终确认的 RollbackAll 或 ContinueOnError。
- UpsertMode: 'ByTableUniqueRules'。
- UniqueRules:弹层展示的单字段、组合唯一规则快照。
UniqueRules 只能用于界面说明和协议诊断,不能作为权限或数据完整性的事实源。接口引擎必须重新读取当前表的权威字段配置:每个 Config.Unique.Type='Alone' 的唯一字段各自是一条规则,所有 Type='All' 字段共同组成一条组合规则。导入行只在一条规则的全部字段都有值时使用该规则;任一规则命中同一条已有记录则修改,全部未命中则新增;多条规则命中不同记录或同一规则命中多条脏数据时,该行必须报冲突,不能任意挑选记录。
_ImportRowsJson 只包含已映射、准备提交的字段,完整原始内容只用于前端核对,不能直接作为可信写入数据。业务端仍须校验模板、字段、菜单/表权限、数据状态和唯一规则,并通过 V8.Method.UpdateBackgroundTask({Current,Total,Msg,Log}) 上报新增、修改、失败及真实工作量。
错误策略由接口引擎显式执行:RollbackAll 遇到首个行错误应返回 Code:0,依靠接口引擎事务整体回滚;ContinueOnError 应捕获并记录行错误、继续下一行,最后返回 Code:1 使成功行提交。两种模式都禁止手动调用 Commit/Rollback。如果所用数据库或外部服务在某个异常后无法继续当前事务,自定义接口应先完整预校验,或把每行写入封装为平台允许的独立幂等操作,不能伪报“已跳过并继续”。页面 V8 不应读取 Base64、拼接上传 DOM或自行实现任务轮询。
js
```
var rows = JSON.parse(V8.Param._ImportRowsJson || '[]');
var meta = JSON.parse(V8.Param._ImportMetaJson || '{}');
var errorPolicy = V8.Param._ImportErrorPolicy || meta.ErrorPolicy || 'RollbackAll';
var errors = [];
var added = 0;
var updated = 0;
for (var i = 0; i < rows.length; i++) {
try {
// 这里必须按当前表权威 Unique/Config 重新解析规则并安全定位 existingId。
// existingId 存在则 UptFormData,不存在则 AddFormData;不得只信任 meta.UniqueRules。
var writeResult = writeOneRowWithAuthoritativeUniqueRules(rows[i]);
if (!writeResult || writeResult.Code !== 1) {
throw new Error((writeResult && writeResult.Msg) || '导入失败');
}
if (writeResult.Data && writeResult.Data.Updated) updated++; else added++;
} catch (error) {
errors.push({ ExcelRow: rows[i]._ExcelRow || i + 1, Msg: error.message || String(error) });
if (errorPolicy === 'RollbackAll') {
return { Code: 0, Msg: errors[0].Msg, Data: { Added: 0, Updated: 0, Failed: 1, Errors: errors } };
}
}
}
return { Code: 1, Data: { Added: added, Updated: updated, Failed: errors.length, Errors: errors } };
```
上例的 writeOneRowWithAuthoritativeUniqueRules 是业务接口应实现的封装名称,并非平台全局函数。
模块自带【导入】按钮也支持上述 Excel/CSV 自动识别、全源数据预览、原始工作簿、人工校正和列映射,并在确认后把原始文件及同一份解析范围交给服务端复核。只有需要固定单元格元数据、后台任务、跨表匹配或特殊业务事务时,才使用 OpenImportDialog;不要仅为复杂表头或 CSV 重复开发自定义上传界面。
菜单配置了【导入接口替换】(ImportApi)时,/apiengine/Import_StockArch 这样的相对地址按当前 ApiBase 拼接(保留 API 路径前缀);完整 HTTP(S) 地址和 $ApiBase$ 占位符继续兼容。上传请求沿用当前登录与租户上下文。如果出现 nginx 的 HTTP 405,应先核对请求是否误发到前端网站,而不能仅凭错误页排除前端地址拼接问题。
同步替换接口完成写入后返回 Code=1,未配置 ImportProgressApi 且未返回运行中状态时,弹层直接展示完成结果及 InputCount/AddedCount/UpdatedCount/FailedCount,不再查询平台标准导入的进度。异步替换接口应显式配置进度接口;标准导入仍保持原进度协议。明确失败后,可保留当前文件、映射和操作标识再次提交。请求超过 90 秒但仍未结束时,弹层提示等待并禁用重复提交;关闭窗口不能保证服务端停止写入,结果不明时应先核对数据。自定义接口仍须自行实现业务幂等,操作标识本身不能保证它不会重复写入。这些修复通过更新 PC 前端镜像生效,无需修改既有菜单地址或扩大权限。
## V8.NewGuid
- 生成前端唯一标识。当前实现返回 26 位 ULID,方法名称为兼容保留。
接收该值的接口应按实际格式校验,不能只接受带连字符的 36 位 GUID。 作为幂等键时,在业务记录与传输层使用同一规范化值,重试时复用原编号。
js
```
var newGuid = V8.NewGuid();
```
## await V8.NewServerGuid
- 生成一个服务器端Guid值
js
```
var newGuid = await V8.NewServerGuid();
```
## V8._
- 访问underscore对象,常用的js实用库
js
```
//underscore用法见:https://underscorejs.org/ https://underscorejs.net/
V8._.where(...)
```
## V8.ModuleEngine
- 当前标准前端 V8 运行时没有公开挂载 V8.ModuleEngine。模块关联查询应配置为接口引擎,或使用后端受控查询;不要仅因后端存在同名能力就在前端调用
## V8.ApiEngine
- 接口引擎
javascript
```
// 推荐:ApiEngineKey + 参数,返回 Promise
var result = await V8.ApiEngine.Run('ApiEngineKey', {
Param1 : '1',
});
// 对象参数形式
var result2 = await V8.ApiEngine.Run({
ApiEngineKey: 'ApiEngineKey',
Param1: '1'
});
// 历史回调形式继续兼容
V8.ApiEngine.Run('ApiEngineKey', { Param1: '1' }, function (r) {
V8.Tips(r.Code == 1 ? '执行成功' : r.Msg, r.Code == 1);
});
// 长任务:返回 Promise;第 4 个参数是持久化任务选项,第 5 个可传 callback
await V8.ApiEngine.RunBackground(
'ApiEngineKey',
{ Param1: '1' },
'后台任务标题',
{
IdempotencyKey: 'import:' + V8.Form.Id,
ConcurrencyKey: 'customer-import',
BusinessTable: 'biz_import',
BusinessId: V8.Form.Id,
BusinessStatusField: 'TaskStatus',
BusinessTaskIdField: 'BackgroundTaskId',
BusinessProgressField: 'TaskProgress',
BusinessEtaField: 'EstimatedEndTime'
}
);
```
预计超过 2 分钟、500 条、1000 个扇出子操作、100 次外部调用,或安装/初始化/迁移/备份类动作,应使用后台任务。未知总量不要伪造百分比;超过 10 分钟必须由后端按 checkpoint 分片。详见任务调度与后台任务 (https://microi.net/doc/system-engine/job.html)。
RunBackground 返回 Code=1 表示任务已持久化入队,Data 只包含 Id、状态、进度、时间、消息及 HasLog/HasResult 等摘要。完整日志和结果应通过通知中心详情,或 /apiengine/platform-background-task 的 Detail 动作按任务 Id 读取;不要依赖提交响应中的任务参数、身份上下文、检查点或完整结果。重复提交同一逻辑操作应复用 IdempotencyKey,并等待任务达到 Succeeded/100% 后再确认业务完成。
## V8.DataSourceEngine
- 历史数据源引擎兼容入口。Run 返回 Promise,并兼容回调;旧 GetData 已弃用
- 新版本将数据源迁移为带 DataSourceType 的接口引擎,推荐新代码直接使用 V8.ApiEngine.Run。本入口和旧 Controller 地址仍会解析旧 DataSourceKey,并执行迁移后的接口引擎
js
```
var result = await V8.DataSourceEngine.Run('DataSourceKey', {
Keyword: '测试'
});
var result2 = await V8.DataSourceEngine.Run({
DataSourceKey: 'DataSourceKey',
Keyword: '测试'
});
V8.DataSourceEngine.Run('DataSourceKey', {}, function (r) {
console.log(r);
});
```
## V8.OpenAnyForm
- 打开一个任意表单
展开查看 JavaScript 代码(26 行)
javascript
```
V8.OpenAnyForm({
TableName: "Diy_BizOrder", //必传。打开哪张表。
FormMode: "Edit", //必传。打开的模式:Add、Edit、View
Id: V8.Form.Id, //当FormMode为Edit、View时,必传Id。
DialogType: "Dialog", //可选。打开的方式,不传则默认为表单属性设置的打开方式。
SelectFields: ["ZhipaiXX", "ShouhouRY"], //可选。只查询、显示哪些字段。不传则默认显示。
Width: "765px", //可选。弹出层的宽度。不传则默认为表单属性设置的弹出宽度。
DataAppend: {
//传入自定义附加数据,DataAppend为固定参数名称。可在指定的打开表单V8事件中使用V8.DataAppend访问。
Abc: 123,
Name: "张三",
},
//替换掉提交事件。可选。
EventReplace: {
//这3个参数一定会接收到,必须执行callback(DosResult)
Submit: async function (v8, param, callback) {
//调用指派接口
var result = await V8.ApiEngine.Run('shouhoudd_zhipai',{
Id: v8.Form.Id,
ShouhouRYID: v8.Form.ShouhouRYID,
ShouhouRY: v8.Form.ShouhouRY,
});
callback(result);
V8.RefreshTable({ _PageIndex: 1 });
},
},
});
```
OpenAnyForm 负责发起打开动作,不是“等待用户关闭后返回结果”的 Promise。需要替换保存行为时使用 EventReplace.Submit(v8, param, callback),其中小写 v8 是被打开的子表单上下文;外层 V8 仍是发起打开动作的父上下文。替换提交后必须调用 callback(DosResult)。
指派人员应提交准确的人员 Id;接口从当前租户主库复核账号状态、商家与权限,并回读姓名、电话和指派人快照。客户端传入的姓名、商家或缓存中的管理员等级不能作为授权依据。
## V8.OpenAnyTable
- 打开一个任意列表
展开查看 JavaScript 代码(28 行)
javascript
```
V8.OpenAnyTable({
SysMenuId: "69a9c7a9-7130-414e-a4f8-9f3690075d22", //SysMenuId、ModuleEngineKey必传一个,打开哪个菜单。
//ModuleEngineKey: "modelKey",
DialogType: "Drawer", // 可选:Dialog / Drawer,默认 Dialog
Width: "80vw", // 可选:支持 80%、80vw、960px、960,数字会按 px 处理
Direction: "rtl", // 可选:Drawer 方向,支持 rtl/ltr/ttb/btt,默认 rtl
MultipleSelect: true, // 是否多选
PropsWhere : [
['FkId', '=', V8.Form.Id]
],//查询条件
ShowLeftSelectionList: true, //左侧选中列表是否显示,选true时下面这5条信息配置可用
ShowPrefix: true, //是否显示前缀
ShowTitleName: 'PeijianBH', //主标题
ShowSubTitleList: ['PeijianMC'], //副标题
ShowPageSize: 10, //显示条数
NoPullDown: false, //是否禁用下拉
SubmitEvent : async function (selectData,callback){//当前选择器提交按钮必传
var addList = [];
if (selectData.length == 0) {
V8.Tips('请选择数据');
V8.Result = false;
} else {
//调用指派接口
var result = await V8.ApiEngine.Run('ApiKeyName', {
Name: V8.Form.Name,
})
callback(result);
V8.RefreshTable({ _PageIndex: 1 });
}
}
})
```
DialogType 不传时继续使用弹窗模式;传 "Drawer" 时使用抽屉模式。Width 同时作用于弹窗宽度和抽屉尺寸,支持百分比、vw 和固定 px,纯数字会按 px 处理。列表型子表建议使用 "80vw" 或 "80%",窄表单类选择可使用 "960px"。
- SysMenuId 与 ModuleEngineKey 必须传一个,用于加载真实菜单/模块上下文;不要传与目标表无关的菜单 Id。
- SubmitEvent(selectData, callback) 在当前选择器提交模式中必须提供,调用 callback(...) 后宿主关闭。
- ContinuousSelection 控制跨页连续选择,TableMultipleSelection 可传入初始选中行,ShowDiyFieldList 可限制选择列表显示列。
- TableChildImportContext、父子表外键、_TableChildAuth 等属于平台内部关系上下文,不要在业务 V8 中手工构造。
## 表单按钮防重复点击
js
```
V8.FieldSet('YijianSCFBMX', 'Config.Button.Loading', true);
var result = await V8.ApiEngine.Run('ApiKeyName', {});
V8.FieldSet('YijianSCFBMX', 'Config.Button.Loading', false);
```
## V8.ClientType
- 访问当前客户端类型
js
```
//可能的值:PC、IOS、Android、H5、WeChat
var clientType = V8.ClientType;
```
## V8.SysConfig
- 访问当前租户允许公开给浏览器的系统设置脱敏投影
js
```
var sysTitle = V8.SysConfig.SysTitle;
var apiBase = V8.SysConfig.ApiBase;
var githubVisible = V8.SysConfig.DisableLoginGitHub !== 1;
var maskBlurEnabled = V8.SysConfig.FormMaskBlur === 1;
```
前端 V8.SysConfig 不是数据库原行,而是当前租户 sys_config 的浏览器安全投影;新增公开配置必须在 sys_config 创建实体字段。它不存在 PublicSettings 或 ServerPrivateSettings 属性,mci_system_setting 的任何普通值或 Secret 都不会注入浏览器。数据库、对象存储、MQ、搜索凭据、ClientSecrets、GlobalServerV8Code 等同样不会公开。需要业务密钥的逻辑必须放到后端接口引擎或后端 V8 事件中。
## V8.FormEngine
- 前端表单引擎 facade,用于受权限约束的单表 CRUD。完整查询参数见:FormEngine用法 (https://microi.net/doc/v8-engine/form-engine.html)
### 前端真实方法
前端 V8.FormEngine 不是后端方法的一比一映射。当前标准运行时公开:
方法 | 常用签名 | 返回 |
GetFormData | (table, params, callback?) / (params, callback?) | Promise |
GetFormDataAnonymous | 同上 | Promise |
GetTableData | 同上 | Promise |
GetTableTree | 同上 | Promise |
AddFormData | 同上 | Promise |
AddFormDataBatch | (rows, callback?) | Promise |
UptFormData | (table, params, callback?) / (params, callback?) | Promise |
UptFormDataBatch | (rows, callback?) | Promise |
UptFormDataByWhere | (table, params, callback?) / (params, callback?) | Promise |
DelFormData | (table, params, callback?) / (params, callback?) | Promise |
DelFormDataBatch | (rows, callback?) | Promise |
DelFormDataByWhere | (table, params, callback?) / (params, callback?) | Promise |
前端没有公开 GetTableDataCount、GetTableDataTree(前端名称为 GetTableTree)、AddTableData、UptTableData、DelTableData、AddField。这些名字可能存在于后端 V8,但不能直接复制到前端代码。
js
```
// 推荐 Promise/await
var listResult = await V8.FormEngine.GetTableData('Diy_Product', {
_Where: [['Status', '=', 1]],
_SelectFields: ['Id', 'Name', 'Status'],
_PageIndex: 1,
_PageSize: 20
});
// 历史回调形式继续兼容
V8.FormEngine.GetFormData('Diy_Product', { Id: V8.Form.ProductId }, function (r) {
if (r.Code == 1) {
V8.FormSet('ProductName', r.Data.Name);
}
});
// 对象参数形式
var rowResult = await V8.FormEngine.GetFormData({
FormEngineKey: 'Diy_Product',
Id: V8.Form.ProductId
});
```
### 菜单上下文、跨表兼容与性能
独立微服务内嵌 SDK 的 AddFormData(table, row) 会把第二参数整体封装为 _RowModel。需要同时传菜单上下文时,使用完整参数对象,避免把控制参数当作业务字段:
js
```
await V8.FormEngine.AddFormData({
FormEngineKey: 'Diy_Product',
_SysMenuId: menuId,
_RowModel: { Name: '测试商品' }
});
```
由平台生成主键时省略行模型的 Id,不要填空字符串。更新时仍传递用户读取到的期望版本,不能因 SDK 补读完整行而覆盖旧版本,从而绕过冲突检测。
- 当前 V8 目标表就是当前菜单绑定表时,前端 scoped facade 自动注入真实 _SysMenuId,历史项目不需要逐个补参数。
- 跨表调用不会错误继承当前主表菜单。未显式传菜单时,后端根据当前用户有效角色可访问的目标表菜单授权快照推断权限,兼容大量历史前端 V8。
- 显式传 _SysMenuId(或兼容的 ModuleEngineKey)会进入严格菜单校验;传错、伪造或借用其它表菜单必须失败,不能退回兼容推断。
- 授权快照按 OsClient 隔离,使用共享 Redis 版本号和带 TTL 的授权快照,并允许平台普通两级缓存加速;每次外部授权检查先读取共享版本,角色、菜单、表权限变更会递增版本使旧快照不可达。Redis 不可用时回源数据库,不能继续使用陈旧快照。
- 菜单 SqlWhere、关联限制和数据范围由后端强制追加。前端 _Where 只能缩小结果,不能覆盖服务端范围。
- 平台敏感表对普通客户端硬拒绝;不要用通用 FormEngine 读写 SaaS、接口引擎、表/字段元数据、菜单角色、用户、任务、数据源、工作流等控制面表。
- 后端接口引擎、后端表单 V8 属于服务端受信任调用,不要求 _SysMenuId;浏览器不能通过提交 _TrustedServerInvocation 或 _InvokeType:'Server' 把自己变成受信任调用。
- Import/Export 使用独立端点和专项权限,必须携带目标模块的真实菜单上下文;它们不是 V8.FormEngine facade 方法。
### TableChild 委托授权
标准 TableChild 会自动携带内部 _TableChildAuth 关系提示。该对象不是授权令牌,浏览器中的值不能被信任;服务端仍会重新加载父/子表、父/子菜单和 TableChild 字段配置,校验父记录数据范围、父键唯一性和子表外键,并强制写入真实外键条件。
业务 V8 不得手工构造、缓存、跨父记录复用或向其它表传播 _TableChildAuth。存量项目也不需要给每个隐藏子表菜单逐角色补权限,合法子表访问由上述父记录范围内的委托授权完成。
## 强身份验证(V8.Identity)
V8.Identity 在登录后的表单/列表 V8 中提供受控的 Passkey、Authenticator TOTP、设备生物识别和严格人脸交互:
方法 | 说明 |
GetCapabilities(callback?) | 读取当前租户开关,以及本人是否已登记可用于二次授权的 Passkey/TOTP/人脸 |
CreateActionHash(value, callback?) | 对稳定字符串或 JSON 计算 SHA-256 操作摘要 |
RegisterPasskey(options?, callback?) | 为当前登录用户登记 Passkey |
Verify(options, callback?) | 完成敏感操作验证并返回一次性 Ticket;Method=Totp 时传当前 6 位 Code |
javascript
```
var actionHash = await V8.Identity.CreateActionHash({
Version: 1,
Action: 'ApproveContract',
Id: V8.Form.Id
});
var result = await V8.Identity.Verify({
Purpose: 'ApproveContract',
ActionHash: actionHash,
Method: 'Auto'
});
if (result.Code !== 1) return V8.Tips(result.Msg, false);
```
前端成功不等于业务已授权。必须把 result.Data.Ticket 交给后端接口引擎,由后端重读权威业务数据、重新计算摘要,并调用 V8.Method.ConsumeIdentityVerificationTicket 原子消费。票据不能复用,也不能代替菜单/表权限和业务状态校验。完整方案见 登录方式、Passkey、Authenticator、第三方登录与严格人脸验证 (https://microi.net/doc/more/identity-verification.html)。
## 蓝牙打印(V8.Print)
V8.Print 是前端蓝牙直连打印能力,支持 TSC/TSPL、CPCL 标签指令和 ESC/POS 小票指令。它不是“仅移动端函数”:5+App 使用 BLE,Android 的 ZICOX CC4 还可回退到 经典蓝牙 SPP;存在 navigator.bluetooth.requestDevice 的 PC/H5 浏览器使用 Web Bluetooth。
型号 | createNew() 标签路径 | createNewESC() | 存量 V8 |
佳博 GP-M322 | 原 TSPL 字节逐字节发送 | ESC/POS 原样发送 | 无需修改 |
ZICOX CC4 | 标准 TSC 调用在发送前转换为 CPCL | ESC/POS 原样发送 | 常用标准调用无需修改 |
其它 TSPL 机型 | 原 TSPL 字节发送 | ESC/POS 原样发送 | 无需修改 |
完整的选型、连接、TSC→CPCL 映射、Android SPP 和实机验收见 蓝牙打印机 (https://microi.net/doc/system-engine/bluetooth-printer.html)。浏览器模板、PDF、A4 和 Print Engine JSON 请使用打印引擎 (https://microi.net/doc/system-engine/print-engine.html),不要把两套打印协议混用。
### 已挂载到哪些前端 V8
当前源码在三处调用幂等的 initV8Print(V8):
源码位置 | 可使用 V8.Print 的场景 |
Microi.Client/src/utils/diy.common.js | 通用前端 V8 基础对象和常规按钮流程 |
Microi.Client/src/views/form-engine/diy-form.vue | 表单、字段、表单按钮 V8 |
Microi.Client/src/views/form-engine/diy-table.vue | 列表、菜单按钮、行按钮等表格 V8 |
租户脚本无需直接导入 utils/ble/tsc.js 或 esc.js。后端接口引擎、后端表单 V8 和微信小程序原生 BLE 不在这条挂载链中。三处挂载现在都会取得同一个应用级 Print 实例;跨模块切换、不同按钮和不同前端 V8 上下文共享连接状态与发送队列, 不会再各自创建一套蓝牙连接。
PC 与平板端的顶部导航在 AI 助手后显示蓝牙图标:绿色圆点表示已连接,橙色表示连接或 重连中,灰色表示未连接。移动端【我的】页显示“蓝牙连接”菜单、状态标签及当前或已记住 的设备名称。两个入口都打开同一个 OpenBluetoothPage() 连接页,可以搜索、查看当前 设备、测试打印、断开或重新选择设备;模块内 V8 打印与这些入口共享同一状态。
### 环境与连接判断
环境 | 实现 | 注意事项 |
Android 5+App | plus.bluetooth BLE;CC4 可用 plus.android SPP | BLE 优先;SPP 需先在系统完成配对;Android 12+ 主动搜索时申请附近设备权限 |
iOS 5+App | plus.bluetooth BLE | 不使用 Android RFCOMM/SPP |
支持 Web Bluetooth 的浏览器 | navigator.bluetooth | 通常要求 HTTPS/localhost 和用户点击手势 |
其它 H5/浏览器 | 无可用引擎 | V8.Print 仍可能存在,但连接会失败并提示 |
微信小程序 | 不属于此模块 | 使用小程序/UniApp 专用 BLE 实现 |
Android 12+ 的宿主包必须声明 BLUETOOTH_SCAN、BLUETOOTH_CONNECT 并启用 DCloud Bluetooth 模块。源码仅在用户点击“搜索”时申请运行时权限,不在自动重连阶段打扰用户。
不要使用 V8.ClientType === 'PC' 排除蓝牙,也不要用 BLEInformation.deviceId 判断实时连接。标准流程:
javascript
```
async function ensurePrinterConnected() {
if (!V8.Print) throw new Error('当前前端未加载蓝牙打印能力');
if (V8.Print.isConnected()) return;
// 先尝试恢复曾经连接的设备,不触发设备选择框。
var restored = await V8.Print.reconnect();
if (restored && V8.Print.isConnected()) return;
// 首次选择 Web Bluetooth 设备必须由用户点击等手势触发。
var connected = await V8.Print.OpenBluetoothPage();
if (!connected || !V8.Print.isConnected()) {
throw new Error('未连接蓝牙打印机');
}
}
```
OpenBluetoothPage() 返回 Promise,在连接弹窗关闭时解析;弹窗已打开时 重复调用会取得同一个 Promise。Web 端 isConnected() 检查实时 GATT 和写特征,5+App 端的 BLE 结合 onBLEConnectionStateChange 维护的在线标记与设备/写特征 ID 判断,Android SPP 则检查实时 RFCOMM Socket 与输出流;写入期间仍可能物理断线,所以任何环境都要捕获发送异常。
设备元数据会同时写入 localStorage 和兼容用 sessionStorage。应用启动、页面恢复、重新 获得焦点或意外断开时会尝试有限次数自动重连:5+App 使用已记住的 deviceId,Web 端只有 在浏览器仍保留该设备授权且实现 navigator.bluetooth.getDevices() 时才能无弹窗取回设备。 主动点击“断开连接”会停止自动重连并忘记设备。操作系统、浏览器权限、打印机关机或离开 范围仍会终止物理连接,前端不能承诺永久不断线;此时入口会展示断开/重连状态并允许用户重选。
### 核心 API
API | 返回/作用 | 使用要求 |
V8.Print.createNew() | TSC/TSPL 构建器 | 标签打印 |
V8.Print.createNewESC() | ESC/POS 构建器 | 热敏小票 |
V8.Print.OpenBluetoothPage() | Promise | 用户手势中打开并 await |
V8.Print.isConnected() | boolean | 推荐的连接判断 |
V8.Print.reconnect() | Promise | 使用已记住的授权或设备 ID 重连,不弹设备选择框 |
V8.Print.getConnectionState() | 状态快照 | 展示引擎、连接/记忆状态、设备名、错误与时间 |
V8.Print.subscribeConnection(listener) | 取消订阅函数 | 注册后立即回调,并持续接收连接变化 |
V8.Print.getPrinterProfile() | 型号快照 | 返回当前型号、标签指令与首选/兜底传输;普通业务无需调用 |
V8.Print.setPrinterProfile(mode) | 型号选择 | auto 或三个内置 profile;设备名无法识别时使用 |
V8.Print.prepareSend(bytes) | Promise | 自动恢复连接,进入应用级队列后分包串行写入;必须 await |
V8.Print.Send(bytes) | 内部状态机入口 | 依赖 prepareSend 设置的共享游标,业务代码不要直接调用 |
V8.Print.setOneTimeData(bytes) | 设置 BLE 包长 | 只接受 1–512 整数;默认 20,5+ BLE 实际发送还受本次连接 maxWriteBytes 限制 |
V8.Print.setPrinterNum(num) | 同一缓冲区重复发送 | 只接受 1–99 整数;连接页候选 1–9 |
V8.Print.disconnect() | 主动断开并忘记设备 | 停止自动重连;下次需要重新选择设备 |
V8.Print.BLEInformation | 设备、通道与特征元数据 | 只作诊断,不是连接状态或打印回执 |
getConnectionState() 还会返回 transport、profileMode、profileId、profileName、 commandLanguage。手工型号值为 gprinter-gp-m322、zicox-cc4、generic-tspl;恢复 设备名自动识别使用 auto。旧 V8 不需要调用型号 API。
### TSC/TSPL 标签示例
下面同一份代码可用于 GP-M322 与 CC4。GP-M322 取得原 TSPL;CC4 在首包写入前取得完整 CPCL:
TSC 的 setText、setQR、setBarCode 会把内容拼入带双引号的协议字段, 必须移除引号、换行、NUL/控制字符并限制长度:
javascript
```
function cleanCommandText(value, maxLength) {
return String(value == null ? '' : value)
.replace(/[\r\n\"\x00-\x1f]/g, ' ')
.slice(0, maxLength || 120);
}
async function printLabel(order) {
await ensurePrinterConnected();
var cmd = V8.Print.createNew();
cmd.setSize(60, 40);
cmd.setGap(2);
cmd.setSpeed(4);
cmd.setDensity(8);
cmd.setDirection(1);
cmd.setCls();
cmd.setText(20, 20, 'TSS24.BF2', 1, 1, cleanCommandText(order.Name, 40));
cmd.setBarCode(20, 80, '128', 60, 1, 2, 2, cleanCommandText(order.Code, 40));
cmd.setQR(340, 30, 'L', 5, 'A', cleanCommandText(order.Id, 120));
cmd.setPagePrint();
await V8.Print.prepareSend(cmd.getData());
V8.Tips('打印数据已发送', true);
}
```
TSC 构建器的 28 个源码方法:
类别 | 方法 |
初始化/原始命令 | init()、addCommand(content) |
纸张/质量 | setSize(w,h)、setSpeed(n)、setDensity(n)、setGap(mm)、setBline(mm)、setCountry(v)、setCodepage(v) |
走纸/方向 | setFeed(n)、setBackFeed(n)、setDirection(n)、setReference(x,y)、setFromfeed()、setHome()、setLimitfeed(n) |
蜂鸣/绘制 | setSound(level,interval)、setBar(...)、setBox(...)、setErase(...)、setReverse(...)、setBitmap(x,y,mode,imageData) |
内容/输出 | setCls()、setText(...)、setQR(...)、setBarCode(...)、setPagePrint()、getData() |
setFromfeed() 是当前公开拼写,实际生成 FORMFEED。addCommand 是低层接口, 只能接受固定、受审查的指令,不能传入表单或接口返回的任意命令。
CC4 自动转换支持常见纸张、质量、方向、坐标、线条、方框、反相、文字、条码、二维码、 位图和单次输出;addCommand、代码页/国家、特殊走纸、蜂鸣、限位和擦除等尚无安全等价映射的 方法会在任何蓝牙写入前拒绝。必须直接发送 cmd.getData(),不要用 Array.from 或 JSON 序列化复制字节数组,否则会丢失不可枚举的转换元数据。完整白名单见 蓝牙打印机:CC4 自动转换范围 (https://microi.net/doc/system-engine/bluetooth-printer.html#cc4-%E8%87%AA%E5%8A%A8%E8%BD%AC%E6%8D%A2%E8%8C%83%E5%9B%B4)。
### ESC/POS 小票示例
javascript
```
async function printReceipt(order) {
await ensurePrinterConnected();
var esc = V8.Print.createNewESC();
esc.init();
esc.setSelectJustification(1);
esc.bold(1);
esc.setFontSize(16);
esc.setText(cleanCommandText(order.ShopName, 50) + '\n');
esc.bold(0);
esc.setFontSize(0);
esc.setSelectJustification(0);
esc.setText('单号:' + cleanCommandText(order.Code, 50) + '\n');
esc.setText('金额:' + Number(order.Amount || 0).toFixed(2) + '\n');
esc.setPrintAndFeedRow(3);
await V8.Print.prepareSend(esc.getData());
}
```
ESC/POS 构建器的 25 个源码方法:
类别 | 方法 |
初始化/文本 | init()、setText(content)、setFontSize(n)、bold(n)、setUnderline(n)、setUnderline2(n) |
二维码 | setSelectSizeOfModuleForQRCode(n)、setSelectErrorCorrectionLevelForQRCode(n)、setStoreQRCodeData(content)、setPrintQRCode() |
位置/间距 | setHorTab()、setAbsolutePrintPosition(n)、setRelativePrintPositon(n)、setSelectJustification(n)、space(n)、setLeftMargin(n)、textMarginRight(n)、rowSpace(n)、setPrintingAreaWidth(n) |
其它/输出 | setSound(n,t)、setBitmap(imageData)、setPrint()、setPrintAndFeed(n)、setPrintAndFeedRow(n)、getData() |
setRelativePrintPositon 中的 Positon 是源码历史拼写,调用时不能改成 Position。ESC/POS 源码目前没有公开条形码方法;不要因为内部存在条码类型数组而 虚构 setBarCode。
### 编码、图片与打印机兼容
- TSC 文本命令、ESC 文本和二维码内容使用本地 utils/ble/encoding.js + encoding-indexes.js 转为 GB18030,不会在运行时联网。 编码成功不代表目标打印机具有中文字库、代码页或 Emoji 支持。
- setBitmap 接受 ImageData 风格的 { width, height, data } RGBA 数据,不接受 URL、Base64 或 DOM 图片。当前黑白转换较简单,大图先缩放、二值化并实机验证。
- Web Bluetooth 只把常见服务 18f0、ff00、 49535343-fe7d-4ae5-8fa9-9fafd205e455、 e7810a71-73ae-499d-8c15-faa9aef0c3f2 放进 optionalServices,并选择发现的 第一个可写特征。当前没有公开的自定义服务 UUID 配置,特殊型号需要扩展源码。Web 浏览器 不能使用经典 SPP;只开放 SPP 或使用其它私有 UUID 的 CC4 固件应在 Android 5+App 使用配对 SPP,或取得厂家准确 BLE UUID 后扩展源码。
### 批量打印
同一写特征和共享分包状态只能串行使用:
javascript
```
async function printBatch(rows, startIndex) {
var list = Array.isArray(rows) ? rows : [];
var begin = Math.max(0, Number(startIndex || 0));
var limit = Math.min(list.length, begin + 100);
for (var i = begin; i < limit; i++) {
try {
await printLabel(list[i]);
V8.Tips('已发送 ' + (i + 1) + '/' + list.length, true);
} catch (error) {
return {
Code: 0,
Msg: '第 ' + (i + 1) + ' 条发送失败:' + (error.message || error),
NextIndex: i
};
}
}
return { Code: 1, Data: { NextIndex: limit, HasMore: limit < list.length } };
}
```
- 不用固定 setTimeout(3000) 猜测上一张是否完成,也不用 Promise.all 表达同一设备的并行打印; 运行时会把所有 V8 上下文的 prepareSend 放进同一队列,但业务仍应逐条 await 保持结果顺序。
- 大批次分段保存 NextIndex;断连后从失败位置人工确认再恢复。
- setPrinterNum(n) 只适合同一缓冲区重复发送,不适合每张内容不同的批次。
- prepareSend 默认每包 20 字节;Android 5+ 佳博 GP-M322 的确认写入逐包等待原生回调, 再保留约 8ms 的 GATT 队列保护窗口,避免真实设备连续写入中断。其它路径保留约 20ms, 多份之间约 100ms;这不是物理走纸确认。
- 5+ 连接快照还包含 mtu、maxWriteBytes、recommendedPacketSize、writeType、 packetIntervalMs。佳博在服务发现后尝试 MTU 协商,只采纳回调中的实际值;空成功、失败、 超时或旧运行时没有 API 时保留 20 字节。能力不跨连接缓存。自定义微服务应读取实际能力, 不能只允许浏览器加速。详见蓝牙打印机 (https://microi.net/doc/system-engine/bluetooth-printer.html)。
### 当前实现限制与安全边界
- prepareSend 使用 Math.ceil(length / packetSize) 分包,长度恰好整除时不会再发送 0 字节末包;空缓冲区、非法包长和非法份数会直接抛错。通过源码校验不等于目标打印机 支持该包长,仍须按型号实测。
- 应用级发送队列能防止不同 V8 上下文覆盖分包游标,但不能把 BLE 写入变成打印机业务事务; 调用方仍要逐条等待、记录失败位置并处理用户取消。
- 自动重连是有上限的最佳努力:Web Bluetooth 是否能恢复授权取决于浏览器实现和用户授权, 5+App 也会受系统蓝牙、距离、休眠与设备电源影响。重试结束后必须由用户点击入口重连。
- 源码会发现 read/notify 特征,但尚未订阅或解析状态。prepareSend 成功只表示 字节写入 BLE 特征或 SPP 输出流,不代表已走纸、无缺纸或物理打印成功。
- GP-M322 的自动 profile 不改写任何 TSPL 字节。CC4 的转换先整包校验再发送;不支持的方法、 缺少高层元数据或不是恰好一次 setPagePrint() 时零写入失败,不能把失败改成盲目透传。
- 设备名称、ID、服务与特征是外部输入。展示时用文本方式转义,不拼 innerHTML; 不记录或上传完整 BLEInformation。
- 业务保存与蓝牙打印不是原子事务。用稳定业务单号支持受控重打,避免打印失败后 重复执行业务写入。
- 至少分别用 GP-M322 与 CC4 验证首次授权/配对、自动与手工选型、重连、刷新、主动断开、 中文/条码/二维码/图片、连续 20 张串行发送、长度恰好整除包长、交替换机,以及关机/缺纸/ 离开范围后的失败恢复;CC4 还要分别记录 BLE 与 Android SPP 结果。
## 移动端函数
### 二维码、条形码扫码 V8.Method?.ScanCode
- 支持H5、小程序、APP
js
```
if (V8.Method?.ScanCode) {
await V8.Method?.ScanCode();//同步等待扫码成功
if(V8.ScanCodeRes){//获取到的扫码值
V8.FormSet('SaomaValue', V8.ScanCodeRes);//赋值
}else{
V8.Tips('扫码结束,未扫到值!', false)
}
}else{
V8.Tips('非移动端环境,暂不支持扫码!', false)
}
```