Bootstrap

Vue3在大数据场景下原生实现单元格合并,让Thead固定让Tbody滚动

尝试在Vue3中使用Element-plus的el-table来实现单元格合并(即rowspan/colspan)功能,在数据量很大的场景下发现有性能问题,于是用原生的方式来实现,另外,生成的表格还能实现表头固定表体滚动的功能,具体代码如下:

.vue组件代码:

<template>
  <div
    class="custom-table-wrapper"
    v-html="mapStore.trajectoryDetailsTable"
    v-loading="loading"
    element-loading-text="加载中..."
    @click="handleClick"
  ></div>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import {
  getAjaxPersonnelData,
  getAjaxPersonnelTableData,
} from "@/api/getAjaxData.js";
import pinia from "@/store/pinia";
import { useMapStore } from "@/store/map";
const mapStore = useMapStore(pinia);

let loading = ref(true);

// 点击单元格时
function handleClick(event) {
  const target = event.target;

  const obj = JSON.parse(
    target.getAttribute("data").replace(/\n/g, "").replace(/%/g, '"')
  );

  if (obj.hasOwnProperty("personName") && !obj.hasOwnProperty("lon")) {
        // ...
  } else {
    if (obj.lat && obj.lon) {
        // ...
    } else {
        // ...
    }
  }
}

onMounted(async () => {
    // ...
});

onBeforeUnmount(() => {
    // ...
});
</script>

<style scoped>
.custom-table-wrapper {
  height: calc(100% - 31px) !important;
  overflow: auto;
}

.custom-table-wrapper /deep/ .custom-table {
  width: 100%;
  border-collapse: collapse;
  background: linear-gradient(to right,rgba(255,255,255, 0.25),rgba(94, 182, 255, 0.35),rgba(255,255,255,0));
}

.custom-table-wrapper /deep/ .custom-table thead {
  position: -webkit-sticky;
  /* Safari 支持 */
  position: sticky;
  top: 0;
  /* 距离顶部的位置 */
  z-index: 1;
  /* 确保表头在其他元素之上 */
}

.custom-table-wrapper /deep/ .custom-table thead tr{
  border: 1px solid rgba(255, 255, 255, 0.1);
}

.custom-table-wrapper /deep/ .custom-table thead th {
  border: 1px solid rgba(255, 255, 255, 0.1);
  background: linear-gradient(to right,rgba(255,255,255,0),rgba(94, 182, 255, 0.35),rgba(255,255,255,0));
  padding: 8px;
  color: rgba(255, 255, 255, 1);
  text-shadow: 1px 1px 1px #000;
  text-align: left;
  font-size: 15px;
  font-weight: bold;
}

.custom-table-wrapper /deep/ tr:nth-child(odd) {
  /* background: rgba(78, 93, 110, 1); */
  background: linear-gradient(to right,rgba(255,255,255,0),rgba(94, 182, 255, 0.05),rgba(255,255,255,0));
}

.custom-table-wrapper /deep/ tr:nth-child(even) {
  /* background: rgba(78, 93, 110, 1); */
  background: linear-gradient(to right,rgba(255,255,255,0),rgba(94, 182, 255, 0.15),rgba(255,255,255,0));
}

.custom-table-wrapper /deep/ tr:nth-child(odd):hover {
  background: linear-gradient(to right,rgba(255,255,255,0),rgba(94, 182, 255, 0.15),rgba(255,255,255,0));
  cursor: pointer;
}

.custom-table-wrapper /deep/ tr:nth-child(even):hover {
  background: linear-gradient(to right,rgba(255,255,255,0),rgba(94, 182, 255, 0.05),rgba(255,255,255,0));
  cursor: pointer;
}

.custom-table-wrapper /deep/ .custom-table td {
  border: 1px solid rgba(255, 255, 255, 0.1);
  padding: 5px 10px;
  color: rgba(255, 255, 255, 1);
  text-shadow: 1px 1px 1px #000;
  word-break: break-all;
  word-wrap: break-word;
  overflow-wrap: break-word;
  font-size: 14px;
}
</style>

getAjaxData.js代码:

import axios from "axios";
import {
  ElMessage
} from "element-plus";
import "element-plus/theme-chalk/el-message.css";
import personalData from "./data.js";
import pinia from "@/store/pinia";
import {
  useMapStore
} from "@/store/map";
const mapStore = useMapStore(pinia);

