添加用户资料点击逻辑,更新页面UI

This commit is contained in:
Conner.G
2025-12-21 20:31:42 +08:00
parent 6b9b74c9e4
commit fa57938a9d
12 changed files with 451 additions and 89 deletions
+283
View File
@@ -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>
);
}