# 容器与复用组件

> `group-panel` / `data-repeater`（数据循环）/ `component-instance`（母版-实例）/ `layout-section`+`nav-views`（视图容器与导航）/ `window-instance`（画面组件）。
> 返回目录：[index.md](index.md)；全量选项字典（最权威）：[widget-schema.generated.json](../widget-schema.generated.json)。

## 5. group-panel —— 分组面板（容器）

- **type**: `group-panel`　**默认尺寸**: `[400,300]`　**默认名**: "分组面板"
- 编辑器插入/`addWidget` 预置 options：`{ background:false, border:false }`。⚠️ 这是 `meta.defaultOptions`，**手写 JSON 载入不补**——按蓝图回落 `background:true`（`#01011362` 深色半透底）+ `border:true`（圆角 15、线型 none）；不要面板底就显式写 `"background": false, "border": false`。
- 子组件放节点的 `widgets: []`，其 `position` 相对面板左上角。

特有 options：

| name | 类型 | 默认 | 说明 |
|---|---|---|---|
| `children-no-events` | `boolean` | `false` | 内部组件穿透 |
| `child-state-follow` | `boolean` | `false` | 内部组件状态跟随 |
| `child-select-exclusive` | `boolean` | `false` | 内部组件选中互斥 |
| `canvas-grow` | `select` | `reflow` | 页面开 `fill-auto-expand` 时：`reflow` 吃满剩余空间(子项重排) / `scale` 保持设计比例。布局容器同款。⚠️ `scale` 只管尺寸不管位置：摆在画布增长那一侧（贴右/贴底）会留背景空白，应放在靠里一侧让主视觉吸收增量，或改用 `responsive-x/y` 锚点贴边（见 `data/布局/适配/18`） |
| `show-scrollbar` | `boolean` | `false` | 显示滚动条 |
| `scrollbar-size`/`scrollbar-color`/`scrollbar-bg-color` | — | `3`/`#a5a5a5`/`transparent` | 滚动条样式 |

无组件特有绑定字段。

---

## 5b. data-repeater —— 数据循环容器（单元模板 × 数据行）

- **type**: `data-repeater`　**默认尺寸**: `[900,240]`　**默认名**: "数据循环容器"
- 默认 options：`{ container-layout:'fluid', flex-direction:'row' }`（单元平铺成流式网格、自动换行）。
- 自定义触发器：`itemClicked`（点击某项时，payload = 该行数据）。
- **结构（关键差异）**：模板**不放 `widgets`**，而是放在节点顶层的 **`unitTemplate`**（与 `options` 平级）——一个 `group-panel` 节点；运行期按 `item-fields` 数据行把它派生成 N 份（派生实例不持久化，工程 JSON 只存这一份模板）。

```jsonc
{
  "type": "data-repeater", "uid": "w_rep", "name": "卡片墙",
  "options": {
    "position": [80,200], "size": [1760,800],
    "container-layout": "fluid", "flex-direction": "row",
    "unit-size": [330,170], "fluid-gap-type": "fixed", "fluid-gap-value": 18, "padding": [10,10,10,10],
    "max-items": 200, "overflow": "scroll-y", "empty-text": "暂无数据", "click-child-as-item": true,
    "item-fields": [["conn_x","tbl_x","名称"]],
    "row-key-field": [["conn_x","tbl_x","名称"]]
  },
  "unitTemplate": {
    "type": "group-panel", "uid": "w_rep__unit", "name": "单元模板",
    "options": { "position":[0,0], "size":[330,170], "background": true, "border": true },
    "widgets": [
      { "type":"basic-text", "uid":"tpl_name",
        "options": { "position":[22,18], "size":[286,32], "value-field": [["conn_x","tbl_x","名称"]] } }
    ]
  }
}
```

特有 options：

| name | 类型 | 默认 | 说明 |
|---|---|---|---|
| `unit-size` | `vector<宽,高>(px,min=1)` | `[180,96]` | 每个单元的宽高；非正/缺省回落默认 |
| `max-items` | `number(min=0)` | `200` | 最大派生单元数；`0`=不限制（防数据过多导致单元爆炸） |
| `overflow` | `select` | `scroll-y` | 单元总尺寸超出容器时的处理：`scroll-y`(卡片换行后纵向滚动条)、`scroll-x`(单行排列横向滚动条)、`hidden`(裁剪隐藏)。编辑模板态恒裁剪不滚动 |
| `empty-text` | `string` | `暂无数据` | 无数据且非编辑态时的占位文案 |
| `click-child-as-item` | `boolean` | `true` | `true`=点单元内任意子组件都触发「单元点击」携带该行数据；`false`=只触发子组件自身事件（点单元空白仍触发） |
| `fluid-gap-type`/`fluid-gap-value`/`fluid-align-items`/`fluid-justify-content`/`padding` | — | — | 流式网格排布（同容器流式布局键，见基类/§布局） |

