今天在工作中发现了一种复用性很广的开发思路,例如在后台管理系统中经常会有提供给用户进行选择的表单:在一些数据记录的功能页面中,常常要通过选择不同时间维度来显示每个时段的数据(如“日”、“月”、“年”),而选完时间维度后,起始日期和结束日期的可选项和格式也不一样(如“日”维度下的时间范围是当天的某几个小时,“月”维度下是当月的某几天,“年”维度下就是当年的某几个月)。
若每次需要运用到此种时间维度查询功能时,都重新写一遍select逻辑就太麻烦了,直接ctrl + cv又会让代码过于坑长。
直接将select做成组件的方法固然也可以,但我们若想将select组件乃至form组件开发得复用性更广,就不该把options的内容写死成时间维度选项,毕竟上面说的只是举个例,在一个后台管理项目中需要复用的选项框还多着呢。
那我们的思路是否可以从选项options的复用上开展呢?将options抽取出来,把要用到的选项做成一个个对象放在js文件中,哪个页面要用到哪个选项时直接调用该js文件中相应的options对象就可以了
在此以图文方式为大家举例:
某个功能页面中运用到了项目中的form组件,该页面需要一个时间维度的选项来查询不同时间维度下的数据
form的数据记录在formList里,而formList应为一个计算属性
图中的红框就是今天的重点了。这个this.$dimension
是什么呢?看过我上一篇文章的小伙伴肯定都知道了:浅谈vue中的
含义及其用法(
含义及其用法(
含义及其用法(xxx引用的位置)
那我们就顺藤摸瓜找到它:
main.js:
dimension.js:
而最初的options选项里的年维度this.$dimension.year
就是图中的year,绑定的value值为’year’,显示的选项label为langt['year']
(这里涉及到国际化的i18n知识,若页面语言选择中文,则取zh.js中的值’年’,若选择英文则取en.js中的值’year’)
当选择完“年”维度后,则显示“年”维度下的时间范围选项“选择年份”
同路径下的dates.js:
图中的pickerOptions即为判断选择时间是否大于当前时间,若大于,则触发该页面的对应逻辑(如该页面若允许选择的时间大于当前时间,则啥也不干;反之则可以提示类似“所选时间不可大于当前时间”的错误弹窗)。
而form组件的设计思路可以参考如下:
<el-form ref="formValidate"
:model="formObj"
:rules="rules"
:inline="inline"
:label-width="labelWidth"
v-bind="$attrs">
<el-form-item v-for="(item) in formListNew"
style="padding-left: 10px"
:key="item.prop"
:prop="item.prop"
:label="item.label">
<!-- input -->
<el-input v-if="item.type === 'input'" v-model="formObj[item.prop]"
:placeholder="item.placeholder"
:clearable="item.clearable"
:disabled="item.disabled">
<el-select v-if="item.appendSelect" v-model="item.selectValue"
slot="prepend"
style="width: 120px;"
:disabled="item.selectDisabled">
<el-option v-for="option in item.options"
:key="option.value"
:value="option.value"
:label="option.label">
</el-option>
</el-select>
<el-button v-if="item.appendButton"
slot="append"
:disabled="item.buttonDisabled"
@click="inputButtonClick(item.prop, item.selectValue)">
{{lang.Taketherecommendedvalue}}
</el-button>
</el-input>
<!-- textarea -->
<el-input v-if="item.type === 'textarea'" v-model="formObj[item.prop]"
type="textarea"
:placeholder="item.placeholder"
:disabled="item.disabled">
</el-input>
<!-- number -->
<el-input-number v-if="item.type ==='number'" v-model="formObj[item.prop]"
controls-position="right"
:style="`width:100%`"
:placeholder="item.placeholder"
:min="item.min"
:max="item.max"
:disabled="item.disabled">
</el-input-number>
<!-- checkbox -->
<el-checkbox v-if="item.type === 'checkbox'" v-model="formObj[item.prop]"
:true-label="item.trueValue"
:false-label="item.falseValue"
:disabled="item.disabled">
</el-checkbox>
<!-- checkboxGroup -->
<el-checkbox-group v-if="item.type==='checkboxGroup'" v-model="formObj[item.prop]" >
<el-checkbox v-for="option in item.options"
:key="option.value"
:label="option.value"
:disabled="option.disabled"
>
{{option.label}}
</el-checkbox>
</el-checkbox-group>
<!-- shiftCheckboxGroup -->
<el-checkbox-group v-if="item.type==='shifts'" v-model="formObj[item.prop]">
<el-checkbox v-for="option in shiftOptions"
:key="option.value"
:label="option.value">
{{option.label}}
</el-checkbox>
</el-checkbox-group>
<!-- shiftRradioGroup -->
<el-radio-group v-if="item.type==='shift'" v-model="formObj[item.prop]">>
<el-radio v-for="option in shiftOptions"
:key="option.value"
:label="option.value">
{{option.label}}
</el-radio>
</el-radio-group>
<el-radio-group v-if="item.type==='radio'" v-model="formObj[item.prop]">>
<el-radio v-for="option in item.options"
:key="option.value"
:label="option.value">
{{option.label}}
</el-radio>
</el-radio-group>
<!-- select -->
<el-select v-if="item.type === 'select'" v-model="formObj[item.prop]"
:placeholder="item.placeholder" @change="selectChange(item,formObj[item.prop])"
:disabled="item.disabled"
:clearable="item.clearable"
:style="`width:${item.width ? item.width : '100%'}`">
<el-option v-for="option in item.options"
:key="option.value"
:value="option.value"
:label="option.label">
</el-option>
</el-select>
<!-- selectMultiple -->
<el-select v-if="item.type==='selectMultiple'" v-model="formObj[item.prop]" @change="selectChange(item,formObj[item.prop])" :collapse-tags='item.collapseTags'
multiple>
<el-option v-for="option in item.options"
:value="option.value"
:key="option.value"
:label="option.label" multiple>
</el-option>
</el-select>
<!-- alarmMethod -->
<el-select v-if="item.type === 'alarmMethod'" v-model="formObj[item.prop]"
clearable
:placeholder="lang.Selectalarmscheme"
:disabled="item.disabled"
:style="`width:100%`">
<el-option v-for="option in alarmMethods"
:key="option.value"
:value="option.value"
:label="option.label">
</el-option>
</el-select>
<!-- product -->
<el-select v-if="item.type === 'product'" v-model="formObj[item.prop]"
remote
filterable
reserve-keyword
:placeholder="lang.pleaseInputKeywords"
:remote-method="getProducts"
:disabled="item.disabled"
:style="`width:100%`">
<el-option v-for="option in products"
:key="option.value"
:value="option.value"
:label="option.label">
</el-option>
</el-select>
<!-- userSelect -->
<vueDynamicLoader v-if="item.type === 'userSelect'" v-model="formObj[item.prop]"
url="xxxx/index.js"
:c-lang="locale"
recombine-symbol=";"
:data="item.data"
:loginId="true"
@change="userChange($event, item.prop)">
</vueDynamicLoader>
<!-- treeSelect -->
<cmpTreeSelect v-if="item.type === 'treeSelect'" v-model="formObj[item.prop]"
:style="`width:${item.width ? item.width : '100%'}`"
:disabled="item.disabled"
:placeholder="item.placeholder"
:isSingleCheck="item.isSingleCheck"
:disabledKeys="item.disabledKeys"
:url="item.url"
:httpParams="item.httpParams"
@change="cmpTreeSelectChange"
>
</cmpTreeSelect>
<!-- tableSelect -->
<cmpTableSelect v-if="item.type === 'tableSelect'" v-model="formObj[item.prop]"
:disabled="item.disabled"
:isSingleCheck="item.isSingleCheck"
:url="item.url"
:httpParams="item.httpParams"
@change="$refs['formValidate'].validateField(item.prop)">
</cmpTableSelect>
<!-- dateTimePicker -->
<el-date-picker v-if="item.type === 'dateTimePicker'" v-model="formObj[item.prop]"
:clearable="false"
:disabled="item.disabled"
:popper-class="item.format == 'yyyy-MM-dd HH' ? 'el-date-picker__hour' : ''"
:value-format="item.valueFormat"
:type="item.pickerType"
:format="item.format"
:placeholder="lang.Pleaseselectatime" size="mini"
:picker-options="item.pickerOptions">
</el-date-picker>
<!-- timeSelect -->
<el-time-select v-if="item.type==='timeSelect'" v-model="formObj[item.prop]"
:picker-options="{
start: '00:00',
step: '00:30',
end: '23:30'
}"
:placeholder="lang.Selecttime">
</el-time-select>
<!-- timePicker -->
<TimePicker v-if="item.type === 'timePicker'" v-model="formObj[item.prop]"
confirm
hide-disabled-options
:key="item.step"
:clearable="false"
:style="`width:${item.width ? item.width : ''}`"
type="time"
format="HH:mm"
:steps="[1, item.step ? item.step : 5, 0]"
:disabled-hours="item.disabledHours||[]"
:disabled-minutes="item.disabledMinutes||[]"
:placeholder="lang.Pleaseselectatime">
</TimePicker>
<cmpTime v-if="item.type === 'cmpTime'" :disabled="item.disabled||false" v-model="formObj[item.prop]"></cmpTime>
<!-- <div v-if="item.type === 'cmpdaytime'">{{formObj[item.prop]}}</div> -->
<cmpMinutetime v-if="item.type === 'cmpMinutetime'" v-model="formObj[item.prop]" :pickerOptions="item.pickerOptions" :step="item.step"></cmpMinutetime>
<cmpdaytime v-if="item.type === 'cmpdaytime'" v-model="formObj[item.prop]" :pickerOptions="item.pickerOptions"></cmpdaytime>
<!-- <cmpdaytime -->
<div v-if="item.type=='section'">
<el-col :span="11">
<el-input type='text' v-model="formObj[item.model1]" clearable></el-input>
</el-col>
<el-col class="line" :span="2">-</el-col>
<el-col :span="11">
<el-input type='text' v-model="formObj[item.model2]" clearable></el-input>
</el-col>
</div>
<div v-if="item.type=='btns'">
<el-button v-for='(li,i) in item.btns' :key="i" :type="li.primary||''" :size="li.size||'mini'" @click="cfmButtonClick(li)">{{li.text}}</el-button>
</div>
</el-form-item>
<el-form-item v-for="(item,index) in dateOptions"
:key="item.key"
:label="item.label">
<el-time-select v-if="item.type === 'time'" v-model="dates[index]"
style="width: 200px"
:picker-options="{
start: '00:00',
step: '01:00',
end: '23:00'
}"
@change="changDate($event,item)">
</el-time-select>
<el-date-picker v-else v-model="dates[index]"
:clearable="false"
:placeholder="lang.Pleaseselectatime"
:popper-class="item.format == 'yyyy-MM-dd HH' ? 'el-date-picker__hour' : ''"
:type="item.type"
:format="item.format"
:picker-options="item.pickerOptions"
@change="changDate($event,item)">
</el-date-picker>
</el-form-item>
<slot></slot>
</el-form>
组件的计算属性formList下对于children的递归算法:
formListNew() {
let list = this.formList
.filter(item => !item.isHidden)
.map(item => {
if(item.type === "checkboxGroup" && item.parentProp && this.formObj[item.parentProp]) {
let parentForm = this.formList.find(form => form.prop === item.parentProp);
if(!parentForm) return item;
let parentOption = parentForm.options.find(option => option.value === this.formObj[item.parentProp]);
if(!parentOption || !parentOption.children || parentOption.children.length == 0) {
item.options = [];
}
else {
item.options = parentOption.children;
}
let values = item.options.map(option => option.value);
let checkedValues = this.formObj[item.prop].filter(value => values.includes(value));
if(checkedValues.length < this.formObj[item.prop].length) {
this.formObj[item.prop].splice(0, this.formObj[item.prop].length, ...checkedValues);
}
return item;
}
if(item.type === "select" && item.parentProp && this.formObj[item.parentProp]) {
let parentForm = this.formList.find(form => form.prop === item.parentProp);
if(!parentForm) return item;
let parentOption = parentForm.options.find(option => option.value === this.formObj[item.parentProp]);
item.options = parentOption.children;
if(item.options.length > 0 && this.formObj[item.prop] && !item.options.map(option => option.value).includes(this.formObj[item.prop])){
this.formObj[item.prop] = item.options[0].value;
}
}
if(item.type === "select"
&& item.options.length > 0
&& this.formObj[item.prop]
&& !item.options.map(option => option.value).includes(this.formObj[item.prop])
) {
this.formObj[item.prop] = item.options[0].value;
return item;
}
if(item.type === "dates" && item.parentProp && this.formObj[item.parentProp]) {
let parentForm = this.formList.find(form => form.prop === item.parentProp);
if(!parentForm) return item;
let parentOption = parentForm.options.find(option => option.value === this.formObj[item.parentProp]);
this.dateOptions = parentOption.children;
this.formObj[item.prop] = this.dates;
return item;
}
if(item.type === "shift" || item.type === "shifts") {
this.getShifts(item);
}
if(item.type === "alarmMethod") {
this.getAlarmMethodList(item);
}
if(item.type === "product") {
this.products = [];
}
if(item.type === "timePicker" && this.formObj[item.prop] && typeof this.formObj[item.prop] === "string") {
this.formObj[item.prop] = moment(`2020-01-01 ${this.formObj[item.prop]}`).toDate();
}
return item;
});
return list;
},
等有时间了再回过头来补充注释吧
以后这个专栏就用来记录在工作中遇到的一些项目上有意思、实用性强、复用性广的开发思路
感谢您的支持,THX