image.png
image.png

6-1 个人中心页

image.png
src/views/profile/index.vue

  1. <template>
  2. <div class="profile-container">
  3. <el-card>
  4. <template #header>
  5. <div class="card-header">
  6. <span>关于我</span>
  7. </div>
  8. </template>
  9. <div class="profile" v-if="userInfo">
  10. <div class="avatar">
  11. <img :src="avatar" alt="" />
  12. </div>
  13. <h2>用户名:{{ userInfo.username }}</h2>
  14. <h3>用户角色:{{ roleNames }}</h3>
  15. <div v-if="userInfo.description">
  16. <span>个人说明</span>
  17. <p>{{ userInfo.description }}</p>
  18. </div>
  19. </div>
  20. </el-card>
  21. </div>
  22. </template>
  23. <script lang="ts">
  24. import { computed, defineComponent } from 'vue'
  25. import { useStore } from '@/store'
  26. import defaultAvatar from '@/assets/logo.png'
  27. export default defineComponent({
  28. name: 'Profile',
  29. setup() {
  30. const store = useStore()
  31. const userInfo = computed(() => store.state.user.userInfo)
  32. const roleNames = computed(() => store.getters.roleNames)
  33. const avatar = computed(() => userInfo?.value?.avatar || defaultAvatar)
  34. return {
  35. userInfo,
  36. avatar,
  37. roleNames
  38. }
  39. }
  40. })
  41. </script>
  42. <style lang="scss" scoped>
  43. .profile-container {
  44. width: 500px;
  45. margin: 10px auto;
  46. .profile {
  47. text-align: center;
  48. .avatar {
  49. width: 100px;
  50. height: 100px;
  51. border-radius: 50%;
  52. margin: 10px auto;
  53. img {
  54. width: 100%;
  55. height: 100%;
  56. }
  57. }
  58. }
  59. }
  60. </style>

路由注册

