Files
m2pool_web_frontend/mining-pool/src/components/HorizontalBroadcast.vue

724 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="horizontal-broadcast-container" v-if="shouldShowBroadcast">
<!-- 广播标题区域 -->
<div class="broadcast-header" v-if="showTitle">
<i class="iconfont icon-tishishuoming" v-if="showIcon"></i>
<span class="broadcast-title">{{ $t('home.describeTitle') }}</span>
</div>
<!-- 横向滚动区域 -->
<div
class="horizontal-scroll-wrapper"
:class="{ 'is-hovering': isHovering, 'full-width': !showTitle }"
@mouseenter="stopHorizontalScroll"
@mouseleave="resumeHorizontalScroll"
@touchstart="stopHorizontalScroll"
@touchend="startHorizontalScrollDelayed"
:title="isHovering ? $t(`backendSystem.broadcastPause`) || '广播已暂停,移开鼠标继续滚动' : $t(`backendSystem.broadcastResume`) || '鼠标悬停可暂停滚动'"
>
<div
class="horizontal-scroll-content"
:style="horizontalScrollStyle"
ref="horizontalScrollContent"
>
<div
v-for="(item, index) in broadcastListForHorizontal"
:key="item.id + '-horizontal-' + index"
class="horizontal-broadcast-item"
@click="handleItemClick(item)"
>
<span class="horizontal-item-content">{{ item.content }}</span>
<!-- 动态渲染多个按钮 -->
<div class="button-group" v-if="item.buttonContent && item.buttonContent.length > 0">
<span
v-for="(buttonText, buttonIndex) in item.buttonContent"
:key="`button-${item.id}-${buttonIndex}`"
class="view"
@click.stop="handelJump(item.buttonPath[buttonIndex])"
>
{{ buttonText || $t(`home.view`) }}
</span>
</div>
<span class="horizontal-item-separator" v-if="index < broadcastListForHorizontal.length - 1"></span>
</div>
</div>
</div>
</div>
</template>
<script>
import { getBroadcast } from '../api/broadcast'
export default {
name: 'HorizontalBroadcast',
props: {
// 广播数据,如果不传则自动获取
broadcastData: {
type: Array,
default: () => []
},
// 是否显示标题
showTitle: {
type: Boolean,
default: true
},
// 是否显示图标
showIcon: {
type: Boolean,
default: true
},
// 自定义标题
title: {
type: String,
default: ''
},
// 滚动速度 (px/step)
scrollSpeed: {
type: Number,
default: 1
},
// 滚动间隔 (ms)
scrollInterval: {
type: Number,
default: 50
},
// 是否自动获取数据
autoFetch: {
type: Boolean,
default: true
},
// 获取数据的语言参数
lang: {
type: String,
default: 'zh'
},
// 最小显示条数(少于此数不显示组件)
minItems: {
type: Number,
default: 1
},
// 组件高度
height: {
type: String,
default: '40px'
},
// 是否启用点击事件
clickable: {
type: Boolean,
default: false
}
},
data() {
return {
// 内部广播数据
internalBroadcastList: [],
// 横向滚动相关
horizontalScrollTimer: null,
horizontalDelayTimer: null,
horizontalScrollOffset: 0,
isHorizontalScrolling: true,
// hover状态控制
isHovering: false,
wasScrollingBeforeHover: true,
// 组件状态
isLoading: false,
hasError: false,
}
},
computed: {
// 最终使用的广播数据
finalBroadcastList() {
let sourceData = this.broadcastData.length > 0
? this.broadcastData
: this.internalBroadcastList;
// 统一处理数据格式转换
const processedData = sourceData.map(item => {
const processedItem = { ...item };
// 处理 buttonContent如果是字符串则分割为数组
if (typeof processedItem.buttonContent === 'string' && processedItem.buttonContent.trim()) {
processedItem.buttonContent = processedItem.buttonContent
.split(/[,]\s*/) // 支持中英文逗号,后面可能跟空格
.map(btn => btn.trim())
.filter(btn => btn); // 过滤空字符串
} else if (!Array.isArray(processedItem.buttonContent)) {
processedItem.buttonContent = [];
}
// 处理 buttonPath如果是字符串则分割为数组
if (typeof processedItem.buttonPath === 'string' && processedItem.buttonPath.trim()) {
processedItem.buttonPath = processedItem.buttonPath
.split(/[,]\s*/) // 支持中英文逗号,后面可能跟空格
.map(path => path.trim())
.filter(path => path); // 过滤空字符串
} else if (!Array.isArray(processedItem.buttonPath)) {
processedItem.buttonPath = [];
}
// 只有当两个数组都有内容时才进行长度校验
if (processedItem.buttonContent.length > 0 && processedItem.buttonPath.length > 0) {
// 确保两个数组长度一致,以较短的为准
const minLength = Math.min(
processedItem.buttonContent.length,
processedItem.buttonPath.length
);
processedItem.buttonContent = processedItem.buttonContent.slice(0, minLength);
processedItem.buttonPath = processedItem.buttonPath.slice(0, minLength);
}
return processedItem;
});
return processedData;
},
// 是否应该显示广播组件
shouldShowBroadcast() {
return this.finalBroadcastList.length >= this.minItems && !this.hasError;
},
// 横向滚动用的数据(复制数据实现无缝循环)
broadcastListForHorizontal() {
const list = this.finalBroadcastList;
if (list.length === 0) return [];
let result;
if (list.length === 1) {
// 单条数据时复制多次以实现连续滚动
result = Array(3).fill().map((_, idx) => ({
...list[0],
id: list[0].id + '-copy-' + idx
}));
} else {
// 多条数据时在末尾添加第一条实现无缝循环
result = [...list, list[0]];
}
return result;
},
// 横向滚动样式
horizontalScrollStyle() {
return {
transform: `translateX(-${this.horizontalScrollOffset}px)`,
transition: this.isHorizontalScrolling ? 'none' : 'transform 0.3s ease',
};
},
},
watch: {
// 监听外部数据变化
broadcastData: {
handler(newData) {
if (newData && newData.length > 0) {
this.resetScroll();
}
},
deep: true,
immediate: true
},
// 监听滚动参数变化
scrollSpeed(newSpeed) {
this.horizontalScrollSpeed = newSpeed;
},
scrollInterval(newInterval) {
this.horizontalScrollInterval = newInterval;
if (this.isHorizontalScrolling) {
this.restartScroll();
}
}
},
mounted() {
// 如果没有外部数据且允许自动获取,则获取数据
if (this.autoFetch && this.broadcastData.length === 0) {
this.fetchBroadcastData();
}
// 启动滚动
this.$nextTick(() => {
this.startHorizontalScroll();
});
},
methods: {
/**
* 获取广播数据
*/
async fetchBroadcastData() {
if (this.isLoading) return;
try {
this.isLoading = true;
this.hasError = false;
const data = await getBroadcast({ lang: this.lang || (this.$i18n ? this.$i18n.locale : 'zh') });
if (data && data.code === 200 && Array.isArray(data.data)) {
this.internalBroadcastList = data.data;
this.resetScroll();
this.$emit('data-loaded', data.data);
} else {
this.hasError = true;
this.$emit('error', '获取广播数据失败');
}
} catch (error) {
console.error('获取广播数据失败:', error);
this.hasError = true;
this.$emit('error', error);
} finally {
this.isLoading = false;
}
},
/**
* 开始横向滚动
*/
startHorizontalScroll() {
// 如果正在hover或数据不足不启动滚动
if (this.isHovering || this.finalBroadcastList.length <= 1) return;
if (this.horizontalScrollTimer) {
clearInterval(this.horizontalScrollTimer);
}
this.isHorizontalScrolling = true;
this.horizontalScrollTimer = setInterval(() => {
this.horizontalScrollStep();
}, this.scrollInterval);
},
/**
* 停止横向滚动
*/
stopHorizontalScroll() {
this.wasScrollingBeforeHover = this.isHorizontalScrolling;
this.isHovering = true;
if (this.horizontalScrollTimer) {
clearInterval(this.horizontalScrollTimer);
this.horizontalScrollTimer = null;
}
this.isHorizontalScrolling = false;
},
/**
* 鼠标移开时恢复滚动
*/
resumeHorizontalScroll() {
this.isHovering = false;
if (this.wasScrollingBeforeHover && this.finalBroadcastList.length > 1) {
setTimeout(() => {
if (!this.isHovering) {
this.startHorizontalScroll();
}
}, 100);
}
},
handelJump(url) {
const lang = this.$i18n.locale;
/**
* 处理单个路径跳转
* @param {string} path - 路径
*/
const handleSinglePath = (path) => {
if (!path || typeof path !== 'string') {
console.warn('无效的路径:', path);
return;
}
// 清理路径
const cleanPath = path.trim();
if (!cleanPath) {
console.warn('路径为空');
return;
}
let targetPath;
// 如果是主页路径
if (cleanPath === '/' || cleanPath === '') {
targetPath = `/${lang}/`;
} else {
// 其他路径:去掉开头的斜杠(如果有的话)
const pathWithoutSlash = cleanPath.startsWith('/') ? cleanPath.substring(1) : cleanPath;
targetPath = `/${lang}/${pathWithoutSlash}`;
}
console.log('跳转路径:', targetPath);
try {
// 在当前页面跳转
this.$router.push(targetPath);
} catch (error) {
console.error('路由跳转失败:', error);
// 如果路由跳转失败,尝试直接跳转
window.location.href = targetPath;
}
};
// 处理传入的路径
if (url) {
handleSinglePath(url);
} else {
console.warn('未提供跳转路径');
}
},
/**
* 延迟启动横向滚动(移动端触摸后)
*/
startHorizontalScrollDelayed() {
if (this.horizontalDelayTimer) {
clearTimeout(this.horizontalDelayTimer);
}
this.horizontalDelayTimer = setTimeout(() => {
this.resumeHorizontalScroll();
}, 1000);
},
/**
* 横向滚动步进
*/
horizontalScrollStep() {
if (!this.$refs.horizontalScrollContent) return;
const contentElement = this.$refs.horizontalScrollContent;
const contentWidth = contentElement.scrollWidth;
// 计算单个循环的宽度
const singleCycleWidth = contentWidth / this.broadcastListForHorizontal.length * this.finalBroadcastList.length;
this.horizontalScrollOffset += this.scrollSpeed;
// 当滚动超过单个循环宽度时,重置到开始位置实现无缝循环
if (this.horizontalScrollOffset >= singleCycleWidth) {
this.horizontalScrollOffset = 0;
}
},
/**
* 重置滚动
*/
resetScroll() {
this.horizontalScrollOffset = 0;
this.$nextTick(() => {
if (this.finalBroadcastList.length > 1) {
this.startHorizontalScroll();
}
});
},
/**
* 重启滚动
*/
restartScroll() {
this.stopHorizontalScroll();
this.$nextTick(() => {
this.startHorizontalScroll();
});
},
/**
* 广播项点击事件
*/
handleItemClick(item) {
if (this.clickable) {
this.$emit('item-click', item);
}
},
/**
* 手动刷新数据
*/
refresh() {
if (this.autoFetch) {
this.fetchBroadcastData();
} else {
this.resetScroll();
}
},
/**
* 暂停/继续滚动
*/
togglePause() {
if (this.isHorizontalScrolling) {
this.stopHorizontalScroll();
} else {
this.resumeHorizontalScroll();
}
},
/**
* 获取组件状态
*/
getStatus() {
return {
isScrolling: this.isHorizontalScrolling,
isHovering: this.isHovering,
currentOffset: this.horizontalScrollOffset,
dataCount: this.finalBroadcastList.length,
isLoading: this.isLoading,
hasError: this.hasError
};
}
},
beforeDestroy() {
// 清理定时器
if (this.horizontalScrollTimer) {
clearInterval(this.horizontalScrollTimer);
}
if (this.horizontalDelayTimer) {
clearTimeout(this.horizontalDelayTimer);
}
// 重置状态
this.isHovering = false;
this.isHorizontalScrolling = false;
}
}
</script>
<style scoped lang="scss">
/* 横向滚动广播容器 */
.horizontal-broadcast-container {
width: 100%;
background: #fff;
// box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
padding: 0px 20px !important;
position: relative;
z-index: 100;
background: #E7DFF3;
}
/* 广播标题区域 */
.broadcast-header {
display: flex;
align-items: center;
margin-right: 15px;
flex-shrink: 0;
i {
color: #5721e4;
font-size: 18px;
margin-right: 8px;
}
.broadcast-title {
color: #5721e4;
font-weight: 600;
font-size: 14px;
white-space: nowrap;
}
}
/* 横向滚动包装器 */
.horizontal-scroll-wrapper {
flex: 1;
overflow: hidden;
position: relative;
height: v-bind(height);
// background: #f8f9fa;
border-radius: 8px;
// border: 1px solid #e9ecef;
cursor: pointer;
display: flex;
align-items: center;
&.full-width {
margin-right: 0;
}
}
/* 横向滚动内容 */
.horizontal-scroll-content {
display: flex;
align-items: center;
height: 100%;
white-space: nowrap;
will-change: transform;
}
/* 横向广播项 */
.horizontal-broadcast-item {
display: inline-flex;
align-items: center;
height: 100%;
margin-right: 30px;
flex-shrink: 0;
font-size: 0.85rem;
.horizontal-item-content {
color: #333;
font-size: 14px;
line-height: 1.4;
padding: 0 15px;
white-space: nowrap;
transition: color 0.3s ease;
// &:hover {
// color: #5721e4;
// }
}
/* 按钮组样式 */
.button-group {
display: inline-flex;
align-items: center;
// margin-left: 10px;
gap: 0px; /* 按钮之间的间距 */
}
.horizontal-item-separator {
color: #ccc;
margin: 0 15px;
font-size: 12px;
}
}
/* 滚动暂停时的视觉反馈 */
.horizontal-scroll-wrapper:hover,
.horizontal-scroll-wrapper.is-hovering {
// background: linear-gradient(135deg, #f0f3ff, #e8f2ff);
border-color: #5721e4;
// box-shadow: 0 2px 8px rgba(87, 33, 228, 0.15);
transition: all 0.3s ease;
}
/* hover状态下的内容样式 */
.horizontal-scroll-wrapper.is-hovering .horizontal-item-content {
// color: #5721e4;
font-weight: 500;
transition: all 0.3s ease;
}
/* 暂停指示器圆点 */
.horizontal-scroll-wrapper.is-hovering::before {
content: '';
position: absolute;
top: 50%;
right: 8px;
transform: translateY(-50%);
width: 6px;
height: 6px;
background: #5721e4;
border-radius: 50%;
animation: pausePulse 1.5s ease-in-out infinite;
z-index: 1;
}
.view{
color: #5721e4;
// font-size: 0.85rem;
// margin-left: 5px;
cursor: pointer;
padding: 2px 8px;
border-radius: 4px;
transition: all 0.3s ease;
white-space: nowrap;
border: 1px solid transparent;
&:hover {
background-color: #5721e4;
color: #fff;
border-color: #5721e4;
transform: translateY(-1px);
}
&:active {
transform: translateY(0);
}
}
@keyframes pausePulse {
0%, 100% {
opacity: 0.6;
transform: translateY(-50%) scale(1);
}
50% {
opacity: 1;
transform: translateY(-50%) scale(1.2);
}
}
/* 响应式设计 */
@media screen and (max-width: 768px) {
.horizontal-broadcast-container {
padding: 8px 15px;
.broadcast-header {
margin-right: 10px;
i {
font-size: 16px;
margin-right: 5px;
}
.broadcast-title {
font-size: 12px;
}
}
}
.horizontal-broadcast-item {
margin-right: 20px;
.horizontal-item-content {
font-size: 13px;
padding: 0 10px;
}
/* 移动端按钮组样式 */
.button-group {
margin-left: 5px;
gap: 5px;
.view {
font-size: 12px;
padding: 1px 6px;
margin-left: 0;
}
}
.horizontal-item-separator {
margin: 0 10px;
}
}
.horizontal-scroll-wrapper.is-hovering::before {
right: 5px;
width: 4px;
height: 4px;
}
}
/* 美化滚动条 */
.horizontal-scroll-wrapper::-webkit-scrollbar {
display: none;
}
</style>