唯杰地图 logo唯杰地图
VJMAP
EN

事件交互:触发器(Trigger) → 动作(Action)(options.interactions)

vjvp/references/interactions.md 21.9 KB213 行
原文

除了「数据交互」(筛选/选中/联动),appvisual 还有一套独立的事件交互系统:某个事件触发 → 执行一个或多个动作(改属性、切页面、打开链接、发/收行为事件…)。完整可导入示例见 data/数据交互/事件交互.vpproj.json。

持久化形态(写在元素 options.interactions 里)

interactions 是元素(页面/组件/子元素都行)的一个普通 option,形如:

"options": {
  "interactions": {
    "version": 2,                         // 固定写 2
    "groups": [                           // ★ 触发分组数组(trigger 即身份,无需键)
      {
        "trigger": { "name": "click", "options": {} },
        "interactions": [                 // 该触发下的若干「交互项」(编辑器里的一行)
          {
            "alias": "点击→打开链接",
            "enable": true,
            "trigger": { "name": "click", "options": {} },   // trigger 副本(与分组同)
            "action": {                   // 单动作模式
              "name": "openUrl",
              "options": { "url": "https://...", "openWay": "_blank",
                           "start": "meanwhile", "delay": 0, "duration": 1 }
            }
          }
        ]
      }
    ]
  }
}
  • 一个分组 = 一种触发器(trigger.name + 触发器自身 options)。同名但 options 不同(如不同 eventName)= 不同分组。
  • 一个分组下可有多条「交互项」;同一项 action(单动作) 或 actions[](多目标模式,trigger 声明了 triggerTargets 时)。
  • 动作通用 options:start(meanwhile 与上一个同时 / follow 在其后)、delay(ms)、duration(ms)。⚠️缺省(不写 start)也是串行新批次:startInteractions 把相邻 meanwhile 项并入前一批并行,其余每项各自成批、Promise 链串行——后项要等前项 delay+duration 全部跑完才开始计自己的 delay(delay:600 项在前时,delay:0 项也到 ~600ms 才执行;写 start:"meanwhile" 则 ~0ms 并行)。delay/duration 由 rAF 帧驱动,页面不可见时停走。
  • 目标元素引用统一写 "element": [{ "targetPath": [...uid路径], "refType": "element" }]。完整形态 ["screenUid","widgetUid"](子图元再加 pieceUid);SDK / playground 也可只写 ["widgetUid"](组件 uid 全局唯一)。空/省略 → 目标 = 触发元素自身。

★ 分组靠 trigger 匹配(无需键 / 无需哈希)

运行时按 group.trigger(name + 该触发器自身 options)的内容匹配派发,不再用任何键。写 groups 数组时只要保证:

  • 同一元素内每个 trigger 不重复——同名且 options 相同只放一个分组,其下可挂多条交互项;
  • 同名但 options 不同(如不同 eventName / 不同 condition)= 不同分组,各占数组一项。

v1 旧形态用「哈希键对象」(interactionKey FNV 值作键)已废弃,无需再算哈希;一律写 version: 2 + groups 数组。

运行时也有对应 API(元素内核层,handle.raw 上):setInteractionsOption(groups) 写回 {version:2, groups}、getInteractionsOption() 读出剥离 version 的 groups——运行时可改写交互且即刻生效(触发器每次派发时按当前 options 重新匹配分组)。

VP-TSX 写法(helper 组装,与上面 JSON 同构)

TSX 里用 runtime 的 interactions()/on()/act() 组装(产物就是上面的 { version:2, groups },见 vptsx.md):

<basic-button interactions={interactions(
  on('click',
    act('switchWindow', { windowUid: [{ targetPath: ['win_detail'] }], operate: 'open' }),
    act('sendBehaviorEvent', { element: [{ targetPath: ['screen_main', 'w_map'], refType: 'element' }], eventName: 'focusDevice' }),
  ),
)} />

// 触发器带参:on 的第一参传 { name, options }(⚠️ 页面级 timer 只在编辑器内预览生效,/view、SDK 静默不跑——见触发器清单下注)
<screen interactions={interactions(
  on({ name: 'timer', options: { mode: 'interval', interval: 5 } }, act('requestApi', { method: 'GET', url: 'https://…', resultMode: 'setData', connection: 'conn_x', table: 'tbl_x' })),
)} … />

