封装el-table组件,实现表头自定义显隐和拖动排序

1、封装el-table组件。


<template>
  <div ref="table-box" class="table-content-box">
    <div>
      <el-table
        :data="data"
        :stripe="stripe"
        :size="size"
        :empty-text="emptyText"
        :height="newHeight || height"
        :max-height="maxHeight"
        v-bind="tableConfig"
        @selection-change="selectionLineChangeHandle"
        ref="zwTable"
        :tree-props="treeProps"
        :expand-row-keys="expandRowKeys"
        :row-key="rowKey"
        :default-expand-all="defaultExpandAll"
      >
        <el-table-column v-if="expand" type="expand" fixed="left">
          <template slot-scope="{ row, $index }">
            <slot name="expand" :row="row" :index="$index"></slot>
          </template>
        </el-table-column>
        <template v-for="item in columns">
          <template v-if="item.show">
            <!-- 渲染 使用插槽 -->
            <template v-if="item.useSlot">
              <el-table-column
                v-if="item.prop === 'action'"
                :row-key="item.prop"
                :key="item.prop"
                :min-width="item.minWidth"
                :align="item.align || 'center'"
                :fixed="item.fixed || 'right'"
                :show-overflow-tooltip="false"
                v-bind="item"
              >
                <!-- 作用域 插槽 -->
                <template slot-scope="{ row, column, index }">
                  <slot
                    name="columns"
                    :row="{ ...row, ...item }"
                    :column="column"
                    :index="index"
                    :value="row[item.prop]"
                    :prop="item.prop"
                  >
                  </slot>
                </template>
              </el-table-column>
              <el-table-column
                v-else-if="item.numSort"
                :row-key="item.prop"
                :key="item.prop"
                :min-width="item.minWidth"
                :align="item.align || 'center'"
                :show-overflow-tooltip="false"
                v-bind="item"
                :sort-method="
                  (a, b) => {
                    return a[item.prop] - b[item.prop];
                  }
                "
              >
                <!-- 作用域 插槽 -->
                <template v-if="item.useTooltip" slot="header">
                  <el-tooltip
                    :content="item.content"
                    placement="top"
                    :open-delay="200"
                  >
                    <span
                      ><i
                        class="el-icon-info"
                        style="color: #409eff; margin-right: 5px"
                      ></i
                      >{{ item.label }}</span
                    >
                  </el-tooltip>
                </template>
                <template slot-scope="{ row, column, index }">
                  <slot
                    name="columns"
                    :row="{ ...row, ...item }"
                    :column="column"
                    :index="index"
                    :value="row[item.prop]"
                    :prop="item.prop"
                  >
                  </slot>
                </template>
              </el-table-column>
              <el-table-column
                v-else
                :row-key="item.prop"
                :key="item.prop"
                :min-width="item.minWidth"
                :align="item.align || 'center'"
                :show-overflow-tooltip="false"
                v-bind="item"
              >
                <!-- 作用域 插槽 -->
                <template v-if="item.useTooltip" slot="header">
                  <el-tooltip
                    :content="item.content"
                    placement="top"
                    :open-delay="200"
                  >
                    <span
                      ><i
                        class="el-icon-info"
                        style="color: #409eff; margin-right: 5px"
                      ></i
                      >{{ item.label }}</span
                    >
                  </el-tooltip>
                </template>
                <template slot-scope="{ row, column, index }">
                  <slot
                    name="columns"
                    :row="{ ...row, ...item }"
                    :column="column"
                    :index="index"
                    :value="row[item.prop]"
                    :prop="item.prop"
                  >
                  </slot>
                </template>
              </el-table-column>
            </template>
            <!-- 渲染 操作 -->

            <template v-else-if="item.actions && item.actions.length > 0">
              <el-table-column
                :row-key="item.prop"
                :key="item.prop"
                :align="item.align || 'center'"
                :fixed="item.fixed || 'right'"
                :show-overflow-tooltip="false"
                v-bind="item"
              >
                <template slot-scope="{ row }">
                  <template v-for="newItem in item.actions">
                    <template
                      v-if="
                        newItem.mold === 'delete' || newItem.mold === 'twice'
                      "
                    >
                      <el-popconfirm
                        :title="newItem.title || '这一段内容确定删除吗?'"
                        :key="newItem.text"
                        @confirm="newItem.click(row)"
                        @cancel="() => {}"
                      >
                        <template slot="reference">
                          <el-tooltip
                            class="item"
                            effect="dark"
                            :content="newItem.text"
                            placement="top"
                          >
                            <el-button
                              style="margin-left: 8px; color: red"
                              :size="newItem.size || 'small '"
                              icon="el-icon-delete"
                              v-bind="newItem"
                              >删除</el-button
                            >
                          </el-tooltip>
                        </template>
                      </el-popconfirm>
                    </template>

                    <template v-else>
                      <el-tooltip
                        class="item"
                        effect="dark"
                        :content="newItem.text"
                        placement="top"
                        :key="newItem.text"
                      >
                        <el-button
                          v-if="newItem.text == '详情'"
                          @click="newItem.click(row)"
                          :type="newItem.type || primary"
                          :size="newItem.size || 'small '"
                          :icon="'el-icon-view'"
                          v-bind="newItem"
                          >{{ $t("xiang-qing") }}</el-button
                        >
                        <el-button
                          v-else-if="newItem.text == '修改'"
                          @click="newItem.click(row)"
                          :type="newItem.type || primary"
                          :size="newItem.size || 'small '"
                          :icon="'el-icon-edit-outline'"
                          v-bind="newItem"
                          >{{ $t("xiu-gai") }}</el-button
                        >
                        <el-button
                          v-else
                          @click="newItem.click(row)"
                          :type="newItem.type || primary"
                          :size="newItem.size || 'small '"
                          :icon="newItem.icon || 'el-icon-cherry'"
                          v-bind="newItem"
                          >{{ newItem.text }}</el-button
                        >
                      </el-tooltip>
                    </template>
                  </template>
                </template>
              </el-table-column>
            </template>
            <!-- 渲染 其他 -->
            <template v-else>
              <el-table-column
                v-if="item.numSort"
                :type="item.type"
                :row-key="item.prop"
                :min-width="item.minWidth"
                :key="item.prop"
                :align="item.align || 'center'"
                :fixed="item.fixed"
                :show-overflow-tooltip="false"
                v-bind="item"
                :sort-method="
                  (a, b) => {
                    return a[item.prop] - b[item.prop];
                  }
                "
              >
                <template v-if="item.useTooltip" slot="header">
                  <el-tooltip
                    :content="item.content"
                    placement="top"
                    :open-delay="200"
                  >
                    <span
                      ><i
                        class="el-icon-info"
                        style="color: #409eff; margin-right: 5px"
                      ></i
                      >{{ item.label }}</span
                    >
                  </el-tooltip>
                </template>
              </el-table-column>
              <el-table-column
                v-else
                :type="item.type"
                :row-key="item.prop"
                :min-width="item.minWidth"
                :key="item.prop"
                :align="item.align || 'center'"
                :fixed="item.fixed"
                :show-overflow-tooltip="false"
                v-bind="item"
              >
                <template v-if="item.useTooltip" slot="header">
                  <el-tooltip
                    :content="item.content"
                    placement="top"
                    :open-delay="200"
                  >
                    <span
                      ><i
                        class="el-icon-info"
                        style="color: #409eff; margin-right: 5px"
                      ></i
                      >{{ item.label }}</span
                    >
                  </el-tooltip>
                </template>
              </el-table-column>
            </template>
          </template>
        </template>
      </el-table>
    </div>
    <!--分页区域-->
    <div class="table-content-foot" v-if="paging">
      <div></div>
      <div class="pagination">
        <el-pagination
          :background="background"
          :current-page.sync="currentPage"
          :page-size.sync="pageSize"
          :layout="layout"
          :page-sizes="pageSizes"
          :pager-count="pagerCount"
          :total="total"
          v-bind="$attrs"
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
        >
        </el-pagination>
      </div>
    </div>
  </div>
