唯杰地图 logo唯杰地图
VJMAP
EN

顶层结构(envelope)详解

vjvp/references/envelope.md 15.7 KB226 行
原文

导出/导入的工程 body 来自编辑器 toProjectBody(),形状由 SDK VpProjectBody 定义。导入时 project 喂给 new VpProject(project, init),其余喂给运行时数据层。

0. 一句话规则

  • 节点 = 一棵 VpNode 树(页面/组件/piece 同构)。
  • 导出不做"去默认值压缩":options 里有什么就导出什么;没写的项在运行时由蓝图默认值回落。所以手写时只写需要的项即可,但 position/size 建议显式写。

1. body 顶层

{
  "project": { /* 必填,见 §2 */ },
  "connections": [],      // 可选,缺省 []
  "connectionData": {},   // 可选,缺省 {}
  "dataRules": [],   // 可选,缺省 []
  "projectParams": {}     // 可选;千人千面参数,一般不写(project.params 已够)
}

类型(SDK VpProjectBody):

interface VpProjectBody {
  project: VpProjectModel
  connections?: VpConnection[]
  connectionData?: VpConnectionData          // Record<connUid, VpDataTable[]>
  dataRules?: unknown[]
  projectParams?: Record<string, unknown>
}

2. project

{
  "name": "工程名",          // 可选
  "screen": { /* 主页面,必填 */ },
  "screens": [ /* 其余普通页面,可选 */ ],
  "foregroundScreen": { /* 前景层,可选 */ },
  "backgroundScreen": { /* 背景层,可选 */ },
  "windows": [ /* 窗口(弹窗/抽屉/锚定窗口),可选,见 §2b */ ],
  "params": {},             // 项目参数(千人千面);一般 {}
  "settings": {}            // 工程级设置(工具栏/访问/访客/演示/sizeModes 断点…);一般 {}
}
  • 导出时把第一个普通页面放 screen,其余放 screens[];导入时再合并回 VpProject.screens 顺序。
  • 前景/背景层也用 type:"screen",区别只在它们挂在 foregroundScreen/backgroundScreen 键上。
  • 最小可导入 project:{ "screen": { "uid":"screen_main", "type":"screen", "widgets":[] } }。

类型(VpProjectModel):id?、name?、screen(必填)、foregroundScreen?、backgroundScreen?、screens?、windows?、params?、settings?、version?。

2b. 窗口 windows(工程级弹窗/抽屉/锚定窗口)

project.windows[] 是与页面平级的顶层实体(对照 ExB config.dialogs):节点 type:"window"(结构同 screen,内容放 widgets),由 switchWindow 动作(windowUid + operate: open/close/toggle)在播放态开关,或用 window-instance 组件静态嵌入页面复用。不用窗口就省略整个键。地图图层的 feature-panel-uid(点击要素弹锚定窗口)引用的正是这里的窗口 uid。

窗口节点关键 options:

option 默认 说明
window-mode fixed fixed 固定位置 / anchored 锚定(贴触发组件弹出,地图点位触发时跟随地图)
window-position center 固定窗口五锚点:center/top/bottom/left/right(顶/底只高度生效宽=页宽,左/右只宽度生效)
size + size-unit — / px 窗口大小;单位 px/percent(相对舞台)
window-offset / window-margin [0,0] / [0,0,0,0] 位置偏移;贴边窗口铺满轴两侧留白(上下左右)
block-page + background-mask-color true / 半透黑 模态:激活期间页面不可交互+遮罩(锚定窗口不适用)
close-outside true 点遮罩/画面外关闭
titlebar-show + titlebar-text/height/bg/color/font-size/draggable false 内置标题栏(留空 text=窗口名;固定窗口播放态可拖动,位置不持久化)
close-btn-show + close-btn-color/size true 关闭按钮
锚定:anchor-placement/anchor-gap/anchor-arrow auto/8/true 弹出朝向(auto 自动翻转)/锚点间距/指向箭头
window-bg / box-shadow-* / show-scroll #1b1f27 / 关 / false 背景色/框阴影/内容滚动

