调整代码

This commit is contained in:
guanj
2025-11-20 15:12:01 +08:00
parent 0a52d1afae
commit 028fd44490
17 changed files with 3910 additions and 3761 deletions

View File

@@ -1,71 +1,70 @@
import { useCache, CACHE_KEY } from '@/hooks/web/useCache'
import { TokenType } from '@/api/login/types'
import { decrypt, encrypt } from '@/utils/jsencrypt'
const { wsCache } = useCache()
const AccessTokenKey = 'ACCESS_TOKEN'
const RefreshTokenKey = 'REFRESH_TOKEN'
// 获取token
export const getAccessToken = () => {
// 此处与TokenKey相同此写法解决初始化时Cookies中不存在TokenKey报错
return wsCache.get(AccessTokenKey) ? wsCache.get(AccessTokenKey) : wsCache.get('ACCESS_TOKEN')
}
// 刷新token
export const getRefreshToken = () => {
return wsCache.get(RefreshTokenKey)
}
// 设置token
export const setToken = (token: TokenType) => {
wsCache.set(RefreshTokenKey, token.refreshToken)
wsCache.set(AccessTokenKey, token.accessToken)
}
// 删除token
export const removeToken = () => {
wsCache.delete(AccessTokenKey)
wsCache.delete(RefreshTokenKey)
}
/** 格式化tokenjwt格式 */
export const formatToken = (token: string): string => {
return 'Bearer ' + token
}
// ========== 账号相关 ==========
export type LoginFormType = {
tenantName: string
username: string
password: string
rememberMe: boolean
}
export const getLoginForm = () => {
const loginForm: LoginFormType = wsCache.get(CACHE_KEY.LoginForm)
if (loginForm) {
loginForm.password = decrypt(loginForm.password) as string
}
return loginForm
}
export const setLoginForm = (loginForm: LoginFormType) => {
loginForm.password = encrypt(loginForm.password) as string
wsCache.set(CACHE_KEY.LoginForm, loginForm, { exp: 30 * 24 * 60 * 60 })
}
export const removeLoginForm = () => {
wsCache.delete(CACHE_KEY.LoginForm)
}
// ========== 租户相关 ==========
export const getTenantId = () => {
return wsCache.get(CACHE_KEY.TenantId)
}
export const setTenantId = (username: string) => {
wsCache.set(CACHE_KEY.TenantId, username)
}
import { useCache, CACHE_KEY } from '@/hooks/web/useCache'
import { decrypt, encrypt } from '@/utils/jsencrypt'
const { wsCache } = useCache()
const AccessTokenKey = 'ACCESS_TOKEN'
const RefreshTokenKey = 'REFRESH_TOKEN'
// 获取token
export const getAccessToken = () => {
// 此处与TokenKey相同此写法解决初始化时Cookies中不存在TokenKey报错
return wsCache.get(AccessTokenKey) ? wsCache.get(AccessTokenKey) : wsCache.get('ACCESS_TOKEN')
}
// 刷新token
export const getRefreshToken = () => {
return wsCache.get(RefreshTokenKey)
}
// 设置token
export const setToken = (token: any) => {
wsCache.set(RefreshTokenKey, token.refreshToken)
wsCache.set(AccessTokenKey, token.accessToken)
}
// 删除token
export const removeToken = () => {
wsCache.delete(AccessTokenKey)
wsCache.delete(RefreshTokenKey)
}
/** 格式化tokenjwt格式 */
export const formatToken = (token: string): string => {
return 'Bearer ' + token
}
// ========== 账号相关 ==========
export type LoginFormType = {
tenantName: string
username: string
password: string
rememberMe: boolean
}
export const getLoginForm = () => {
const loginForm: LoginFormType = wsCache.get(CACHE_KEY.LoginForm)
if (loginForm) {
loginForm.password = decrypt(loginForm.password) as string
}
return loginForm
}
export const setLoginForm = (loginForm: LoginFormType) => {
loginForm.password = encrypt(loginForm.password) as string
wsCache.set(CACHE_KEY.LoginForm, loginForm, { exp: 30 * 24 * 60 * 60 })
}
export const removeLoginForm = () => {
wsCache.delete(CACHE_KEY.LoginForm)
}
// ========== 租户相关 ==========
export const getTenantId = () => {
return wsCache.get(CACHE_KEY.TenantId)
}
export const setTenantId = (username: string) => {
wsCache.set(CACHE_KEY.TenantId, username)
}

View File

