724 lines
18 KiB
Vue
724 lines
18 KiB
Vue
|
|
<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>
|