本文主要是基于element中table拼接动态列并对动态列合计、行合并、加合计行、对table默认样式的修改、table中局部loading效果的实现。
实现效果图:
目录
数据结构
[
{
"id": "1",
"projectBudgetId": "1",
"subjectName": "测试名",
"itemName": "测试名",
"remark": null,
"startTime": 2024,
"endTime": 2026,
"totalAmount": "2200.0000",
"proPlanAndItemQuarters": [
{
"capitalOutflowPlanId": null,
"year": "2024",
"quarter": null,
"quarterAmount": null,
"totalAmountForYear": "2200"
},
{
"capitalOutflowPlanId": null,
"year": "2024",
"quarter": "Q1",
"quarterAmount": "1200",
"totalAmountForYear": null
},
]
}
]
动态列主要是拼接
proPlanAndItemQuarters
中的字段
详细代码
下面为详细代码:
- template中
<div class="table-con">
<el-table
:data="tableData"
:span-method="objectSpanMethod"
show-summary
:summary-method="getSummaries"
border
style="width: 100%; height: 100%;"
v-loading="loading"
element-loading-text="拼命加载中..."
element-loading-spinner="el-icon-loading"
>
<el-table-column prop="subjectName" label="科目" align="center" fixed />
<el-table-column prop="itemName" label="成本项" align="center" fixed/>
<el-table-column prop="totalAmount" label="合计" align="center" fixed/>
<el-table-column
v-for="(items, index) in tableCols"
:key="index + items"
:label="curLable(items)"
align="center" >
<template v-slot="scope">
{{ handleReplaceNum(scope.row.proPlanAndItemQuarters[index].quarterAmount, '-') }}
</template>
</el-table-column>
</el-table>
</div>
- css
.table-con {
height: calc(100% - 21px);
}
:deep(.el-table) {
--el-table-border-color: transparent;
}
:deep(.el-scrollbar__wrap),
:deep(.el-table th),
:deep(.el-table tr),
:deep(.el-table td),
:deep(.el-table tfoot td.el-table__cell) {
background-color: #0A3C9C !important;
border-color: #04113E;
color: #FFFFFF;
font-size: 24px;
}
:deep(.el-table th) {
background-color: #1B66E9 !important;
}
:deep(.el-table th.el-table__cell.is-leaf) {
border-color: #04113E !important;
}
/* 去掉最下面的那一条线 */
.el-table::before {
height: 0px;
}
/* 设置表格行高度 */
:deep(.el-table__body tr),
:deep(.el-table__body td) {
padding: 0;
height: 54px;
}
/* 修改高亮当前行颜色 */
:deep(.el-table tbody tr:hover>td) {
background: #063570 !important;
}
/* 取消当前行高亮 */
:deep(.el-table tbody tr) {
pointer-events: none;
}
/* 表格斑马自定义颜色 */
:deep(.el-table__row.warning-row) {
background: #04113E;
}
:deep(.el-table .cell),
.el-table th div {
font-size: 14px;
}
:deep(.el-table--border .el-table__inner-wrapper) {
height: 100%;
}
:deep(.el-table tfoot td.el-table__cell) {
border-top: 1px solid #04113E;
}
:deep(.el-table.is-scrolling-left.el-table--border .el-table-fixed-column--left.is-last-column.el-table__cell) {
border-right: 1px solid #04113E;
}
- script
<script setup>
import EmptyEle from '@/components/EmptyEle/index';
import titleCon from "../components/titleCon"
import { planItem } from "@/api/largeScreenProject/projectKanban"
import { changeQuarter, handleReplaceNum } from '../utils/index'
let props = defineProps({
projectId: {
type: String,
required: true
},
})
let tableData = ref([]); // 表格数据
let tableCols = ref([]); //动态列
let spanArr = ref([]);
let pos = ref(0);
let loading = ref(false);
let totalArr = ref([0, 0, 0]); //合计行数据初始化
watch(() => props.projectId, (newValue, oldValue) => {
handlePlanItem();
})
// 获取列表数据
const handlePlanItem = () => {
tableData.value = [];
tableCols.value = [];
totalArr = ref([0, 0, 0]);
loading.value = true;
planItem(props.projectId).then(res => {
if(res?.code === 200) {
tableData.value = res?.data;
tableCols.value = tableData.value?.[0]?.proPlanAndItemQuarters || [];
handleResData();
calcQuarterTotal();
}
}).catch(error => {
console.error('获取计划项数据发生错误', error);
}).finally(() => {
loading.value = false;
});
}
// 对后台返回数据的处理,用于合并行
const handleResData = () => {
spanArr.value = [];
pos.value = 0;
for (var i = 0; i < tableData.value.length; i++) {
if (i === 0) {
spanArr.value.push(1);
} else {
// 判断当前元素与上一个元素subjectName是否相同
if (tableData.value[i].subjectName === tableData.value[i - 1].subjectName) {
spanArr.value[pos.value] += 1;
spanArr.value.push(0);
} else {
spanArr.value.push(1);
pos.value = i;
}
}
}
}
// 合并行的计算规则(当前行row、当前列column、当前行号rowIndex、当前列号columnIndex)
const objectSpanMethod = ({ row, column, rowIndex, columnIndex }) => {
if (columnIndex === 0) {
const _row = spanArr.value[rowIndex];
return {
rowspan: _row,
colspan: 1
};
}
return {
rowspan: 1,
colspan: 1
};
}
// 季度求和
const calcQuarterTotal = () => {
const quarterSums = {};
tableData.value.forEach(item => {
item.proPlanAndItemQuarters.forEach(quarter => {
const key = quarter.year + '-' + quarter.quarter;
quarterSums[key] = (quarterSums[key] || 0) + Number(quarter.quarterAmount);
});
});
for (const key in quarterSums) {
totalArr.value.push(`${quarterSums[key].toFixed(2)}`);
}
}
const getSummaries = (param) => {
const { columns, data } = param;
const summaries = {};
columns.forEach((column, index) => {
if (index === 0) {
summaries[index] = '合计';
} else if (index === 2) {
const values = data.map((item) => Number(item[column.property]));
const sum = values.reduce((prev, curr) => {
const value = Number(curr);
return Number.isNaN(value) ? Number(prev) : Number(prev) + value;
}, 0);
summaries[index] = sum.toFixed(2);
} else if (index >= 3) {
summaries[index] = handleReplaceNum(totalArr.value[index], '-');
}
});
return summaries;
};
// 动态列label
const curLable = (items) => {
return items.quarter != -1 ? items.year +'年度第'+ changeQuarter(items.quarter) + '季度' : items.year +'年度';
}
onMounted(() => {
handlePlanItem();
})
</script>
具体代码块-动态列实现
<el-table>
<!-- 这块可写其它固定的列展示 -->
<el-table-column
v-for="(items, index) in tableCols"
:key="index + items"
:label="curLable(items)"
align="center" >
<template v-slot="scope">
{{ handleReplaceNum(scope.row.proPlanAndItemQuarters[index].quarterAmount, '-') }}
</template>
</el-table-column>
</el-table>
对上面代码的简单说明:
1, 我这块tableCols取的后端返回数据取的第一条数据中的proPlanAndItemQuarters
字段,因为动态拼接的列头都一样,所以随便遍历一条即可。【列头label显示需要前端自行转换拼接为:X年度第X季度。若quarter为-1,则标识只有年度】
2. 插槽进行具体数据显示,这里注意与vue2插槽
用法的区别,这里只能用v-slot
3. handleReplaceNum这个封装的方法主要是将为0的数据或者没有的数据替换为 - 显示(可不要)
具体代码块-行合并
:span-method="objectSpanMethod"
// 在获取到数据后对数据进行合并
const handleResData = () => {
spanArr.value = []; // 用于存储跨越行数
pos.value = 0; // 该变量用于跟踪 spanArr数组中最后一个非零元素的位置
for (var i = 0; i < tableData.value.length; i++) {
if (i === 0) {
// 数组中的第一个元素, 添加到 spanArr数组中。这是因为第一个单元格不需要合并,所以它的跨越行数为 1
spanArr.value.push(1);
} else {
// 数组中的其他数据,代码检查当前元素的 subjectName 属性是否与前一个元素的 subjectName 属性相同
if (tableData.value[i].subjectName === tableData.value[i - 1].subjectName) {
// 相同则意味着当前单元格需要与上一个单元格合并,将 spanArr数组中对应位置的值加 1。然后在 spanArr数组末尾添加一个 0,表示接下来的单元格不需要跨越任何行
spanArr.value[pos.value] += 1;
spanArr.value.push(0);
} else {
// 如果不同,则在 spanArr数组末尾添加一个 1,表示当前单元格不需要跨越任何行,并更新 pos为当前索引 i,以便在下次迭代时能够正确更新跨越行数
spanArr.value.push(1);
pos.value = i;
}
}
}
}
const objectSpanMethod = ({ row, column, rowIndex, columnIndex }) => {
if (columnIndex === 0) { // 对第一列数据进行处理
const _row = spanArr.value[rowIndex]; // 从 spanArr数组中获取与当前行索引对应的值,并将其存储在 _row 变量中,列不需要合并,因此值均为1
return {
rowspan: _row,
colspan: 1
};
}
return {
rowspan: 1,
colspan: 1
};
}
若想合并多列,这里举个栗子 合workName 和 firstLevelNodeName两列
const handleResData = () => {
spanArr.value = [];
pos.value = 0;
for (var i = 0; i < dataSource.value.length; i++) {
if (i === 0) {
spanArr.value.push(1);
} else {
if (dataSource.value[i].workName === dataSource.value[i - 1].workName ||
dataSource.value[i].firstLevelNodeName === dataSource.value[i - 1].firstLevelNodeName) {
spanArr.value[pos.value] += 1;
spanArr.value.push(0);
} else {
spanArr.value.push(1);
pos.value = i;
}
}
}
}
const objectSpanMethod = ({ row, column, rowIndex, columnIndex }) => {
// 对这两列数据进行合并
if (['workName', 'firstLevelNodeName'].includes(column.property)) {
const _row = spanArr.value[rowIndex];
return {
rowspan: _row,
colspan: 1
};
}
return {
rowspan: 1,
colspan: 1
};
}
具体代码块-列合计
// 首先table先开启这两属性
show-summary
:summary-method="getSummaries"
let totalArr = ref([0, 0, 0]); //合计行数据初始化(这块初始化主要是为下面取数据方便,因为我前三列不需要取这块计算到的值,到时候直接使用对应的index取对应列和即可
// 季度求和--获取到table数据后就可执行
const calcQuarterTotal = () => {
const quarterSums = {};
tableData.value.forEach(item => {
item.proPlanAndItemQuarters.forEach(quarter => {
const key = quarter.year + '-' + quarter.quarter; // 将年度和季度拼接 作为key值
quarterSums[key] = (quarterSums[key] || 0) + Number(quarter.quarterAmount); //对当前这个季度数据求和
});
});
for (const key in quarterSums) {
totalArr.value.push(`${quarterSums[key].toFixed(2)}`); //将求和的数据push到一个数组中
}
}
// 合计行中数据-第0列显示“合并”, 第1列都是文字不需要,第2列对后端返回的数据求和,从第3列开始就取我们计算好的值
const getSummaries = (param) => {
const { columns, data } = param;
const summaries = {}; //储存最终的合计数据
columns.forEach((column, index) => {
if (index === 0) {
summaries[index] = '合计';
} else if (index === 2) {
const values = data.map((item) => Number(item[column.property]));
const sum = values.reduce((prev, curr) => {
const value = Number(curr);
return Number.isNaN(value) ? Number(prev) : Number(prev) + value;
}, 0);
summaries[index] = sum.toFixed(2);
} else if (index >= 3) {
summaries[index] = handleReplaceNum(totalArr.value[index], '-');
}
});
return summaries;
};
具体代码块-修改el-table默认样式
:deep(.el-table) {
--el-table-border-color: transparent;
}
:deep(.el-scrollbar__wrap),
:deep(.el-table th),
:deep(.el-table tr),
:deep(.el-table td),
:deep(.el-table tfoot td.el-table__cell) {
background-color: #0A3C9C !important;
border-color: #04113E;
color: #FFFFFF;
font-size: 24px;
}
:deep(.el-table th) {
background-color: #1B66E9 !important;
}
:deep(.el-table th.el-table__cell.is-leaf) {
border-color: #04113E !important;
}
/* 去掉最下面的那一条线 */
.el-table::before {
height: 0px;
}
/* 设置表格行高度 */
:deep(.el-table__body tr),
:deep(.el-table__body td) {
padding: 0;
height: 54px;
}
/* 修改高亮当前行颜色 */
:deep(.el-table tbody tr:hover>td) {
background: #063570 !important;
}
/* 取消当前行高亮 */
:deep(.el-table tbody tr) {
pointer-events: none;
}
/* 表格斑马自定义颜色 */
:deep(.el-table__row.warning-row) {
background: #04113E;
}
:deep(.el-table .cell),
.el-table th div {
font-size: 14px;
}
:deep(.el-table--border .el-table__inner-wrapper) {
height: 100%;
}
:deep(.el-table tfoot td.el-table__cell) {
border-top: 1px solid #04113E;
}
:deep(.el-table.is-scrolling-left.el-table--border .el-table-fixed-column--left.is-last-column.el-table__cell) {
border-right: 1px solid #04113E;
}
table中局部loading效果
v-loading="loading"
element-loading-text="拼命加载中..."
element-loading-spinner="el-icon-loading"
- 下面定义变量loading
- 在发请求时可开启loading效果
表格高度根据页面自适应
表格高设置100% 即可,若设置不正确则排查它的
父以及再往上
是不是都100%布的