人生轨迹代码初始化

This commit is contained in:
2025-12-21 16:57:54 +08:00
parent 06a3638c29
commit f3c06ce6af
42 changed files with 7746 additions and 0 deletions
@@ -0,0 +1,328 @@
import React, { useState, useEffect } from 'react';
import { useStoreData } from '../../hooks/useStoreData';
import { Store } from '../../utils/store';
import { AI } from '../../utils/aiLogic';
import {
Map,
Ghost,
Wand2,
RefreshCw,
MapPin,
ChevronDown,
Target,
Activity,
Package,
Repeat,
Edit3,
Trash2,
Check,
ArrowRight
} from 'lucide-react';
import { Button } from '../ui/Button';
import { GlassCard } from '../ui/GlassCard';
import { Select } from '../ui/Input';
export function PathView({ onSwitchToScript }) {
const data = useStoreData();
const [selectedScriptId, setSelectedScriptId] = useState('');
const [loading, setLoading] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const [expandedStep, setExpandedStep] = useState(null);
const [editedPath, setEditedPath] = useState(null);
// Initialize selectedScriptId when scripts are available
useEffect(() => {
if (data.generatedScripts.length > 0 && !selectedScriptId) {
setSelectedScriptId(data.generatedScripts[0].id);
}
}, [data.generatedScripts, selectedScriptId]);
// Find current path based on selected script
const currentPath = data.paths?.find(p => p.scriptId === selectedScriptId);
// Update local edited state when path changes
useEffect(() => {
if (currentPath) {
setEditedPath(JSON.parse(JSON.stringify(currentPath))); // Deep copy
} else {
setEditedPath(null);
}
}, [currentPath]);
const handleGenerate = async () => {
if (currentPath && !window.confirm('重新生成将覆盖现有路径规划,确定吗?')) return;
const script = data.generatedScripts.find(s => s.id === selectedScriptId);
if (!script) return;
setLoading(true);
try {
const newPath = await AI.generatePath(script, data.userProfile);
Store.addPath(newPath);
setIsEditing(false);
} catch (e) {
console.error(e);
alert('规划失败,请重试');
} finally {
setLoading(false);
}
};
const handleDelete = () => {
if (window.confirm('确定要删除这个路径规划吗?')) {
Store.deletePath(currentPath.id);
setIsEditing(false);
}
};
const handleSaveEdit = () => {
if (editedPath) {
Store.updatePath(editedPath.id, { steps: editedPath.steps });
setIsEditing(false);
}
};
const handleStepEdit = (stepIndex, field, value) => {
if (!editedPath) return;
const newSteps = [...editedPath.steps];
newSteps[stepIndex] = { ...newSteps[stepIndex], [field]: value };
setEditedPath({ ...editedPath, steps: newSteps });
};
if (data.generatedScripts.length === 0) {
return (
<div className="max-w-4xl mx-auto md:p-12 pb-24">
<header className="mb-8">
<h2 className="text-3xl font-bold text-white mb-2 flex items-center gap-2">
<span className="bg-green-100/10 p-2 rounded-lg text-aurora-green">
<Map className="w-6 h-6 icon-glow" />
</span>
实现路径
</h2>
<p className="text-gray-400">将幻想落地为行动AI为你定制专属计划</p>
</header>
<div className="p-16 text-center text-gray-400 glass-card rounded-2xl border-dashed border-2 border-white/10">
<Ghost className="w-16 h-16 mx-auto mb-4 opacity-30" />
<p className="mb-4">你需要先有一个剧本才能生成通往它的路径</p>
<button
className="text-primary font-bold hover:underline flex items-center justify-center gap-1 mx-auto"
onClick={onSwitchToScript}
>
去生成剧本 <ArrowRight className="w-4 h-4" />
</button>
</div>
</div>
);
}
return (
<div className="max-w-4xl mx-auto md:p-12 pb-24">
<header className="mb-8">
<h2 className="text-3xl font-bold text-white mb-2 flex items-center gap-2">
<span className="bg-green-100/10 p-2 rounded-lg text-aurora-green">
<Map className="w-6 h-6 icon-glow" />
</span>
实现路径
</h2>
<p className="text-gray-400">将幻想落地为行动AI为你定制专属计划</p>
</header>
<GlassCard className="mb-8 p-6 rounded-2xl border-l-4 border-accent flex flex-col md:flex-row items-center gap-4 shadow-sm">
<div className="flex-1 w-full">
<label className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">
选择目标剧本
</label>
<div className="relative">
<Select
value={selectedScriptId}
onChange={(e) => setSelectedScriptId(e.target.value)}
className="w-full"
>
{data.generatedScripts.map(s => (
<option key={s.id} value={s.id}>{s.title}</option>
))}
</Select>
</div>
</div>
<Button
onClick={handleGenerate}
isLoading={loading}
className="w-full md:w-auto mt-4 md:mt-0"
>
{currentPath ? (
<>
<RefreshCw className="w-4 h-4 mr-2" /> 重新生成
</>
) : (
<>
<Wand2 className="w-4 h-4 mr-2" /> 生成路径
</>
)}
</Button>
</GlassCard>
<div className="transition-all duration-300 min-h-[300px]">
{!currentPath ? (
<div className="text-center py-20 opacity-50">
<div className="w-20 h-20 bg-white/5 rounded-full flex items-center justify-center mx-auto mb-4 border border-white/10">
<MapPin className="w-8 h-8 text-gray-400" />
</div>
<p className="text-gray-400">尚未生成路径点击上方按钮开始规划</p>
</div>
) : (
<>
<div className="flex justify-between items-center mb-6 animate-fade-in">
<div className="text-sm text-gray-400">
规划生成于 {new Date(currentPath.createdAt).toLocaleDateString()}
</div>
<div className="flex gap-2">
<button
onClick={isEditing ? handleSaveEdit : () => setIsEditing(true)}
className={`py-1.5 px-4 text-xs flex items-center gap-2 rounded-lg border transition-colors ${
isEditing
? 'bg-primary text-white border-primary hover:bg-primary/90'
: 'border-white/20 text-gray-400 hover:text-primary hover:border-primary bg-transparent'
}`}
>
{isEditing ? (
<><Check className="w-3 h-3" /> 保存</>
) : (
<><Edit3 className="w-3 h-3" /> 编辑</>
)}
</button>
<button
onClick={handleDelete}
className="py-1.5 px-4 text-xs flex items-center gap-2 rounded-lg border border-red-500/30 text-red-400 hover:bg-red-500/10 hover:border-red-400 bg-transparent"
>
<Trash2 className="w-3 h-3" /> 删除
</button>
</div>
</div>
<div className="space-y-6 pb-12">
{(isEditing ? editedPath.steps : currentPath.steps).map((step, idx) => (
<GlassCard
key={idx}
className="relative p-0 overflow-hidden group hover:shadow-lg hover:-translate-y-1 transition-all duration-300"
>
{/* Progress Line */}
<div className="absolute left-8 top-0 bottom-0 w-0.5 bg-white/5 z-0"></div>
{/* Header */}
<div
className="relative z-10 p-6 cursor-pointer flex items-center justify-between"
onClick={() => setExpandedStep(expandedStep === idx ? null : idx)}
>
<div className="flex items-center gap-5">
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-primary to-green-400 text-white flex items-center justify-center font-bold text-lg shadow-lg shadow-primary/30 shrink-0">
{idx + 1}
</div>
<div>
<h4 className="font-bold text-lg text-white">{step.phase}</h4>
<span className="text-xs text-primary font-medium bg-primary/10 px-2 py-0.5 rounded-full border border-primary/20">
{step.time}
</span>
</div>
</div>
<ChevronDown
className={`w-5 h-5 text-gray-400 transition-transform duration-300 ${
expandedStep === idx ? 'rotate-180' : ''
}`}
/>
</div>
{/* Details (Collapsible) */}
<div
className={`relative z-10 px-6 pb-6 pt-0 border-t border-white/5 pl-[4.5rem] transition-all duration-300 overflow-hidden ${
expandedStep === idx ? 'max-h-[800px] opacity-100' : 'max-h-0 opacity-0 py-0'
}`}
>
<div className="space-y-5 pt-4">
{/* Core Content */}
<div>
<h5 className="text-xs font-bold text-gray-400 uppercase tracking-wider mb-2 flex items-center gap-1">
<Target className="w-3 h-3" /> 核心策略
</h5>
<div
contentEditable={isEditing}
onBlur={(e) => handleStepEdit(idx, 'content', e.currentTarget.textContent)}
className={`p-3 rounded-lg text-sm text-gray-300 leading-relaxed border transition-colors ${
isEditing
? 'bg-white/10 border-primary/50 ring-2 ring-primary/20'
: 'bg-white/5 border-white/5'
}`}
>
{step.content}
</div>
</div>
{/* Actions */}
<div>
<h5 className="text-xs font-bold text-blue-400 uppercase tracking-wider mb-2 flex items-center gap-1">
<Activity className="w-3 h-3" /> 关键行动
</h5>
<div
contentEditable={isEditing}
onBlur={(e) => handleStepEdit(idx, 'action', e.currentTarget.textContent)}
className={`p-3 rounded-lg text-sm text-gray-300 transition-colors ${
isEditing
? 'bg-white/10 border border-primary/50 ring-2 ring-primary/20'
: 'bg-blue-500/10 border border-blue-500/20'
}`}
>
{step.action}
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Resources */}
<div>
<h5 className="text-xs font-bold text-amber-400 uppercase tracking-wider mb-2 flex items-center gap-1">
<Package className="w-3 h-3" /> 所需资源
</h5>
<div
contentEditable={isEditing}
onBlur={(e) => handleStepEdit(idx, 'resources', e.currentTarget.textContent)}
className={`p-3 rounded-lg text-xs text-gray-400 leading-relaxed transition-colors ${
isEditing
? 'bg-white/10 border border-primary/50 ring-2 ring-primary/20'
: 'bg-amber-500/10 border border-amber-500/20'
}`}
>
{step.resources}
</div>
</div>
{/* Habits */}
<div>
<h5 className="text-xs font-bold text-green-400 uppercase tracking-wider mb-2 flex items-center gap-1">
<Repeat className="w-3 h-3" /> 养成习惯
</h5>
<div
contentEditable={isEditing}
onBlur={(e) => handleStepEdit(idx, 'habit', e.currentTarget.textContent)}
className={`p-3 rounded-lg text-xs text-gray-400 leading-relaxed transition-colors ${
isEditing
? 'bg-white/10 border border-primary/50 ring-2 ring-primary/20'
: 'bg-green-500/10 border border-green-500/20'
}`}
>
{step.habit}
</div>
</div>
</div>
</div>
</div>
</GlassCard>
))}
</div>
<div className="text-center text-gray-500 text-xs italic mt-8">
"路虽远,行则将至。"
</div>
</>
)}
</div>
</div>
);
}