Index.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. <!--
  2. * @Author: fxs bjnsfxs@163.com
  3. * @Date: 2024-08-20 14:06:49
  4. * @LastEditors: fxs bjnsfxs@163.com
  5. * @LastEditTime: 2024-09-09 10:39:52
  6. * @FilePath: \Game-Backstage-Management-System\src\views\Index.vue
  7. * @Description:
  8. *
  9. -->
  10. <script setup lang="ts">
  11. import { zhCn } from 'element-plus/es/locales.mjs'
  12. import { RouterView } from 'vue-router'
  13. import { onMounted, reactive, ref, computed, watch } from 'vue'
  14. import { useRoute } from 'vue-router'
  15. import { ElMessage } from 'element-plus'
  16. import { getAllGameInfo } from '@/utils/table/table'
  17. import router from '@/router'
  18. import type { DropDownInfo } from '@/types/dataAnalysis'
  19. import DropDownSelection from '@/components/dataAnalysis/DropDownSelection.vue'
  20. import { useCommonStore } from '@/stores/useCommon'
  21. import { initLoadResouce } from '@/utils/resource'
  22. const route = useRoute()
  23. const { selectInfo } = useCommonStore()
  24. const isCollapse = ref(false)
  25. const navBarSelect = ref<string>('Home')
  26. const siderBarOpened = ref<Array<string>>(['数据总览'])
  27. const siderBar = ref()
  28. const loadingState = ref(false) // 用来标记必要信息的加载状态
  29. // 路由信息,同时也是侧边栏生成的依据信息
  30. const menuList = reactive<Array<any>>([])
  31. // 默认选中
  32. const defaultActive = computed(() => {
  33. return route.meta.activeMenu
  34. })
  35. // 顶部导航栏信息
  36. const navBarMenuList = [
  37. {
  38. name: 'Home',
  39. title: '应用分析'
  40. },
  41. {
  42. name: 'AppManage',
  43. title: '应用管理'
  44. }
  45. ]
  46. /**
  47. * @description: 侧边栏折叠改变
  48. * @return {*}
  49. */
  50. const changeCollapse = () => {
  51. isCollapse.value = !isCollapse.value
  52. }
  53. // 登出
  54. const logOut = () => {
  55. ElMessage({
  56. type: 'success',
  57. message: '退出成功',
  58. duration: 1000
  59. })
  60. localStorage.removeItem('token')
  61. localStorage.removeItem('refreshToken')
  62. router.push('/login')
  63. }
  64. // 游戏下拉选择框需要的数据
  65. const gameSelectInfo = reactive<DropDownInfo>({
  66. defaultSelect: '1001',
  67. title: '请选择游戏',
  68. optionsList: []
  69. })
  70. /**
  71. * @description: 更新整个页面的游戏选择
  72. * @param {*} gid 游戏id
  73. * @return {*}
  74. */
  75. const changeGame = (gid: any) => {
  76. selectInfo.gid = gid
  77. }
  78. /**
  79. * @description: 头部导航栏改变
  80. * @param {*} val 对应的name
  81. * @return {*}
  82. */
  83. const changeNavBar = (val: string) => {
  84. navBarSelect.value = val
  85. router.push(`/${val}`)
  86. createdMenuList()
  87. let title = navBarMenuList.find((item) => item.name === val)?.title
  88. if (title) {
  89. siderBarOpened.value.splice(0, 1, title)
  90. }
  91. }
  92. // 资源的加载路径
  93. const resourceInfo: Record<string, string> = {
  94. logo: `/img/logo.svg`,
  95. defaultHead: `/img/default/defaultHead.png`
  96. }
  97. // 使用blob的资源路径信息
  98. const blobUrlInfo = reactive<Record<string, string>>({})
  99. // 侧边栏跳转路由的基本路由
  100. const basePath = ref<string | undefined>()
  101. /**
  102. * @description: 创建侧边栏menu
  103. * @return {*}
  104. */
  105. const createdMenuList = () => {
  106. let routes = router.options.routes // 获取路由信息
  107. let indexRoutesChild = routes.find((item) => item.name === 'Index')?.children
  108. let activeMenu = indexRoutesChild?.find((item) => {
  109. return item.name === navBarSelect.value // 根据顶部导航栏的选中情况来选择选中哪个具体的路由信息,可以打印自己看一下
  110. })
  111. basePath.value = activeMenu?.path // 找到需要激活的菜单的路由,后续用来拼接需要跳转的路由
  112. menuList.splice(0, menuList.length, ...(activeMenu?.children as Array<any>)) // 清空原来的路由信息,并且加入新选中的
  113. }
  114. /**
  115. * @description: 当路由地址改变的时候,去获取最新的导航栏位置,并且重新生成侧边栏,不然刷新后,侧边栏会无法选中
  116. * @param {*} router
  117. * @return {*}
  118. */
  119. watch(
  120. () => [router.currentRoute.value.fullPath],
  121. ([newFullPath]) => {
  122. let routes = router.options.routes // 获取路由信息
  123. let indexRoutesChild = routes.find((item) => item.name === 'Index')?.children
  124. let activeMenu = indexRoutesChild?.find((item) => {
  125. return newFullPath.includes(item.path) // 根据顶部导航栏的选中情况来选择选中哪个具体的路由信息,可以打印自己看一下
  126. })
  127. navBarSelect.value = activeMenu?.name as string
  128. createdMenuList()
  129. },
  130. {
  131. immediate: true
  132. }
  133. )
  134. /**
  135. * @description: 获取所有游戏列表
  136. * @return {*}
  137. */
  138. getAllGameInfo().then((data) => {
  139. if (data) {
  140. data.map((item) => {
  141. gameSelectInfo.optionsList.push({
  142. value: item.gid,
  143. label: item.gameName
  144. })
  145. })
  146. gameSelectInfo.defaultSelect = data[0].gid
  147. changeGame(data[0].gid)
  148. loadingState.value = true
  149. } else {
  150. throw new Error('游戏信息获取失败')
  151. }
  152. })
  153. onMounted(() => {
  154. // 去加载所有需要的资源
  155. initLoadResouce(resourceInfo).then((data) => {
  156. Object.assign(blobUrlInfo, data)
  157. })
  158. })
  159. </script>
  160. <template>
  161. <el-config-provider :locale="zhCn">
  162. <div class="body" v-if="loadingState">
  163. <div class="navBarBox">
  164. <div class="logoBox">
  165. <el-image :fit="'fill'" class="logoImg" :src="blobUrlInfo.logo"></el-image>
  166. <span>淳皓科技</span>
  167. </div>
  168. <div class="gameSelect">
  169. <el-icon class="gameIcon" :size="20">
  170. <icon-icon-park-game-three></icon-icon-park-game-three>
  171. </el-icon>
  172. <DropDownSelection
  173. :default-select="gameSelectInfo.defaultSelect"
  174. :title="gameSelectInfo.title"
  175. :options-list="gameSelectInfo.optionsList"
  176. :size="'default'"
  177. @change-select="changeGame"
  178. ></DropDownSelection>
  179. </div>
  180. <!-- 顶部导航栏 -->
  181. <div class="navBarMenu">
  182. <el-menu
  183. :default-active="navBarSelect"
  184. class="el-menu-demo"
  185. mode="horizontal"
  186. @select="changeNavBar"
  187. >
  188. <el-menu-item
  189. v-for="item in navBarMenuList"
  190. class="navBarMenuItem"
  191. :index="item.name"
  192. >{{ item.title }}</el-menu-item
  193. >
  194. </el-menu>
  195. </div>
  196. <div class="headPortraitBox">
  197. <el-popover popper-class="headPopper" placement="bottom-end" trigger="click">
  198. <template #reference>
  199. <el-image class="headPortrait" :src="blobUrlInfo.defaultHead"></el-image>
  200. </template>
  201. <div class="userTools">
  202. <span class="userToolsItem" @click="logOut">
  203. <icon-material-symbols-light-logout></icon-material-symbols-light-logout>
  204. <span> 退出登录</span>
  205. </span>
  206. </div>
  207. </el-popover>
  208. </div>
  209. </div>
  210. <!-- 侧边栏 -->
  211. <div class="sideBarBox">
  212. <el-menu
  213. :default-active="defaultActive"
  214. class="sideBar"
  215. :collapse="isCollapse"
  216. ref="siderBar"
  217. >
  218. <template v-for="(item, index) in menuList">
  219. <el-sub-menu :index="`${index}`" v-if="item.children && item.showChild">
  220. <template #title>
  221. <el-icon><component :is="item.icon"></component></el-icon>
  222. <span>{{ item.cnName }}</span>
  223. </template>
  224. <router-link
  225. style="text-decoration: none"
  226. v-for="val in item.children"
  227. :to="{ path: basePath + '/' + item.path + '/' + val.path }"
  228. :key="index"
  229. >
  230. <el-menu-item :index="val.path">{{ val.cnName }}</el-menu-item>
  231. </router-link>
  232. </el-sub-menu>
  233. <router-link
  234. style="text-decoration: none"
  235. v-else
  236. :to="{ path: basePath + '/' + item.path }"
  237. :key="index"
  238. >
  239. <el-menu-item :index="item.path">
  240. <el-icon><component :is="item.icon" /></el-icon>
  241. <template #title>
  242. <span class="menuTitle">{{ item.cnName }}</span>
  243. </template>
  244. </el-menu-item>
  245. </router-link>
  246. </template>
  247. <div class="sideBarFold" @click="changeCollapse">
  248. <el-icon :size="25"><Fold /></el-icon>
  249. </div>
  250. </el-menu>
  251. </div>
  252. <div class="content">
  253. <router-view v-slot="{ Component, route }">
  254. <keep-alive>
  255. <component
  256. :is="Component"
  257. :key="route.meta.activeMenu"
  258. v-if="route.meta.needKeepAlive == true"
  259. ></component>
  260. </keep-alive>
  261. <component
  262. :is="Component"
  263. :key="route.meta.activeMenu"
  264. v-if="route.meta.needKeepAlive == false"
  265. ></component>
  266. </router-view>
  267. </div>
  268. </div>
  269. </el-config-provider>
  270. </template>
  271. <style scoped>
  272. .body {
  273. width: 100%;
  274. display: flex;
  275. height: 100vh;
  276. }
  277. /* 设置宽度后,content无法适应宽度,只能去间接的调整内部元素的宽度 */
  278. .sideBarBox {
  279. position: relative;
  280. /* width: 12%; */
  281. z-index: 1;
  282. height: 93vh;
  283. margin-top: 7vh;
  284. top: 0;
  285. }
  286. .sideBar {
  287. /* width: 12vw; */
  288. height: 93vh;
  289. position: relative;
  290. overflow: scroll;
  291. }
  292. /* 设置弹出层的样式 */
  293. .el-popper > .logoText {
  294. width: 100px;
  295. font-size: 16px;
  296. /* color: red; */
  297. }
  298. .logoImg {
  299. display: flex;
  300. align-items: center;
  301. width: 33px;
  302. /* margin-right: 20px; */
  303. /* height: 50px; */
  304. }
  305. .logoText {
  306. width: 80%;
  307. height: 100%;
  308. margin-left: 15%;
  309. display: flex;
  310. font-size: 18px;
  311. align-items: center;
  312. /* background-color: lightcoral; */
  313. }
  314. /* 主要用来调整整个menu的宽度 */
  315. .menuTitle {
  316. margin-right: 40px;
  317. }
  318. .sideBarFold {
  319. width: 5%;
  320. height: 3%;
  321. position: absolute;
  322. right: 40px;
  323. bottom: 20px;
  324. }
  325. .navBarBox {
  326. position: fixed;
  327. display: flex;
  328. align-items: center;
  329. width: 100vw;
  330. z-index: 2;
  331. height: 7vh;
  332. top: 0;
  333. background-color: white;
  334. right: 0;
  335. border-bottom: 1px solid gainsboro;
  336. }
  337. /* 调整LOGO */
  338. .logoBox {
  339. box-sizing: border-box;
  340. left: 30px;
  341. position: relative;
  342. display: flex;
  343. justify-content: space-between;
  344. align-items: center;
  345. }
  346. .gameSelect {
  347. position: relative;
  348. height: 80%;
  349. display: flex;
  350. align-items: center;
  351. left: 5%;
  352. display: flex;
  353. align-items: center;
  354. }
  355. .gameIcon {
  356. /* box-sizing: border-box; */
  357. /* padding-right: 12px; */
  358. margin-right: 12px;
  359. }
  360. .navBarMenu {
  361. width: 60%;
  362. position: relative;
  363. left: 6%;
  364. }
  365. .headPortraitBox {
  366. position: absolute;
  367. right: 3%;
  368. top: 50%;
  369. transform: translateY(-50%);
  370. }
  371. .userTools {
  372. width: 100%;
  373. height: 100%;
  374. display: flex;
  375. flex-direction: column;
  376. justify-content: space-around;
  377. align-items: center;
  378. }
  379. .userToolsItem {
  380. cursor: pointer;
  381. width: 100%;
  382. height: 4vh;
  383. display: flex;
  384. align-items: center;
  385. justify-content: center;
  386. /* padding: 10px; */
  387. margin: 2%;
  388. }
  389. .userToolsItem > span {
  390. margin-left: 10%;
  391. }
  392. .userToolsItem:hover {
  393. background-color: #f2f3f5;
  394. }
  395. .headPortrait {
  396. cursor: pointer;
  397. width: 50px;
  398. }
  399. .content {
  400. /* flex-grow: 1; */
  401. /* position: absolute; */
  402. width: 100%;
  403. /* height: 93%; */
  404. margin-top: 7vh;
  405. overflow: scroll;
  406. background-color: #f2f3f5;
  407. right: 0vw;
  408. top: 0vh;
  409. }
  410. </style>
  411. <!-- 为了让popper-class生效,需要的单独写一份 -->
  412. <style>
  413. .headPopper {
  414. padding: 0px !important;
  415. border: 1px solid #e5e6eb;
  416. background-color: white;
  417. }
  418. .el-menu--horizontal.el-menu {
  419. border-bottom: none;
  420. }
  421. </style>