</template>
 
<script>
import { scrollTo } from "@/utils/scroll-to";
export default {
  name: "TablePlus",
  props: {
    columns: {
      type: Array,
      default: () => [],
    },
    data: {
      // 表格数据
      type: Array,
      default: () => [],
    },
    total: {
      required: true,
      type: Number,
    },
    page: {
      type: Number,
      default: 1,
    },
    limit: {
      type: Number,
      default: 20,
    },
    pageSizes: {
      type: Array,
      default() {
        return [10, 20, 30, 50, 200];
      },
    },
    // 移动端页码按钮的数量端默认值5
    pagerCount: {
      type: Number,
      default: document.body.clientWidth < 992 ? 5 : 7,
    },
    layout: {
      type: String,
      default: "total, sizes, prev, pager, next, jumper",
    },
    background: {
      type: Boolean,
      default: true,
    },
    autoScroll: {
      type: Boolean,
      default: false,
    },
    selectIsShow: {
      // 选择框是否显示
      type: Boolean,
      default: false,
    },
    idxIsShow: {
      // 序列号是否显示
      type: Boolean,
      default: true,
    },
    height: {
      type: [Number, String],
      default: "calc(100vh - 15rem)",
    },
    maxHeight: {
      type: [Number, String],
      default: "100%",
    },
    emptyText: {
      // 无数据时显示文字
      type: String,
      default: "暂无数据",
    },
    stripe: {
      // 是否为斑马纹 table
      type: Boolean,
      default: false,
    },
    size: {
      // Table 的尺寸
      type: String,
      default: "medium",
    },
    tableConfig: {
      // 更多table 组件的配置
      type: Object,
      default: () => {
        return {
          headerCellStyle: {
            height: "52px",
            background: "#f8f7f8",
            color: "#54585A",
            fontWeight: 500,
            fontSize: "14px",
            fontFamily: "font-family: Source Han Sans CN, Source Han Sans CN",
            padding: "0",
          },
          rowStyle: {
            height: "72px ",
            fontFamily: "font-family: Source Han Sans CN, Source Han Sans CN",
            fontWeight: 400,
            fontSize: "14px",
            color: "#2B2C31",
          },
          cellStyle: {
            padding: "0",
            fontSize: "14px",
          },
        };
      },
    },
    paging: {
      // 分页
      type: Boolean,
      default: true,
    },
    treeProps: {
      // 树形表格配置
      type: Object,
      default: () => {
        return {};
      },
    },
    expandRowKeys: {
      // 展开行
      type: Array,
      default: () => [],
    },
    rowKey: {
      // 行key
      type: String,
      default: "id",
    },
    defaultExpandAll: {
      // 默认展开全部
      type: Boolean,
      default: false,
    },
    expand: {
      // 是否可展开
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      // 复选框选中的行
      selectRows: [],
      // 内部高度,动态计算
      innerHeight: 0,
      // 行编辑开启的行索引集合
      editRowsKey: [],
      // 行编辑的编辑数据,key值为行索引
      editRowsData: {},
      // 按钮区域的高度 TODO: 暂时写死
      buttonHeight: 42,
      newHeight: 0,
      rowBackground: {
        background: "#ffff66",
      },
      on_view: require("@/assets/operate_icons/on_view.png"),
      on_edit: require("@/assets/operate_icons/on_edit.png"),
      on_delete: require("@/assets/operate_icons/on_delete.png"),
      on_other: require("@/assets/operate_icons/on_other.png"),
    };
  },

  updated() {
    // 解决表格抖动
    this.$refs.zwTable.doLayout();
    setTimeout(() => {
      this.$forceUpdate();
    }, 5);
  },
  computed: {
    currentPage: {
      get() {
        return this.page;
      },
      set(val) {
        this.$emit("update:page", val);
      },
    },
    pageSize: {
      get() {
        return this.limit;
      },
      set(val) {
        this.$emit("update:limit", val);
      },
    },
  },
  methods: {
    // 当选择项发生变化时会触发该事件
    selectionLineChangeHandle(val) {
      this.$emit("selectionLineChangeHandle", val);
    },

    // 对外暴露table实列
    createTable() {
      this.$emit("createTable", this.$refs.zwTable);
    },

    //  设置某个选中
    toggleRowSelection(val) {
      this.$refs.zwTable.toggleRowSelection();
    },

    // 设置全选
    toggleAllSelection() {
      this.$refs.zwTable.toggleAllSelection();
    },

    handleSizeChange(val) {
      this.$emit("pagination", { page: this.currentPage, limit: val });
      if (this.autoScroll) {
        scrollTo(0, 800);
      }
    },
    handleCurrentChange(val) {
      this.$emit("pagination", { page: val, limit: this.pageSize });
      if (this.autoScroll) {
        scrollTo(0, 800);
      }
    },
  },
};
</script>
 
