import { create } from 'zustand'; import Taro from '@tarojs/taro'; import * as authApi from '@/services/auth'; import { secureGet, secureSet, secureRemove } from '@/utils/secure-storage'; import { clearRequestCache, invalidateHeadersCache, markLoggingOut, clearLoggingOut, setCachedPatientId } from '@/services/request'; // secureGet 已内置明文键 fallback,无需再手动 fallback function storageGet(key: string): string { return secureGet(key); } import { resetAllStores } from './index'; import { resetAnalyticsDisabled } from '@/services/analytics'; // --- 内存缓存,避免每次 Tab 切换重复 Storage IPC + JSON.parse --- let cachedUserJson = ''; let cachedUserObj: AuthState['user'] = null; let cachedRolesJson = ''; let cachedRolesObj: string[] = []; let cachedPatientJson = ''; let cachedPatientObj: authApi.PatientInfo | null = null; interface AuthState { user: { id: string; username: string; display_name?: string; phone?: string; tenant_id?: string } | null; roles: string[]; currentPatient: authApi.PatientInfo | null; patients: authApi.PatientInfo[]; loading: boolean; login: (code: string) => Promise; credentialLogin: (username: string, password: string) => Promise; bindPhone: (encryptedData: string, iv: string) => Promise; setCurrentPatient: (patient: authApi.PatientInfo) => void; loadPatients: () => Promise; logout: () => void; restore: () => void; isMedicalStaff: () => boolean; isDoctor: () => boolean; isNurse: () => boolean; isHealthManager: () => boolean; hasRole: (code: string) => boolean; hasPatientProfile: () => boolean; } export const useAuthStore = create((set, get) => ({ user: null, roles: [], currentPatient: null, patients: [], loading: false, isMedicalStaff: () => { const { roles } = get(); return roles.some((r) => r === 'doctor' || r === 'nurse' || r === 'admin' || r === 'health_manager'); }, isDoctor: () => { const { roles } = get(); return roles.some((r) => r === 'doctor' || r === 'admin'); }, isNurse: () => { const { roles } = get(); return roles.some((r) => r === 'nurse' || r === 'admin'); }, isHealthManager: () => { const { roles } = get(); return roles.some((r) => r === 'health_manager' || r === 'admin'); }, hasRole: (code: string) => { const { roles } = get(); return roles.some((r) => r === code || r === 'admin'); }, hasPatientProfile: () => { return !!get().currentPatient; }, restore: () => { // 利用内存缓存避免重复 Storage IPC + JSON.parse try { const userData = storageGet('user_data'); if (userData !== cachedUserJson) { cachedUserJson = userData; cachedUserObj = userData ? JSON.parse(userData) : null; } const rolesData = storageGet('user_roles'); if (rolesData !== cachedRolesJson) { cachedRolesJson = rolesData; cachedRolesObj = rolesData ? JSON.parse(rolesData) : []; } } catch { /* secure storage 不可用时保持默认值 */ } try { const patientStr = storageGet('current_patient'); let patientRaw = patientStr ? JSON.parse(patientStr) : null; const patientJson = patientRaw ? JSON.stringify(patientRaw) : ''; if (patientJson !== cachedPatientJson) { cachedPatientJson = patientJson; cachedPatientObj = patientRaw || null; } } catch { cachedPatientObj = null; } const user = cachedUserObj; const roles = cachedRolesObj; const currentPatient = cachedPatientObj; // 同步 cachedPatientId 到 request.ts if (currentPatient?.id) { setCachedPatientId(currentPatient.id); } // 跳过未变更的 set() const cur = get(); const userChanged = cur.user?.id !== user?.id; const rolesChanged = cur.roles.length !== roles.length || cur.roles.some((r, i) => r !== roles[i]); const patientChanged = cur.currentPatient?.id !== currentPatient?.id; if (!userChanged && !rolesChanged && !patientChanged) return; // 状态有变化时清理请求缓存,避免返回过期数据 clearRequestCache(); set({ user, roles, currentPatient }); }, login: async (code: string) => { if (get().loading) return false; set({ loading: true }); try { const resp = await authApi.wechatLogin(code); if (resp.bound && resp.token) { const { access_token, refresh_token, user } = resp.token; const userObj = user as Record; const roles = Array.isArray(userObj?.roles) ? (userObj.roles as Array>).map((r) => r.code || r.name || String(r)) : []; secureSet('access_token', access_token); secureSet('refresh_token', refresh_token); if (resp.token.expires_in) { secureSet('token_expires_at', String(Date.now() + resp.token.expires_in * 1000)); } secureSet('user_data', JSON.stringify(user)); secureSet('user_roles', JSON.stringify(roles)); secureSet('tenant_id', user.tenant_id || ''); set({ user, roles, loading: false }); clearLoggingOut(); invalidateHeadersCache(); resetAnalyticsDisabled(); get().loadPatients(); return true; } secureSet('wechat_openid', resp.openid); set({ loading: false }); return false; } catch (err) { console.warn('[auth] 微信登录失败:', err); set({ loading: false }); return false; } }, credentialLogin: async (username: string, password: string) => { if (get().loading) return false; set({ loading: true }); try { const tenantId = Taro.getStorageSync('tenant_id') || process.env.TARO_APP_DEFAULT_TENANT_ID || ''; const resp = await authApi.credentialLogin(username, password, tenantId); const user = resp.user as Record; const roles = Array.isArray(user?.roles) ? (user.roles as Array>).map((r) => r.code || r.name || String(r)) : []; secureSet('access_token', resp.access_token); secureSet('refresh_token', resp.refresh_token); if (resp.expires_in) { secureSet('token_expires_at', String(Date.now() + resp.expires_in * 1000)); } secureSet('user_data', JSON.stringify(resp.user)); secureSet('user_roles', JSON.stringify(roles)); secureSet('tenant_id', resp.user?.tenant_id || tenantId); set({ user: resp.user, roles, loading: false }); clearLoggingOut(); invalidateHeadersCache(); resetAnalyticsDisabled(); get().loadPatients(); return true; } catch (err) { console.warn('[auth] 账号密码登录失败:', err); set({ loading: false }); return false; } }, bindPhone: async (encryptedData: string, iv: string) => { if (get().loading) return false; set({ loading: true }); try { const openid = secureGet('wechat_openid') || ''; if (!openid) { set({ loading: false }); throw new Error('登录态丢失,请返回重试'); } const resp = await authApi.wechatBindPhone(openid, encryptedData, iv) as Record; const tokenData = resp as { access_token: string; refresh_token: string; expires_in?: number; user: AuthState['user'] }; const userObj = tokenData.user as Record; const roles = Array.isArray(userObj?.roles) ? (userObj.roles as Array>).map((r) => r.code || r.name || String(r)) : []; secureSet('access_token', tokenData.access_token); secureSet('refresh_token', tokenData.refresh_token); if (tokenData.expires_in) { secureSet('token_expires_at', String(Date.now() + tokenData.expires_in * 1000)); } secureSet('user_data', JSON.stringify(tokenData.user)); secureSet('user_roles', JSON.stringify(roles)); secureSet('tenant_id', tokenData.user?.tenant_id || ''); secureRemove('wechat_openid'); set({ user: tokenData.user, roles, loading: false }); clearLoggingOut(); invalidateHeadersCache(); resetAnalyticsDisabled(); get().loadPatients(); return true; } catch (err: unknown) { secureRemove('wechat_openid'); set({ loading: false }); throw err; } }, setCurrentPatient: (patient) => { const safePatient: authApi.PatientInfo = { id: patient.id, name: patient.name, gender: patient.gender, birth_date: patient.birth_date, relation: patient.relation, }; secureSet('current_patient_id', safePatient.id); secureSet('current_patient', JSON.stringify(safePatient)); setCachedPatientId(safePatient.id); clearRequestCache(); set({ currentPatient: safePatient }); }, loadPatients: async () => { try { const summaries = await authApi.getPatientSummaries(); const patients: authApi.PatientInfo[] = summaries.map((p) => ({ id: p.id, name: p.name, gender: p.gender, birth_date: p.birth_date, relation: 'self', })); set({ patients }); if (patients.length > 0 && !get().currentPatient) { get().setCurrentPatient(patients[0]); } } catch (err) { console.warn('[auth] 患者列表加载失败:', err); } }, logout: () => { markLoggingOut(); clearRequestCache(); setCachedPatientId(''); // 清理模块级缓存 cachedUserJson = ''; cachedUserObj = null; cachedRolesJson = ''; cachedRolesObj = []; cachedPatientJson = ''; cachedPatientObj = null; secureRemove('access_token'); secureRemove('refresh_token'); secureRemove('token_expires_at'); secureRemove('user_data'); secureRemove('user_roles'); secureRemove('tenant_id'); secureRemove('wechat_openid'); secureRemove('current_patient'); secureRemove('current_patient_id'); // analytics_queue 使用明文存储(analytics.ts STORAGE_KEY = 'analytics_queue') Taro.removeStorageSync('analytics_queue'); secureRemove('edit_patient'); secureRemove('ai_chat_history'); // 清理 BLE DataBuffer 缓存(key 格式:ble_buffer_{patientId}_{bucket}) const storageInfo = Taro.getStorageInfoSync(); storageInfo.keys.forEach((key) => { if (key.startsWith('ble_buffer_') || key.startsWith('last_ble_sync')) { Taro.removeStorageSync(key); } }); resetAllStores(); set({ user: null, roles: [], currentPatient: null, patients: [] }); Taro.reLaunch({ url: '/pages/index/index' }).catch((err) => { console.warn('[auth] reLaunch after logout failed:', err); Taro.redirectTo({ url: '/pages/index/index' }).catch(() => {}); }); }, }));