const getAjaxPersonnelData = async (obj) => {
  // 获取异步数据
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(personalData);
    }, 1000);
  });
};

const getAjaxPersonnelTableData = (obj) => {
  getAjaxPersonnelData(obj).then(res => {
    let tableString =
      '<table class="custom-table"><thead><tr><th width="100">姓名</th><th>身份证号码</th><th width="200">拍摄位置</th><th width="150">拍摄时间</th><th width="150">经度</th><th width="150">纬度</th></tr></thead><tbody>';

    for (let n = 0; n < res["personTrack"].length; n++) {
      mapStore.trajectoryNameList.push({
        value: res["personTrack"][n]["personName"],
        label: res["personTrack"][n]["personName"],
      });

      for (
        let m = 0; m < res["personTrack"][n]["trackList"].length; m++
      ) {
        if (res["personTrack"][n]["trackList"].length === 0) {
          tableString += `<tr>
            <td rowspan="${res["personTrack"][n]["trackList"].length}" data='{%personName%: %${res["personTrack"][n]["personName"]}%}'>${res["personTrack"][n]["personName"]}</td>
            <td rowspan="${res["personTrack"][n]["trackList"].length}" data='{%personName%: %${res["personTrack"][n]["personName"]}%}'>${res["personTrack"][n]["personImageUrl"]}</td>
            <td></td>
            <td></td>
            <td></td>
            <td></td>
            </tr>
            `;
        } else {
          if (m === 0) {
            tableString += `<tr>
                <td rowspan="${res["personTrack"][n]["trackList"].length}" data='{%personName%: %${res["personTrack"][n]["personName"]}%}'>${res["personTrack"][n]["personName"]}</td>
                <td rowspan="${res["personTrack"][n]["trackList"].length}" data='{%personName%: %${res["personTrack"][n]["personName"]}%}'>${res["personTrack"][n]["personImageUrl"]}</td>
                <td data='{%personName%: %${res["personTrack"][n]["personName"]}%, %lon%: ${res["personTrack"][n]["trackList"][m]["matchLongitude"]}, %lat%: ${res["personTrack"][n]["trackList"][m]["matchLatitude"]}, %matchShotTime%: %${res["personTrack"][n]["trackList"][m]["matchShotTime"]}%, %matchDevicePosition%: %${res["personTrack"][n]["trackList"][m]["matchDevicePosition"]}%}'>${res["personTrack"][n]["trackList"][m]["matchDevicePosition"]}</td>
                <td data='{%personName%: %${res["personTrack"][n]["personName"]}%, %lon%: ${res["personTrack"][n]["trackList"][m]["matchLongitude"]}, %lat%: ${res["personTrack"][n]["trackList"][m]["matchLatitude"]}, %matchShotTime%: %${res["personTrack"][n]["trackList"][m]["matchShotTime"]}%, %matchDevicePosition%: %${res["personTrack"][n]["trackList"][m]["matchDevicePosition"]}%}'>${res["personTrack"][n]["trackList"][m]["matchShotTime"]}</td>
                <td data='{%personName%: %${res["personTrack"][n]["personName"]}%, %lon%: ${res["personTrack"][n]["trackList"][m]["matchLongitude"]}, %lat%: ${res["personTrack"][n]["trackList"][m]["matchLatitude"]}, %matchShotTime%: %${res["personTrack"][n]["trackList"][m]["matchShotTime"]}%, %matchDevicePosition%: %${res["personTrack"][n]["trackList"][m]["matchDevicePosition"]}%}'>${res["personTrack"][n]["trackList"][m]["matchLongitude"]}</td>
                <td data='{%personName%: %${res["personTrack"][n]["personName"]}%, %lon%: ${res["personTrack"][n]["trackList"][m]["matchLongitude"]}, %lat%: ${res["personTrack"][n]["trackList"][m]["matchLatitude"]}, %matchShotTime%: %${res["personTrack"][n]["trackList"][m]["matchShotTime"]}%, %matchDevicePosition%: %${res["personTrack"][n]["trackList"][m]["matchDevicePosition"]}%}'>${res["personTrack"][n]["trackList"][m]["matchLatitude"]}</td>
                </tr>
                `;
          } else {
            tableString += `
                <tr>
                <td data='{%personName%: %${res["personTrack"][n]["personName"]}%, %lon%: ${res["personTrack"][n]["trackList"][m]["matchLongitude"]}, %lat%: ${res["personTrack"][n]["trackList"][m]["matchLatitude"]}, %matchShotTime%: %${res["personTrack"][n]["trackList"][m]["matchShotTime"]}%, %matchDevicePosition%: %${res["personTrack"][n]["trackList"][m]["matchDevicePosition"]}%}'>${res["personTrack"][n]["trackList"][m]["matchDevicePosition"]}</td>
                <td data='{%personName%: %${res["personTrack"][n]["personName"]}%, %lon%: ${res["personTrack"][n]["trackList"][m]["matchLongitude"]}, %lat%: ${res["personTrack"][n]["trackList"][m]["matchLatitude"]}, %matchShotTime%: %${res["personTrack"][n]["trackList"][m]["matchShotTime"]}%, %matchDevicePosition%: %${res["personTrack"][n]["trackList"][m]["matchDevicePosition"]}%}'>${res["personTrack"][n]["trackList"][m]["matchShotTime"]}</td>
                <td data='{%personName%: %${res["personTrack"][n]["personName"]}%, %lon%: ${res["personTrack"][n]["trackList"][m]["matchLongitude"]}, %lat%: ${res["personTrack"][n]["trackList"][m]["matchLatitude"]}, %matchShotTime%: %${res["personTrack"][n]["trackList"][m]["matchShotTime"]}%, %matchDevicePosition%: %${res["personTrack"][n]["trackList"][m]["matchDevicePosition"]}%}'>${res["personTrack"][n]["trackList"][m]["matchLongitude"]}</td>
                <td data='{%personName%: %${res["personTrack"][n]["personName"]}%, %lon%: ${res["personTrack"][n]["trackList"][m]["matchLongitude"]}, %lat%: ${res["personTrack"][n]["trackList"][m]["matchLatitude"]}, %matchShotTime%: %${res["personTrack"][n]["trackList"][m]["matchShotTime"]}%, %matchDevicePosition%: %${res["personTrack"][n]["trackList"][m]["matchDevicePosition"]}%}'>${res["personTrack"][n]["trackList"][m]["matchLatitude"]}</td>
                </tr>
                `;
          }
        }
      }
    }

    tableString += "</tbody></table>";
    mapStore.trajectoryDetailsTable = tableString;
  }).catch(error => {
    ElMessage.error(error);
  });
}