打开方式除 switchWindow 外:页面 open-with-window(进页自动开,一次会话一次)、工程 settings.splashWindowUid(启动闪屏,至多一个)、SDK ctx.openWindow(uid)(宿主程序化开关)。多窗口按打开顺序成 z 序,Esc 关最顶层。

3. 通用节点 VpNode

所有页面/组件/piece 都是这一种结构:

interface VpNode {
  uid: string
  type: string
  name?: string
  enabled?: boolean            // piece 用,缺省 true
  locked?: boolean
  options?: Record<string, any>
  stateOverrides?: Record<string, Record<string, any>>
  sizeModeOverrides?: Partial<Record<'MEDIUM'|'SMALL', Record<string, any>>>  // 设备差异覆盖(LARGE=基线 options)
  states?: VpState[]
  state?: string
  widgets?: VpNode[]           // 容器(页面/分组面板)的子组件
  pieces?: VpNode[]            // builder(地图/组态)的子元素
  unitTemplate?: VpNode        // 仅 data-repeater:单元模板(group-panel 节点)
  pending?: VpNode[]           // 仅页面根:待处理暂存区(不渲染,编辑器「加回画布」用)
  privateData?: VpPrivateData  // 组件私有数据(表格默认示例数据用)
  conditionalStyles?: VpConditionalStyle[]
  extensions?: VpExtension[]   // 二次开发脚本(代码面板):{uid,name,enabled,code}
  guideLines?: Record<string, unknown>
}

手写时通常只用到:uid、type、name?、options、(容器)widgets、(地图/组态)pieces、(数据循环容器)unitTemplate、(多状态)stateOverrides、(设备差异)sizeModeOverrides。component-instance 节点另有顶层 masterId/masterVersion/props/overrides/master(见 widgets/containers.md §5bb)。

4. 页面节点 screen

{
  "uid": "screen_main",
  "type": "screen",            // 恒为 screen
  "name": "主页面",            // 可选
  "options": {
    "fullscreen-mode": "stretch",  // 铺满屏幕;不写则默认 scaling 会留黑边
    "size": [1920, 1080],          // 画布尺寸 [宽,高]
    "background-color": "#0c0d0e"  // 背景色
  },
  "stateOverrides": {},
  "widgets": [ /* 顶层组件 */ ]
}

页面默认 options(省略即用默认):

option 默认 说明
layout-profile 推断 布局画像主声明(canvas-scale固定大屏 / canvas-percent百分比自适应 / flow-fluid流式),生成工程时必写(见 visual-style.md §3 铺满守则、本文 §4.1)
size [1920,1080] 画布大小 [宽,高]
background-color #1c222a 背景色(新建工程模板常设 #0c0d0e)
fullscreen-mode scaling 全屏适配方式(见下)
fullscreen-fill-color #000000 适配留白色(仅 scaling 留边时可见)
fill-auto-expand false 仅 fill-uniform/responsive:把无显式锚点的主视觉拉满剩余画布(见 4.1)
container-layout static static/fluid/grid
carousel-animate false 页面轮播动画

响应式断点:project.settings.sizeModes.breakpoints 声明 SMALL/MEDIUM 宽度阈值,形状 "settings": { "sizeModes": { "breakpoints": { "small": 767, "medium": 1279 } } }(视口宽 ≤small→SMALL、≤medium→MEDIUM、否则 LARGE;缺省即此默认,非法/small≥medium 回退默认;同级 widths/heights 只管编辑态预览画布;配合节点 sizeModeOverrides 与 device-visible 做设备差异化,示例见 data/组件/入门/28)。设备档位对所有适配方式生效(固定画布也按视口宽切档),但固定画布页里 position/size 不参与设备差量——写进 sizeModeOverrides 也不生效,几何由整屏缩放 + 锚点/自动填充负责;可按设备覆盖的是 device-visible、字号配色、容器 grid-columns/fluid-wrap/container-layout、文案等。

4.1 fullscreen-mode(全屏适配方式)—— 让大屏铺满屏幕 ⚠️

