import { defineStore } from 'pinia' import { ref, computed } from 'vue' import { post } from '@/api/client' import { setAccessToken, clearAccessToken } from '@/utils/token' interface OperatorInfo { operatorId: string name: string username: string permissions: string[] organizationId: string | null } const previewRolePermissions: Record = { 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'], } export const useAuthStore = defineStore('auth', () => { 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 const token = ref(previewOperator ? 'ui-preview-only' : '') const operator = ref(previewOperator) let sessionInitialized = false const isLoggedIn = computed(() => !!token.value && !!operator.value) const permissions = computed(() => operator.value?.permissions || []) 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) } async function login(username: string, password: string) { const res: any = await post('/auth/operator/login', { username, password }) if (res.success) { applyAuthData(res.data) return res.data } throw new Error(res.message || '登录失败') } /** * 静默恢复会话:页面刷新后内存态清空,凭 HttpOnly refresh Cookie(dy_op_rt)刷新令牌, * 无需用户重新登录。refresh token 不落地、不可被 JS 读取,抗 XSS。 * 开发跨端口代理下 Cookie 不共享,刷新将失败并回落到登录页(符合预期)。 */ async function initializeSession(): Promise { 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 } 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 } })