# 页面与交互组件

本格式描述结构、位置和交互，不接收任意坐标、CSS、HTML 或脚本。先用常规布局；旧 `title + blocks + back` 页面保持兼容。可下载并导入 [完整组件示例](examples/interface-patterns.json)。

## 项目画面

可选 `manifest.display:{"type":"tablet"}`，创建时确认，整个项目共用，创建后暂不支持修改。type 为 phone/tablet/desktop/web，手机固定竖屏，其他固定横屏，不配置方向；省略时默认手机竖屏。预览按可用区域适应，也可 100% 查看，导出保留逻辑设计尺寸。组件不随应用类型自动重排，沿用 row、stack、网格和区域布局；不是任意坐标或自动响应式设计。

## 页面骨架

`blocks` 是滚动正文。`regions.top` 和 `regions.bottom` 各包含 `{placement:"fixed"|"scroll",blocks:[]}`，默认 fixed。固定区属于原型窗口，正文在两区之间滚动，不会被底栏覆盖；scroll 区随正文滚动。内容过多时缩减固定区，不要把整个表单放进固定区。

```json
{
  "title":"资料库",
  "regions":{
    "top":{"placement":"fixed","blocks":[{"id":"nav","type":"navigation","text":"资料库","label":"顶部导航","children":[{"id":"filter","type":"action","text":"筛选","icon":"filter","labelDisplay":"icon","action":"openFilters"}]}]},
    "bottom":{"placement":"fixed","blocks":[{"id":"actions","type":"toolbar","label":"资料操作","children":[{"id":"new","type":"action","text":"新建","icon":"add","emphasis":"primary","action":"openNew"}]}]}
  },
  "blocks":[{"id":"intro","type":"text","text":"你的资料"}]
}
```

- `navigation`：标题放 `text`，操作放 `children`，操作 `slot:"leading"` 在左侧，默认 trailing 在右侧。自定义顶部 navigation 替代默认标题／返回栏；明确提供返回动作。
- `toolbar`：放在需要的区域，也可以放在正文中随内容滚动。`children` 支持操作或 row/stack/group 分组。优先一个主操作，其余为普通操作；危险操作用 danger。不要把所有正文动作塞进工具栏。
- `tabbar`：2 至 5 个 action 子项，放 bottom 固定区；`selectedWhen` 指向选中条件。它表达导航目的地，不要把删除或提交当 Tab。
- `action`：`text` 是可读名称；`icon` 来自固定图标集合，`labelDisplay` 为 text/both/icon。即使只显示图标也必须提供名称。`emphasis` 为 primary/secondary/danger；`enabled` 和 `when` 仍引用已声明条件。
- 图标：back、close、more、add、search、filter、grid、list、home、settings、check、chevron-right、share、trash、bookmark、arrow-left、arrow-right、download、refresh。

## 内容组织

| type | 用途与属性 |
| --- | --- |
| stack / row | 纵排／横排，children 可嵌套；gap 为 none/small/medium/large，align 为 start/center/end/stretch/between |
| group / card | 分组底色／卡片边界，可有 text 标题及 children |
| list / list-row | 列表与包含图片、标题、描述的列表行 |
| image / placeholder | aspect 为 square/portrait/landscape/wide；image 需要 alt 或 text。无 src 时显示比例占位；src 只接受 https 图片，不执行代码。srcField 可从当前集合项或状态读取图片地址；非法地址回退为比例占位。点击入口可直接给 action |
| card / list-row | 可给 action 使整个模块可点击，此时不能再嵌套按钮或输入框 |

同一页面所有区域、子项的 id 均须唯一，最多 500 个组件、8 层嵌套。不要利用层级模拟任意定位。真实图片会由浏览器请求指定地址；示例使用比例占位。

## 弹层与动作语义

弹层仍存储在 `flow.screens`，以保持按页读取、编辑和流程图定位。给页面 `presentation:"sheet"|"dialog"|"menu"`；它的区域、正文和表单与普通页面相同。弹层出现在当前页面上，底层不可操作，保留字段、集合选择与滚动位置。

```json
{
  "openFilters":{"present":"filters"},
  "closeFilters":{"dismiss":true},
  "changeView":{"stay":true,"effect":"setView"},
  "openDetail":{"to":"detail"}
}
```

每个动作或分支必须恰好选择 to/present/dismiss/stay/routes 之一，可搭配 effect、guard、validate。`to` 不能指向弹层；跳转普通页面会清空弹层栈。默认允许关闭按钮、遮罩和 Esc；`dismissible:false` 禁止这三种快捷关闭，但必须提供显式取消或完成操作。最多叠 4 层；取消只关闭当前层，回到上一层或原页面。

关闭不自动撤销状态修改。可取消的编辑使用独立 draft 字段，确认时通过 effectCopies 写入正式字段；如果需要清空草稿，把清空声明在取消动作的 effect。不得把“回到上一层”写成跳到某个固定首页。

## 集合、选择、筛选和排序

