AdminLayout.vue 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. <script setup lang="ts">
  2. import {useLoginUserStore, useMetaStore} from "../store";
  3. import {computed, onMounted, ref, watch} from "vue";
  4. import router, {adminRoutes} from "../router";
  5. const { websiteMeta, fetchMeta } = useMetaStore()
  6. const loginUserStore = useLoginUserStore()
  7. const login = computed(()=>loginUserStore.loginUser.isLogin)
  8. const loginHandler = () => {
  9. router.push('/login')
  10. }
  11. const logoutHandler = () => {
  12. loginUserStore.logoutUser()
  13. router.push('/')
  14. }
  15. const menus = computed(() => {
  16. const userRole = loginUserStore.loginUser.user?.role
  17. return adminRoutes[0].children?.filter(child => {
  18. if (child.meta?.hidden) return false
  19. const allowedRoles = child.meta?.roles as string[] | undefined
  20. return !allowedRoles || allowedRoles.includes(userRole ?? '')
  21. })
  22. })
  23. const isCollapse = ref(false)
  24. const mobileMenuVisible = ref(false)
  25. onMounted(() => {
  26. fetchMeta()
  27. })
  28. watch(() => websiteMeta.title, (val) => {
  29. document.title = val
  30. }, { immediate: true })
  31. watch(() => websiteMeta.logo, (val) => {
  32. let link = document.querySelector<HTMLLinkElement>("link[rel*='icon']")
  33. if (!link) {
  34. link = document.createElement('link')
  35. link.rel = 'icon'
  36. document.head.appendChild(link)
  37. }
  38. link.href = val
  39. }, { immediate: true })
  40. const handleOpen = (key: string, keyPath: string[]) => {
  41. console.log(key, keyPath)
  42. }
  43. const handleClose = (key: string, keyPath: string[]) => {
  44. console.log(key, keyPath)
  45. }
  46. </script>
  47. <template>
  48. <el-container>
  49. <el-header class="header">
  50. <div class="header-left">
  51. <el-button class="menu-toggle" @click="mobileMenuVisible = true">☰</el-button>
  52. <div class="brand" @click="router.push('/')">
  53. <img :src="websiteMeta.logo" alt="logo" class="logo">
  54. <h2 class="title">{{ websiteMeta.title }}</h2>
  55. </div>
  56. </div>
  57. <h1 class="page-title">后台管理</h1>
  58. <div class="header-right">
  59. <template v-if="login">
  60. <el-dropdown placement="bottom">
  61. <el-button> {{ loginUserStore.loginUser?.user?.name }} </el-button>
  62. <template #dropdown>
  63. <el-dropdown-menu>
  64. <el-dropdown-item>个人信息</el-dropdown-item>
  65. <el-dropdown-item @click="logoutHandler">退出</el-dropdown-item>
  66. </el-dropdown-menu>
  67. </template>
  68. </el-dropdown>
  69. </template>
  70. <template v-else>
  71. <el-button type="primary" size="small" @click="loginHandler">登录</el-button>
  72. <el-button size="small">注册</el-button>
  73. </template>
  74. </div>
  75. </el-header>
  76. <el-container class="main-container">
  77. <el-aside class="menu">
  78. <el-radio-group v-model="isCollapse" style="margin-bottom: 20px;display: flex;justify-content: center">
  79. <el-radio-button :value="false">expand</el-radio-button>
  80. <el-radio-button :value="true">collapse</el-radio-button>
  81. </el-radio-group>
  82. <el-menu
  83. router
  84. class="el-menu-vertical"
  85. :collapse="isCollapse"
  86. @open="handleOpen"
  87. @close="handleClose"
  88. >
  89. <template v-for="menu in menus" :key="menu.path">
  90. <el-sub-menu v-if="menu.children?.length" :index="menu.path">
  91. <template #title>
  92. <el-icon><Component :is="menu.meta?.icon"/></el-icon>
  93. <span>{{menu.meta?.title}}</span>
  94. </template>
  95. <el-menu-item v-for="child in menu.children" :key="child.path" :index="child.path">
  96. <template #title>{{child.meta?.title}}</template>
  97. </el-menu-item>
  98. </el-sub-menu>
  99. <el-menu-item v-else :index="menu.path">
  100. <el-icon><Component :is="menu.meta?.icon"/></el-icon>
  101. <template #title>{{menu.meta?.title}}</template>
  102. </el-menu-item>
  103. </template>
  104. </el-menu>
  105. </el-aside>
  106. <el-drawer
  107. v-model="mobileMenuVisible"
  108. direction="ltr"
  109. size="auto"
  110. :with-header="false"
  111. >
  112. <el-menu router @select="mobileMenuVisible = false">
  113. <template v-for="menu in menus" :key="menu.path">
  114. <el-sub-menu v-if="menu.children?.length" :index="menu.path">
  115. <template #title>
  116. <el-icon><Component :is="menu.meta?.icon"/></el-icon>
  117. <span>{{menu.meta?.title}}</span>
  118. </template>
  119. <el-menu-item v-for="child in menu.children" :key="child.path" :index="child.path">
  120. <template #title>{{child.meta?.title}}</template>
  121. </el-menu-item>
  122. </el-sub-menu>
  123. <el-menu-item v-else :index="menu.path">
  124. <el-icon><Component :is="menu.meta?.icon"/></el-icon>
  125. <template #title>{{menu.meta?.title}}</template>
  126. </el-menu-item>
  127. </template>
  128. </el-menu>
  129. </el-drawer>
  130. <el-main>
  131. <router-view/>
  132. </el-main>
  133. </el-container>
  134. </el-container>
  135. </template>
  136. <style scoped>
  137. .el-container {
  138. height: 100vh;
  139. }
  140. .header {
  141. display: flex;
  142. align-items: center;
  143. justify-content: space-between;
  144. padding: 0 20px;
  145. margin-bottom: 10px;
  146. border-bottom: 1px solid
  147. }
  148. .header-left {
  149. display: flex;
  150. align-items: center;
  151. gap: 10px;
  152. }
  153. .brand {
  154. display: flex;
  155. align-items: center;
  156. gap: 10px;
  157. cursor: pointer;
  158. }
  159. .logo {
  160. width: 40px;
  161. height: 40px;
  162. }
  163. .title {
  164. margin: 0;
  165. font-size: 20px;
  166. }
  167. .header-right {
  168. display: flex;
  169. align-items: center;
  170. gap: 10px;
  171. }
  172. .menu{
  173. width: 200px;
  174. }
  175. .main-container {
  176. flex: 1;
  177. }
  178. .el-menu-vertical-demo:not(.el-menu--collapse) {
  179. width: 200px;
  180. min-height: 400px;
  181. }
  182. .menu-toggle {
  183. display: none;
  184. }
  185. .page-title {
  186. font-size: 20px;
  187. }
  188. @media (max-width: 768px) {
  189. .menu {
  190. display: none;
  191. }
  192. .menu-toggle {
  193. display: inline-flex;
  194. }
  195. .header {
  196. padding: 0 10px;
  197. }
  198. .header-left .title {
  199. font-size: 16px;
  200. }
  201. .logo {
  202. width: 32px;
  203. height: 32px;
  204. }
  205. .page-title {
  206. font-size: 16px;
  207. }
  208. }
  209. </style>