vue3 + antd Table组件排序filters、筛选sorter、自定义表头展示

  • Post author:
  • Post category:vue




1、需求:可以自定义设置表格表头展示哪些内容,如默认展示(图一),点击表格最后一列表格的图标弹出表头的全部字段(图二),经过设置之后(图三)

图一:

在这里插入图片描述

图二:

在这里插入图片描述

图三:

在这里插入图片描述



2、具体实现(因为还没确认表头默认展示和全部展示的数据是否是后台返回,所以自己写了静态数据)
父组件:
html
<a-table
  :columns="state.columns"
  :row-key="(record) => record.id"
  :data-source="state.dataSource"
  :pagination="pagination"
  :loading="loading"
  @change="handleTableChange"
>
  <template #pt="{ text }">
    {{ formatTime(text) }}
  </template>
  <template #message="text">
    <span class="redColor">
      <span
        >{{ text && text.text ? text.text.substr(0, 4) : "" }}&nbsp;
      </span>
      <span
        >{{ text && text.text ? text.text.substr(4, 2) : "" }}&nbsp;
      </span>
      <span
        >{{ text && text.text ? text.text.substr(6, 10) : "" }}&nbsp;
      </span>
      <span
        >{{ text && text.text ? text.text.substr(16, 4) : "" }}&nbsp;
      </span>
      <span
        >{{ text && text.text ? text.text.substr(20, 2) : "" }}&nbsp;
      </span>
      <span
        >{{ text && text.text ? text.text.substr(22, 4) : "" }}&nbsp;
      </span>
      <span
        >{{ text && text.text ? text.text.substr(26, 2) : "" }}&nbsp;
      </span>
    </span>
    <span class="greenColor">
      <span
        >{{ text && text.text ? text.text.slice(28, -6) : "" }}&nbsp;
      </span>
    </span>
    <span class="blueColor">
      <span
        >{{ text && text.text ? text.text.slice(-6, -4) : "" }}&nbsp;
      </span>
    </span>
    <span class="yellowColor">
      <span>{{
        text && text.text
          ? text.text.substr(text.text.length - 4)
          : ""
      }}</span>
    </span>
  </template>
  <template #type="{ text }">
    {{
      text.indexOf("_") == -1
        ? sfReportLabel[text]
        : state.reportLabel[text]
    }}
  </template>
  <template #setTableTitle>
    <a-tooltip>
      <template #title>设置</template>
      <span @click="handleTableHead" class="table-head-btn"><SettingOutlined /></span>
    </a-tooltip>
  </template>
  <template #action="{ text }">
    <a-tooltip>
      <template #title>操作</template>
      <a-popover title="" trigger="click">
        <template #content>
          <div class="action-box" style="display: flex; flex-direction: column;">
            <a-popconfirm
              title="Are you sure change this task?"
              ok-text="Yes"
              cancel-text="No"
            >
              <a @click="handleEdit(text)">修改</a>
            </a-popconfirm>
            <!-- <a @click="handleEdit(text)">修改</a> -->
            <a>删除</a>
          </div>
        </template>
        <MenuOutlined />
      </a-popover>
    </a-tooltip>
  </template>
</a-table>
<a-modal
  class="table-head-modal"
  :visible="tableHeadPop"
  title="列表视图设置"
  :confirmLoading="tableHeadLoading"
  :width="750"
  @cancel="handleCancel"
>
  <table-head-content
    ref="tableHeadRef"
    :columns="state.setColumns"
  ></table-head-content>
  <template #footer>
    <div class="pop-btn-box">
      <div class="btn-left">
        <a-button @click="checkAll">全选</a-button>
        <a-button  @click="checkNoAll">全不选</a-button>
      </div>
      <div class="btn-right">
        <a-button @click="handleCancel">返回</a-button>
        <a-button  type="primary" @click="handleOk" class="search-btn">确定</a-button>
      </div>
    </div>
  </template>
</a-modal>
js:
<script setup>
import tableHeadContent from "./components//tableHeadContent.vue";
import { onMounted, reactive, ref, toRaw, getCurrentInstance, computed, nextTick } from "vue";
import moment from "moment";
import {
  UpOutlined,
  SearchOutlined,
  SyncOutlined,
  DownOutlined,
  SettingOutlined,
  MenuOutlined
} from "@ant-design/icons-vue";
import {
  getReportTypeList,
  getBeforeDate,
  getQuery,
  formatTime,
} from "./index";
import { request } from "../../../utils/request";
import { Form } from "ant-design-vue";
import { useStore } from 'vuex';
const store = useStore();
const useForm = Form.useForm;
const globalProperties =
  getCurrentInstance().appContext.config.globalProperties; // 获取全局挂载
