Преглед изворни кода

使用 gemini 优化 Home.vue, VideoCard.vue, SiteNotice.vue 等页面的 UI

reghao пре 1 дан
родитељ
комит
637e55fd11
3 измењених фајлова са 455 додато и 371 уклоњено
  1. 109 57
      src/components/card/SiteNotice.vue
  2. 142 152
      src/components/card/VideoCard.vue
  3. 204 162
      src/views/home/Home.vue

+ 109 - 57
src/components/card/SiteNotice.vue

@@ -1,15 +1,20 @@
 <template>
 <template>
-  <el-card class="box-card" style="height: 300px">
-    <div slot="header" class="clearfix">
-      <img src="@/assets/img/speaker.png" alt="" class="recommand-icon">
-      <span>站点公告</span>
+  <el-card class="notice-card" shadow="never">
+    <div slot="header" class="notice-header">
+      <div class="header-left">
+        <img src="@/assets/img/speaker.png" alt="icon" class="notice-icon">
+        <span class="notice-title">站点公告</span>
+      </div>
     </div>
     </div>
-    <div class="text item" style="height: 20vh;">
-      <el-scrollbar style="width: 100%; height: 100%;">
-        <el-row>
-          <span v-html="content" />
-        </el-row>
-      </el-scrollbar>
+
+    <div class="notice-content">
+      <el-skeleton :loading="loading" :rows="5" animated>
+        <template>
+          <el-scrollbar class="custom-scrollbar">
+            <div class="html-container" v-html="content || '暂无公告'" />
+          </el-scrollbar>
+        </template>
+      </el-skeleton>
     </div>
     </div>
   </el-card>
   </el-card>
 </template>
 </template>
@@ -21,68 +26,115 @@ export default {
   name: 'SiteNotice',
   name: 'SiteNotice',
   data() {
   data() {
     return {
     return {
-      content: ''
+      content: '',
+      loading: true
     }
     }
   },
   },
   created() {
   created() {
-    getSiteNotice().then(resp => {
-      if (resp.code === 0) {
-        this.content = resp.data
-      }
-    }).catch(error => {
-      this.$message.error(error.message)
-    }).finally(() => {
-    })
+    this.fetchNotice()
   },
   },
-  methods: {}
+  methods: {
+    async fetchNotice() {
+      this.loading = true
+      try {
+        const resp = await getSiteNotice()
+        if (resp.code === 0) {
+          this.content = resp.data
+        }
+      } catch (error) {
+        this.$message.error('获取公告失败')
+      } finally {
+        this.loading = false
+      }
+    }
+  }
 }
 }
 </script>
 </script>
 
 
-<style scoped>
-@media screen and (max-width: 768px) {
-  .clearfix span {
-    font-size: 15px;
-    position: absolute;
-    bottom: 8px;
-    left: 35px;
+<style lang="scss" scoped>
+.notice-card {
+  height: 100%; // 关键:配合 Home.vue 的 align-items: stretch
+  display: flex;
+  flex-direction: column;
+  border-radius: 12px;
+  border: none;
+
+  // 深度选择器修改 el-card 内部结构
+  ::v-deep .el-card__header {
+    padding: 12px 16px;
+    border-bottom: 1px solid #f0f2f5;
+  }
+
+  ::v-deep .el-card__body {
+    flex: 1; // 让 body 撑开,充满剩余空间
+    overflow: hidden;
+    padding: 15px;
   }
   }
 }
 }
 
 
-.item {
-  height: 25px;
-  margin-top: 4px;
-  margin-bottom: 16px;
+.notice-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
 
 
-  overflow: hidden;
-  text-overflow: ellipsis;
-  display: -webkit-box;
-  -webkit-line-clamp: 1; /*行数*/
-  -webkit-box-orient: vertical;
-}
+  .header-left {
+    display: flex;
+    align-items: center;
+    gap: 10px; // 图标和文字的间距
+  }
 
 
-.clearfix:before,
-.clearfix:after {
-  display: table;
-  content: "";
-}
-.clearfix:after {
-  clear: both;
-}
+  .notice-icon {
+    width: 24px;
+    height: 24px;
+    object-fit: contain;
+  }
 
 
-.clearfix {
-  position: relative;
-}
-.clearfix span {
-  font-size: 20px;
-  position: absolute;
-  bottom: 8px;
-  left: 35px;
+  .notice-title {
+    font-size: 16px;
+    font-weight: 600;
+    color: #303133;
+  }
 }
 }
