Index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. <!--
  2. * @Author: fxs bjnsfxs@163.com
  3. * @Date: 2024-08-20 14:06:49
  4. * @LastEditors: fxs bjnsfxs@163.com
  5. * @LastEditTime: 2024-10-09 16:31:29
  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, allGameInfo, saveSelectInfo } = 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. saveSelectInfo()
  78. }
  79. /**
  80. * @description: 头部导航栏改变
  81. * @param {*} val 对应的name
  82. * @return {*}
  83. */
  84. const changeNavBar = (val: string) => {
  85. navBarSelect.value = val
  86. router.push(`/${val}`)
  87. createdMenuList()
  88. let title = navBarMenuList.find((item) => item.name === val)?.title
  89. if (title) {
  90. siderBarOpened.value.splice(0, 1, title)
  91. }
  92. }
  93. // 资源的加载路径
  94. const resourceInfo: Record<string, string> = {
  95. logo: `/img/logo.svg`,
  96. defaultHead: `/img/default/defaultHead.png`
  97. }
  98. // 使用blob的资源路径信息
  99. const blobUrlInfo = reactive<Record<string, string>>({})
  100. // 侧边栏跳转路由的基本路由
  101. const basePath = ref<string | undefined>()
  102. /**
  103. * @description: 创建侧边栏menu
  104. * @return {*}
  105. */
  106. const createdMenuList = () => {
  107. let routes = router.options.routes // 获取路由信息
  108. let indexRoutesChild = routes.find((item) => item.name === 'Index')?.children
  109. let activeMenu = indexRoutesChild?.find((item) => {
  110. return item.name === navBarSelect.value // 根据顶部导航栏的选中情况来选择选中哪个具体的路由信息,可以打印自己看一下
  111. })
  112. basePath.value = activeMenu?.path // 找到需要激活的菜单的路由,后续用来拼接需要跳转的路由
  113. menuList.splice(0, menuList.length, ...(activeMenu?.children as Array<any>)) // 清空原来的路由信息,并且加入新选中的
  114. }
  115. /**
  116. * @description: 当路由地址改变的时候,去获取最新的导航栏位置,并且重新生成侧边栏,不然刷新后,侧边栏会无法选中
  117. * @param {*} router
  118. * @return {*}
  119. */
  120. watch(
  121. () => [router.currentRoute.value.fullPath],
  122. ([newFullPath]) => {
  123. let routes = router.options.routes // 获取路由信息
  124. let indexRoutesChild = routes.find((item) => item.name === 'Index')?.children
  125. let activeMenu = indexRoutesChild?.find((item) => {
  126. return newFullPath.includes(item.path) // 根据顶部导航栏的选中情况来选择选中哪个具体的路由信息,可以打印自己看一下
  127. })
  128. navBarSelect.value = activeMenu?.name as string
  129. createdMenuList()
  130. },
  131. {
  132. immediate: true
  133. }
  134. )
  135. /**
  136. * @description: 获取所有游戏列表
  137. * @return {*}
  138. */
  139. const getGameInfo = () => {
  140. getAllGameInfo()
  141. .then((data) => {
  142. if (data) {
  143. allGameInfo.splice(0, allGameInfo.length)
  144. gameSelectInfo.optionsList.splice(0, gameSelectInfo.optionsList.length)
  145. data.map((item) => {
  146. allGameInfo.push({
  147. gid: item.gid,
  148. gameName: item.gameName
  149. })
  150. gameSelectInfo.optionsList.push({
  151. value: item.gid,
  152. label: item.gameName
  153. })
  154. })
  155. gameSelectInfo.defaultSelect = data[0].gid
  156. // 去找本地的gid,如果有,就赋值,否则用请求回来的第一个gid
  157. changeGame(selectInfo.gid)
  158. gameSelectInfo.defaultSelect = selectInfo.gid
  159. loadingState.value = true
  160. } else {
  161. throw new Error('游戏信息获取失败')
  162. }
  163. })
  164. .catch((err) => {
  165. console.log(err)
  166. })
  167. }
  168. /**
  169. * @description: 监听游戏列表的变化
  170. * @return {*}
  171. */
  172. let watchGameListChange: () => void = () => {}
  173. /**
  174. * @description: 监听加载状态的变化,加载完成的时候,给游戏列表的监听器赋值,然后把自己这个监听器摧毁
  175. * @return {*}
  176. */
  177. const watchLoadingState = watch(
  178. () => loadingState,
  179. (newval) => {
  180. if (newval) {
  181. watchGameListChange = watch(
  182. () => allGameInfo,
  183. (newGameInfo: Array<any>) => {
  184. gameSelectInfo.optionsList.splice(0, gameSelectInfo.optionsList.length)
  185. newGameInfo.forEach((item) => {
  186. gameSelectInfo.optionsList.push({
  187. value: item.gid,
  188. label: item.gameName
  189. })
  190. })
  191. // getGameInfo()
  192. // newGameInfo.map((item) => console.log(item))
  193. // console.log(...newGameInfo)
  194. // gameSelectInfo.optionsList.splice(0, gameSelectInfo.optionsList.length, ...newGameInfo)
  195. // if (newGameInfo.length > oldGameInfo.length) {
  196. // gameSelectInfo.optionsList.push({
  197. // value: newGameInfo[newGameInfo.length - 1].gid,
  198. // label: newGameInfo[newGameInfo.length - 1].gameName
  199. // })
  200. // }else{
  201. // }
  202. },
  203. { deep: true }
  204. )
  205. watchLoadingState()
  206. } else {
  207. watchGameListChange()
  208. }
  209. },
  210. {
  211. deep: true
  212. }
  213. )
  214. getGameInfo()
  215. onMounted(() => {
  216. // 去加载所有需要的资源
  217. initLoadResouce(resourceInfo).then((data) => {
  218. Object.assign(blobUrlInfo, data)
  219. })
  220. })
  221. </script>
  222. <template>
  223. <el-config-provider :locale="zhCn">
  224. <div class="body" v-if="loadingState">
  225. <div class="navBarBox">
  226. <div class="logoBox">
  227. <el-image :fit="'fill'" class="logoImg" :src="blobUrlInfo.logo"></el-image>
  228. <span>淳皓科技</span>
  229. </div>
  230. <div class="gameSelect">
  231. <el-icon class="gameIcon" :size="20">
  232. <icon-icon-park-game-three></icon-icon-park-game-three>
  233. </el-icon>
  234. <DropDownSelection
  235. :default-select="gameSelectInfo.defaultSelect"
  236. :title="gameSelectInfo.title"
  237. :options-list="gameSelectInfo.optionsList"
  238. :size="'default'"
  239. @change-select="changeGame"
  240. ></DropDownSelection>
  241. </div>
  242. <!-- 顶部导航栏 -->
  243. <div class="navBarMenu">
  244. <el-menu
  245. :default-active="navBarSelect"
  246. class="el-menu-demo"
  247. mode="horizontal"
  248. @select="changeNavBar"
  249. >
  250. <el-menu-item
  251. v-for="item in navBarMenuList"
  252. class="navBarMenuItem"
  253. :index="item.name"
  254. >{{ item.title }}</el-menu-item
  255. >
  256. </el-menu>
  257. </div>
  258. <div class="headPortraitBox">
  259. <el-popover popper-class="headPopper" placement="bottom-end" trigger="click">
  260. <template #reference>
  261. <el-image class="headPortrait" :src="blobUrlInfo.defaultHead"></el-image>
  262. </template>
  263. <div class="userTools">
  264. <span class="userToolsItem" @click="logOut">
  265. <icon-material-symbols-light-logout></icon-material-symbols-light-logout>
  266. <span> 退出登录</span>
  267. </span>
  268. </div>
  269. </el-popover>
  270. </div>
  271. </div>
  272. <!-- 侧边栏 -->
  273. <div class="sideBarBox">
  274. <el-menu
  275. :default-active="defaultActive"
  276. class="sideBar"
  277. :collapse="isCollapse"
  278. ref="siderBar"
  279. >
  280. <template v-for="(item, index) in menuList">
  281. <el-sub-menu :index="`${index}`" v-if="item.children && item.showChild">
  282. <template #title>
  283. <el-icon><component :is="item.icon"></component></el-icon>
  284. <span>{{ item.cnName }}</span>
  285. </template>
  286. <router-link
  287. style="text-decoration: none"
  288. v-for="val in item.children"
  289. :to="{ path: basePath + '/' + item.path + '/' + val.path }"
  290. :key="index"
  291. >
  292. <el-menu-item :index="val.path">{{ val.cnName }}</el-menu-item>
  293. </router-link>
  294. </el-sub-menu>
  295. <router-link
  296. style="text-decoration: none"
  297. v-else
  298. :to="{ path: basePath + '/' + item.path }"
  299. :key="index"
  300. >
  301. <el-menu-item :index="item.path">
  302. <el-icon><component :is="item.icon" /></el-icon>
  303. <template #title>
  304. <span class="menuTitle">{{ item.cnName }}</span>
  305. </template>
  306. </el-menu-item>
  307. </router-link>
  308. </template>
  309. <div class="sideBarFold" @click="changeCollapse">
  310. <el-icon :size="25"><Fold /></el-icon>
  311. </div>
  312. </el-menu>
  313. </div>
  314. <div class="content">
  315. <router-view v-slot="{ Component, route }">
  316. <keep-alive>
  317. <component
  318. :is="Component"
  319. :key="route.meta.activeMenu"
  320. v-if="route.meta.needKeepAlive == true"
  321. ></component>
  322. </keep-alive>
  323. <component
  324. :is="Component"
  325. :key="route.meta.activeMenu"
  326. v-if="route.meta.needKeepAlive == false"
  327. ></component>
  328. </router-view>
  329. </div>
  330. </div>
  331. </el-config-provider>
  332. </template>
  333. <style scoped>
  334. .body {
  335. width: 100%;
  336. display: flex;
  337. height: 100vh;
  338. }
  339. /* 设置宽度后,content无法适应宽度,只能去间接的调整内部元素的宽度 */
  340. .sideBarBox {
  341. position: relative;
  342. /* width: 12%; */
  343. z-index: 1;
  344. height: 93vh;
  345. margin-top: 7vh;
  346. top: 0;
  347. }
  348. .sideBar {
  349. /* width: 12vw; */
  350. height: 93vh;
  351. position: relative;
  352. overflow: scroll;
  353. }
  354. /* 设置弹出层的样式 */
  355. .el-popper > .logoText {
  356. width: 100px;
  357. font-size: 16px;
  358. /* color: red; */
  359. }
  360. .logoImg {
  361. display: flex;
  362. align-items: center;
  363. width: 33px;
  364. /* margin-right: 20px; */
  365. /* height: 50px; */
  366. }
  367. .logoText {
  368. width: 80%;
  369. height: 100%;
  370. margin-left: 15%;
  371. display: flex;
  372. font-size: 18px;
  373. align-items: center;
  374. /* background-color: lightcoral; */
  375. }
  376. /* 主要用来调整整个menu的宽度 */
  377. .menuTitle {
  378. margin-right: 40px;
  379. }
  380. .sideBarFold {
  381. width: 5%;
  382. height: 3%;
  383. position: absolute;
  384. right: 40px;
  385. bottom: 20px;
  386. }
  387. .navBarBox {
  388. position: fixed;
  389. display: flex;
  390. align-items: center;
  391. width: 100vw;
  392. z-index: 2;
  393. height: 7vh;
  394. top: 0;
  395. background-color: white;
  396. right: 0;
  397. border-bottom: 1px solid gainsboro;
  398. }
  399. /* 调整LOGO */
  400. .logoBox {
  401. box-sizing: border-box;
  402. left: 30px;
  403. position: relative;
  404. display: flex;
  405. justify-content: space-between;
  406. align-items: center;
  407. }
  408. .gameSelect {
  409. position: relative;
  410. height: 80%;
  411. display: flex;
  412. align-items: center;
  413. left: 5%;
  414. display: flex;
  415. align-items: center;
  416. }
  417. .gameIcon {
  418. /* box-sizing: border-box; */
  419. /* padding-right: 12px; */
  420. margin-right: 12px;
  421. }
  422. .navBarMenu {
  423. width: 60%;
  424. position: relative;
  425. left: 6%;
  426. }
  427. .headPortraitBox {
  428. position: absolute;
  429. right: 3%;
  430. top: 50%;
  431. transform: translateY(-50%);
  432. }
  433. .userTools {
  434. width: 100%;
  435. height: 100%;
  436. display: flex;
  437. flex-direction: column;
  438. justify-content: space-around;
  439. align-items: center;
  440. }
  441. .userToolsItem {
  442. cursor: pointer;
  443. width: 100%;
  444. height: 4vh;
  445. display: flex;
  446. align-items: center;
  447. justify-content: center;
  448. /* padding: 10px; */
  449. margin: 2%;
  450. }
  451. .userToolsItem > span {
  452. margin-left: 10%;
  453. }
  454. .userToolsItem:hover {
  455. background-color: #f2f3f5;
  456. }
  457. .headPortrait {
  458. cursor: pointer;
  459. width: 50px;
  460. }
  461. .content {
  462. /* flex-grow: 1; */
  463. /* position: absolute; */
  464. width: 100%;
  465. /* height: 93%; */
  466. margin-top: 7vh;
  467. overflow: scroll;
  468. background-color: #f2f3f5;
  469. right: 0vw;
  470. top: 0vh;
  471. }
  472. </style>
  473. <!-- 为了让popper-class生效,需要的单独写一份 -->
  474. <style>
  475. .headPopper {
  476. padding: 0px !important;
  477. border: 1px solid #e5e6eb;
  478. background-color: white;
  479. }
  480. .el-menu--horizontal.el-menu {
  481. border-bottom: none;
  482. }
  483. </style>