<style lang="scss" scoped>
.table-content-box {
  .table-content-foot {
    margin-top: 10px;
    display: flex;
    justify-content: space-between;
  }
}
</style>

2、创建一个mixins文件夹,新建一个TablePlusMixin.js ,这是混入js文件,放在vue里。这里的接口是保存自定义表到服务器的,也可以不要。

import{ listColumn, addColumn, updateColumn } from "@/api/column.js"
export const TablePlusMixin = {
  data() {
    return {
      columns: [], // 后台显示
      columnsHide: [], // 后台不显示
      columnProp: [], //服务器获取列表的prop
      url: this.$route.path,
      userId: this.$store.state.user.userId,
      id: null,
      allColumnProp:[],
    };
  },
  watch: {
    columnsHide: {
      handler(newV, oldV) {
        this.columns.forEach((item) =>{
          if(newV.includes(item.prop)){
            item.show = true
          }else{
            item.show = false
          }
        });
      },
      immediate: true,
      deep: true,
    },
  },
  created(){
    this.getColumnProp()
  },

  methods: {
    // 改变列表位置
    changeListIdx({ oldIndex, newIndex }) {
      let obj = this.columns.splice(oldIndex, 1);
      this.$nextTick(() => {
        this.columns.splice(newIndex, 0, obj[0]);
      });
    },

    updateColumnsHide(val){
      this.columnsHide = val
    },

    //获取表头
    async getColumnProp(){
      this.loading = true
      let params = {
        url: this.url,
        userId: this.userId
      }
      this.allColumnProp = this.columnsList.map(item => item.prop)
      await listColumn(params).then( res => {
        let tempres = res.rows[0]
        if(res.rows.length == 0){
          this.columnProp = this.allColumnProp
        }else{
          this.id = tempres.id
          let obj = JSON.parse(tempres.columnName)
          let check = []
          for( let key in obj){
            if(obj[key] === 1){
              check.push(key)
            }
          }
          this.columnProp = check //已经选中的prop
          this.columnsList = this.sortList(obj ,this.columnsList)
        }
        this.loading = false
      }).catch(err => {
        this.columnProp = this.allColumnProp
        this.loading = false
      })
      this.columns = this.columnsList.map( item =>{
        if(this.columnProp.includes(item.prop)){
          return {
            ...item,
            show: true
          }
        }else{
          return {
            ...item,
            show: false
          }
        }
      })
    },
    //编辑表头
    async updateColumnProp(){
      let columnName = {}
      this.columnProp = this.columns.map(item => {
        if(item.show){ 
          columnName[item.prop] = 1
          return item.prop
        }
        else{
          columnName[item.prop] = 0
        }
      })
      let params3= {
        id: this.id,
        url: this.url,
        userId: this.userId,
        columnName: JSON.stringify(columnName),
      }
      await addColumn(params3)
    },

    // 排序 list按照此顺序排序数组, needSortList要排序的对象数组, 按照prop属性升序排序
    sortList(obj, needSortList, prop = 'sortId') {
      let list = Object.keys(obj)
      needSortList.forEach( item => {
        let sortId = list.indexOf(item.prop)
        if(sortId === -1){
          item.sortId = needSortList.length
        }else{
          item.sortId = sortId
        }
      })
      return needSortList.sort((a, b) => {
          return a.sortId - b.sortId;
        });
    },
  },
};