导出代码(page_export_tsx)里 interactions 是 JSON 字面量而非 helper 调用——两种形态等价,改写时保持任一均可。

触发器清单(trigger.name)

name 别名 适用 触发器 options payload(传给动作的 {:字段})
click 点击 组件 — 无
mouseEnter/mouseLeave 移入/移出 组件 — 无
appear/disappear 入场/离场 组件 — 无(带 pre/post,配合进/退场动画)
receiveBehaviorEvent 接收二开事件 组件与页面(页面/窗口上仅编辑器内预览生效,见下注) eventName(string) 发送方带的 data
itemClicked 点击某项/选中项 绝大多数数据组件(图表全系、表格、列表/分页/日历/时间选择、图标/图形/卡片、tabs/buttons、repeater、地图、视图容器/导航、轮播…) — 选中项维度(图表如 {城市:"北京"},仅类目);repeater=整行;分页={页码,每页条数}
click-row/select-row/unselect-row/click-operate 表格行/操作 表格 — 行 payload:{rowNumber,value,row:{别名:值},…}
地图触发器(14 个):marker-click/region-click/map-select/map-unselect/map-blank-click/map-click/map-dblclick/map-hover/map-contextmenu/map-menu-item/map-ready/map-view-change/map-source-loaded/map-error 地图要素/交互/视角/就绪/错误 2D 地图 部分可选目标图层 要素类 payload 为命中要素属性({:name}/{:value});map-view-change 带视角、map-menu-item 带菜单项
scada-node-click/scada-node-dblclick/scada-link-click/scada-blank-click 组态图元/连线/空白 组态画板 可选目标图元 uid {nodeUid,name,shape,dataKey,value}(见 widgets/scada-2d.md §7.4)
viewChanged 切换视图时 视图容器/视图导航/轮播图 — {index,label}(任何来源切换都派发)
timeChanged 时间变化 时间轴 basic-timescale — {time}(当前游标时间)
switchChanged 开关切换时 开关 basic-switch — {checked,value}(value=开启值/关闭值归一后的字面量;接组态 scadaWritePoint 时「payload 字段」填 value 即写测点)
mediaEnded 播放结束 视频 basic-video — {src}(配「播完切页/下一个」)
conditionMeet 数据规则满足 页面(仅编辑器内预览) condition(string[],dataRules uid) 无
screenAppear 页面入场 页面(仅编辑器内预览) appearSwitch(first/always) 无
timer 定时器 页面(仅编辑器内预览) mode(interval每隔一段/at每天时刻)、interval(秒,默认5)、at(HH:mm:ss)、startDelay(秒)、maxCount(0不限)、onlyActiveScreen(仅当前页运行) {time,count}(供 requestApi 等动态值)

触发器(除地图/表格自定义)多在预览/播放态才生效。itemClicked 的 payload 只含类目字段(如 城市),不含数值;要取数值用表格的 click-row(payload 含整行 row)或动态取值 fromCurrentValue。

⚠️ 页面/窗口级触发器只在编辑器内预览生效:页面/窗口元素只有编辑器 store 的 prepareScreen 钩子会调 initAfterConstructor(→ starTriggertListen/watchSelection/watchLinkage);发布 /view(RuntimeHost→buildRuntimeProject)、独立 viewer、SDK createVpViewer 都不调——screen._listenTriggers 恒空,写在页面/窗口 options.interactions 里的 timer/screenAppear(窗口「画面打开时」)/conditionMeet/receiveBehaviorEvent 静默不触发,页面级 select-sync-receive/cross-filter-receive 开关在运行态也恒为关(交互链本身有效:手动 startInteractions(getInteractionsByTrigger(...)) 可跑通)。组件级触发器(click/mouseEnter/appear/disappear/receiveBehaviorEvent/各组件自定义)正常监听。反过来也一样:timer 写到组件上同样静默——组件触发器集只有 click/mouseEnter/mouseLeave/appear/disappear/receiveBehaviorEvent+类型自定义,不含 timer。需要周期取数改用数据源的定时刷新(api 连接 refreshInterval)、SDK 侧 setInterval + ctx.refreshData(),或扩展脚本 setInterval;事件接收写在组件上。

mouseEnter 的 DOM 链:组件 DOM 监听写 status.mouseEnterTime → 触发器 watch 该字段 → 派发(直接写 status.mouseEnterTime 同效)。

