XSchemaForm 组件详细设计文档
引言
因为 @formily/core 作为一个独立的包而存在,它的核心意义是将领域模型从 UI 框架中抽离出来,同时给开发者带来了以下两个直观收益:
可以方便 formily 开发者从 UI 与逻辑的耦合关系中释放出来,提升代码可维护性;
可以让 formily 拥有跨终端,跨框架的能力,不管你是 React 用户,Vue 用户还是 Angular 用户,都能享受到 formily 的领域模型带来的提效。
超高性能
借助 @formily/reactive,@formily/core 天然获得了依赖追踪,高效更新,按需渲染的能力,不管是在 React 下,还是 Vue/Angular 下,不管是字段频繁输入,还是字段联动,都能给用户带来 O(1)的性能体验,开发者无需关心性能优化的事情,只需要专注于业务逻辑实现即可。
领域模型
如果把表单问题做分解,其实我们可以分解出:
数据管理问题
字段管理问题
校验管理问题
联动管理问题
这几个方向的问题其实都可以作为领域级问题去解决,每一个领域问题,其实都是非常复杂的问题,在 Formily 中,全部一一给您突破解决了,所以您只需要专注于业务逻辑即可。
Formily 内核架构非常复杂,因为要解决一个领域级的问题,而不是单点具体的问题,架构图示意:

组件概述
XSchemaForm 是一个基于 Vue 3 和 Formily 框架开发的表单组件,它封装了 @formily/element-plus 中的表单相关组件,为开发者提供了一个可复用、可定制的表单解决方案。该组件支持自定义表单组件、自动聚焦输入框、通过插槽自定义表单内容等功能,并且在按下回车键时可以触发提交事件。
功能需求
表单属性配置:支持通过
formProps和form属性自定义表单的布局、标签显示方式等。自动聚焦:通过
needFocus属性控制表单在挂载后是否自动聚焦到第一个输入框。提交事件:按下回车键或点击提交按钮时,触发
enter事件。组件扩展:支持注册自定义组件,以满足不同的表单需求。
组件设计
组件结构
| 文件路径 | 功能描述 |
|---|---|
| schema-form/src/schema-form.vue | 组件的模板和逻辑实现,包含表单的渲染和事件处理。 |
| schema-form/src/schema-form.ts | 定义组件的 props 类型和默认值。 |
| schema-form/index.ts | 导出组件并进行安装处理,方便在项目中使用。 |
| schema-form/src/useSchema.ts | 提供组件状态管理和自定义组件注册功能。 |
组件关系
XSchemaForm组件依赖于@formily/element-plus提供的表单组件,如Form、FormButtonGroup和Submit。useSchema函数用于管理组件状态和注册自定义组件,为XSchemaForm提供了扩展能力。
组件属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| formProps | FormLayout | { wrapperWrap: false, labelWrap: true, layout: 'vertical' } | 表单的属性配置,用于控制表单的布局和显示方式。 |
| form | Object | {} | 表单对象,可用于传递表单数据和验证规则。 |
| needFocus | Boolean | false | 控制表单在挂载后是否自动聚焦到第一个输入框。 |
| schema | ISchema | {} | 表单配置项 |
| iptWidth | String | '240px' | 表单组件在 inline模式下的宽度 |
FormLayout 属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| style | CSSProperties | - | 样式 |
| className | string | - | 类名 |
| colon | boolean | true | 是否有冒号 |
| labelAlign | 'right' | 'left' | ('right' | 'left')[] | - | 标签内容对齐 |
| wrapperAlign | 'right' | 'left' | ('right' | 'left')[] | - | 组件容器内容对齐 |
| labelWrap | boolean | false | 标签内容换行 |
| labelWidth | number | - | 标签宽度(px) |
| wrapperWidth | number | - | 组件容器宽度(px) |
| wrapperWrap | boolean | false | 组件容器换行 |
| labelCol | number | number[] | - | 标签宽度(24 column) |
| wrapperCol | number | number[] | - | 组件容器宽度(24 column) |
| fullness | boolean | false | 组件容器宽度 100% |
| size | 'small' | 'default' | 'large' | default | 组件尺寸 |
| layout | 'vertical' | 'horizontal' | 'inline' | ('vertical' | 'horizontal' | 'inline')[] | horizontal | 布局模式 |
| direction | 'rtl' | 'ltr' | ltr | 方向(暂不支持) |
| inset | boolean | false | 内联布局 |
| shallow | boolean | true | 上下文浅层传递 |
| feedbackLayout | 'loose' | 'terse' | 'popover' | 'none' | true | 反馈布局 |
| tooltipLayout | 'icon' | 'text' | 'icon' | 问提示布局 |
| bordered | boolean | true | 是否有边框 |
| breakpoints | number[] | - | 容器尺寸断点 |
| gridColumnGap | number | 8 | 网格布局列间距 |
| gridRowGap | number | 4 | 网格布局行间距 |
| spaceGap | number | 8 | 弹性间距 |
ISchema 属性
| 属性 | 类型 | 字段模型映射 | 描述 |
|---|---|---|---|
| type | SchemaTypes | GeneralField(opens new window) | 类型 |
| title | React.ReactNode | title | 标题 |
| description | React.ReactNode | description | 描述 |
| default | Any | initialValue | 默认值 |
| readOnly | Boolean | readOnly | 是否只读 |
| writeOnly | Boolean | editable | 是否只写 |
| enum | SchemaEnum | dataSource | 枚举 |
| const | Any | validator | 校验字段值是否与 const 的值相等 |
| multipleOf | Number | validator | 校验字段值是否可被 multipleOf 的值整除 |
| maximum | Number | validator | 校验最大值(大于) |
| exclusiveMaximum | Number | validator | 校验最大值(大于等于 |
| minimum | Number | validator | 校验最小值(小于) |
| exclusiveMinimum | Number | validator | 最小值(小于等于) |
| maxLength | Number | validator | 校验最大长度 |
| minLength | Number | validator | 校验最小长度 |
| pattern | RegExpString | validator | 正则校验规则 |
| maxItems | Number | validator | 最大条目数 |
| minItems | Number | validator | 最小条目数 |
| uniqueItems | Boolean | validator | 是否校验重复 |
| maxProperties | Number | validator | 最大属性数量 |
| minProperties | Number | validator | 最小属性数量 |
| required | Boolean | validator | 必填 |
| format | ValidatorFormats(opens new window) | validator | 正则校验格式 |
| properties | SchemaProperties | - | 属性描述 |
| items | SchemaItems | - | 数组描述 |
| additionalItems | Schema | - | 额外数组元素描述 |
| patternProperties | SchemaProperties | - | 动态匹配对象的某个属性的 Schema |
| additionalProperties | Schema | - | 匹配对象额外属性的 Schema |
| x-index | Number | - | UI 展示顺序 |
| x-pattern | FieldPatternTypes(opens new window) | pattern | UI 交互模式 |
| x-display | FieldDisplayTypes(opens new window) | display | UI 展示 |
| x-validator | FieldValidator(opens new window) | validator | 字段校验器 |
| x-decorator | String | React.FC | decorator | 字段 UI 包装器组件 |
| x-decorator-props | Any | decorator | 字段 UI 包装器组件属性 |
| x-component | String | React.FC | component | 字段 UI 组件 |
| x-component-props | Any | component | 字段 UI 组件属性 |
| x-reactions | SchemaReactions | reactions | 字段联动协议 |
| x-content | React.ReactNode | ReactChildren | 字段内容,用来传入某个组件的子节点 |
| x-visible | Boolean | visible | 字段显示隐藏 |
| x-hidden | Boolean | hidden | 字段 UI 隐藏(保留数据) |
| x-disabled | Boolean | disabled | 字段禁用 |
| x-editable | Boolean | editable | 字段可编辑 |
| x-read-only | Boolean | readOnly | 字段只读 |
| x-read-pretty | Boolean | readPretty | 字段阅读态 |
| definitions | SchemaProperties | - | Schema 预定义 |
| $ref | String | - | 从 Schema 预定义中读取 Schema 并合并至当前 Schema |
| x-data | Object | data | 扩展属性 |
组件事件
| 事件名 | 参数 | 描述 |
|---|---|---|
| enter | params(可选) | 按下回车键或点击提交按钮时触发的事件。 |
组件插槽
| 插槽名 | 描述 |
|---|---|
| 默认插槽 | 用于自定义表单内容,开发者可以在插槽中添加自定义的表单字段。 |
详细设计
**schema-form** 组件
- 模板部分
使用 Form 组件渲染表单,包含一个隐藏的提交按钮,用于支持回车键提交表单。
<template>
<div ref="schemaFormRef" :class="[n()]">
<Form v-bind="formProps" :form="form" @auto-submit="onSubmit">
<slot />
<!-- enter 键 提交必须的条件, css隐藏 -->
<FormButtonGroup align-form-item class="schema-form-box-btn">
<Submit> </Submit>
</FormButtonGroup>
</Form>
</div>
</template>- 逻辑部分
在 onMounted 钩子中,如果 needFocus 为 true,则自动聚焦到第一个输入框。
<script lang="ts" setup>
import { onMounted, ref,nextTick } from 'vue'
import { createNamespace } from '@eco-library/utils'
import { Form, FormButtonGroup, Submit } from '@formily/element-plus'
import { SchemaFormProps } from './schema-form'
interface EmitEvent {
(e: 'enter', params?: any): void
}
const emit = defineEmits<EmitEvent>()
defineOptions({
name: 'XSchemaForm'
})
const schemaFormRef = ref()
const props = defineProps(SchemaFormProps)
function onSubmit() {
emit('enter')
}
const { n, classes } = createNamespace('schema-form')
onMounted(() => {
if (props.needFocus) {
// 页面渲染完,寻找第一个输入框将其置为 focus
nextTick(() => {
schemaFormRef.value?.querySelector('input').focus()
})
}
})
</script>- 样式部分
定义了表单的样式,包括隐藏提交按钮、表单布局和输入框样式等。
.x-schema-form {
.schema-form-box-btn {
height: 0;
width: 0;
position: absolute;
z-index: -9999;
opacity: 0;
left: -9999px;
bottom: -9999px;
}
&.filter-form {
box-sizing: border-box;
border-radius: 8px 8px 0 0;
padding: 24px 24px 12px;
width: 100%;
background-color: #ffffff;
}
.formily-element-plus-form-item-label {
font-weight: 500;
label {
color: #222222;
}
}
.formily-element-plus-form-inline {
.formily-element-plus-form-item {
margin-right: 12px;
.el-select-v2,
.el-select,
.el-cascader,
.sc-input-v2 {
width: v-bind('props.iptWidth'); // 绑定输入值
.el-select__tags-text {
max-width: 80px !important; // select 多选情况下 tag的最大长度,保证select样式正常显示
}
}
.el-date-editor--daterange {
width: 280px; // 日期区间选择框,最小宽度
}
.el-date-editor--datetimerange {
width: 360px; // 日期带时间区间选择框,最小宽度
}
}
}
.formily-element-plus-form-item-error-help {
font-size: 12px;
color: #f14846;
}
}- Props定义
export const SchemaFormProps = {
/* 表单布局:表单的配置属性,包括包装器是否换行、标签是否换行和布局方式等 */
formProps: {
type: Object,
default: () => ({
wrapperWrap: false,
labelWrap: true,
layout: "vertical"
})
},
/* 表单实例,能够通过它对表单进行一系列操作 */
form: {
type: Object,
default: () => ({})
},
/* 是否需要进行首次 focus */
needFocus: {
type: Boolean,
default: false
},
/* inline 状态下的表单长度*/
iptWidth: {
type: String,
default: "240px"
}
};**useSchema** 函数
useSchema函数用于管理表单组件库和自定义组件,提供组件状态管理和自定义组件注册的功能。
- 组件状态管理
在 useSchema.ts 文件中,使用 reactive 创建了两个响应式对象:
publicComponentStore:用于存储公共组件,全局可用。
customComponentStore:用于存储应用内部组件,相同命名的内部组件会覆盖公共组件。
// 组件状态管理
const publicComponentStore = reactive<SchemaVueComponents>({
// 布局组件
FormItem,
FormLayout,
FormGrid,
Space,
// 表单组件,样式不易变
Radio,
Checkbox,
Switch,
// 预览组件
PreviewText
})
export enum FormilyComponentType {
PUBLIC, // 公共库
CUSTOM, // 私有库
}
// formily UI桥阶层与 UI组件库绑定,并抛出 Schmea 实例
export function useSchema() {
// 应用内部组件,不污染全局,相同命名的,内部组件会覆盖公共组件
const customComponentStore = reactive<SchemaVueComponents>({})
// 桥阶层与UI组件库融合形成新组件供应用使用
const FormilySchema = computed(() => {
return createSchemaField({
components: { ...publicComponentStore, ...customComponentStore }
})
})
/**
* 注册自定义组件到组件库
* @param name
* @param component
* @param type
*/
function setFormilyComponent(
name: string,
component: VueComponent,
type: FormilyComponentType = FormilyComponentType.PUBLIC
) {
switch (type) {
case FormilyComponentType.PUBLIC:
publicComponentStore[name] = component
break
case FormilyComponentType.CUSTOM:
default:
customComponentStore[name] = component
}
}
return {
FormilySchema,
setFormilyComponent
}
}- 自定义组件注册
setFormilyComponent 函数用于注册自定义组件,支持将组件注册为公共组件或应用内部组件。
- 组件融合
FormilySchema 是一个计算属性,将 publicComponentStore 和 customComponentStore 中的组件合并,生成新的 SchemaField 实例供应用使用。
入口文件
export * from './src/schema-form'
export * from './src/useSchema'
export * from '@formily/vue'
export { onFieldValueChange } from '@formily/core'
export * from '@formily/reactive-vue'
export * from '@formily/shared'
import { withInstall } from '@eco-library/utils'
import SchemaForm from './src/schema-form.vue'
export const XSchemaForm = withInstall(SchemaForm)
export default XSchemaForm该文件作为组件的入口,导出了
schema-form和useSchema相关的内容,并将SchemaForm组件进行了安装处理,方便在项目中使用。
自定义组件
1. 实现业务自定义组件主要是使用@formily/react 或@formily/vue中的 Hooks API 与 observer API
2. 接入现成组件库的话,我们主要使用 connect/mapProps/mapReadPretty API
3. 如果想要实现一些更复杂的自定义组件,我们强烈推荐直接看@formily/antd或 @formily/next的源码
**核心概念与原理:**Formily 通过 连接器(Connect) 和 属性映射(mapProps) 机制,将普通组件转换为表单组件:
连接器(Connect):将组件与 Formily 表单系统连接
属性映射(mapProps):将 Formily 标准属性(如
value)映射到组件实际属性(如modelValue)预览态(mapReadPretty):定义组件在只读模式下的显示方式
自定义表单组件
- 创建基础组件
首先需要创建一个普通的 Vue组件,例如 ScInputView.vue
<template>
<el-input v-model="value" v-bind="$attr" />
</template>
<script lang="ts" setup>
import { defineModel } from 'vue'
// 通过 defineModel 实现双向绑定
const value = defineModel<string | undefined>()
// 提供 更新函数
defineEmits(['update:modelValue'])
</script>- 使用
**connect**函数封装组件
创建 view.tsx 文件,将基础组件转换为 Formily 表单组件:
import ScInputView from './ScInputView.vue'
import { connect, mapProps, mapReadPretty } from '@eco-library/core'
import { PreviewText } from '../model/preview'
export const FormilyScInput = connect(
ScInputView, // 基础组件
mapProps(
{ value: 'modelValue' }, // 将 Formily 的 value 映射到组件的 modelValue
(props: any) => {
// 额外属性转换:withCount 转换为 showWordLimit
const { withCount } = props
return {
...props,
showWordLimit: withCount,
}
}
),
mapReadPretty(PreviewText.Input) // 预览态组件
)- 导出组件
创建 index.ts 文件,统一导出组件:
export { FormilyScInput } from './view'- 注册组件
在应用中注册自定义组件:
import { useSchema } from '@eco-library/core'
import { FormilyScInput } from './components/ScInput'
const { setFormilyComponent } = useSchema()
// 默认存储到公共组件库(FormilyComponentType.PUBLIC)中, 可在函数第三个参数中传入 FormilyComponentType.CUSTOM 存储到私有库中
setFormilyComponent('FormilyScInput', FormilyScInput)- 在 Schema 中使用
{
"type": "object",
"properties": {
"username": {
"type": "string",
"title": "用户名",
"x-decorator": "FormItem", // 容器组件
"x-component": "FormilyScInput", // 表单组件
"x-component-props": {
"withCount": true, // 会被映射为 showWordLimit
// ...
// 等同于 el-input 的属性
"maxlength": 20
}
}
}
}自定义容器组件
容器组件是表单系统中的高级组件,用于组织和布局多个表单字段组件。与普通表单组件的主要区别在于:
递归渲染子组件:通过
RecursionField组件渲染所有子 Schema子组件管理:使用
useFieldSchema获取和处理子组件布局控制:定义内部表单组件的排版规则和样式
- 创建基础容器组件
<template>
<section class="form-container">
<div class="container-header">
<h3>{{ title }}</h3>
<p v-if="description">{{ description }}</p>
</div>
<div class="container-body">
<!-- 子组件将在这里渲染 -->
</div>
</section>
</template>
<script lang="ts" setup>
defineProps({
title: String,
description: String,
})
</script>- 集成 Formily 容器逻辑
<template>
<section class="form-container">
<div class="container-header">
<h3>{{ title }}</h3>
<p v-if="description">{{ description }}</p>
</div>
<div class="container-body">
<!-- 递归渲染所有子组件 -->
<RecursionField
v-for="item in childrenSchema"
:key="item.id"
:schema="item.schema"
:name="item.id"
/>
</div>
</section>
</template>
<script lang="ts" setup>
import { useFieldSchema, RecursionField } from '@eco-library/core'
import { reactive } from 'vue'
const props = defineProps({
title: String,
description: String,
})
// 获取子组件 Schema
const schema = useFieldSchema()
const childrenSchema = reactive<any[]>([])
// 处理子组件 Schema
schema.value.mapProperties((schema, name) => {
childrenSchema.push({
id: name,
schema,
})
})
</script>- 实现子组件分类和布局
如果需要区分不同类型的子组件(如操作按钮和普通字段),可以添加分类逻辑:
const useChildrenSchema = () => {
const schema = useFieldSchema()
const childrenSchema = []
const actionSchema = []
schema.value.mapProperties((schema, name) => {
if (props.isActionRender?.includes(name)) {
// 分类为操作按钮
actionSchema.push({ id: name, schema })
} else {
// 分类为普通字段
childrenSchema.push({ id: name, schema })
}
})
return {
childrenSchema,
actionSchema,
}
}
const { childrenSchema, actionSchema } = useChildrenSchema()- 处理表单状态与交互
添加对表单状态的响应逻辑,如根据表单编辑状态显示 / 隐藏容器:
import { useForm } from '@eco-library/core'
const form = useForm()
const containerVisible = ref(true)
// 监听表单状态变化
form.value.subscribe(({ type }) => {
if (type === 'onFieldValueChange' && props.controlKey) {
const fieldValue = form.value.getFieldValue(props.controlKey)
containerVisible.value = !!fieldValue
}
})- 样式设计与布局
根据需求设计容器的样式和内部组件的排版规则:
css样式
布局组件
使用示例
- 安装插件
# 首先查看 npm 源是不是在 http://192.168.0.248:4873/ (内网环境)
# 不是的话需要切换源
pnpm install @eco-library/core --registry=http://192.168.0.248:4873/
#或者在 .npmrc 中添加
# @eco-library/core="http://192.168.0.248:4873/"- 页面使用
<template>
<x-schema-form :form="editForm" @enter="handleConfirm">
<component :is="FormilySchema.SchemaField" :schema="formSchema" />
</x-schema-form>
</template>
<script lang="ts" steup>
import { reactive } from 'vue'
import { XSchemaForm, createForm, ISchema, useSchema } from '@eco-library/core';
// 创建表单实例
const editForm = createForm()
// 引入UI桥接后的 formily 实例组件 (SchemaField 是使用 jsonSchema 模式构建表单)
const { FormilySchema } = useSchema
const formSchema = reactive<ISchema>({
type: 'object',
properties: {
// ... 配置表单项或布局容器
}
})
function handleConfirm() {
// 表单 enter 提交
}
</script>项目实践
设备调测(乐充运维平台)
- UI



- 测试报告
附件一:移动运维专项测试报告-批准.pdf
设备静态属性配置(乐充运维平台)
- UI



- 测试报告
附件二:新增录入设备基础静态属性测试报告-批准.pdf
注意事项
注册自定义组件时,确保组件名称的唯一性,避免与现有组件名称冲突。
相同命名的自定义组件会覆盖公共组件,因此在使用时需要注意组件的优先级。
FormilySchema是一个计算属性,会根据publicComponentStore和customComponentStore的变化自动更新。
总结
XSchemaForm 组件通过结合 Formily 库,提供了强大的表单开发能力。schema-form 组件实现了基本的表单功能,useSchema 函数则提供了组件状态管理和自定义组件注册的功能,使得表单开发更加灵活和可扩展。
