From 3c33aa92d9b5257b6652595f84913ccae207ec7c Mon Sep 17 00:00:00 2001 From: 17792275749 <812100002@qq.com> Date: Wed, 11 Jun 2025 08:34:54 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=8F=E4=BC=98=E5=8C=96=EF=BC=8C=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E4=BA=86=E4=B8=80=E6=99=9A=E4=B8=8A=EF=BC=8C=E6=B2=A1?= =?UTF-8?q?=E6=9C=89=E5=87=BA=E7=8E=B0=E5=8D=A1=E6=AD=BB=E7=9A=84=E6=83=85?= =?UTF-8?q?=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dietaryNutritionTips2.vue | 206 ++++++++++++------ 1 file changed, 143 insertions(+), 63 deletions(-) diff --git a/src/views/dietaryNutritionTips/dietaryNutritionTips2.vue b/src/views/dietaryNutritionTips/dietaryNutritionTips2.vue index 512cfcf..3a98b99 100644 --- a/src/views/dietaryNutritionTips/dietaryNutritionTips2.vue +++ b/src/views/dietaryNutritionTips/dietaryNutritionTips2.vue @@ -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 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 完成。"); }, }; @@ -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%;