最新改版
This commit is contained in:
@@ -13,7 +13,9 @@
|
||||
<div>体重管理行动(员工)</div>
|
||||
<div>
|
||||
<div>{{ currentData }}</div>
|
||||
<div>活动开始第<span>{{ doingDay }}</span>天</div>
|
||||
<div>
|
||||
活动开始第<span>{{ doingDay }}</span>天
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div></div>
|
||||
@@ -29,26 +31,27 @@
|
||||
</div>
|
||||
<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>
|
||||
transition: scrollAnimationDuration > 0 ? `transform ${scrollAnimationDuration}s linear` : 'none',
|
||||
}"
|
||||
>
|
||||
<template v-if="list && list.length">
|
||||
<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>
|
||||
</template>
|
||||
</div>
|
||||
<template v-else>
|
||||
<template v-if="!list || list.length === 0">
|
||||
<div class="nullData">
|
||||
<div></div>
|
||||
<div>~ 暂无体重管理信息 ~</div>
|
||||
@@ -59,7 +62,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<template v-if="personnelType === 'department'">
|
||||
<div class="title departmentTitle">
|
||||
<div></div>
|
||||
@@ -108,11 +110,17 @@ import { getCurrentDate } from "@/utils/times";
|
||||
|
||||
export default {
|
||||
name: "dietaryNutritionTips",
|
||||
data: function () { // 箭头函数转为普通函数
|
||||
data: function () {
|
||||
return {
|
||||
setTimeoutParams: {
|
||||
status: true,
|
||||
times: 15, // 页面切换的间隔时间,单位:秒
|
||||
retryDelay: 5, // 错误或无数据时重试/切换的延迟时间,单位:秒
|
||||
scrollDelay: 2, // 滚动前停顿时间,单位:秒
|
||||
scrollEndDelay: 2, // 滚动结束后等待时间,单位:秒
|
||||
initialRenderDelay: 100, // 首次渲染延迟,确保 DOM 准备好
|
||||
maxRefRetry: 10, // 获取 ref 的最大重试次数
|
||||
refRetryInterval: 100, // 获取 ref 的重试间隔,单位:毫秒
|
||||
},
|
||||
|
||||
requestConfig: {
|
||||
@@ -127,232 +135,430 @@ export default {
|
||||
currentData: "",
|
||||
doingDay: "-",
|
||||
list: [],
|
||||
timerId: null, // 用于控制页面切换的定时器ID
|
||||
timerId: null, // 用于控制页面切换或重试的定时器ID
|
||||
|
||||
isRequesting: false, // 防止重复请求
|
||||
isScrolling: false, // 标志是否正在进行滚动动画
|
||||
|
||||
// 新增用于单次滚动的状态
|
||||
scrollPosition: 0, // 滚动位置
|
||||
scrollAnimationDuration: 0, // 滚动动画持续时间
|
||||
isScrolling: false, // 标志是否正在进行滚动动画
|
||||
initialDelayTimer: null, // 用于滚动前停顿的定时器ID
|
||||
|
||||
isPageVisible: true, // 页面是否可见
|
||||
|
||||
onTransitionEndHandlerRef: null, // 存储 transitionend 事件处理函数的引用
|
||||
refRetryCount: 0, // 记录 $refs 获取的重试次数
|
||||
mutationObserver: null, // MutationObserver 实例
|
||||
};
|
||||
},
|
||||
created: function () { // 箭头函数转为普通函数
|
||||
let url = this.$route.query.url;
|
||||
created: function () {
|
||||
const _this = this;
|
||||
|
||||
let url = _this.$route.query.url;
|
||||
if (url) {
|
||||
this.requestConfig.url = url;
|
||||
_this.requestConfig.url = url;
|
||||
}
|
||||
let code = this.$route.query.orgCode;
|
||||
let code = _this.$route.query.orgCode;
|
||||
if (code) {
|
||||
this.requestConfig.orgCode = code;
|
||||
_this.requestConfig.orgCode = code;
|
||||
}
|
||||
this.currentData = getCurrentDate();
|
||||
this.startSequentialLoading();
|
||||
_this.currentData = getCurrentDate();
|
||||
|
||||
// 绑定页面可见性监听器
|
||||
document.addEventListener("visibilitychange", _this.handleVisibilityChange);
|
||||
|
||||
// 组件创建后,首次启动流程时增加一个小的延迟,确保 DOM 结构基本准备好
|
||||
_this.timerId = setTimeout(() => {
|
||||
_this.startSequentialLoading();
|
||||
}, _this.setTimeoutParams.initialRenderDelay);
|
||||
},
|
||||
mounted() {
|
||||
// 在 mounted 钩子中初始化 MutationObserver
|
||||
this.setupMutationObserver();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 调用 Android 端的 postLog 方法,用于调试日志输出
|
||||
*/
|
||||
callAndroidPostLog(content) {
|
||||
// Android 接口检查
|
||||
if (typeof window.Android !== 'undefined' && window.Android.postLog) {
|
||||
try {
|
||||
window.Android.postLog(content);
|
||||
} catch (e) {
|
||||
console.error("调用 Android.postLog 失败:", e);
|
||||
}
|
||||
} else {
|
||||
console.log("H5 Log (Android Interface Not Available):", content);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置 MutationObserver 来监控关键元素的 DOM 变化
|
||||
*/
|
||||
setupMutationObserver: function() {
|
||||
const _this = this;
|
||||
// 确保只设置一次
|
||||
if (_this.mutationObserver) return;
|
||||
|
||||
const targetNode = _this.$el; // 监控组件的根元素
|
||||
const config = { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] };
|
||||
|
||||
_this.mutationObserver = new MutationObserver((mutationsList) => {
|
||||
for (let mutation of mutationsList) {
|
||||
if (mutation.type === 'childList' || mutation.type === 'attributes') {
|
||||
// 当 `staffContent` 或 `staffList` 相关的 DOM 发生变化时,尝试重新评估是否需要滚动
|
||||
// 这里只是一个简单的触发机制,实际的滚动逻辑在 startSingleScroll 中处理
|
||||
if (_this.personnelType === 'staff' && _this.isPageVisible && !_this.isScrolling && _this.list.length > 0) {
|
||||
// 避免过于频繁的触发,仅在当前没有滚动且有数据时才检查
|
||||
// 实际的滚动启动在 loadDataSequential 的 nextTick 中
|
||||
// 此处只做辅助性监控,若发现异常再做处理
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (targetNode) {
|
||||
_this.mutationObserver.observe(targetNode, config);
|
||||
_this.callAndroidPostLog("MutationObserver 已设置在组件根元素上。");
|
||||
} else {
|
||||
_this.callAndroidPostLog("警告:无法获取组件根元素设置 MutationObserver。");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面可见性处理
|
||||
*/
|
||||
handleVisibilityChange: function () {
|
||||
const _this = this;
|
||||
const listWrapperEl = _this.$refs.staffList;
|
||||
|
||||
if (document.hidden) {
|
||||
_this.isPageVisible = false;
|
||||
_this.callAndroidPostLog("页面不可见,暂停所有流程。");
|
||||
_this.clearAllTimers(); // 统一清除所有定时器和动画状态
|
||||
if (listWrapperEl) {
|
||||
listWrapperEl.style.transition = "none";
|
||||
listWrapperEl.style.transform = "translateY(0px)";
|
||||
void listWrapperEl.offsetWidth; // 强制浏览器重绘
|
||||
}
|
||||
} else {
|
||||
_this.isPageVisible = true;
|
||||
_this.callAndroidPostLog("页面可见,恢复所有流程。");
|
||||
// 页面恢复可见时,重置重试计数并重新开始加载流程
|
||||
_this.refRetryCount = 0;
|
||||
_this.startSequentialLoading();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 统一清除所有定时器和滚动状态
|
||||
*/
|
||||
clearAllTimers: function() {
|
||||
const _this = this;
|
||||
if (_this.timerId) {
|
||||
clearTimeout(_this.timerId);
|
||||
_this.timerId = null;
|
||||
}
|
||||
if (_this.initialDelayTimer) {
|
||||
clearTimeout(_this.initialDelayTimer);
|
||||
_this.initialDelayTimer = null;
|
||||
}
|
||||
_this.isScrolling = false;
|
||||
|
||||
const listWrapperEl = _this.$refs.staffList;
|
||||
if (listWrapperEl && _this.onTransitionEndHandlerRef) {
|
||||
listWrapperEl.removeEventListener("transitionend", _this.onTransitionEndHandlerRef);
|
||||
_this.onTransitionEndHandlerRef = null; // 清除引用
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面切换回调函数,用于在员工和部门模式之间切换。
|
||||
*/
|
||||
pageTimerCallback: function () { // 箭头函数转为普通函数
|
||||
// 切换人员类型,并重新启动加载和滚动流程
|
||||
this.personnelType = this.personnelType === "department" ? "staff" : "department";
|
||||
this.startSequentialLoading();
|
||||
pageTimerCallback: function () {
|
||||
const _this = this;
|
||||
if (!_this.isPageVisible) {
|
||||
_this.callAndroidPostLog("页面不可见,暂停 pageTimerCallback。");
|
||||
return;
|
||||
}
|
||||
_this.refRetryCount = 0; // 每次切换页面或加载数据前重置重试计数
|
||||
_this.personnelType = _this.personnelType === "department" ? "staff" : "department";
|
||||
_this.startSequentialLoading();
|
||||
},
|
||||
|
||||
/**
|
||||
* 启动或重置顺序加载流程。
|
||||
* 根据当前人员类型设置 pageSize,并调用数据加载方法。
|
||||
*/
|
||||
startSequentialLoading: function () { // 箭头函数转为普通函数
|
||||
this.list = []; // 清空列表,准备加载新数据
|
||||
if (this.personnelType === "staff") {
|
||||
this.pageSize = 45;
|
||||
} else {
|
||||
this.pageSize = 10;
|
||||
startSequentialLoading: function () {
|
||||
const _this = this;
|
||||
if (!_this.isPageVisible) {
|
||||
_this.callAndroidPostLog("页面不可见,暂停 startSequentialLoading。");
|
||||
return;
|
||||
}
|
||||
this.loadDataSequential();
|
||||
|
||||
_this.clearAllTimers(); // 确保在启动新流程前清除所有旧的定时器
|
||||
|
||||
_this.list = []; // 清空列表,准备加载新数据
|
||||
if (_this.personnelType === "staff") {
|
||||
_this.pageSize = 45;
|
||||
} else {
|
||||
_this.pageSize = 10;
|
||||
}
|
||||
|
||||
_this.loadDataSequential();
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载数据并根据人员类型处理滚动或定时器。
|
||||
*/
|
||||
loadDataSequential: function () { // 箭头函数转为普通函数
|
||||
let this_ = this; // 缓存 this 指向
|
||||
loadDataSequential: function () {
|
||||
const _this = this;
|
||||
// **保留 urlPrefix 变量**
|
||||
let urlPrefix = "/health-intervene/api/anon";
|
||||
let apiUrl = "";
|
||||
|
||||
// 根据人员类型构建 API URL
|
||||
if (this_.personnelType === "staff") {
|
||||
apiUrl = urlPrefix + "/userRank";
|
||||
} else {
|
||||
apiUrl = urlPrefix + "/deptRank";
|
||||
}
|
||||
|
||||
let url = this_.requestConfig.agreement + "://" + this_.requestConfig.url + apiUrl + "?orgCode=" + this_.requestConfig.orgCode + "&pageNo=1&pageSize=" + this_.pageSize;
|
||||
|
||||
// 防止重复请求
|
||||
if (this_.isRequesting) {
|
||||
console.warn('请求正在进行中,跳过本次调用。');
|
||||
if (!_this.isPageVisible) {
|
||||
_this.callAndroidPostLog("页面不可见,暂停 loadDataSequential。");
|
||||
return;
|
||||
}
|
||||
this_.isRequesting = true; // 设置请求状态为进行中
|
||||
|
||||
// 发送 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;
|
||||
if (_this.isRequesting) {
|
||||
_this.callAndroidPostLog('请求正在进行中,跳过本次调用。');
|
||||
return;
|
||||
}
|
||||
_this.isRequesting = true;
|
||||
_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;
|
||||
|
||||
httpRequest("POST", requestUrl, {}, {})
|
||||
.then(
|
||||
(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.$nextTick(() => {
|
||||
// 只有在 staff 模式且有数据时才尝试滚动
|
||||
if (_this.personnelType === "staff" && _this.list.length > 0) {
|
||||
_this.refRetryCount = 0; // 每次尝试滚动前重置重试计数
|
||||
_this.startSingleScroll();
|
||||
} else {
|
||||
// 部门模式或员工无数据,直接设置页面切换定时器
|
||||
_this.timerId = setTimeout(
|
||||
_this.pageTimerCallback,
|
||||
_this.setTimeoutParams.times * 1000
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
_this.list = []; // 数据不成功或无数据时清空列表
|
||||
_this.callAndroidPostLog("请求成功但数据无效或为空,将在 " + _this.setTimeoutParams.retryDelay + " 秒后重试/切换。");
|
||||
_this.timerId = setTimeout(
|
||||
_this.pageTimerCallback,
|
||||
_this.setTimeoutParams.retryDelay * 1000
|
||||
);
|
||||
}
|
||||
}
|
||||
this_.list = res.result.rankList; // 更新列表数据
|
||||
} else {
|
||||
this_.list = []; // 数据不成功或无数据时清空列表
|
||||
}
|
||||
}).catch(function (error) { // 回调函数使用普通函数
|
||||
console.error("请求失败:", error);
|
||||
this_.list = []; // 请求失败时清空列表
|
||||
}).finally(function () { // 回调函数使用普通函数
|
||||
this_.isRequesting = false; // 请求完成,重置请求状态
|
||||
|
||||
// 清除可能存在的页面切换定时器
|
||||
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);
|
||||
)
|
||||
.catch(
|
||||
(error) => {
|
||||
_this.callAndroidPostLog("请求失败: " + JSON.stringify(error));
|
||||
_this.list = []; // 请求失败时清空列表
|
||||
_this.callAndroidPostLog('请求失败,将在 ' + _this.setTimeoutParams.retryDelay + ' 秒后重试/切换。');
|
||||
_this.timerId = setTimeout(
|
||||
_this.pageTimerCallback,
|
||||
_this.setTimeoutParams.retryDelay * 1000
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
)
|
||||
.finally(
|
||||
() => {
|
||||
_this.isRequesting = false;
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* 开始员工列表的单次滚动动画。
|
||||
* 包含滚动前停顿、精确高度计算、动画重置和动画结束回调。
|
||||
* 确保滚动容器和列表元素已就绪。
|
||||
*/
|
||||
startSingleScroll: function () { // 箭头函数转为普通函数
|
||||
let contentEl = this.$refs.staffContent; // 滚动容器 (staffContent)
|
||||
let listWrapperEl = this.$refs.staffList; // 实际滚动的元素 (staff-list-wrapper)
|
||||
let this_ = this; // 缓存 this 指向,在 setTimeout 和 requestAnimationFrame 回调中使用
|
||||
startSingleScroll: function () {
|
||||
const _this = this;
|
||||
const contentEl = _this.$refs.staffContent; // 滚动容器
|
||||
const listWrapperEl = _this.$refs.staffList; // 实际滚动的元素
|
||||
|
||||
// 如果元素不存在或正在滚动中,则不执行
|
||||
if (!contentEl || !listWrapperEl || this.isScrolling) {
|
||||
if (!_this.isPageVisible) {
|
||||
_this.callAndroidPostLog("页面不可见,暂停 startSingleScroll 内部执行。");
|
||||
_this.timerId = setTimeout(_this.pageTimerCallback, _this.setTimeoutParams.retryDelay * 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
// **重试机制:如果 refs 未就绪,进行重试**
|
||||
if (!contentEl || !listWrapperEl || contentEl.clientHeight === 0 || listWrapperEl.offsetHeight === 0) {
|
||||
_this.callAndroidPostLog(`Refs未就绪或尺寸为0。contentEl: ${contentEl}, listWrapperEl: ${listWrapperEl}, contentHeight: ${contentEl ? contentEl.clientHeight : 'N/A'}, listHeight: ${listWrapperEl ? listWrapperEl.offsetHeight : 'N/A'}`);
|
||||
|
||||
if (_this.refRetryCount < _this.setTimeoutParams.maxRefRetry) {
|
||||
_this.refRetryCount++;
|
||||
_this.callAndroidPostLog(`尝试获取refs,重试次数:${_this.refRetryCount}/${_this.setTimeoutParams.maxRefRetry}。将在 ${_this.setTimeoutParams.refRetryInterval}ms 后重试。`);
|
||||
_this.initialDelayTimer = setTimeout(() => {
|
||||
_this.startSingleScroll(); // 再次尝试启动滚动
|
||||
}, _this.setTimeoutParams.refRetryInterval);
|
||||
return; // 退出当前函数,等待重试
|
||||
} else {
|
||||
_this.callAndroidPostLog("致命错误:滚动元素staffContent或staffList多次尝试后仍未就绪或尺寸为0,放弃滚动。将切换页面。");
|
||||
// 此时无法滚动,直接切换页面
|
||||
_this.timerId = setTimeout(_this.pageTimerCallback, _this.setTimeoutParams.retryDelay * 1000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_this.isScrolling) {
|
||||
_this.callAndroidPostLog("滚动正在进行中,跳过本次启动。");
|
||||
return;
|
||||
}
|
||||
|
||||
// 在每次启动滚动前,确保 CSS 动画属性被清除,防止旧动画残留
|
||||
listWrapperEl.style.transition = "none";
|
||||
_this.scrollPosition = 0; // 重置位置到顶部
|
||||
listWrapperEl.style.transform = `translateY(${_this.scrollPosition}px)`;
|
||||
void listWrapperEl.offsetWidth; // 强制浏览器重绘
|
||||
|
||||
let contentHeight = contentEl.clientHeight; // 容器的可见高度
|
||||
let actualListHeight = 0; // 存储列表的真实总高度,包括 margin-bottom
|
||||
let actualListHeight = 0;
|
||||
|
||||
// 确保列表项已经渲染,才能获取其高度
|
||||
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)
|
||||
let itemHeight = firstItem.offsetHeight;
|
||||
let itemMarginBottom = parseFloat(window.getComputedStyle(firstItem).marginBottom);
|
||||
actualListHeight = (itemHeight + itemMarginBottom) * listWrapperEl.children.length;
|
||||
|
||||
// 注意:如果你的设计是滚动到最后一个列表项的底部(而不是它下方margin-bottom的位置),
|
||||
// 可以在这里减去最后一个元素的 margin-bottom:
|
||||
// if (listWrapperEl.children.length > 0) {
|
||||
// actualListHeight -= itemMarginBottom;
|
||||
// }
|
||||
} else {
|
||||
_this.callAndroidPostLog("staffList内部无子元素,无法计算滚动高度。将切换页面。");
|
||||
// 无列表项时也直接切换页面
|
||||
_this.timerId = setTimeout(_this.pageTimerCallback, _this.setTimeoutParams.times * 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果列表实际高度小于或等于容器高度,则无需滚动
|
||||
if (actualListHeight <= contentHeight) {
|
||||
this.scrollAnimationDuration = 0;
|
||||
this.scrollPosition = 0; // 确保位置重置为顶部
|
||||
// 即使不滚动,也要在一段时间后切换到部门视图
|
||||
this.timerId = setTimeout(function () { // 回调函数使用普通函数
|
||||
this_.pageTimerCallback();
|
||||
}, this.setTimeoutParams.times * 1000);
|
||||
_this.scrollAnimationDuration = 0;
|
||||
_this.scrollPosition = 0;
|
||||
_this.callAndroidPostLog("列表高度 (" + actualListHeight + "px) 小于或等于容器高度 (" + contentHeight + "px),无需滚动。将切换页面。");
|
||||
_this.timerId = setTimeout(_this.pageTimerCallback, _this.setTimeoutParams.times * 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
this.isScrolling = true; // 标记开始滚动
|
||||
let scrollDistance = actualListHeight - contentHeight; // 计算需要滚动的总距离
|
||||
_this.isScrolling = true;
|
||||
let scrollDistance = actualListHeight - contentHeight;
|
||||
let scrollSpeed = 40; // 像素/秒
|
||||
_this.scrollAnimationDuration = scrollDistance / scrollSpeed;
|
||||
|
||||
let scrollSpeed = 40; // 像素/秒,你可以根据需要调整这个值,值越小滚动越慢
|
||||
this.scrollAnimationDuration = scrollDistance / scrollSpeed; // 计算动画持续时间(秒)
|
||||
// --- 滚动前停顿 ---
|
||||
_this.initialDelayTimer = setTimeout(
|
||||
() => {
|
||||
if (window.requestAnimationFrame) {
|
||||
window.requestAnimationFrame(() => {
|
||||
_this.performScrollAnimation(listWrapperEl, scrollDistance);
|
||||
});
|
||||
} else {
|
||||
_this.performScrollAnimation(listWrapperEl, scrollDistance);
|
||||
}
|
||||
},
|
||||
_this.setTimeoutParams.scrollDelay * 1000
|
||||
);
|
||||
},
|
||||
|
||||
// --- 关键重置步骤 ---
|
||||
// 1. 立即重置 transform 属性到顶部 (0px)
|
||||
listWrapperEl.style.transform = 'translateY(0px)';
|
||||
// 2. 立即移除 transition 属性,确保没有旧动画的干扰
|
||||
listWrapperEl.style.transition = 'none';
|
||||
this.scrollPosition = 0; // 更新数据模型中的位置
|
||||
// 统一处理 transitionend 事件,方便移除监听器
|
||||
onTransitionEnd: function(event) {
|
||||
const _this = this;
|
||||
const listWrapperEl = _this.$refs.staffList;
|
||||
if (event.target === listWrapperEl && event.propertyName === 'transform') {
|
||||
listWrapperEl.removeEventListener("transitionend", _this.onTransitionEndHandlerRef);
|
||||
_this.onTransitionEndHandlerRef = null; // 清除引用
|
||||
_this.isScrolling = false;
|
||||
_this.callAndroidPostLog("滚动动画完成。");
|
||||
|
||||
// 3. 强制浏览器重绘/回流,使其立即应用上述样式变化
|
||||
// 这是确保动画从正确初始状态开始的关键一步。访问 offsetWidth/Height 等属性可以触发回流。
|
||||
void listWrapperEl.offsetWidth;
|
||||
// --- 滚动结束后,设置页面切换定时器 ---
|
||||
_this.timerId = setTimeout(
|
||||
_this.pageTimerCallback,
|
||||
_this.setTimeoutParams.scrollEndDelay * 1000
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
// --- 新增:滚动前停顿一秒 ---
|
||||
this.initialDelayTimer = setTimeout(function () { // 回调函数使用普通函数
|
||||
// 在延迟结束后,通过 requestAnimationFrame 确保在浏览器下一次重绘前开始动画
|
||||
requestAnimationFrame(function () { // 回调函数使用普通函数
|
||||
// 设置目标位置(负值表示向上滚动)
|
||||
this_.scrollPosition = -scrollDistance;
|
||||
// 重新添加 transition 属性,启动平滑滚动
|
||||
listWrapperEl.style.transition = 'transform ' + this_.scrollAnimationDuration + 's linear';
|
||||
performScrollAnimation: function (listWrapperEl, scrollDistance) {
|
||||
const _this = this;
|
||||
|
||||
// 监听动画结束事件
|
||||
// 使用一个局部变量来存储事件处理函数,以便在事件触发后正确移除
|
||||
let onTransitionEnd = function () { // 回调函数使用普通函数
|
||||
listWrapperEl.removeEventListener('transitionend', onTransitionEnd); // 移除监听,防止重复触发
|
||||
this_.isScrolling = false; // 标记滚动结束
|
||||
// 移除任何旧的监听器,防止重复绑定
|
||||
if (_this.onTransitionEndHandlerRef) {
|
||||
listWrapperEl.removeEventListener("transitionend", _this.onTransitionEndHandlerRef);
|
||||
_this.onTransitionEndHandlerRef = null;
|
||||
}
|
||||
|
||||
// --- 滚动结束后,设置页面切换定时器 ---
|
||||
this_.timerId = setTimeout(function () { // 回调函数使用普通函数
|
||||
this_.pageTimerCallback();
|
||||
}.bind(this_), 2000);
|
||||
}.bind(this_); // 绑定 this_
|
||||
listWrapperEl.addEventListener('transitionend', onTransitionEnd);
|
||||
});
|
||||
}, 2000); // 停顿 2000 毫秒(2秒)
|
||||
_this.onTransitionEndHandlerRef = _this.onTransitionEnd.bind(_this);
|
||||
listWrapperEl.addEventListener("transitionend", _this.onTransitionEndHandlerRef);
|
||||
|
||||
// 设置目标位置(负值表示向上滚动)
|
||||
_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`);
|
||||
|
||||
// 备用定时器,确保即使 transitionend 未触发也能继续流程
|
||||
_this.timerId = 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.scrollAnimationDuration + _this.setTimeoutParams.scrollEndDelay) * 1000 + 500 // 动画持续时间 + 动画结束后等待时间 + 0.5秒缓冲
|
||||
);
|
||||
},
|
||||
},
|
||||
beforeDestroy: function () { // 箭头函数转为普通函数
|
||||
// 组件销毁前清除所有可能存在的定时器,防止内存泄漏
|
||||
if (this.timerId) {
|
||||
clearTimeout(this.timerId);
|
||||
this.timerId = null;
|
||||
}
|
||||
if (this.initialDelayTimer) {
|
||||
clearTimeout(this.initialDelayTimer);
|
||||
this.initialDelayTimer = null;
|
||||
beforeDestroy: function () {
|
||||
const _this = this;
|
||||
_this.clearAllTimers(); // 清除所有定时器
|
||||
|
||||
// 确保在组件销毁时,正在进行的 CSS 动画被停止并归位
|
||||
const listWrapperEl = _this.$refs.staffList;
|
||||
if (listWrapperEl) {
|
||||
listWrapperEl.style.transition = "none";
|
||||
listWrapperEl.style.transform = "translateY(0px)";
|
||||
void listWrapperEl.offsetWidth;
|
||||
}
|
||||
|
||||
// 确保移除可能残余的 transitionend 监听器,特别是在动画未完成时组件被销毁的情况
|
||||
let listWrapperEl = this.$refs.staffList;
|
||||
if (listWrapperEl) {
|
||||
// 由于 onTransitionEnd 是在 startSingleScroll 内部定义的局部函数,
|
||||
// 且每次调用 startSingleScroll 都会创建一个新的 onTransitionEnd,
|
||||
// 因此这里无法直接移除特定的命名函数。
|
||||
// 对于当前实现,由于事件会在结束后自行移除,且 setTimeout 会被清除,
|
||||
// 通常情况下不会造成严重问题。
|
||||
// 移除页面可见性监听器
|
||||
document.removeEventListener("visibilitychange", _this.handleVisibilityChange);
|
||||
|
||||
// 断开 MutationObserver
|
||||
if (_this.mutationObserver) {
|
||||
_this.mutationObserver.disconnect();
|
||||
_this.mutationObserver = null;
|
||||
_this.callAndroidPostLog("MutationObserver 已断开。");
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
/* 样式部分保持不变 */
|
||||
#dietaryNutritionTips2 {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -361,10 +567,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%;
|
||||
@@ -512,7 +718,11 @@ export default {
|
||||
padding: 105px 24px 0;
|
||||
box-sizing: border-box;
|
||||
border: 2px solid #ffc548;
|
||||
background: linear-gradient(180deg, rgba(225, 225, 225, 0) 0%, rgba(225, 225, 225, 0.3) 100%);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(225, 225, 225, 0) 0%,
|
||||
rgba(225, 225, 225, 0.3) 100%
|
||||
);
|
||||
|
||||
.staff {
|
||||
width: 100%;
|
||||
@@ -562,9 +772,11 @@ export default {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
height: calc(100% - 152px); // 根据实际高度调整
|
||||
position: relative; // 为 nullData 定位提供基础
|
||||
|
||||
.staff-list-wrapper {
|
||||
width: 100%;
|
||||
min-height: 1px; // 确保即使无子元素也有高度,避免 refs 无法获取
|
||||
}
|
||||
|
||||
.staffContentList {
|
||||
@@ -658,6 +870,7 @@ export default {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
height: calc(100% - 142px);
|
||||
position: relative; // 为 nullData 定位提供基础
|
||||
|
||||
.departmentContentList {
|
||||
width: 100%;
|
||||
@@ -716,7 +929,13 @@ export default {
|
||||
|
||||
.nullData {
|
||||
width: 100%;
|
||||
padding-top: 350px;
|
||||
// 定位到容器中心,确保在有滚动列表时不会覆盖
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
padding-top: 0; // 移除之前的 paddingTop
|
||||
text-align: center; // 确保内容居中
|
||||
|
||||
> div:nth-of-type(1) {
|
||||
width: 280px;
|
||||
|
||||
Reference in New Issue
Block a user