组件库背景:使用elementplus+vue封装了一个通过表单组件。通过JSX对el-form下的el-input和el-button等表单进行统一封装,最后达到,通过数据即可一键生成页面表单的功能。
npm create vite@latest elementplus-auto-form -- --template vue
注意此处main.js入口文件只是当前项目入口文件,组件库打包的入口文件还是封装Form表单组件下的index.js
表单+表单校验+JSX生成表单项
TForm.vue:
- <template>
- <el-form ref="FormRef"
- :class="formBorder?'form-border':''"
- :model="modelForm"
- :rules="editable ? rules : {}"
- :inline="inline"
- :label-position="labelPosition"
- :label-width="labelWidth">
- <slot name="header">slot>
- <el-row :gutter="elRowGutter">
- <el-col v-for="(item,index) in data"
- :span="item.span" :key="index">
-
- <el-form-item v-if="!item.isHidden" :class="isCustom?'custom-form-item':''" :label="item.label ? item.label + ':' : ''"
- :prop="item.prop"
- :label-width="item.labelWidth">
- <FormItem :formData="modelForm"
- :editable="editable"
- :data="item">
- FormItem>
- <FormItem v-if="item.children" :formData="modelForm"
- :editable="editable"
- :clearable="false"
- :data="item.children">
- FormItem>
-
- el-form-item>
- el-col>
- <el-col class="button-list" v-if="btnList && btnList.length"
- :span="24">
- <el-form-item :class="isCustom?'custom-form-item':''">
- <div v-for="(item,index) in btnList" :key="index">
- <FormButton :formData="modelForm"
- :editable="editable"
- :data="item"
- @on-click="onClick(item)"
- >FormButton>
- div>
- el-form-item>
- el-col>
- <slot name="footer">slot>
- el-row>
- el-form>
- template>
-
- <script setup>
- import { ref } from 'vue'
- import formItem from './FormItem.jsx'
- import formButton from './FormButton.jsx'
-
- // 除data外其他都不是必传项
- const prop = defineProps({
- modelForm:{
- type: Object,
- require: true,
- },
- rules: {
- type: Object,
- default: {}
- },
- data: {
- type: Object,
- require: true,
- default: []
- },
- inline:{
- type: Boolean,
- default: true
- },
- labelWidth: {
- type: String,
- default: '120'
- },
- labelPosition: {
- type: String,
- default: 'right'
- },
- editable: {
- type: Boolean,
- default: true
- },
- colLayout: {
- type: Object,
- default(){
- return {
- xl: 5, //2K屏等
- lg: 8, //大屏幕,如大桌面显示器
- md: 12, //中等屏幕,如桌面显示器
- sm: 24, //小屏幕,如平板
- xs: 24 //超小屏,如手机
- }
- }
- },
- elRowGutter: {
- type: Number,
- default: 10
- },
- size: {
- type: String,
- default: 'default'
- },
- btnList:{
- type: Object,
- default: []
- },
- formBorder:{
- type: Boolean,
- default: false
- },
- formRef:{
- type: String,
- default: 'formRef'
- },
- customFormItem:{
- type: Boolean,
- default: false
- }
-
- })
-
- const FormItem = formItem();
- const FormButton = formButton();
-
- const FormRef = ref()
- const isCustom = ref(false);
-
- // 表单按钮
- function onClick(data) {
- if (!data.onClick) return
- data.onClick()
- }
-
- // 表单校验
- async function validate() {
- if (!FormRef.value) return
- const result = await FormRef.value.validate()
- return result;
- }
-
- // 清除表单验证
- async function resetFields() {
- if(!FormRef.value) return await FormRef.value.resetFields();
- return await FormRef.value.resetFields()
- }
-
- // 自定义el-form-item样式
- if(prop.customFormItem){
- isCustom.value = true;
- }
-
- defineExpose({
- validate,
- resetFields,
- })
-
- script>
- <style scoped>
- .button-list{
- display: flex;
- justify-content: center;
- }
-
- .form-border {
- width: 94%;
- border: solid 2px rgba(219, 217, 217, 0.6);
- border-radius: 10px;
- margin-left: auto;
- margin-right: auto;
- padding: 20px;
- }
-
- .custom-form-item {
- margin-bottom: 4px;
- margin-right: 12px;
- margin-left: 12px;
- }
- style>
-
FormItem.jsx:
- import {
- ElInput,
- ElSelect,
- ElOption,
- ElButton
- } from 'element-plus'
-
- import { defineComponent } from 'vue'
-
- // 普通显示
- const Span = (form, data) => (
- <span>{data}span>
- )
-
- // 输入框
- const Input = (form, data) => (
- <ElInput
- v-model={form[data.field]}
- type={data.type}
- input-style={data.inputStyle}
- size={data.size}
- autocomplete={data.autocomplete}
- show-password={data.type == 'password'}
- clearable
- placeholder={data.placeholder}
- autosize = {{
- minRows: 3,
- maxRows: 4,
- }}
- {...data.props}
- >
- ElInput>
- )
- // 文本框
- const Textarea = (form, data) => (
- <ElInput
- v-model={form[data.field]}
- type={data.type}
- input-style={data.inputStyle}
- size={data.size}
- // 设置rows就不能设置自适应autosize
- rows={data.rows}
- clearable={data.clearable}
- placeholder={data.placeholder}
- {...data.props}
- >{data.rows}
- ElInput>
- )
-
- const setLabelValue = (_item, { optionsKey } = {}) => {
- return {
- label: optionsKey ? _item[optionsKey.label] : _item.label,
- value: optionsKey ? _item[optionsKey.value] : _item.value,
- }
- }
- // 选择框
- const Select = (form, data) => (
- <ElSelect
- size={data.size}
- v-model={form[data.field]}
- filterable
- style={data.style}
- clearable={data.clearable}
- placeholder={data.placeholder}
- {...data.props}
- >
- {data.options.map((item) => {
- return <ElOption {...setLabelValue(item, data)} />
- })}
- ElSelect>
- )
-
- const Button = (form, data) =>{
- <ElButton
- type={data.type}
- size={data.size}
- icon={data.icon}
- plain={data.plain}
- click={data.clickBtn}
- value={data.value}
- >ElButton>
- }
-
- const setFormItem = (
- form,
- data,
- editable,
- ) => {
- if (!form) return null
- if (!editable) return Span(form, data)
-
- switch (data.type) {
- case 'input':
- return Input(form, data)
- case 'textarea':
- return Textarea(form, data)
- case 'password':
- return Input(form, data)
- // 输入框只能输入数字
- case 'number':
- return Input(form, data)
- case 'select':
- return Select(form, data)
- case 'date':
- case 'daterange':
- return Date(form, data)
- case 'time':
- return Time(form, data)
- case 'radio':
- return Radio(form, data)
- case 'checkbox':
- return Checkbox(form, data)
- case 'button':
- return Button(form, data)
- default:
- return null
- }
- }
-
- export default () =>
- defineComponent({
- props: {
- data: Object,
- formData: Object,
- editable: Boolean,
- },
- setup(props) {
- return () =>
- props.data
- ? setFormItem(props.formData, props.data, props.editable)
- : null
- },
- })
按需引入elementplus:
- // element-plus按需导入
- import AutoImport from 'unplugin-auto-import/vite'
- import Components from 'unplugin-vue-components/vite'
- import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
- import vueJsx from '@vitejs/plugin-vue-jsx'
- import path from 'path'
-
- ...
- plugins: [
- vue(),
- // 用到JSX语法
- vueJsx(),
- AutoImport({
- resolvers: [ElementPlusResolver()],
- }),
- Components({
- resolvers: [ElementPlusResolver()],
- }),
- ],
- resolve: {
- alias: {
- '@': path.resolve(__dirname, 'src')
- }
- },
- ...
通过install插件方式进行使用:
- import TForm from "./TForm.vue";
-
- export default {
- install (app) {
- // 在app上进行扩展,app提供 component directive 函数
- // 如果要挂载原型 app.config.globalProperties 方式
- // "TForm"自定义即可
- app.component("TForm", TForm);
- }
- }
设置打包文件名,包路径等
注意打包入口为index.js文件(需要使用导出install方法中的组件),而不是main.js文件(main.js中引入index.js只是用于本地测试)
- build: {
- outDir: "elementplus-auto-form", //输出文件名称
- lib: {
- entry: path.resolve(__dirname, "./src/package/index.js"), //指定组件编译入口文件
- name: "elementplus-auto-form",
- fileName: "elementplus-auto-form",
- }, //库编译模式配置
- rollupOptions: {
- // 确保外部化处理那些你不想打包进库的依赖
- external: ["vue"],
- output: {
- // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
- globals: {
- vue: "Vue",
- },
- },
- },
- },
npm run build进行打包
在package.json文件中对,包版本等信息进行配置
- {
- "name": "elementplus-auto-form",
- "version": "1.0.0",
- "description": "对elementplus的form表单进行封装,达到根据数据一键生成表单功能",
- "keywords": ["elementplus","el-form","auto-form"],
- "main": "elementplus-auto-form.js",
- "scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
- },
- "author": "xxx",
- "license": "ISC",
- "private": false
- }
将镜像切回到淘宝源:
npm config set registry https://registry.npm.taobao.org
查看当前镜像源:
npm config get registry
配置到淘宝镜像后,首先会到淘宝镜像中下载,没有则去npm官网进行下载
下载后node_modules下的包:
- //main.js
- import 'elementplus-auto-form/style.css'
- import TForm from "elementplus-auto-form";
-
- const app = createApp(App);
- app.use(router).use(TForm).mount('#app')
Form.vue页面使用:
- <script setup>
- import { reactive } from 'vue'
- import cronjobConfig from './cronjobConfig'
- const formItems = cronjobConfig.value.formItems ? cronjobConfig.value.formItems : {};
- const cronjobForm = reactive({
- iffLength: '1793',
- keySize: '',
- dataFileName: '',
- wfdName: '',
- version:''
- })
- script>
-
- <template>
- <t-form ref="cronjobFormRef" :btnList="cronjobConfig.buttons" :modelForm="cronjobForm" :formBorder="true"
- :rules="cronjobConfig.rules" :data="formItems">
- <template #header>
- <b>请输入转码程序生成条件:b><br /><br />
- template>
- t-form>
- template>
测试数据:
- import { DocumentDelete, Edit, Download } from '@element-plus/icons-vue'
- import { shallowRef ,ref } from 'vue'
-
- let checkNum = (rule, value, callback) => {
- // 函数用于检查其参数是否是非数字值,如果参数值为 NaN 或字符串、对象、undefined等非数字值则返回 true, 否则返回 false。
- if (isNaN(value)) {
- return callback("iffLength must be a number");
- }
- return callback();
- }
- let checkVersion = (rule, value, callback) => {
- let regex = /^V(\d{2})[A-L]$/;
- if (regex.test(value)) {
- callback();
- return true;
- } else {
- callback(new Error("Version must be similar to 'V23G'"));
- return false;
- }
- }
-
- const cronjobConfig = ref({
- rules: {
- iffLength: [
- { required: true, message: 'Please input iff length', trigger: 'blur' },
- { validator: checkNum, trigger: "blur" }
- ],
- keySize: [
- { required: true, message: 'Please select key size', trigger: 'change', }
- ],
- dataFileName: [{
- required: true,
- message: 'Please input data filename',
- trigger: 'blur',
- }],
- wfdName: [{
- required: true,
- message: 'Please input wfd name',
- trigger: 'blur',
- }],
- version: [{ required: true, message: 'Please input version', trigger: 'blur' },
- { validator: checkVersion, trigger: "blur" }
- ]
- },
- formItems: [{
- field: 'iffLength',
- prop: 'iffLength',
- label: 'iff length',
- placeholder: '1793',
- labelWidth: '150px',
- type: 'input',
- // size: 'small',
- span: 12,
- },
- {
- field: 'keySize',
- prop: 'keySize',
- type: 'select',
- label: 'key size',
- placeholder: 'select key size',
- // editable: true,
- // size: 'small',
- span: 12,
- options: [{ label: 6, value: 6 }, { label: 9, value: 9 }]
- },
- {
- field: 'dataFileName',
- prop: 'dataFileName',
- type: 'input',
- label: 'data filename',
- labelWidth: '150px',
- placeholder: 'data filename',
- // isHidden: false,
- span: 12,
- },
- {
- field: 'wfdName',
- prop: 'wfdName',
- type: 'input',
- label: 'WFD name',
- placeholder: 'WFD name',
- span: 12,
- },
- {
- field: 'version',
- prop: 'version',
- type: 'input',
- label: 'version',
- labelWidth: '150px',
- placeholder: 'version',
- span: 12,
- },
- ],
- // 按钮
- buttons: [{
- name: '生成转码程序',
- title: 'generateCronjob',
- type: 'primary',
- size: 'default', //可以是default,small,large
- icon: shallowRef(Edit),
- // 按钮是否为朴素类型
- // plain: true,
- onClick: null
- }, {
- name: '重置',
- type: 'info',
- title: 'resetCronjob',
- size: 'default',
- icon: shallowRef(DocumentDelete),
- // plain: true,
- onClick: null
- },
- {
- name: '下载转码程序',
- type: 'success',
- title: 'downloadCronjob',
- size: 'default',
- icon: shallowRef(Download),
- isHidden: true,
- // plain: true,
- onClick: null
- }
- ],
- ref: 'cronjobFormRef',
- labelWidth: '120px',
- labelPosition: 'right',
- inline: true,
- editable: true,
- // 单元列之间的间隔
- elRowGutter: 20,
- // size: 'small',
- // 是否需要form边框
- formBorder: true,
- colLayout: {
- xl: 5, //2K屏等
- lg: 8, //大屏幕,如大桌面显示器
- md: 12, //中等屏幕,如桌面显示器
- sm: 24, //小屏幕,如平板
- xs: 24 //超小屏,如手机
- }
- });
-
- export default cronjobConfig;