滚动更新
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user