first commit

This commit is contained in:
仲么了
2023-12-21 16:42:39 +08:00
commit 0f7b59f55b
79 changed files with 7638 additions and 0 deletions

65
src/utils/common.ts Normal file
View File

@@ -0,0 +1,65 @@
import type { App } from 'vue'
import { adminBaseRoutePath } from '@/router/static'
import router from '@/router/index'
import { trimStart } from 'lodash-es'
import * as elIcons from '@element-plus/icons-vue'
import Icon from '@/components/icon/index.vue'
export function registerIcons(app: App) {
/*
* 全局注册 Icon
* 使用方式: <Icon name="name" size="size" color="color" />
* 详见<待完善>
*/
app.component('Icon', Icon)
/*
* 全局注册element Plus的icon
*/
const icons = elIcons as any
for (const i in icons) {
app.component(`el-icon-${icons[i].name}`, icons[i])
}
}
/**
* 是否在后台应用内
* @param path 不传递则通过当前路由 path 检查
*/
export const isAdminApp = (path = '') => {
const regex = new RegExp(`^${adminBaseRoutePath}`)
if (path) {
return regex.test(path)
}
if (regex.test(getCurrentRoutePath())) {
return true
}
return false
}
/**
* 获取路由 path
*/
export const getCurrentRoutePath = () => {
let path = router.currentRoute.value.path
if (path == '/') path = trimStart(window.location.hash, '#')
if (path.indexOf('?') !== -1) path = path.replace(/\?.*/, '')
return path
}
/**
* 获取资源完整地址
* @param relativeUrl 资源相对地址
* @param domain 指定域名
*/
export const fullUrl = (relativeUrl: string, domain = '') => {
return domain + relativeUrl
}
/**
* 是否是外部链接
* @param path
*/
export function isExternal(path: string): boolean {
return /^(https?|ftp|mailto|tel):/.test(path)
}

View File

@@ -0,0 +1,33 @@
/**
* 横向滚动条
*/
export default class horizontalScroll {
private el: HTMLElement
constructor(nativeElement: HTMLElement) {
this.el = nativeElement
this.handleWheelEvent()
}
handleWheelEvent() {
let wheel = ''
if ('onmousewheel' in this.el) {
wheel = 'mousewheel'
} else if ('onwheel' in this.el) {
wheel = 'wheel'
} else if ('attachEvent' in window) {
wheel = 'onmousewheel'
} else {
wheel = 'DOMMouseScroll'
}
this.el['addEventListener'](wheel, this.scroll, { passive: true })
}
scroll = (event: any) => {
if (this.el.clientWidth >= this.el.scrollWidth) {
return
}
this.el.scrollLeft += event.deltaY ? event.deltaY : event.detail && event.detail !== 0 ? event.detail : -event.wheelDelta
}
}

21
src/utils/iconfont.ts Normal file
View File

@@ -0,0 +1,21 @@
import { nextTick } from 'vue'
import * as elIcons from '@element-plus/icons-vue'
/*
* 获取element plus 自带的图标
*/
export function getElementPlusIconfontNames() {
return new Promise<string[]>((resolve, reject) => {
nextTick(() => {
const iconfonts = []
const icons = elIcons as any
for (const i in icons) {
iconfonts.push(`el-icon-${icons[i].name}`)
}
if (iconfonts.length > 0) {
resolve(iconfonts)
} else {
reject('No ElementPlus Icons')
}
})
})
}

41
src/utils/layout.ts Normal file
View File

@@ -0,0 +1,41 @@
import type { CSSProperties } from 'vue'
import { useNavTabs } from '@/stores/navTabs'
import { useConfig } from '@/stores/config'
/**
* main高度
* @param extra main高度额外减去的px数,可以实现隐藏原有的滚动条
* @returns CSSProperties
*/
export function mainHeight(extra = 0): CSSProperties {
let height = extra
const adminLayoutMainExtraHeight: anyObj = {
Default: 70,
Classic: 50,
Streamline: 60
}
const config = useConfig()
const navTabs = useNavTabs()
if (!navTabs.state.tabFullScreen) {
height += adminLayoutMainExtraHeight[config.layout.layoutMode]
}
return {
height: 'calc(100vh - ' + height.toString() + 'px)'
}
}
/**
* 设置导航栏宽度
* @returns
*/
export function setNavTabsWidth() {
const navTabs = document.querySelector('.nav-tabs') as HTMLElement
if (!navTabs) {
return
}
const navBar = document.querySelector('.nav-bar') as HTMLElement
const navMenus = document.querySelector('.nav-menus') as HTMLElement
const minWidth = navBar.offsetWidth - (navMenus.offsetWidth + 20)
navTabs.style.width = minWidth.toString() + 'px'
}

