Index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  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-15 12:02:38
  6. * @FilePath: \Game-Backstage-Management-System\src\views\Index.vue
  7. * @Description:
  8. *
  9. -->
  10. <script setup lang="ts">
  11. import type { DropDownInfo } from '@/types/dataAnalysis'
  12. import { zhCn } from 'element-plus/es/locales.mjs'
  13. import { RouterView } from 'vue-router'
  14. import { onMounted, reactive, ref, computed, watch } from 'vue'
  15. import { useRoute } from 'vue-router'
  16. import { ElMessage } from 'element-plus'
  17. import { getAllGameInfo } from '@/utils/table/table'
  18. import { useCommonStore } from '@/stores/useCommon'
  19. import { initLoadResouce } from '@/utils/resource'
  20. import { setLoginState } from '@/utils/localStorage/localStorage'
  21. import { removeAllToeken } from '@/utils/token/token'
  22. import router from '@/router'
  23. import DropDownSelection from '@/components/dataAnalysis/DropDownSelection.vue'
  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. })
  212. </script>
  213. <template>
  214. <el-config-provider :locale="zhCn">
  215. <div class="body" v-if="loadingState">
  216. <div class="navBarBox">
  217. <div class="logoBox">
  218. <el-image :fit="'fill'" class="logoImg" :src="blobUrlInfo.logo"></el-image>
  219. <span>淳皓科技</span>
  220. </div>
  221. <div class="gameSelect">
  222. <el-icon class="gameIcon" :size="20">
  223. <icon-icon-park-game-three></icon-icon-park-game-three>
  224. </el-icon>
  225. <DropDownSelection
  226. :default-select="gameSelectInfo.defaultSelect"
  227. :title="gameSelectInfo.title"
  228. :options-list="gameSelectInfo.optionsList"
  229. :size="'default'"
  230. @change-select="changeGame"
  231. ></DropDownSelection>
  232. </div>
  233. <!-- 顶部导航栏 -->
  234. <div class="navBarMenu">
  235. <el-menu
  236. :default-active="navBarSelect"
  237. class="el-menu-demo"
  238. mode="horizontal"
  239. @select="changeNavBar"
  240. >
  241. <el-menu-item
  242. v-for="item in navBarMenuList"
  243. class="navBarMenuItem"
  244. :index="item.name"
  245. >{{ item.title }}</el-menu-item
  246. >
  247. </el-menu>
  248. </div>
  249. <div class="headPortraitBox">
  250. <el-popover popper-class="headPopper" placement="bottom-end" trigger="click">
  251. <template #reference>
  252. <el-image class="headPortrait" :src="blobUrlInfo.defaultHead"></el-image>
  253. </template>
  254. <div class="userTools">
  255. <span class="userToolsItem" @click="logOut">
  256. <icon-material-symbols-light-logout></icon-material-symbols-light-logout>
  257. <span> 退出登录</span>
  258. </span>
  259. </div>
  260. </el-popover>
  261. </div>
  262. </div>
  263. <!-- 侧边栏 -->
  264. <div class="sideBarBox">
  265. <el-menu
  266. :default-active="defaultActive"
  267. class="sideBar"
  268. :collapse="isCollapse"
  269. ref="siderBar"
  270. >
  271. <template v-for="(item, index) in menuList">
  272. <el-sub-menu :index="`${index}`" v-if="item.children && item.showChild">
  273. <template #title>
  274. <el-icon><component :is="item.icon"></component></el-icon>
  275. <span>{{ item.cnName }}</span>
  276. </template>
  277. <router-link
  278. style="text-decoration: none"
  279. v-for="val in item.children"
  280. :to="{ path: basePath + '/' + item.path + '/' + val.path }"
  281. :key="index"
  282. >
  283. <el-menu-item :index="val.path">{{ val.cnName }}</el-menu-item>
  284. </router-link>
  285. </el-sub-menu>
  286. <router-link
  287. style="text-decoration: none"
  288. v-else
  289. :to="{ path: basePath + '/' + item.path }"
  290. :key="index"
  291. >
  292. <el-menu-item :index="item.path">
  293. <el-icon><component :is="item.icon" /></el-icon>
  294. <template #title>
  295. <span class="menuTitle">{{ item.cnName }}</span>
  296. </template>
  297. </el-menu-item>
  298. </router-link>
  299. </template>
  300. <div class="sideBarFold" @click="changeCollapse">
  301. <el-icon :size="25"><Fold /></el-icon>
  302. </div>
  303. </el-menu>
  304. </div>
  305. <div class="content">
  306. <router-view v-slot="{ Component, route }">
  307. <keep-alive>
  308. <component
  309. :is="Component"
  310. :key="route.meta.activeMenu"
  311. v-if="route.meta.needKeepAlive == true"
  312. ></component>
  313. </keep-alive>
  314. <component
  315. :is="Component"
  316. :key="route.meta.activeMenu"
  317. v-if="route.meta.needKeepAlive == false"
  318. ></component>
  319. </router-view>
  320. </div>
  321. </div>
  322. </el-config-provider>
  323. </template>
  324. <style scoped>
  325. .body {
  326. width: 100%;
  327. display: flex;
  328. height: 100vh;
  329. }
  330. /* 设置宽度后,content无法适应宽度,只能去间接的调整内部元素的宽度 */
  331. .sideBarBox {
  332. position: relative;
  333. /* width: 12%; */
  334. z-index: 1;
  335. height: 93vh;
  336. margin-top: 7vh;
  337. top: 0;
  338. }
  339. .sideBar {
  340. /* width: 12vw; */
  341. height: 93vh;
  342. position: relative;
  343. overflow: scroll;
  344. }
  345. /* 设置弹出层的样式 */
  346. .el-popper > .logoText {
  347. width: 100px;
  348. font-size: 16px;
  349. /* color: red; */
  350. }
  351. .logoImg {
  352. display: flex;
  353. align-items: center;
  354. width: 33px;
  355. /* margin-right: 20px; */
  356. /* height: 50px; */
  357. }
  358. .logoText {
  359. width: 80%;
  360. height: 100%;
  361. margin-left: 15%;
  362. display: flex;
  363. font-size: 18px;
  364. align-items: center;
  365. /* background-color: lightcoral; */
  366. }
  367. /* 主要用来调整整个menu的宽度 */
  368. .menuTitle {
  369. margin-right: 40px;
  370. }
  371. .sideBarFold {
  372. width: 5%;
  373. height: 3%;
  374. position: absolute;
  375. right: 40px;
  376. bottom: 20px;
  377. }
  378. .navBarBox {
  379. position: fixed;
  380. display: flex;
  381. align-items: center;
  382. width: 100vw;
  383. z-index: 2;
  384. height: 7vh;
  385. top: 0;
  386. background-color: white;
  387. right: 0;
  388. border-bottom: 1px solid gainsboro;
  389. }
  390. /* 调整LOGO */
  391. .logoBox {
  392. box-sizing: border-box;
  393. left: 30px;
  394. position: relative;
  395. display: flex;
  396. justify-content: space-between;
  397. align-items: center;
  398. }
  399. .gameSelect {
  400. position: relative;
  401. height: 80%;
  402. display: flex;
  403. align-items: center;
  404. left: 5%;
  405. display: flex;
  406. align-items: center;
  407. }
  408. .gameIcon {
  409. /* box-sizing: border-box; */
  410. /* padding-right: 12px; */
  411. margin-right: 12px;
  412. }
  413. .navBarMenu {
  414. width: 60%;
  415. position: relative;
  416. left: 6%;
  417. }
  418. .headPortraitBox {
  419. position: absolute;
  420. right: 3%;
  421. top: 50%;
  422. transform: translateY(-50%);
  423. }
  424. .userTools {
  425. width: 100%;
  426. height: 100%;
  427. display: flex;
  428. flex-direction: column;
  429. justify-content: space-around;
  430. align-items: center;
  431. }
  432. .userToolsItem {
  433. cursor: pointer;
  434. width: 100%;
  435. height: 4vh;
  436. display: flex;
  437. align-items: center;
  438. justify-content: center;
  439. /* padding: 10px; */
  440. margin: 2%;
  441. }
  442. .userToolsItem > span {
  443. margin-left: 10%;
  444. }
  445. .userToolsItem:hover {
  446. background-color: #f2f3f5;
  447. }
  448. .headPortrait {
  449. cursor: pointer;
  450. width: 50px;
  451. }
  452. .content {
  453. /* flex-grow: 1; */
  454. /* position: absolute; */
  455. width: 100%;
  456. /* height: 93%; */
  457. margin-top: 7vh;
  458. overflow: scroll;
  459. background-color: #f2f3f5;
  460. right: 0vw;
  461. top: 0vh;
  462. }
  463. </style>
  464. <!-- 为了让popper-class生效,需要的单独写一份 -->
  465. <style>
  466. .headPopper {
  467. padding: 0px !important;
  468. border: 1px solid #e5e6eb;
  469. background-color: white;
  470. }
  471. .el-menu--horizontal.el-menu {
  472. border-bottom: none;
  473. }
  474. </style>