运行/预览态由运行时的 useStageScaling 按激活页面的 fullscreen-mode 把画布缩放进视口:

值 含义 铺满? 取舍
scaling(默认) 等比缩放 contain:min(cw/bw,ch/bh) 居中 ❌ 视口比例≠画布时留黑边 保比、不裁不变形
fill-uniform 等比铺满(左上·不变形):等比 min 缩放锚左上,画布撑大到视口、右/下空白由页面背景延展填充(仿 autofit) ✅ 不变形、内容完整、无黑边;内容锚左上不居中,空白区只有背景(背景为纯色/渐变时视觉无缝)——「不变形又要铺满」的正解
adaptive 铺满裁切 cover:max(...) 居中溢出裁剪 ✅ 保比,但裁掉贴边内容
stretch 拉伸:X/Y 独立铺满 ✅ 所有内容都在;真 16:9 屏与画布同比时无变形,偏离比例才拉伸变形
responsive 页面尺寸跟随容器、内容不缩放回流 ✅(尺寸) 固定坐标布局会错位,配百分比/流式用
fit-width / fit-height 按宽/高等比铺满,另一轴溢出滚动 ✅(单轴) 竖屏长页门户 / LED 横带条屏
original 1:1 不缩放居中 ❌ 仅调试/特殊场景
  • 缺省 scaling 不占满屏幕:因为默认画布是 16:9(1920×1080),而预览/发布视口(带工具条、非 16:9 显示器、窗口化)常不是 16:9,于是 contain 留黑边。
  • 结论:要铺满,就在每个 type:"screen" 节点(含 screens[]/foregroundScreen/backgroundScreen)的 options 显式写 fullscreen-mode。固定画布(container-layout:"static")默认用 "fill-uniform" + "fill-auto-expand":true(不变形+铺满,主视觉吃掉撑开的画布,见 data/布局/适配/13~15)。"stretch" 会把圆拉成椭圆,只留对照例 data/布局/适配/16。要裁贴边用 "adaptive"。
  • 流式/百分比页面(container-layout:"fluid" 或画像 flow-fluid/canvas-percent)只能写 "responsive":画布=视口、内容回流,fill-uniform 对它们无意义(fill-auto-expand 也只对 static 生效)。

编辑器的新建工程模板起手式已默认 "fullscreen-mode":"fill-uniform" + fill-auto-expand;但手写/生成的 JSON 不经过模板——生成工程时仍须显式写 fullscreen-mode。

fill-uniform 让内容吃掉扩展区(不变形 + 不留空)

fill-uniform 与 scaling 的内容倍率相同,差别是留白成了真实画布(画布 = 视口 ÷ 缩放系数,多出的在右/下)。谁去占用它有两种写法,优先第一种:

  • 锚点(精确):主视觉写 "responsive-x":"left-right","responsive-y":"top-bottom"(四边贴边),侧栏写 "responsive-x":"right","responsive-y":"top-bottom"。锚点原先只在 responsive 下生效,现在 fill-uniform 同样生效。
  • 自动填充(兜底):页面写 "fill-auto-expand": true,把没有显式锚点的 map-2d/scene-3d/scada-2d/basic-table/basic-list/deco-border/布局容器,以及够大的 basic-card / chart-*(宽 ≥ 画布 15%、高 ≥ 10%)拉到剩余空间,并夹在包裹它的外框、同宽顶栏、右侧与下方邻居之内。外框若也是填充候选,会先撑开,再让内部主视觉跟到新内缘(组态套 deco-border 时才能铺满)。贴着设计稿右缘的窄栏(距右约 96px 内的状态卡)随画布变宽右移,与贴边栏水平重叠的同侧小控件作为侧栏簇一起走;横排 KPI 末卡不搬(不依赖是否有装饰边框)。说明顶栏比主视觉宽但右侧还有贴边控件时,主视觉吃到控件左缘。侧栏也可写 responsive-x: right。自定义组件可用 meta.canvasFill 声明是否候选。

