《Vue项目开发实战》第五章:组件封装--Form
系列文章
《Vue项目开发实战》
https://blog.csdn.net/sen_shan/category_13075264.html
第四章:组件封装--ToolBar
https://blog.csdn.net/sen_shan/article/details/154640827
文章目录
前言
文章详细讲解了DynamicForm通用表单组件的技术方案,包括字段描述接口定义、布局逻辑、内置校验规则、日期格式化处理等功能特性。该组件支持通过字段描述数组实现多类型表单渲染,自动处理双向绑定和数据校验,可无缝集成到弹窗、抽屉等场景。文章还提供了组件使用范例和样式主题定制方法,展示了如何通过expose暴露validate/resetFields等表单操作方法。组件采用TypeScript开发,实现数据驱动表单生成能力,提升开发效率。
Form组件
src\components\ActionFormCont.vue 基于原逻辑微调,细节略。
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import router from './router';
import App from './App.vue'
import { createPinia } from 'pinia'
//import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
import { createPersistedState } from 'pinia-plugin-persistedstate'
// 引入 Mock 数据
//import './mock' //暂时关闭
import { ReloadData,useAllDataStore } from '@/stores';
import { i18n } from '@/locales';
import defaultConfig from '@config/index.js'; // 相对路径
import '@/assets/styles/index.css';
import '@unocss/reset/tailwind.css' // 可选重置
import 'uno.css'
const mockEnabled = defaultConfig.mockEnabled;if (mockEnabled) {import('./mock').then(() => {console.log('Mock 已加载');});
}// 创建 Pinia 实例
const pinia = createPinia();const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {app.component(key, component)}// app.use(router);// 使用插件// pinia.use(piniaPluginPersistedstate);pinia.use(createPersistedState({key: id => `__persisted__${id}`,storage: sessionStorage,}))app.use(pinia);
// 国际化// 设置 i18n 的 locale
let currentLanguage = navigator.language.replace(/-(\S*)/, '');
// 如果本地缓存记录了语言环境,则使用本地缓存
const Store = useAllDataStore();
let lsLocale = Store.getLocale() || '';
console.log('lsLocale', lsLocale);
if (lsLocale) {try {const parsedLocale = JSON.parse(lsLocale);currentLanguage = parsedLocale?.curLocale || 'zh'; // 确保有默认值} catch (error) {// console.error('Failed to parse lsLocale:', error);currentLanguage = lsLocale; // 如果解析失败,直接使用 lsLocale}
}// 校验语言是否合法,仅允许 "zh" 或 "en"
const allowedLocales: ("zh" | "en")[] = ["zh", "en"];
currentLanguage = allowedLocales.includes(currentLanguage as "zh" | "en")? currentLanguage: "zh"; // 如果非法,默认设置为 "zh"// 设置 i18n 的 locale
i18n.global.locale.value = currentLanguage as "zh" | "en";ReloadData();app.use(router);app.use(ElementPlus, {locale: zhCn,})app.use(i18n);app.mount('#app')
技术文档:DynamicForm 组件(基于 Element Plus)
版本:v1.3
文件路径: src/components/DynamicForm.vue
一、组件定位
通用数据驱动表单引擎,零代码扩展:
仅需一份“字段描述数组”即可渲染出多行、多列、多类型的表单;
自动双向绑定、校验、分组布局;
支持只读、隐藏、行列合并、日期格式化等常用能力;
通过 expose 向外暴露 validate / resetFields ,可无缝嵌入抽屉、弹窗、步骤条等场景。
二、接口一览(props & events)

事件
update:data (隐式)—— 内部 formData 变化时实时向外抛出,可直接 v-model:data 使用。
三、Field 字段描述子项
interface Field {field: string; // 对应 data 的 keylabel: string; // 标签文本input_type: 'text'|'select'|'date'|'number'|'switch'|'textarea';row_id?: number; // 分组行号,默认 1col_span?: number; // 列宽,默认 24(即一行一列)hide?: boolean; // true 时不渲染read_only?: boolean; // 是否禁用is_null?: boolean; // 是否允许为空(false=必填)Validation?: any; // Element 规则对象或自定义校验函数options?: {label:string; value:any; disabled?:boolean}[];rows_num?: number; // textarea 行高,默认 2
}
四、布局逻辑
1. 先过滤 hide=true 的字段;
2. 按 row_id 归并到同一 <el-row> ;
3. 每行内部按 col_span 做响应式栅格;
4. 未指定 row_id 的字段默认归入第 1 行。
五、内置校验
当 is_null=false (即必填)时,组件自动追加规则
{required:true, message:'${label}不能为空', trigger:'blur'}
若提供 Validation ,则追加到同字段规则数组,支持异步、自定义校验。
六、日期格式化
el-date-picker 选择后自动格式化为 YYYY-MM-DD 字符串,回写到 formData[field] 。
七、样式主题
通过 size="large" 统一大字号;
提供 .big-form 作用域类名(已注释在 <style> ),可在父级加 class="big-form" 一键放大字体与行高;
popper-class="bg-amber-50/70 rounded-md" 给下拉面板加浅色圆角背景,保持视觉统一。
八、使用范例【详见后章节】
<template><!-- 弹窗内直接使用 --><DynamicFormv-model:data="entity":fields="fields"ref="formRef"/><el-button type="primary" @click="submit">保存</el-button>
</template><script setup>
import { ref } from 'vue'
const entity = ref({name: '',gender: '',birthday: ''
})const fields = [{ field:'name', label:'姓名', input_type:'text', row_id:1, col_span:12 },{ field:'gender', label:'性别', input_type:'select', row_id:1, col_span:12,options:[{label:'男',value:1},{label:'女',value:0}] },{ field:'birthday', label:'生日', input_type:'date', row_id:2 }
]const formRef = ref()
async function submit(){try {await formRef.value.validate()// 调接口保存} catch {}
}
</script>
九、暴露方法(已注释,可按需打开)
defineExpose({
resetFields: () => formRef.value?.resetFields(),
validate: () => formRef.value?.validate()
})
