在平常開發(fā)中,form表單是寫的最多且最繁瑣的一個(gè)功能。為了避免代碼冗余,我把form表單抽出來當(dāng)做一個(gè)組件并且根據(jù)數(shù)據(jù)來動(dòng)態(tài)生成基本表單
現(xiàn)在只寫了input和select 后續(xù)會(huì)加上更多,且優(yōu)化雙向綁定的代碼
用到的技術(shù)
1.Vue3基礎(chǔ)知識(shí)
2.Element-plus組件
定義formTemplate
判斷是否需要布局(el-col)
判斷是否需要form包裹(el-form-item)
根據(jù)傳遞的參數(shù)來生成指定組件(components):比如輸入框 下拉框等
注意:使用的時(shí)候需要全局注冊(cè)一下指定組件 具體看 倒數(shù)第二步 全局注冊(cè)組件
formTemplate.vue
<template>
<el-col v-if="isCol" :span="colSpan">
<div v-if="isForm">
<el-form-item :prop="prop" :label="label">
<component
:is="isCom"
:config="config"
v-model="templateValue"
@change="change"
/>
</el-form-item>
</div>
<div v-else>
<component
:is="isCom"
:config="config"
v-model="templateValue"
@change="change"
/>
</div>
</el-col>
<div v-else>
<div v-if="isForm">
<el-form-item :prop="prop" :label="label">
<component
:is="isCom"
:config="config"
v-model="templateValue"
@change="change"
/>
</el-form-item>
</div>
<div v-else>
<component
:is="isCom"
:config="config"
v-model="templateValue"
@change="change"
/>
</div>
</div>
</template>
<script setup>
import { watch, ref } from "vue";
/**
* @author BlackKey
* @params modelValue 雙向綁定值
* @params colSpan 布局占位
* @params isCol 是否啟用布局
* @params isForm 是否用form包裹
* @params label 標(biāo)題
* @params prop model的鍵名
* @params isCom 需要的生成的組件
* @params config 組件對(duì)應(yīng)的配置文件
* @desc form表單默認(rèn)格式
*/
const { colSpan, isCol, isForm, label, prop, isCom, modelValue, config } =
defineProps({
colSpan: {
type: Number,
default: 6,
},
isCol: {
type: Boolean,
default: true,
},
isForm: {
type: Boolean,
default: true,
},
label: {
type: String,
default: "",
},
prop: {
type: String,
default: "",
},
isCom: {
type: String,
default: "wx-input",
},
modelValue: {
type: [String, Number, Object, Array],
default: "",
},
config: {
type: Object,
default: () => ({}),
},
});
const templateValue = ref(modelValue);
const emit = defineEmits(["change"]);
const change = (data) => {
emit("change", data);
};
</script>
<style lang="less" scoped></style>
默認(rèn)輸入框組件
wxInput.vue
<template>
<el-input
v-model="inputValue"
:placeholder="placeholder"
:clearable="clearable"
:disabled="disabled"
:type="type"
:maxlength="maxlength"
:autosize="autosize"
/>
</template>
<script setup>
import { watch, ref } from "vue";
/**
* @author BlackKey
* @params modelValue 雙向綁定值
* @params placeholder 輸入框占位符文本
* @params type 輸入框類型
* @params maxlength 最大長度
* @params disabled 是否禁用
* @params size 輸入框尺寸
* @params autosize textarea 高度是否自適應(yīng),僅 type 為 'textarea' 時(shí)生效。
* @params clearable 是否展示清空按鈕
* @desc 輸入框
*/
const { config, modelValue } = defineProps({
config: {
type: Object,
default: () => ({}),
},
modelValue: {
type: [String, Number, Object, Array],
default: "",
},
});
const {
placeholder = "",
type = "text",
maxlength = "",
disabled = false,
size = "",
autosize = false,
clearable = true,
} = config;
const emit = defineEmits(["change"]);
const inputValue = ref(modelValue);
watch(
() => inputValue.value,
(data) => {
change(data);
}
);
const change = (data) => {
emit("change", data);
};
</script>
默認(rèn)下拉框組件
為了防止枚舉寫一遍 options又寫一遍的情況 我把枚舉值引入進(jìn)來了 (baseSelectSource)
如果枚舉寫了,就直接傳dataType即可
meta.js
const sex = [
{
label: "男",
value: "man",
},
{
label: "女",
value: "woman",
},
];
export const baseSelectSource = {
sex,
};
wxSelect.vue
<template>
<el-select
v-model="selectValue"
:clearable="clearable"
:placeholder="placeholder"
:disabled="disabled"
:size="size"
>
<el-option
v-for="item of (options.length && options) || source || []"
:label="item[label]"
:value="item[value]"
></el-option>
</el-select>
</template>
<script setup>
import { watch, ref } from "vue";
import { baseSelectSource } from "@/meta";
/**
* @author BlackKey
* @params modelValue 雙向綁定值
* @params placeholder 輸入框占位符文本
* @params disabled 是否禁用
* @params size 輸入框尺寸
* @params clearable 是否展示清空按鈕
* @params options 下拉框的值
* @params dataType 預(yù)先設(shè)定好的枚舉
* @params labelKey 下拉框labelKey
* @params valueKey 下拉框valueKey
* @desc 下拉框
*/
const { config, modelValue } = defineProps({
config: {
type: Object,
default: () => ({}),
},
modelValue: {
type: [String, Number, Object, Array],
default: "",
},
});
const {
placeholder = "",
disabled = false,
size = "",
clearable = true,
options = [],
dataType = "",
labelKey = "label",
valueKet = "value",
} = config;
// 如果dateType有值 代表下拉框取枚舉值 從meta里面獲取數(shù)組用來展示下拉框
const source = ref([]);
if (dataType) {
source.value = baseSelectSource[dataType];
}
const emit = defineEmits(["change"]);
// 接收labelKey和valueKey且綁定
const label = ref(labelKey);
const value = ref(valueKet);
const selectValue = ref(modelValue);
watch(
() => selectValue.value,
(data) => {
change(data);
}
);
const change = (data) => {
emit("change", data);
};
</script>
全局注冊(cè)組件
使用該組件的時(shí)候如果不全局注冊(cè)需要的組件 這樣會(huì)導(dǎo)致生成失敗
registerComponent.js
import FormTemplate from "@/components/FormTemplate/index.vue";
import wxInput from "@/components/FormTemplate/components/wxInput.vue";
import wxSelect from "@/components/FormTemplate/components/wxSelect.vue";
export default function install(app) {
app.component("wx-form", FormTemplate);
app.component("wx-input", wxInput);
app.component("wx-select", wxSelect);
}
注意:記得在main里面引入registerComponent.js文件 否則注冊(cè)失敗
main.js
import { createApp } from "vue";
import App from "./App.vue";
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
import "@/assets/css/index.less";
import registerComponent from "./registerComponent";
const app = createApp(App);
app.use(registerComponent);
app.use(ElementPlus);
app.mount("#app");
使用方法
index.vue
注意:使用時(shí)記得用el-form和el-row包裹一下 因?yàn)槲以O(shè)置的默認(rèn)使用布局和form包裹住
<template>
<el-form :model="form" ref="formRef" label-width="80px">
<el-row>
<wx-form
v-for="item in formTemplate"
:label="item.label"
:config="item.config"
:isCom="item.isCom"
@change="item.modelValue = $event"
v-model="item.modelValue"
></wx-form>
<el-col :span="24">
<div class="btn">
<el-button type="primary" @click="search">查詢</el-button>
</div>
</el-col>
</el-row>
</el-form>
</template>
<script setup>
import { reactive, toRefs, ref } from "vue";
const userData = reactive({
form: { name: "", account: "", tel: "", sex: "" },
});
const { form } = { ...toRefs(userData) };
const formTemplate = ref([
{
label: "昵稱",
modelValue: form.value.name,
prop: "name",
config: {
placeholder: "請(qǐng)輸入昵稱",
},
},
{
label: "賬號(hào)",
modelValue: form.value.account,
prop: "account",
config: {
placeholder: "請(qǐng)輸入賬號(hào)",
},
},
{
label: "手機(jī)",
modelValue: form.value.tel,
prop: "tel",
config: {
placeholder: "請(qǐng)輸入手機(jī)",
},
},
{
label: "性別",
modelValue: form.value.sex,
isCom: "wx-select",
prop: "sex",
config: {
placeholder: "請(qǐng)選擇性別",
dataType: "sex",
},
},
]);
// 查詢
const search = () => {
formTemplate.value.forEach((val) => {
form.value[val.prop] = val.modelValue;
});
console.log(form.value);
};
</script>
<style lang="less" scoped>
.btn {
text-align: right;
}
</style>
效果圖

效果圖