<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]" :type="item.type" :disabled="item.disabled" :clearable="item.clearable === false ? item.clearable : true" :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" :clearable="item.clearable === false ? item.clearable : 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 === false ? item.clearable : true" :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]" value-format="yyyy-MM-dd HH:mm:ss" :picker-options=" item.datePickerOptions ? item.datePickerOptions === 'pickerOptionsStart' ? pickerOptionsStart : pickerOptionsEnd : datePickerOptions " :type="item.dateType" default-time="['00:00:00', '23:59:59']" :disabled="item.disabled" :clearable="item.clearable === false ? item.clearable : true" :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 }, /** * 限制选择的时间区段 * false:不限制 * ture:限制的时间区段,默认为半年 */ limitTime: { type: Boolean, default: false }, //限制选择的时间区段值,配合limitTime使用 limitTimePeriod: { type: Number, default: 365 } }, data() { return { timer: null, //时间相关设置 datePickerOptions: { // disabledDate (time) { // //大于当前的时间都不能选 (+十分钟让点击此刻的时候可以选择到当前时间) // return time.getTime() > +new Date() + 1000 * 600 * 1 // } }, pickerOptionsStart: { //结束时间不能大于开始时间 disabledDate: time => { if (this.data.endTime) { let halfYear = (this.limitTimePeriod / 2) * 24 * 3600 * 1000; let endTime = new Date(this.data.endTime).getTime(); let limitTime = endTime - halfYear; let result = this.limitTime ? limitTime > time.getTime() || time.getTime() > endTime : time.getTime() > endTime; return result; } } }, pickerOptionsEnd: { disabledDate: time => { if (this.data.startTime) { let halfYear = (this.limitTimePeriod / 2) * 24 * 3600 * 1000; let startTime = new Date(this.data.startTime).getTime(); let limitTime = startTime + halfYear; let result = this.limitTime ? limitTime < time.getTime() || time.getTime() < startTime : time.getTime() < startTime; return result; } } }, //当日具体时刻 defaultTime: ["00:00:00", "23:59:59"] }; }, 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, node) { if (this.$refs.cascader && Array.isArray(this.$refs.cascader)) { const checkedNode = this.$refs.cascader[0].getCheckedNodes(); const nodeObj = checkedNode[0]; this.$emit("currentAreaJwd", nodeObj); } this.data.area = val.length ? val[val.length - 1] : ""; }, //级联下拉框出现/隐藏触发事件 handleVisibleChange(visible) { let that = this; 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(); }; el.ondblclick = function() { that.$refs.cascader[0].dropDownVisible = false; }; }); }, 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; } .el-icon-date:before { color: #fff; } } /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 var(--selecthover); background-color: transparent; } .vue-treeselect__single-value { color: var(--white); } .vue-treeselect__menu { color: var(--white); z-index: 99999; background-color: var(--shouselect); border: 1px solid var(--selecthover); } .vue-treeselect__menu-container { z-index: 99999; } .vue-treeselect__option--highlight { background: var(--selecthover); } .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 var(--selecthover); } /deep/ .vue-treeselect--single .vue-treeselect__option--selected { color: var(--white) !important; background: var(--selecthover); } /****级联样式重置****/ /deep/ .el-cascader { width: 100%; } </style>