前言
开发中有一个需要呈现不同时间点各个气象要素的值需求,我觉得一个table可以实现这类数据的展示,只是因为时间点时关注的重点,所以需要列选中效果,清晰的展示时间点下的要素数据。我选择的是antd的table组件,这个组件没有列选中的效果,所以还是需要自己动手丰衣足食,改造一下。
分析
这个功能的难点在于列选中效果,我们需要给他一个背景加上边框,虽然antd的table没有列选中效果,但是它提供了customCell,customHeaderCell,我们可以根据这些回调函数的特点灵活使用实现列选中效果。
参数 | 说明 | 类型 | 用途 |
---|---|---|---|
customCell | 设置单元格属性 | Function(record, rowIndex) | 根据activeColIndex参数,为选中列包含的单元格添加class,并且为最后一个单元格加上“lastCol”的class,因为最后一个需要加上下边框 |
customHeaderCell | 设置头部单元格属性 | Function(column) | 主要为选中列的第一个单元格加上class,因为表头单元格需要加上上边框 |
源代码
/**
* CustomTable.vue
* @Author ZhangJun
* @Date 2024/5/28 11:39
**/
<template><a-spin :spinning="loading"><template v-if="dataSource&&dataSource.length>0"><a-table :pagination="false":columns="getColumns":dataSource="dataSource"rowKey="itemCode":scroll="{y:dataSource.length>9?'280px':false}"></a-table></template><custom-empty v-elsedesc="暂无机场高影响数据"sub-title="NO DATA IS AVAILABLE"style="margin: 8% auto;"></custom-empty></a-spin>
</template><script>
import CustomEmpty from "@/components/CustomEmpty.vue";
import moment from "moment";
import {HighImpactWeatherApi} from "@/api/HighImpactWeatherApi";
import WizStyleHelper from "@/utils/leaflet/WizStyleHelper";
import BigNumber from "bignumber.js";export default {name: "CustomTable",components: {CustomEmpty},props: {currentDateTime: {type: String},//选择的可显示的要素visibleItemsConfig: {type: Array, default: () => ([])},airportId: {type: String}},data() {return {loading: false,itemDefaultConfig: {visibility: {unit: 'm', name: '能见度'},wins: {unit: 'm/s', name: '风'},rain: {unit: 'mm', name: '雨'},lowTemperature: {unit: '℃', name: '低温'},},itemColors: {},//要素配色缓存dataSource: [],activeColIndex: -1,//现在选中的列}},computed: {/*** 动态获取表格的columns* @returns {*[]}*/getColumns() {//获取各种要素的颜色let colors = this.visibleItemsConfig?.map(({styleCode}) => {return [styleCode, this.getItemColor(styleCode)];}) || [];this.itemColors = Object.fromEntries(colors);//要素名称列自定义let itemCodeCustomRender = (text) => {let {styleCode, pCode, name} = this.visibleItemsConfig?.find(({code}) => code === text);let unit = this.itemDefaultConfig?.[pCode]?.unit || '';let color = this.itemColors?.[styleCode];return <div class="flex justify-end" style="width:80px;">{name}<div style={{padding: '0 4px', color}}>{unit}</div></div>};let dayDate = moment(this.currentDateTime, 'YYYYMMDDHHmmss');let columns_temp = [];for (let i = 0; i < 24; i++) {let dataIndex = dayDate.clone().add(i, 'hour');columns_temp = [...columns_temp, {align: 'center',title: dataIndex.format('HH:mm'),dataIndex: dataIndex.format('YYYYMMDDHH'),key: dataIndex.format('YYYYMMDDHH'),className: 'customCell',//为了实现列选中高亮效果customCell: (record, rowIndex) => {return {class: {activeCol: i === this.activeColIndex,//如果该cell所以选中的那一列中,就在上这个样式lastCol: rowIndex === this.dataSource.length - 1//如果该cell属于选中列的最后一个cell,就加上这个样式,因为要在这个cell加上下边框},on: {mouseenter: (e) => {//赋值当前cell所在的列索引,为高亮列做准备this.activeColIndex = i;},mouseleave: (event) => {//清空this.activeColIndex = -1;}}}},//头部需要加上上边框customHeaderCell: (column) => {//自定义表头return {class: {activeCol: i === this.activeColIndex,//该表头为选中列的表头,加上这个样式,因为列头需要加上边框headerCell: true},}},customRender: (text, record) => {text = Number(text);if (text >= 1000) {text = new BigNumber(text).toFixed(0);} else if (text < 1000 && text >= 100) {text = new BigNumber(text).toFixed(0);} else if (text < 100 && text >= 10) {text = new BigNumber(text).toFixed(1);} else if (text < 10 && text > 0) {text = new BigNumber(text).toFixed(2);}let {itemCode} = record;let {styleCode, legendImage, name, pCode} = this.visibleItemsConfig?.find(({code}) => code === itemCode);let unit = this.itemDefaultConfig?.[pCode]?.unit || '';//如果有图标就显示图标if (legendImage) {if (text > 0) {return <img height="15" title={`${name}:${text} ${unit}`} src={legendImage} alt={name}/>;}return <div style={{padding: '4px 0',overflow: 'hidden',width: '28px',textAlign: 'center',cursor: 'default',}} title={`${name}:${text} ${unit}`}>-</div>}let color = this.itemColors?.[styleCode];return <div style={{background: text > 0 ? color : '',padding: '4px 0',overflow: 'hidden',width: '28px',textAlign: 'center',cursor: 'default',}} title={`${name}:${text} ${unit}`}>{text}</div>;}}];}return [{title: '',dataIndex: 'itemCode',key: 'itemCode',className: 'customCell',align: 'right',width: 80,customRender: itemCodeCustomRender,}, ...columns_temp];},},methods: {/*** 获取数据*/fetchData() {if (this.currentDateTime && this.visibleItemsConfig?.length > 0 && this.airportId) {let startTime = moment(this.currentDateTime, 'YYYYMMDDHH').format('YYYYMMDDHH');let endTime = moment(this.currentDateTime, 'YYYYMMDDHH').add(23, 'hour').format('YYYYMMDDHH');let itemCodes = this.visibleItemsConfig?.map(({code}) => code).join(',');this.loading = true;HighImpactWeatherApi.getHighImpactSingleAirportFutureImpactInfo({startTime,// 2024051102,endTime,itemCodes,airportId: this.airportId}).then((res = {}) => {let {data} = res;if (data) {this.dataSource = itemCodes.split(',').map(itemCode => {return {itemCode, ...data[itemCode]};});}}, () => {this.dataSource = [];}).finally(() => {this.loading = false;});}},/*** 获取站点图例颜色* @param styleCode 配色code*/getItemColor(styleCode) {if (styleCode) {const wizStyleHelper = new WizStyleHelper();const colorConfig = wizStyleHelper.getStyleImpl(styleCode);let {colors: [, color]} = colorHelper.getColorImpl(colorConfig.shaded.color).colorPalettes;return `rgba(${color.join(',')})`;}return undefined},},mounted() {this.fetchData();this.$watch(() => [this.currentDateTime, this.airportId, this.visibleItemsConfig], ([params1, params2, params3]) => {this.fetchData();});}
}
</script><style scoped lang="less">
/deep/ .customCell {background: transparent;padding: 4px 0 !important;border-color: transparent;border-width: 0 1px 0 1px;* {font-family: D-DIN, sans-serif;font-size: 12px;font-weight: normal;line-height: 12px;text-align: center;letter-spacing: 0;color: rgba(255, 255, 255, 0.6);white-space: nowrap;}
}/deep/ .customRow {> td {font-family: D-DIN, sans-serif;font-size: 12px;font-weight: normal;line-height: normal;text-align: center;letter-spacing: 0;color: white;}
}/deep/ .ant-table-header {background: transparent;&.ant-table-hide-scrollbar {margin-right: -0.69rem;}
}/deep/ .ant-table-body {background: transparent !important;&::-webkit-scrollbar {/* 对应纵向滚动条的宽度 */width: 0.425rem;/* 对应横向滚动条的宽度 */height: 0.425rem;}&::-webkit-scrollbar-thumb {background: #198CF8;border-radius: 32px;}&::-webkit-scrollbar-track {background: rgba(7, 28, 65, 0.5);border-radius: 32px;}.ant-table-tbody {> tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected) > td {background: unset;}}
}/deep/ .headerCell {border-width: 1px 1px 0;border-style: solid;border-color: transparent;
}/deep/ .lastCol {border-width: 0 1px 1px;border-style: solid;border-color: transparent;
}/deep/ .activeCol {background: rgba(77, 136, 255, 0.19) !important;//border-width: 0 1px 0 1px;border-style: solid;border-color: #5FACFF;&.headerCell {border-radius: 2px 2px 0 0;}&.lastCol {border-radius: 0 0 2px 2px;}
}
</style>