Ver código fonte

使用 gemini 优化用户主页 UserHome.vue

reghao 10 horas atrás
pai
commit
938e7892ad
4 arquivos alterados com 373 adições e 644 exclusões
  1. 0 257
      src/components/StampBadge.vue
  2. 4 4
      src/router/user.js
  3. 0 383
      src/views/user/Home.vue
  4. 369 0
      src/views/user/UserHome.vue

+ 0 - 257
src/components/StampBadge.vue

@@ -1,257 +0,0 @@
-<template>
-  <div
-    class="first-ring"
-    v-bind="getBindValue"
-    :class="getStampBadgeClass"
-    :style="{ transform: `rotate(${rotate}deg)` }"
-  >
-    <div class="second-ring" :class="getStampBadgeClass">
-      <div class="third-ring" :class="getStampBadgeClass">
-        <div class="forth-ring" :class="getStampBadgeClass">
-          <div class="content-rectangle ellipsis" :class="getStampBadgeClass">
-            <span class="">{{ content }}</span>
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'StampBadge',
-  // inheritAttrs: false,
-  props: {
-    color: {
-      type: String,
-      default: 'primary',
-      validator: (v) =>
-        ['primary', 'error', 'warning', 'success', 'info'].includes(v)
-    },
-    /**
-     * stamp badge size.
-     * @default: middle
-     */
-    size: {
-      type: String,
-      default: 'middle',
-      validator: (v) => ['large', 'middle', 'small'].includes(v)
-    },
-    /**
-     * stamp badge rotate deg.
-     * @default: 0
-     */
-    rotate: { type: Number, default: 0 },
-    content: { type: String, default: 'Unknown' }
-  },
-  computed: {
-    getStampBadgeClass() {
-      const { color, size } = this.$props
-      return [
-        {
-          [`stamp-badge-${color}`]: !!color,
-          [`stamp-badge-${size}`]: !!size
-        }
-      ]
-    },
-    getBindValue() {
-      return { ...this.$attrs, ...this.$props }
-    }
-  },
-  methods: {}
-}
-</script>
-
-<style lang="scss" scoped>
-.first-ring {
-  border-radius: 100px;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-}
-
-.second-ring {
-  background: #fff;
-  border-radius: 100px;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-}
-
-.third-ring {
-  border-radius: 100px;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-}
-
-.forth-ring {
-  background: #fff;
-  border-radius: 100px;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  position: relative;
-}
-
-.content-rectangle {
-  background: #fff;
-  font-weight: bold;
-  text-align: center;
-  position: absolute;
-}
-
-.ellipsis {
-  overflow: hidden;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-}
-
-// primary
-.stamp-badge-primary.first-ring {
-  background: #1890ff;
-}
-
-.stamp-badge-primary.third-ring {
-  background: #1890ff;
-}
-
-.stamp-badge-primary.content-rectangle {
-  border: 1px solid #1890ff;
-  color: #1890ff;
-}
-
-// success
-.stamp-badge-success.first-ring {
-  background: #52c41a;
-}
-
-.stamp-badge-success.third-ring {
-  background: #52c41a;
-}
-
-.stamp-badge-success.content-rectangle {
-  border: 1px solid #52c41a;
-  color: #52c41a;
-}
-
-// error
-.stamp-badge-error.first-ring {
-  background: #ff4d4f;
-}
-
-.stamp-badge-error.third-ring {
-  background: #ff4d4f;
-}
-
-.stamp-badge-error.content-rectangle {
-  border: 1px solid #ff4d4f;
-  color: #ff4d4f;
-}
-
-// warning
-.stamp-badge-warning.first-ring {
-  background: #faad14;
-}
-
-.stamp-badge-warning.third-ring {
-  background: #faad14;
-}
-
-.stamp-badge-warning.content-rectangle {
-  border: 1px solid #faad14;
-  color: #faad14;
-}
-
-// info
-.stamp-badge-info.first-ring {
-  background: #ccc;
-}
-
-.stamp-badge-info.third-ring {
-  background: #ccc;
-}
-
-.stamp-badge-info.content-rectangle {
-  border: 1px solid #ccc;
-  color: #ccc;
-}
-
-// large
-.stamp-badge-large.first-ring {
-  width: 84px;
-  height: 84px;
-}
-
-.stamp-badge-large.second-ring {
-  width: 80px;
-  height: 80px;
-}
-
-.stamp-badge-large.third-ring {
-  width: 74px;
-  height: 74px;
-}
-
-.stamp-badge-large.forth-ring {
-  width: 64px;
-  height: 64px;
-}
-
-.stamp-badge-large.content-rectangle {
-  width: 90px;
-  font-size: 1.2rem;
-}
-
-// middle
-.stamp-badge-middle.first-ring {
-  width: 64px;
-  height: 64px;
-}
-
-.stamp-badge-middle.second-ring {
-  width: 60px;
-  height: 60px;
-}
-
-.stamp-badge-middle.third-ring {
-  width: 56px;
-  height: 56px;
-}
-
-.stamp-badge-middle.forth-ring {
-  width: 48px;
-  height: 48px;
-}
-
-.stamp-badge-middle.content-rectangle {
-  width: 70px;
-  font-size: 1rem;
-}
-
-// small
-.stamp-badge-small.first-ring {
-  width: 54px;
-  height: 54px;
-}
-
-.stamp-badge-small.second-ring {
-  width: 50px;
-  height: 50px;
-}
-
-.stamp-badge-small.third-ring {
-  width: 46px;
-  height: 46px;
-}
-
-.stamp-badge-small.forth-ring {
-  width: 38px;
-  height: 38px;
-}
-
-.stamp-badge-small.content-rectangle {
-  width: 60px;
-  font-size: 0.8rem;
-}
-</style>