click 触发器的自动化通道:click 触发器 watch [status.clickTime, status.behaviorEventsTime?.click],三条路都能派发——① handle.click()(本质就是写 status.behaviorEventsTime.click=Date.now());② 直写 status.clickTime 或 status.behaviorEventsTime.click(Vue3 响应式对新键也触发);③ 部分组件的真实 DOM click(basic-text 容器 dispatchEvent 可派发;basic-button 的 DOM click 走的是 select 链路、不写 click 字段)。注意 status 初始键集里没有 clickTime(只有 behaviorEventsTime,error,isNew,isVisible,isMounted,isBehaviorReady,appearTime),但写入后 watcher 照常触发。自动化验证交互链首选 handle.click()/handle.emit(name,data)。

⚠️ element 目标寻址只认 uid 不认 name:changeOption/sendBehaviorEvent 等动作的 element:[{targetPath:[screenUid,widgetUid]}] 经 getElementByUID 解析——targetPath 段必须是 uid(addWidget 自动生成的,如 kpqvmtcqo4u1),写 widget 的 name 会静默解析失败:触发器照常派发、action 拿到空目标 no-op,sink 保持原值,无任何报错。跨组件动作调试第一步先查 targetPath 是 uid。

appear/disappear 是播放态生命周期触发器:appear watch [status.isMounted, status.appearTime](guard isVisible!==false && isMounted),status.appearTime 在 isPlaying(运行/预览态)由挂载钩子自动写——runtime viewer(createVpViewer)实例addWidget 挂载即自动派发 appear 链。disappear 同理 watch status.disappearTime。

basic-button 的 selectTrigger/unselectTrigger 经真实选中态驱动:触发器 watch isSelected 字段(immediate),setOption('checked',true) 不写 isSelected(checked 是 option 属性、isSelected 是运行态)、status.* 直写无效;对内层按钮 DOM dispatchEvent(click) 可派发(组件 click 处理器置 isSelected 并写 status.behaviorEventsTime.selectTrigger)。

⚠️ 合成 dispatchEvent 能否驱动自定义触发器取决于组件内部实现:basic-table 的 .vxe-body--row 行 click 静默但 .vxe-cell 单元格 click 能派发 click-row(vxe 事件委托处理合成事件);basic-text 容器 click 可派发 click。但 map-2d 的 marker-click(容器+canvas click)、scene-3d 的 scene3d-piece-click(canvas click)、basic-timeselect 改值后的 timeChanged 仍静默——这些触发器需要组件内部计算产物(vjmap/scene3d 命中检测结果、值提交钩子),不是纯 DOM 事件可达。验证这类链路用 handle 方法或写对应 status.*Time 字段,勿只靠 DOM 注入。

动作清单(action.name)