```json
{
  "id":"items","type":"repeat","label":"资料列表",
  "itemsField":"items","itemKey":"id","mode":"grid",
  "selectedField":"selectedId","onSelect":"openDetail",
  "filterField":"query","searchFields":["title","category"],
  "filterBy":{"stateField":"category","itemField":"category"},
  "sortField":"order","sorts":[{"value":"new","key":"date","direction":"desc"}],
  "pageField":"page","pageSize":6,"emptyText":"没有匹配内容",
  "children":[{"id":"cover","type":"image","alt":"{item.title}","aspect":"portrait"},{"id":"name","type":"heading","text":"{item.title}"}]
}
```

- defaults.state.items 先声明数组，每项有唯一 itemKey；最多 100 项，每项仅支持简单字段。不是无限数据源或远程数据库。
- mode 为 list/grid/carousel。grid 两列；carousel 一项一屏，提供上一项／下一项按钮，并支持触摸横滑。手势有按钮替代；不声称已完成触摸硬件验收。
- filterField 按 searchFields 文本查找；filterBy 精确匹配分类，空分类表示全部。sortField 对应 sorts.value，key 指定项目字段，direction 为 asc/desc，数值按数值排序。
- pageField 保存从 0 开始的页码；pageSize 为 1 至 50，carousel 固定 1。过滤、排序改变后重置页码；超出范围时收敛到有效页。
- selectedField 保存项目键，不是序号；onSelect 可进入详情或打开弹层。模板里用 `{item.title}`，其他页面可用 `{selectedId.title}` 引用所选记录。不执行表达式。
- 重复模板只放内容与容器；整项选择用 onSelect，不在模板内嵌套输入和按钮。不支持重复集合内再次重复。

通用集合写入：`effectLists` 按效果名存储操作数组，最多 10 项。支持 append/update/remove；item 值为简单值或 `{from:"stateField"}`，复制本次动作之前的状态。

```json
{
  "effectLists":{
    "addItem":[{"field":"items","operation":"append","item":{"id":{"from":"draftId"},"title":{"from":"draftTitle"}}}],
    "renameItem":[{"field":"items","operation":"update","key":"id","selectedField":"selectedId","item":{"title":{"from":"draftTitle"}}}],
    "deleteItem":[{"field":"items","operation":"remove","key":"id","selectedField":"selectedId"}]
  }
}
```

对应 effects 和 effectValues 仍须声明。重复键、超出上限或选中项失效时整次动作失败，状态不部分写入。删除后明确选择回哪里、展示什么空态。

## 同页交互与反馈

- `segmented`：与 choice 一样使用 field/label/options，可配 onChange；切换只改状态，不必建两张页面。不同内容用 when 显隐。
- `accordion`：text 标题、expandedField 布尔字段、children；支持按钮与键盘展开收起。
- 分页与轮播内置按钮替代；选择、展开、翻页均保留在当前页面。
- `feedback`：variant 为 loading/progress/empty/error/toast，text 为反馈内容，children 放恢复、重试或关闭动作。位置由所在区域决定，正文反馈留在正文、底部提示放 bottom，避免覆盖操作。
- progress 用 progressField，范围 0 至 100；loading 是不确定进度。工具不会自动调用服务、计时完成或随机成功，结果应由声明的动作／外部体验结果驱动。
- blocking:true 只禁用该反馈所在区域的其他控件，保留反馈自己的取消／恢复操作。必须提供恢复操作。Toast 不允许 blocking。
- 状态条件用 when；清除 Toast 或错误状态需要显式动作，不制造未经定义的超时行为。

## 系统任务、外部事件与控制台（页面外评审）

产品画面只渲染真实用户交互，不要把系统逻辑、分支判断或异常模拟做成手机正文按钮。

- **系统任务 (`flow.tasks`)**：在动作中声明 `{"task": "connectDevice"}`。启动时保留当前页面，通过同页状态更新为 loading/feedback 反馈。任务声明 `outcomes`（如 success、failure、timeout），各个结果再驱动同页状态、弹层或跳转。
- **情境预设 (`scenarios.taskResults`)**：情境不仅可设置初始 state，还可预设任务结果（如 `taskResults: {"connectDevice": "success"}` 或 `"failure"`），无需在画面中放模拟按钮即可演示完整分支。
- **外部事件 (`flow.events`)**：定义非用户点击驱动的环境变化（如设备断连、网络中断、推送消息），按当前状态条件路由；支持 `cancelTasks` 取消正在运行的任务。
- **迟到失效保护**：任务启动具有唯一 Token；用户取消任务或离开时 Token 失效，后续迟到注入的结果会被明确忽略，保护状态不被污染。
- **体验控制台**：在手机原型下方的体验控制台中展示运行中任务与外部事件，评审者在页面外提供模拟结果。

## 检查与交接

先核对骨架与操作位置，再核对字段、滚动、弹层关闭和集合空态。检查器递归读取所有区域与嵌套内容，验证动作、条件、ID、集合键与能力边界；流程图识别弹层进入与返回。图可达不等于条件可满足，也不等于全部行为已验收。

给视觉 Agent 的交接须说明哪些操作固定、哪些随正文滚动、哪些是模态层、当前集合和选中状态、禁用与恢复条件。区域、字段、触发对象和关闭语义不能为美观被改变；图片占位不代表已有正式素材。
