需求与最终实现
需求:
- 表格可以全屏放大
- 表头冻结,左侧两列冻结,右侧一列冻结
- 首列单元格合并
- 第二列单元格点击和高亮
- 最后一列冻结
- 单元格色阶设置,可以给每个单元格设置颜色
- 滚动条自定义,滚动事件自定义
- 列头也有合并
- 表格的自适应
- 单元格的表格线也是自己画的
问题及解决方案
问题1:表格全屏放大之后的沾满全屏,放大及缩小,滚动条
解决方案:
vue-fullscreen 组件,遇到的问题多半是样式方面的,简单调一下就行
npm安装
npm install vue-fullscreen --save
main.js文件中添加
import VueFullscreen from "vue-fullscreen";
Vue.use(VueFullscreen);
问题2:表格冻结行列遇到的样式不适配问题及自适应问题
解决方案:
宽度用100%,高度使用动态设置列的高度和maxheight,注意,如果同时设置了maxheight和冻结列及对滚动条的高度进行了修改(减少了滚动条高度),可能会导致冻结的那列占不满,这是因为el-table的冻结列对于滚动条位置的预留,因为设置了整个表格maxheight之后,冻结列也会自己算出来一个maxheight,我们在动态设置table的maxheight的同时,也要对冻结列的maxheight进行设置
问题3:合并问题,都是需要使用el-table的自带属性
解决方案:
合并用 :span-method="objectSpanMethod"
设置合并规则,我是用的同名合并
行合并的话需要用 el-table-column 标签嵌套另外的 el-table-column 标签
问题4:关于单元格色阶和点击事件
解决方案:
单元格色阶::cell-style
单元格点击:@cell-click
问题5:表格滑动有一些迟滞性就是有错行掉帧的感觉
解决方案:
- 在入口文件(main.js)中删除原来的滑动效果,手动加一新的,记得要放在引入所有的element-ui前
- 这种滑动的效果一般,有一种生硬的感觉
- 原生代码的迟滞性可能是因为数据多或者是这个表格的cell-style影响到了整个表格横轴纵轴的滚动事件
import { Table } from "element-ui";
delete Table._Ctor;
const bindEvents = Table.methods.bindEvents;
const SCROLL_INTERVAL = 10; // 每次滚动的时间间隔,单位为毫秒
const SCROLL_DISTANCE = 0.5; // 每次滚动的距离
Object.assign(Table.methods, {
bindEvents() {
bindEvents.call(this);
this.bodyWrapper.addEventListener("mousewheel", this.handleBodyMousewheel);
},
handleBodyMousewheel(event) {
const fixedWrapper = this.$refs.fixedWrapper;
if (fixedWrapper) {
const fixedBodyWrapper = fixedWrapper.querySelector(
".el-table__fixed-body-wrapper"
);
if (fixedBodyWrapper) {
event.preventDefault();
this.scrollSmoothly(fixedBodyWrapper, event.deltaX, event.deltaY);
this.scrollSmoothly(this.$refs.bodyWrapper, event.deltaX, event.deltaY);
}
}
},
scrollSmoothly(element, deltaX, deltaY) {
let startTimestamp = null;
const startScrollLeft = element.scrollLeft;
const startScrollTop = element.scrollTop;
const targetScrollLeft = startScrollLeft + deltaX * SCROLL_DISTANCE;
const targetScrollTop = startScrollTop + deltaY * SCROLL_DISTANCE;
const step = timestamp => {
if (!startTimestamp) {
startTimestamp = timestamp;
}
const progress = timestamp - startTimestamp;
const easeProgress = Math.min(progress / SCROLL_INTERVAL, 1);
const scrollLeft =
startScrollLeft + (targetScrollLeft - startScrollLeft) * easeProgress;
const scrollTop =
startScrollTop + (targetScrollTop - startScrollTop) * easeProgress;
element.scrollLeft = scrollLeft;
element.scrollTop = scrollTop;
if (progress < SCROLL_INTERVAL) {
requestAnimationFrame(step);
}
};
requestAnimationFrame(step);
}
});
Vue.component(Table.name, Table);
import ElementUI from "element-ui";
代码:代码略多,建议看个思路就行
<template>
<div class="compass-content">
<div class="top">
<div class="topContent">
<div class="top-left">
<fullscreen
:page-only="true"
v-model="fullscreen"
:exit-on-click-wrapper="false"
fullscreen-class="big-view bg-white"
>
<div
:class="fullscreen ? 'city-contentTSFull' : 'city-contentTS'"
v-loading="loading"
element-loading-background="rgba(24, 41, 88, 0)"
>
<!-- <el-button @click="toggle" class="fullButton"
>{{ fullscreen ? "退出" : "进入" }}全屏</el-button
> -->
<img
class="fullImg"
@click="toggle"
src="../../assets/images/quanping.png"
alt=""
/>
<!-- <img
v-else
@click="toggle"
src="../../assets/images/quanping.png"
alt=""
/> -->
<el-table
ref="myTable"
:data="tableDataTOP"
style="width: auto;"
:fit="true"
:max-height="BomHeights"
:cell-style="cellStyle"
:span-method="objectSpanMethod"
@cell-click="cellClick"
:row-key="row => row.id"
>
<el-table-column
fixed="left"
prop="列名"
label="列名"
align="center"
width="120"
>
<template slot-scope="scope">
<div>
<!-- <img
class="listimg1"
src="../../assets/images/.png"
alt=""
/> -->
<span class="listcol1">{{ scope.row.列名 }}</span>
</div>
</template>
</el-table-column>
<el-table-column
fixed="left"
prop="列名"
label="列名"
align="center"
width="120"
>
<template slot-scope="scope">
<el-tooltip
v-if="scope.row.列名.length > 6"
class="item"
effect="dark"
:content="scope.row.列名"
placement="bottom"
>
<div class="cell-content" alt="111">
{{ nameFormatter(scope.row.列名) }}
</div>
</el-tooltip>
<div v-else class="cell-content" alt="111">
{{ scope.row.列名 }}
</div>
</template>
</el-table-column>
<el-table-column label="列名" align="center">
<el-table-column
prop="QYSL"
label="列名"
width="150"
align="center"
>
</el-table-column>
<el-table-column
prop="ZCZB"
label="列名)"
width="150"
align="center"
>
</el-table-column>
<el-table-column
prop="SJZB"
label="列名"
width="150"
align="center"
>
</el-table-column>
<el-table-column
prop="SSQYSL"
label="列名"
width="150"
align="center"
>
</el-table-column>
</el-table-column>
<el-table-column
fixed="right"
label="操作"
width="100"
align="center"
>
<template slot-scope="scope">
<el-button
@click="handleClick(scope.row)"
type="text"
size="small"
>跳转</el-button
>
</template>
</el-table-column>
</el-table>
</div>
</fullscreen>
</div>
</div>
</div>
<el-dialog
:title="partTitle"
:visible.sync="modalMy"
:modal-append-to-body="false"
custom-class="colorModal"
width="60vw"
>
<section class="l-box">
<div class="l-ent-info">
<div class="l-ent-info-box">
<div class="tableBoxItem">
<div class="itemBody">
<div
class="chartsBar"
v-loading="loadingFormChart"
element-loading-background="rgba(24, 41, 88, 0)"
>
<div
v-show="noDataBar == false"
id="bar"
class="chartViewBar"
></div>
<p
v-if="noDataBar"
style="text-align:center;font-size: 16px;color: #fff"
>
暂无数据
</p>
</div>
<!-- <div class="tuBox" partTitle="优企发展&企业类型">
</div> -->
</div>
</div>
</div>
</div>
</section>
<!-- <div slot="footer" class="dialog-footer" style="text-align: center;">
<el-button @click="modalMy = false">关 闭</el-button>
</div> -->
</el-dialog>
</div>
</template>
<script>
import Vue from "vue";
import { tempNum } from "@/utils/index";
// import DevelopmentTrend from "./components/DevelopmentTrend.vue";
import {
接口
} from "@/api";
export default {
data() {
return {
targetRowId: 0,
mapShow: true, //控制map地图显示
noData: false,
pieChart: null,
pageNum: 1,
currentPage: 1,
news: [],
noDataBar: false,
tableDataQyList: [],
pageSize: 20,
total: null,
barChart: null,
scatterChart: null,
loading: false,
scatterLoading: false,
indLoading: false,
trendLoading: false,
BomHeights: 0,
industryList: [],
industryValue: "",
params: {},
industryTypeData: [], //产业研判
industryTypeValue: "",
fullscreen: false,
listcol1Length: [],
modalMy: false, //控制 弹窗
modalMyList: false,
partTitle: "", //当前选中的模块名称
partTitleList: "", //列表title
lineargroup: [],
roleList: [],
tableDataTOP: [],
tableData: [],
tableDataRight: [],
heightWatch: "",
areaMyCode: "",
clickedRow: 0,
clickedColumn: 1,
CYFLCode: "",
CYMCCode: "",
};
},
watch: {
CYMCCode() {
this.pieView();
// this.getFilterLabels
this.industryChange();
},
heightWatch(newValue) {
let _this = this;
console.log(newValue);
this.$nextTick(() => {
_this.tablechange();
this.$nextTick(() => {
_this.changeMaxHeight();
});
});
}
},
computed: {
getRowClass() {
return function(row) {
return row.highlight ? "highlight-row" : "";
};
}
},
mounted() {
this.getTopList();
this.pieChart = this.$echarts.init(document.getElementById("pie"));
window.addEventListener("resize", this.chartResize);
let _this = this;
this.$nextTick(function() {
_this.heightWatch = document.getElementsByClassName(
"top-left"
)[0].scrollHeight;
});
window.onresize = function() {
_this.heightWatch = document.getElementsByClassName(
"top-left"
)[0].scrollHeight;
};
},
methods: {
//全屏
toggle() {
this.fullscreen = !this.fullscreen;
this.BomHeights = this.fullscreen
? window.innerHeight - 50
: document.getElementsByClassName("top-left")[0].scrollHeight * 0.9;
let _this = this;
this.$nextTick(() => {
_this.tablechange();
this.$nextTick(() => {
_this.changeMaxHeight();
});
});
},
//table高度设置
tablechange() {
this.BomHeights = this.fullscreen
? window.innerHeight - 50
: document.getElementsByClassName("top-left")[0].scrollHeight * 0.9;
},
//table maxheight设置
changeMaxHeight() {
const fixedBodyWrapper = this.$refs.myTable.$el.querySelectorAll(
".el-table__fixed-body-wrapper"
);
const BodyWrapper = this.$refs.myTable.$el.querySelector(
".el-table__body-wrapper"
);
//冻结列的高度 -8p是去掉滚动条的高度
fixedBodyWrapper[0].style.maxHeight =
BodyWrapper.style.maxHeight.split("px")[0] - 8 + "px";
fixedBodyWrapper[1].style.maxHeight =
BodyWrapper.style.maxHeight.split("px")[0] - 8 + "px";
const fixedBodyWrapperTwo = this.$refs.myTableTwo.$el.querySelectorAll(
".el-table__fixed-body-wrapper"
);
const BodyWrapperTwo = this.$refs.myTableTwo.$el.querySelector(
".el-table__body-wrapper"
);
fixedBodyWrapperTwo[0].style.maxHeight =
BodyWrapperTwo.style.maxHeight.split("px")[0] - 8 + "px";
},
//单元格色阶设置
cellStyle({ row, column, rowIndex, columnIndex }) {
//灵活修改
if (columnIndex != 1 && columnIndex != 0 && columnIndex != 20) {
let ageData = "transparent";
if (row[column.property] != 0) {
// 获取对应列的数据
const columnData = this.tableDataTOP.map(
item => item[column.property]
);
console.log("columnData", columnData);
// 根据数据的值进行排序
const sortedColumnData = columnData.sort((a, b) => b - a);
console.log("sortedColumnData", sortedColumnData);
// 计算数据在百分比范围中的位置
const index = sortedColumnData.findIndex(
item => item === row[column.property]
);
console.log("index", index);
const percentage = Math.ceil(
((index + 1) / sortedColumnData.length) * 100
);
console.log("percentage", percentage);
if (percentage <= 10) {
ageData = " rgba(18, 0, 255, 1)";
} else if (percentage <= 20) {
ageData = " rgba(1, 92, 229, 1)";
} else if (percentage <= 40) {
ageData = "rgba(63, 148, 186, 1)";
} else if (percentage <= 70) {
ageData = "rgba(19, 60, 140, 1)";
} else {
ageData = "rgba(4, 6, 94, 1)";
}
} else {
ageData = "transparent";
}
return { backgroundColor: `${ageData} !important`, cursor: "pointer" };
}
//第二列的选中样式
if (rowIndex === this.clickedRow && columnIndex === 1) {
return { border: `1px solid #2C89E5 !important` };
}
if (columnIndex === 1) {
return { cursor: "pointer" };
}
},
//单元格点击
cellClick(row, column, cell, event) {
//禁止点击
if (
column.label === "列名" ||
column.label == "列名" ||
column.property == "列名"
) {
//阻断
event.stopPropagation();
// 在这里可以添加其他限制点击的逻辑
} else if (column.label === "列名") {
this.clickedRow = row.$index;
this.locateRow("", true);
} else {
this.showModal(column.label, column.property);
}
// console.log(row, column, cell, event);
},
/* 表格合并列和行 */
objectSpanMethod({ rowIndex, columnIndex }) {
if (columnIndex === 0) {
const _row = this.flitterData(this.tableDataTOP).one[rowIndex];
// console.log("_row", _row);
const _col = _row > 0 ? 1 : 0;
return {
rowspan: _row,
colspan: _col
};
}
},
/**合并表格的第一列,处理表格数据 这里是相同的会合并*/
flitterData(arr) {
let spanOneArr = [];
let concatOne = 0;
arr.forEach((item, index) => {
if (index === 0) {
spanOneArr.push(1);
} else {
//注意这里的typeName是表格绑定的字段,根据自己的需求来改
if (item.typeName === arr[index - 1].typeName) {
//第一列需合并相同内容的判断条件
spanOneArr[concatOne] += 1;
spanOneArr.push(0);
} else {
spanOneArr.push(1);
concatOne = index;
}
}
});
return {
one: spanOneArr
};
},
//产业名称最多显示6个子,超出隐藏
nameFormatter(cellValue) {
// console.log(cellValue);
if (cellValue.length > 6) {
return cellValue.slice(0, 6) + "...";
} else {
return cellValue;
}
},
nameTooltip(row) {
// console.log(row);
return row.date;
},
//表头样式
headerCellStyle() {
return {
background: "rgba(11, 87, 252, 0.38)",
boxShadow: "inset 0 0 20px rgba(11, 87, 252, 1)"
};
},
//获取上面列表值
async getTopList() {
this.pieView();
this.roleList = await 接口();
let { data } = await 接口();
//给每一行数据设置唯一值
this.tableDataTOP.forEach((item, index) => {
item.$index = index;
});
},
//点击弹出 弹窗
showModal(type, labelValue) {
let _this = this;
_this.modalMy = true;
_this.$nextTick(() => {
_this.lineview(type, labelValue);
});
_this.partTitle = type;
},
//查看详情跳转
handleClick(row) {
// console.log(row);
let _this = this;
let param = {
// activeCode: _this.tooltipContent.code
};
const { href } = this.$router.resolve({
path: "跳转路由",
query: param
});
// window.open(href, "_blank");
_this.$router.push(href);
},
},
};
</script>
<style lang="less" scoped>
.top {
flex: 1;
height: 0;
margin: 0px 26px 20px 26px;
// display: flex;
// flex-direction: row;
background: url("../../assets/images/patternTop.png") no-repeat center center;
background-size: cover;
.topContent {
height: 90%;
display: flex;
flex-direction: row;
}
& .top-left {
padding-left: 34px;
flex: 0.7;
width: 0;
.listimg1 {
}
.listcol1 {
background: url("../../assets/images/tableColList1.png") no-repeat center
center;
line-height: 42px;
font-size: 16px;
font-family: Microsoft YaHei;
font-weight: 400;
color: #ffffff;
display: block;
}
}
& .top-right {
padding-right: 25px;
flex: 0.3;
width: 0;
background-size: 100% 100%;
display: flex;
flex-direction: column;
}
}
::v-deep .el-table,
::v-deep .el-table__expanded-cell {
background-color: transparent !important;
}
.city-title {
display: flex;
flex-direction: row;
height: 70px;
font-size: 16px;
font-family: Microsoft YaHei;
font-weight: 400;
color: #ffffff;
// line-height: 44px;
.city-title-left {
// line-height: 70px;
padding: 25px 40px;
}
.city-title-right {
line-height: 35px;
}
}
.city-content,
.city-contentTS,
.city-contentTSFull,
.itemBody {
::v-deep .el-table th,
::v-deep .el-table tr,
::v-deep .el-table td {
background-color: transparent !important;
border: 0; //去除表格
}
/*去除底边框*/
::v-deep.el-table td.el-table__cell {
border: 0;
}
/* 表格内背景颜色 */
::v-deep .el-table thead {
background: rgba(11, 87, 252, 0.3);
box-shadow: inset 0 0 50px #0b57fc;
}
::v-deep.el-table th.el-table__cell.is-leaf {
border: 0;
}
::v-deep .el-table--border th.gutter:last-of-type {
border-bottom-width: 0px;
}
::v-deep .no-border.el-table__header-wrapper {
border: 0;
}
::v-deep .no-border.el-table__body-wrapper {
border: 0;
}
::v-deep .el-table::before {
background: none;
}
::v-deep .el-table::after {
background: none;
}
::v-deep .el-table--border,
.el-table--group {
border: 0;
}
// ::v-deep td,
// th {
// border: 1px solid rgba(141, 149, 165, 0.4);
// }
::v-deep thead {
font-size: 14px;
font-family: Microsoft YaHei;
font-weight: 400;
color: #ffffff;
tr:nth-child(1) {
th:nth-child(1) {
border-right: 1px solid rgba(141, 149, 165, 0.4);
}
}
tr:nth-child(1) {
th {
border-bottom: 1px solid rgba(141, 149, 165, 0.4);
border-right: 1px solid rgba(141, 149, 165, 0.4);
}
th:nth-child(1) {
border-bottom: 0;
}
}
tr:nth-child(2) {
th:nth-child(4),
th:nth-child(7),
th:nth-child(16),
th:nth-child(18) {
border-right: 1px solid rgba(141, 149, 165, 0.4);
}
}
tr {
th {
border-right: 0;
}
}
}
::v-deep .el-table__row {
font-size: 16px;
font-family: Microsoft YaHei;
font-weight: 400;
color: #ffffff;
td {
padding: 2px 0;
border-top: 0;
border-bottom: 0;
}
td:nth-child(1),
td:nth-child(2),
td:nth-child(6),
td:nth-child(9),
td:nth-child(18) {
border-right: 1px solid rgba(141, 149, 165, 0.4);
}
// td:nth-child(2n + 1) {
// border-right: 1px solid rgba(141, 149, 165, 0.4);
// }
// td:last-of-type {
// border-right: 0;
// }
}
}
.city-contentTS,
.city-contentTSFull {
height: 100%;
::v-deep .el-table th,
::v-deep .el-table tr,
::v-deep .el-table td {
background-color: transparent !important;
border: 1px solid rgba(141, 149, 165, 0.4); //去除表格
}
/*去除底边框*/
::v-deep.el-table td.el-table__cell {
border: 1px solid rgba(141, 149, 165, 0.4);
}
.fullImg {
width: 18px;
height: 18px;
z-index: 50;
position: fixed;
top: 121px;
left: 68%;
}
}
.itemBody {
::v-deep .el-table th,
::v-deep .el-table tr,
::v-deep .el-table td {
background-color: transparent !important;
border: 1px solid rgba(141, 149, 165, 0.4); //去除表格
}
}
.city-contentTSFull {
height: 100%;
padding-top: 30px;
::v-deep .el-table__body-wrapper::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.fullImg {
width: 18px;
height: 18px;
z-index: 1000;
position: fixed;
top: 7px !important;
left: 98.5% !important;
}
}
/**设置关于el-table的滚动条*/
//滚动条长宽
::v-deep .el-table__body-wrapper::-webkit-scrollbar {
z-index: 5 !important;
// position: relative;
// top: 10px;
// right: 10px;
width: 3px;
height: 8px;
}
//滚动条样式
::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb {
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
z-index: 1000;
border-radius: 0px;
background-color: rgba(0, 138, 255, 1);
}
//滚动条悬浮
::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb:hover {
background-color: rgba(0, 138, 255, 1);
}
//滚动条背景
::v-deep .el-table__body-wrapper::-webkit-scrollbar-track {
background-color: rgba(77, 178, 255, 0.38);
border-radius: 0px;
}
//滚动条交合处
::v-deep .el-table__body-wrapper::-webkit-scrollbar-corner {
// display: none;
background-color: rgba(77, 178, 255, 0.38);
}
.big-view {
z-index: 999;
}
::v-deep .el-table__fixed {
right: 0 !important;
bottom: 0 !important;
}
::v-deep .el-table__fixed-right {
right: 3px !important;
bottom: 0 !important;
}
::v-deep .el-table__body-wrapper {
.el-table--scrollable-y {
z-index: 51 !important;
}
}
::v-deep .el-table__body-wrapper {
overflow-y: scroll !important;
}
::v-deep .el-table__fixed-body-wrapper {
// max-height: 200px !important;
}
::v-deep .el-table__fixed-right .el-table__fixed-body-wrapper {
background: #03277c;
box-shadow: -6px 0px 16px 0px rgba(0, 15, 47, 0.45);
}
::v-deep .el-table__fixed .el-table__fixed-body-wrapper,
.el-table__fixed-header-wrapper {
background: #03277c;
}
::v-deep .el-table__fixed-right .el-table__fixed-header-wrapper {
background: #03277c;
}
::v-deep .el-table__fixed .el-table__fixed-header-wrapper {
background: #03277c;
}
::v-deep .el-table__fixed::before {
height: 0;
}
::v-deep .el-table__fixed-right::before {
height: 0;
}
::v-deep .el-table__fixed-right-patch {
// display: none;
width: 3px !important;
background: rgba(11, 87, 252, 0.3);
box-shadow: inset 0 0 50px #0b57fc;
border-bottom: 0;
}
::v-deep .el-table th.gutter {
display: table-cell !important;
}
.cell-content {
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
}
/deep/.colorModal {
background: url("../../assets/images/modalBg.png") no-repeat center center;
background-size: 101% 101%;
.el-dialog__header {
display: flex;
align-items: center;
justify-content: center;
padding: 30px 15px;
.el-dialog__title {
color: #fff;
font-size: 26px;
&::before {
display: none;
}
}
.el-dialog__headerbtn {
right: 60px;
.el-dialog__close {
font-size: 20px;
color: #fff;
}
}
}
.el-dialog__body {
padding: 30px 50px;
}
.itemBody {
}
}
.itemBody {
height: 50vh;
}
.tuBox {
width: 100%;
height: 100%;
display: flex;
& > div {
width: 0;
flex: 1;
height: 100%;
}
}
</style>