vue2企业级项目(七)

vue2企业级项目(七)

组件封装(一)

  • src目录下创建components/index.js

    const components = require.context("./modules", true, /index\.js$/);
    
    export default {
      install: (Vue) => {
        components.keys().forEach((key) => {
          let component = components(key).default;
          component && Vue.component(component.name, component);
        });
      },
    };
    
    
  • 创建src/components/modules目录

1、form
  • modules下创建modules/x-form/index.js

    import XForm from "./index.vue";
    export default XForm;
    
    
  • 创建modules/x-form/index.vue

    <script>
    import { cloneDeep } from "lodash";
    import {
      getDefaultValue,
      getPlaceholder,
      getRules,
      createInput,
    } from "../../createInput.js";
    
    /**
     * 配置说明文档
     * @param { Array } columns: 表单配置项
     * @param { String } labelWidth: 设置el-form-item的label宽度。(优先级低于item)
     * @param { Number } contentSpan: 设置el-form-item的内容占比宽度
     * @param { Object } defaultValue: 设置el-form的默认值。(级别最高)
     * @param { Object } rules: 设置el-item的校验规则。(优先级低于item)
     * @param { Number } gutter: 设置el-row的gutter
     * @param { Number, Array } grid: 设置el-col的span,如果没有,则不创建el-col
     * @param { Boolean } disabled: 表单统一仅用。(优先级低于item)
     *
     * @param { Boolean } ctrl: 是否显示表单控制按钮
     * @param { String } ctrlSpan: 表单控制器占比,默认跟随grid
     * @param { Boolean } submitBtn: 是否显示提交按钮
     * @param { String } submitText: 提交按钮内容
     * @param { String } submitIcon: 提交按钮icon
     * @param { Boolean } submitLoading: 提交按钮的loading开启
     * @param { Boolean } resetBtn: 是否显示重置按钮
     * @param { String } resetText: 重置按钮内容
     * @param { String } resetIcon: 重置按钮icon
     * @param { Boolean } resetLoading: 重置按钮的loading开启
     *
     * @event { Function } submit: 表单提交方法,返回表单值和校验结果
     * @event { Function } reset: 表单重置方法
     * @event { Function } clear: 表单清空方法
     * @event { Function } getFormByKey: 获取表单单值方法
     * @event { Function } getForm: 获取表单全值方法
     * @event { Function } setForm: 设置表单单值方法
     */
    
    /**
     * 表单配置项 columns-item
     * @param { Object } form: 单独作用form的配置
     * @param { Object } table: 单独作用table的配置
     * @param { Object } search: 单独作用search的配置
     * 以上的配置和下面一样,但优先级高
     *
     *
     * @param { String } type: 表单控件的类型。(参考element-ui的控件类型)
     * @param { String } label: 字段名称
     * @param { String } prop: 字段绑定key
     * @param { Any } default: 控件默认值。(无法被重置)
     * @param { String } placeholder: 提示信息
     * @param { Array } rules: 校验规则。(级别最高)
     * @param { Number } order: 排序序号。(大到小)
     * @param { Number } span: 当前表单控件col占比。(级别高)
     * @param { Object } col: el-col的其他配置。(级别最高)
     * @param { String } labelWidth: label的宽度。(级别最高)
     * @param { Number } contentSpan: 内容占比宽度
     * @param { Boolean } required: 是否必填
     * @param { Boolean } disabled: 是否禁用。(权限最高)
     *
     * @param { Object } children: 子节点, (一个el-form-item里面多个控件)
     * @param { Number } gutter: 子节点的el-row的gutter。(仅在设置children后生效)
     * @param { Number, Array } grid: 子节点的栅格布局col排布
     *
     * @param { Object } className: 表单控件的类名,对象的形式:{ box: true }
     * @param { Object } style: 样式控制
     * @param { Object } attrs: element-ui表单控件的attribute,参考element-ui文档。
     * @param { Object } props: element-ui表单控件的props。(有的时候文档上写的时attrs设置,其实时props设置。所以不生效的时候可以尝试一下)
     * @param { Object } on: 表单控件的绑定事件。{ input: (value) => console.log(value) }
     * @param { Array } options: radio,checkbox, select, cascader,
     * @param { String } text: button按钮的文字内容
     * @param { String } btnType: button按钮的类型
     * @param { String } btnIcon: button的icon
     * @param { String } marking: button的标记
     * @param { Boolean } hasFilter: 是否进行筛选,只有存在options才可以进行
     *
     * @param { Function } slotDefault: el-table-column 的 default的插槽
     * @param { Function } slotHeader: el-table-column 的 header的插槽
     */
    
    export default {
      name: "XForm",
      props: {
        labelWidth: String,
        columns: {
          type: Array,
          default: () => [],
        },
        defaultValue: {
          type: Object,
          default: () => ({}),
        },
        contentSpan: {
          type: Number,
          default: 24,
        },
        rules: {
          type: Object,
          default: () => ({}),
        },
        gutter: {
          type: Number,
          default: 0,
        },
        grid: [Number, Array],
        disabled: Boolean,
        ctrl: {
          type: [Boolean, String],
          default: "left",
        },
        ctrlSpan: {
          type: Number,
          default: null,
        },
        submitBtn: {
          type: Boolean,
          default: true,
        },
        submitText: {
          type: String,
          default: "提交",
        },
        submitIcon: {
          type: String,
          default: "",
        },
        submitLoading: {
          type: Boolean,
          default: false,
        },
        resetBtn: {
          type: Boolean,
          default: true,
        },
        resetText: {
          type: String,
          default: "重置",
        },
        resetIcon: {
          type: String,
          default: "",
        },
        resetLoading: {
          type: Boolean,
          default: false,
        },
      },
      render(h) {
        return h(
          "el-form",
          {
            attrs: { ...this.$attrs },
            props: {
              model: this.form,
            },
            style: {
              padding: `0 ${this.gutter / 2}px`,
            },
            ref: "form",
            nativeOn: {
              submit(e) {
                e.preventDefault();
                e.stopPropagation();
              },
            },
          },
          [this.renderRow(h, this.fields, this.gutter), this.$slots.default],
        );
      },
      data() {
        return {
          form: {},
          fields: [],
          sLoading: false,
          rLoading: false,
        };
      },
      watch: {
        columns: {
          deep: true,
          handler() {
            this.init();
          },
        },
        defaultValue: {
          deep: true,
          handler(value) {
            this.form = cloneDeep(value);
            this.init();
          },
        },
      },
      created() {
        this.form = cloneDeep(this.defaultValue);
        this.init();
      },
      methods: {
        init() {
          let form = cloneDeep(this.form);
    
          // 获取配置属性
          this.fields = this.handleFieldsChild(form, this.columns);
    
          // 添加校验按钮
          if (this.ctrl) {
            let _this = this;
    
            this.fields.push({
              span: _this.ctrlSpan,
              render: ({ h }) => {
                return h(
                  "div",
                  {
                    class: { formBtns: true },
                    style: {
                      textAlign: this.ctrl,
                    },
                  },
                  [
                    _this.submitBtn &&
                      createInput(
                        h,
                        {
                          type: "button",
                          btnType: "primary",
                          text: _this.submitText,
                          on: {
                            click: _this.submit,
                          },
                          props: {
                            icon: _this.submitIcon,
                            loading: _this.sLoading,
                            disabled: _this.disabled,
                          },
                        },
                        _this,
                      ),
                    _this.resetBtn &&
                      createInput(
                        h,
                        {
                          type: "button",
                          text: _this.resetText,
                          on: {
                            click: _this.reset,
                          },
                          props: {
                            icon: _this.resetIcon,
                            loading: _this.rLoading,
                            disabled: _this.disabled,
                          },
                        },
                        _this,
                      ),
                  ],
                );
              },
            });
          }
    
          this.form = { ...form };
        },
    
        handleFieldsChild(form, columns, parent = null) {
          let fields = columns.filter((item) => item?.isShow !== false);
          fields = fields.map((item) => {
            let formItem = { ...item, ...item?.form };
            delete formItem.table;
            delete formItem.search;
            delete formItem.form;
            delete formItem.dialog;
    
            if (formItem.children && Array.isArray(item.children)) {
              formItem.children = this.handleFieldsChild(
                form,
                formItem.children,
                formItem,
              );
            }
    
            if (parent && !formItem.label) formItem.label = parent?.label;
            getDefaultValue(form, formItem);
            getPlaceholder(formItem);
            getRules(formItem, this.rules);
            if (parent) {
              formItem.grid = parent?.grid;
              delete formItem.label;
            }
    
            return formItem;
          });
    
          fields = fields.sort((a, b) => (b?.order || 0) - (a?.order || 0));
    
          return fields;
        },
    
        renderRow(h, fields, gutter, isChild = false) {
          let style = null;
          if (isChild) {
            style = {
              position: "relative",
              left: `-${gutter / 2}px`,
              marginLeft: "0",
              marginRight: "0",
              width: `calc(100% + ${gutter}px)`,
            };
          }
    
          return h(
            "el-row",
            { attrs: { gutter }, style },
            this.renderItem(h, fields, isChild),
          );
        },
    
        renderItem(h, fields, isChild) {
          return fields.map((item, index) =>
            this.renderCol(h, { ...item, isChild }, index),
          );
        },
    
        renderCol(h, item, index) {
          delete item.order;
    
          const gridConfig = item.grid || this.grid;
          let grid = 0;
          if (typeof gridConfig === "number") grid = gridConfig;
          if (Array.isArray(gridConfig))
            grid = this.grid[index % gridConfig?.length];
          let span = item.span || item?.col?.span || grid;
    
          if (span) {
            return h(
              "el-col",
              {
                class: { "form-item-col": true },
                attrs: { span, ...item.col },
              },
              [this.renderFormItem(h, item)],
            );
          } else {
            return this.renderFormItem(h, item);
          }
        },
    
        renderFormItem(h, item) {
          let labelWidth = item?.labelWidth || this.labelWidth || "0px";
          let isChild = item.isChild;
          let hasChild = Array.isArray(item.children);
          if (isChild) labelWidth = "0px";
          delete item?.labelWidth;
          delete item?.isChild;
    
          let childDom = null;
          if (hasChild) {
            const gutter = item?.gutter || this.gutter;
            const children = item?.children || [];
            childDom = this.renderRow(h, children, gutter, true);
          } else {
            childDom = h(
              "el-col",
              {
                attrs: {
                  span: isChild ? 24 : item.contentSpan || this.contentSpan,
                },
              },
              [createInput(h, { ...item }, this)],
            );
          }
    
          delete item?.children;
          delete item?.gutter;
    
          let itemRender = null;
          if (item.render && typeof item.render === "function") {
            itemRender = item.render({
              h: this.getHypeScript(),
              value: this.form[item.prop],
              item,
              _this: this,
            });
          }
          delete item.render;
    
          return h(
            "el-form-item",
            {
              style: { marginBottom: hasChild ? "0px" : "22px" },
              attrs: { ...item, labelWidth },
            },
            [childDom, itemRender],
          );
        },
    
        // 表单方法
        getHypeScript() {
          return this.$parent.$createElement;
        },
    
        submit() {
          return new Promise((resolve) => {
            this.$refs.form.validate((valid) => {
              if (this.submitLoading && valid) this.sLoading = true;
              const _this = this;
              const callback = () => {
                _this.sLoading = false;
              };
              this.$emit("submit", this.getForm(), valid, callback);
              resolve({ form: this.getForm(), valid, callback });
            });
          });
        },
    
        reset() {
          return new Promise((resolve) => {
            this.$refs.form.resetFields && this.$refs.form.resetFields();
            this.form = {};
            this.init();
            if (this.resetLoading) this.rLoading = true;
            const callback = () => {
              this.rLoading = false;
            };
            this.$emit("reset", callback);
            resolve(callback);
          });
        },
    
        clear() {
          this.$refs.form.clearValidate && this.$refs.form.clearValidate();
        },
    
        getFormByKey(key) {
          return this.form[key];
        },
    
        getForm() {
          return { ...this.form };
        },
    
        setForm(key, value) {
          key && (this.form[key] = value);
        },
      },
    };
    </script>
    
    <style lang="less" scoped>
    .el-readonly-input {
      .el-input__inner {
        border: none;
      }
    
      .el-input__inner:focus {
        border-color: #dcdfe6;
      }
    }
    
    .form-item-col {
      .el-form-item {
        margin-bottom: 22px;
      }
    }
    </style>
    
    
  • src/components创建createInput.js

    import { isEqual } from "lodash";
    
    export const tagsMenu = new Map([
      ["text", "el-input"],
      ["xnumber", "x-input-number"],
      ["button", "el-button"],
      ["input", "el-input"],
      ["password", "el-input"],
      ["textarea", "el-input"],
      ["number", "el-input-number"],
      ["radio", "el-radio-group"],
      ["radio-item", "el-radio"],
      ["radio-button", "el-radio-button"],
      ["checkbox", "el-checkbox-group"],
      ["checkbox-item", "el-checkbox"],
      ["checkbox-button", "el-checkbox-button"],
      ["select", "el-select"],
      ["select-group", "el-option-group"],
      ["select-item", "el-option"],
      ["cascader", "el-cascader"],
      ["switch", "el-switch"],
      ["slider", "el-slider"],
      ["time-select", "el-time-select"],
      ["time", "el-time-picker"],
      ["date", "el-date-picker"],
      ["rate", "el-rate"],
      ["color", "el-color-picker"],
      ["transfer", "el-transfer"],
    ]);
    
    export const defaultValuesMenu = new Map([
      ["radio", ""],
      ["checkbox", []],
      ["text", ""],
      ["input", ""],
      ["password", ""],
      ["textarea", ""],
      ["number", ""],
      ["xnumber", ""],
      ["select", null],
      ["cascader", null],
      ["switch", ""],
      ["slider", 0],
      ["time-select", ""],
      ["time", ""],
      ["date", ""],
      ["rate", null],
      ["color", null],
      ["transfer", []],
    ]);
    
    export function getDefaultValue(form, defaultValue = {}, item) {
      if (!item.prop) return;
    
      let value = defaultValuesMenu.get(item.type);
      if (item?.props?.multiple || item?.props?.isRange) value = [];
    
      form[item.prop] =
        form[item.prop] || item.default || defaultValue[item.prop] || value;
    }
    
    export function getPlaceholder(item) {
      const inputTypes = ["input", "password", "textarea", "number", "xnumber"];
      const selectType = ["select", "cascader", "time-select", "time", "date"];
      const dateRangeTypes = ["datetimerange", "daterange", "monthrange"];
    
      item.attrs = item.attrs || {};
      item.props = item.props || {};
    
      // 添加date-range的标识
      if (item.props?.type && dateRangeTypes.includes(item.props.type)) {
        item.props.isRange = true;
      }
    
      if (item.placeholder) {
        item.attrs.placeholder = item.placeholder;
      } else {
        if (inputTypes.includes(item.type))
          item.attrs.placeholder = `请输入${item.label}`;
        if (selectType.includes(item.type))
          item.attrs.placeholder = `请选择${item.label}`;
        if (item.props.isRange) {
          item.props.startPlaceholder = `请选择开始${item.label}`;
          item.props.endPlaceholder = `请选择结束${item.label}`;
        }
      }
    
      return item;
    }
    
    export function getRules(item, rules) {
      if (item.rules) return item;
      else if (rules && rules[item.prop]) {
        item.rules = rules[item.prop];
      } else if (item.required) {
        item.rules = [
          {
            required: true,
            message:
              item.attrs.placeholder ||
              item.props.startPlaceholder ||
              item.props.endPlaceholder,
            trigger: "blur",
          },
        ];
      }
    
      return item;
    }
    
    export function createInput(h, item, _this) {
      item.attrs = {
        type: item.type,
        options: item.type === "cascader" ? item.options : null,
        ...item.attrs,
      };
    
      item.props = {
        disabled: item.disabled !== undefined ? item.disabled : _this.disabled,
        ...item.props,
      };
    
      if (item.btnType) item.attrs.type = item.btnType;
      if (item.btnIcon) item.attrs.icon = item.btnIcon;
      if (item.marking) item.attrs.marking = item.marking;
    
      item.style = {
        width: item.type !== "button" ? "100%" : null,
        ...item.style,
      };
    
      let {
        type,
        prop,
        className = {},
        style = {},
        attrs = {},
        props = {},
        on = {},
        text = "",
      } = item;
    
      const config = {
        type,
        prop,
        class: className,
        style,
        attrs,
        props,
        on,
        text,
        children: generateOptions(h, item) || [],
      };
    
      if (type === "time-select" || type === "date") {
        let minTimeKey = props?.pickerOptions?.minTimeKey;
        let maxTimeKey = props?.pickerOptions?.maxTimeKey;
    
        if (minTimeKey) {
          if (type === "time-select") {
            config.props = {
              ...config.props,
              pickerOptions: {
                ...config.props.pickerOptions,
                minTime: _this.form[minTimeKey],
              },
            };
          } else {
            let disabledDate = config.props.pickerOptions.disabledDate;
            if (typeof disabledDate === "function") {
              config.props = {
                ...config.props,
                pickerOptions: {
                  ...config.props.pickerOptions,
                  disabledDate: (time) =>
                    disabledDate(time, _this.form[minTimeKey]),
                },
              };
            }
          }
        }
    
        if (maxTimeKey) {
          if (type === "time-select") {
            config.props = {
              ...config.props,
              pickerOptions: {
                ...config.props.pickerOptions,
                maxTime: _this.form[maxTimeKey],
              },
            };
          } else {
            let disabledDate = config.props.pickerOptions.disabledDate;
            if (typeof disabledDate === "function") {
              config.props = {
                ...config.props,
                pickerOptions: {
                  ...config.props.pickerOptions,
                  disabledDate: (time) =>
                    disabledDate(time, _this.form[maxTimeKey]),
                },
              };
            }
          }
        }
      }
    
      if (type === "text") {
        config.class["el-readonly-input"] = true;
        config.attrs.readonly = true;
      }
    
      return generateTag(h, config, _this);
    }
    
    export function generateOptions(h, item) {
      const canTag = ["radio", "checkbox", "select"];
      if (!canTag.includes(item.type)) return null;
    
      // 后续需要添加字典
      const options = item?.options || [];
    
      return (options || []).map((option) => {
        let type = `${item.type}-${item.button ? "button" : "item"}`;
        if (option.options) {
          const cloneItem = { ...item };
          cloneItem.options = option.options;
          type = `${item.type}-group`;
    
          return h(
            tagsMenu.get(type),
            {
              attrs: {
                label: option.label,
              },
            },
            generateOptions(h, cloneItem),
          );
        }
    
        return h(
          tagsMenu.get(type),
          {
            attrs: {
              label: item.type === "select" ? option.label : option.value,
              value: item.labelInValue ? option : option.value,
            },
          },
          [item.type === "select" ? null : [option.label]],
        );
      });
    }
    
    export function generateTag(h, config, _this) {
      config.props.value = _this.form[config.prop];
      const inputEvent = config.on?.input;
      const clickEvent = config.on?.click;
      config.on = {
        ...config.on,
        input: (value) => {
          value = formatDateValue(value, config);
          if (!isEqual(_this.form[config.prop], value)) {
            _this.form[config.prop] = value;
            typeof inputEvent === "function" && inputEvent(value, config);
          }
        },
        click: (event) => {
          typeof clickEvent === "function" && clickEvent(event, config);
        },
      };
    
      config.nativeOn = {
        keydown: (e) => {
          if (e.keyCode === 13 && this.enterSubmit && config.type !== "textarea") {
            _this.submit();
          }
        },
      };
    
      return h(tagsMenu.get(config.type), config, [
        ...config.children,
        config.text,
      ]);
    }
    
    export function formatDateValue(value, config) {
      if ((config.type === "time" || config.type === "date") && !value) {
        return config.props.isRange ? ["", ""] : "";
      }
      return value;
    }
    
    
  • 使用案例

    <template>
      <div>
        <!-- 基础表单 -->
        <hr />
        <x-form :columns="columns1" labelWidth="100px" :ctrl="false"></x-form>
    
        <!-- 禁用表单 -->
        <hr />
        <x-form
          :columns="columns1"
          labelWidth="100px"
          :ctrl="false"
          disabled
        ></x-form>
    
        <!-- 表单布局 -->
        <hr />
        <x-form
          :columns="columns2"
          labelWidth="100px"
          :ctrl="false"
          :gutter="10"
          :grid="8"
          label-suffix=":"
        ></x-form>
    
        <!-- 灵活表单布局 -->
        <hr />
        <x-form
          :columns="columns3"
          labelWidth="100px"
          :gutter="8"
          label-suffix=":"
          :contentSpan="20"
          :submitLoading="true"
          @submit="handleSubmit"
        ></x-form>
    
        <!-- 自定义表单元素 -->
        <hr />
        <x-form
          :columns="columns4"
          :submitLoading="true"
          labelWidth="100px"
          @submit="handleSubmit"
        ></x-form>
      </div>
    </template>
    
    <script>
    import { cloneDeep } from "lodash";
    
    export default {
      data() {
        return {
          columns1: [
            { type: "text", label: "标题", prop: "title", default: "基础表单" },
            { type: "input", label: "用户名", prop: "username" },
            { type: "password", label: "密码", prop: "password" },
            { type: "textarea", label: "信息", prop: "info", disabled: false },
          ],
          columns2: [
            {
              type: "text",
              label: "标题",
              prop: "title",
              default: "表单布局",
              span: 24,
            },
            { type: "input", label: "输入1", prop: "value1" },
            { type: "input", label: "输入2", prop: "value2" },
            { type: "input", label: "输入3", prop: "value3" },
            { type: "input", label: "输入4", prop: "value4" },
            { type: "input", label: "输入5", prop: "value5" },
            { type: "input", label: "输入6", prop: "value6" },
          ],
          columns3: [
            { type: "text", label: "标题", prop: "title", default: "灵活表单" },
            {
              type: "input",
              label: "输入1",
              prop: "value1",
            },
            {
              type: "input",
              label: "输入2",
              prop: "value2",
              required: true,
            },
            {
              label: "输入3",
              required: true,
              children: [
                { type: "input", prop: "value31", required: true, span: 10 },
                { type: "input", prop: "value32", required: true, span: 10 },
                { type: "input", prop: "value33", required: true, span: 10 },
                { type: "input", prop: "value34", required: true, span: 10 },
                { type: "input", prop: "value35", required: true, span: 10 },
                { type: "input", prop: "value36", required: true, span: 10 },
              ],
            },
            {
              type: "input",
              label: "输入4",
              prop: "value4",
              required: true,
            },
            {
              type: "input",
              label: "输入5",
              prop: "value5",
              contentSpan: 24,
              required: true,
            },
            {
              type: "input",
              label: "输入6",
              prop: "value6",
              required: true,
            },
            {
              label: "时间选择",
              gutter: 10,
              required: true,
              children: [
                {
                  type: "time-select",
                  label: "开始时间",
                  prop: "startTime",
                  required: true,
                  span: 10,
                  props: {
                    pickerOptions: {
                      start: "08:30",
                      step: "00:15",
                      end: "18:30",
                      maxTimeKey: "endTime",
                    },
                  },
                },
                {
                  type: "time-select",
                  label: "结束时间",
                  prop: "endTime",
                  required: true,
                  span: 10,
                  props: {
                    pickerOptions: {
                      start: "08:30",
                      step: "00:15",
                      end: "18:30",
                      minTimeKey: "startTime",
                    },
                  },
                },
              ],
            },
            {
              label: "日期选择",
              gutter: 10,
              required: true,
              children: [
                {
                  type: "date",
                  label: "开始日期",
                  prop: "startDate",
                  required: true,
                  span: 10,
                  props: {
                    pickerOptions: {
                      maxTimeKey: "endDate",
                      disabledDate: (time, date) => {
                        if (!date) return false;
                        return time.getTime() > new Date(date).getTime();
                      },
                    },
                  },
                },
                {
                  type: "date",
                  label: "结束日期",
                  prop: "endDate",
                  required: true,
                  span: 10,
                  props: {
                    pickerOptions: {
                      minTimeKey: "startDate",
                      disabledDate: (time, date) => {
                        if (!date) return false;
                        return time.getTime() < new Date(date).getTime();
                      },
                    },
                  },
                },
              ],
            },
            {
              label: "动态1",
              gutter: 10,
              marking: "1",
              children: [
                { type: "input", prop: "active1", span: 20 },
                {
                  type: "button",
                  btnType: "primary",
                  btnIcon: "el-icon-plus",
                  span: 2,
                  marking: "1",
                  on: {
                    click: this.addForm,
                  },
                },
                {
                  type: "button",
                  btnIcon: "el-icon-delete-solid",
                  span: 2,
                  marking: "1",
                  on: {
                    click: this.delForm,
                  },
                },
              ],
            },
          ],
          activeItem: {
            label: "动态",
            gutter: 10,
            children: [
              { type: "input", prop: "active", span: 20 },
              {
                type: "button",
                btnType: "primary",
                btnIcon: "el-icon-plus",
                span: 2,
                on: {
                  click: this.addForm,
                },
              },
              {
                type: "button",
                btnIcon: "el-icon-delete-solid",
                span: 2,
                on: {
                  click: this.delForm,
                },
              },
            ],
          },
          columns4: [
            { type: "text", label: "标题", prop: "title", default: "灵活表单" },
            {
              type: "date",
              label: "日期",
              prop: "date",
              props: { valueFormat: "yyyy-MM-DD" },
            },
            {
              label: "自定义",
              prop: "input",
              render: ({ value, _this }) => {
                return (
                  <el-input
                    value={value}
                    onInput={(value) => {
                      _this.setForm("input", value);
                    }}
                  />
                );
              },
            },
          ],
        };
      },
      methods: {
        handleSubmit(form, valid, callback) {
          console.log(form, valid);
          setTimeout(callback, 1000);
      },
    
        addForm(e, item) {
          let marking = item.attrs.marking;
          let newMarking = Number(marking) + 1;
          let columns = cloneDeep(this.columns3);
          let index =
            this.columns3.findIndex((formItem) => formItem?.marking === marking) +
            1;
          let addItem = cloneDeep(this.activeItem);
    
          addItem.label += newMarking;
          addItem.children[0].prop += newMarking;
          addItem.children[1].marking = newMarking;
          addItem.children[2].marking = newMarking;
          addItem.marking = newMarking;
    
          columns.splice(index, 0, addItem);
          this.columns3 = columns;
        },
    
        delForm(e, item) {
          let marking = item.attrs.marking;
          if (marking === "1") return;
          let columns = cloneDeep(this.columns3);
          let index = this.columns3.findIndex(
            (formItem) => formItem?.marking === marking,
          );
          columns.splice(index, 1);
          this.columns3 = columns;
        },
      },
    };
    </script>
    
    
