新增人生轨迹模块代码

This commit is contained in:
2025-12-21 17:44:59 +08:00
parent f3c06ce6af
commit cfd12f01db
14 changed files with 1156 additions and 72 deletions
+101 -9
View File
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { Store } from '../utils/store';
import { ArrowLeft, ArrowRight, Check, X, Sparkles, Star, AlertCircle, CheckCircle } from 'lucide-react';
import { userApi } from '../api/user';
import { ArrowLeft, ArrowRight, Check, X, Sparkles, Star, AlertCircle, CheckCircle, Loader } from 'lucide-react';
import { Button } from '../components/ui/Button';
import { Input, Select, Textarea } from '../components/ui/Input';
import clsx from 'clsx';
@@ -17,6 +18,55 @@ export function OnboardingPage({ onFinish }) {
const [step, setStep] = useState(0);
const [formData, setFormData] = useState(Store.get().userProfile);
const [toast, setToast] = useState(null); // { msg, type }
const [submitting, setSubmitting] = useState(false);
// Load existing profile from backend on mount
useEffect(() => {
const loadProfile = async () => {
try {
const res = await userApi.getCurrentUser();
if (res.data) {
// Merge backend data with local state, handling JSON strings if necessary
// Backend returns scripts/paths as strings, but profile fields are direct
// Note: formData structure matches userProfile in store.js
// We need to map backend fields (camelCase) to frontend structure if they differ
// Backend: childhoodDate, childhoodContent etc.
// Frontend: history: { childhood: { date, content } }
// We need a mapper if structures differ.
// Backend UserProfileResponse has: nickname, gender, zodiac, mbti, hobbies (String), childhoodDate...
const backendData = res.data;
const mappedData = {
...formData,
nickname: backendData.nickname || formData.nickname,
gender: backendData.gender || formData.gender,
zodiac: backendData.zodiac || formData.zodiac,
mbti: backendData.mbti || formData.mbti,
hobbies: backendData.hobbies ? JSON.parse(backendData.hobbies) : formData.hobbies,
history: {
childhood: {
date: backendData.childhoodDate || '',
content: backendData.childhoodContent || ''
},
peak: {
date: backendData.peakDate || '',
content: backendData.peakContent || ''
},
valley: {
date: backendData.valleyDate || '',
content: backendData.valleyContent || ''
}
},
futureVision: backendData.futureVision || formData.futureVision
};
setFormData(mappedData);
}
} catch (e) {
console.warn("Failed to load profile", e);
}
};
loadProfile();
}, []);
const showToast = (msg, type = 'error') => {
setToast({ msg, type });
@@ -40,15 +90,57 @@ export function OnboardingPage({ onFinish }) {
}));
};
const handleNext = () => {
const handleNext = async () => {
if (step === 0) {
if (!formData.nickname?.trim()) { showToast('请填写昵称'); return; }
if (!formData.mbti) { showToast('请选择MBTI人格类型'); return; }
} else if (step === 4) {
if (!formData.futureVision?.trim()) { showToast('写下一句对未来的期许吧'); return; }
Store.updateProfile(formData);
Store.completeOnboarding();
onFinish();
setSubmitting(true);
try {
// 1. Save to local store
Store.updateProfile(formData);
Store.completeOnboarding();
// 2. Prepare data for backend
// Flatten history structure to match UserProfileCreateRequest
const requestData = {
nickname: formData.nickname,
gender: formData.gender,
zodiac: formData.zodiac,
mbti: formData.mbti,
hobbies: JSON.stringify(formData.hobbies),
childhoodDate: formData.history?.childhood?.date || null,
childhoodContent: formData.history?.childhood?.content || '',
peakDate: formData.history?.peak?.date || null,
peakContent: formData.history?.peak?.content || '',
valleyDate: formData.history?.valley?.date || null,
valleyContent: formData.history?.valley?.content || '',
futureVision: formData.futureVision,
// Also sync scripts/paths if they exist in store (re-registration case)
scripts: JSON.stringify(Store.get().generatedScripts || []),
paths: JSON.stringify(Store.get().paths || [])
};
// 3. Call backend
// Check if profile exists first
const currentProfile = await userApi.getCurrentUser();
if (currentProfile.data) {
// Update
await userApi.updateUserProfile({ ...requestData, id: currentProfile.data.id });
} else {
// Create
await userApi.createUserProfile(requestData);
}
onFinish();
} catch (e) {
console.error(e);
showToast('保存失败,请重试');
} finally {
setSubmitting(false);
}
return;
}
setStep(prev => prev + 1);
@@ -225,9 +317,9 @@ export function OnboardingPage({ onFinish }) {
</button>
) : <div></div>}
<Button onClick={handleNext} className="flex items-center gap-2 px-8 group hover:scale-105 active:scale-95 shadow-lg shadow-primary/20">
<span>{step === 4 ? '完成创建' : '下一步'}</span>
{step === 4 ? <Check className="w-4 h-4" /> : <ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />}
<Button onClick={handleNext} disabled={submitting} className="flex items-center gap-2 px-8 group hover:scale-105 active:scale-95 shadow-lg shadow-primary/20 disabled:opacity-70 disabled:scale-100">
<span>{step === 4 ? (submitting ? '保存中...' : '开启旅程') : '下一步'}</span>
{step === 4 ? (submitting ? <Loader className="w-4 h-4 animate-spin" /> : <Check className="w-4 h-4" />) : <ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />}
</Button>
</div>
</div>