添加用户资料点击逻辑,更新页面UI
This commit is contained in:
Generated
+11
@@ -64,6 +64,7 @@
|
||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.5",
|
||||
@@ -1399,6 +1400,7 @@
|
||||
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
@@ -1440,6 +1442,7 @@
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -1599,6 +1602,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
@@ -1912,6 +1916,7 @@
|
||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -3052,6 +3057,7 @@
|
||||
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -3078,6 +3084,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -3125,6 +3132,7 @@
|
||||
"resolved": "https://registry.npmmirror.com/react/-/react-19.2.3.tgz",
|
||||
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -3134,6 +3142,7 @@
|
||||
"resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
@@ -3399,6 +3408,7 @@
|
||||
"resolved": "https://registry.npmmirror.com/rolldown-vite/-/rolldown-vite-7.2.5.tgz",
|
||||
"integrity": "sha512-u09tdk/huMiN8xwoiBbig197jKdCamQTtOruSalOzbqGje3jdHiV0njQlAW0YvzoahkirFePNQ4RYlfnRQpXZA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@oxc-project/runtime": "0.97.0",
|
||||
"fdir": "^6.5.0",
|
||||
@@ -3521,6 +3531,7 @@
|
||||
"integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
+25
-3
@@ -10,9 +10,31 @@ function App() {
|
||||
// Logic is now in CurrentPage, App just provides layout
|
||||
return (
|
||||
<div className="relative min-h-screen font-sans text-gray-100 overflow-hidden">
|
||||
{/* Global Background */}
|
||||
<div className="fixed inset-0 -z-20 bg-gradient-to-br from-deep-sea via-[#1a3c46] to-[#155e55] opacity-100 transition-colors duration-1000"></div>
|
||||
<div className="fixed inset-0 -z-10 bg-noise opacity-20 brightness-100 contrast-150 pointer-events-none"></div>
|
||||
{/* Global Background with Gradient and Glow Effects - Similar to image */}
|
||||
<div className="fixed inset-0 -z-30">
|
||||
{/* Base Gradient Background - From blue (left) to dark brown (right) with blue gradient */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-[#1a1c2c] via-[#1a1c2c] via-[#1f1f2e] to-[#2d1b1b]"></div>
|
||||
{/* Blue gradient layer for left side */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-[#1e3a5f]/40 via-[#1a1c2c]/30 to-transparent"></div>
|
||||
{/* Additional gradient layer for smoother transition */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-[#1a1c2c]/75 via-[#1f1f2e]/55 to-[#2d1b1b]/75"></div>
|
||||
{/* Vertical gradient for depth */}
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-[#1a1c2c]/65 via-transparent to-[#2d1b1b]/55"></div>
|
||||
|
||||
{/* Glow Effects - Enhanced blue on left */}
|
||||
{/* Left side - Enhanced Blue glow */}
|
||||
<div className="absolute top-[-8%] left-[-8%] w-[60%] h-[60%] bg-blue-700/25 blur-[140px] rounded-full animate-float"></div>
|
||||
<div className="absolute bottom-[5%] left-[5%] w-[45%] h-[45%] bg-blue-800/20 blur-[110px] rounded-full animate-pulse-slow"></div>
|
||||
<div className="absolute top-[20%] left-[15%] w-[35%] h-[35%] bg-cyan-800/15 blur-[100px] rounded-full animate-float"></div>
|
||||
|
||||
{/* Right side - Reddish-orange glow (bottom right prominent like in image) */}
|
||||
<div className="absolute bottom-[-8%] right-[-8%] w-[55%] h-[55%] bg-orange-800/22 blur-[140px] rounded-full animate-float-delayed"></div>
|
||||
<div className="absolute top-[15%] right-[10%] w-[40%] h-[40%] bg-amber-800/16 blur-[120px] rounded-full animate-pulse-slow"></div>
|
||||
|
||||
{/* Central subtle glow */}
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[50%] h-[50%] bg-orange-900/12 blur-[160px] rounded-full"></div>
|
||||
</div>
|
||||
<div className="fixed inset-0 -z-10 bg-noise opacity-10 brightness-120 contrast-130 pointer-events-none"></div>
|
||||
|
||||
{/* Page Content */}
|
||||
<CurrentPage />
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { useStoreData } from '../hooks/useStoreData';
|
||||
import { Store } from '../utils/store';
|
||||
import { User, Settings, LogOut, X, Edit2 } from 'lucide-react';
|
||||
import { GlassCard } from './ui/GlassCard';
|
||||
import { Button } from './ui/Button';
|
||||
import clsx from 'clsx';
|
||||
|
||||
/**
|
||||
* 用户资料菜单组件
|
||||
* 显示用户信息、编辑资料和退出登录选项
|
||||
*/
|
||||
export function UserMenu({ isOpen, onClose, onLogout }) {
|
||||
const data = useStoreData();
|
||||
const [showEditModal, setShowEditModal] = useState(false);
|
||||
const menuRef = useRef(null);
|
||||
|
||||
// 每次打开菜单时,重置编辑模态框状态(模仿 PncyssD 的逻辑)
|
||||
// 确保每次打开菜单都显示主菜单界面,而不是编辑界面
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
// 菜单打开时,确保编辑模态框是关闭的
|
||||
setShowEditModal(false);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
// 点击外部关闭菜单
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
|
||||
const handleClickOutside = (event) => {
|
||||
if (menuRef.current && !menuRef.current.contains(event.target)) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, [isOpen, onClose]);
|
||||
|
||||
// 如果菜单未打开,不渲染任何内容(除非正在显示编辑模态框)
|
||||
if (!isOpen && !showEditModal) return null;
|
||||
|
||||
// 如果正在显示编辑模态框,只渲染编辑模态框
|
||||
if (showEditModal) {
|
||||
return (
|
||||
<EditProfileModal
|
||||
onClose={() => {
|
||||
setShowEditModal(false);
|
||||
// 编辑模态框关闭后,如果菜单是打开的,会显示主菜单
|
||||
}}
|
||||
userProfile={data.userProfile}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// 显示主菜单(isOpen 为 true 且 showEditModal 为 false)
|
||||
|
||||
// 显示主菜单
|
||||
return (
|
||||
<>
|
||||
{/* 菜单遮罩层 */}
|
||||
<div
|
||||
className="fixed inset-0 z-40"
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
{/* 用户菜单 */}
|
||||
<div
|
||||
ref={menuRef}
|
||||
className="fixed top-24 md:top-4 left-4 md:left-[300px] z-50 w-[calc(100%-2rem)] md:w-80 animate-fade-in"
|
||||
>
|
||||
<div className="bg-[#1a1c2c]/95 border border-white/20 shadow-2xl rounded-2xl p-6 space-y-6 backdrop-blur-sm">
|
||||
{/* 用户信息头部 */}
|
||||
<div className="flex items-center gap-4 pb-4 border-b border-white/10">
|
||||
<div className="w-16 h-16 rounded-2xl bg-gradient-to-br from-primary/30 to-blue-600/30 flex items-center justify-center text-2xl font-bold text-white border border-white/20 shadow-lg">
|
||||
{data.userProfile.nickname?.[0] || 'U'}
|
||||
</div>
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<div className="font-bold text-gray-100 text-lg truncate">
|
||||
{data.userProfile.nickname || '旅人'}
|
||||
</div>
|
||||
<div className="text-xs text-primary flex items-center gap-1.5 mt-1">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-primary shadow-[0_0_5px_rgba(205,133,63,0.5)] animate-pulse"></span>
|
||||
{data.userProfile.mbti || '未知'} · {data.userProfile.zodiac || '未知'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 统计数据 */}
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="p-4 bg-white/5 rounded-xl border border-white/5 text-center">
|
||||
<div className="text-2xl font-bold text-primary">
|
||||
{data.lifeTimeline?.length || 0}
|
||||
</div>
|
||||
<div className="text-[10px] text-white/40 uppercase tracking-widest mt-1">
|
||||
生命足迹
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4 bg-white/5 rounded-xl border border-white/5 text-center">
|
||||
<div className="text-2xl font-bold text-blue-400">
|
||||
{data.generatedScripts?.length || 0}
|
||||
</div>
|
||||
<div className="text-[10px] text-white/40 uppercase tracking-widest mt-1">
|
||||
剧本生成
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<div className="space-y-2 pt-2 border-t border-white/5">
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="md"
|
||||
className="w-full justify-start"
|
||||
onClick={() => {
|
||||
setShowEditModal(true);
|
||||
// 不关闭主菜单,让编辑模态框显示在主菜单之上
|
||||
// 这样关闭编辑模态框后,主菜单仍然可见
|
||||
}}
|
||||
>
|
||||
<Settings className="w-4 h-4 mr-2" />
|
||||
编辑资料
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="md"
|
||||
className="w-full justify-start text-red-400 hover:text-red-300 hover:bg-red-500/10"
|
||||
onClick={() => {
|
||||
if (window.confirm('确定要退出登录并清除所有数据吗?此操作不可逆。')) {
|
||||
Store.reset();
|
||||
if (onLogout) onLogout();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LogOut className="w-4 h-4 mr-2" />
|
||||
退出登录
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑资料模态框组件
|
||||
*/
|
||||
function EditProfileModal({ onClose, userProfile }) {
|
||||
const [formData, setFormData] = useState({
|
||||
nickname: userProfile.nickname || '',
|
||||
mbti: userProfile.mbti || '',
|
||||
zodiac: userProfile.zodiac || '',
|
||||
hobbies: userProfile.hobbies?.join(', ') || '',
|
||||
gender: userProfile.gender || 'secret'
|
||||
});
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
const handleSave = () => {
|
||||
setIsSaving(true);
|
||||
|
||||
// 更新用户资料
|
||||
Store.updateProfile({
|
||||
nickname: formData.nickname,
|
||||
mbti: formData.mbti,
|
||||
zodiac: formData.zodiac,
|
||||
hobbies: formData.hobbies.split(',').map(s => s.trim()).filter(s => s),
|
||||
gender: formData.gender
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
setIsSaving(false);
|
||||
onClose();
|
||||
}, 300);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black/60 backdrop-blur-xl">
|
||||
<GlassCard className="w-full max-w-lg p-8 relative max-h-[90vh] overflow-y-auto">
|
||||
{/* 关闭按钮 */}
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute top-6 right-6 text-white/40 hover:text-white transition-colors z-10"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
{/* 标题 */}
|
||||
<div className="flex items-center gap-4 mb-8">
|
||||
<div className="w-12 h-12 rounded-xl bg-primary/20 flex items-center justify-center">
|
||||
<Edit2 className="text-primary w-6 h-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-2xl font-bold text-gray-100">编辑资料</h3>
|
||||
<p className="text-xs text-gray-400 mt-1">调整你的人生航向基础信息</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 表单 */}
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-gray-300 mb-2 block">昵称</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.nickname}
|
||||
onChange={(e) => setFormData({ ...formData, nickname: e.target.value })}
|
||||
placeholder="你想被如何称呼?"
|
||||
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder-white/30 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary/50 transition-all"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-gray-300 mb-2 block">MBTI</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.mbti}
|
||||
onChange={(e) => setFormData({ ...formData, mbti: e.target.value })}
|
||||
placeholder="性格色彩"
|
||||
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder-white/30 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary/50 transition-all"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-gray-300 mb-2 block">星座</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.zodiac}
|
||||
onChange={(e) => setFormData({ ...formData, zodiac: e.target.value })}
|
||||
placeholder="星辰指引"
|
||||
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder-white/30 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary/50 transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-medium text-gray-300 mb-2 block">兴趣爱好</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.hobbies}
|
||||
onChange={(e) => setFormData({ ...formData, hobbies: e.target.value })}
|
||||
placeholder="让灵魂起舞的事物(用逗号分隔)"
|
||||
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder-white/30 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary/50 transition-all"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-medium text-gray-300 mb-2 block">性别</label>
|
||||
<select
|
||||
value={formData.gender}
|
||||
onChange={(e) => setFormData({ ...formData, gender: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary/50 transition-all"
|
||||
>
|
||||
<option value="secret">保密</option>
|
||||
<option value="male">男</option>
|
||||
<option value="female">女</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<div className="flex gap-4 mt-8 pt-6 border-t border-white/5">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="md"
|
||||
className="flex-1"
|
||||
onClick={handleSave}
|
||||
isLoading={isSaving}
|
||||
>
|
||||
保存修改
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="md"
|
||||
onClick={onClose}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ export function GlassCard({ children, className, ...props }) {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"bg-white/5 backdrop-blur-xl border border-white/10 shadow-2xl rounded-2xl transition-all duration-300",
|
||||
"bg-white/10 backdrop-blur-xl border border-white/15 shadow-2xl rounded-2xl transition-all duration-300",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -123,8 +123,8 @@ export function PathView({ onSwitchToScript }) {
|
||||
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">
|
||||
<h2 className="text-3xl font-bold text-gold-gradient mb-2 flex items-center gap-2">
|
||||
<span className="bg-primary/10 p-2 rounded-lg text-primary">
|
||||
<Map className="w-6 h-6 icon-glow" />
|
||||
</span>
|
||||
实现路径
|
||||
@@ -132,14 +132,14 @@ export function PathView({ onSwitchToScript }) {
|
||||
<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>
|
||||
<div className="p-16 text-center rounded-2xl border border-white/10 bg-[#1a1c2c]/40 backdrop-blur-sm">
|
||||
<Ghost className="w-16 h-16 mx-auto mb-4 opacity-20 text-gray-500" />
|
||||
<p className="mb-4 text-gray-400">你需要先有一个剧本,才能生成通往它的路径。</p>
|
||||
<button
|
||||
className="text-primary font-bold hover:underline flex items-center justify-center gap-1 mx-auto"
|
||||
className="text-primary font-bold hover:text-accent transition-colors flex items-center justify-center gap-1 mx-auto group"
|
||||
onClick={onSwitchToScript}
|
||||
>
|
||||
去生成剧本 <ArrowRight className="w-4 h-4" />
|
||||
去生成剧本 <ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -149,7 +149,7 @@ export function PathView({ onSwitchToScript }) {
|
||||
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">
|
||||
<h2 className="text-3xl font-bold text-gold-gradient 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>
|
||||
@@ -245,11 +245,11 @@ export function PathView({ onSwitchToScript }) {
|
||||
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">
|
||||
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-primary to-accent 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>
|
||||
<h4 className="font-bold text-lg text-beige">{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>
|
||||
@@ -277,7 +277,7 @@ export function PathView({ onSwitchToScript }) {
|
||||
<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 ${
|
||||
className={`p-3 rounded-lg text-sm text-beige/90 leading-relaxed border transition-colors ${
|
||||
isEditing
|
||||
? 'bg-white/10 border-primary/50 ring-2 ring-primary/20'
|
||||
: 'bg-white/5 border-white/5'
|
||||
@@ -325,7 +325,7 @@ export function PathView({ onSwitchToScript }) {
|
||||
</div>
|
||||
{/* Habits */}
|
||||
<div>
|
||||
<h5 className="text-xs font-bold text-green-400 uppercase tracking-wider mb-2 flex items-center gap-1">
|
||||
<h5 className="text-xs font-bold text-primary uppercase tracking-wider mb-2 flex items-center gap-1">
|
||||
<Repeat className="w-3 h-3" /> 养成习惯
|
||||
</h5>
|
||||
<div
|
||||
@@ -334,7 +334,7 @@ export function PathView({ onSwitchToScript }) {
|
||||
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'
|
||||
: 'bg-primary/10 border border-primary/20'
|
||||
}`}
|
||||
>
|
||||
{step.habit}
|
||||
|
||||
@@ -81,9 +81,9 @@ export function ScriptView({ onSwitchToPath }) {
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto md:p-8 h-full flex flex-col pb-20 md:pb-0">
|
||||
<header className="mb-8 px-4 md:px-0 pt-4 md:pt-0">
|
||||
<h2 className="text-3xl font-bold text-white mb-2 flex items-center gap-3">
|
||||
<h2 className="text-3xl font-bold text-gold-gradient mb-2 flex items-center gap-3">
|
||||
<span className="bg-primary/10 p-2 rounded-xl text-primary ring-1 ring-primary/20"><Film className="w-6 h-6 icon-glow" /></span>
|
||||
<span className="bg-clip-text text-transparent bg-gradient-to-r from-white to-gray-400">剧本生成器</span>
|
||||
<span className="bg-clip-text text-transparent bg-gradient-to-r from-white to-gray-400">爽文剧本</span>
|
||||
</h2>
|
||||
<p className="text-gray-400 ml-1">基于你的真实画像,编织平行时空的无限可能。</p>
|
||||
</header>
|
||||
@@ -108,7 +108,7 @@ export function ScriptView({ onSwitchToPath }) {
|
||||
{data.userProfile.nickname ? data.userProfile.nickname[0] : 'U'}
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-bold text-lg text-white">{data.userProfile.nickname || '未命名'}</div>
|
||||
<div className="font-bold text-lg text-beige">{data.userProfile.nickname || '未命名'}</div>
|
||||
<div className="text-xs text-primary flex items-center gap-2 mt-1">
|
||||
<span className="px-2 py-0.5 bg-primary/10 rounded-full border border-primary/20">{data.userProfile.mbti}</span>
|
||||
<span className="text-gray-400">{data.userProfile.zodiac || '星辰'}</span>
|
||||
@@ -116,12 +116,12 @@ export function ScriptView({ onSwitchToPath }) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className="text-xs text-gray-300 bg-black/20 p-3 rounded-xl border border-white/5">
|
||||
<span className="text-primary font-bold block mb-1">天赋</span>
|
||||
<div className="text-xs text-beige/90 bg-black/20 p-3 rounded-xl border border-white/5">
|
||||
<span className="text-gold-gradient font-bold block mb-1">天赋</span>
|
||||
{(data.userProfile.hobbies || []).join(' / ') || '暂无'}
|
||||
</div>
|
||||
<div className="text-xs text-gray-300 bg-black/20 p-3 rounded-xl border border-white/5">
|
||||
<span className="text-primary font-bold block mb-1">愿景</span>
|
||||
<div className="text-xs text-beige/90 bg-black/20 p-3 rounded-xl border border-white/5">
|
||||
<span className="text-gold-gradient font-bold block mb-1">愿景</span>
|
||||
<span className="line-clamp-2">{data.userProfile.futureVision || '暂无'}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -130,7 +130,7 @@ export function ScriptView({ onSwitchToPath }) {
|
||||
|
||||
{/* Input Form */}
|
||||
<Card className="p-5">
|
||||
<label className="block text-sm font-bold text-gray-300 mb-2 flex items-center gap-2">
|
||||
<label className="block text-sm font-bold text-beige/90 mb-2 flex items-center gap-2">
|
||||
<span className="w-5 h-5 rounded-full bg-white/10 flex items-center justify-center text-xs">1</span>
|
||||
你的渴望主题
|
||||
</label>
|
||||
@@ -170,7 +170,7 @@ export function ScriptView({ onSwitchToPath }) {
|
||||
variant="primary"
|
||||
onClick={handleGenerate}
|
||||
disabled={loading}
|
||||
className="w-full py-3 shadow-[0_0_20px_rgba(16,185,129,0.2)] flex justify-center items-center gap-2 group"
|
||||
className="w-full py-3 shadow-[0_0_20px_rgba(205,133,63,0.2)] flex justify-center items-center gap-2 group"
|
||||
>
|
||||
{loading ? <><Loader className="animate-spin w-4 h-4" /> 编织命运中...</> : <><Sparkles className="w-4 h-4 group-hover:rotate-12 transition-transform" /> 生成平行人生</>}
|
||||
</Button>
|
||||
@@ -197,11 +197,11 @@ export function ScriptView({ onSwitchToPath }) {
|
||||
className={clsx(
|
||||
"group relative p-3 rounded-xl cursor-pointer transition-all border",
|
||||
selectedScriptId === s.id
|
||||
? "bg-primary/10 border-primary/30 shadow-[inset_0_0_10px_rgba(16,185,129,0.05)]"
|
||||
? "bg-primary/10 border-primary/30 shadow-[inset_0_0_10px_rgba(205,133,63,0.05)]"
|
||||
: "bg-transparent border-transparent hover:bg-white/5 hover:border-white/10"
|
||||
)}
|
||||
>
|
||||
<div className={clsx("font-bold text-sm truncate mb-1 pr-6 transition-colors", selectedScriptId === s.id ? "text-primary" : "text-gray-300 group-hover:text-white")}>
|
||||
<div className={clsx("font-bold text-sm truncate mb-1 pr-6 transition-colors", selectedScriptId === s.id ? "text-primary" : "text-beige/90 group-hover:text-beige")}>
|
||||
{s.title}
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
@@ -233,8 +233,8 @@ export function ScriptView({ onSwitchToPath }) {
|
||||
<div className="w-24 h-24 rounded-full bg-white/5 flex items-center justify-center mb-6 animate-pulse-slow ring-1 ring-white/10">
|
||||
<Film className="w-10 h-10 opacity-40" />
|
||||
</div>
|
||||
<p className="text-xl mb-2 font-medium text-gray-300">舞台已就绪</p>
|
||||
<p className="text-sm opacity-60">请在左侧输入设定,生成你的平行人生。</p>
|
||||
<p className="text-xl mb-2 font-medium text-beige/90">舞台已就绪</p>
|
||||
<p className="text-sm opacity-60">请在左侧设定需求,开启你的爽文人生。</p>
|
||||
</div>
|
||||
) : (
|
||||
<Card className="h-full overflow-hidden flex flex-col relative group bg-black/20 backdrop-blur-xl border-white/10">
|
||||
@@ -251,11 +251,11 @@ export function ScriptView({ onSwitchToPath }) {
|
||||
|
||||
<div className="flex-1 overflow-y-auto custom-scrollbar p-6 md:p-12">
|
||||
<div className="text-center mb-12 border-b border-white/10 pb-8">
|
||||
<span className="inline-flex items-center gap-1 px-3 py-1 bg-gradient-to-r from-primary/20 to-accent/20 text-primary text-xs font-bold rounded-full mb-6 tracking-widest uppercase border border-primary/20 shadow-[0_0_10px_rgba(16,185,129,0.1)]">
|
||||
<span className="inline-flex items-center gap-1 px-3 py-1 bg-gradient-to-r from-primary/20 via-accent/20 to-aurora-green/20 text-gold-gradient text-xs font-bold rounded-full mb-6 tracking-widest uppercase border border-primary/20 shadow-[0_0_10px_rgba(205,133,63,0.1)]">
|
||||
<Stars className="w-3 h-3" />
|
||||
{selectedScript.style === 'career' ? '职场逆袭' : selectedScript.style === 'love' ? '情感圆满' : '玄幻觉醒'}
|
||||
</span>
|
||||
<h2 className="text-3xl md:text-5xl font-bold text-white leading-tight mb-4 tracking-tight drop-shadow-lg">{selectedScript.title}</h2>
|
||||
<h2 className="text-3xl md:text-5xl font-bold text-gold-gradient leading-tight mb-4 tracking-tight drop-shadow-lg">{selectedScript.title}</h2>
|
||||
<div className="text-sm text-gray-500 flex items-center justify-center gap-2">
|
||||
<span>生成于 {new Date(selectedScript.createdAt).toLocaleDateString()}</span>
|
||||
<span>·</span>
|
||||
@@ -275,7 +275,7 @@ export function ScriptView({ onSwitchToPath }) {
|
||||
<div className="w-10 h-10 rounded-full bg-white/10 text-primary font-bold flex items-center justify-center border border-white/10 shadow-[0_0_10px_rgba(0,0,0,0.2)] z-10 shrink-0 text-lg font-serif">1</div>
|
||||
<h4 className="font-bold text-gray-100 text-xl group-hover/chapter:text-primary transition-colors">序幕:低谷回响</h4>
|
||||
</div>
|
||||
<div className="md:pl-14 text-gray-300 leading-loose text-justify text-lg font-light tracking-wide">
|
||||
<div className="md:pl-14 text-beige/90 leading-loose text-justify text-lg font-light tracking-wide">
|
||||
{selectedScript.plot.intro}
|
||||
</div>
|
||||
</div>
|
||||
@@ -283,11 +283,11 @@ export function ScriptView({ onSwitchToPath }) {
|
||||
{/* Chapter 2 */}
|
||||
<div className="mb-12 relative group/chapter">
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<div className="w-10 h-10 rounded-full bg-primary/20 text-primary font-bold flex items-center justify-center border border-primary/20 shadow-[0_0_15px_rgba(16,185,129,0.1)] z-10 shrink-0 text-lg font-serif">2</div>
|
||||
<h4 className="font-bold text-white text-xl group-hover/chapter:text-primary transition-colors">转折:契机出现</h4>
|
||||
<div className="w-10 h-10 rounded-full bg-primary/20 text-primary font-bold flex items-center justify-center border border-primary/20 shadow-[0_0_15px_rgba(205,133,63,0.1)] z-10 shrink-0 text-lg font-serif">2</div>
|
||||
<h4 className="font-bold text-beige text-xl group-hover/chapter:text-primary transition-colors">转折:契机出现</h4>
|
||||
</div>
|
||||
<div className="md:pl-14">
|
||||
<div className="text-gray-200 leading-loose text-justify p-6 bg-gradient-to-b from-white/5 to-transparent rounded-2xl border border-white/5 text-lg font-light tracking-wide shadow-inner">
|
||||
<div className="text-beige/95 leading-loose text-justify p-6 bg-gradient-to-b from-white/5 to-transparent rounded-2xl border border-white/5 text-lg font-light tracking-wide shadow-inner">
|
||||
{selectedScript.plot.turning}
|
||||
</div>
|
||||
</div>
|
||||
@@ -297,9 +297,9 @@ export function ScriptView({ onSwitchToPath }) {
|
||||
<div className="mb-12 relative group/chapter">
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<div className="w-10 h-10 rounded-full bg-accent/20 text-accent font-bold flex items-center justify-center border border-accent/20 shadow-[0_0_15px_rgba(255,165,0,0.1)] z-10 shrink-0 text-lg font-serif">3</div>
|
||||
<h4 className="font-bold text-white text-xl group-hover/chapter:text-accent transition-colors">高潮:命运抉择</h4>
|
||||
<h4 className="font-bold text-beige text-xl group-hover/chapter:text-accent transition-colors">高潮:命运抉择</h4>
|
||||
</div>
|
||||
<div className="md:pl-14 text-gray-300 leading-loose text-justify text-lg font-light tracking-wide">
|
||||
<div className="md:pl-14 text-beige/90 leading-loose text-justify text-lg font-light tracking-wide">
|
||||
{selectedScript.plot.climax}
|
||||
</div>
|
||||
</div>
|
||||
@@ -310,7 +310,7 @@ export function ScriptView({ onSwitchToPath }) {
|
||||
<div className="w-10 h-10 rounded-full bg-white/10 text-gray-400 font-bold flex items-center justify-center border border-white/10 z-10 shrink-0 text-lg font-serif">4</div>
|
||||
<h4 className="font-bold text-gray-100 text-xl group-hover/chapter:text-gray-300 transition-colors">结局:新的开始</h4>
|
||||
</div>
|
||||
<div className="md:pl-14 text-gray-300 leading-loose text-justify text-lg font-light tracking-wide">
|
||||
<div className="md:pl-14 text-beige/90 leading-loose text-justify text-lg font-light tracking-wide">
|
||||
{selectedScript.plot.ending}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -44,11 +44,11 @@ export function TimelineView() {
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto md:p-12 pb-24">
|
||||
<header className="mb-8 md:mb-12">
|
||||
<h2 className="text-3xl font-bold text-white mb-2 flex items-center gap-2">
|
||||
<h2 className="text-3xl font-bold text-gold-gradient mb-2 flex items-center gap-2">
|
||||
<span className="bg-primary/20 p-2 rounded-lg text-primary"><BookHeart className="w-6 h-6 icon-glow" /></span>
|
||||
时空日记
|
||||
人生轨迹
|
||||
</h2>
|
||||
<p className="text-gray-400 text-sm md:text-base">记录每一个当下,让AI为你照见未来。</p>
|
||||
<p className="text-gray-400 text-sm md:text-base">塑造你的每一刻,都被星辰见证。</p>
|
||||
</header>
|
||||
|
||||
<Card className="p-5 md:p-8 mb-12 relative overflow-hidden group hover:shadow-xl border-l-4 border-primary">
|
||||
@@ -83,7 +83,7 @@ export function TimelineView() {
|
||||
{events.length === 0 ? (
|
||||
<div className="text-center text-gray-400 py-20 bg-white/5 rounded-3xl border border-dashed border-white/10 relative z-10">
|
||||
<PenTool className="w-12 h-12 mx-auto mb-4 opacity-30 text-primary" />
|
||||
<p>暂无记录,写下你的第一篇日记吧。</p>
|
||||
<p>执笔写下,哪些经历让你成为了现在的自己。</p>
|
||||
</div>
|
||||
) : (
|
||||
events.map((item, index) => (
|
||||
@@ -99,16 +99,16 @@ export function TimelineView() {
|
||||
</div>
|
||||
|
||||
{/* Center Dot */}
|
||||
<div className="absolute left-6 md:left-1/2 -translate-x-1/2 w-4 h-4 rounded-full border-4 border-deep-sea bg-primary z-20 shadow-[0_0_15px_rgba(42,157,143,0.5)] group-hover:scale-125 transition-transform order-2"></div>
|
||||
<div className="absolute left-6 md:left-1/2 -translate-x-1/2 w-4 h-4 rounded-full border-4 border-deep-sea bg-primary z-20 shadow-[0_0_15px_rgba(205,133,63,0.5)] group-hover:scale-125 transition-transform order-2"></div>
|
||||
|
||||
{/* Content Card */}
|
||||
<div className={`md:w-5/12 pl-12 md:pl-0 order-3 ${index % 2 === 1 ? 'md:order-1 md:text-right md:pr-10' : 'md:pl-10'}`}>
|
||||
<Card className="p-5 hover:-translate-y-1 transition-transform relative overflow-hidden">
|
||||
<div className="absolute top-0 right-0 p-3 opacity-10">
|
||||
<Quote className="w-8 h-8 text-white" />
|
||||
<Quote className="w-8 h-8 text-beige" />
|
||||
</div>
|
||||
<h3 className="font-bold text-lg text-white mb-2">{item.title}</h3>
|
||||
<p className="text-gray-300 text-sm leading-relaxed mb-4">{item.content}</p>
|
||||
<h3 className="font-bold text-lg text-beige mb-2">{item.title}</h3>
|
||||
<p className="text-beige/90 text-sm leading-relaxed mb-4">{item.content}</p>
|
||||
|
||||
{item.aiReply && (
|
||||
<div className="bg-primary/10 rounded-xl p-3 text-sm text-primary/90 flex gap-3 border border-primary/10">
|
||||
|
||||
+38
-16
@@ -1,11 +1,16 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
--color-primary: #2A9D8F;
|
||||
--color-secondary: #264653;
|
||||
--color-accent: #E9C46A;
|
||||
--color-aurora-green: #4CC9F0;
|
||||
--color-deep-sea: #0f1c2e;
|
||||
--color-primary: #CD853F;
|
||||
--color-secondary: #0a0c10;
|
||||
--color-accent: #DAA520;
|
||||
--color-aurora-green: #FFD700;
|
||||
--color-deep-sea: #1a1c2c;
|
||||
--color-text-beige: #E8D5B7;
|
||||
--color-text-warm: #E8D5B7;
|
||||
--color-gold-dark: #CD853F;
|
||||
--color-gold-medium: #DAA520;
|
||||
--color-gold-light: #FFD700;
|
||||
|
||||
--font-sans: "Noto Sans SC", sans-serif;
|
||||
|
||||
@@ -30,9 +35,12 @@
|
||||
100% { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-10px); }
|
||||
0%, 100% { transform: translate(0, 0) scale(1); }
|
||||
50% { transform: translate(5%, 5%) scale(1.1); }
|
||||
}
|
||||
|
||||
.animate-float { animation: float 15s infinite ease-in-out; }
|
||||
.animate-float-delayed { animation: float 20s infinite ease-in-out reverse; }
|
||||
@keyframes breathe {
|
||||
0%, 100% { opacity: 0.8; transform: scale(1); }
|
||||
50% { opacity: 1; transform: scale(1.02); }
|
||||
@@ -42,9 +50,9 @@
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
@keyframes pulse-ring {
|
||||
0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(42, 157, 143, 0.7); }
|
||||
70% { transform: scale(1); box-shadow: 0 0 0 10px rgba(42, 157, 143, 0); }
|
||||
100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(42, 157, 143, 0); }
|
||||
0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(255, 171, 145, 0.7); }
|
||||
70% { transform: scale(1); box-shadow: 0 0 0 10px rgba(255, 171, 145, 0); }
|
||||
100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(255, 171, 145, 0); }
|
||||
}
|
||||
@keyframes audio-wave {
|
||||
0%, 100% { height: 5px; }
|
||||
@@ -55,7 +63,7 @@
|
||||
body {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
font-family: 'Noto Sans SC', sans-serif;
|
||||
@apply bg-deep-sea text-gray-800 overflow-x-hidden selection:bg-primary selection:text-white;
|
||||
@apply bg-deep-sea text-gray-100 overflow-x-hidden selection:bg-primary/30 selection:text-white;
|
||||
}
|
||||
|
||||
/* Background Noise Utility */
|
||||
@@ -76,14 +84,14 @@ body {
|
||||
border-radius: 4px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(42, 157, 143, 0.6);
|
||||
background: rgba(255, 171, 145, 0.6);
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(42, 157, 143, 0.4);
|
||||
background: rgba(255, 171, 145, 0.4);
|
||||
}
|
||||
|
||||
/* Transitions */
|
||||
@@ -128,8 +136,8 @@ body {
|
||||
}
|
||||
|
||||
.input-field:focus {
|
||||
border-color: #2A9D8F;
|
||||
box-shadow: 0 0 0 4px rgba(42, 157, 143, 0.15);
|
||||
border-color: #CD853F;
|
||||
box-shadow: 0 0 0 4px rgba(205, 133, 63, 0.15);
|
||||
background: white;
|
||||
}
|
||||
|
||||
@@ -140,7 +148,7 @@ body {
|
||||
}
|
||||
.input-field-dark:focus {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-color: #2A9D8F;
|
||||
border-color: #CD853F;
|
||||
}
|
||||
|
||||
select.input-field {
|
||||
@@ -172,6 +180,20 @@ select.input-field-dark option {
|
||||
.audio-waves span:nth-child(3) { animation-delay: 0.2s; }
|
||||
.audio-waves span:nth-child(4) { animation-delay: 0.3s; }
|
||||
|
||||
/* Text Beige Color Utility - 浅米色文字 */
|
||||
.text-beige {
|
||||
color: #E8D5B7;
|
||||
}
|
||||
|
||||
/* Advanced Gold Gradient Text - 高级金色渐变文字(更白亮、更明显、降低饱和度) */
|
||||
.text-gold-gradient {
|
||||
background: linear-gradient(135deg, #E8D5B7 0%, #F4D03F 30%, #FFD700 60%, #FFF8DC 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
filter: brightness(1.1) saturate(0.9);
|
||||
}
|
||||
|
||||
/* Utilities */
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { AudioEngine } from '../utils/audioEngine';
|
||||
import { TimelineView } from '../components/views/TimelineView';
|
||||
import { ScriptView } from '../components/views/ScriptView';
|
||||
import { PathView } from '../components/views/PathView';
|
||||
import { UserMenu } from '../components/UserMenu';
|
||||
import {
|
||||
Compass,
|
||||
BookOpen,
|
||||
@@ -24,6 +25,7 @@ export function DashboardPage() {
|
||||
const [activeTab, setActiveTab] = useState('timeline');
|
||||
const [isMusicPlaying, setIsMusicPlaying] = useState(false);
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
const [isUserMenuOpen, setIsUserMenuOpen] = useState(false);
|
||||
|
||||
// Initialize Audio Engine state based on store
|
||||
useEffect(() => {
|
||||
@@ -55,32 +57,40 @@ export function DashboardPage() {
|
||||
className={clsx(
|
||||
"w-full text-left px-4 py-3 rounded-xl flex items-center gap-3 transition-all duration-300 relative overflow-hidden group",
|
||||
activeTab === tab
|
||||
? "bg-primary/20 text-primary font-bold shadow-[0_0_15px_rgba(16,185,129,0.1)] border border-primary/20"
|
||||
: "text-gray-400 hover:text-white hover:bg-white/5 border border-transparent"
|
||||
? "bg-primary/20 text-primary font-bold shadow-[0_0_15px_rgba(205,133,63,0.1)] border border-primary/20"
|
||||
: "text-gray-400 hover:text-primary hover:bg-white/5 border border-transparent"
|
||||
)}
|
||||
>
|
||||
<Icon className={clsx("w-5 h-5 transition-transform", activeTab === tab ? "text-primary" : "text-gray-400 group-hover:text-white", activeTab !== tab && "group-hover:scale-110")} />
|
||||
<Icon className={clsx("w-5 h-5 transition-transform", activeTab === tab ? "text-primary" : "text-gray-400 group-hover:text-primary", activeTab !== tab && "group-hover:scale-110")} />
|
||||
<span className="hidden md:inline">{label}</span>
|
||||
<span className="md:hidden">{mobileLabel || label}</span>
|
||||
{activeTab === tab && (
|
||||
<div className="absolute right-0 top-0 bottom-0 w-1 bg-primary shadow-[0_0_10px_#10b981]"></div>
|
||||
<div className="absolute right-0 top-0 bottom-0 w-1 bg-primary shadow-[0_0_10px_rgba(205,133,63,0.8)]"></div>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col md:flex-row transition-all duration-500 font-sans text-gray-100 bg-deep-sea overflow-hidden">
|
||||
{/* Ambient Background */}
|
||||
{/* Ambient Background - Additional Layer for Dashboard with Blue Gradient */}
|
||||
<div className="fixed inset-0 pointer-events-none z-0">
|
||||
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_50%_50%,rgba(16,185,129,0.05),transparent_50%)]"></div>
|
||||
<div className="absolute top-[-20%] left-[-10%] w-[50%] h-[50%] bg-primary/5 rounded-full blur-[120px] animate-pulse-slow"></div>
|
||||
{/* Blue gradient overlay on left */}
|
||||
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_20%_50%,rgba(30,58,95,0.15),transparent_60%)]"></div>
|
||||
{/* Subtle radial gradient overlay */}
|
||||
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_30%_50%,rgba(205,133,63,0.08),transparent_70%)]"></div>
|
||||
<div className="absolute top-0 right-0 w-full h-full bg-[radial-gradient(circle_at_70%_50%,rgba(255,140,0,0.06),transparent_65%)]"></div>
|
||||
{/* Blue glow effects on left */}
|
||||
<div className="absolute top-[20%] left-[15%] w-[40%] h-[40%] bg-blue-700/10 rounded-full blur-[120px] animate-pulse-slow"></div>
|
||||
<div className="absolute bottom-[20%] left-[10%] w-[35%] h-[35%] bg-cyan-800/8 rounded-full blur-[100px] animate-float"></div>
|
||||
{/* Subtle glow effects on right */}
|
||||
<div className="absolute top-[25%] right-[25%] w-[35%] h-[35%] bg-orange-800/8 rounded-full blur-[120px] animate-pulse-slow"></div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Header */}
|
||||
<div className="md:hidden bg-black/20 backdrop-blur-xl border-b border-white/10 p-4 flex justify-between items-center z-50 relative">
|
||||
<div className="md:hidden bg-black/15 backdrop-blur-xl border-b border-white/10 p-4 flex justify-between items-center z-50 relative">
|
||||
<div className="flex items-center gap-2 font-bold text-lg">
|
||||
<Compass className="text-primary w-6 h-6 animate-spin-slow" />
|
||||
<span className="bg-clip-text text-transparent bg-gradient-to-r from-primary to-accent">人生轨迹</span>
|
||||
<span className="text-gold-gradient">人生OS</span>
|
||||
</div>
|
||||
<button onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}>
|
||||
{isMobileMenuOpen ? <X className="text-white" /> : <Menu className="text-white" />}
|
||||
@@ -89,47 +99,50 @@ export function DashboardPage() {
|
||||
|
||||
{/* Sidebar (Desktop) / Drawer (Mobile) */}
|
||||
<nav className={clsx(
|
||||
"bg-black/20 backdrop-blur-xl border-r border-white/10 w-full md:w-72 flex-shrink-0 flex flex-col justify-between z-40 fixed md:relative h-[calc(100vh-64px)] md:h-screen top-16 md:top-0 left-0 transition-transform duration-300",
|
||||
"bg-black/15 backdrop-blur-xl border-r border-white/10 w-full md:w-72 flex-shrink-0 flex flex-col justify-between z-40 fixed md:relative h-[calc(100vh-64px)] md:h-screen top-16 md:top-0 left-0 transition-transform duration-300",
|
||||
isMobileMenuOpen ? "translate-x-0" : "-translate-x-full md:translate-x-0"
|
||||
)}>
|
||||
{/* Background Decoration */}
|
||||
<div className="absolute top-0 left-0 w-full h-full bg-gradient-to-b from-transparent to-primary/5 pointer-events-none"></div>
|
||||
|
||||
<div className="p-6 overflow-y-auto relative z-10">
|
||||
<h1 className="hidden md:flex text-2xl font-bold tracking-wider mb-8 items-center gap-3 text-transparent bg-clip-text bg-gradient-to-r from-primary to-white">
|
||||
<Compass className="text-primary stroke-2 animate-spin-slow" /> 人生轨迹
|
||||
<h1 className="hidden md:flex text-2xl font-bold tracking-wider mb-8 items-center gap-3 text-gold-gradient">
|
||||
<Compass className="text-primary stroke-2 animate-spin-slow" /> 人生OS
|
||||
</h1>
|
||||
|
||||
{/* User Card */}
|
||||
<div className="flex items-center gap-4 mb-8 p-4 bg-white/5 rounded-2xl backdrop-blur-sm border border-white/10 hover:bg-white/10 transition-colors cursor-default group">
|
||||
<button
|
||||
onClick={() => setIsUserMenuOpen(!isUserMenuOpen)}
|
||||
className="flex items-center gap-4 mb-8 p-4 bg-white/5 rounded-2xl backdrop-blur-sm border border-white/10 hover:bg-white/10 transition-colors cursor-pointer group w-full text-left"
|
||||
>
|
||||
<div className="w-12 h-12 rounded-full bg-gradient-to-br from-primary to-blue-600 flex items-center justify-center text-white font-bold text-xl shadow-inner relative overflow-hidden group-hover:scale-105 transition-transform shrink-0 border border-white/20">
|
||||
{data.userProfile.nickname?.[0] || 'U'}
|
||||
<div className="absolute inset-0 bg-white/20 opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
||||
</div>
|
||||
<div className="overflow-hidden">
|
||||
<div className="font-bold text-white truncate">{data.userProfile.nickname || '旅人'}</div>
|
||||
<div className="overflow-hidden flex-1">
|
||||
<div className="font-bold text-gray-100 truncate">{data.userProfile.nickname || '旅人'}</div>
|
||||
<div className="text-xs text-primary flex items-center gap-1.5 mt-0.5">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-primary shadow-[0_0_5px_rgba(16,185,129,0.5)] animate-pulse"></span>
|
||||
{data.userProfile.mbti} · {data.userProfile.zodiac || '未知'}
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-primary shadow-[0_0_5px_rgba(205,133,63,0.5)] animate-pulse"></span>
|
||||
{data.userProfile.mbti || '未知'} · {data.userProfile.zodiac || '未知'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* Navigation */}
|
||||
<div className="space-y-2">
|
||||
<NavButton tab="timeline" icon={BookOpen} label="时空日记" mobileLabel="日记" />
|
||||
<NavButton tab="script" icon={Film} label="剧本生成器" mobileLabel="剧本" />
|
||||
<NavButton tab="timeline" icon={BookOpen} label="人生轨迹" mobileLabel="轨迹" />
|
||||
<NavButton tab="script" icon={Film} label="爽文剧本" mobileLabel="剧本" />
|
||||
<NavButton tab="path" icon={Map} label="实现路径" mobileLabel="路径" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-6 text-xs text-gray-500 border-t border-white/5 space-y-4 bg-black/40 backdrop-blur-md md:bg-transparent relative z-10">
|
||||
<div className="p-6 text-xs text-gray-500 border-t border-white/5 space-y-4 bg-black/20 backdrop-blur-md md:bg-transparent relative z-10">
|
||||
{/* Music Player */}
|
||||
<button
|
||||
onClick={handleMusicToggle}
|
||||
className={clsx(
|
||||
"flex items-center justify-between w-full px-4 py-3 rounded-xl bg-white/5 hover:bg-white/10 transition-all border group",
|
||||
isMusicPlaying ? "border-primary/30 shadow-[0_0_10px_rgba(16,185,129,0.1)]" : "border-white/5"
|
||||
isMusicPlaying ? "border-primary/30 shadow-[0_0_10px_rgba(205,133,63,0.1)]" : "border-white/5"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-3 text-gray-300">
|
||||
@@ -163,6 +176,16 @@ export function DashboardPage() {
|
||||
{activeTab === 'script' && <ScriptView onSwitchToPath={() => setActiveTab('path')} />}
|
||||
{activeTab === 'path' && <PathView onSwitchToScript={() => setActiveTab('script')} />}
|
||||
</main>
|
||||
|
||||
{/* User Menu */}
|
||||
<UserMenu
|
||||
isOpen={isUserMenuOpen}
|
||||
onClose={() => setIsUserMenuOpen(false)}
|
||||
onLogout={() => {
|
||||
// 退出登录后,App.jsx 会检测到 onboardingComplete 变为 false,自动跳转到登录页
|
||||
window.location.reload();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ export function LandingPage({ onStart }) {
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-6xl font-bold text-white tracking-tight landing-title">
|
||||
人生轨迹
|
||||
人生OS
|
||||
<span className="block text-xl md:text-2xl font-light mt-4 text-primary/80">Life Trajectory</span>
|
||||
</h1>
|
||||
|
||||
@@ -113,7 +113,7 @@ export function LandingPage({ onStart }) {
|
||||
variant="primary"
|
||||
size="lg"
|
||||
onClick={() => setShowLoginModal(true)}
|
||||
className="w-full shadow-[0_0_20px_rgba(16,185,129,0.3)] hover:shadow-[0_0_30px_rgba(16,185,129,0.5)] transition-all"
|
||||
className="w-full shadow-[0_0_20px_rgba(205,133,63,0.3)] hover:shadow-[0_0_30px_rgba(205,133,63,0.5)] transition-all"
|
||||
>
|
||||
登录账号
|
||||
</Button>
|
||||
|
||||
@@ -171,7 +171,7 @@ export function OnboardingPage({ onFinish }) {
|
||||
{[0,1,2,3,4].map(i => (
|
||||
<div key={i} className={clsx(
|
||||
"h-1 rounded-full transition-all duration-500",
|
||||
i <= step ? 'w-8 bg-primary shadow-[0_0_10px_rgba(42,157,143,0.5)]' : 'w-2 bg-white/10'
|
||||
i <= step ? 'w-8 bg-primary shadow-[0_0_10px_rgba(205,133,63,0.5)]' : 'w-2 bg-white/10'
|
||||
)}></div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,8 @@ export default defineConfig({
|
||||
server: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:19089',
|
||||
// target: 'http://localhost:19089',
|
||||
target: 'http://101.200.208.45:19089',
|
||||
changeOrigin: true,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user