15 KiB
低码移动端组件开发指南
概述
本文档介绍低码平台的移动端组件开发指南。移动端组件基于 Vue 进行开发,以 H5 形式运行,组件库采用 Vant。
阅读前须知: 在阅读本文档前,请先阅读《字段类型与物料组件开发指南.md》。移动端开发与浏览器端在整体设计、开发流程上保持一致,主要差异在于底层规划、文件目录、运行和初始化等差异。对于组件开发者,只需了按移动端的目录标准开发组件、配置组件即可。
本文档主要介绍移动端开发中的差异部分。关于功能开发的详细说明、开发标准与规范,请参考《字段类型与物料组件开发指南.md》。
目录结构
移动端组件的相关配置文件位于 packages/widget-mobile/src/config 目录下,主要包含以下文件:
packages/widget-mobile/src/config/
├── index.ts # 配置入口文件,注册移动端设计器和预览器配置
├── Material.ts # 物料面板配置,定义组件在物料面板中的分类和展示
├── RegisterWidget.ts # 组件注册配置,注册移动端组件的实现
├── RegisterWidgetHelper.ts # 组件辅助工具注册配置
├── RegisterSettings.ts # 组件设置配置
├── WidgetType.ts # 组件类型枚举定义
├── widget/ # 组件实现目录
│ ├── Root.vue # 页面根容器组件
│ └── TestWidget.vue # 测试组件示例
└── widget-helper/ # 组件辅助工具目录
└── TestWidgetHelper.ts # 测试组件辅助工具示例
主要文件说明
- index.ts: 配置入口,提供
setMobileConfigAll()函数,用于注册设计器和预览器的配置。 - Material.ts: 定义组件在物料面板中的分类组织,通过
getMaterialPanelConfig()返回物料配置数组。 - RegisterWidget.ts: 注册移动端组件的实现,将组件类型与对应的 Vue 组件进行映射。
- WidgetType.ts: 定义所有移动端组件的类型枚举,需要与后端定义的控件类型保持一致。
开发流程
添加一个新的移动端物料组件,需要完成以下 6 个步骤的配置。下面以 TestWidget 组件为例,详细说明每个步骤的操作。
步骤 1:添加组件类型枚举
在 WidgetType.ts 文件中添加组件类型枚举,枚举值需要与后端定义的控件类型保持一致。
export enum WidgetType {
// 测试组件
TestWidget = "TEST_WIDGET",
// 你的新组件
// YourWidget = 'YOUR_WIDGET',
}
步骤 2:创建 Vue 组件
在 widget/ 目录下创建组件的 Vue 文件,例如 YourWidget.vue。
<script setup lang="ts">
import { Rate } from "vant";
import { ref } from "vue";
const value = ref(3);
</script>
<template>
<div class="your-widget-container">
<!-- 组件内容 -->
<Rate v-model="value" />
</div>
</template>
<style lang="css" scoped>
.your-widget-container {
/* 组件样式 */
}
</style>
注意事项:
- 组件使用 Vant 作为 UI 组件库
- 组件应支持响应式设计,适配移动端屏幕
步骤 3:创建 WidgetHelper 配置
在 widget-helper/ 目录下创建 Helper 配置文件,例如 YourWidgetHelper.ts。
Helper 文件需要实现 WidgetHelper 接口,主要包含以下方法:
buildMaterialConfig(): 定义组件在物料面板中的展示信息(图标、名称等)buildSettings(): 定义组件的设置面板配置(属性、样式、事件等)buildWidget(): 定义组件的默认 Schema 结构buildSchemaController(): 定义组件的 Schema 控制器方法
参考示例:
import type { WidgetHelper } from "~@/types/WidgetHelper";
import { WidgetType } from "../WidgetType";
const config: WidgetHelper = {
type: WidgetType.TestWidget,
buildMaterialConfig() {
// 返回物料配置
},
buildSettings(params) {
// 返回设置面板配置
},
buildWidget(params) {
// 返回组件默认 Schema
},
buildSchemaController() {
// 返回 Schema 控制器方法
},
// 其他可选方法...
};
export default config;
详细说明请参考: widget-helper/TestWidgetHelper.ts 文件的完整实现。
步骤 4:注册组件
在 RegisterWidget.ts 文件的 SYS_COMPS_MAP 对象中添加组件映射。
import TestWidget from "./widget/TestWidget.vue";
import { WidgetType } from "./WidgetType";
export const SYS_COMPS_MAP: Recordable<RegisterFormComponentOptions> = {
[WidgetType.TestWidget]: {
instance: TestWidget,
},
// 你的新组件
// [WidgetType.YourWidget]: {
// instance: YourWidget,
// },
};
步骤 5:注册 Helper
在 RegisterWidgetHelper.ts 文件的 SYS_MOBILE_WIDGET_HELPERS 数组中添加 Helper 的导入和注册。
import TestWidgetHelper from "./widget-helper/TestWidgetHelper";
export const SYS_MOBILE_WIDGET_HELPERS = [
TestWidgetHelper,
// 你的新组件
// YourWidgetHelper,
];
步骤 6:添加到物料面板
在 Material.ts 文件的 getMaterialPanelConfig() 函数中添加组件到物料面板配置。
import { MaterialCollapseTypeEnum } from "~@/types/MaterialPanelType";
import { WidgetType } from "./WidgetType";
export function getMaterialPanelConfig() {
return [
{
name: MaterialCollapseTypeEnum.Common, // 通用组件分类
widgetTypeList: [
WidgetType.TestWidget,
// WidgetType.YourWidget, // 你的新组件
],
},
];
}
分类说明:
MaterialCollapseTypeEnum.Common: 通用组件- 其他分类可根据组件特性选择合适的分类
完整开发清单
开发新组件时,请按以下清单逐项完成:
- 步骤 1:在
WidgetType.ts中添加枚举 - 步骤 2:创建
widget/YourWidget.vue组件文件 - 步骤 3:创建
widget-helper/YourWidgetHelper.tsHelper 文件 - 步骤 4:在
RegisterWidget.ts中注册组件 - 步骤 5:在
RegisterWidgetHelper.ts中注册 Helper - 步骤 6:在
Material.ts中添加到物料面板配置
参考示例
完整示例可参考 TestWidget 组件:
- 组件实现:
widget/TestWidget.vue - Helper 配置:
widget-helper/TestWidgetHelper.ts - 类型定义:
WidgetType.TestWidget - 组件注册:
RegisterWidget.ts中的SYS_COMPS_MAP[WidgetType.TestWidget] - Helper 注册:
RegisterWidgetHelper.ts中的SYS_MOBILE_WIDGET_HELPERS - 物料配置:
Material.ts中的物料面板配置
BaseField / Field 三种使用方式说明
移动端字段容器在实际业务中有三种典型用法,本节重点说明 BaseField 与 Vant Field 的配置与使用模式。
整体上可以分为三种模式:
- 模式一:基础模式(只用 BaseField,完全由 Schema 驱动)
- 模式二:插槽扩展模式(在 BaseField 上增加自定义插槽)
- 模式三:完全自定义模式(基于 useField + Vant Field 自行开发)
下面分别说明三种模式的使用方式与适用场景。
模式一:基础模式(只用 BaseField,零插槽开发)
示例代码:
<script setup lang="ts">
import type { WidgetProps, WidgetSchema } from "~@/types/WidgetSchema";
import { computed, toRefs, useAttrs } from "vue";
import { usInitValueHook } from "~@/config/widget/utils/UseInitValueHook";
import { useCompEvent } from "~@/config/user-script/hooks/UseComp";
import BaseField from "../components/BaseField.vue";
import { useField } from "./hook/useField";
const props = defineProps<
WidgetProps<WidgetSchema<any>> & {
placeholder?: string;
}
>();
const { schema, fieldPath, placeholder } = toRefs(props);
const attrs = useAttrs();
const { fieldProps, wrapperStyle, fieldSlots } = useField({
schema,
attrs,
placeholder,
});
type ModelValue = string | undefined;
const value = defineModel<ModelValue>();
usInitValueHook(value, { schema, fieldPath });
const { handleBlur, handleFocus, handleValueChange } = useCompEvent(
schema as any,
);
function setValue(nv: ModelValue) {
value.value = nv;
handleValueChange(nv);
}
</script>
<template>
<BaseField
v-bind="fieldProps"
:style="wrapperStyle"
:field-slots="fieldSlots"
:model-value="value"
@update:model-value="setValue"
@focus="handleFocus"
@blur="handleBlur"
/>
</template>
- 使用方式说明:
- 模板中直接使用
BaseField,传入由useField计算出的fieldProps/wrapperStyle/fieldSlots; - 通过
v-model和事件处理器完成双向绑定和事件派发; fieldProps/wrapperStyle/fieldSlots均由useField(schema, attrs, placeholder)等 Hook 计算而来。
- 模板中直接使用
- 字段配置来源:
- 标题、占位符、字数限制、校验规则、必填标记等,全部从 Schema(低码配置)中读取;
- 开发者只需要通过 Helper 的
buildSettings/buildWidget暴露字段配置即可。
- 适合场景:
- 只想快速用一个“通用输入组件”,只需要提供标准样式;
总结: 模式一中,BaseField 被看作“通用字段容器”,开发者只需要准备好 fieldProps / fieldSlots,绝大多数展示和行为由平台内置。
模式二:插槽扩展模式(BaseField + 自定义插槽)
示例代码:
<script setup lang="ts">
// ... script 部分与模式一相同,这里省略
</script>
<template>
<BaseField
v-bind="fieldProps"
:style="wrapperStyle"
:field-slots="fieldSlots"
:model-value="value"
@update:model-value="setValue"
@focus="handleFocus"
@blur="handleBlur"
>
<!-- 自定义标签区域 -->
<template #label>
<div class="custom-label">
<span>自定义标签</span>
<i class="help-icon" />
</div>
</template>
<!-- 自定义左侧图标 -->
<template #left-icon>
<van-icon name="search" />
</template>
<!-- 自定义右侧图标 -->
<template #right-icon>
<van-icon name="clear" @click="handleClear" />
</template>
<!-- 自定义输入区域 -->
<template #input>
<input
:value="value"
@input="setValue(($event.target as HTMLInputElement).value)"
class="custom-input"
/>
</template>
</BaseField>
</template>
- 使用方式说明:
- 继续使用
BaseField作为外层容器,并保留基础属性和事件绑定; - 同时在
<BaseField>内部编写插槽:#label:自定义标签区域(可加入图标、说明、换行布局等);#left-icon/#right-icon:替换为自定义图标/按钮(如搜索图标、清空按钮等);#input:替换内部输入区域(可以是<input>,也可以是更复杂的组合,如“区号 + 手机号”)。
- 继续使用
- 与基础模式的关系:
- 仍然完全复用 BaseField 的:
- 表单布局(label 区、内容区、错误提示等);
- 事件派发(focus / blur / valueChange);
- Schema 驱动的属性(
fieldProps/fieldSlots)。
- 只是把“局部渲染区域”交给业务组件来自定义。
- 仍然完全复用 BaseField 的:
- 适合人群/场景:
- 需要在标签上插入说明/图标,或改变标签的布局;
- 需要在左右两侧放置单位、按钮、icon 等;
- 需要对输入 DOM 做轻量改造,但又不希望重写整个
Field逻辑。
总结: 模式二是在 “不放弃 BaseField 能力” 的前提下,通过插槽对部分区域进行增强,兼顾 低码配置 与 少量编码定制。
模式三:完全自定义模式(useField + Vant Field)
示例代码:
<script setup lang="ts">
import type { WidgetProps, WidgetSchema } from "~@/types/WidgetSchema";
import { Field } from "vant-cisdi";
import { toRefs, useAttrs } from "vue";
import { usInitValueHook } from "~@/config/widget/utils/UseInitValueHook";
import { useCompEvent } from "~@/config/user-script/hooks/UseComp";
import { useField } from "./hook/useField";
const props = defineProps<
WidgetProps<WidgetSchema<any>> & {
placeholder?: string;
}
>();
const { schema, fieldPath, placeholder } = toRefs(props);
const attrs = useAttrs();
const { fieldProps, wrapperStyle, fieldSlots } = useField({
schema,
attrs,
placeholder,
});
type ModelValue = string | undefined;
const value = defineModel<ModelValue>();
usInitValueHook(value, { schema, fieldPath });
const { handleBlur, handleFocus, handleValueChange } = useCompEvent(
schema as any,
);
</script>
<template>
<Field
v-model="value"
v-bind="fieldProps"
:style="wrapperStyle"
@update:model-value="handleValueChange"
@focus="handleFocus"
@blur="handleBlur"
>
<!-- 根据 fieldSlots 数据动态渲染标签 -->
<template v-if="fieldSlots.hasLabelSlot" #label>
<div
v-if="fieldSlots.labelData?.value?.showLabel"
class="van-field__label-wrapper"
>
<label :style="{ width: fieldSlots.labelData?.value?.labelWidth }">
<span :style="fieldSlots.labelData?.value?.customLabelStyle">
自定义 + {{ fieldSlots.labelData?.value?.label }}
</span>
<i
v-if="fieldSlots.labelData?.value?.required"
class="van-badge__wrapper van-icon__wrap mbicon mbicon-Required van-field__required-mark"
/>
</label>
</div>
</template>
<!-- 根据 fieldSlots 数据动态渲染左侧图标 -->
<template v-if="fieldSlots.hasPrepend" #left-icon>
{{ fieldSlots.prependContent }}
</template>
<!-- 根据 fieldSlots 数据动态渲染右侧图标 -->
<template v-if="fieldSlots.hasAppend" #right-icon>
{{ fieldSlots.appendContent }}
</template>
</Field>
</template>
- 使用方式说明:
- 不再使用
BaseField,直接使用 Vant 的Field组件:v-model="value":字段双向绑定;v-bind="fieldProps":透传由useField计算出的属性(如 placeholder、校验相关 props 等);:style="wrapperStyle":应用统一样式;@update:model-value="handleValueChange"、@focus="handleFocus"、@blur="handleBlur":自行接入事件。
- 使用
fieldSlots提供的数据决定是否渲染 label / icon 等:fieldSlots.hasLabelSlot+fieldSlots.labelData?.value控制标签显隐、宽度、样式、必填星号等;fieldSlots.hasPrepend/fieldSlots.hasAppend+ 内容字段控制左右自定义区域。
- 不再使用
- 与前两种模式的区别:
- 布局与 DOM 结构完全由业务组件掌控;
- 只复用数据与配置层能力(
fieldProps/fieldSlots/ 事件 Hook),不复用 BaseField 的 UI 结构。
- 适合人群/场景:
- 对 DOM 结构、动画、样式有强定制需求;
- 由前端开发者主导开发,低码配置主要提供字段元数据与校验规则;
总结: 模式三是 “只拿 Schema & Hook,不拿默认 UI” 的高级用法,保留低码平台的数据/事件体系,同时对视觉与交互做彻底自定义。
建议实践:
- 通用表单场景优先使用 模式一(基础模式);
- 需要少量 UI 扩展时优先考虑 模式二(插槽扩展模式);
- 仅在前两种模式无法满足需求时,再采用 模式三(完全自定义模式),以控制维护成本。