调整了ExchangeMonitor和RealTimeStats组件的字体大小,增加了服务单位的上边距;在路由中添加了OutdoorDisplay组件的路径。

main
Swanky 6 months ago
parent dc5658513c
commit 55a7f3c79c
  1. 7
      src/components/ExchangeMonitor.vue
  2. 359
      src/components/Outdoor/PendingYakData.vue
  3. 552
      src/components/Outdoor/PriceTrendChart.vue
  4. 197
      src/components/Outdoor/ScrollingAnnouncement.vue
  5. 333
      src/components/Outdoor/TransactionSettlement.vue
  6. 225
      src/components/Outdoor/YakTradingOverview.vue
  7. 6
      src/components/RealTimeStats.vue
  8. 6
      src/router/index.js
  9. 303
      src/views/OutdoorDisplay.vue

@ -199,14 +199,14 @@ onUnmounted(() => {
}
.service-label {
font-size: 16px;
font-size: 20px;
color: #a0a8b8;
font-weight: 500;
line-height: 1.2;
}
.service-value {
font-size: 24px;
font-size: 30px;
font-weight: bold;
color: #ffffff;
line-height: 1.2;
@ -242,7 +242,7 @@ onUnmounted(() => {
}
.service-unit {
font-size: 14px;
font-size: 18px;
color: #8cc8ff;
font-weight: normal;
}
@ -254,6 +254,7 @@ onUnmounted(() => {
font-weight: 500;
align-self: flex-start;
line-height: 1;
margin-top: 4px;
}
.service-status.abundant {

@ -0,0 +1,359 @@
<template>
<div class="pending-yak-card">
<div class="card-header">
<div class="title-container">
<h2 class="card-title">待交易牦牛数据</h2>
<div class="title-tibetan">བསཔའགཡགངས</div>
</div>
<div class="pagination-info">
<span> {{ currentPage }} / {{ totalPages }} </span>
<span class="total-count">总计 {{ totalRecords }} 条记录</span>
</div>
</div>
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th>
<div class="header-text">卖家姓名</div>
<div class="header-tibetan">པའ</div>
</th>
<th>
<div class="header-text">车牌号</div>
<div class="header-tibetan">ཊའཨངངས</div>
</th>
<th>
<div class="header-text">联系方式</div>
<div class="header-tibetan">འབཐབས</div>
</th>
<th>
<div class="header-text">待交易牦牛数</div>
<div class="header-tibetan">བསཔའགཡགངས</div>
</th>
</tr>
</thead>
<tbody>
<tr
v-for="(record, index) in currentPageData"
:key="record.id"
:class="{ 'highlight': index % 2 === 0 }"
>
<td class="seller-name">{{ record.sellerName }}</td>
<td class="license-plate">{{ record.licensePlate }}</td>
<td class="contact">{{ record.contact }}</td>
<td class="yak-count">
<span class="count-number">{{ record.yakCount }}</span>
<span class="unit"></span>
</td>
</tr>
</tbody>
</table>
</div>
<div class="table-footer">
<div class="auto-flip-indicator">
<div class="flip-icon"></div>
<span>{{ countdown }}秒后自动翻页</span>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
// -
const allRecords = ref([
{ id: 1, sellerName: '扎西多吉', licensePlate: '藏A·12345', contact: '138****5678', yakCount: 25 },
{ id: 2, sellerName: '次仁央吉', licensePlate: '藏A·23456', contact: '139****6789', yakCount: 18 },
{ id: 3, sellerName: '洛桑旺堆', licensePlate: '藏B·34567', contact: '137****7890', yakCount: 32 },
{ id: 4, sellerName: '普布扎西', licensePlate: '藏A·45678', contact: '136****8901', yakCount: 14 },
{ id: 5, sellerName: '德吉措姆', licensePlate: '藏C·56789', contact: '135****9012', yakCount: 28 },
{ id: 6, sellerName: '旺久多吉', licensePlate: '藏A·67890', contact: '134****0123', yakCount: 21 },
{ id: 7, sellerName: '卓玛拉姆', licensePlate: '藏B·78901', contact: '133****1234', yakCount: 35 },
{ id: 8, sellerName: '格桑花措', licensePlate: '藏A·89012', contact: '132****2345', yakCount: 16 },
{ id: 9, sellerName: '丹增诺布', licensePlate: '藏C·90123', contact: '131****3456', yakCount: 29 },
{ id: 10, sellerName: '白玛次仁', licensePlate: '藏A·01234', contact: '130****4567', yakCount: 22 },
{ id: 11, sellerName: '索朗达瓦', licensePlate: '藏B·12340', contact: '158****5678', yakCount: 19 },
{ id: 12, sellerName: '央金卓玛', licensePlate: '藏A·23451', contact: '157****6789', yakCount: 26 },
{ id: 13, sellerName: '阿旺罗布', licensePlate: '藏C·34562', contact: '156****7890', yakCount: 31 },
{ id: 14, sellerName: '拉巴次仁', licensePlate: '藏A·45673', contact: '155****8901', yakCount: 17 },
{ id: 15, sellerName: '曲珍措姆', licensePlate: '藏B·56784', contact: '154****9012', yakCount: 24 },
{ id: 16, sellerName: '边巴扎西', licensePlate: '藏A·67895', contact: '153****0123', yakCount: 33 },
{ id: 17, sellerName: '尼玛次仁', licensePlate: '藏C·78906', contact: '152****1234', yakCount: 20 },
{ id: 18, sellerName: '桑杰卓玛', licensePlate: '藏A·89017', contact: '151****2345', yakCount: 27 },
{ id: 19, sellerName: '强巴次仁', licensePlate: '藏B·90128', contact: '150****3456', yakCount: 15 },
{ id: 20, sellerName: '次央拉姆', licensePlate: '藏A·01239', contact: '149****4567', yakCount: 30 }
])
//
const pageSize = 8 // 8
const currentPage = ref(1)
const countdown = ref(10) //
//
const totalRecords = computed(() => allRecords.value.length)
const totalPages = computed(() => Math.ceil(totalRecords.value / pageSize))
const currentPageData = computed(() => {
const start = (currentPage.value - 1) * pageSize
const end = start + pageSize
return allRecords.value.slice(start, end)
})
const currentPageTotal = computed(() => {
return currentPageData.value.reduce((sum, record) => sum + record.yakCount, 0)
})
//
let flipTimer = null
let countdownTimer = null
const resetCountdown = () => {
countdown.value = 10
}
const startCountdown = () => {
countdownTimer = setInterval(() => {
countdown.value--
if (countdown.value <= 0) {
flipPage()
resetCountdown()
}
}, 1000)
}
const flipPage = () => {
if (currentPage.value >= totalPages.value) {
currentPage.value = 1
} else {
currentPage.value++
}
}
const startAutoFlip = () => {
resetCountdown()
startCountdown()
}
const stopAutoFlip = () => {
if (flipTimer) {
clearInterval(flipTimer)
flipTimer = null
}
if (countdownTimer) {
clearInterval(countdownTimer)
countdownTimer = null
}
}
//
const updateData = () => {
//
allRecords.value.forEach(record => {
if (Math.random() > 0.8) { // 20%
const change = Math.floor(Math.random() * 5) - 2 // -22
record.yakCount = Math.max(1, record.yakCount + change)
}
})
}
onMounted(() => {
startAutoFlip()
// 30
setInterval(updateData, 30000)
})
onUnmounted(() => {
stopAutoFlip()
})
</script>
<style scoped>
.pending-yak-card {
height: 100%;
background: rgba(15, 25, 45, 0.4);
backdrop-filter: blur(20px) saturate(180%);
border-radius: 32px;
padding: 24px;
display: flex;
flex-direction: column;
box-shadow:
0 16px 64px rgba(0, 0, 0, 0.3),
0 8px 32px rgba(64, 158, 255, 0.1),
inset 0 2px 0 rgba(255, 255, 255, 0.1);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.title-container {
display: flex;
align-items: center;
gap: 16px;
}
.card-title {
font-size: 28px;
font-weight: 700;
background: linear-gradient(135deg, #409EFF 0%, #67C23A 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin: 0;
}
.title-tibetan {
font-size: 28px;
color: #67C23A;
font-weight: 600;
opacity: 0.8;
}
.pagination-info {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
font-size: 14px;
color: #ffffff;
opacity: 0.8;
}
.total-count {
color: #409EFF;
font-weight: 500;
}
.table-container {
flex: 1;
overflow: hidden;
}
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 20px;
}
.data-table thead th {
background: rgba(64, 158, 255, 0.2);
color: #409EFF;
font-weight: 600;
padding: 16px 12px;
text-align: center;
border-bottom: 2px solid rgba(64, 158, 255, 0.3);
vertical-align: middle;
}
.header-text {
font-size: 22px;
line-height: 1.2;
margin-bottom: 4px;
}
.header-tibetan {
font-size: 18px;
opacity: 0.8;
line-height: 1.2;
}
.data-table tbody tr {
transition: background-color 0.3s ease;
}
.data-table tbody tr:hover {
background: rgba(255, 255, 255, 0.05);
}
.data-table tbody tr.highlight {
background: rgba(255, 255, 255, 0.03);
}
.data-table tbody td {
padding: 14px 12px;
text-align: center;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
color: #ffffff;
font-size: 20px;
vertical-align: middle;
}
.seller-name {
font-weight: 600;
font-size: 22px;
color: #67C23A !important;
}
.license-plate {
font-family: 'Courier New', monospace;
font-weight: 500;
font-size: 20px;
color: #409EFF !important;
}
.contact {
font-family: 'Courier New', monospace;
font-size: 20px;
color: #E6A23C !important;
}
.yak-count {
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
}
.count-number {
font-size: 26px;
font-weight: 700;
color: #F56C6C !important;
}
.unit {
font-size: 18px;
color: #ffffff;
opacity: 0.7;
}
.table-footer {
display: flex;
justify-content: center;
align-items: center;
margin-top: 16px;
padding-top: 12px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.auto-flip-indicator {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
color: #ffffff;
opacity: 0.8;
}
.flip-icon {
font-size: 16px;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 0.6; }
50% { opacity: 1; }
}
.summary-stats {
font-size: 18px;
font-weight: 600;
color: #67C23A;
}
</style>

@ -0,0 +1,552 @@
<template>
<div class="price-trend-card">
<div class="card-header">
<div class="title-container">
<h2 class="card-title">
活牛鲜肉价格趋势
<span class="title-tibetan">ཆགསདངའག</span>
</h2>
</div>
<div class="period-buttons">
<button
v-for="(period, index) in periodTypes"
:key="period"
:class="['period-btn', { active: currentPeriodIndex === index }]"
@click="switchToPeriod(index)"
>
{{ priceData[period]?.title || '加载中' }}
</button>
</div>
</div>
<div v-if="loading" class="loading-message">
<div class="loading-spinner"></div>
<div>正在加载价格数据...</div>
</div>
<div v-else-if="error" class="error-message">
<div>{{ error }}</div>
</div>
<div v-else class="chart-container">
<div ref="chartRef" class="chart"></div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import * as echarts from 'echarts'
const chartRef = ref(null)
let chartInstance = null
let switchTimer = null
//
const priceData = ref({})
const loading = ref(true)
const error = ref('')
const currentPeriodType = ref('daily')
const currentPeriod = ref({ title: '日统计', title_tibetan: 'ཉན་ར་གང་རས།' })
const autoSwitch = ref(true)
//
const periodTypes = ['daily', 'weekly', 'monthly']
const currentPeriodIndex = ref(0)
//
const generateDynamicData = () => {
const now = new Date()
// - 10
const dailyDates = []
const dailyLiveCattle = []
const dailyFreshMeat = []
for (let i = 9; i >= 0; i--) {
const date = new Date(now.getTime() - i * 24 * 60 * 60 * 1000)
dailyDates.push(`${date.getMonth() + 1}/${date.getDate()}`)
// /
dailyLiveCattle.push(Math.round((28 + Math.sin(i * 0.5) * 2 + Math.cos(i * 0.3) * 1.5) * 10) / 10)
dailyFreshMeat.push(Math.round((76.5 + Math.sin(i * 0.4) * 1.2 + Math.cos(i * 0.6) * 0.8) * 10) / 10)
}
// - 10
const weeklyDates = []
const weeklyLiveCattle = []
const weeklyFreshMeat = []
for (let i = 9; i >= 0; i--) {
const startDate = new Date(now.getTime() - i * 7 * 24 * 60 * 60 * 1000)
const endDate = new Date(startDate.getTime() + 6 * 24 * 60 * 60 * 1000)
//
const startMonth = startDate.getMonth() + 1
const startDay = startDate.getDate()
const endMonth = endDate.getMonth() + 1
const endDay = endDate.getDate()
if (startMonth === endMonth) {
weeklyDates.push(`${startMonth}/${startDay}-${endDay}`)
} else {
weeklyDates.push(`${startMonth}/${startDay}-${endMonth}/${endDay}`)
}
// /
weeklyLiveCattle.push(Math.round((29.5 + Math.sin(i * 0.6) * 2.5 + Math.cos(i * 0.4) * 2.0) * 10) / 10)
weeklyFreshMeat.push(Math.round((78.8 + Math.sin(i * 0.7) * 2.0 + Math.cos(i * 0.5) * 1.5) * 10) / 10)
}
// - 12
const monthlyDates = []
const monthlyLiveCattle = []
const monthlyFreshMeat = []
for (let i = 11; i >= 0; i--) {
const monthDate = new Date(now.getFullYear(), now.getMonth() - i, 1)
const year = monthDate.getFullYear()
const month = monthDate.getMonth() + 1
monthlyDates.push(`${year}${month}`)
// /
monthlyLiveCattle.push(Math.round((27.5 + Math.sin(i * 0.8) * 3.5 + Math.cos(i * 0.6) * 2.8) * 10) / 10)
monthlyFreshMeat.push(Math.round((76.0 + Math.sin(i * 0.9) * 3.0 + Math.cos(i * 0.7) * 2.5) * 10) / 10)
}
return {
daily: {
title: '日统计',
title_tibetan: 'ཉན་ར་གང་རས།',
dates: dailyDates,
liveCattlePrice: dailyLiveCattle,
freshMeatPrice: dailyFreshMeat
},
weekly: {
title: '周统计',
title_tibetan: 'གཟའ་འཁར་གང་རས།',
dates: weeklyDates,
liveCattlePrice: weeklyLiveCattle,
freshMeatPrice: weeklyFreshMeat
},
monthly: {
title: '月统计',
title_tibetan: 'ཟ་བའ་གང་རས།',
dates: monthlyDates,
liveCattlePrice: monthlyLiveCattle,
freshMeatPrice: monthlyFreshMeat
}
}
}
// JSON
const loadPriceData = async () => {
try {
loading.value = true
error.value = ''
// 使
priceData.value = generateDynamicData()
//
updateCurrentPeriod()
console.log('价格数据生成成功:', priceData.value)
} catch (err) {
console.error('生成价格数据失败:', err)
error.value = '生成价格数据失败,请稍后重试'
// 使
const fallbackDate = new Date()
priceData.value = {
daily: {
title: '日统计',
title_tibetan: 'ཉན་ར་གང་རས།',
dates: [`${fallbackDate.getMonth() + 1}/${fallbackDate.getDate()}`],
liveCattlePrice: [5600],
freshMeatPrice: [76.5]
}
}
updateCurrentPeriod()
} finally {
loading.value = false
}
}
//
const updateCurrentPeriod = () => {
const periodType = periodTypes[currentPeriodIndex.value]
currentPeriodType.value = periodType
if (priceData.value[periodType]) {
currentPeriod.value = {
title: priceData.value[periodType].title,
title_tibetan: priceData.value[periodType].title_tibetan
}
//
if (chartInstance && !loading.value) {
initChart()
}
}
}
//
const switchPeriod = () => {
currentPeriodIndex.value = (currentPeriodIndex.value + 1) % periodTypes.length
updateCurrentPeriod()
}
//
const switchToPeriod = (index) => {
currentPeriodIndex.value = index
updateCurrentPeriod()
console.log(`手动切换到: ${periodTypes[index]}`)
}
// /
const toggleAutoSwitch = () => {
autoSwitch.value = !autoSwitch.value
if (autoSwitch.value) {
//
if (switchTimer) {
clearInterval(switchTimer)
}
switchTimer = setInterval(() => {
switchPeriod()
}, 10000)
console.log('启动自动切换模式')
} else {
//
if (switchTimer) {
clearInterval(switchTimer)
switchTimer = null
}
console.log('切换到手动模式')
}
}
const initChart = () => {
if (!chartRef.value || loading.value) return
const currentData = priceData.value[currentPeriodType.value]
if (!currentData) return
if (!chartInstance) {
chartInstance = echarts.init(chartRef.value)
}
const option = {
backgroundColor: 'transparent',
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(15, 25, 45, 0.95)',
borderColor: '#409EFF',
borderWidth: 2,
textStyle: {
color: '#fff',
fontSize: 18
},
formatter: function(params) {
let result = `<div style="padding: 15px;">`
result += `<strong style="color: #409EFF; font-size: 20px;">${params[0].axisValue}</strong><br/>`
params.forEach(param => {
result += `<span style="color: ${param.color}; font-size: 18px;">● ${param.seriesName}: ${param.value.toFixed(param.seriesName.includes('活牛') ? 1 : 1)}元/斤</span><br/>`
})
result += `</div>`
return result
}
},
legend: {
data: ['活牛价格', '鲜肉价格'],
top: '5%',
textStyle: {
color: '#a0a8b8',
fontSize: 20
},
itemWidth: 30,
itemHeight: 20
},
grid: {
left: '8%',
right: '8%',
bottom: '10%',
top: '15%',
containLabel: true
},
xAxis: {
type: 'category',
data: currentData.dates,
axisLine: {
lineStyle: {
color: 'rgba(255, 255, 255, 0.3)',
width: 2
}
},
axisLabel: {
color: '#a0a8b8',
fontSize: 18
},
splitLine: {
show: false
}
},
yAxis: {
type: 'value',
name: '价格(元/斤)',
nameTextStyle: {
color: '#ffffff',
fontSize: 20,
padding: [0, 0, 0, 20]
},
position: 'left',
axisLine: {
lineStyle: {
color: 'rgba(255, 255, 255, 0.3)',
width: 2
}
},
axisLabel: {
color: '#a0a8b8',
fontSize: 18,
formatter: '{value}'
},
splitLine: {
lineStyle: {
color: 'rgba(255, 255, 255, 0.1)',
width: 1
}
}
},
series: [
{
name: '活牛价格',
type: 'line',
data: currentData.liveCattlePrice,
smooth: true,
symbol: 'circle',
symbolSize: 12,
lineStyle: {
color: '#409EFF',
width: 6
},
itemStyle: {
color: '#409EFF'
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(64, 158, 255, 0.3)' },
{ offset: 1, color: 'rgba(64, 158, 255, 0.05)' }
]
}
}
},
{
name: '鲜肉价格',
type: 'line',
data: currentData.freshMeatPrice,
smooth: true,
symbol: 'circle',
symbolSize: 12,
lineStyle: {
color: '#67C23A',
width: 6
},
itemStyle: {
color: '#67C23A'
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(103, 194, 58, 0.3)' },
{ offset: 1, color: 'rgba(103, 194, 58, 0.05)' }
]
}
}
}
]
}
chartInstance.setOption(option, true)
}
onMounted(async () => {
//
await loadPriceData()
//
initChart()
//
window.addEventListener('resize', () => {
chartInstance?.resize()
})
// autoSwitch
if (autoSwitch.value) {
switchTimer = setInterval(() => {
switchPeriod()
}, 10000)
console.log('PriceTrendChart组件已加载,自动切换模式已启动')
} else {
console.log('PriceTrendChart组件已加载,手动模式')
}
})
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose()
chartInstance = null
}
if (switchTimer) {
clearInterval(switchTimer)
switchTimer = null
}
window.removeEventListener('resize', () => {
chartInstance?.resize()
})
})
</script>
<style scoped>
.price-trend-card {
height: 100%;
background: rgba(15, 25, 45, 0.4);
backdrop-filter: blur(20px) saturate(180%);
border-radius: 32px;
padding: 24px;
display: flex;
flex-direction: column;
box-shadow:
0 16px 64px rgba(0, 0, 0, 0.3),
0 8px 32px rgba(64, 158, 255, 0.1),
inset 0 2px 0 rgba(255, 255, 255, 0.1);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.title-container {
flex: 1;
}
.card-title {
font-size: 28px;
font-weight: 700;
background: linear-gradient(135deg, #409EFF 0%, #67C23A 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin: 0;
text-align: left;
line-height: 1.2;
}
.title-tibetan {
font-size: 28px;
font-weight: 600;
color: #a0a8b8;
margin-left: 16px;
display: inline;
white-space: nowrap;
}
.loading-message {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100%;
color: #a0a8b8;
font-size: 32px;
}
.loading-spinner {
width: 60px;
height: 60px;
border: 4px solid rgba(64, 158, 255, 0.2);
border-top: 4px solid #409EFF;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 20px;
}
.error-message {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
color: #F56C6C;
font-size: 32px;
text-align: center;
}
.chart-container {
flex: 1;
min-height: 0;
}
.chart {
width: 100%;
height: 100%;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.period-buttons {
display: flex;
gap: 12px;
flex-shrink: 0;
}
.period-btn {
background: rgba(64, 158, 255, 0.1);
border: 2px solid rgba(64, 158, 255, 0.3);
border-radius: 8px;
padding: 8px 16px;
cursor: pointer;
transition: all 0.3s ease;
color: #a0a8b8;
font-size: 20px;
font-weight: 600;
min-width: 80px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
}
.period-btn:hover {
background: rgba(64, 158, 255, 0.2);
border-color: rgba(64, 158, 255, 0.5);
transform: translateY(-2px);
}
.period-btn.active {
background: rgba(64, 158, 255, 0.3);
border-color: #409EFF;
color: #409EFF;
box-shadow: 0 8px 24px rgba(64, 158, 255, 0.3);
}
</style>

@ -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,333 @@
<template>
<div class="settlement-card">
<div class="card-header">
<div class="title-container">
<h2 class="card-title">交易结算数据</h2>
<div class="title-tibetan">འདངས</div>
</div>
<div class="pagination-info">
<span> {{ currentPage + 1 }} / {{ totalPages }} </span>
<span class="total-count">总计 {{ settlementData.length }} 条记录</span>
</div>
</div>
<div class="table-container">
<table class="settlement-table">
<thead>
<tr>
<th>
<div class="header-text">结算方</div>
<div class="header-tibetan"></div>
</th>
<th>
<div class="header-text">车牌号</div>
<div class="header-tibetan">ཨངངས</div>
</th>
<th>
<div class="header-text">结算牦牛数</div>
<div class="header-tibetan">གཡགངས</div>
</th>
<th>
<div class="header-text">结算时间</div>
<div class="header-tibetan"></div>
</th>
</tr>
</thead>
<tbody>
<tr v-for="(record, index) in currentPageData" :key="record.id">
<td>
<div class="seller-name">{{ record.settler }}</div>
</td>
<td>
<div class="license-plate">{{ record.licensePlate }}</div>
</td>
<td>
<div class="yak-count">{{ record.yakCount }}</div>
</td>
<td>
<div class="settlement-time">{{ record.settlementTime }}</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="table-footer">
<div class="auto-flip-indicator">
<div class="flip-icon"></div>
<span>{{ countdown }}秒后自动翻页</span>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
//
const currentPage = ref(0)
const pageSize = 8
const countdown = ref(10)
let timer = null
let countdownTimer = null
//
const settlementData = ref([
{ id: 1, settler: '扎西多吉', licensePlate: '藏A·12345', yakCount: 15, settlementTime: '09:30' },
{ id: 2, settler: '次仁央吉', licensePlate: '藏B·67890', yakCount: 8, settlementTime: '09:45' },
{ id: 3, settler: '洛桑旺堆', licensePlate: '藏C·11111', yakCount: 22, settlementTime: '10:15' },
{ id: 4, settler: '普布扎西', licensePlate: '藏A·22222', yakCount: 12, settlementTime: '10:30' },
{ id: 5, settler: '德吉梅朵', licensePlate: '藏B·33333', yakCount: 18, settlementTime: '11:00' },
{ id: 6, settler: '格桑平措', licensePlate: '藏C·44444', yakCount: 6, settlementTime: '11:20' },
{ id: 7, settler: '拉巴次仁', licensePlate: '藏A·55555', yakCount: 25, settlementTime: '11:45' },
{ id: 8, settler: '卓玛央金', licensePlate: '藏B·66666', yakCount: 14, settlementTime: '12:10' },
{ id: 9, settler: '索朗多吉', licensePlate: '藏C·77777', yakCount: 20, settlementTime: '12:30' },
{ id: 10, settler: '白玛曲珍', licensePlate: '藏A·88888', yakCount: 9, settlementTime: '13:00' },
{ id: 11, settler: '丹增诺布', licensePlate: '藏B·99999', yakCount: 16, settlementTime: '13:25' },
{ id: 12, settler: '央吉拉姆', licensePlate: '藏C·00000', yakCount: 11, settlementTime: '13:50' },
{ id: 13, settler: '土登群培', licensePlate: '藏A·12121', yakCount: 28, settlementTime: '14:15' },
{ id: 14, settler: '曲珍旺姆', licensePlate: '藏B·23232', yakCount: 7, settlementTime: '14:40' },
{ id: 15, settler: '阿旺洛桑', licensePlate: '藏C·34343', yakCount: 19, settlementTime: '15:05' },
{ id: 16, settler: '措姆拉姆', licensePlate: '藏A·45454', yakCount: 13, settlementTime: '15:30' },
{ id: 17, settler: '平措扎西', licensePlate: '藏B·56565', yakCount: 24, settlementTime: '15:55' },
{ id: 18, settler: '桑杰卓玛', licensePlate: '藏C·67676', yakCount: 10, settlementTime: '16:20' },
{ id: 19, settler: '尼玛次仁', licensePlate: '藏A·78787', yakCount: 17, settlementTime: '16:45' },
{ id: 20, settler: '央金拉姆', licensePlate: '藏B·89898', yakCount: 21, settlementTime: '17:10' }
])
//
const totalPages = computed(() => {
return Math.ceil(settlementData.value.length / pageSize)
})
//
const currentPageData = computed(() => {
const start = currentPage.value * pageSize
const end = start + pageSize
return settlementData.value.slice(start, end)
})
//
const currentPageTotal = computed(() => {
return currentPageData.value.reduce((sum, record) => sum + record.yakCount, 0)
})
//
const nextPage = () => {
currentPage.value = (currentPage.value + 1) % totalPages.value
countdown.value = 10
}
//
const startTimer = () => {
//
timer = setInterval(() => {
nextPage()
}, 10000)
//
countdownTimer = setInterval(() => {
countdown.value--
if (countdown.value <= 0) {
countdown.value = 10
}
}, 1000)
}
//
const stopTimer = () => {
if (timer) {
clearInterval(timer)
timer = null
}
if (countdownTimer) {
clearInterval(countdownTimer)
countdownTimer = null
}
}
onMounted(() => {
startTimer()
})
onUnmounted(() => {
stopTimer()
})
</script>
<style scoped>
.settlement-card {
height: 100%;
background: rgba(15, 25, 45, 0.4);
backdrop-filter: blur(20px) saturate(180%);
border-radius: 32px;
padding: 24px;
display: flex;
flex-direction: column;
box-shadow:
0 16px 64px rgba(0, 0, 0, 0.3),
0 8px 32px rgba(64, 158, 255, 0.1),
inset 0 2px 0 rgba(255, 255, 255, 0.1);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.title-container {
display: flex;
align-items: center;
gap: 16px;
}
.card-title {
font-size: 28px;
font-weight: 700;
background: linear-gradient(135deg, #409EFF 0%, #67C23A 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin: 0;
}
.title-tibetan {
font-size: 28px;
color: #67C23A;
font-weight: 600;
opacity: 0.8;
}
.pagination-info {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
font-size: 14px;
color: #ffffff;
opacity: 0.8;
}
.total-count {
color: #409EFF;
font-weight: 500;
}
.table-container {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
}
.settlement-table {
width: 100%;
border-collapse: collapse;
font-size: 20px;
flex: 1;
}
.settlement-table thead th {
background: rgba(64, 158, 255, 0.2);
color: #409EFF;
font-weight: 600;
padding: 16px 12px;
text-align: center;
border-bottom: 2px solid rgba(64, 158, 255, 0.3);
vertical-align: middle;
}
.header-text {
font-size: 22px;
line-height: 1.2;
margin-bottom: 4px;
}
.header-tibetan {
font-size: 18px;
opacity: 0.8;
line-height: 1.2;
}
.settlement-table tbody tr {
transition: background-color 0.3s ease;
}
.settlement-table tbody tr:hover {
background: rgba(255, 255, 255, 0.05);
}
.settlement-table tbody tr:nth-child(even) {
background: rgba(255, 255, 255, 0.03);
}
.settlement-table tbody td {
padding: 14px 12px;
text-align: center;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
color: #ffffff;
font-size: 20px;
vertical-align: middle;
}
.seller-name {
font-weight: 600;
font-size: 22px;
color: #67C23A !important;
}
.license-plate {
font-family: 'Courier New', monospace;
font-weight: 500;
font-size: 20px;
color: #409EFF !important;
}
.yak-count {
font-size: 26px;
font-weight: 700;
color: #F56C6C !important;
}
.settlement-time {
font-size: 20px;
color: #E6A23C !important;
}
.table-footer {
display: flex;
justify-content: center;
align-items: center;
margin-top: 16px;
padding-top: 12px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
flex-shrink: 0;
}
.auto-flip-indicator {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
color: #ffffff;
opacity: 0.8;
}
.flip-icon {
font-size: 16px;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 0.6; }
50% { opacity: 1; }
}
.summary-stats {
font-size: 18px;
font-weight: 600;
color: #67C23A;
}
</style>

@ -0,0 +1,225 @@
<template>
<div class="trading-overview-card">
<div class="card-header">
<div class="title-container">
<h2 class="card-title">牦牛实时交易概况</h2>
<div class="title-tibetan">འབགཡགའདགནསངས</div>
</div>
</div>
<div class="overview-content">
<div class="stats-grid">
<div class="stat-item">
<div class="stat-label">
<span class="chinese">今日入场牦牛数</span>
<span class="tibetan">གསཔའགཡགངས</span>
</div>
<div class="stat-value primary">{{ todayEntryYaks }}</div>
<div class="stat-unit"></div>
</div>
<div class="stat-item">
<div class="stat-label">
<span class="chinese">已交易牦牛数</span>
<span class="tibetan">པའགཡགངས</span>
</div>
<div class="stat-value success">{{ tradedYaks }}</div>
<div class="stat-unit"></div>
</div>
<div class="stat-item">
<div class="stat-label">
<span class="chinese">待交易牦牛数</span>
<span class="tibetan">བསཔའགཡགངས</span>
</div>
<div class="stat-value warning">{{ waitingYaks }}</div>
<div class="stat-unit"></div>
</div>
<div class="stat-item">
<div class="stat-label">
<span class="chinese">卖家人数</span>
<span class="tibetan">པའངས</span>
</div>
<div class="stat-value info">{{ sellerCount }}</div>
<div class="stat-unit"></div>
</div>
<div class="stat-item">
<div class="stat-label">
<span class="chinese">剩余车位</span>
<span class="tibetan">པའགནསསའངས</span>
</div>
<div class="stat-value danger">{{ remainingParkingSpots }}</div>
<div class="stat-unit"></div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
//
const todayEntryYaks = ref(1256) //
const tradedYaks = ref(847) //
const waitingYaks = ref(409) //
const sellerCount = ref(168) //
const remainingParkingSpots = ref(32) //
//
onMounted(() => {
setInterval(() => {
//
const newEntry = Math.floor(Math.random() * 5)
todayEntryYaks.value += newEntry
//
const newTrades = Math.floor(Math.random() * 3)
if (waitingYaks.value >= newTrades) {
tradedYaks.value += newTrades
waitingYaks.value -= newTrades
}
// -
waitingYaks.value += (newEntry - newTrades)
if (waitingYaks.value < 0) waitingYaks.value = 0
//
if (Math.random() > 0.7) {
sellerCount.value += Math.floor(Math.random() * 3) - 1 // -11
remainingParkingSpots.value += Math.floor(Math.random() * 5) - 2 // -22
//
if (sellerCount.value < 100) sellerCount.value = 100
if (sellerCount.value > 300) sellerCount.value = 300
if (remainingParkingSpots.value < 0) remainingParkingSpots.value = 0
if (remainingParkingSpots.value > 50) remainingParkingSpots.value = 50
}
}, 30000) // 30
})
</script>
<style scoped>
.trading-overview-card {
height: 100%;
background: rgba(15, 25, 45, 0.4);
backdrop-filter: blur(20px) saturate(180%);
border-radius: 32px;
padding: 24px;
display: flex;
flex-direction: column;
box-shadow:
0 16px 64px rgba(0, 0, 0, 0.3),
0 8px 32px rgba(64, 158, 255, 0.1),
inset 0 2px 0 rgba(255, 255, 255, 0.1);
}
.card-header {
margin-bottom: 40px;
}
.title-container {
display: flex;
align-items: center;
gap: 32px;
}
.card-title {
font-size: 28px;
font-weight: 700;
background: linear-gradient(135deg, #409EFF 0%, #67C23A 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin: 0;
text-align: left;
}
.title-tibetan {
font-size: 28px;
color: #67C23A;
font-weight: 600;
opacity: 0.8;
}
.overview-content {
flex: 1;
display: flex;
flex-direction: column;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 20px;
flex: 1;
}
.stat-item {
background: rgba(255, 255, 255, 0.05);
border-radius: 20px;
padding: 24px;
text-align: center;
border: 1px solid rgba(255, 255, 255, 0.1);
display: flex;
flex-direction: column;
justify-content: center;
min-height: 0;
}
.stat-label {
margin-bottom: 20px;
display: flex;
flex-direction: column;
gap: 8px;
}
.stat-label .chinese {
font-size: 28px;
color: #ffffff;
font-weight: 600;
line-height: 1.2;
}
.stat-label .tibetan {
font-size: 28px;
color: #67C23A;
opacity: 0.8;
line-height: 1.2;
}
.stat-value {
font-size: 64px;
font-weight: 700;
margin-bottom: 8px;
line-height: 1;
}
.stat-value.primary {
color: #409EFF;
}
.stat-value.success {
color: #67C23A;
}
.stat-value.warning {
color: #E6A23C;
}
.stat-value.danger {
color: #F56C6C;
}
.stat-value.info {
color: #17A2B8;
}
.stat-unit {
font-size: 32px;
color: #a0a8b8;
margin-top: 8px;
}
</style>

@ -252,7 +252,7 @@ onUnmounted(() => {
justify-content: space-between;
align-items: center;
text-align: center;
padding: 20px 12px 16px 12px;
/* padding: 20px 12px 16px 12px; */
background: url('../images/数据底座.png') center/contain no-repeat;
background-size: 80% 70%;
border-radius: 8px;
@ -277,7 +277,7 @@ onUnmounted(() => {
align-items: center;
justify-content: center;
flex-direction: column;
font-size: 22px;
font-size: 30px;
font-weight: 900;
color: #ffffff;
text-shadow:
@ -291,7 +291,7 @@ onUnmounted(() => {
}
.stat-label {
font-size: 18px;
font-size: 24px;
font-weight: 700;
color: #409EFF;
/* text-shadow:

@ -1,6 +1,7 @@
import { createRouter, createWebHistory } from 'vue-router'
import Dashboard from '../views/Dashboard.vue'
import TV86Display from '../views/TV86Display.vue'
import OutdoorDisplay from '../views/OutdoorDisplay.vue'
const routes = [
{
@ -12,6 +13,11 @@ const routes = [
path: '/tv86',
name: 'TV86Display',
component: TV86Display
},
{
path: '/outdoor',
name: 'OutdoorDisplay',
component: OutdoorDisplay
}
]

@ -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…
Cancel
Save