name 别名 关键 options
changeOption 修改设置 targetElement(searchElement/filterElementChildren/filterCurrentChildren)、element、optionKey(属性名)、<optionKey>:静态值;或 valueDynamic:true+valueMode(value/formula/fromCurrentValue)+valueFormula;可 isTween+easing(数值/向量类型才可缓动)。⚠️写入瞬时层 setTransientOption(getOption 能读到,node.options 持久层不变、不入历史/不随导出)
switchState 切换状态 element、state(状态 uid)
switchPopup 打开/关闭弹窗 popupUid(目标 group-popup 组件引用)、operate(show/hide,默认 show)——页面内轻弹层
switchWindow 打开画面(窗口) windowUid([{targetPath:["窗口uid"]}],缺省=动作所在窗口)、operate(open/close/toggle)——开关 project.windows[] 工程级窗口(弹窗/抽屉/锚定窗口);打开时自动带触发元素锚点(地图点位跟随)
switchScreen 切换页面 screenUID([{targetPath:["页面uid"]}])
openUrl 打开链接 url(支持 {:param}/{:payload字段} 插值)、openWay(_blank/_self)
sendBehaviorEvent 发送行为事件 element(目标)、eventName;无 data 选项——透传的是本条交互的触发器 payload(click 为空、itemClicked 为 {别名:值}、click-row 为行 payload)给目标的 receiveBehaviorEvent,并旁路通知宿主 ctx.on;element 解析不到时静默返回(宿主也收不到)
transfer 传递触发 element(目标)、trigger(代触发的触发器名);⚠️不要带 start/delay/duration,否则匹配不到目标分组(这些会并入触发匹配的 options)。⚠️该动作缺一次性守卫(源码无 elapsed>0 早退,且每次 start 的 cycleGuard 默认新建):命中后目标链在 elapsed=0 与结束帧各执行一遍——下游非幂等动作(sendBehaviorEvent 等)会派发两次(帧被节流时可能退化为一次)。目标链里若挂计数/累加类副作用注意去重
executeScript 执行脚本 commond(由宿主二开 onScript 接管)
requestApi 调用接口 出站 HTTP:method/url/headers(JSON 串)/body;{{payload.x}}/{{参数名}} 占位注入 payload 与项目参数;结果落地 resultMode:ignore/setParams(valuePath+paramKey 写项目参数)/setData(connection+table+dataPath 写进数据源,同取数写回路径)/emit(resultEventName 派发事件驱动 receiveBehaviorEvent)。带超时/在途去重;周期轮询取数用 api 连接 refreshInterval(页面 timer 触发器只在编辑器内预览生效)
editProjectParams 修改项目参数 params-key(string[]) + <key>-params-value
reloadData 刷新数据 connection(连接 uid,空=全部连接);目标元素被忽略。仅 api/webhook(有 url)/mock/pocketbase 真正重取,json/csv 等静态源无变化
writePayloadToEmbedded 写入内嵌数据集 table、overwrite。⚠️ 当前为空实现:宿主未提供 writeEmbeddedData,只打 console.warn,勿依赖(写数据源用 requestApi 的 resultMode:"setData" 或 SDK ctx.setData)
changeFullscreen/logoutVisitor 全屏控制/访客登出 进退全屏;logoutVisitor ⚠️ 当前为空实现(宿主未提供 logoutVisitor,只打 console.warn),勿依赖
appear/disappear 进/退场动画 effect(fade 淡 / fly 飞+direction(left/right/top/bottom)+distance(px) / enlarge 缩放+from-scale / rotate+rotate-turns / flicker 闪+flicker-times)、easing(非 flicker);时长取动作 duration(ms)。未知 effect 静默回落 fade
custom / empty 自定义(二开注册)/ 空动作 —

组件自定义动作(目标元素指向该类型组件时可用):

  • 视图容器 layout-section:openView(view: 目标视图序号)、nextView/prevView(无参循环切换)。
  • 2D 地图 17 个(mapFlyTo/mapFitData/mapLayerVisible/mapSwitchBasemap/mapRefreshData…):见 widgets/map-2d.md §6.8。
  • 组态画板:scadaWritePoint(写测点)、scadaSetOption(改图元选项):见 widgets/scada-2d.md §7.4。options 形状(官方工程里):
    { "name": "scadaWritePoint", "options": {
      "element": [{ "targetPath": ["screen_main", "w_scada"], "refType": "element" }],  // 目标画板
      "key": "PUMP_STATE",          // 字面测点键(不支持 {:x} 公式插值);值可经 payload-field 从 payload 取
      "value": "run",               // 字面值;填 "toggle" 则 0↔1 翻转
      "payload-field": "",          // 非空时改从 payload 该字段取值(覆盖 value)
      "start": "meanwhile", "delay": 0, "duration": 1   // 时序(与官方示例一致)
    } }
    
    scadaSetOption:piece-uid(留空取 payload.uid)+ option + value(JSON 优先解析),如 sprite-stop-frame 指定图像集第几张。
  • 开关 basic-switch:switchOn/switchOff/switchToggle(置开/置关/切换,均派发 switchChanged)。⚠️ switchToggle 经交互链调用是空操作:动作 start() 无 elapsed>0 一次性守卫,startInteraction 的 elapsed=0 帧与结束帧各执行一遍 → 双 toggle 净零(changeSeq +2、checked 回原态)。switchOn/switchOff 幂等不受害。要"切换"请用 switchOn/switchOff 按条件分路,或 SDK 直调 handle.raw.toggle()。

changeOption 动态取值(公式)

valueMode:"formula" + valueFormula 里用 {:expr} 插值,取值优先级:先取 payload 字段({:字段} 直名 / {:row.字段} 点路径;命中要素数据即用,{:value} 亦优先取 payload 的 value 要素数值),payload 无该字段时 {:value} 回退为目标当前值,其余名按项目参数取。结果若为纯数字串则转 number,否则当字符串。例:

"action": { "name": "changeOption", "options": {
  "targetElement": "searchElement",
  "element": [{ "targetPath": ["screen_main","w_text"], "refType": "element" }],
  "optionKey": "text", "valueDynamic": true, "valueMode": "formula",
  "valueFormula": "已选中:{:城市}", "start": "meanwhile", "delay": 0, "duration": 1 } }