3、再写个控制按钮组件。拖拽使用的Sortable插件。Sortable.js中文网


<template>
  <div class="toolBar ml10">
    <el-button
      v-if="isFilter"
      size="small"
      :icon="iconData"
      @click="toggleSearch()"
      >高级筛选</el-button
    >
    <el-tooltip
      v-if="isRefresh"
      class="item"
      effect="dark"
      content="刷新"
      placement="top"
    >
      <el-button
        size="small"
        circle
        icon="el-icon-refresh"
        @click="refresh()"
      />
    </el-tooltip>
    <el-tooltip
      v-if="columnsList.length > 0"
      class="item"
      effect="dark"
      content="显隐列"
      placement="top"
    >
      <el-button size="small" icon="el-icon-setting" @click="open = !open" />
    </el-tooltip>

    <el-dialog
      v-dialogDrag
      :close-on-click-modal="false"
      :title="title"
      width="864px"
      :visible.sync="open"
      append-to-body
      @opened="handleDialogOpened"
    >
      <el-card>
        <div slot="header" class="clearfix">
          <span>表头(拖动以调换排序顺序)</span>
        </div>
        <el-checkbox-group
          v-model="value"
          @change="dataChange"
          ref="el-checkbox"
        >
          <el-checkbox
            v-for="(item, idx) in columnsList"
            :label="item.prop"
            :key="idx"
            >{{ item.label }}</el-checkbox
          >
        </el-checkbox-group>
        <div style="height: 40px; padding-top: 20px">
          注:点击确定将表头设置保存于服务器
        </div>
      </el-card>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="submit">确定</el-button>
        <el-button @click="cancel">取消</el-button>
      </div>
    </el-dialog>
  </div>