-.box-card {
-  width: 100%;
+
+.notice-content {
+  height: 100%;
+
+  .custom-scrollbar {
+    height: 100%;
+
+    // 隐藏横向滚动条
+    ::v-deep .el-scrollbar__wrap {
+      overflow-x: hidden;
+    }
+  }
+
+  .html-container {
+    font-size: 14px;
+    line-height: 1.6;
+    color: #606266;
+    word-break: break-all;
+
+    // 优化 v-html 内部可能出现的图片
+    ::v-deep img {
+      max-width: 100%;
+      height: auto;
+      border-radius: 4px;
+    }
+
+    ::v-deep p {
+      margin-bottom: 8px;
+    }
+  }
 }
 }
 
 
-.recommand-icon {
-  width: 30px;
+/* 响应式适配 */
+@media screen and (max-width: 768px) {
+  .notice-title {
+    font-size: 14px;
+  }
+
+  .notice-card {
+    height: 240px; // 移动端固定一个高度,防止无限拉长
+  }
 }
 }
 </style>
 </style>

+ 142 - 152
src/components/card/VideoCard.vue

@@ -1,52 +1,53 @@
 <template>
 <template>
-  <el-col style="padding-right: 7px; padding-left: 7px">
-    <div style="cursor: pointer" :title="video.title">
-      <el-card :body-style="{ padding: '0px' }" class="card">
-        <router-link target="_blank" :to="`/video/${video.videoId}`">
-          <div class="imgs">
-            <el-image
+  <div class="video-card-wrapper">
+    <el-card :body-style="{ padding: '0px' }" class="video-card shadow-hover">
+      <router-link :to="`/video/${video.videoId}`" class="cover-link">
+        <div class="image-container">
+          <el-image
               lazy
               lazy
               fit="cover"
               fit="cover"
-              class="coverImg"
+              class="cover-img"
               :src="video.coverUrl"
               :src="video.coverUrl"
-            />
-            <span style="position: absolute; top: 0; left: 0; color:red">
-              <i v-if="!video.cached" class="el-icon-close" />
-            </span>
-            <span style="position: absolute; top: 0; left: 60%; color:white"> {{ video.duration }} </span>
-            <span style="position: absolute; bottom: 0; left: 0; color:white">
+          >
+          </el-image>
+
+          <div class="status-top">
+            <i v-if="!video.cached" class="el-icon-close icon-uncached" title="未缓存" />
+            <span class="duration">{{ video.duration }}</span>
+          </div>
+
+          <div class="status-bottom">
+            <div class="status-left">
               <i v-if="video.horizontal" class="el-icon-monitor" />
               <i v-if="video.horizontal" class="el-icon-monitor" />
               <i v-else class="el-icon-mobile-phone" />
               <i v-else class="el-icon-mobile-phone" />
-            </span>
-            <span style="position: absolute; bottom: 0; left: 10%; color:white">
-              <i class="el-icon-video-play">{{ getVisited(video.view) }}</i>
-            </span>
-            <span style="position: absolute; bottom: 0; left: 40%; color:white">
-              <i class="el-icon-s-comment">{{ getVisited(video.comment) }}</i>
-            </span>
+              <span class="data-item">
+                <i class="el-icon-video-play"></i> {{ getVisited(video.view) }}
+              </span>
+              <span class="data-item">
+                <i class="el-icon-s-comment"></i> {{ getVisited(video.comment) }}
+              </span>
+            </div>
           </div>
           </div>
-        </router-link>
-        <div style="padding: 14px">
-          <router-link style="text-decoration-line: none" target="_blank" :to="`/video/${video.videoId}`">
-            <span style="left: 0;margin-bottom: 0px;color: black;">
-              {{ video.title | ellipsis }}
-            </span>
-          </router-link>
-        </div>
-        <div v-if="video.user !== undefined && video.user !== null" style="padding: 14px">
-          <span style="left: 0;margin-bottom: 0px;color: black;">
-            <router-link target="_blank" :to="`/user/${video.user.userId}`">
-              <i class="el-icon-user"> {{ video.user.screenName | ellipsisUsername }} </i></router-link> • {{ video.pubDateStr }}
-          </span>
         </div>
         </div>