绑定字段：

| name | 数量 | 说明 |
|---|---|---|
| `item-fields` | ≥1 | 要重复的数据（决定行数；其所在表即「行作用域」覆盖范围） |
| `row-key-field` | 0~1 | 行稳定标识；数据刷新/排序时按此键复用单元实例、保留交互状态（缺省用行序号） |

**行作用域（核心机制）**：`unitTemplate` 内的子组件用**普通字段绑定**（文本 `value-field`、图表 `metric-fields`/`category-field`、表格 `table-data-fields` 等）即可——只要绑定的表落在 `item-fields`/`row-key-field` 所在表内，取数会**自动落到「当前行」**（类比 Vue `v-for` 的行作用域），无须为每行单独配置；绑定别的表则照常全局取数（老组件零回归）。

**推荐视觉/布局方案**：
- **页面/外层主区用 `container-layout:"static"`（绝对定位 `position`+`size`），流式只放在 `data-repeater` 内部**（卡片墙 = `flex-direction:"row"` + 固定 `unit-size` + `overflow:"scroll-y"`）。这是最稳的“静态画布内嵌流式”配方：标题/卡片区/详情区各给明确 `position`+`size`，互不挤压，绝不拉伸或越界。
- ⚠️ **慎用根页面 `container-layout:"fluid"` + `flex-direction:"column"`**：fluid 布局的 `flex-wrap` 由 `fluid-wrap` 选项控制、**默认 `"wrap"`**——纵向流式内容高于视口时会**换到第二列**，表现为“内容整体跑到最右边、几乎看不见”。确需纵向流式时把 `fluid-wrap` 显式设为 `"nowrap"`；否则优先静态画布，或用单行 `row` 且子项总宽可控。
- 卡片固定 `unit-size`（如 `[300,186]`），靠 `overflow:"scroll-y"` 出滚动条；详情面板用固定 `position`+`size` 并在其内部也用 static 给子项排位，避免纵向流式的换列风险。
- 深色主题推荐背景 `#0B1220`、卡片 `#101B2D`、详情 `#0F172A`，主色 `#38BDF8`、数值色 `#22C55E`、警示/按钮 `#FBBF24`、次级文字 `#8EA3C2`。

**项级事件交互（三种绑定，均逐项独立派发）**：

| 想绑在哪 | 怎么配 | payload / 目标 |
|---|---|---|
| **整项**（点任意卡片） | 在 `data-repeater` **自身** `options.interactions` 配 `itemClicked` | payload = **本行整行数据**（键=字段 uid），公式里 `{:字段}` 可取任意字段（如 `{:设备}`、`{:负载}`）。`click-child-as-item:true` 时点卡片内任意子组件都会触发 |
| **项内某元素**（如卡内按钮/标签） | 在 `unitTemplate` 内**该子组件** `options.interactions` 配它自己的触发器（`click`/`mouseEnter`…），动作目标选**自身**（`element: []`） | 随模板克隆到每张卡片、各自独立触发。⚠️子组件自身的 `click` 无行数据 payload；要带本行数据请用容器的 `itemClicked` |
| **每项整体**（如整卡悬停高亮） | 在 `unitTemplate` **根面板** `options.interactions` 配 `mouseEnter`/`mouseLeave`，动作目标=自身（`element: []`） | 行包裹继承模板根 options（含 interactions），逐卡片独立 |

> ⚠️ 项内/根面板的自指动作 `element` 必须留空数组 `[]`（=自身）——**不要**写模板里的 uid，否则派生后会指向模板 uid 而非各派生实例，命不中。要联动**容器外**组件（如详情面板）则照常用 `[{ "targetPath":["screenUid","widgetUid"], "refType":"element" }]`。
> 触发器多在**预览/播放**态才生效。完整可导入示例：`data/组件/入门/26-数据循环容器-事件交互-data-repeater.vpproj.json`。

---

