Initial commit

This commit is contained in:
2026-04-23 16:58:11 +08:00
commit 267eba1eca
2582 changed files with 273338 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
import type { App } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const store = createPinia()
store.use(piniaPluginPersistedstate)
export const setupStore = (app: App<Element>) => {
app.use(store)
}
export { store }
+340
View File
@@ -0,0 +1,340 @@
import { defineStore } from 'pinia'
import { store } from '../index'
import { setCssVar, humpToUnderline } from '@/utils'
import { colorIsDark, hexToRGB, lighten, mix } from '@/utils/color'
import { ElMessage, ComponentSize } from 'element-plus'
import { useCssVar } from '@vueuse/core'
import { unref } from 'vue'
import { useDark } from '@vueuse/core'
interface AppState {
breadcrumb: boolean
breadcrumbIcon: boolean
collapse: boolean
uniqueOpened: boolean
hamburger: boolean
screenfull: boolean
size: boolean
locale: boolean
tagsView: boolean
tagsViewIcon: boolean
logo: boolean
fixedHeader: boolean
greyMode: boolean
dynamicRouter: boolean
serverDynamicRouter: boolean
pageLoading: boolean
layout: LayoutType
title: string
isDark: boolean
currentSize: ComponentSize
sizeMap: ComponentSize[]
mobile: boolean
footer: boolean
theme: ThemeTypes
fixedMenu: boolean
}
export const useAppStore = defineStore('app', {
state: (): AppState => {
return {
sizeMap: ['default', 'large', 'small'],
mobile: false, // 是否是移动端
title: import.meta.env.VITE_APP_TITLE, // 标题
pageLoading: false, // 路由跳转loading
breadcrumb: true, // 面包屑
breadcrumbIcon: true, // 面包屑图标
collapse: false, // 折叠菜单
uniqueOpened: false, // 是否只保持一个子菜单的展开
hamburger: true, // 折叠图标
screenfull: true, // 全屏图标
size: true, // 尺寸图标
locale: true, // 多语言图标
tagsView: true, // 标签页
tagsViewIcon: true, // 是否显示标签图标
logo: true, // logo
fixedHeader: true, // 固定toolheader
footer: true, // 显示页脚
greyMode: false, // 是否开始灰色模式,用于特殊悼念日
dynamicRouter: true, // 是否动态路由
serverDynamicRouter: false, // 是否服务端渲染动态路由
fixedMenu: false, // 是否固定菜单
layout: 'classic', // layout布局
isDark: false, // 是否是暗黑模式
currentSize: 'default', // 组件尺寸
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
},
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
},
getDynamicRouter(): boolean {
return this.dynamicRouter
},
getServerDynamicRouter(): boolean {
return this.serverDynamicRouter
},
getFixedMenu(): boolean {
return this.fixedMenu
},
getPageLoading(): boolean {
return this.pageLoading
},
getLayout(): LayoutType {
return this.layout
},
getTitle(): string {
return this.title
},
getIsDark(): boolean {
return this.isDark
},
getCurrentSize(): ComponentSize {
return this.currentSize
},
getSizeMap(): ComponentSize[] {
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
},
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
},
setDynamicRouter(dynamicRouter: boolean) {
this.dynamicRouter = dynamicRouter
},
setServerDynamicRouter(serverDynamicRouter: boolean) {
this.serverDynamicRouter = serverDynamicRouter
},
setFixedMenu(fixedMenu: boolean) {
this.fixedMenu = fixedMenu
},
setPageLoading(pageLoading: boolean) {
this.pageLoading = pageLoading
},
setLayout(layout: LayoutType) {
if (this.mobile && layout !== 'classic') {
ElMessage.warning('移动端模式下不支持切换其它布局')
return
}
this.layout = 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')
}
this.setPrimaryLight()
},
setCurrentSize(currentSize: ComponentSize) {
this.currentSize = currentSize
},
setMobile(mobile: boolean) {
this.mobile = mobile
},
setTheme(theme: ThemeTypes) {
this.theme = Object.assign(this.theme, theme)
},
setCssVarTheme() {
for (const key in this.theme) {
setCssVar(`--${humpToUnderline(key)}`, this.theme[key])
}
this.setPrimaryLight()
},
setFooter(footer: boolean) {
this.footer = footer
},
setPrimaryLight() {
if (this.theme.elColorPrimary) {
const elColorPrimary = this.theme.elColorPrimary
const color = this.isDark ? '#000000' : '#ffffff'
const lightList = [3, 5, 7, 8, 9]
lightList.forEach((v) => {
setCssVar(`--el-color-primary-light-${v}`, mix(color, elColorPrimary, v / 10))
})
setCssVar(`--el-color-primary-dark-2`, mix(color, elColorPrimary, 0.2))
}
},
setMenuTheme(color: string) {
const primaryColor = useCssVar('--el-color-primary', document.documentElement)
const isDarkColor = colorIsDark(color)
const theme: Recordable = {
// 左侧菜单边框颜色
leftMenuBorderColor: isDarkColor ? 'inherit' : '#eee',
// 左侧菜单背景颜色
leftMenuBgColor: color,
// 左侧菜单浅色背景颜色
leftMenuBgLightColor: isDarkColor ? lighten(color!, 6) : color,
// 左侧菜单选中背景颜色
leftMenuBgActiveColor: isDarkColor
? 'var(--el-color-primary)'
: hexToRGB(unref(primaryColor) as string, 0.1),
// 左侧菜单收起选中背景颜色
leftMenuCollapseBgActiveColor: isDarkColor
? 'var(--el-color-primary)'
: hexToRGB(unref(primaryColor) as string, 0.1),
// 左侧菜单字体颜色
leftMenuTextColor: isDarkColor ? '#bfcbd9' : '#333',
// 左侧菜单选中字体颜色
leftMenuTextActiveColor: isDarkColor ? '#fff' : 'var(--el-color-primary)',
// logo字体颜色
logoTitleTextColor: isDarkColor ? '#fff' : 'inherit',
// logo边框颜色
logoBorderColor: isDarkColor ? color : '#eee'
}
this.setTheme(theme)
this.setCssVarTheme()
},
setHeaderTheme(color: string) {
const isDarkColor = colorIsDark(color)
const textColor = isDarkColor ? '#fff' : 'inherit'
const textHoverColor = isDarkColor ? lighten(color!, 6) : '#f6f6f6'
const topToolBorderColor = isDarkColor ? color : '#eee'
setCssVar('--top-header-bg-color', color)
setCssVar('--top-header-text-color', textColor)
setCssVar('--top-header-hover-color', textHoverColor)
this.setTheme({
topHeaderBgColor: color,
topHeaderTextColor: textColor,
topHeaderHoverColor: textHoverColor,
topToolBorderColor
})
if (this.getLayout === 'top') {
this.setMenuTheme(color)
}
},
initTheme() {
const isDark = useDark({
valueDark: 'dark',
valueLight: 'light'
})
isDark.value = this.getIsDark
const newTitle = import.meta.env.VITE_APP_TITLE
newTitle !== this.getTitle && this.setTitle(newTitle)
}
},
persist: true
})
export const useAppStoreWithOut = () => {
return useAppStore(store)
}
+59
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 { useStorage } from '@/hooks/web/useStorage'
import { LocaleDropdownType } from '@/components/LocaleDropdown'
const { getStorage, setStorage } = useStorage('localStorage')
const elLocaleMap = {
'zh-CN': zhCn,
en: en
}
interface LocaleState {
currentLocale: LocaleDropdownType
localeMap: LocaleDropdownType[]
}
export const useLocaleStore = defineStore('locales', {
state: (): LocaleState => {
return {
currentLocale: {
lang: getStorage('lang') || 'zh-CN',
elLocale: elLocaleMap[getStorage('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]
setStorage('lang', localeMap?.lang)
}
}
})
export const useLocaleStoreWithOut = () => {
return useLocaleStore(store)
}
+48
View File
@@ -0,0 +1,48 @@
import { defineStore } from 'pinia'
import { store } from '../index'
interface lockInfo {
isLock?: boolean
password?: string
}
interface LockState {
lockInfo: lockInfo
}
export const useLockStore = defineStore('lock', {
state: (): LockState => {
return {
lockInfo: {
// isLock: false, // 是否锁定屏幕
// password: '' // 锁屏密码
}
}
},
getters: {
getLockInfo(): lockInfo {
return this.lockInfo
}
},
actions: {
setLockInfo(lockInfo: lockInfo) {
this.lockInfo = lockInfo
},
resetLockInfo() {
this.lockInfo = {}
},
unLock(password: string) {
if (this.lockInfo?.password === password) {
this.resetLockInfo()
return true
} else {
return false
}
}
},
persist: true
})
export const useLockStoreWithOut = () => {
return useLockStore(store)
}
+98
View File
@@ -0,0 +1,98 @@
import { defineStore } from 'pinia'
import { asyncRouterMap, constantRouterMap } from '@/router'
import {
generateRoutesByFrontEnd,
generateRoutesByServer,
flatMultiLevelRoutes
} from '@/utils/routerHelper'
import { store } from '../index'
import { cloneDeep } from 'lodash-es'
export interface PermissionState {
routers: AppRouteRecordRaw[]
addRouters: AppRouteRecordRaw[]
isAddRouters: boolean
menuTabRouters: AppRouteRecordRaw[]
}
export const usePermissionStore = defineStore('permission', {
state: (): PermissionState => ({
routers: [],
addRouters: [],
isAddRouters: false,
menuTabRouters: []
}),
getters: {
getRouters(): AppRouteRecordRaw[] {
return this.routers
},
getAddRouters(): AppRouteRecordRaw[] {
return flatMultiLevelRoutes(cloneDeep(this.addRouters))
},
getIsAddRouters(): boolean {
return this.isAddRouters
},
getMenuTabRouters(): AppRouteRecordRaw[] {
return this.menuTabRouters
}
},
actions: {
generateRoutes(
type: 'server' | 'frontEnd' | 'static',
routers?: AppCustomRouteRecordRaw[] | string[]
): Promise<unknown> {
return new Promise<void>((resolve) => {
let routerMap: AppRouteRecordRaw[] = []
if (type === 'server') {
// 模拟后端过滤菜单
routerMap = generateRoutesByServer(routers as AppCustomRouteRecordRaw[])
} else if (type === 'frontEnd') {
// 模拟前端过滤菜单
routerMap = generateRoutesByFrontEnd(cloneDeep(asyncRouterMap), routers as string[])
} else {
// 直接读取静态路由表
routerMap = cloneDeep(asyncRouterMap)
}
// 动态路由,404一定要放到最后面
this.addRouters = routerMap.concat([
{
path: '/:path(.*)*',
redirect: '/404',
name: '404Page',
meta: {
hidden: true,
breadcrumb: false
}
}
])
// 渲染菜单的所有路由
this.routers = cloneDeep(constantRouterMap).concat(routerMap)
resolve()
})
},
setIsAddRouters(state: boolean): void {
this.isAddRouters = state
},
setMenuTabRouters(routers: AppRouteRecordRaw[]): void {
this.menuTabRouters = routers
}
},
persist: [
{
pick: ['routers'],
storage: localStorage
},
{
pick: ['addRouters'],
storage: localStorage
},
{
pick: ['menuTabRouters'],
storage: localStorage
}
]
})
export const usePermissionStoreWithOut = () => {
return usePermissionStore(store)
}
+163
View File
@@ -0,0 +1,163 @@
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'
import { useUserStoreWithOut } from './user'
export interface TagsViewState {
visitedViews: RouteLocationNormalizedLoaded[]
cachedViews: Set<string>
selectedTag?: RouteLocationNormalizedLoaded
}
export const useTagsViewStore = defineStore('tagsView', {
state: (): TagsViewState => ({
visitedViews: [],
cachedViews: new Set(),
selectedTag: undefined
}),
getters: {
getVisitedViews(): RouteLocationNormalizedLoaded[] {
return this.visitedViews
},
getCachedViews(): string[] {
return Array.from(this.cachedViews)
},
getSelectedTag(): RouteLocationNormalizedLoaded | undefined {
return this.selectedTag
}
},
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 userStore = useUserStoreWithOut()
// const affixTags = this.visitedViews.filter((tag) => tag.meta.affix)
this.visitedViews = userStore.getUserInfo
? this.visitedViews.filter((tag) => tag?.meta?.affix)
: []
},
// 删除其它
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
}
}
},
// 设置当前选中的tag
setSelectedTag(tag: RouteLocationNormalizedLoaded) {
this.selectedTag = tag
},
setTitle(title: string, path?: string) {
for (const v of this.visitedViews) {
if (v.path === (path ?? this.selectedTag?.path)) {
v.meta.title = title
break
}
}
}
},
persist: false
})
export const useTagsViewStoreWithOut = () => {
return useTagsViewStore(store)
}
+102
View File
@@ -0,0 +1,102 @@
import { defineStore } from 'pinia'
import { store } from '../index'
import { UserLoginType, UserType } from '@/api/login/types'
import { ElMessageBox } from 'element-plus'
import { useI18n } from '@/hooks/web/useI18n'
import { loginOutApi } from '@/api/login'
import { useTagsViewStore } from './tagsView'
import router from '@/router'
interface UserState {
userInfo?: UserType
tokenKey: string
token: string
roleRouters?: string[] | AppCustomRouteRecordRaw[]
rememberMe: boolean
loginInfo?: UserLoginType
}
export const useUserStore = defineStore('user', {
state: (): UserState => {
return {
userInfo: undefined,
tokenKey: 'Authorization',
token: '',
roleRouters: undefined,
// 记住我
rememberMe: true,
loginInfo: undefined
}
},
getters: {
getTokenKey(): string {
return this.tokenKey
},
getToken(): string {
return this.token
},
getUserInfo(): UserType | undefined {
return this.userInfo
},
getRoleRouters(): string[] | AppCustomRouteRecordRaw[] | undefined {
return this.roleRouters
},
getRememberMe(): boolean {
return this.rememberMe
},
getLoginInfo(): UserLoginType | undefined {
return this.loginInfo
}
},
actions: {
setTokenKey(tokenKey: string) {
this.tokenKey = tokenKey
},
setToken(token: string) {
this.token = token
},
setUserInfo(userInfo?: UserType) {
this.userInfo = userInfo
},
setRoleRouters(roleRouters: string[] | AppCustomRouteRecordRaw[]) {
this.roleRouters = roleRouters
},
logoutConfirm() {
const { t } = useI18n()
ElMessageBox.confirm(t('common.loginOutMessage'), t('common.reminder'), {
confirmButtonText: t('common.ok'),
cancelButtonText: t('common.cancel'),
type: 'warning'
})
.then(async () => {
const res = true // await loginOutApi().catch(() => {})
if (res) {
this.reset()
}
})
.catch(() => {})
},
reset() {
const tagsViewStore = useTagsViewStore()
tagsViewStore.delAllViews()
this.setToken('')
this.setUserInfo(undefined)
this.setRoleRouters([])
router.replace('/login')
},
logout() {
this.reset()
},
setRememberMe(rememberMe: boolean) {
this.rememberMe = rememberMe
},
setLoginInfo(loginInfo: UserLoginType | undefined) {
this.loginInfo = loginInfo
}
},
persist: true
})
export const useUserStoreWithOut = () => {
return useUserStore(store)
}