Browse Source

优化页面 ui

reghao 1 month ago
parent
commit
282a3a5453

+ 2 - 2
src/api/admin.js

@@ -30,8 +30,8 @@ export function setAccountCode(payload) {
   return post(adminApi.setAccountCodeApi, payload)
 }
 
-export function getUserList(nextId) {
-  return get(adminApi.getUsersApi + '?nextId=' + nextId)
+export function getUserList(queryParams) {
+  return get(adminApi.getUsersApi, queryParams)
 }
 
 export function getChargeReq(pn) {

+ 10 - 8
src/api/user.js

@@ -11,9 +11,7 @@ const userApi = {
   userFollowingApi: '/api/user/relation/following',
   userContactApi: '/api/user/contact/list',
   searchUserApi: '/api/user/search',
-  unreadMessageApi: '/api/message/user/unread',
-  unreadMessagesApi: '/api/message/user/unread/list',
-  setReadMessageApi: '/api/message/user/detail'
+  userMessageApi: '/api/message/user'
 }
 
 export function getUserInfo(userId) {
@@ -59,13 +57,17 @@ export function searchUser(queryForm) {
 }
 
 export function getUnreadCount() {
-  return get(userApi.unreadMessageApi)
+  return get(userApi.unreadMessageApi + '/unread')
 }
 
-export function getUnreadMessages(page) {
-  return get(userApi.unreadMessagesApi + '?pageNumber=' + page)
+export function getUnreadMessages(queryParams) {
+  return get(userApi.userMessageApi + '/unread/list', queryParams)
 }
 
-export function getMessageDetail(messageId) {
-  return get(userApi.setReadMessageApi + '/' + messageId)
+export function getMessageDetail(queryParams) {
+  return get(userApi.userMessageApi + '/detail', queryParams)
+}
+
+export function clearMessages() {
+  return post(userApi.userMessageApi + '/unread/clear')
 }

+ 0 - 9
src/router/background_account.js

@@ -4,7 +4,6 @@ const Background = () => import('views/admin/Background')
 
 // ********************************************************************************************************************
 // 用户后台主页
-const MyProfile = () => import('views/my/MyProfile')
 const MyRecord = () => import('views/my/MyRecord')
 const MyMessage = () => import('views/my/MyMessage')
 const MyContact = () => import('views/my/MyContact')
@@ -19,14 +18,6 @@ export default {
   component: Background,
   meta: { needAuth: true, roles: ['tnb_admin', 'tnb_user', 'tnb_disk'] },
   children: [
-    {
-      path: '/bg/account/profile',
-      name: 'MyProfile',
-      title: '我的资料',
-      icon: 'el-icon-user',
-      component: MyProfile,
-      meta: { needAuth: true, roles: ['tnb_admin', 'tnb_user', 'tnb_disk'] }
-    },
     {
       path: '/bg/account/record',
       name: 'MyRecord',

+ 9 - 9
src/router/background_admin.js

@@ -6,7 +6,7 @@ const Background = () => import('views/admin/Background')
 const AdminCharge = () => import('views/admin/aaa/AdminCharge')
 const AdminUserList = () => import('views/admin/aaa/AdminUserList')
 const AdminVideoList = () => import('views/admin/aaa/AdminVideoList')
-const AdminAvatarList = () => import('views/admin/aaa/AdminAvatarList')
+const AdminRole = () => import('views/admin/aaa/AdminRole')
 
 export default {
   path: '/bg/admin',
@@ -17,6 +17,14 @@ export default {
   component: Background,
   meta: { needAuth: true, roles: ['tnb_admin'] },
   children: [
+    {
+      path: '/bg/admin/role',
+      name: 'AdminRole',
+      title: '角色申请',
+      icon: 'el-icon-edit',
+      component: AdminRole,
+      meta: { needAuth: true, roles: ['tnb_admin'] }
+    },
     {
       path: '/bg/admin/charge',
       name: 'AdminCharge',
@@ -40,14 +48,6 @@ export default {
       icon: 'el-icon-edit',
       component: AdminVideoList,
       meta: { needAuth: true, roles: ['tnb_admin'] }
-    },
-    {
-      path: '/bg/admin/avatar',
-      name: 'AdminAvatarList',
-      title: '用户头像',
-      icon: 'el-icon-edit',
-      component: AdminAvatarList,
-      meta: { needAuth: true, roles: ['tnb_admin'] }
     }
   ]
 }

+ 174 - 113
src/views/admin/Background.vue

@@ -2,33 +2,34 @@
   <el-container class="admin-layout">
     <el-aside :width="collapsed ? '64px' : '240px'" class="aside-container">
       <div class="logo-section">
-        <router-link to="/bg">
+        <router-link to="/bg" class="logo-link">
           <img class="logo-img" src="@/assets/img/logo.png" alt="logo">
-          <transition name="el-fade-in">
+          <transition name="logo-fade">
             <h1 v-show="!collapsed" class="logo-text">用户后台</h1>
           </transition>
         </router-link>
       </div>
 
       <el-menu
-          :default-active="$route.path"
-          router
-          class="admin-menu"
-          background-color="#001529"
-          text-color="rgba(255, 255, 255, 0.65)"
-          active-text-color="#fff"
-          :unique-opened="true"
-          :collapse="collapsed"
-          :collapse-transition="false"
+        :default-active="$route.path"
+        router
+        class="admin-menu"
+        background-color="#0d131a"
+        text-color="rgba(255, 255, 255, 0.65)"
+        active-text-color="#ffffff"
+        :unique-opened="true"
+        :collapse="collapsed"
+        :collapse-transition="false"
       >
         <el-submenu v-for="item in menuList" :key="item.path" :index="item.path">
           <template slot="title">
-            <i :class="item.icon" />
-            <span>{{ item.title }}</span>
+            <i :class="[item.icon, 'menu-icon']" />
+            <span class="menu-title-text">{{ item.title }}</span>
           </template>
+
           <el-menu-item v-for="child in item.children" :key="child.path" :index="child.path">
-            <i :class="child.icon" />
-            <span slot="title">{{ child.title }}</span>
+            <i :class="[child.icon, 'menu-icon-child']" />
+            <span slot="title" class="menu-title-text">{{ child.title }}</span>
           </el-menu-item>
         </el-submenu>
       </el-menu>
@@ -38,20 +39,27 @@
       <el-header class="admin-header">
         <div class="header-left">
           <el-button
-              class="toggle-btn"
-              :icon="collapsed ? 'el-icon-s-unfold' : 'el-icon-s-fold'"
-              @click="collapsed = !collapsed"
+            class="toggle-btn"
+            :icon="collapsed ? 'el-icon-s-unfold' : 'el-icon-s-fold'"
+            @click="collapsed = !collapsed"
           />
         </div>
 
         <div class="header-right">
+          <div class="icon-item-wrapper" @click="goToMessage">
+            <el-badge :value="msgCount" :max="99" :hidden="msgCount <= 0" class="badge-item">
+              <i class="el-icon-message-solid custom-icon" />
+            </el-badge>
+            <span class="icon-label">消息</span>
+          </div>
+
           <el-dropdown trigger="hover" class="user-dropdown">
             <div class="user-info">
               <el-avatar
-                  :size="28"
-                  :src="user && user.avatarUrl ? user.avatarUrl : ''"
-                  icon="el-icon-user-solid"
-                  class="mgr-8"
+                :size="28"
+                :src="user && user.avatarUrl ? user.avatarUrl : ''"
+                icon="el-icon-user-solid"
+                class="mgr-8"
               />
               <span class="username">{{ user ? user.username : '管理员' }}</span>
               <i class="el-icon-arrow-down el-icon--right" />
@@ -60,7 +68,7 @@
               <el-dropdown-item icon="el-icon-s-home" @click.native="backToHome">
                 回到前台
               </el-dropdown-item>
-              <el-dropdown-item icon="el-icon-switch-button" @click.native="goToLogout" divided>
+              <el-dropdown-item icon="el-icon-switch-button" divided @click.native="goToLogout">
                 退出登录
               </el-dropdown-item>
             </el-dropdown-menu>
@@ -81,6 +89,7 @@
 import store from '@/store'
 import { userMixin } from 'assets/js/mixin'
 import { getAuthedUser } from '@/utils/auth'
+import { getUnreadCount } from '@/api/user'
 
 export default {
   name: 'Background',
@@ -89,11 +98,19 @@ export default {
     return {
       collapsed: false,
       user: null,
-      menuList: []
+      menuList: [],
+      msgCount: 0
     }
   },
   created() {
     this.user = getAuthedUser()
+    if (this.user !== null) {
+      getUnreadCount().then(resp => {
+        if (resp.code === 0) {
+          this.msgCount = resp.data.total
+        }
+      })
+    }
     this.initMenus()
   },
   methods: {
@@ -109,6 +126,9 @@ export default {
     },
     backToHome() {
       this.$router.push('/')
+    },
+    goToMessage() {
+      this.$router.push('/bg/account/message')
     }
   }
 }
@@ -120,142 +140,183 @@ export default {
   width: 100%;
 }
 
-/* 侧边栏整体背景 */
+/* ==================== 🌸 现代化侧边栏重塑 ==================== */
 .aside-container {
-  background-color: #001529;
-  transition: width 0.3s cubic-bezier(0.2, 0, 0, 1);
+  background-color: #0d131a; /* 更高级的黑青色,远离沉闷死黑 */
+  transition: width 0.25s cubic-bezier(0.3, 0.1, 0.3, 1);
   overflow: hidden;
-  box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
+  box-shadow: 4px 0 24px rgba(0, 0, 0, 0.15);
+  display: flex;
+  flex-direction: column;
 }
 
 .admin-menu {
   border-right: none;
+  flex: 1;
+  padding: 8px 0; /* 让菜单顶部有呼吸感 */
 }
 
-/* Logo 区域优化 */
+/* Logo 区域重绘 */
 .logo-section {
   height: 64px;
-  line-height: 64px;
   display: flex;
   align-items: center;
-  justify-content: center;
-  background-color: #002140; /* 比菜单稍微深一点 */
-  overflow: hidden;
+  padding: 0 16px;
+  background-color: #0d131a;
+  border-bottom: 1px solid rgba(255, 255, 255, 0.04); /* 极细精致分界线 */
+  box-sizing: border-box;
 }
-
-.logo-section a {
+.logo-link {
   display: flex;
   align-items: center;
+  text-decoration: none;
+  width: 100%;
+  padding-left: 4px;
 }
-
 .logo-img {
-  height: 32px;
-  width: 32px;
+  height: 28px;
+  width: 28px;
+  object-fit: contain;
+  filter: drop-shadow(0 2px 8px rgba(24, 144, 255, 0.3)); /* 让 Logo 微发光 */
 }
-
 .logo-text {
-  color: white;
-  font-size: 18px;
+  color: #ffffff;
+  font-size: 16px;
   margin-left: 12px;
   font-weight: 600;
   white-space: nowrap;
+  letter-spacing: 0.5px;
 }
-
-/* 顶部导航样式优化 */
-.admin-header {
-  background-color: #fff;
-  padding: 0 16px;
-  height: 64px !important;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
-  z-index: 10;
+/* Logo 丝滑文字动效 */
+.logo-fade-enter-active {
+  transition: all 0.3s ease-out;
 }
-
-.toggle-btn {
-  font-size: 20px;
-  border: none;
-  background: transparent;
-  color: #333;
-  padding: 8px;
-  transition: all 0.3s;
+.logo-fade-leave-active {
+  transition: all 0.1s ease-in;
 }
-
-.toggle-btn:hover {
-  background-color: rgba(0,0,0,0.025);
-  color: #1890ff;
+.logo-fade-enter, .logo-fade-leave-to {
+  opacity: 0;
+  transform: translateX(-10px);
 }
 
-/* 用户区域样式 */
-.user-info {
-  display: flex;
-  align-items: center;
-  padding: 0 12px;
-  height: 64px;
-  cursor: pointer;
-  transition: all 0.3s;
-}
+/* ==================== 🛠️ Element UI 菜单高级覆构 ==================== */
 
-.user-info:hover {
-  background-color: rgba(0, 0, 0, 0.025);
+/* 1. 基础间距与弧度微调 */
+::v-deep .el-submenu,
+::v-deep .el-menu-item {
+  padding: 0 12px !important; /* 给左右空出间距,打造胶囊浮动感 */
+  margin: 4px 0;
 }
 
-.username {
-  font-size: 14px;
-  font-weight: 500;
-  color: rgba(0, 0, 0, 0.65);
+::v-deep .el-submenu__title,
+::v-deep .el-menu-item {
+  height: 44px !important;
+  line-height: 44px !important;
+  border-radius: 6px !important; /* 统一小圆角 */
+  margin: 2px 0;
 }
 
-.mgr-8 { margin-right: 8px; }
-
-/* 主内容区优化 */
-.admin-main {
-  background-color: #f0f2f5;
-  padding: 24px;
+/* 图标与文本精致感调整 */
+.menu-icon {
+  font-size: 16px;
+  margin-right: 12px !important;
+  transition: transform 0.25s ease;
 }
-
-.main-content-card {
-  background: #fff;
-  min-height: calc(100vh - 112px); /* 减去 header 和 padding */
-  border-radius: 2px;
-  padding: 24px;
+.menu-icon-child {
+  font-size: 14px;
+  margin-right: 12px !important;
+  opacity: 0.7;
 }
-
-/* --- Element UI Menu 样式重写 (蓝色主题核心) --- */
-
-/* 激活项背景色 */
-::v-deep .el-menu-item.is-active {
-  background-color: #1890ff !important; /* 蓝色高亮 */
-  color: #fff !important;
+.menu-title-text {
+  font-size: 14px;
+  letter-spacing: 0.3px;
 }
 
-/* 悬浮背景色 */
+/* 2. 悬浮态(Hover)美化 */
 ::v-deep .el-submenu__title:hover,
 ::v-deep .el-menu-item:hover {
-  color: #fff !important;
-  background-color: transparent !important;
+  color: #ffffff !important;
+  background-color: rgba(255, 255, 255, 0.05) !important; /* 极其柔和的半透明浅白 */
+}
+::v-deep .el-submenu__title:hover .menu-icon {
+  transform: scale(1.08); /* 悬浮时图标微放大交互 */
 }
 
-::v-deep .el-menu-item:hover {
-  color: #1890ff !important;
+/* 3. 子菜单展开后的深色背景(层级化) */
+::v-deep .el-menu--inline {
+  background-color: #06090d !important; /* 子菜单背景明显更深,营造内陷嵌套感 */
+  border-radius: 6px;
+  padding: 4px 0;
+  margin-top: 2px;
 }
 
-/* 菜单项高度与图标间距 */
-::v-deep .el-menu-item, ::v-deep .el-submenu__title {
-  height: 50px;
-  line-height: 50px;
+/* 4. 核心:高难度现代高亮激活态(Floating Capsule & Side Indicator) */
+::v-deep .el-menu-item.is-active {
+  background-color: #1890ff !important; /* 主题蓝渐变基础色 */
+  background: linear-gradient(135deg, #1890ff 0%, #0076e4 100%) !important; /* 微渐变极具质感 */
+  color: #ffffff !important;
+  font-weight: 500;
+  box-shadow: 0 4px 12px rgba(24, 144, 255, 0.25); /* 胶囊发光悬浮感 */
+}
+::v-deep .el-menu-item.is-active .menu-icon-child {
+  opacity: 1;
+  color: #ffffff !important;
 }
 
-::v-deep .el-menu-item i, ::v-deep .el-submenu__title i {
-  color: inherit;
-  margin-right: 10px;
-  width: 14px;
-  text-align: center;
+/* 当子菜单项激活时,父级标题的亮白突出 */
+::v-deep .el-submenu.is-active > .el-submenu__title {
+  color: #ffffff !important;
+}
+::v-deep .el-submenu.is-active > .el-submenu__title i {
+  color: #1890ff !important; /* 父级图标变蓝 */
 }
 
-/* 侧边栏展开宽度 */
+/* 5. 折叠状态(Collapse)特殊处理 */
 .admin-menu:not(.el-menu--collapse) {
   width: 240px;
 }
+::v-deep .el-menu--collapse {
+  width: 64px;
+}
+::v-deep .el-menu--collapse .el-submenu,
+::v-deep .el-menu--collapse .el-menu-item {
+  padding: 0 8px !important; /* 折叠后更紧凑 */
+}
+::v-deep .el-menu--collapse .menu-icon {
+  margin-right: 0 !important;
+}
+
+/* ==================== 顶部和常规组件保持优秀样式 ==================== */
+.admin-header {
+  background-color: #fff;
+  padding: 0 20px;
+  height: 64px !important;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.05);
+  z-index: 10;
+}
+.toggle-btn {
+  font-size: 20px;
+  border: none;
+  background: transparent;
+  color: #333;
+  padding: 8px;
+  cursor: pointer;
+}
+.toggle-btn:hover { color: #1890ff; }
+.header-right { display: flex; align-items: center; gap: 8px; }
+.user-info { display: flex; align-items: center; padding: 0 12px; height: 64px; cursor: pointer; transition: all 0.3s; }
+.user-info:hover { background-color: rgba(0, 0, 0, 0.025); }
+.username { font-size: 14px; font-weight: 500; color: rgba(0, 0, 0, 0.65); }
+.mgr-8 { margin-right: 8px; }
+.admin-main { background-color: #f4f7f9; padding: 24px; }
+.main-content-card { background: #fff; min-height: calc(100vh - 112px); border-radius: 8px; padding: 24px; box-shadow: 0 1px 3px rgba(0,0,0,0.02); }
+.icon-item-wrapper { display: flex; align-items: center; justify-content: center; gap: 6px; height: 64px; padding: 0 16px; cursor: pointer; transition: all 0.3s; border-radius: 4px; }
+.icon-item-wrapper:hover { background-color: rgba(0, 0, 0, 0.025); }
+.icon-item-wrapper:hover .custom-icon, .icon-item-wrapper:hover .icon-label { color: #1890ff; }
+.custom-icon { font-size: 18px; color: rgba(0, 0, 0, 0.65); transition: color 0.3s; }
+.icon-label { font-size: 14px; font-weight: 500; color: rgba(0, 0, 0, 0.65); transition: color 0.3s; }
+.badge-item ::v-deep .el-badge__content { font-family: sans-serif; border: 1px solid #fff; top: 2px; right: 2px; }
 </style>

+ 422 - 110
src/views/admin/Dashboard.vue

@@ -1,103 +1,235 @@
 <template>
   <el-container class="dashboard-container">
-    <el-main class="movie-list">
-      <div class="section-block">
-        <h3 class="section-title">我的角色</h3>
-        <div class="btn-group">
-          <el-button
-            v-for="item in roles"
-            :key="item"
-            icon="el-icon-user-solid"
-            size="small"
-            @click="goToRole(item)"
-          >
-            {{ item }}
-          </el-button>
+    <el-main class="dashboard-main">
+
+      <div class="top-blocks-row">
+        <div class="section-block flex-item">
+          <h3 class="section-title">我的角色</h3>
+          <div class="btn-group">
+            <el-button
+              v-for="item in roles"
+              :key="item"
+              icon="el-icon-user-solid"
+              size="small"
+              class="custom-btn"
+              @click="goToRole(item)"
+            >
+              {{ item }}
+            </el-button>
+          </div>
         </div>
-      </div>
 
-      <div class="section-block">
-        <h3 class="section-title">Dashboard</h3>
-        <div class="btn-group">
-          <el-button icon="el-icon-files" size="small" @click="navTo('/disk')">Disk</el-button>
-          <el-button icon="el-icon-film" size="small" @click="navTo('/')">VOD</el-button>
-          <el-button icon="el-icon-document" size="small" @click="navTo('/blog')">Blog</el-button>
+        <div class="section-block flex-item">
+          <h3 class="section-title">快捷工作台</h3>
+          <div class="btn-group">
+            <el-button icon="el-icon-files" size="small" class="custom-btn" @click="navTo('/disk')">Disk</el-button>
+            <el-button icon="el-icon-film" size="small" class="custom-btn" @click="navTo('/')">VOD</el-button>
+            <el-button icon="el-icon-document" size="small" class="custom-btn" @click="navTo('/blog')">Blog</el-button>
+          </div>
         </div>
       </div>
 
-      <div class="card-grid">
-        <el-card class="box-card" shadow="hover">
-          <div slot="header" class="clearfix">
-            <span>我的资料</span>
+      <el-card class="box-card profile-card" shadow="hover">
+        <div slot="header" class="card-header-custom">
+          <i class="el-icon-postcard" />
+          <span>个人档案资料</span>
+        </div>
+        <div class="profile-card-content">
+          <div class="avatar-section">
+            <div class="avatar-wrapper">
+              <el-image :src="loginUser.avatarUrl" class="user-avatar" fit="cover">
+                <div slot="error" class="avatar-error-slot">
+                  <i class="el-icon-user" />
+                </div>
+              </el-image>
+            </div>
+            <el-button
+              type="primary"
+              size="mini"
+              icon="el-icon-camera"
+              class="update-avatar-btn"
+              @click="openAvatarDialog"
+            >
+              更换头像
+            </el-button>
           </div>
-          <el-form ref="profileForm" :model="loginUser" label-width="80px" size="small">
-            <el-form-item label="性别">
-              <el-input v-model="loginUser.gender" class="w-70" readonly />
-            </el-form-item>
-            <br />
-            <el-form-item label="签名">
+
+          <el-form ref="profileForm" :model="loginUser" label-width="80px" size="small" class="profile-form">
+            <div class="form-grid">
+              <el-form-item label="用户 ID">
+                <el-input v-model="loginUser.userId" readonly class="is-readonly" />
+              </el-form-item>
+              <el-form-item label="用户名">
+                <el-input v-model="loginUser.username" readonly class="is-readonly" />
+              </el-form-item>
+
+              <el-form-item label="显示名">
+                <div class="input-with-action">
+                  <el-input v-model="loginUser.screenName" readonly />
+                  <el-button type="text" icon="el-icon-edit" @click="openEditDialog('screenName', '显示名', loginUser.screenName)">修改</el-button>
+                </div>
+              </el-form-item>
+
+              <el-form-item label="电子邮箱">
+                <div class="input-with-action">
+                  <el-input v-model="loginUser.email" readonly />
+                  <el-button type="text" icon="el-icon-edit" @click="openEditDialog('email', '电子邮箱', loginUser.email)">修改</el-button>
+                </div>
+              </el-form-item>
+
+              <el-form-item label="手机号码">
+                <div class="input-with-action">
+                  <el-input v-model="loginUser.mobile" readonly />
+                  <el-button type="text" icon="el-icon-edit" @click="openEditDialog('mobile', '手机号码', loginUser.mobile)">修改</el-button>
+                </div>
+              </el-form-item>
+
+              <el-form-item label="安全密码">
+                <div class="input-with-action">
+                  <el-input value="******" type="password" readonly show-password />
+                  <el-button type="text" icon="el-icon-key" @click="passwordDialogVisible = true">重置</el-button>
+                </div>
+              </el-form-item>
+            </div>
+
+            <el-form-item label="个性签名" class="full-width-item signature-item">
               <el-input
+                ref="signatureInput"
                 v-model="loginUser.signature"
                 type="textarea"
                 :rows="3"
-                class="w-70"
-                readonly
+                :readonly="!isEditingSignature"
+                :placeholder="isEditingSignature ? '请输入您的个性签名...' : '这个人很懒,什么都没有留下~'"
+                resize="none"
               />
+              <div class="signature-actions">
+                <el-button v-if="!isEditingSignature" type="text" icon="el-icon-edit" @click="startEditSignature">修改签名</el-button>
+                <template v-else>
+                  <el-button size="mini" type="primary" round @click="saveSignature">保存</el-button>
+                  <el-button size="mini" round @click="cancelEditSignature">取消</el-button>
+                </template>
+              </div>
             </el-form-item>
           </el-form>
-        </el-card>
+        </div>
+      </el-card>
+    </el-main>
 
-        <el-card class="box-card" shadow="hover">
-          <div slot="header" class="clearfix">
-            <span>我的帐号</span>
+    <el-dialog title="更新个人头像" :visible.sync="avatarDialogVisible" width="400px" append-to-body center custom-class="custom-dialog">
+      <div class="avatar-upload-box">
+        <el-upload
+          class="avatar-uploader"
+          :action="imgOssUrl"
+          :headers="imgHeaders"
+          :data="imgData"
+          :with-credentials="false"
+          :show-file-list="false"
+          :before-upload="beforeAvatarUpload"
+          :on-success="handleAvatarSuccess"
+        >
+          <div v-if="previewUrl" class="upload-preview-wrapper">
+            <img :src="previewUrl" class="upload-preview" alt="preview">
+            <div class="upload-mask"><i class="el-icon-edit" /><span>更换图片</span></div>
           </div>
-          <el-form ref="accountForm" :model="loginUser" label-width="80px" size="small">
-            <el-form-item label="用户 ID">
-              <el-input v-model="loginUser.userId" class="w-70" readonly />
-            </el-form-item>
-            <el-form-item label="用户名">
-              <el-input v-model="loginUser.username" class="w-70" readonly />
-            </el-form-item>
-            <el-form-item label="显示名">
-              <el-input v-model="loginUser.screenName" class="w-70" readonly />
-            </el-form-item>
-            <el-form-item label="手机">
-              <el-input v-model="loginUser.mobile" class="w-70" readonly />
-              <el-button type="primary" plain size="mini" class="ml-10" @click="showUpdateDialog(1)">更新</el-button>
-            </el-form-item>
-            <el-form-item label="密码">
-              <el-input value="******" type="password" class="w-70" readonly show-password />
-              <el-button type="primary" plain size="mini" class="ml-10" @click="showUpdateDialog(2)">更新</el-button>
-            </el-form-item>
-          </el-form>
-        </el-card>
+          <div v-else-if="loginUser.avatarUrl" class="upload-preview-wrapper">
+            <img :src="loginUser.avatarUrl" class="upload-preview" alt="current">
+            <div class="upload-mask"><i class="el-icon-plus" /><span>上传新头像</span></div>
+          </div>
+          <div v-else class="upload-placeholder">
+            <i class="el-icon-plus" /><p>点击选择新头像</p>
+          </div>
+        </el-upload>
       </div>
-    </el-main>
+      <div class="upload-tip-text">仅支持 JPG 格式,大小不超过 2MB</div>
+      <span slot="footer" class="dialog-footer">
+        <el-button size="small" round @click="cancelAvatarUpdate">取 消</el-button>
+        <el-button size="small" type="primary" round :loading="submitLoading" @click="confirmAvatarUpdate">确 认</el-button>
+      </span>
+    </el-dialog>
+
+    <el-dialog :title="'修改' + editModel.title" :visible.sync="editDialogVisible" width="420px" append-to-body custom-class="custom-dialog">
+      <el-form label-position="top" size="small" style="padding: 0 10px;">
+        <el-form-item :label="'新的' + editModel.title">
+          <el-input v-model="editModel.value" autocomplete="off" clearable :placeholder="'请输入新的' + editModel.title" />
+        </el-form-item>
+      </el-form>
+      <span slot="footer" class="dialog-footer">
+        <el-button size="small" round @click="editDialogVisible = false">取 消</el-button>
+        <el-button size="small" type="primary" round :loading="editSubmitLoading" @click="submitSingleField">确 认</el-button>
+      </span>
+    </el-dialog>
+
+    <el-dialog title="重置安全密码" :visible.sync="passwordDialogVisible" width="420px" append-to-body custom-class="custom-dialog">
+      <el-form ref="passwordForm" :model="passwordForm" label-position="top" size="small" style="padding: 0 10px;">
+        <el-form-item label="当前旧密码">
+          <el-input v-model="passwordForm.oldPassword" type="password" show-password placeholder="请输入当前密码验证身份" />
+        </el-form-item>
+        <el-form-item label="输入新密码">
+          <el-input v-model="passwordForm.newPassword" type="password" show-password placeholder="新密码长度建议大于6位" />
+        </el-form-item>
+      </el-form>
+      <span slot="footer" class="dialog-footer">
+        <el-button size="small" round @click="closePasswordDialog">取 消</el-button>
+        <el-button size="small" type="primary" round :loading="passwordSubmitLoading" @click="submitPassword">确 认</el-button>
+      </span>
+    </el-dialog>
+
   </el-container>
 </template>
 
 <script>
 import { userMixin } from 'assets/js/mixin'
-import { getAuthedUser } from '@/utils/auth'
+import { getAuthedUser, updateAuthedUser } from '@/utils/auth'
+import { updateAvatar } from '@/api/account' // 假设你的其它更新 API 也在 account 或对应模块里
+import { getAvatarChannelInfo } from '@/api/file'
 
 export default {
   name: 'Dashboard',
   mixins: [userMixin],
   data() {
     return {
-      // 初始化防空指针报错
       loginUser: {
+        avatarUrl: '',
         gender: '',
         signature: '',
         userId: '',
         username: '',
         screenName: '',
         mobile: '',
+        email: '',
         roles: []
       },
       roles: [],
-      machineStatList: [],
-      sysInfo: null
+
+      // 头像上传状态
+      avatarDialogVisible: false,
+      submitLoading: false,
+      imgOssUrl: '',
+      imgHeaders: { Authorization: '' },
+      imgData: { channelCode: 0 },
+      previewUrl: '',
+      uploadSuccessData: null,
+
+      // 常规单字段修改弹窗状态
+      editDialogVisible: false,
+      editSubmitLoading: false,
+      editModel: {
+        field: '',
+        title: '',
+        value: ''
+      },
+
+      // 签名行内修改状态
+      isEditingSignature: false,
+      cachedSignature: '',
+
+      // 密码修改弹窗状态
+      passwordDialogVisible: false,
+      passwordSubmitLoading: false,
+      passwordForm: {
+        oldPassword: '',
+        newPassword: ''
+      }
     }
   },
   created() {
@@ -111,18 +243,7 @@ export default {
         this.loginUser = user
         this.roles = user.roles || []
       }
-      this.getData()
-    },
-    getData() {
-      // 异步获取其他数据
     },
-    goToRole(role) {
-      this.$message.info(`role -> ${role}`)
-    },
-    /**
-     * 统一路由跳转方法,代替原有的三个重复方法
-     * @param {string} path 目标路径
-     */
     navTo(path) {
       if (this.$route.path === path) {
         this.$router.go(0)
@@ -130,58 +251,249 @@ export default {
       }
       this.$router.push(path)
     },
-    showUpdateDialog(type) {
-      // 弹出更新弹窗的逻辑
-      this.$message.success(`打开更新弹窗: ${type === 1 ? '手机' : '密码'}`)
-    }
+
+    // ==================== 1. 常规单字段(显示名、邮箱、手机)修改业务 ====================
+    openEditDialog(field, title, currentVal) {
+      this.editModel.field = field
+      this.editModel.title = title
+      this.editModel.value = currentVal
+      this.editDialogVisible = true
+    },
+    submitSingleField() {
+      if (!this.editModel.value.trim()) {
+        return this.$message.warning(`请输入有效的${this.editModel.title}`)
+      }
+
+      this.editSubmitLoading = true
+
+      // 模拟或者组装你的 API 请求参数,例如:
+      // const updateData = { [this.editModel.field]: this.editModel.value }
+      // updateUserInfo(updateData).then(res => { ... })
+
+      setTimeout(() => {
+        // 1. 动态同步到本地模型
+        this.loginUser[this.editModel.field] = this.editModel.value
+        // 2. 持久化到 localStorage/cookie 缓存
+        updateAuthedUser(this.loginUser)
+
+        this.$message.success(`${this.editModel.title}已成功修改`)
+        this.editDialogVisible = false
+        this.editSubmitLoading = false
+      }, 600)
+    },
+
+    // ==================== 2. 签名行内即时修改业务 ====================
+    startEditSignature() {
+      this.cachedSignature = this.loginUser.signature // 缓存旧签名以便取消
+      this.isEditingSignature = true
+      this.$nextTick(() => {
+        this.$refs.signatureInput.focus() // 自动聚焦到输入框
+      })
+    },
+    saveSignature() {
+      // 触发后端 API 保存签名逻辑
+      this.$message.success('个性签名已同步更新')
+      updateAuthedUser(this.loginUser)
+      this.isEditingSignature = false
+    },
+    cancelEditSignature() {
+      this.loginUser.signature = this.cachedSignature // 恢复原状
+      this.isEditingSignature = false
+    },
+
+    // ==================== 3. 独立密码修改业务 ====================
+    submitPassword() {
+      if (!this.passwordForm.oldPassword || !this.passwordForm.newPassword) {
+        return this.$message.warning('请完整填写当前密码与新密码')
+      }
+      if (this.passwordForm.newPassword.length < 6) {
+        return this.$message.warning('为了您的账户安全,新密码不得少于6位')
+      }
+
+      this.passwordSubmitLoading = true
+      // 调用你的密码更新 API 接口
+      // changePassword(this.passwordForm).then(res => { ... })
+
+      setTimeout(() => {
+        this.$message.success('安全密码修改成功,请妥善保管')
+        this.closePasswordDialog()
+      }, 800)
+    },
+    closePasswordDialog() {
+      this.passwordDialogVisible = false
+      this.passwordForm.oldPassword = ''
+      this.passwordForm.newPassword = ''
+      this.passwordSubmitLoading = false
+    },
+
+    // ==================== 4. 头像模块云存储控制(保持不变) ====================
+    openAvatarDialog() {
+      const pageLoading = this.$loading({
+        lock: true,
+        text: '正在开辟安全云传输通道...',
+        spinner: 'el-icon-loading',
+        background: 'rgba(255, 255, 255, 0.7)'
+      })
+      getAvatarChannelInfo().then(res => {
+        if (res.code === 0) {
+          this.imgData.channelCode = res.data.channelCode
+          this.imgOssUrl = res.data.ossUrl
+          this.imgHeaders.Authorization = 'Bearer ' + res.data.token
+          this.avatarDialogVisible = true
+        } else {
+          this.$message.error(res.msg)
+        }
+      }).catch(error => {
+        this.$message.error(error.message)
+      }).finally(() => {
+        pageLoading.close()
+      })
+    },
+    beforeAvatarUpload(file) {
+      if (!this.imgOssUrl) {
+        this.$message.error('未检测到可用的云端存储节点!')
+        return false
+      }
+      const isJPG = file.type === 'image/jpeg'
+      const isLt2M = file.size / 1024 / 1024 < 2
+      if (!isJPG) this.$message.error('仅允许上传 JPG 格式的图片!')
+      if (!isLt2M) this.$message.error('图片体积不能超过 2MB!')
+      return isJPG && isLt2M
+    },
+    handleAvatarSuccess(res, file) {
+      if (res.code === 0) {
+        this.previewUrl = URL.createObjectURL(file.raw)
+        this.uploadSuccessData = res.data
+        this.$message.success('新头像本地预载完成,请点击确认进行同步')
+      } else {
+        this.$message.error('文件服务器解析异常: ' + res.msg)
+      }
+    },
+    confirmAvatarUpdate() {
+      if (!this.uploadSuccessData) {
+        return this.$message.warning('尚未检测到新上传的图像资源')
+      }
+      this.submitLoading = true
+      const postParam = {
+        channelCode: this.imgData.channelCode,
+        uploadId: this.uploadSuccessData.uploadId
+      }
+      updateAvatar(postParam).then(resp => {
+        if (resp.code === 0) {
+          this.loginUser.avatarUrl = resp.data.avatarUrl
+          updateAuthedUser(this.loginUser)
+          this.$message.success('个人头像已成功同步至云端')
+          this.avatarDialogVisible = false
+          this.clearUploadStates()
+        } else {
+          this.$message.warning('配置档案同步失败,请稍后重试')
+        }
+      }).finally(() => {
+        this.submitLoading = false
+      })
+    },
+    cancelAvatarUpdate() {
+      this.avatarDialogVisible = false
+      this.clearUploadStates()
+    },
+    clearUploadStates() {
+      if (this.previewUrl && this.previewUrl.startsWith('blob:')) {
+        URL.revokeObjectURL(this.previewUrl)
+      }
+      this.previewUrl = ''
+      this.uploadSuccessData = null
+    },
+    goToRole(role) { this.$message.info(`当前操作角色: ${role}`) }
   }
 }
 </script>
 
 <style scoped>
-.dashboard-container {
-  background-color: #f5f7fa;
-  min-height: 100vh;
-}
+/* 基础布局样式保持不变 */
+.dashboard-container { background-color: #f6f8f9; min-height: 100vh; }
+.dashboard-main { padding: 24px; }
+.top-blocks-row { display: flex; gap: 20px; margin-bottom: 20px; flex-wrap: wrap; }
+.flex-item { flex: 1; min-width: 320px; margin-bottom: 0 !important; }
+.section-block { background: #ffffff; padding: 20px; border-radius: 8px; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.02); border: 1px solid #eef1f5; }
+.section-title { margin-top: 0; margin-bottom: 18px; color: #1f2d3d; font-size: 15px; font-weight: 600; border-left: 4px solid #1890ff; padding-left: 12px; }
+.btn-group { display: flex; flex-wrap: wrap; gap: 10px; }
+.custom-btn { border-radius: 6px; transition: all 0.2s ease; background: #f8fafc; border-color: #e2e8f0; color: #475569; }
+.custom-btn:hover { background: #fff; border-color: #1890ff; color: #1890ff; transform: translateY(-1px); box-shadow: 0 2px 6px rgba(24, 144, 255, 0.15); }
 
-.section-block {
-  margin-bottom: 25px;
-  background: #fff;
-  padding: 15px 20px;
-  border-radius: 4px;
-  box-shadow: 0 2px 12px 0 rgba(0,0,0,0.05);
-}
+/* 资料档案卡片 */
+.profile-card { border-radius: 8px; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.02) !important; border: 1px solid #eef1f5; }
+.card-header-custom { display: flex; align-items: center; gap: 8px; font-weight: 600; color: #334155; font-size: 15px; }
+.card-header-custom i { color: #1890ff; font-size: 17px; }
+.profile-card-content { display: flex; gap: 40px; padding: 10px 0; flex-wrap: wrap; }
 
-.section-title {
-  margin-top: 0;
-  margin-bottom: 15px;
-  color: #303133;
-  font-size: 16px;
-  border-left: 4px solid #409EFF;
-  padding-left: 10px;
-}
+/* 左侧头像展示区 */
+.avatar-section { display: flex; flex-direction: column; align-items: center; flex-shrink: 0; width: 140px; }
+.avatar-wrapper { padding: 4px; border: 1px solid #e2e8f0; border-radius: 50%; background: #fff; box-shadow: 0 4px 10px rgba(0,0,0,0.03); }
+.user-avatar { width: 110px; height: 110px; border-radius: 50%; background-color: #f1f5f9; }
+.avatar-error-slot { display: flex; justify-content: center; align-items: center; width: 100%; height: 100%; background: #f8fafc; color: #94a3b8; font-size: 36px; }
+.update-avatar-btn { margin-top: 16px !important; width: 100%; border-radius: 20px; box-shadow: 0 4px 10px rgba(24, 144, 255, 0.15); }
+
+/* 右侧多列网格表单及动作按钮整合 */
+.profile-form { flex: 1; min-width: 280px; }
+.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 4px 32px; }
 
-.btn-group .el-button {
-  margin-right: 10px;
-  margin-bottom: 10px;
+/* 核心:带操作按钮的复合型 Input */
+.input-with-action {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+.input-with-action .el-input {
+  flex: 1;
+}
+.input-with-action .el-button--text {
+  padding: 0;
+  font-weight: 500;
+  color: #1890ff;
 }
 
-/* 布局网格 */
-.card-grid {
-  display: grid;
-  grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
-  gap: 20px;
+/* 独立修饰绝对不可编辑的项 */
+.is-readonly ::v-deep .el-input__inner {
+  background-color: #f1f5f9 !important;
+  color: #94a3b8 !important;
 }
 
-.box-card {
-  margin-bottom: 0;
+/* 签名大文本定制 */
+.full-width-item { grid-column: 1 / -1; margin-top: 12px; }
+.signature-item { position: relative; }
+.signature-actions {
+  display: flex;
+  justify-content: flex-end;
+  gap: 10px;
+  margin-top: 6px;
 }
 
-/* 工具类样式 */
-.w-70 {
-  width: 70% !important;
+/* 全局统一下降级 Input 的只读底色质感 */
+::v-deep .el-input__inner,
+::v-deep .el-textarea__inner {
+  border-color: #e2e8f0 !important;
+  color: #334155 !important;
+  background-color: #ffffff; /* 允许编辑的框默认亮白 */
 }
-.ml-10 {
-  margin-left: 10px !important;
+::v-deep .el-input__inner[readonly],
+::v-deep .el-textarea__inner[readonly] {
+  background-color: #f8fafc !important; /* 处于只读状态时柔和变灰 */
 }
+::v-deep .el-form-item__label { color: #64748b; font-weight: 500; }
+
+/* 弹窗及云上传通用 */
+.avatar-upload-box { display: flex; justify-content: center; padding: 10px 0 5px 0; }
+.avatar-uploader ::v-deep .el-upload { border: 2px dashed #cbd5e1; border-radius: 50%; cursor: pointer; position: relative; overflow: hidden; width: 140px; height: 140px; background-color: #f8fafc; transition: all 0.25s ease; }
+.avatar-uploader ::v-deep .el-upload:hover { border-color: #1890ff; background-color: #f0f7ff; }
+.upload-placeholder { display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100%; color: #64748b; }
+.upload-placeholder i { font-size: 28px; margin-bottom: 6px; color: #94a3b8;}
+.upload-placeholder p { margin: 0; font-size: 13px; }
+.upload-preview-wrapper { position: relative; width: 140px; height: 140px; }
+.upload-preview { width: 140px; height: 140px; object-fit: cover; border-radius: 50%; }
+.upload-mask { position: absolute; inset: 0; background: rgba(15, 23, 42, 0.6); color: #fff; display: flex; flex-direction: column; gap: 6px; justify-content: center; align-items: center; font-size: 12px; border-radius: 50%; opacity: 0; transition: opacity 0.25s ease; }
+.upload-preview-wrapper:hover .upload-mask { opacity: 1; }
+.upload-tip-text { text-align: center; font-size: 12px; color: #64748b; margin-top: 12px; }
+
+/* 统一高级圆角弹窗 */
+::v-deep .custom-dialog { border-radius: 12px; overflow: hidden; }
 </style>

+ 0 - 48
src/views/admin/aaa/AdminAvatarList.vue

@@ -1,48 +0,0 @@
-<template>
-  <el-row id="movie-list">
-    <el-col v-for="(user, index) in userList" :key="index" :md="1" :sm="3" :xs="3">
-      <router-link target="_blank" :to="`/user/` + user.userIdStr">
-        <el-avatar>
-          <el-image :src="user.avatarUrl" />
-        </el-avatar>
-      </router-link>
-    </el-col>
-  </el-row>
-</template>
-
-<script>
-import { getUserAvatarList } from '@/api/admin'
-
-export default {
-  name: 'Discover',
-  data() {
-    return {
-      // 屏幕宽度, 为了控制分页条的大小
-      screenWidth: document.body.clientWidth,
-      currentPage: 1,
-      userList: [],
-      showEmpty: true
-    }
-  },
-  created() {
-    document.title = '已发布稿件的用户'
-    getUserAvatarList().then(resp => {
-      if (resp.code === 0) {
-        this.userList = resp.data
-      } else {
-        this.$message.error(resp.msg)
-      }
-    })
-  },
-  methods: {
-  }
-}
-</script>
-
-<style scoped>
-#movie-list {
-  padding-top: 10px;
-  padding-left: 3%;
-  padding-right: 3%;
-}
-</style>

+ 40 - 0
src/views/admin/aaa/AdminRole.vue

@@ -0,0 +1,40 @@
+<template>
+  <el-row>
+    <el-col v-for="(user, index) in userList" :key="index" :md="1" :sm="3" :xs="3">
+      <router-link target="_blank" :to="`/user/` + user.userIdStr">
+        <el-avatar>
+          <el-image :src="user.avatarUrl" />
+        </el-avatar>
+      </router-link>
+    </el-col>
+  </el-row>
+</template>
+
+<script>
+import { getUserAvatarList } from '@/api/admin'
+
+export default {
+  name: 'AdminRole',
+  data() {
+    return {
+      userList: []
+    }
+  },
+  created() {
+  },
+  methods: {
+    getData() {
+      getUserAvatarList().then(resp => {
+        if (resp.code === 0) {
+          this.userList = resp.data
+        } else {
+          this.$message.error(resp.msg)
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+</style>

+ 283 - 109
src/views/admin/aaa/AdminUserList.vue

@@ -1,148 +1,194 @@
 <template>
-  <el-container>
-    <el-header height="220">
-      <el-row style="margin-top: 10px">
-        <el-select
-          v-model="queryInfo.scope"
-          clearable
-          placeholder="查询条件"
-          style="margin-left: 5px"
-          @change="onSelectChange"
-        />
-        <el-button type="plain" icon="el-icon-refresh" style="margin-left: 5px" @click="onRefresh">刷新</el-button>
-      </el-row>
+  <el-container class="admin-user-container">
+    <el-header height="auto" class="search-header">
+      <el-form :inline="true" :model="queryParams" size="small" class="search-form" @submit.native.prevent>
+        <el-form-item label="用户筛选">
+          <el-input
+            v-model="queryParams.screenName"
+            clearable
+            placeholder="输入显示名搜索"
+            prefix-icon="el-icon-search"
+            class="search-input"
+            @keyup.enter.native="handleSearch"
+          />
+        </el-form-item>
+
+        <el-form-item label="状态">
+          <el-select
+            v-model="queryParams.status"
+            clearable
+            placeholder="全部状态"
+            class="status-select"
+            @change="handleSearch"
+          >
+            <el-option label="正常" :value="1" />
+            <el-option label="封禁中" :value="2" />
+            <el-option label="已注销" :value="3" />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item class="search-actions">
+          <el-button type="primary" icon="el-icon-search" @click="handleSearch">查询</el-button>
+          <el-button icon="el-icon-refresh" class="btn-refresh" @click="onRefresh">重置</el-button>
+        </el-form-item>
+      </el-form>
     </el-header>
-    <el-main>
+
+    <el-main class="table-main">
       <el-table
         :data="dataList"
         border
-        height="480"
+        stripe
+        height="520"
         style="width: 100%"
+        class="custom-table"
+        v-loading="loading"
       >
         <el-table-column
           fixed="left"
           label="No"
           type="index"
+          width="60"
+          align="center"
         />
+
         <el-table-column
-          prop="avatarUrl"
           label="头像"
-          width="90"
+          width="80"
+          align="center"
         >
           <template slot-scope="scope">
-            <el-image :src="scope.row.avatarUrl" min-width="30" height="20" />
+            <el-avatar
+              :size="36"
+              :src="scope.row.avatarUrl"
+              icon="el-icon-user-solid"
+              class="table-avatar"
+            />
           </template>
         </el-table-column>
+
         <el-table-column
           prop="userId"
           label="用户 ID"
-        >
-        </el-table-column>
+          width="120"
+          align="center"
+        />
+
         <el-table-column
           prop="username"
           label="用户名"
+          min-width="120"
         />
+
         <el-table-column
           prop="screenName"
           label="显示名"
+          min-width="120"
         >
           <template slot-scope="scope">
-            <router-link target="_blank" :to="`/user/${scope.row.userId}`">
-              <span>{{ scope.row.screenName }}</span>
+            <router-link :to="`/user/${scope.row.userId}`" class="user-link">
+              <span>{{ scope.row.screenName || '未设置' }}</span>
+              <i class="el-icon-link link-icon" />
             </router-link>
           </template>
         </el-table-column>
+
         <el-table-column
           prop="signature"
-          label="签名"
-          :show-overflow-tooltip="true"
+          label="个性签名"
+          min-width="180"
         >
           <template slot-scope="scope">
             <el-tooltip
               v-if="scope.row.signature"
               :content="scope.row.signature"
-              raw-content
-              placement="top-start"
+              placement="top"
+              :open-delay="400"
             >
-              <span v-if="scope.row.signature && scope.row.signature.length <= 15">
-                {{ scope.row.signature }}
-              </span>
-              <span v-if="scope.row.signature && scope.row.signature.length > 15">
-                {{ scope.row.signature.substr(0, 15) + "..." }}
-              </span>
+              <span class="ellipsis-text">{{ scope.row.signature }}</span>
             </el-tooltip>
-            <span v-else-if="scope.row.signature === null">-</span>
+            <span v-else class="text-muted">-</span>
           </template>
         </el-table-column>
+
         <el-table-column
           prop="pubDate"
           label="注册时间"
+          width="160"
+          align="center"
         />
+
         <el-table-column
-          prop="scope"
           label="帐号状态"
+          width="100"
+          align="center"
         >
           <template slot-scope="scope">
-            <el-tag v-if="scope.row.status === 1" :type="'warning'" disable-transitions>
+            <el-tag v-if="scope.row.status === 1" type="success" size="mini" effect="light">
               正常
             </el-tag>
-            <el-tag v-else-if="scope.row.status === 2" :type="'success'" disable-transitions>
+            <el-tag v-else-if="scope.row.status === 2" type="danger" size="mini" effect="light">
               封禁中
             </el-tag>
-            <el-tag v-else :type="'danger'" disable-transitions>
+            <el-tag v-else type="info" size="mini" effect="light">
               已注销
             </el-tag>
           </template>
         </el-table-column>
+
         <el-table-column
-          prop="status"
-          label="当前会话"
-        >
-          <template slot-scope="scope">
-            <el-tag v-if="scope.row.status === 1" :type="'warning'" disable-transitions>
-              正常
-            </el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column
-          prop="status"
           label="拥有的角色"
+          min-width="140"
         >
           <template slot-scope="scope">
-            <el-tag v-if="scope.row.status === 1" :type="'warning'" disable-transitions>
-              正常
-            </el-tag>
+            <div class="role-tags-wrapper" v-if="scope.row.roles && scope.row.roles.length">
+              <el-tag
+                v-for="role in (Array.isArray(scope.row.roles) ? scope.row.roles : scope.row.roles.split(','))"
+                :key="role"
+                size="mini"
+                type="warning"
+                class="role-tag"
+              >
+                {{ role }}
+              </el-tag>
+            </div>
+            <span v-else class="text-muted">普通用户</span>
           </template>
         </el-table-column>
+
         <el-table-column
           fixed="right"
           label="操作"
-          width="180"
+          width="100"
+          align="center"
         >
           <template slot-scope="scope">
             <el-button
               size="mini"
-              @click="handleEdit(scope.$index, scope.row)"
-            >编辑</el-button>
-            <el-button
-              size="mini"
-              type="danger"
-              @click="handleDelete(scope.$index, scope.row)"
-            >删除</el-button>
+              type="text"
+              class="btn-action-delete"
+              icon="el-icon-lock"
+              :disabled="scope.row.status !== 1"
+              @click="handleDisable(scope.$index, scope.row)"
+            >
+              禁用
+            </el-button>
           </template>
         </el-table-column>
       </el-table>
-      <el-pagination
-        background
-        :small="screenWidth <= 768"
-        layout="prev, pager, next"
-        :page-size="pageSize"
-        :current-page="currentPage"
-        :total="totalSize"
-        @current-change="handleCurrentChange"
-        @prev-click="handleCurrentChange"
-        @next-click="handleCurrentChange"
-      />
+
+      <div class="pagination-container">
+        <el-pagination
+          background
+          layout="total, prev, pager, next, sizes"
+          :page-sizes="[10, 12, 20, 50]"
+          :page-size="pageSize"
+          :current-page="currentPage"
+          :total="totalSize"
+          @current-change="handleCurrentChange"
+          @size-change="handleSizeChange"
+        />
+      </div>
     </el-main>
   </el-container>
 </template>
@@ -154,79 +200,207 @@ export default {
   name: 'AdminUserList',
   data() {
     return {
-      queryInfo: {
-        scope: null,
-        pn: 1
+      queryParams: {
+        pn: 1,
+        status: '', // 初始化为空字符串,以便触发 el-select 的 clearable
+        screenName: ''
       },
-      // 屏幕宽度, 为了控制分页条的大小
-      screenWidth: document.body.clientWidth,
       currentPage: 1,
       pageSize: 12,
       totalSize: 0,
       dataList: [],
-      nextId: 0,
-      // **********************************************************************
-      showEditScopeDialog: false,
-      form: {
-        videoId: null,
-        scope: 1
-      }
+      loading: false
     }
   },
   created() {
-    document.title = 'AdminUserList'
     this.getData()
   },
   methods: {
     getData() {
-      this.dataList = []
-      getUserList(this.nextId).then(resp => {
+      this.loading = true
+      this.queryParams.pn = this.currentPage
+
+      // 过滤掉空字符串参数
+      const params = { ...this.queryParams }
+      if (!params.screenName.trim()) delete params.screenName
+      if (params.status === '') delete params.status
+
+      getUserList(params).then(resp => {
         if (resp.code === 0) {
           const respData = resp.data
-          this.dataList = respData.list
-          this.totalSize = respData.totalSize
+          this.dataList = respData.list || []
+          this.totalSize = respData.totalSize || 0
         } else {
           this.$message.error(resp.msg)
         }
+      }).catch(err => {
+        this.$message.error(err.message || '获取列表失败')
+      }).finally(() => {
+        this.loading = false
       })
     },
+    // 点击查询或下拉切换时调用
+    handleSearch() {
+      this.currentPage = 1 // 搜索时重置回第一页
+      this.getData()
+    },
+    // 重置/刷新
     onRefresh() {
+      this.queryParams.screenName = ''
+      this.queryParams.status = ''
+      this.currentPage = 1
       this.getData()
+      this.$message.success('数据已重置并刷新')
     },
     handleCurrentChange(pageNumber) {
       this.currentPage = pageNumber
       this.getData()
-      // 回到顶部
-      scrollTo(0, 0)
+      // 丝滑滚动回顶部
+      const tableBody = this.$el.querySelector('.el-table__body-wrapper')
+      if (tableBody) tableBody.scrollTop = 0
     },
-    handleEdit(index, row) {
-      this.$message.info('handleEdit')
+    handleSizeChange(newSize) {
+      this.pageSize = newSize
+      this.currentPage = 1
+      this.getData()
     },
-    handleDelete(index, row) {
-      this.$confirm('确定要删除 ' + row.title + '?', '提示', {
-        confirmButtonText: '确定',
+    handleDisable(index, row) {
+      this.$confirm(`确定要封禁用户 [${row.screenName || row.username}] 吗?封禁后其所有正在进行的会话将被强制中断。`, '安全警告', {
+        confirmButtonText: '确定封禁',
         cancelButtonText: '取消',
+        confirmButtonClass: 'el-button--danger',
         type: 'warning'
       }).then(() => {
-        this.$message.info('handleDelete')
-      }).catch(() => {
-        this.$message({
-          type: 'info',
-          message: '已取消'
-        })
-      })
-    },
-    onUpdateScope() {
-      this.showEditScopeDialog = false
-    },
-    onSelectChange() {
-      this.$message.info(this.queryInfo)
-    },
-    handleClose() {
+        // 调用封禁 API 成功后应执行刷新:this.getData()
+        this.$message.success(`用户 ${row.userId} 封禁成功`)
+      }).catch(() => {})
     }
   }
 }
 </script>
 
-<style>
+<style scoped>
+/* 容器精细布局 */
+.admin-user-container {
+  background: #fff;
+  border-radius: 8px;
+  display: flex;
+  flex-direction: column;
+}
+
+/* 顶部搜索栏现代化微调 */
+.search-header {
+  padding: 16px 4px 4px 4px;
+  border-bottom: 1px solid #f1f5f9;
+}
+.search-form {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 4px 16px;
+}
+::v-deep .el-form-item {
+  margin-bottom: 12px;
+}
+::v-deep .el-form-item__label {
+  color: #64748b;
+  font-weight: 500;
+  padding-right: 8px;
+}
+.search-input {
+  width: 200px;
+}
+.status-select {
+  width: 130px;
+}
+.btn-refresh {
+  background: #f8fafc;
+  border-color: #cbd5e1;
+  color: #64748b;
+}
+.btn-refresh:hover {
+  color: #1890ff;
+  border-color: #1890ff;
+  background: #fff;
+}
+
+/* 表格主体美化 */
+.table-main {
+  padding: 16px 0 0 0;
+}
+.custom-table {
+  border-radius: 6px;
+  overflow: hidden;
+}
+
+/* 只保留需要的行内溢出裁剪,优化自带 tooltip 样式 */
+.ellipsis-text {
+  display: block;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  color: #334155;
+}
+.text-muted {
+  color: #94a3b8;
+}
+
+/* 头像与连接项美化 */
+.table-avatar {
+  border: 1px solid #e2e8f0;
+  background-color: #f8fafc;
+  box-shadow: 0 2px 6px rgba(0,0,0,0.02);
+}
+.user-link {
+  color: #1890ff;
+  text-decoration: none;
+  display: inline-flex;
+  align-items: center;
+  gap: 4px;
+  font-weight: 500;
+}
+.user-link:hover {
+  color: #40a9ff;
+  text-decoration: underline;
+}
+.link-icon {
+  font-size: 12px;
+  opacity: 0.7;
+}
+
+/* 角色标签容器包裹弹性盒模型 */
+.role-tags-wrapper {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 4px;
+}
+.role-tag {
+  border-radius: 4px;
+  border: 1px solid #fee2e2;
+  background: #fff7ed;
+  color: #ea580c;
+}
+
+/* 操作项去纽扣化改文字 */
+.btn-action-delete {
+  color: #ef4444;
+  font-weight: 500;
+  padding: 0;
+}
+.btn-action-delete:hover {
+  color: #dc2626;
+}
+.btn-action-delete.is-disabled {
+  color: #cbd5e1;
+}
+
+/* 底部分页精细对齐 */
+.pagination-container {
+  margin-top: 20px;
+  display: flex;
+  justify-content: flex-end;
+}
+::v-deep .el-pagination.is-background .el-pager li:not(.disabled).active {
+  background-gradient: linear-gradient(135deg, #1890ff 0%, #0076e4 100%);
+  background-color: #1890ff;
+}
 </style>

+ 37 - 9
src/views/my/MyMessage.vue

@@ -4,6 +4,11 @@
       <el-col :md="20">
         <el-row>
           <h2>我的消息</h2>
+          <el-button
+            size="mini"
+            type="danger"
+            @click="handleClear"
+          >清空</el-button>
         </el-row>
         <el-card>
           <el-table
@@ -40,13 +45,13 @@
     </el-row>
 
     <el-dialog title="消息内容" :visible.sync="messageDialog" @close="onCloseDialog">
-      <span>{{ this.messageContent }}</span>
+      <span>{{ messageContent }}</span>
     </el-dialog>
   </div>
 </template>
 
 <script>
-import { getUnreadMessages, getMessageDetail } from '@/api/user'
+import { getUnreadMessages, getMessageDetail, clearMessages } from '@/api/user'
 
 export default {
   name: 'MyMessage',
@@ -59,16 +64,22 @@ export default {
   },
   created() {
     document.title = '我的消息'
-
-    getUnreadMessages(1).then(resp => {
-      if (resp.code === 0) {
-        this.dataList = resp.data
-      }
-    })
+    this.getData()
   },
   methods: {
+    getData() {
+      var queryParams = {}
+      queryParams.pn = 1
+      getUnreadMessages(queryParams).then(resp => {
+        if (resp.code === 0) {
+          this.dataList = resp.data
+        }
+      })
+    },
     view(row) {
-      getMessageDetail(row.messageId).then(resp => {
+      var queryParams = {}
+      queryParams.messageId = row.messageId
+      getMessageDetail(queryParams).then(resp => {
         if (resp.code === 0) {
           this.messageDialog = true
           this.messageContent = resp.data.content
@@ -77,6 +88,23 @@ export default {
     },
     onCloseDialog() {
       this.messageContent = ''
+    },
+    handleClear() {
+      this.$confirm('确定要清空所有消息?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        clearMessages().then(resp => {
+          if (resp.code === 0) {
+            this.getData()
+          }
+
+          this.$message.info(resp.msg)
+        })
+      }).catch(() => {
+        this.$message.info('已取消')
+      })
     }
   }
 }

+ 0 - 292
src/views/my/MyProfile.vue

@@ -1,292 +0,0 @@
-<template>
-  <el-row class="movie-list">
-    <el-col :md="8" style="padding-right: 5px; padding-left: 5px; padding-bottom: 5px">
-      <el-row style="padding-right: 5px; padding-left: 5px; padding-bottom: 5px">
-        <el-card class="box-card">
-          <div slot="header" class="clearfix">
-            <span>更新我的头像</span>
-          </div>
-          <div class="text item">
-            <el-tooltip class="item" effect="dark" content="点击更新我的头像" placement="top-end">
-              <el-upload
-                class="avatar-uploader"
-                :action="imgOssUrl"
-                :headers="imgHeaders"
-                :data="imgData"
-                :with-credentials="false"
-                :show-file-list="false"
-                :before-upload="beforeAvatarUpload"
-                :on-success="handleAvatarSuccess"
-              >
-                <img v-if="loginUser" :src="loginUser.avatarUrl" class="avatar">
-                <i v-else class="el-icon-plus avatar-uploader-icon" />
-              </el-upload>
-            </el-tooltip>
-          </div>
-        </el-card>
-      </el-row>
-    </el-col>
-
-    <el-dialog
-      :title="updateTitle"
-      append-to-body
-      :visible.sync="updateMobileDialog"
-      width="30%"
-      center
-    >
-      <el-row style="padding-right: 5px; padding-left: 5px; padding-bottom: 5px; text-align: center">
-        <el-form ref="form" :model="updateMobileForm" label-width="100px">
-          <el-form-item label="邮箱/手机号" label-width="100px">
-            <el-input
-              v-model="updateMobileForm.principal"
-              placeholder="请输入邮箱或手机号"
-              style="width: 45%; padding-right: 10px"
-              clearable
-            />
-            <el-button :disabled="isBtn" @click="sendVerifyCode">{{ code }}</el-button>
-          </el-form-item>
-          <el-form-item label="验证码" label-width="90px">
-            <el-input
-              v-model="updateMobileForm.verifyCode"
-              placeholder="请输入短信验证码"
-              style="padding-right: 1px"
-              clearable
-            />
-          </el-form-item>
-          <el-form-item label="密码">
-            <el-input
-              v-model="updateMobileForm.credential"
-              type="password"
-              placeholder="请输入密码"
-              style="padding-right: 1px"
-              clearable
-            />
-          </el-form-item>
-          <el-form-item>
-            <el-button
-              type="primary"
-              @click.native="updateUserEmail"
-            >更新</el-button>
-          </el-form-item>
-        </el-form>
-      </el-row>
-    </el-dialog>
-    <el-dialog
-      :title="updateTitle"
-      append-to-body
-      :visible.sync="updatePasswordDialog"
-      width="30%"
-      center
-    >
-      <el-row style="padding-right: 5px; padding-left: 5px; padding-bottom: 5px; text-align: center">
-        <el-card class="box-card">
-          <div class="text item">
-            <el-form ref="form" :model="updatePasswordForm" label-width="100px">
-              <el-form-item label="当前密码">
-                <el-input
-                  v-model="updatePasswordForm.credential"
-                  type="password"
-                  placeholder="请输入当前密码"
-                  style="padding-right: 1px"
-                  clearable
-                />
-              </el-form-item>
-              <el-form-item label="新密码">
-                <el-input
-                  v-model="updatePasswordForm.credential"
-                  type="password"
-                  placeholder="请输入新密码"
-                  style="padding-right: 1px"
-                  clearable
-                />
-              </el-form-item>
-              <el-form-item label="确认新密码">
-                <el-input
-                  v-model="updatePasswordForm.credential"
-                  type="password"
-                  placeholder="请确认新密码"
-                  style="padding-right: 1px"
-                  clearable
-                />
-              </el-form-item>
-              <el-form-item>
-                <el-button
-                  type="primary"
-                  @click.native="updateUserPassword"
-                >更新</el-button>
-              </el-form-item>
-            </el-form>
-          </div>
-        </el-card>
-      </el-row>
-    </el-dialog>
-  </el-row>
-</template>
-
-<script>
-import { userMixin } from 'assets/js/mixin'
-import { updateAvatar } from '@/api/account'
-import { getAuthedUser, updateAuthedUser } from '@/utils/auth'
-import { getAvatarChannelInfo } from '@/api/file'
-
-export default {
-  name: 'MyProfile',
-  mixins: [userMixin],
-  data() {
-    return {
-      imgOssUrl: '',
-      imgHeaders: {
-        Authorization: ''
-      },
-      imgData: {
-        channelCode: 0
-      },
-      coverUrl: null,
-      // ****************************************************************************************************************
-      loginUser: null,
-      updateType: 1,
-      updateTitle: '',
-      updateMobileDialog: false,
-      updateMobileForm: {
-        principal: null,
-        verifyCode: null,
-        credential: null,
-        captchaCode: null,
-        channel: 1,
-        plat: 2
-      },
-      updatePasswordDialog: false,
-      updatePasswordForm: {
-        principal: null,
-        verifyCode: null,
-        credential: null,
-        captchaCode: null,
-        channel: 1,
-        plat: 2
-      }
-    }
-  },
-  created() {
-    this.loginUser = getAuthedUser()
-    getAvatarChannelInfo().then(res => {
-      if (res.code === 0) {
-        const resData = res.data
-        this.imgData.channelCode = resData.channelCode
-        this.imgOssUrl = resData.ossUrl
-        this.imgHeaders.Authorization = 'Bearer ' + resData.token
-      } else {
-        this.$message.error(res.msg)
-      }
-    }).catch(error => {
-      this.$message.error(error.message)
-    })
-  },
-  mounted() {
-  },
-  methods: {
-    // ****************************************************************************************************************
-    beforeAvatarUpload(file) {
-      if (this.imgOssUrl === '') {
-        this.$message.error('没有可上传的 OSS 地址!')
-        return
-      }
-
-      const isJPG = file.type === 'image/jpeg'
-      const isLt2M = file.size / 1024 / 1024 < 2
-      if (!isJPG) {
-        this.$message.error('头像文件只能是 JPG 格式!')
-      }
-      if (!isLt2M) {
-        this.$message.error('头像文件大小不能超过 2MB!')
-      }
-      return isJPG && isLt2M
-    },
-    handleAvatarSuccess(res, file) {
-      if (res.code === 0) {
-        const resData = res.data
-        this.coverUrl = URL.createObjectURL(file.raw)
-
-        const avatar = {}
-        avatar.channelCode = this.imgData.channelCode
-        avatar.uploadId = resData.uploadId
-        updateAvatar(avatar).then(resp => {
-          if (resp.code === 0) {
-            this.loginUser.avatarUrl = resp.data.avatarUrl
-            updateAuthedUser(this.loginUser)
-          } else {
-            this.$message.warning('头像更新失败')
-          }
-        })
-      } else {
-        this.$message.error('头像上传失败,请重试!' + res.msg)
-      }
-    },
-    // ****************************************************************************************************************
-    showUpdateDialog(type) {
-      if (type === 1) {
-        this.updateType = 1
-        this.updateTitle = '更新用户手机号'
-        this.updateMobileDialog = true
-      } else if (type === 2) {
-        this.updateType = 2
-        this.updateTitle = '更新用户密码'
-        this.updatePasswordDialog = true
-      }
-    },
-    onUpdate() {
-      if (this.updateType === 1) {
-        this.updateMobileDialog = false
-      } else {
-        this.updatePasswordDialog = false
-      }
-    },
-    sendVerifyCode() {
-      this.$message.info('发送验证码')
-    },
-    updateUserEmail() {
-      this.$message.info(this.updateMobileForm)
-      this.updateMobileDialog = false
-    },
-    updateUserPassword() {
-      this.$message.info(this.updatePasswordForm)
-      this.updatePasswordDialog = false
-    }
-  }
-}
-</script>
-
-<style>
-.uploader-example .uploader-btn {
-  margin-right: 4px;
-}
-.uploader-example .uploader-list {
-  max-height: 440px;
-  overflow: auto;
-  overflow-x: hidden;
-  overflow-y: auto;
-}
-
-.avatar-uploader .el-upload {
-  border: 1px dashed #d9d9d9;
-  border-radius: 6px;
-  cursor: pointer;
-  position: relative;
-  overflow: hidden;
-}
-.avatar-uploader .el-upload:hover {
-  border-color: #409EFF;
-}
-.avatar-uploader-icon {
-  font-size: 28px;
-  color: #8c939d;
-  width: 256px;
-  height: 256px;
-  line-height: 178px;
-  text-align: center;
-}
-.avatar {
-  width: 256px;
-  height: 256px;
-  display: block;
-}
-</style>