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 ? item.maxlength : 0"
          :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;
}
/****级联样式重置****/
/deep/ .el-cascader {
  width: 100%;
}
</style>