parent
dc5658513c
commit
55a7f3c79c
@ -0,0 +1,197 @@ |
||||
<template> |
||||
<div class="scrolling-announcement"> |
||||
<div class="announcement-header"> |
||||
<div class="header-icon">📢</div> |
||||
<div class="header-title">最新公告</div> |
||||
</div> |
||||
<div class="announcement-content"> |
||||
<div class="scrolling-container" ref="scrollingContainer"> |
||||
<div |
||||
class="announcement-item current" |
||||
:key="'current-' + currentIndex" |
||||
> |
||||
{{ announcements[currentIndex]?.content }} |
||||
</div> |
||||
<div |
||||
class="announcement-item next" |
||||
:key="'next-' + nextIndex" |
||||
> |
||||
{{ announcements[nextIndex]?.content }} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup> |
||||
import { ref, onMounted, onUnmounted, reactive, computed } from 'vue' |
||||
|
||||
const scrollingContainer = ref(null) |
||||
const currentIndex = ref(0) |
||||
const isAnimating = ref(false) |
||||
let scrollTimer = null |
||||
|
||||
const announcements = reactive([]) |
||||
|
||||
// 计算下一个索引 |
||||
const nextIndex = computed(() => { |
||||
return announcements.length > 0 ? (currentIndex.value + 1) % announcements.length : 0 |
||||
}) |
||||
|
||||
// 加载公告数据 |
||||
const loadAnnouncements = async () => { |
||||
// 室外页面直接使用默认模拟数据,确保显示效果 |
||||
console.log('室外页面使用专用公告数据') |
||||
loadDefaultAnnouncements() |
||||
} |
||||
|
||||
// 加载默认公告 |
||||
const loadDefaultAnnouncements = () => { |
||||
announcements.splice(0, announcements.length, |
||||
{ content: '【重要通知】今日牦牛交易价格上涨2.5%,活牛32.8元/公斤,鲜肉78.5元/公斤,受春季需求增长影响,请商户关注行情变化,合理安排采购计划' }, |
||||
{ content: '【招商信息】新增北京、上海、广州、深圳等一线城市优质采购商入驻,涵盖餐饮连锁、精品超市、生鲜电商等业态,欢迎牧民朋友联系洽谈合作' }, |
||||
{ content: '【系统维护】本周末(3月18日22:00-19日08:00)系统升级维护,交易时间调整为09:00-17:00,支付功能暂停4小时,请提前安排交易事宜' }, |
||||
{ content: '【市场资讯】受春季牧草丰茂、气候适宜等因素影响,预计下月牦牛供应量增加15%,达8500头左右,肉质品质提升,建议采购商提前规划库存' }, |
||||
{ content: '【质量报告】西藏、青海、甘肃等主要产区肉品检测合格率达98.5%,创历史新高,蛋白质含量21.8%,重金属含量远低于国标,品质安全有保障' }, |
||||
{ content: '【政策利好】国家三部委发布牦牛产业发展指导意见,三年内投入50亿元专项资金,支持养殖技术、品种改良、冷链物流等领域,并给予税收减免支持' }, |
||||
{ content: '【安全提醒】近期发现不法分子冒充平台工作人员诈骗,以系统升级、账户异常为借口要求转账,请提高警惕,平台绝不私下要求转账,疑问请联系客服400-8888-666' }, |
||||
{ content: '【物流升级】与顺丰、德邦、中通等企业合作开通专业冷链服务,覆盖全国主要城市,配备冷藏车辆恒温仓储,一线城市24小时送达,配送效率提升60%' }, |
||||
{ content: '【技术创新】推出AI智能定价系统2.0,整合市场供需、季节变化、品质等级等数据,运用深度学习算法提供精准价格参考,预计节省交易成本15-20%' }, |
||||
{ content: '【节日祝福】藏历新年洛萨节即将到来,平台全体员工向牧民朋友和合作伙伴致以节日祝福,祝大家新年健康幸福、生意兴隆,牦牛产业蒸蒸日上,扎西德勒!' } |
||||
) |
||||
} |
||||
|
||||
// 执行滚动动画 |
||||
const performScroll = () => { |
||||
if (isAnimating.value || announcements.length <= 1) return |
||||
|
||||
isAnimating.value = true |
||||
const container = scrollingContainer.value |
||||
|
||||
// 添加滚动类来触发动画 |
||||
container.classList.add('scrolling') |
||||
|
||||
// 动画完成后更新索引并重置状态 |
||||
setTimeout(() => { |
||||
currentIndex.value = nextIndex.value |
||||
container.classList.remove('scrolling') |
||||
isAnimating.value = false |
||||
}, 800) // 动画持续时间 |
||||
} |
||||
|
||||
// 开始垂直滚动 |
||||
const startVerticalScroll = () => { |
||||
if (announcements.length <= 1) return |
||||
|
||||
scrollTimer = setInterval(() => { |
||||
performScroll() |
||||
}, 5000) // 每5秒切换一次 |
||||
} |
||||
|
||||
// 停止滚动 |
||||
const stopVerticalScroll = () => { |
||||
if (scrollTimer) { |
||||
clearInterval(scrollTimer) |
||||
scrollTimer = null |
||||
} |
||||
} |
||||
|
||||
onMounted(async () => { |
||||
await loadAnnouncements() |
||||
// 等待数据加载完成后开始滚动 |
||||
if (announcements.length > 1) { |
||||
startVerticalScroll() |
||||
} |
||||
}) |
||||
|
||||
onUnmounted(() => { |
||||
stopVerticalScroll() |
||||
}) |
||||
</script> |
||||
|
||||
<style scoped> |
||||
.scrolling-announcement { |
||||
position: absolute; |
||||
top: 20px; |
||||
left: 0; |
||||
right: 0; |
||||
height: 70px; |
||||
background: rgba(26, 31, 46, 0.95); |
||||
border: 1px solid rgba(64, 158, 255, 0.4); |
||||
border-radius: 8px; |
||||
backdrop-filter: blur(10px); |
||||
z-index: 100; |
||||
overflow: hidden; |
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); |
||||
} |
||||
|
||||
.announcement-header { |
||||
position: absolute; |
||||
left: 0; |
||||
top: 0; |
||||
bottom: 0; |
||||
width: 140px; |
||||
background: rgba(64, 158, 255, 0.2); |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
gap: 8px; |
||||
border-right: 1px solid rgba(64, 158, 255, 0.3); |
||||
} |
||||
|
||||
.header-icon { |
||||
font-size: 20px; |
||||
} |
||||
|
||||
.header-title { |
||||
font-size: 20px; |
||||
font-weight: bold; |
||||
color: #409EFF; |
||||
} |
||||
|
||||
.announcement-content { |
||||
margin-left: 140px; |
||||
height: 100%; |
||||
overflow: hidden; |
||||
position: relative; |
||||
} |
||||
|
||||
.scrolling-container { |
||||
position: relative; |
||||
height: 100%; |
||||
width: 100%; |
||||
} |
||||
|
||||
.announcement-item { |
||||
position: absolute; |
||||
top: 0; |
||||
left: 0; |
||||
right: 0; |
||||
height: 100%; |
||||
display: flex; |
||||
align-items: center; |
||||
font-size: 28px; |
||||
color: #fff; |
||||
padding: 0 30px; |
||||
white-space: nowrap; |
||||
transition: transform 0.8s ease-in-out; |
||||
font-weight: 500; |
||||
} |
||||
|
||||
.announcement-item.current { |
||||
transform: translateY(0); |
||||
} |
||||
|
||||
.announcement-item.next { |
||||
transform: translateY(100%); |
||||
} |
||||
|
||||
/* 滚动动画状态 */ |
||||
.scrolling-container.scrolling .announcement-item.current { |
||||
transform: translateY(-100%); |
||||
} |
||||
|
||||
.scrolling-container.scrolling .announcement-item.next { |
||||
transform: translateY(0); |
||||
} |
||||
</style> |
||||
@ -0,0 +1,303 @@ |
||||
<template> |
||||
<div class="outdoor-viewport"> |
||||
<div class="outdoor-container" ref="containerRef"> |
||||
<!-- 顶部时间日期天气区域 --> |
||||
<div class="header-datetime"> |
||||
<div class="datetime-info"> |
||||
<span class="date">{{ currentDate }}</span> |
||||
<span class="weekday">{{ currentWeekday }}</span> |
||||
<span class="lunar">{{ lunarDate }}</span> |
||||
<span class="weather">{{ weather }}</span> |
||||
<span class="temperature">{{ temperature }}</span> |
||||
</div> |
||||
</div> |
||||
|
||||
<!-- 滚动公告区域 --> |
||||
<div class="announcement-section"> |
||||
<ScrollingAnnouncement /> |
||||
</div> |
||||
|
||||
<!-- 主内容四象限布局 --> |
||||
<div class="main-content"> |
||||
<!-- 左上:牦牛实时交易概况 --> |
||||
<div class="content-area top-left"> |
||||
<YakTradingOverview /> |
||||
</div> |
||||
|
||||
<!-- 右上:活牛、鲜肉价格趋势 --> |
||||
<div class="content-area top-right"> |
||||
<PriceTrendChart /> |
||||
</div> |
||||
|
||||
<!-- 左下:待交易牦牛数据 --> |
||||
<div class="content-area bottom-left"> |
||||
<PendingYakData /> |
||||
</div> |
||||
|
||||
<!-- 右下:交易结算数据 --> |
||||
<div class="content-area bottom-right"> |
||||
<TransactionSettlement /> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup> |
||||
import { ref, onMounted, onUnmounted } from 'vue' |
||||
import ScrollingAnnouncement from '../components/Outdoor/ScrollingAnnouncement.vue' |
||||
import YakTradingOverview from '../components/Outdoor/YakTradingOverview.vue' |
||||
import PriceTrendChart from '../components/Outdoor/PriceTrendChart.vue' |
||||
import PendingYakData from '../components/Outdoor/PendingYakData.vue' |
||||
import TransactionSettlement from '../components/Outdoor/TransactionSettlement.vue' |
||||
import { Lunar, Solar } from 'lunar-javascript' |
||||
|
||||
// 基准分辨率 3000×1500 |
||||
const BASE_WIDTH = 3000 |
||||
const BASE_HEIGHT = 1500 |
||||
|
||||
// 响应式数据 |
||||
const containerRef = ref(null) |
||||
const currentDate = ref('') |
||||
const currentWeekday = ref('') |
||||
const lunarDate = ref('') |
||||
const weather = ref('晴') |
||||
const temperature = ref('18°C') |
||||
|
||||
// 更新时间日期 |
||||
const updateDateTime = () => { |
||||
const now = new Date() |
||||
|
||||
// 格式化日期 |
||||
const year = now.getFullYear() |
||||
const month = now.getMonth() + 1 |
||||
const day = now.getDate() |
||||
currentDate.value = `${year}年${month}月${day}日` |
||||
|
||||
// 星期 |
||||
const weekdays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'] |
||||
currentWeekday.value = weekdays[now.getDay()] |
||||
|
||||
// 农历日期计算 |
||||
try { |
||||
const solar = Solar.fromDate(now) |
||||
const lunar = solar.getLunar() |
||||
|
||||
const lunarMonth = lunar.getMonthInChinese() |
||||
const lunarDay = lunar.getDayInChinese() |
||||
|
||||
const lunarString = lunar.toString() |
||||
const isLeapMonth = lunarString.includes('闰') |
||||
|
||||
let monthName = lunarMonth |
||||
|
||||
const monthMap = { |
||||
'正': '正月', '一': '正月', '二': '二月', '三': '三月', |
||||
'四': '四月', '五': '五月', '六': '六月', '七': '七月', |
||||
'八': '八月', '九': '九月', '十': '十月', '冬': '冬月', '腊': '腊月' |
||||
} |
||||
|
||||
if (monthMap[monthName]) { |
||||
monthName = monthMap[monthName] |
||||
} else if (!monthName.includes('月')) { |
||||
monthName = monthName + '月' |
||||
} |
||||
|
||||
if (isLeapMonth && !monthName.startsWith('闰')) { |
||||
monthName = '闰' + monthName |
||||
} |
||||
|
||||
lunarDate.value = `农历${monthName}${lunarDay}` |
||||
} catch (error) { |
||||
console.error('农历计算错误:', error) |
||||
lunarDate.value = '农历计算中...' |
||||
} |
||||
} |
||||
|
||||
// 页面缩放适配 |
||||
const updateScale = () => { |
||||
if (!containerRef.value) return |
||||
|
||||
const windowWidth = window.innerWidth |
||||
const windowHeight = window.innerHeight |
||||
|
||||
// 计算缩放比例 |
||||
const scaleX = windowWidth / BASE_WIDTH |
||||
const scaleY = windowHeight / BASE_HEIGHT |
||||
|
||||
// 取较小的缩放比例,确保内容完全可见 |
||||
const scale = Math.min(scaleX, scaleY) |
||||
|
||||
// 应用缩放 |
||||
containerRef.value.style.transform = `scale(${scale})` |
||||
containerRef.value.style.transformOrigin = 'top left' |
||||
|
||||
// 调整容器位置,使其居中 |
||||
const scaledWidth = BASE_WIDTH * scale |
||||
const scaledHeight = BASE_HEIGHT * scale |
||||
|
||||
const offsetX = (windowWidth - scaledWidth) / 2 |
||||
const offsetY = (windowHeight - scaledHeight) / 2 |
||||
|
||||
containerRef.value.style.left = `${offsetX}px` |
||||
containerRef.value.style.top = `${offsetY}px` |
||||
} |
||||
|
||||
let timer = null |
||||
|
||||
onMounted(() => { |
||||
updateDateTime() |
||||
timer = setInterval(updateDateTime, 60000) // 每分钟更新一次 |
||||
|
||||
// 隐藏页面滚动条 |
||||
document.body.style.overflow = 'hidden' |
||||
document.documentElement.style.overflow = 'hidden' |
||||
|
||||
// 初始化缩放 |
||||
updateScale() |
||||
|
||||
// 监听窗口大小变化 |
||||
window.addEventListener('resize', updateScale) |
||||
}) |
||||
|
||||
onUnmounted(() => { |
||||
if (timer) { |
||||
clearInterval(timer) |
||||
} |
||||
window.removeEventListener('resize', updateScale) |
||||
|
||||
// 恢复页面滚动条 |
||||
document.body.style.overflow = 'auto' |
||||
document.documentElement.style.overflow = 'auto' |
||||
}) |
||||
</script> |
||||
|
||||
<style scoped> |
||||
.outdoor-viewport { |
||||
width: 100vw; |
||||
height: 100vh; |
||||
background: #000000; |
||||
overflow: hidden; |
||||
position: relative; |
||||
} |
||||
|
||||
.outdoor-container { |
||||
width: 3000px; |
||||
height: 1500px; |
||||
background: |
||||
radial-gradient(ellipse at 25% 25%, rgba(64, 158, 255, 0.08) 0%, transparent 50%), |
||||
radial-gradient(ellipse at 75% 75%, rgba(139, 92, 246, 0.06) 0%, transparent 50%), |
||||
linear-gradient(135deg, #0a0e1a 0%, #0f1419 25%, #131825 50%, #0f1419 75%, #0a0e1a 100%); |
||||
display: flex; |
||||
flex-direction: column; |
||||
font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif; |
||||
color: #ffffff; |
||||
overflow: hidden; |
||||
padding: 30px; |
||||
box-sizing: border-box; |
||||
position: absolute; |
||||
} |
||||
|
||||
/* 顶部时间日期天气区域 */ |
||||
.header-datetime { |
||||
height: 80px; |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
margin-bottom: 0; |
||||
} |
||||
|
||||
.datetime-info { |
||||
display: flex; |
||||
align-items: center; |
||||
gap: 50px; |
||||
font-size: 36px; |
||||
font-weight: 600; |
||||
} |
||||
|
||||
.date { |
||||
color: #ffffff; |
||||
font-size: 36px; |
||||
font-weight: 600; |
||||
} |
||||
|
||||
.weekday { |
||||
color: #409EFF; |
||||
font-size: 36px; |
||||
font-weight: 600; |
||||
} |
||||
|
||||
.lunar { |
||||
color: #67C23A; |
||||
font-size: 36px; |
||||
font-weight: 600; |
||||
} |
||||
|
||||
.weather { |
||||
color: #E6A23C; |
||||
font-size: 36px; |
||||
font-weight: 600; |
||||
} |
||||
|
||||
.temperature { |
||||
color: #F56C6C; |
||||
font-size: 36px; |
||||
font-weight: 600; |
||||
} |
||||
|
||||
/* 滚动公告区域 */ |
||||
.announcement-section { |
||||
height: 90px; |
||||
margin-bottom: 25px; |
||||
position: relative; |
||||
} |
||||
|
||||
/* 主内容四象限网格布局 */ |
||||
.main-content { |
||||
flex: 1; |
||||
display: grid; |
||||
grid-template-columns: 1fr 1fr; |
||||
grid-template-rows: 1fr 1fr; |
||||
gap: 25px; |
||||
min-height: 0; |
||||
} |
||||
|
||||
.content-area { |
||||
background: rgba(15, 25, 45, 0.4); |
||||
backdrop-filter: blur(20px) saturate(180%); |
||||
border-radius: 20px; |
||||
border: 1px solid rgba(255, 255, 255, 0.1); |
||||
box-shadow: |
||||
0 12px 48px rgba(0, 0, 0, 0.3), |
||||
0 6px 24px rgba(64, 158, 255, 0.1), |
||||
inset 0 2px 0 rgba(255, 255, 255, 0.1); |
||||
overflow: hidden; |
||||
transition: all 0.3s ease; |
||||
} |
||||
|
||||
.content-area:hover { |
||||
transform: translateY(-3px); |
||||
box-shadow: |
||||
0 18px 72px rgba(0, 0, 0, 0.4), |
||||
0 9px 36px rgba(64, 158, 255, 0.15), |
||||
inset 0 2px 0 rgba(255, 255, 255, 0.15); |
||||
} |
||||
|
||||
.top-left { |
||||
grid-area: 1 / 1 / 2 / 2; |
||||
} |
||||
|
||||
.top-right { |
||||
grid-area: 1 / 2 / 2 / 3; |
||||
} |
||||
|
||||
.bottom-left { |
||||
grid-area: 2 / 1 / 3 / 2; |
||||
} |
||||
|
||||
.bottom-right { |
||||
grid-area: 2 / 2 / 3 / 3; |
||||
} |
||||
|
||||
|
||||
</style> |
||||
Loading…
Reference in new issue