一、el-table滚动条移到合计的下方
当我们将elementUI的参数show-summary
设置为true
时,有时候滚动条会出现在合计行的上方,如何移动到合计行下方:
1、直接添加css样式,复制即用
::v-deep .el-table--scrollable-x .el-table__body-wrapper{
overflow-x: hidden!important;
z-index: 2!important;}
::v-deep .el-table__footer-wrapper {
overflow-x: auto;
border-top: 1px solid #f4f4f4;}
2、监听footerWrapper滚动条
① table添加ref="detailTable" (detailTable只是随便取的一个名字而已,这里如果要修改,记得下面mounted里面也有同步修改)
<el-table
border
:data="processedData"
style="width: 100%"
:header-cell-style="{background:'#f5f7fa'}"
:span-method="spanRows"
show-summary
ref="detailTable"
>
② mounted监听,复制即用
mounted() {
var tableBody = this.$refs.detailTable.$refs.footerWrapper
tableBody.addEventListener('scroll', () => {
// 滚动距离
const scrollLeft = tableBody.scrollLeft
this.$refs.detailTable.$refs.bodyWrapper.scrollLeft = scrollLeft
})
},
为什么是 detailTable
?
ref
是你自己定义的标识符名称,可以随意命名。在你的代码里,你为<el-table>
组件指定了ref="detailTable"
,这使得你可以在this.$refs.detailTable
中访问该组件。- 通过
this.$refs.detailTable
,你就可以访问该表格组件内部的一些子组件引用,比如footerWrapper
(合计行)和bodyWrapper
(数据主体部分的滚动条)。
滚动同步代码的原理
this.$refs.detailTable.$refs.footerWrapper
是表格的合计行区域。addEventListener('scroll', ...)
监听合计行的滚动事件,每当用户在合计行滚动时触发回调。this.$refs.detailTable.$refs.bodyWrapper.scrollLeft = scrollLeft
通过设置表格数据主体部分(bodyWrapper
)的scrollLeft
值,使得它跟随合计行滚动条的滚动距离保持一致。
二、自定义合计行(只在最下面显示合计)
因为 show-summary 计算的合计,会将已合并的数值✖行数(例如,735*4+340*4...),如果仅需要累加一次合并了单元格的数值,需要自定义合计方法
1、定义summary-method方法
<el-table
border
:data="processedData"
style="width: 100%"
:header-cell-style="{background:'#f5f7fa'}"
:span-method="spanRows"
:summary-method="getSummary"
>
2、使用set集合避免重复累加
//计算合计
getSummary() {
const summary = {
crklrcssum: 0, //初始化“领入纯水”
crkckslsum: 0, //初始化“出库数量”
};
const uniqueValues = new Set(); //uniqueValues 被定义为一个 Set 集合,用于跟踪并存储 crklrcssum 字段中已经累计的值,避免重复累加。
this.processedData.forEach(item => {
if (item.crklrcssum && !uniqueValues.has(item.crklrcssum)) {
summary.crklrcssum += Number(item.crklrcssum);
uniqueValues.add(item.crklrcssum);
}
if (item.crkckslsum) {
summary.crkckslsum += Number(item.crkckslsum);
}
});
return summary;
},
为什么使用 Set
Set
是 JavaScript 中的一种数据结构,类似于数组,但它只能存储唯一值。这意味着在 Set
中,重复添加的值会自动被忽略,这就非常适合用来跟踪和去除重复项。
具体作用
在这段代码中,uniqueValues
集合用来确保 crklrcssum
字段的值不会被重复累加。以下是实现逻辑:
- 检查唯一性:代码在
forEach
遍历中,通过uniqueValues.has(item.crklrcssum)
检查item.crklrcssum
是否已被处理。 - 避免重复累加:只有当
uniqueValues
中没有该值时,才将其累加到summary.crklrcssum
。这样即便processedData
中包含多个相同的crklrcssum
值,它们也只会被累加一次。 - 添加到 Set:在累加后,通过
uniqueValues.add(item.crklrcssum)
把当前的crklrcssum
值加入Set
,确保后续遇到相同的值时不会再次累加。
3、插入合计行(我这里是要将后端返回的数据根据逗号分成一条一条的,如果你的后端返回的本来就是单条数据,则不用使用split分割)
processData(data) {
let result = [];
data.forEach(item => {
let crklycjArray = item.crklycjsum.split(',');
let crkckslArray = item.crkckslsum.split(',');
let crkbzArray = item.crkbzsum.split(',');
let crkbmArray = item.crkbmsum.split(','); // 处理部门数据
if (item.crkbmsum.includes(',')) {
this.monthList.push(item.day); // 如果包含逗号,存储对应的 month
}
const maxLength = Math.max(crklycjArray.length, crkckslArray.length, crkbzArray.length, crkbmArray.length);
for (let index = 0; index < maxLength; index++) {
result.push({
day: item.day,
crklrcssum: item.crklrcssum,
crklycjsum: crklycjArray[index] ? crklycjArray[index].trim() : '',
crkbmsum: crkbmArray[index] ? crkbmArray[index].trim() : '', // 处理部门数据
crkckslsum: crkckslArray[index] ? crkckslArray[index].trim() : '',
crkbzsum: crkbzArray[index] ? crkbzArray[index].trim() : '',
});
}
});
this.processedData = result;
const summary = this.getSummary();
// 插入合计行
this.processedData.push({
day: '合计',
crklrcssum: summary.crklrcssum,
crklycjsum: '',
crkbmsum: '',
crkckslsum: summary.crkckslsum,
crkbzsum: '',
});
},
三、自定义合计行(根据物料名称来显示多条合计)
1、先自定义summary-method方法
<el-table
border
:data="reportData"
style="width: 100%"
:header-cell-style="{background:'#f5f7fa'}"
:span-method="spanRows"
:summary-method="getSummary"
>
2、这里暂时还不知道后端返回的数据结构,我前端先是这样定义的
//报表数据
reportData: [
{
materialName: '硫酸',
number: 'A级',
inQuantities: {
lastPeriod: '19858.10',
dayLevel:{
putInStorage:'157.92',
thatDayConfiguration:'',
},
monthLevel:{
putInStorage:'1518.78',
thatMonthConfiguration:'',
}
}],
3、 定义合计方法
①初始化每个字段
②对字段的累加格式化。调用 .toFixed(2)
将浮点数格式化为字符串形式,并保留两位小数(根据个人情况使用,我这里如果不用格式化,累加数值就会出现类似1456.0000002330的错误情况)
③!!!计算插入位置,将每种物料的“合计”插入到 reportData
的相应位置中
getSummary() {
const summaryMap = {}; // 用于存储每种物料的合计
// 计算合计值
this.reportData.forEach(item => {
// 确保每种物料都有一个初始化的合计对象
if (!summaryMap[item.materialName]) {
summaryMap[item.materialName] = {
materialName: '小计',
number: '小计',
inQuantities: {
lastPeriod: 0,
dayLevel: {
putInStorage: 0,
thatDayConfiguration: 0,
},
monthLevel: {
putInStorage: 0,
thatMonthConfiguration: 0,
}
},
outQuantities: {
thatDay: 0,
thatMonth: 0,
},
monthlyBalance: 0,
};
}
// 累加当前物料的值
const summaryItem = summaryMap[item.materialName];
summaryItem.inQuantities.lastPeriod += parseFloat(item.inQuantities.lastPeriod || 0);
summaryItem.inQuantities.dayLevel.putInStorage += parseFloat(item.inQuantities.dayLevel.putInStorage || 0);
summaryItem.inQuantities.dayLevel.thatDayConfiguration += parseFloat(item.inQuantities.dayLevel.thatDayConfiguration || 0);
summaryItem.inQuantities.monthLevel.putInStorage += parseFloat(item.inQuantities.monthLevel.putInStorage || 0);
summaryItem.inQuantities.monthLevel.thatMonthConfiguration += parseFloat(item.inQuantities.monthLevel.thatMonthConfiguration || 0);
summaryItem.outQuantities.thatDay += parseFloat(item.outQuantities.thatDay || 0);
summaryItem.outQuantities.thatMonth += parseFloat(item.outQuantities.thatMonth || 0);
summaryItem.monthlyBalance += parseFloat(item.monthlyBalance || 0);
});
// 格式化小计数据
Object.values(summaryMap).forEach(summaryItem => {
summaryItem.inQuantities.lastPeriod = parseFloat(summaryItem.inQuantities.lastPeriod).toFixed(2);
summaryItem.inQuantities.dayLevel.putInStorage = parseFloat(summaryItem.inQuantities.dayLevel.putInStorage).toFixed(2);
summaryItem.inQuantities.dayLevel.thatDayConfiguration = parseFloat(summaryItem.inQuantities.dayLevel.thatDayConfiguration).toFixed(2);
summaryItem.inQuantities.monthLevel.putInStorage = parseFloat(summaryItem.inQuantities.monthLevel.putInStorage).toFixed(2);
summaryItem.inQuantities.monthLevel.thatMonthConfiguration = parseFloat(summaryItem.inQuantities.monthLevel.thatMonthConfiguration).toFixed(2);
summaryItem.outQuantities.thatDay = parseFloat(summaryItem.outQuantities.thatDay).toFixed(2);
summaryItem.outQuantities.thatMonth = parseFloat(summaryItem.outQuantities.thatMonth).toFixed(2);
summaryItem.monthlyBalance = parseFloat(summaryItem.monthlyBalance).toFixed(2);
});
// 将每种物料的合计汇总到一个数组中
const summaryArray = Object.values(summaryMap);
// this.reportData.push(...summaryArray);
const insertPositions = []; // 用于记录插入位置
// 计算插入位置
Object.entries(summaryMap).forEach(([materialName]) => {
const lastIndex = this.reportData.reduce((acc, item, index) => {
return item.materialName === materialName ? index : acc;
}, -1);
if (lastIndex !== -1) {
insertPositions.push(lastIndex + 1); // 记录插入位置
}
});
// 按照插入位置插入小计
insertPositions.forEach((pos, index) => {
this.reportData.splice(pos + index, 0, summaryArray[index]);
});
// 返回只包含每种物料合计的数组
return summaryArray;
},
这里是详细的分步解释:
1. 记录每种物料的最后位置 (insertPositions
数组)
insertPositions
:用于记录每种物料小计的插入位置。Object.entries(summaryMap).forEach(...)
:遍历summaryMap
中的每种物料,materialName
是物料名称。lastIndex
:找到reportData
中该物料名称的最后一项位置。例如,如果reportData
有多条记录属于同一物料,小计应该插入在最后一条记录的后面。reduce()
函数的逻辑是遍历reportData
中的每一项,如果item.materialName
和materialName
相等,则将当前index
赋值给acc
,即更新最后出现的位置。
- 如果找到物料(
lastIndex !== -1
),就在insertPositions
中记录插入位置lastIndex + 1
(即该物料最后一条记录的后面)。
2. 根据 insertPositions
插入小计
insertPositions.forEach(...)
:遍历 insertPositions
中每个插入位置 pos
。
pos + index
:index
是当前的插入次数,用来偏移插入位置,以确保每次插入不会影响后续插入位置的准确性。summaryArray[index]
:取summaryArray
中对应的物料小计。
* Object.entries()
方法的原理:
Object.entries()
是 JavaScript 的一个标准方法,用来获取对象的键值对。它将对象的每个属性(即键值对)转换成一个数组中的 [key, value]
数组对,然后把这些 [key, value]
数组组合成一个新的数组返回。示例如下:
* .reduce
方法
array.reduce((accumulator, currentValue, index, array) => {...}, initialValue);
具体来说:
accumulator
(缩写为acc
):累加器,用于累积每次回调的返回值。这个值会在每次迭代中更新,并最终成为.reduce
的返回值。currentValue
(缩写为item
):当前被处理的数组元素。index
:当前元素的索引,可选参数。array
:被遍历的数组本身,也是一个可选参数。
示例如下:
const numbers = [10, 20, 30];
const sum = numbers.reduce((acc, item, index, array) => {
console.log(`累加器: ${acc}, 当前值: ${item}, 索引: ${index}, 数组: ${array}`);
return acc + item;
}, 0);
console.log(sum);
//打印输出如下
累加器: 0, 当前值: 10, 索引: 0, 数组: [10, 20, 30]
累加器: 10, 当前值: 20, 索引: 1, 数组: [10, 20, 30]
累加器: 30, 当前值: 30, 索引: 2, 数组: [10, 20, 30]
四、合并行(目前还不懂,该代码是上面第一个示例表的)
spanRows({ row, column, rowIndex }) {
const getCount = (prop) => this.processedData.filter(r => r[prop] === row[prop]).length;
if (column.property === 'day' ) {
const prevRow = this.processedData[rowIndex - 1];
if (prevRow && prevRow[column.property] === row[column.property] && (prevRow.day === row.day )) {
return { rowspan: 0, colspan: 0 }; // 取消合并
}
const count = getCount(column.property);
return { rowspan: count, colspan: 1 }; // 合并当前值的行
}
return { rowspan: 1, colspan: 1 }; // 默认不合并
},
day 是你要合并行的这一列的名称,如:我要按日期合并行
processedData 是我table表数据存放的数组
五、el-date-picker默认日期设置
1、年份选择器(type="year")
data() {
return {
filterConditionForm: {
date: new Date(2023, 0, 1) // 2023年
}
}
}
2、月份选择器(type="month")
data() {
return {
filterConditionForm: {
date: new Date(2023, 5) // 2023年6月
}
}
}
3、日期选择器(type="date")
data() {
return {
filterConditionForm: {
date: new Date(2023, 5, 15) // 2023年6月15日
}
}
}
4、日期时间选择器(type="datetime")
data() {
return {
filterConditionForm: {
date: new Date(2023, 5, 15, 14, 30) // 2023年6月15日 14:30
}
}
}
获取日期的常用方法:
- 获取年份:
getFullYear()
: 返回四位数的年份(如 2023)getYear()
: 返回年份减去 1900(不推荐使用)
- 获取月份:
getMonth()
: 返回月份(0-11,其中 0 表示一月)
- 获取日期:
getDate()
: 返回月份中的第几天(1-31)
- 获取星期:
getDay()
: 返回星期几(0-6,其中 0 表示星期日)
- 获取小时:
getHours()
: 返回小时(0-23)
- 获取分钟:
getMinutes()
: 返回分钟(0-59)
- 获取秒:
getSeconds()
: 返回秒(0-59)
- 获取毫秒:
getMilliseconds()
: 返回毫秒(0-999)
- 获取时间戳:
getTime()
: 返回自 1970 年 1 月 1 日以来的毫秒数
此外,还有一些实用的方法来格式化和解析日期:
- 转换为字符串:
toDateString()
: 返回日期部分的字符串toTimeString()
: 返回时间部分的字符串toISOString()
: 返回 ISO 格式的日期字符串
- 解析日期字符串:
Date.parse()
: 解析日期字符串,返回时间戳
使用示例:
let now = new Date();
console.log("年份:", now.getFullYear());
console.log("月份:", now.getMonth() + 1); // 注意要加1,因为月份从0开始
console.log("日期:", now.getDate());
console.log("星期:", now.getDay());
console.log("小时:", now.getHours());
console.log("分钟:", now.getMinutes());
console.log("秒:", now.getSeconds());
// 格式化日期
let year = now.getFullYear();
let month = (now.getMonth() + 1).toString().padStart(2, '0'); // 补零
let day = now.getDate().toString().padStart(2, '0'); // 补零
console.log(`格式化日期:${year}-${month}-${day}`);
ps : 时间戳通常是一个很长的数字
六、el-table树状结构默认展开层数
5层结构我想要默认加载时只展开一层,如下图:
①必要定义 row-key,:expand-row-keys="expandRowKeys"
②初始化
③一定要转化为String类型!!!
第一步
<el-table
v-loading="loading"
:data="list"
border
size="mini"
row-key="id"
lazy
:expand-row-keys="expandRowKeys"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
>
<el-table-column label="ID" align="left" prop="id" width="140"/>
<el-table-column label="分類名稱" align="left" prop="name"/>
第二步
data() {
return {
expandRowKeys: []
第三步
getAllCategoriesTreeList(){
this.loading = true
getAllCategoriesTreeList({
}).then((res)=>{
this.loading = false
if (res) {
this.recursionStatusTextAndCls(res, 'children', this.availableOption)
this.list = res
this.$nextTick(() => {
this.list.forEach(item => {
this.expandRowKeys.push(String(item.id)); // 第一层展开,转字符串
// if (item.children) {
// item.children.forEach(child => {
// this.expandRowKeys.push(String(child.id)); // 第二层展开,转字符串
// });
// }
});
});
}
})
},