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;
  }
}

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;
  }
}

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;
    }
  }
}

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;
  }
}

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;
  }
}

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"
  })
}

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>

9. 创建store缓存

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

创建 fieldFormat.js,内容如下:

export default {
  namespaced: true,
  state: {
    types: {}
  },
  mutations: {
    ADD_TYPE: (state, params) => {
      state.types[params.type] = params.value;
    }
  }
}

并添加到 store 的 modules 中:

import fieldFormat from "./modules/fieldFormat";
const store = new Vuex.Store({
  modules: {
    fieldFormat
  },
  getters: {
    fieldFormat: state => state.fieldFormat.types
  }
})

export default store

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];
}

11. 添加全局组件

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

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

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>

2. 自定义插槽

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

<field-format :value="form.vehicleType" type="vehicleType">
  <template #format="{data}">{{ data.name }}</template>
</field-format>

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>

4. 默认插槽

用以自定义追加数据

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
config.params是一个配置对象,用于设置请求的参数。在给定的引用,这个配置对象被用于axios请求的GET方法。具体来说,这段代码检查了请求的方法是否为GET,如果是,则设置了一个参数序列化函数来将参数转换为URL查询字符串的形式。这个参数序列化函数使用了qs库的stringify方法,并传入了一个选项对象来指定数组的格式化方式。这样做是为了保证在GET请求传递数组类型的参数时,参数会被正确地序列化和传递。 另外,路由传参分为params传参和query传参。params传参类似于网络请求的POST请求,参数会被传递到请求的主体,并且不会在地址栏显示。但是这种方式无法刷新页面,并且只能与name属性一起使用。而query传参类似于网络请求的GET请求,参数会被拼接在地址栏,以?name=value的形式显示。query传参更加灵活,既可以与path属性一起使用,也可以与name属性一起使用。 综上所述,config.params是一个用于设置请求参数的配置对象,可以在GET请求使用,并且可以通过参数序列化函数来设置参数的序列化方式。而路由传参则分为params传参和query传参,分别对应POST请求和GET请求的参数传递方式。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [axios传递数组格式参数](https://blog.csdn.net/lml_little/article/details/126340584)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [params传参处理](https://blog.csdn.net/dayexiaofan/article/details/126917063)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

会功夫的李白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值