el-table-v2element plus+大量数据展示虚拟表格实现自定义排序,选择样式调整行高亮

1.https://element-plus.org/en-US/component/table-v2#typings

2.使用cellRenderer,headerCellRenderer构造表格的头部和表结构

复制代码
<template>
  <div>
    <el-input-number
    v-model="total"
  ></el-input-number>
    <el-button type="primary" @click="initData">创造</el-button>
      <div style="height: 685px">
        <el-auto-resizer>
          <template #default="{ height, width }">
            <el-table-v2
            highlight-current-row
              header-class="table_header"
              class="custom-table"
              :row-class="rowClass"
              :row-height="29"
              :estimated-row-height="29"
              :columns="columns"
              :data="tableData"
              :width="width"
              :height="height"
              fixed
            />
          </template>
        </el-auto-resizer>
      </div>

      
  </div>
</template>

<script setup >
import { onMounted } from "vue";
import { ref, unref,reactive,h  } from "vue";
import { ElCheckbox, ElButton,ElMessage } from "element-plus";

import { CaretTop, CaretBottom } from "@element-plus/icons-vue";


const parameColums = [
  
{
      "key": "name",
      "title": "名字",
      "dataKey": "name",
      "width": 200,
      "align": "center",
      "sortable": true
  },
  {
    "key": "age",
    "title": "年龄",
    "dataKey": "age",
    "width": 250,
    "align": "center",
    "sortable": true
},
  {
      "key": "phone",
      "title": "phone",
      "dataKey": "phone",
      "width": 250,
      "align": "center",
      "sortable": true
  },
]
const queryParams = ref({
  pageNum: 1,
  pageSize: 100000,
  orderByColumn: null,
  isAsc: null,
  isToUnder: "1",
  // orderByChinese:"1",

  url: 'tablelistUrL',
});
const total = ref(1000)
// 表格数据
const tableData = ref([]);
// 选中状态
const selectedRowKeys = ref(new Set());
const selectedRows = ref([]);
const getTestData=()=>{
  const data = []
  const names = ['张三', '李四', '王五', '赵六', '钱七', '孙八', '周九', '吴十']
  const positions = ['前端工程师', '后端工程师', 'UI设计师', '产品经理', '测试工程师', '运维工程师', '项目经理']
  const departments = ['技术部', '产品部', '设计部', '市场部', '销售部', '人事部']
  
  for (let i = 1; i <= total.value; i++) {
    const nameIndex = i % names.length
    const positionIndex = i % positions.length
    const deptIndex = i % departments.length
    
    data.push({
      deviceId: i,
      name: names[nameIndex] + i,
      gender: i % 2 === 0 ? '男' : '女',
      age: 20 + (i % 20),
      position: positions[positionIndex],
      department: departments[deptIndex],
      phone: `138${String(10000000 + i).slice(1)}`,
      hireDate: `202${i % 4}-${String((i % 12) + 1).padStart(2, '0')}-${String((i % 28) + 1).padStart(2, '0')}`,
      status: i % 3 === 0 ? '在职' : (i % 3 === 1 ? '离职' : '休假')
    })
  }
  return data
}
function applySorting(data) {
  const { orderByColumn, isAsc } = queryParams.value
  
  // 如果没有排序要求,返回原始数据
  if (!orderByColumn || !isAsc) {
    return data
  }
  
  // 创建数据副本以避免修改原始数据
  const sortedData = [...data]
  
  // 根据字段进行排序
  sortedData.sort((a, b) => {
    let valueA = a[orderByColumn]
    let valueB = b[orderByColumn]
    
    // 如果是age字段,确保转换为数字进行比较
    if (orderByColumn === 'age') {
      valueA = Number(valueA)
      valueB = Number(valueB)
    }
    
    // 如果是phone字段,直接比较字符串
    if (orderByColumn === 'phone') {
      // 直接比较字符串即可
    }
    
    // 根据排序方向返回值
    if (isAsc === 'asc') {
      return valueA > valueB ? 1 : (valueA < valueB ? -1 : 0)
    } else if (isAsc === 'desc') {
      return valueA < valueB ? 1 : (valueA > valueB ? -1 : 0)
    }
    
    return 0
  })
  
  return sortedData
}

