# 常见陷阱（gotchas）

生成/修改工程前扫一遍本表可以省掉大多数返工。每条：症状 → 正确写法 → 详见。

## 页面与布局

| # | 陷阱 | 正确写法 | 详见 |
|---|---|---|---|
| 1 | **播放留黑边**：`fullscreen-mode` 内核缺省是 `scaling`（contain），视口非 16:9 就有黑边（编辑器新建模板/子页已默认 `fill-uniform`，但手写/生成 JSON 不经过模板） | 每个 `type:"screen"` 节点（含 screens[]/前景/背景层）**显式写** `fullscreen-mode`：默认 `fill-uniform` + `fill-auto-expand`；不要用 `stretch`（会变形） | [envelope.md](envelope.md) §4.1、[visual-style.md](visual-style.md) §3 |
| 2 | 编辑器属性面板行为不对：缺 `layout-profile` | 每个 screen 必写 `layout-profile`（`canvas-scale`/`canvas-percent`/`flow-fluid`，与铺满配方对应） | [visual-style.md](visual-style.md) §3 |
| 3 | **流式内容跑到最右边**：`.vp-container.fluid` 的 `flex-wrap` 由 `fluid-wrap` 选项控制、**默认 `wrap`**，根页面 `fluid`+`column` 内容超高会横向换列 | 根页面流式必写 **`"fluid-wrap":"nowrap"`**；行高 px+adaptive 之和不超视口；能用 static 优先 static | [visual-style.md](visual-style.md) §3 |
| 4 | 组件全部叠在左上角 | 静态布局下每个组件**必须显式写** `position` + `size`（流式子组件改用 `fluid-*`，无须 position/size） | [envelope.md](envelope.md) §3/§5 |
| 5 | `padding` 写成 CSS 顺序不生效 | 各组件顺序不同：页面/容器/`data-repeater` 的 `padding` 是 **[上,下,左,右]**（非 CSS 的上右下左），且**只在 fluid 布局生效**（栅格用独立的 `grid-padding`，同为 [上,下,左,右]）；`basic-button` 的 `padding` 是 CSS 序 **[上,右,下,左]**；`basic-paragraph`/`basic-markdown` 的 `text-padding` 是 **[左,右,上,下]**——以 schema 的 `vector<…>` 类型串为准 | [widgets/base-options.md](widgets/base-options.md)、[widgets/text.md](widgets/text.md)、[widgets/controls.md](widgets/controls.md) |

## 数据与绑定

| # | 陷阱 | 正确写法 | 详见 |
|---|---|---|---|
| 6 | 用了不存在的 `"static"` 连接器类型，导入被当未知类型 | 内联静态数据用 `connectorType:"json"`；实时演示用 `mock`（不要把造数脚本塞进 `api.config.script`）；`pocketbase` 依赖部署环境，生成工程时不要用 | [data.md](data.md) |
| 7 | 绑定不出数：三元组 uid 对不上 | `[[connUid, tableUid, fieldUid]]` 三者必须与 `connections`/`connectionData` **逐字一致**；`connections[].tables[].uid === connectionData[connUid][i].tableId`；rows 键名 == 字段 uid | [data.md](data.md) 开篇 + §3 |
| 8 | 表格显示的是示例数据 | `basic-table` 默认绑私有示例数据（`__vp_private__`），要重绑 `table-data-fields` 到真实三元组 | [widgets/tables.md](widgets/tables.md) |
| 9 | **动态数据项导入报错** `... is not valid JSON` | `dynamic-prop-entries[].name` 与 `dynamic-prop-fields` 的键必须是选项路径数组的 JSON 串：`"[\"opacity\"]"`，不是裸 `"opacity"` | [widgets/data-interactions.md](widgets/data-interactions.md) §0.3 |
| 10 | 条件样式不生效 | `conditionalStyles` 写在**节点顶层**（与 options 平级），`ruleId` 必须命中 `dataRules[].uid` | [widgets/data-interactions.md](widgets/data-interactions.md) §0.4 |
| 11 | 联动/选中传不动 | 发起方 `*-emit`、接收方 `*-receive` 都要开；cross-filter 只看接收组件自身开关。选中传递：`basic-table`（及 `map-2d`）自带 `onSelection`，自身 `select-sync-receive` 即可（表格还需 `select-sync-fields`）；**图表/列表/排行等其余接收方靠父容器广播**——运行态（/view、SDK）页面级开关无效，须把它们包进开了 `select-sync-receive` 的 `group-panel` | [widgets/data-interactions.md](widgets/data-interactions.md) §0.2 |

## builder 型组件（地图 / 组态 / 三维）

