Bladeren bron

使用 gemini 优化 404.vue 的 UI

reghao 17 uur geleden
bovenliggende
commit
f507b50022
3 gewijzigde bestanden met toevoegingen van 343 en 146 verwijderingen
  1. 197 134
      src/components/card/SearchVideoCard.vue
  2. 1 1
      src/components/layout/NavBar.vue
  3. 145 11
      src/views/404.vue

+ 197 - 134
src/components/card/SearchVideoCard.vue

@@ -1,48 +1,48 @@
 <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-col class="video-card-container">
+    <div class="video-card-wrapper" :title="video.title">
+      <el-card :body-style="{ padding: '0px' }" class="video-card shadow-hover">
+        <router-link target="_blank" :to="`/video/${video.videoId}`" class="cover-link">
+          <div class="image-container">
             <el-image
               lazy
               fit="cover"
-              class="coverImg"
+              class="cover-img"
               :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">
-              <i v-if="video.horizontal" class="el-icon-monitor" />
-              <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>
+
+            <div class="status-bar top-bar">
+              <span class="left-tag">
+                <i v-if="!video.cached" class="el-icon-warning-outline uncache-icon" />
+              </span>
+              <span class="duration-tag">{{ video.duration }}</span>
+            </div>
+
+            <div class="status-bar bottom-bar">
+              <div class="data-group">
+                <i v-if="video.horizontal" class="el-icon-monitor device-icon" />
+                <i v-else class="el-icon-mobile-phone device-icon" />
+                <span class="data-item"><i class="el-icon-video-play" /> {{ getVisited(video.view) }}</span>
+                <span class="data-item"><i class="el-icon-chat-dot-round" /> {{ 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;" v-html="video.title">
-<!--              {{ video.title | ellipsis }}-->
-            </span>
+
+        <div class="content-body">
+          <router-link class="title-link" target="_blank" :to="`/video/${video.videoId}`">
+            <h3 class="video-title" v-html="video.title"></h3>
           </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 v-if="video.user === undefined || video.user === null" style="padding: 14px">
-          <span style="left: 0;margin-bottom: 0px;color: black;">
-            {{ video.pubDateStr }}
-          </span>
+
+          <div class="meta-info">
+            <template v-if="video.user">
+              <router-link class="user-link" target="_blank" :to="`/user/${video.user.userId}`">
+                <i class="el-icon-user"></i> {{ video.user.screenName | ellipsisUsername }}
+              </router-link>
+              <span class="dot-split">•</span>
+            </template>
+            <span class="date-text">{{ video.pubDateStr }}</span>
+          </div>
         </div>
       </el-card>
     </div>
@@ -55,21 +55,10 @@ import { handleVisited } from 'assets/js/utils'
 export default {
   name: 'SearchVideoCard',
   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
+      const max = 8 // 增加了一点用户名的可见长度
+      return value.length > max ? value.slice(0, max) + '...' : value
     }
   },
   props: {
@@ -77,7 +66,6 @@ export default {
       type: Object,
       default: null
     },
-    // 时间前的描述
     dateTit: {
       type: String,
       default: ''
@@ -86,117 +74,192 @@ export default {
   methods: {
     getVisited(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>
 
-<style scoped>
-.time {
-  font-size: 15px;
-  color: #999;
+<style lang="scss" scoped>
+.video-card-container {
+  padding: 8px; // 统一通过容器控制间距
 }
 
-.bottom {
-  margin-top: 13px;
-  line-height: 12px;
+.video-card-wrapper {
+  cursor: pointer;
+  width: 100%;
 }
 
-.tit {
-  font-weight: 700;
-  font-size: 18px;
-
-  height: 50px;
+.video-card {
+  border: none !important;
+  border-radius: 12px;
   overflow: hidden;
-  text-overflow: ellipsis;
-  text-overflow: ellipsisUsername;
-  display: -webkit-box;
-  -webkit-line-clamp: 2; /*行数*/
-  -webkit-box-orient: vertical;
+  transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
+  background: #fff;
+
+  &:hover {
+    transform: translateY(-5px);
+    box-shadow: 0 10px 20px rgba(0,0,0,0.1) !important;
+  }
 }
 
-.num {
+.image-container {
   position: relative;
-  font-size: 15px;
-  padding-top: 9px;
-}
+  width: 100%;
+  aspect-ratio: 16 / 9; // 锁定比例,防止布局跳动
+  background: #f0f0f0;
+  overflow: hidden;
 
-/*处于手机屏幕时*/
-@media screen and (max-width: 768px) {
-  .tit {
-    font-weight: 600;
-    font-size: 12px;
-    height: 32px;
-  }
-  .time {
-    font-size: 10px;
-    color: #999;
+  .cover-img {
+    width: 100%;
+    height: 100%;
+    transition: transform 0.5s;
   }
-  .num {
-    font-size: 9px;
-    padding-top: 3px;
-  }
-  .bottom {
-    margin-top: 2px;
-    line-height: 7px;
-  }
-  .coverImg {
-    height: 120px !important;
+
+  &:hover .cover-img {
+    transform: scale(1.05);
   }
 }
 
-.imgs {
-  position: relative;
+/* 状态栏通用样式 */
+.status-bar {
+  position: absolute;
+  left: 0;
+  right: 0;
+  padding: 8px 10px;
+  color: #fff;
+  font-size: 12px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  z-index: 2;
+  pointer-events: none;
 }
 
-.coverImg {
-  width: 100%;
-  height: 175px;
-  display: block;
+.top-bar {
+  top: 0;
+  background: linear-gradient(to bottom, rgba(0,0,0,0.5), transparent);
+
+  .uncache-icon {
+    color: #ff4d4f;
+    font-weight: bold;
+    font-size: 14px;
+    filter: drop-shadow(0 0 2px rgba(0,0,0,0.5));
+  }
 }
 
-.clearfix:before,
-.clearfix:after {
-  display: table;
-  content: "";
+.bottom-bar {
+  bottom: 0;
+  background: linear-gradient(to top, rgba(0,0,0,0.6), transparent);
+
+  .data-group {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+
+    .device-icon {
+      font-size: 14px;
+      margin-right: -4px;
+    }
+
+    .data-item {
+      display: flex;
+      align-items: center;
+      gap: 3px;
+      text-shadow: 0 1px 2px rgba(0,0,0,0.8);
+    }
+  }
 }
 
-.clearfix:after {
-  clear: both;
+/* 文字内容区域 */
+.content-body {
+  padding: 12px;
+
+  .title-link {
+    text-decoration: none;
+    display: block;
+    margin-bottom: 8px;
+  }
+
+  .video-title {
+    margin: 0;
+    font-size: 14px;
+    color: #222;
+    line-height: 1.5;
+    height: 42px; // 固定两行高度
+    font-weight: 500;
+    display: -webkit-box;
+    -webkit-line-clamp: 2;
+    -webkit-box-orient: vertical;
+    overflow: hidden;
+    transition: color 0.2s;
+
+    &:hover {
+      color: #409EFF;
+    }
+
+    /* 处理搜索高亮词的颜色 */
+    ::v-deep em {
+      color: #f73131;
+      font-style: normal;
+    }
+  }
 }
 
-.card {
-  margin-bottom: 20px;
-  transition: all 0.6s; /*所有属性变化在0.6秒内执行动画*/
+.meta-info {
+  display: flex;
+  align-items: center;
+  font-size: 12px;
+  color: #9499a0;
+  white-space: nowrap;
+  overflow: hidden;
+
+  .user-link {
+    text-decoration: none;
+    color: #61666d;
+    display: flex;
+    align-items: center;
+    gap: 4px;
+    max-width: 60%;
+    overflow: hidden;
+    text-overflow: ellipsis;
+
+    &:hover {
+      color: #409EFF;
+    }
+  }
+
+  .dot-split {
+    margin: 0 6px;
+  }
+
+  .date-text {
+    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) {
+  .video-card-container {
+    padding: 5px; // 手机端缩小边距
+  }
+
+  .content-body {
+    padding: 8px;
+
+    .video-title {
+      font-size: 13px;
+      height: 38px;
+      line-height: 1.4;
+    }
+  }
+
+  .status-bar {
+    padding: 4px 6px;
+    font-size: 10px;
+  }
+
+  .meta-info {
+    font-size: 11px;
+  }
 }
 </style>

+ 1 - 1
src/components/layout/NavBar.vue

@@ -152,7 +152,7 @@ export default {
       }
     },
     toSearchPage() {
-      const query = { searchType: 1, keyword: this.keyword, pn: 1 }
+      const query = { keyword: this.keyword, pn: 1 }
       if (this.$route.path === '/search') {
         this.$router.push({ path: '/search', query }).catch(() => {})
       } else {

+ 145 - 11
src/views/404.vue

@@ -1,19 +1,35 @@
 <template>
-  <el-container>
-    <el-main>
-      <nav-bar />
-      <el-row>
-        <div style="text-align: center;">
-          <h1>Not Found!</h1>
-          <el-button type="text" @click="goIndex">回到首页</el-button>
+  <div class="not-found-page">
+    <nav-bar />
+
+    <el-main class="error-container">
+      <div class="error-content">
+        <div class="error-code">
+          <span>4</span>
+          <div class="circle">
+            <i class="el-icon-video-pause"></i>
+          </div>
+          <span>4</span>
         </div>
-      </el-row>
+
+        <h1 class="error-title">哎呀,页面断片了...</h1>
+        <p class="error-msg">你请求的地址可能被外星人劫持,或者已经下架了。</p>
+
+        <div class="error-actions">
+          <el-button type="primary" round class="action-btn" @click="goIndex">
+            返回首页
+          </el-button>
+          <el-button round class="action-btn" @click="goBack">
+            返回上一页
+          </el-button>
+        </div>
+      </div>
     </el-main>
-  </el-container>
+  </div>
 </template>
 
 <script>
-import NavBar from 'components/layout/LoginBar'
+import NavBar from '@/components/layout/LoginBar'
 
 export default {
   name: 'NotFound',
@@ -23,10 +39,128 @@ export default {
   methods: {
     goIndex() {
       this.$router.push('/')
+    },
+    goBack() {
+      this.$router.back()
     }
   }
 }
 </script>
 
-<style>
+<style scoped lang="scss">
+.not-found-page {
+  background-color: #f6f8fa;
+  min-height: 100vh;
+  display: flex;
+  flex-direction: column;
+}
+
+.error-container {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 20px;
+}
+
+.error-content {
+  text-align: center;
+  max-width: 500px;
+  width: 100%;
+}
+
+.error-code {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 120px;
+  font-weight: 900;
+  color: #fe2c55; // 使用你搜索框的主题红
+  line-height: 1;
+  margin-bottom: 20px;
+
+  .circle {
+    width: 100px;
+    height: 100px;
+    background: #fff;
+    border-radius: 50%;
+    margin: 0 10px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    box-shadow: 0 10px 20px rgba(254, 44, 85, 0.1);
+
+    i {
+      font-size: 60px;
+      animation: pulse 2s infinite;
+    }
+  }
+}
+
+.error-title {
+  font-size: 24px;
+  color: #333;
+  margin-bottom: 15px;
+}
+
+.error-msg {
+  font-size: 16px;
+  color: #9499a0;
+  margin-bottom: 40px;
+}
+
+.error-actions {
+  display: flex;
+  justify-content: center;
+  gap: 15px;
+
+  .action-btn {
+    padding: 12px 30px;
+    font-weight: 600;
+    transition: all 0.3s;
+
+    &:hover {
+      transform: translateY(-2px);
+    }
+  }
+}
+
+/* 简单的呼吸灯动画 */
+@keyframes pulse {
+  0% { transform: scale(1); opacity: 1; }
+  50% { transform: scale(0.9); opacity: 0.7; }
+  100% { transform: scale(1); opacity: 1; }
+}
+
+/* 移动端适配 */
+@media screen and (max-width: 768px) {
+  .error-code {
+    font-size: 80px;
+    .circle {
+      width: 70px;
+      height: 70px;
+      i { font-size: 40px; }
+    }
+  }
+
+  .error-title {
+    font-size: 20px;
+  }
+
+  .error-msg {
+    font-size: 14px;
+    padding: 0 20px;
+  }
+
+  .error-actions {
+    flex-direction: column;
+    padding: 0 40px;
+    gap: 10px;
+
+    .el-button {
+      margin-left: 0 !important;
+      width: 100%;
+    }
+  }
+}
 </style>