滚动更新

This commit is contained in:
17792275749
2025-06-09 17:22:06 +08:00
parent b966f9160c
commit 5b8d213f08
@@ -27,17 +27,27 @@
<div>姓名</div>
<div>体重管理得分</div>
</div>
<div class="staffContent">
<template v-if="list && list.length">
<template v-for="item in list">
<div class="staffContentList" :key="item.rank + item.departName + item.realName">
<div>{{ item.rank }}</div>
<div>{{ item.departName }}</div>
<div>{{ item.realName }}</div>
<div>{{ item.value }}</div>
</div>
</template>
</template>
<div class="staffContent" ref="staffContent">
<div
v-if="list && list.length"
class="staff-list-wrapper"
ref="staffList"
:style="{
transform: `translateY(${scrollPosition}px)`,
transition: isScrolling ? `transform ${scrollAnimationDuration}s linear` : 'none'
}"
@transitionend="isScrolling = false">
<div
class="staffContentList"
v-for="item in list"
:key="item.rank + item.departName + item.realName"
>
<div>{{ item.rank }}</div>
<div>{{ item.departName }}</div>
<div>{{ item.realName }}</div>
<div>{{ item.value }}</div>
</div>
</div>
<template v-else>
<div class="nullData">
<div></div>
@@ -88,60 +98,72 @@
</div>
</template>
<div class="footer">公共事业部</div>
<div class="footer">公共事务中心</div>
</div>
</template>
<script>
import {httpRequest} from "@/XMLHttpRequest";
import {getCurrentDate} from "@/utils/times";
import { httpRequest } from "@/XMLHttpRequest";
import { getCurrentDate } from "@/utils/times";
export default {
name: "dietaryNutritionTips",
data() {
data: function () { // 箭头函数转为普通函数
return {
setTimeoutParams: {
status: true,
times: 15
times: 15, // 页面切换的间隔时间,单位:秒
},
requestConfig: {
agreement: "https",
url: "api.cqygjk.com",
orgCode: "A01A86"
orgCode: "A01A86",
},
personnelType: "staff", // 员工:staff / 部门:department
personnelType: "staff", // 员工:staff / 部门:department
pageSize: 0,
currentData: "",
doingDay: "-",
list: [],
timerId: null
}
timerId: null, // 用于控制页面切换的定时器ID
// 新增用于单次滚动的状态
scrollPosition: 0, // 滚动位置
scrollAnimationDuration: 0, // 滚动动画持续时间
isScrolling: false, // 标志是否正在进行滚动动画
initialDelayTimer: null, // 用于滚动前停顿的定时器ID
};
},
created() {
created: function () { // 箭头函数转为普通函数
let url = this.$route.query.url;
if(url) {
if (url) {
this.requestConfig.url = url;
}
let code = this.$route.query.orgCode;
if(code) {
if (code) {
this.requestConfig.orgCode = code;
}
this.currentData = getCurrentDate();
this.startSequentialLoading();
},
methods: {
pageTimerCallback() {
this.personnelType = this.personnelType === "staff" ? "department" : "staff";
/**
* 页面切换回调函数,用于在员工和部门模式之间切换。
*/
pageTimerCallback: function () { // 箭头函数转为普通函数
// 切换人员类型,并重新启动加载和滚动流程
this.personnelType = this.personnelType === "department" ? "staff" : "department";
this.startSequentialLoading();
},
// 启动或重置顺序加载流程
startSequentialLoading() {
this.list = [];
/**
* 启动或重置顺序加载流程。
* 根据当前人员类型设置 pageSize,并调用数据加载方法。
*/
startSequentialLoading: function () { // 箭头函数转为普通函数
this.list = []; // 清空列表,准备加载新数据
if (this.personnelType === "staff") {
this.pageSize = 45;
} else {
@@ -150,60 +172,184 @@ export default {
this.loadDataSequential();
},
loadDataSequential() {
let this_ = this;
let urlPrefix = '/health-intervene/api/anon';
let apiUrl = '';
/**
* 加载数据并根据人员类型处理滚动或定时器。
*/
loadDataSequential: function () { // 箭头函数转为普通函数
let this_ = this; // 缓存 this 指向
let urlPrefix = "/health-intervene/api/anon";
let apiUrl = "";
// 根据人员类型构建 API URL
if (this_.personnelType === "staff") {
apiUrl = urlPrefix + '/userRank';
apiUrl = urlPrefix + "/userRank";
} else {
apiUrl = urlPrefix + '/deptRank';
apiUrl = urlPrefix + "/deptRank";
}
let url = this_.requestConfig.agreement + '://' + this_.requestConfig.url + apiUrl + '?orgCode=' + this_.requestConfig.orgCode + '&pageNo=1&pageSize=' + this_.pageSize;
let url = this_.requestConfig.agreement + "://" + this_.requestConfig.url + apiUrl + "?orgCode=" + this_.requestConfig.orgCode + "&pageNo=1&pageSize=" + this_.pageSize;
// 防止重复请求
if (this_.isRequesting) {
console.warn('请求正在进行中,跳过本次调用。');
return;
}
this_.isRequesting = true;
this_.isRequesting = true; // 设置请求状态为进行中
httpRequest('POST', url, {}, {}).then(function(res) {
// 发送 HTTP 请求
httpRequest('POST', url, {}, {}).then(function (res) { // 回调函数使用普通函数
if (res.success && res.result && res.result.rankList) {
if (this_.personnelType === "staff") {
this_.doingDay = res.result.doingDay === null ? '-' : res.result.doingDay;
this_.doingDay =
res.result.doingDay === null ? "-" : res.result.doingDay;
}
this_.list = res.result.rankList;
this_.list = res.result.rankList; // 更新列表数据
} else {
this_.list = [];
this_.list = []; // 数据不成功或无数据时清空列表
}
}).catch(function() {
this_.list = [];
}).finally(() => {
this_.isRequesting = false;
}).catch(function (error) { // 回调函数使用普通函数
console.error("请求失败:", error);
this_.list = []; // 请求失败时清空列表
}).finally(function () { // 回调函数使用普通函数
this_.isRequesting = false; // 请求完成,重置请求状态
// if (this_.setTimeoutParams.status) {
// if (this_.timerId) {
// clearTimeout(this_.timerId);
// }
// this_.timerId = setTimeout(() => {
// this_.pageTimerCallback();
// this_.timerId = null;
// }, this_.setTimeoutParams.times * 1000);
// }
// 清除可能存在的页面切换定时器
if (this_.timerId) {
clearTimeout(this_.timerId);
this_.timerId = null;
}
// 清除可能存在的初始延迟定时器
if (this_.initialDelayTimer) {
clearTimeout(this_.initialDelayTimer);
this_.initialDelayTimer = null;
}
if (this_.setTimeoutParams.status) {
if (this_.personnelType === "staff" && this_.list.length > 0) {
// 员工模式下,如果数据存在,则在下一渲染周期启动单次滚动
this_.$nextTick(function () { // 回调函数使用普通函数
this_.startSingleScroll();
});
} else {
// 部门模式或员工无数据,直接设置页面切换定时器
this_.timerId = setTimeout(function () { // 回调函数使用普通函数
this_.pageTimerCallback();
}, this_.setTimeoutParams.times * 1000);
}
}
});
},
}
/**
* 开始员工列表的单次滚动动画。
* 包含滚动前停顿、精确高度计算、动画重置和动画结束回调。
*/
startSingleScroll: function () { // 箭头函数转为普通函数
let contentEl = this.$refs.staffContent; // 滚动容器 (staffContent)
let listWrapperEl = this.$refs.staffList; // 实际滚动的元素 (staff-list-wrapper)
let this_ = this; // 缓存 this 指向,在 setTimeout 和 requestAnimationFrame 回调中使用
// 如果元素不存在或正在滚动中,则不执行
if (!contentEl || !listWrapperEl || this.isScrolling) {
return;
}
let contentHeight = contentEl.clientHeight; // 容器的可见高度
let actualListHeight = 0; // 存储列表的真实总高度,包括 margin-bottom
// 确保列表项已经渲染,才能获取其高度
if (listWrapperEl.children.length > 0) {
let firstItem = listWrapperEl.children[0];
let itemHeight = firstItem.offsetHeight; // 元素自身高度 (内容+内边距+边框)
let itemMarginBottom = parseFloat(
window.getComputedStyle(firstItem).marginBottom
); // 获取计算后的 margin-bottom
// 累加所有元素的实际占据高度 (每个item的高度 + 它的margin-bottom)
actualListHeight = (itemHeight + itemMarginBottom) * listWrapperEl.children.length;
// 注意:如果你的设计是滚动到最后一个列表项的底部(而不是它下方margin-bottom的位置),
// 可以在这里减去最后一个元素的 margin-bottom
// if (listWrapperEl.children.length > 0) {
// actualListHeight -= itemMarginBottom;
// }
}
// 如果列表实际高度小于或等于容器高度,则无需滚动
if (actualListHeight <= contentHeight) {
this.scrollAnimationDuration = 0;
this.scrollPosition = 0; // 确保位置重置为顶部
// 即使不滚动,也要在一段时间后切换到部门视图
this.timerId = setTimeout(function () { // 回调函数使用普通函数
this_.pageTimerCallback();
}, this.setTimeoutParams.times * 1000);
return;
}
this.isScrolling = true; // 标记开始滚动
let scrollDistance = actualListHeight - contentHeight; // 计算需要滚动的总距离
let scrollSpeed = 40; // 像素/秒,你可以根据需要调整这个值,值越小滚动越慢
this.scrollAnimationDuration = scrollDistance / scrollSpeed; // 计算动画持续时间(秒)
// --- 关键重置步骤 ---
// 1. 立即重置 transform 属性到顶部 (0px)
listWrapperEl.style.transform = 'translateY(0px)';
// 2. 立即移除 transition 属性,确保没有旧动画的干扰
listWrapperEl.style.transition = 'none';
this.scrollPosition = 0; // 更新数据模型中的位置
// 3. 强制浏览器重绘/回流,使其立即应用上述样式变化
// 这是确保动画从正确初始状态开始的关键一步。访问 offsetWidth/Height 等属性可以触发回流。
void listWrapperEl.offsetWidth;
// --- 新增:滚动前停顿一秒 ---
this.initialDelayTimer = setTimeout(function () { // 回调函数使用普通函数
// 在延迟结束后,通过 requestAnimationFrame 确保在浏览器下一次重绘前开始动画
requestAnimationFrame(function () { // 回调函数使用普通函数
// 设置目标位置(负值表示向上滚动)
this_.scrollPosition = -scrollDistance;
// 重新添加 transition 属性,启动平滑滚动
listWrapperEl.style.transition = 'transform ' + this_.scrollAnimationDuration + 's linear';
// 监听动画结束事件
// 使用一个局部变量来存储事件处理函数,以便在事件触发后正确移除
let onTransitionEnd = function () { // 回调函数使用普通函数
listWrapperEl.removeEventListener('transitionend', onTransitionEnd); // 移除监听,防止重复触发
this_.isScrolling = false; // 标记滚动结束
// --- 滚动结束后,设置页面切换定时器 ---
this_.timerId = setTimeout(function () { // 回调函数使用普通函数
this_.pageTimerCallback();
}.bind(this_), 2000);
}.bind(this_); // 绑定 this_
listWrapperEl.addEventListener('transitionend', onTransitionEnd);
});
}, 2000); // 停顿 2000 毫秒(2秒)
},
},
beforeDestroy() {
beforeDestroy: function () { // 箭头函数转为普通函数
// 组件销毁前清除所有可能存在的定时器,防止内存泄漏
if (this.timerId) {
clearTimeout(this.timerId);
this.timerId = null;
}
if (this.initialDelayTimer) {
clearTimeout(this.initialDelayTimer);
this.initialDelayTimer = null;
}
// 确保移除可能残余的 transitionend 监听器,特别是在动画未完成时组件被销毁的情况
let listWrapperEl = this.$refs.staffList;
if (listWrapperEl) {
// 由于 onTransitionEnd 是在 startSingleScroll 内部定义的局部函数,
// 且每次调用 startSingleScroll 都会创建一个新的 onTransitionEnd
// 因此这里无法直接移除特定的命名函数。
// 对于当前实现,由于事件会在结束后自行移除,且 setTimeout 会被清除,
// 通常情况下不会造成严重问题。
}
},
}
};
</script>
<style scoped lang="less">
@@ -232,12 +378,12 @@ export default {
> div:nth-of-type(1) {
width: 120px;
height: 4px;
background: linear-gradient(270deg, #FFC87C 0%, rgba(42,199,159,0) 100%);
background: linear-gradient(270deg, #ffc87c 0%, rgba(42, 199, 159, 0) 100%);
border-radius: 2px;
}
> div:nth-of-type(2) {
color: #FFC87C;
color: #ffc87c;
height: 36px;
font-size: 36px;
font-weight: bold;
@@ -248,7 +394,7 @@ export default {
> div:nth-of-type(3) {
width: 120px;
height: 4px;
background: linear-gradient(90deg, #FFC87C 0%, rgba(42,199,159,0) 100%);
background: linear-gradient(90deg, #ffc87c 0%, rgba(42, 199, 159, 0) 100%);
border-radius: 2px;
}
}
@@ -280,10 +426,10 @@ export default {
padding: 18px 30px 0;
border-radius: 24px;
box-sizing: border-box;
background: linear-gradient(-90deg, #FFC77B 0%, #FFF4E6 47%, #FFC879 100%);
background: linear-gradient(-90deg, #ffc77b 0%, #fff4e6 47%, #ffc879 100%);
> div:nth-of-type(1) {
color: #8D0503;
color: #8d0503;
width: 100%;
height: 48px;
font-size: 48px;
@@ -318,14 +464,14 @@ export default {
align-items: flex-end;
> div:nth-of-type(1) {
color: #8D0503;
color: #8d0503;
height: 32px;
font-size: 30px;
line-height: 32px;
}
> div:nth-of-type(2) {
color: #8D0503;
color: #8d0503;
height: 40px;
font-size: 30px;
line-height: 40px;
@@ -347,7 +493,7 @@ export default {
}
> div:nth-of-type(2) {
color: #8D0503;
color: #8d0503;
width: 100%;
height: 30px;
font-size: 30px;
@@ -365,7 +511,7 @@ export default {
border-radius: 16px;
padding: 105px 24px 0;
box-sizing: border-box;
border: 2px solid #FFC548;
border: 2px solid #ffc548;
background: linear-gradient(180deg, rgba(225, 225, 225, 0) 0%, rgba(225, 225, 225, 0.3) 100%);
.staff {
@@ -379,12 +525,12 @@ export default {
flex-wrap: nowrap;
padding: 0 30px;
box-sizing: border-box;
margin-bottom: 60px;
margin-bottom: 40px;
border-radius: 16px;
background-color: #7E0B08;
background-color: #7e0b08;
> div {
color: #FFC87C;
color: #ffc87c;
height: 82px;
font-size: 32px;
font-weight: bold;
@@ -414,20 +560,24 @@ export default {
.staffContent {
width: 100%;
overflow-x: hidden;
overflow-y: scroll;
height: calc(100% - 142px);
overflow: hidden;
height: calc(100% - 152px); // 根据实际高度调整
.staff-list-wrapper {
width: 100%;
}
.staffContentList {
width: 100%;
height: 40px;
padding: 0 30px;
box-sizing: border-box;
display: flex;
flex-wrap: nowrap;
margin-bottom: 56px;
margin-bottom: 56px; // 调整行间距,确保和 singleHeight 匹配
> div {
color: #FFFFFF;
color: #ffffff;
height: 40px;
font-size: 40px;
line-height: 40px;
@@ -478,10 +628,10 @@ export default {
box-sizing: border-box;
margin-bottom: 60px;
border-radius: 16px;
background-color: #7E0B08;
background-color: #7e0b08;
> div {
color: #FFC87C;
color: #ffc87c;
height: 82px;
font-size: 32px;
font-weight: bold;
@@ -518,7 +668,7 @@ export default {
margin-bottom: 106px;
> div {
color: #FFFFFF;
color: #ffffff;
height: 40px;
font-size: 40px;
line-height: 40px;
@@ -553,7 +703,7 @@ export default {
}
.footer {
color: #FFFFFF;
color: #ffffff;
width: 100%;
height: 95px;
font-size: 36px;
@@ -579,7 +729,7 @@ export default {
}
> div:nth-of-type(2) {
color: #FFC87C;
color: #ffc87c;
width: 100%;
height: 40px;
font-size: 40px;