新增人生轨迹模块代码
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user