<template> <el-form inline ref="form" class="page-form" :class="className" :model="data" :rules="rules" :size="size" :label-width="labelWidth" > <template v-for="(item, index) in getConfigList()"> <el-form-item v-if="item.value !== 'parentId' || (item.value === 'parentId' && data[item.value] !== '-1')" :key="index" :prop="item.value" :label="item.label" :class="item.className" > <!-- solt --> <template v-if="item.type === 'slot'"> <slot :name="'form-' + item.value" /> </template> <!-- 普通输入框 --> <el-input v-if="item.type === 'input' || item.type === 'password'" v-model="data[item.value]" :show-password="item.type === 'password' ? true : false" :type="item.type" :disabled="item.disabled" :placeholder="getPlaceholder(item)" @focus="handleEvent(item.event)" /> <!-- 文本输入框 --> <el-input v-if="item.type === 'textarea'" v-model.trim="data[item.value]" :type="item.type" :disabled="item.disabled" :placeholder="getPlaceholder(item)" :show-word-limit="true" :maxlength="item.limit ? 150 : ''" :autosize="item.autosize || {minRows: 2, maxRows: 4}" @focus="handleEvent(item.event)" /> <!--级联选择框--> <el-cascader ref="cascader" popper-class="form-cascader" v-if="item.type === 'cascader'" :options="cascaderOptions" :props="cascaderProps" :clearable="item.clearable === false ? item.clearable : true" @change="handleChange" @visible-change="handleVisibleChange" :show-all-levels="true" v-model="data[item.value]" /> <!-- 头像 --> <el-upload v-if="item.type === 'avatar'" class="avatar-uploader" action="123" :show-file-list="false" :auto-upload="false" accept="image/png, image/jpeg" :on-change="fileChange" :on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload"> <img v-if="data[item.value]" :src="`data:image/png;base64,${data[item.value]}`" class="avatar"> <i v-else class="el-icon-plus avatar-uploader-icon"></i> </el-upload> <!-- 选择框 --> <el-select v-if="item.type === 'select'" v-model="data[item.value]" :disabled="item.disabled" :clearable="item.clearable" :filterable="item.filterable" :placeholder="getPlaceholder(item)" @change="handleEvent(item.event, data[item.value])" > <el-option v-for="(childItem, childIndex) in listTypeInfo[item.list]" :key="childIndex" :label="childItem.key" :value="childItem.value" /> </el-select> <!-- 树形选择器 --> <treeselect v-if="item.type === 'treeselect'" v-model="data[item.value]" :options="listTypeInfo[item.list]" :normalizer="normalizer" :placeholder="getPlaceholder(item)" @change="handleEvent(item.event)" /> <!-- 日期选择框 --> <el-date-picker v-if="item.type === 'date'" v-model="data[item.value]" :type="item.dateType" :picker-options="item.TimePickerOptions" :clearable="item.clearable" :disabled="item.disabled" :placeholder="getPlaceholder(item)" @focus="handleEvent(item.event)" /> </el-form-item> </template> </el-form> </template> <script> import Treeselect from '@riophae/vue-treeselect' import '@riophae/vue-treeselect/dist/vue-treeselect.css' export default { name: 'PageForm', components:{ Treeselect }, props: { // 自定义类名 className: { type: String }, // 表单数据 data: { type: Object }, // 相关字段 fieldList: { type: Array }, // 验证规则 rules: { type: Object }, // 相关的列表 listTypeInfo: { type: Object }, // label宽度 labelWidth: { type: String, default: '100px' }, refObj: { type: Object }, /** * 父组件传来的loading字段 * false:则由子组件控制loading,反之由父组件控制 */ loading:{ type: Boolean, default: false }, //级联数据源 cascaderOptions:{ type:Array }, /** * 级联数据配置选项 * checkStrictly为父子节点不关联,即可任选一级 * value/label为映射的显示/传递的值的字段 */ cascaderProps:{ type: Object, default: () => ({ checkStrictly: true, value: 'id', label: 'name', }) }, //表单尺寸大小 size:{ type: String, default: 'small' }, /** * 级联组件第二次编辑时会高亮第一次的选中数据(一次性加载全部数据的bug) * 通过监听该值来设置activePath及调用clearCheckedNodes方法 */ needClearNodes:{ type:Boolean, default:false } }, data() { return { timer: null } }, watch: { data: { handler: function (val) { // 将form实例返回到父级 this.$emit('update:refObj', this.$refs.form) }, deep: true, // 深度监听 }, //是否需要清理节点高亮 needClearNodes(val){ if(val){ this.$refs.cascader[0].$refs.panel.activePath = []; this.$refs.cascader[0].$refs.panel.clearCheckedNodes(); } }, 'data.area': { handler(newVal, oldVal) { if(this.$refs.cascader) { let children = this.$refs.cascader[0].getCheckedNodes(); if(children.length && children[0].children.length < 1) { this.$refs.cascader[0].dropDownVisible = false; } } }, deep: true, // 深度监听 } }, mounted () { // 将form实例返回到父级 this.$emit('update:refObj', this.$refs.form) }, methods: { // 获取字段列表 getConfigList () { return this.fieldList.filter(item => !item.hasOwnProperty('show') || (item.hasOwnProperty('show') && item.show)) }, // 得到placeholder的显示 getPlaceholder (row) { let placeholder if (row.type === 'input' || row.type === 'textarea' || row.type === 'password') { placeholder = '请输入' + row.label } else if (row.type === 'select' || row.type === 'time' || row.type === 'date' || row.type === 'treeselect') { placeholder = '请选择' + row.label } else { placeholder = row.label } return placeholder }, // 绑定的相关事件 handleEvent (evnet) { this.$emit('handleEvent', evnet) }, // 派发按钮点击事件 handleClick (event, data) { this.$emit('handleClick', event, data) }, //上传头像 fileChange(file){ let reader = new FileReader() reader.readAsDataURL(file.raw) reader.onload=()=>{ this.$emit('showCropper',reader.result) } }, //上传获取ID handleAvatarSuccess(res, file){ console.log(file) }, //上传 beforeAvatarUpload(file){ const isJPG = file.type === 'image/jpeg'; if(!isJPG){ this.$message.error('上传头像图片只能是 JPG 格式!'); } return isJPG }, //转换数据结构 normalizer(node){ if(node.children && !node.children.length){ delete node.children } return { id: node.roleId || node.orgId, label: node.roleName || node.name, children: node.children } }, //级联选中节点时触发事件 handleChange(val){ this.data.area = val.length ? val[val.length-1] : '' }, //级联下拉框出现/隐藏触发事件 handleVisibleChange(visible) { if(visible) { this.timer = setInterval(() =>{ NodeList.prototype.forEach = Array.prototype.forEach document.querySelectorAll(".el-cascader-node__label").forEach(el => { el.onclick = function() { if (this.previousElementSibling) this.previousElementSibling.click(); }; }); },1000); }else{ clearInterval(this.timer); } } }, destroyed() { if(this.timer){ clearInterval(this.timer); } } } </script> <style lang="scss" scoped> .page-form{ @include fj(); flex-wrap: wrap; /deep/ .el-form-item{ display: flex; width: 100%; margin-right: 0; .el-form-item__content{ @include fa(); flex: 1; .el-input, .el-select, .el-textarea{ width: 100%; } .el-input-number{ .el-input{ width: inherit; } } } .el-form-item__label{ text-align: right !important; } } /deep/ .el-form-block{ width: 100%; align-items: center; .el-form-item__content{ display: inline-block; .el-textarea{ width: 100%; } } } } .page-form-block{ /deep/ .el-form-item{ display: block; .el-form-item__content{ display: inline-block; .el-input, .el-select, .el-textarea{ width: inherit; } .el-input-number{ .el-input{ width: inherit; } } } } .el-form-block{ display: block; width: 100%; .el-form-item__content{ .el-textarea{ width: 100%; } } } } .avatar-uploader{ .avatar{ height: 60px; max-width: 100%; } .avatar-uploader-icon{ width: 60px; height: 60px; line-height: 60px; font-size: 28px; color: #8c939d; text-align: center; } } .avatar-uploader .el-upload { border: 1px dashed #d9d9d9; border-radius: 6px; cursor: pointer; position: relative; overflow: hidden; } .avatar-uploader .el-upload:hover { border-color: #409EFF; } /****treeselect样式重置****/ /deep/ .vue-treeselect{ .vue-treeselect__control{ padding-left: 10px; border: 1px solid rgb(4, 51, 52); background-color: transparent; } .vue-treeselect__single-value{ color: #fff; } .vue-treeselect__menu{ color: #fff; z-index: 99999; background-color: #3c3939; border: 1px solid rgb(4, 51, 52); } .vue-treeselect__menu-container{ z-index: 99999; } .vue-treeselect__option--highlight{ background: rgb(4, 51, 52); } .vue-treeselect__checkbox--checked { border-color: #039be5; background: #000; } } /deep/ .vue-treeselect:not(.vue-treeselect--disabled):not(.vue-treeselect--focused) .vue-treeselect__control:hover{ border: 1px solid rgb(4, 51, 52); } /deep/ .vue-treeselect--single .vue-treeselect__option--selected{ color: #efe201 !important; background: rgb(4, 51, 52); } /****级联样式重置****/ /deep/ .el-cascader{ width: 100%; } </style>