@@ -1,159 +1,302 @@
const dataProcessing = (arr: any[]) => {
return arr
.filter(item => typeof item === 'number' || (typeof item === 'string' && !isNaN(parseFloat(item))))
.map(item => (typeof item === 'number' ? item : parseFloat(item)))
}
const calculateValue = (o: number, value: number, num: number, isMin: boolean) => {
if (value === 0) {
return 0
} else if (value > 0 && Math.abs(value) < 1 && isMin == true) {
return 0
} else if (value > -1 && value < 0 && isMin == false) {
return 0
}
let base
if (Math.abs(o) >= 100) {
base = 100
} else if (Math.abs(o) >= 10) {
base = 10
} else if (Math.abs(o) >= 1) {
base = 1
} else {
base = 0.1
}
let calculatedValue
if (isMin) {
if (value < 0) {
calculatedValue = value + num * value
} else {
calculatedValue = value - num * value
}
} else {
if (value < 0) {
calculatedValue = value - num * value
} else {
calculatedValue = value + num * value
}
}
if (base === 0.1) {
return parseFloat(calculatedValue.toFixed(1))
} else if (isMin) {
return Math.floor(calculatedValue / base) * base
} else {
return Math.ceil(calculatedValue / base) * base
}
}
// 处理y轴最大最小值
export const yMethod = (arr: any) => {
let num = 0.1
let numList = dataProcessing(arr)
let maxValue = 0
let minValue = 0
let max = 0
let min = 0
maxValue = Math.max(...numList)
minValue = Math.min(...numList)
const o = maxValue - minValue
if (Math.abs(o) >= 300) {
num = 0.02
}
min = calculateValue(o, minValue, num, true)
max = calculateValue(o, maxValue, num, false)
// if (-100 >= minValue) {
// min = Math.floor((minValue + num * minValue) / 100) * 100
// } else if (-10 >= minValue && minValue > -100) {
// min = Math.floor((minValue + num * minValue) / 10) * 10
// } else if (-1 >= minValue && minValue > -10) {
// min = Math.floor(minValue + num * minValue)
// } else if (0 > minValue && minValue > -1) {
// min = parseFloat((minValue + num * minValue).toFixed(1))
// } else if (minValue == 0) {
// min = 0
// } else if (0 < minValue && minValue < 1) {
// min = parseFloat((minValue - num * minValue).toFixed(1))
// } else if (1 <= minValue && minValue < 10) {
// min = Math.floor(minValue - num * minValue)
// } else if (10 <= minValue && minValue < 100) {
// min = Math.floor((minValue - num * minValue) / 10) * 10
// } else if (100 <= minValue) {
// min = Math.floor((minValue - num * minValue) / 100) * 100
// }
// if (-100 >= maxValue) {
// max = Math.ceil((maxValue - num * maxValue) / 100) * 100
// } else if (-10 >= maxValue && maxValue > -100) {
// max = Math.ceil((maxValue - num * maxValue) / 10) * 10
// } else if (-1 >= maxValue && maxValue > -10) {
// max = Math.ceil(maxValue - num * maxValue)
// } else if (0 > maxValue && maxValue > -1) {
// max = parseFloat((maxValue - num * maxValue).toFixed(1))
// } else if (maxValue == 0) {
// max = 0
// } else if (0 < maxValue && maxValue < 1) {
// max = parseFloat((maxValue + num * maxValue).toFixed(1))
// } else if (1 <= maxValue && maxValue < 10) {
// max = Math.ceil(maxValue + num * maxValue)
// } else if (10 <= maxValue && maxValue < 100) {
// max = Math.ceil((maxValue + num * maxValue) / 10) * 10
// } else if (100 <= maxValue) {
// max = Math.ceil((maxValue + num * maxValue) / 100) * 100
// }
// if (maxValue > 1000 || minValue < -1000) {
// max = Math.ceil(maxValue / 100) * 100
// if (minValue == 0) {
// min = 0
// } else {
// min = Math.floor(minValue / 100) * 100
// }
// } else if (maxValue < 60 && minValue > 40) {
// max = 60
// min = 40
// } else if (maxValue == minValue && maxValue < 10 && minValue > 0) {
// max = Math.ceil(maxValue / 10) * 10
// min = Math.floor(minValue / 10) * 10
// } else if (maxValue == minValue && maxValue != 0 && minValue != 0) {
// max = Math.ceil(maxValue / 10 + 1) * 10
// min = Math.floor(minValue / 10 - 1) * 10
// } else {
// max = Math.ceil(maxValue / 10) * 10
// min = Math.floor(minValue / 10) * 10
// }
// if (maxValue > 0 && maxValue < 1) {
// max = 1
// } else if (max == 0 && minValue > -1 && minValue < 0) {
// min = -1
// }
return [min, max]
}
/**
* title['A相','B相',]
* data[[1,2],[3,4]]
*/
// 导出csv文件
const convertToCSV = (title: object, data: any) => {
console.log('🚀 ~ convertToCSV ~ data:', data)
let csv = ''
// 添加列头
csv += ',' + title.join(',') + '\n'
// 遍历数据并添加到CSV字符串中
data?.map(item => {
csv += item.join(',') + '\n'
})
return csv
}
export const exportCSV = (title: object, data: any, filename: string) => {
const csv = convertToCSV(title, data)
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = filename
link.click()
// 释放URL对象
URL.revokeObjectURL(link.href)
}
const dataProcessing = (arr: any[]) => {
return arr
.filter(item => typeof item === 'number' || (typeof item === 'string' && !isNaN(parseFloat(item))))
.map(item => (typeof item === 'number' ? item : parseFloat(item)))
}
const calculateValue = (o: number, value: number, num: number, isMin: boolean) => {
if (value === 0) {
return 0
} else if (value > 0 && Math.abs(value) < 1 && isMin == true) {
return 0
} else if (value > -1 && value < 0 && isMin == false) {
return 0
}
let base
if (Math.abs(o) >= 100) {
base = 100
} else if (Math.abs(o) >= 10) {
base = 10
} else if (Math.abs(o) >= 1) {
base = 1
} else {
base = 0.1
}
let calculatedValue
if (isMin) {
if (value < 0) {
calculatedValue = value + num * value
} else {
calculatedValue = value - num * value
}
} else {
if (value < 0) {
calculatedValue = value - num * value
} else {
calculatedValue = value + num * value
}
}
if (base === 0.1) {
return parseFloat(calculatedValue.toFixed(1))
} else if (isMin) {
return Math.floor(calculatedValue / base) * base
} else {
return Math.ceil(calculatedValue / base) * base
}
}
// 处理y轴最大最小值
export const yMethod = (arr: any) => {
let num = 0.1
let numList = dataProcessing(arr)
let maxValue = 0
let minValue = 0
let max = 0
let min = 0
maxValue = Math.max(...numList)
minValue = Math.min(...numList)
const o = maxValue - minValue
if (Math.abs(o) >= 300) {
num = 0.02
}
min = calculateValue(o, minValue, num, true)
max = calculateValue(o, maxValue, num, false)
// if (-100 >= minValue) {
// min = Math.floor((minValue + num * minValue) / 100) * 100
// } else if (-10 >= minValue && minValue > -100) {
// min = Math.floor((minValue + num * minValue) / 10) * 10
// } else if (-1 >= minValue && minValue > -10) {
// min = Math.floor(minValue + num * minValue)
// } else if (0 > minValue && minValue > -1) {
// min = parseFloat((minValue + num * minValue).toFixed(1))
// } else if (minValue == 0) {
// min = 0
// } else if (0 < minValue && minValue < 1) {
// min = parseFloat((minValue - num * minValue).toFixed(1))
// } else if (1 <= minValue && minValue < 10) {
// min = Math.floor(minValue - num * minValue)
// } else if (10 <= minValue && minValue < 100) {
// min = Math.floor((minValue - num * minValue) / 10) * 10
// } else if (100 <= minValue) {
// min = Math.floor((minValue - num * minValue) / 100) * 100
// }
// if (-100 >= maxValue) {
// max = Math.ceil((maxValue - num * maxValue) / 100) * 100
// } else if (-10 >= maxValue && maxValue > -100) {
// max = Math.ceil((maxValue - num * maxValue) / 10) * 10
// } else if (-1 >= maxValue && maxValue > -10) {
// max = Math.ceil(maxValue - num * maxValue)
// } else if (0 > maxValue && maxValue > -1) {
// max = parseFloat((maxValue - num * maxValue).toFixed(1))
// } else if (maxValue == 0) {
// max = 0
// } else if (0 < maxValue && maxValue < 1) {
// max = parseFloat((maxValue + num * maxValue).toFixed(1))
// } else if (1 <= maxValue && maxValue < 10) {
// max = Math.ceil(maxValue + num * maxValue)
// } else if (10 <= maxValue && maxValue < 100) {
// max = Math.ceil((maxValue + num * maxValue) / 10) * 10
// } else if (100 <= maxValue) {
// max = Math.ceil((maxValue + num * maxValue) / 100) * 100
// }
// if (maxValue > 1000 || minValue < -1000) {
// max = Math.ceil(maxValue / 100) * 100
// if (minValue == 0) {
// min = 0
// } else {
// min = Math.floor(minValue / 100) * 100
// }
// } else if (maxValue < 60 && minValue > 40) {
// max = 60
// min = 40
// } else if (maxValue == minValue && maxValue < 10 && minValue > 0) {
// max = Math.ceil(maxValue / 10) * 10
// min = Math.floor(minValue / 10) * 10
// } else if (maxValue == minValue && maxValue != 0 && minValue != 0) {
// max = Math.ceil(maxValue / 10 + 1) * 10
// min = Math.floor(minValue / 10 - 1) * 10
// } else {
// max = Math.ceil(maxValue / 10) * 10
// min = Math.floor(minValue / 10) * 10
// }
// if (maxValue > 0 && maxValue < 1) {
// max = 1
// } else if (max == 0 && minValue > -1 && minValue < 0) {
// min = -1
// }
return [min, max]
}
/**
* title['A相','B相',]
* data[[1,2],[3,4]]
*/
// 导出csv文件
const convertToCSV = (title: object, data: any) => {
console.log('🚀 ~ convertToCSV ~ data:', data)
let csv = ''
// 添加列头
csv += ',' + title.join(',') + '\n'
// 遍历数据并添加到CSV字符串中
data?.map(item => {
csv += item.join(',') + '\n'
})
return csv
}
export const exportCSV = (title: object, data: any, filename: string) => {
const csv = convertToCSV(title, data)
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = filename
link.click()
// 释放URL对象
URL.revokeObjectURL(link.href)
}
/**
* 补全时间序列数据中缺失的条目
* @param rawData 原始数据,格式为 [["时间字符串", "数值", "单位", "类型"], ...]
* @returns 补全后的数据,缺失条目数值为 null
*/
export const completeTimeSeries = (rawData: string[][]): (string | null)[][] => {
// 步骤1校验原始数据并解析时间
if (rawData.length < 2) {
console.warn('数据量不足2条无法计算时间间隔直接返回原始数据')
return rawData.map(item => [...item])
}
// 解析所有时间为Date对象过滤无效时间并按时间排序
const validData = rawData
.map(item => {
// 确保至少有时间和数值字段
if (!item[0]) {
return { time: new Date(0), item, isValid: false }
}
const time = new Date(item[0])
return { time, item, isValid: !isNaN(time.getTime()) }
})
.filter(data => data.isValid)
.sort((a, b) => a.time.getTime() - b.time.getTime()) // 确保数据按时间排序
.map(data => data.item)
if (validData.length < 2) {
throw new Error('有效时间数据不足2条无法继续处理')
}
// 步骤2计算时间间隔分析前几条数据确定最可能的间隔
const intervals: number[] = []
// 分析前10条数据来确定间隔避免单一间隔出错
const analyzeCount = Math.min(10, validData.length - 1)
for (let i = 0; i < analyzeCount; i++) {
const currentTime = new Date(validData[i][0]!).getTime()
const nextTime = new Date(validData[i + 1][0]!).getTime()
const interval = nextTime - currentTime
if (interval > 0) {
intervals.push(interval)
}
}
// 取最常见的间隔作为标准间隔
const timeInterval = getMostFrequentValue(intervals)
if (timeInterval <= 0) {
throw new Error('无法确定有效的时间间隔')
}
// 步骤3生成完整的时间序列范围从第一条到最后一条
const startTime = new Date(validData[0][0]!).getTime()
const endTime = new Date(validData[validData.length - 1][0]!).getTime()
const completeTimes: Date[] = []
// 生成从 startTime 到 endTime 的所有间隔时间点
for (let time = startTime; time <= endTime; time += timeInterval) {
completeTimes.push(new Date(time))
}
// 步骤4将原始数据转为时间映射表使用精确的时间字符串匹配
const timeDataMap = new Map<string, (string | undefined)[]>()
validData.forEach(item => {
// 使用原始时间字符串作为键,避免格式转换导致的匹配问题
if (item[0]) {
timeDataMap.set(item[0], item)
}
})
// 提取模板数据(从第一条有效数据中提取单位和类型,处理可能的缺失)
const template = validData[0]
// 步骤5对比补全数据缺失条目数值为 null
const completedData = completeTimes.map(time => {
// 保持与原始数据相同的时间格式
const timeStr = formatTime(time)
const existingItem = timeDataMap.get(timeStr)
if (existingItem) {
// 存在该时间,返回原始数据
return [...existingItem]
} else {
// 缺失该时间,数值设为 null其他字段沿用第一个有效数据的格式
// 处理可能缺失的单位和类型字段
const result: (string | null | undefined)[] = [timeStr, '/']
// 仅在原始数据有单位字段时才添加
if (template.length > 2) {
result.push(template[2])
}
// 仅在原始数据有类型字段时才添加
if (template.length > 3) {
result.push(template[3])
}
return result
}
})
return completedData
}
/**
* 格式化时间为 "YYYY-MM-DD HH:mm:ss" 格式
* @param date 日期对象
* @returns 格式化后的时间字符串
*/
function formatTime(date: Date): string {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
/**
* 获取数组中出现频率最高的值
* @param arr 数字数组
* @returns 出现频率最高的值
*/
function getMostFrequentValue(arr: number[]): number {
if (arr.length === 0) return 0
const frequencyMap = new Map<number, number>()
arr.forEach(num => {
frequencyMap.set(num, (frequencyMap.get(num) || 0) + 1)
})
let maxFrequency = 0
let mostFrequent = arr[0]
frequencyMap.forEach((frequency, num) => {
if (frequency > maxFrequency) {
maxFrequency = frequency
mostFrequent = num
}
})
return mostFrequent
}

View File

@@ -1,295 +1,295 @@
import router from '@/router/index'
import { isNavigationFailure, NavigationFailureType } from 'vue-router'
import type { RouteRecordRaw, RouteLocationRaw } from 'vue-router'
import { ElNotification } from 'element-plus'
import { useConfig } from '@/stores/config'
import { useNavTabs } from '@/stores/navTabs'
import { closeShade } from '@/utils/pageShade'
import { adminBaseRoute } from '@/router/static'
import { compact, isEmpty, reverse } from 'lodash-es'
import { isAdminApp } from '@/utils/common'
/**
* 导航失败有错误消息的路由push
* @param to — 导航位置,同 router.push
*/
export const routePush = async (to: RouteLocationRaw) => {
try {
const failure = await router.push(to)
if (isNavigationFailure(failure, NavigationFailureType.aborted)) {
ElNotification({
message: 'utils.Navigation failed, navigation guard intercepted!',
type: 'error'
})
} else if (isNavigationFailure(failure, NavigationFailureType.duplicated)) {
// ElNotification({
// message: '已在目标页',
// type: 'warning'
// })
}
} catch (error) {
ElNotification({
message: '导航失败,路由无效',
type: 'error'
})
console.error(error)
}
}
/**
* 获取第一个菜单
*/
export const getFirstRoute = (routes: RouteRecordRaw[], menuType = 'tab'): false | RouteRecordRaw => {
const routerPaths: string[] = []
const routers = router.getRoutes()
routers.forEach(item => {
if (item.path) routerPaths.push(item.path)
})
let find: boolean | RouteRecordRaw = false
for (const key in routes) {
if (
routes[key].meta?.type == 'menu' &&
routes[key].meta?.menu_type == menuType &&
routerPaths.indexOf(routes[key].path) !== -1
) {
return routes[key]
} else if (routes[key].children && routes[key].children?.length) {
find = getFirstRoute(routes[key].children!)
if (find) return find
}
}
return find
}
/**
* 打开侧边菜单
* @param menu 菜单数据
*/
export const onClickMenu = (menu: RouteRecordRaw) => {
switch (menu.meta?.menu_type) {
case 'iframe':
case 'tab':
routePush({ path: menu.path })
break
case 'link':
window.open(menu.path, '_blank')
break
default:
ElNotification({
message: 'utils.Navigation failed, the menu type is unrecognized!',
type: 'error'
})
break
}
const config = useConfig()
if (config.layout.shrink) {
closeShade(() => {
config.setLayout('menuCollapse', true)
})
}
}
/**
* 处理后台的路由
*/
export const handleAdminRoute = (routes: any) => {
const viewsComponent = import.meta.glob('/src/views/**/*.vue')
addRouteAll(viewsComponent, routes, adminBaseRoute.name as string)
const menuAdminBaseRoute = (adminBaseRoute.path as string) + '/'
// 更新stores中的路由菜单数据
const navTabs = useNavTabs()
navTabs.setTabsViewRoutes(handleMenuRule(routes, menuAdminBaseRoute))
navTabs.fillAuthNode(handleAuthNode(routes, menuAdminBaseRoute))
}
/**
* 获取菜单的paths
*/
export const getMenuPaths = (menus: RouteRecordRaw[]): string[] => {
let menuPaths: string[] = []
menus.forEach(item => {
menuPaths.push(item.path)
if (item.children && item.children.length > 0) {
menuPaths = menuPaths.concat(getMenuPaths(item.children))
}
})
return menuPaths
}
/**
* 后台的菜单处理
*/
const handleMenuRule = (routes: any, pathPrefix = '/', type = ['menu', 'menu_dir']) => {
const menuRule: RouteRecordRaw[] = []
for (const key in routes) {
if (routes[key].extend == 'add_rules_only') {
continue
}
if (!type.includes(routes[key].type)) {
continue
}
if (routes[key].type == 'menu_dir' && routes[key].children && !routes[key].children.length) {
continue
}
if (
['route', 'menu', 'nav_user_menu', 'nav'].includes(routes[key].type) &&
((routes[key].menu_type == 'tab' && !routes[key].component) ||
(['link', 'iframe'].includes(routes[key].menu_type) && !routes[key].url))
) {
continue
}
const currentPath = ['link', 'iframe'].includes(routes[key].menu_type)
? routes[key].url
: pathPrefix + routes[key].path
let children: RouteRecordRaw[] = []
if (routes[key].children && routes[key].children.length > 0) {
children = handleMenuRule(routes[key].children, pathPrefix, type)
}
menuRule.push({
path: currentPath,
name: routes[key].name,
component: routes[key].component,
meta: {
id: routes[key].id,
title: routes[key].title,
icon: routes[key].icon,
keepalive: routes[key].keepalive,
menu_type: routes[key].menu_type,
type: routes[key].type
},
children: children
})
}
return menuRule
}
/**
* 处理权限节点
* @param routes 路由数据
* @param prefix 节点前缀
* @returns 组装好的权限节点
*/
const handleAuthNode = (routes: any, prefix = '/') => {
const authNode: Map<string, string[]> = new Map([])
assembleAuthNode(routes, authNode, prefix, prefix)
return authNode
}
const assembleAuthNode = (routes: any, authNode: Map<string, string[]>, prefix = '/', parent = '/') => {
const authNodeTemp = []
for (const key in routes) {
if (routes[key].type == 'button') authNodeTemp.push(prefix + routes[key].name)
if (routes[key].children && routes[key].children.length > 0) {
assembleAuthNode(routes[key].children, authNode, prefix, prefix + routes[key].name)
}
}
if (authNodeTemp && authNodeTemp.length > 0) {
authNode.set(parent, authNodeTemp)
}
}
/**
* 动态添加路由-带子路由
* @param viewsComponent
* @param routes
* @param parentName
* @param analyticRelation 根据 name 从已注册路由分析父级路由
*/
export const addRouteAll = (
viewsComponent: Record<string, any>,
routes: any,
parentName: string,
analyticRelation = false
) => {
for (const idx in routes) {
if (routes[idx].extend == 'add_menu_only') {
continue
}
if (
(routes[idx].menu_type == 'tab' && viewsComponent[routes[idx].component]) ||
routes[idx].menu_type == 'iframe'
) {
addRouteItem(viewsComponent, routes[idx], parentName, analyticRelation)
}
if (routes[idx].children && routes[idx].children.length > 0) {
addRouteAll(viewsComponent, routes[idx].children, parentName, analyticRelation)
}
}
}
/**
* 动态添加路由
* @param viewsComponent
* @param route
* @param parentName
* @param analyticRelation 根据 name 从已注册路由分析父级路由
*/
export const addRouteItem = (
viewsComponent: Record<string, any>,
route: any,
parentName: string,
analyticRelation: boolean
) => {
let path = '',
component
if (route.menu_type == 'iframe') {
path = (isAdminApp() ? adminBaseRoute.path : '') + '/iframe/' + encodeURIComponent(route.url)
component = () => import('@/layouts/common/router-view/iframe.vue')
} else {
path = parentName ? route.path : '/' + route.path
component = viewsComponent[route.component]
}
if (route.menu_type == 'tab' && analyticRelation) {
const parentNames = getParentNames(route.name)
if (parentNames.length) {
for (const key in parentNames) {
if (router.hasRoute(parentNames[key])) {
parentName = parentNames[key]
break
}
}
}
}
const routeBaseInfo: RouteRecordRaw = {
path: path,
name: route.name,
component: component,
meta: {
...route,
title: route.title,
extend: route.extend,
icon: route.icon,
keepalive: route.keepalive,
menu_type: route.menu_type,
type: route.type,
url: route.url,
addtab: true
}
}
if (parentName) {
router.addRoute(parentName, routeBaseInfo)
} else {
router.addRoute(routeBaseInfo)
}
}
/**
* 根据name字符串获取父级name组合的数组
* @param name
*/
const getParentNames = (name: string) => {
const names = compact(name.split('/'))
const tempNames = []
const parentNames = []
for (const key in names) {
tempNames.push(names[key])
if (parseInt(key) != names.length - 1) {
parentNames.push(tempNames.join('/'))
}
}
return reverse(parentNames)
}
import router from '@/router/index'
import { isNavigationFailure, NavigationFailureType } from 'vue-router'
import type { RouteRecordRaw, RouteLocationRaw } from 'vue-router'
import { ElNotification } from 'element-plus'
import { useConfig } from '@/stores/config'
import { useNavTabs } from '@/stores/navTabs'
import { closeShade } from '@/utils/pageShade'
import { adminBaseRoute } from '@/router/static'
import { compact, isEmpty, reverse } from 'lodash-es'
import { isAdminApp } from '@/utils/common'
/**
* 导航失败有错误消息的路由push
* @param to — 导航位置,同 router.push
*/
export const routePush = async (to: RouteLocationRaw) => {
try {
const failure = await router.push(to)
if (isNavigationFailure(failure, NavigationFailureType.aborted)) {
ElNotification({
message: 'utils.Navigation failed, navigation guard intercepted!',
type: 'error'
})
} else if (isNavigationFailure(failure, NavigationFailureType.duplicated)) {
// ElNotification({
// message: '已在目标页',
// type: 'warning'
// })
}
} catch (error) {
ElNotification({
message: '导航失败,路由无效',
type: 'error'
})
console.error(error)
}
}
/**
* 获取第一个菜单
*/
export const getFirstRoute = (routes: RouteRecordRaw[], menuType = 'tab'): false | RouteRecordRaw => {
const routerPaths: string[] = []
const routers = router.getRoutes()
routers.forEach(item => {
if (item.path) routerPaths.push(item.path)
})
let find: boolean | RouteRecordRaw = false
for (const key in routes) {
if (
routes[key].meta?.type == 'menu' &&
routes[key].meta?.menu_type == menuType &&
routerPaths.indexOf(routes[key].path) !== -1
) {
return routes[key]
} else if (routes[key].children && routes[key].children?.length) {
find = getFirstRoute(routes[key].children!)
if (find) return find
}
}
return find
}
/**
* 打开侧边菜单
* @param menu 菜单数据
*/
export const onClickMenu = (menu: RouteRecordRaw) => {
switch (menu.meta?.menu_type) {
case 'iframe':
case 'tab':
routePush({ path: menu.path })
break
case 'link':
window.open(menu.path, '_blank')
break
default:
ElNotification({
message: 'utils.Navigation failed, the menu type is unrecognized!',
type: 'error'
})
break
}
const config = useConfig()
if (config.layout.shrink) {
closeShade(() => {
config.setLayout('menuCollapse', true)
})
}
}
/**
* 处理后台的路由
*/
export const handleAdminRoute = (routes: any) => {
const viewsComponent = import.meta.glob('/src/views/**/*.vue')
addRouteAll(viewsComponent, routes, adminBaseRoute.name as string)
const menuAdminBaseRoute = (adminBaseRoute.path as string) + '/'
// 更新stores中的路由菜单数据
const navTabs = useNavTabs()
navTabs.setTabsViewRoutes(handleMenuRule(routes, menuAdminBaseRoute))
navTabs.fillAuthNode(handleAuthNode(routes, menuAdminBaseRoute))
}
/**
* 获取菜单的paths
*/
export const getMenuPaths = (menus: RouteRecordRaw[]): string[] => {
let menuPaths: string[] = []
menus.forEach(item => {
menuPaths.push(item.path)
if (item.children && item.children.length > 0) {
menuPaths = menuPaths.concat(getMenuPaths(item.children))
}
})
return menuPaths
}
/**
* 后台的菜单处理
*/
const handleMenuRule = (routes: any, pathPrefix = '/', type = ['menu', 'menu_dir']) => {
const menuRule: RouteRecordRaw[] = []
for (const key in routes) {
if (routes[key].extend == 'add_rules_only') {
continue
}
if (!type.includes(routes[key].type)) {
continue
}
if (routes[key].type == 'menu_dir' && routes[key].children && !routes[key].children.length) {
continue
}
if (
['route', 'menu', 'nav_user_menu', 'nav'].includes(routes[key].type) &&
((routes[key].menu_type == 'tab' && !routes[key].component) ||
(['link', 'iframe'].includes(routes[key].menu_type) && !routes[key].url))
) {
continue
}
const currentPath = ['link', 'iframe'].includes(routes[key].menu_type)
? routes[key].url
: pathPrefix + routes[key].path
let children: RouteRecordRaw[] = []
if (routes[key].children && routes[key].children.length > 0) {
children = handleMenuRule(routes[key].children, pathPrefix, type)
}
menuRule.push({
path: currentPath,
name: routes[key].name,
component: routes[key].component,
meta: {
id: routes[key].id,
title: routes[key].title,
icon: routes[key].icon,
keepalive: routes[key].keepalive,
menu_type: routes[key].menu_type,
type: routes[key].type
},
children: children
})
}
return menuRule
}
/**
* 处理权限节点
* @param routes 路由数据
* @param prefix 节点前缀
* @returns 组装好的权限节点
*/
const handleAuthNode = (routes: any, prefix = '/') => {
const authNode: Map<string, string[]> = new Map([])
assembleAuthNode(routes, authNode, prefix, prefix)
return authNode
}
const assembleAuthNode = (routes: any, authNode: Map<string, string[]>, prefix = '/', parent = '/') => {
const authNodeTemp = []
for (const key in routes) {
if (routes[key].type == 'button') authNodeTemp.push(prefix + routes[key].name)
if (routes[key].children && routes[key].children.length > 0) {
assembleAuthNode(routes[key].children, authNode, prefix, prefix + routes[key].name)
}
}
if (authNodeTemp && authNodeTemp.length > 0) {
authNode.set(parent, authNodeTemp)
}
}
/**
* 动态添加路由-带子路由
* @param viewsComponent
* @param routes
* @param parentName
* @param analyticRelation 根据 name 从已注册路由分析父级路由
*/
export const addRouteAll = (
viewsComponent: Record<string, any>,
routes: any,
parentName: string,
analyticRelation = false
) => {
for (const idx in routes) {
if (routes[idx].extend == 'add_menu_only') {
continue
}
if (
(routes[idx].menu_type == 'tab' && viewsComponent[routes[idx].component]) ||
routes[idx].menu_type == 'iframe'
) {
addRouteItem(viewsComponent, routes[idx], parentName, analyticRelation)
}
if (routes[idx].children && routes[idx].children.length > 0) {
addRouteAll(viewsComponent, routes[idx].children, parentName, analyticRelation)
}
}
}
/**
* 动态添加路由
* @param viewsComponent
* @param route
* @param parentName
* @param analyticRelation 根据 name 从已注册路由分析父级路由
*/
export const addRouteItem = (
viewsComponent: Record<string, any>,
route: any,
parentName: string,
analyticRelation: boolean
) => {
let path = '',
component
if (route.menu_type == 'iframe') {
path = (isAdminApp() ? adminBaseRoute.path : '') + '/iframe/' + encodeURIComponent(route.url)
component = () => import('@/layouts/common/router-view/iframe.vue')
} else {
path = parentName ? route.path : '/' + route.path
component = viewsComponent[route.component]
}
if (route.menu_type == 'tab' && analyticRelation) {
const parentNames = getParentNames(route.name)
if (parentNames.length) {
for (const key in parentNames) {
if (router.hasRoute(parentNames[key])) {
parentName = parentNames[key]
break
}
}
}
}
const routeBaseInfo: RouteRecordRaw = {
path: path,
name: route.name,
component: component,
meta: {
...route,
title: route.title,
extend: route.extend,
icon: route.icon,
keepalive: route.keepalive,
menu_type: route.menu_type,
type: route.type,
url: route.url,
addtab: true
}
}
if (parentName) {
router.addRoute(parentName, routeBaseInfo)
} else {
router.addRoute(routeBaseInfo)
}
}
/**
* 根据name字符串获取父级name组合的数组
* @param name
*/
const getParentNames = (name: string) => {
const names = compact(name.split('/'))
const tempNames = []
const parentNames = []
for (const key in names) {
tempNames.push(names[key])
if (parseInt(key) != names.length - 1) {
parentNames.push(tempNames.join('/'))
}
}
return reverse(parentNames)
}