Newer
Older
operation_web / src / components / common / PageForm / index.vue
<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
    }
  },
  data() {
    return {
      timer: null,
      //时间相关设置
      datePickerOptions: {
        // disabledDate (time) {
        //   //大于当前的时间都不能选 (+十分钟让点击此刻的时候可以选择到当前时间)
        //   return time.getTime() > +new Date() + 1000 * 600 * 1
        // }
      },
      pickerOptionsStart:{
        //结束时间不能大于开始时间
        disabledDate: time => {
          if(this.data.endTime){
            return time.getTime() >  new Date(this.data.endTime).getTime();
          }
        }
      },
      pickerOptionsEnd:{
        disabledDate: time => {
          return time.getTime() < new Date(this.data.startTime).getTime();
        }
      },
      //当日具体时刻
      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){
      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;
    }
    .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 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>