【前端】实现Table表格自定义列

一、需求场景

在Web开发的时候,Table表格是十分常见的组件。有的时候,表格的列项会有很多项,全部加载出来表格就会出现横向滚动条,靠后的就需要拖动滚动条才能看见。从易用性的角度来说,是不太方便的,虽然可以将不太重要的信息放置最后,例如下面这样。
在这里插入图片描述
在这里插入图片描述
这个时候可以采用配置Table表格的列来实现优化。默认展示重要的信息,其他相对不是那么重要的信息就可以根据不同的用户,由用户自行配置显示。

二、准备工作

本Demo使用的是vue2 + Ant Design Vue组件库开发,所以需要在本地自行提前安装好开发环境(你也可以根据具体开发环境安装vue3)。
还有就是本实例中使用到了拖动排序,
在这里插入图片描述

所以需要安装 sortablejs 这个依赖。

"dependencies": {
    "ant-design-vue": "^4.2.6",
    "core-js": "^3.8.3",
    "sortablejs": "1.15.2",
    "vue": "^3.2.13",
    "vue-router": "^4.0.3",
    "vuex": "^4.0.0"
  },

实现过程

开发环境准备就绪过后就开始代码实现
首先,将Table表格的列项准备,这里我是使用浏览器缓存 localStorage 实现选择暂存,这里就直接贴图代码了,代码也是有注释的,不明白的可以留言。

// 设置本地缓存
export const storageKeys = {
  // 上一次选择的列表
  COLUMN: "COLUMN",
  // 上一次选择的列
  CHECKED_OPTIONS: "CHECKED_OPTIONS",
  // 上一次选择的
  CHECKED_GROUP: "CHECKED_GROUP"
}

export const setStorage = (key, value) => {
  const jsonData = JSON.stringify(value);
  localStorage.setItem(key, jsonData);
}

export const getStorage = (key, response) => {
  const jsonData = localStorage.getItem(key);
  if (jsonData) {
    return JSON.parse(jsonData);
  }
  return response || null;
}

// 全部table column 数据
export const columns = [
  {
    label: "列项一",
    key: "lx1",
    disabled: false,
    content: {
      title: "列项一",
      dataIndex: "lx1",
      key: "lx1",
      resizable: true,
      minWidth: 100
    }
  },
  {
    label: "列项二",
    key: "lx2",
    disabled: false,
    content: {
      title: "列项二",
      dataIndex: "lx2",
      key: "lx2",
      resizable: true,
      minWidth: 120
    }
  },
  {
    label: "列项三",
    key: "lx3",
    disabled: false,
    content: {
      title: "列项三",
      dataIndex: "lx3",
      key: "lx3",
      resizable: true,
      minWidth: 120
    }
  },
  {
    label: "列项四",
    key: "lx4",
    disabled: false,
    content: {
      title: "列项四",
      dataIndex: "lx4",
      key: "lx4",
      resizable: true,
      minWidth: 240
    }
  },
  {
    label: "列项五",
    key: "lx5",
    disabled: false,
    content: {
      title: "列项五",
      dataIndex: "lx5",
      key: "lx5",
      resizable: true,
      minWidth: 80
    }
  },
  {
    label: "列项六",
    key: "lx6",
    disabled: false,
    content: {
      title: "列项六",
      dataIndex: "lx6",
      key: "lx6",
      resizable: true,
      minWidth: 120
    }
  },
  {
    label: "列项七",
    key: "lx7",
    disabled: false,
    content: {
      title: "列项七",
      dataIndex: "lx7",
      key: "lx7",
      resizable: true,
      minWidth: 80
    }
  },
  {
    label: "列项八",
    key: "lx8",
    disabled: false,
    content: {
      title: "列项八",
      dataIndex: "lx8",
      key: "lx8",
      resizable: true,
      minWidth: 80
    }
  },
  {
    label: "列项九",
    key: "lx9",
    disabled: false,
    content: {
      title: "列项九",
      dataIndex: "lx9",
      key: "lx9",
      resizable: true,
      minWidth: 100
    }
  },
  {
    label: "列项十",
    key: "lx10",
    disabled: false,
    content: {
      title: "列项十",
      dataIndex: "lx10",
      key: "lx10",
      resizable: true,
      minWidth: 100
    }
  },
  {
    label: "列项十一",
    key: "lx11",
    disabled: false,
    content: {
      title: "列项十一",
      dataIndex: "lx11",
      key: "lx11",
      resizable: true,
      minWidth: 100
    }
  },
  {
    label: "列项十二",
    key: "lx12",
    disabled: false,
    content: {
      title: "列项十二",
      dataIndex: "lx12",
      key: "lx12",
      resizable: true,
      minWidth: 100
    }
  },
  {
    label: "列项十三",
    key: "lx12",
    disabled: false,
    content: {
      title: "列项十三",
      dataIndex: "lx12",
      key: "lx12",
      resizable: true,
      minWidth: 100
    }
  },
  {
    label: "列项十四",
    key: "lx12",
    disabled: false,
    content: {
      title: "列项十四",
      dataIndex: "lx12",
      key: "lx12",
      resizable: true,
      minWidth: 100
    }
  },
  {
    label: "列项十五",
    key: "lx12",
    disabled: false,
    content: {
      title: "列项十五",
      dataIndex: "lx12",
      key: "lx12",
      resizable: true,
      minWidth: 100
    }
  },
  {
    label: "列项十六",
    key: "lx12",
    disabled: false,
    content: {
      title: "列项十六",
      dataIndex: "lx12",
      key: "lx12",
      resizable: true,
      minWidth: 100
    }
  },
];

