封装Form表单【后台控制表单搜索项的显隐和排序】

概要

为了实现需求:后台控制表单搜索项的显隐和排序;

整体思路流程

表单搜索项统一配置,封装成一个组件,把不同类别再封装成单个的组件,配置项数组由前端控制(暂由前端配置,这样虽然代码量多,但是对于前端修改配置比较灵活,暂时不考虑数据由后端取过来的原因是,有些配置,如表单类型不好控制)

接下来看具体实现,再回看也许好理解。

具体实现

一、Form表单封装文件,自定义命名为FreeForm.vue

在vue项目中我们可以把它放到src/components这个文件夹下,新建目录sortForm,将此文件放入

HTML部分:

<template>
  <div class="container">
    <!-- 这里加了超过4行展开收起的功能 -->
    <div
      :class="['content', { collapsed: isCollapsed }]"
      :style="contentStyle"
      ref="content"
    >
      <el-form
        :ref="formRef"
        size="small"
        :inline="true"
        :model="model"
        v-bind="$attrs"
        v-show="showSearch"
        :label-width="labelWidth"
        label-position="left"
        label-suffix=":"
      >
      <!-- formItemConfig  表单数据源 -->
        <div
          v-for="(item, index) in formItemConfig"
          :key="index"
          :style="{ display: 'inline-block' }"
        >
        <!-- 渲染表单项 -->
          <el-form-item
            v-if="item.show"
            :label="item.label"
            :prop="item.prop"
            style="width: 100%"
            :label-width="item.labelWidth"
          >
            <!-- 动态渲染组件 -->
            <component
              :is="isComponentName(item)"
              v-model="model[item.prop]"
              :placeholder="placeholder(item)"
              v-bind="item"
              :style="{ width: item.width }"
              :multiple="item.multiple"
              @input="changeValue(item, $event)"
              @click="handleClick(item, $event)"
              @change="handleChange(item, $event)"
            />
          </el-form-item>
        </div>
      </el-form>
    </div>
    <!-- 按钮组:这里是封装了搜索重置按钮 -->
    <div class="form-op-container">
      <el-button type="text" @click="toggle" v-if="parseInt(maxHeight) > 210">
        <div class="button-text">
          {{ buttonText }}
          <div :class="{ isTransIcon: !isCollapsed }">
            <span class="iconfont icon-zhankai"></span>
          </div>
        </div>
      </el-button>
      <div class="header_btns">
        <slot name="header_btns_before"></slot>
      </div>
      <div class="header_btns">
        <el-button size="mini" type="primary" @click="handleSearch"
          >搜索</el-button
        >
        <el-button size="mini" @click="handleReset">重置</el-button>
      </div>
      <div class="header_btns">
        <slot name="header_btns_after"></slot>
      </div>
    </div>
  </div>
</template>

JS部分:

<script>
import { cloneDeep } from "lodash";
/**
 * @desc 表单组件
 * @param {Object} formRef - el-form 的 ref 名称
 * @param {Object} model - 表单数据模型
 * @param {Object} formItemConfig - el-form-item 配置项
 */
