通过一个vue+elementUI的小实例来讲解一下它们是如何使用的
需求:点击一个按钮,弹出一个模态框,这个模态框有两个tab,tab中是各种报警条件,这些报警条件是从数据库中动态取出的,数据库中数据变更后,这个界面也要变更,我们可以查看和编辑这些报警条件。底部“确定”按钮点击的时候,会同时将这两个tab中的内容都报错到数据库中去,数据录入要验证输入的格式。
对于熟练的人来说,实现其实很简单,但是对于没有经验的人来说,如果按照官网给的那些简单实例来做,你会发现,出现一些奇怪的问题,诸如,文本框不能编辑内容,表单验证无效等。
界面效果如下图所示:
分析:用到的组件:el-dialog、el-tabs
AlarmSet.vue代码:
<template> <div> <div class="alarm-set" v-loading="winLoading"> <el-tabs v-model="activeName" type="border-card" v-if="alarmTmplData.length>0"> <el-tab-pane label="制冷站" name="coldSet"> <ColdSet ref="coldSet" :alarmTmplData="alarmTmplData" @onSubmit="coldSetSummit" :showAlarmSetWin.sync="showAlarmSetWin" ></ColdSet> </el-tab-pane> <el-tab-pane label="末端" name="endSet"> <EndSet ref="endSet" :alarmTmplData="alarmTmplData" @onSubmit="endSetSummit" :showAlarmSetWin.sync="showAlarmSetWin" ></EndSet> </el-tab-pane> </el-tabs> </div> <div slot="footer" class="dialog-footer"> <div style="display: inline-block"> <el-button type="primary" @click="onSubmit" :loading="btnLoading">确 定</el-button> <el-button @click="isHide">取 消</el-button> </div> </div> </div> </template> <script> import ColdSet from "./ColdSet"; import EndSet from "./EndSet"; import mixinsOption from "@/mixins/mixin-options"; import { alarmService } from "@/services/cold-station-service"; // import { alarmConditionData } from "@/mock/json.js"; export default { mixins: [mixinsOption], props: { showAlarmSetWin: { type: Boolean, default: false }, alarmTmpl: { type: Object, default: {} } }, components: { ColdSet, EndSet }, data() { return { alarmConditionData: [], //报警条件数据 activeName: "coldSet", alarmTmplData: [], coldConditionData: [], //冷站报警条件数据 endConditionData: [], //末端报警条件数据 isColdValid: false, isEndValid: false, btnLoading: false }; }, watch: { showAlarmSetWin: { handler(val) { console.log("showAlarmSetWin", val); if (val) { this.initData(); } }, immediate: true } }, methods: { //初始化数据 initData() { this.$store.commit("base/setWinLoading", true); console.log("initData"); alarmService .getConditionList({ groupNumber: this.groupNumber, projectNumber: this.projectNumber }) .then(res => { if (res.code === 200) { this.alarmConditionData = res.data; this.createAlarmTmplData(res.data); } this.$store.commit("base/setWinLoading", false); }); }, //构造报警模板数据 createAlarmTmplData(conditionData) { let res = []; this.alarmTmplData = this.alarmTmpl.data; if (this.alarmTmpl) { this.alarmTmplData.forEach(n => { // debugger; n.descr = eval(n.descr); let item = conditionData.find(m => m.tempId == n.id); if (item) { n["alarmLevel"] = item.alarmLevel; n["suggestion"] = item.suggestion; n["firstVal"] = item.firstVal; n["secondVal"] = item.secondVal; n["fourthVal"] = item.fourthVal; n["thirdVal"] = item.thirdVal; n["status"] = item.status; } }); } // console.log("this.alarmTmplData :>> ", this.alarmTmplData); }, //确定操作 onSubmit() { this.$refs["coldSet"].onSubmit(); this.$refs["endSet"].onSubmit(); if (this.isColdValid && this.isEndValid) { this.btnLoading = true; let list = this.coldConditionData .concat(this.endConditionData) .map(m => { return { tempId: m.id, alarmLevel: m.alarmLevel, firstVal: m.firstVal, secondVal: m.secondVal, thirdVal: m.thirdVal, fourthVal: m.fourthVal, status: m.status, suggestion: m.suggestion }; }); alarmService .batchEdit({ projectNumber: this.projectNumber, groupNumber: this.groupNumber, list: list }) .then(res => { if (res.code === 200) { this.$message({ message: "操作成功!", type: "success", duration: this.$baseConfig.messageDuration }); } this.btnLoading = false; }) .catch(() => { this.btnLoading = false; }); } }, coldSetSummit(val, isValid) { if (isValid) { this.isColdValid = isValid; this.coldConditionData = val; } }, endSetSummit(val, isValid) { if (isValid) { this.isEndValid = isValid; this.endConditionData = val; } }, //取消 isHide() { this.$emit("update:showAlarmSetWin", false); } } }; </script> <style lang="scss" scoped> .alarm-set { height: 600px; /deep/ .el-tabs--border-card { height: 100%; } /deep/ .el-tabs__content { height: calc(100% - 60px); } } </style>
关于表单验证:由于是动态生成的表单,所以不能按照官网提供的示例来做。
el-form中不指定验证规则,而是在el-form-item中指定,如下:
:prop="`list.${rowIndex}.${fieldArr[2*index+index+1]}`" :rules="rules.numberRule"
表单数据ruleForm中药对数据进行初始化,否则会无法自动识别数据变化,建议采用深拷贝的形式
考虑到冷站和末端这两个组件中的代码可以复用,抽取公共js文件set-mixin.js
分析界面虽然是动态的,但是总共只有几种类型,分别是:1个文本框,2个文本框,没有文本框。他们都带有建议信息这一行,所以可以用几个v-if来区分。
import { alarmLevelOptions, fieldArr } from '@/enum/alarm-enum.js'; import Regexps from '@/utils/regexp.js'; import mixinsOption from '@/mixins/mixin-options'; export default { props: { alarmTmplData: { type: Array, default: () => { return []; }, }, showAlarmSetWin: { type: Boolean, default: false, }, }, mixins: [mixinsOption], data() { return { levelOptions: alarmLevelOptions(), ruleForm: { list: [], }, rules: { numberRule: [ { pattern: Regexps.commonNumber, message: '仅支持3位数和带1位小数', trigger: 'blur', }, ], suggestion: [ { max: 10, message: '长度不能超过10个字符', trigger: 'blur' }, ], }, fieldArr, tmplData: [], }; }, computed: { activeNames() { let activeNames = this.tmplData .filter((f) => f.descParamType != 0) .map((n) => { return n.id; }); console.log('activeNames :>> ', activeNames); return activeNames; }, }, methods: { initData(type) { console.log('initData', type); if (this.alarmTmplData.length > 0) { this.tmplData = this.alarmTmplData.filter((n) => n.sys == type); this.ruleForm.list = JSON.parse(JSON.stringify(this.tmplData)); // console.log('条件设置initData :>> ', this.ruleForm.list); } }, }, };
ColdSet.vue代码:
<template> <div> <el-form :model="ruleForm" ref="ruleForm"> <el-collapse v-model="activeNames"> <el-collapse-item :name="item.id" v-for="(item,rowIndex) in ruleForm.list" :key="item.id"> <template slot="title"> <div class="header"> <el-checkbox v-model="item.status" :true-label="0" :false-label="1">{{item.name}}</el-checkbox> <el-select v-model="item.alarmLevel" size="small"> <el-option v-for="item in levelOptions" :key="item.value" :label="item.label" :value="item.value" ></el-option> </el-select> </div> </template> <div class="content vertical"> <div class="row one" v-if="item.descParamType==1"> <div class="item" v-for="(subItem,index) in item.descr" :key="index"> <label>{{subItem}}</label> <el-form-item label="大于" :prop="`list.${rowIndex}.${fieldArr[index]}`" :rules="rules.numberRule" > <el-input v-model="item[`${fieldArr[index]}`]" size="small"></el-input> </el-form-item> </div> </div> <template v-if="item.descParamType==2"> <div class="row two" v-for="(subItem,index) in item.descr" :key="index"> <div class="item"> <label>{{subItem}}</label> <el-form-item label="大于" :prop="`list.${rowIndex}.${fieldArr[2*index+index]}`" :rules="rules.numberRule" > <el-input v-model="item[`${fieldArr[2*index+index]}`]" size="small" ></el-input> </el-form-item> <el-form-item label="或小于" :prop="`list.${rowIndex}.${fieldArr[2*index+index+1]}`" :rules="rules.numberRule" > <el-input v-model="item[`${fieldArr[2*index+index+1]}`]" size="small" ></el-input> </el-form-item> </div> </div> </template> <div class="row one"> <el-form-item label="建议信息" :prop="`list.${rowIndex}.suggestion`" :rules="rules.suggestion" > <el-input v-model="item.suggestion" class="max-width" size="small" placeholder="请尽快处理" ></el-input> </el-form-item> </div> </div> </el-collapse-item> </el-collapse> </el-form> </div> </template> <script> import { alarmLevelOptions, fieldArr } from "@/enum/alarm-enum.js"; import Regexps from "@/utils/regexp.js"; import { validateVal } from "@/utils/validate-utils.js"; import { alarmService } from "@/services/cold-station-service"; import setMixin from "./set-mixin"; export default { mixins: [setMixin], watch: { showAlarmSetWin: { handler(val) { console.log("showAlarmSetWin cold :>> ", val); if (val) { this.initData(1); }
}, immediate: true } }, methods: { onSubmit() { console.log("冷站确定 :>> "); this.$refs["ruleForm"].validate(valid => { if (valid) { console.log("验证成功"); this.$emit("onSubmit", this.ruleForm.list, true); } }); } } }; </script> <style lang="scss" scoped> @import "./set.scss"; </style>
EndSet.vue:
<template> <div> <el-form :model="ruleForm" ref="ruleForm"> <el-collapse v-model="activeNames"> <el-collapse-item :name="item.id" v-for="(item,rowIndex) in ruleForm.list" :key="item.id"> <template slot="title"> <div class="header"> <el-checkbox v-model="item.status" :true-label="0" :false-label="1">{{item.name}}</el-checkbox> <el-select v-model="item.alarmLevel" size="small"> <el-option v-for="item in levelOptions" :key="item.value" :label="item.label" :value="item.value" ></el-option> </el-select> </div> </template> <div class="content vertical"> <div class="row two" v-if="item.descParamType==1"> <div class="item" v-for="(subItem,index) in item.descr" :key="index"> <label>{{subItem}}</label> <el-form-item label="大于" :prop="`list.${rowIndex}.${fieldArr[index]}`" :rules="rules.numberRule" v-if="index==0" > <el-input v-model="item[fieldArr[index]]" size="small"></el-input> </el-form-item> </div> </div> <div class="row two" v-if="item.descParamType==2"> <div class="item" v-for="(subItem,index) in item.descr" :key="index"> <label>{{subItem}}</label> <el-form-item label="大于" :prop="`list.${rowIndex}.${fieldArr[2*index+index]}`" :rules="rules.numberRule" > <el-input v-model="item[fieldArr[2*index+index]]" size="small"></el-input> </el-form-item> <el-form-item label="或小于" :prop="`list.${rowIndex}.${fieldArr[2*index+index+1]}`" :rules="rules.numberRule" > <el-input v-model="item[fieldArr[2*index+index+1]]" size="small"></el-input> </el-form-item> </div> </div> <div class="row two" v-if="item.descParamType==3"> <div class="item"> <el-form-item :label="subItem" :prop="`list.${rowIndex}.${fieldArr[index]}`" :rules="rules.numberRule" v-for="(subItem,index) in item.descr" :key="index" > <el-input v-model="item[fieldArr[index]]" size="small"></el-input> </el-form-item> </div> </div> <template v-if="item.descParamType==4"> <div class="row one" v-for="(subItem,index) in item.descr" :key="index"> <div class="item">{{subItem}}</div> <div class="item"> <el-form-item :label="index==0?'小于':'大于'" :prop="`list.${rowIndex}.${fieldArr[index]}`" :rules="rules.numberRule" > <el-input v-model="item[fieldArr[index]]" size="small"></el-input> </el-form-item> </div> </div> </template> <!-- <div class="row multi-row" v-if="item.descParamType==4"> multi-item </div>--> <div class="row one"> <el-form-item label="建议信息" :prop="`list.${rowIndex}.suggestion`" :rules="rules.suggestion" > <el-input v-model="item.suggestion" class="max-width" size="small" placeholder="请尽快处理" ></el-input> </el-form-item> </div> </div> </el-collapse-item> </el-collapse> </el-form> </div> </template> <script> import setMixin from "./set-mixin"; import { alarmService } from "@/services/cold-station-service"; export default { mixins: [setMixin], watch: { showAlarmSetWin: { handler(val) { console.log("showAlarmSetWin end:>> ", val); if (val) { this.initData(2); }
}, immediate: true } }, methods: { onSubmit() { console.log("末端确定 :>> "); this.$refs["ruleForm"].validate(valid => { if (valid) { console.log("验证成功"); this.$emit("onSubmit", this.ruleForm.list, true); } }); } } }; </script> <style lang="scss" scoped> @import "./set.scss"; </style>
由于要两个tab中都验证通过时,才提交表单,所以两个表单都要验证,只有都验证通过时在提交表单,提交表单之前,要合并表单数据再统一提交。父组件调用子组件的方法 this.$refs[“coldSet”].onSubmit();
考虑到弹窗组件的性能问题,我们可以通过将显示标识以 :showAlarmSetWin.sync=”showAlarmSetWin”这样的形式传递给子组件,子组件监听showAlarmSetWin,当弹窗显示时,加载数据并初始化,并设置属性:immediate: true,让弹窗第一次执行时也加载数据。
为了防止同一时间多次点击操作按钮“确定”,可以给按钮加上loading