|
|
@ -4,8 +4,6 @@ |
|
|
<div class="common-title">通知公告</div> |
|
|
<div class="common-title">通知公告</div> |
|
|
<div class="small-module module-content"> |
|
|
<div class="small-module module-content"> |
|
|
<span class="notice-icon-gif" /> |
|
|
<span class="notice-icon-gif" /> |
|
|
|
|
|
|
|
|
<!-- 核心:给容器加固定宽度,避免宽度溢出导致滚动失效 --> |
|
|
|
|
|
<div ref="box" class="seamless-warp"> |
|
|
<div ref="box" class="seamless-warp"> |
|
|
<div ref="text" class="notice-txt" v-html="content" /> |
|
|
<div ref="text" class="notice-txt" v-html="content" /> |
|
|
</div> |
|
|
</div> |
|
|
@ -21,22 +19,51 @@ export default { |
|
|
data() { |
|
|
data() { |
|
|
return { |
|
|
return { |
|
|
content: '', |
|
|
content: '', |
|
|
scrollTimer: null, // 滚动定时器 |
|
|
|
|
|
transitionTimer: null, // 过渡结束定时器 |
|
|
|
|
|
boxH: 0, // 容器高度 |
|
|
|
|
|
textH: 0, // 内容高度 |
|
|
|
|
|
duration: 40000 // 单次滚动时长 |
|
|
|
|
|
|
|
|
scrollTimer: null, |
|
|
|
|
|
transitionTimer: null, |
|
|
|
|
|
boxH: 0, |
|
|
|
|
|
textH: 0, |
|
|
|
|
|
duration: 40000, |
|
|
|
|
|
isDestroyed: false, // 标记组件是否销毁 |
|
|
|
|
|
isScrollRunning: false // 防止重复启动滚动 |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
mounted() { |
|
|
mounted() { |
|
|
|
|
|
this.isDestroyed = false |
|
|
|
|
|
document.addEventListener('visibilitychange', this.handleVisibility) |
|
|
this.getInitNotice() |
|
|
this.getInitNotice() |
|
|
}, |
|
|
}, |
|
|
beforeDestroy() { |
|
|
beforeDestroy() { |
|
|
// 销毁时清除所有定时器 |
|
|
|
|
|
|
|
|
this.isDestroyed = true |
|
|
|
|
|
this.isScrollRunning = false |
|
|
|
|
|
|
|
|
if (this.scrollTimer) clearInterval(this.scrollTimer) |
|
|
if (this.scrollTimer) clearInterval(this.scrollTimer) |
|
|
if (this.transitionTimer) clearTimeout(this.transitionTimer) |
|
|
if (this.transitionTimer) clearTimeout(this.transitionTimer) |
|
|
|
|
|
|
|
|
|
|
|
// ✅ 移除dom事件,防止内存泄漏 |
|
|
|
|
|
const text = this.$refs.text |
|
|
|
|
|
if (text) { |
|
|
|
|
|
text.removeEventListener('transitionend', this.handleTransitionEnd) |
|
|
|
|
|
} |
|
|
|
|
|
document.removeEventListener('visibilitychange', this.handleVisibility) |
|
|
}, |
|
|
}, |
|
|
methods: { |
|
|
methods: { |
|
|
|
|
|
// 页面切后台/切前台 |
|
|
|
|
|
handleVisibility() { |
|
|
|
|
|
if (this.isDestroyed) return |
|
|
|
|
|
if (document.hidden) { |
|
|
|
|
|
// 页面隐藏,清除延时,暂停滚动循环 |
|
|
|
|
|
if (this.transitionTimer) { |
|
|
|
|
|
clearTimeout(this.transitionTimer) |
|
|
|
|
|
this.transitionTimer = null |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
// 切回来,继续滚动 |
|
|
|
|
|
if (this.isScrollRunning) { |
|
|
|
|
|
this.rollToBottom() |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
escapeHtml(str) { |
|
|
escapeHtml(str) { |
|
|
if (!str) return '' |
|
|
if (!str) return '' |
|
|
var arrEntities = { |
|
|
var arrEntities = { |
|
|
@ -48,14 +75,16 @@ export default { |
|
|
} |
|
|
} |
|
|
return str.replace(/&(lt|gt|nbsp|amp|quot);/ig, (all, t) => arrEntities[t]) |
|
|
return str.replace(/&(lt|gt|nbsp|amp|quot);/ig, (all, t) => arrEntities[t]) |
|
|
.replace(/<pre>|<\/pre>/gi, '') |
|
|
.replace(/<pre>|<\/pre>/gi, '') |
|
|
.replace(/\\r\\n/g, '') // 移除多余换行符 |
|
|
|
|
|
|
|
|
.replace(/\\r\\n/g, '') |
|
|
}, |
|
|
}, |
|
|
getInitNotice() { |
|
|
getInitNotice() { |
|
|
|
|
|
if (this.isDestroyed) return |
|
|
FetchInitNotice().then(res => { |
|
|
FetchInitNotice().then(res => { |
|
|
|
|
|
if (this.isDestroyed) return |
|
|
if (res.errCode === 0) { |
|
|
if (res.errCode === 0) { |
|
|
this.content = this.escapeHtml(res.data) |
|
|
this.content = this.escapeHtml(res.data) |
|
|
// 等DOM完全渲染后再启动滚动 |
|
|
|
|
|
this.$nextTick(() => { |
|
|
this.$nextTick(() => { |
|
|
|
|
|
if (this.isDestroyed) return |
|
|
this.startScroll() |
|
|
this.startScroll() |
|
|
}) |
|
|
}) |
|
|
} |
|
|
} |
|
|
@ -64,65 +93,61 @@ export default { |
|
|
}) |
|
|
}) |
|
|
}, |
|
|
}, |
|
|
startScroll() { |
|
|
startScroll() { |
|
|
|
|
|
if (this.isDestroyed || this.isScrollRunning) return |
|
|
const box = this.$refs.box |
|
|
const box = this.$refs.box |
|
|
const text = this.$refs.text |
|
|
const text = this.$refs.text |
|
|
|
|
|
|
|
|
// 先打印调试,看是否获取到DOM和高度 |
|
|
|
|
|
console.log('容器DOM:', box) |
|
|
|
|
|
console.log('内容DOM:', text) |
|
|
|
|
|
if (!box || !text) { |
|
|
if (!box || !text) { |
|
|
console.log('DOM未获取到,滚动启动失败') |
|
|
console.log('DOM未获取到,滚动启动失败') |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 强制计算高度(解决rem布局高度计算延迟) |
|
|
|
|
|
this.boxH = box.clientHeight || parseInt(getComputedStyle(box).height) |
|
|
this.boxH = box.clientHeight || parseInt(getComputedStyle(box).height) |
|
|
this.textH = text.offsetHeight || parseInt(getComputedStyle(text).height) |
|
|
this.textH = text.offsetHeight || parseInt(getComputedStyle(text).height) |
|
|
|
|
|
|
|
|
console.log('容器高度:', this.boxH, '内容高度:', this.textH) |
|
|
console.log('容器高度:', this.boxH, '内容高度:', this.textH) |
|
|
|
|
|
|
|
|
// 内容不够高,不滚动 |
|
|
|
|
|
if (this.textH <= this.boxH) { |
|
|
if (this.textH <= this.boxH) { |
|
|
console.log('内容高度小于容器,无需滚动') |
|
|
console.log('内容高度小于容器,无需滚动') |
|
|
|
|
|
this.isScrollRunning = false |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 初始化样式 |
|
|
|
|
|
|
|
|
// 重置样式 |
|
|
text.style.position = 'absolute' |
|
|
text.style.position = 'absolute' |
|
|
text.style.top = '0px' |
|
|
text.style.top = '0px' |
|
|
text.style.left = '0px' |
|
|
text.style.left = '0px' |
|
|
text.style.width = '100%' |
|
|
text.style.width = '100%' |
|
|
text.style.transition = 'none' |
|
|
text.style.transition = 'none' |
|
|
|
|
|
|
|
|
// 监听过渡结束事件,实现循环 |
|
|
|
|
|
|
|
|
// 先解绑旧事件,避免重复绑定 |
|
|
|
|
|
text.removeEventListener('transitionend', this.handleTransitionEnd) |
|
|
text.addEventListener('transitionend', this.handleTransitionEnd) |
|
|
text.addEventListener('transitionend', this.handleTransitionEnd) |
|
|
|
|
|
|
|
|
// 启动第一次滚动 |
|
|
|
|
|
|
|
|
this.isScrollRunning = true |
|
|
this.rollToBottom() |
|
|
this.rollToBottom() |
|
|
}, |
|
|
}, |
|
|
// 滚动到底部 |
|
|
|
|
|
rollToBottom() { |
|
|
rollToBottom() { |
|
|
|
|
|
if (this.isDestroyed || !this.isScrollRunning) return |
|
|
const text = this.$refs.text |
|
|
const text = this.$refs.text |
|
|
// 重置过渡,立即回到顶部 |
|
|
|
|
|
|
|
|
if (!text) return |
|
|
|
|
|
|
|
|
text.style.transition = 'none' |
|
|
text.style.transition = 'none' |
|
|
text.style.top = '0px' |
|
|
text.style.top = '0px' |
|
|
|
|
|
|
|
|
// 重新设置过渡,滚动到底部 |
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
|
|
|
// 使用requestAnimationFrame替代固定100ms延时,等待浏览器重绘完成 |
|
|
|
|
|
requestAnimationFrame(() => { |
|
|
|
|
|
if (this.isDestroyed || !this.$refs.text) return |
|
|
text.style.transition = `top ${this.duration}ms linear` |
|
|
text.style.transition = `top ${this.duration}ms linear` |
|
|
text.style.top = `-${this.textH - this.boxH}px` |
|
|
text.style.top = `-${this.textH - this.boxH}px` |
|
|
}, 100) |
|
|
|
|
|
|
|
|
}) |
|
|
}, |
|
|
}, |
|
|
// 过渡结束处理函数 |
|
|
|
|
|
handleTransitionEnd() { |
|
|
|
|
|
// const text = this.$refs.text |
|
|
|
|
|
// 清除之前的定时器,避免重复触发 |
|
|
|
|
|
if (this.transitionTimer) clearTimeout(this.transitionTimer) |
|
|
|
|
|
|
|
|
handleTransitionEnd(e) { |
|
|
|
|
|
// 只处理text本身的transition事件,避免子元素触发 |
|
|
|
|
|
if (this.isDestroyed || e.target !== this.$refs.text) return |
|
|
|
|
|
|
|
|
// 停留2秒后重新滚动 |
|
|
|
|
|
|
|
|
if (this.transitionTimer) clearTimeout(this.transitionTimer) |
|
|
this.transitionTimer = setTimeout(() => { |
|
|
this.transitionTimer = setTimeout(() => { |
|
|
this.rollToBottom() |
|
|
this.rollToBottom() |
|
|
}, 2000) // 2000毫秒 = 2秒 |
|
|
|
|
|
|
|
|
}, 2000) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|