// 默认展示列
export const defaultOptions = ["lx1", "lx3", "lx5", "lx7", "lx9", "lx11"];

// 获取全部列
export const getAllColumns = () => {
  return columns;
}

// 获取默认选项
export const getDefaultOptions = () => {
  return defaultOptions;
}

// 获取初始化的 table 列
export const getInitOptions = () => {
  let options = getStorage(storageKeys.CHECKED_OPTIONS, []);
  if (!options.length) {
    // 缓存没有走默认
    options = getDefaultOptions();
    setStorage(storageKeys.CHECKED_OPTIONS, options);
  }
  return options;
}

export const getInitTableColumns = () => {
  let tableColumns = [];
  let checkedColumns = getInitOptions();
  tableColumns = columns.map(item => {
    if (checkedColumns.includes(item.key)) {
      return item.content;
    }
  }).filter(item => item);
  return tableColumns;
}

关于前端页面,为了复用性,我这里是将列项选择弹窗写成了一个组件。
表格组件(父组件)页面
在这里插入图片描述
代码


```html
<template>
  <div class="about">
    <Space>
      <Button type="primary" @click="openColumnModal">
        <SettingOutlined />
      </Button>
    </Space>

    <Table :dataSource="dataSource" :scroll="{ x: 'max-content', y: 400 }" style="margin-top: 16px;">
      <a-table-column v-for="item in columns" :key="item.key" :title="item.title" :dataIndex="item.dataIndex"
        :resizable="item.resizable" :min-width="item.minWidth" :width="item.minWidth">
      </a-table-column>
    </Table>
    <CustomColumn ref="customColumnRef" @apply="apply" />
  </div>
</template>

<script>
import CustomColumn from "@/components/CustomColumn.vue"
import { Table, Space, Button } from "ant-design-vue";
import { SettingOutlined } from '@ant-design/icons-vue';
import {
  getInitTableColumns,
  getStorage,
  storageKeys
} from "@/data";

export default {
  name: 'AboutView',
  components: {
    CustomColumn,
    Table, Space, Button,
    SettingOutlined
  },
  data() {
    return {
      dataSource: [{
        id: "lx1",
        lx1: "lx1",
        lx2: 'lx1',
        lx3: "lx3",
        lx4: "lx4",
        lx5: "lx5",
        lx6: "lx6",
        lx7: "lx7",
        lx8: "lx8",
        lx9: "lx9",
        lx10: "lx10",
        lx11: "lx11",
        lx12: "lx12",
      },
      {
        id: "lx2",
        lx1: "lx1",
        lx2: 'lx1',
        lx3: "lx3",
        lx4: "lx4",
        lx5: "lx5",
        lx6: "lx6",
        lx7: "lx7",
        lx8: "lx8",
        lx9: "lx9",
        lx10: "lx10",
        lx11: "lx11",
        lx12: "lx12",
      },
      {
        id: "lx3",
        lx1: "lx1",
        lx2: 'lx1',
        lx3: "lx3",
        lx4: "lx4",
        lx5: "lx5",
        lx6: "lx6",
        lx7: "lx7",
        lx8: "lx8",
        lx9: "lx9",
        lx10: "lx10",
        lx11: "lx11",
        lx12: "lx12",
      },
      {
        id: "lx4",
        lx1: "lx1",
        lx2: 'lx1',
        lx3: "lx3",
        lx4: "lx4",
        lx5: "lx5",
        lx6: "lx6",
        lx7: "lx7",
        lx8: "lx8",
        lx9: "lx9",
        lx10: "lx10",
        lx11: "lx11",
        lx12: "lx12",
      },
      {
        id: "lx5",
        lx1: "lx1",
        lx2: 'lx1',
        lx3: "lx3",
        lx4: "lx4",
        lx5: "lx5",
        lx6: "lx6",
        lx7: "lx7",
        lx8: "lx8",
        lx9: "lx9",
        lx10: "lx10",
        lx11: "lx11",
        lx12: "lx12",
      },
      {
        id: "lx6",
        lx1: "lx1",
        lx2: 'lx1',
        lx3: "lx3",
        lx4: "lx4",
        lx5: "lx5",
        lx6: "lx6",
        lx7: "lx7",
        lx8: "lx8",
        lx9: "lx9",
        lx10: "lx10",
        lx11: "lx11",
        lx12: "lx12",
      },
      {
        id: "lx7",
        lx1: "lx1",
        lx2: 'lx1',
        lx3: "lx3",
        lx4: "lx4",
        lx5: "lx5",
        lx6: "lx6",
        lx7: "lx7",
        lx8: "lx8",
        lx9: "lx9",
        lx10: "lx10",
        lx11: "lx11",
        lx12: "lx12",
      },
      {
        id: "lx8",
        lx1: "lx1",
        lx2: 'lx1',
        lx3: "lx3",
        lx4: "lx4",
        lx5: "lx5",
        lx6: "lx6",
        lx7: "lx7",
        lx8: "lx8",
        lx9: "lx9",
        lx10: "lx10",
        lx11: "lx11",
        lx12: "lx12",
      },
      {
        id: "lx9",
        lx1: "lx1",
        lx2: 'lx1',
        lx3: "lx3",
        lx4: "lx4",
        lx5: "lx5",
        lx6: "lx6",
        lx7: "lx7",
        lx8: "lx8",
        lx9: "lx9",
        lx10: "lx10",
        lx11: "lx11",
        lx12: "lx12",
      },
      {
        id: "lx10",
        lx1: "lx1",
        lx2: 'lx1',
        lx3: "lx3",
        lx4: "lx4",
        lx5: "lx5",
        lx6: "lx6",
        lx7: "lx7",
        lx8: "lx8",
        lx9: "lx9",
        lx10: "lx10",
        lx11: "lx11",
        lx12: "lx12",
      },
      ],
      columns: getStorage(storageKeys.COLUMN, null) || getInitTableColumns(),
    }
  },
  mounted() {
  },
  methods: {
    openColumnModal() {
      this.$refs.customColumnRef.showModal();
    },

    apply(cloumn) {
      this.columns = cloumn;
    }
  },
}
</script>

选项弹窗操作组件(子组件)页面
在这里插入图片描述
代码

<template>
  <Modal v-model:open="visible" :width="650" title="列项自定义" okText="应用" cancelText="取消" @ok="confirm" @cancel="cancel">
    <div style="width: 100%;">
      <div style="margin-bottom: 16px;">
        您可以勾选常用的列项,保存为自定义模板
        <Checkbox v-model="allChecked" style="margin-left: 16px;" :checked="allChecked" :indeterminate="indeterminate"
          @click="changeIndeterminate">全选</Checkbox>
      </div>
      <CheckboxGroup v-model:value="checkedOptions" style="width: 100%;">
        <div id="columnMove" style="width: 100%;display: flex;flex-wrap: wrap;justify-content: flex-start;">
          <transition-group type="transition" name="flip-list">
            <Checkbox v-for="item in allOptions" :key="item.key" :value="item.key" :disabled="item.disabled"
              :class="{ filter: item.disabled }" style="width: 150px;margin-bottom: 16px;margin-left: 0px;">
              <span>{{ item.label }}</span>
            </Checkbox>
          </transition-group>
        </div>
      </CheckboxGroup>
    </div>
  </Modal>
</template>

<script>
import { Modal, Checkbox, CheckboxGroup } from "ant-design-vue";
import {
  getInitOptions,
  getAllColumns,
  getDefaultOptions,
  getStorage,
  setStorage,
  storageKeys
} from "@/data";
import { Sortable } from "sortablejs";

export default {
  name: 'CustomColumn',
  components: {
    Modal,
    Checkbox,
    CheckboxGroup
  },
  data() {
    return {
      visible: false,
      allChecked: false,
      checkedOptions: getInitOptions(), //已选择的项
      allOptions: getStorage(storageKeys.CHECKED_GROUP) || getAllColumns(),//所有的操作项
    }
  },
  computed: {
    indeterminate() {
      return this.checkedOptions.length !== this.allOptions.length && this.checkedOptions.length !== 0;
    }
  },
  watch: {
    checkedOptions: {
      handler(newValue) {
        if (newValue.length === this.allOptions.length) {
          this.allChecked = true;
        } else {
          this.allChecked = false;
        }
      },
      immediate: true
    }
  },
  mounted() {
  },
  methods: {
    changeIndeterminate(ev) {
      if (ev.target.checked) {
        this.checkedOptions = this.allOptions.map(item => item.key);
      } else {
        this.checkedOptions = getDefaultOptions();
      }
    },

    rowDrop() {
      const _this = this;
      const tBody = document.getElementById("columnMove");
      Sortable.create(tBody, {
        group: {
          name: "moveSetting",
          pull: false,
          put: false,
        },
        handle: "#columnMove",
        sort: true,
        animation: 300,
        filter: ".filter",
        onEnd: function (event) {
          const start = event.oldIndex;
          const end = event.newIndex;
          const arr = [..._this.allOptions];
          if (start > end) {
            arr.splice(end, 0, arr[start]);
            arr.splice(start + 1, 1);
          } else {
            arr.splice(end + 1, 0, arr[start]);
            arr.splice(start, 1);
          }
          _this.allOptions = arr;
        }
      })
    },

    showModal() {
      this.visible = true;
      this.$nextTick(() => {
        this.rowDrop();
      })
    },

    cancel() {
      this.checkedOptions = getStorage(storageKeys.CHECKED_OPTIONS);
      this.visible = false;
    },

    confirm() {
      let columnOption = [];
      this.allOptions.forEach(item => {
        if (this.checkedOptions.includes(item.key)) {
          columnOption.push({
            ...item.content,
          });
        }
      });
      setStorage(storageKeys.COLUMN, columnOption);
      setStorage(storageKeys.CHECKED_OPTIONS, this.checkedOptions);
      setStorage(storageKeys.CHECKED_GROUP, this.allOptions);
      this.$emit("apply", columnOption);
      this.visible = false;
    }
  },
}
</script>

<style scoped lang='scss'></style>

说明:
1、在页面通过点击 这个按钮图标
2、唤起 这个操作列项弹窗。可以拖动改变顺序,勾选选择显示。
3、点击 应用 按钮,关闭弹窗,并将处理后的数据渲染在在Table表格。
4、演示
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
其他玩法:
可以通过配置,让某些选项不可操作,如:列项三、列项四
在这里插入图片描述
只需要将代码中的 disabled 改为 true

{
    label: "列项三",
    key: "lx3",
    disabled: true,
    content: {
      title: "列项三",
      dataIndex: "lx3",
      key: "lx3",
      resizable: true,
      minWidth: 120
    }
  },
  {
    label: "列项四",
    key: "lx4",
    disabled: true,
    content: {
      title: "列项四",
      dataIndex: "lx4",
      key: "lx4",
      resizable: true,
      minWidth: 240
    }
  },

其他的操作,可以根据自己去灵活变更。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值