前言
开发中有一个需要呈现不同时间点各个气象要素的值需求,我觉得一个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-else
desc="暂无机场高影响数据"
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>