+ 4 - 4
src/router/user.js

@@ -2,10 +2,10 @@ const Index = () => import('views/Index')
 // ********************************************************************************************************************
 // 用户前台主页
 // ********************************************************************************************************************
-const UserHome = () => import('views/user/Home')
-const UserVideo = () => import('views/user/Home')
-const UserImage = () => import('views/user/Home')
-const UserAlbum = () => import('views/user/Home')
+const UserHome = () => import('views/user/UserHome')
+const UserVideo = () => import('views/user/UserHome')
+const UserImage = () => import('views/user/UserHome')
+const UserAlbum = () => import('views/user/UserHome')
 const UserRelation = () => import('views/user/UserRelation')
 
 export default {

+ 0 - 383
src/views/user/Home.vue

@@ -1,383 +0,0 @@
-<template>
-  <div>
-    <el-row class="movie-list">
-      <el-col v-if="user" :md="24">
-        <el-card :body-style="{ padding: '0px' }" class="card">
-          <div slot="header" class="clearfix">
-            <el-row>
-              <el-col :md="1">
-                <el-avatar>
-                  <el-image :src="user.avatarUrl" />
-                </el-avatar>
-              </el-col>
-              <el-col :md="22">
-                <span>{{ user.screenName }}</span>
-                <span v-html="'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'" />
-                <el-button
-                  type="danger"
-                  size="mini"
-                  :icon="followButton.icon"
-                  @click="followUser(user.userId)"
-                >
-                  <span>{{ followButton.text }}</span>
-                </el-button>
-                <el-button
-                  type="danger"
-                  size="mini"
-                  icon="el-icon-message"
-                  @click="sendMessage(user.userId)"
-                >
-                  <span>发消息</span>
-                </el-button>
-                <el-tag v-if="user.biliUserId !== undefined && user.biliUserId !== null" size="mini" type="'success'" disable-transitions>
-                  <a target="_blank" :href="`https://space.bilibili.com/` + user.biliUserId">bili</a>
-                </el-tag>
-              </el-col>
-              <el-col :md="1">
-                <StampBadge
-                  v-if="user.vip"
-                  style="position: relative; top: 0; right: 0"
-                  size="small"
-                  color="warning"
-                  content="小会员"
-                  :rotate="0"
-                />
-              </el-col>
-            </el-row>
-            <el-row>
-              <span v-if="user.signature !== null" v-html="user.signature" />
-              <span v-if="user.signature === undefined || user.signature === null">-</span>
-            </el-row>
-            <el-row>
-              <br>
-              <router-link target="_blank" :to="`/user/${user.userId}/following`">
-                <span class="el-icon-user">关注数: {{ user.following }}</span>
-              </router-link>
-              <span v-html="'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'" />
-              <router-link target="_blank" :to="`/user/${user.userId}/follower`">
-                <span class="el-icon-user">粉丝数: {{ user.follower }}</span>
-              </router-link>
-            </el-row>
-          </div>
-        </el-card>
-      </el-col>
-    </el-row>
-    <el-row>
-      <el-col :md="24" class="movie-list">
-        <el-tabs v-if="userContentData !== null" v-model="activeName" @tab-click="tabClick">
-          <el-tab-pane name="video">
-            <span slot="label">
-              视频<el-badge :value="userContentData.videoCount" :max="9999" class="item" type="warning" />
-            </span>
-            <div v-if="activeName === 'video'">
-              <el-col v-for="(video, index) in dataList" :key="index" :md="6" :sm="12" :xs="12">
-                <video-card :video="video" />
-              </el-col>
-            </div>
-          </el-tab-pane>
-          <el-tab-pane name="image">
-            <span slot="label">
-              图片<el-badge :value="userContentData.imageCount" :max="9999" class="item" type="warning" />
-            </span>
-            <div v-if="activeName === 'image'">
-              <el-col v-for="(album, index) in dataList" :key="index" :md="6" :sm="12" :xs="12">
-                <image-album-card :image-album="album" />
-              </el-col>
-            </div>
-          </el-tab-pane>
-          <el-tab-pane name="album">
-            <span slot="label">
-              播放列表<el-badge :value="userContentData.albumCount" :max="9999" class="item" type="warning" />
-            </span>
-            <div v-if="activeName === 'album'">
-              <el-col
-                v-for="(item, index) in dataList"
-                :key="index"
-                :md="6"
-                :sm="12"
-                :xs="12"
-                style="padding-right: 5px; padding-left: 5px; padding-bottom: 5px"
-              >
-                <el-card :body-style="{ padding: '0px' }" class="card">
-                  <div class="imgs" style="cursor: pointer" :title="item.albumName">
-                    <router-link style="text-decoration-line: none" target="_blank" :to="`/playlist/${item.albumId}`">
-                      <el-image
-                        lazy
-                        fit="cover"
-                        class="coverImg"
-                        :src="item.coverUrl"
-                      />
-                      <span class="el-icon-files" style="position: absolute; bottom: 0; right: 10%; color:red">
-                        {{ item.total }}
-                      </span>
-                    </router-link>
-                  </div>
-                  <div style="padding: 14px">
-                    <router-link style="text-decoration-line: none" target="_blank" :to="`/playlist/${item.albumId}`">
-                      <span style="left: 0;margin-bottom: 0px;color: black;">{{ item.albumName | ellipsis }}</span>
-                    </router-link>
-                  </div>
-                </el-card>
-              </el-col>
-            </div>
-          </el-tab-pane>
-        </el-tabs>
-      </el-col>
-      <el-col :span="24" class="pagination">
-        <el-pagination
-          background
-          :small="screenWidth <= 768"
-          hide-on-single-page
-          layout="prev, pager, next"
-          :page-size="pageSize"
-          :current-page="currentPage"
-          :total="totalSize"
-          @current-change="handleCurrentChange"
-          @prev-click="handleCurrentChange"
-          @next-click="handleCurrentChange"
-        />
-      </el-col>
-    </el-row>
-    <el-row v-if="showEmpty" class="not-result">
-      <el-col :span="12" :offset="6">
-        <img src="@/assets/img/not-collection.png">
-        <div>该用户还没发布任何东西呢</div>
-      </el-col>
-    </el-row>
-  </div>
-</template>
-
-<script>
-import StampBadge from '@/components/StampBadge'
-import VideoCard from '@/components/card/VideoCard'
-import ImageAlbumCard from '@/components/card/ImageAlbumCard'
-
-import { getUserInfo, checkRelation, followUser, unfollowUser } from '@/api/user'
-import { getUserContentData, getUserVideos } from '@/api/video'
-import { getAlbumImage1 } from '@/api/image'
-import { getUserPlaylist } from '@/api/collect'
-
-export default {
-  name: 'UserHome',
-  filters: {
-    ellipsis(value) {
-      if (!value) return ''
-      const max = 15
-      if (value.length > max) {
-        return value.slice(0, max) + '...'
-      }
-      return value
-    },
-    ellipsisUsername(value) {
-      if (!value) return ''
-      const max = 10
-      if (value.length > max) {
-        return value.slice(0, max) + '...'
-      }
-      return value
-    }
-  },
-  components: { StampBadge, VideoCard, ImageAlbumCard },
-  data() {
-    return {
-      // 屏幕宽度, 为了控制分页条的大小
-      screenWidth: document.body.clientWidth,
-      user: null,
-      userId: null,
-      followButton: {
-        icon: 'el-icon-plus',
-        text: '关注'
-      },
-      activeName: 'video',
-      currentPage: 1,
-      pageSize: 12,
-      totalSize: 0,
-      dataList: [],
-      queryInfo: {
-        userId: null,
-        pn: 1
-      },
-      showEmpty: true,
-      userContentData: null
-    }
-  },
-  watch: {
-    $route() {
-      this.$router.go()
-    }
-  },
-  created() {
-    this.userId = this.$route.params.id
-    this.queryInfo.userId = this.userId
-    getUserInfo(this.userId).then(resp => {
-      if (resp.code === 0) {
-        this.user = resp.data
-        const path = this.$route.path
-        if (path.endsWith('video')) {
-          this.activeName = 'video'
-          document.title = this.user.screenName + '的视频'
-        } else if (path.endsWith('image')) {
-          this.activeName = 'image'
-          document.title = this.user.screenName + '的图片'
-        } else if (path.endsWith('album')) {
-          this.activeName = 'album'
-          document.title = this.user.screenName + '的播放列表'
-        } else {
-          document.title = this.user.screenName + '的主页'
-        }
-        this.getData()
-      }
-    })
-    checkRelation(this.userId).then(resp => {
-      if (resp.code === 0) {
-        if (resp.data) {
-          this.followButton.text = '已关注'
-          this.followButton.icon = 'el-icon-check'
-        }
-      }
-    })
-    getUserContentData(this.userId).then(resp => {
-      if (resp.code === 0) {
-        this.userContentData = resp.data
-      }
-    })
-  },
-  mounted() {
-    // 当窗口宽度改变时获取屏幕宽度
-    window.onresize = () => {
-      return () => {
-        window.screenWidth = document.body.clientWidth
-        this.screenWidth = window.screenWidth
-      }
-    }
-  },
-  methods: {
-    followUser(userId) {
-      if (this.followButton.text === '关注') {
-        followUser(userId).then(resp => {
-          if (resp.code === 0) {
-            this.followButton.text = '已关注'
-            this.followButton.icon = 'el-icon-check'
-          }
-        })
-      } else {
-        unfollowUser(userId).then(resp => {
-          if (resp.code === 0) {
-            this.followButton.text = '关注'
-            this.followButton.icon = 'el-icon-plus'
-          }
-        })
-      }
-    },
-    sendMessage(userId) {
-      this.$message.info('暂未实现')
-    },
-    handleCurrentChange(pageNumber) {
-      this.currentPage = pageNumber
-      this.queryInfo.pn = pageNumber
-      this.getData()
-      // 回到顶部
-      scrollTo(0, 0)
-    },
-    tabClick(tab) {
-      this.dataList = []
-      this.activeName = tab.name
-      this.goToTab(this.activeName)
-    },
-    goToTab(activeName) {
-      const path = '/user/' + this.userId + '/' + activeName
-      if (this.$route.path === path) {
-        this.$router.go(0)
-        return
-      }
-      this.$router.push(path)
-    },
-    getData() {
-      this.dataList = []
-      if (this.activeName === 'video') {
-        this.userVideoListWrapper(this.currentPage, this.userId)
-      } else if (this.activeName === 'image') {
-        this.userImageWrapper(this.currentPage, this.userId)
-      } else if (this.activeName === 'album') {
-        this.userAlbumWrapper(this.currentPage, this.userId)
-      }
-    },
-    userVideoListWrapper(pageNumber, userId) {
-      getUserVideos(userId, pageNumber).then(resp => {
-        if (resp.code === 0) {
-          const respData = resp.data
-          this.dataList = respData.list
-          this.totalSize = respData.totalSize
-          this.showEmpty = this.dataList.length === 0
-        }
-      })
-    },
-    userImageWrapper(pageNumber, userId) {
-      getAlbumImage1(pageNumber, userId).then(resp => {
-        if (resp.code === 0) {
-          const respData = resp.data
-          this.dataList = respData.list
-          this.totalSize = respData.totalSize
-          this.showEmpty = this.dataList.length === 0
-        }
-      })
-    },
-    userAlbumWrapper(pageNumber, userId) {
-      getUserPlaylist(this.queryInfo).then(resp => {
-        if (resp.code === 0) {
-          const respData = resp.data
-          this.dataList = respData.list
-          this.totalSize = respData.totalSize
-          this.showEmpty = this.dataList.length === 0
-        }
-      })
-    }
-  }
-}
-</script>
-
-<style scoped>
-.movie-list {
-  padding-top: 15px;
-  padding-left: 6%;
-  padding-right: 6%;
-}
-
-.pagination {
-  text-align: center;
-  padding: 10px;
-}
-
-/*处于手机屏幕时*/
-@media screen and (max-width: 768px){
-  .movie-list {
-    padding-top: 8px;
-    padding-left: 0.5%;
-    padding-right: 0.5%;
-  }
-  .coverImg {
-    height: 120px !important;
-  }
-}
-
-.not-result {
-  padding-top: 100px;
-  padding-bottom: 100px;
-  text-align: center;
-}
-
-.item {
-  margin-top: 10px;
-  margin-right: 40px;
-}
-
-.coverImg {
-  width: 100%;
-  height: 90px;
-  display: block;
-}
-
-.imgs {
-  position: relative;
-}
-</style>

+ 369 - 0
src/views/user/UserHome.vue

@@ -0,0 +1,369 @@
+<template>
+  <div class="user-home-container">
+    <div class="user-header-wrapper">
+      <el-card v-if="user" class="user-info-card" :body-style="{ padding: '20px' }">
+        <div class="info-content-top">
+          <div class="avatar-section">
+            <el-avatar :size="90" :src="user.avatarUrl" class="user-avatar-main" />
+            <el-tag
+                v-if="user.vip"
+                size="mini"
+                effect="dark"
+                type="warning"
+                class="vip-tag-overlay"
+            >
+              VIP
+            </el-tag>
+          </div>
+
+          <div class="action-section">
+            <el-button
+                :type="followButton.text === '已关注' ? 'info' : 'danger'"
+                round
+                size="medium"
+                :icon="followButton.icon"
+                @click="followUser(user.userId)"
+            >
+              {{ followButton.text }}
+            </el-button>
+            <el-button icon="el-icon-message" round size="medium" @click="sendMessage(user.userId)">发消息</el-button>
+
+            <el-dropdown trigger="click" class="more-btn">
+              <el-button icon="el-icon-more" circle plain size="medium"></el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item v-if="user.biliUserId">
+                  <a :href="'https://space.bilibili.com/' + user.biliUserId" target="_blank" class="no-link-style">访问B站空间</a>
+                </el-dropdown-item>
+                <el-dropdown-item divider>举报用户</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
+          </div>
+        </div>
+
+        <div class="info-text-bottom">
+          <h2 class="user-name">
+            {{ user.screenName }}
+            <el-tag v-if="user.vip" size="mini" type="warning" effect="plain" class="name-vip-tag">会员</el-tag>
+            <el-tag v-if="user.biliUserId" size="mini" effect="plain" class="bili-tag">B站认证</el-tag>
+          </h2>
+          <p class="user-signature">{{ user.signature || '这个人很心大,居然没有写签名~' }}</p>
+
+          <div class="user-stats">
+            <router-link :to="`/user/${user.userId}/following`" class="stat-item">
+              <span class="stat-value">{{ user.following }}</span>
+              <span class="stat-label">关注</span>
+            </router-link>
+            <div class="stat-divider"></div>
+            <router-link :to="`/user/${user.userId}/follower`" class="stat-item">
+              <span class="stat-value">{{ user.follower }}</span>
+              <span class="stat-label">粉丝</span>
+            </router-link>
+          </div>
+        </div>
+      </el-card>
+    </div>
+
+    <div class="user-content-section">
+      <el-tabs v-if="userContentData" v-model="activeName" class="custom-tabs" @tab-click="tabClick">
+        <el-tab-pane name="video">
+          <span slot="label">视频 <span class="tab-count">{{ userContentData.videoCount }}</span></span>
+          <el-row :gutter="15" class="grid-container" v-loading="loading">
+            <el-col v-for="(video, index) in dataList" :key="index" :xs="12" :sm="8" :md="6">
+              <video-card :video="video" class="content-card-hover" />
+            </el-col>
+          </el-row>
+        </el-tab-pane>
+
+        <el-tab-pane name="image">
+          <span slot="label">图片 <span class="tab-count">{{ userContentData.imageCount }}</span></span>
+          <el-row :gutter="15" class="grid-container" v-loading="loading">
+            <el-col v-for="(album, index) in dataList" :key="index" :xs="12" :sm="8" :md="6">
+              <image-album-card :image-album="album" class="content-card-hover" />
+            </el-col>
+          </el-row>
+        </el-tab-pane>
+
+        <el-tab-pane name="album">
+          <span slot="label">收藏夹 <span class="tab-count">{{ userContentData.albumCount }}</span></span>
+          <el-row :gutter="15" class="grid-container" v-loading="loading">
+            <el-col v-for="(item, index) in dataList" :key="index" :xs="12" :sm="8" :md="6">
+              <el-card :body-style="{ padding: '0px' }" class="playlist-card content-card-hover">
+                <router-link :to="`/playlist/${item.albumId}`" class="no-link-style">
+                  <div class="playlist-cover-wrapper">
+                    <el-image lazy fit="cover" :src="item.coverUrl" class="playlist-img" />
+                    <div class="playlist-mask">
+                      <i class="el-icon-collection"></i> <span>{{ item.total }}</span>
+                    </div>
+                  </div>
+                  <div class="playlist-info">
+                    <span class="playlist-title">{{ item.albumName }}</span>
+                  </div>
+                </router-link>
+              </el-card>
+            </el-col>
+          </el-row>
+        </el-tab-pane>
+      </el-tabs>
+
+      <el-empty v-if="showEmpty && !loading" :image-size="200" description="该分类下空空如也" />
+
+      <div class="pagination-wrapper" v-if="totalSize > pageSize">
+        <el-pagination
+            background
+            layout="prev, pager, next"
+            :current-page="currentPage"
+            :page-size="pageSize"
+            :total="totalSize"
+            @current-change="handleCurrentChange"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import VideoCard from '@/components/card/VideoCard'
+import ImageAlbumCard from '@/components/card/ImageAlbumCard'
+import { getUserInfo, checkRelation, followUser, unfollowUser } from '@/api/user'
+import { getUserContentData, getUserVideos } from '@/api/video'
+import { getAlbumImage1 } from '@/api/image'
+import { getUserPlaylist } from '@/api/collect'
+
+export default {
+  name: 'UserHome',
+  components: { VideoCard, ImageAlbumCard },
+  data() {
+    return {
+      user: null,
+      userId: null,
+      followButton: { icon: 'el-icon-plus', text: '关注' },
+      activeName: 'video',
+      currentPage: 1,
+      pageSize: 12,
+      totalSize: 0,
+      dataList: [],
+      userContentData: null,
+      loading: false,
+      showEmpty: false
+    }
+  },
+  watch: {
+    '$route.query': {
+      handler() {
+        this.loadDataFromRoute();
+      },
+      deep: true
+    }
+  },
+  created() {
+    this.userId = this.$route.params.id;
+    this.initUserContext();
+  },
+  methods: {
+    initUserContext() {
+      Promise.all([
+        getUserInfo(this.userId),
+        checkRelation(this.userId),
+        getUserContentData(this.userId)
+      ]).then(([info, relation, content]) => {
+        if (info.code === 0) {
+          this.user = info.data;
+          this.loadDataFromRoute();
+        }
+        if (relation.code === 0 && relation.data) {
+          this.followButton = { icon: 'el-icon-check', text: '已关注' };
+        }
+        if (content.code === 0) {
+          this.userContentData = content.data;
+        }
+      });
+    },
+    loadDataFromRoute() {
+      const query = this.$route.query;
+      this.activeName = query.tab || 'video';
+      this.currentPage = query.page ? parseInt(query.page) : 1;
+      this.updatePageTitle();
+      this.fetchContentList();
+    },
+    syncStateToURL() {
+      this.$router.push({
+        path: this.$route.path,
+        query: { tab: this.activeName, page: this.currentPage }
+      }).catch(err => {
+        if (err.name !== 'NavigationDuplicated') throw err;
+      });
+    },
+    fetchContentList() {
+      this.loading = true;
+      this.dataList = [];
+      this.showEmpty = false;
+      let apiCall;
+      if (this.activeName === 'video') {
+        apiCall = getUserVideos(this.userId, this.currentPage);
+      } else if (this.activeName === 'image') {
+        apiCall = getAlbumImage1(this.currentPage, this.userId);
+      } else if (this.activeName === 'album') {
+        apiCall = getUserPlaylist({ userId: this.userId, pn: this.currentPage });
+      }
+      apiCall.then(resp => {
+        if (resp.code === 0) {
+          this.dataList = resp.data.list;
+          this.totalSize = resp.data.totalSize;
+          this.showEmpty = this.dataList.length === 0;
+        }
+      }).finally(() => { this.loading = false; });
+    },
+    tabClick(tab) {
+      this.activeName = tab.name;
+      this.currentPage = 1;
+      this.syncStateToURL();
+    },
+    handleCurrentChange(page) {
+      this.currentPage = page;
+      this.syncStateToURL();
+      window.scrollTo({ top: 0, behavior: 'smooth' });
+    },
+    updatePageTitle() {
+      if (!this.user) return;
+      const suffix = { video: '的视频', image: '的图片', album: '的收藏夹' };
+      document.title = `${this.user.screenName}${suffix[this.activeName] || '的主页'}`;
+    },
+    followUser(userId) {
+      const isFollowing = this.followButton.text === '已关注';
+      const api = isFollowing ? unfollowUser : followUser;
+      api(userId).then(resp => {
+        if (resp.code === 0) {
+          this.followButton = isFollowing ? { icon: 'el-icon-plus', text: '关注' } : { icon: 'el-icon-check', text: '已关注' };
+          this.$message.success(isFollowing ? '已取消关注' : '关注成功');
+        }
+      });
+    },
+    sendMessage() { this.$message.info('私信功能暂未开放'); }
+  }
+}
+</script>
+
+<style scoped>
+.user-home-container {
+  background-color: #f4f5f7;
+  min-height: 100vh;
+  padding: 20px 0 50px 0;
+}
+
+.user-header-wrapper {
+  max-width: 1100px;
+  margin: 0 auto 20px;
+  padding: 0 15px;
+}
+
+.user-info-card {
+  border-radius: 12px;
+  border: none;
+  box-shadow: 0 4px 12px rgba(0,0,0,0.05);
+}
+
+.info-content-top {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+}
+
+.avatar-section {
+  position: relative;
+  display: flex;
+}
+
+.user-avatar-main {
+  border: 2px solid #fff;
+  background: #fff;
+}
+
+/* 会员 Tag 覆盖在头像上的样式 */
+.vip-tag-overlay {
+  position: absolute;
+  bottom: 0;
+  right: -10px;
+  border-radius: 4px;
+  border: 1px solid #fff;
+  font-weight: bold;
+  transform: scale(0.9);
+}
+
+.name-vip-tag {
+  margin-left: 5px;
+  font-weight: normal;
+  vertical-align: middle;
+}
+
+.action-section {
+  display: flex;
+  gap: 10px;
+}
+
+.user-name {
+  margin: 0 0 8px 0;
+  font-size: 24px;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.user-signature {
+  color: #9499a0;
+  font-size: 14px;
+  margin-bottom: 20px;
+}
+
+.user-stats {
+  display: flex;
+  align-items: center;
+  gap: 30px;
+}
+
+.stat-item {
+  text-decoration: none;
+  display: flex;
+  gap: 5px;
+  font-size: 14px;
+}
+
+.stat-value { font-weight: bold; color: #18191c; }
+.stat-label { color: #9499a0; }
+.stat-divider { width: 1px; height: 14px; background: #e3e5e7; }
+
+.user-content-section {
+  max-width: 1100px;
+  margin: 0 auto;
+  padding: 0 15px;
+}
+
+.custom-tabs /deep/ .el-tabs__header {
+  background: #fff;
+  padding: 0 20px;
+  border-radius: 8px;
+  margin-bottom: 20px;
+}
+
+.tab-count { font-size: 12px; color: #9499a0; margin-left: 4px; }
+.content-card-hover { transition: all 0.3s ease; margin-bottom: 20px; }
+.content-card-hover:hover { transform: translateY(-5px); }
+.no-link-style { text-decoration: none; color: inherit; }
+
+.playlist-card { border-radius: 8px; overflow: hidden; }
+.playlist-cover-wrapper { position: relative; aspect-ratio: 16/9; }
+.playlist-img { width: 100%; height: 100%; }
+.playlist-mask { position: absolute; bottom: 0; right: 0; background: rgba(0,0,0,0.6); color: #fff; padding: 2px 8px; font-size: 12px; border-top-left-radius: 8px; }
+.playlist-info { padding: 10px; text-align: left; }
+.playlist-title { font-size: 14px; color: #18191c; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.pagination-wrapper { margin-top: 30px; display: flex; justify-content: center; }
+
+@media screen and (max-width: 768px) {
+  .user-home-container { padding-top: 10px; }
+  .user-avatar-main { width: 70px !important; height: 70px !important; }
+  .user-name { font-size: 20px; }
+  .vip-tag-overlay { right: -5px; bottom: -2px; }
+  .action-section .el-button { padding: 8px 12px; font-size: 12px; }
+  .more-btn { display: none; }
+}
+</style>