基于ElementUI封装了CRUD的弹框组件

6,736 阅读1分钟

开头先BB两句

回头看自己以前的代码,感觉就像屎一样。我会想,为什么不封装一下,如果用的是ElementUI框架,也可以在此基础上进行二次封装。

譬如说,这个用来对列表数据进行增删改查的弹框。

f139478516edb77ddc56edff2e29c80.png

开始封装

原本只是个小功能,但是别的模块也需要用到。

我的想法就是,把弹框标题,table表头,必填字节,接口请求路径,增删改查CRUD,等等,放在一个json对象里面。通过父组件向子组件传参的方式,展示不同内容,调用不同的接口。

极大提高了代码的复用性。

json对象如下所示

  // 示例:
    let example = {
      // 弹框标题
      popTitle: "编辑主题",
      // table
      columnList: [
        {
          prop: "themeName",
          label: "主题名称",
        },
        {
          prop: "themeDescribe",
          label: "主题描述",
        },
      ],
      // 必填的字段
      requiredKeys: ["themeName"],
      tableData: this.themeList,
      // 主键,默认为id
      idKey: "id",
      // 删除的参数名称,默认为ids
      deleteKey: "ids",
      // 批量的参数名称,默认为ids
      batchDeleteKey: "ids",
      // 接口请求路径,增删改查CRUD
      interfaceUrl: {
        add: "/target/addTheme",
        edit: "/target/updateTheme",
        delete: "/target/deleteTheme",
        // 批量删除
        batchDelete: "/target/deleteTheme",
        list: "/target/themelist",
      },
    };

table表头作为列表传入,数据结构如下

 columnList: [
    {
      prop: "themeName",
      label: "主题名称",
    },
    {
      prop: "themeDescribe",
      label: "主题描述",
    },
],

在子组件中循环渲染出表头

<el-table-column
  v-for="(item, index) in columnList"
  :key="index"
  :show-overflow-tooltip="item.showOverflowTooltip || true"
  :align="item.align || 'center'"
  :header-align="item.headerAlign || item.align || 'center'"
  :label="item.label"
  :width="item.width"
>
  <template slot-scope="scope">
    <span v-if="scope.row.statusBtn === false">{{ scope.row[item.prop] }}</span>
    <el-input
      v-else-if="scope.row.statusBtn === true"
      v-model="scope.row[item.prop]"
      size="mini"
    />
  </template>
</el-table-column>

在父组件中调用

<!-- 编辑主题的弹框 -->
<edit-table-modal
  ref="editTableModal"
  :visible.sync="editTableModal.show"
  :tableObject="themeTableObject"
  v-if="editTableModal.show"
  @ok="editTableFunction"
/>

完整代码,点击下方详细信息查看

<template>
  <el-dialog
    v-if="visible"
    v-dialogDrag
    :visible.sync="visible"
    :modal="true"
    :before-close="cancelModal"
    :title="title"
    center
  >
    <div
      v-loading="loading"
      class="base-box"
      element-loading-text="加载中"
      element-loading-spinner="el-icon-loading"
    >
      <el-button
        type="primary"
        icon="el-icon-plus"
        style="margin-bottom: 10px"
        size="mini"
        @click="addRow()"
        >添加</el-button
      >
      <el-button
        v-if="selectionList.length"
        type="danger"
        icon="el-icon-close"
        style="margin-bottom: 10px"
        size="mini"
        @click="batchDelete()"
        >批量删除</el-button
      >
      <el-table
        :data="tableData"
        style="width: 100%"
        :height="tableHeight"
        border
        size="small"
        :header-cell-class-name="headerClassNameFun"
        @selection-change="handleSelectionChange"
      >
        <el-table-column
          align="center"
          header-align="center"
          type="selection"
          width="55"
        />
        <el-table-column
          v-for="(item, index) in columnList"
          :key="index"
          :show-overflow-tooltip="item.showOverflowTooltip || true"
          :align="item.align || 'center'"
          :header-align="item.headerAlign || item.align || 'center'"
          :label="item.label"
          :width="item.width"
        >
          <template slot-scope="scope">
            <span v-if="scope.row.statusBtn === false">{{ scope.row[item.prop] }}</span>
            <el-input
              v-else-if="scope.row.statusBtn === true"
              v-model="scope.row[item.prop]"
              size="mini"
            />
          </template>
        </el-table-column>
        <el-table-column
          align="center"
          header-align="center"
          label="操作"
          :width="optColumnWidth"
        >
          <template slot-scope="scope">
            <el-button
              v-if="scope.row.statusBtn === false"
              type="primary"
              size="mini"
              icon="el-icon-edit"
              @click="editCheck(scope.row)"
              >编 辑</el-button
            >
            <el-button
              v-else-if="scope.row.statusBtn === true"
              type="success"
              size="mini"
              icon="el-icon-check"
              @click="sureCheck(scope.row)"
              >保 存</el-button
            >
            <el-button
              type="danger"
              size="mini"
              icon="el-icon-delete"
              @click="deleteFun(scope, 'delete')"
              >删除</el-button
            >
          </template>
        </el-table-column>
      </el-table>
    </div>
    <div slot="footer" class="dialog-footer">
      <el-button v-loading="loading" type="primary" @click="submit">确定</el-button>
      <el-button @click="cancelModal">取消</el-button>
    </div>
  </el-dialog>