2、table
  • 创建components/modules/x-table/index.js

    import XTable from "./index.vue";
    export default XTable;
    
    
  • 创建components/modules/x-table/index.vue

    <script>
    import { cloneDeep, isMatch } from "lodash";
    import { delEmptyKey } from "@/utils/utils";
    import dayjs from "dayjs";
    
    /**
     * 配置说明文档
     * @param { Boolean } autoHeight: 是否自动填充表格高度
     * @param { Array } columns: 表格的配置项,见下面详细说明
     * @param { Array } tableData: 表格的值
     * @param { Object } search: 表单默认搜索值、或者api默认入参
     * @param { Function } api: 接口
     * @param { String } itemAlign: 单元格align风格(left、center、right)
     * @param { String } emptyText: 无数据描述
     * @param { Any } emptyImage: 无数据图片展示地址
     * @param { Number } emptyImageSize: 无数据图片大小
     * @param { Boolean } hasPagination: 是否显示分页
     * @param { String } paginationLayout: 分页显示内容
     * @param { Boolean } multiple: 是否多选
     * @param { Boolean } hasRadio: 是否单选
     *
     * @event { function } toggleSelection:设置默认多选
     * @event { function } setCurrent: 设置单选
     * @event { function } clearFilter:清空筛选项
     * @event { function } getSelection: 获取选中结果
     * @event { function } getTableData: 获取表格值
     */
    
    /**
     * 表单配置项 columns-item
     * @param { Object } form: 单独作用form的配置
     * @param { Object } table: 单独作用table的配置
     * @param { Object } search: 单独作用search的配置
     * 以上的配置和下面一样,但优先级高
     *
     *
     * @param { String } type: 表单控件的类型。(参考element-ui的控件类型)
     * @param { String } label: 字段名称
     * @param { String } prop: 字段绑定key
     * @param { Any } default: 控件默认值。(级别最高)
     * @param { String } placeholder: 提示信息
     * @param { Array } rules: 校验规则。(级别最高)
     * @param { Number } order: 排序序号。(大到小)
     * @param { Number } span: 当前表单控件col占比。(级别高)
     * @param { Object } col: el-col的其他配置。(级别最高)
     * @param { String } labelWidth: label的宽度。(级别最高)
     * @param { Number } contentSpan: 内容占比宽度
     * @param { Boolean } required: 是否必填
     * @param { Boolean } disabled: 是否禁用。(权限最高)
     *
     * @param { Object } children: 子节点, (一个el-form-item里面多个控件)
     * @param { Number } gutter: 子节点的el-row的gutter。(仅在设置children后生效)
     * @param { Number, Array } grid: 子节点的栅格布局col排布
     *
     * @param { Object } className: 表单控件的类名,对象的形式:{ box: true }
     * @param { Object } style: 样式控制
     * @param { Object } attrs: element-ui表单控件的attribute,参考element-ui文档。
     * @param { Object } props: element-ui表单控件的props。(有的时候文档上写的时attrs设置,其实时props设置。所以不生效的时候可以尝试一下)
     * @param { Object } on: 表单控件的绑定事件。{ input: (value) => console.log(value) }
     * @param { Array } options: radio,checkbox, select, cascader,
     * @param { String } text: button按钮的文字内容
     * @param { String } btnType: button按钮的类型
     * @param { String } btnIcon: button的icon
     * @param { String } marking: button的标记
     * @param { Boolean } hasFilter: 是否进行筛选,只有存在options才可以进行
     *
     * @param { Function } slotDefault: el-table-column 的 default的插槽
     * @param { Function } slotHeader: el-table-column 的 header的插槽
     */
    
    export default {
      name: "XTable",
      props: {
        autoHeight: {
          type: Boolean,
          default: true,
        },
        columns: {
          type: Array,
          default: () => [],
        },
        tableData: {
          type: Array,
          default: () => [],
        },
        search: {
          type: Object,
          default: () => ({}),
        },
        api: {
          type: Function,
          default: null,
        },
        itemAlign: {
          type: String,
          default: "left",
        },
        emptyText: {
          type: String,
          default: "暂无数据",
        },
        emptyImage: null,
        emptyImageSize: {
          type: Number,
          default: null,
        },
        hasPagination: {
          type: Boolean,
          default: true,
        },
        paginationLayout: {
          type: String,
          default: "total, sizes, prev, pager, next, jumper",
        },
        multiple: {
          type: Boolean,
          default: false,
        },
        hasRadio: {
          type: Boolean,
          default: false,
        },
      },
      data() {
        return {
          height: 0,
          fields: [],
          showData: [],
          searchForm: {},
          pageInfo: {
            total: 0,
            pagesize: 10,
            current: 1,
          },
          multipleSelection: [],
          loading: false,
        };
      },
      render(h) {
        return h(
          "div",
          {
            class: { "x-table-wrap": true, "x-table-radio": this.hasRadio },
            style: { height: `${this.height}px` },
          },
          [this.renderTable(h), this.renderPagination(h)],
        );
      },
      watch: {
        columns: {
          immediate: true,
          deep: true,
          handler() {
            this.handleFields();
          },
        },
        tableData: {
          deep: true,
          handler() {
            this.setTableData(this.searchForm);
          },
        },
        search: {
          immediate: true,
          deep: true,
          handler(val) {
            this.searchForm = val;
          },
        },
      },
      mounted() {
        this.init();
      },
      methods: {
        init() {
          this.getParentHeight();
          window.addEventListener("reset", this.getParentHeight.bind(this));
          this.setTableData(this.searchForm);
        },
    
        handleFields() {
          this.fields = this.handleFieldsChild(this.columns);
    
          if (this.multiple || this.hasRadio) {
            this.fields.unshift({
              type: "selection",
              width: "40",
            });
          }
        },
    
        handleFieldsChild(columns) {
          let fields = columns.filter((item) => item?.isShow !== false);
          fields = fields.map((item) => {
            item.inputType = item.type;
            delete item.type;
            const tableItem = { ...item, ...item?.table };
            delete tableItem.table;
            delete tableItem.search;
            delete tableItem.form;
            delete tableItem.dialog;
    
            if (tableItem.children && Array.isArray(tableItem.children)) {
              delete tableItem.prop;
              tableItem.children = this.handleFieldsChild(tableItem.children);
            }
    
            tableItem.align = tableItem.align || this.itemAlign;
    
            return tableItem;
          });
    
          fields = fields.sort((a, b) => (b?.order || 0) - (a?.order || 0));
    
          return fields;
        },
    
        getParentHeight() {
          const parentDom = this.$el.parentElement;
          const height = parentDom.clientHeight;
          const paddingTop = parseFloat(
            getComputedStyle(parentDom, false)["padding-top"],
          );
          const paddingBottom = parseFloat(
            getComputedStyle(parentDom, false)["padding-bottom"],
          );
          this.height = height - paddingTop - paddingBottom;
        },
    
        renderTable(h) {
          if (!this.height) return null;
    
          const config = {
            props: {
              highlightCurrentRow: this.hasRadio,
              ...this.$attrs,
              data: this.showData,
            },
            on: {
              "selection-change": this.handleSelectionChange,
              "current-change": this.selectCurrentChange,
            },
            ref: "table",
            directives: [{ name: "loading", value: this.loading }],
          };
    
          if (this.autoHeight) {
            let paginationHeight = this.hasPagination ? 35 : 0;
            config.props.height = this.height - paginationHeight;
          }
    
          return h("el-table", config, [
            ...this.renderColumns(h, this.fields),
            this.renderEmpty(h),
            this.$slots.default,
          ]);
        },
    
        /* 空内容 */
        renderEmpty(h) {
          if (this.showData && this.showData.length) return null;
          const config = {
            props: { description: this.emptyText },
            slot: "empty",
          };
          if (this.emptyImage) config.props.image = this.emptyImage;
          if (this.emptyImageSize) config.props.imageSize = this.emptyImageSize;
    
          return h("el-empty", config);
        },
    
        /* table-column */
        renderColumns(h, fields) {
          return fields.map((item) => this.renderTableItem(h, item));
        },
    
        renderTableItem(h, item) {
          const scopedSlots = {};
    
          if (item.slotDefault && typeof item.slotDefault === "function") {
            scopedSlots.default = (scope) =>
              item.slotDefault(this.getHypeScript(), scope);
          }
    
          if (item.slotHeader && typeof item.slotHeader === "function") {
            scopedSlots.header = (scope) =>
              item.slotHeader(this.getHypeScript(), scope);
          }
    
          if (!item.slotDefault && item.children && Array.isArray(item.children)) {
            item.childrenDom = this.renderColumns(h, item.children);
          }
    
          const attrs = { ...item, ...item?.attrs };
          let props = item.props;
          delete item.props;
          delete item.attrs;
    
          // 后续需要添加字典
          if (!item.children && item.options) {
            props = {
              formatter: (row, column) => {
                const property = column["property"];
                const value = row[property];
                const findItem = item.options.find((item) => item.value === value);
                return findItem.label;
              },
              ...props,
            };
          }
    
          let formatType = ["time-select", "time", "date"];
          if (
            !item.slotDefault &&
            item?.props?.valueFormat &&
            formatType.includes(item.inputType)
          ) {
            let formatReg = item.props.valueFormat.replace(/yyyy/g, "YYYY");
            props = {
              formatter: (row, column) => {
                const property = column["property"];
                const value = row[property];
                return dayjs(value).format(formatReg);
              },
              ...props,
            };
          }
    
          if (item.options && item.hasFilter) {
            props = {
              ...props,
              filters: item.options.map(({ label, value }) => ({
                text: label,
                value,
              })),
              filterMethod: (value, row, column) => {
                const property = column["property"];
                return row[property] === value;
              },
            };
          }
    
          return h(
            "el-table-column",
            { attrs, props, scopedSlots },
            item.childrenDom,
          );
        },
    
        /* 分页 */
        renderPagination(h) {
          if (!this.hasPagination) return null;
          return h("div", { class: { "x-table-bottom": true } }, [
            h("el-pagination", {
              props: {
                layout: this.paginationLayout,
                ...this.pageInfo,
              },
              on: {
                "size-change": this.handleSizeChange,
                "current-change": this.handleCurrentChange,
              },
            }),
    
            this.multiple &&
              h("div", null, [`已选中 ${this.multipleSelection.length} 个`]),
          ]);
        },
    
        handleSizeChange(size) {
          this.pageInfo.pagesize = size;
          this.setTableData(this.searchForm);
        },
    
        handleCurrentChange(current) {
          this.pageInfo.current = current;
          this.setTableData(this.searchForm);
        },
    
        /* 方法 */
        getHypeScript() {
          return this.$parent.$createElement;
        },
    
        setTableData(search) {
          this.searchForm = search;
          let searchForm = delEmptyKey(search);
          let handleResolve = null;
          const promiseObj = new Promise((resolve) => {
            handleResolve = resolve;
          });
    
          if (this.tableData && this.tableData.length) {
            this.loading = true;
            const { pagesize, current } = this.pageInfo;
    
            let tableData = cloneDeep(this.tableData);
            tableData = tableData.filter((item) => isMatch(item, searchForm));
    
            this.pageInfo.total = tableData.length;
            this.showData = [...tableData].splice(
              (current - 1) * pagesize,
              pagesize,
            );
    
            this.loading = false;
            handleResolve();
            handleResolve = null;
          } else if (this.api && typeof this.api === "function") {
            const param = {
              ...searchForm,
              pagesize: this.pageInfo.pagesize,
              pageno: this.pageInfo.current,
            };
            this.loading = true;
            // 需要和后端协商好接口字段,保持所有table接口返回统一
            this.api(param)
              .then((res) => {
                this.showData = res.data.records;
                const { total, current, pagesize } = res.data;
                this.pageInfo = { total, current, pagesize };
              })
              .catch(() => {
                this.showData = [];
                this.pageInfo = {
                  total: 0,
                  current: 1,
                  pagesize: 10,
                };
              })
              .finally(() => {
                this.loading = false;
                handleResolve();
                handleResolve = null;
              });
          }
    
          return promiseObj;
        },
    
        handleSelectionChange(val) {
          if (this.hasRadio && val.length > 1) {
            const delItem = this.multipleSelection[0];
            const item = val.find((child) => !isMatch(child, delItem));
            this.$refs.table.clearSelection();
            this.setCurrent(item);
          } else {
            this.multipleSelection = val;
          }
        },
    
        toggleSelection(rows = []) {
          if (!this.multiple) return false;
          if (rows) {
            rows.forEach((row) => {
              const findItem = this.findRow(row);
              this.$refs.table.toggleRowSelection(findItem);
            });
          } else {
            this.$refs.table.clearSelection();
          }
        },
    
        selectCurrentChange(val) {
          this.multipleSelection = [val];
          if (this.hasRadio) {
            this.$refs.table.clearSelection();
            const findItem = this.findRow(val);
            this.$refs.table.toggleRowSelection(findItem);
          }
        },
    
        setCurrent(row) {
          if (!this.hasRadio) return false;
          this.$refs.table.setCurrentRow(row);
        },
    
        findRow(row) {
          return this.showData.find((item) => isMatch(item, row));
        },
    
        clearFilter(key) {
          this.$refs.table.clearFilter(key);
        },
    
        getSelection() {
          return this.multipleSelection;
        },
    
        getTableData() {
          return this.showData;
        },
      },
    };
    </script>
    
    <style lang="less" scoped>
    .x-table-wrap {
      width: 100%;
    
      .x-table-bottom {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-top: 3px;
        font-size: 13px;
        font-weight: 400;
        color: #606266;
        flex-direction: row-reverse;
      }
    }
    
    .x-table-radio {
      ::v-deep {
        .el-table__header-wrapper {
          .el-checkbox {
            display: none;
          }
        }
      }
    }
    </style>
    
    
  • 使用案例

    <template>
      <div class="wrap">
        <!-- 基础空表单 -->
        <h3>基础空表单</h3>
        <div class="box">
          <x-table
            itemAlign="center"
            :border="true"
            :autoHeight="false"
            :hasPagination="false"
            :columns="columns1"
            :tableData="[]"
          ></x-table>
        </div>
    
        <!-- 基础表单 -->
        <h3>基础表单</h3>
        <div class="box">
          <x-table
            itemAlign="center"
            :border="true"
            :autoHeight="false"
            :hasPagination="false"
            :columns="columns1"
            :tableData="tableData1"
          ></x-table>
        </div>
    
        <!-- 自动填充和分页 -->
        <h3>自动填充和分页</h3>
        <p>自动填充高度,并且能自动处理radio、checkbox、select的值</p>
        <div class="box">
          <x-table
            itemAlign="center"
            :border="true"
            :columns="columns2"
            :tableData="tableData2"
          ></x-table>
        </div>
    
        <!-- 自定义内容 -->
        <h3>自定义内容</h3>
        <div class="box">
          <x-table
            itemAlign="center"
            :border="true"
            :columns="columns3"
            :tableData="tableData3"
          ></x-table>
        </div>
    
        <!-- 多表头 -->
        <h3>多表头</h3>
        <div class="box">
          <x-table
            itemAlign="center"
            :border="true"
            :columns="columns4"
            :tableData="tableData4"
          ></x-table>
        </div>
    
        <!-- 多选 -->
        <h3>多选</h3>
        <div>
          <el-button @click="getSelection('multipleTable')">获取选中结果</el-button>
          <el-button @click="setSelection([tableData1[1], tableData1[2]])">
            设置选择第二项, 第三项
          </el-button>
          <el-button @click="setSelection()">清空选中</el-button>
        </div>
        <div class="box">
          <x-table
            ref="multipleTable"
            itemAlign="center"
            :border="true"
            :multiple="true"
            :columns="columns1"
            :tableData="tableData1"
          ></x-table>
        </div>
    
        <!-- 单选 -->
        <h3>单选</h3>
        <div>
          <el-button @click="getSelection('singleTable')">获取选中结果</el-button>
          <el-button @click="setRadio(tableData1[2])">
            设置选择第三项
          </el-button>
          <el-button @click="setRadio()">清空选中</el-button>
        </div>
        <div class="box">
          <x-table
            ref="singleTable"
            itemAlign="center"
            :border="true"
            :hasRadio="true"
            :columns="columns1"
            :tableData="tableData1"
          ></x-table>
        </div>
    
        <!-- 排序和筛选 -->
        <h3>排序和筛选</h3>
        <div class="box">
          <x-table
            itemAlign="center"
            :border="true"
            :autoHeight="false"
            :hasPagination="false"
            :columns="columns5"
            :tableData="tableData5"
          ></x-table>
        </div>
    
        <!-- 嵌套内容 -->
        <h3>嵌套内容</h3>
        <div class="box">
          <x-table
            itemAlign="center"
            :border="true"
            :autoHeight="false"
            :hasPagination="false"
            :columns="columns6"
            :tableData="tableData5"
          ></x-table>
        </div>
    
        <!-- 接口获取 -->
        <h3>接口获取</h3>
        <div class="box">
          <x-table
            itemAlign="center"
            :border="true"
            :columns="columns7"
            :api="getList"
          ></x-table>
        </div>
    
        <!-- 搜索 -->
        <h3>搜索</h3>
        <div class="box">
          <x-table
            itemAlign="center"
            :border="true"
            :columns="columns7"
            :api="getList"
            :search="{ name: '张三' }"
          ></x-table>
        </div>
      </div>
    </template>
    
    <script>
    import { getList } from "@/api/mock.js";
    
    export default {
      name: "Page5",
    
      data() {
        return {
          search: "",
          columns1: [
            { label: "姓名", prop: "name" },
            { label: "年龄", prop: "age" },
            { label: "地址", prop: "address" },
          ],
          tableData1: [
            { name: "张三1", age: 12, address: "北京市" },
            { name: "张三2", age: 12, address: "北京市" },
            { name: "张三3", age: 12, address: "北京市" },
            { name: "张三4", age: 12, address: "北京市" },
            { name: "张三5", age: 12, address: "北京市" },
          ],
    
          columns2: [
            { label: "姓名", prop: "name" },
            { label: "年龄", prop: "age" },
            {
              label: "性别",
              prop: "sex",
              options: [
                { label: "男", value: "0" },
                { label: "女", value: "1" },
              ],
            },
            { label: "地址", prop: "address" },
          ],
          tableData2: [
            { name: "张三", age: 12, sex: "0", address: "北京市" },
            { name: "张三", age: 12, sex: "1", address: "北京市" },
            { name: "张三", age: 12, sex: "0", address: "北京市" },
            { name: "张三", age: 12, sex: "1", address: "北京市" },
            { name: "张三", age: 12, sex: "0", address: "北京市" },
            { name: "张三", age: 12, sex: "1", address: "北京市" },
            { name: "张三", age: 12, sex: "0", address: "北京市" },
            { name: "张三", age: 12, sex: "1", address: "北京市" },
            { name: "张三", age: 12, sex: "0", address: "北京市" },
            { name: "张三", age: 12, sex: "1", address: "北京市" },
            { name: "张三", age: 12, sex: "0", address: "北京市" },
            { name: "张三", age: 12, sex: "1", address: "北京市" },
            { name: "张三", age: 12, sex: "0", address: "北京市" },
            { name: "张三", age: 12, sex: "1", address: "北京市" },
            { name: "张三", age: 12, sex: "0", address: "北京市" },
            { name: "张三", age: 12, sex: "1", address: "北京市" },
            { name: "张三", age: 12, sex: "0", address: "北京市" },
            { name: "张三", age: 12, sex: "1", address: "北京市" },
            { name: "张三", age: 12, sex: "0", address: "北京市" },
            { name: "张三", age: 12, sex: "1", address: "北京市" },
            { name: "张三", age: 12, sex: "0", address: "北京市" },
            { name: "张三", age: 12, sex: "1", address: "北京市" },
            { name: "张三", age: 12, sex: "0", address: "北京市" },
          ],
    
          columns3: [
            { label: "姓名", prop: "name" },
            { label: "年龄", prop: "age" },
            { label: "地址", prop: "address" },
            {
              slotHeader: () => {
                return (
                  <el-input
                    value={this.search}
                    onInput={(val) => (this.search = val)}
                    size="mini"
                    placeholder="输入关键字搜索"
                  />
                );
              },
              slotDefault: (h, scope) => {
                return (
                  <div>
                    <el-button
                      size="mini"
                      onClick={() => this.handleEdit(scope.$index, scope.row)}
                    >
                      Edit
                    </el-button>
                    <el-button
                      size="mini"
                      type="danger"
                      onClick={() => this.handleDelete(scope.$index, scope.row)}
                    >
                      Delete
                    </el-button>
                  </div>
                );
              },
            },
          ],
          tableData3: [
            { name: "张三", age: 12, address: "北京市" },
            { name: "张三", age: 12, address: "北京市" },
            { name: "张三", age: 12, address: "北京市" },
          ],
    
          columns4: [
            {
              label: "用户",
              children: [
                { label: "姓名", prop: "name" },
                { label: "年龄", prop: "age" },
                {
                  label: "信息",
                  children: [
                    { label: "身高", prop: "height" },
                    { label: "体重", prop: "weight" },
                    {
                      label: "性别",
                      prop: "sex",
                      options: [
                        { label: "男", value: "0" },
                        { label: "女", value: "1" },
                      ],
                    },
                  ],
                },
              ],
            },
            { label: "地址", prop: "address" },
          ],
          tableData4: [
            {
              name: "张三",
              age: 12,
              sex: "0",
              height: "175cm",
              weight: "65kg",
              address: "北京市",
            },
            {
              name: "张三",
              age: 12,
              sex: "1",
              height: "175cm",
              weight: "65kg",
              address: "北京市",
            },
            {
              name: "张三",
              age: 12,
              sex: "0",
              height: "175cm",
              weight: "65kg",
              address: "北京市",
            },
          ],
    
          columns5: [
            { label: "姓名", prop: "name" },
            { label: "年龄", prop: "age", sortable: true },
            {
              label: "性别",
              prop: "sex",
              hasFilter: true,
              options: [
                { label: "男", value: "0" },
                { label: "女", value: "1" },
              ],
            },
            { label: "地址", prop: "address" },
          ],
          tableData5: [
            { name: "张三1", age: 12, sex: "1", address: "北京市" },
            { name: "张三2", age: 13, sex: "0", address: "北京市" },
            { name: "张三3", age: 14, sex: "0", address: "北京市" },
            { name: "张三4", age: 15, sex: "1", address: "北京市" },
            { name: "张三5", age: 16, sex: "0", address: "北京市" },
          ],
    
          columns6: [
            {
              table: { type: "expand" },
              slotDefault: (h, scope) => {
                return (
                  <el-form label-position="left">
                    <el-form-item label="姓名">
                      <span>{scope.row.name}</span>
                    </el-form-item>
                    <el-form-item label="年龄">
                      <span>{scope.row.age}</span>
                    </el-form-item>
                    <el-form-item label="性别">
                      <span>{scope.row.sex}</span>
                    </el-form-item>
                    <el-form-item label="地址">
                      <span>{scope.row.address}</span>
                    </el-form-item>
                  </el-form>
                );
              },
            },
            { label: "姓名", prop: "name" },
            { label: "年龄", prop: "age", sortable: true },
            {
              label: "性别",
              prop: "sex",
              hasFilter: true,
              options: [
                { label: "男", value: "0" },
                { label: "女", value: "1" },
              ],
            },
            { label: "地址", prop: "address" },
          ],
    
          columns7: [
            { label: "姓名", prop: "name" },
            { label: "年龄", prop: "age" },
            {
              label: "性别",
              prop: "sex",
              options: [
                { label: "男", value: "0" },
                { label: "女", value: "1" },
              ],
            },
            { label: "日期", prop: "date", props: { valueFormat: "yyyy-MM-DD" } },
            { label: "地址", prop: "address" },
          ],
        };
      },
      methods: {
        getList: getList,
    
        handleEdit(index, row) {
          console.log(index, row);
        },
    
        handleDelete(index, row) {
          console.log(index, row);
        },
    
        getSelection(key) {
          const selection = this.$refs[key].getSelection();
          console.log(selection);
        },
    
        setSelection(rows) {
          this.$refs.multipleTable.toggleSelection(rows);
        },
    
        setRadio(row) {
          this.$refs.singleTable.setCurrent(row);
        },
      },
    };
    </script>
    
    <style lang="less" scoped>
    .wrap {
      width: 100%;
      height: 100%;
      display: flex;
      align-items: center;
      flex-direction: column;
    }
    
    .box {
      width: 800px;
      height: 500px;
      padding: 10px;
      margin-bottom: 20px;
      background: #1f03034d;
    }
    </style>
    
    
  • mockjs

    function getList() {
      let list = new Array(99).fill({});
      list = list.map((item, index) => {
        return {
          name: index > 20 ? `张三${index}` : "张三",
          age: index.toString(),
          date: Mock.mock("@date('yyyy-MM-dd')"),
          sex: (index % 2).toString(),
          address: `北京市朝阳区${index}`,
        };
      });
      return new MockPort({
        template: {
          code: 200,
          msg: "success",
          data: {
            records: [],
            pagesiez: 0,
            current: 1,
            total: 0,
          },
        },
        action(options) {
          const params = this.paramsBackRes(options.body) || {};
          let { pagesize, pageno, ...search } = params;
          pagesize = pagesize || 10;
          pageno = pageno || 1;
          let records = list.filter((item) => isMatch(item, search));
          this.template.data = {
            records: [...records].splice((pageno - 1) * pagesize, pagesize),
            total: records.length,
            pagesize,
            current: pageno,
          };
          return this.template;
        },
      });
    }
    
