UserLayout.vue 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. <template>
  2. <div class="user-layout">
  3. <el-container>
  4. <el-header class="header">
  5. <div class="header-left" @click="router.push('/problems')">
  6. <img :src="metaStore.logo" alt="logo" class="logo">
  7. <h2 class="title">{{metaStore.title}}</h2>
  8. </div>
  9. <nav class="nav">
  10. <router-link
  11. v-for="menu in menus"
  12. :key="menu.path"
  13. :to="menu.path"
  14. class="nav-link"
  15. :class="{ active: currentPath.startsWith(menu.path) }"
  16. >
  17. <el-icon><component :is="menu.meta?.icon"/></el-icon>
  18. <span>{{ menu.meta?.title }}</span>
  19. </router-link>
  20. <router-link
  21. v-if="isStaff"
  22. to="/admin"
  23. class="nav-link"
  24. :class="{ active: currentPath.startsWith('/admin') }"
  25. >
  26. <el-icon><Tools/></el-icon>
  27. <span>后台管理</span>
  28. </router-link>
  29. </nav>
  30. <div class="header-right">
  31. <template v-if="login">
  32. <el-dropdown placement="bottom">
  33. <el-button class="user-btn" text>
  34. <el-icon class="user-avatar"><UserFilled /></el-icon>
  35. <span class="username">{{ loginUserStore.loginUser?.user?.name }}</span>
  36. </el-button>
  37. <template #dropdown>
  38. <el-dropdown-menu>
  39. <el-dropdown-item @click="handleProfile">个人信息</el-dropdown-item>
  40. <el-dropdown-item divided @click="logoutHandler">退出登录</el-dropdown-item>
  41. </el-dropdown-menu>
  42. </template>
  43. </el-dropdown>
  44. </template>
  45. <template v-else>
  46. <el-button type="primary" size="small" @click="loginHandler">登录</el-button>
  47. <el-button size="small" @click="router.push({ path: '/login', query: { tab: 'register' } })">注册</el-button>
  48. </template>
  49. </div>
  50. </el-header>
  51. <el-main>
  52. <router-view/>
  53. </el-main>
  54. <el-footer>Footer</el-footer>
  55. </el-container>
  56. </div>
  57. </template>
  58. <script setup lang="ts">
  59. import {computed} from 'vue'
  60. import {useRoute} from 'vue-router'
  61. import {ElMessage} from 'element-plus'
  62. import {useLoginUserStore, useMetaStore} from "../store";
  63. import router, {userRoutes} from "../router";
  64. import {
  65. Tools,
  66. UserFilled
  67. } from '@element-plus/icons-vue'
  68. const route = useRoute()
  69. const metaStore = useMetaStore()
  70. const loginUserStore = useLoginUserStore()
  71. const login = computed(() => loginUserStore.loginUser.isLogin)
  72. const isStaff = computed(() => {
  73. const role = loginUserStore.loginUser.user?.role
  74. return role === 'admin' || role === 'coach'
  75. })
  76. const loginHandler = () => {
  77. router.push('/login')
  78. }
  79. const currentPath = computed(() => route.path)
  80. const menus = computed(()=>{
  81. return userRoutes[0].children?.filter((child) => !String(child.path).includes(':') && child.path !== '/');
  82. })
  83. const logoutHandler = () => {
  84. loginUserStore.logoutUser()
  85. router.push('/problems')
  86. }
  87. const handleProfile = () => {
  88. ElMessage.info('功能开发中,敬请期待')
  89. }
  90. </script>
  91. <style scoped>
  92. .user-layout {
  93. display: flex;
  94. flex-direction: column;
  95. min-height: 100vh;
  96. }
  97. el-main{
  98. flex: 1;
  99. }
  100. .header {
  101. display: flex;
  102. align-items: center;
  103. justify-content: space-between;
  104. padding: 0 20px;
  105. border-bottom: 1px solid var(--el-border-color);
  106. }
  107. .header-left {
  108. display: flex;
  109. align-items: center;
  110. gap: 10px;
  111. }
  112. .logo {
  113. width: 40px;
  114. height: 40px;
  115. }
  116. .title {
  117. margin: 0;
  118. font-size: 20px;
  119. }
  120. .nav {
  121. flex: 1;
  122. display: flex;
  123. justify-content: center;
  124. align-items: center;
  125. gap: 28px;
  126. }
  127. .nav-link {
  128. display: flex;
  129. align-items: center;
  130. gap: 6px;
  131. height: auto;
  132. padding: 2px 4px;
  133. font-size: 14px;
  134. color: var(--el-text-color-regular);
  135. text-decoration: none;
  136. border-bottom: 2px solid transparent;
  137. transition: color 0.2s, border-color 0.2s;
  138. }
  139. .nav-link:hover {
  140. color: var(--el-color-primary);
  141. }
  142. .nav-link.active {
  143. color: var(--el-color-primary);
  144. border-bottom-color: var(--el-color-primary);
  145. }
  146. .header-right {
  147. display: flex;
  148. align-items: center;
  149. gap: 10px;
  150. }
  151. .username {
  152. font-size: 14px;
  153. color: var(--el-text-color-regular);
  154. }
  155. .user-btn {
  156. display: flex;
  157. align-items: center;
  158. gap: 6px;
  159. cursor: pointer;
  160. transition: color 0.2s;
  161. }
  162. .user-btn:hover {
  163. color: var(--el-color-primary);
  164. }
  165. .user-avatar {
  166. font-size: 20px;
  167. color: var(--el-color-primary);
  168. }
  169. </style>