const http = globalProperties.$http;
const sfReportLabel = {
  30: "测试报",
  32: "遥测站定时报",
  33: "遥测站加报报",
  34: "遥测站小时报",
  35: "遥测站人工置数报",
  36: "遥测站图片报",
  37: "中心站查询遥测站实时数据",
  38: "中心站查询遥测站历史数据",
  40: "中心站修改遥测站基本配置表",
  42: "中心站修改遥测站运行参数配置表",
  49: "修改密码",
  4: "设置遥测站时钟",
};
const formData = reactive({
  devId: "",
  region: undefined,
  time: [],
  type: undefined,
  typeId: undefined,
  deviceTypeId: undefined,
});
const state = reactive({
  siteTypeList: [],
  regionList: [],
  reportTypeList: [],
  deviceTypeList: [],
  reportLabel: {},
  dataSource: [],
  selectedDate: undefined,
  columns: [ // 默认显示
    {
      title: "测站编码",
      dataIndex: "stationCode",
      ellipsis: true,
    },
    {
      title: "测站名称",
      ellipsis: true,
      dataIndex: "stationName",
    },
    {
      title: "设备编码",
      ellipsis: true,
      dataIndex: "devId",
    },
    {
      title: "上报时间",
      dataIndex: "pt",
      ellipsis: true,
      slots: {
        customRender: "pt",
      },
      sorter: (a, b) => {
        return moment(a.pt).valueOf() - moment(b.pt).valueOf()
      },
    },
    {
      title: "原始报文",
      dataIndex: "message",
      width: 340,
      slots: {
        customRender: "message",
      },
    },
    {
      title: "上报类型",
      dataIndex: "type",
      ellipsis: true,
      slots: {
        customRender: "type",
      },
      // filters: [], // 需要筛选时添加
      filterMultiple: false, // 不可多选
      // onFilter: (value, record) => record.type.includes(value)
    },
  ],
  setColumns: [], // 用于传给子组件
});
const determine = ref(false);
const isAll = ref(false);
const loading = ref(false);
const timer = ref(null);
const tableHeadPop = ref(false);
const tableHeadLoading = ref(false);
const tableHeadRef = ref(null);
const pagination = reactive({
  current: 1,
  pageSize: 10,
  pageSizeOptions: ["10", "20", "30", "40", "50"],
  showSizeChanger: true, // 显示切换页码
  showTotal: () => {
    return `总共${pagination.total}条数据`
  },
  total: 0,
});
state.siteTypeList = computed(()=>store.state.siteTypeList);
state.regionList = computed(()=>store.state.regionList);
state.deviceTypeList = computed(()=>store.state.deviceTypeList);
// 切换页
const handleTableChange = (page, filters, sorter) => {
  pagination.current = page.current;
  pagination.pageSize = page?.pageSize;
  // console.log(filters); // 选中值为数组(单选直接拿第一项,多选把数组转化成字符串拼接 - 看后台需要什么数据结构)
  if(filters.type) {
    // 必须判断,未操作切换页会报错(无type属性)
    formData.type = filters.type[0];
  }
  getTableData(getQuery(formData));
  // console.log(sorter); // 等后台参数
};
// 获取表格数据
const getTableData = (formData) => {
  loading.value = true;
  // 判断最后一项是否已有设置列(因为顺序会打乱,保持设置在最后一列)
  if(state.columns.length && (state.columns[state.columns.length -1].key != "setItem")) {
    state.columns.push({
      key: "setItem", // 不可使用dataIndex, 会导致操作拿不到当前行的数据
      width: 60,
      slots: {
        title: "setTableTitle",
        customRender: "action",
      },
    })
  }
  request(http.GET_DEVICE_ORG_MSG_LIST, "get", {
    pageNum: pagination.current,
    pageSize: pagination.pageSize,
    ...formData,
  })
    .then((res) => {
      loading.value = false;
      state.dataSource = res.rows;
      pagination.current = res.currentPage;
      pagination.total = res.total;
    })
    .catch(() => {
      loading.value = false;
    });
};
// 点击动态设置表头
const handleTableHead = () => {
  tableHeadPop.value = true;
  nextTick(() => {
    tableHeadRef.value.setValues();
  });
  state.setColumns = state.columns;
  let num = state.setColumns.length - 1;
  state.setColumns = toRaw(state.setColumns).slice(0, num); // 把设置那行去掉
};
// 全选
const checkAll = () => {
  nextTick(() => {
    tableHeadRef.value.checkAllSon();
  })
};
// 全不选
const checkNoAll = () => {
  nextTick(() => {
    tableHeadRef.value.checkNoAllSon();
  })
};
const handleOk = async() => {
  let arr = await tableHeadRef.value.getValues();
  if(arr.length) {
    tableHeadPop.value = false;
    state.columns = arr;
    getTableFilters(); // 筛选列数据获取
    pagination.current = 1;
    getTableData(getQuery(formData));
  }
};
const handleCancel = () => {
  tableHeadPop.value = false;
};
// 限制日期选中(前后6天,即一周)
const disabledDate = (current) => {
  toRaw(state.selectedDate)
  if (determine.value == true) {
    return (
      current < moment(state.selectedDate).add(-6, "d") ||
      current > moment(state.selectedDate).add(+6, "d")
    );
  } else {
    return;
  }
};
// 日期面板变化
const calendarChange = (moment) => {
  if (moment.length == 1) {
    determine.value = true;
    state.selectedDate = moment[0];
  } else {
    state.selectedDate = undefined;
    determine.value = false;
  }
};
// 查询
const handleSearch = () => {
  pagination.current = 1;
  getTableData(getQuery(formData));
};
const rulesRef = ref({});
const { resetFields } = useForm(formData, rulesRef);
// 重置
const resetSearch = () => {
  resetFields();
  formData.time = [moment(getBeforeDate(7)), moment(getBeforeDate(0))];
  formData.region = toRaw(state.regionList)[0].dictLabel;
};
// 初始化下拉数据
const initSel = async () => {
  formData.region = (state.regionList)[0].dictLabel; // 默认选中区域
  formData.time = [moment(getBeforeDate(7)), moment(getBeforeDate(0))]; // 默认选中时间
  let reportRes = await getReportTypeList(http);
  state.reportTypeList = reportRes;
  getTableFilters();
};
// 获取上报类型的filters
const getTableFilters = () => {
  let filters = [];
  if (state.reportTypeList && state.reportTypeList.length) {
    state.reportTypeList.forEach((i) => {
      state.reportLabel[i.dictCode] = i.dictLabel;
      let obj = {
        text: "",
        value: ""
      }
      state.columns.forEach((ins, index) => {
        if(ins.dataIndex == "type") { // 指定是上报类型
          obj.text = i.dictLabel;
          obj.value = i.dictCode;
          filters.push(obj);
          state.columns[index].filters = filters;
          // state.columns[index].filters.push(obj);
        }
      })
    });
  }
}
// 操作列模拟修改
const handleEdit = (text) => {
  console.log(text);
};
onMounted(async () => {
  await initSel();
  getTableData(getQuery(formData));
  timer.value = setInterval(() => { // 定时触发
    getTableData(getQuery(formData));
  }, 300000);
});
</script>
子组件:
<template>
  <div class="table-head-content">
    <a-checkbox-group
      v-model:value="state.checkedList"
      name="checkboxgroup"
      @change="onChange"
    >
      <ul class="check-ul">
        <li v-for="(item, index) in state.columnsAll" :key="index" class="check-li" :class="{'isChecked': item.checked}">
          <a-checkbox :value="item.dataIndex" @change="getCheckOne">
            <span class="commonCode checkCode">{{ item.title }}</span>
          </a-checkbox>
        </li>
      </ul>
    </a-checkbox-group>
  </div>
