2026-09-09 22:05:00 +08:00
|
|
|
|
import { defineStore } from 'pinia'
|
|
|
|
|
|
import { ref, computed } from 'vue'
|
|
|
|
|
|
import { post } from '@/api/client'
|
2026-09-30 12:10:00 +08:00
|
|
|
|
import { setAccessToken, clearAccessToken } from '@/utils/token'
|
2026-09-09 22:05:00 +08:00
|
|
|
|
|
|
|
|
|
|
interface OperatorInfo {
|
|
|
|
|
|
operatorId: string
|
|
|
|
|
|
name: string
|
|
|
|
|
|
username: string
|
|
|
|
|
|
permissions: string[]
|
|
|
|
|
|
organizationId: string | null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-10 16:06:11 +08:00
|
|
|
|
const previewRolePermissions: Record<string, string[]> = {
|
|
|
|
|
|
reviewer: ['review.view', 'user.view'],
|
|
|
|
|
|
counselor: ['counseling.service.view', 'referral.manage'],
|
|
|
|
|
|
station: ['service_location.view', 'service_appointment.view'],
|
|
|
|
|
|
operator: ['review.view', 'user.view', 'organization.manage', 'permission.manage', 'activity.review_registration', 'risk.report.view', 'risk.report.process', 'analytics.view', 'banner.view', 'content.view', 'service_location.view', 'service_appointment.view', 'service_program.manage', 'counseling.service.view', 'referral.manage'],
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-09 22:05:00 +08:00
|
|
|
|
export const useAuthStore = defineStore('auth', () => {
|
2026-09-10 16:06:11 +08:00
|
|
|
|
const previewParams = import.meta.env.DEV ? new URLSearchParams(window.location.search) : null
|
|
|
|
|
|
const previewRole = previewParams?.get('preview') || ''
|
|
|
|
|
|
const previewOperator: OperatorInfo | null = previewRolePermissions[previewRole]
|
|
|
|
|
|
? { operatorId: 'ui-preview', name: '界面验收员', username: 'preview', permissions: previewRolePermissions[previewRole], organizationId: null }
|
|
|
|
|
|
: null
|
2026-09-09 22:05:00 +08:00
|
|
|
|
|
2026-09-30 12:10:00 +08:00
|
|
|
|
const token = ref<string>(previewOperator ? 'ui-preview-only' : '')
|
|
|
|
|
|
const operator = ref<OperatorInfo | null>(previewOperator)
|
|
|
|
|
|
let sessionInitialized = false
|
|
|
|
|
|
|
|
|
|
|
|
const isLoggedIn = computed(() => !!token.value && !!operator.value)
|
2026-09-09 22:05:00 +08:00
|
|
|
|
const permissions = computed(() => operator.value?.permissions || [])
|
|
|
|
|
|
|
2026-09-30 12:10:00 +08:00
|
|
|
|
function applyAuthData(data: any) {
|
|
|
|
|
|
token.value = data.accessToken || ''
|
|
|
|
|
|
operator.value = {
|
|
|
|
|
|
operatorId: data.operatorId,
|
|
|
|
|
|
name: data.name,
|
|
|
|
|
|
username: data.username,
|
|
|
|
|
|
permissions: data.permissions || [],
|
|
|
|
|
|
organizationId: data.organizationId ?? null,
|
|
|
|
|
|
}
|
|
|
|
|
|
// 同步内存态令牌(供 axios 拦截器注入 Authorization 头;生产同源场景 Cookie 也会自动携带)
|
|
|
|
|
|
setAccessToken(token.value)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-09 22:05:00 +08:00
|
|
|
|
async function login(username: string, password: string) {
|
|
|
|
|
|
const res: any = await post('/auth/operator/login', { username, password })
|
|
|
|
|
|
if (res.success) {
|
2026-09-30 12:10:00 +08:00
|
|
|
|
applyAuthData(res.data)
|
2026-09-09 22:05:00 +08:00
|
|
|
|
return res.data
|
|
|
|
|
|
}
|
|
|
|
|
|
throw new Error(res.message || '登录失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-30 12:10:00 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 静默恢复会话:页面刷新后内存态清空,凭 HttpOnly refresh Cookie(dy_op_rt)刷新令牌,
|
|
|
|
|
|
* 无需用户重新登录。refresh token 不落地、不可被 JS 读取,抗 XSS。
|
|
|
|
|
|
* 开发跨端口代理下 Cookie 不共享,刷新将失败并回落到登录页(符合预期)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function initializeSession(): Promise<boolean> {
|
|
|
|
|
|
if (sessionInitialized) return isLoggedIn.value
|
|
|
|
|
|
sessionInitialized = true
|
|
|
|
|
|
if (token.value) return true
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res: any = await post('/auth/operator/refresh', {})
|
|
|
|
|
|
if (res.success) {
|
|
|
|
|
|
applyAuthData(res.data)
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
// 未登录或 refresh 失效,交由路由守卫跳转登录页
|
|
|
|
|
|
}
|
|
|
|
|
|
return false
|
2026-09-09 22:05:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-30 12:10:00 +08:00
|
|
|
|
function logout() {
|
|
|
|
|
|
// 尽力通知后端注销并清除 Cookie(best-effort)
|
|
|
|
|
|
try {
|
|
|
|
|
|
post('/auth/operator/logout', {}).catch(() => {})
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
// ignore
|
|
|
|
|
|
}
|
|
|
|
|
|
token.value = ''
|
|
|
|
|
|
operator.value = null
|
|
|
|
|
|
clearAccessToken()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return { token, operator, isLoggedIn, permissions, login, logout, initializeSession }
|
2026-09-09 22:05:00 +08:00
|
|
|
|
})
|