function initData() {
  //造假数据
 const data = getTestData()
  //模拟后端排序
  const sortedData = applySorting(data)
  console.log('sortedData',sortedData)
  tableData.value = sortedData
  if (data.length<1500) {
    const validDeviceIDs = new Set(
          tableData.value
            .filter((item) => item.deviceId !== undefined)
            .map((item) => item.deviceId)
        );

        selectedRowKeys.value = new Set(
          [...selectedRowKeys.value].filter((key) => validDeviceIDs.has(key))
        );
        updateSelectedRows();
  } else {
    ElMessage.error("为避免数据量过大,请填后查询!");
    tableData.value = [];
    clearSelection();
  }
}



// 全选/取消全选当前页
const handleSelectAll = (checked) => {
  if (checked) {
    // 获取当前页的数据ID

    selectedRowKeys.value = new Set(
      tableData.value.map((item) => item.deviceId)
    );
  } else {
    // 获取当前页的数据ID
    // clearSelection()
    selectedRowKeys.value.clear();
  }
  updateSelectedRows();
};

// 更新选中的行数据
const updateSelectedRows = () => {

  selectedRows.value = tableData.value.filter((item) =>
    selectedRowKeys.value.has(item.deviceId)
  );
};

const getSortAsc = (isAsc) => {

  queryParams.value.isAsc = queryParams.value.isAsc === isAsc ? null : isAsc;
};
// 单行选择
const handleSelect = (rowId, checked) => {
  if (checked) {
    selectedRowKeys.value.add(rowId);
  } else {
    selectedRowKeys.value.delete(rowId);
  }
  updateSelectedRows();
};
const currentIndex = ref(null)
// 行点击事件
const handleRowClick = ({rowIndex}) => {

  currentIndex.value = rowIndex


};

// 清空选择
const clearSelection = () => {
  selectedRowKeys.value.clear();
  selectedRows.value = [];
};

initColumns();

function initColumns(params) {
  parameColums.forEach((item, index) => {
    if (item.sortable === true) {
      parameColums[index].headerCellRenderer = ({ column }) => {
        return h("div", { class: "sortable-header" }, [
          h("span", column.title),
          h("div", { class: "sort_icon_box" }, [
            h(CaretTop, {
              class: [
                "sort-icon",
                "sort-icon-top",
                "sort-caret ascending",
                queryParams.value.sortColumn === item.key &&
                queryParams.value.isAsc === "asc"
                  ? "active"
                  : "",
              ],
              onClick: (e) => {
                e.stopPropagation();
                handleSortChange({
                  column,
                  key: item.key,
                  order: "asc",
                });
              },
            }),
            h(CaretBottom, {
              class: [
                "sort-icon",
                "sort-icon-bottom",
                queryParams.value.sortColumn === item.key &&
                queryParams.value.isAsc === "desc"
                  ? "active"
                  : "",
              ],
              onClick: (e) => {
                e.stopPropagation();
                handleSortChange({
                  column,
                  key: item.key,
                  order: "desc",
                });
              },
            }),
          ]),
        ]);
      };
    }
    parameColums[index].cellRenderer = ({ rowData, column,rowIndex }) => {
        return h("div", { style: "width:100%",     onClick: (e) => {
                e.stopPropagation();
                handleRowClick({
                  rowIndex
                  
                });
              }, }, [
          h("span",  rowData[item.key]),
          
        ]);
      };

  });
}

// 表格列定义
const columns = reactive([
  ...[
    {
      key: "selection",
      width: 30,
    
      align: "center",
      cellRenderer: ({ rowData }) => {
        return h(ElCheckbox, {
          modelValue: selectedRowKeys.value.has(rowData.deviceId),
          "onUpdate:modelValue": (val) => handleSelect(rowData.deviceId, val),
          onClick: (e) => e.stopPropagation(), // 阻止事件冒泡,避免触发行点击
        });
      },
      headerCellRenderer: () => {
        // 检查当前页是否全部选中

        let allSelected = false;
        if (selectedRowKeys.value.length === 0) {
          allSelected = false;
        } else if (selectedRowKeys.value.length === tableData.value.length) {
          allSelected = true;
        }

        return h(ElCheckbox, {
          modelValue: allSelected,
          "onUpdate:modelValue": handleSelectAll,
          indeterminate: selectedRowKeys.value.size > 0 && !allSelected,
        });
      },
    },
  ],

  ...parameColums,
]);

const handleSortChange = ({ column, key, order }) => {
  queryParams.value.sortColumn = key
  if (key) {
    getSortAsc(order);
    if (queryParams.value.isAsc) {
        queryParams.value.orderByColumn = key;
      queryParams.value.nullLast = true;
    } else {
      queryParams.value.orderByColumn = undefined;
      queryParams.value.nullLast = undefined;
    }
  }
  initData();
};
const rowClass = ({ rowIndex,row }) => {
  if (rowIndex ===  currentIndex.value) {
    return 'table-v2_row_active'
  } else {
    return ''
  }
  return ''
}
</script>

