通过VFOR封装的一个搜索条件表头的组件

84 阅读4分钟

因业务需要封装的一个搜索条件表头组件,可通过动态插槽和一些内置的组件配置显示所需要的。

<template>
  <!-- 配置表头 -->
  <div :style="`display:flex;justify-content: ${allAlign};gap:${gap}px`">
    <div
      class="config-table-header"
      :style="`justify-content:${align};gap:${gap}px`"
    >
      <div
        v-for="(item, index) in mergeList[0]"
        :key="index"
        class="table-header-item"
      >
        <span
          v-if="item.label"
          :style="`width:${item.lableWidth}px;text-align:right `"
          >{{ item.label }}</span
        >
        <!-- 输入框 -->
        <el-input
          v-if="item.type === 'input'"
          v-model="form[`${item.prop}`]"
          :placeholder="item.placeholder || '请输入'"
          :style="`width:${item.width}px`"
          :type="item.inputType || 'text'"
          :disabled="item.disabled || false"
          :clearable="item.clearable === false ? false : true"
          @keyup.enter.native="emitMethod(item.enterMethod || '')"
          @blur="emitMethod(item.method || '')"
        ></el-input>
        <!-- 选择器 -->
        <el-select
          v-if="item.type === 'select'"
          v-model="form[`${item.prop}`]"
          :placeholder="item.placeholder || '请选择'"
          :disabled="item.disabled || false"
          :clearable="item.clearable === false ? false : true"
          :multiple="item.multiple || false"
          :filterable="item.filterable === false ? false : true"
          :default-first-option="true"
          :allow-create="item.allowCreate || false"
          :style="`width:${item.width}px`"
          @change="emitMethod(item.method || '')"
        >
          <!-- 这里需要你传入该单选循环的数组 -->
          <el-option
            v-for="_item in item.options"
            :key="_item[item.optionValue || 'value']"
            :label="_item[item.optionLabel || 'label']"
            :value="_item[item.optionValue || 'value']"
          >
          </el-option>
        </el-select>
        <!-- 按钮 -->
        <el-button
          v-if="item.type === 'button'"
          :type="item.btnType || 'primary'"
          :icon="item.btnIcon || ''"
          @click="emitMethod(item.method || '')"
          >{{ item.text }}</el-button
        >
        <!-- 日期选择器 -->
        <el-date-picker
          v-model="form[`${item.prop}`]"
          v-if="item.type === 'datePicker'"
          :type="item.dateType || 'date'"
          :placeholder="item.placeholder || '选择日期'"
          :format="item.format || 'yyyy-MM-dd'"
          :value-format="item.valueFormat || 'yyyy-MM-dd'"
          :range-separator="item.separator || '至'"
          :start-placeholder="item.startPlaceholder || '开始日期'"
          :end-placeholder="item.endPlaceholder || '结束日期'"
          :default-time="item.defaultTime || false"
          :style="`width:${item.width}px`"
          @change="emitMethod(item.method || '')"
        >
        </el-date-picker>
        <!-- 预留一个插槽 -->
        <slot v-if="item.scope" :name="item.scope" />
        <!-- 
          插槽用法
          <template v-slot:fieldBtn>
            <SelectColumn />
          </template> 
          -->
      </div>
    </div>
    <div
      class="config-table-header"
      :style="`justify-content:${align};gap:${gap}px`"
    >
      <div
        v-for="(item, index) in mergeList[1]"
        :key="index"
        class="table-header-item"
      >
        <el-button
          :type="item.btnType || 'primary'"
          @click="emitMethod(item.method || '')"
          >{{ item.text }}</el-button
        >
      </div>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    headerConfig: {
      type: Array,
      default: () => [],
    },
    form: {
      type: Object,
      default: () => {},
    },
    align: {
      type: String,
      default: () => "flex-start",
    },
    gap: {
      type: String,
      default: () => "20",
    },
    isMerge: {
      type: Boolean,
      default: () => false,
    },
    allAlign: {
      type: String,
      default: () => "space-between",
    },
  },

  computed: {
    mergeList() {
      // 合并的排法 按钮和其他选项全部一起排
      if (this.isMerge) {
        return [this.headerConfig, []];
      } else {
        // 不合并的排法
        return [
          this.headerConfig.filter((i) => i.type !== "button"),
          this.headerConfig.filter((i) => i.type === "button"),
        ];
      }
    },
  },

  methods: {
    emitMethod(value) {
      this.$emit(value);
    },
  },
};
</script>

<style lang="scss">
.config-table-header {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  font-size: 13px;
  .table-header-item {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 10px;
  }
}
</style>

使用示例:

// 配置的列表本身也是一个数组的形式
const XTableHeader = [
  {
    type: "input", //输入框 必填
    prop: "input", //输入框绑定的对象 如类型不是 button 必填
    placeholder: "提示语,默认是请输入或者请选择",
    width: 210, //该组件的宽度 请注意,这并不是代表整个元素的宽度
    inputType: "text", //输入框的类型 可以选择富文本,一般情况下都是文本
    label: "我是input前的label",
    labelWidth: "180", //最好配置一下,不然会因为宽度不够而被挤压
    disabled: false, //是否禁用,默认false
    clearable: true, //是否可清空,默认true
    method: "inputMethod", // input输入框在失去焦点或者在获得焦点的情况下按下回车键后触发的事件
  },
  {
    type: "select", //下拉选择框
    prop: "select", //下拉选择框绑定的数据对象
    placeholder: "请选择",
    disabled: false, //是否禁用
    clearable: true, //是否可清除,默认true
    multiple: false, //是否多选,默认false
    filterable: true, //是否可搜索,默认true
    allowCreate: false, //是否可创建子选择,默认false
    width: 210, //组件宽度
    method: "selectMethod", //选择器变化后触发的组件事件
    options: [], //选择器的选择options
    optionValue: "value", //options 的value 值 默认是 value
    optionLabel: "label", //options label 值 默认是 label
    label: "我是select前的label",
    labelWidth: "180", //最好配置一下,不然会因为宽度不够而被挤压
  },
  {
    type: "button", //按钮
    btnType: "primary", //按钮状态 默认是 primary
    method: "btnClick", //按钮点击后触发的事件
    text: "搜索", //按钮文字
    btnIcon: "", //按钮图标
  },
  {
    type: "datePicker", //时间选择器
    prop: "dateRange", //时间选择器绑定的数据对象
    dateType: "daterange", //选择器的类型 与 官方的选择器类型一致 iview 的看 iview 的 ; element 的看 element 的
    placeholder: "选择器的提示语",
    valueFormat: "yyyy-MM-dd", //选择器值的输出值规则 默认 yyyy-MM-dd 如要精确到秒 请替换
    format: "", //选择器值的显示的规则 默认 yyyy-MM-dd 如要精确到秒 请替换
    separator: "至", //中间那个占位
    startPlaceholder: "开始日期",
    endPlaceholder: "结束日期",
    defaultTime: null,
    width: 400,
    label: "我是datePicker前的label",
    labelWidth: "180", //最好配置一下,不然会因为宽度不够而被挤压
  },
  //动态插槽
  {
    scope: "mySlot",
  },
];

//以下是使用示例
 <XTableHeader
          :form="headerForm"
          :header-config="XTableHeader"
          class="mb-2"
          @search="(pageInfo.page = 1), getTableData()"
          @reset="(pageInfo.page = 1), (form = {}), getTableData()"
        >
          <template v-slot:organization>
            <label class="slot-style">组织</label>
            <el-select
              v-model="form.organizationId"
              remote
              clearable
              filterable
              :remote-method="remoteMethod1"
              style="width: 192px"
            >
              <el-option
                v-for="item in organizationList"
                :key="item.value"
                :value="item.value"
                :label="item.label"
              ></el-option>
            </el-select>
          </template>
          <template v-slot:fieldBtn>
            <SelectColumn
              :tableColumn="tableColumn"
              @change="SelectColumnChange"
            />
          </template>
          <template v-slot:showMoreBtn>
            <ShowMoreCondition @handleChange="isShowMore" />
          </template>
        </XTableHeader>


        data(){
            return{
                pageInfo:{
                    page:1,
                    total:0,
                    size:10
                },
                headerForm:{}
            }
        },
        methods:{
            getTableData(){}
        }

根据此组件,延伸出了一个级联Hooks的写法 先上 hooks 的代码:

import request from "@/utils/http";

