first commit

This commit is contained in:
2024-04-08 21:34:02 +08:00
commit a41a7f32ab
223 changed files with 44629 additions and 0 deletions

13
src/stores/index.ts Normal file
View File

@@ -0,0 +1,13 @@
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
export * from './modules/app'
export * from './modules/route'
export * from './modules/tabs'
export * from './modules/dict'
export * from './modules/user'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
export default pinia

68
src/stores/modules/app.ts Normal file
View File

@@ -0,0 +1,68 @@
import { defineStore } from 'pinia'
import { computed, reactive, toRefs } from 'vue'
import { generate, getRgbStr } from '@arco-design/color'
import defaultSettings from '@/config/setting.json'
const storeSetup = () => {
// App配置
const settingConfig = reactive({ ...defaultSettings }) as App.SettingConfig
// 页面切换动画类名
const transitionName = computed(() => (settingConfig.animate ? settingConfig.animateMode : ''))
// 深色菜单主题色变量
const themeCSSVar = computed<Record<string, string>>(() => {
const obj: Record<string, string> = {}
const list = generate(settingConfig.themeColor, { list: true, dark: true })
list.forEach((color: string, index: number) => {
obj[`--primary-${index + 1}`] = getRgbStr(color)
})
return obj
})
// 切换主题 暗黑模式|简白模式
const toggleTheme = (dark: boolean) => {
if (dark) {
settingConfig.theme = 'dark'
document.body.setAttribute('arco-theme', 'dark')
} else {
settingConfig.theme = 'light'
document.body.removeAttribute('arco-theme')
}
setThemeColor(settingConfig.themeColor)
}
// 设置主题色
const setThemeColor = (color: string) => {
if (!color) return
settingConfig.themeColor = color
const list = generate(settingConfig.themeColor, { list: true, dark: settingConfig.theme === 'dark' })
list.forEach((color: string, index: number) => {
const rgbStr = getRgbStr(color)
document.body.style.setProperty(`--primary-${index + 1}`, rgbStr)
})
}
// 初始化主题
const initTheme = () => {
if (!settingConfig.themeColor) return
setThemeColor(settingConfig.themeColor)
}
// 设置左侧菜单折叠状态
const setMenuCollapse = (collapsed: boolean) => {
settingConfig.menuCollapse = collapsed
}
return {
...toRefs(settingConfig),
transitionName,
themeCSSVar,
toggleTheme,
setThemeColor,
initTheme,
setMenuCollapse
}
}
export const useAppStore = defineStore('app', storeSetup, { persist: true })

View File

@@ -0,0 +1,57 @@
import { defineStore } from 'pinia'
import type { DictState, LabelValueState } from '@/types/global'
const storeSetup = () => {
const dictData = ref<DictState[]>([])
// 设置字典
const setDict = (_code: string, items: Array<LabelValueState>) => {
if (_code !== null && _code !== '') {
dictData.value.push({
code: _code,
items
})
}
}
// 获取字典
const getDict = (_code: string) => {
if (_code == null || _code === '') {
return null
}
for (let i = 0; i < dictData.value.length; i += 1) {
if (dictData.value[i].code === _code) {
return dictData.value[i].items
}
}
return null
}
// 删除字典
const deleteDict = (_code: string) => {
let bln = false
try {
for (let i = 0; i < dictData.value.length; i += 1) {
if (dictData.value[i].code === _code) {
dictData.value.splice(i, 1)
return true
}
}
} catch (e) {
bln = false
}
return bln
}
// 清空字典
const cleanDict = () => {
dictData.value = []
}
return {
setDict,
getDict,
deleteDict,
cleanDict
}
}
export const useDictStore = defineStore('dict', storeSetup, { persist: true })

100
src/stores/modules/route.ts Normal file
View File