-        <div v-if="video.user === undefined || video.user === null" style="padding: 14px">
-          <span style="left: 0;margin-bottom: 0px;color: black;">
-            {{ video.pubDateStr }}
-          </span>
+      </router-link>
+
+      <div class="content-section">
+        <router-link :to="`/video/${video.videoId}`" class="title-link">
+          <h3 class="video-title">{{ video.title }}</h3>
+        </router-link>
+
+        <div class="meta-info">
+          <template v-if="video.user">
+            <router-link :to="`/user/${video.user.userId}`" class="user-link">
+              <i class="el-icon-user"></i> {{ video.user.screenName }}
+            </router-link>
+            <span class="dot">•</span>
+          </template>
+          <span class="pub-date">{{ video.pubDateStr }}</span>
         </div>
         </div>
-      </el-card>
-    </div>
-  </el-col>
+      </div>
+    </el-card>
+  </div>
 </template>
 </template>
 
 
 <script>
 <script>
@@ -54,149 +55,138 @@ import { handleVisited } from 'assets/js/utils'
 
 
 export default {
 export default {
   name: 'VideoCard',
   name: 'VideoCard',
-  filters: {
-    ellipsis(value) {
-      if (!value) return ''
-      const max = 10
-      if (value.length > max) {
-        return value.slice(0, max) + '...'
-      }
-      return value
-    },
-    ellipsisUsername(value) {
-      if (!value) return ''
-      const max = 5
-      if (value.length > max) {
-        return value.slice(0, max) + '...'
-      }
-      return value
-    }
-  },
   props: {
   props: {
     video: {
     video: {
       type: Object,
       type: Object,
-      default: null
-    },
-    // 时间前的描述
-    dateTit: {
-      type: String,
-      default: ''
+      required: true,
+      default: () => ({})
     }
     }
   },
   },
   methods: {
   methods: {
     getVisited(visited) {
     getVisited(visited) {
       return handleVisited(visited)
       return handleVisited(visited)
-    },
-    convertTimestamp(value) {
-      const date = new Date(value * 1000)
-      var month = date.getMonth()
-      if (month < 10) {
-        if (month === 0) {
-          month = '01'
-        } else {
-          month = '0' + month
-        }
-      }
-
-      var day = date.getDay()
-      if (day < 10) {
-        day = '0' + day
-      }
-      return month + '-' + day
     }
     }
   }
   }
 }
 }
 </script>
 </script>
 
 
-<style scoped>
-.time {
-  font-size: 15px;
-  color: #999;
-}
-
-.bottom {
-  margin-top: 13px;
-  line-height: 12px;
+<style lang="scss" scoped>
+.video-card-wrapper {
+  padding: 0 7px;
+  margin-bottom: 20px;
 }
 }
 
 
-.tit {
-  font-weight: 700;
-  font-size: 18px;
-
-  height: 50px;
+.video-card {
+  border-radius: 8px;
+  border: none;
   overflow: hidden;
   overflow: hidden;
-  text-overflow: ellipsis;
-  text-overflow: ellipsisUsername;
-  display: -webkit-box;
-  -webkit-line-clamp: 2; /*行数*/
-  -webkit-box-orient: vertical;
+  transition: transform 0.3s ease;
+
+  &:hover {
+    transform: translateY(-4px);
+  }
 }
 }
 
 