## 5bb. component-instance —— 组合组件实例（母版-实例，对照「有身份的组件」）

- **type**: `component-instance`　**默认尺寸**: `[400,300]`　**默认名**: 取母版名 / "组合组件"
- 默认 options：`{ background:false, border:false }`（外观取自母版派生子项）。
- **用途**：把「多组件相互调用 + 数据 + 交互」整组沉淀成**母版（VpComponentMaster）**，在大屏上以**实例**形式复用；改母版 `version+1`，实例据 `masterVersion` 判过期可同步。区别于 `group-panel`「快照图章」：实例是**有身份**的引用（masterId），可批量同步、可传 props。
- **结构（关键差异）**：实例**不放 `widgets`**；母版子树放在节点顶层的 **`master.node`**（一个 `group-panel`，与 `options` 平级的 `master` 对象内）。运行期把母版按 `props`/`overrides` 展开为具体子树，内部 uid 派生为 **`实例uid::母版子uid`**（确定性、与其它实例天然隔离，组内 element 引用随派生 uid 自动改写）。派生实例不持久化，工程 JSON 只存母版快照 + props/overrides。

```jsonc
{
  "type": "component-instance", "uid": "inst_kpi", "name": "在线用户(实例)",
  "masterId": "master_kpi_card",          // 母版 id（库记录主键）
  "masterVersion": 1,                       // 实例记录的母版版本（< 母版 version 即过期）
  "options": { "position": [300,380], "size": [460,320], "background": false, "border": false },
  "props": {                                // 输入参数：键=master.propsSchema[].key
    "title": "在线用户数",
    "unit": "人",
    "valueField": [["conn_kpi","tbl_kpi","在线用户"]]   // 数据输入 prop：指向**消费工程**的连接三元组
  },
  "overrides": { "m_title": { "text-font": { "size": 20, "color": "#00FFC6" } } },  // 按母版原 uid 打 option 补丁（props 之后）
  "master": {                               // 内嵌母版快照（离线/导出/SDK 渲染用；编辑器发布时由库回灌）
    "masterId": "master_kpi_card", "name": "KPI 卡片", "version": 1,
    "propsSchema": [
      { "key": "title", "label": "标题", "type": "string", "target": { "uid": "m_title", "path": ["text"] }, "default": "指标标题" },
      { "key": "valueField", "label": "数值字段", "type": "field", "target": { "uid": "m_value", "path": ["value-field"] } }
    ],
    "eventsSchema": [{ "key": "alerted", "label": "已置告警", "sourceUid": "m_btn" }],
    "node": { "type": "group-panel", "uid": "m_root", "options": { "position":[0,0], "size":[460,320] }, "widgets": [ /* m_title / m_value(绑 value-field) / m_btn(switchState 目标=组内 m_value) … */ ] }
  }
}
```

- **propsSchema**：把母版内某子节点的某 option 暴露为对外参数。`target.uid`=母版**原始**子 uid，`target.path`=该节点 `options` 内路径（如 `["text"]`、`["value-field"]`）。实例 `props[key]` 缺省时回落 `default`。数据输入 prop（`type:"field"`）的值是三元组，指向消费工程连接（**不**随派生改写）。
- **母版内部引用**：组内 `switchState`/cross-filter 等 `element.targetPath` 写**母版子 uid**（如 `["screen_main","m_value"]`）；派生时 widget 段自动改成 `实例uid::m_value`，故各实例切的是**各自**的卡。
- **同步/解除**：母版 `version+1` 后，过期实例可「同步最新 / 保留本地 overrides」；「解除关联」把实例展开为普通 `group-panel`（核心纯函数：`isInstanceStale`/`syncInstanceNode`/`unlinkInstanceToGroup`）。
- **作者流**（纯函数骨架，UI 可直接调）：`buildMasterFromNode(node,opts,body)` 选中分组→母版（按需打包依赖）、`buildInstanceFromMaster(master,opts)` 母版→实例、`bumpMasterVersion(master)` 升版。
- 完整可导入示例：`data/组件/组合/04-组合组件-母版与实例.vpproj.json`（一母版派生「在线用户 / CPU」两实例，props 各异、组内按钮切各自的卡）。

---

## 5c. layout-section —— 视图容器（多视图 · 一次一显，对照 arcgis ExB Section）

