大湾社区城市书房智慧大屏
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

408 lines
14 KiB

5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
  1. <template>
  2. <div class="page-wrapper page-four-wrapper">
  3. <div class="page-four">
  4. <div class="four-video">
  5. <!-- {{ slideData[videoIndex].title }} -->
  6. <h4>宣传视频</h4>
  7. <el-carousel
  8. ref="carousel"
  9. indicator-position="none"
  10. :autoplay="false"
  11. :autoplay-hover-pause="true"
  12. @setActiveItem="setActiveItem"
  13. @change="carouselChange"
  14. >
  15. <div v-if="slideData && slideData.length > 0">
  16. <el-carousel-item v-for="(item, index) in slideData" :key="index">
  17. <video
  18. ref="videos"
  19. class="tsgz-video"
  20. width="100%"
  21. height="100%"
  22. controls
  23. preload="auto"
  24. :src="item.cover"
  25. :poster="poster"
  26. autoplay
  27. type="video/mp4"
  28. muted
  29. @ended="playNextVideo(index)"
  30. @loadedmetadata="playVideo"
  31. >
  32. 您的浏览器不支持 video 标签
  33. </video>
  34. </el-carousel-item>
  35. </div>
  36. <el-empty
  37. v-else
  38. description="暂无视频"
  39. style="height: 710px"
  40. :image-size="40"
  41. image=""
  42. />
  43. </el-carousel>
  44. </div>
  45. <div class="four-right">
  46. <div class="four-notice">
  47. <div class="database-title">通知公告</div>
  48. <div class="seamless-warp">
  49. <swiper ref="mySwiper" :options="swiperOption" class="big-list">
  50. <swiper-slide v-for="(item,index) in noticeList" :key="index">
  51. <div class="notice-text">
  52. <div class="notice-title">
  53. <span>{{ item.title }}</span>
  54. <span>{{ item.startTime | parseTime('{y}-{m}-{d}') }}</span>
  55. </div>
  56. <div style="width: calc(100%); height: .5rem;" />
  57. <p :ref="el => { if (el) marqueeRefs[index] = el }" v-html="item.context" />
  58. </div>
  59. </swiper-slide>
  60. </swiper>
  61. </div>
  62. </div>
  63. <div class="four-ranking lending-ranking">
  64. <div class="database-title">图书借阅排行榜</div>
  65. <div class="ranking-cont">
  66. <ul class="ranking-title">
  67. <li style="width: 0.725rem;">排名</li>
  68. <li style="flex:1; text-align: left;">题名</li>
  69. <!-- <li style="flex:1;" /> -->
  70. <li style="width: 1.5rem; padding-right: .25rem; text-align: right;">借阅数量</li>
  71. </ul>
  72. <ul class="ranking-list">
  73. <li v-for="(item,index) in rankingData" :key="index" :class="{ 'hovered': index === currentHover }" style="font-size: .28rem !important; line-height: 36px !important;">
  74. <div style="width: 0.725rem; color: #79B8FF;" :class="[{'ranking-num1':index===0},{'ranking-num2':index===1},{'ranking-num3':index===2}]">{{ index>=3 ? index+1 : null }}</div>
  75. <div style="flex:1; text-align: left; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical;">{{ item.TITLE }}</div>
  76. <!-- <div class="ranking-progress" style="flex:1; align-self: center;">
  77. <el-progress :percentage="item.percentage" :stroke-width="8" :show-text="false" color="#009afb" />
  78. </div> -->
  79. <div style="width: 1.5rem; padding-right: .25rem; text-align: right;">{{ item.TOTALNUM }}<i style="padding-left:.0625rem;"></i></div>
  80. </li>
  81. </ul>
  82. </div>
  83. </div>
  84. <!-- <div class="four-contact">
  85. <div class="wechat-img">
  86. <img :src="wecharQrCodeSrc">
  87. </div>
  88. <span>扫一扫关注我们</span>
  89. </div> -->
  90. </div>
  91. </div>
  92. </div>
  93. </template>
  94. <script>
  95. import { FetchInitSetting, FetchTotalResource, FetchNoticeList, FetchSync36 } from '@/api/library'
  96. import { swiper, swiperSlide } from 'vue-awesome-swiper'
  97. import 'swiper/dist/css/swiper.css'
  98. export default {
  99. name: 'PageFour',
  100. components: {
  101. swiper,
  102. swiperSlide
  103. },
  104. data() {
  105. // const _self = this
  106. return {
  107. wecharQrCodeSrc: null,
  108. currentHover: -1,
  109. poster: require('@/assets/images/poster.png'),
  110. videoIndex: 0,
  111. slideData: [],
  112. rankingData: [],
  113. noticeList: [],
  114. noticeIndex: 0,
  115. rankInterval: null,
  116. marqueeRefs: [], // 存所有 p 元素
  117. currentIndex: 0, // 当前播放的公告索引
  118. scrollTimer: null, // 滚动动画定时器(防止内存泄漏)
  119. waitTimer: null, // 新增:停留等待定时器
  120. swiperOption: {
  121. direction: 'vertical',
  122. autoHeight: true,
  123. observer: true,
  124. observeParents: true,
  125. autoplay: false, // 关闭自带自动轮播
  126. loop: true
  127. },
  128. config: {
  129. waitBeforeScroll: 3000, // 内容加载后先停留3秒再滚动(单位:毫秒)
  130. waitAfterScroll: 1000, // 滚动到底部后停留1秒再切换(单位:毫秒)
  131. scrollSpeed: 60 // 滚动速度(px/秒,数值越小越慢)
  132. }
  133. }
  134. },
  135. computed: {
  136. },
  137. beforeDestroy() {
  138. this.destroy()
  139. },
  140. created() {
  141. this.getNotice()
  142. this.getBookRanking()
  143. },
  144. activated() {
  145. this.getVideoResource()
  146. this.load()
  147. if (this.rankingData.length !== 0) {
  148. this.currentHover = -1
  149. this.rankInterval = setInterval(() => {
  150. this.currentHover = (this.currentHover + 1) % this.rankingData.length
  151. }, 1000)
  152. }
  153. },
  154. deactivated() {
  155. this.destroy()
  156. },
  157. mounted() {
  158. // 二维码部分
  159. if (localStorage.getItem('wecharQrCodeSrc')) {
  160. this.wecharQrCodeSrc = localStorage.getItem('wecharQrCodeSrc')
  161. } else {
  162. this.getInitData()
  163. }
  164. // 缓存重要通知的index
  165. if (localStorage.getItem('noticeIndex')) {
  166. this.$nextTick(() => {
  167. const index = localStorage.getItem('noticeIndex')
  168. this.$refs.mySwiper.swiper.slideTo(index, 1000, true)
  169. })
  170. }
  171. },
  172. methods: {
  173. load() {
  174. const videos = this.$refs.videos
  175. if (videos) {
  176. videos[this.videoIndex].load()
  177. videos[this.videoIndex].currentTime = localStorage.getItem('videoCurrentTime') ? localStorage.getItem('videoCurrentTime') : 0
  178. // videos[this.videoIndex].pause()
  179. // setTimeout(() => {
  180. // // videos[this.videoIndex].play()
  181. // }, 2000)
  182. }
  183. },
  184. destroy() {
  185. clearInterval(this.rankInterval)
  186. localStorage.setItem('videoIndex', this.videoIndex)
  187. localStorage.setItem('videoCurrentTime', this.$refs.videos[this.videoIndex].currentTime)
  188. localStorage.setItem('noticeIndex', this.noticeIndex)
  189. this.$refs.videos[this.videoIndex].pause()
  190. this.rankInterval = null
  191. clearTimeout(this.scrollTimer)
  192. clearTimeout(this.waitTimer)
  193. },
  194. getInitData() {
  195. // wecharQrCode 二维码 用/downloadFile/+wecharQrCode
  196. const linkSrc = process.env.NODE_ENV === 'production' ? window.g.ApiUrl : process.env.VUE_APP_BASE_API
  197. console.log('linkSrc', linkSrc)
  198. FetchInitSetting().then(res => {
  199. const result = JSON.parse(res.data)
  200. this.wecharQrCodeSrc = linkSrc + '/downloadFile' + result.wecharQrCode
  201. })
  202. },
  203. getNotice() {
  204. FetchNoticeList().then(res => {
  205. this.noticeList = res.data
  206. this.$nextTick(() => {
  207. this.playNoticeByIndex(this.currentIndex)
  208. })
  209. }).catch(error => {
  210. console.error('Error', error)
  211. })
  212. },
  213. // 公告
  214. playNoticeByIndex(index) {
  215. clearTimeout(this.scrollTimer)
  216. clearTimeout(this.waitTimer)
  217. const pEl = this.marqueeRefs[index]
  218. if (!pEl) return
  219. // 1. 重置样式和动画,先让内容完整显示
  220. pEl.classList.remove('scroll-animation')
  221. pEl.style.transform = 'translateY(0)' // 强制回到顶部
  222. const container = this.$el.querySelector('.seamless-warp')
  223. const containerHeight = container.offsetHeight
  224. const contentHeight = pEl.offsetHeight
  225. // 2. 如果内容高度 <= 容器高度(无需滚动)
  226. if (contentHeight <= containerHeight) {
  227. // 停留指定时间后切换下一条
  228. this.waitTimer = setTimeout(() => {
  229. this.switchToNextNotice()
  230. }, this.config.waitBeforeScroll + 2000) // 多停留2秒
  231. return
  232. }
  233. // 3. 内容需要滚动:先停留,再滚动
  234. this.waitTimer = setTimeout(() => {
  235. // 设置CSS变量(精准计算滚动终点)
  236. pEl.style.setProperty('--content-height', `${contentHeight}px`)
  237. pEl.style.setProperty('--container-height', `${containerHeight}px`)
  238. // 计算滚动时长(速度越慢,时长越长)
  239. const scrollDistance = contentHeight - containerHeight // 实际需要滚动的距离
  240. const scrollDuration = scrollDistance / this.config.scrollSpeed
  241. // 设置动画时长并启动滚动
  242. pEl.style.setProperty('--scroll-duration', `${scrollDuration}s`)
  243. pEl.classList.add('scroll-animation')
  244. console.log('scrollDuration', scrollDuration)
  245. // 4. 滚动完成后,停留一段时间再切换
  246. this.scrollTimer = setTimeout(() => {
  247. // 滚动到底部后停留
  248. this.waitTimer = setTimeout(() => {
  249. this.switchToNextNotice()
  250. }, this.config.waitAfterScroll)
  251. }, scrollDuration * 1000) // 等待滚动完成
  252. }, this.config.waitBeforeScroll) // 先停留再滚动
  253. },
  254. // 切换下一条(保证循环)
  255. switchToNextNotice() {
  256. // 计算下一个索引(循环)
  257. this.currentIndex = (this.currentIndex + 1) % this.noticeList.length
  258. // 切换swiper
  259. const swiperInstance = this.$refs.mySwiper?.swiper
  260. if (swiperInstance) {
  261. swiperInstance.slideTo(this.currentIndex)
  262. }
  263. // 切换后播放新的一条
  264. this.$nextTick(() => {
  265. this.playNoticeByIndex(this.currentIndex)
  266. })
  267. },
  268. // 视频资源
  269. getVideoResource() {
  270. FetchTotalResource().then(res => {
  271. const result = JSON.parse(res.data)
  272. const linkSrc = process.env.NODE_ENV === 'production' ? window.g.ApiUrl : process.env.VUE_APP_BASE_API
  273. this.slideData = result.map((item, index) => {
  274. if (item.filePath) {
  275. item.cover = linkSrc + '/downloadFile' + item.filePath
  276. } else {
  277. item.cover = null
  278. }
  279. return item
  280. })
  281. // 下次进入页面时优先缓存的部分
  282. if (localStorage.getItem('videoIndex')) {
  283. this.videoIndex = parseInt(localStorage.getItem('videoIndex'))
  284. this.$nextTick(() => {
  285. this.$refs.carousel.setActiveItem(this.videoIndex)
  286. const videos = this.$refs.videos
  287. const nextVideo = videos[this.videoIndex]
  288. videos.forEach((video) => {
  289. video.pause()
  290. video.currentTime = 0
  291. })
  292. setTimeout(() => {
  293. nextVideo.currentTime = localStorage.getItem('videoCurrentTime') ? localStorage.getItem('videoCurrentTime') : 0
  294. nextVideo.play()
  295. }, 2000)
  296. })
  297. }
  298. }).catch(error => {
  299. console.error('Error', error)
  300. })
  301. },
  302. playVideo() {
  303. this.$refs.videos[this.videoIndex].play().catch(error => {
  304. console.error(error)
  305. })
  306. },
  307. setActiveItem(index) {
  308. this.$refs.carousel.setActiveItem(index)
  309. },
  310. carouselChange(index) {
  311. const videos = this.$refs.videos
  312. this.videoIndex = index
  313. videos.forEach((video) => {
  314. video.currentTime = 0 // 将视频回到起始时间
  315. video.pause() // 暂停视频播放
  316. })
  317. videos[index].play()
  318. },
  319. playNextVideo(index) {
  320. const videos = this.$refs.videos
  321. let nextIndex = index
  322. this.videoIndex = nextIndex
  323. if (index < this.slideData.length - 1) {
  324. nextIndex = nextIndex + 1
  325. } else {
  326. nextIndex = 0
  327. }
  328. const carousel = this.$refs.carousel
  329. carousel.setActiveItem(nextIndex)
  330. const nextVideo = videos[nextIndex]
  331. videos.forEach((video) => {
  332. video.pause()
  333. video.currentTime = 0
  334. })
  335. setTimeout(() => {
  336. nextVideo.play()
  337. }, 1000)
  338. },
  339. getBookRanking() {
  340. const currentDate = new Date() // 获取当前日期
  341. currentDate.setDate(currentDate.getDate() - 30) // 将当前日期减去30天
  342. const year = currentDate.getFullYear() // 获取年份
  343. const month = currentDate.getMonth() + 1 // 获取月份(注意月份从0开始,需要加1)
  344. const day = currentDate.getDate() // 获取日期
  345. const formattedDate = `${year}-${month < 10 ? '0' + month : month}-${day < 10 ? '0' + day : day}`
  346. const params = {
  347. 'libcode': this.libcode,
  348. 'starttime': formattedDate,
  349. 'endtime': this.getFormattedDate(new Date()),
  350. 'rownum': 5
  351. }
  352. FetchSync36(params).then(res => {
  353. const result = JSON.parse(res.data)
  354. if (result.success && result.resultlist.length > 0) {
  355. this.rankingData = result.resultlist.sort((a, b) => b.TOTALNUM - a.TOTALNUM).slice(0, 6)
  356. this.rankInterval = setInterval(() => {
  357. this.currentHover = (this.currentHover + 1) % this.rankingData.length
  358. }, 1000)
  359. } else {
  360. throw new Error('Failed' + libcode)
  361. }
  362. }).catch(error => {
  363. console.error('Error', error)
  364. })
  365. }
  366. }
  367. }
  368. </script>
  369. <style lang="scss" scoped>
  370. @import "~@/assets/styles/index.scss";
  371. .el-carousel{
  372. margin-top: 0.475rem !important;
  373. }
  374. ::v-deep .el-carousel__container{
  375. height: 8.875rem !important;
  376. }
  377. video {
  378. width: 100%;
  379. height: 100%;
  380. }
  381. .big-list>.swiper-wrapper>.swiper-slide {
  382. height: 100vh;
  383. overflow: hidden;
  384. }
  385. .swiper-container-vertical .swiper-wrapper{
  386. flex-direction: column;
  387. }
  388. .swiper-container {
  389. width: 100%;
  390. height: 100%;
  391. }
  392. </style>