export default {
  props: {
    // 表单引用名称
    formRef: {
      type: String,
      default: "formRef",
    },
    // 表单数据模型
    model: {
      type: Object,
      default: () => ({}),
    },
    // 表单项配置
    formItemConfig: {
      type: Array,
      default: () => [],
    },
    showSearch: {
      type: Boolean,
      default: true,
    },
    labelWidth: {
      type: String,
      default: "100px",
    },
  },
  data() {
    return {
      init: null,
      isCollapsed: true,
      maxHeight: "200px",
    };
  },
  computed: {
    contentStyle() {
      return {
        maxHeight: this.isCollapsed ? "200px" : this.maxHeight,
        transition: "max-height 0.2s ease",
      };
    },
    buttonText() {
      return this.isCollapsed ? "展开" : "收起";
    },
    /**
     * 根据组件类型获取需要渲染的组件名称
     */
    isComponentName() {
      return (item) => {
        if (item.component === "el-select") {
          return "SelectForm";
        } else if (item.component === "radio") {
          return "RadioGroupForm";
        } else if (item.component === "checkbox") {
          return "CheckboxGroupForm";
        } else if (item.component === "date-picker") {
          return "DatePickerForm";
        } else if (item.component === "number-range") {
          return "NumberRange";
        } else {
          return item.component || "el-input";
        }
      };
    },
    /**
     * 根据表单项配置获取占位符
     */
    placeholder() {
      return (item) => {
        if (item.placeholder) return item.placeholder;
        const arr = ["el-input", "el-input-number"];
        return !item.component || arr.includes(item.component)
          ? `请输入${item.label || ""}`
          : `请选择${item.label || ""}`;
      };
    },
  },
  methods: {
    // 搜索
    handleSearch() {
      this.$emit("search");
    },
    // 重置
    handleReset() {
      this.resetFields();
      this.$emit("reset");
    },
    refreshRealHeight() {
      this.$nextTick(() => {
        this.maxHeight = this.$refs.content.scrollHeight + "px";
      });
    },
    toggle() {
      this.isCollapsed = !this.isCollapsed;
    },
    resetFields() {
      this.copyAndClear(this.model, this.init);
      this.$refs[this.formRef].resetFields();
    },
    copyAndClear(sourceObj, targetObj) {
      // 删除原对象的所有属性
      for (const key in sourceObj) {
        if (sourceObj.hasOwnProperty(key)) {
          delete sourceObj[key];
        }
      }
      // 遍历原对象的所有属性
      for (const key in targetObj) {
        if (targetObj.hasOwnProperty(key)) {
          // 将属性值复制到目标对象
          sourceObj[key] = targetObj[key];
        }
      }
    },
    /**
     * 验证表单并执行回调函数
     * @param {Function} cb - 表单验证通过后的回调函数
     * @returns {boolean} - 表单验证结果
     */
    validate(cb) {
      this.$refs[this.formRef].validate((valid) => {
        cb(valid, this.model);

        if (valid) {
          // 如果表单验证通过,执行提交操作
        } else {
          // 如果表单验证失败,处理失败情况
          return false;
        }
      });
    },
    /**
     * 处理表单项的点击事件
     * @param {Object} item - 当前点击的表单项配置
     */
    handleClick(item, e) {
      // 处理数据改变的逻辑
      item.onClick ? item.onClick(e) : () => {};
    },
    //change型式的回调
    handleChange(item, e) {
      item.onChange ? item.onChange(e) : () => {};
    },
    /**
     * 更新表单数据模型到父组件
     */
    changeValue(item, e) {
      this.$emit("input", e);
    },
    convertKeysToNested(obj) {
      const result = {};
      for (const key in obj) {
        if (obj.hasOwnProperty(key)) {
          // 递归判断当前对象是否存在 ['a.b'] 的 key
          this.setNestedProperty(result, key, obj[key]);
        }
      }
      return result;
    },
    setNestedProperty(obj, path, value) {
      // 根据path 解析属性路径
      const keys = path.split(".");
      let current = obj;
      for (let i = 0; i < keys.length; i++) {
        const key = keys[i];
        if (i === keys.length - 1) {
          // 不是最叶子节点赋值
          this.$set(current, key, value);
        } else {
          // 不是最叶子节点赋值空对象
          if (!current[key]) {
            this.$set(current, key, {});
          }
          current = current[key];
        }
      }
    },
    getFormParams() {
      return this.convertKeysToNested(this.model);
    },
  },
  watch: {
    model: {
      handler(val) {
        if (!this.init) {
          this.init = cloneDeep(val);
        }
      },
      deep: true,
      immediate: true,
    },
    formItemConfig: {
      handler() {
        this.refreshRealHeight();
      },
      deep: true,
      immediate: true,
    },
  },
  mounted() {
    window.addEventListener("resize", this.refreshRealHeight);
  },
  beforeDestroy() {
    window.removeEventListener("resize", this.refreshRealHeight);
  },
};
</script>

css部分:

<style lang="scss" scoped>
.container {
  width: 100%;
  margin: 0 auto;
}

