数据大屏

master
lion 3 days ago
parent de783bd825
commit 3ec41ee520

@ -23,13 +23,19 @@ import { alumniNews } from '@/api/home/index'
export default {
name: 'AlumniNews',
props: {
displayMode: { type: Boolean, default: false }
displayMode: { type: Boolean, default: false },
rotationPaused: { type: Boolean, default: false }
},
data() { return { rows: [], page: 1, lastPage: 1, total: 0, loading: false, error: false, sequence: 0, rotationTimer: null } },
created() { this.load(1) },
mounted() {
if (this.displayMode) this.startRotation()
},
watch: {
rotationPaused() {
if (this.displayMode) this.startRotation()
}
},
beforeDestroy() {
clearInterval(this.rotationTimer)
this.sequence++
@ -38,11 +44,16 @@ export default {
startRotation() {
clearInterval(this.rotationTimer)
this.rotationTimer = setInterval(() => {
if (!this.loading) this.load(this.page % this.lastPage + 1)
if (this.loading) return
if (this.rotationPaused) {
this.load(this.page)
} else {
this.load(this.page % this.lastPage + 1, true)
}
}, 15000)
},
changePage(direction) {
if (this.loading) return
if (this.loading && !this.displayMode) return
const target = this.page + direction
if (this.displayMode) {
if (this.lastPage <= 1) return
@ -56,14 +67,14 @@ export default {
if (typeof value !== 'string' || !/^https?:\/\//i.test(value.trim())) return ''
try { const url = new URL(value.trim()); return ['http:', 'https:'].includes(url.protocol) ? url.href : '' } catch (_) { return '' }
},
async load(page) {
async load(page, automatic = false) {
const sequence = ++this.sequence
this.loading = true
this.error = false
try {
const result = await alumniNews({ page }, this.displayMode)
if (sequence !== this.sequence) return
if (page > result.last_page && result.last_page >= 1) return this.load(result.last_page)
if (sequence !== this.sequence || (automatic && this.rotationPaused)) return
if (page > result.last_page && result.last_page >= 1) return this.load(result.last_page, automatic)
this.rows = result.data
this.page = result.current_page
this.lastPage = Math.max(1, result.last_page)

@ -1,5 +1,5 @@
<template>
<div ref="dataScreen" class="data-screen" :class="{ 'display-screen': displayMode }">
<div ref="dataScreen" class="data-screen" :class="{ 'display-screen': displayMode, 'remote-active': displayRemoteActive }">
<!-- 标题 -->
<!-- 标题 -->
@ -256,7 +256,7 @@
{{ tab.label }}
</div>
</div>
<alumni-news :display-mode="displayMode" />
<alumni-news :display-mode="displayMode" :rotation-paused="displayRemoteActive" />
</div>
</div>
</div>
@ -279,6 +279,8 @@ export default {
return {
displayDataState: 'loading',
displayCourseRowStyle: null,
displayRemoteActive: false,
displayRemoteIdleTimer: null,
displayCourseTimer: null,
displayMapTimer: null,
isFullscreen: false,
@ -559,6 +561,7 @@ export default {
document.removeEventListener('fullscreenchange', this.onFullscreenChange)
document.removeEventListener('webkitfullscreenchange', this.onFullscreenChange)
document.removeEventListener('keydown', this.handleDisplayKeydown, true)
clearTimeout(this.displayRemoteIdleTimer)
clearInterval(this.displayCourseTimer)
clearInterval(this.displayMapTimer)
}
@ -621,6 +624,20 @@ export default {
const button = this.$refs.fullscreenButton || screen.querySelector('.year-button.active, .map-tabs .active')
if (button) button.focus()
},
registerDisplayRemoteActivity() {
this.displayRemoteActive = true
clearTimeout(this.displayRemoteIdleTimer)
this.displayRemoteIdleTimer = setTimeout(() => {
this.displayRemoteActive = false
this.startDisplayCourseRotation()
this.startDisplayMapRotation()
this.startCourseRotation()
this.startCourseSystemRotation()
this.startRankingRotation()
if (this.mapType === 'national') this.startNationalRankingRotation()
this.scrollToActiveButton()
}, 60000)
},
handleDisplayKeydown(event) {
const screen = this.$refs.dataScreen
const focused = document.activeElement
@ -629,15 +646,20 @@ export default {
}
const legacyKeys = { 37: 'ArrowLeft', 38: 'ArrowUp', 39: 'ArrowRight', 40: 'ArrowDown' }
const direction = directions[event.key] || directions[legacyKeys[event.keyCode]]
if (['Enter', 'OK', 'Select'].includes(event.key) || event.keyCode === 13 || event.keyCode === 23) {
const confirm = ['Enter', 'OK', 'Select'].includes(event.key) || event.keyCode === 13 || event.keyCode === 23
if (!direction && !confirm) return
this.registerDisplayRemoteActivity()
if (confirm) {
if (screen.contains(focused) && focused.tagName === 'BUTTON') {
event.preventDefault()
event.stopPropagation()
if (!event.repeat) focused.click()
} else {
this.focusDisplayControl()
}
return
}
if (!direction) return
event.preventDefault()
event.stopPropagation()
@ -669,6 +691,7 @@ export default {
clearInterval(this.displayCourseTimer)
if (this.yearConfigs.length <= 1) return
this.displayCourseTimer = setInterval(() => {
if (this.displayRemoteActive) return
this.switchYear((this.currentYearIndex + 1) % this.yearConfigs.length)
}, 12000)
},
@ -679,6 +702,7 @@ export default {
startDisplayMapRotation() {
clearInterval(this.displayMapTimer)
this.displayMapTimer = setInterval(() => {
if (this.displayRemoteActive) return
this.mapType = this.mapType === 'suzhou' ? 'national' : 'suzhou'
}, 12000)
},
@ -957,7 +981,7 @@ export default {
if (!body || !this.currentCourseTypes.length) return
const available = body.getBoundingClientRect().height
if (!available) return
const rowHeight = Math.min(window.innerHeight * 0.03, available / this.currentCourseTypes.length)
const rowHeight = Math.min(window.innerHeight * 0.042, available / this.currentCourseTypes.length)
this.displayCourseRowStyle = {
'--display-course-row-height': `${rowHeight}px`,
'--display-course-font-size': `${Math.min(window.innerWidth * 0.01, rowHeight * 0.72)}px`
@ -1079,6 +1103,7 @@ export default {
}
if (!this.rankingPages.length || this.rankingPages.length <= 1) return
this.rankingTimer = setInterval(() => {
if (this.displayRemoteActive) return
this.rankingPageIndex = (this.rankingPageIndex + 1) % this.rankingPages.length
}, 5000)
},
@ -1090,6 +1115,7 @@ export default {
}
if (!this.nationalRankingPages.length || this.nationalRankingPages.length <= 1) return
this.nationalRankingTimer = setInterval(() => {
if (this.displayRemoteActive) return
this.nationalRankingPageIndex = (this.nationalRankingPageIndex + 1) % this.nationalRankingPages.length
}, 5000)
},
@ -1099,6 +1125,7 @@ export default {
}
if (!this.courseGroups.length) return
this.courseTimer = setInterval(() => {
if (this.displayRemoteActive) return
this.courseGroupIndex = (this.courseGroupIndex + 1) % this.courseGroups.length
}, 5000)
},
@ -1108,6 +1135,7 @@ export default {
}
if (!this.courseSystemPages.length || this.courseSystemPages.length <= 1) return
this.courseSystemTimer = setInterval(() => {
if (this.displayRemoteActive) return
this.courseSystemPageIndex = (this.courseSystemPageIndex + 1) % this.courseSystemPages.length
}, 3000)
},
@ -1126,7 +1154,7 @@ export default {
scrollToActiveButton() {
this.$nextTick(() => {
const buttonsContainer = this.$refs.yearButtons
if (!buttonsContainer || (this.displayMode && buttonsContainer.contains(document.activeElement))) return
if (!buttonsContainer || (this.displayRemoteActive && buttonsContainer.contains(document.activeElement))) return
// 通过查询选择器找到激活的按钮
const activeButton = buttonsContainer.querySelector('.year-button.active')
@ -2285,8 +2313,24 @@ export default {
}
::v-deep button:focus {
outline: 2px solid #ffe3a1;
outline-offset: -2px;
outline: none;
}
.map-tabs .tab { transition: none; }
&.remote-active {
::v-deep button:focus {
outline: 2px solid #ffe3a1;
outline-offset: -2px;
}
.map-tabs .tab:focus {
position: relative;
z-index: 1;
outline: none;
color: #ffe3a1;
filter: drop-shadow(0 0 3px #ffe3a1) drop-shadow(0 0 1px #ffe3a1);
}
}
.course-system-container .table-cell {
@ -2316,12 +2360,6 @@ export default {
cursor: pointer;
}
.display-controls button:focus,
.display-status button:focus {
outline: 2px solid #f4dcae;
outline-offset: 3px;
}
.display-message {
position: absolute;
top: 100%;

Loading…
Cancel
Save