import { create } from 'zustand'; import { persist } from 'zustand/middleware'; import * as authService from '../services/auth'; import * as userProfileService from '../services/userProfile'; import * as lifeEventService from '../services/lifeEvent'; import * as epicScriptService from '../services/epicScript'; import * as lifePathService from '../services/lifePath'; /** * 默认注册数据 */ const defaultRegistrationData = { id: null, nickname: '', gender: '', zodiac: '', mbti: '', profession: '', hobbies: [], childhood: { date: '', text: '' }, joy: { date: '', text: '' }, low: { date: '', text: '' }, future: { vision: '', ideal: '' } }; /** * 应用状态存储 * 使用 Zustand 进行状态管理,支持 localStorage 持久化 */ const useStore = create( persist( (set, get) => ({ // 认证状态 isLoggedIn: false, phone: '', userId: null, // 视图状态 view: 'login', currentStep: 1, // 用户注册数据 registrationData: { ...defaultRegistrationData }, // 生命事件 lifeEvents: [], // 剧本 scripts: [], selectedScriptId: null, // 路径 selectedPath: null, // 加载状态 loading: false, error: null, /** * 设置加载状态 */ setLoading: (loading) => set({ loading }), /** * 设置错误信息 */ setError: (error) => set({ error }), /** * 获取短信验证码 * @param {string} phone - 手机号 */ getSmsCode: async (phone) => { set({ loading: true, error: null }); try { const response = await authService.getSmsCode(phone); set({ loading: false }); return response; } catch (error) { set({ loading: false, error: error.message }); throw error; } }, /** * 登录 * @param {string} phone - 手机号 * @param {string} smsCode - 验证码 * @returns {Promise} 包含 hasProfile 标识,用于判断是否需要跳转到 onboarding */ login: async (phone, smsCode) => { set({ loading: true, error: null }); try { const response = await authService.login({ phone, smsCode }); const { userId } = response.data || {}; set({ isLoggedIn: true, phone, userId, loading: false }); // 尝试加载用户档案,判断用户是否已完成注册 let hasProfile = false; try { const profileData = await get().loadUserProfile(); // 检查档案是否完整(有昵称和未来愿景) hasProfile = !!(profileData && profileData.nickname && profileData.future?.vision); } catch { // 档案不存在,需要进入入站流程 hasProfile = false; } // 根据档案状态设置视图 set({ view: hasProfile ? 'dashboard' : 'onboarding' }); return { ...response, hasProfile }; } catch (error) { set({ loading: false, error: error.message }); throw error; } }, /** * 设置登录状态(本地模式) */ setLogin: (isLoggedIn, phone = '') => set({ isLoggedIn, phone, view: isLoggedIn ? 'onboarding' : 'login' }), /** * 登出 */ logout: async () => { try { await authService.logout(); } catch { // 忽略登出错误 } set({ isLoggedIn: false, phone: '', userId: null, view: 'login', currentStep: 1, registrationData: { ...defaultRegistrationData }, lifeEvents: [], scripts: [], selectedScriptId: null, selectedPath: null }); }, /** * 设置当前视图 */ setView: (view) => set({ view }), /** * 设置当前步骤 */ setCurrentStep: (step) => set({ currentStep: step }), /** * 加载用户档案 */ loadUserProfile: async () => { set({ loading: true }); try { const response = await userProfileService.getCurrentProfile(); if (response.data) { const profileData = userProfileService.transformToFrontendFormat(response.data); set({ registrationData: { ...defaultRegistrationData, ...profileData }, loading: false }); return profileData; } set({ loading: false }); return null; } catch (error) { set({ loading: false }); throw error; } }, /** * 更新注册数据(本地) */ updateRegistration: (data) => set((state) => ({ registrationData: { ...state.registrationData, ...data } })), /** * 保存用户档案到后端 */ saveUserProfile: async () => { const { registrationData } = get(); set({ loading: true, error: null }); try { let response; if (registrationData.id) { // 更新 response = await userProfileService.updateProfile(registrationData); } else { // 创建 response = await userProfileService.createProfile(registrationData); } if (response.data) { const profileData = userProfileService.transformToFrontendFormat(response.data); set({ registrationData: { ...registrationData, ...profileData }, loading: false }); } else { set({ loading: false }); } return response; } catch (error) { set({ loading: false, error: error.message }); throw error; } }, /** * 加载生命事件列表 */ loadLifeEvents: async () => { set({ loading: true }); try { const response = await lifeEventService.getEventList(); const events = lifeEventService.transformListToFrontend(response.data || []); set({ lifeEvents: events, loading: false }); return events; } catch (error) { set({ loading: false }); throw error; } }, /** * 添加生命事件 */ addLifeEvent: async (event) => { set({ loading: true, error: null }); try { const response = await lifeEventService.createEvent(event); if (response.data) { const newEvent = lifeEventService.transformToFrontendFormat(response.data); set((state) => ({ lifeEvents: [...state.lifeEvents, newEvent], loading: false })); return newEvent; } set({ loading: false }); return null; } catch (error) { set({ loading: false, error: error.message }); // 降级到本地存储 const localEvent = { ...event, id: Date.now().toString() }; set((state) => ({ lifeEvents: [...state.lifeEvents, localEvent] })); return localEvent; } }, /** * 删除生命事件 */ deleteLifeEvent: async (id) => { set({ loading: true }); try { await lifeEventService.deleteEvent(id); set((state) => ({ lifeEvents: state.lifeEvents.filter(e => e.id !== id), loading: false })); } catch (error) { set({ loading: false }); // 降级到本地删除 set((state) => ({ lifeEvents: state.lifeEvents.filter(e => e.id !== id) })); } }, /** * 加载剧本列表 */ loadScripts: async () => { set({ loading: true }); try { const response = await epicScriptService.getScriptList(); const scripts = epicScriptService.transformListToFrontend(response.data || []); // 找到选中的剧本 const selectedScript = scripts.find(s => s.isSelected); set({ scripts, selectedScriptId: selectedScript?.id || scripts[0]?.id || null, loading: false }); return scripts; } catch (error) { set({ loading: false }); throw error; } }, /** * 添加剧本 */ addScript: async (script) => { set({ loading: true, error: null }); try { const response = await epicScriptService.createScript({ ...script, isSelected: true }); if (response.data) { const newScript = epicScriptService.transformToFrontendFormat(response.data); set((state) => ({ scripts: [newScript, ...state.scripts], selectedScriptId: newScript.id, loading: false })); return newScript; } set({ loading: false }); return null; } catch (error) { set({ loading: false, error: error.message }); // 降级到本地存储 const localScript = { ...script, id: Date.now().toString(), date: new Date().toLocaleDateString() }; set((state) => ({ scripts: [localScript, ...state.scripts], selectedScriptId: localScript.id })); return localScript; } }, /** * 设置选中的剧本ID */ setSelectedScriptId: async (id) => { set({ selectedScriptId: id }); try { await epicScriptService.selectScript(id); } catch { // 忽略错误,本地已更新 } }, /** * 获取选中的剧本 */ getSelectedScript: () => { const state = get(); return state.scripts.find(s => s.id === state.selectedScriptId); }, /** * 删除剧本 */ deleteScript: async (id) => { set({ loading: true }); try { await epicScriptService.deleteScript(id); set((state) => { const newScripts = state.scripts.filter(s => s.id !== id); return { scripts: newScripts, selectedScriptId: state.selectedScriptId === id ? (newScripts[0]?.id || null) : state.selectedScriptId, loading: false }; }); } catch (error) { set({ loading: false }); // 降级到本地删除 set((state) => { const newScripts = state.scripts.filter(s => s.id !== id); return { scripts: newScripts, selectedScriptId: state.selectedScriptId === id ? (newScripts[0]?.id || null) : state.selectedScriptId }; }); } }, /** * 加载路径 */ loadPath: async (scriptId) => { if (!scriptId) return null; set({ loading: true }); try { const response = await lifePathService.getPathByScriptId(scriptId); if (response.data) { const path = lifePathService.transformToFrontendFormat(response.data); set({ selectedPath: path.content, loading: false }); return path; } set({ loading: false }); return null; } catch { set({ loading: false }); return null; } }, /** * 设置路径 */ setPath: async (pathContent, scriptId) => { set({ selectedPath: pathContent }); if (scriptId) { try { // 检查是否已有路径 const existingPath = await lifePathService.getPathByScriptId(scriptId).catch(() => null); if (existingPath?.data?.id) { // 更新 await lifePathService.updatePath({ id: existingPath.data.id, scriptId, content: pathContent }); } else { // 创建 await lifePathService.createPath({ scriptId, content: pathContent }); } } catch { // 忽略错误,本地已更新 } } }, /** * 清除所有数据 */ clear: async () => { try { await authService.logout(); } catch { // 忽略错误 } localStorage.removeItem('life_trajectory_v3'); localStorage.removeItem('access_token'); localStorage.removeItem('refresh_token'); set({ isLoggedIn: false, phone: '', userId: null, view: 'login', currentStep: 1, registrationData: { ...defaultRegistrationData }, lifeEvents: [], scripts: [], selectedScriptId: null, selectedPath: null, loading: false, error: null }); window.location.reload(); } }), { name: 'life_trajectory_v3', onRehydrateStorage: () => (state, error) => { if (error) { console.error('Failed to load state from localStorage:', error); } } } ) ); export default useStore;