.content {
  overflow: hidden;
}
.form-op-container {
  display: flex;
  flex-direction: row;
  justify-content: end;
  margin-bottom: 10px;
}
.header_btns {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-left: 10px;
}
.isTransIcon {
  transform: rotateX(180deg);
}
.button-text {
  display: flex;
  gap: 5px;
  align-items: center;
  font-size: 12px;
  .icon-zhankai {
    font-size: 11px;
  }
}
</style>

 注释1:这里就是将不同类型的搜索项进行封装,通过组件名字控制渲染的是什么组件,比如输入框,单选框,select选择器、时间选择器等,还可以传入自己封装的组件。

表单搜索项类别

  • Cascader级联选择器
  • Checkbox 多选框
  • DatePicker 日期选择器
  • Radio 单选框
  • Select 选择器

1.Cascader级联选择器

<template>
  <el-cascader
    :options="options"
    :props="cascaderProps"
    :collapse-tags="collapseTags"
    collapse-tags-tooltip
    clearable
    v-model="internalValue"
    v-on="$listeners"
    v-bind="$attrs"
  />
</template>

<script>
export default {
  name: 'CascaderForm',
  props: {
    value: {
      type: [String, Array, Object],
      default: () => ([]),
    },
    options: {
      type: Array,
      required: true,
    },
    cascaderProps: {
      type: Object,
      default: () => ({}),
    },
    collapseTags: {
      type: Boolean,
      default: true,
    },
  },
  data() {
    return {
      internalValue: this.value,
    };
  },
  watch: {
    value(newVal) {
      this.internalValue = newVal;
    },
    internalValue(newVal) {
      this.$emit('input', newVal);
    },
  },
};
</script>

2. Checkbox 多选框

<template>
  <el-checkbox-group v-model="internalValue" v-on="$listeners" v-bind="$attrs">
    <el-checkbox
      v-for="option in options"
      :key="option.value"
      :label="option.value"
    >
      {{ option.label }}
    </el-checkbox>
  </el-checkbox-group>
</template>

<script>
export default {
  name: "CheckboxGroupForm",
  props: {
    value: {
      type: Array,
      default: () => [],
    },
    options: {
      type: Array,
      default: () => [],
    },
  },
  data() {
    return {
      internalValue: this.value,
    };
  },
  watch: {
    value(newVal) {
      this.internalValue = newVal;
    },
    internalValue(newVal) {
      this.$emit("input", newVal);
    },
  },
};
</script>

3.DatePicker 日期选择器

<template>
  <el-date-picker v-model="internalValue" v-on="$listeners" v-bind="$attrs" />
</template>

<script>
export default {
  name: "DatePickerForm",
  props: {
    value: {
      type: [String, Array, Date],
      default: "",
    },
  },
  data() {
    return {
      internalValue: this.value,
    };
  },
  watch: {
    value(newVal) {
      this.internalValue = newVal;
    },
    internalValue(newVal) {
      this.$emit("input", newVal);
    },
  },
};
</script>

 4.Radio 单选框

<template>
  <el-radio-group
    v-model="internalValue"
    v-on="$listeners"
    v-bind="$attrs"
    size="small"
    class="radioGroupForm"
    :class="$attrs.isButton ? 'is-button' : ''"
  >
    <template v-if="$attrs.isButton">
      <el-radio-button
        v-for="(option, index) in options"
        :key="index"
        :label="option.value"
      >
        {{ option.label }}
      </el-radio-button>
    </template>
    <template v-else>
      <el-radio
        v-for="(option, index) in options"
        :key="index"
        :label="option.value"
      >
        {{ option.label }}
      </el-radio>
    </template>
  </el-radio-group>
</template>

<script>
export default {
  props: {
    value: [String, Number],
    options: {
      type: Array,
      default: () => [],
    },
  },
  data() {
    return {
      internalValue: this.value,
    };
  },
  watch: {
    value(newVal) {
      this.internalValue = newVal;
    },
    internalValue(newVal) {
      this.$emit("input", newVal);
    },
  },
};
</script>

 5.Select 选择器

