|
|
@@ -1,47 +1,66 @@
|
|
|
<template>
|
|
|
- <el-row class="movie-list">
|
|
|
- <el-row>
|
|
|
- <el-col :md="12">
|
|
|
- <el-row style="padding-right: 5px; padding-left: 5px; padding-bottom: 5px">
|
|
|
- <site-notice />
|
|
|
- </el-row>
|
|
|
- </el-col>
|
|
|
- <el-col :md="12">
|
|
|
- <el-row style="padding-right: 5px; padding-left: 5px; padding-bottom: 5px">
|
|
|
- <el-carousel :interval="3000" height="300px">
|
|
|
- <el-carousel-item v-for="(item, index) in carouselList" :key="index">
|
|
|
- <router-link target="_blank" :to="`/video/${item.videoId}`">
|
|
|
- <img class="carousel_image_type" :src="item.coverUrl" alt="img">
|
|
|
+ <div class="home-container">
|
|
|
+ <el-row class="recommend-section top-recommend-row" type="flex">
|
|
|
+ <el-col :xs="24" :sm="24" :md="15" :lg="17" class="carousel-col">
|
|
|
+ <div class="carousel-full-height-container">
|
|
|
+ <el-carousel
|
|
|
+ v-if="carouselList.length > 0"
|
|
|
+ :interval="5000"
|
|
|
+ arrow="hover"
|
|
|
+ class="custom-carousel shadow-hover"
|
|
|
+ :height="carouselHeight"
|
|
|
+ >
|
|
|
+ <el-carousel-item v-for="(item, index) in carouselList" :key="'banner-'+index">
|
|
|
+ <router-link :to="`/video/${item.videoId}`">
|
|
|
+ <div class="carousel-item-wrapper">
|
|
|
+ <img class="carousel-img" :src="item.coverUrl" alt="banner">
|
|
|
+ <div class="carousel-mask"></div>
|
|
|
+ <div class="carousel-info">
|
|
|
+ <h3 class="carousel-title">{{ item.title || '精彩内容' }}</h3>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</router-link>
|
|
|
</el-carousel-item>
|
|
|
</el-carousel>
|
|
|
- </el-row>
|
|
|
+ <div v-else class="carousel-placeholder" :style="{height: carouselHeight}">
|
|
|
+ <i class="el-icon-loading"></i>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <el-col :xs="24" :sm="24" :md="9" :lg="7" class="notice-col">
|
|
|
+ <site-notice class="equal-height-component shadow-hover" />
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
- <el-scrollbar style="width: 100%; height: 100vh">
|
|
|
- <el-row>
|
|
|
- <el-row
|
|
|
- v-if="dataList.length !== 0"
|
|
|
- v-infinite-scroll="load"
|
|
|
- infinite-scroll-disabled="loading"
|
|
|
- infinite-scroll-distance="10"
|
|
|
+
|
|
|
+ <div
|
|
|
+ class="video-grid-container"
|
|
|
+ v-infinite-scroll="load"
|
|
|
+ :infinite-scroll-disabled="disabled"
|
|
|
+ :infinite-scroll-distance="200"
|
|
|
+ :infinite-scroll-immediate="false"
|
|
|
+ >
|
|
|
+ <el-row class="video-row">
|
|
|
+ <el-col
|
|
|
+ v-for="(item, index) in dataList"
|
|
|
+ :key="'video-'+index"
|
|
|
+ :xs="12" :sm="8" :md="6" :lg="4"
|
|
|
+ class="video-card-col"
|
|
|
>
|
|
|
- <!--电影列表-->
|
|
|
- <el-col :md="24">
|
|
|
- <el-col v-for="(item, index) in dataList" :key="index" :md="6" :sm="12" :xs="12">
|
|
|
- <video-card :video="item" />
|
|
|
- </el-col>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
- <el-row v-else class="not-result">
|
|
|
- <el-col :span="12" :offset="6">
|
|
|
- <img src="@/assets/img/not-collection.png">
|
|
|
- <div>推荐数据正在计算中</div>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
+ <video-card :video="item" />
|
|
|
+ </el-col>
|
|
|
</el-row>
|
|
|
- </el-scrollbar>
|
|
|
- </el-row>
|
|
|
+
|
|
|
+ <div class="load-status">
|
|
|
+ <div v-if="loading" class="loading-bar">
|
|
|
+ <i class="el-icon-loading"></i> <span>加载中...</span>
|
|
|
+ </div>
|
|
|
+ <div v-if="noMore" class="no-more-bar">
|
|
|
+ <span class="dot"></span> 到底啦 <span class="dot"></span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
@@ -50,165 +69,188 @@ import SiteNotice from '@/components/card/SiteNotice'
|
|
|
import { getBannerVideo, videoRecommend } from '@/api/video'
|
|
|
|
|
|
export default {
|
|
|
- name: 'Index',
|
|
|
+ name: 'Home',
|
|
|
components: { VideoCard, SiteNotice },
|
|
|
data() {
|
|
|
return {
|
|
|
- // 屏幕宽度, 为了控制分页条的大小
|
|
|
- screenWidth: document.body.clientWidth,
|
|
|
nextId: 0,
|
|
|
dataList: [],
|
|
|
loading: false,
|
|
|
- max: 0,
|
|
|
- carouselList: []
|
|
|
+ noMore: false,
|
|
|
+ isFirstLoading: true,
|
|
|
+ carouselList: [],
|
|
|
+ carouselHeight: '340px',
|
|
|
+ timer: null
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ disabled() {
|
|
|
+ return this.loading || this.noMore || this.isFirstLoading;
|
|
|
}
|
|
|
},
|
|
|
created() {
|
|
|
- this.videoRecommendWrapper(this.nextId)
|
|
|
- this.getHotVideoWrapper()
|
|
|
- // this.initServerSendEvent()
|
|
|
+ this.handleResize();
|
|
|
+ this.initData();
|
|
|
},
|
|
|
mounted() {
|
|
|
- // 当窗口宽度改变时获取屏幕宽度
|
|
|
- window.onresize = () => {
|
|
|
- return () => {
|
|
|
- window.screenWidth = document.body.clientWidth
|
|
|
- this.screenWidth = window.screenWidth
|
|
|
- }
|
|
|
- }
|
|
|
+ window.addEventListener('resize', this.debounceResize);
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ window.removeEventListener('resize', this.debounceResize);
|
|
|
},
|
|
|
methods: {
|
|
|
- videoRecommendWrapper(nextId) {
|
|
|
- videoRecommend(nextId).then(resp => {
|
|
|
- if (resp.code === 0) {
|
|
|
- this.loading = false
|
|
|
- const respData = resp.data
|
|
|
- if (respData.length === 0) {
|
|
|
- this.$message(
|
|
|
- {
|
|
|
- message: '已经到底啦~~~',
|
|
|
- type: 'info',
|
|
|
- duration: 1000
|
|
|
- }
|
|
|
- )
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- for (const item of respData) {
|
|
|
- this.dataList.push(item)
|
|
|
- }
|
|
|
- this.nextId++
|
|
|
- this.$message(
|
|
|
- {
|
|
|
- message: '已加载新数据~~~',
|
|
|
- type: 'info',
|
|
|
- duration: 1000
|
|
|
- }
|
|
|
- )
|
|
|
- } else {
|
|
|
- this.$message(
|
|
|
- {
|
|
|
- message: '获取数据失败, 请重新刷新页面',
|
|
|
- type: 'warning',
|
|
|
- duration: 1000
|
|
|
- }
|
|
|
- )
|
|
|
- }
|
|
|
- }).catch(error => {
|
|
|
- this.$message(
|
|
|
- {
|
|
|
- message: error.message,
|
|
|
- type: 'error',
|
|
|
- duration: 1000
|
|
|
- }
|
|
|
- )
|
|
|
- }).finally(() => {
|
|
|
- })
|
|
|
+ debounceResize() {
|
|
|
+ if (this.timer) clearTimeout(this.timer);
|
|
|
+ this.timer = setTimeout(() => this.handleResize(), 200);
|
|
|
},
|
|
|
- load() {
|
|
|
- this.loading = true
|
|
|
- setTimeout(() => {
|
|
|
- this.videoRecommendWrapper(this.nextId)
|
|
|
- }, 1000)
|
|
|
+ handleResize() {
|
|
|
+ const width = window.innerWidth || document.documentElement.clientWidth;
|
|
|
+ if (width < 992) {
|
|
|
+ this.carouselHeight = width < 768 ? '190px' : '230px';
|
|
|
+ } else {
|
|
|
+ this.carouselHeight = '340px';
|
|
|
+ }
|
|
|
},
|
|
|
- getHotVideoWrapper() {
|
|
|
- getBannerVideo().then(resp => {
|
|
|
- if (resp.code === 0) {
|
|
|
- this.carouselList = resp.data
|
|
|
- }
|
|
|
- }).catch(error => {
|
|
|
- this.$message(
|
|
|
- {
|
|
|
- message: error.message,
|
|
|
- type: 'error',
|
|
|
- duration: 1000
|
|
|
- }
|
|
|
- )
|
|
|
- }).finally(() => {
|
|
|
- })
|
|
|
+ async initData() {
|
|
|
+ try {
|
|
|
+ await Promise.all([
|
|
|
+ this.getHotVideoWrapper(),
|
|
|
+ this.videoRecommendWrapper(this.nextId)
|
|
|
+ ]);
|
|
|
+ } finally {
|
|
|
+ this.isFirstLoading = false;
|
|
|
+ }
|
|
|
},
|
|
|
- initServerSendEvent() {
|
|
|
- if (typeof (EventSource) !== 'undefined') {
|
|
|
- const sseUrl = process.env.VUE_APP_SERVER_URL + '/api/data/video/hot'
|
|
|
- const source = new EventSource(sseUrl)
|
|
|
- source.addEventListener('test', function(e) {
|
|
|
- console.log(e)
|
|
|
- })
|
|
|
-
|
|
|
- const that = this
|
|
|
- source.onmessage = function(event) {
|
|
|
- console.log('update sse data')
|
|
|
- const dataList = JSON.parse(event.data)
|
|
|
- if (dataList.length !== 0) {
|
|
|
- that.carouselList = dataList
|
|
|
+ async videoRecommendWrapper(nextId) {
|
|
|
+ if (this.noMore) return;
|
|
|
+ this.loading = true;
|
|
|
+ try {
|
|
|
+ const resp = await videoRecommend(nextId);
|
|
|
+ if (resp.code === 0) {
|
|
|
+ const respData = resp.data;
|
|
|
+ if (!respData || respData.length === 0) {
|
|
|
+ this.noMore = true;
|
|
|
+ } else {
|
|
|
+ this.dataList = [...this.dataList, ...respData];
|
|
|
+ this.nextId++;
|
|
|
}
|
|
|
}
|
|
|
- } else {
|
|
|
- this.$message(
|
|
|
- {
|
|
|
- message: '抱歉,你的浏览器不支持 SSE...',
|
|
|
- type: 'warning',
|
|
|
- duration: 1000
|
|
|
- }
|
|
|
- )
|
|
|
+ } catch (e) {
|
|
|
+ console.error(e);
|
|
|
+ } finally {
|
|
|
+ setTimeout(() => { this.loading = false; }, 400);
|
|
|
}
|
|
|
+ },
|
|
|
+ load() {
|
|
|
+ this.videoRecommendWrapper(this.nextId);
|
|
|
+ },
|
|
|
+ async getHotVideoWrapper() {
|
|
|
+ const resp = await getBannerVideo();
|
|
|
+ if (resp.code === 0) this.carouselList = resp.data;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
-<style scoped>
|
|
|
-/*处于手机屏幕时*/
|
|
|
-@media screen and (max-width: 768px){
|
|
|
- .movie-list {
|
|
|
- padding-top: 5px;
|
|
|
- padding-left: 0.5%;
|
|
|
- padding-right: 0.5%;
|
|
|
+<style lang="scss" scoped>
|
|
|
+.home-container {
|
|
|
+ padding: 15px 20px;
|
|
|
+ max-width: 1700px;
|
|
|
+ margin: 0 auto;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+/* 推荐位布局:PC端强制对齐的关键 */
|
|
|
+.top-recommend-row {
|
|
|
+ display: flex !important;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ align-items: stretch; /* 关键:让子项高度一致 */
|
|
|
+ margin: 0 !important;
|
|
|
+ margin-bottom: 25px !important;
|
|
|
+
|
|
|
+ .el-col {
|
|
|
+ padding: 0 8px !important;
|
|
|
+ margin-bottom: 15px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 包裹容器,确保高度撑满 col */
|
|
|
+.carousel-full-height-container {
|
|
|
+ height: 100%;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.custom-carousel {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ border-radius: 12px;
|
|
|
+ overflow: hidden;
|
|
|
+ background: #f0f0f0;
|
|
|
+ // 必须穿透设置 container 为 100%
|
|
|
+ ::v-deep .el-carousel__container { height: 100% !important; }
|
|
|
+}
|
|
|
+
|
|
|
+.carousel-placeholder {
|
|
|
+ width: 100%;
|
|
|
+ background: #f5f7fa;
|
|
|
+ border-radius: 12px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ color: #c0c4cc;
|
|
|
+}
|
|
|
+
|
|
|
+.carousel-item-wrapper {
|
|
|
+ width: 100%; height: 100%; position: relative;
|
|
|
+ .carousel-img { width: 100%; height: 100%; object-fit: cover; }
|
|
|
+ .carousel-mask {
|
|
|
+ position: absolute; bottom: 0; width: 100%; height: 45%;
|
|
|
+ background: linear-gradient(to top, rgba(0,0,0,0.8), transparent);
|
|
|
+ }
|
|
|
+ .carousel-info {
|
|
|
+ position: absolute; bottom: 15px; left: 15px; color: #fff;
|
|
|
+ .carousel-title { font-size: 17px; font-weight: 500; margin: 0; }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-.movie-list {
|
|
|
- padding-top: 5px;
|
|
|
- padding-bottom: 5px;
|
|
|
- padding-left: 5px;
|
|
|
- padding-right: 5px;
|
|
|
+.video-row {
|
|
|
+ margin: 0 !important;
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ .el-col {
|
|
|
+ padding: 0 8px !important;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-.not-result {
|
|
|
- padding-top: 100px;
|
|
|
- padding-bottom: 100px;
|
|
|
+.load-status {
|
|
|
text-align: center;
|
|
|
+ padding: 40px 0;
|
|
|
+ color: #909399;
|
|
|
}
|
|
|
|
|
|
-.el-carousel__item h3 {
|
|
|
- color: #475669;
|
|
|
- font-size: 18px;
|
|
|
- opacity: 0.75;
|
|
|
- line-height: 300px;
|
|
|
- margin: 0;
|
|
|
+/* 响应式断点 */
|
|
|
+@media screen and (min-width: 992px) {
|
|
|
+ .top-recommend-row {
|
|
|
+ height: 340px;
|
|
|
+ }
|
|
|
+ .carousel-col, .notice-col {
|
|
|
+ height: 100%; /* 确保 Col 也是 100% */
|
|
|
+ }
|
|
|
+ .equal-height-component {
|
|
|
+ height: 100% !important;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-.carousel_image_type{
|
|
|
- width: 100%;
|
|
|
+@media screen and (max-width: 991px) {
|
|
|
+ .home-container { padding: 10px; }
|
|
|
+ .top-recommend-row { height: auto !important; }
|
|
|
+ .carousel-col {
|
|
|
+ height: 190px !important;
|
|
|
+ ::v-deep .el-carousel__container { height: 190px !important; }
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
+body { overflow-x: hidden; }
|
|
|
+.shadow-hover { transition: all 0.3s; &:hover { box-shadow: 0 10px 25px rgba(0,0,0,0.1); } }
|
|
|
</style>
|