初始化项目,自 v1.7.1 版本开始

This commit is contained in:
YunaiV
2023-02-11 00:44:00 +08:00
parent 11161afc1a
commit 56f3017baa
548 changed files with 52096 additions and 61 deletions

10
src/store/index.ts Normal file
View File

@ -0,0 +1,10 @@
import type { App } from 'vue'
import { createPinia } from 'pinia'
const store = createPinia()
export const setupStore = (app: App<Element>) => {
app.use(store)
}
export { store }

274
src/store/modules/app.ts Normal file
View File

@ -0,0 +1,274 @@
import { defineStore } from 'pinia'
import { store } from '../index'
import { setCssVar, humpToUnderline } from '@/utils'
import { ElMessage } from 'element-plus'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
import { ElementPlusSize } from '@/types/elementPlus'
import { LayoutType } from '@/types/layout'
import { ThemeTypes } from '@/types/theme'
const { wsCache } = useCache()
interface AppState {
breadcrumb: boolean
breadcrumbIcon: boolean
collapse: boolean
uniqueOpened: boolean
hamburger: boolean
screenfull: boolean
size: boolean
locale: boolean
message: boolean
tagsView: boolean
tagsViewIcon: boolean
logo: boolean
fixedHeader: boolean
greyMode: boolean
pageLoading: boolean
layout: LayoutType
title: string
userInfo: string
isDark: boolean
currentSize: ElementPlusSize
sizeMap: ElementPlusSize[]
mobile: boolean
footer: boolean
theme: ThemeTypes
fixedMenu: boolean
}
export const useAppStore = defineStore('app', {
state: (): AppState => {
return {
userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段,避免与其他项目冲突
sizeMap: ['default', 'large', 'small'],
mobile: false, // 是否是移动端
title: import.meta.env.VITE_APP_TITLE, // 标题
pageLoading: false, // 路由跳转loading
breadcrumb: true, // 面包屑
breadcrumbIcon: true, // 面包屑图标
collapse: false, // 折叠菜单
uniqueOpened: true, // 是否只保持一个子菜单的展开
hamburger: true, // 折叠图标
screenfull: true, // 全屏图标
size: true, // 尺寸图标
locale: true, // 多语言图标
message: true, // 消息图标
tagsView: true, // 标签页
tagsViewIcon: true, // 是否显示标签图标
logo: true, // logo
fixedHeader: true, // 固定toolheader
footer: true, // 显示页脚
greyMode: false, // 是否开始灰色模式,用于特殊悼念日
fixedMenu: wsCache.get('fixedMenu') || false, // 是否固定菜单
layout: wsCache.get(CACHE_KEY.LAYOUT) || 'classic', // layout布局
isDark: wsCache.get(CACHE_KEY.IS_DARK) || false, // 是否是暗黑模式
currentSize: wsCache.get('default') || 'default', // 组件尺寸
theme: wsCache.get(CACHE_KEY.THEME) || {
// 主题色
elColorPrimary: '#409eff',
// 左侧菜单边框颜色
leftMenuBorderColor: 'inherit',
// 左侧菜单背景颜色
leftMenuBgColor: '#001529',
// 左侧菜单浅色背景颜色
leftMenuBgLightColor: '#0f2438',
// 左侧菜单选中背景颜色
leftMenuBgActiveColor: 'var(--el-color-primary)',
// 左侧菜单收起选中背景颜色
leftMenuCollapseBgActiveColor: 'var(--el-color-primary)',
// 左侧菜单字体颜色
leftMenuTextColor: '#bfcbd9',
// 左侧菜单选中字体颜色
leftMenuTextActiveColor: '#fff',
// logo字体颜色
logoTitleTextColor: '#fff',
// logo边框颜色
logoBorderColor: 'inherit',
// 头部背景颜色
topHeaderBgColor: '#fff',
// 头部字体颜色
topHeaderTextColor: 'inherit',
// 头部悬停颜色
topHeaderHoverColor: '#f6f6f6',
// 头部边框颜色
topToolBorderColor: '#eee'
}
}
},
getters: {
getBreadcrumb(): boolean {
return this.breadcrumb
},
getBreadcrumbIcon(): boolean {
return this.breadcrumbIcon
},
getCollapse(): boolean {
return this.collapse
},
getUniqueOpened(): boolean {
return this.uniqueOpened
},
getHamburger(): boolean {
return this.hamburger
},
getScreenfull(): boolean {
return this.screenfull
},
getSize(): boolean {
return this.size
},
getLocale(): boolean {
return this.locale
},
getMessage(): boolean {
return this.message
},
getTagsView(): boolean {
return this.tagsView
},
getTagsViewIcon(): boolean {
return this.tagsViewIcon
},
getLogo(): boolean {
return this.logo
},
getFixedHeader(): boolean {
return this.fixedHeader
},
getGreyMode(): boolean {
return this.greyMode
},
getFixedMenu(): boolean {
return this.fixedMenu
},
getPageLoading(): boolean {
return this.pageLoading
},
getLayout(): LayoutType {
return this.layout
},
getTitle(): string {
return this.title
},
getUserInfo(): string {
return this.userInfo
},
getIsDark(): boolean {
return this.isDark
},
getCurrentSize(): ElementPlusSize {
return this.currentSize
},
getSizeMap(): ElementPlusSize[] {
return this.sizeMap
},
getMobile(): boolean {
return this.mobile
},
getTheme(): ThemeTypes {
return this.theme
},
getFooter(): boolean {
return this.footer
}
},
actions: {
setBreadcrumb(breadcrumb: boolean) {
this.breadcrumb = breadcrumb
},
setBreadcrumbIcon(breadcrumbIcon: boolean) {
this.breadcrumbIcon = breadcrumbIcon
},
setCollapse(collapse: boolean) {
this.collapse = collapse
},
setUniqueOpened(uniqueOpened: boolean) {
this.uniqueOpened = uniqueOpened
},
setHamburger(hamburger: boolean) {
this.hamburger = hamburger
},
setScreenfull(screenfull: boolean) {
this.screenfull = screenfull
},
setSize(size: boolean) {
this.size = size
},
setLocale(locale: boolean) {
this.locale = locale
},
setMessage(message: boolean) {
this.message = message
},
setTagsView(tagsView: boolean) {
this.tagsView = tagsView
},
setTagsViewIcon(tagsViewIcon: boolean) {
this.tagsViewIcon = tagsViewIcon
},
setLogo(logo: boolean) {
this.logo = logo
},
setFixedHeader(fixedHeader: boolean) {
this.fixedHeader = fixedHeader
},
setGreyMode(greyMode: boolean) {
this.greyMode = greyMode
},
setFixedMenu(fixedMenu: boolean) {
wsCache.set('fixedMenu', fixedMenu)
this.fixedMenu = fixedMenu
},
setPageLoading(pageLoading: boolean) {
this.pageLoading = pageLoading
},
setLayout(layout: LayoutType) {
if (this.mobile && layout !== 'classic') {
ElMessage.warning('移动端模式下不支持切换其他布局')
return
}
this.layout = layout
wsCache.set(CACHE_KEY.LAYOUT, this.layout)
},
setTitle(title: string) {
this.title = title
},
setIsDark(isDark: boolean) {
this.isDark = isDark
if (this.isDark) {
document.documentElement.classList.add('dark')
document.documentElement.classList.remove('light')
} else {
document.documentElement.classList.add('light')
document.documentElement.classList.remove('dark')
}
wsCache.set(CACHE_KEY.IS_DARK, this.isDark)
},
setCurrentSize(currentSize: ElementPlusSize) {
this.currentSize = currentSize
wsCache.set('currentSize', this.currentSize)
},
setMobile(mobile: boolean) {
this.mobile = mobile
},
setTheme(theme: ThemeTypes) {
this.theme = Object.assign(this.theme, theme)
wsCache.set(CACHE_KEY.THEME, this.theme)
},
setCssVarTheme() {
for (const key in this.theme) {
setCssVar(`--${humpToUnderline(key)}`, this.theme[key])
}
},
setFooter(footer: boolean) {
this.footer = footer
}
}
})
export const useAppStoreWithOut = () => {
return useAppStore(store)
}