</template>
<script setup>
import { reactive, toRaw, getCurrentInstance } from "vue";
import { getJsonArrEqual } from "../index";
const globalProperties = getCurrentInstance().appContext.config.globalProperties; // 获取全局挂载
const toast = globalProperties.$toast;
const props = defineProps({
  columns: Array,
});
const state = reactive({
  checkedList: [],
  columnsAll: [
    {
      title: "测站编码",
      dataIndex: "stationCode",
      ellipsis: true,
    },
    {
      title: "测站名称",
      ellipsis: true,
      dataIndex: "stationName",
    },
    {
      title: "设备编码",
      ellipsis: true,
      dataIndex: "devId",
    },
    {
      title: "上报时间",
      dataIndex: "pt",
      ellipsis: true,
      slots: {
        customRender: "pt",
      },
      sorter: (a, b) => {
        return moment(a.pt).valueOf() - moment(b.pt).valueOf()
      },
    },
    {
      title: "原始报文",
      dataIndex: "message",
      width: 340,
      slots: {
        customRender: "message",
      },
    },
    {
      title: "上报类型",
      dataIndex: "type",
      ellipsis: true,
      slots: {
        customRender: "type",
      },
      // filters: [], // 需要筛选时添加
      filterMultiple: false, // 不可多选
      // onFilter: (value, record) => record.type.includes(value)
    },
    {
      title: "上报类型编码",
      dataIndex: "functionCode",
      ellipsis: true,
    },
  ]
});
// 初始化
const setValues = () => {
  state.checkedList = [];
  let newArr = getJsonArrEqual(toRaw(props.columns), state.columnsAll); // 获取相同项
  state.columnsAll.forEach(i => { // 初始化checked属性
    i.checked = false;
  })
  state.columnsAll.forEach(item => {
    newArr.map(i => {
      if(item.dataIndex == i.dataIndex) {
        item.checked = true; // 初始化选中的checked值
        state.checkedList.push(i.dataIndex);
      }
    })
  });
};
// 选中
const onChange = (check) => {
  state.checkedList = check;
};
// 单个选中
const getCheckOne = (e) => {
  state.columnsAll.forEach((item, index) => {
    if(item.dataIndex == e.target.value) {
      state.columnsAll[index].checked = e.target.checked; // 根据选中与否赋值
    }
  })
}; 
// 获取选中
const getValues = () => {
  return new Promise((resolve, reject) => {
    if(state.checkedList.length >= 3) { // 必须选择三项或者三项以上
      let arr = [];
      state.checkedList.forEach(item => {
        state.columnsAll.map(i => {
          if(i.dataIndex == item) {
            arr.push(i);
          }
        })
      })
      resolve(arr);
    } else {
      toast('必须选中三项或者三项以上喔!')
      reject([]);
    }
  })
};
// 全选
const checkAllSon = () => {
  if(state.columnsAll.length) {
    state.checkedList = []; // 防止叠加
    state.columnsAll.map(i => {
      state.checkedList.push(i.dataIndex);
      return i.checked = true;
    })
  }
};
// 全不选
const checkNoAllSon = () => {
  state.checkedList = [];
  state.columnsAll.map(i => {
    return i.checked = false;
  })
};
defineExpose({
  setValues,
  getValues,
  checkAllSon,
  checkNoAllSon
})
</script>
<style lang="less" scoped>
.table-head-content {
  .check-ul {
    display: flex;
    flex-wrap: wrap;
    .check-li {
      padding: 2px 8px;
      border-radius: 4px;
      margin-right: .1rem;
      margin-bottom: .1rem;
      white-space: nowrap;
      ::v-deep(.ant-checkbox-wrapper) {
        width: 200px;
      }
      &:hover {
        background: @table-head-set-bg;
      }
    }
    .isChecked {
      background: @table-head-set-bg;
    }
  }
};
</style>