- **type**: `layout-section`　**默认尺寸**: `[640,400]`　**默认名**: "视图容器"
- **结构**：每个视图 = 一个 `group-panel` 子面板（放在节点 `widgets: []` 里，**面板 `name` 即视图名**），面板统一 `position:[0,0]`、`size`=容器尺寸；运行时只显示 `activeIndex` 对应的视图，其余隐藏不卸载。每视图背景/边框直接配在对应 group-panel 上（独立背景）。
- `sections` 选项（逗号/换行分隔标题）与子面板**全量同步**：改名/增删标题即同步面板；省事写法是 `sections` 与面板 name 一一对应。
- 自定义触发器：`viewChanged`（payload `{index,label}`，任何来源切换都派发）、`itemClicked`。
- 自定义动作（任意组件可指向本容器执行）：`openView`（options 带 `view`: 目标视图序号）、`nextView` / `prevView`（无参，循环切换）。

```jsonc
{
  "type": "layout-section", "uid": "sec_main", "name": "经营视图容器",
  "options": {
    "position": [80,176], "size": [1080,640],
    "sections": "经营总览,实时趋势,告警明细",      // 视图标题（=子面板 name）
    "default-active": 0, "transition": "slide-h", "transition-duration": 0.4,
    "arrows-visible": true, "dots-visible": true, "dots-position": "bottom",
    "auto-play": false                              // true 时配 play-interval/play-loop/hover-pause
  },
  "widgets": [
    { "type": "group-panel", "uid": "p_view1", "name": "经营总览",
      "options": { "position": [0,0], "size": [1080,640], "background": true, "background-color": "linear-gradient(135deg,#0d1b33,#10264a)" },
      "widgets": [ /* 该视图内容 */ ] },
    { "type": "group-panel", "uid": "p_view2", "name": "实时趋势", "options": { "position": [0,0], "size": [1080,640] }, "widgets": [] },
    { "type": "group-panel", "uid": "p_view3", "name": "告警明细", "options": { "position": [0,0], "size": [1080,640] }, "widgets": [] }
  ]
}
```

特有 options：

| name | 类型 | 默认 | 说明 |
|---|---|---|---|
| `sections` | `section-list(item=视图)` | `视图一,视图二,视图三` | 视图标题；**值仍是逗号/换行分隔字符串**（编辑器渲染为可视化视图列表管理器），与子面板全量同步 |
| `default-active` | `number(min=0)` | `0` | 初始视图序号（从 0 起） |
| `transition` / `transition-duration` | `select` / `number(秒)` | `fade` / `0.3` | 切换过渡：`none`/`fade`/`slide-h`/`slide-v` |
| `lazy-render` | `boolean` | `false` | 非激活视图首次激活才渲染（已访问保持挂载） |
| `arrows-visible` + `arrows-direction`/`arrows-size`/`arrows-offset`/`arrows-color`/`arrows-hover-color`/`arrows-bg` | — | 显示=true | 内置上一/下一箭头；`play-loop=false` 时到端禁用 |
| `dots-visible` + `dots-position`/`dots-size`/`dots-offset`/`dots-gap`/`dots-color`/`dots-active-color` | — | 显示=false | 内置指示点（位置 `bottom`/`top`/`left`/`right`），点击可切换 |
| `auto-play` + `play-interval`/`play-loop`/`hover-pause` | — | 关 | 自动播放（秒级间隔；悬停暂停；不循环则播到最后停止） |

无组件特有绑定字段（视图内容各自绑定）。

---

## 5d. nav-views —— 视图导航（链接视图容器，对照 arcgis ExB Views Navigation）

- **type**: `nav-views`　**默认尺寸**: `[360,44]`　**默认名**: "视图导航"
- **必须放在视图容器之外**，经 `link-section`（element 引用）链接同工程任一 `layout-section`；点击导航直接驱动容器切视图，容器被任何来源切换（内置箭头/自动播放/openView 动作）时导航高亮即时跟随（双向同步）。
- 自定义触发器：`viewChanged`、`itemClicked`。
- 未链接容器时编辑态显示虚线占位，播放态渲染空。

```jsonc
{
  "type": "nav-views", "uid": "nav_tabs", "name": "导航·选项卡",
  "options": {
    "position": [1220,208], "size": [620,48],
    "link-section": [{ "targetPath": ["screen_main","sec_main"], "refType": "element" }],  // 链接的视图容器
    "nav-style": "tabs",            // tabs 选项卡 / symbol 符号 / arrows 翻页按钮 / slider 滑块
    "tab-variant": "underline",     // 仅 tabs：default / underline / pill
    "views-mode": "auto",           // auto=全部视图；custom 时配 "views-subset": "0,2"
    "item-align": "flex-start", "item-gap": 10
  }
}
```