| # | 陷阱 | 正确写法 | 详见 |
|---|---|---|---|
| 12 | 空组态/三维场景被"播种"了演示内容 | `scada-2d` 必写 `"scada-initialized": true`；`scene-3d` 必写 `"scene3d-initialized": true` | [widgets/scada-2d.md](widgets/scada-2d.md)、[widgets/scene-3d.md](widgets/scene-3d.md) |
| 13 | 地图不渲染/图层顺序乱 | `map-2d` 的 `pieces` 第一个必须是 `map-body`（单例，建议 uid 就叫 `map-body`）；叠放顺序 = 数组顺序 | [widgets/map-2d.md](widgets/map-2d.md) |
| 14 | 三维场景结构非法 | `pieces` 首两个是**固定 uid 单例**：`scene3d-scene` 与 `scene3d-camera`（uid 逐字用这两个值）；同页实例上限 4 且须同引擎同服务 | [widgets/scene-3d.md](widgets/scene-3d.md) |
| 15 | 三维/地图选项名臆造 | 禁止从 vjmap SDK 文档猜 `pieces` 选项名；用 `get_doc({type:"scene-3d"})` + 各 piece 的 `get_widget_metadata`，或查 [widget-schema.generated.json](widget-schema.generated.json) | [widgets/scene-3d.md](widgets/scene-3d.md) |
| 16 | 组态图元用了 position/size 不生效 | 图元/连线用 `geo-x/y/w/h/rotate` 场景坐标；样式色留空=跟随画板 `theme` | [widgets/scada-2d.md](widgets/scada-2d.md) §7.2 |

## 容器与交互

| # | 陷阱 | 正确写法 | 详见 |
|---|---|---|---|
| 17 | data-repeater 模板放错位置 | 模板放节点顶层 `unitTemplate`（type 必须 `group-panel`），**不放 `widgets`**；模板内子组件用普通字段绑定（自动取本行） | [widgets/containers.md](widgets/containers.md) §5b |
| 18 | 循环卡片内交互命不中 | 模板内自指动作 `element` 必须留空 `[]`（写模板 uid 会指向模板而非派生实例）；要带行数据用容器自身的 `itemClicked` | [widgets/containers.md](widgets/containers.md) §5b |
| 19 | `transfer` 动作匹配不到目标 | 不要带 `start`/`delay`/`duration`（会并入触发匹配 options） | [interactions.md](interactions.md) |
| 20 | 同一触发器写了多个 group 只生效一个 | `groups` 按 trigger 内容匹配：同名同 options 只写一项，其下挂多条交互 | [interactions.md](interactions.md) |
| 20b | **按钮点了没反应** | trigger 写成了该组件没有的名字。`itemClicked` 只属于「有选中项」的数据组件（图表/表格/列表/下拉/日历/轮播/循环容器/导航…）；按钮、文本、图形这类要用通用的 `click`。写错不会报错，只是永远不触发 | 各组件文档的「触发器」表；schema 里每个 widget 的 `triggers` 字段 |
| 20c | **页面级触发器静默不跑** | `timer`/`screenAppear`/`conditionMeet` 只能写页面节点；但页面/窗口元素只在**编辑器内预览**经 `prepareScreen` 初始化监听——发布 `/view`、独立 viewer、SDK `createVpViewer` 都不走 `starTriggertListen`（`_listenTriggers` 恒空）→ **即使写对页面也不触发**；页面/窗口上的 `receiveBehaviorEvent` 与页面级 `*-receive` 开关同样失效。周期任务改用 api 连接 `refreshInterval`、SDK `setInterval`+`ctx.refreshData()` 或扩展脚本 `setInterval`；事件接收写在组件上 | [interactions.md](interactions.md) 触发器清单下注 |
| 20d | **交互动作 delay 没生效/顺序反直觉** | `start` 不写 = 串行新批次：**后项等前项 delay+duration 跑完才开始**；要并行必须显式 `start:"meanwhile"` 并入前批 | [interactions.md](interactions.md) |

## VP-TSX 环路（写 .vp.tsx 时）

| # | 陷阱 | 正确写法 | 详见 |
|---|---|---|---|
| 21 | 编译报 `React is not defined` | 文件头 `/** @jsx h */` `/** @jsxFrag Fragment */` 两行 pragma 不可省 | [vptsx.md](vptsx.md) |
| 22 | live apply 被拒：多余 import | **全文件只允许一条 import**（runtime）；循环/常量/函数全写在文件内 | [page-live-loop.md](page-live-loop.md) |
| 23 | 属性名臆造（`font-color`/`font-size`/`text-align`…） | 属性名 = schema 的 kebab-case 原名（文字颜色在 `text-font.color`，背景是 `background-color`）；拿不准查 `page_get_schemas`/schema | [page-live-loop.md](page-live-loop.md) |
| 24 | 重复 apply 报 uid 重复 / 组件被整屏替换 | 想保留/被引用的组件**显式写 uid**；省略 uid 自动生成 `type-N` 可能撞车 | [page-live-loop.md](page-live-loop.md) |
| 25 | 想恢复某选项默认值时删了那一行 | 删键会触发整屏替换；**显式写出默认值**代替删行 | [page-live-loop.md](page-live-loop.md)「应用语义」 |
| 26 | 离线交付含窗口引用的单屏文件，`window-instance`/`switchWindow` 悬空 | 含画面/窗口复用的工程导出**整工程**；增删窗口本身也要整工程 export→apply | [page-live-loop.md](page-live-loop.md) |