<template>
  <el-select
    v-bind="$attrs"
    v-on="$listeners"
    v-model="modelValue"
    :multiple="multiple"
    collapse-tags
  >
    <el-option
      v-for="(option, index) in options"
      :key="index"
      :label="option.label"
      :value="option.value"
    >
    </el-option>
  </el-select>
</template>

<script>
export default {
  props: {
    value: {
      required: true,
    },
    options: {
      type: Array,
      default: () => [],
    },
    multiple:{
      type: Boolean,
      default: false
    }
  },
  computed: {
    modelValue: {
      get() {
        return this.value;
      },
      set(val) {
        this.$emit("input", val);
      },
    },
  },
};
</script>

封装控制显隐排序的按钮

HTML部分:

<template>
  <el-dropdown trigger="click">
    <slot name="cus_button" v-if="cusButton" />
    <el-button icon="el-icon-s-operation" size="mini" v-else>列设置</el-button>
    <el-dropdown-menu slot="dropdown">
      <el-tree
        draggable
        :data="formItemConfig"
        :props="defaultProps"
        :allow-drop="allowDrop"
        @node-drag-over="handleNodeDragOver"
      >
        <span slot-scope="{ data }" class="tree-table-setting">
          <el-checkbox
            v-model="data.show"
            :disabled="data.setFromDisabled"
            @change="handleFormChange(data.id)"
          />
          <span class="tree-label">{{
            data.label || data.startPlaceholder || "无标题"
          }}</span>
          <i class="iconfont icon-tuodong1 tree-icon"></i>
        </span>
      </el-tree>
      <el-button
        type="primary"
        size="mini"
        class="save-form-config"
        @click="saveFormConfig"
        >保存</el-button
      >
    </el-dropdown-menu>
  </el-dropdown>
</template>

JS部分:

<script>
import { reportedFormPageInfo } from "@/api/custom/index.js";
export default {
  name: "formConfig",
  props: {
    formItemConfig: {
      type: Array,
      default: () => [],
    },
    cusButton: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      defaultProps: {
        children: "children",
        label: "label",
        disabled: "setFromDisabled",
      },
      hiddenForms: [], // 存储隐藏表单的项的 id
    };
  },
  methods: {
    // 筛选数组:重新格式化数组中的对象
    filterArray(arr) {
      return arr.map((item, index) => ({
        code: item.id || item,
        sort: index + 1,
      }));
    },
    allowDrop(draggingNode, dropNode, type) {
      // 控制拖放的逻辑:仅允许Tree节点上下拖动
      return type !== "inner";
    },
    // el-tree拖拽删除禁用标志
    handleNodeDragOver(node, enter, e) {
      e.preventDefault(); // 防止默认处理
      e.dataTransfer.dropEffect = "move"; // 设置拖动效果为move
    },
    handleFormChange(itemId) {
      const columnIndex = this.hiddenForms.indexOf(itemId);
      const item = this.formItemConfig.find((item) => item.id === itemId);

      if (item) {
        if (!item.show) {
          // 如果列被隐藏且不在数组中,则添加
          if (columnIndex === -1) {
            this.hiddenForms.push(itemId);
          }
        } else {
          // 如果列被显示且在数组中,则移除
          if (columnIndex !== -1) {
            this.hiddenForms.splice(columnIndex, 1);
          }
        }
      }
      // 返回show为true的数组
      return this.formItemConfig.filter((item) => item.show === true);
    },
    // 保存表单配置
    saveFormConfig() {
      const params = this.filterArray(this.handleFormChange());
      reportedFormPageInfo("custom", params).then((res) => {
        if (res.code === 200) {
          this.$message({
            message: "保存成功",
            type: "success",
            duration: 1500,
          });
        }
      });
    },
  },
};
</script>

 css部分:

