| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731 |
- <!--
- * @Author: fxs bjnsfxs@163.com
- * @Date: 2024-08-20 14:06:49
- * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2025-06-03
- * @Description:
- *
- -->
- <script setup lang="ts">
- import { useRequest } from '@/hooks/useRequest.ts'
- import { useUser } from '@/stores/useUser.ts'
- import type { DropDownGroupInfo } from '@/types/dataAnalysis'
- import type { ResponseInfo } from '@/types/res.ts'
- import AxiosInstance from '@/utils/axios/axiosInstance.ts'
- import { zhCn } from 'element-plus/es/locales.mjs'
- import { type RouteRecordRaw, RouterView, useRoute } from 'vue-router'
- import { computed, onMounted, reactive, ref, watch } from 'vue'
- import { ElMessage } from 'element-plus'
- import { getAllGameInfo } from '@/utils/table/table'
- import { useCommonStore } from '@/stores/useCommon'
- import { initLoadResource } from '@/utils/resource'
- import { getUserInfo } from '@/utils/localStorage/localStorage'
- import router from '@/router'
- // interface GameSelectItemInfo {
- // id: number
- // pid: string
- // gid: string
- // gameName: string
- // }
- interface GameItem {
- id: number
- pid: string
- gid: string
- gameName: string
- pidName: string
- }
- interface PlatformInfo {
- pidName: string
- pid: string
- gidList: GameItem[]
- }
- const route = useRoute()
- const { selectInfo, allGameInfo, saveSelectInfo } = useCommonStore()
- const { AllApi } = useRequest()
- const { updateUserInfo } = useUser()
- const userInfo = getUserInfo()
- const isCollapse = ref(false)
- const navBarSelect = ref<string>('Home')
- const loadingState = ref(false) // 用来标记必要信息的加载状态
- // 路由信息,同时也是侧边栏生成的依据信息
- const menuList = reactive<Array<any>>([])
- // 默认选中
- const defaultActive = computed<string>(() => {
- return route.meta.activeMenu as string
- })
- // 顶部导航栏信息
- const navBarMenuList = computed(() => {
- const allNavBar = [
- {
- name: 'Home',
- title: '应用分析',
- needSuper: false
- },
- {
- name: 'AppManage',
- title: '应用管理',
- needSuper: true
- },
- {
- name: 'FileManage',
- title: '文件管理',
- needSuper: true
- },
- {
- name: 'MemberManage',
- title: '成员管理',
- needSuper: true
- }
- ]
- return allNavBar.filter((item) => {
- if (item.needSuper) {
- return userInfo?.isSuper
- }
- return true
- })
- })
- /**
- * 侧边栏折叠改变
- */
- const changeCollapse = () => {
- isCollapse.value = !isCollapse.value
- }
- const backupOptionsList: Array<PlatformInfo> = []
- // 游戏下拉选择框需要的数据
- const gameSelectInfo = reactive<DropDownGroupInfo>({
- defaultSelect: '1001',
- placeholder: '请选择游戏',
- // optionsList: []
- optionsList: []
- })
- /**
- * 更新整个页面的游戏选择
- * @param {*} gid 游戏id
- */
- const changeGame = (gid: string) => {
- selectInfo.gid = gid
- saveSelectInfo()
- }
- /**
- * 更新头部导航栏,跳转到对应页面
- * @param val 对应的name
- */
- const changeNavBar = (val: string) => {
- navBarSelect.value = val
- router.push(`/${val}`)
- // createdMenuList()
- }
- // 资源的加载路径
- const resourceInfo: Record<string, string> = {
- logo: `/img/logo.svg`,
- // logo: `/img/logoTest.svg`,
- defaultHead: `/img/default/defaultHead.png`
- }
- // 使用blob的资源路径信息
- const blobUrlInfo = reactive<Record<string, string>>({})
- // 侧边栏跳转路由的基本路由
- const basePath = ref<string | undefined>()
- // 是否过滤不活跃的游戏
- const isFilterNotActiveGame = ref<boolean>(true)
- // 游戏选择框的加载状态
- const gameSelectLoading = ref<boolean>(false)
- // 选中的游戏
- const selectedGame = ref<string>('')
- /**
- * 创建侧边栏菜单
- */
- const createdMenuList = (topRoute: RouteRecordRaw | null) => {
- if (!topRoute) return
- basePath.value = topRoute?.path // 找到需要激活的菜单的路由,后续用来拼接需要跳转的路由
- menuList.splice(0, menuList.length, ...(topRoute?.children as Array<any>)) // 清空原来的路由信息,并且加入新选中的
- }
- /**
- * 从URL中解析出路径片段
- * @param fullUrl 完整的 URL
- * @returns 路径片段组成的数组
- */
- const parsePathFromUrl = (fullUrl: string): string[] => {
- const hashIndex = fullUrl.indexOf('#')
- const hashPart = hashIndex !== -1 ? fullUrl.slice(hashIndex + 1) : fullUrl
- const pathPart = hashPart.split('?')[0] // 去除查询参数
- return pathPart.split('/').filter(Boolean) // ['home', 'infoManage', 'gameManageView']
- }
- /**
- * 查找子路由所属的顶级路由
- * @param routes 路由数组
- * @param fullUrl 目标子路由的 name
- * @returns 顶级路由的 name 或 null
- */
- const findTopRouteName = (routes: RouteRecordRaw[], fullUrl: string): RouteRecordRaw | null => {
- const pathSegments = parsePathFromUrl(fullUrl)
- if (pathSegments.length === 0) return null
- for (const route of routes) {
- const routeSegments = route.path.split('/').filter(Boolean)
- if (routeSegments[0] === pathSegments[0]) {
- return route || null
- }
- }
- return null
- }
- /**
- * 当路由地址改变的时候,去获取最新的导航栏位置,并且重新生成侧边栏,不然刷新后,侧边栏会无法选中
- */
- watch(
- () => [router.currentRoute.value.fullPath],
- ([newFullPath]) => {
- let routes = router.options.routes // 获取路由信息
- let indexRoutesChild = routes.find((item) => item.name === 'Index')?.children
- if (!indexRoutesChild) {
- ElMessage.error('路由错误')
- return
- }
- const topRoute = findTopRouteName(indexRoutesChild, newFullPath)
- navBarSelect.value = topRoute?.name?.toString() || ''
- createdMenuList(topRoute)
- },
- {
- immediate: true
- }
- )
- /**
- * 获取所有游戏列表
- */
- const updateGameInfo = () => {
- getAllGameInfo()
- .then((data) => {
- if (data) {
- allGameInfo.splice(0, allGameInfo.length)
- data.map((item) => {
- allGameInfo.push({
- gid: item.gid,
- gameName: item.gameName
- })
- })
- loadingState.value = true
- } else {
- throw new Error('游戏信息获取失败')
- }
- })
- .catch((err) => {
- console.log(err)
- })
- }
- /**
- * 设置导航栏的游戏选择框数据
- */
- const setNavbarGameSelect = (optionsList: Array<PlatformInfo>) => {
- // gameSelectInfo.optionsList.clear()
- gameSelectInfo.optionsList.splice(0, gameSelectInfo.optionsList.length)
- if (!optionsList || optionsList.length === 0) {
- selectedGame.value = ''
- gameSelectInfo.optionsList.length = 0
- return
- }
- gameSelectInfo.optionsList.splice(0, gameSelectInfo.optionsList.length, ...optionsList)
- backupOptionsList.splice(0, backupOptionsList.length, ...optionsList)
- // optionsList.forEach((item) => {
- // if (!gameSelectInfo.optionsList.has(item.gameName)) {
- // gameSelectInfo.optionsList.set(item.gameName, [
- // {
- // value: item.gid,
- // label: item.gameName
- // }
- // ])
- // } else {
- // gameSelectInfo.optionsList.get(item.gameName)?.push({
- // value: item.gid,
- // label: item.gameName
- // })
- // }
- // })
- console.log('更新后')
- console.log(gameSelectInfo.optionsList)
- // optionsList.forEach((item) => {
- // gameSelectInfo.optionsList.push({
- // value: item.gid,
- // label: item.gameName
- // })
- // })
- // 这个主要是用在初始化
- // if (selectInfo.gid) {
- // selectedGame.value = selectInfo.gid
- // }
- }
- /**
- *
- *
- * 获取导航栏游戏选择框的数据
- */
- const updateNavbarGameSelect = async (query: string, force = false) => {
- if (!query && !force) {
- return
- }
- try {
- gameSelectLoading.value = true
- // const res = (await AxiosInstance.post(AllApi.getGidList, {
- // active: isFilterNotActiveGame.value,
- // search: query
- // })) as ResponseInfo
- const res = (await AxiosInstance.post(AllApi.pidToGidList, {
- active: isFilterNotActiveGame.value,
- search: query
- })) as ResponseInfo
- if (res.code !== 0) {
- setNavbarGameSelect([])
- return false
- }
- setNavbarGameSelect(res.data)
- } catch (err) {
- setNavbarGameSelect([])
- console.error(err)
- ElMessage.error('游戏列表获取失败')
- } finally {
- gameSelectLoading.value = false
- }
- }
- const filterSelect = (query: string) => {
- console.log('执行')
- gameSelectInfo.optionsList = backupOptionsList.filter((item) => {
- return (
- item.pidName.includes(query) || item.gidList.find((item) => item.gameName.includes(query))
- )
- })
- }
- /**
- * 监听游戏列表的变化
- *
- * 此处只是声明,在后续加载完成后,会被赋值唯一一个监听器
- *
- */
- let watchGameListChange: () => void = () => {}
- /**
- * @description: 监听加载状态的变化,加载完成的时候,给游戏列表的监听器赋值,然后把自己这个监听器摧毁
- * @return {*}
- */
- const watchLoadingState = watch(
- () => loadingState,
- (newVal) => {
- if (newVal) {
- // 用来监听游戏列表的变化
- watchGameListChange = watch(
- () => allGameInfo,
- () => {
- updateNavbarGameSelect('', true)
- },
- { deep: true }
- )
- watchLoadingState()
- } else {
- watchGameListChange()
- }
- },
- {
- deep: true
- }
- )
- updateGameInfo()
- updateNavbarGameSelect('', true)
- onMounted(() => {
- // 去加载所有需要的资源
- initLoadResource(resourceInfo).then((data) => {
- Object.assign(blobUrlInfo, data)
- })
- selectedGame.value = selectInfo.gid
- if (userInfo) {
- updateUserInfo({
- ...userInfo
- })
- }
- })
- </script>
- <template>
- <el-config-provider :locale="zhCn">
- <div class="body" v-if="loadingState">
- <div class="navBarBox">
- <div class="logoBox">
- <el-image :fit="'fill'" class="logoImg" :src="blobUrlInfo.logo"></el-image>
- <!-- <span class="logoTitle">测试库</span>-->
- <span class="logoTitle">淳皓科技</span>
- </div>
- <div class="gameSelect">
- <el-icon class="gameIcon" :size="20">
- <icon-icon-park-game-three></icon-icon-park-game-three>
- <!-- <icon-icon-park-solid-ad></icon-icon-park-solid-ad>-->
- </el-icon>
- <div style="width: 150px">
- <el-select
- style="width: 100%"
- @change="changeGame"
- v-model="selectedGame"
- :placeholder="gameSelectInfo.placeholder"
- filterable
- :filter-method="filterSelect"
- :loading="gameSelectLoading"
- v-bind="$attrs"
- >
- <template #header>
- <el-checkbox
- v-model="isFilterNotActiveGame"
- @change="updateNavbarGameSelect('', true)"
- >过滤不活跃游戏
- </el-checkbox>
- </template>
- <el-option-group
- :key="item.pid"
- :label="item.pidName"
- v-for="item in gameSelectInfo.optionsList"
- >
- <el-option
- v-for="option in item.gidList"
- :key="option.gid"
- :label="option.gameName"
- :value="option.gid"
- />
- </el-option-group>
- </el-select>
- <!-- <el-select-->
- <!-- style="width: 100%"-->
- <!-- @change="changeGame"-->
- <!-- v-model="selectedGame"-->
- <!-- :placeholder="gameSelectInfo.title"-->
- <!-- filterable-->
- <!-- :loading="gameSelectLoading"-->
- <!-- v-bind="$attrs"-->
- <!-- >-->
- <!-- <!– :remote-method="updateNavbarGameSelect"–>-->
- <!-- <!– remote–>-->
- <!-- <template #header>-->
- <!-- <el-checkbox-->
- <!-- v-model="isFilterNotActiveGame"-->
- <!-- @change="updateNavbarGameSelect('', true)"-->
- <!-- >过滤不活跃游戏-->
- <!-- </el-checkbox>-->
- <!-- </template>-->
- <!-- <el-option-->
- <!-- v-for="item in gameSelectInfo.optionsList"-->
- <!-- :key="item.value"-->
- <!-- :label="item.label"-->
- <!-- :value="item.value"-->
- <!-- />-->
- <!-- </el-select>-->
- </div>
- <!-- <DropDownSelection-->
- <!-- :default-select="gameSelectInfo.defaultSelect"-->
- <!-- :title="gameSelectInfo.title"-->
- <!-- :options-list="gameSelectInfo.optionsList"-->
- <!-- :size="'default'"-->
- <!-- @change-select="changeGame"-->
- <!-- >-->
- <!-- <template #header>-->
- <!-- <el-checkbox v-model="isFilterNotActiveGame" @change="updateGameInfo"-->
- <!-- >是否过滤不活跃游戏</el-checkbox-->
- <!-- >-->
- <!-- </template>-->
- <!-- </DropDownSelection>-->
- </div>
- <!-- 顶部导航栏 -->
- <div class="navBarMenu">
- <el-menu
- :default-active="navBarSelect"
- class="el-menu-demo"
- mode="horizontal"
- @select="changeNavBar"
- >
- <el-menu-item
- v-for="item in navBarMenuList"
- :key="item.name"
- class="navBarMenuItem"
- :index="item.name"
- >{{ item.title }}</el-menu-item
- >
- </el-menu>
- </div>
- <div class="headPortraitBox">
- <UserHeadIcon :head-icon="blobUrlInfo.defaultHead"></UserHeadIcon>
- <!-- <el-popover popper-class="headPopper" placement="bottom-end" trigger="click">-->
- <!-- <template #reference>-->
- <!-- <el-image class="headPortrait" :src="blobUrlInfo.defaultHead"></el-image>-->
- <!-- </template>-->
- <!-- <div class="userTools">-->
- <!-- <span class="userToolsItem" @click="logOut">-->
- <!-- <icon-material-symbols-light-logout></icon-material-symbols-light-logout>-->
- <!-- <span> 退出登录</span>-->
- <!-- </span>-->
- <!-- </div>-->
- <!-- </el-popover>-->
- </div>
- </div>
- <!-- 侧边栏 -->
- <div class="sideBarBox">
- <el-menu :default-active="defaultActive" class="sideBar" :collapse="isCollapse">
- <!-- ref="sideBar"-->
- <template v-for="(item, index) in menuList">
- <el-sub-menu
- :index="`${index}`"
- v-if="item.children && item.showChild"
- :key="item.name"
- >
- <template #title>
- <!-- <el-icon><component :is="item.icon"></component></el-icon>-->
- <DynamicIcon :icon="item.icon" />
- <span>{{ item.cnName }}</span>
- </template>
- <router-link
- style="text-decoration: none"
- v-for="val in item.children"
- :to="{ path: basePath + '/' + item.path + '/' + val.path }"
- :key="val"
- >
- <el-menu-item :index="val.path">{{ val.cnName }}</el-menu-item>
- </router-link>
- </el-sub-menu>
- <router-link
- style="text-decoration: none"
- v-else
- :to="{ path: basePath + '/' + item.path }"
- :key="index"
- >
- <el-menu-item :index="item.path">
- <!-- <el-icon><component :is="item.icon" /></el-icon>-->
- <DynamicIcon :icon="item.icon" />
- <template #title>
- <span class="menuTitle">{{ item.cnName }}</span>
- </template>
- </el-menu-item>
- </router-link>
- </template>
- <div class="sideBarFold" @click="changeCollapse">
- <el-icon :size="25"><Fold /></el-icon>
- </div>
- </el-menu>
- </div>
- <div class="content">
- <router-view v-slot="{ Component, route }">
- <keep-alive>
- <component
- :is="Component"
- :key="route.meta.activeMenu"
- v-if="route.meta.needKeepAlive == true"
- ></component>
- </keep-alive>
- <component
- :is="Component"
- :key="route.meta.activeMenu"
- v-if="route.meta.needKeepAlive == false"
- ></component>
- </router-view>
- </div>
- </div>
- </el-config-provider>
- </template>
- <style scoped>
- .body {
- width: 100%;
- display: flex;
- height: 100vh;
- }
- /* 设置宽度后,content无法适应宽度,只能去间接的调整内部元素的宽度 */
- .sideBarBox {
- position: relative;
- /* width: 12%; */
- z-index: 1;
- height: 93vh;
- margin-top: 7vh;
- top: 0;
- }
- .sideBar {
- /* width: 12vw; */
- height: 93vh;
- position: relative;
- overflow: scroll;
- }
- .logoImg {
- display: flex;
- align-items: center;
- width: 33px;
- margin-right: 10px;
- }
- .logoTitle {
- font-family: 'Helvetica Neue', 'Hiragino Sans GB', 'Segoe UI', 'Microsoft Yahei', '微软雅黑',
- Tahoma, Arial, STHeiti, sans-serif;
- font-size: 18px;
- font-weight: 600;
- }
- /* 主要用来调整整个menu的宽度 */
- .menuTitle {
- margin-right: 40px;
- }
- .sideBarFold {
- width: 5%;
- height: 3%;
- position: absolute;
- right: 40px;
- bottom: 20px;
- }
- .navBarBox {
- position: fixed;
- display: flex;
- align-items: center;
- width: 100vw;
- z-index: 2;
- height: 7vh;
- top: 0;
- background-color: white;
- right: 0;
- border-bottom: 1px solid gainsboro;
- }
- /* 调整LOGO */
- .logoBox {
- box-sizing: border-box;
- left: 30px;
- position: relative;
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- .gameSelect {
- position: relative;
- height: 80%;
- display: flex;
- align-items: center;
- left: 5%;
- }
- .gameIcon {
- /* box-sizing: border-box; */
- /* padding-right: 12px; */
- margin-right: 12px;
- }
- .navBarMenu {
- width: 60%;
- position: relative;
- left: 6%;
- }
- .headPortraitBox {
- position: absolute;
- right: 3%;
- top: 50%;
- transform: translateY(-50%);
- }
- .userTools {
- width: 100%;
- height: 100%;
- display: flex;
- flex-direction: column;
- justify-content: space-around;
- align-items: center;
- }
- .userToolsItem {
- cursor: pointer;
- width: 100%;
- height: 4vh;
- display: flex;
- align-items: center;
- justify-content: center;
- /* padding: 10px; */
- margin: 2%;
- }
- .userToolsItem > span {
- margin-left: 10%;
- }
- .userToolsItem:hover {
- background-color: #f2f3f5;
- }
- .headPortrait {
- cursor: pointer;
- width: 30px;
- }
- .content {
- /* flex-grow: 1; */
- /* position: absolute; */
- width: 100%;
- /* height: 93%; */
- margin-top: 7vh;
- overflow: scroll;
- background-color: #f2f3f5;
- right: 0;
- top: 0;
- }
- </style>
- <!-- 为了让popper-class生效,需要的单独写一份 -->
- <style>
- .headPopper {
- padding: 0 !important;
- border: 1px solid #e5e6eb;
- background-color: white;
- }
- .el-menu--horizontal.el-menu {
- border-bottom: none;
- }
- </style>
|