103
src/store/modules/dict.ts Normal file
View File

@ -0,0 +1,103 @@
import { defineStore } from 'pinia'
import { store } from '../index'
import { DictDataVO } from '@/api/system/dict/types'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache('sessionStorage')
import { listSimpleDictDataApi } from '@/api/system/dict/dict.data'
export interface DictValueType {
value: any
label: string
clorType?: string
cssClass?: string
}
export interface DictTypeType {
dictType: string
dictValue: DictValueType[]
}
export interface DictState {
dictMap: Map<string, any>
isSetDict: boolean
}
export const useDictStore = defineStore('dict', {
state: (): DictState => ({
dictMap: new Map<string, any>(),
isSetDict: false
}),
getters: {
getDictMap(): Recordable {
const dictMap = wsCache.get(CACHE_KEY.DICT_CACHE)
if (dictMap) {
this.dictMap = dictMap
}
return this.dictMap
},
getIsSetDict(): boolean {
return this.isSetDict
}
},
actions: {
async setDictMap() {
const dictMap = wsCache.get(CACHE_KEY.DICT_CACHE)
if (dictMap) {
this.dictMap = dictMap
this.isSetDict = true
} else {
const res = await listSimpleDictDataApi()
// 设置数据
const dictDataMap = new Map<string, any>()
res.forEach((dictData: DictDataVO) => {
// 获得 dictType 层级
const enumValueObj = dictDataMap[dictData.dictType]
if (!enumValueObj) {
dictDataMap[dictData.dictType] = []
}
// 处理 dictValue 层级
dictDataMap[dictData.dictType].push({
value: dictData.value,
label: dictData.label,
colorType: dictData.colorType,
cssClass: dictData.cssClass
})
})
this.dictMap = dictDataMap
this.isSetDict = true
wsCache.set(CACHE_KEY.DICT_CACHE, dictDataMap, { exp: 60 }) // 60 秒 过期
}
},
getDictByType(type: string) {
if (!this.isSetDict) {
this.setDictMap()
}
return this.dictMap[type]
},
async resetDict() {
wsCache.delete(CACHE_KEY.DICT_CACHE)
const res = await listSimpleDictDataApi()
// 设置数据
const dictDataMap = new Map<string, any>()
res.forEach((dictData: DictDataVO) => {
// 获得 dictType 层级
const enumValueObj = dictDataMap[dictData.dictType]
if (!enumValueObj) {
dictDataMap[dictData.dictType] = []
}
// 处理 dictValue 层级
dictDataMap[dictData.dictType].push({
value: dictData.value,
label: dictData.label,
colorType: dictData.colorType,
cssClass: dictData.cssClass
})
})
this.dictMap = dictDataMap
this.isSetDict = true
wsCache.set(CACHE_KEY.DICT_CACHE, dictDataMap, { exp: 60 }) // 60 秒 过期
}
}
})
export const useDictStoreWithOut = () => {
return useDictStore(store)
}