-.num {
+.image-container {
   position: relative;
   position: relative;
-  font-size: 15px;
-  padding-top: 9px;
-}
+  width: 100%;
+  aspect-ratio: 16 / 9; // 锁定比例,防止布局跳动
+  overflow: hidden;
+  background-color: #f0f0f0;
 
 
-/*处于手机屏幕时*/
-@media screen and (max-width: 768px) {
-  .tit {
-    font-weight: 600;
-    font-size: 12px;
-    height: 32px;
+  .cover-img {
+    width: 100%;
+    height: 100%;
+    display: block;
   }
   }
-  .time {
-    font-size: 10px;
-    color: #999;
-  }
-  .num {
-    font-size: 9px;
-    padding-top: 3px;
+
+  // 图片上的文字阴影,保证清晰度
+  .status-top, .status-bottom {
+    position: absolute;
+    z-index: 2;
+    color: #fff;
+    font-size: 12px;
+    padding: 6px 8px;
+    width: 100%;
+    box-sizing: border-box;
+    text-shadow: 0 1px 2px rgba(0,0,0,0.8);
   }
   }
-  .bottom {
-    margin-top: 2px;
-    line-height: 7px;
+
+  .status-top {
+    top: 0;
+    display: flex;
+    justify-content: space-between;
+    background: linear-gradient(to bottom, rgba(0,0,0,0.5), transparent);
+
+    .icon-uncached { color: #ff4d4f; font-weight: bold; }
   }
   }
-  .coverImg {
-    height: 120px !important;
+
+  .status-bottom {
+    bottom: 0;
+    background: linear-gradient(to top, rgba(0,0,0,0.5), transparent);
+
+    .data-item {
+      margin-left: 10px;
+      i { margin-right: 2px; }
+    }
   }
   }
 }
 }
 
 
-.imgs {
-  position: relative;
-}
+.content-section {
+  padding: 12px;
 
 
-.coverImg {
-  width: 100%;
-  height: 175px;
-  display: block;
-}
+  .title-link {
+    text-decoration: none;
+    color: #303133;
 
 
-.clearfix:before,
-.clearfix:after {
-  display: table;
-  content: "";
-}
+    .video-title {
+      margin: 0 0 8px 0;
+      font-size: 14px;
+      font-weight: 500;
+      line-height: 1.4;
+      // CSS 多行截断:替代 JS Filter
+      display: -webkit-box;
+      -webkit-line-clamp: 2;
+      -webkit-box-orient: vertical;
+      overflow: hidden;
+      height: 40px; // 固定高度防止抖动
+      &:hover { color: #409EFF; }
+    }
+  }
 
 
-.clearfix:after {
-  clear: both;
-}
+  .meta-info {
+    font-size: 12px;
+    color: #909399;
+    display: flex;
+    align-items: center;
+    white-space: nowrap;
+    overflow: hidden;
 
 
-.card {
-  margin-bottom: 20px;
-  transition: all 0.6s; /*所有属性变化在0.6秒内执行动画*/
+    .user-link {
+      color: #606266;
+      text-decoration: none;
+      max-width: 50%;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      &:hover { color: #409EFF; }
+    }
+
+    .dot { margin: 0 4px; }
+    .pub-date { flex-shrink: 0; }
+  }
 }
 }
 
 
-/*.card:hover {
-  !*鼠标放上之后元素变成1.06倍大小*!
-  transform: scale(1.06);
-}*/
-.play-icon {
-  position: absolute;
-  /*top: -15px;*/
-  right: 2%;
-  bottom: 5px;
-  z-index: 7;
-  width: 40px;
+/* 移动端适配 */
+@media screen and (max-width: 768px) {
+  .content-section {
+    padding: 8px;
+    .video-title {
+      font-size: 12px;
+      height: 34px;
+    }
+  }
+  .image-container {
+    .status-top, .status-bottom { font-size: 10px; }
+  }
 }
 }
 </style>
 </style>

+ 204 - 162
src/views/home/Home.vue

@@ -1,47 +1,66 @@
 <template>
 <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>
               </router-link>
             </el-carousel-item>
             </el-carousel-item>
           </el-carousel>
           </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-col>
     </el-row>
     </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-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>
 </template>
 
 
 <script>
 <script>
@@ -50,165 +69,188 @@ import SiteNotice from '@/components/card/SiteNotice'
 import { getBannerVideo, videoRecommend } from '@/api/video'
 import { getBannerVideo, videoRecommend } from '@/api/video'
 
 
 export default {
 export default {
-  name: 'Index',
+  name: 'Home',
   components: { VideoCard, SiteNotice },
   components: { VideoCard, SiteNotice },
   data() {
   data() {
     return {
     return {
-      // 屏幕宽度, 为了控制分页条的大小
-      screenWidth: document.body.clientWidth,
       nextId: 0,
       nextId: 0,
       dataList: [],
       dataList: [],
       loading: false,
       loading: false,
-      max: 0,
-      carouselList: []
+      noMore: false,
+      isFirstLoading: true,
+      carouselList: [],
+      carouselHeight: '340px',
+      timer: null
+    }
+  },
+  computed: {
+    disabled() {
+      return this.loading || this.noMore || this.isFirstLoading;
     }
     }
   },
   },
   created() {
   created() {
-    this.videoRecommendWrapper(this.nextId)
-    this.getHotVideoWrapper()
-    // this.initServerSendEvent()
+    this.handleResize();
+    this.initData();
   },
   },
   mounted() {
   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: {
   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>
 </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;
   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>
 </style>