182 lines
4.9 KiB
TypeScript
182 lines
4.9 KiB
TypeScript
import { computed, ref } from 'vue'
|
|
import { defineStore } from 'pinia'
|
|
import { loginApi, logoutApi, meApi } from '@/api/auth'
|
|
import { listDictItemsApi } from '@/api/system/dict'
|
|
import type { CurrentUserProfile, LoginRequest, MeResponse, MenuNode } from '@/types/auth'
|
|
import type { DictItem } from '@/types/system/dict'
|
|
|
|
const TOKEN_KEY = 'platform.token'
|
|
|
|
export interface TabItem {
|
|
name: string
|
|
title: string
|
|
path: string
|
|
closable: boolean
|
|
}
|
|
|
|
function normalizeTabPath(path: string) {
|
|
if (path === '/workbench') return '/dashboard'
|
|
return path
|
|
}
|
|
|
|
export const useAuthStore = defineStore('auth', () => {
|
|
const token = ref<string>(localStorage.getItem(TOKEN_KEY) ?? '')
|
|
const user = ref<CurrentUserProfile | null>(null)
|
|
const menus = ref<MenuNode[]>([])
|
|
const permissions = ref<string[]>([])
|
|
const layoutCollapsed = ref(false)
|
|
const dictCache = ref<Record<string, DictItem[]>>({})
|
|
const tabs = ref<TabItem[]>([
|
|
{ name: 'workbench-home', title: '工作台', path: '/dashboard', closable: false }
|
|
])
|
|
const tabRefreshMarks = ref<Record<string, number>>({})
|
|
const loaded = ref(false)
|
|
|
|
const isAuthed = computed(() => Boolean(token.value))
|
|
|
|
function setToken(nextToken: string) {
|
|
token.value = nextToken
|
|
localStorage.setItem(TOKEN_KEY, nextToken)
|
|
}
|
|
|
|
function clearAuth() {
|
|
token.value = ''
|
|
user.value = null
|
|
menus.value = []
|
|
permissions.value = []
|
|
dictCache.value = {}
|
|
loaded.value = false
|
|
tabs.value = [{ name: 'workbench-home', title: '工作台', path: '/dashboard', closable: false }]
|
|
tabRefreshMarks.value = {}
|
|
localStorage.removeItem(TOKEN_KEY)
|
|
}
|
|
|
|
async function login(payload: LoginRequest) {
|
|
const result = await loginApi(payload)
|
|
setToken(result.accessToken)
|
|
await loadProfile()
|
|
}
|
|
|
|
async function logout() {
|
|
try {
|
|
if (token.value) {
|
|
await logoutApi()
|
|
}
|
|
} finally {
|
|
clearAuth()
|
|
}
|
|
}
|
|
|
|
async function loadProfile(force = false): Promise<MeResponse | null> {
|
|
if (!token.value) return null
|
|
if (loaded.value && !force) {
|
|
return {
|
|
user: user.value!,
|
|
menus: menus.value,
|
|
permissions: permissions.value
|
|
}
|
|
}
|
|
const profile = await meApi()
|
|
user.value = profile.user
|
|
menus.value = profile.menus
|
|
permissions.value = [...profile.permissions]
|
|
loaded.value = true
|
|
return profile
|
|
}
|
|
|
|
function hasPermission(permission?: string | null) {
|
|
if (!permission) return true
|
|
return permissions.value.includes(permission)
|
|
}
|
|
|
|
async function loadDictByTypeId(typeId: string, force = false) {
|
|
const cached = dictCache.value[typeId]
|
|
if (cached && !force) return cached
|
|
const result = await listDictItemsApi({ typeId, page: 1, pageSize: 200 })
|
|
dictCache.value[typeId] = result.items.filter((item) => item.status === 'ENABLED')
|
|
return dictCache.value[typeId]
|
|
}
|
|
|
|
function getDictLabel(typeId: string, value: string) {
|
|
const item = dictCache.value[typeId]?.find((entry) => entry.value === value)
|
|
return item?.label ?? value
|
|
}
|
|
|
|
function getDictColor(typeId: string, value: string) {
|
|
const item = dictCache.value[typeId]?.find((entry) => entry.value === value)
|
|
return item?.color ?? undefined
|
|
}
|
|
|
|
function addTab(tab: TabItem) {
|
|
const normalizedPath = normalizeTabPath(tab.path)
|
|
if (tabs.value.some((item) => item.path === normalizedPath)) return
|
|
tabs.value.push({
|
|
...tab,
|
|
path: normalizedPath
|
|
})
|
|
}
|
|
|
|
function closeTab(path: string) {
|
|
const targetPath = normalizeTabPath(path)
|
|
const fixed = tabs.value.filter((item) => !item.closable)
|
|
tabs.value = [
|
|
...fixed,
|
|
...tabs.value.filter((item) => item.closable && item.path !== targetPath)
|
|
]
|
|
delete tabRefreshMarks.value[targetPath]
|
|
}
|
|
|
|
function closeOtherTabs(path: string) {
|
|
const targetPath = normalizeTabPath(path)
|
|
tabs.value = tabs.value.filter((item) => !item.closable || item.path === targetPath)
|
|
Object.keys(tabRefreshMarks.value).forEach((tabPath) => {
|
|
if (!tabs.value.some((item) => item.path === tabPath)) {
|
|
delete tabRefreshMarks.value[tabPath]
|
|
}
|
|
})
|
|
}
|
|
|
|
function closeAllTabs() {
|
|
const fixedTabs = tabs.value.filter((item) => !item.closable)
|
|
const fixedPaths = new Set(fixedTabs.map((item) => item.path))
|
|
Object.keys(tabRefreshMarks.value).forEach((path) => {
|
|
if (!fixedPaths.has(path)) {
|
|
delete tabRefreshMarks.value[path]
|
|
}
|
|
})
|
|
tabs.value = fixedTabs
|
|
}
|
|
|
|
function refreshTab(path: string) {
|
|
const targetPath = normalizeTabPath(path)
|
|
tabRefreshMarks.value[targetPath] = Date.now()
|
|
}
|
|
|
|
return {
|
|
token,
|
|
user,
|
|
menus,
|
|
permissions,
|
|
layoutCollapsed,
|
|
dictCache,
|
|
tabs,
|
|
tabRefreshMarks,
|
|
loaded,
|
|
isAuthed,
|
|
setToken,
|
|
clearAuth,
|
|
login,
|
|
logout,
|
|
loadProfile,
|
|
hasPermission,
|
|
addTab,
|
|
closeTab,
|
|
closeOtherTabs,
|
|
closeAllTabs,
|
|
refreshTab,
|
|
loadDictByTypeId,
|
|
getDictLabel,
|
|
getDictColor
|
|
}
|
|
})
|