View File

@ -0,0 +1,59 @@
import { defineStore } from 'pinia'
import { store } from '../index'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import en from 'element-plus/es/locale/lang/en'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
import { LocaleDropdownType } from '@/types/localeDropdown'
const { wsCache } = useCache()
const elLocaleMap = {
'zh-CN': zhCn,
en: en
}
interface LocaleState {
currentLocale: LocaleDropdownType
localeMap: LocaleDropdownType[]
}
export const useLocaleStore = defineStore('locales', {
state: (): LocaleState => {
return {
currentLocale: {
lang: wsCache.get(CACHE_KEY.LANG) || 'zh-CN',
elLocale: elLocaleMap[wsCache.get(CACHE_KEY.LANG) || 'zh-CN']
},
// 多语言
localeMap: [
{
lang: 'zh-CN',
name: '简体中文'
},
{
lang: 'en',
name: 'English'
}
]
}
},
getters: {
getCurrentLocale(): LocaleDropdownType {
return this.currentLocale
},
getLocaleMap(): LocaleDropdownType[] {
return this.localeMap
}
},
actions: {
setCurrentLocale(localeMap: LocaleDropdownType) {
// this.locale = Object.assign(this.locale, localeMap)
this.currentLocale.lang = localeMap?.lang
this.currentLocale.elLocale = elLocaleMap[localeMap?.lang]
wsCache.set(CACHE_KEY.LANG, localeMap?.lang)
}
}
})
export const useLocaleStoreWithOut = () => {
return useLocaleStore(store)
}

