小优化,测试了一晚上,没有出现卡死的情况

This commit is contained in:
17792275749
2025-06-11 08:34:54 +08:00
parent db94e5fee1
commit 3c33aa92d9
@@ -135,14 +135,17 @@ export default {
currentData: "",
doingDay: "-",
list: [],
timerId: null, // 用于控制页面切换或重试的定时器ID
// 明确区分不同用途的定时器ID
pageChangeTimerId: null, // 用于页面切换或重试的定时器
scrollDelayTimerId: null, // 用于滚动前停顿的定时器
scrollFallbackTimerId: null, // 滚动动画的备用定时器
isRequesting: false, // 防止重复请求
isScrolling: false, // 标志是否正在进行滚动动画
scrollPosition: 0, // 滚动位置
scrollAnimationDuration: 0, // 滚动动画持续时间
initialDelayTimer: null, // 用于滚动前停顿的定时器ID
isPageVisible: true, // 页面是否可见
@@ -168,7 +171,7 @@ export default {
document.addEventListener("visibilitychange", _this.handleVisibilityChange);
// 组件创建后,首次启动流程时增加一个小的延迟,确保 DOM 结构基本准备好
_this.timerId = setTimeout(() => {
_this.pageChangeTimerId = setTimeout(() => {
_this.startSequentialLoading();
}, _this.setTimeoutParams.initialRenderDelay);
},
@@ -256,21 +259,30 @@ export default {
*/
clearAllTimers: function() {
const _this = this;
if (_this.timerId) {
clearTimeout(_this.timerId);
_this.timerId = null;
if (_this.pageChangeTimerId) {
clearTimeout(_this.pageChangeTimerId);
_this.pageChangeTimerId = null;
_this.callAndroidPostLog("清除 pageChangeTimerId。");
}
if (_this.initialDelayTimer) {
clearTimeout(_this.initialDelayTimer);
_this.initialDelayTimer = null;
if (_this.scrollDelayTimerId) {
clearTimeout(_this.scrollDelayTimerId);
_this.scrollDelayTimerId = null;
_this.callAndroidPostLog("清除 scrollDelayTimerId。");
}
_this.isScrolling = false;
if (_this.scrollFallbackTimerId) {
clearTimeout(_this.scrollFallbackTimerId);
_this.scrollFallbackTimerId = null;
_this.callAndroidPostLog("清除 scrollFallbackTimerId。");
}
_this.isScrolling = false; // 确保滚动状态被重置
const listWrapperEl = _this.$refs.staffList;
if (listWrapperEl && _this.onTransitionEndHandlerRef) {
listWrapperEl.removeEventListener("transitionend", _this.onTransitionEndHandlerRef);
_this.onTransitionEndHandlerRef = null; // 清除引用
_this.callAndroidPostLog("移除 transitionend 监听器。");
}
_this.callAndroidPostLog("所有定时器和相关状态已清除。");
},
/**
@@ -278,12 +290,14 @@ export default {
*/
pageTimerCallback: function () {
const _this = this;
_this.callAndroidPostLog("pageTimerCallback 被调用。");
if (!_this.isPageVisible) {
_this.callAndroidPostLog("页面不可见,暂停 pageTimerCallback。");
_this.callAndroidPostLog("页面不可见,暂停 pageTimerCallback 内部执行。");
return;
}
_this.refRetryCount = 0; // 每次切换页面或加载数据前重置重试计数
_this.personnelType = _this.personnelType === "department" ? "staff" : "department";
_this.callAndroidPostLog(`切换到模式:${_this.personnelType}`);
_this.startSequentialLoading();
},
@@ -293,8 +307,9 @@ export default {
*/
startSequentialLoading: function () {
const _this = this;
_this.callAndroidPostLog("startSequentialLoading 被调用。");
if (!_this.isPageVisible) {
_this.callAndroidPostLog("页面不可见,暂停 startSequentialLoading。");
_this.callAndroidPostLog("页面不可见,暂停 startSequentialLoading 内部执行。");
return;
}
@@ -306,7 +321,7 @@ export default {
} else {
_this.pageSize = 10;
}
_this.callAndroidPostLog(`设置 pageSize: ${_this.pageSize}`);
_this.loadDataSequential();
},
@@ -315,34 +330,36 @@ export default {
*/
loadDataSequential: function () {
const _this = this;
// **保留 urlPrefix 变量**
let urlPrefix = "/health-intervene/api/anon";
let apiUrl = "";
_this.callAndroidPostLog("loadDataSequential 被调用。");
if (!_this.isPageVisible) {
_this.callAndroidPostLog("页面不可见,暂停 loadDataSequential。");
_this.callAndroidPostLog("页面不可见,暂停 loadDataSequential 内部执行。");
return;
}
if (_this.isRequesting) {
_this.callAndroidPostLog('请求正在进行中,跳过本次调用。');
_this.callAndroidPostLog('请求正在进行中,跳过本次 loadDataSequential 调用。');
return;
}
_this.isRequesting = true;
_this.clearAllTimers(); // 在发送请求前再次清除定时器
_this.clearAllTimers(); // 在发送请求前再次清除定时器,以防万一
// **使用 urlPrefix 构建 apiUrl**
apiUrl = urlPrefix + (_this.personnelType === "staff" ? "/userRank" : "/deptRank");
let requestUrl = _this.requestConfig.agreement + "://" + _this.requestConfig.url + apiUrl + "?orgCode=" + _this.requestConfig.orgCode + "&pageNo=1&pageSize=" + _this.pageSize;
_this.callAndroidPostLog(`发起请求:${requestUrl}`);
httpRequest("POST", requestUrl, {}, {})
.then(
(res) => {
_this.callAndroidPostLog(`请求返回:${JSON.stringify(res)}`);
if (res.success && res.result && res.result.rankList) {
if (_this.personnelType === "staff") {
_this.doingDay = res.result.doingDay === null ? "-" : res.result.doingDay;
}
_this.list = res.result.rankList; // 更新列表数据
_this.callAndroidPostLog(`数据加载成功,列表长度:${_this.list.length}`);
_this.$nextTick(() => {
// 只有在 staff 模式且有数据时才尝试滚动
@@ -351,7 +368,8 @@ export default {
_this.startSingleScroll();
} else {
// 部门模式或员工无数据,直接设置页面切换定时器
_this.timerId = setTimeout(
_this.callAndroidPostLog("无需滚动或无数据,设置页面切换定时器。");
_this.pageChangeTimerId = setTimeout(
_this.pageTimerCallback,
_this.setTimeoutParams.times * 1000
);
@@ -360,7 +378,7 @@ export default {
} else {
_this.list = []; // 数据不成功或无数据时清空列表
_this.callAndroidPostLog("请求成功但数据无效或为空,将在 " + _this.setTimeoutParams.retryDelay + " 秒后重试/切换。");
_this.timerId = setTimeout(
_this.pageChangeTimerId = setTimeout(
_this.pageTimerCallback,
_this.setTimeoutParams.retryDelay * 1000
);
@@ -372,7 +390,7 @@ export default {
_this.callAndroidPostLog("请求失败: " + JSON.stringify(error));
_this.list = []; // 请求失败时清空列表
_this.callAndroidPostLog('请求失败,将在 ' + _this.setTimeoutParams.retryDelay + ' 秒后重试/切换。');
_this.timerId = setTimeout(
_this.pageChangeTimerId = setTimeout(
_this.pageTimerCallback,
_this.setTimeoutParams.retryDelay * 1000
);
@@ -381,6 +399,7 @@ export default {
.finally(
() => {
_this.isRequesting = false;
_this.callAndroidPostLog("请求结束,isRequesting 设置为 false。");
}
);
},
@@ -394,9 +413,11 @@ export default {
const contentEl = _this.$refs.staffContent; // 滚动容器
const listWrapperEl = _this.$refs.staffList; // 实际滚动的元素
_this.callAndroidPostLog("startSingleScroll 被调用。");
if (!_this.isPageVisible) {
_this.callAndroidPostLog("页面不可见,暂停 startSingleScroll 内部执行。");
_this.timerId = setTimeout(_this.pageTimerCallback, _this.setTimeoutParams.retryDelay * 1000);
_this.callAndroidPostLog("页面不可见,暂停 startSingleScroll 内部执行,直接切换页面。");
_this.pageChangeTimerId = setTimeout(_this.pageTimerCallback, _this.setTimeoutParams.retryDelay * 1000);
return;
}
@@ -407,28 +428,25 @@ export default {
if (_this.refRetryCount < _this.setTimeoutParams.maxRefRetry) {
_this.refRetryCount++;
_this.callAndroidPostLog(`尝试获取refs,重试次数:${_this.refRetryCount}/${_this.setTimeoutParams.maxRefRetry}。将在 ${_this.setTimeoutParams.refRetryInterval}ms 后重试。`);
_this.initialDelayTimer = setTimeout(() => {
_this.scrollDelayTimerId = setTimeout(() => { // 使用 scrollDelayTimerId
_this.startSingleScroll(); // 再次尝试启动滚动
}, _this.setTimeoutParams.refRetryInterval);
return; // 退出当前函数,等待重试
} else {
_this.callAndroidPostLog("致命错误:滚动元素staffContent或staffList多次尝试后仍未就绪或尺寸为0,放弃滚动。将切换页面。");
// 此时无法滚动,直接切换页面
_this.timerId = setTimeout(_this.pageTimerCallback, _this.setTimeoutParams.retryDelay * 1000);
_this.pageChangeTimerId = setTimeout(_this.pageTimerCallback, _this.setTimeoutParams.retryDelay * 1000);
return;
}
}
if (_this.isScrolling) {
_this.callAndroidPostLog("滚动正在进行中,跳过本次启动。");
_this.callAndroidPostLog("滚动正在进行中,跳过本次启动 startSingleScroll。");
return;
}
// 在每次启动滚动前,确保 CSS 动画属性被清除,防止旧动画残留
listWrapperEl.style.transition = "none";
_this.scrollPosition = 0; // 重置位置到顶部
listWrapperEl.style.transform = `translateY(${_this.scrollPosition}px)`;
void listWrapperEl.offsetWidth; // 强制浏览器重绘
// **核心优化:在开始动画前,确保清理旧的动画状态和监听器**
_this.clearScrollAnimationState(listWrapperEl);
let contentHeight = contentEl.clientHeight; // 容器的可见高度
let actualListHeight = 0;
@@ -438,11 +456,21 @@ export default {
let firstItem = listWrapperEl.children[0];
let itemHeight = firstItem.offsetHeight;
let itemMarginBottom = parseFloat(window.getComputedStyle(firstItem).marginBottom);
actualListHeight = (itemHeight + itemMarginBottom) * listWrapperEl.children.length;
// 计算实际总高度(包括所有列表项和它们之间的 margin-bottom
actualListHeight = 0;
for(let i=0; i<listWrapperEl.children.length; i++){
actualListHeight += listWrapperEl.children[i].offsetHeight + parseFloat(window.getComputedStyle(listWrapperEl.children[i]).marginBottom);
}
// 减去最后一个元素的 margin-bottom因为它下面没有其他元素
if (listWrapperEl.children.length > 0) {
actualListHeight -= parseFloat(window.getComputedStyle(listWrapperEl.children[listWrapperEl.children.length - 1]).marginBottom);
}
_this.callAndroidPostLog(`容器高度: ${contentHeight}px, 实际列表总高度: ${actualListHeight}px。`);
} else {
_this.callAndroidPostLog("staffList内部无子元素,无法计算滚动高度。将切换页面。");
// 无列表项时也直接切换页面
_this.timerId = setTimeout(_this.pageTimerCallback, _this.setTimeoutParams.times * 1000);
_this.pageChangeTimerId = setTimeout(_this.pageTimerCallback, _this.setTimeoutParams.times * 1000);
return;
}
@@ -451,7 +479,7 @@ export default {
_this.scrollAnimationDuration = 0;
_this.scrollPosition = 0;
_this.callAndroidPostLog("列表高度 (" + actualListHeight + "px) 小于或等于容器高度 (" + contentHeight + "px),无需滚动。将切换页面。");
_this.timerId = setTimeout(_this.pageTimerCallback, _this.setTimeoutParams.times * 1000);
_this.pageChangeTimerId = setTimeout(_this.pageTimerCallback, _this.setTimeoutParams.times * 1000);
return;
}
@@ -459,10 +487,12 @@ export default {
let scrollDistance = actualListHeight - contentHeight;
let scrollSpeed = 40; // 像素/秒
_this.scrollAnimationDuration = scrollDistance / scrollSpeed;
_this.callAndroidPostLog(`计算滚动距离:${scrollDistance}px,动画时长:${_this.scrollAnimationDuration}s。`);
// --- 滚动前停顿 ---
_this.initialDelayTimer = setTimeout(
_this.scrollDelayTimerId = setTimeout( // 使用 scrollDelayTimerId
() => {
_this.callAndroidPostLog("滚动前停顿结束,准备执行动画。");
if (window.requestAnimationFrame) {
window.requestAnimationFrame(() => {
_this.performScrollAnimation(listWrapperEl, scrollDistance);
@@ -475,65 +505,112 @@ export default {
);
},
/**
* 清理滚动动画相关的CSS属性和事件监听器
*/
clearScrollAnimationState: function(listWrapperEl) {
const _this = this;
// 移除任何旧的 transitionend 监听器,防止重复绑定
if (_this.onTransitionEndHandlerRef) {
listWrapperEl.removeEventListener("transitionend", _this.onTransitionEndHandlerRef);
_this.onTransitionEndHandlerRef = null;
_this.callAndroidPostLog("已在动画开始前移除旧的 transitionend 监听器。");
}
// 清除之前的动画效果,并重置位置
listWrapperEl.style.transition = "none";
_this.scrollPosition = 0; // 重置位置到顶部
listWrapperEl.style.transform = `translateY(${_this.scrollPosition}px)`;
// 强制浏览器重绘/回流,确保 'transition: none' 立即生效,防止动画闪烁
void listWrapperEl.offsetWidth;
_this.callAndroidPostLog("已清除旧动画状态并重置滚动位置。");
},
// 统一处理 transitionend 事件,方便移除监听器
onTransitionEnd: function(event) {
const _this = this;
const listWrapperEl = _this.$refs.staffList;
_this.callAndroidPostLog("onTransitionEnd triggered.");
// 检查事件是否来自正确的元素和正确的属性,避免其他transition影响
if (event.target === listWrapperEl && event.propertyName === 'transform') {
listWrapperEl.removeEventListener("transitionend", _this.onTransitionEndHandlerRef);
_this.onTransitionEndHandlerRef = null; // 清除引用
_this.callAndroidPostLog("滚动动画完成,清除 scrollFallbackTimerId。");
clearTimeout(_this.scrollFallbackTimerId); // 动画正常完成,清除备用定时器
_this.scrollFallbackTimerId = null;
_this.isScrolling = false;
_this.callAndroidPostLog("滚动动画完成。");
// --- 滚动结束后,设置页面切换定时器 ---
_this.timerId = setTimeout(
_this.pageChangeTimerId = setTimeout( // 使用 pageChangeTimerId
_this.pageTimerCallback,
_this.setTimeoutParams.scrollEndDelay * 1000
);
_this.callAndroidPostLog(`设置 pageChangeTimerId,将在 ${_this.setTimeoutParams.scrollEndDelay} 秒后调用 pageTimerCallback。`);
// 动画完成后,再次确保移除监听器,防止重复触发
if (_this.onTransitionEndHandlerRef) {
listWrapperEl.removeEventListener("transitionend", _this.onTransitionEndHandlerRef);
_this.onTransitionEndHandlerRef = null;
_this.callAndroidPostLog("移除 transitionend 监听器。");
}
} else {
_this.callAndroidPostLog(`onTransitionEnd triggered by unexpected target or property: ${event.target.tagName}, ${event.propertyName}`);
}
},
performScrollAnimation: function (listWrapperEl, scrollDistance) {
const _this = this;
_this.callAndroidPostLog("performScrollAnimation 被调用。");
// 移除任何旧的监听器,防止重复绑定
if (_this.onTransitionEndHandlerRef) {
listWrapperEl.removeEventListener("transitionend", _this.onTransitionEndHandlerRef);
_this.onTransitionEndHandlerRef = null;
}
// 再次确保在设置新监听器前移除旧的
_this.clearScrollAnimationState(listWrapperEl);
_this.onTransitionEndHandlerRef = _this.onTransitionEnd.bind(_this);
listWrapperEl.addEventListener("transitionend", _this.onTransitionEndHandlerRef);
_this.callAndroidPostLog("添加 transitionend 监听器。");
// 设置目标位置(负值表示向上滚动)
_this.scrollPosition = -scrollDistance;
listWrapperEl.style.transition = `transform ${_this.scrollAnimationDuration}s linear`;
listWrapperEl.style.transform = `translateY(${_this.scrollPosition}px)`; // 立即应用样式
_this.callAndroidPostLog(`开始滚动动画。距离${scrollDistance}px,时长:${_this.scrollAnimationDuration}s`);
_this.callAndroidPostLog(`开始滚动动画。目标位置${_this.scrollPosition}px,时长:${_this.scrollAnimationDuration}s`);
// 备用定时器,确保即使 transitionend 未触发也能继续流程
_this.timerId = setTimeout(
// **优化:更可靠的备用定时器,在动画预期结束时间后触发**
// 这个定时器会在动画正常完成(`transitionend` 触发并清除它)或动画未完成时触发
_this.scrollFallbackTimerId = setTimeout(
() => {
if (_this.isScrolling) {
_this.callAndroidPostLog("TransitionEnd 未触发,使用备用定时器强制页面切换。");
_this.isScrolling = false;
listWrapperEl.style.transition = "none";
listWrapperEl.style.transform = "translateY(0px)";
void listWrapperEl.offsetWidth; // 强制重绘
if (_this.onTransitionEndHandlerRef) {
listWrapperEl.removeEventListener("transitionend", _this.onTransitionEndHandlerRef);
_this.onTransitionEndHandlerRef = null;
}
_this.pageTimerCallback();
}
_this.callAndroidPostLog("备用定时器触发!TransitionEnd 可能未正常触发或动画完成但流程卡顿。强制进入下一流程。");
// 即使动画被中断或未触发 transitionend,也强制执行清理和切换逻辑
_this.forceNextStepAfterScroll(listWrapperEl);
},
(_this.scrollAnimationDuration + _this.setTimeoutParams.scrollEndDelay) * 1000 + 500 // 动画持续时间 + 动画结束后等待时间 + 0.5秒缓冲
);
_this.callAndroidPostLog(`设置 scrollFallbackTimerId,将在 ${(_this.scrollAnimationDuration + _this.setTimeoutParams.scrollEndDelay + 0.5)} 秒后触发。`);
},
/**
* 强制进入滚动动画后的下一个流程
* 用于 transitionend 未触发时的备用方案
*/
forceNextStepAfterScroll: function(listWrapperEl) {
const _this = this;
_this.callAndroidPostLog("forceNextStepAfterScroll 被调用。");
// 清理所有相关定时器,防止多重触发
_this.clearAllTimers();
// 强制停止动画并归位
listWrapperEl.style.transition = "none";
listWrapperEl.style.transform = "translateY(0px)";
void listWrapperEl.offsetWidth; // 强制重绘
_this.callAndroidPostLog("强制停止动画并重置位置。");
// 直接调用页面切换逻辑,避免卡住
_this.pageTimerCallback();
}
},
beforeDestroy: function () {
const _this = this;
_this.callAndroidPostLog("组件即将销毁,执行 beforeDestroy。");
_this.clearAllTimers(); // 清除所有定时器
// 确保在组件销毁时,正在进行的 CSS 动画被停止并归位
@@ -541,11 +618,13 @@ export default {
if (listWrapperEl) {
listWrapperEl.style.transition = "none";
listWrapperEl.style.transform = "translateY(0px)";
void listWrapperEl.offsetWidth;
void listWrapperEl.offsetWidth; // 强制重绘
_this.callAndroidPostLog("组件销毁时,列表动画已停止并归位。");
}
// 移除页面可见性监听器
document.removeEventListener("visibilitychange", _this.handleVisibilityChange);
_this.callAndroidPostLog("已移除页面可见性监听器。");
// 断开 MutationObserver
if (_this.mutationObserver) {
@@ -553,6 +632,7 @@ export default {
_this.mutationObserver = null;
_this.callAndroidPostLog("MutationObserver 已断开。");
}
_this.callAndroidPostLog("beforeDestroy 完成。");
},
};
</script>
@@ -567,10 +647,10 @@ export default {
flex-direction: column;
padding: 44px 40px 0;
box-sizing: border-box;
//background-size: cover;
//background-repeat: no-repeat;
//background-position: center top;
//background-image: url("@/assets/images/dietaryNutritionTips/bg.png");
background-size: cover;
background-repeat: no-repeat;
background-position: center top;
background-image: url("@/assets/images/dietaryNutritionTips/bg.png");
.tips {
width: 100%;