界面运行效果如下图所示:
界面第一次加载时,默认会显示一个空的查询条件,如下图所示:
点击“加”图标,可以无限增加查询条件,也可以点击“减”图标删除新增的查询条件,如下图所示:
说明:第一个下拉框的数据变化时,第三个下拉框的数据要进行联动,第三个组件可以是下拉框也可以是文本框,它是根据第一个下拉框的数据来决定的。第二个下拉框是固定的四个选项>、<、=、!=。如下图所示:
后端接口返回的数据结构:
{ "data": { "array": [{ "opts": [{ "val": "0", "name": "停止" }, { "val": "1", "name": "运行" }], "paramCode": "runStatus", "name": "运行状态" }, { "opts": [{ "val": "0", "name": "否" }, { "val": "1", "name": "是" }], "paramCode": "alarmStatus", "name": "报警与否" }, { "opts": [{ "val": "0", "name": "就地" }, { "val": "1", "name": "远程" }], "paramCode": "remoteLocal", "name": "远程就地" }, { "opts": [{ "val": 0, "name": "禁用" }, { "val": 1, "name": "启用" }], "paramCode": "startUse", "name": "是否启用" }, { "opts": [{ "val": "0", "name": "变频" }, { "val": "1", "name": "工频" }], "paramCode": "runMode", "name": "工频启停" }, { "opts": [{ "val": 0, "name": "手动" }, { "val": 1, "name": "自动" }], "paramCode": "controlMode", "name": "控制模式" }, { "opts": [{ "val": "0", "name": "手动" }, { "val": "1", "name": "自动" }], "paramCode": "frequencyMode", "name": "频率手自动" }, { "opts": [], "paramCode": "frequencySetValue", "name": "频率设定" }, { "opts": [], "paramCode": "frequencyReturnValue", "name": "频率反馈" }, { "opts": [], "paramCode": "tempSetValue", "name": "温度设定" }, { "opts": [], "paramCode": "tempReturnValue", "name": "温度反馈" }, { "opts": [{ "val": 0, "name": "关" }, { "val": 1, "name": "开" }], "paramCode": "newWindValveOnOff", "name": "新风阀启停" }, { "opts": [], "paramCode": "newWindValveOpen", "name": "新风阀开度" }, { "opts": [], "paramCode": "newWindTemp", "name": "新风阀温度" }, { "opts": [], "paramCode": "newWindHumidity", "name": "新风阀湿度" }, { "opts": [{ "val": 0, "name": "关" }, { "val": 1, "name": "开" }], "paramCode": "returnWindValveOnOff", "name": "回风阀启停" }, { "opts": [], "paramCode": "returnWindValveOpen", "name": "回风阀开度" }, { "opts": [], "paramCode": "returnWindHumidity", "name": "回风阀湿度" }, { "opts": [], "paramCode": "co2", "name": "回风阀co2" }, { "opts": [{ "val": "0", "name": "手动" }, { "val": "1", "name": "自动" }], "paramCode": "waterValveHandAuto", "name": "水阀手自动" }, { "opts": [{ "val": 0, "name": "关" }, { "val": 1, "name": "开" }], "paramCode": "waterValveOnOff", "name": "水阀启停" }, { "opts": [], "paramCode": "waterValveOpenSet", "name": "水阀开度设定" }, { "opts": [], "paramCode": "waterValveOpenReturn", "name": "水阀开度反馈" }, { "opts": [], "paramCode": "supplyAirTemp", "name": "送风温度" }, { "opts": [], "paramCode": "supplyAirHumidity", "name": "送风湿度" }, { "opts": [], "paramCode": "supplyAirPressure", "name": "送风静压" }, { "opts": [{ "val": 0, "name": "关" }, { "val": 1, "name": "开" }], "paramCode": "humidityValveOnOff", "name": "加湿阀启停" }, { "opts": [], "paramCode": "humidityValveOpen", "name": "加湿阀开度" }, { "opts": [], "paramCode": "filterDiffPressure", "name": "过滤网压差" }, { "opts": [{ "val": 0, "name": "关" }, { "val": 1, "name": "开" }], "paramCode": "elecHeatOnOff", "name": "电加热" }, { "opts": [], "paramCode": "power", "name": "功率" }, { "opts": [], "paramCode": "runTime", "name": "运行时间" }, { "opts": [], "paramCode": "loadPower", "name": "供冷负荷" }, { "opts": [], "paramCode": "cold", "name": "累计供冷量" }, { "opts": [{ "val": 0, "name": "正常" }, { "val": 1, "name": "中断" }], "paramCode": "commuInterrupt", "name": "通讯是否正常" }] }, "code": 200, "msg": "", "errors": null }
把接口数据赋值给paramList。
因为数据量不大,为了提升性能,数据是接口一次性返回的,数据的联动是在内存当中进行数据筛选,从而避免频繁的接口调用。
在数据结构当中,当opts属性值为空数组时,第三个组件显示为文本框,否则显示为下拉框,并把opts中的数据作为第三个组件的下拉框内容展示出来。当第三个组件是下拉框时,第二个下拉框只能显示=和!=这两项,如果是文本框时,都可以显示。
后端接口需要的查询参数是:
dataListParams: [{paramName: "runStatus", operator: 2, value: "0"}, {paramName: "startUse", operator: 2, value: 0}]
接下来,我们定义vue组件中的内容,Dom部分:
<div class='search-item'> <label>组合条件:</label> </div> <div class="search-item" v-for="(paramObj,index) in dataListParams" :key="index"> <!-- 参数名 --> <el-select v-model="paramObj.paramName" filterable clearable @change="(e)=>changeParam(e,index)" placeholder="请选择" style="width:100px"> <el-option v-for="item in paramList" :key="item.paramCode" :label="item.name" :value="item.paramCode"></el-option> </el-select> <!-- 操作符列表 --> <el-select v-model="paramObj.operator" clearable placeholder="" style="width:60px"> <el-option v-for="(item,sindex) in operatorOptions" :disabled="getDisabled(sindex,index)" :key="item.id" :label="item.name" :value="item.id"></el-option> </el-select> <!-- 参数值 --> <el-select v-if="listOpts[index].length>0" v-model="paramObj.value" filterable clearable placeholder="请选择" style="width:100px"> <el-option v-for="item in listOpts[index]" :key="item.val" :label="item.name" :value="item.val"></el-option> </el-select> <el-input v-else v-model="paramObj.value" :clearable="true" placeholder="请输入" style="width:100px"></el-input> <span v-if="index==0" class="add-where"><i class="iconfont icon-add" @click="addWhere"></i></span> <span v-else class="remove-where"><i class="iconfont icon-shanchu1" @click="removeWhere(index)"></i></span> </div>
js部分:
export default { mixins: [indexOptions], data () { return { //操作符列表 operatorOptions: [ { id: 0, name: '>' }, { id: 1, name: '<' }, { id: 2, name: '=' }, { id: 3, name: '!=' } ], //组合条件 dataListParams: [{ paramName: "", operator: '', value: "" }], deviceIds: [],
paramList:[],//参数列表,包含操作名称、操作值列表(JSon数据结构)
listOpts: [[]]//操作值列表 } }, computed: {//筛选参数列表,如果参数列表dataListParams当中有任何一个属性值为空,则不传递 filterDataListParams () { return this.dataListParams.filter(f => { return f.paramName !== "" && f.operator !== "" && f.value !== "" }); } }, methods: {//获取操作选项启用、禁用 getDisabled (sindex, index) { if (this.listOpts.length > 0 && this.listOpts[index].length > 0) { return [0, 1].includes(sindex); } else { return false; } }, //添加组合条件 addWhere () { this.dataListParams.push({ paramName: '', operator: '', value: '' }); this.listOpts.push([]); }, //移除组合条件 removeWhere (index) { this.dataListParams.splice(index, 1); this.listOpts.splice(index, 1); }, //根据参数编码获取操作列表 getOptsByParamCode (code,) { let res = this.paramList.find(f => f.paramCode == code); return res ? res.opts : []; }, //参数选项变化 changeParam (code, index) { //变化之前的类型 let preType = this.listOpts[index].length > 0; //是否下拉框 let arr = this.getOptsByParamCode(code); this.listOpts[index] = arr; this.dataListParams[index].value = ''; //变化之后的类型 let nextType = arr.length > 0;//是否是下拉框 //前后类型不一致时,清空操作符 if (preType != nextType) { this.dataListParams[index].operator = ''; } } } };
当第一个下拉框选项变化时,如果第三个组件是相同类型(下拉框或文本框),则第二个下拉框的选项不清空,否则清空。因为文本框可以选4个选项,下拉框只能选两个,不能记录上一次的选中状态。
只有一组查询条件当中三个选项的值都不为空时,才会把参数传递给后端,所以前端通过计算熟悉filterDataListParams进行了数据过滤。
由于数据结构是动态变化的,所以为了保存查询条件的保存状态,下拉框的数据列表项也应当是动态的(数组存储)。
这里其实还漏了东西,那就是查询条件去重,我虽然知道,但是我并没有在前端进行去重,因为需求不提、产品不提,待他们发现这个问题并且提时bug时,我会把锅帅给后端,让后端对查询条件的数据进行去重处理...O(∩_∩)O哈哈~(^_^)
还有查询条件参数值是文本框时的输入值的合法性校验,这个产品不提、测试不提、我也不做,因为做也不知道要做成什么样,到时候,接口报错了,我一并再把锅甩给后端~O(∩_∩)O哈哈~甩锅非我愿,然而,我这样做,都是为了高效的完成开发任务,然后留出时间搞自己的事情,毕竟我一个前端要对接4个后端,每天净干些打杂的事情,太浪费时间了...像我们这样的小企业,每天都讲产出,有时候自己花太多时间和精力去想把东西做好,领导一句:搞个这么简单的玩意怎么要花费那么久。为此,对于所有开发任务,你给一天时间我有一天时间的玩法,你给三天时间我有三天的玩法,你给一周我有一周的玩法,反正最终都是结果导向:按时完成开发和上线。一分价钱一分货,一分时间也是一分货。我相信有许多像我一样的老程序员、老油条,面对一些很不合理的要求,总是有各种办法偷工减料,然后让领导满意。
最后把filterDataListParams作为参数传递给后端接口就可以了,这是一个很典型的vue动态数据驱动应用。
数据驱动的核心,在于构造数据结构,因为dom的变化都是根据数据来渲染的,在过去用js或者jquery的方式,会频繁的操作dom,不单要写一大坨代码,而且性能还很低,数据驱动之后,只要定义好了数据结构,程序员可以把更多的精力投入到业务场景当中,而并非dom操作当中。
在实际工作中,关于接口的数据结构,可能是后端开发人员自定义的,也可能是前端开发人员自定义好后给后端你的,也可能是前后端一起协商的。当后端接口提供的数据若是无法直接满足前端的需求时,前端人员将不得不对数据进行二次构造,这是很痛苦的一件事情,作为前端开发者而言,必然希望接口返回的数据是不需要二次构造和清洗的,拿来赋值就可以使用。