22
src/utils/pageShade.ts Normal file
View File

@@ -0,0 +1,22 @@
import { useEventListener } from '@vueuse/core'
/*
* 显示页面遮罩
*/
export const showShade = function (className = 'shade', closeCallBack: Function): void {
const containerEl = document.querySelector('.layout-container') as HTMLElement
const shadeDiv = document.createElement('div')
shadeDiv.setAttribute('class', 'ba-layout-shade ' + className)
containerEl.appendChild(shadeDiv)
useEventListener(shadeDiv, 'click', () => closeShade(closeCallBack))
}
/*
* 隐藏页面遮罩
*/
export const closeShade = function (closeCallBack: Function = () => {}): void {
const shadeEl = document.querySelector('.ba-layout-shade') as HTMLElement
shadeEl && shadeEl.remove()
closeCallBack()
}

57
src/utils/random.ts Normal file
View File

@@ -0,0 +1,57 @@
const hexList: string[] = []
for (let i = 0; i <= 15; i++) {
hexList[i] = i.toString(16)
}
/**
* 生成随机数
* @param min 最小值
* @param max 最大值
* @returns 生成的随机数
*/
export function randomNum(min: number, max: number) {
switch (arguments.length) {
case 1:
return parseInt((Math.random() * min + 1).toString(), 10)
break
case 2:
return parseInt((Math.random() * (max - min + 1) + min).toString(), 10)
break
default:
return 0
break
}
}
/**
* 生成全球唯一标识
* @returns uuid
*/
export function uuid(): string {
let uuid = ''
for (let i = 1; i <= 36; i++) {
if (i === 9 || i === 14 || i === 19 || i === 24) {
uuid += '-'
} else if (i === 15) {
uuid += 4
} else if (i === 20) {
uuid += hexList[(Math.random() * 4) | 8]
} else {
uuid += hexList[(Math.random() * 16) | 0]
}
}
return uuid
}
/**
* 生成唯一标识
* @param prefix 前缀
* @returns 唯一标识
*/
export function shortUuid(prefix = ''): string {
const time = Date.now()
const random = Math.floor(Math.random() * 1000000000)
if (!window.unique) window.unique = 0
window.unique++
return prefix + '_' + random + window.unique + String(time)
}

295
src/utils/router.ts Normal file
View File

@@ -0,0 +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: 'utils.Navigation failed, it is at the navigation target position!',
type: 'warning'
})
}
} catch (error) {
ElNotification({
message: 'utils.Navigation failed, invalid route!',
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: {
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)
}

45
src/utils/storage.ts Normal file
View File

@@ -0,0 +1,45 @@
/**
* window.localStorage
* @method set 设置
* @method get 获取
* @method remove 移除
* @method clear 移除全部
*/
export const Local = {
set(key: string, val: any) {
window.localStorage.setItem(key, JSON.stringify(val))
},
get(key: string) {
const json: any = window.localStorage.getItem(key)
return JSON.parse(json)
},
remove(key: string) {
window.localStorage.removeItem(key)
},
clear() {
window.localStorage.clear()
},
}
/**
* window.sessionStorage
* @method set 设置会话缓存
* @method get 获取会话缓存
* @method remove 移除会话缓存
* @method clear 移除全部会话缓存
*/
export const Session = {
set(key: string, val: any) {
window.sessionStorage.setItem(key, JSON.stringify(val))
},
get(key: string) {
const json: any = window.sessionStorage.getItem(key)
return JSON.parse(json)
},
remove(key: string) {
window.sessionStorage.removeItem(key)
},
clear() {
window.sessionStorage.clear()
},
}

View File

@@ -0,0 +1,13 @@
import { getCurrentInstance } from 'vue'
import type { ComponentInternalInstance } from 'vue'
export default function useCurrentInstance() {
if (!getCurrentInstance()) {
throw new Error('useCurrentInstance() can only be used inside setup() or functional components!')
}
const { appContext } = getCurrentInstance() as ComponentInternalInstance
const proxy = appContext.config.globalProperties
return {
proxy,
}
}