refactor(auth): 将认证状态迁移至独立登录 store

This commit is contained in:
yelan
2026-08-31 10:58:05 +08:00
parent a5e0a80191
commit a261a978f2
6 changed files with 111 additions and 73 deletions

View File

@@ -1,7 +1,6 @@
import axios from 'axios' import axios from 'axios'
import { request } from '@/utils/request' import { request } from '@/utils/request'
import { saveAuthSession } from '@/utils/auth-storage'
import type { import type {
AuthenticatedUser, AuthenticatedUser,
CurrentUserResponse, CurrentUserResponse,
@@ -78,7 +77,6 @@ export async function login(payload: LoginRequest): Promise<LoginResponse> {
user: normalizeAuthenticatedUser(session.user), user: normalizeAuthenticatedUser(session.user),
} }
saveAuthSession(normalizedSession)
return normalizedSession return normalizedSession
} }

View File

@@ -1,8 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { RouterLink, useRouter } from 'vue-router' import { RouterLink, useRouter } from 'vue-router'
import { logout as logoutRequest } from '@/api/auth' import { useLoginStore } from '@/stores/login'
import { useAppStore } from '@/stores/app'
interface MenuItem { interface MenuItem {
path: string path: string
@@ -14,7 +13,7 @@ interface MenuGroup {
items: MenuItem[] items: MenuItem[]
} }
const appStore = useAppStore() const loginStore = useLoginStore()
const router = useRouter() const router = useRouter()
const menuGroups: MenuGroup[] = [ const menuGroups: MenuGroup[] = [
@@ -39,11 +38,10 @@ const menuGroups: MenuGroup[] = [
async function handleLogout() { async function handleLogout() {
try { try {
await logoutRequest() await loginStore.logout()
} catch { } catch {
// 即使服务暂时不可用,也要清理本地会话。 // 即使服务暂时不可用,也要清理本地会话。
} finally { } finally {
appStore.logout()
await router.push('/login') await router.push('/login')
} }
} }
@@ -92,10 +90,10 @@ async function handleLogout() {
</nav> </nav>
<div class="menu-profile"> <div class="menu-profile">
<div class="menu-profile-avatar">{{ appStore.userName.slice(0, 1) }}</div> <div class="menu-profile-avatar">{{ loginStore.userName.slice(0, 1) }}</div>
<div class="menu-profile-copy"> <div class="menu-profile-copy">
<strong>{{ appStore.userName }}</strong> <strong>{{ loginStore.userName }}</strong>
<span>{{ appStore.department }}</span> <span>{{ loginStore.department }}</span>
</div> </div>
<button class="menu-logout" type="button" @click="handleLogout">退出</button> <button class="menu-logout" type="button" @click="handleLogout">退出</button>
</div> </div>

View File

@@ -1,6 +1,5 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
import { useAppStore } from '@/stores/app'
import ClinicalLayout from '@/layouts/ClinicalLayout.vue' import ClinicalLayout from '@/layouts/ClinicalLayout.vue'
import LoginView from '@/views/auth/LoginView.vue' import LoginView from '@/views/auth/LoginView.vue'
import DocumentPermissionsView from '@/views/management/document-permissions/index.vue' import DocumentPermissionsView from '@/views/management/document-permissions/index.vue'
@@ -10,6 +9,7 @@ import SettingsView from '@/views/management/settings/index.vue'
import UserPermissionsView from '@/views/management/users/index.vue' import UserPermissionsView from '@/views/management/users/index.vue'
import ConsentTasksView from '@/views/workbench/signing/index.vue' import ConsentTasksView from '@/views/workbench/signing/index.vue'
import DashboardView from '@/views/workbench/home/index.vue' import DashboardView from '@/views/workbench/home/index.vue'
import { useLoginStore } from '@/stores/login'
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
@@ -93,13 +93,13 @@ const router = createRouter({
}) })
router.beforeEach((to) => { router.beforeEach((to) => {
const appStore = useAppStore() const loginStore = useLoginStore()
if (to.name === 'login' && appStore.isLoggedIn) { if (to.name === 'login' && loginStore.isLoggedIn) {
return { name: 'workbench-home' } return { name: 'workbench-home' }
} }
if (to.meta.requiresAuth && !appStore.isLoggedIn) { if (to.meta.requiresAuth && !loginStore.isLoggedIn) {
return { return {
name: 'login', name: 'login',
query: { redirect: to.fullPath }, query: { redirect: to.fullPath },

View File

@@ -1,60 +1,16 @@
import { ref } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import type { WorkbenchCampus } from '@/api/workbench/types' import type { WorkbenchCampus } from '@/api/workbench/types'
import type { AuthenticatedUser, LoginResponse } from '@/types/auth'
import { clearAuthStorage, readStoredAuthSession, saveAuthSession } from '@/utils/auth-storage'
const dataScopeLabels: Record<AuthenticatedUser['dataScope'], string> = { export const useAppStore = defineStore('app', () => {
ALL: '全院', const environment = ref('接口联调环境')
CAMPUS: '院区', const campuses = ref<WorkbenchCampus[]>(['本部院区', '东院区', '西院区'])
DEPARTMENT: '科室', const selectedCampus = ref<WorkbenchCampus>('本部院区')
READ_ONLY_ALL: '全院只读',
}
function getUserContext(user: AuthenticatedUser | null) { return {
return user ? `${dataScopeLabels[user.dataScope]}数据范围` : '医签通用户' environment,
} campuses,
selectedCampus,
export const useAppStore = defineStore('app', { }
state: () => {
const session = readStoredAuthSession()
const user = session?.user ?? null
return {
isLoggedIn: Boolean(session?.token),
token: session?.token ?? '',
expiresAt: session?.expiresAt ?? '',
user,
userName: user?.displayName || user?.username || '医护用户',
department: getUserContext(user),
environment: '接口联调环境',
campuses: ['本部院区', '东院区', '西院区'] as WorkbenchCampus[],
selectedCampus: '本部院区' as WorkbenchCampus,
}
},
actions: {
login(session: LoginResponse) {
saveAuthSession(session)
this.token = session.token
this.expiresAt = session.expiresAt
this.user = session.user
this.userName = session.user.displayName || session.user.username || '医护用户'
this.department = getUserContext(session.user)
this.isLoggedIn = true
},
updateCurrentUser(user: AuthenticatedUser) {
this.user = user
this.userName = user.displayName || user.username || '医护用户'
this.department = getUserContext(user)
},
logout() {
clearAuthStorage()
this.isLoggedIn = false
this.token = ''
this.expiresAt = ''
this.user = null
this.userName = '医护用户'
this.department = getUserContext(null)
},
},
}) })

View File

@@ -0,0 +1,87 @@
import { computed, ref } from 'vue'
import { defineStore } from 'pinia'
import { getCurrentUser, login as loginRequest, logout as logoutRequest } from '@/api/auth'
import type { AuthenticatedUser, LoginRequest, LoginResponse } from '@/types/auth'
import { clearAuthStorage, readStoredAuthSession, saveAuthSession } from '@/utils/auth-storage'
const dataScopeLabels: Record<AuthenticatedUser['dataScope'], string> = {
ALL: '全院',
CAMPUS: '院区',
DEPARTMENT: '科室',
READ_ONLY_ALL: '全院只读',
}
function getUserContext(user: AuthenticatedUser | null) {
return user ? `${dataScopeLabels[user.dataScope]}数据范围` : '医签通用户'
}
export const useLoginStore = defineStore('login', () => {
const storedSession = readStoredAuthSession()
const token = ref(storedSession?.token ?? '')
const expiresAt = ref(storedSession?.expiresAt ?? '')
const user = ref<AuthenticatedUser | null>(storedSession?.user ?? null)
const isLoggedIn = computed(() => Boolean(token.value))
const userName = computed(() => user.value?.displayName || user.value?.username || '医护用户')
const department = computed(() => getUserContext(user.value))
function setSession(session: LoginResponse) {
saveAuthSession(session)
token.value = session.token
expiresAt.value = session.expiresAt
user.value = session.user
}
function setCurrentUser(nextUser: AuthenticatedUser) {
user.value = nextUser
if (token.value) {
saveAuthSession({
token: token.value,
expiresAt: expiresAt.value,
user: nextUser,
})
}
}
function clearSession() {
clearAuthStorage()
token.value = ''
expiresAt.value = ''
user.value = null
}
async function login(payload: LoginRequest) {
const session = await loginRequest(payload)
setSession(session)
return session
}
async function refreshCurrentUser() {
const currentUser = await getCurrentUser()
setCurrentUser(currentUser)
return currentUser
}
async function logout() {
try {
await logoutRequest()
} finally {
clearSession()
}
}
return {
token,
expiresAt,
user,
isLoggedIn,
userName,
department,
login,
refreshCurrentUser,
logout,
clearSession,
}
})

View File

@@ -2,12 +2,12 @@
import { ref } from 'vue' import { ref } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { getAuthErrorMessage, login as loginRequest } from '@/api/auth' import { getAuthErrorMessage } from '@/api/auth'
import { useAppStore } from '@/stores/app' import { useLoginStore } from '@/stores/login'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const appStore = useAppStore() const loginStore = useLoginStore()
const username = ref('') const username = ref('')
const password = ref('') const password = ref('')
@@ -33,12 +33,11 @@ async function handleLogin() {
isSubmitting.value = true isSubmitting.value = true
try { try {
const session = await loginRequest({ await loginStore.login({
username: normalizedUsername, username: normalizedUsername,
password: password.value, password: password.value,
}) })
appStore.login(session)
await router.replace(getRedirectPath()) await router.replace(getRedirectPath())
} catch (error: unknown) { } catch (error: unknown) {
errorMessage.value = getAuthErrorMessage(error) errorMessage.value = getAuthErrorMessage(error)