@@ -0,0 +1,100 @@
import { ref, toRaw } from 'vue'
import { defineStore } from 'pinia'
import type { RouteRecordRaw } from 'vue-router'
import { constantRoutes } from '@/router'
import Layout from '@/layout/index.vue'
import ParentView from '@/components/ParentView/index.vue'
import { getUserRoute, type RouteItem } from '@/apis'
import { mapTree } from 'xe-utils'
import { transformPathToName } from '@/utils'
// 匹配views里面所有的.vue文件
const modules = import.meta.glob('@/views/**/*.vue')
/** 加载模块 */
export const loadView = (view: string) => {
let res
for (const path in modules) {
const dir = path.split('views/')[1].split('.vue')[0]
if (dir === view) {
res = () => modules[path]()
}
}
return res
}
/** 将component由字符串转成真正的模块 */
const transformComponentView = (component: string) => {
if (component === 'Layout') {
return Layout as never
} else if (component === 'ParentView') {
return ParentView as never
} else {
return loadView(component) as never
}
}
/**
* @description 前端来做排序、格式化
* @params {menus} 后端返回的路由数据,已经根据当前用户角色过滤掉了没权限的路由
* 1. 对后端返回的路由数据进行排序,格式化
* 2. 同时将component由字符串转成真正的模块
*/
const formatAsyncRoutes = (menus: RouteItem[]) => {
if (!menus.length) return []
menus.sort((a, b) => (a?.sort ?? 0) - (b?.sort ?? 0)) // 排序
const routes = mapTree(menus, (item) => {
if (item.children && item.children.length) {
item.children.sort((a, b) => (a?.sort ?? 0) - (b?.sort ?? 0)) // 排序
}
return {
path: item.path,
name: item.name ?? transformPathToName(item.path),
component: transformComponentView(item.component),
redirect: item.redirect,
meta: {
title: item.title,
hidden: item.isHidden,
keepAlive: item.isCache,
alwaysShow: item.type == 1,
svgIcon: item.icon.startsWith('svg-') ? item.icon : '',
icon: item.icon.startsWith('icon-') ? item.icon : ''
}
}
})
return routes as RouteRecordRaw[]
}
const storeSetup = () => {
// 所有路由(常驻路由 + 动态路由)
const routes = ref<RouteRecordRaw[]>([])
// 动态路由(异步路由)
const asyncRoutes = ref<RouteRecordRaw[]>([])
// 合并路由
const setRoutes = (data: RouteRecordRaw[]) => {
routes.value = constantRoutes.concat(data)
asyncRoutes.value = data
console.log('路由', toRaw(routes.value))
}
// 生成路由
const generateRoutes = (): Promise<RouteRecordRaw[]> => {
return new Promise((resolve) => {
// 向后端请求路由数据 这个接口已经根据用户角色过滤了没权限的路由(后端根据用户角色过滤路由显得比较安全些)
getUserRoute().then((res) => {
const asyncRoutes = formatAsyncRoutes(res.data)
setRoutes(asyncRoutes)
resolve(asyncRoutes)
})
})
}
return {
routes,
asyncRoutes,
generateRoutes
}
}
export const useRouteStore = defineStore('route', storeSetup, { persist: true })

115
src/stores/modules/tabs.ts Normal file
View File