<style scoped>

  /* 头部图标 */
.tablev2_box {
  height: 400px;
  width: 100%;
}
:deep(.sortable-header) {
  display: flex;
  align-items: center;
  justify-content: space-between;
  cursor: pointer;
  user-select: none;
  color: var(--custom-head-color);
}

:deep(.sort-icon) {
  width: 14px;
  height: 14px;
  color: #c0c4cc;
  transition: color 0.2s;
  position: absolute;
}
:deep(.sort-icon-top) {
 border-bottom-color: var(--el-text-color-placeholder);
    top: 3px;
}

:deep(.sort-icon-bottom) {
  border-top-color: var(--el-text-color-placeholder);
    bottom: 3px;
}


:deep(.sort-icon.active) {
  color: #409eff;
}

:deep(.el-table-v2__sort-icon) {
  display: none !important;
}
:deep(.sort_icon_box) {
  height: 29px;
  flex-direction: column;
  display: flex;
  width: 15px;
  color: #a8abb2;
  padding-top: 3px;
  cursor: pointer;
  position: relative;

}

</style>
<style  scoped>


:deep(.el-table-v2) {
  --table-border-color: #f0e3cc; /* 自定义边框颜色变量 */
  --row-height: 29px;
  --custom-head-color:#f6fcff;
  --custom-head-bgcolor:#d59371;
  --custom-table-bgcolor:#ffffff;
  --custom-table-color:#000;
  --table-cell-hover:#fee4d7;
}
:deep(.el-table-v2__body) {
    background: var(--custom-table-bgcolor) !important;
    color: var(--custom-table-color);
  }
:deep(.el-table-v2) {
  border-color: var(--table-border-color);
}

:deep(.el-empty__image) {
  opacity: 0 !important;
}



:deep(.el-table-v2__row) {
  height: var(--row-height) !important;
  min-height: var(--row-height) !important;
  max-height: var(--row-height) !important;
  line-height: var(--row-height) !important; /* 确保文字垂直居中 */
  border: 0 solid  var(--table-border-color) !important;
  background-color: var(--custom-table-bgcolor) !important;
}

:deep(.el-table-v2__row:hover) {
  background: var(--table-cell-hover) !important;
}
:deep(.table-v2_row_active) {
  background: var(--custom-head-bgcolor) !important;
}
:deep(.el-table-v2__row-cell) {
  padding: 0 !important;
  border-bottom: var(--el-table-border);
  border-right: var(--el-table-border);
  border-color:  var(--table-border-color);
}


:deep(.el-table-v2__header) {
  height: "32px!important";
  border: 0 solid  var(--table-border-color) !important;
  line-height: "32px";
}
:deep(.el-table-v2__header-row) {
  border-color: var(--table-border-color);
  height: var(--row-height) !important ;
}
:deep(.el-table-v2__header-wrapper) {
  height: var(--row-height) !important ;
}
:deep(.el-table-v2__header-cell) {
  background-color: var(--custom-head-bgcolor) !important;
  border-bottom: var(--el-table-border);
  border-right: var(--el-table-border);
  border-color: var(--table-border-color);
  padding: 0 !important;
  color: var( --custom-head-color);
}
</style>
  <style>
.hover_row_pointer:hover {
  cursor: pointer;
}
</style>
  <style scoped>
:deep(.el-table-v2) {
  row-gap: 0 !important;
  gap: 0 !important;
}
</style>
相关推荐
1024肥宅2 小时前
面试和算法:常见面试题实现与深度解析
前端·javascript·面试
计算机程序设计小李同学2 小时前
基于 Spring Boot 和 Vue.js 技术栈的网上订餐系统
vue.js·spring boot·后端
Ashley_Amanda3 小时前
JavaScript 中数组的常用处理方法
开发语言·javascript·网络
BD_Marathon3 小时前
Router_路由的基本使用
javascript
float_六七3 小时前
行级与块级元素:核心区别与应用场景
开发语言·前端·javascript
毕设十刻3 小时前
基于Vue的家教预约系统7fisz(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js
前端无涯3 小时前
深度解析:fetch 与 Promise 结合实战及面试重点
前端·javascript
风舞红枫3 小时前
node代理vue打包后的文件,实现本地测试
前端·javascript·vue.js·node.js
Yanni4Night3 小时前
使用URLPattern API构建自己的路由器 🛣️
前端·javascript