<style scoped lang="scss">
::v-deep div[aria-disabled="true"] {
  display: none;
}
.tree-table-setting {
  display: flex;
  align-items: center;
  justify-content: space-around;
  .tree-label {
    width: 120px; /* 设置固定宽度,根据需要调整 */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    margin-left: 5px;
  }

  .tree-icon {
    margin-left: auto; /* 将图标推到容器的最右边 */
    margin-right: 15px; /* 将图标推到容器的最右边 */
  }
}
.save-form-config {
  margin-left: 24px;
}
.cus-shezhi {
  border: 1px solid #e7e7e7;
  border-radius: 10px;
}
</style>

如何使用 

前端通过配置项可以控制显隐,在FreeForm组件里是通过v-show控制的;

排序的话是element-UI组件里Tree 树形控件可拖拽节点实现的;

现在在前端保存后,刷新后还是会复原的,所以我们要保存起来,如果没有特别高的要求,可以放在localstorage里,这里我们的需求是需要保存在后端,不同的部门看到的表格、表单不一样。我们要把配置好的数据传给后端,但是最开始渲染的时候最好还是从后端拿数据,哪个部门能看到哪些搜索项,所以我们和后端约定了一个方案,提供了一个版本号的概念,前端每次更改有新增表单配置项的时候,就修改版本号,版本号一样就正常从后端拿数据,然后渲染,不一样或者最开始没有版本号的时候,把前端的表单配置项数组上报给后端。

1.在列表页引入该表单组件FreeForm,HTML部分:

    <free-form
      ref="form"
      formRef="freeForm"
      :model="formData"
      :formItemConfig="showFormItemConfig"
      :labelWidth="labelWidth"
      label-position="top"
      :showSearch.sync="showSearch"
      @search="handleQuery"
      @reset="resetQuery"
      @keyup.enter.native="handleQuery"
    >
      <template #header_btns_after>
        <formConfig :formItemConfig="formItemConfig" cusButton>
          <template #cus_button>
            <el-button
              size="mini"
              circle
              icon="iconfont icon-shezhi cus-shezhi"
            />
          </template>
        </formConfig>
      </template>
    </free-form>

2.data里配置:

   // 表单数据
      formData: {
        queryParams: { pageNum: 1, pageSize: 10 },
      },
      labelWidth: "120px",
      formItemConfig: [], // 存储表单项的配置
      version: null,
  
  created() {
    // 初始化表单配置
    this.initFormItemConfig();
    // 获取页面信息:版本号、form表单、table表格配置项
    this.getPageInfo();
  },

这些数据源走的是接口,做了统一的处理: 

  computed: {
    // 获取渲染数据源
    showFormItemConfig() {
      return this.formItemConfig.map((item) => {
        if (item.id === "xxx") {
          return {
            ...item,
            options: this.xxxList,
          };
        } else if (item.id === "zzz") {
          return {
            ...item,
            options: this.zzzList,
          };
        } else if (item.id === "yyy") {
          return {
            ...item,
            options: this.yyyOptions,
            cascaderProps: this.deptProps,
          };
        } else if (
          item.id === "xx" ||
          item.id === "xx" ||
          item.id === "xx"
        ) {
          return {
            ...item,
            options: this.aaaOptions,
            cascaderProps: this.props,
          };
        }
        // 其他配置项的处理逻辑...
        return item;
      });
    },
  },

 