主视觉被四周固定件包围的通用配方(左右面板 + 顶/底工具条,见 data/布局/适配/20):两侧面板写 "responsive-x":"left"/"right" + "top-bottom",底部按钮排写 "responsive-x":"left-right" + "responsive-y":"bottom",中间主视觉不写锚点——锚点先跑,自动填充再把主视觉夹进「右侧邻居左缘、下方邻居上缘」。按钮排要按钮跟着变宽重排就用 layout-flow-row(带主轴对齐项,fluid-wrap:"nowrap" + fluid-justify-content:"space-between";group-panel 只有换行开关)。

两者共存时锚点优先。容器不想被撑大(要卡片数不变)就写 "canvas-grow":"scale"——但它只管尺寸不管位置:这种容器要放在画布增长的背面(靠里一侧)让主视觉吸收增量,摆在右缘/底缘会留一块没人占的背景空白;要贴边又不变大就用锚点 "responsive-x":"right"。只写运行态几何,不改 options、不进历史。

5. 组件节点 widget

{
  "uid": "w_bar1",            // 任意唯一串(一般无前缀)
  "type": "chart-bar",        // 见 widgets/index.md 注册类型
  "name": "销售柱图",          // 可选;省略用组件默认名
  "options": {
    "position": [100, 100],   // 左上角 [x,y],默认 [0,0]
    "size": [420, 300],       // [宽,高],默认随组件
    "category-field": [["conn_x","tbl_x","月份"]],
    "metric-fields": [["conn_x","tbl_x","销售额"]]
  },
  "stateOverrides": {}
}
  • name 仅在你想固定显示名时写;否则按 defaultName(如柱状图取 title 或回落"柱状图")。
  • 容器型(group-panel)把子组件放 widgets: [],子组件 position 相对容器左上角。
  • builder 型(map-2d)把本体/图层放 pieces: [](见 §7)。

6. stateOverrides(多状态)

  • 默认状态的值放 options;额外状态的差异覆盖放 stateOverrides[stateUid]。
  • 不用多状态:给 {} 或直接省略 stateOverrides/states。
  • stateOverrides["s_x"]: {} 表示状态 s_x 存在但无覆盖(读取回落 options)。没有 stateOverrides[""] 这种写法(默认态永远读 options)。

绝大多数手写工程不需要多状态,留 {} 即可。

7. piece 节点(仅 builder 型,如 2D 地图)

{
  "uid": "map-body",
  "type": "map-body",
  "name": "场景",
  "enabled": true,            // 缺省 true
  "options": { "coordinate-system": "geographic", "basemap": "gaode-vec", "map-center": [116.4,39.9], "map-zoom": 4 },  // coordinate-system 缺省为 "cad",不写则 basemap 被忽略、渲染空白 CAD 图
  "stateOverrides": {}
}
  • map-2d 的 pieces 数组第一个应是 map-body(本体,单例不可删,建议 uid:"map-body")。
  • 其余 piece 为图层:map-tile / map-marker / map-geojson(见 widgets/map-2d.md)。
  • piece 可再嵌 pieces(一般用不到)。

8. options 序列化的真相(重要)

  • 编辑器不在导出时和默认值比对裁剪 —— node.options 里存在的键就原样导出,被"重置"过的键才会被删掉。
  • 因此真实导出文件是稀疏混合:新建组件会带 size/position 等少量键,其余走默认。
  • 手写策略:只写需要偏离默认的项 + 必要的 position/size + 字段绑定。其它一律省略,交给默认值,文件更短更稳。
  • 想知道某项默认值:查 widgets/index.md 对应家族文档,或自动生成的 widget-schema.generated.json。

9. 特殊 option 值形态(少见,按需)

  • 元素引用:{ "refType":"element", "targetPath":[screenUid, widgetUid?, pieceUid?] }(link-section/target-map/window 等引用型 option 与动作目标都用它)。
  • 文件引用:{ "refType":"file", "relativePath":"...", "url":"..." }。
  • 交互(options.interactions):{ "version": 2, "groups": [...] },分组按 trigger 内容匹配(v1 哈希键形态已废弃);完整写法见 interactions.md。