View File

@ -0,0 +1,70 @@
import { defineStore } from 'pinia'
import { store } from '../index'
import { cloneDeep } from 'lodash-es'
import remainingRouter from '@/router/modules/remaining'
import { generateRoute, flatMultiLevelRoutes } from '@/utils/routerHelper'
import { getAsyncRoutesApi } from '@/api/login'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache()
export interface PermissionState {
routers: AppRouteRecordRaw[]
addRouters: AppRouteRecordRaw[]
menuTabRouters: AppRouteRecordRaw[]
}
export const usePermissionStore = defineStore('permission', {
state: (): PermissionState => ({
routers: [],
addRouters: [],
menuTabRouters: []
}),
getters: {
getRouters(): AppRouteRecordRaw[] {
return this.routers
},
getAddRouters(): AppRouteRecordRaw[] {
return flatMultiLevelRoutes(cloneDeep(this.addRouters))
},
getMenuTabRouters(): AppRouteRecordRaw[] {
return this.menuTabRouters
}
},
actions: {
async generateRoutes(): Promise<unknown> {
return new Promise<void>(async (resolve) => {
let res: AppCustomRouteRecordRaw[]
if (wsCache.get(CACHE_KEY.ROLE_ROUTERS)) {
res = wsCache.get(CACHE_KEY.ROLE_ROUTERS) as AppCustomRouteRecordRaw[]
} else {
res = await getAsyncRoutesApi()
wsCache.set(CACHE_KEY.ROLE_ROUTERS, res)
}
const routerMap: AppRouteRecordRaw[] = generateRoute(res as AppCustomRouteRecordRaw[])
// 动态路由404一定要放到最后面
this.addRouters = routerMap.concat([
{
path: '/:path(.*)*',
redirect: '/404',
name: '404Page',
meta: {
hidden: true,
breadcrumb: false
}
}
])
// 渲染菜单的所有路由
this.routers = cloneDeep(remainingRouter).concat(routerMap)
resolve()
})
},
setMenuTabRouters(routers: AppRouteRecordRaw[]): void {
this.menuTabRouters = routers
}
}
})
export const usePermissionStoreWithOut = () => {
return usePermissionStore(store)
}

View File

