• Vue项目中的字段格式化工具(进阶版)


    场景: 对于一些全局公用的状态,或是字典,甚至是后端枚举,为了方便维护,我们应尽量使它们全局公用,但我们在开发往往会忽略这个问题,总想着后面再改,可随着项目的不断推进,我们往往都视之不理。
    功能: 解决vue项目中字段、字典、状态类全局维护问题。
    优势: 一次配置全局公用、可单独变更、可自定义、低请求、方便、快捷。
    特点: 组件化、全局化、公用化。

    重点:为了大家更好、更方便的使用,现已推送到npm,直接执行npm install field-format即可安装,见:gitee公开代码github公开代码

    1. 创建组件

    components 下创建 FieldFormat 包,然后在该包下创建 index.vue

    2. 创建基础属性类

    该类为所有属性类的基类,也用以规范属性列表。

    FieldFormat 包下创建 Base.js,内容如下:

    export default class Base {
      constructor() {
        this.serve = undefined;
        this.id = undefined;
        this.label = undefined;
        this.method = 'get';
        this.dataField = 'data';
        this.isEnum = false;
        this.isDict = false;
        this.isCustom = false;
        this.customData = undefined;
        this.render = undefined;
        this.tagTypes = undefined;
      }
    
      /**
       * 添加tag属性,用以匹配el-tag样式
       * @param tags
       * @returns {Base}
       */
      tags(tags) {
        this.tagTypes = tags;
        return this;
      }
    
      /**
       * 添加自定义渲染,传入函数,将渲染返回的内容
       * @param render
       * @returns {Base}
       */
      renders(render) {
        this.render = render;
        return this;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    3. 创建自定义数据类

    该类为自定义数据属性,可传入自定义的属性用于匹配字段。

    FieldFormat 包下创建 Custom.js,内容如下:

    import Base from "./Base";
    
    /**
     * 自定义数据
     */
    export default class Custom extends Base {
      constructor(data, id, label) {
        super();
        this.customData = data;
        this.isCustom = true;
        this.id = id;
        this.label = label;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    4. 创建可扩展属性类

    该类用于自定义请求、字段等属性,扩展性高。

    FieldFormat 包下创建 Field.js,内容如下:

    import Base from "./Base";
    
    /**
     * 字段,用以匹配后端字段
     */
    export default class Field extends Base {
      constructor(serve, id, label, method, dataField) {
        super();
        this.serve = serve;
        this.id = id;
        this.label = label;
        if (method) {
          this.method = method;
        }
        if (dataField) {
          this.dataField = dataField;
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    5. 创建字典类

    该类用于定义字典属性列表,用于匹配字典数据。

    FieldFormat 包下创建 Dict.js,内容如下:

    import Base from "./Base";
    
    /**
     * 字典,用以匹配后端字典
     */
    export default class Dict extends Base {
      constructor(serve) {
        super();
        this.serve = serve;
        this.id = "dictValue";
        this.label = "dictLabel";
        this.isDict = true;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    6. 创建后端枚举类

    该类用于定义枚举属性列表,用于匹配后端枚举数据。

    FieldFormat 包下创建 Enum.js,内容如下:

    import Base from "./Base";
    
    /**
     * 枚举,根据name字段匹配
     */
    export default class Enum extends Base {
      constructor(serve) {
        super();
        this.id = "name";
        this.label = "description";
        this.isEnum = true;
        this.serve = serve;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    7. 创建格式化类型列表

    创建 formatOptions.js,内容如下(仅提供部分参考,依个人需求变更):

    import * as vehicleTypeService from "@/api/bayonet/vehicleType";
    import Enum from "./Enum";
    import Dict from "./Dict";
    import Field from "./Field";
    import Custom from "./Custom";
    
    // 枚举路径前缀
    const pathBayonetPrefix = "com.jl15988.project.bayonet.enums.";
    const pathApplicationPrefix = "com.jl15988.project.application.enums.";
    
    /**
     * 字段格式化组件参数
     *
     * @param serve 请求地址或请求方法或枚举类型,请求方法可以是api中的,必须是Function: () => Promise格式
     * @param id 请求后的数据列表字段,用于匹配那一条数据
     * @param label 请求后的数据列表字段,用于自动格式化字段
     * @param method 请求方式,默认get
     * @param dataField 请求后的data字段,默认data
     * @param isEnum 是否枚举,开启将请求后端枚举
     * @param isDict 是否字典,开启将请求后端字典
     * @param isCustom 是否自定义,开启自定义数据模式
     * @param customData 自定义的数据
     * @param render 用于自定义渲染操作,参数为(data, list),data为当前数据项,list为全部数据列表
     */
    export default {
      // 车辆类型。普通的拓展属性
      vehicleType: new Field(vehicleTypeService.getList, "vehicleTypeId", "name"),
      // 车辆类型(全路径)。自定义渲染的拓展属性
      vehicleTypeFull: new Field(vehicleTypeService.listAll, "vehicleTypeId", "name")
        .renders((data, list) => {
          // 这里可以通过 data、list 参数来完成个人需要的格式。下面是根据节点ID,拼完整的类型。
          if (!data || !data.name) {
            return "";
          }
          const names = [data.name];
    
          findParent(data);
    
          function findParent(row) {
            if (!row.parentId) {
              return;
            }
            const vehicleType = list.find(item => item.vehicleTypeId === row.parentId);
            if (vehicleType && vehicleType.name) {
              names.push(vehicleType.name);
            }
            if (vehicleType && list.filter(item => item.vehicleTypeId === vehicleType.parentId).length > 0) {
              findParent(vehicleType);
            }
          }
    
          names.reverse();
          return names.join("/");
        }),
      // 审批状态。枚举属性
      approvalStatusEnum: new Enum(pathApplicationPrefix + "ApprovalStatus")
        // 通过定义 tag 可以实现显示 el-tag 样式
        .tags({
          "10": "", // 待审核
          "20": "info",
          "30": "warning", // 企业驳回
          "40": "success", // 已通过
          "50": "warning", // 已驳回
          "60": "danger" // 停运
        }),
      // 车辆是否进入。字典属性
      vehicle_enter_status: new Dict("vehicle_enter_status")
        .tags({
          "0": "",
          "1": "success"
        }),
      // 异常车辆状态。自定义属性
      abnormalVehicleStatus: new Custom({
        "0": "正常",
        "1": "异常"
      }).tags({
        "0": "success",
        "1": "danger"
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80

    8. 修改组件

    将 index.vue 改为一下内容:

    <template>
      <span>
        <template v-if="!hasSlot && !tag && !tags && !tagTypes">{{ labelValue }}template>
        <el-tag v-if="!hasSlot && labelValue && (tag || tags || tagTypes)" :type="tagType">{{ labelValue }}el-tag>
        <slot>slot>
        <slot name="format" :data="data">slot>
        <slot name="list" :list="list">slot>
      span>
    template>
    
    <script>
    import {cacheGet, cachePost} from '@/utils/request';
    import formatOptions from "./formatOptions";
    
    export default {
      name: "FieldFormat",
      props: {
        /**
         * 用于匹配的值
         */
        value: [String, Number],
        /**
         * 要格式化的类型
         */
        type: String,
        /**
         * 发起请求的额外参数
         */
        params: Object,
        /**
         * 没有匹配的数据时,代替显示的内容
         */
        alternate: String,
        /**
         * 关闭Tag标签样式
         */
        closeTag: Boolean,
        /**
         * 要显示的Tag标签样式(默认的为default),见Element文档
         */
        tag: String,
        /**
         * 按数据显示的Tag标签样式,数据值为key,样式为值
         */
        tags: Object
      },
      data() {
        return {
          enumUrl: 'common/utility/getEnumList',
          dictUrl: 'system/dict/data/dictType/',
          data: undefined,
          list: [],
          serve: undefined,
          id: undefined,
          label: undefined,
          method: 'get',
          dataField: 'data',
          isEnum: false,
          isDict: false,
          isCustom: false,
          customData: undefined,
          render: undefined,
          tagTypes: undefined
        }
      },
      computed: {
        fieldFormats() {
          // 获取vuex中缓存的数据
          return this.$store.state.fieldFormat.types;
        },
        hasSlot() {
          // 判断有没有插槽(默认插槽除外)
          return (this.$scopedSlots && (!!this.$scopedSlots.list || !!this.$scopedSlots.format))
            || (this.$slots && (!!this.$slots.list || !!this.$slots.format));
        },
        labelValue() {
          if (this.render) {
            return this.render(this.data, this.list);
          } else if (this.isCustom && !this.id) {
            return this.customData[this.value];
          } else if (this.data && this.label) {
            return this.data[this.label];
          } else {
            return this.alternate;
          }
        },
        tagType() {
          if (this.closeTag) {
            return "";
          }
          if (this.tag) {
            return this.tag;
          } else if (this.tags) {
            return this.tags[this.value];
          } else if (this.tagTypes) {
            return this.tagTypes[this.value];
          } else {
            return "";
          }
        }
      },
      watch: {
        type: {
          handler(n) {
            // 类型改变时重新获取数据
            this.getData();
          }
        },
        value: {
          handler(n) {
            // 值改变时重新解析
            this.format();
          }
        }
      },
      methods: {
        /**
         * 解析
         */
        format() {
          // 在列表中查找对应数据
          if (this.isCustom && this.id) {
            this.list = this.customData;
          }
    
          const list = this.list;
          if (list && list.length > 0) {
            this.data = list.find(datum => String(datum[this.id]) === String(this.value));
          }
        },
        /**
         * 获取参数
         * @returns {string|*}
         */
        getOption() {
          // 根据type获取option
          const option = formatOptions[this.type];
          // 赋值属性
          Object.assign(this.$data, option);
          return option.serve;
        },
        /**
         * 获取数据
         */
        async getData() {
          const serve = this.getOption();
    
          // 如果vuex中有当前类型缓存,则取缓存
          if (this.fieldFormats[this.type]) {
            this.list = this.fieldFormats[this.type];
            this.format();
            return;
          }
    
          if (serve instanceof Function) {
            // 如果serve类型为Function,则直接调用取值
            serve().then(res => {
              this.relRes(res);
            });
          } else {
            if (this.isDict) {
              await this.relDict();
            } else if (this.isEnum) {
              await this.relEnum();
            } else if (this.isCustom) {
              this.format();
            } else {
              let res;
              if (this.method === "post") {
                res = await cachePost(serve, this.params);
              } else {
                res = await cacheGet(serve, this.params);
              }
              this.relRes(res);
            }
          }
        },
        /**
         * 解析枚举
         */
        async relEnum() {
          const res = await cacheGet(this.enumUrl, {
            enumType: this.serve
          })
          this.relRes(res);
        },
        /**
         * 解析字典
         */
        async relDict() {
          const res = await cacheGet(this.dictUrl + this.serve);
          this.relRes(res);
        },
        /**
         * 解析结果
         */
        relRes(res) {
          let list = this.list = res[this.dataField];
    
          this.$store.commit("fieldFormat/ADD_TYPE", {
            type: this.type,
            value: list
          });
    
          this.format();
        }
      },
      created() {
        this.getData();
      }
    }
    script>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213

    9. 创建store缓存

    为了降低请求频率,降低后端请求压力,我们添加 store 进行缓存获取的数据。

    创建 fieldFormat.js,内容如下:

    export default {
      namespaced: true,
      state: {
        types: {}
      },
      mutations: {
        ADD_TYPE: (state, params) => {
          state.types[params.type] = params.value;
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    并添加到 store 的 modules 中:

    import fieldFormat from "./modules/fieldFormat";
    const store = new Vuex.Store({
      modules: {
        fieldFormat
      },
      getters: {
        fieldFormat: state => state.fieldFormat.types
      }
    })
    
    export default store
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    10. 创建缓存请求

    在表格渲染中,会创建多个相同类型的组件,由于第一次请求,缓存中没有该类型的数据,造成大量的请求,所以我们添加缓存请求,降低请求次数。

    我们使用的使 axios.js,但其实普通的 ajax 也可以。

    const cacheMap = {};
    // 响应拦截器
    service.interceptors.response.use(res => {
      try {
        // 删除缓存,这里的 api 根据个人需求变更
        const baseApi = res.config.url.replace(process.env.VUE_APP_BASE_API, "");
        let api;
        if (res.config.method === 'get') {
          api = baseApi + JSON.stringify(res.config.params);
        } else {
          api = baseApi + JSON.stringify(res.config.data);
        }
        if (cacheMap.hasOwnProperty(api)) {
          delete cacheMap[api];
        }
      } catch (err) {
      }
    }
    
    /**
     * Get缓存请求
     */
    export const cacheGet = async (api, params) => {
      if (api.indexOf("/") !== 0) {
        api = "/" + api;
      }
      const key = api + JSON.stringify(params);
      if (!cacheMap.hasOwnProperty(key)) {
        cacheMap[key] = service({
          url: api,
          method: 'get',
          params
        });
      }
      return cacheMap[key];
    }
    
    /**
     * Post缓存请求
     */
    export const cachePost = async (api, data) => {
      if (api.indexOf("/") !== 0) {
        api = "/" + api;
      }
      const key = api + JSON.stringify(data);
      if (cacheMap.hasOwnProperty(key)) {
        cacheMap[key] = service({
          url: api,
          method: 'post',
          data
        });
      }
      return cacheMap[key];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    11. 添加全局组件

    为了能够使组件能够全局调用,而不用单独引入,我们在 main.js 中引入,并挂载到全局

    import FieldFormat from "@/components/FieldFormat";
    Vue.component('FieldFormat', FieldFormat);
    
    • 1
    • 2

    12. 说明

    属性类可自行创建,也可以直接使用 JSON 格式,创建属性类是为了能够更方便、快捷的使用。

    13. 属性

    1. 类属性

    属性类型说明
    serveString 或 Function请求地址或请求方法或枚举类型,请求方法可以是api中的,必须是Function: () => Promise格式
    idString请求后的数据列表字段,用于匹配那一条数据
    labelString请求后的数据列表字段,用于自动格式化字段
    methodString请求方式,默认get
    dataFieldString请求后的data字段,默认data
    isEnumBoolean是否枚举,开启将请求后端枚举
    isDictBoolean是否字典,开启将请求后端字典
    isCustomBoolean是否自定义,开启自定义数据模式
    customDataObject 或 Array自定义的数据
    renderFunction用于自定义渲染操作,参数为(data, list),data为当前数据项,list为全部数据列表

    2. 组件属性

    属性类型说明
    valueString 或 Number用于匹配的值
    typeString要格式化的类型
    paramsObject发起请求的额外参数
    alternateString没有匹配的数据时,代替显示的内容
    closeTagBoolean关闭Tag标签样式
    tagString要显示的Tag标签样式(默认的为default),见Element文档
    tagsObject按数据显示的Tag标签样式,数据值为key,样式为值

    14. 使用

    1. 格式化

    在需要格式化的地方,使用组件 field-format,value为已知数据值, type 为 formatOptions 中添加的名称,另外还有 params 字段用于请求自定义传参

    <field-format :value="form.vehicleType" type="vehicleType">field-format>
    
    • 1

    2. 自定义插槽

    可以使用插槽实现更多场景的功能,如

    <field-format :value="form.vehicleType" type="vehicleType">
      <template #format="{data}">{{ data.name }}template>
    field-format>
    
    • 1
    • 2
    • 3

    3. 遍历

    或者获取所有列表,用于遍历

    <field-format type="vehicleType">
        <template #list="{list}">
          <el-select v-model="form.vehicleType">
            <el-option
              v-for="item in list"
              :label="item.name"
              :value="item.vehicleTypeId"
              :key="item.vehicleTypeId"
            >el-option>
          el-select>
        template>
      field-format>
    el-form-item>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    4. 默认插槽

    用以自定义追加数据

  • 相关阅读:
    芯片科普 |ATE测试如何入门?ATE测试的工作内容和要求?
    分享!JetBrains IDE中的GitLab支持
    学习 C++ 到底有什么好处?
    (十一)Jmeter调用webService接口
    thinkphp中区块block和模板继承extend用法举例,和公共头部
    阿里的 24W 字 Java 面试复盘指南!
    linux python 保存图形savefig import matplotlib.pyplot as plt
    19、Flink 的Table API 和 SQL 中的自定义函数及示例(2)
    WPF在win10/11上启用模糊特效 适配Dark/Light Mode
    【广州华锐互动】石油钻井井控VR互动实训系统
  • 原文地址:https://blog.csdn.net/jl15988/article/details/133693777