</template>


<script>
import Sortable from "sortablejs";
export default {
  name: "RightToolbar",
  data() {
    return {
      // 显隐数据
      value: [],
      // 弹出层标题
      title: "表单设置",
      // 是否显示弹出层
      open: false,
      oldIndex: "", //旧的排序
      newIndex: "", //新的排序
      // checkOptions: [], //全部选项
      iconData: "el-icon-arrow-down",
    };
  },
  props: {
    showSearch: {
      type: Boolean,
      default: true,
    },
    columnsList: {
      type: Array,
      default: () => [],
    },
    isFilter: {
      //是否显示高级筛选按钮
      type: Boolean,
      default: true,
    },
    isRefresh: {
      //是否显示刷新按钮
      type: Boolean,
      default: false,
    },
    checkOptions: {
      type: Array,
      default: () => [],
    },
    search: {
      type: Boolean,
      default: true,
    },
    gutter: {
      type: Number,
      default: 10,
    },
  },
  computed: {
    style() {
      const ret = {};
      if (this.gutter) {
        ret.marginRight = `${this.gutter / 2}px`;
      }
      return ret;
    },
  },
  watch: {
    checkOptions() {
      this.value = this.checkOptions;
    },
  },
  methods: {
    // 显隐搜索
    toggleSearch() {
      if (this.iconData == "el-icon-arrow-down") {
        this.iconData = "el-icon-arrow-up";
      } else {
        this.iconData = "el-icon-arrow-down";
      }
      this.$emit("update:showSearch", !this.showSearch);
    },

    // 刷新
    refresh() {
      this.$emit("queryTable");
    },

    // 勾选改变
    dataChange() {
      this.$emit("updateColumnsHide", this.value);
    },

    Sortable() {
      // const aa = document.querySelector('.el-checkbox-group')
      const aa = this.$refs["el-checkbox"].$el;
      new Sortable(aa, {
        group: {
          name: "shared",
          pull: [false],
        },
        handle: ".el-checkbox",
        draggable: ".el-checkbox",
        animation: 150,
        onEnd: (evt) => {
          console.log(evt);
          const { oldIndex, newIndex } = evt;
          this.$emit("changeListIdx", { oldIndex, newIndex });
        },
      });
    },
    // 取消按钮
    cancel() {
      this.open = false;
      this.value = this.checkOptions;
    },
    //确认按钮
    submit() {
      // this.dataChange()
      // this.$emit('changeListIdx', { oldIndex: this.oldIndex, newIndex: this.newIndex })
      this.$emit("updateColumnProp");
      this.open = false;
    },
    handleDialogOpened() {
      this.Sortable();
    },
  },
};
</script>
<style lang="scss" scoped>
.toolBar {
  position: relative;
  display: inline-block;
}