@ -0,0 +1,140 @@
import router from '@/router'
import type { RouteLocationNormalizedLoaded } from 'vue-router'
import { getRawRoute } from '@/utils/routerHelper'
import { defineStore } from 'pinia'
import { store } from '../index'
import { findIndex } from '@/utils'
export interface TagsViewState {
visitedViews: RouteLocationNormalizedLoaded[]
cachedViews: Set<string>
}
export const useTagsViewStore = defineStore('tagsView', {
state: (): TagsViewState => ({
visitedViews: [],
cachedViews: new Set()
}),
getters: {
getVisitedViews(): RouteLocationNormalizedLoaded[] {
return this.visitedViews
},
getCachedViews(): string[] {
return Array.from(this.cachedViews)
}
},
actions: {
// 新增缓存和tag
addView(view: RouteLocationNormalizedLoaded): void {
this.addVisitedView(view)
this.addCachedView()
},
// 新增tag
addVisitedView(view: RouteLocationNormalizedLoaded) {
if (this.visitedViews.some((v) => v.path === view.path)) return
if (view.meta?.noTagsView) return
this.visitedViews.push(
Object.assign({}, view, {
title: view.meta?.title || 'no-name'
})
)
},
// 新增缓存
addCachedView() {
const cacheMap: Set<string> = new Set()
for (const v of this.visitedViews) {
const item = getRawRoute(v)
const needCache = !item.meta?.noCache
if (!needCache) {
continue
}
const name = item.name as string
cacheMap.add(name)
}
if (Array.from(this.cachedViews).sort().toString() === Array.from(cacheMap).sort().toString())
return
this.cachedViews = cacheMap
},
// 删除某个
delView(view: RouteLocationNormalizedLoaded) {
this.delVisitedView(view)
this.addCachedView()
},
// 删除tag
delVisitedView(view: RouteLocationNormalizedLoaded) {
for (const [i, v] of this.visitedViews.entries()) {
if (v.path === view.path) {
this.visitedViews.splice(i, 1)
break
}
}
},
// 删除缓存
delCachedView() {
const route = router.currentRoute.value
const index = findIndex<string>(this.getCachedViews, (v) => v === route.name)
if (index > -1) {
this.cachedViews.delete(this.getCachedViews[index])
}
},
// 删除所有缓存和tag
delAllViews() {
this.delAllVisitedViews()
this.addCachedView()
},
// 删除所有tag
delAllVisitedViews() {
// const affixTags = this.visitedViews.filter((tag) => tag.meta.affix)
this.visitedViews = []
},
// 删除其他
delOthersViews(view: RouteLocationNormalizedLoaded) {
this.delOthersVisitedViews(view)
this.addCachedView()
},
// 删除其他tag
delOthersVisitedViews(view: RouteLocationNormalizedLoaded) {
this.visitedViews = this.visitedViews.filter((v) => {
return v?.meta?.affix || v.path === view.path
})
},
// 删除左侧
delLeftViews(view: RouteLocationNormalizedLoaded) {
const index = findIndex<RouteLocationNormalizedLoaded>(
this.visitedViews,
(v) => v.path === view.path
)
if (index > -1) {
this.visitedViews = this.visitedViews.filter((v, i) => {
return v?.meta?.affix || v.path === view.path || i > index
})
this.addCachedView()
}
},
// 删除右侧
delRightViews(view: RouteLocationNormalizedLoaded) {
const index = findIndex<RouteLocationNormalizedLoaded>(
this.visitedViews,
(v) => v.path === view.path
)
if (index > -1) {
this.visitedViews = this.visitedViews.filter((v, i) => {
return v?.meta?.affix || v.path === view.path || i < index
})
this.addCachedView()
}
},
updateVisitedView(view: RouteLocationNormalizedLoaded) {
for (let v of this.visitedViews) {
if (v.path === view.path) {
v = Object.assign(v, view)
break
}
}
}
}
})
export const useTagsViewStoreWithOut = () => {
return useTagsViewStore(store)
}

83
src/store/modules/user.ts Normal file
View File

@ -0,0 +1,83 @@
import { store } from '../index'
import { defineStore } from 'pinia'
import { getAccessToken, removeToken } from '@/utils/auth'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
import { getInfoApi, loginOutApi } from '@/api/login'
const { wsCache } = useCache()
interface UserVO {
id: number
avatar: string
nickname: string
}
interface UserInfoVO {
permissions: string[]
roles: string[]
isSetUser: boolean
user: UserVO
}
export const useUserStore = defineStore('admin-user', {
state: (): UserInfoVO => ({
permissions: [],
roles: [],
isSetUser: false,
user: {
id: 0,
avatar: '',
nickname: ''
}
}),
getters: {
getPermissions(): string[] {
return this.permissions
},
getRoles(): string[] {
return this.roles
},
getIsSetUser(): boolean {
return this.isSetUser
},
getUser(): UserVO {
return this.user
}
},
actions: {
async setUserInfoAction() {
if (!getAccessToken()) {
this.resetState()
return null
}
let userInfo = wsCache.get(CACHE_KEY.USER)
if (!userInfo) {
userInfo = await getInfoApi()
}
this.permissions = userInfo.permissions
this.roles = userInfo.roles
this.user = userInfo.user
this.isSetUser = true
wsCache.set(CACHE_KEY.USER, userInfo)
},
async loginOut() {
await loginOutApi()
removeToken()
wsCache.clear()
this.resetState()
},
resetState() {
this.permissions = []
this.roles = []
this.isSetUser = false
this.user = {
id: 0,
avatar: '',
nickname: ''
}
}
}
})
export const useUserStoreWithOut = () => {
return useUserStore(store)
}