前言
前端页面存在大部分的条件搜索的需求,相似度高,复用性强。
基于vue3和element实现,当然内部的组件不是重点可以随意切换
基于JSON对象自动生成,后期也可探索加入可视化拖拽界面
基础页面
interface queryFormConfig {
prop: string,
type: string,
label?: string | undefined,
slotName?: string | undefined,
options?: Array<options>,
rules?: Array<object>,
children?: Array<object>
dictCode?: string,
showLabel?: boolean,
unit?: string,
}
interface options {
value: string,
label: string
}
}
// 配置对象案例
动态生成页面
{ prop: "a", type: "input", label: "输入框一号:" },
{ prop: "b", type: "cascader", dictCode: '2', label: "输入框二号:" },
{
prop: "c",
dictCode: 'ca', // 字典码,下拉框点击的时候自动获取字典数据
type: "multipleSelect",
label: "输入框三号:",
},
{
prop: "21",
dictCode: '32',
type: "multipleSelect",
label: "输入框四号:",
},
{ prop: "2", type: "picker", label: "输入框五号:" },
{ prop: "232", type: "cascader", label: "输入框六号:", },
{ prop: "32", type: "picker", label: "输入框七号:" },
{
prop: "43",
type: "complexSelector",
label: "复合输入框八号:",
children: [
{
label: "输入框八号:",
prop: "fs",
type: "amountRange",
},
{
label: "输入框八号多选框:",
prop: "dsa",
type: "checkbox",
options: [
{ label: "dsa", prop: "1" },
{ label: "dsa", prop: "2" },
{ label: "dsa", prop: "3" },
],
},
{
label: "输入框八号单选框:",
prop: "dads",
type: "radio",
options: [
{ label: "sa", prop: "1" },
{ label: "dsa", prop: "2" }
],
checkbox: 'dada'
}
],
},
<el-form
:model="transfer"
:disabled="disabled"
label-width="120px"
ref="formRef"
:rules="rules"
>
<el-row>
<el-col
v-for="(item, index) of queryFormConfig"
:span="elColSpan(item.type || '')"
:key="`col_${item.prop}_${item.type}_${index}`"
>
<el-form-item
v-if="item.prop"
:key="`item_${item.prop}_${item.type}_${index}`"
:label="titleDisplay(item.type, item.label)"
:label-width="labelWidth(item.type)"
>
<template v-if="item.slotName != null && item.slotName != ''">
<slot
:key="`slot_${item.prop}_${item.type}_${index}`"
:name="item.slotName"
:item="item"
:index="index"
></slot>
</template>
<template v-if="item.type == 'interval'">
<div style="display: flex">
<el-input
:key="`item_interval_start_${item.prop}_${item.type}_${index}`"
style="width: 45%"
oninput="value = value.replace(/[^0-9]/g,'')"
v-model.number="transfer[`${item.prop}_start`]"
:placeholder="`${item.placeholder || '请输入'} `"
:clearable="`${item.clearable || true}`"
>
<template #append v-if="item.unit"
>{{
["", "㎡", "m", "层", "km/h", "个", "床", "吨", "%", "岁"][
item.unit
]
}}
</template>
</el-input>
<span style="margin: 0 10px"> - </span>
<el-input
:key="`item_interval_end_${item.prop}_${item.type}_${index}`"
style="width: 45%"
oninput="value = value.replace(/[^0-9]/g,'')"
v-model.number="transfer[`${item.prop}_end`]"
:placeholder="`${item.placeholder || '请输入'} `"
:clearable="`${item.clearable || true}`"
>
<template #append v-if="item.unit">{{
["", "㎡", "m", "层", "km/h", "个", "床", "吨", "%", "岁"][
item.unit
]
}}</template>
</el-input>
</div>
</template>
<template v-if="!item.type || item.type == 'input'">
<el-input
:key="`input_${item.prop}_${item.type}_${index}`"
v-model="transfer[`${item.prop}`]"
:placeholder="`${item.placeholder || '请输入'} `"
:clearable="`${item.clearable || true}`"
/>
</template>
<template v-if="item.type == 'numberinput'">
<el-input
oninput="value = value.replace(/[^0-9]/g,'')"
:key="`numberinput_${item.prop}_${item.type}_${index}`"
v-model.number="transfer[`${item.prop}`]"
:placeholder="`${item.placeholder || '请输入'} `"
:clearable="`${item.clearable || true}`"
/>
</template>
<template v-if="item.type == 'picker'">
<el-date-picker
v-model="transfer[`${item.prop}`]"
type="daterange"
:prefix-icon="Calendar"
value-format="YYYY-MM-DD"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</template>
<template v-if="item.type == 'select'">
<template
v-if="Array.isArray(item.options) && item.options.length > 0"
>
<el-select
:key="`select_${item.prop}_${item.type}_${index}`"
v-model="transfer[`${item.prop}`]"
class="m-2"
placeholder="请选择"
collapse-tags
:loading="dictionaryLoading"
collapse-tags-tooltip
>
<el-option
v-for="(e, index1) in item.options"
:key="`selectOption_${index1}`"
:label="e.label"
:value="e.value"
/>
</el-select>
</template>
<template v-else>
<el-select
:key="`select_${item.prop}_${item.type}_${index}`"
v-model="transfer[`${item.prop}`]"
class="m-2"
placeholder="请选择"
collapse-tags
:loading="dictionaryLoading"
collapse-tags-tooltip
@focus="selectChange(item.dictCode, item.prop)"
>
<el-option
v-for="e in store.selectOptions[item.prop]"
:key="e.id"
:label="e.itemText"
:value="e.itemValue"
/>
</el-select>
</template>
</template>
<template v-if="item.type == 'labelSelector'"> </template>
</template>
<template v-if="item.type == 'multipleSelect'">
<template
v-if="Array.isArray(item.options) && item.options.length > 0"
>
<el-select
:key="`multipleSelec_${item.prop}_${item.type}_${index}`"
v-model="transfer[`${item.prop}`]"
class="m-2"
placeholder="请选择"
multiple
collapse-tags
:loading="dictionaryLoading"
collapse-tags-tooltip
>
<el-option
v-for="(e, index1) in item.options"
:key="`selectOption_${index1}`"
:label="e.label"
:value="e.prop"
/>
</el-select>
</template>
<template v-else>
<el-select
:key="`multipleSelec_${item.prop}_${item.type}_${index}`"
v-model="transfer[`${item.prop}`]"
class="m-2"
placeholder="请选择"
multiple
collapse-tags
:loading="dictionaryLoading"
collapse-tags-tooltip
@focus="selectChange(item.dictCode, item.prop)"
>
<el-option
v-for="e in store.selectOptions[item.prop]"
:key="e.id"
:label="e.itemText"
:value="e.itemValue"
/>
</el-select>
</template>
</template>
<template v-if="item.type == 'cascader'">
<el-cascader
v-model="transfer[`${item.prop}`]"
@focus="cascaderChange(item.dictCode, item.prop)"
:options="store.selectOptions[item.prop]"
placement="bottom-end"
:props="
item.cascaderProps || { multiple: true, checkStrictly: false }
"
collapse-tags
clearable
/>
</template>
<template v-if="item.type == 'remoteSelect'">
<el-select
v-model="transfer[`${item.prop}`]"
multiple
filterable
remote
:reserve-keyword="false"
placeholder="请输入"
remote-show-suffix
:remote-method="remoteMethod"
:loading="loading"
style="width: 240px"
>
<el-option
v-for="item in projectNameOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</template>
</el-form-item>
</el-col>
</el-row>
</el-form>
需要嵌套的其他内容可以通过slot加入
多个选项可以通过ref动态获取,
:ref="
(el) =>
complexSelectorRef(el, `complexSelectorRef_${item.prop}`)
"
let complexSelectorRefList = new Map()
const complexSelectorRef = (el: any, key: string) => {
if (el) {
complexSelectorRefList.set(key,el)
}
};
// 赋值 || 重置 || 提交
Object.keys(complexSelectorCustomRefList).forEach(function (key) {
res.push(complexSelectorCustomRefList[key].queryReset());
});
通过map结构来维护,key,value的结构可以很方便的通过模块的唯一标识来获取组件ref的值
后续可以通过promise来进行赋值,重置,提交。
数据绑定
前期本来打算直接通过v-model,提交,重置,都通过一个值去进行维护,过程中不需要思考其他的问题。 但是如果是多级嵌套,组件的渲染顺序上会存在一定的影响。
为了方便控制提交重置,使用promise来进行流程进度的维护。
每个子组件内部通过独立的对象来进行数据绑定,具有一定的隔离性,由外部统一重置,赋值。
以提交为例:
子组件内部
const doSubmit = () => {
return new Promise((res, rej) => {
if (props.item) {
res({
[props.item.prop]: transfer.value
});
}
});
};
基础表单中集合数据
Object.keys(complexSelectorCustomRefList).forEach(function (key) {
res.push(complexSelectorCustomRefList.get(key).doSubmit());
});
return new Promise((resolve, rej) => {
Promise.all(res).then((e) => {
resolve(e);
});
});
题外话vue3组件渲染顺序
父组件:beforeCreate -> created -> beforeMount -> 子组件beforeCreate -> 子组件created -> beforeMount -> 子组件mounted -> 父组件mounted
扩展性质
页面由数据驱动,所以需要维护好一个清晰明了的数据结构,用来渲染生成表单。后续手动勾选添加表单内容,或者接入配置页面都需要通过维护渲染数据来进行
JSX版本灵活性更高后面尝试改成JSX