@@ -0,0 +1,115 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import router from '@/router'
import type { RouteRecordRaw, RouteRecordName } from 'vue-router'
import { useRouteStore } from '@/stores'
import _XEUtils_ from 'xe-utils'
const storeSetup = () => {
const tagList = ref<RouteRecordRaw[]>([]) // 保存页签tab的数组
const cacheList = ref<RouteRecordName[]>([]) // keep-alive缓存的数组, 元素是组件名
// 添加一个页签, 如果当前路由已经打开, 则不再重复添加
const addTagItem = (item: RouteRecordRaw) => {
if (tagList.value.some((i) => i.path === item.path)) return
if (item.meta?.showInTabs ?? true) {
tagList.value.push(item)
}
}
// 删除一个页签
const deleteTagItem = (path: string) => {
const index = tagList.value.findIndex((item) => item.path === path && !item.meta?.affix)
if (index >= 0) {
const isActive = router.currentRoute.value.path === tagList.value[index]['path']
tagList.value.splice(index, 1)
if (isActive) {
router.push({ path: tagList.value[tagList.value.length - 1]['path'] })
}
}
}
// 清空页签
const clearTagList = () => {
const routeStore = useRouteStore()
const arr: RouteRecordRaw[] = []
_XEUtils_.eachTree(routeStore.routes, (item) => {
if (item.meta?.affix ?? false) {
arr.push(item)
}
})
tagList.value = arr
}
// 添加缓存页
const addCacheItem = (item: RouteRecordRaw) => {
if (item.name) {
if (cacheList.value.includes(item.name)) return
if (item.meta?.keepAlive) {
cacheList.value.push(item.name)
}
}
}
// 删除一个缓存页
const deleteCacheItem = (name: RouteRecordName) => {
const index = cacheList.value.findIndex((item) => item === name)
if (index >= 0) {
cacheList.value.splice(index, 1)
}
}
// 清空缓存页
const clearCacheList = () => {
cacheList.value = []
}
// 关闭当前
const closeCurrent = (path: string) => {
deleteTagItem(path)
const item = tagList.value.find((i) => i.path === path)
if (item?.name) {
deleteCacheItem(item.name)
}
}
// 关闭其他
const closeOther = (path: string) => {
const arr = tagList.value.filter((i) => i.path !== path)
arr.forEach((item) => {
deleteTagItem(item.path)
if (item?.name) {
deleteCacheItem(item.name)
}
})
}
// 关闭全部
const closeAll = () => {
clearTagList()
router.push({ path: '/' })
}
// 重置
const reset = () => {
clearTagList()
clearCacheList()
}
return {
tagList,
cacheList,
addTagItem,
deleteTagItem,
clearTagList,
addCacheItem,
deleteCacheItem,
clearCacheList,
closeCurrent,
closeOther,
closeAll,
reset
}
}
export const useTabsStore = defineStore('tabs', storeSetup, { persist: false })

View File

@@ -0,0 +1,83 @@
import { defineStore } from 'pinia'
import { ref, reactive, computed } from 'vue'
import { resetRouter } from '@/router'
import { accountLogin as accountLoginApi, logout as logoutApi, getUserInfo as getUserInfoApi } from '@/apis'
import type { UserInfo } from '@/apis'
import { setToken, clearToken, getToken } from '@/utils/auth'
import { resetHasRouteFlag } from '@/router/permission'
import getAvatar from '@/utils/avatar'
const storeSetup = () => {
const userInfo = reactive<Pick<UserInfo, 'nickname' | 'avatar'>>({
nickname: '',
avatar: ''
})
const name = computed(() => userInfo.nickname)
const avatar = computed(() => userInfo.avatar)
const token = ref(getToken() || '')
const roles = ref<string[]>([]) // 当前用户角色
const permissions = ref<string[]>([]) // 当前角色权限标识集合
// 重置token
const resetToken = () => {
token.value = ''
clearToken()
resetHasRouteFlag()
}
// 登录
const accountLogin = async (params: any) => {
const res = await accountLoginApi(params)
setToken(res.data.token)
token.value = res.data.token
}
// 退出登录
const logout = async () => {
try {
await logoutApi()
await logoutCallBack()
return true
} catch (error) {
return false
}
}
// 退出登录回调
const logoutCallBack = async () => {
roles.value = []
permissions.value = []
resetToken()
resetRouter()
}
// 获取用户信息
const getInfo = async () => {
const res = await getUserInfoApi()
userInfo.nickname = res.data.nickname
userInfo.avatar = getAvatar(res.data.avatar, res.data.gender)
if (res.data.roles && res.data.roles.length) {
roles.value = res.data.roles
permissions.value = res.data.permissions
}
}
return {
userInfo,
name,
avatar,
token,
roles,
permissions,
accountLogin,
logout,
logoutCallBack,
getInfo,
resetToken
}
}
export const useUserStore = defineStore('user', storeSetup, {
persist: { paths: ['token', 'roles', 'permissions'], storage: localStorage }
})