Files
happy-life-star/life-script/src/store/useStore.js
T
2025-12-22 23:40:47 +08:00

497 lines
14 KiB
JavaScript

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<Object>} 包含 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;