src/router/index.ts

  1. import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
  2. import Layout from '@/layout/index.vue'
  3. // 看作是异步获取路由
  4. export const asyncRoutes: Array<RouteRecordRaw> = [
  5. {
  6. path: '/documentation',
  7. component: Layout, // 布局组件作为一级路由
  8. redirect: '/documentation/index',
  9. name: 'DocumentationLayout',
  10. children: [
  11. {
  12. path: 'index',
  13. name: 'Documentation',
  14. component: () => import(/* webpackChunkName: "documentation" */ '@/views/documentation/index.vue'),
  15. meta: {
  16. title: 'Documentation',
  17. icon: 'documentation',
  18. hidden: false, // 菜单栏不显示
  19. // 路由是否缓存 没有这个属性或false都会缓存 true不缓存
  20. noCache: true
  21. }
  22. }
  23. ]
  24. },
  25. {
  26. path: '/async',
  27. component: Layout,
  28. redirect: '/async/index',
  29. name: 'AsyncLayout',
  30. children: [
  31. {
  32. path: 'index',
  33. name: 'Async',
  34. component: () => import(/* webpackChunkName: "async" */ '@/views/async.vue'),
  35. meta: {
  36. title: '动态路由',
  37. icon: 'guide'
  38. // 当guide路由激活时高亮选中的是 documentation/index菜单
  39. // activeMenu: '/documentation/index'
  40. }
  41. }
  42. ]
  43. },
  44. {
  45. path: '/guide',
  46. component: Layout,
  47. redirect: '/guide/index',
  48. name: 'GuideLayout',
  49. meta: {
  50. title: 'GuideLay',
  51. icon: 'guide'
  52. },
  53. children: [
  54. {
  55. path: 'index',
  56. name: 'Guide',
  57. component: () => import(/* webpackChunkName: "guide" */ '@/views/guide/index.vue'),
  58. meta: {
  59. title: 'Guide',
  60. icon: 'guide'
  61. // 当guide路由激活时高亮选中的是 documentation/index菜单
  62. // activeMenu: '/documentation/index'
  63. }
  64. },
  65. {
  66. path: 'guide2',
  67. name: 'Guide2',
  68. component: () => import(/* webpackChunkName: "guide" */ '@/views/guide/index.vue'),
  69. meta: {
  70. title: 'Guide2',
  71. icon: 'guide'
  72. // 当guide路由激活时高亮选中的是 documentation/index菜单
  73. // activeMenu: '/documentation/index'
  74. }
  75. },
  76. {
  77. path: 'guide3',
  78. name: 'Guide3',
  79. component: () => import(/* webpackChunkName: "guide" */ '@/views/guide/index.vue'),
  80. meta: {
  81. title: 'Guide3',
  82. icon: 'guide'
  83. // 当guide路由激活时高亮选中的是 documentation/index菜单
  84. // activeMenu: '/documentation/index'
  85. }
  86. }
  87. ]
  88. },
  89. {
  90. path: '/system',
  91. component: Layout,
  92. redirect: '/system/user',
  93. name: 'SystemLayout',
  94. meta: {
  95. title: 'System',
  96. icon: 'lock',
  97. alwaysShow: true // 根路由始终显示 哪怕只有一个子路由
  98. },
  99. children: [
  100. {
  101. path: 'menu',
  102. name: 'Menu Management',
  103. component: () => import(/* webpackChunkName: "menu" */ '@/views/system/menu/index.vue'),
  104. meta: {
  105. title: 'Menu Management',
  106. hidden: false,
  107. breadcrumb: false
  108. }
  109. },
  110. {
  111. path: 'role',
  112. name: 'Role Management',
  113. component: () => import(/* webpackChunkName: "role" */ '@/views/system/role/index.vue'),
  114. meta: {
  115. title: 'Role Management',
  116. hidden: false
  117. }
  118. },
  119. {
  120. path: 'user',
  121. name: 'User Management',
  122. component: () => import(/* webpackChunkName: "user" */ '@/views/system/user/index.vue'),
  123. meta: {
  124. title: 'User Management'
  125. }
  126. }
  127. ]
  128. },
  129. { // 外链路由
  130. path: '/external-link',
  131. component: Layout,
  132. children: [
  133. {
  134. path: 'https://www.baidu.com/',
  135. redirect: '/',
  136. meta: {
  137. title: 'External Link',
  138. icon: 'link'
  139. }
  140. }
  141. ]
  142. },
  143. { // 404一定放在要在最后面
  144. path: '/:pathMatch(.*)*',
  145. redirect: '/404',
  146. meta: {
  147. hidden: true
  148. }
  149. }
  150. ]
  151. export const constantRoutes: Array<RouteRecordRaw> = [
  152. {
  153. path: '/',
  154. component: Layout,
  155. redirect: '/dashboard',
  156. name: 'DashboardLayout',
  157. children: [
  158. {
  159. path: 'dashboard',
  160. name: 'Dashboard',
  161. component: () => import(/* webpackChunkName: "dashboard" */ '@/views/dashboard/index.vue'),
  162. meta: {
  163. title: 'Dashboard',
  164. // icon: 'dashboard'
  165. icon: 'el-icon-platform-eleme',
  166. affix: true // 固定显示在tagsView中
  167. }
  168. }
  169. ]
  170. },
  171. {
  172. path: '/redirect',
  173. component: Layout,
  174. meta: {
  175. hidden: true
  176. },
  177. name: 'Redirect',
  178. children: [
  179. { // 带参数的动态路由正则匹配
  180. // https://next.router.vuejs.org/zh/guide/essentials/route-matching-syntax.html#%E5%8F%AF%E9%87%8D%E5%A4%8D%E7%9A%84%E5%8F%82%E6%95%B0
  181. path: '/redirect/:path(.*)', // 要匹配多级路由 应该加*号
  182. component: () => import('@/views/redirect/index.vue')
  183. }
  184. ]
  185. },
  186. {
  187. path: '/login',
  188. name: 'Login',
  189. component: () => import('@/views/login/index.vue')
  190. },
  191. {
  192. path: '/profile',
  193. component: Layout,
  194. redirect: '/profile/index',
  195. name: 'ProfileLayout',
  196. children: [
  197. {
  198. path: 'index',
  199. name: 'Profile',
  200. component: () => import('@/views/profile/index.vue'),
  201. meta: {
  202. hidden: true,
  203. title: '个人中心'
  204. }
  205. }
  206. ]
  207. },
  208. {
  209. path: '/401',
  210. component: Layout,
  211. name: '401Layout',
  212. children: [
  213. {
  214. path: '',
  215. component: () => import('@/views/error-page/401.vue'),
  216. meta: {
  217. title: '401',
  218. icon: '404',
  219. hidden: true
  220. }
  221. }
  222. ]
  223. },
  224. {
  225. path: '/404',
  226. component: () => import('@/views/error-page/404.vue'),
  227. meta: {
  228. hidden: true // 404 hidden掉
  229. }
  230. }
  231. ]
  232. export const routes = [
  233. ...constantRoutes
  234. // ...asyncRoutes
  235. ]
  236. const router = createRouter({
  237. history: createWebHashHistory(),
  238. routes
  239. })
  240. export default router