methods:{
    // 初始化表单配置
    initFormItemConfig() {
      this.formItemConfig = [
        {
          id: "namePhone",
          label: "姓名/手机号",
          prop: "queryParams.namePhone",
          clearable: true,
          component: "el-input", // el-input可以省略,默认使用el-input
          placeholder: "姓名/手机号", // placeholder可以省略,默认显示“请输入+label”
          show: true, // 展示与隐藏
          maxlength: "11",
        },
        {
          id: "status",
          label: "状态",
          prop: "queryParams.status",
          clearable: true,
          component: SelectForm, // el-input可以省略,默认使用el-input
          placeholder: "状态", // placeholder可以省略,默认显示“请输入+label”
          show: false, // 展示与隐藏
          multiple: true,
          options: that.dict.type.xx_status,//这里走的是字典
        },
        {
          id: "xxx",
          label: "新之助",
          prop: "queryParams.xxx",
          component: SelectForm, // 可以传入任意组件
          placeholder: "新之助",
          clearable: true,
          options: that.customerManagerList,
          show: false, // 展示与隐藏
          multiple: true,
        },
        {
          id: "xxx",
          label: "上学时间",
          prop: "xxx",
          component: DatePickerForm, // el-input可以省略,默认使用el-input
          type: "daterange",
          startPlaceholder: "上学时间",
          valueFormat: "yyyy-MM-dd",
          hidden: false,
          show: false,
          width: "205px",
          setFromDisabled: false, //设置表单配置项是否禁用
        },
        {
          id: "xxx",
          label: "放学时间",
          prop: "xxx",
          component: DatePickerForm, // el-input可以省略,默认使用el-input
          type: "daterange",
          startPlaceholder: "放学时间",
          valueFormat: "yyyy-MM-dd",
          hidden: false,
          show: false,
          width: "205px",
          setFromDisabled: false, //设置表单配置项是否禁用
        },
        {
          id: "remark",
          label: "备注",
          prop: "queryParams.remark",
          clearable: true,
          component: "el-input", // el-input可以省略,默认使用el-input
          placeholder: "备注", // placeholder可以省略,默认显示“请输入+label”
          show: false, // 展示与隐藏
        },
        {
          id: "xxx",
          label: "xx状态",
          prop: "queryParams.xxx",
          component: SelectForm, // 可以传入任意组件
          placeholder: "xx状态",
          clearable: true,
          options: [
            {
              value: 0,
              label: "xxx",
            },
            {
              value: 1,
              label: "xxx",
            },
          ],
          show: false, // 展示与隐藏
        },

        {
          id: "xxx",
          label: "妮妮",
          prop: "queryParams.xxx",
          component: SelectForm, // 可以传入任意组件
          placeholder: "妮妮",
          clearable: true,
          options: that.provinceList,
          show: false, // 展示与隐藏
        },
        {
          id: "xxx",
          label: "风间",
          prop: "xxx",
          component: CascaderForm, // 可以传入任意组件
          placeholder: "风间",
          clearable: true,
          options: that.deptNameOptions, // 设置 options
          cascaderProps: that.deptProps, // 设置 props
          show: false, // 展示与隐藏
        },
        {
          id: "xxx",
          label: "阿呆",
          prop: "xxx",
          component: CascaderForm, // 可以传入任意组件
          placeholder: "阿呆",
          clearable: true,
          options: that.channelOptions, // 设置 options
          cascaderProps: that.props, // 设置 props
          show: false, // 展示与隐藏
        },
        {
          id: "xxx",
          label: "正南",
          prop: "xxx",
          component: NumberRange,
          clearable: true,
          width: "205px",
          show: false, // 展示与隐藏
          startPlaceholder: "请输入",
          endPlaceholder: "请输入",
        },
      ];
    },
}
   // 获取页面信息:版本号、form表单、table表格配置项
    getPageInfo() {
      // 要保证每个页面的pageCode唯一
      getPageInfo("xx").then((res) => {
        if (res.code === 200) {
          // 获取当前版本号
          this.version = process.env.VUE_APP_VERSION;
          // 判断后端版本号是否存在、是否一致
          if (res.data && res.data.version === this.version) {
            // 不上报版本号,将获取到的表单、表格配置项数组渲染到前端界面
            let searchFields = res.data.searchFields;
            let tableFields = res.data.tableFields;
            this.formItemConfig = this.matchAndModify(
              this.formItemConfig,
              searchFields
            );
            this.tableItemConfig = this.matchAndModify(
              this.tableItemConfig,
              tableFields
            );
            return;
          } else if (!res.data || res.data.version !== this.version) {
            // 版本不一致,将表单、表格配置项数组传给后端
            reportedPageInfo({
              pageCode: "xx",
              version: process.env.VUE_APP_VERSION,
              searchFields: this.filterArray(this.formItemConfig),
              tableFields: this.filterArray(this.tableItemConfig),
            }).then((res) => {
              if (res.code === 200) {
                let searchFields = res.data.searchFields;
                let tableFields = res.data.tableFields;
                // 从后端拿到的表单、表格配置项数组渲染到前端界面
                this.formItemConfig = this.matchAndModify(
                  this.formItemConfig,
                  searchFields
                );
                this.tableItemConfig = this.matchAndModify(
                  this.tableItemConfig,
                  tableFields
                );
              }
            });
          }
        }
      });
    },

 

    // 定义一个方法来处理匹配
    matchAndModify(originalArray, backendArray) {
      // 克隆一份原始数组,以免直接修改原数组
      const clonedArray = [...originalArray];

      // 根据 backendArray 中的顺序对 clonedArray 进行排序
      clonedArray.sort((a, b) => {
        let indexA = backendArray.findIndex((item) => item.code === a.id);
        let indexB = backendArray.findIndex((item) => item.code === b.id);
        return indexA - indexB;
      });

      // 对排序后的 clonedArray 进行遍历和处理
      const res = clonedArray.map((item) => {
        const flag = backendArray.find((backendItem) => {
          return backendItem.code === item.id;
        });
        if (flag) {
          return {
            ...item,
            show: true,
          };
        }
        return item;
      });

      return res;
    },
   // 筛选数组:重新格式化数组中的对象
    filterArray(arr) {
      return arr.map((item, index) => ({
        code: item.id || item,
        sort: index + 1,
      }));
    },