3、json数组获取相同项方法:
// 获取两个JSON数组的相同项
export const getJsonArrEqual = (arr1, arr2) => {
  var newArr = [], kvIndex = {};
  for (var i = 0; i < arr1.length; i++) {
    for (var j = 0; j < arr2.length; j++) {
      if (arr1[i].dataIndex == arr2[j].dataIndex) {
        var item
        if (kvIndex[arr1[i].dataIndex] == undefined) {
          kvIndex[arr1[i].dataIndex] = newArr.length;
          item = {};
          for (var attr in arr1[i]) item[attr] = arr1[i][attr];
          newArr[kvIndex[arr1[i].dataIndex]] = item;
        } else {
          item = newArr[kvIndex[arr1[i].dataIndex]];
          for (var attr in arr2[j]) item[attr] = arr2[j][attr];
        }
      }
    }
  }
  return newArr
}


4、后面遇到一个问题,就是 columns中不一定每个项都有dataIndex(会导致数据重叠),所以改为使用title去做唯一性
export const getJsonArrEqual = (arr1, arr2) => {
  var newArr = [], kvIndex = {};
  for (var i = 0; i < arr1.length; i++) {
    for (var j = 0; j < arr2.length; j++) {
      if (arr1[i].title == arr2[j].title) {
        var item
        if (kvIndex[arr1[i].title] == undefined) {
          kvIndex[arr1[i].title] = newArr.length;
          item = {};
          for (var attr in arr1[i]) item[attr] = arr1[i][attr];
          newArr[kvIndex[arr1[i].title]] = item;
        } else {
          item = newArr[kvIndex[arr1[i].title]];
          for (var attr in arr2[j]) item[attr] = arr2[j][attr];
        }
      }
    }
  }
  return newArr
}


5、注意:在子组件使用到dataIndex去判断的地方也要全部改为title喔



阳光温柔,生机盎然,喜欢春天的一切。



版权声明:本文为agua001原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。