3、dialog
  • 创建components/dialog/index.js

    import XDialog from "./index.vue";
    export default XDialog;
    
    
  • 创建components/dialog/index.vue

    <template>
      <el-dialog
        :title="title"
        v-bind="$attrs"
        v-on="$listeners"
        :visible.sync="dialogVisible"
        @close="close"
      >
        <template slot="title">
          <slot name="title"></slot>
        </template>
    
        <slot></slot>
    
        <template slot="footer">
          <slot
            v-if="!hasHandle"
            name="footer"
            :submit="handleSubmit"
            :cancel="handleCancel"
          ></slot>
    
          <div v-else>
            <el-button v-if="cancel" @click="handleCancel">
              {{ cancelText }}
            </el-button>
            <el-button
              v-if="submit"
              type="primary"
              @click="handleSubmit"
              :loading="btnloading"
              :disabled="sumbitDiasbled"
              :icon="submitIcon"
            >
              {{ submitText }}
            </el-button>
          </div>
        </template>
      </el-dialog>
    </template>
    
    <script>
    import { debounce } from "lodash";
    import { Transmit } from "@/utils/utils";
    
    /**
     * @param { String } title: 标题
     * @param { Boolean } hasHandle: 控件按钮
     * @param { Boolean } submit: 确认按钮是否显示
     * @param { String } submitText: 确认按钮文字
     * @param { String } submitIcon: 确认按钮icon
     * @param { Boolean } submitLoading: 确认按钮是否开启loading
     * @param { Boolean } sumbitDiasbled: 确认按钮是否禁用
     * @param { Boolean } cancel: 取消按钮是否显示
     * @param { Boolean } cancelText: 取消按钮文字
     *
     * @event { Function } open: 打开dialog,返回promise
     * @event { Function } handleSubmit: 手动触发确认
     * @event { Function } handleCancel: 手动出发取消并关闭dialog
     */
    
    export default {
      name: "XDialog",
      props: {
        title: {
          type: String,
          default: "dialog",
        },
        hasHandle: {
          type: Boolean,
          default: true,
        },
        submit: {
          type: Boolean,
          default: true,
        },
        submitText: {
          type: String,
          default: "确认",
        },
        submitIcon: {
          type: String,
          default: "",
        },
        submitLoading: {
          type: Boolean,
          default: false,
        },
        sumbitDiasbled: {
          type: Boolean,
          default: false,
        },
        cancel: {
          type: Boolean,
          default: true,
        },
        cancelText: {
          type: String,
          default: "取消",
        },
      },
      data() {
        return {
          dialogVisible: false,
          btnloading: false,
          resolveHandle: null,
          rejectHandle: null,
          submitHandle: null,
        };
      },
      methods: {
        open() {
          return new Transmit((resolve, reject) => {
            this.resolveHandle = resolve;
            this.rejectHandle = reject;
            this.dialogVisible = true;
          });
        },
    
        handleSubmit: debounce(() => {
          if (this.submitLoading) this.btnloading = true;
          const callback = (closeStatus = true) => {
            this.btnloading = false;
            closeStatus && this.close();
          };
    
          this.resolveHandle(callback.bind(this));
        }, 500),
    
        handleCancel: debounce(() => {
          this.rejectHandle();
          this.close();
        }, 500),
    
        close() {
          this.dialogVisible = false;
          this.btnloading = false;
          this.resolveHandle = null;
          this.rejectHandle = null;
        },
      },
    };
    </script>
    
    
  • 使用案例

    <template>
      <div>
        <el-button type="text" @click="openDialog1">
          点击打开 Dialog1
        </el-button>
    
        <x-dialog ref="dialog1" :submitLoading="true">
          <span slot="title">我的标题</span>
          <span>这是一段信息</span>
        </x-dialog>
    
        <el-button @click="openDialog2">
          点击打开 Dialog2
        </el-button>
    
        <x-dialog ref="dialog2" :submitLoading="true" :hasHandle="false">
          <span>这是一段信息</span>
    
          <template v-slot:footer="scoped">
            <el-button @click="() => handleCancel(scoped.cancel)">取消</el-button>
            <el-button @click="handleNext">下一步</el-button>
            <el-button @click="() => handleSubmit(scoped.submit)">提交</el-button>
          </template>
        </x-dialog>
      </div>
    </template>
    
    <script>
    export default {
      name: "Page6",
      data() {
        return {
          dialogVisible: false,
        };
      },
      mounted() {},
      methods: {
        openDialog1() {
          this.$refs.dialog1
            .open()
            .then((callback) => {
              setTimeout(callback, 1000);
            })
            .catch(() => {});
        },
    
        openDialog2() {
          this.$refs.dialog2
            .open()
            .then((callback) => {
              setTimeout(callback, 1000);
            })
            .catch(() => {});
        },
    
        handleCancel(cancel) {
          this.$confirm("是否取消并关闭弹窗?", "提示", {
            confirmButtonText: "确定",
            cancelButtonText: "取消",
            type: "warning",
          }).then(() => {
            cancel();
            this.$message({
              type: "success",
              message: "取消成功!",
            });
          });
        },
    
        handleNext() {
          this.$message({
            type: "success",
            message: "下一步成功!",
          });
        },
    
        handleSubmit(submit) {
          this.$confirm("是否提交并关闭弹窗?", "提示", {
            confirmButtonText: "确定",
            cancelButtonText: "取消",
            type: "warning",
          }).then(() => {
            submit();
            this.$message({
              type: "success",
              message: "提交成功!",
            });
          });
        },
      },
    };
    </script>
    
    
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值