特有 options：

| name | 类型 | 默认 | 说明 |
|---|---|---|---|
| `link-section` | `element(types=layout-section)` | `[]` | 链接的视图容器（必配；形态同动作 element 引用） |
| `nav-style` | `select` | `tabs` | 样式族：`tabs`/`symbol`/`arrows`/`slider` |
| `tab-variant` | `select` | `default` | 仅 tabs：`default`/`underline`(下划线)/`pill`(药丸) |
| `orientation` / `item-gap` / `item-align` | — | `horizontal`/`6`/`center` | 排布（slider 不分方向） |
| `views-mode` + `views-subset` | `select`+`string` | `auto`/`` | `custom` 时按序号子集显示（如 `"0,2"`，保序去重） |
| `nav-font-size`、`nav-color`/`nav-active-color`/`nav-hover-color`、`nav-bg`/`nav-active-bg`/`nav-hover-bg`、`nav-radius`、`underline-width` | — | — | 三态文字/背景样式（underline 厚度仅下划线变体） |
| `symbol-shape`/`symbol-size`/`symbol-color`/`symbol-active-color`/`symbol-tooltip` | — | `dot`/10/—/—/true | 仅 symbol：`dot`/`square`/`diamond`，悬停 title 显示视图名 |
| `prev-text`/`next-text`/`show-page-no`/`arrows-loop`/`nav-disabled-color`/`nav-border-color` | — | 上一个/下一个/true/false/—/— | 仅 arrows：分页数字「2 / 5」；不循环则到端禁用 |
| `slider-thumb`/`slider-active-color`/`slider-track-color` | — | true/—/— | 仅 slider：拖动按比例换算视图序号 |

无组件特有绑定字段。完整可导入示例（容器+四种导航+动作+触发器）：`data/组件/入门/27-视图容器与视图导航-layout-section-nav-views.vpproj.json`。

---

## 5e. window-instance —— 画面组件（页面内嵌画面）

- **type**: `window-instance`　**默认尺寸**: `[400,300]`　**默认名**: "画面组件"
- 用途：把工程里的某个**画面（窗口，`project.windows[]` 顶层实体，见 envelope.md §2b）**嵌入页面复用（画面改一处、各嵌入点同步）。区别于 `switchWindow` 动作（弹出式打开），这是**静态嵌入**。

| name | 类型 | 默认 | 说明 |
|---|---|---|---|
| `window` | `window` 引用 | `[]` | 目标画面：`[{ "targetPath": ["窗口uid"], "refType": "element" }]` |
| `content-fit` | `select` | `stretch` | 画面内容缩放：`stretch`/`contain`/`cover`/`none` |
| `show-window-bg` | `boolean` | `false` | 是否显示画面自身背景 |

无组件特有绑定字段（画面内容各自绑定）。

---

## VP-TSX 写法（本家族的非平凡映射）

本文所有 JSON 形态按 [vptsx.md](../vptsx.md) 机械映射规则转 TSX；本家族三处不走 children、走**顶层保留 props**：

```tsx
// ① data-repeater：模板放 unitTemplate prop（值=一个 group-panel JSX 节点），不是 children
<data-repeater uid="w_rep" unit-size={[330, 170]} item-fields={bind('conn_x', 'tbl_x', '名称')}
  unitTemplate={<group-panel uid="w_rep__unit" position={[0, 0]} size={[330, 170]}>…</group-panel>} />

// ② component-instance：masterId/masterVersion/props/overrides/master 全是顶层 props
<component-instance uid="inst_kpi" masterId="master_kpi" masterVersion={1}
  props={{ title: '在线用户数' }} overrides={{ m_title: { 'text-font': { size: 20 } } }} master={masterDef} />

// ③ layout-section / group-tabs：视图=子面板，正常写 children（第 N 个 group-panel = 第 N 个视图）
<layout-section uid="sec_main" sections="总览,趋势" default-active={0}>
  <group-panel uid="p_v1" name="总览" position={[0, 0]} size={[1080, 640]}>…</group-panel>
  <group-panel uid="p_v2" name="趋势" position={[0, 0]} size={[1080, 640]} />
</layout-section>
```