export default () => {
  // 正常 的设置 options
  const setOptions = ({
    api = "",
    method = "get",
    params = {},
    optionsList = [],
    valueKey = "",
    labelKey = "",
  }) => {
    return new Promise(async (resolve) => {
      const { data: res } = await request[method](
        api,
        method === "get" ? { params } : params
      );

      // request({
      //   url: api,
      //   method: method,
      //   data: params,
      //   isParams: method === "get" ? true : false,
      // });

      const list = res.data || [];

      optionsList.length = 0;

      list.forEach((e) => {
        optionsList.push({
          label: labelKey === "item" ? e : e[labelKey],
          value: valueKey === "item" ? e : e[valueKey],
        });
      });

      resolve();
    });
  };
  /**
   * @author xuzy
   * @time 2022-11-11
   * 如果是使用了级联的表头 hooks
   * @param {XTableHeader Options Config} cascadeList
   * @param {XTableHeader Bind Form} tableHeaderForm
   */
  const useCascade = ({ cascadeList = [], tableHeaderForm = {} }) => {
    cascadeList.forEach((e) => {
      if (!e.isCascade) return; //不是级联就返回
      let mTableHeaderFormKey = tableHeaderForm[e.prop] || ""; // 值记录
      // 监听绑定数据的变化
      Object.defineProperty(tableHeaderForm, e.prop, {
        set: function (newValue) {
          mTableHeaderFormKey = newValue;

          //如果需要 label值  改为数组是为了契合新老接口的值
          if (e.turnKey && Array.isArray(e.turnKey)) {
            const find = e.options.find((te) => te.value === newValue);

            e.turnKey.map((_tk) => {
              if (find) {
                this[_tk] = find.label;
              } else {
                this[_tk] && delete this[_tk];
              }
            });
          }

          // 需要改变对象键的数组List
          let changeSelectObjList = [];

          // 如果changeKey 是一个数组 (要改变多个)
          if (Array.isArray(e.changeKey)) {
            changeSelectObjList = [...e.changeKey];
          } else {
            changeSelectObjList = [e.changeKey];
          }

          e.changeKey &&
            changeSelectObjList.forEach((changeKey) => {
              // 获取改变key的对象值
              const changeSelectObj = cascadeList.find(
                (_e) => _e.prop === changeKey
              );

              if (!changeSelectObj) return; // 如果找不到就返回 防止因为COPY的原因漏写而报错

              // 将级联参数封装成一个对象
              const params = {};
              // 这是取表头对象内的参数值
              changeSelectObj.params &&
                changeSelectObj.params.forEach((_e) => {
                  params[_e.requestKey] = tableHeaderForm[_e.formKey];
                });

              // 清空下一级的绑定值
              changeSelectObj.options.length = 0;
              tableHeaderForm[changeSelectObj.prop] = undefined;
              // 然后触发setOptions值
              tableHeaderForm[e.prop] != undefined &&
                setOptions({
                  api: changeSelectObj.getApi,
                  method: changeSelectObj.getMethod,
                  params: params,
                  optionsList: changeSelectObj.options,
                  valueKey: changeSelectObj.valueKey,
                  labelKey: changeSelectObj.labelKey,
                });
            });
        },
        get: function () {
          return mTableHeaderFormKey;
        },
      });
    });
  };

  return {
    setOptions,
    useCascade,
  };
};

以下是如何使用这个 hooks 的示例:

const defaultCascade = [
  {
    type: "select",
    placeholder: "请选择品牌",
    prop: "brandNameId",
    options: [],
    method: "update", //通知组件自更新
    isCascade: true, //是否是级联
    changeKey: "vehicleSeriesId", //更新的 key
    labelKey: "vehicleName",
    valueKey: "id",
    getMethod: "get", //请求方法
    getApi: "/rxd-biz-intelligent-diagnosis-backend-api/intelligentPush/brands", //获取该options 的api
    isFirstCascade: true, // 是否是第一级 (额外标记)
    turnKey: ["brandName", "vehicleBrand"], // 转化 key 无特殊需要可以不填
  },
  {
    type: "select",
    placeholder: "请选择车系",
    prop: "vehicleSeriesId", // 这个 key 会和上级 key 对应
    options: [],
    method: "update", // 通知 组件自更新
    isCascade: true, //是否是级联
    changeKey: "vehicleModelId", // 改变的key
    params: [{ requestKey: "belongId", formKey: "brandNameId" }], // 请求参数 参数名,表格key
    labelKey: "vehicleName",
    valueKey: "id",
    getMethod: "get",
    getApi:
      "/rxd-biz-intelligent-diagnosis-backend-api/intelligentPush/vehicleInfoByBelongId", //获取改options 的 api
    turnKey: ["vehicleSeries"], //转化key 可不填
  },
  {
    type: "select",
    placeholder: "请选择车型",
    prop: "vehicleModelId", // 这个 key 会和上级 key 对应
    options: [],
    method: "update", // 通知 组件自更新
    params: [{ requestKey: "belongId", formKey: "vehicleSeriesId" }], // 请求参数 参数名,表格key
    labelKey: "vehicleName",
    valueKey: "id",
    getMethod: "get",
    getApi:
      "/rxd-biz-intelligent-diagnosis-backend-api/intelligentPush/vehicleInfoByBelongId", //获取该 options 的api
    turnKey: ["vehicleModel", "vehicleModel"], //转化key 可不填
    isCascade: true, //是否是级联
  },
];

// 这是另一个例子
header: [
  {
    type: "select",
    placeholder: "请选择总成类型",
    prop: "series",
    options: [],
    method: "update",
    isCascade: true,
    changeKey: "model",
    labelKey: "item",
    valueKey: "item",
    getMethod: "get",
    isFirstCascade: true,
    getApi: "/scala-ms/ecuBasicInfoHelperController/getEcuSeries",
  },
  {
    type: "select",
    placeholder: "请选择总成型号",
    prop: "model",
    options: [],
    method: "update",
    params: [{ requestKey: "series", formKey: "series" }],
    labelKey: "model",
    valueKey: "model",
    getMethod: "get",
    getApi: "/scala-ms/ecuBasicInfoHelperController/getModels",
  },
  {
    type: "input",
    placeholder: "请输入故障码/故障码描述",
    prop: "keyword",
    labelWidth: 0,
  },
  {
    type: "button",
    text: "查询",
    method: "search",
  },
  {
    type: "button",
    text: "新增",
    method: "openDialog",
  },
  {
    type: "button",
    text: "导入",
    method: "importTable",
  },
  {
    type: "button",
    btnType: "default",
    text: "导出",
    method: "exportTable",
  },
];