export {
  getAjaxPersonnelData,
  getAjaxPersonnelTableData
};

data.js数据格式:

let data = {
    personTrack: [
      {
        personId: 1,
        personNumber: "xxx",
        personName: "xxx",
        personSex: null,
        personImageUrl: "xxx.jpg",
        trackList: [
          {
            matchId: 25489,
            matchSimilarity: 0.953387975692749,
            matchCoordinatesFix: null,
            matchDevicePosition: "xxx",
            matchLongitude: x,
            matchLatitude: x,
            matchShotTime: "xxx",
          }
        ],
      },
      {
        personId: 2,
        personNumber: "xxx",
        personName: "xxx",
        personSex: null,
        personImageUrl: "xxx.jpg",
        trackList: [
          {
            matchId: 25509,
            matchSimilarity: 0.9613999724388123,
            matchCoordinatesFix: null,
            matchDevicePosition: "xxx",
            matchLongitude: x,
            matchLatitude: x,
            matchShotTime: "xxx",
          },
        ],
      }
    ],
    queryInfo: {
      nameList: null,
      numberList: null,
      minSimilarity: 0.9,
      maxSimilarity: 1,
      startDateTime: "2024-09-14 00:00:00",
      endDateTime: "2024-10-14 23:59:59",
    },
  };
  
  export default data;
  

Pinia

pinia.js:

import { createPinia } from 'pinia';
const pinia = createPinia();
export default pinia;

map.js

// 引入defineStore用于创建store
import { defineStore } from "pinia";

// 定义并暴露一个store
export const useMapStore = defineStore("defineMap", {
  // 动作
  actions: {},
  // 状态
  state() {
    return {
      trajectoryDetailsTable: "",
    };
  },
  // 计算
  getters: {},
});

;