Index.vue 13 KB

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