6-2 修改头像下拉选项

src/layout/components/avatar/index.vue

  1. <template>
  2. <el-dropdown
  3. class="avatar-container">
  4. <div class="avatar-wrapper">
  5. <img :src="avatar" class="user-avatar">
  6. <i class="el-icon-caret-bottom" />
  7. </div>
  8. <template #dropdown>
  9. <el-dropdown-menu>
  10. <el-dropdown-item v-if="username">
  11. <span style="display: block" :style="{fontWeight: '500'}">用户名:{{username}}</span>
  12. </el-dropdown-item>
  13. <router-link to="/">
  14. <el-dropdown-item>首页</el-dropdown-item>
  15. </router-link>
  16. <router-link to="/profile/index">
  17. <el-dropdown-item>个人中心</el-dropdown-item>
  18. </router-link>
  19. <el-dropdown-item divided @click="logout">
  20. <span style="display: block">退出登录</span>
  21. </el-dropdown-item>
  22. </el-dropdown-menu>
  23. </template>
  24. </el-dropdown>
  25. </template>
  26. <script lang="ts">
  27. import defaultAvatar from '@/assets/logo.png'
  28. import { defineComponent, getCurrentInstance, computed } from 'vue'
  29. import { useStore } from '@/store'
  30. export default defineComponent({
  31. setup() {
  32. const store = useStore()
  33. const { proxy } = getCurrentInstance()!
  34. const logout = () => {
  35. store.dispatch('user/logout').then(() => {
  36. proxy?.$message.success('退出登录')
  37. window.location.reload()
  38. })
  39. }
  40. const userInfo = computed(() => store.state.user.userInfo)
  41. const avatar = computed(() => userInfo.value?.avatar || defaultAvatar)
  42. const username = computed(() => userInfo.value?.username || '')
  43. // onMounted(() => {
  44. // // 获取用户信息
  45. // store.dispatch('user/getUserInfo')
  46. // })
  47. return {
  48. logout,
  49. avatar,
  50. username
  51. }
  52. }
  53. })
  54. </script>
  55. <style lang="scss" scoped>
  56. .avatar-container {
  57. margin-right: 30px;
  58. .avatar-wrapper {
  59. margin-top: 5px;
  60. .user-avatar {
  61. width: 40px;
  62. height: 40px;
  63. border-radius: 10px;
  64. cursor: pointer;
  65. }
  66. .el-icon-caret-bottom {
  67. cursor: pointer;
  68. font-size: 12px;
  69. }
  70. }
  71. }
  72. </style>