多状态(state) 与 switchState(状态切换 / 数据驱动 / 页面联动)

组件/页面可声明多个状态(state),每个状态对一部分 option 做覆盖;运行时切到某状态即套用其覆盖(其余项回退基础值)。完整可导入示例见 data/数据交互/多状态切换.vpproj.json。

持久化形态(写在节点上,与 options 平级):

{
  "uid": "w_status", "type": "basic-text",
  "options": { "text": "待机", "text-font": { "size": 30, "color": "#c9d1d9" }, "background-color": "#1b2233" },
  "states": [
    { "uid": "", "name": "待机", "followScreenStates": [], "followDataRules": [] },  // ★ 默认态 uid 必须是空串 ""
    { "uid": "cs_alarm", "name": "告警" },
    { "uid": "cs_ok", "name": "正常" }
  ],
  "stateOverrides": {                                  // 键=状态 uid(不含默认态 "");只写覆盖项
    "cs_alarm": { "text": "⚠ 告警", "text-font": { "size": 30, "color": "#ff5c5c" }, "background-color": "#3a1418" },
    "cs_ok":    { "text": "✔ 正常", "text-font": { "size": 30, "color": "#52c41a" }, "background-color": "#10301a" }
  }
}

要点:

  • options = 默认态(uid:"")的基础值;stateOverrides[stateUid] 只放覆盖项,未覆盖的项回退 options。覆盖按顶层键整体替换(如改字体颜色要写完整 text-font 对象,不能只写半个)。
  • states 必须含默认态 { "uid": "", ... },否则 switchState 切不回默认。
  • 三种切换方式(引擎均有单测覆盖):
方式 怎么写 何时切
① 主动切(事件→动作) switchState 动作:{ "element":[{targetPath,refType:"element"}], "state":"<uid>" }(state:"" 回默认;目标可为组件或页面) 触发器(如 click)命中时
② 数据驱动 状态加 "useCurrentRuleField": true, "dataRuleField": <索引>, "dataRuleFunc": ">=", "dataRuleArgs": "80";组件 options 里配 "state-rule-fields": [[connUid,tableUid,fieldUid], ...](索引指向其第 N 项),取该字段首行值比较 数据/参数变化即自动重算
③ 跟随页面态 状态加 "followScreenStates": ["<页面状态uid>"];页面自身也声明 states,由按钮 switchState(目标=页面) 切页面态 页面切到列表内状态时自动切
③′ 跟随工程数据规则 状态加 "followDataRules": ["<dataRules uid>"] 该数据规则满足时自动切
  • 多个状态按 states 顺序求值,首个命中生效;都不命中时保持当前态、不回退(数据驱动场景把"常态"放默认态 options 即可)。
  • 运算符同数据规则:= > < >= <= != ⊃。

自检(事件交互 + 多状态)

[ ] interactions 为 { "version": 2, "groups": [...] } 形态
[ ] groups 数组里每个 trigger 不重复(同名同 options 只一项)
[ ] 分组 trigger.name / 各 action.name 都在上面清单里
[ ] 目标 element 写成 [{targetPath:[screenUid,widgetUid], refType:"element"}],uid 真实存在
[ ] changeOption 静态值键名 = optionKey 末段;动态值用 valueMode/valueFormula
[ ] transfer 动作 options 只留 element + trigger(不带 start/delay/duration)
[ ] conditionMeet/screenAppear/timer 写在「页面」节点的 options.interactions(只在编辑器内预览生效,/view、SDK 不跑);其余写在组件上
[ ] switchWindow 的 windowUid 指向 project.windows[] 里的窗口;switchPopup 的目标是 group-popup 组件
[ ] requestApi 的 resultMode=setData 时 connection/table 在 connections 中存在;setParams 时 paramKey 有意义
[ ] 多状态:states 含默认态 {uid:""};stateOverrides 键都是已声明的非默认状态 uid
[ ] switchState 的 state 是目标元素 states 里声明过的 uid(""=回默认)
[ ] useCurrentRuleField 状态:dataRuleField 索引在组件 state-rule-fields 范围内
[ ] followScreenStates 引用的 uid 在所属页面 states 中;followDataRules 引用 dataRules[].uid

SDK 正式用法见 platform/sdk.md。