.el-checkbox {
  padding: 10px 10px;
}
</style>

4、零件准备好了,开始组装使用。

<template>
  <div class="app-container">
    <right-toolbar
          :isFilter="false"
          :columnsList="columnsList"
          :checkOptions="columnProp"
          @updateColumnsHide="updateColumnsHide"
          @changeListIdx="changeListIdx"
          @updateColumnProp="updateColumnProp"
     ></right-toolbar>

    <TablePlus
      ref="tableplus"
      v-loading="loading"
      :columns="columns"
      :data="accountList"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      :page-sizes="[10, 20, 30, 50, 200]"
      @pagination="getList"
    >
      <!-- 作用域插槽 -->
      <template #columns="scope">
        <!-- scope.prop: 属性名 -->
        <!-- scope.value:属性值 -->
        <!-- scope.row:所有属性 -->
        <template v-if="scope.prop === 'accessoryCategoryId'">
          <AccessoryTypeTag
            :value="scope.row.accessoryCategoryId"
          ></AccessoryTypeTag>
        </template>
        <template v-if="scope.prop === 'action'">
          <el-button
            size="small"
            type="text"
            icon="el-icon-edit"
            @click="handleUpdate(scope.row)"
            v-hasPermi="['accessory:account:edit']"
            :disabled="!checkExample(scope.row.createBy)"
            >{{ $t("xiu-gai") }}</el-button
          >
          <el-button
            size="small"
            type="text"
            icon="el-icon-delete"
            @click="handleDelete(scope.row)"
            v-hasPermi="['accessory:account:remove']"
            :disabled="!checkExample(scope.row.createBy)"
            >{{ $t("shan-chu") }}</el-button
          >
        </template>
      </template>
    </TablePlus>
  </div>
</template>

<script>
import { TablePlusMixin } from "@/mixins/TablePlusMixin";

export default {
  name: "Account",
  mixins: [TablePlusMixin],

  data() {
    return {
    //label是表头名称,prop是对应的字段,useSlot是使用插槽,其他的都和el-table对应。
      columnsList: [
        {
          label: this.$t("xu-hao"),
          prop: "index",
          type: "index",
          align: "center",
          width: "50px",
          fixed: "left",
        },
        {
          label: this.$t("pei-jian-bian-hao-0"),
          prop: "accessoryCode",
          minWidth: "140px",
          align: "center",
          sortable: true, // 开启排序
          sortOrders: ["ascending", "descending", null],
        },
  
        {
          label: this.$t("pei-jian-fen-lei-0"),
          prop: "accessoryCategoryId",
          minWidth: "180px",
          align: "center",
          sortable: true,
          sortOrders: ["ascending", "descending", null],
          useSlot: true,
        },
     
        {
          label: this.$t("dan-wei-0"),
          prop: "unit",
          minWidth: "80px",
          align: "center",
        },
        
        
        {
          width: "130px",
          prop: "action",
          label: "操作",
          useSlot: true,
        },
      ],
    };
  },
  computed: {
    onSearchChange() {
      if (this.showSearch) {
        return "calc(100vh - 17rem)";
      } else {
        return "calc(100vh - 12rem)";
      }
    },
  },

  created() {
    countVisits({
      client: 0,
      pageId: 2141,
      pageName: "配件台账",
    });
    
  },
  methods: {
    
  },
};
</script>
<style lang="scss" scoped>
</style>

5、 效果展示。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值