</template>

<script>
export default {
  components: {},
  props: {
    tableHeight: {
      type: String,
      default: "50vh",
    },
    optColumnWidth: {
      type: Number,
      default: 200,
    },
    visible: {
      type: Boolean,
      default: false,
    },
    tableObject: {
      type: Object,
      default: null,
    },
  },
  data() {
    return {
      loading: false,
      title: "编辑",
      selectionList: [],
      url: {},
      tableData: [],
      editObject: {},
      basicObject: {},
      columnList: [],
      idKey: "id",
      deleteKey: "id",
      batchDeleteKey: "ids",
      requiredKeys: [],
    };
  },
  computed: {},
  created() {
    // tableObject 是从父组件传过来的json对象
    // 示例:
    let example = {
      // 弹框标题
      popTitle: "编辑主题",
      // table
      columnList: [
        {
          prop: "themeName",
          label: "主题名称",
        },
        {
          prop: "themeDescribe",
          label: "主题描述",
        },
      ],
      // 必填的字段
      requiredKeys: ["themeName"],
      tableData: this.themeList,
      // 主键,默认为id
      idKey: "id",
      // 删除的参数名称,默认为ids
      deleteKey: "ids",
      // 批量的参数名称,默认为ids
      batchDeleteKey: "ids",
      // 接口请求路径,增删改查CRUD
      interfaceUrl: {
        add: "/target/addTheme",
        edit: "/target/updateTheme",
        delete: "/target/deleteTheme",
        // 批量删除
        batchDelete: "/target/deleteTheme",
        list: "/target/themelist",
      },
    };
    if (this.tableObject) this.init(this.tableObject);
  },
  methods: {
    init(item) {
      this.title = item.popTitle || item.label;
      this.columnList = item.columnList;
      this.url = item.interfaceUrl;
      this.tableData = [];
      if (item.idKey) this.idKey = item.idKey;
      if (item.deleteKey) this.deleteKey = item.deleteKey;
      if (item.batchDeleteKey) this.batchDeleteKey = item.batchDeleteKey;
      if (item.requiredKeys) this.requiredKeys = item.requiredKeys;
      item.tableData.forEach((element) => {
        this.tableData.push({ ...element, statusBtn: false });
      });
      this.basicObject = {};
      this.columnList.forEach((element) => {
        this.basicObject[element.prop] = "";
      });
    },
    submit() {
      this.cancelModal();
    },
    // 判断值内容是否全部填写
    flagFun() {
      const flag = this.tableData.every((item) => {
        return Object.values(item).every((element) => element !== "");
      });
      return flag;
    },
    // 勾选弹框表格添加一行
    addRow() {
      if (this.tableData.length > 0) {
        const statusBtnFlag = this.tableData.some((item) => item.statusBtn);
        if (statusBtnFlag) {
          this.$sweetAlert.warnWithTimer("请先保存当前编辑项!");
          return;
        }
      }
      const addTempObject = { ...this.basicObject, statusBtn: true };
      addTempObject[this.idKey] = "addTempKey" + new Date().getTime();
      this.tableData.unshift(addTempObject);
    },
    editCheck(row) {
      row.statusBtn = true;
      // this.$forceUpdate();
      this.editObject = row;
    },
    // 勾选弹框保存按钮
    sureCheck(row) {
      let flag = false;
      for (const key in row) {
        const element = row[key];
        if (this.requiredKeys.includes(key) && element === "") {
          flag = true;
          break;
        }
      }
      const values = Object.values(row);
      if (flag) {
        this.$sweetAlert.warnWithTimer("请填写编辑项的内容!");
        return;
      } else {
        const formData = JSON.parse(JSON.stringify(row));
        if (formData[this.idKey].includes("addTempKey")) {
          delete formData[this.idKey];
        }
        Promise.all([this.interfaceOperate(formData)]).then((result) => {
          row.statusBtn = false;
          this.$forceUpdate();
        });
      }
    },
    interfaceOperate(form, type, methodType) {
      return new Promise((resolve, reject) => {
        let httpurl = this.url.add;
        const formData = {
          ...form,
        };
        if (formData[this.idKey]) {
          httpurl = this.url.edit;
        }
        if (type) httpurl = this.url[type];

        if (formData.statusBtn != undefined) delete formData.statusBtn;
        let method = "post";
        if (methodType) method = methodType;
        this.loading = true;
        this.$api.httpAction(httpurl, formData, method).then((res) => {
          this.loading = false;
          if (res.code == 200) {
            this.$sweetAlert.successWithTimer(res.msg);
            resolve();
          } else {
            this.$sweetAlert.errorWithTimer(res.msg);
          }
        });
      });
    },
    // 删除一行
    deleteFun(scope) {
      const formData = {};
      formData[this.deleteKey] = scope.row[this.idKey];
      const type = "delete";
      this.confirmDelete(formData, type, () => {
        this.tableData.splice(scope.$index, 1);
      });
    },
    batchDelete() {
      if (this.selectionList.length == 0) {
        this.$sweetAlert.warnWithTimer("至少选择一条数据!");
        return;
      }
      const array = this.selectionList.map((item) => item[this.idKey]);
      const ids = array.join(",");
      const formData = {};
      formData[this.batchDeleteKey] = ids;
      const type = "batchDelete";
      this.confirmDelete(formData, type, () => {
        this.tableData = this.tableData.filter(
          (item) => !array.includes(item[this.idKey])
        );
      });
    },
    confirmDelete(formData, type, callback) {
      const msg = "您是否确定删除?";
      const title = "温馨提示";
      this.$confirm(msg, title, {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      })
        .then(() => {
          if (type == "delete") {
            if (formData[this.deleteKey].includes("addTempKey")) return callback();
          } else if (type == "batchDelete") {
            let tempDeleteOptions = formData[this.batchDeleteKey];
            tempDeleteOptions = tempDeleteOptions.split(",");
            tempDeleteOptions = tempDeleteOptions.filter(
              (item) => !item.includes("addTempKey")
            );
            if (tempDeleteOptions.length > 0) {
              formData[this.batchDeleteKey] = tempDeleteOptions.join(",");
            } else {
              return callback();
            }
          }
          Promise.all([this.interfaceOperate(formData, type)]).then((result) => {
            callback();
          });
        })
        .catch(() => {});
    },
    handleSelectionChange(val) {
      console.log(val);
      this.selectionList = val;
    },
    headerClassNameFun({ row, column, rowIndex, columnIndex }) {
      //  columnIndex 列下标
      if (column == 0 || column == row.length - 1) {
        return "";
      }
      if (
        this.columnList[columnIndex - 1] &&
        this.requiredKeys.includes(this.columnList[columnIndex - 1].prop)
      ) {
        return "star";
      } else {
        return "";
      }
    },
    cancelModal() {
      if (this.tableData.length > 0) {
        const statusBtnFlag = this.tableData.some((item) => item.statusBtn);
        if (statusBtnFlag) {
          this.$sweetAlert.warnWithTimer("请先保存当前编辑项!");
          return;
        }
      }
      // 关闭弹窗,触发父组件修改visible值
      this.$emit("update:visible", false);
      this.$emit("ok");
    },
  },
};
</script>
<style lang="scss" scoped>
.box-item {
  margin: 10px 0;
}
.bot-btn-list {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-start;
  overflow-y: auto;
  max-height: 300px;
  & > div {
    width: 33.33%;
    margin-top: 20px;
  }
  & > div:hover {
    font-weight: bolder;
  }
}
</style>