小结

参考文章链接:

https://juejin.cn/post/7022140926906597384icon-default.png?t=N7T8https://juejin.cn/post/7022140926906597384

https://juejin.cn/post/7311602153826402313?searchId=20240726140625580D4C02135D9973239D#heading-21icon-default.png?t=N7T8https://juejin.cn/post/7311602153826402313?searchId=20240726140625580D4C02135D9973239D#heading-21

  • 12
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,您可以这样封装: ```vue <template> <a-drawer :visible="visible" :mask-closable="false" :closable="false" :width="420" @close="onClose" > <slot></slot> <div class="drawer-footer"> <a-button style="margin-right: 8px;" @click="onClose">取消</a-button> <a-button type="primary" :loading="submitting" @click="onSubmit"> 提交 </a-button> </div> </a-drawer> </template> <script> import { Drawer, Button } from 'ant-design-vue'; export default { name: 'MyDrawer', components: { 'a-drawer': Drawer, 'a-button': Button, }, props: { visible: { type: Boolean, default: false, }, }, data() { return { submitting: false, }; }, methods: { onClose() { this.$emit('update:visible', false); }, onSubmit() { this.submitting = true; // 处理表单提交逻辑 this.$emit('submit', () => { this.submitting = false; this.onClose(); }); }, }, }; </script> <style> .drawer-footer { display: flex; justify-content: flex-end; align-items: center; height: 56px; border-top: 1px solid #e8e8e8; padding: 0 16px; } </style> ``` 然后在父组件中使用: ```vue <template> <div> <a-button type="primary" @click="visible = true"> 打开抽屉 </a-button> <my-drawer :visible.sync="visible" @submit="onSubmit"> <a-form :form="form"> <a-form-item label="姓名" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }"> <a-input v-model="form.name" /> </a-form-item> <a-form-item label="年龄" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }"> <a-input-number v-model="form.age" /> </a-form-item> </a-form> </my-drawer> </div> </template> <script> import MyDrawer from './MyDrawer.vue'; import { Form, Input, InputNumber } from 'ant-design-vue'; export default { components: { MyDrawer, 'a-form': Form, 'a-form-item': Form.Item, 'a-input': Input, 'a-input-number': InputNumber, }, data() { return { visible: false, form: { name: '', age: undefined, }, }; }, methods: { onSubmit(callback) { this.$refs.form.validate(async (valid) => { if (!valid) { return; } // 处理表单提交逻辑 await this.$api.submitForm(this.form); callback(); }); }, }, }; </script> ``` 这样,您就可以在父组件中通过控制 `visible` 来控制抽屉的显隐,并且抽屉中的表单数据可以通过 `submit` 事件提交到父组件中进行处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值