# 曦寒表单设计器 · 全站正文 共 18 页。索引见 https://form.docs.xihanfun.com/llms.txt --- 来源:https://form.docs.xihanfun.com/changelog # 更新日志 本页记录 `@xihan-form/core`、`@xihan-form/runtime`、`@xihan-form/designer` 各版本使用者能感知的变更,每条标注 **新增 / 修复 / 调整 / 移除**。这几个包总是同一个版本号一起发布;逐包的完整记录见仓库里各包的 `CHANGELOG.md`。 ## v1.0.0-rc.1(2026-10-21) 首次发布,以原生 Web Components 交付,不绑定前端框架。模板是纯 JSON,按模板渲染出带排布、校验、联动、异步选项与列表的表单;设计器可视化编辑模板,按实例传入的扩展登记新种类、属性编辑器、命令与面板。 - **新增** `@xihan-form/core`:表单模板的结构(字段、布局容器、子表单与子表、静态元素,校验与联动规则,表单设置)、种类登记表与属性描述、模板自检与问题码、按模板检查表单数据、联动求值、模板的编辑操作与复制粘贴片段的检查。表达式由宿主注入的引擎求值,纯 TypeScript,不依赖 DOM。 - **新增** `@xihan-form/runtime`:`` 按模板渲染表单:字段、布局、列表、校验与联动,选项与附件经 `loadOptions`、`uploadFile` 交给宿主,整表只读与禁用;`createFormController` 不经界面取值、联动与校验;与 XiHan.UI 内建语言一一对应的界面语言包(`@xihan-form/runtime/locale`)。 - **新增** `@xihan-form/designer`:`` 可视化设计器,包括以带种类图标的方形卡片列出的物料,画布上的拖放、选择、选中工具条、拖动工具、键盘编辑与复制粘贴,属性面板(属性、校验规则、联动规则三个页签,表达式边输入边检查),问题列表,按树列出模板内容的页面结构(能多选,与画布是同一份选择),面板的停靠、浮出与显隐(布局随设置保存),最上一条菜单栏(文件、编辑、视图、帮助四张菜单,F10 进出,宿主的命令挂得进来,宿主的模板标题放在菜单栏正中、保存状态放在状态栏右端),预览态与预览宽度,撤销重做(每一步带随语言切换的说明,历史记录面板列出各步、选一步就跳过去,宿主经 historyState、goToHistory、clearHistory 与 history-change 读写,步数上限 historyLimit),状态栏,设置对话框(基础、语言、布局、工具栏、帮助、快捷键与关于七页:主题与主题色只作用于设计器,主题色有跟随页面、八种预设与自定义颜色;界面语言可以单独选,同样只作用于设计器;工具栏的按钮可以藏起、换先后与分组;开发者模式与渲染调试日志;版本常量 FORM_DESIGNER_VERSION),按 `draftKey` 在本机自动保存的草稿(下次装进同一个模板时问恢复还是丢弃),第一次打开时自动开始的新手引导(autoTour、startTour() 与 tour-end,走完后记在设置里),以及开发者模式下整份模板的 JSON 编辑框(openJsonEditor(),应用后作为一步可撤销)。种类、渲染方式、编辑方式、属性编辑器、模板规则、命令、面板、物料来源、设置分区、引导步骤、菜单栏的菜单、「关于」页的几行与种类图标都能经 `extensions` 登记。 --- 来源:https://form.docs.xihanfun.com/ # 曦寒表单设计器 ```yaml layout: home title: 曦寒表单设计器 titleTemplate: 框架无关的表单设计、渲染与校验 hero: name: 曦寒表单设计器 text: 框架无关的表单设计器 tagline: 原生 Web Components · 模板即 JSON · 声明式校验与联动 · 可扩展的设计器 image: src: /images/logo.png alt: 曦寒表单设计器 actions: - theme: brand text: 快速开始 link: /quickstart - theme: alt text: 介绍 link: /introduction features: - title: 框架无关 icon: 🧩 details: 以原生 Web Components 交付,Vue、React、Angular 或不用框架的页面都能直接使用;同一页面放多个实例互不影响。 link: /introduction linkText: 了解设计原则 - title: 模板即数据 icon: 📄 details: 模板是纯 JSON,不含也不执行任何代码;需要计算的地方用受限表达式,导入模板不会带来脚本注入。 link: /guide/template linkText: 了解模板结构 - title: 排布与列表 icon: 🧱 details: 栅格、标签页、卡片、折叠面板、分组与表格布局排出版面;子表单与子表按行录入,行可增删换序。 link: /guide/template linkText: 了解布局与列表 - title: 校验与联动 icon: ✅ details: 必填、长度、范围、正则与表达式校验;按条件显示隐藏、启用禁用、必填与赋值,规则都写在模板里。 link: /guide/validation linkText: 了解校验与联动 - title: 选项与附件 icon: 🔗 details: 选项可以写死,也可以交给宿主异步加载:按搜索串筛、按上级字段级联、树形逐级展开;附件上传同样交给宿主。 link: /guide/renderer linkText: 了解渲染器 - title: 可视化设计器 icon: 🎨 details: 物料面板、拖放排布、属性与规则编辑、问题列表、复制粘贴与撤销重做;扩展可以登记新种类、属性编辑器、命令与面板。 link: /guide/designer linkText: 了解设计器 ``` --- 来源:https://form.docs.xihanfun.com/introduction # 介绍 曦寒表单设计器(XiHan.FormDesigner)是一款框架无关的表单设计器:以原生 Web Components 交付,可视化编辑表单模板,在业务页面里按模板渲染出带排布、校验、联动、异步选项与列表录入的表单。属于曦寒懿(XiHanFun)开源生态,采用 MIT 授权。 ## 能做什么 | 场景 | 用什么 | | --- | --- | | 设计与修改表单 | ``:物料面板、拖放排布、属性与规则编辑、问题列表、复制粘贴、撤销重做,可切到预览态 | | 在业务页面里填表 | ``:按模板渲染表单,取值、赋值、校验、重置与提交 | | 不经界面处理表单数据 | `createFormController`:同一套取值、联动与校验,不碰 DOM | | 在服务端或脚本里检查 | `validateDocument` 检查模板,`validateValues` 按模板校验一份数据 | 模板里能放的东西: - **字段**:单行与多行文本、可提及文本、密码、数字、验证码,下拉、可输入下拉、树选择、级联、单选组、多选组、按钮组、列表、可选标签、穿梭框、勾选框、开关,日期、日期范围、时间、时间范围,滑块、评分、颜色、标签、文件上传与签名。 - **容器**:栅格、标签页、卡片、折叠面板、分组与表格布局只管排版;子表单与子表的值是一组行,行可增删换序。 - **静态元素**:分隔线、段落、标题、提示、图片、条码与二维码,只展示、没有值。 - **规则**:校验(必填、必须勾选、长度、范围、正则、表达式)与联动(显示隐藏、启用禁用、必填与否、赋值),条件与取值都写成受限表达式。 ## 设计原则 - **框架无关**:以原生 Web Components 交付,不绑定任何前端框架。元素用 Light DOM,控件、样式与图标都来自 [XiHan.UI](https://ui.docs.xihanfun.com),样式只消费它的设计令牌。 - **零第三方依赖**:运行时只依赖 `@xihan-form/*` 与 `@xihan-ui/*`,受限表达式与格式化也来自 XiHan.UI。 - **模板即数据**:模板是纯 JSON,不含也不执行任何代码。需要计算的地方用受限表达式,导入模板不会带来脚本注入。 - **数据由使用方掌控**:组件不发网络请求。模板存在哪里、选项从哪里取、附件传到哪里、谁能改模板,都由使用方决定,经属性、回调与事件交给组件。 - **没有全局状态**:不往 `window` 上挂对象,不读写固定键名的本地存储,同一页面放多个实例互不影响。 - **按实例扩展**:新的字段种类、属性编辑器、校验规则、命令与面板都写成扩展,经元素的 `extensions` 属性传入,只对这一个实例生效。 ## 包与入口 | 入口 | 内容 | | --- | --- | | `@xihan-form/core` | 模板的类型定义、模板自检、取值校验、联动求值与编辑操作。纯 TypeScript,不依赖 DOM,浏览器与 Node.js 里都能用 | | `@xihan-form/runtime` | 业务页面用的 `` 与表单控制器 `createFormController`,样式在 `@xihan-form/runtime/style.css` | | `@xihan-form/runtime/locale` | 语言包,与 XiHan.UI 的内建语言一一对应(`formZhCN`、`formJaJP` 等),渲染器与设计器共用 | | `@xihan-form/designer` | ``,样式在 `@xihan-form/designer/style.css` | 依赖方向固定为 `designer → runtime → core`。业务页面只需要 runtime,设计器只在需要编辑模板的地方加载。 ## 工作流程 ```text 设计器 │ schema-change / save 事件 ▼ 宿主保存模板 JSON(数据库、文件,由宿主决定) │ ▼ 业务页面取模板与已有数据 │ :选项加载、附件上传经回调交给宿主 ▼ submit 事件交出表单数据 ──▶ 宿主提交,服务端再用 validateValues 校验一遍 ``` 1. 设计:在设计器里编辑模板,设计器派发 `schema-change` 与 `save`,宿主把模板 JSON 存起来。 2. 填写:业务页面取出模板与已有数据交给 ``,下拉的选项、附件的上传经回调交给宿主。 3. 提交:渲染器校验通过后派发 `submit`,宿主拿到表单数据自行提交;服务端可以用同一份模板再校验一遍。 ## 运行环境 - `@xihan-form/core` 与 `createFormController` 不依赖 DOM,可以在浏览器、Node.js 或其它 JavaScript 运行环境里检查模板、校验数据。 - `` 与 `` 要在支持 Custom Elements 的浏览器里运行。 ## 下一步 - [快速开始](./quickstart):在任意前端项目里装包、渲染一张表单并拿到提交的数据。 - [模板结构](./guide/template):字段、容器、布局与列表,以及表单数据的形状。 - [校验与联动](./guide/validation):校验规则、联动规则与表达式。 - [渲染器](./guide/renderer):取值与赋值、提交、选项加载、附件上传与只读禁用。 - [设计器](./guide/designer):编辑、属性与规则、预览、保存与命令排列。 - [扩展](./guide/extensions):新种类、渲染方式、规则、命令、面板与属性编辑器。 - [与宿主集成](./guide/host):模板的保存、权限、服务端复核与在前端框架里使用。 --- 来源:https://form.docs.xihanfun.com/quickstart # 快速开始 这一页在任意前端项目里装包、渲染一张表单并拿到提交的数据。示例都是普通的 TypeScript,Vue、React 等框架里的写法见[与宿主集成](./guide/host#在前端框架里使用)。 ## 安装 ```bash pnpm add @xihan-form/runtime @xihan-ui/web-components @xihan-ui/styles ``` `@xihan-form/runtime` 会带上 `@xihan-form/core` 与它用到的 `@xihan-ui/*` 包。只在需要编辑模板的地方再装设计器 `@xihan-form/designer`,见[设计器](./guide/designer)。 ## 写一份模板 模板是纯 JSON:一组节点,字段带数据键 `key`,校验规则写在 `validation` 里。下面是一张四个字段的联系人表单: ```ts export const schema: FormDocument = { schemaVersion: 1, settings: { labelPlacement: 'top', size: 'md' }, children: [ { type: 'field', id: 'f-name', kind: 'text', key: 'name', basic: { label: '姓名', placeholder: '请输入姓名' }, data: {}, appearance: {}, validation: [ { id: 'r-name-required', kind: 'required' }, { id: 'r-name-length', kind: 'length', min: 2, max: 20 }, ], linkage: [], }, { type: 'field', id: 'f-email', kind: 'text', key: 'email', basic: { label: '邮箱', hint: '用于接收回复' }, data: { inputType: 'email' }, appearance: {}, validation: [ { id: 'r-email-required', kind: 'required' }, { id: 'r-email-pattern', kind: 'pattern', pattern: '^[^@\\s]+@[^@\\s]+$', message: '请输入有效的邮箱' }, ], linkage: [], }, { type: 'field', id: 'f-topic', kind: 'select', key: 'topic', basic: { label: '咨询类型', initialValue: 'product' }, data: { options: [ { value: 'product', label: '产品咨询' }, { value: 'billing', label: '账单问题' }, { value: 'other', label: '其他' }, ], }, appearance: {}, validation: [], linkage: [], }, { type: 'field', id: 'f-message', kind: 'textarea', key: 'message', basic: { label: '留言' }, data: {}, appearance: { minRows: 3 }, validation: [{ id: 'r-message-length', kind: 'length', max: 200 }], linkage: [], }, ], } ``` 每个节点的字段与各种类能写的属性见[模板结构](./guide/template)与[种类与属性](./reference/kinds)。 ## 注册元素与样式 ```ts import type { FormRendererEventMap } from '@xihan-form/runtime' import { defineFormRenderer } from '@xihan-form/runtime' import { schema, values } from '../templates/contact' import '@xihan-ui/styles/index.css' import '@xihan-form/runtime/style.css' defineFormRenderer() ``` `defineFormRenderer()` 注册 `` 以及内置字段、容器用到的 XiHan.UI 元素,重复调用不报错。样式要同时引入 XiHan.UI 的皮肤与渲染器自己的样式表。 ## 渲染与提交 把模板交给元素的 `schema` 属性,已有的数据交给 `values`;校验通过后渲染器派发 `submit`,没通过派发 `invalid`: ```ts const renderer = document.createElement('xh-form-renderer') renderer.schema = schema renderer.values = values renderer.addEventListener('submit', (event: Event) => { const { values } = (event as FormRendererEventMap['submit']).detail output.textContent = JSON.stringify(values, null, 2) }) renderer.addEventListener('invalid', (event) => { const { issues } = (event as FormRendererEventMap['invalid']).detail output.textContent = `还有 ${issues.length} 处没填对` }) ``` 调用 `submit()` 走一次提交,与在文本框里按回车是同一条路径: ```ts const submit = document.createElement('button') submit.type = 'button' submit.textContent = '提交' submit.addEventListener('click', () => renderer.submit()) ``` 先点「提交」看校验:邮箱是必填的;填好后再提交,下方显示交出来的表单数据。 渲染器的事件是冒泡的 `CustomEvent`,载荷的类型在 `FormRendererEventMap` 里。`submit` 与原生表单事件同名,TypeScript 会把它推成 `SubmitEvent`,所以示例把参数标成 `Event` 再按 `FormRendererEventMap['submit']` 取 `detail`。 ## 界面语言 渲染器与设计器的界面文字缺省是英文。表单给 XiHan.UI 的每种内建语言各出一份语言包,在 `@xihan-form/runtime/locale`,名字是 `form` 加 UI 语言包的名字:`zhCN` 对 `formZhCN`,`jaJP` 对 `formJaJP`。每份都带上设计器零件同一语言的文案,与 UI 同一语言的语言包合并后全局设置,所有实例都跟着换。下面以中文为例: ```ts import { formZhCN } from '@xihan-form/runtime/locale' import { mergeXhConfig, setXhConfig } from '@xihan-ui/web-components' import { zhCN } from '@xihan-ui/web-components/locale' setXhConfig(mergeXhConfig(zhCN, formZhCN)) ``` 只改某一个渲染器的文案时,给它的 `translations` 属性传要覆盖的键,见[渲染器](./reference/renderer#formrenderertranslations)。 ## 下一步 - [模板结构](./guide/template):字段、容器、布局与列表,以及表单数据的形状。 --- 来源:https://form.docs.xihanfun.com/ai/agents # For Agents 给 Claude Code、Cursor、Codex、GitHub Copilot 这类 AI 编程工具的入口:在项目里用表单设计器时,怎样让 AI 写对接法;参与表单仓库开发时,AI 该读什么。 表单设计器没有单独的命令行或 MCP 服务端,AI 工具要的资料在两处:本站构建时产出的[机读文档](./llms),以及仓库根的 `AGENTS.md`。表单用到的 XiHan.UI 另有自己的 [AI 辅助开发](https://ui.docs.xihanfun.com/ai/agents)资料。 ## 复制这段提示词 把下面这段放进对话,或写进项目的 `AGENTS.md` / `CLAUDE.md` / `.cursor/rules`: ```text 这个项目用曦寒表单设计器(@xihan-form/*)设计表单模板、按模板渲染表单并校验提交的数据。 写代码前先读 https://form.docs.xihanfun.com/llms.txt,按需读取其中列出的页面(站点地址后加 .md 取 Markdown)。 元素、属性、事件、模板结构、种类与问题码以文档和项目里装的版本为准,不要凭记忆写,也不要套用别的表单组件的 API。 用到 XiHan.UI 的组件、设计令牌或语言包时,再读 https://ui.docs.xihanfun.com/llms.txt。 ``` ## 要装的包 | 用途 | 安装 | 注册 | 引入的样式 | | --- | --- | --- | --- | | 业务页面填表 | `pnpm add @xihan-form/runtime @xihan-ui/web-components @xihan-ui/styles` | `defineFormRenderer()` | `@xihan-ui/styles/index.css`、`@xihan-form/runtime/style.css` | | 设计模板 | `pnpm add @xihan-form/designer @xihan-form/runtime @xihan-ui/styles @xihan-ui/designer-shell` | `defineFormDesigner()` | 上面两份,再加 `@xihan-ui/designer-shell/style.css`、`@xihan-form/designer/style.css` | | 服务端或脚本里复核 | `pnpm add @xihan-form/core` | — | — | `@xihan-form/runtime` 会带上 `@xihan-form/core`。代码里要 import `@xihan-form/core`(模板类型 `FormDocument`、`validateDocument`、`validateValues` 等)时,也把它写进项目的依赖。`@xihan-ui/*` 是表单各包的 peer 依赖,由项目装唯一的一份,与项目自己用的 XiHan.UI 同一个版本,页面上不会出现两份 UI;代码里直接 import 的 `@xihan-ui/*`(如设置界面语言用的 `@xihan-ui/web-components`)同样写进项目的依赖。 ## 关键约定 把这几条一并告诉 AI,常见的错写多出在这里: - **先注册再用**:元素不在 import 时注册,应用启动时调用一次 `defineFormRenderer()` 或 `defineFormDesigner()`,重复调用不报错;`defineFormDesigner()` 已经包含渲染器。 - **对象走属性**:模板、数据、扩展与回调这类对象交给元素的属性(如 `schema`、`values`、`extensions`、`loadOptions`、`uploadFile`),不写成 attribute。Vue 里用 `.prop` 修饰,并把 `xh-` 开头的标签声明为自定义元素,见[在前端框架里使用](../guide/host#在前端框架里使用)。 - **模板是纯 JSON**:模板是一份 `FormDocument`,不含函数与脚本;字段的 `key` 是表单数据的键,校验与联动的条件、取值写成受限表达式。保存前用设计器的 `validate()` 或 core 的 `validateDocument` 检查,问题码见[问题码](../reference/issues)。 - **宿主管保存、取数与权限**:组件不发网络请求。设计器每次修改派发 `schema-change`,保存命令派发 `save`,宿主存好后调用 `markSaved()`;渲染器的选项与附件经 `loadOptions`、`uploadFile` 回调,校验通过派发 `submit`、没通过派发 `invalid`。服务端用 `resolveLinkage` 求出联动状态后再交给 `validateValues` 复核,见[与宿主集成](../guide/host)。 - **扩展走 `extensions`**:种类、渲染方式、属性编辑器、模板规则、命令与面板都经 `extensions` 登记,只对这一个实例生效,不改包内源码,见[扩展](../guide/extensions)。 - **语言包合并后全局设置**:界面文字缺省是英文。语言包在 `@xihan-form/runtime/locale`,名字是 `form` 加 XiHan.UI 语言包的名字(`zhCN` 对 `formZhCN`),与 UI 同一语言的那份合并:`setXhConfig(mergeXhConfig(zhCN, formZhCN))`,见[快速开始](../quickstart#界面语言)。 ## 参与表单仓库开发 仓库根的 `AGENTS.md` 是写给 AI 的仓库规范: | 内容 | 说明 | | --- | --- | | 仓库概览与目录结构 | `form/` 是三个包与开发预览页的工作区,`docs/` 是本站 | | 常用命令 | 构建、类型检查、单测、Lint、产物契约与冒烟,以及文档站的命令 | | 文档站 | 参考页由源码生成、不手改,正文的示例放在 `examples/` 并经类型检查与模板自检 | | 分层与依赖 | `designer → runtime → core`,core 不碰 DOM,运行时依赖只允许 `@xihan-form/*` 与 `@xihan-ui/*` | | 硬性约定 | 模板即数据、无全局状态、数据由使用方提供、对外契约、命名与语言包、样式、注释 | | 发版、分支、Git 与提交 | 各自的做法与禁止事项 | `CLAUDE.md` 是指向 `AGENTS.md` 的链接,Claude Code 打开仓库即读到;Codex 直接读 `AGENTS.md`;Cursor、GitHub Copilot 等工具在各自的规则文件里写一句「先读仓库根的 AGENTS.md」即可。 ## 相关 - [LLMs.txt](./llms) · [快速开始](../quickstart) · [与宿主集成](../guide/host) - [XiHan.UI 的 AI 辅助开发](https://ui.docs.xihanfun.com/ai/agents) --- 来源:https://form.docs.xihanfun.com/ai/llms # LLMs.txt 文档站在构建时产出一组给大语言模型读的机读资产,内容与站点正文同源。AI 工具读取它们即可了解表单设计器的模板结构、元素、API 与用法。 ## 可用资源 | 文件 | 说明 | | --- | --- | | [llms.txt](https://form.docs.xihanfun.com/llms.txt) | 索引:全部页面的标题、一句话摘要与地址,以及下列资产的入口 | | [llms-full.txt](https://form.docs.xihanfun.com/llms-full.txt) | 全站正文,正文引用的示例源码已内联 | | [llms-guide.txt](https://form.docs.xihanfun.com/llms-guide.txt) | 指南:模板结构、校验与联动、渲染器、设计器、扩展与宿主集成 | | [llms-reference.txt](https://form.docs.xihanfun.com/llms-reference.txt) | 参考:种类与属性、模板类型、问题码,以及渲染器、设计器与 core 的公开接口 | **单页 Markdown**:任何一页的站点地址后加 `.md` 即可取到这一页的 Markdown,如 [guide/template.md](https://form.docs.xihanfun.com/guide/template.md)、[reference/kinds.md](https://form.docs.xihanfun.com/reference/kinds.md)。每页正文上方的「取本页 Markdown」也指向它。 `llms-full.txt` 体积较大,多数场景读 `llms.txt` 索引后按需取单页,或只取 `llms-guide.txt`、`llms-reference.txt` 中的一份。 表单的控件、样式与语言包来自 XiHan.UI,它另有一套机读资产: | 站点 | 索引 | | --- | --- | | 视图组件 | [ui.docs.xihanfun.com/llms.txt](https://ui.docs.xihanfun.com/llms.txt) | ## 在 AI 工具中使用 所有工具都可以用同一段提示词: ```text 阅读 https://form.docs.xihanfun.com/llms.txt 了解曦寒表单设计器,写表单相关代码时按需读取其中列出的页面(站点地址后加 .md 取 Markdown);用到 XiHan.UI 的组件、令牌或语言包时再读 https://ui.docs.xihanfun.com/llms.txt。以文档为准,不要凭记忆写元素、属性、事件与模板结构。 ``` | 工具 | 做法 | | --- | --- | | Claude Code | 写进项目的 `CLAUDE.md`,或用 `/memory` 记下 | | Cursor | 在对话中用 `@Docs` 添加 `https://form.docs.xihanfun.com/llms.txt`,或写进 `.cursor/rules` | | Codex | 写进项目的 `AGENTS.md` | | GitHub Copilot | 写进 `.github/copilot-instructions.md` | | Windsurf | 写进 `.windsurf/rules` | 文档站上的资产对应最新发布的版本。表单没有自己的 MCP 服务端;用到 XiHan.UI 的部分,能接入 MCP 的工具可以用 [XiHan.UI 的 MCP Server](https://ui.docs.xihanfun.com/ai/mcp) 按项目装的版本查组件与令牌。 ## 相关 - [For Agents](./agents) · [XiHan.UI 的 LLMs.txt](https://ui.docs.xihanfun.com/ai/llms) --- 来源:https://form.docs.xihanfun.com/guide/designer # 设计器 `` 是可视化的表单设计器:物料、画布、属性与问题列表都在里面,可以切到预览看真实效果。它只吃模板、吐模板与事件,模板存在哪里、谁能改,都由宿主决定。全部属性、方法与事件见[设计器参考](../reference/designer)。 ## 接入 ```bash pnpm add @xihan-form/designer @xihan-form/runtime @xihan-ui/styles @xihan-ui/designer-shell ``` ```ts import type { FormDocument } from '@xihan-form/core' import type { FormDesignerEventMap } from '@xihan-form/designer' import { defineFormDesigner, FORM_TOOLBAR_LAYOUT } from '@xihan-form/designer' import { schema, values } from '../templates/travel' import '@xihan-ui/styles/index.css' import '@xihan-ui/designer-shell/style.css' import '@xihan-form/runtime/style.css' import '@xihan-form/designer/style.css' defineFormDesigner() ``` `defineFormDesigner()` 注册设计器以及它用到的全部元素,包括画布与预览共用的渲染器。样式要引入四份:XiHan.UI 的皮肤、设计器零件、渲染器与设计器自己的样式表。设计器要有高度,由宿主的布局给出。 ```ts const designer = document.createElement('xh-form-designer') designer.schema = schema designer.previewValues = values designer.commandLayout = { toolbar: [...FORM_TOOLBAR_LAYOUT, ['save']] } ``` `schema` 是正在编辑的模板,读它就是当前的模板;给 `schema` 赋一个新模板会清空撤销历史与选择。 ## 界面 设计器的外框从上到下是菜单栏、工具栏、中间一行与状态栏。中间一行左边是物料与页面结构,中间是画布,右边是属性,画布下方是问题列表。 | 区域 | 内容 | | --- | --- | | 菜单栏 | 文件、编辑、视图、帮助四张菜单;正中放宿主的模板标题,右边一段放宿主别的常显内容,见[菜单栏](#菜单栏) | | 工具栏 | 撤销、重做;删除、创建副本;设计与预览;拖动与预览宽度三档;末组是面板 | | 物料(左) | 卡片。按分类列出可以放进表单的种类:每组一行组名,下面是方形小卡片,图标在上、种类名在下,按面板宽度排成几列,悬停显示全名;全部展开,可搜索。拖到画布上放进想要的位置,点一下插到选中节点之后,没有选中时放到末尾。扩展可以经[物料来源](./extensions#物料来源)在后面加几组 | | 页面结构(左,物料下面) | 卡片。按树列出模板里的节点,见[页面结构](#页面结构) | | 画布(中) | 按模板排出的表单,所见即所得;只选中一个节点时它的右下角带一条选中工具条:选中上一级、上移、下移、创建副本、删除 | | 历史记录(右,属性下面) | 卡片,缺省隐藏,从工具栏或「视图」菜单的「面板」打开。按先后列出撤销历史的每一步,见[撤销与历史记录](#撤销与历史记录) | | 属性(右) | 卡片。副标题写当前编辑的对象,面板头下面一排三个页签:属性、校验规则、联动规则。没有选中时编辑表单设置,选中区域时编辑区域属性 | | 问题(画布下方) | 卡片。模板自检出的问题,面板头上是错误数与警告数,兼作筛选开关;点一条就选中并滚到出问题的节点 | | 状态栏 | 左边写选中的字段种类与字段名,选中区域写它所在的容器,多选写个数,什么都没选写字段数;右边是预览宽度,点一下回到全宽;宿主的保存状态排在预览宽度左边 | 预览态的外框不显示面板,只留菜单栏、工具栏与状态栏,画布换成渲染器,见[预览](#预览)。 ### 卡片与面板 物料、页面结构、属性、历史记录与问题列表各是一块面板,画成一张卡片:面板头写图标与标题,「?」弹出这块面板的说明,右边依次是收起、「更多」与关闭。面板停在左、右、下三栏,缺省物料与页面结构在左栏(页面结构在物料下面)、属性与历史记录在右栏(历史记录缺省隐藏)、问题在下栏;可以换栏、调先后、收成标题条,也可以浮出成小窗再吸回: - 按住面板头拖到另一栏或两块面板之间就停在那里,拖到画布上就地浮出;不用拖的话,「更多」菜单里有移到左栏、右栏、下栏,上移、下移,浮出与停靠,键盘与读屏也靠它。 - 「×」隐藏这块面板,工具栏或「视图」菜单的「面板」里勾回来再显示。菜单里还有「显示 / 隐藏所有面板」(Ctrl / ⌘ + \)、「还原面板显隐」(Ctrl / ⌘ + Shift + \)与「恢复默认布局」。 - 每块面板另有一条开关命令 `panel:<面板 id>`,可以排进工具栏、菜单栏或右键菜单。 挪了面板、改了显隐或大小,设计器把整份布局写进设置的 `layout.panels`(`DESIGNER_SETTING_KEYS.layout`),并像改了别的设置一样派发 `settings-change`,宿主存下来、下次随 `settings` 赋回即可,见[设置](#设置)。宿主要换掉内置的缺省布局时给 `defaultLayout`:用户还没存过布局时用它,「恢复默认布局」也回到它,写了的字段整项覆盖内置缺省。面板 id 用包入口导出的 `FORM_DESIGNER_PANELS`(`palette`、`outline`、`properties`、`issues`、`history`)。`settingsPolicy` 也管布局:`layout.panels.<面板 id>` 写 `hidden` 就没有这块面板,写 `locked` 就固定它;`layout.panels` 锁定整份布局。 ```ts export function hostLayout(designer: XhFormDesigner): void { // 缺省收起问题面板;物料面板固定在原处,不能挪动或隐藏 designer.defaultLayout = { version: 1, hidden: [FORM_DESIGNER_PANELS.issues] } designer.settingsPolicy = { [`${DESIGNER_SETTING_KEYS.layout}.${FORM_DESIGNER_PANELS.palette}`]: 'locked' } } ``` ### 页面结构 页面结构按树列出模板里的节点,像图层面板一样找东西、删东西、挪东西: - 顶层是模板的节点,容器下面按区域分组:栅格写「第几列」,表格布局写「第几行第几列」,标签页与折叠项写标题(没写标题时写「区域几」);卡片、分组、子表单、子表只有一个区域,子节点直接挂在容器下。表格布局只列有内容的格。 - 节点带种类的图标,写「种类 · 数据键」,下面一行小字是它的标签或标题。上面有搜索框,按名字与小字过滤。 - 点一行就在画布上选中它并滚过去,节点或区域在没切到的标签页、收着的折叠项里时先切过去、展开;画布上的选择变了,树上跟着变。悬停或用方向键走到一行时,画布上用虚线框标出它。 - 能多选,与画布上的选择是同一份:按住 Ctrl(macOS 为 ⌘)点一行或按 Ctrl / ⌘ + Space 加选、取消,按住 Shift 点一行从上一次点的那行(画布上刚选过的话从画布上最后选的那个)选到这一行。节点与区域不能同时选:新选上的有节点就只留节点;只新选上了区域时,只留和它同一个容器的区域。新选上的第一个在画布上滚进可见区。焦点在选中的行上时,Delete 删掉全部选中的行。 - Delete 或行尾的删除钮删掉这一行,与工具栏的删除同一条路,区域照删区域的规矩(表格布局的格不删,删完不少于容器的下限)。 - 拖动一行或按 Alt + 方向键挪动节点,放得下才许放(子表只收字段,不能放进自己里面),一次挪动记为一步撤销。区域行不能挪。 - 只读时能选、能标出,不能删、不能挪;预览态不显示这块面板。 扩展登记的面板给了标题也画成卡片,同样能挪动、浮出与隐藏,见[扩展](./extensions#面板)。画布总在。状态栏只受设置里的「状态栏」开关控制。 ### 菜单栏 最上一条是菜单栏,放通用的功能,四张菜单与四款设计器一致: - 「文件」:保存(Ctrl / ⌘ + S,派发 `save`,见[保存](#保存))、设置(Ctrl / ⌘ + ,,同 `openSettings()`)。 - 「编辑」:撤销、重做,剪切、复制、粘贴、删除、创建副本,全选,编辑 JSON(Ctrl / ⌘ + Shift + J,开发者模式开着时才有,见[编辑 JSON](#编辑-json))。 - 「视图」:设计、预览,拖动,预览宽度三档,「面板」与「外观」两个子菜单:面板里逐块面板一项,勾上的显示;外观里是工具栏、状态栏两个显示开关(改的是设置)。 - 「帮助」:开始引导、帮助(在新窗口打开本站的这一页,地址是包入口导出的 `FORM_DESIGNER_HELP_URL`)、快捷键(打开设置的快捷键页),关于(打开设置的关于页)。 按 F10 把焦点交给菜单栏,左右键在菜单之间走,下键、Enter 或 Space 展开;再按 F10,或在菜单栏上按 Esc,回到原来的地方。窄屏时菜单栏收成行首一颗「菜单」钮,各张菜单是它的子菜单。菜单栏常在,没有关掉它的开关。 宿主自己的操作(新建、发布、返回列表)写成一个扩展的命令,`placement` 写 `menubar:file` 就进「文件」菜单,排在设置之前,写了 `toolbar` 也上工具栏;模板标题写成设计器的子元素 `slot="menubar-center"`,显示在整条菜单栏的正中,放不下时截断加省略号,再窄就藏起;保存状态写成 `slot="status-end"`,显示在状态栏右端、预览宽度的左边,状态栏在设置里关掉时跟着不显示;别的要常显的内容写 `slot="menubar-end"`,显示在菜单栏右边。后来加的子元素也一样转过去。这样设计器外面不必另起一条操作条。扩展还能登记自己的菜单,见[扩展](./extensions#命令)。 ```ts export function hostMenubar(name: string): void { designer.extensions = [{ id: 'acme.host', commands: [ { id: 'acme.new', label: '新建', placement: ['menubar:file'], run: () => location.assign('/forms/new') }, { id: 'acme.back', label: '返回列表', placement: ['menubar:file', 'toolbar'], run: () => history.back() }, ], }] const title = document.createElement('span') title.slot = 'menubar-center' title.textContent = name const saved = document.createElement('span') saved.slot = 'status-end' saved.textContent = '已保存' designer.append(title, saved) } ``` ### 工具栏 工具栏缺省只显示图标,设置「工具栏」页的「按钮显示方式」可以换成只显示文字或图标加文字,那一页还能隐藏整条工具栏、调按钮的先后与分组、藏起不用的按钮(见[设置](#设置))。按钮的名字与快捷键报给读屏,悬停或键盘聚焦到按钮时提示命令的文字与快捷键;会删掉内容的命令按危险色显示。宽度不够时,放不下的按钮收进末尾的「更多」菜单。 末组是 designer-shell 内置的「面板」:点开一张菜单,列出能开关的面板,勾选的显示,后面是全部显隐、还原显隐与恢复默认布局。设置与帮助在菜单栏里。宿主不要哪一条,用 `commandLayout` 排掉,见[工具栏与菜单的排列](#工具栏与菜单的排列)。 撤销、重做、清空历史、剪切、复制、粘贴、创建副本、删除、全选、设计与预览,以及保存、面板、拖动、设置、快捷键、关于、帮助、外观与两个显示开关,这些四款设计器共用的命令,文字取 designer-shell 语言包的 `xh-designer-commands`,菜单栏四张菜单的名字、工具栏与菜单栏的可访问名也在这里;各语言的 `form…` 语言包已经带上。要换这些说法就改这一桶,设计器的 `translations` 属性只覆盖表单自己的文案。 ## 编辑 **放进节点**:把物料拖到画布上的位置,或点物料条目,插到选中节点之后,没有选中时放到末尾。区域只收它能放的节点,如子表的区域只收字段;放不下的位置拖过去时不给落点。 **选择**:点节点选中它,按住 Shift、Ctrl 或 ⌘ 再点是追加或取消。也可以选中容器里的区域:栅格的一列、标签页的一页、折叠面板的一项、表格布局的单元格。 **移动**:把画布上的节点拖到新位置,或用选中工具条、右键菜单与快捷键上移下移。 **拖动画布**:工具栏上的「拖动」(`H`)打开以后,在画布任意处按住拖动都是滚动画布,节点与跨度把手上也一样,不选中、不挪节点,点一下也不改选择,光标是手形,拖动中握拳;只读与预览态照样能拖。拖动途中按 Esc 停在此处,拖动工具还开着;再点一下「拖动」或按 Esc 退出,状态在外框的 `canvasTool` 上。 **右键菜单**按选中的东西给出命令:节点有剪切、复制、粘贴、创建副本、选中上一级、上移、下移与删除;表格布局的单元格另有合并、拆分、插入与删除行列;画布空白处有粘贴,开发者模式开着时还有编辑 JSON。菜单键或 Shift + F10 也能打开。 **复制粘贴**走系统剪贴板:复制的节点以表单自己的格式与 JSON 文本两种写进剪贴板,可以粘贴到另一个设计器、另一个页签,也可以粘贴一段 JSON 文本。粘贴时 id 全部换新,与目标作用域冲突的数据键改成新的键;剪贴板里的内容不是合法的表单节点时不粘贴。 快捷键分两类。带 Ctrl / ⌘ 的在整个设计器里都认,焦点在物料、属性或问题列表里也一样;焦点在输入框、文本框这些能输入文字的地方时,这些键归输入框自己(比如撤销的是输入框里的字),面板里的组件自己处理了的键也不再当快捷键。复制、剪切在画布外选着一段文字时,复制的是那段文字。 | 按键 | 作用 | | --- | --- | | Ctrl / ⌘ + Z | 撤销 | | Ctrl / ⌘ + Shift + Z,Ctrl + Y | 重做 | | Ctrl / ⌘ + C、X、V | 复制、剪切、粘贴 | | Ctrl / ⌘ + D | 创建副本:复制并紧跟着粘贴 | | Ctrl / ⌘ + A | 全选 | | Ctrl / ⌘ + S | 保存:派发 `save` | | Ctrl / ⌘ + , | 打开设置 | | Ctrl / ⌘ + \ | 显示或隐藏所有面板 | | Ctrl / ⌘ + Shift + \ | 还原面板显隐 | | Ctrl / ⌘ + Shift + J | 打开 JSON 编辑框(开发者模式开着时) | F10 不论焦点在画布、面板还是工具栏上都认:把焦点交给菜单栏,再按一次回到原来的地方。 不带 Ctrl / ⌘ 的单键只在焦点在画布里时认,物料与属性面板里的列表、开关照常用这些键: | 按键 | 作用 | | --- | --- | | Delete,Backspace | 删除选中的节点或区域 | | ↑ ↓ | 在同一层里移动选择 | | Alt + ↑ ↓ | 上移、下移选中的节点,连续按的并成一步撤销 | | Enter | 选中第一个子节点 | | H | 打开或关闭拖动工具 | | Esc | 选中上一级,再按一次清空选择;拖动工具开着时先退出拖动,选择不动 | | 菜单键,Shift + F10 | 打开右键菜单 | ## 属性、校验与联动 属性面板按选中的节点列出它的属性,分基础、数据、外观三组,每个属性用合适的编辑方式:文本、数字、开关、下拉、选项表等。多个节点同时选中时只列出它们共有的属性,取值不同的显示「多个值」,改了就写给每个节点。 下拉、单选组等字段的选项在「数据」组的选项表里编辑:一项一行,三格依次是值、文字与禁用,可以新增、删除、上移、下移,键盘都能操作。值为空、值重复或缺了文字时,模板自检报出问题,问题列表里列出这一条,选项表把它标在那一项的那一格上。 树选择、级联的选项是一棵树,在「数据」组的选项树里编辑:一项一行,下一层缩进排在上一层下面,四格依次是值、文字、禁用与操作。操作格里可以给这一项加子项或删掉它(连同下一层),表格下面的「新增一项」加在顶层末尾。键盘上方向键在格子之间走,Enter 或 F2 进格内编辑、Esc 回到格子;Alt + 上下键在同一层换序,树里已有层级时 Alt + 左右键改一层缩进,也可以按住行拖动。同一层里值重复、值为空或缺了文字时,问题同样标在那一项的那一格上。 「校验规则」与「联动规则」两个页签编辑节点的规则:新增、删除、调整顺序、改每条规则的字段。条件、取值与表达式规则用表达式输入框,边输入边检查,并补全数据键、`$value` 等变量与函数名。规则的含义见[校验与联动](./validation)。 ## 撤销与历史记录 改动随输入写进模板,连续改同一个属性合成一步撤销。拖栅格一列的跨度从按下到松手是一步,拖动中按 Esc 取消、跨度回到原样;连续按 Alt + ↑ ↓ 挪同一个节点,相隔一秒以内的并成一步,换了选择就另起一步。 撤销历史的每一步带一句说明,写成「动作 + 对象名」,如「删除「单行文本 · name」」「修改「标签」」「移动 2 个组件」,跟着设计器的语言变。工具栏上撤销、重做的全名带上下一步的说明,如「撤销:删除「单行文本 · name」」,按钮上看得见的仍是图标与「撤销」。直接用 `createFormEditor` 的宿主把 `describeFormOperations` 传给它的 `describe`,也能得到同样的说明。 历史记录面板按先后列出每一步:第一行是打开时(步数超过上限丢掉过最早的几步时写「更早的步骤已丢弃」),当前位置那一行选中,已撤销的步骤字色减淡,保存时所在的那一步标「已保存」。选一行就跳到那一步,一次撤销或重做到位,只派发一次 `schema-change`;跳回去以后再改,后面已撤销的步骤照常丢掉。只读时只能看。 清空历史(`clear-history`)丢掉全部撤销与重做的步骤,模板不变;它在历史记录面板的面板头上,不上工具栏,执行时先弹确认框,也可以经 `commandLayout` 排进工具栏或右键菜单。只读时撤销、重做与清空历史都不可用,`undo()`、`redo()` 返回 `false`。 宿主也能读写撤销历史:`historyState` 是此刻的快照(各步的说明、位置号、当前位置与起点),`goToHistory(position)` 一次跳到某一步或起点(只读、预览或拖动中返回 `false`),`clearHistory()` 清空撤销与重做;历史有任何变化(执行、撤销、重做、跳转、装载、清空、保存)都派发 `history-change`,载荷就是 `historyState`。撤销步数上限是 `historyLimit`(同名特性 `history-limit`),缺省 100,下一次给 `schema` 赋值时生效。 ## 编辑 JSON 「编辑」菜单或画布空白处右键菜单里的「编辑 JSON」、快捷键 Ctrl / ⌘ + Shift + J 或 `openJsonEditor()` 打开 JSON 编辑框,写出此刻的整份模板,缩进两格。它是调试入口,菜单、快捷键与命令只在设置「基础」里的开发者模式开着时才有;`openJsonEditor()` 方法照样能调。宿主要让用户用它,就在 `settings` 里预先写 `'general.developer': true`,或用 `settingsPolicy` 锁住 `general.developer`。 编辑框里: - 每改一次都重新解析,并按 `validate()` 同一套检查:扩展登记的种类与规则都认,给了 `expressionEngine` 时也查表达式。问题列在编辑框下面,说明与问题列表一样;点一条,光标跳到它在文字里的位置。解析不了或有错误级问题时「应用」不可用。 - 按「应用」整份换上,作为一步进撤销栈,历史里写「编辑 JSON」,撤销就回到原来那一份;派发的 `schema-change` 里是一条 `replaceDocument` 操作。 - 只读或预览态时只能看、格式化与复制,不能应用。 - 命令 id 是 `edit-json`,开发者模式开着时缺省在「编辑」菜单里、不上工具栏,要放就写进 `commandLayout`;关着时它不存在,写进排列也不显示。 ## 问题列表 设计器随改随做模板自检,问题列在问题面板里,也随 `issues-change` 派发给宿主;问题码见[问题码](../reference/issues#schema)。需要时宿主也可以调用 `validate()` 取一次。 ## 预览 工具栏或「视图」菜单的「预览」,或 `mode = 'preview'` 切到预览态:画布换成真实的渲染器,可以填写、校验、看联动,外框不显示面板,菜单栏与工具栏照常在,从那里切回设计态时面板照原来的布局回来。`previewValues` 是预览用的示例数据,`loadOptions` 是预览时的选项加载函数,`uploadFile` 是预览里文件字段的上传函数,都与渲染器的同名属性一样;不给 `uploadFile` 时,预览里选的文件不会上传,也不会写进字段值。 预览宽度有全宽、平板与手机三档(`previewWidth`),设计态的画布也按这个宽度排。 ## 设置 设置是用户偏好,每人每机一份,不随模板保存。「文件」菜单的「设置」(Ctrl / ⌘ + ,)或 `openSettings(section?)` 打开设置对话框,左边一个分区一页: - 「基础」(`general`):主题(跟随页面、跟随系统、亮色、暗色)、主题色(跟随页面、八种预设、自定义颜色)、状态栏、开发者模式(`general.developer`,缺省关;关着时编辑 JSON 这类调试入口都不出现),分页调试日志与渲染调试日志两个开关(开发者模式开着时才能改,日志见下文),以及自动保存(`general.autoSave`,缺省关,见[保存](#保存))。 - 「语言」(`language`):跟随页面(缺省)、跟随系统,或在十种语言里选一种,名字是各自的本名(简体中文、繁體中文、English、日本語……)。 - 「布局」(`layout`):一张面板表,逐块面板一行,显示开关、停在哪一栏(或浮动)、上移下移与收成标题条,表下「恢复默认布局」;和面板头的「更多」菜单一样,不靠拖动也能改布局,改的就是 `layout.panels`(见[卡片与面板](#卡片与面板))。 - 「工具栏」(`toolbar`):按钮显示方式(`toolbar.display`)、显示工具栏(`toolbar.visible`),以及一张按钮表(`toolbar.layout`):一组一块,组头三态勾选框,每个按钮一行,可以勾掉、拖动换先后或挪到别的组,表下「恢复默认」。只管顶部工具栏:右键菜单与选中工具条不在这里改,由宿主的 `commandLayout` 排。 - 「帮助」(`help`):表单的几段说明,以及「开始引导」「打开文档」两颗钮;点了先关上对话框,再跑对应的命令(`start-tour`、`help`)。 - 「快捷键」(`shortcuts`):按命令表列出,分通用、编辑、本设计器与扩展几组,同一条命令的第二个快捷键写在同一行,保存、设置这些外壳命令在「通用」里。「本设计器」那组末尾是表单不是命令的按键:↑ ↓ 选上一个、下一个,Alt + ↑ ↓ 上移、下移,Enter 选中里面第一个,Esc 选中上一级,Shift + F10 打开右键菜单。「通用」组末尾只列表单有的三个按键:F10(进出菜单栏)、Alt + F10(焦点在画布里时,焦点移到选中工具条)与 Esc;表单的画布不能微移,也不能按住空格拖动。 - 「关于」(`about`):产品名、版本(包入口导出的 `FORM_DESIGNER_VERSION`)、界面库版本与许可证,后面接各扩展给的几行(见[扩展](./extensions#关于))。 - 扩展登记的分区按各自的 `order` 排:基础、语言、布局、工具栏是 0–3,帮助、快捷键、关于是 900–920,没写 `order` 的是 0。分区 id 与内置的或别的扩展重名时,给 `extensions` 赋值就抛 `DesignerExtensionError`。 表单没有自己的设置分区:预览宽度是正在看的视图,不是偏好,仍是 `previewWidth` 与工具栏、「视图」菜单里的三档。 ```ts export function openGeneralSettings(designer: XhFormDesigner): void { designer.openSettings('general') } ``` 值是扁平的键值,键是「分区 id.条目 id」,如 `general.theme`、`language.locale`,没写的键取缺省值;基础、语言、布局、工具栏几个分区的键用 designer-shell 导出的 `DESIGNER_SETTING_KEYS`,不手写字符串。`settings` 赋值即生效;用户在对话框里改了一条,设计器换成新的一份并派发 `settings-change`,宿主存起来、下次赋回。`settingsPolicy` 隐藏或锁定条目,键是条目键或分区 id:`hidden` 不显示,一个分区的条目全藏了就没有这一页;`locked` 只读,说明换成「由所在系统管理」。 ```ts export function wireSettings(designer: XhFormDesigner, saved: FormSettings, store: (settings: FormSettings) => void): void { designer.settings = saved designer.settingsPolicy = { [DESIGNER_SETTING_KEYS.theme]: 'hidden', [DESIGNER_SETTING_KEYS.brand]: 'hidden' } designer.addEventListener('settings-change', (event) => { store((event as CustomEvent<{ settings: FormSettings }>).detail.settings) }) } ``` - **主题只作用于设计器**:主题与主题色写在 `` 自己的 `data-theme`、`data-brand` 上,页面其余部分不变;设置对话框、菜单这些浮层跟着设计器变。主题色是内置的:跟随页面(缺省),或 UI 的八种预设,或自定义颜色,宿主不用传预设。 - **语言只作用于设计器**:设置里选了语言,`` 把这一语言的 UI、designer-shell 与表单语言包作为自己的 `xhConfig` 交出去,设计器、里面的元素(含预览里的渲染器与日期选择这些 UI 元素)与它弹出的浮层都换成这种语言,页面别处不变。跟随系统按浏览器的语言偏好挑最接近的一种,挑不到用英文;语言包第一次用到时才加载。跟随页面时 `xhConfig` 是空对象,照页面的全局配置。 - **工具栏**:按钮显示方式有只显示图标(缺省)、只显示文字、图标加文字,只显示图标时按钮的名字照样报给读屏,悬停或聚焦时提示文字。关掉「显示工具栏」只是不显示那一条,命令照样能经快捷键与右键菜单执行。按钮表的缺省排列是宿主 `commandLayout` 的 `toolbar`(没写时是 `FORM_TOOLBAR_LAYOUT`)再追加扩展的那一组,用户的定制套在它上面:后装的扩展命令放到缺省排列里挨着的位置,已经没有的命令跳过。宿主不让用户改时用 `settingsPolicy` 锁定或藏掉 `toolbar.layout`。 - **调试日志**:只写到浏览器的控制台,开发者模式与这一路都开着时才记。渲染日志每次重画记一组「[xh-form-designer] render · update: 改动的属性 耗时」,重铺画布(canvas,带节点数)与模板自检(validate,带问题数)各记一组;表单没有分页,分页日志开着也没有内容。关掉开发者模式,两路日志随即停。 - **扩展读设置**:命令、面板与扩展从上下文的 `settings` 读值,各分区没写的条目已经取了缺省值,如 `context.settings['acme.dense']`。 独立使用、没有宿主替你存时,给 `settingsStorageKey`:连上文档时若没赋 `settings` 就从根所在窗口的 localStorage 读,用户改了设置就写回去;标了 `secret` 的文本条目(如密钥)不存。读写失败派发 `settings-storage-error`,设置留在内存里。不给就不读写存储。 ```ts export function standalone(designer: XhFormDesigner): void { designer.settingsStorageKey = 'form-designer:settings' designer.addEventListener('settings-storage-error', (event) => { console.warn('设置没能读写本机存储,这次改的只在内存里', (event as CustomEvent<{ error: unknown }>).detail.error) }) } ``` ## 新手引导 第一次打开设计器时,画布铺好后自动开始一遍新手引导,一步高亮一处:欢迎之后先是菜单栏(各张菜单里有什么、F10 进出菜单栏),再是工具栏、物料、画布、属性、问题列表与状态栏,然后是表单自己的三步——页面结构、属性面板的「校验规则」「联动规则」两页,以及工具栏的预览(说明里带一句「视图」菜单里也能切换设计与预览、换预览宽度),最后是设置:高亮「文件」菜单的入口,说设置在那里、引导能从「帮助」菜单重新开始。目标面板隐藏、工具栏上没有那条命令时那一步跳过;只读时不讲校验联动那一步。进入一步前,设计器把那块面板亮出来:展开收起的栏或标题条,窄屏时打开抽屉。 窄屏时菜单栏收成一颗「菜单」钮:菜单栏一步高亮那一条,设置一步落在「菜单」钮上。 - 走完、跳过(含按 Esc)或关掉时,设计器派发 `tour-end`:`reason` 是 `complete`、`skip` 或 `close`,`step` 是收起时那一步的 id。同时在设置里记下 `help.tourDone`,照常派发 `settings-change`;宿主把设置存好,下次就不再自动开始。 - 引导只铺在设计器的外框里:遮罩与高亮都不出外框,页面别处照常可用。同一页同时只开一个引导:别的设计器的引导开着时,自动开始让开;`startTour()` 与「开始引导」先收起那一个再开始。 - `autoTour` 写 `false`(特性写 `auto-tour="false"`)就不自动开始。`startTour()` 随时从头开始,没有能走的步骤时返回 `false`。「帮助」菜单与设置对话框「帮助」页的「开始引导」、命令 `start-tour` 做同一件事;命令缺省不上工具栏,要放就写进 `commandLayout`。 - 扩展用 `tourSteps` 加自己的步骤,见[扩展](./extensions#引导步骤)。 本站的示例都关掉了自动开始。 ## 保存 设计器不存模板,只告诉宿主什么时候变了、用户想什么时候存: - 每次修改派发 `schema-change`,载荷里是变化后的模板与这一步的操作,可以用来自动保存。拖动跨度这样的手势在松手时派发一次,操作是整个手势的;取消的手势不派发。 - 有没有未保存的修改变了时派发 `dirty-change`。 - 用户按 Ctrl / ⌘ + S 或点「文件」菜单的「保存」(designer-shell 内置的命令 `save`)时派发 `save`;宿主存好后调用 `markSaved()`,设计器把当前模板记为已保存。 ```ts let saved: FormDocument = schema designer.addEventListener('dirty-change', (event) => { status.textContent = (event as FormDesignerEventMap['dirty-change']).detail.dirty ? '有未保存的修改' : `已保存,共 ${saved.children.length} 个顶层节点` }) designer.addEventListener('save', () => { // 这里由宿主把模板存进数据库或文件 saved = designer.schema designer.markSaved() }) ``` 设置「基础」里开着自动保存时,设计器把未保存的修改存成这台浏览器里的草稿:停手约一秒写一次,连着改只写最后一次;存好(`markSaved()`)或撤销回已保存的那一步时删掉。下次宿主给 `schema` 装进同一个草稿键的模板、和草稿不一样时,设计器弹框问「发现未保存的草稿」:恢复就整份换成草稿、记一步撤销,丢弃就删掉;一样就悄悄删掉,只读时不问。草稿键是 `draftKey`(同名特性 `draft-key`),宿主写模板 id,不写时按页面地址区分。草稿只在本机,宿主不用存、也不用接新事件;不想让用户开,就用 `settingsPolicy` 藏起或锁住 `general.autoSave`。 `readonly` 让设计器只能选择、浏览与预览,不能修改。写成特性(attribute)时按三态读:没写或写 `"false"` 可修改,空串、`"true"` 与别的写法只读,移除后回到可修改。这是一处行为变化:此前 `readonly="false"` 也算只读,现在是可修改。 ## 工具栏与菜单的排列 `commandLayout` 按位置写命令 id 的分组:`toolbar` 是工具栏,`menubar:file`、`menubar:edit`、`menubar:view`、`menubar:help` 是菜单栏的四张菜单(扩展登记的菜单是 `menubar:<菜单 id>`),`menu:node`、`menu:region`、`menu:cell`、`menu:blank` 是选中节点、区域、表格单元格与空白处的右键菜单,`selection:node` 是只选中一个节点时浮在它右下角的选中工具条。写了某个位置就只显示列出的命令;没写的位置用内置的排列 `FORM_TOOLBAR_LAYOUT`、`FORM_MENU_LAYOUTS` 与 `FORM_SELECTION_LAYOUTS`,菜单栏用 designer-shell 的缺省排列(预览宽度三档经 `placement` 落在「视图」菜单),再把登记在这个位置的扩展命令追加成一组(菜单栏里排在那张菜单收尾的一组之前)。写成 `{ 'selection:node': [] }` 就没有选中工具条,`{ 'menubar:help': [] }` 就没有「帮助」菜单。示例在内置的工具栏末尾加了保存: ```ts designer.commandLayout = { toolbar: [...FORM_TOOLBAR_LAYOUT, ['save']] } ``` 内置命令的 id:`undo`、`redo`、`clear-history`、`copy`、`cut`、`paste`、`duplicate`、`delete`、`select-all`、`select-parent`、`move-up`、`move-down`、`merge-cells`、`split-cell`、`insert-row-above`、`insert-row-below`、`insert-column-left`、`insert-column-right`、`remove-row`、`remove-column`、`design`、`preview`、`width-full`、`width-tablet`、`width-phone`,以及 designer-shell 内置的 `save`、`panels`、`panels-toggle-all`、`panels-restore`、`layout-reset`、`pan-tool`、`settings`、`shortcuts`、`about`、`help`、`start-tour`、`edit-json`、`appearance`(外观子菜单)、`show-toolbar`、`show-status-bar` 与各面板的开关命令 `panel:palette`、`panel:outline`、`panel:properties`、`panel:history`、`panel:issues`。扩展可以登记自己的命令,见[扩展](./extensions#命令)。 ## 示例 出差申请:菜单栏正中是模板标题;点物料加一个字段,状态栏右端的保存状态显示有未保存的修改;点一下画布再按 Ctrl / ⌘ + S,或点「文件」菜单、工具栏的保存,状态显示已保存。 --- 来源:https://form.docs.xihanfun.com/guide/extensions # 扩展 新的字段种类、渲染方式、属性编辑器、模板规则、命令与面板都写成扩展:一个普通对象,经元素的 `extensions` 属性传入,只对这一个实例生效,同一页面的其它实例不受影响。内置的种类、渲染与命令也写成扩展,排在最前面,与传入的走同一条路。 ## 三层 | 层 | 类型 | 在内置之上加的字段 | 谁用 | | --- | --- | --- | --- | | core | `FormCoreExtension` | `id`、`kinds`、`documentProperties`、`controls`、`rules`、`issueText` | `validateDocument`、`createFormModel` | | 运行时 | `FormRuntimeExtension` | `fieldRenderers`、`containerRenderers`、`staticRenderers` | ``、`createFormController` | | 设计器 | `FormDesignerExtension` | `editors`、`commands`、`panels`、`paletteSources`、`settings`、`tourSteps`、`about`、`kindIcons`、`setup` | `` | 各层是超集:设计器层的扩展可以原样交给渲染器与 `validateDocument`,它们只读自己那一层的字段。业务页面只用渲染器时,扩展只写到运行时层,不会带进设计器的代码。 模板里用了扩展登记的种类,渲染器、设计器与 `validateDocument` 都要给同一组扩展,否则模板自检报 `node.unknownKind`。 ## 一个例子 下面的扩展新增「身份证号」字段,渲染沿用单行文本,规则提醒加上格式校验,工具栏多一条复制 JSON: ```ts export const idCard: FormDesignerExtension = { id: 'acme/id-card', kinds: [idCardKind], // 渲染器按种类名找渲染方式:身份证号就用单行文本的 fieldRenderers: { 'id-card': FORM_FIELD_RENDERERS.text! }, rules: [(document) => { const issues: FormSchemaIssue[] = [] patternMissing(document.children, ['children'], issues) return issues }], issueText: issue => (issue.code === 'id-card.pattern-missing' ? '身份证号字段建议加上正则校验' : undefined), commands: [{ id: 'acme/id-card.copy-json', label: '复制 JSON', placement: ['toolbar'], run: ({ designer }) => void navigator.clipboard.writeText(JSON.stringify(designer.schema, null, 2)), }], } ``` ### 种类 `kinds` 里的每一项是一个种类定义。字段的定义: ```ts /** 身份证号:值是字符串,放在物料的「输入」分类里,属性沿用单行文本的基础与外观;显示名按语言自带。 */ const idCardKind: FieldKindDefinition = { type: 'field', kind: 'id-card', label: { 'en-US': 'ID card number', 'zh-CN': '身份证号', 'zh-TW': '身分證號', 'ja-JP': '身分証番号', 'ko-KR': '신분증 번호', 'de-DE': 'Ausweisnummer', 'es-ES': 'Número de identidad', 'fr-FR': 'Numéro d’identité', 'pt-BR': 'Número de identidade', 'ru-RU': 'Номер удостоверения личности', }, category: 'input', element: 'xh-text-field', properties: { basic: text.properties.basic, data: [], appearance: text.properties.appearance }, validations: ['required', 'pattern', 'expression'], valueKind: () => 'string', emptyValue: () => '', } ``` | 字段 | 说明 | | --- | --- | | `type` | `field`、`container` 或 `static` | | `kind` | 种类名,不能与内置的或别的扩展的重名 | | `label` | 可选,显示名,按语言各给一个,见[名字](#名字) | | `category` | 物料面板里的分类,内置的有 `input`、`choice`、`datetime`、`advanced`、`layout`、`data`、`static` | | `element` | 缺省渲染用的 XiHan.UI 元素标签 | | `properties` | 三组属性描述,属性面板按它们给出编辑方式,模板自检按它们检查取值;写法见[种类与属性](../reference/kinds)里内置种类的属性表 | | `validations` | 字段与容器适用的校验规则种类 | | `valueKind` / `emptyValue` | 字段的值形状与空值,可随 `data` 变化 | 容器另有 `scope`(`list` 时带数据键、值是行的数组,`none` 只管排版)、`regionProperties`、`regions`(区域个数的上下限与新建时的区域)、`accepts`(区域能放任意节点还是只放字段)与可选的 `check`(种类自己的结构检查)。 ### 渲染 渲染器按种类名找渲染方式。`fieldRenderers` 的值是一个函数:收到字段的上下文,建出控件元素,返回 `{ element, update?, destroy? }`;标签、提示与错误文案由渲染器外面的字段外壳给出,控件不必自带。上下文里有字段节点、数据路径、当前值、状态、错误文案、选项、文案,以及 `setValue` 与 `commit`,见[渲染器参考](../reference/renderer#fieldrenderercontext)。 内置的字段渲染表 `FORM_FIELD_RENDERERS` 也导出了,新种类的控件与某个内置种类一样时可以直接拿来用,例子里身份证号就用了单行文本的。渲染方式按种类名登记,不能与内置的重名,内置种类的渲染不能被替换。 模板里的种类没有渲染方式时,渲染器不渲染整张表单,派发 `schema-error`(`XH_FORM_RT_NO_RENDERER`)。 ### 规则与问题说明 `rules` 里的每一项是 `(document, context) => 问题[]`,与内置检查一起跑,结果进 `validateDocument` 的返回值与设计器的问题列表。问题码必须以扩展 `id` 的末段加点开头,例子的 `id` 是 `acme/id-card`,问题码就是 `id-card.…`;问题可以写 `severity: 'warning'`。 ```ts /** 身份证号字段没有正则校验时给一条警告;问题码以扩展 id 的末段 `id-card.` 开头。 */ function patternMissing(nodes: readonly FormNode[], path: JsonPath, out: FormSchemaIssue[]): void { nodes.forEach((node, index) => { const at = [...path, index] if (node.type === 'field' && node.kind === 'id-card' && !node.validation.some(rule => rule.kind === 'pattern')) out.push({ code: 'id-card.pattern-missing', path: at, target: { kind: 'node', id: node.id }, severity: 'warning' }) if (node.type === 'container') node.regions.forEach((region, r) => patternMissing(region.children, [...at, 'regions', r, 'children'], out)) }) } ``` `issueText` 给自己的问题码说明文字,设计器的问题列表显示它。 ### 命令 `commands` 里的命令与内置命令一样:`{ id, label, shortLabel?, icon?, placement?, shortcut?, enabled?, checked?, tone?, run }`。`id` 用扩展 id 加点加名字;`placement` 写 `toolbar`、`menubar:file`(菜单栏的「文件」菜单,另有 `menubar:edit`、`menubar:view`、`menubar:help`)、`menu:node` 或 `selection:node`(选中一个节点时浮在它右下角的选中工具条)等时出现在对应位置的缺省排列末尾(菜单栏里排在那张菜单收尾的一组之前);扩展还能用 `menubarMenus` 往菜单栏上加自己的菜单 `{ id, label, order? }`(`order` 写在 30–80 之间,排在「视图」与「帮助」之间),命令写 `menubar:<菜单 id>` 落进去;宿主用 `commandLayout` 排列时按 id 引用;`shortcut` 是快捷键:带 `Mod` 的在整个设计器里认(焦点在文字输入里时除外),单键只在焦点在画布里时认,`Mod` 在 macOS 上是 ⌘、其余平台是 Ctrl;`icon` 取 `@xihan-ui/icons` 的图标,工具栏缺省只显示图标,没给图标的命令显示文字(`shortLabel`,没给就是 `label`);破坏性命令的 `tone` 写 `danger`。`label`、`enabled` 与 `run` 收到的上下文里有设计器实例 `designer`、设计器文案 `translations`,以及四款设计器共用的命令文案 `commands`(如 `commands.undo`)。 ## 名字 新种类在物料、画布、属性面板、状态栏与问题列表里的名字,写在种类的 `label` 上,按语言各给一个,键是 BCP 47 语言标记,与 XiHan.UI 内建语言包的语言一一对应(`en-US`、`zh-CN`……)。设计器按全局配置的 `locale`(与 XiHan.UI 的语言包合并后就有)取名:先找语言标记完全相同的,再找同一语种的(没写 `zh-TW` 时用 `zh-CN`),再用 `en-US`;都没有时显示种类的键。宿主不用再往文案里补这些名字;真要换个说法,给设计器元素的 `translations` 写 `kind`,它压在 `label` 之上。上面[种类](#种类)里的身份证号就给了十种语言的名字。 分类名取设计器文案的 `category`,属性名取 `property`,扩展新加的分类与属性的名字仍由宿主补进文案。 ```ts const designer = document.createElement('xh-form-designer') designer.extensions = [idCard] designer.schema = schema ``` 物料的「输入」分类末尾多了身份证号;点它放进画布,问题列表给出一条警告;工具栏末尾多了「复制 JSON」。 ## 种类图标 `kindIcons` 给本扩展登记的种类配图标,键是种类名,值取 `@xihan-ui/icons` 的图标;物料面板的条目与页面结构的行都用它。只能给自己 `kinds` 里的种类,给了别的种类时给 `extensions` 赋值就抛 `TypeError`。不给的种类只显示名字。内置种类的图标是包入口导出的 `FORM_KIND_ICONS`。 ## 面板 `panels` 在设计器外框里加自己的面板:`region` 给出区名(即面板 id)、缺省停的栏(`start`、`end`、`bottom`)与标题,给了 `title` 就画成卡片,面板头写标题,还可以给 `icon`、`subtitle` 与 `help`;`defaultHidden` 的面板缺省不显示。卡片和内置面板一样能挪动、浮出、收起与隐藏,写 `movable`、`floatable`、`minimizable`、`closable` 为 `false` 就不出对应的操作;每块面板自动有一条开关命令 `panel:<区名>`。区名与内置的区或别的扩展面板重名时,给 `extensions` 赋值就抛 `DesignerExtensionError`。面板元素用设计器零件时写 `headless`,标题交给卡片画。`create({ designer })` 建出面板元素,可选的 `update` 在设计器状态变化后调用、`destroy` 在撤掉时调用。面板里的条目可以拖到画布上:拖动载荷写成 `{ kind }` 是新建一个节点,写成 `{ clipboard: { nodes } }` 是按粘贴的规则插入一段节点。面板与外框的约定见视图组件文档的 [designer-shell](https://ui.docs.xihanfun.com/runtime/designer-shell)。 ## 物料来源 `paletteSources` 往物料面板里加自己的几组条目,如一组常用的字段片段:每个来源是 `{ id, order?, groups(context), insert(item, context, at?) }`。 - `groups` 每次刷新现取,组接在内置分组后面,几个来源按 `order` 排(不写按 100)。组 id 与条目 id 在整个物料面板里不能重名,与内置的种类名也不能重。 - 条目被点按(或回车)时调 `insert(item, context)`;拖到画布上放下时调 `insert(item, context, at)`,`at` 是放下处的 `FormDropTarget`(`{ parent, index? }`,`parent` 是区域 id,顶层为 `null`)。插入怎么做由来源定,比如按粘贴的规则插入一组节点:`designer.paste(clipboard, { at })`,不给 `at` 时插到选中节点之后。 - 来源 id 与别的扩展的重名时,给 `extensions` 赋值就抛 `DesignerExtensionError`。 ## 设置分区 `settings` 给设置对话框加自己的分区:`id`、`label`、可选的 `order` 与条目 `items`,条目用属性描述的写法,写了 `default` 的是缺省值。用户的取值以「分区 id.条目 id」为键,命令、面板与扩展从上下文的 `settings` 读,没写的已经取了缺省值。分区 id 不能是内置的 `general`、`language`、`layout`、`toolbar`、`help`、`shortcuts`、`about`,也不能与别的扩展重名,见[设置](./designer#设置)。 ## 引导步骤 `tourSteps` 往新手引导里加步骤,每一步是 `{ id, target?, title, description, placement?, order?, when? }`: - `id` 用扩展 id 加点加名字。不能与表单的(`form.` 开头)或别的扩展的重名,也不能用 designer-shell 留着的 `shell.` 前缀,否则给 `extensions` 赋值就抛 `DesignerExtensionError`。 - `target` 是一块面板 `{ panel, part? }`、外框的一个区 `{ region }`、工具栏上的一条命令 `{ command }`、菜单栏上的一张菜单 `{ menubar: '<菜单 id>' }`(如 `file`、`view`,或扩展登记的菜单)或任意选择器 `{ selector }`;不给就居中显示。目标面板隐藏、命令不在工具栏上、菜单栏上没有这张菜单时这一步跳过;窄屏时菜单栏收成「菜单」钮,菜单的目标落在那颗钮上。 - `title`、`description` 可以写成收上下文的函数,按此刻的语言取文字。 - `order` 定先后:designer-shell 的通用步骤是 0–60,收尾的设置一步是 900,表单的是 100–120,扩展从 200 起;同值按登记顺序。`when` 返回 `false` 时跳过这一步,如只读时不讲编辑。 引导怎么开始、怎么记下走完,见[新手引导](./designer#新手引导)。 ## 关于 `about` 给设置对话框「关于」页加几行,如附加包的版本与授权:`[{ label, value }]`,按扩展顺序接在表单自己那几行后面。 ## 属性编辑器与编辑方式 - `controls` 登记新的编辑方式:键是属性描述里的 `control.kind`,值的 `check(value)` 返回问题码或 `undefined`,模板自检按它检查取值。文本、数字、开关、下拉、选项表 `options`、选项树 `treeOptions` 等是 designer-kit 的内建编辑方式。 - `editors` 给属性面板换上自己的编辑器,可以按编辑方式、属性路径或「种类 + 路径」查找;路径带分组前缀,如 `basic.label`、`data.options`,数据键是 `key`;种类在节点上是种类名,区域是 `region`,表单设置是 `settings`。编辑器的写法见视图组件文档的 [designer-kit](https://ui.docs.xihanfun.com/runtime/designer-kit)。 - `documentProperties` 给表单设置加属性,没有选中节点时属性面板编辑它们。 ## 扩展数据 扩展自己的数据放在文档或节点的 `extensions` 里,键是扩展 `id`,值是一个 JSON 对象,与业务属性分开。设计器的编辑操作 `setExtensionData` 写它,复制粘贴时原样带着走: ```ts designer.execute([{ type: 'setExtensionData', nodeId: 'f-name', id: 'acme/id-card', data: { masked: true } }]) ``` ## 冲突与装卸 - 种类名、渲染方式、编辑方式、命令 id、快捷键与面板区名在扩展之间不能重名。给 `extensions` 赋值时当场查重,冲突抛 `DesignerExtensionError`,什么都不变。 - `setup(context)` 在设计器连上页面时调用,返回的函数在断开或换掉扩展时调用,适合挂监听。 --- 来源:https://form.docs.xihanfun.com/guide/host # 与宿主集成 组件只管设计、渲染与校验,不发网络请求,也不碰存储与权限。把它嵌进业务系统时,下面这些由宿主来做: | 事情 | 宿主做什么 | 组件给什么 | | --- | --- | --- | | 模板的保存 | 存模板 JSON,管名称、分类、版本与审核 | `schema`、`schema-change`、`save`、`dirty`、`markSaved()` | | 已有数据与提交 | 取出已有的数据,收下提交的数据并保存 | `values`、`submit`、`submitAction`、`getFormData()` | | 选项与附件 | 按选项来源返回选项,接收上传的文件 | `loadOptions`、`uploadFile` | | 权限 | 决定谁能改模板、谁能填哪些字段,服务端再复核 | `readonly`、`disabled`、`settingsPolicy`、控制器的强制设定 | | 校验 | 保存前拦住有错误的模板,服务端复核提交的数据 | `validate()`、`issues-change`、`validateDocument`、`validateValues` | ## 模板的保存 模板是一份完整的 JSON,原样存、原样取,不要拆成字段存。`schemaVersion` 记录模板格式的版本;扩展自己的数据放在文档与节点的 `extensions` 里,组件原样保留。 打开时把取回的模板赋给设计器的 `schema`,用户保存时取 `schema` 交给后端,成功后 `markSaved()`: ```ts export async function openTemplate(id: string): Promise { designer.schema = await api.loadTemplate(id) designer.addEventListener('save', async () => { await api.saveTemplate(id, designer.schema) designer.markSaved() }) } ``` 设计器在内存里改模板,页面关掉修改就没了(用户在设置里开了自动保存时,未保存的修改另有一份本机草稿,下次打开同一个模板时问要不要恢复,见[保存](./designer#保存);给设计器写 `draftKey` 为模板 id,草稿才不会串到别的模板上)。有未保存的修改时提醒用户: ```ts window.addEventListener('beforeunload', (event) => { if (designer.dirty) event.preventDefault() }) ``` 模板定下来以后,表单数据就按它的数据键存。改模板时删掉或改名的字段,旧数据里的值不会自动迁移,由宿主决定怎么处理。 ## 已有数据与提交 - **打开**:宿主取出已有的数据交给渲染器的 `values`;没有的键取模板里的初始值。 - **提交**:渲染器校验通过后派发 `submit`,交出不含隐藏字段的数据;需要等后端回应时用 `submitAction`,见[渲染器](./renderer#提交)。 - **草稿**:随时用 `getFormData()` 取当前数据存草稿,再打开时交给 `values`。 ## 选项与附件 下拉等字段的选项来源只是一个名字,渲染器按它调用宿主的 `loadOptions`;文件字段按 `uploadFile` 上传。宿主在这两个回调里调自己的接口,组件不知道接口的地址与鉴权方式。写法见[渲染器](./renderer#选项)。 ## 权限 - 只能看模板的用户把设计器设成 `readonly`:能选择、浏览与预览,不能修改;用 `settingsPolicy` 的 `layout.panels.palette` 写 `hidden` 去掉物料面板。 - 只能看表单的用户把渲染器设成 `readonly`(仍可提交)或 `disabled`(不能提交)。 - 某些字段只给特定角色填写时,按角色用控制器的 `setReadOnly`、`setHidden` 强制,见[渲染器](./renderer#只读与禁用)。 - 这些都只在前端生效。模板与数据的保存权限要在服务端判断,服务端也应复核收到的模板与数据。 ## 服务端复核 `@xihan-form/core` 不依赖 DOM,可以在 Node.js 后端直接复核: ```ts const expressionEngine = createExpressionEngine() /** 服务端收到模板时:有问题就拒绝保存。 */ export function checkTemplate(schema: unknown): boolean { return validateDocument(schema, { expressionEngine, identifiers: ['$limit'] }).length === 0 } /** 服务端收到表单数据时:先按联动求出各字段的状态与赋值,再按这些状态校验。 */ export function checkSubmission(schema: FormDocument, submitted: Record) { const variables = { $limit: 5000 } const linkage = resolveLinkage(schema, submitted, { expressionEngine, variables }) const issues = validateValues(schema, linkage.values, { expressionEngine, states: linkage.states, variables }) // 赋值字段(小计、合计)以重算出的值为准,不用提交上来的 return { values: linkage.values, issues } } ``` - 复核数据时先用 `resolveLinkage` 按联动求出各字段的状态,再交给 `validateValues`。不求联动时,按条件才变成必填的字段会被漏掉,隐藏的字段也会被当成要校验。 - 联动赋值的字段(如合计)以 `resolveLinkage` 重算出的值为准,不要相信提交上来的。 - 宿主变量(如 `$limit`)在服务端用同一份值。 后端不是 JavaScript 时,前端在提交前已经校验过;服务端把模板当作不透明的 JSON 存取,需要时另起一个 Node.js 服务做复核。 ## 在前端框架里使用 渲染器与设计器是原生 Web Components,任何框架都能用:在应用启动时调用一次 `defineFormRenderer()` 或 `defineFormDesigner()`,之后像普通元素一样放进模板。模板、数据、回调这类对象要交给元素的属性,不要写成 attribute。 在 Vue 里,先告诉编译器 `xh-` 开头的标签是自定义元素,免得被当成组件去解析: ```ts // vite.config.ts 里 @vitejs/plugin-vue 的选项 vue({ template: { compilerOptions: { isCustomElement: tag => tag.startsWith('xh-'), }, }, }) ``` 对象用 `.prop` 修饰交给属性,事件用 `@` 监听: ```vue ``` React 19 起会把对象直接赋给自定义元素的同名属性;事件可以用 `ref` 拿到元素后 `addEventListener` 监听。 ## 一个页面里放多个实例 组件不往 `window` 上挂对象,也不读写固定键名的本地存储,同一页面里的多个渲染器、多个设计器互不影响,扩展也按实例给。只有语言包是全局的:`setXhConfig` 对页面上所有元素生效,单个实例的文案用它的 `translations` 属性覆写。 --- 来源:https://form.docs.xihanfun.com/guide/renderer # 渲染器 `` 按模板渲染出表单:字段、布局、列表、校验与联动都按模板来,选项与附件经回调交给宿主,组件自己不发请求。全部属性、方法与事件见[渲染器参考](../reference/renderer)。 ## 放到页面上 ```ts import { defineFormRenderer } from '@xihan-form/runtime' import '@xihan-ui/styles/index.css' import '@xihan-form/runtime/style.css' defineFormRenderer() const renderer = document.createElement('xh-form-renderer') renderer.schema = schema document.body.append(renderer) ``` 复杂数据都走属性(property):`schema`、`values`、`variables`、`loadOptions`、`uploadFile`、`submitAction`、`translations` 与 `extensions`。可以写成 HTML 特性(attribute)的只有 `readonly`、`disabled` 与 `validate-on`。 `readonly` 与 `disabled` 按三态读:没写取缺省(不生效),写 `"false"` 是关,空串、`"true"` 与别的写法都是开,移除后回到缺省。这与 HTML 原生的布尔特性不同:原生的只要写了就算开。这是一处行为变化,渲染器此前也照原生读,`readonly="false"` 会让整表只读,现在不会;经属性(property)赋值不受影响。 模板没通过自检时表单不渲染,渲染器派发 `schema-error`,载荷里是错误码与全部模板问题;建好表单时派发 `ready`。换了 `schema`、`extensions`、`expressionEngine` 或 `loadOptions`,渲染器重建表单,再派发一次 `ready`。 ## 取值与赋值 ```ts renderer.schema = schema // 已有的数据:也是 reset 与 isDirty 的基准 renderer.values = { applicant: '张三', items: [{ name: '高铁', qty: 2, price: 553 }] } const all = renderer.getFormData() // 整份数据的副本,含联动赋值 const visible = renderer.getFormData({ omitHidden: true }) // 去掉隐藏字段的值,与 submit 交出的一样 const first = renderer.getValue(['items', 0, 'name']) // 按数据路径读 renderer.setValue(['items', 0, 'qty'], 3) // 写一个字段,形状不符时抛错 renderer.setFormData({ applicant: '李四' }, { baseline: true }) // 整份替换,同时作为新的基准 renderer.reset() // 回到基准值并清空校验状态 const changed = renderer.isDirty() ``` - 数据路径是数组:顶层字段是 `['applicant']`,列表里的字段带行号,如 `['items', 0, 'name']`。 - `values` 是初始数据,也是 `reset()` 与 `isDirty()` 的基准;表单建好后再给 `values`,等于 `setFormData(values, { baseline: true })`。 - 值的形状由种类决定,写入形状不符的值时抛 `FormRuntimeError`(`XH_FORM_RT_INVALID_VALUE`),见[问题码](../reference/issues#runtime)。 - 用户输入、联动赋值、增删行与宿主赋值都会派发 `value-change`,载荷里是变化的路径与整份数据。 ## 提交 调用 `submit()`,或在文本框里按回车,都会走一次提交:整表校验,通过时派发 `submit`,没通过时派发 `invalid`,焦点移到第一个出问题的字段。`submit` 交出的数据不含隐藏字段的值。 提交要等后端回应时,把异步动作交给 `submitAction`: ```ts renderer.submitAction = async ({ values }) => { // 返回的 Promise 落定之前表单处于提交中,期间再提交不会发生;失败时派发 submit-error await saveOrder(values) } ``` ## 选项 选择类字段(下拉、可输入下拉、树选择、级联、单选组、多选组、按钮组、列表、可选标签、穿梭框)的选项有两种来源。 **写在模板里**:`data.options` 是选项数组,每项 `{ value, label }`,可带 `disabled`;树选择与级联的选项是树,每项还可带 `children`。 ```ts { type: 'field', id: 'f-province', kind: 'select', key: 'province', basic: { label: '收货省份', placeholder: '请选择' }, data: { options: [{ value: 'zj', label: '浙江' }, { value: 'js', label: '江苏' }, { value: 'gd', label: '广东' }] }, appearance: {}, validation: [], linkage: [], }, ``` **交给宿主加载**:`data.optionSource` 是一个由宿主解释的名字,渲染器按它调用 `loadOptions(request)`,宿主返回选项数组或它的 Promise。表单建好时加载一次全部带选项来源的字段。 ```ts renderer.loadOptions = (request) => { if (request.source === 'cities') return later(CITIES[String(request.values.province)] ?? []) if (request.source === 'vendors') return later(VENDORS.filter(vendor => vendor.label.includes(request.query?.trim() ?? ''))) return later(CATEGORIES[request.parent ?? ''] ?? []) } ``` 请求里有 `source`(选项来源)、`nodeId`、`path`(字段的数据路径)与 `values`(当前表单数据),以及按情况带上的: - **依赖其它字段**:`data.optionDependencies` 列出这个字段依赖的数据键。依赖的值一变,字段回到空值并重新加载,宿主从 `request.values` 读依赖的值。依赖的键在字段所在作用域里找,找不到再往外层找。 <<< @/examples/templates/purchase.ts#cascade - **搜索**:可输入下拉在用户输入时带上 `query`,宿主按它返回匹配的选项;没有选项来源时按文字包含在静态选项里筛。可提及文本的候选也是这样取:用户敲提及符号(缺省 `@`)后面的字就是 `query`,选中的候选文字插回正文,字段值是整段正文。 <<< @/examples/templates/purchase.ts#search - **逐级展开**:树选择与级联的选项可以带 `hasChildren: true` 而不给 `children`,用户第一次展开它时请求带上 `parent`(那个节点的值),宿主只返回它的直接子项。级联在那一列末尾显示在途与失败,失败时点重试再请求一次。 <<< @/examples/templates/purchase.ts#tree 加载中字段显示在途状态;加载失败时下拉显示失败提示与重试钮,点重试再加载一次。宿主也可以经控制器的 `reloadOptions()` 主动重新加载。 单选的下拉、树选择与级联在字段不必填时带清空钮:选了值以后它顶替展开钮那一格,点一下把值清成空;字段必填(校验规则里有必填,或联动让它必填)时不带,多选能逐个去掉已选项,也不带。只读与禁用时清空钮收起。 ## 附件上传 文件字段的值是附件记录的数组,每条 `{ id, name, size?, type?, url? }`。用户选了文件,渲染器调用宿主的 `uploadFile(request)`: ```ts renderer.uploadFile = request => new Promise((resolve, reject) => { let progress = 0 const timer = setInterval(() => { progress += 20 request.onProgress(progress) if (progress >= 100) { clearInterval(timer) resolve({ id: `${request.target}-${Date.now()}` }) } }, 200) request.signal.addEventListener('abort', () => { clearInterval(timer) reject(request.signal.reason) }) }) ``` - `request.file` 是用户选的文件,`request.target` 是字段的 `data.uploadTarget`,另有 `nodeId` 与 `path`。 - 用 `request.onProgress(0–100)` 汇报进度;用户删掉文件或表单卸载时 `request.signal` 中止,把它传给底层请求。 - 返回带 `id` 的结果时,这个文件转成一条附件记录写进字段值;上传失败或被中止的文件不会写进字段值。 - 已有的附件经 `values` 交给渲染器,用户可以删掉,删掉后字段值里就没有那一条。 ## 只读与禁用 - 整张表单:渲染器的 `readonly` 与 `disabled` 属性,或模板 `settings` 里的同名设置。只读仍可提交,禁用不能提交。 - 单个节点:`basic.readOnly`、`basic.disabled`、`basic.hidden`,联动规则可以按条件改变它们,见[校验与联动](./validation)。 - 宿主强制:控制器的 `setHidden`、`setDisabled`、`setReadOnly` 盖过模板与联动,传 `null` 撤销。 ```ts // ready 之后拿到控制器;模板、扩展、表达式引擎或选项加载函数一换就是新的控制器 renderer.addEventListener('ready', () => { const controller = renderer.controller! controller.setReadOnly(['applicant'], true) // 强制只读,盖过联动 controller.setHidden('f-remark', true) // 也可以用节点 id controller.setReadOnly(['applicant'], null) // null 撤销强制 }) ``` ## 文案 内置文案缺省是英文,各语言的语言包(如中文的 `formZhCN`)与 XiHan.UI 同一语言的语言包合并后经 `setXhConfig` 全局生效,见[快速开始](../quickstart#界面语言)。某一个渲染器要用别的说法时,给它的 `translations` 传要覆盖的键,如错误摘要的标题 `errorSummary` 与新增行的 `addRow`;规则写了 `message` 时问题提示用规则的,键见[渲染器参考](../reference/renderer#formrenderertranslations)。 ## 不经界面使用 `createFormController` 是渲染器背后的表单控制器:同一套取值、联动、选项与校验,不碰 DOM,可以在没有界面的地方处理表单数据,或给自己写的界面用。 ```ts const controller = createFormController({ document: schema, expressionEngine: createExpressionEngine(), values: { type: 'travel' }, loadOptions, }) const unsubscribe = controller.subscribe((event) => { if (event.type === 'values') sync(controller.getFormData()) }) controller.setValue(['city'], '杭州') const outcome = controller.validate() // { valid, issues } unsubscribe() controller.dispose() ``` 只做校验时,也可以直接用 `@xihan-form/core` 的 `validateValues`,见[与宿主集成](./host#服务端复核)。 --- 来源:https://form.docs.xihanfun.com/guide/template # 模板结构 表单模板是一份 `FormDocument`,纯 JSON,不含函数,也不含任何会被执行的代码。它可以存进数据库、写成文件、在网络上传输,导入时只做结构检查。 ## 文档 | 字段 | 内容 | | --- | --- | | `schemaVersion` | 结构版本,当前是 `1`(`FORM_SCHEMA_VERSION`) | | `settings` | 表单设置:标签位置、尺寸、整表禁用与只读,见[表单设置](#settings) | | `children` | 顶层节点,按顺序排列 | | `extensions` | 可省,各扩展放在文档上的数据,见[扩展数据](#extension-data) | 每个类型的完整字段见[模板类型](../reference/schema)。 ## 节点 节点分三类,`type` 区分: | `type` | 是什么 | 有值吗 | | --- | --- | --- | | `field` | 字段:文本、数字、选择、日期、上传等 | 有,存在表单数据的 `key` 下 | | `container` | 容器:栅格、卡片、标签页等布局,以及子表单、子表两种列表 | 布局容器没有;列表容器的值是行的数组 | | `static` | 静态元素:分隔线、段落、标题、提示、图片、条码、二维码 | 没有 | 每个节点都有: - `id`:在整份文档里唯一。设计器的选择、问题的定位、联动的实例都按它找节点。 - `kind`:种类,如 `text`、`grid`、`divider`,决定渲染成哪个元素、能写哪些属性。 - 三个属性包 `basic`、`data`、`appearance`:基础(标签、提示、初始值、隐藏与禁用)、数据(选项、精度、数据来源)与外观(尺寸、前后缀)。只写用到的属性,各种类的属性与取值见[种类与属性](../reference/kinds)。 - `linkage`:联动规则;字段与容器还有校验规则 `validation`。规则的写法见[校验与联动](./validation)。 容器另有区域 `regions`:一个区域是放子节点的一格,带自己的属性 `props`,如栅格一列的跨度、标签页一页的标题。 ## 字段与表单数据 字段的数据键 `key` 是它的值在表单数据里的键:ASCII 字母或 `_` 开头,后接字母、数字或 `_`,在所在作用域里不能重复;`$` 开头的名字留给表达式里的变量。 值的形状随种类:文本是字符串,数字是数值,开关是布尔,多选组是字符串数组。打开某些属性会改变形状,如下拉打开「多选」后值是字符串数组。每个种类的值形状与空值见[种类与属性](../reference/kinds)。 渲染时字段的初始值取 `basic.initialValue`,没写就是种类的空值;交给渲染器的 `values` 叠在上面。 布局容器对数据透明:放在栅格、卡片、标签页里的字段,值仍在表单数据的顶层。列表容器(子表单、子表)有自己的数据键,值是行对象的数组,行里字段的键相对于行对象。下面这张出差申请的数据长这样: ```ts export const values = { applicant: '张三', department: 'rd', companions: [{ name: '李四', phone: '13800000001' }], segments: [{ from: '杭州', to: '北京', by: 'train' }], nights: 3, } ``` ## 布局容器 | 种类 | 用法 | | --- | --- | | 栅格 `grid` | 按 12 列排版,每个区域是一格,`props.span` 是这一格占几列,排满一行自动换到下一行 | | 卡片 `card` | 带标题 `basic.title` 的一块面板,只有一个区域 | | 分组 `fieldset` | 带分组标题 `basic.legend` 的一组,只有一个区域 | | 标签页 `tabs` | 每个区域是一页,页签文字是区域的 `props.title` | | 折叠面板 `collapse` | 每个区域是一项,标题是 `props.title`,`props.expanded` 决定初始是否展开 | | 表格布局 `table` | `appearance.rows` × `appearance.columns` 的格子,每个区域用 `props.row`、`props.column`、`props.rowSpan`、`props.columnSpan` 占一块;全部区域要恰好铺满表格,不能越界、重叠或留空 | 容器可以嵌套。卡片里放一个两列的栅格: ```ts { type: 'container', id: 'c-basic', kind: 'card', basic: { title: '基本信息' }, data: {}, appearance: {}, validation: [], linkage: [], regions: [{ id: 'c-basic-body', props: {}, children: [{ type: 'container', id: 'c-basic-grid', kind: 'grid', basic: {}, data: {}, appearance: {}, validation: [], linkage: [], regions: [ { id: 'c-basic-left', props: { span: 6 }, children: [{ type: 'field', id: 'f-applicant', kind: 'text', key: 'applicant', basic: { label: '申请人' }, data: {}, appearance: {}, validation: [{ id: 'r-applicant', kind: 'required' }], linkage: [], }], }, { id: 'c-basic-right', props: { span: 6 }, children: [{ type: 'field', id: 'f-department', kind: 'select', key: 'department', basic: { label: '部门' }, data: { options: [{ value: 'rd', label: '研发部' }, { value: 'sales', label: '销售部' }, { value: 'finance', label: '财务部' }] }, appearance: {}, validation: [{ id: 'r-department', kind: 'required' }], linkage: [], }], }, ], }], }], }, ``` 标签页的每个区域是一页: ```ts { type: 'container', id: 'c-more', kind: 'tabs', basic: {}, data: {}, appearance: {}, validation: [], linkage: [], regions: [ { id: 'c-more-hotel', props: { title: '住宿' }, children: [{ type: 'field', id: 'f-nights', kind: 'number', key: 'nights', basic: { label: '住几晚' }, data: { precision: 0 }, appearance: {}, validation: [{ id: 'r-nights', kind: 'range', min: 0, max: 30 }], linkage: [] }], }, { id: 'c-more-note', props: { title: '备注' }, children: [{ type: 'field', id: 'f-note', kind: 'textarea', key: 'note', basic: { label: '备注' }, data: {}, appearance: {}, validation: [], linkage: [] }], }, ], }, ``` ## 列表 子表单与子表让用户按行录入,行可以新增、删除,`data.movable` 不为 `false` 时还能上下换序;`data.initialRows` 是没有数据时先给几行。 - **子表单** `subform`:每一行照着区域里的节点排一遍,区域里能放任意节点,包括栅格与另一个列表。 - **子表** `subtable`:按表格排,区域里只能放字段,一个字段是一列,列头取字段的标签。字段的外观 `columnMinWidth`(像素)是这一列的最小宽度,金额这类值长的列给它,免得被截;各列下限加起来超过子表宽时,表格区横向滚动,行号与把手留在两侧。这一项只在字段放在子表里时生效,属性面板也只在那时列出它。 子表的区域里放两个字段,就是两列: ```ts { type: 'container', id: 'c-companions', kind: 'subtable', key: 'companions', basic: { title: '同行人员' }, data: { movable: false }, appearance: {}, validation: [], linkage: [], regions: [{ id: 'c-companions-row', props: {}, children: [ { type: 'field', id: 'f-companion-name', kind: 'text', key: 'name', basic: { label: '姓名' }, data: {}, appearance: {}, validation: [{ id: 'r-companion-name', kind: 'required' }], linkage: [], }, { type: 'field', id: 'f-companion-phone', kind: 'text', key: 'phone', basic: { label: '手机号' }, data: { inputType: 'tel' }, appearance: {}, validation: [], linkage: [], }, ], }], }, ``` 列表容器自己也能写校验:必填表示至少一行,长度按行数计。 ## 静态元素 静态元素只展示、没有值:分隔线 `divider`、段落 `paragraph`、标题 `heading`、提示 `alert`、图片 `image`、条码 `barcode` 与二维码 `qrcode`。它们只有联动,没有校验,联动也只有显示与隐藏。 ```ts { type: 'static', id: 's-title', kind: 'heading', basic: { content: '出差申请' }, data: {}, appearance: { level: 3 }, linkage: [], }, ``` ## 表单设置 {#settings} `settings` 管整张表单: | 属性 | 取值 | | --- | --- | | `labelPlacement` | 标签在控件上方 `top`(缺省)还是左侧 `start` | | `size` | 控件尺寸 `sm`、`md`、`lg`;字段自己写了 `appearance.size` 时以字段的为准 | | `disabled` | 整表禁用,不能提交 | | `readOnly` | 整表只读,仍可提交 | ## 扩展数据 {#extension-data} 文档与每个节点都可以带 `extensions`:扩展 id → 这个扩展自己的 JSON 对象。它只给扩展用,与业务属性分开,设计器编辑与复制粘贴时原样带着走。登记扩展的写法见[扩展](./extensions)。 ## 用代码拼模板 不经设计器、在代码里拼模板时,用 `createNode` 按种类新建节点:它会写好属性的缺省值,容器还会带上缺省的区域。拼好后用 `validateDocument` 自检: ```ts const { registry } = createFormModel() const ids = createIdAllocator('n') const name = createNode(registry, 'text', ids, { key: 'name' }) as FieldNode const document: FormDocument = { ...createDocument(), children: [ { ...name, basic: { ...name.basic, label: '姓名' }, validation: [{ id: ids.next(), kind: 'required' }] }, ], } const issues = validateDocument(document) ``` `validateDocument` 返回问题列表,空数组表示没有问题。问题码见[问题码](../reference/issues)。 ## 示例 出差申请:卡片里的栅格、子表、子表单与标签页。改一个值,下方显示整份表单数据。 --- 来源:https://form.docs.xihanfun.com/guide/validation # 校验与联动 校验与联动都是写在模板里的规则:字段与容器的 `validation` 是校验规则,`linkage` 是联动规则。需要计算的地方写成受限表达式,表达式只能读表单数据与宿主给的变量,不能执行任何代码。 ## 校验规则 | `kind` | 不通过的情况 | 规则里的字段 | | --- | --- | --- | | `required` | 值为空:`null`、只含空白的字符串、空数组,或元素全为空的数组;`false` 与 `0` 不算空 | — | | `accepted` | 值不是 `true`,用于「必须勾选同意」,只适用于布尔值字段 | — | | `length` | 字符串的字数或数组的元素个数不在 `[min, max]` 里;字数按用户看到的字计,一个表情算一个 | `min`、`max`,至少给一个 | | `range` | 数值不在 `[min, max]` 里 | `min`、`max`,至少给一个 | | `pattern` | 整串不匹配正则 | `pattern`(不带两边的斜杠)、`flags`(只允许 `i`、`m`、`s`、`u`) | | `expression` | 表达式的结果是 `false`;`true` 与 `null` 都算通过 | `expression`,`$value` 是字段自己的值 | 每条规则都有 `id`,在所在规则列表里唯一;`message` 可省,写了就是不通过时给用户看的提示,不写时用渲染器按问题码给的缺省文案,见[问题码](../reference/issues#value)。每个种类适用哪些规则见[种类与属性](../reference/kinds),写了不适用的规则时模板自检报 `rule.notApplicable`。 校验的约定: - 值为空时只看 `required` 与 `accepted`,其余规则跳过,「选填但填了就要合规」不必另写条件。 - 隐藏或禁用的字段不校验,连同被隐藏、禁用的容器里的全部字段;只读的字段照常校验。 - 列表容器(子表单、子表)自己的规则针对整份列表:`required` 表示至少一行,`length` 按行数计;行里的字段各按各的规则校验。 ```ts { type: 'field', id: 'f-invoices', kind: 'number', key: 'invoices', basic: { label: '发票张数' }, data: { precision: 0 }, appearance: {}, validation: [ { id: 'r-invoices-required', kind: 'required' }, { id: 'r-invoices-max', kind: 'expression', expression: '$value <= 20', message: '一次最多报销 20 张发票' }, ], linkage: [], }, { type: 'field', id: 'f-agree', kind: 'checkbox', key: 'agree', basic: { label: '我确认以上费用真实发生' }, data: {}, appearance: {}, validation: [{ id: 'r-agree', kind: 'accepted', message: '提交前请先确认' }], linkage: [], }, ``` ## 什么时候校验 - 一个字段第一次校验的时机由渲染器的 `validateOn` 决定:缺省 `blur` 是离开字段时,`change` 是改值时,`submit` 是提交时。校验过的字段此后随改值重算。 - 提交时校验全部字段。没通过时派发 `invalid`,焦点移到第一个出问题的字段,表单上方列出错误摘要;通过时派发 `submit`。 - 宿主也可以随时调用 `validate()` 校验全部字段、`validateField(path)` 校验一个字段,两者都返回 `{ valid, issues }`,不派发提交事件。 ## 联动规则 联动规则按条件改变节点的状态,或给字段赋值: | `action` | 条件成立时 | 适用于 | | --- | --- | --- | | `show` / `hide` | 显示 / 隐藏 | 字段、容器、静态元素 | | `enable` / `disable` | 可用 / 禁用 | 字段、容器 | | `require` / `optional` | 必填 / 不必填 | 字段 | | `assign` | 把 `value` 表达式的结果赋给字段 | 字段 | `when` 是条件,结果为 `true` 时成立;不写表示总是成立。条件不成立时节点回到模板里的静态设定:`basic.hidden`、`basic.disabled`、`basic.readOnly`,以及有没有 `required` 规则。 按类型显示、按条件必填: ```ts { type: 'field', id: 'f-city', kind: 'text', key: 'city', basic: { label: '出差城市' }, data: {}, appearance: {}, validation: [], linkage: [ { id: 'l-city-hide', action: 'hide', when: 'type != \'travel\'' }, { id: 'l-city-require', action: 'require', when: 'type == \'travel\'' }, ], }, ``` 联动的约定: - 每个节点从静态设定出发,按文档顺序依次执行条件成立的规则,同一状态有多条规则成立时排在后面的生效。 - 赋值立即生效,后面的表达式读到的是新值;赋值引起的连锁变化会重算到稳定为止,仍不稳定时渲染器派发 `linkage-error`。 - 隐藏与禁用向下叠加:容器隐藏了,里面的节点都隐藏。 - 被隐藏的字段保留它的值,重新显示时还在;提交时交出的数据不含隐藏字段的值。 - `require` 让没有 `required` 规则的字段变成必填,提示用缺省文案;`optional` 让有 `required` 规则的字段暂时不必填。 ## 表达式 表达式是 [XiHan.UI 的受限表达式](https://ui.docs.xihanfun.com/runtime/expression):有四则运算、比较、`&&`、`||`、`!`、条件运算 `?:`,以及逻辑、数学、聚合、文本与日期函数,不能调用宿主代码以外的任何东西。数字按十进制计算,`0.1 + 0.2 == 0.3` 成立。 表达式里能用的名字: | 名字 | 是什么 | | --- | --- | | 顶层的数据键 | 表单数据顶层的值,如 `type`、`total`;列表容器的键是行的数组,如 `items` | | `$value` | 字段或列表容器自己的值 | | `$row` | 列表里的节点所在的那一行,如 `$row.qty` | | `$index` | 列表里的节点所在的行号,从 0 起 | | `$` 开头的宿主变量 | 渲染器 `variables` 属性给的值,如 `$limit` | `a.b` 在 `a` 是数组时逐行取字段,聚合函数会把数组展开,所以 `SUM(items.amount)` 就是明细各行小计之和。值为 `null` 时运算大多得到 `null`,条件是 `null` 时不成立。 行里的小计与表单的合计都用赋值算出: ```ts { type: 'container', id: 'c-items', kind: 'subtable', key: 'items', basic: { title: '明细' }, data: { initialRows: 1 }, appearance: {}, validation: [{ id: 'r-items', kind: 'required', message: '至少填写一条明细' }], linkage: [], regions: [{ id: 'c-items-row', props: {}, children: [ { type: 'field', id: 'f-item', kind: 'text', key: 'name', basic: { label: '名目' }, data: {}, appearance: { columnMinWidth: 120 }, validation: [{ id: 'r-item', kind: 'required' }], linkage: [] }, { type: 'field', id: 'f-qty', kind: 'number', key: 'qty', basic: { label: '数量' }, data: { precision: 0 }, appearance: { columnMinWidth: 104 }, validation: [{ id: 'r-qty', kind: 'range', min: 1 }], linkage: [] }, { type: 'field', id: 'f-price', kind: 'number', key: 'price', basic: { label: '单价' }, data: { precision: 2 }, appearance: { columnMinWidth: 144 }, validation: [], linkage: [] }, { type: 'field', id: 'f-amount', kind: 'number', key: 'amount', basic: { label: '小计', readOnly: true }, data: { precision: 2 }, appearance: { columnMinWidth: 144 }, validation: [], linkage: [{ id: 'l-amount', action: 'assign', value: '$row.qty * $row.price' }], }, ], }], }, ``` ```ts { type: 'field', id: 'f-total', kind: 'number', key: 'total', basic: { label: '合计(元)', readOnly: true }, data: { precision: 2 }, appearance: {}, validation: [], linkage: [{ id: 'l-total', action: 'assign', value: 'SUM(items.amount)' }], }, { type: 'field', id: 'f-reason', kind: 'textarea', key: 'reason', basic: { label: '事由', hint: '合计超过限额时必填' }, data: {}, appearance: {}, validation: [{ id: 'r-reason', kind: 'length', max: 200 }], linkage: [{ id: 'l-reason', action: 'require', when: 'total > $limit' }], }, ``` 合计超过宿主给的限额时,事由变成必填。限额经渲染器的 `variables` 传入,名字以 `$` 开头: ```ts export const variables = { $limit: 5000 } ``` ```ts const renderer = document.createElement('xh-form-renderer') renderer.schema = schema renderer.values = values renderer.variables = variables ``` 模板自检会检查每条表达式:写错语法、用了不认识的名字或函数,都报 `expression.invalid`。宿主变量要告诉自检:`validateDocument(schema, { expressionEngine, identifiers: ['$limit'] })`。 ## 示例 切到「差旅」,出差城市出现并变成必填;改数量或单价,小计与合计跟着变;合计超过 5000 时事由变成必填。直接提交,下方列出没通过的规则。 --- 来源:https://form.docs.xihanfun.com/reference/core # 模型层 core `@xihan-form/core` 是纯 TypeScript 的模型层:模板类型、模板自检、取值校验、联动求值与编辑操作,不依赖 DOM,浏览器、Node.js 与其它 JavaScript 运行环境里都能用。模板类型的字段见[模板类型](./schema),种类的属性见[种类与属性](./kinds)。 ## 检查与校验 ### validateDocument() {#validatedocument} ```ts declare function validateDocument(document: unknown, options?: ValidateDocumentOptions): readonly FormSchemaIssue[] ``` 校验文档:输入可以是任意值(如反序列化的 JSON),逐项核对后返回全部问题,没有问题时返回空数组。种类、表单设置、编辑方式与规则按 `extensions` 合出的模型认;文档与各节点上的扩展数据只查形状。问题的路径从文档根算起,只用键与下标;问题落在节点、区域或规则上时由 target 标出。提供表达式引擎时,再按各规则所在位置可引用的名字检查表达式。文档与各节点的必需部分齐全、类型都对时,最后按扩展顺序跑各扩展的规则。严重程度缺省为 error,可用 `severity` 按问题码改。扩展冲突时抛 `DesignerExtensionError`。 ### ValidateDocumentOptions {#validatedocumentoptions} schema 自校验的选项。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `extensions?` | `readonly FormCoreExtension[]` | 种类、表单设置、编辑方式与规则都从这组扩展来,内置扩展自动排在最前;与渲染器、设计器的 extensions 是同一份。 | | `expressionEngine?` | `ExpressionEngine` | 表达式引擎;提供时逐条检查规则里的表达式。 | | `identifiers?` | `readonly string[]` | 宿主提供给表达式的额外顶层名字。 | | `maxDepth?` | `number` | 节点嵌套层数上限,缺省 32。 | | `severity?` | `Readonly>>` | 按问题码改严重程度,`off` 去掉该码;缺省全部为 error。扩展规则的问题码也认。 | ### validateValues() {#validatevalues} ```ts declare function validateValues(document: FormDocument, values: Readonly>, options?: ValidateValuesOptions): readonly ValueIssue[] ``` 按声明式规则校验表单值,返回全部问题。隐藏或禁用的实例连同其后代都不校验;值为空时只看必填与 accepted(必须为 true),其余规则跳过;必填由节点状态决定,联动可以加上或去掉。正则整串匹配。有表达式规则却没有提供 expressionEngine 时抛 `TypeError`。 ### ValidateValuesOptions {#validatevaluesoptions} 值校验的选项。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `expressionEngine?` | `ExpressionEngine` | 表达式引擎;有表达式规则时必须提供。 | | `states?` | `ReadonlyMap` | 联动求出的节点状态;缺省时按静态设定:`basic.hidden` / `disabled` 与有没有必填规则。 | | `variables?` | `Readonly>` | 宿主提供给表达式的额外变量。 | ### ValueIssue {#valueissue} 值校验的问题:在表单问题之上带着实例路径与规则里作者写的提示。 继承 `FormIssue`。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `path` | `readonly (string \| number)[]` | 出问题的值在表单数据里的路径,列表里的字段带行号。 | | `message?` | `string` | 规则里作者写的提示;没写时不带,由界面按问题码给出文案。 | ## 联动 ### resolveLinkage() {#resolvelinkage} ```ts declare function resolveLinkage(document: FormDocument, values: Readonly>, options?: ResolveLinkageOptions): LinkageResult ``` 按文档顺序求联动:每个节点实例从静态设定(`basic.hidden` / `disabled` / `readOnly`、有没有必填规则)出发,依次执行条件成立的规则,排在后面的覆盖前面的;赋值立即写进工作副本,后面的表达式读到的是新值。规则执行完后叠上宿主的强制设定,再与表单设置的 `disabled` / `readOnly`、祖先容器的隐藏与禁用向下叠加。有带表达式的规则却没有提供 expressionEngine 时抛 `TypeError`。 ### ResolveLinkageOptions {#resolvelinkageoptions} 联动求值的选项。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `expressionEngine?` | `ExpressionEngine` | 表达式引擎;有带条件或赋值的规则时必须提供。 | | `variables?` | `Readonly>` | 宿主提供给表达式的额外变量,名字建议以 `$` 开头,避免与数据键冲突。 | | `overrides?` | `ReadonlyMap` | 宿主的强制设定,键是节点 id(作用于全部实例)或实例键(只作用于该实例,叠在节点 id 的设定之上)。 | ### LinkageResult {#linkageresult} 联动求值的结果。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `states` | `ReadonlyMap` | 各节点实例的状态,键由 `instanceKey` 生成。 | | `values` | `Record` | 执行赋值之后的表单值;输入不会被修改。 | | `assignments` | `readonly Assignment[]` | 按执行顺序列出的赋值。 | | `issues` | `readonly FormIssue[]` | 求值中出的问题,如表达式求值失败、条件结果不是布尔。 | ### NodeState {#nodestate} 节点实例的状态:已经叠加了祖先容器的隐藏与禁用。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `hidden` | `boolean` | 隐藏:不显示,不参与校验。 | | `disabled` | `boolean` | 禁用:不能编辑,不参与校验。 | | `readOnly` | `boolean` | 只读:不能编辑,仍参与校验。 | | `required` | `boolean` | 必填。 | ## 新建 ### createDocument() {#createdocument} ```ts declare function createDocument(settings?: PropertyBag): FormDocument ``` 新建一份空文档。 ### createNode() {#createnode} ```ts declare function createNode(registry: KindRegistry, kind: string, ids: IdAllocator, options?: CreateNodeOptions): FormNode ``` 按种类原型新建节点;种类没有登记时抛 `TypeError`。 ### CreateNodeOptions {#createnodeoptions} 新建节点的选项。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `key?` | `string` | 字段与列表容器的数据键;缺省由种类名与 id 派生。 | ## 扩展 ### FormCoreExtension {#formcoreextension} 表单 core 层的扩展:种类(字段、容器、静态元素)、表单设置的属性、编辑方式与规则。运行时层与设计器层的扩展在它之上再加渲染、命令与面板,core 函数只读这里的字段。 继承 `DesignerExtensionCore`。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `kinds?` | `readonly KindDefinition[]` | 种类;种类名不能与内置的或别的扩展的重名。 | | `documentProperties?` | `readonly FormPropertyDescriptor[]` | 表单设置的属性,path 是设置里的名字,追加在内置之后;没选中任何节点时属性面板编辑它们。 | | 方法 | 说明 | | --- | --- | | `issueText(issue: FormSchemaIssue): string \| undefined` | 这个扩展自己的问题码(规则与编辑方式给出的)的说明文字;返回 undefined 时交给设计器的文案。 | ### createFormModel() {#createformmodel} ```ts declare function createFormModel(extensions?: readonly FormCoreExtension[]): FormModel ``` 合出表单模型:内置扩展 `FORM_CORE_EXTENSION` 在前,`extensions` 按顺序在后。扩展 id、种类名、表单设置的属性名、编辑方式等重名时抛 `DesignerExtensionError`;种类名不合规或容器区域个数的上下限不合法时抛 `TypeError`。同一个数组只合一次:元素把同一份 extensions 交给各个 core 函数时不重复合并。 ## 全部导出 ### bindings | 名称 | 种类 | 说明 | | --- | --- | --- | | `createInitialValues` | 函数 | 由文档得出表单的初始值:字段取 `basic.initialValue`,没有则取种类的空值;列表容器取 `data.initialRows` 行。 | | `createRowValues` | 函数 | 列表容器新增一行时的行对象:行内各字段取初始值,嵌套列表取各自的初始行数。 | | `DataBinding` | 接口 | 一处数据绑定:字段,或带数据键的列表容器。 | | `formDataFields` | 函数 | 表单数据的字段树(designer-kit 的 DataField):字段是 value,标签取 `basic.label`;列表容器是 list,标签取 `basic.title`,子字段是一行里的字段;布局容器不占层级。 | | `identifiersAt` | 函数 | 节点上的表达式可以引用的顶层名字:顶层作用域的全部数据键、`extra`,字段与列表容器另有 `$value`,位于列表容器里的节点另有 `$row` 与 `$index`。 | | `isValueOfKind` | 函数 | 值是否符合值形状;`null` 只对标量与对象表示「没有值」。 | | `listBindings` | 函数 | 按文档顺序列出全部数据绑定。 | | `scopeKeys` | 函数 | 某个作用域里直接绑定的数据键,按文档顺序。 | ### copy | 名称 | 种类 | 说明 | | --- | --- | --- | | `cloneNodesForInsert` | 函数 | 把节点改造成能插进 parent 区域(`null` 为文档顶层)的副本:节点、区域与规则的 id 全部由 ids 重新分配;直接落在目标作用域里的数据键与该作用域已有的键或彼此冲突时,改为 `deriveKey(种类, 新 id)`。 | | `regionScope` | 函数 | 区域所在的数据作用域:最近的列表容器 id,文档顶层与不在列表里的区域为 `null`;区域不存在时返回 `undefined`。 | ### create | 名称 | 种类 | 说明 | | --- | --- | --- | | `createDocument` | 函数 | 新建一份空文档。 | | `createNode` | 函数 | 按种类原型新建节点;种类没有登记时抛 `TypeError`。 | | `CreateNodeOptions` | 接口 | 新建节点的选项。 | | `createRegion` | 函数 | 按容器种类新建一个区域;`props` 覆盖区域属性的缺省值。 | | `deriveKey` | 函数 | 由种类名与 id 派生字段键:种类名转成小驼峰,接 `_` 与 id 里的字母数字。 | | `FORM_SETTING_PROPERTIES` | 常量 | 表单设置的属性描述:标签位置、尺寸、整表禁用与只读,存放在文档的 `settings` 里。 | | `isValidKey` | 函数 | 字段键是否合法:ASCII 字母或 `_` 开头,后接字母、数字、`_`,不是保留字;`$` 开头的名字留给运行时变量。 | ### expression | 名称 | 种类 | 说明 | | --- | --- | --- | | `RUNTIME_VARIABLES` | 常量 | 运行时变量:当前字段的值、所在行与行号。 | ### extensions | 名称 | 种类 | 说明 | | --- | --- | --- | | `createFormModel` | 函数 | 合出表单模型:内置扩展 `FORM_CORE_EXTENSION` 在前,`extensions` 按顺序在后。 | | `FORM_CORE_EXTENSION` | 常量 | 内置扩展:内置种类与表单设置。 | | `FORM_EXTENSION_SPACES` | 常量 | 表单各层扩展共用的名字空间:种类名与表单设置的属性名。 | | `FormCoreExtension` | 接口 | 表单 core 层的扩展:种类(字段、容器、静态元素)、表单设置的属性、编辑方式与规则。 | | `FormModel` | 接口 | 一组扩展合出的表单模型。 | | `FormRuleContext` | 接口 | 规则拿到的上下文:合并后的种类登记表。 | ### issues | 名称 | 种类 | 说明 | | --- | --- | --- | | `FormIssue` | 接口 | 一条值校验或联动问题。 | | `FormIssueCode` | 类型 | 值校验与联动的问题码: | | `FormIssueData` | 类型 | 问题附带的定位数据:名字、数量、区间,不放文案。 | | `FormSchemaIssue` | 类型 | 一条模板结构问题:路径从文档根算起,节点、区域、规则用 target 标出(kind 为 node、region、rule)。 | | `FormSchemaIssueCode` | 类型 | 模板结构问题的问题码:通用问题码加表单专属问题码。 | | `FormSchemaProductCode` | 类型 | 模板结构问题里表单专属的问题码(通用问题码见 designer-kit/schema 的 CommonIssueCode): | ### linkage | 名称 | 种类 | 说明 | | --- | --- | --- | | `Assignment` | 接口 | 一次赋值:字段的实例路径与赋进去的值。 | | `instanceKey` | 函数 | 节点实例的键:顶层节点是它的 id,列表里的节点在 id 后接 `#` 与逐层行号。 | | `LinkageResult` | 接口 | 联动求值的结果。 | | `NodeState` | 接口 | 节点实例的状态:已经叠加了祖先容器的隐藏与禁用。 | | `resolveLinkage` | 函数 | 按文档顺序求联动:每个节点实例从静态设定(`basic.hidden` / `disabled` / `readOnly`、有没有必填规则)出发,依次执行条件成立的规则,排在后面的覆盖前面的;赋值立即写进工作副本,后面的表达式读到的是新值。 | | `ResolveLinkageOptions` | 接口 | 联动求值的选项。 | | `StateOverride` | 接口 | 宿主对节点自身状态的强制设定:给出的项覆盖联动结果,祖先的隐藏、禁用、只读仍向下叠加。 | ### operations | 名称 | 种类 | 说明 | | --- | --- | --- | | `applyOperation` | 函数 | 对文档应用一个操作,返回新文档与逆操作。 | | `applyOperations` | 函数 | 依次应用一组操作;逆操作是各操作逆操作按反序拼接。 | | `FormOperation` | 类型 | 编辑操作。 | | `FormOperationError` | 类 | 编辑操作无法应用时抛出;`message` 就是错误码,不带文案。 | | `FormOperationErrorCode` | 类型 | 编辑操作的错误码。 | | `OperationResult` | 接口 | 操作的结果:新文档与逆操作;没有带来变化时返回同一个文档、逆操作为空。 | | `RuleList` | 类型 | 规则所在的列表。 | ### registry/builtin | 名称 | 种类 | 说明 | | --- | --- | --- | | `BUILTIN_KINDS` | 常量 | 全部内置种类:字段、容器、静态元素。 | ### registry/builtin-containers | 名称 | 种类 | 说明 | | --- | --- | --- | | `BUILTIN_CONTAINERS` | 常量 | 内置容器种类。 | | `BUILTIN_STATICS` | 常量 | 内置静态元素种类。 | | `tableCells` | 函数 | 读表格布局的全部单元格,`id` 是单元格区域的 id。 | | `tableSize` | 函数 | 读表格布局的行数与列数。 | ### registry/builtin-fields | 名称 | 种类 | 说明 | | --- | --- | --- | | `BUILTIN_FIELDS` | 常量 | 内置字段种类。 | ### registry/kinds | 名称 | 种类 | 说明 | | --- | --- | --- | | `ContainerKindDefinition` | 接口 | 容器种类。 | | `createKindRegistry` | 函数 | 创建种类登记表;种类名必须是小写短横线形式且不重复,否则抛 `TypeError`。 | | `FieldKindDefinition` | 接口 | 字段种类。 | | `KindDefinition` | 类型 | 节点种类。 | | `kindLabel` | 函数 | 种类在某种语言下的显示名:先取语言标记完全相同的,再取同一语种的(zh-TW 没写时用 zh-CN),再取 en-US;种类没写 label、或这几样都没有时为 undefined。 | | `KindLabel` | 类型 | 种类自带的显示名:键是 BCP 47 语言标记(如 en-US、zh-CN),值是这种语言下的名字。 | | `KindRegistry` | 接口 | 节点种类登记表。 | | `LINKAGE_ACTIONS` | 常量 | 各类节点可用的联动动作。 | | `StaticKindDefinition` | 接口 | 静态元素种类。 | | `StructureProblem` | 接口 | 容器的结构问题,由容器种类自行检查,问题码不带文案:给出 regionId 时落在该区域上,否则落在容器本身。 | | `ValueKind` | 类型 | 字段值的形状: | ### registry/properties | 名称 | 种类 | 说明 | | --- | --- | --- | | `checkFormPropertyValue` | 函数 | 检查属性值是否符合编辑方式;符合时为 `undefined`,否则给出问题码。 | | `EMPTY_PROPERTY_SCHEMA` | 常量 | 空的属性描述表。 | | `findFormPropertyValueIssue` | 函数 | 找出属性值第一处不符合编辑方式的地方;符合时为 `undefined`。 | | `FORM_PROPERTY_EDITORS` | 常量 | 表单内置的属性编辑器查找表,交给 designer-kit 的 `resolvePropertyEditor` 使用:按编辑方式取缺省编辑器,字段初始值按路径另用 `value` 编辑器(形状随字段种类)。 | | `FormPropertyControl` | 类型 | 表单的编辑方式:designer-kit 的内建编辑方式,或扩展登记的编辑方式(`kind` 是登记的键)。 | | `FormPropertyDescriptor` | 类型 | 表单的属性描述:`path` 是属性在所在属性包(basic / data / appearance、区域或表单设置)里的路径,表单只用一段 `[名字]`。 | | `FormPropertyValueIssue` | 接口 | 属性值的一处问题:`path` 相对这个值,落在值本身时是 `[]`。 | | `isValidPropertyValue` | 函数 | 属性值是否符合描述;`controls` 同 `checkFormPropertyValue`。 | | `numberControl` | 函数 | 内建的数字编辑方式:control 是 number 时原样返回,否则为 undefined。 | | `propertyName` | 函数 | 属性名,即属性路径唯一的一段。 | | `PropertySchema` | 类型 | 按分组列出的属性描述。 | ### schema | 名称 | 种类 | 说明 | | --- | --- | --- | | `AcceptedRule` | 接口 | 必须为 `true`:用于「必须勾选同意」,值为 `false` 或空时都不通过。 | | `ContainerNode` | 接口 | 容器:子节点放在区域里。 | | `ExpressionRule` | 接口 | 表达式:结果为 `false` 时不通过,为 `true` 或 `null` 时通过。 | | `ExtensionData` | 类型 | 扩展数据:扩展 id → 这个扩展自己的 JSON 对象。 | | `FieldNode` | 接口 | 字段:有数据键与值。 | | `FORM_SCHEMA_VERSION` | 常量 | 当前的文档结构版本。 | | `FormDocument` | 接口 | 表单文档:纯 JSON,不含任何代码。 | | `FormNode` | 类型 | 表单节点。 | | `LengthRule` | 接口 | 长度:字符串按字素计(`@xihan-ui/headless` 的 `graphemeLength`,与文本框的字数一致)、列表按元素个数,闭区间。 | | `LinkageAction` | 类型 | 联动动作:条件成立时生效,不成立时回到节点的静态设定。 | | `LinkageRule` | 接口 | 声明式联动规则;`when` 缺省表示总是成立。 | | `NodeType` | 类型 | 节点类型。 | | `PatternRule` | 接口 | 正则:`flags` 只允许 `i`、`m`、`s`、`u`,整串不匹配即不通过。 | | `PROPERTY_GROUPS` | 常量 | 全部属性分组。 | | `PropertyBag` | 类型 | 属性包:一组按名字存放的 JSON 值。 | | `PropertyGroup` | 类型 | 节点的属性分组:基础、数据、外观。 | | `RangeRule` | 接口 | 数值范围,闭区间。 | | `Region` | 接口 | 区域:容器里放子节点的一格,如栅格的一列、标签页的一页、表格布局的一个单元格。 | | `RequiredRule` | 接口 | 必填:值为空时不通过,空值判定取 `@xihan-ui/headless` 的 `isEmptyFormValue`:null、只含空白的字符串、空列表与元素全为空的列表算空,`false` 与 `0` 不算空。 | | `StaticNode` | 接口 | 静态元素:只展示,没有值,如分隔线、说明文字。 | | `ValidationKind` | 类型 | 校验规则的种类。 | | `ValidationRule` | 类型 | 声明式校验规则。 | ### table-commands | 名称 | 种类 | 说明 | | --- | --- | --- | | `insertTableLine` | 函数 | 在第 `at` 行(或列)之前插入一整行(列),`at` 等于行数时追加在末尾。 | | `mergeTableCells` | 函数 | 合并单元格:所选单元格必须恰好拼成一个矩形,且矩形里没有未选的单元格。 | | `removeTableLine` | 函数 | 删除第 `at` 行(或列):只占这一行(列)的单元格删除,跨过它的单元格缩短,其后的单元格前移;表格至少保留一行(列)。 | | `splitTableCell` | 函数 | 拆分单元格:跨行跨列都还原成 1,空出来的位置按行优先补上新的空单元格,紧跟在原单元格之后。 | ### traverse | 名称 | 种类 | 说明 | | --- | --- | --- | | `collectDocumentIds` | 函数 | 文档里已用的全部 id:节点、区域与规则共用一个命名空间。 | | `collectIds` | 函数 | 把节点、区域、规则里的全部 id 按出现顺序收进 out;不是对象的值跳过。 | | `descendantsOf` | 函数 | 节点自身及全部后代。 | | `findNode` | 函数 | 按 id 查节点;找不到返回 `undefined`。 | | `findRegion` | 函数 | 按 id 查区域;找不到返回 `undefined`。 | | `FoundNode` | 接口 | 节点的查找结果。 | | `FoundRegion` | 接口 | 区域的查找结果:所在容器、容器链与区域下标。 | | `NodeLocation` | 接口 | 节点在树里的位置:所在区域(文档顶层为 `null`)与下标。 | | `NodeVisitor` | 类型 | 访问函数返回 `false` 时不再进入该节点的子节点。 | | `VisitContext` | 接口 | 遍历时传给访问函数的上下文。 | | `walkNodes` | 函数 | 按文档顺序深度优先访问全部节点。 | ### validate-document | 名称 | 种类 | 说明 | | --- | --- | --- | | `validateDocument` | 函数 | 校验文档:输入可以是任意值(如反序列化的 JSON),逐项核对后返回全部问题,没有问题时返回空数组。 | | `ValidateDocumentOptions` | 接口 | schema 自校验的选项。 | ### validate-values | 名称 | 种类 | 说明 | | --- | --- | --- | | `validateValues` | 函数 | 按声明式规则校验表单值,返回全部问题。 | | `ValidateValuesOptions` | 接口 | 值校验的选项。 | | `ValueIssue` | 接口 | 值校验的问题:在表单问题之上带着实例路径与规则里作者写的提示。 | --- 来源:https://form.docs.xihanfun.com/reference/designer # 设计器 `@xihan-form/designer` 的公开接口。样式在 `@xihan-form/designer/style.css`,还要同时引入 `@xihan-ui/designer-shell/style.css` 与 `@xihan-form/runtime/style.css`。 ## `` 注册 <xh-form-designer> 与它用到的全部元素:渲染器(画布与预览共用)及其库内元素、工具栏与菜单等库内元素、设计器零件;重复调用不报错。宿主自定义字段渲染器用到的元素由宿主注册。 ### 属性 | 属性 | attribute | 类型 | 说明 | | --- | --- | --- | --- | | `expressionEngine` | — | `ExpressionEngine \| undefined` | 校验表达式与预览用,缺省为 createExpressionEngine()。 | | `commandLayout` | — | `DesignerCommandLayout \| undefined` | 菜单栏、工具栏、右键菜单与选中工具条的排列:位置(menubar:file、menubar:edit、menubar:view、menubar:help 与扩展登记的菜单 menubar:<菜单 id>,toolbar、menu:node、menu:region、menu:cell、menu:blank、selection:node)→ 若干组命令 id,内置命令与扩展命令混排。写了某个位置就只显示列出的;没写的位置用内置的缺省排列,再把 placement 含它的扩展命令追加成一组。 | | `previewValues` | — | `Readonly> \| undefined` | 预览的示例数据。 | | `loadOptions` | — | `OptionLoader \| undefined` | 预览的选项加载。 | | `uploadFile` | — | `FileUploader \| undefined` | 预览里文件字段的上传函数,与渲染器的同名属性一样交给 xh-file-upload;组件自己不发请求。 | | `translations` | — | `Partial \| undefined` | 元素自己的文案,压在全局配置之上。 | | `readonly` | `readonly` | `boolean \| undefined` | 可选择、浏览与预览,不能修改。attribute 按三态读:没写或写 "false" 为可修改,其余写法为只读。 | | `mode` | `mode` | `FormDesignerMode \| undefined` | 设计态或预览态,缺省 design。 | | `previewWidth` | `preview-width` | `FormPreviewWidth \| undefined` | 预览宽度,缺省 full。 | | `defaultLayout` | — | `DesignerLayout \| undefined` | 宿主的缺省面板布局:用户没存过布局(settings 里没有 layout.panels)时用它,「恢复默认布局」也回到它;写了的字段整项覆盖内置缺省(物料在左栏、属性在右栏、问题在下栏,全部显示)。面板 id 见 FORM_DESIGNER_PANELS。用户挪了面板、改了显隐或大小,整份布局写进设置的 layout.panels 并派发 settings-change。预览态不显示面板,不动布局。 | | `settings` | — | `DesignerSettings \| undefined` | 设置的值:扁平的键值,键是「分区 id.条目 id」,如 general.theme;没写的键取条目的缺省值。赋值即生效;用户在设置对话框里改了以后设计器换成新的一份并派发 settings-change。 | | `settingsPolicy` | — | `DesignerSettingsPolicy \| undefined` | 宿主对设置条目的管控:键是条目键或分区 id,hidden 不显示,locked 只读、说明换成「由所在系统管理」。 | | `settingsStorageKey` | `settings-storage-key` | `string \| undefined` | 设置存在根所在窗口 localStorage 的哪个键下:给了,连上文档时若没赋 settings 就从这里读,用户改了设置就写回去,标了 secret 的条目不存;读写失败派发 settings-storage-error。不给就不读写。同名 attribute settings-storage-key。 | | `draftKey` | `draft-key` | `string \| undefined` | 自动保存草稿的键:区分同一页、同一种设计器里的不同模板(宿主写模板 id);不给时按页面地址区分。设置里开着自动保存时,未保存的修改停手约一秒存成这台浏览器里的草稿;下次装进同一个键的模板、和草稿不一样时问恢复还是丢弃。同名 attribute draft-key。 | | `historyLimit` | `history-limit` | `number \| undefined` | 撤销步数上限,缺省 100;在下一次装载(赋 schema)时生效。同名 attribute history-limit。 | | `autoTour` | `auto-tour` | `boolean \| undefined` | 第一次铺好画布后自动开始新手引导,缺省开;设置里记着引导已经走完或跳过(help.tourDone)时不开。写 false 不自动开始,仍能用 startTour() 或命令 start-tour 开。同名 attribute auto-tour,写 "false" 为关。 | ### 存取器 | 属性 | 读写 | 类型 | 说明 | | --- | --- | --- | --- | | `schema` | 可读写 | `FormDocument` | 当前模板;赋值即装载:清空历史与选择,赋回当前这一份不重新装载;同一个草稿键下有不一样的草稿时问恢复还是丢弃。 | | `extensions` | 可读写 | `readonly FormDesignerExtension[] \| undefined` | 扩展:种类、渲染、属性编辑器、编辑方式、规则、命令与面板都从这里来,内置扩展自动排在最前;画布、预览与校验用同一份。赋值时当场合并查重,冲突抛 DesignerExtensionError、什么都不变;各扩展的 setup 在元素连上文档时调用、断开时清理。同一个扩展对象换了引用当作卸下再装上,宿主要保持引用稳定。 | | `canUndo` | 只读 | `boolean` | 有可撤销的修改。 | | `canRedo` | 只读 | `boolean` | 有可重做的修改。 | | `canPaste` | 只读 | `boolean` | 内部剪贴板里有内容可粘贴。 | | `xhConfig` | 只读 | `XhConfig` | 设计器这棵子树的配置:设置里选了语言时是这一语言的 UI、designer-shell 与表单语言包,跟随页面时是空对象。设计器自己、里面的元素与它弹出的浮层都按它取 locale 与文案,页面别处不受影响。 | | `dirty` | 只读 | `boolean` | 有没保存的修改:上次 `markSaved` 之后改过。 | | `historyState` | 只读 | `HistorySnapshot` | 撤销历史的快照:各步的说明按此刻的语言取好,位置号用于 goToHistory。 | ### 方法 | 方法 | 说明 | | --- | --- | | `goToHistory(position: number): boolean` | 跳到撤销历史里的一步或起点(historyState 里的位置号),一次跳到;只读、预览、拖动中或位置号不认识时返回 false。 | | `clearHistory(): void` | 清空撤销与重做;模板与是否已保存不变。只读或拖动中什么都不做。 | | `execute(operations: readonly FormOperation[], options?: ExecuteOptions): FormDocument` | 执行一组操作并记为一步撤销;返回新模板。 | | `undo(): boolean` | 撤销一步;只读、正在拖动跨度或没有可撤销的时返回 false。 | | `redo(): boolean` | 重做一步;只读、正在拖动跨度或没有可重做的时返回 false。 | | `markSaved(): void` | 把当前模板记为已保存:`dirty` 变为 false,并派发 `dirty-change`。 | | `addNode(kind: string, target?: FormDropTarget): FormNode \| undefined` | 按种类新建节点并选中它;目标缺省为选中节点之后,没有选中时为末尾。目标不收时返回 undefined。 | | `select(selection: Partial): void` | 选中节点或区域:给了非空的 regions 就选区域,否则选 nodes。 | | `getSelection(): FormSelection` | 当前的选择。 | | `startTour(): boolean` | 开始新手引导,从第一步起;同一页别的设计器的引导开着时先收起它。没有能走的步骤时返回 false。引导收起时派发 tour-end,并在设置里记下 help.tourDone。命令 start-tour 与设置对话框帮助页的「开始引导」都走这里。 | | `openJsonEditor(): void` | 打开 JSON 编辑框,写出此刻的整份模板。改一次就按 validate() 同一套检查一次,有错误时不能应用;应用后整份换上,作为一步进撤销栈。只读或预览态时只能看、能复制,不能应用。 | | `openSettings(section?: string): void` | 打开设置对话框,给了分区 id 就显示那一页:基础是 general,扩展的分区用它们自己的 id。 | | `deleteSelection(): boolean` | 删除选中的节点或区域;只读或什么都没删时返回 false。 | | `copy(): FormClipboard \| undefined` | 复制选中的节点,放进这个设计器的剪贴板并返回;没有选中时返回 undefined。 | | `cut(): FormClipboard \| undefined` | 复制并删除选中的节点;只读或没有选中时返回 undefined。 | | `paste(clipboard?: FormClipboard, options?: { readonly at?: FormDropTarget }): FormSelection` | 粘贴:给了内容就粘贴它,否则粘贴最近一次复制或剪切的内容;返回新的选择。给了 at 就粘贴到那里(如物料来源的条目拖到画布上的落点),不给时在选中节点之后,没有选中时在末尾。 | | `duplicate(): FormSelection` | 复制选中节点并紧跟在原节点之后粘贴,不改动剪贴板;返回新的选择。 | | `validate(): readonly FormSchemaIssue[]` | 检查模板结构,规则里的表达式用 expressionEngine 检查。 | | `scrollToNode(id: string): void` | 滚动到节点:必要时先切到所在的标签页、展开所在的折叠项。 | | `revealNode(id: string): void` | 节点不在可见区内时才滚动到它。 | ### 事件 事件都是冒泡的 `CustomEvent`。 | 事件 | detail | 说明 | | --- | --- | --- | | `schema-change` | `{ schema: FormDocument, operations: readonly FormOperation[], kind: FormSchemaChangeKind }` | 模板变了:执行操作、撤销、重做或换了 `schema`;`schema` 是变化后的模板,`operations` 是这一步实际应用的操作。 | | `selection-change` | `FormSelection` | 选择变了。 | | `dirty-change` | `{ dirty: boolean }` | 有没有未保存修改的状态变了。 | | `issues-change` | `{ issues: readonly FormSchemaIssue[] }` | 模板问题变了;`issues` 是当前全部问题。 | | `save` | `Record` | 用户要保存:按了 Ctrl / ⌘ + S 或点了保存命令(designer-shell 内置的 save)。保存由宿主做,存好后调用 `markSaved()`。 | | `node-dblclick` | `{ id: string }` | 双击了画布上的节点。 | | `node-contextmenu` | `{ id: string, x: number, y: number }` | 要在节点上打开右键菜单,`x`、`y` 是视口坐标;可取消,取消后不打开内建菜单。 | | `blank-contextmenu` | `{ x: number, y: number }` | 要在画布空白处打开右键菜单;可取消,取消后不打开内建菜单。 | | `mode-change` | `{ mode: FormDesignerMode }` | 在设计态与预览态之间切换了。 | | `settings-change` | `{ key: string, value: JsonValue \| undefined, settings: DesignerSettings }` | 在设置对话框里改了一条设置,或在外框上挪了面板(键是 layout.panels,值是整份布局):设计器已换成新的 settings 并生效(主题、主题色、状态栏与面板布局);value 为 undefined 表示删掉这个键、回到缺省值。宿主存起来,下次赋回 settings。 | | `settings-storage-error` | `VisualEnvironmentStorageError` | 读写 settingsStorageKey 指的本机存储失败(内容不是对象、无痕窗口、配额满);设置留在内存里。 | | `history-change` | `HistorySnapshot` | 撤销历史变了:执行(含并入上一步)、撤销、重做、跳转、装载、清空与保存之后都派发,拖动跨度这样的事务进行中不派发。detail 是此刻的 historyState。 | | `tour-end` | `{ reason: 'complete' \| 'skip' \| 'close', step: string \| undefined }` | 新手引导收起了:reason 是 complete(走完)、skip(跳过或 Esc)或 close(关闭钮),step 是收起时那一步的 id。设计器同时在设置里记下 help.tourDone(会派发 settings-change),下次不再自动开始。 | ## 类型 ### FormDesignerMode {#formdesignermode} 设计态或预览态。取值:`design`、`preview`。 ### FormPreviewWidth {#formpreviewwidth} 预览宽度:全宽、平板、手机,设计态画布同样生效。取值:`full`、`tablet`、`phone`。 ### FORM_DESIGNER_PANELS {#form_designer_panels} 设计器的面板 id:宿主写 defaultLayout、settingsPolicy(layout.panels.<面板 id>)时用;每块面板有一条开关命令 panel:<面板 id>。 ```ts const FORM_DESIGNER_PANELS = { palette: 'palette', outline: 'outline', properties: 'properties', issues: 'issues', history: 'history' } as const ``` ### FormSchemaChangeKind {#formschemachangekind} schema-change 的来源。取值:`execute`、`undo`、`redo`、`load`。 ### FormSelection {#formselection} 选择:选中的节点,或同一个容器里的区域(栅格的列、标签页、折叠项、表格单元格),二者互斥,按选中的先后。选中区域时 nodes 为空。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `nodes` | `readonly string[]` | 选中节点的 id。 | | `regions?` | `readonly string[]` | 选中区域的 id,都在同一个容器里。 | ### FormDropTarget {#formdroptarget} 放置位置:区域 id(`null` 为文档顶层)与放在该区域现有子节点里的第几个之前,缺省放到末尾。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `parent` | `string \| null` | 区域 id;`null` 为文档顶层。 | | `index?` | `number` | 放在该区域现有子节点里的第几个之前,从 0 起;缺省放到末尾。 | ### FormClipboard {#formclipboard} 剪贴板内容:被复制的节点,按文档顺序,不含被选中节点的后代里重复选中的那些。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `nodes` | `readonly FormNode[]` | 被复制的节点,粘贴时 id 全部换新。 | ## 扩展与命令 ### FormDesignerExtension {#formdesignerextension} 表单设计器层的扩展:运行时层的全部字段,加上属性编辑器、命令、面板、物料来源、导入导出与 setup。物料来源的条目拖到画布上放下时,落点是 FormDropTarget。 其余成员见视图组件文档的 [designer-shell](https://ui.docs.xihanfun.com/runtime/designer-shell)。 继承 `FormRuntimeExtension`、`DesignerShellExtension`。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `kindIcons?` | `Readonly>` | 本扩展登记的种类的图标,物料面板与页面结构用;键是种类名,只能给本扩展 kinds 里的种类。 | ### FormDesignerContext {#formdesignercontext} 命令、面板与扩展装上时拿到的上下文。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `designer` | `XhFormDesigner` | 这个设计器实例。 | | `translations` | `FormDesignerTranslations` | 此刻生效的设计器文案。 | | `commands` | `DesignerCommandsTranslations` | 此刻生效的共用命令文案(designer-shell 语言包的 xh-designer-commands):撤销、剪切、删除、设计与预览这些四款都有的命令。 | | `settings` | `DesignerSettings` | 此刻生效的设置:键是「分区 id.条目 id」,各分区没写的条目取了缺省值。 | | `frame` | `XhDesignerFrameElement \| undefined` | 设计器的外框:面板的开关经它;外框还没建出来时为 undefined。 | ### FormDesignerCommand {#formdesignercommand} 设计器的命令。 ```ts type FormDesignerCommand = DesignerCommand ``` ### FormMenuSubject {#formmenusubject} 右键菜单落在哪类对象上:节点、区域、表格布局的单元格,或空白处。取值:`node`、`region`、`cell`、`blank`。 ### FormSelectionSubject {#formselectionsubject} 选中工具条落在哪类选中上:眼下只有单个节点。取值:`node`。 ## 文案 ### FormDesignerTranslations {#formdesignertranslations} 表单设计器的文案:无参的是字符串,带参的是函数。 文案键是 `xh-form-designer`,各语言的文案在 `@xihan-form/runtime/locale`,如中文的 `formZhCN`。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `label` | `string` | 设计器的可访问名。 | | `canvas` | `string` | 画布的可访问名。 | | `palette` | `string` | 物料面板的标题。 | | `properties` | `string` | 属性面板的标题。 | | `issues` | `string` | 问题列表的标题。 | | `paletteHelp` | `string` | 物料面板头的帮助。 | | `propertiesHelp` | `string` | 属性面板头的帮助。 | | `issuesHelp` | `string` | 问题列表面板头的帮助。 | | `selectParent` | `string` | 选中上一级命令。 | | `moveUp` | `string` | 上移命令。 | | `moveDown` | `string` | 下移命令。 | | `clear` | `string` | 清空命令。 | | `previewWidth` | `string` | 预览宽度这组按钮的可访问名。 | | `widthFull` | `string` | 全宽。 | | `widthTablet` | `string` | 平板宽度。 | | `widthPhone` | `string` | 手机宽度。 | | `propertiesTab` | `string` | 属性区的「属性」页签。 | | `validationTab` | `string` | 属性区的「校验」页签。 | | `linkageTab` | `string` | 属性区的「联动」页签。 | | `emptyCanvas` | `string` | 画布为空时的提示。 | | `emptyRegion` | `string` | 区域为空时的提示。 | | `settings` | `string` | 什么都没选时属性区的标题。 | | `addRule` | `string` | 新增规则的按钮。 | | `removeRule` | `string` | 删除规则的按钮。 | | `ruleMessage` | `string` | 规则的「提示」输入框。 | | `ruleWhen` | `string` | 联动规则的「条件」输入框。 | | `ruleValue` | `string` | 联动赋值的「取值」输入框。 | | `ruleMin` | `string` | 规则的「最小值」输入框。 | | `ruleMax` | `string` | 规则的「最大值」输入框。 | | `rulePattern` | `string` | 正则规则的「正则」输入框。 | | `ruleFlags` | `string` | 正则规则的「标志」输入框。 | | `ruleExpression` | `string` | 表达式规则的「表达式」输入框。 | | `ruleAction` | `string` | 联动规则的「动作」选择框。 | | `noRules` | `string` | 还没有规则时的提示。 | | `variableValue` | `string` | 表达式补全里运行时变量 `$value` 的说明。 | | `variableRow` | `string` | 表达式补全里运行时变量 `$row` 的说明。 | | `variableIndex` | `string` | 表达式补全里运行时变量 `$index` 的说明。 | | `mergeCells` | `string` | 表格布局右键菜单:合并单元格。 | | `splitCell` | `string` | 表格布局右键菜单:拆分单元格。 | | `insertRowAbove` | `string` | 表格布局右键菜单:在上方插入行。 | | `insertRowBelow` | `string` | 表格布局右键菜单:在下方插入行。 | | `insertColumnLeft` | `string` | 表格布局右键菜单:在左侧插入列。 | | `insertColumnRight` | `string` | 表格布局右键菜单:在右侧插入列。 | | `removeRow` | `string` | 表格布局右键菜单:删除行。 | | `removeColumn` | `string` | 表格布局右键菜单:删除列。 | | `history` | `FormHistoryTranslations` | 撤销历史里各步的说明。 | | `tour` | `FormTourTranslations` | 新手引导里表单自己的几步。 | | `help` | `FormHelpTranslations` | 设置对话框「帮助」「关于」两页里表单自己的文字。 | | `keys` | `FormKeyTranslations` | 快捷键表里不是命令的按键。 | | `sourceStructure` | `string` | 问题列表里结构问题的来源小标签。 | | `sourceBinding` | `string` | 问题列表里数据绑定问题的来源小标签。 | | 方法 | 说明 | | --- | --- | | `statusFields(count: number): string` | 状态栏:什么都没选中时写表单里的字段数。 | | `statusSelected(count: number): string` | 状态栏:选中多项时写已选的个数。 | | `nodeTag(kind: string, key: string \| undefined): string` | 画布上选中节点的标签,如「单行文本 · name」。 | | `category(category: string): string` | 物料分类名,按种类的 category。 | | `kind(kind: string): string` | 种类名,按 kind。 | | `group(group: string): string` | 属性分组名:basic / data / appearance / region / settings。 | | `property(kind: string, group: string, name: string): string` | 属性名:先按「种类:分组.名」,再按「分组.名」,最后按名查。 | | `option(name: string, value: string): string` | select 选项的文案,按属性名与选项值。 | | `ruleKind(kind: string): string` | 校验规则种类名。 | | `linkageAction(action: string): string` | 联动动作名。 | | `outlineColumn(n: number): string` | 页面结构里栅格的一列,n 从 1 起。 | | `outlineCell(row: number, column: number): string` | 页面结构里表格布局的一格,行与列从 1 起。 | | `outlineRegion(n: number): string` | 页面结构里没有标题的区域,n 从 1 起。 | | `unboundKey(key: string): string` | 数据键不在宿主数据模型里。 | | `issue(issue: FormSchemaIssue): string` | 模板结构问题的说明,按问题码与参数;扩展给出的问题码先取扩展的 issueText,都没有时显示问题码本身。 | ## 编辑模型 设计器内部的编辑模型也一并导出,不显示界面、只想按操作改模板时可以直接用。创建表单编辑模型。 ```ts declare function createFormEditor(options: FormEditorOptions): FormEditor ``` ### FormEditorOptions {#formeditoroptions} 创建编辑模型的选项。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `document` | `FormDocument` | 初始文档。 | | `extensions?` | `readonly FormCoreExtension[]` | 种类、编辑方式与规则从这组扩展来,内置扩展自动排在最前;与设计器的 extensions 是同一份。 | | `expressionEngine?` | `ExpressionEngine` | 校验时检查表达式用的引擎,不给时不检查表达式内容。 | | `identifiers?` | `readonly string[]` | 宿主提供给表达式的额外顶层名字。 | | `historyLimit?` | `number` | 撤销步数上限,缺省 100。 | | `mergeWithin?` | `number` | 同一合并键相隔多少毫秒内并入上一步,缺省 1000。 | | 方法 | 说明 | | --- | --- | | `now(): number` | 取当前时间(毫秒),合并相邻修改时用;缺省 `Date.now`。 | | `describe(operations: readonly FormOperation[], before: FormDocument): HistoryLabel \| undefined` | 没写说明的一步按它的操作与应用前的文档生成说明,如 describeFormOperations;不给时这些步没有说明。 | ### FormEditor {#formeditor} 表单编辑模型:当前文档、撤销历史与选择,以及各种编辑操作;不碰 DOM。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `document` | `FormDocument` | 当前文档。 | | `registry` | `KindRegistry` | 按扩展合出的种类登记表。 | | `history` | `History` | 撤销历史:能否撤销、重做,是否有未保存的修改。装载时按新的步数上限重建过的话,这里换成新的一份。 | | `selection` | `FormSelection` | 当前的选择。 | | 方法 | 说明 | | --- | --- | | `setExtensions(extensions: readonly FormCoreExtension[]): void` | 换一组扩展:之后新建、放置与校验都按它;冲突时抛 DesignerExtensionError、什么都不变。 | | `load(document: FormDocument, options?: FormEditorLoadOptions): void` | 装载新文档:清空历史与选择,剪贴板保留;文档变化的 cause 为 reset。给了与此刻不同的 historyLimit 时按它重建撤销历史,之后的 history 是新的一份。 | | `execute(operations: readonly FormOperation[], options?: ExecuteOptions): FormDocument` | 执行一组操作并记为一步撤销;返回新文档。 | | `undo(): boolean` | 撤销一步;没有可撤销的时返回 false。 | | `redo(): boolean` | 重做一步;没有可重做的时返回 false。 | | `seal(): void` | 封口:此后带同一合并键的修改另起一步;选择变了时自动封口。 | | `transact(run: () => T, label?: HistoryLabel): T` | 把 run 里的全部修改合为一步撤销。 | | `markSaved(): void` | 把当前文档记为已保存,此后 `history.dirty` 为 false,直到再改。 | | `select(selection: Partial, mode?: FormSelectMode): void` | 选中节点或区域:给了非空的 regions 就选区域(只留与第一个同一容器的),否则选 nodes。 | | `clearSelection(): void` | 清空选择。 | | `canDrop(nodeIds: readonly string[], target: FormDropTarget): boolean` | 节点能否放到目标位置:目标区域存在,不放进自己或自己的后代,目标区域收这种节点(如子表只收字段)。 | | `canAdd(kind: string, target: FormDropTarget): boolean` | 种类能否新建到目标位置;种类未登记时为 false。 | | `canPasteAt(clipboard: FormClipboard, target: FormDropTarget): boolean` | 一段剪贴板内容能否粘贴到目标位置:非空、目标区域收这些节点、种类都已登记。 | | `addNode(kind: string, target?: FormDropTarget, options?: ExecuteOptions): FormNode \| undefined` | 按种类新建节点并选中它;目标缺省为选中节点之后,没有选中时为顶层末尾。种类未登记时抛错;目标不收时返回 undefined。 | | `moveNodes(nodeIds: readonly string[], target: FormDropTarget, options?: ExecuteOptions): boolean` | 移动一组节点到目标位置,保持它们在文档里的先后;放不下时返回 false。 | | `deleteSelection(options?: ExecuteOptions): boolean` | 删除选中的节点;选中区域时删除这些区域,删完要少于容器的区域下限或容器是表格布局时不删。什么都没删时返回 false。 | | `setProperty(nodeId: string, group: PropertyGroup, name: string, value: JsonValue \| undefined, options?: ExecuteOptions): void` | 写节点的属性;value 为 undefined 时删除该属性。连续写同一属性合为一步,直到 seal。 | | `setRegionProperty(regionId: string, name: string, value: JsonValue \| undefined, options?: ExecuteOptions): void` | 写区域的属性,合并规则同 setProperty。 | | `setSetting(name: string, value: JsonValue \| undefined, options?: ExecuteOptions): void` | 写表单设置,合并规则同 setProperty。 | | `setKey(nodeId: string, key: string, options?: ExecuteOptions): void` | 改数据键,合并规则同 setProperty。 | | `addRule(nodeId: string, list: RuleList, rule: FormRuleInit \| Omit, index?: number, options?: ExecuteOptions): ValidationRule \| LinkageRule` | 新增一条规则并返回它;index 缺省为末尾。 | | `updateRule(nodeId: string, list: RuleList, rule: ValidationRule \| LinkageRule, options?: ExecuteOptions): void` | 整条替换规则,连续替换同一条规则合为一步。 | | `removeRule(nodeId: string, list: RuleList, ruleId: string, options?: ExecuteOptions): void` | 删除一条规则。 | | `moveRule(nodeId: string, list: RuleList, ruleId: string, index: number, options?: ExecuteOptions): void` | 把一条规则移到 index 处。 | | `addRegion(containerId: string, index?: number, props?: Readonly>, options?: ExecuteOptions): Region` | 给容器新增一个区域并返回它;index 缺省为末尾,props 覆盖区域属性的缺省值。 | | `removeRegion(regionId: string, options?: ExecuteOptions): void` | 删除一个区域,连同其中的节点。 | | `moveRegion(regionId: string, index: number, options?: ExecuteOptions): void` | 把一个区域移到容器里的 index 处。 | | `setGridSpan(regionId: string, span: number, options?: ExecuteOptions): void` | 设栅格区域的跨度:取整并夹在属性描述的上下限之间。 | | `mergeCells(containerId: string, regionIds: readonly string[], options?: ExecuteOptions): void` | 合并表格布局的单元格,所选单元格须恰好拼成一个矩形。 | | `splitCell(containerId: string, regionId: string, options?: ExecuteOptions): void` | 拆分表格布局的单元格。 | | `insertTableLine(containerId: string, axis: TableAxis, at: number, options?: ExecuteOptions): void` | 在表格布局的第 at 行或列之前插入一行或一列。 | | `removeTableLine(containerId: string, axis: TableAxis, at: number, options?: ExecuteOptions): void` | 删除表格布局的第 at 行或列。 | | `copy(): FormClipboard \| undefined` | 复制选中的节点;没有选中时返回 undefined。 | | `cut(options?: ExecuteOptions): FormClipboard \| undefined` | 复制并删除选中的节点。 | | `paste(clipboard?: FormClipboard, target?: FormDropTarget, options?: ExecuteOptions): FormSelection` | 粘贴并选中粘贴出的节点:id 全部换新,与目标作用域冲突的数据键改为派生键。内容缺省为最近一次 copy 的,目标缺省为选中节点之后、没有选中时为顶层末尾;目标不收时什么都不做,返回空选择。 | | `duplicate(options?: ExecuteOptions): FormSelection` | 复制选中节点并紧跟在原节点之后粘贴,不改动剪贴板。 | | `validate(): readonly FormSchemaIssue[]` | 按当前登记表做 schema 自校验。 | | `subscribe(listener: (change: FormEditorChange) => void): () => void` | 订阅文档与选择的变化,返回取消函数。 | ## 全部导出 ### clipboard | 名称 | 种类 | 说明 | | --- | --- | --- | | `checkFormClipboard` | 函数 | 检查一段剪贴板内容(如拖来的片段):节点非空、结构合法、种类在内置种类与 extensions 里、id 不重复时原样返回,否则为 undefined。 | | `decodeFormClipboard` | 函数 | 解析剪贴板文本:不是表单的剪贴板包、版本不认得时返回 undefined,内容按 checkFormClipboard 检查。 | | `encodeFormClipboard` | 函数 | 把剪贴板内容序列化成剪贴板包的 JSON 文本。 | | `readFormClipboard` | 函数 | 从剪贴板事件的数据里读出表单的剪贴板内容:先取表单自己的类型,没有再取 text/plain。 | | `writeFormClipboard` | 函数 | 把剪贴板内容写进剪贴板事件的数据:表单自己的类型一份,text/plain 一份。 | ### commands | 名称 | 种类 | 说明 | | --- | --- | --- | | `FORM_COMMANDS` | 常量 | 内置命令。 | | `FORM_DESIGNER_HELP_URL` | 常量 | 表单文档站上设计器的一页,帮助菜单的「帮助」在新窗口打开它。 | | `FORM_DESIGNER_PANELS` | 常量 | 设计器的面板 id:宿主写 defaultLayout、settingsPolicy(layout.panels.<面板 id>)时用;每块面板有一条开关命令 panel:<面板 id>。 | | `FORM_MENU_LAYOUTS` | 常量 | 右键菜单各主体的缺省排列;commandLayout 没写 menu:<主体> 时用它。 | | `FORM_SELECTION_LAYOUTS` | 常量 | 选中工具条(命令落点 selection:<主体>)的缺省排列:选父级、上移、下移,创建副本、删除。 | | `FORM_TOOLBAR_LAYOUT` | 常量 | 工具栏的缺省排列;commandLayout 没写 toolbar 时用它,再把 placement 含 toolbar 的扩展命令追加成一组。 | | `FormDesignerCommand` | 类型 | 设计器的命令。 | | `FormDesignerContext` | 接口 | 命令、面板与扩展装上时拿到的上下文。 | | `FormMenuSubject` | 类型 | 右键菜单落在哪类对象上:节点、区域、表格布局的单元格,或空白处。 | | `FormSelectionSubject` | 类型 | 选中工具条落在哪类选中上:眼下只有单个节点。 | ### editor | 名称 | 种类 | 说明 | | --- | --- | --- | | `createFormEditor` | 函数 | 创建表单编辑模型。 | | `FormClipboard` | 接口 | 剪贴板内容:被复制的节点,按文档顺序,不含被选中节点的后代里重复选中的那些。 | | `FormDropTarget` | 接口 | 放置位置:区域 id(`null` 为文档顶层)与放在该区域现有子节点里的第几个之前,缺省放到末尾。 | | `FormEditor` | 接口 | 表单编辑模型:当前文档、撤销历史与选择,以及各种编辑操作;不碰 DOM。 | | `FormEditorChange` | 类型 | 编辑模型的变化:文档变化带上历史变化的种类与实际应用的操作;选择变化带上新的选择。 | | `FormEditorLoadOptions` | 接口 | 装载新文档时的选项。 | | `FormEditorOptions` | 接口 | 创建编辑模型的选项。 | | `FormRuleInit` | 类型 | 校验规则或联动规则,不带 id;id 由编辑模型分配。 | | `FormSelection` | 接口 | 选择:选中的节点,或同一个容器里的区域(栅格的列、标签页、折叠项、表格单元格),二者互斥,按选中的先后。 | | `FormSelectMode` | 类型 | 选择方式:替换、追加或切换。 | ### extensions | 名称 | 种类 | 说明 | | --- | --- | --- | | `createFormDesignerModel` | 函数 | 合出设计器用的模型;任何一处重名(设置分区与 general 重名、扩展面板与外框的区重名、扩展面板的开关命令与已有命令重名、引导步骤重名或用了 shell. 前缀、物料来源重名、菜单栏的菜单重名也算)都抛 DesignerExtensionError。 | | `FORM_DESIGNER_EXTENSION` | 常量 | 设计器的内置扩展:内置命令、页面结构与历史记录两块面板,以及引导里表单自己的几步。 | | `FormDesignerExtension` | 接口 | 表单设计器层的扩展:运行时层的全部字段,加上属性编辑器、命令、面板、物料来源、导入导出与 setup。 | | `FormDesignerModel` | 接口 | 一组设计器扩展合出的东西:运行时模型、命令表、属性编辑器表与设置分区。 | ### form-designer | 名称 | 种类 | 说明 | | --- | --- | --- | | `defineFormDesigner` | 函数 | 注册 <xh-form-designer> 与它用到的全部元素:渲染器(画布与预览共用)及其库内元素、工具栏与菜单等库内元素、设计器零件;重复调用不报错。 | | `FORM_DESIGNER_TAG` | 常量 | 设计器的标签名。 | | `FormDesignerEventMap` | 接口 | 设计器派发的事件。 | | `FormDesignerMode` | 类型 | 设计态或预览态。 | | `FormPreviewWidth` | 类型 | 预览宽度:全宽、平板、手机,设计态画布同样生效。 | | `FormSchemaChangeKind` | 类型 | schema-change 的来源。 | | `XhFormDesigner` | 类 | ``:可视化编辑表单模板,物料、画布、属性与问题列表都在里面;模板的保存与权限交给宿主,经 `schema-change`、`save` 事件与 `markSaved()` 往来。 | ### history-labels | 名称 | 种类 | 说明 | | --- | --- | --- | | `describeFormOperations` | 函数 | 表单编辑模型的 describe:按这一步的正向操作与应用前的文档给出说明,文案经 translations 现取。 | ### kind-icons | 名称 | 种类 | 说明 | | --- | --- | --- | | `FORM_KIND_ICONS` | 常量 | 内置种类的图标,键是种类名。 | ### palette | 名称 | 种类 | 说明 | | --- | --- | --- | | `FormCanvasDragPayload` | 接口 | 从画布上拖动节点时的数据;owner 是发起拖动的设计器,只在同一个设计器里放下。 | | `FormDragSubject` | 类型 | 拖来的东西:新建的种类、未经检查的片段,或本设计器画布上的节点。 | | `FormFragmentDragPayload` | 接口 | 拖放片段:任意来源(如扩展的模板库面板)经 startDrag 交来的一段节点,松手时按粘贴的规则换 id 插入。 | | `FormPalettePayload` | 类型 | 物料随 item-activate 与拖动交出的数据:新建某个种类,或插入一段片段(如模板库里的一组字段)。 | ### properties | 名称 | 种类 | 说明 | | --- | --- | --- | | `FormPropertyEditor` | 类型 | 宿主的属性编辑器,经 `editors` 压在内建编辑器之上。 | ### version | 名称 | 种类 | 说明 | | --- | --- | --- | | `FORM_DESIGNER_VERSION` | 常量 | @xihan-form/designer 的版本号。 | --- 来源:https://form.docs.xihanfun.com/reference/issues # 问题码 问题都不带文案,只有问题码、定位与参数;下表的说明来自中文语言包 `formZhCN`,其中的 {名} 是参数。 ## 模板问题 {#schema} 模板问题由 `validateDocument` 与设计器的问题列表给出,缺省都是错误级,`severity` 选项可以按问题码改为警告或去掉;有错误级问题的模板,渲染器不渲染、派发 `schema-error`。 一条模板结构问题:路径从文档根算起,节点、区域、规则用 target 标出(kind 为 node、region、rule)。问题码除了 FormSchemaIssueCode,还有扩展给出的:扩展规则的问题码以「扩展 id 末段.」开头,扩展登记的编辑方式由它自己的 check 给出。 ### 通用问题码 属性值与结构的通用检查,各款设计器共用。 | 问题码 | 设计器里的说明 | 参数 | | --- | --- | --- | | `required` | 缺少必填的值 | — | | `invalid-type` | 取值类型不对 | — | | `invalid-value` | 取值无效 | — | | `out-of-range` | 取值超出范围 | — | | `not-integer` | 必须是整数 | — | | `invalid-option` | 不是可选的值 | — | | `invalid-color` | 颜色无效 | — | | `empty` | 不能为空 | — | | `unknown-key` | 未定义的属性 | — | | `duplicate-key` | 数据键 {key} 重复 | `key` | | `duplicate-id` | id {id} 重复 | `id` | | `duplicate-value` | 选项的值重复 | — | | `unknown-control` | 没有登记这种编辑方式 | — | ### 表单问题码 模板结构问题里表单专属的问题码(通用问题码见 designer-kit/schema 的 CommonIssueCode): - `document.unsupportedVersion`:结构版本不受支持;`document.tooDeep`:嵌套层数超过上限。 - `node.unknownKind` / `node.kindTypeMismatch`:种类没有登记 / 登记的类型与节点类型不符。 - `region.childNotAllowed`:区域不收这种节点(如子表只收字段)。 - `table.cellOutOfBounds` / `table.cellOverlap` / `table.cellGap`:表格布局的单元格越界、重叠、留空。 - `rule.notApplicable`:校验规则不适用于字段的种类或值形状;`rule.boundsMissing`:长度或范围规则上下限都没给;`rule.invalidRange`:下限大于上限;`rule.invalidPattern`:正则编译不过,参数 reason 给出原因。 - `linkage.actionNotApplicable`:联动动作不适用于节点类型。 - `expression.invalid`:表达式引擎报出问题,参数带上引擎的问题码与源码区间。 - `field.dependencyWithoutSource`:声明了选项依赖却没有选项来源;`field.unknownDependency`:依赖的键在所在作用域及其外层都找不到;`field.selfDependency`:依赖指向字段自己。 | 问题码 | 设计器里的说明 | 参数 | | --- | --- | --- | | `document.unsupportedVersion` | 不支持的模板版本 {version} | `version` | | `document.tooDeep` | 嵌套超过 {max} 层 | `max` | | `node.unknownKind` | 未登记的种类 {kind} | `kind` | | `node.kindTypeMismatch` | 种类 {kind} 与节点类型不符 | `kind` | | `region.childNotAllowed` | 这里不能放{kind} | `kind` | | `table.cellOutOfBounds` | 单元格超出表格 | — | | `table.cellOverlap` | 单元格与其它单元格重叠 | — | | `table.cellGap` | 表格第 … 行第 … 列空着 | `row`、`column` | | `rule.notApplicable` | {kind}规则不适用于这个字段 | `kind` | | `rule.boundsMissing` | 最小值、最大值至少给一个 | — | | `rule.invalidRange` | 最小值大于最大值 | — | | `rule.invalidPattern` | 正则表达式无效 | — | | `linkage.actionNotApplicable` | 「{action}」不适用于这里 | `action` | | `expression.invalid` | 表达式有误 | — | | `field.dependencyWithoutSource` | 声明了选项依赖,但没有选项来源 | — | | `field.unknownDependency` | 找不到依赖的字段 {key} | `key` | | `field.selfDependency` | 字段不能依赖自己 | — | ## 值问题 {#value} 值问题由 `validateValues`、控制器与渲染器的校验给出,落在字段上,渲染器把文案写在字段下方并汇总到错误摘要。规则写了 `message` 时显示它,否则用下表的缺省文案。 值校验与联动的问题码: - `XH_FORM_VALUE_REQUIRED`、`XH_FORM_VALUE_NOT_ACCEPTED`、`XH_FORM_VALUE_TOO_SHORT`、`XH_FORM_VALUE_TOO_LONG`、`XH_FORM_VALUE_TOO_SMALL`、`XH_FORM_VALUE_TOO_LARGE`、`XH_FORM_VALUE_PATTERN_MISMATCH`、`XH_FORM_VALUE_EXPRESSION_FALSE`:值不满足规则。 - `XH_FORM_VALUE_TYPE_MISMATCH`:值的形状与字段不符,规则无法判定。 - `XH_FORM_INVALID_PATTERN`:校验时正则编译不过。 - `XH_FORM_EXPRESSION_FAILED`:求值失败;`XH_FORM_CONDITION_NOT_BOOLEAN`:条件结果不是布尔或 null。 | 问题码 | 渲染器里的缺省文案 | 参数 | | --- | --- | --- | | `XH_FORM_VALUE_REQUIRED` | 此项必填 | — | | `XH_FORM_VALUE_NOT_ACCEPTED` | 请勾选此项 | — | | `XH_FORM_VALUE_TOO_SHORT` | 至少输入 {min} 个字符 | `min` | | `XH_FORM_VALUE_TOO_LONG` | 最多输入 {max} 个字符 | `max` | | `XH_FORM_VALUE_TOO_SMALL` | 不能小于 {min} | `min` | | `XH_FORM_VALUE_TOO_LARGE` | 不能大于 {max} | `max` | | `XH_FORM_VALUE_PATTERN_MISMATCH` | 格式不正确 | — | | `XH_FORM_VALUE_EXPRESSION_FALSE` | 取值不符合要求 | — | | `XH_FORM_VALUE_TYPE_MISMATCH` | 取值类型不正确 | — | | `XH_FORM_INVALID_PATTERN` | 校验用的正则表达式无效 | — | | `XH_FORM_EXPRESSION_FAILED` | 校验表达式求值失败 | — | | `XH_FORM_CONDITION_NOT_BOOLEAN` | 校验表达式的结果不是真或假 | — | ## 运行时错误 {#runtime} 运行时错误码: - `XH_FORM_RT_INVALID_DOCUMENT`:文档没通过 schema 自校验(有 error 级问题),全部问题在 `issues`。 - `XH_FORM_RT_NO_ENGINE`:文档里有表达式,却没有提供表达式引擎。 - `XH_FORM_RT_UNKNOWN_NODE` / `XH_FORM_RT_UNKNOWN_PATH`:节点 id 或数据路径找不到。 - `XH_FORM_RT_INVALID_VALUE`:值的形状与字段不符。 - `XH_FORM_RT_NOT_A_LIST`:路径指向的不是列表容器。 - `XH_FORM_RT_INDEX_OUT_OF_RANGE`:行号越界。 - `XH_FORM_RT_NO_OPTIONS`:目标不是选项类字段。 - `XH_FORM_RT_NO_OPTION_LOADER`:字段有选项来源,却没有提供选项加载函数。 - `XH_FORM_RT_NO_OPTION_SOURCE`:按搜索串或父节点加载选项,但字段没有选项来源(静态选项)。 - `XH_FORM_RT_INVALID_OPTIONS`:加载函数为某个父节点返回的子项不符合字段的选项形状。 - `XH_FORM_RT_DISPOSED`:控制器已经销毁。 - `XH_FORM_RT_NOT_READY`:渲染器还没有可用的控制器(没给模板,或模板无效)。 - `XH_FORM_RT_NO_RENDERER`:渲染器找不到某个种类的渲染方式,`data.kind` 是种类名。 `FormRuntimeError` 的 `code` 是上面的错误码之一,`data` 带定位数据,模板无效时 `issues` 是全部模板问题。 --- 来源:https://form.docs.xihanfun.com/reference/kinds # 种类与属性 内置种类按物料面板的分类列出。属性写成所在属性包里的路径(如 `basic.label` 即节点的 `basic` 里的 `label`);「名称」是属性面板里的中文名;「新建时」是在设计器里新建节点时写入的值,过长的值以 … 代替。 每个节点还有共有字段 `type`、`id`、`kind`、`validation`(静态元素没有)、`linkage` 与 `extensions`;字段与列表容器另有数据键 `key`,容器另有区域 `regions`。见[模板类型](./schema)。 ## 表单设置 {#settings} 没有选中节点时,属性面板编辑表单设置,存放在文档的 `settings` 里。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `settings.labelPlacement` | 标签位置 | `top` 上方、`start` 左侧 | `"top"` | | `settings.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `settings.disabled` | 禁用 | 布尔 | `false` | | `settings.readOnly` | 只读 | 布尔 | `false` | ## 输入 {#category-input} ### 单行文本 `text` {#text} 字段 · 渲染为 `` · 值:字符串,空值 `""` · 适用的校验:必填、长度、正则、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.placeholder` | 占位符 | 文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `data.inputType` | 输入类型 | `text` 文本、`email` 邮箱、`tel` 电话、`url` 网址 | `"text"` | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | | `appearance.prefix` | 前缀 | 文本 | — | | `appearance.suffix` | 后缀 | 文本 | — | ### 多行文本 `textarea` {#textarea} 字段 · 渲染为 `` · 值:字符串,空值 `""` · 适用的校验:必填、长度、正则、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.placeholder` | 占位符 | 文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | | `appearance.minRows` | 最少行数 | 整数,≥ 1,≤ 50 | `3` | | `appearance.maxRows` | 最多行数 | 整数,≥ 1,≤ 50 | — | ### 可提及文本 `mention` {#mention} 字段 · 渲染为 `` · 值:字符串,空值 `""` · 适用的校验:必填、长度、正则、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.placeholder` | 占位符 | 文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `data.options` | 候选 | [选项](./schema#options)数组 | `[]` | | `data.optionSource` | 候选来源 | 文本 | — | | `data.optionDependencies` | 候选依赖 | 字符串数组 | — | | `data.triggerPrefix` | 提及符号 | 文本 | `"@"` | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | ### 密码 `password` {#password} 字段 · 渲染为 `` · 值:字符串,空值 `""` · 适用的校验:必填、长度、正则、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.placeholder` | 占位符 | 文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | ### 数字 `number` {#number} 字段 · 渲染为 `` · 值:数值,空值 `null` · 适用的校验:必填、范围、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.placeholder` | 占位符 | 文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `data.step` | 步长 | 数值 | `1` | | `data.precision` | 小数位数 | 整数,≥ 0,≤ 20 | — | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | | `appearance.prefix` | 前缀 | 文本 | — | | `appearance.suffix` | 后缀 | 文本 | — | ### 验证码 `pin` {#pin} 字段 · 渲染为 `` · 值:字符串,空值 `""` · 适用的校验:必填、长度、正则、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.placeholder` | 占位符 | 文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `data.length` | 位数 | 整数,≥ 1,≤ 12 | `6` | | `data.mask` | 遮挡输入 | 布尔 | `false` | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | ## 选择 {#category-choice} ### 下拉选择 `select` {#select} 字段 · 渲染为 `` · 值:字符串(打开「多选」时为字符串数组),空值 `null` · 适用的校验:必填、长度、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.placeholder` | 占位符 | 文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `data.options` | 选项 | [选项](./schema#options)数组 | `[]` | | `data.optionSource` | 选项来源 | 文本 | — | | `data.optionDependencies` | 选项依赖 | 字符串数组 | — | | `data.multiple` | 多选 | 布尔 | `false` | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | ### 可输入下拉 `combobox` {#combobox} 字段 · 渲染为 `` · 值:字符串(打开「多选」时为字符串数组),空值 `null` · 适用的校验:必填、长度、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.placeholder` | 占位符 | 文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `data.options` | 选项 | [选项](./schema#options)数组 | `[]` | | `data.optionSource` | 选项来源 | 文本 | — | | `data.optionDependencies` | 选项依赖 | 字符串数组 | — | | `data.multiple` | 多选 | 布尔 | `false` | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | ### 级联选择 `cascader` {#cascader} 字段 · 渲染为 `` · 值:字符串数组(打开「多选」时为数组),空值 `[]` · 适用的校验:必填、长度、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.placeholder` | 占位符 | 文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `data.options` | 选项 | [选项](./schema#options)树,每项可带 `children` 与 `hasChildren` | `[]` | | `data.optionSource` | 选项来源 | 文本 | — | | `data.optionDependencies` | 选项依赖 | 字符串数组 | — | | `data.multiple` | 多选 | 布尔 | `false` | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | ### 树选择 `tree-select` {#tree-select} 字段 · 渲染为 `` · 值:字符串(打开「多选」时为字符串数组),空值 `null` · 适用的校验:必填、长度、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.placeholder` | 占位符 | 文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `data.options` | 选项 | [选项](./schema#options)树,每项可带 `children` 与 `hasChildren` | `[]` | | `data.optionSource` | 选项来源 | 文本 | — | | `data.optionDependencies` | 选项依赖 | 字符串数组 | — | | `data.multiple` | 多选 | 布尔 | `false` | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | ### 单选组 `radio-group` {#radio-group} 字段 · 渲染为 `` · 值:字符串,空值 `null` · 适用的校验:必填、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `data.options` | 选项 | [选项](./schema#options)数组 | `[]` | | `data.optionSource` | 选项来源 | 文本 | — | | `data.optionDependencies` | 选项依赖 | 字符串数组 | — | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | | `appearance.orientation` | 排列方向 | `horizontal` 横排、`vertical` 竖排 | `"horizontal"` | ### 多选组 `checkbox-group` {#checkbox-group} 字段 · 渲染为 `` · 值:字符串数组,空值 `[]` · 适用的校验:必填、长度、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `data.options` | 选项 | [选项](./schema#options)数组 | `[]` | | `data.optionSource` | 选项来源 | 文本 | — | | `data.optionDependencies` | 选项依赖 | 字符串数组 | — | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | | `appearance.orientation` | 排列方向 | `horizontal` 横排、`vertical` 竖排 | `"horizontal"` | ### 按钮组 `toggle-group` {#toggle-group} 字段 · 渲染为 `` · 值:字符串(打开「多选」时为字符串数组),空值 `null` · 适用的校验:必填、长度、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `data.options` | 选项 | [选项](./schema#options)数组 | `[]` | | `data.optionSource` | 选项来源 | 文本 | — | | `data.optionDependencies` | 选项依赖 | 字符串数组 | — | | `data.multiple` | 多选 | 布尔 | `false` | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | ### 列表 `listbox` {#listbox} 字段 · 渲染为 `` · 值:字符串(打开「多选」时为字符串数组),空值 `null` · 适用的校验:必填、长度、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `data.options` | 选项 | [选项](./schema#options)数组 | `[]` | | `data.optionSource` | 选项来源 | 文本 | — | | `data.optionDependencies` | 选项依赖 | 字符串数组 | — | | `data.multiple` | 多选 | 布尔 | `false` | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | ### 可选标签 `tag-group` {#tag-group} 字段 · 渲染为 `` · 值:字符串(打开「多选」时为字符串数组),空值 `null` · 适用的校验:必填、长度、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `data.options` | 选项 | [选项](./schema#options)数组 | `[]` | | `data.optionSource` | 选项来源 | 文本 | — | | `data.optionDependencies` | 选项依赖 | 字符串数组 | — | | `data.multiple` | 多选 | 布尔 | `false` | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | ### 穿梭框 `transfer` {#transfer} 字段 · 渲染为 `` · 值:字符串数组,空值 `[]` · 适用的校验:必填、长度、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `data.options` | 选项 | [选项](./schema#options)数组 | `[]` | | `data.optionSource` | 选项来源 | 文本 | — | | `data.optionDependencies` | 选项依赖 | 字符串数组 | — | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | ### 勾选框 `checkbox` {#checkbox} 字段 · 渲染为 `` · 值:布尔,空值 `false` · 适用的校验:必填、必须勾选、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | ### 开关 `switch` {#switch} 字段 · 渲染为 `` · 值:布尔,空值 `false` · 适用的校验:必填、必须勾选、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | ## 日期时间 {#category-datetime} ### 日期 `date` {#date} 字段 · 渲染为 `` · 值:字符串,空值 `null` · 适用的校验:必填、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.placeholder` | 占位符 | 文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `data.withTime` | 带时间 | 布尔 | `false` | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | ### 日期范围 `date-range` {#date-range} 字段 · 渲染为 `` · 值:字符串数组,空值 `[]` · 适用的校验:必填、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.placeholder` | 占位符 | 文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `data.withTime` | 带时间 | 布尔 | `false` | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | ### 时间 `time` {#time} 字段 · 渲染为 `` · 值:字符串,空值 `null` · 适用的校验:必填、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `data.withSeconds` | 带秒 | 布尔 | `false` | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | ### 时间范围 `time-range` {#time-range} 字段 · 渲染为 `` · 值:字符串数组,空值 `[]` · 适用的校验:必填、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `data.withSeconds` | 带秒 | 布尔 | `false` | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | ## 高级 {#category-advanced} ### 滑块 `slider` {#slider} 字段 · 渲染为 `` · 值:数值(打开「区间」时为数值数组),空值 `null` · 适用的校验:必填、范围、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `data.min` | 最小值 | 数值 | `0` | | `data.max` | 最大值 | 数值 | `100` | | `data.step` | 步长 | 数值 | `1` | | `data.range` | 区间 | 布尔 | `false` | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | ### 评分 `rating` {#rating} 字段 · 渲染为 `` · 值:数值,空值 `null` · 适用的校验:必填、范围、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `data.count` | 星数 | 整数,≥ 1,≤ 10 | `5` | | `data.allowHalf` | 允许半星 | 布尔 | `false` | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | ### 颜色 `color` {#color} 字段 · 渲染为 `` · 值:字符串,空值 `null` · 适用的校验:必填、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `data.format` | 格式 | `hex`、`rgba`、`hsla`、`oklch` | `"hex"` | | `data.swatches` | 色板颜色 | 字符串数组 | `[]` | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | | `appearance.display` | 展示方式 | `picker` 取色器、`swatches` 色板 | `"picker"` | ### 标签 `tags` {#tags} 字段 · 渲染为 `` · 值:字符串数组,空值 `[]` · 适用的校验:必填、长度、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.placeholder` | 占位符 | 文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `data.max` | 最大值 | 整数,≥ 1,≤ 1000 | — | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | ### 文件 `file` {#file} 字段 · 渲染为 `` · 值:数组,空值 `[]` · 适用的校验:必填、长度、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `data.accept` | 允许的类型 | 文本 | — | | `data.maxFiles` | 最多文件数 | 整数,≥ 1,≤ 100 | — | | `data.uploadTarget` | 上传位置 | 文本 | — | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | ### 签名 `signature` {#signature} 字段 · 渲染为 `` · 值:对象,空值 `null` · 适用的校验:必填、表达式 · 联动动作:显示、隐藏、启用、禁用、必填、选填、赋值。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | `""` | | `basic.hint` | 提示 | 多行文本 | — | | `basic.initialValue` | 初始值 | 任意 JSON 值,形状随字段的值 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `basic.readOnly` | 只读 | 布尔 | `false` | | `appearance.labelHidden` | 隐藏标签 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | | `appearance.columnMinWidth` | 列最小宽度 | 整数,≥ 48,≤ 1024 | — | ## 布局 {#category-layout} ### 栅格 `grid` {#grid} 容器 · 渲染为 `` · 只管排版,对数据透明 · 区域 1 个起、最多 12 个 · 区域里能放任意节点 · 联动动作:显示、隐藏、启用、禁用。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `appearance.gap` | 间距 | `xs` 极小、`sm` 小、`md` 中、`lg` 大、`xl` 极大 | `"md"` | 区域属性,存放在各区域的 `props` 里: | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `props.span` | 跨列 | 整数,≥ 1,≤ 12 | `6` | | `props.offset` | 左侧留空 | 整数,≥ 0,≤ 11 | — | ### 标签页 `tabs` {#tabs} 容器 · 渲染为 `` · 只管排版,对数据透明 · 区域 1 个起、不限上限 · 区域里能放任意节点 · 联动动作:显示、隐藏、启用、禁用。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | 区域属性,存放在各区域的 `props` 里: | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `props.title` | 标题 | 文本 | `""` | ### 卡片 `card` {#card} 容器 · 渲染为 `` · 只管排版,对数据透明 · 区域固定 1 个 · 区域里能放任意节点 · 联动动作:显示、隐藏、启用、禁用。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.title` | 标题 | 文本 | `""` | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | ### 折叠面板 `collapse` {#collapse} 容器 · 渲染为 `` · 只管排版,对数据透明 · 区域 1 个起、不限上限 · 区域里能放任意节点 · 联动动作:显示、隐藏、启用、禁用。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `appearance.multiple` | 多选 | 布尔 | `true` | 区域属性,存放在各区域的 `props` 里: | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `props.title` | 标题 | 文本 | `""` | | `props.expanded` | 默认展开 | 布尔 | `true` | ### 分组 `fieldset` {#fieldset} 容器 · 渲染为 `` · 只管排版,对数据透明 · 区域固定 1 个 · 区域里能放任意节点 · 联动动作:显示、隐藏、启用、禁用。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.legend` | 分组标题 | 文本 | `""` | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | ### 表格布局 `table` {#table} 容器 · 渲染为 `` · 只管排版,对数据透明 · 区域 1 个起、不限上限 · 区域里能放任意节点 · 联动动作:显示、隐藏、启用、禁用。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `appearance.rows` | 行数 | 整数,≥ 1,≤ 100 | `2` | | `appearance.columns` | 列数 | 整数,≥ 1,≤ 12 | `2` | | `appearance.bordered` | 显示格线 | 布尔 | `true` | 区域属性,存放在各区域的 `props` 里: | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `props.row` | 行 | 整数,≥ 0,≤ 99 | `0` | | `props.column` | 列 | 整数,≥ 0,≤ 11 | `0` | | `props.rowSpan` | 跨行 | 整数,≥ 1,≤ 100 | `1` | | `props.columnSpan` | 跨列 | 整数,≥ 1,≤ 12 | `1` | ## 数据 {#category-data} ### 子表单 `subform` {#subform} 容器 · 渲染为 `` · 带数据键,值是行对象的数组 · 区域固定 1 个 · 区域里能放任意节点 · 适用的校验:必填、长度、表达式 · 联动动作:显示、隐藏、启用、禁用。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.title` | 标题 | 文本 | `""` | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `data.initialRows` | 初始行数 | 整数,≥ 0,≤ 100 | `0` | | `data.movable` | 可调整行序 | 布尔 | `true` | ### 子表 `subtable` {#subtable} 容器 · 渲染为 `` · 带数据键,值是行对象的数组 · 区域固定 1 个 · 区域里只能放字段 · 适用的校验:必填、长度、表达式 · 联动动作:显示、隐藏、启用、禁用。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.title` | 标题 | 文本 | `""` | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `basic.disabled` | 禁用 | 布尔 | `false` | | `data.initialRows` | 初始行数 | 整数,≥ 0,≤ 100 | `0` | | `data.movable` | 可调整行序 | 布尔 | `true` | ## 静态 {#category-static} ### 分隔线 `divider` {#divider} 静态元素 · 渲染为 `` · 联动动作:显示、隐藏。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.label` | 标签 | 文本 | — | | `basic.hidden` | 隐藏 | 布尔 | `false` | ### 段落 `paragraph` {#paragraph} 静态元素 · 渲染为 `` · 联动动作:显示、隐藏。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.content` | 内容 | 多行文本 | `""` | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `appearance.size` | 尺寸 | `sm` 小、`md` 中、`lg` 大 | `"md"` | ### 标题 `heading` {#heading} 静态元素 · 渲染为 `` · 联动动作:显示、隐藏。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.content` | 内容 | 文本 | `""` | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `appearance.level` | 级别 | `2` 二级、`3` 三级、`4` 四级、`5` 五级、`6` 六级 | `3` | ### 提示 `alert` {#alert} 静态元素 · 渲染为 `` · 联动动作:显示、隐藏。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.title` | 标题 | 文本 | `""` | | `basic.content` | 内容 | 多行文本 | `""` | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `appearance.tone` | 语气 | `info` 信息、`success` 成功、`warning` 警告、`danger` 危险、`neutral` 中性 | `"info"` | ### 图片 `image` {#image} 静态元素 · 渲染为 `` · 联动动作:显示、隐藏。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.src` | 图片地址 | 文本 | `""` | | `basic.alt` | 替代文本 | 文本 | `""` | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `appearance.width` | 宽度 | 整数,≥ 16,≤ 4096 | — | ### 条码 `barcode` {#barcode} 静态元素 · 渲染为 `` · 联动动作:显示、隐藏。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.value` | 编码内容 | 文本 | `""` | | `basic.format` | 码制 | `code128`、`ean13`、`ean8`、`upca`、`upce`、`itf14`、`code39` | `"code128"` | | `basic.hidden` | 隐藏 | 布尔 | `false` | | `appearance.showText` | 显示文字 | 布尔 | `true` | ### 二维码 `qrcode` {#qrcode} 静态元素 · 渲染为 `` · 联动动作:显示、隐藏。 | 属性 | 名称 | 取值 | 新建时 | | --- | --- | --- | --- | | `basic.value` | 编码内容 | 文本 | `""` | | `basic.format` | 码制 | `qr` QR 码、`data-matrix` Data Matrix、`pdf417` PDF417、`aztec` Aztec | `"qr"` | | `basic.hidden` | 隐藏 | 布尔 | `false` | --- 来源:https://form.docs.xihanfun.com/reference/renderer # 渲染器 `@xihan-form/runtime` 的公开接口:渲染器元素、表单控制器与相关类型。样式在 `@xihan-form/runtime/style.css`,语言包在 `@xihan-form/runtime/locale`。 ## `` 注册 <xh-form-renderer> 与内置字段、容器用到的库内元素;重复调用不报错。宿主自定义字段渲染器用到的元素由宿主注册。 ### 属性 | 属性 | attribute | 类型 | 说明 | | --- | --- | --- | --- | | `schema` | — | `FormDocument \| undefined` | 表单模板。 | | `expressionEngine` | — | `ExpressionEngine \| undefined` | 表达式引擎,缺省为 @xihan-ui/expression 的 createExpressionEngine()。 | | `values` | — | `Readonly> \| undefined` | 初始数据兼 reset 与 isDirty 的基准;控制器建好后再赋值等于 setFormData(values, { baseline: true }),与当前值相同时不动。 | | `variables` | — | `Readonly> \| undefined` | 宿主提供给表达式的变量,名字以 `$` 开头。 | | `loadOptions` | — | `OptionLoader \| undefined` | 选项加载函数。 | | `uploadFile` | — | `FileUploader \| undefined` | 文件字段的上传函数,交给 xh-file-upload 的 upload;组件自己不发请求。 | | `readonly` | `readonly` | `boolean \| undefined` | 整表只读:仍可提交。attribute 按三态读:没写或写 "false" 不生效,其余写法生效。 | | `disabled` | `disabled` | `boolean \| undefined` | 整表禁用:不能提交。attribute 按三态读:没写或写 "false" 不生效,其余写法生效。 | | `validateOn` | `validate-on` | `FormValidateOn \| undefined` | 单字段首次校验的时机,缺省 `blur`;校验过的字段此后随改值重算。 | | `submitAction` | — | `FormSubmitAction \| undefined` | 提交通过校验后调用的异步动作。 | | `translations` | — | `Partial \| undefined` | 元素自己的文案,压在全局配置之上。 | ### 存取器 | 属性 | 读写 | 类型 | 说明 | | --- | --- | --- | --- | | `extensions` | 可读写 | `readonly FormRuntimeExtension[] \| undefined` | 扩展:种类、渲染、编辑方式与规则都从这里来,内置扩展自动排在最前;与设计器、validateDocument 的 extensions 是同一份。赋值时当场合并查重,冲突抛 DesignerExtensionError、什么都不变;换了就重建控制器。同一个扩展对象换了引用也算换了,宿主要保持引用稳定。 | | `controller` | 只读 | `FormController \| null` | 当前的表单控制器;没给模板或模板无效时为 `null`。模板等属性一变就换一个新的,见 `ready` 事件。 | ### 方法 | 方法 | 说明 | | --- | --- | | `getFormData(options?: GetFormDataOptions): Record` | 当前表单值的副本。 | | `setFormData(data: Readonly>, options?: SetFormDataOptions): void` | 按模板替换全部绑定值,见控制器的 setFormData。 | | `getValue(path: JsonPath): JsonValue \| undefined` | 按数据路径读值的副本。 | | `setValue(path: JsonPath, value: JsonValue): void` | 写一个字段或列表容器的值。 | | `validate(): ValidationOutcome` | 校验全部字段并显示问题。 | | `validateField(path: JsonPath): ValidationOutcome` | 校验一个字段或列表容器并显示它的问题。 | | `reset(): void` | 回到基准值并清空校验状态。 | | `isDirty(): boolean` | 当前值与基准值是否不同。 | | `submit(): void` | 走一次提交:与回车、提交键同一条路径;结果看 `submit` / `invalid` 事件。 | | `focusField(target: string \| JsonPath): boolean` | 让一个字段获得焦点;target 是节点 id 或数据路径。字段不存在时返回 false。 | ### 事件 事件都是冒泡的 `CustomEvent`。 | 事件 | detail | 说明 | | --- | --- | --- | | `ready` | `{ controller: FormController }` | 按模板建好控制器、渲染出表单时派发;换了模板、扩展、表达式引擎或选项加载函数后重建,再派发一次。 | | `schema-error` | `{ code: FormRuntimeErrorCode, data: Readonly>, issues: readonly FormSchemaIssue[] }` | 模板没通过自检或缺少表达式引擎,表单不渲染;`issues` 是全部模板问题。 | | `value-change` | `{ paths: readonly JsonPath[], values: Record }` | 表单值变了:用户输入、联动赋值、增删行或宿主赋值;`paths` 是变化的数据路径,`values` 是变化后的整份数据。 | | `issues-change` | `{ paths: readonly JsonPath[], issues: readonly ValueIssue[] }` | 字段的校验问题变了;`paths` 是问题有变化的数据路径,`issues` 是当前全部问题。 | | `submit` | `{ values: Record }` | 提交通过了校验;`values` 不含隐藏字段的值。 | | `invalid` | `{ issues: readonly ValueIssue[], values: Record }` | 提交没通过校验,焦点已移到第一个出问题的字段;`issues` 是全部问题。 | | `submit-error` | `{ cause: unknown, values: Record }` | `submitAction` 返回的 thenable 失败了;`cause` 是失败的原因。 | | `linkage-error` | `{ issues: readonly LinkageIssue[] }` | 联动规则求值出了问题,或多轮重算后仍不稳定;问题有变化时派发。 | ### FormValidateOn {#formvalidateon} 单字段首次校验的时机。 ```ts type FormValidateOn = 'submit' | 'blur' | 'change' ``` ### FormSubmitAction {#formsubmitaction} 提交通过校验后调用的异步动作;返回的 thenable 落定前表单处于提交中。 ```ts type FormSubmitAction = (detail: { readonly values: Record }) => PromiseLike | void ``` ## 表单控制器 ### createFormController() {#createformcontroller} ```ts declare function createFormController(options: FormControllerOptions): FormController ``` 创建表单控制器。创建时先做 schema 自校验,有 error 级问题就抛 `XH_FORM_RT_INVALID_DOCUMENT`(问题列表在 `issues`);文档里有表达式却没有引擎时抛 `XH_FORM_RT_NO_ENGINE`。不碰 DOM,不写全局状态,多个实例互不影响。 ### FormControllerOptions {#formcontrolleroptions} 创建表单控制器的选项。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `document` | `FormDocument` | 表单模板。 | | `extensions?` | `readonly FormCoreExtension[]` | 种类、编辑方式与规则从这组扩展来,内置扩展自动排在最前;与渲染器的 extensions 是同一份。 | | `expressionEngine?` | `ExpressionEngine` | 表达式引擎;文档里有表达式时必须提供。 | | `variables?` | `Readonly>` | 宿主提供给表达式的额外变量,名字以 `$` 开头。 | | `values?` | `Readonly>` | 初始数据,叠在 schema 的初始值之上;也是 `reset` 与 `isDirty` 最初的基准。 | | `loadOptions?` | `OptionLoader` | 选项加载函数;有字段带选项来源时,`reloadOptions` 需要它。 | ### FormController {#formcontroller} 表单控制器。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `document` | `FormDocument` | 创建时传入的表单模板。 | | 方法 | 说明 | | --- | --- | | `getFormData(options?: GetFormDataOptions): Record` | 当前表单值的副本,已含联动赋值。 | | `getValue(path: JsonPath): JsonValue \| undefined` | 按数据路径读值的副本;不存在时为 `undefined`。 | | `setFormData(data: Readonly>, options?: SetFormDataOptions): void` | 按 schema 替换全部绑定值:没给的回到初始值,多带的键原样保留;同时清空校验状态。`baseline` 为 `true` 时,填入的值同时成为 `reset` 与 `isDirty` 的基准。 | | `setValue(path: JsonPath, value: JsonValue): void` | 写一个字段或列表容器的值;形状不符时抛错。值确实变了时,依赖它的选项字段回到空值,并逐级带上依赖这些字段的字段。 | | `getState(target: FormTarget): NodeState \| undefined` | 节点实例的状态;节点 id 只取顶层实例,列表里的实例用实例键或数据路径。 | | `getIssues(): readonly ValueIssue[]` | 当前展示的校验问题。 | | `getLinkageIssues(): readonly LinkageIssue[]` | 最近一次联动重算的问题。 | | `validate(): ValidationOutcome` | 校验全部字段,隐藏或禁用的实例跳过、值保留;此后每次改值都会重算问题。 | | `validateField(path: JsonPath): ValidationOutcome` | 校验一个字段或列表容器(含此刻各行里的字段);此后它们的问题随改值重算,之后新增的行不算在内。 | | `reset(): void` | 回到基准值并清空校验状态;宿主的强制设定与已加载的选项保留。基准是创建时的值,或最近一次带 `baseline` 填入的值。 | | `isDirty(): boolean` | 当前值与 `reset` 之后的值是否不同。 | | `setHidden(target: FormTarget, hidden: boolean \| null): void` | 强制隐藏、禁用、只读:`true` / `false` 强制,`null` 撤销强制。 | | `setDisabled(target: FormTarget, disabled: boolean \| null): void` | 强制禁用:`true` / `false` 强制,`null` 撤销强制。 | | `setReadOnly(target: FormTarget, readOnly: boolean \| null): void` | 强制只读:`true` / `false` 强制,`null` 撤销强制。 | | `addRow(listPath: JsonPath, options?: AddRowOptions): void` | 列表容器的行:新增、删除、移动。行级的强制设定、选项与校验进度跟着行走。 | | `removeRow(listPath: JsonPath, index: number): void` | 删掉列表容器的第 `index` 行(从 0 起)。 | | `moveRow(listPath: JsonPath, from: number, to: number): void` | 把列表容器的第 `from` 行移到第 `to` 行(从 0 起)。 | | `reloadOptions(target?: FormTarget): Promise` | 重新加载选项:不给目标时加载全部带选项来源的字段实例。提供了加载函数时,选项依赖的值一变(改值、整份替换、重置、联动赋值),依赖它的字段实例会自动重新加载。 | | `searchOptions(target: FormTarget, query: string): Promise` | 带搜索串重新加载一个字段实例的选项:请求的 `query` 即这个串,之后因依赖值变化的自动重载也带着它。只用于带选项来源的字段;连续调用时只认最后一次的结果。 | | `loadChildren(target: FormTarget, parent: string): Promise` | 懒加载选项树里某个节点的直接子项:请求带上 `parent`,返回校验过的子项,不写进字段的选项状态。字段没有选项来源时报 `XH_FORM_RT_NO_OPTION_SOURCE`,返回的子项形状不对时报 `XH_FORM_RT_INVALID_OPTIONS`。 | | `getOptions(target: FormTarget): OptionsState` | 字段实例的选项:带选项来源时是加载状态,否则是 schema 里的静态选项。 | | `setVariables(variables: Readonly>): void` | 替换宿主变量并重算联动。 | | `subscribe(listener: (event: FormEvent) => void): () => void` | 订阅变更,返回取消函数。 | | `dispose(): void` | 销毁:取消全部订阅,之后调用任何方法都抛错,晚到的选项结果被丢弃。 | ### ValidationOutcome {#validationoutcome} 校验结果。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `valid` | `boolean` | 没有问题。 | | `issues` | `readonly ValueIssue[]` | 校验出的问题。 | ### GetFormDataOptions {#getformdataoptions} 读取表单数据的选项。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `omitHidden?` | `boolean` | 为 `true` 时去掉隐藏实例的值(含被隐藏容器里的字段与整个被隐藏的列表)。 | ### SetFormDataOptions {#setformdataoptions} 替换表单数据的选项。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `baseline?` | `boolean` | 为 `true` 时,填入的值同时成为 `reset` 与 `isDirty` 的基准。 | ### AddRowOptions {#addrowoptions} 新增行的选项。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `index?` | `number` | 插入位置,缺省追加到末尾。 | | `values?` | `Readonly>` | 新行的数据,缺的键取 schema 初始值。 | ## 选项与上传 ### OptionLoader {#optionloader} 宿主提供的选项加载函数,返回选项数组或它的 Promise。 ```ts type OptionLoader = (request: OptionRequest) => readonly OptionItem[] | Promise ``` ### OptionRequest {#optionrequest} 交给宿主加载函数的请求。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `source` | `string` | 字段的 `data.optionSource`。 | | `nodeId` | `string` | 字段节点的 id。 | | `path` | `JsonPath` | 字段实例的数据路径。 | | `values` | `Readonly>` | 当前表单值的副本。 | | `query?` | `string` | 搜索串:可输入筛选的字段(组合框)在输入时带上,宿主按它返回匹配的选项;没有输入过时不带。 | | `parent?` | `string` | 懒加载:要取直接子项的那个节点的值,宿主只返回它的子项;取整棵(顶层)时不带。 | ### OptionItem {#optionitem} 一个选项;树形选项可带 `children`。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `value` | `string` | 选中后写进表单数据的值。 | | `label` | `string` | 显示的文字。 | | `disabled?` | `boolean` | 禁用:显示但不能选。 | | `children?` | `readonly OptionItem[]` | 子项,只有树形选项(级联、树选择)用。 | | `hasChildren?` | `boolean` | 选项树里有子项但还没给出:首次展开时经 `parent` 再请求一次它的直接子项。 | ### OptionsState {#optionsstate} 一个字段实例的选项状态。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `status` | `'idle' \| 'loading' \| 'ready' \| 'error'` | 加载状态:没加载过、加载中、已就绪、失败。静态选项总是 `ready`。 | | `items` | `readonly OptionItem[]` | 当前的选项;加载中时是上一次的结果,失败时为空。 | ### FileUploader {#fileuploader} 宿主提供的上传函数:返回带 `id` 的结果时,这个文件转成附件记录写进字段值。 ```ts type FileUploader = (request: UploadRequest) => FileUploadResult | undefined | void | Promise ``` ### UploadRequest {#uploadrequest} 交给宿主上传函数的请求:UI 的单个文件上传请求,加上所属字段。 继承 `FileUploadRequest`。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `target` | `string \| undefined` | 字段的 `data.uploadTarget`。 | | `nodeId` | `string` | 字段节点的 id。 | | `path` | `JsonPath` | 字段实例的数据路径。 | ## 扩展 ### FormRuntimeExtension {#formruntimeextension} 表单运行时层的扩展:core 层的全部字段,加上各种类的渲染,键是种类名。列表容器(子表单、子表)由渲染器自己按 xh-field-array 建,不经 containerRenderers。 继承 `FormCoreExtension`。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `fieldRenderers?` | `Readonly>` | 字段种类的渲染方式:种类名 → 字段渲染器。 | | `containerRenderers?` | `Readonly>` | 容器种类的部件树:种类名 → 建出容器元素与各区域放子节点的位置。 | | `staticRenderers?` | `Readonly>` | 静态元素种类的部件树:种类名 → 建出静态元素。 | ### FieldRenderer {#fieldrenderer} 字段渲染器:按种类登记,渲染器为每个字段实例调用一次。 ```ts type FieldRenderer = (context: FieldRendererContext) => FieldRendererInstance ``` ### FieldRendererContext {#fieldrenderercontext} 交给字段渲染器的上下文。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `document` | `Document` | 渲染器所在的文档,建节点用。 | | `node` | `FieldNode` | 字段节点。 | | `path` | `JsonPath` | 字段实例的数据路径。 | | `value` | `JsonValue \| undefined` | 字段当前的值。 | | `state` | `NodeState` | 联动状态与整表的只读、禁用合并后的结果。 | | `error` | `string \| undefined` | 当前展示的错误文案;没有错误时为 `undefined`。 | | `options` | `OptionsState \| undefined` | 选项类字段的选项状态,其余为 `undefined`。 | | `searchOptions` | `((query: string) => void) \| undefined` | 带搜索串重新加载本字段的选项,结果经 `options` 回来;字段没有选项来源或宿主没给 `loadOptions` 时为 `undefined`。 | | `reloadOptions` | `(() => void) \| undefined` | 重新加载本字段的选项(如取数失败后重试),结果经 `options` 回来;字段没有选项来源或宿主没给 `loadOptions` 时为 `undefined`。 | | `loadChildren` | `((parent: string) => Promise) \| undefined` | 懒加载选项树里某个节点的直接子项;字段没有选项来源或宿主没给 `loadOptions` 时为 `undefined`。 | | `size` | `FieldSize` | 控件尺寸:字段自己的 `appearance.size`,没写时取表单设置的 `size`。 | | `translations` | `FormRendererTranslations` | 渲染器当前的文案。 | | `uploadFile` | `((request: FileUploadRequest) => ReturnType) \| undefined` | 上传一个文件,请求里已带上本字段;宿主没给 `uploadFile` 时为 `undefined`。 | | 方法 | 说明 | | --- | --- | | `setValue(value: JsonValue): void` | 写值,交给控制器的 setValue;值的形状由种类登记的 valueKind 把关,不符时抛 `XH_FORM_RT_INVALID_VALUE`。 | | `commit(): void` | 一次编辑结束(如失焦)。 | ### FieldRendererInstance {#fieldrendererinstance} 字段渲染器产出的实例。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `element` | `HTMLElement` | 控件节点:作为 `` 的 control 部件放进字段外壳,标签、提示与错误文案由外壳给出,不必自带。 | | 方法 | 说明 | | --- | --- | | `update(context: FieldRendererContext): void` | 值、状态、错误或选项变化时调用。 | | `destroy(): void` | 节点移除前调用。 | ## 文案 ### FormRendererTranslations {#formrenderertranslations} 渲染器的文案:无参的是字符串,带参的是函数。 文案键是 `xh-form-renderer`,各语言的文案在 `@xihan-form/runtime/locale`,如中文的 `formZhCN`。 | 属性 | 类型 | 说明 | | --- | --- | --- | | `optionsEmpty` | `string` | 没有选项。 | | `addRow` | `string` | 列表容器的新增把手。 | | `fileDropzone` | `string` | 文件字段投放区的说明。 | | `valueRequired` | `string` | 值校验问题的文案,按问题码取:`XH_FORM_VALUE_REQUIRED`。规则写了 message 时用规则的。 | | `valueNotAccepted` | `string` | `XH_FORM_VALUE_NOT_ACCEPTED`。 | | `valuePatternMismatch` | `string` | `XH_FORM_VALUE_PATTERN_MISMATCH`。 | | `valueExpressionFalse` | `string` | `XH_FORM_VALUE_EXPRESSION_FALSE`。 | | `valueTypeMismatch` | `string` | `XH_FORM_VALUE_TYPE_MISMATCH`。 | | `invalidPattern` | `string` | `XH_FORM_INVALID_PATTERN`。 | | `expressionFailed` | `string` | `XH_FORM_EXPRESSION_FAILED`。 | | `conditionNotBoolean` | `string` | `XH_FORM_CONDITION_NOT_BOOLEAN`。 | | 方法 | 说明 | | --- | --- | | `errorSummary(count: number): string` | 错误摘要的标题。 | | `errorSummaryItem(label: string, message: string): string` | 错误摘要里的一条:字段标签与问题文案。 | | `valueTooShort(min: number): string` | `XH_FORM_VALUE_TOO_SHORT`,参数是长度下限。 | | `valueTooLong(max: number): string` | `XH_FORM_VALUE_TOO_LONG`,参数是长度上限。 | | `valueTooSmall(min: number): string` | `XH_FORM_VALUE_TOO_SMALL`,参数是下限。 | | `valueTooLarge(max: number): string` | `XH_FORM_VALUE_TOO_LARGE`,参数是上限。 | ## 全部导出 ### designer-translations | 名称 | 种类 | 说明 | | --- | --- | --- | | `DEFAULT_FORM_DESIGNER_TRANSLATIONS` | 常量 | 表单设计器的缺省文案:英文,与其余语言同一张表组出,种类、属性与选项写可读的名字。 | | `FormDesignerTranslations` | 接口 | 表单设计器的文案:无参的是字符串,带参的是函数。 | | `FormHelpTranslations` | 接口 | 设置对话框「帮助」「关于」两页里表单自己的文字。 | | `FormHistoryTranslations` | 接口 | 撤销历史里一步的说明。 | | `FormKeyTranslations` | 接口 | 快捷键表里不是命令的按键(归「本设计器」那一组)。 | | `FormTourTranslations` | 接口 | 新手引导里表单自己的几步:标题与说明。 | ### extensions | 名称 | 种类 | 说明 | | --- | --- | --- | | `createFormRuntimeModel` | 函数 | 合出运行时模型:core 的合并查重见 `createFormModel`,内置渲染 `FORM_RUNTIME_EXTENSION` 排在传入的之前;同一种类的渲染登记了两次时抛 `DesignerExtensionError`。 | | `FORM_RUNTIME_EXTENSION` | 常量 | 运行时层的内置扩展:内置种类的渲染。 | | `FormRuntimeExtension` | 接口 | 表单运行时层的扩展:core 层的全部字段,加上各种类的渲染,键是种类名。 | | `FormRuntimeModel` | 接口 | 一组运行时扩展合出的模型:core 模型加上按种类名查的三张渲染表。 | ### renderer/containers | 名称 | 种类 | 说明 | | --- | --- | --- | | `ContainerBuilder` | 类型 | 按种类建容器节点。 | | `ContainerView` | 接口 | 容器的节点:宿主元素与每个区域放子节点的位置。 | | `StaticBuilder` | 类型 | 按种类建静态元素节点。 | | `StaticView` | 接口 | 静态元素的节点。 | ### renderer/element | 名称 | 种类 | 说明 | | --- | --- | --- | | `defineFormRenderer` | 函数 | 注册 <xh-form-renderer> 与内置字段、容器用到的库内元素;重复调用不报错。 | | `FORM_RENDERER_TAG` | 常量 | 渲染器的标签名。 | | `FormRendererEventMap` | 接口 | 渲染器派发的事件。 | | `FormSubmitAction` | 类型 | 提交通过校验后调用的异步动作;返回的 thenable 落定前表单处于提交中。 | | `FormValidateOn` | 类型 | 单字段首次校验的时机。 | | `XhFormRenderer` | 类 | ``:按表单模板渲染出表单,取值、赋值、校验、重置与提交都经它的方法;选项加载与附件上传经回调交给宿主,组件自己不发请求。 | ### renderer/fields/index | 名称 | 种类 | 说明 | | --- | --- | --- | | `FORM_FIELD_RENDERERS` | 常量 | 内置字段种类的渲染器。 | ### renderer/types | 名称 | 种类 | 说明 | | --- | --- | --- | | `FieldRenderer` | 类型 | 字段渲染器:按种类登记,渲染器为每个字段实例调用一次。 | | `FieldRendererContext` | 接口 | 交给字段渲染器的上下文。 | | `FieldRendererInstance` | 接口 | 字段渲染器产出的实例。 | | `FieldSize` | 类型 | 字段的尺寸档。 | | `FileUploader` | 类型 | 宿主提供的上传函数:返回带 `id` 的结果时,这个文件转成附件记录写进字段值。 | | `UploadRequest` | 接口 | 交给宿主上传函数的请求:UI 的单个文件上传请求,加上所属字段。 | ### state/controller | 名称 | 种类 | 说明 | | --- | --- | --- | | `AddRowOptions` | 接口 | 新增行的选项。 | | `createFormController` | 函数 | 创建表单控制器。 | | `FormController` | 接口 | 表单控制器。 | | `FormControllerOptions` | 接口 | 创建表单控制器的选项。 | | `FormEvent` | 类型 | 变更通知,带着变化范围: | | `FormEventType` | 类型 | 变更通知的种类。 | | `FormTarget` | 类型 | 寻址:字符串是节点 id 或实例键(节点 id 后接 `#` 与逐层行号,即 states 通知里的键),数组是数据路径。 | | `GetFormDataOptions` | 接口 | 读取表单数据的选项。 | | `LinkageIssue` | 类型 | 联动问题:表达式求值的问题,或联动多轮重算后仍不稳定。 | | `SetFormDataOptions` | 接口 | 替换表单数据的选项。 | | `ValidationOutcome` | 接口 | 校验结果。 | ### state/errors | 名称 | 种类 | 说明 | | --- | --- | --- | | `FormRuntimeError` | 类 | 运行时错误;`message` 就是错误码。 | | `FormRuntimeErrorCode` | 类型 | 运行时错误码: | ### state/options | 名称 | 种类 | 说明 | | --- | --- | --- | | `OptionItem` | 接口 | 一个选项;树形选项可带 `children`。 | | `OptionLoader` | 类型 | 宿主提供的选项加载函数,返回选项数组或它的 Promise。 | | `OptionRequest` | 接口 | 交给宿主加载函数的请求。 | | `OptionsState` | 接口 | 一个字段实例的选项状态。 | ### translations | 名称 | 种类 | 说明 | | --- | --- | --- | | `DEFAULT_FORM_RENDERER_TRANSLATIONS` | 常量 | 渲染器的缺省文案。 | | `FormRendererTranslations` | 接口 | 渲染器的文案:无参的是字符串,带参的是函数。 | ## `@xihan-form/runtime/locale` 的导出 ### locale | 名称 | 种类 | 说明 | | --- | --- | --- | | `formDeDE` | 常量 | 德语:渲染器、设计器与 designer-shell 零件。 | | `formEnUS` | 常量 | 英语:渲染器、设计器与 designer-shell 零件。 | | `formEsES` | 常量 | 西班牙语:渲染器、设计器与 designer-shell 零件。 | | `formFrFR` | 常量 | 法语:渲染器、设计器与 designer-shell 零件。 | | `formJaJP` | 常量 | 日语:渲染器、设计器与 designer-shell 零件。 | | `formKoKR` | 常量 | 韩语:渲染器、设计器与 designer-shell 零件。 | | `formPtBR` | 常量 | 葡萄牙语(巴西):渲染器、设计器与 designer-shell 零件。 | | `formRuRU` | 常量 | 俄语:渲染器、设计器与 designer-shell 零件。 | | `formZhCN` | 常量 | 简体中文:渲染器、设计器与 designer-shell 零件。 | | `formZhTW` | 常量 | 繁体中文(台湾):渲染器、设计器与 designer-shell 零件。 | --- 来源:https://form.docs.xihanfun.com/reference/schema # 模板类型 表单文档 `FormDocument` 的全部类型,按 `@xihan-form/core` 的源码列出。当前结构版本 `FORM_SCHEMA_VERSION` 为 `1`。 各种类的属性包里有哪些属性见[种类与属性](./kinds)。 ## 文档、节点与规则 ### PropertyBag {#propertybag} 属性包:一组按名字存放的 JSON 值。 类型:Readonly<Record<string, JsonValue>> ### ExtensionData {#extensiondata} 扩展数据:扩展 id → 这个扩展自己的 JSON 对象。只给扩展用,与业务属性分开;编辑与复制粘贴原样带着走。 类型:Readonly<Record<string, JsonObject>> ### PropertyGroup {#propertygroup} 节点的属性分组:基础、数据、外观。校验与联动另以规则列表存放。 类型:'basic' | 'data' | 'appearance' ### RequiredRule {#requiredrule} 必填:值为空时不通过,空值判定取 `@xihan-ui/headless` 的 `isEmptyFormValue`:null、只含空白的字符串、空列表与元素全为空的列表算空,`false` 与 `0` 不算空。 | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `id` | string | 是 | 规则 id,在所在规则列表里唯一;设计器编辑与问题定位都按它找规则。 | | `kind` | 'required' | 是 | 规则种类。 | | `message` | string | 可省 | 不通过时给最终用户看的提示;不写时由运行时按问题码给出缺省文案。 | ### AcceptedRule {#acceptedrule} 必须为 `true`:用于「必须勾选同意」,值为 `false` 或空时都不通过。只适用于布尔值字段。 | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `id` | string | 是 | 规则 id,在所在规则列表里唯一;设计器编辑与问题定位都按它找规则。 | | `kind` | 'accepted' | 是 | 规则种类。 | | `message` | string | 可省 | 不通过时给最终用户看的提示;不写时由运行时按问题码给出缺省文案。 | ### LengthRule {#lengthrule} 长度:字符串按字素计(`@xihan-ui/headless` 的 `graphemeLength`,与文本框的字数一致)、列表按元素个数,闭区间。 | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `id` | string | 是 | 规则 id,在所在规则列表里唯一;设计器编辑与问题定位都按它找规则。 | | `kind` | 'length' | 是 | 规则种类。 | | `min` | number | 可省 | 最少多少个字符或元素。 | | `max` | number | 可省 | 最多多少个字符或元素。 | | `message` | string | 可省 | 不通过时给最终用户看的提示;不写时由运行时按问题码给出缺省文案。 | ### RangeRule {#rangerule} 数值范围,闭区间。 | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `id` | string | 是 | 规则 id,在所在规则列表里唯一;设计器编辑与问题定位都按它找规则。 | | `kind` | 'range' | 是 | 规则种类。 | | `min` | number | 可省 | 下限。 | | `max` | number | 可省 | 上限。 | | `message` | string | 可省 | 不通过时给最终用户看的提示;不写时由运行时按问题码给出缺省文案。 | ### PatternRule {#patternrule} 正则:`flags` 只允许 `i`、`m`、`s`、`u`,整串不匹配即不通过。 | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `id` | string | 是 | 规则 id,在所在规则列表里唯一;设计器编辑与问题定位都按它找规则。 | | `kind` | 'pattern' | 是 | 规则种类。 | | `pattern` | string | 是 | 正则的源码,不带两边的斜杠。 | | `flags` | string | 可省 | 正则标志。 | | `message` | string | 可省 | 不通过时给最终用户看的提示;不写时由运行时按问题码给出缺省文案。 | ### ExpressionRule {#expressionrule} 表达式:结果为 `false` 时不通过,为 `true` 或 `null` 时通过。 | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `id` | string | 是 | 规则 id,在所在规则列表里唯一;设计器编辑与问题定位都按它找规则。 | | `kind` | 'expression' | 是 | 规则种类。 | | `expression` | string | 是 | 受限表达式,`$value` 是字段自己的值。 | | `message` | string | 可省 | 不通过时给最终用户看的提示;不写时由运行时按问题码给出缺省文案。 | ### ValidationRule {#validationrule} 声明式校验规则。`message` 是模板作者写给最终用户的提示,缺省时由运行时按规则种类给出。 取其一:RequiredRule、AcceptedRule、LengthRule、RangeRule、PatternRule、ExpressionRule。 ### ValidationKind {#validationkind} 校验规则的种类。 类型:ValidationRule['kind'] ### LinkageAction {#linkageaction} 联动动作:条件成立时生效,不成立时回到节点的静态设定。 - `show` / `hide`:显示 / 隐藏。 - `enable` / `disable`:可用 / 禁用。 - `require` / `optional`:设为必填 / 取消必填。 - `assign`:把 `value` 表达式的结果赋给字段。 类型:'show' | 'hide' | 'enable' | 'disable' | 'require' | 'optional' | 'assign' ### LinkageRule {#linkagerule} 声明式联动规则;`when` 缺省表示总是成立。同一状态有多条规则成立时,排在后面的生效。 | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `id` | string | 是 | 规则 id,在所在规则列表里唯一。 | | `action` | LinkageAction | 是 | 条件成立时做什么。 | | `when` | string | 可省 | 条件,受限表达式,结果为 `true` 时成立;缺省总是成立。 | | `value` | string | 可省 | `assign` 的取值,受限表达式;其它动作不用。 | ### FieldNode {#fieldnode} 字段:有数据键与值。 | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `type` | 'field' | 是 | 节点类型。 | | `id` | string | 是 | 节点 id,在整份文档里唯一。 | | `kind` | string | 是 | 种类,如 `text`、`select`,决定渲染方式与可用的属性。 | | `key` | string | 是 | 数据键:字段的值在表单数据里的键,在所在作用域里唯一。 | | `basic` | PropertyBag | 是 | 基础属性,如标签、提示、初始值、隐藏与禁用。 | | `data` | PropertyBag | 是 | 数据属性,如选项、精度、数据来源。 | | `appearance` | PropertyBag | 是 | 外观属性,如尺寸、前后缀。 | | `validation` | readonly ValidationRule[] | 是 | 校验规则。 | | `linkage` | readonly LinkageRule[] | 是 | 联动规则。 | | `extensions` | ExtensionData | 可省 | 各扩展放在这个节点上的数据。 | ### Region {#region} 区域:容器里放子节点的一格,如栅格的一列、标签页的一页、表格布局的一个单元格。 | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `id` | string | 是 | 区域 id,在整份文档里唯一。 | | `props` | PropertyBag | 是 | 区域属性,如栅格的列宽、标签页的标题、表格布局单元格的位置。 | | `children` | readonly FormNode[] | 是 | 放在这一格里的节点。 | ### ContainerNode {#containernode} 容器:子节点放在区域里。列表作用域的容器(子表单、子表)带 `key`,它的值是行对象的列表,区域里字段的键相对于行对象。 | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `type` | 'container' | 是 | 节点类型。 | | `id` | string | 是 | 节点 id,在整份文档里唯一。 | | `kind` | string | 是 | 种类,如 `grid`、`subform`,决定渲染方式与可用的属性。 | | `key` | string | 可省 | 数据键:只有列表容器有,值是行对象的列表。 | | `basic` | PropertyBag | 是 | 基础属性,如标签、提示、初始值、隐藏与禁用。 | | `data` | PropertyBag | 是 | 数据属性,如选项、精度、数据来源。 | | `appearance` | PropertyBag | 是 | 外观属性,如尺寸、前后缀。 | | `validation` | readonly ValidationRule[] | 是 | 校验规则:只对列表容器的整份列表生效。 | | `linkage` | readonly LinkageRule[] | 是 | 联动规则。 | | `regions` | readonly Region[] | 是 | 区域,按顺序排列。 | | `extensions` | ExtensionData | 可省 | 各扩展放在这个节点上的数据。 | ### StaticNode {#staticnode} 静态元素:只展示,没有值,如分隔线、说明文字。 | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `type` | 'static' | 是 | 节点类型。 | | `id` | string | 是 | 节点 id,在整份文档里唯一。 | | `kind` | string | 是 | 种类,如 `divider`、`paragraph`,决定渲染方式与可用的属性。 | | `basic` | PropertyBag | 是 | 基础属性,如正文、标题文字与隐藏。 | | `data` | PropertyBag | 是 | 数据属性。 | | `appearance` | PropertyBag | 是 | 外观属性,如级别、宽度。 | | `linkage` | readonly LinkageRule[] | 是 | 联动规则:只有显示与隐藏。 | | `extensions` | ExtensionData | 可省 | 各扩展放在这个节点上的数据。 | ### FormNode {#formnode} 表单节点。 取其一:FieldNode、ContainerNode、StaticNode。 ### NodeType {#nodetype} 节点类型。 类型:FormNode['type'] ### FormDocument {#formdocument} 表单文档:纯 JSON,不含任何代码。 | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `schemaVersion` | typeof FORM_SCHEMA_VERSION | 是 | 结构版本,当前是 `FORM_SCHEMA_VERSION`。 | | `settings` | PropertyBag | 是 | 表单设置,如标签位置、尺寸、整表禁用与只读。 | | `children` | readonly FormNode[] | 是 | 顶层节点,按顺序排列。 | | `extensions` | ExtensionData | 可省 | 各扩展放在文档上的数据。 | ## 选项 {#options} 选项:选项列表用 designer-kit 的内建编辑方式 `options`,选项树用 `treeOptions`,都要求 label。每项是 `{ value: string, label: string, disabled?: boolean, tone?: Tone, description?: string }`,选项树的项另可带 `children`(下一层的选项)与 `hasChildren`(有子项但还没给出,首次展开时向宿主要)。按 `@xihan-ui/core/options` 的 `findOptionViolation` 严格检查:不许多余的键,`value` 不为空、同一层里不重复,选项树最多 32 层;问题落在那一项的那个键上,如 `[2, "value"]`、`[0, "children", 1, "label"]`。