521 lines
27 KiB
TypeScript
521 lines
27 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
||
import { useNavigate } from 'react-router-dom';
|
||
import { useAuth } from '../../contexts/AuthContext';
|
||
import { supabase } from '../../supabase/client';
|
||
import { motion } from 'framer-motion';
|
||
import { Import, Warehouse, CreditCard, ArrowLeft, Users, Droplets, Package, CheckCircle, Clock, LogOut, HelpCircle, X, BookOpen, MessageSquare, Info, Camera, FileText, Plus, TrendingUp, Building2 } from 'lucide-react';
|
||
import { useResponsive } from '../../hooks/useResponsive';
|
||
import { OnboardingGuide, shouldShowOnboarding } from '../../components/OnboardingGuide';
|
||
import { NotificationCenter } from '../../components/NotificationCenter';
|
||
import { HelpCenter, HelpButton } from '../../components/HelpCenter';
|
||
import { VersionUpdate, useVersionCheck, AboutModal } from '../../components/VersionUpdate';
|
||
import { FeedbackModal } from '../../components/FeedbackModal';
|
||
import { UserManual } from '../../components/UserManual';
|
||
import { CrashReporter } from '../../components/CrashReporter';
|
||
import type { WashingPlan } from '../../types';
|
||
|
||
const statusMap: Record<string, { label: string; color: string; bgColor: string; icon: any }> = {
|
||
pending: { label: '待处理', color: 'text-amber-600', bgColor: 'bg-amber-50', icon: Clock },
|
||
processing: { label: '处理中', color: 'text-violet-500', bgColor: 'bg-violet-50', icon: TrendingUp },
|
||
completed: { label: '已完成', color: 'text-emerald-600', bgColor: 'bg-emerald-50', icon: Package }
|
||
};
|
||
|
||
const getQuickActions = (isMaster: boolean) => {
|
||
const actions = [
|
||
{ id: 'plans', icon: FileText, label: '计划总览', path: '/washing/plans', gradient: 'from-violet-400 to-fuchsia-400', description: '查看所有计划' },
|
||
{ id: 'pending', icon: Package, label: '待处理坯布', path: '/washing/pending', gradient: 'from-amber-500 to-orange-500', description: '处理待水洗坯布' },
|
||
{ id: 'completed', icon: CheckCircle, label: '已完成坯布', path: '/washing/completed', gradient: 'from-emerald-500 to-green-500', description: '查看已完成水洗' },
|
||
{ id: 'finished', icon: Warehouse, label: '成品仓库', path: '/washing/finished-warehouse', gradient: 'from-violet-400 to-fuchsia-400', description: '管理成品库存' },
|
||
{ id: 'payment', icon: CreditCard, label: '待结款', path: '/washing/payments', gradient: 'from-rose-500 to-pink-500', description: '查看待结款项' }
|
||
];
|
||
if (isMaster) {
|
||
actions.push({ id: 'members', icon: Users, label: '账号管理', path: '/members', gradient: 'from-fuchsia-400 to-purple-400', description: '管理团队账号' });
|
||
}
|
||
return actions;
|
||
};
|
||
|
||
const containerVariants = {
|
||
hidden: { opacity: 0 },
|
||
visible: { opacity: 1, transition: { staggerChildren: 0.1 } }
|
||
};
|
||
|
||
const itemVariants = {
|
||
hidden: { opacity: 0, y: 20 },
|
||
visible: { opacity: 1, y: 0, transition: { duration: 0.4 } }
|
||
};
|
||
|
||
export function WashingDashboard() {
|
||
const navigate = useNavigate();
|
||
const { auth, logout } = useAuth();
|
||
const { isMobile } = useResponsive();
|
||
const [plans, setPlans] = useState<WashingPlan[]>([]);
|
||
const [loading, setLoading] = useState(true);
|
||
|
||
// 帮助中心状态
|
||
const [showHelp, setShowHelp] = useState(false);
|
||
const [showOnboarding, setShowOnboarding] = useState(false);
|
||
const [showUserManual, setShowUserManual] = useState(false);
|
||
const [showFeedback, setShowFeedback] = useState(false);
|
||
const [showAbout, setShowAbout] = useState(false);
|
||
|
||
// 版本更新检查
|
||
const { showUpdate, setShowUpdate } = useVersionCheck();
|
||
|
||
// 账户卡片弹窗状态
|
||
const [showAccountCard, setShowAccountCard] = useState(false);
|
||
|
||
// 最近计划轮播索引
|
||
const [currentPlanIndex, setCurrentPlanIndex] = useState(0);
|
||
|
||
// 检查是否显示新手引导
|
||
useEffect(() => {
|
||
if (shouldShowOnboarding('washing')) {
|
||
setShowOnboarding(true);
|
||
}
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
if (!auth.company) return;
|
||
fetchPlans();
|
||
}, [auth.company]);
|
||
|
||
const fetchPlans = async () => {
|
||
const { data: washingPlans } = await supabase
|
||
.from('washing_plans')
|
||
.select('*')
|
||
.eq('washing_factory_id', auth.company!.id)
|
||
.order('created_at', { ascending: false });
|
||
|
||
setPlans(washingPlans || []);
|
||
setLoading(false);
|
||
};
|
||
|
||
const stats = {
|
||
all: plans.length,
|
||
producing: plans.filter(p => p.status === 'processing').length,
|
||
completed: plans.filter(p => p.status === 'completed').length
|
||
};
|
||
|
||
// 获取进行中的计划用于轮播
|
||
const processingPlans = plans.filter(p => p.status === 'processing').slice(0, 5);
|
||
|
||
return (
|
||
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-violet-50/40 to-fuchsia-50/30">
|
||
{/* Header - 与采购商一致 */}
|
||
<header className="bg-white/80 backdrop-blur-xl border-b border-gray-200/50 sticky top-0 z-10">
|
||
<div className="max-w-7xl mx-auto px-3 sm:px-4 md:px-8 py-3 sm:py-4 flex justify-between items-center">
|
||
<div className="flex items-center gap-2 sm:gap-3">
|
||
<motion.button
|
||
whileHover={{ scale: 1.05 }}
|
||
whileTap={{ scale: 0.95 }}
|
||
onClick={() => setShowAccountCard(true)}
|
||
className="w-8 h-8 sm:w-10 sm:h-10 bg-gradient-to-br from-violet-400 to-fuchsia-400 rounded-xl flex items-center justify-center shadow-lg shadow-violet-400/20 cursor-pointer overflow-hidden"
|
||
>
|
||
{(auth.user as any)?.image_url ? (
|
||
<img
|
||
src={(auth.user as any).image_url}
|
||
alt="头像"
|
||
className="w-full h-full object-cover"
|
||
onError={(e) => {
|
||
(e.target as HTMLImageElement).style.display = 'none';
|
||
}}
|
||
/>
|
||
) : (
|
||
<Droplets className="w-4 h-4 sm:w-5 sm:h-5 text-white" />
|
||
)}
|
||
</motion.button>
|
||
<div>
|
||
<h1 className="text-sm sm:text-lg font-bold text-gray-900 flex items-center gap-2 whitespace-nowrap">
|
||
<span className="sm:hidden truncate max-w-[80px]">{(auth.user as any)?.display_name || auth.user?.username || '用户'}</span>
|
||
<span className="hidden sm:inline">{auth.company?.name || '水洗厂'}工作台</span>
|
||
<span className="text-[10px] sm:text-xs bg-gradient-to-r from-violet-400 to-fuchsia-400 text-white px-1.5 sm:px-2 py-0.5 rounded-full font-medium whitespace-nowrap">限时免费</span>
|
||
</h1>
|
||
<p className="text-xs text-gray-500 hidden sm:block truncate max-w-[150px]">{(auth.user as any)?.display_name || auth.user?.username || '用户'},欢迎回来</p>
|
||
</div>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<HelpButton onClick={() => setShowHelp(true)} />
|
||
<NotificationCenter theme="violet" />
|
||
<button
|
||
onClick={async () => { await logout(); navigate('/login'); }}
|
||
className="p-2 sm:px-3 sm:py-1.5 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
|
||
title="退出登录"
|
||
>
|
||
<LogOut className="w-5 h-5 sm:hidden" />
|
||
<span className="hidden sm:inline text-xs sm:text-sm">退出</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<div className="p-3 sm:p-4 md:p-8 max-w-7xl mx-auto">
|
||
{loading ? (
|
||
<div className="flex items-center justify-center py-20">
|
||
<div className="w-8 h-8 border-2 border-violet-400 border-t-transparent rounded-full animate-spin" />
|
||
</div>
|
||
) : (
|
||
<motion.div variants={containerVariants} initial="hidden" animate="visible" className="space-y-4 sm:space-y-6">
|
||
{/* 统计卡片 - 与采购商一致 */}
|
||
<div className="grid grid-cols-3 gap-2 sm:gap-4">
|
||
<motion.div
|
||
variants={itemVariants}
|
||
whileHover={isMobile ? {} : { scale: 1.02, y: -2 }}
|
||
onClick={() => navigate('/washing/plans')}
|
||
className="bg-white rounded-xl sm:rounded-2xl p-3 sm:p-5 shadow-sm border border-gray-100 cursor-pointer hover:border-violet-300 hover:shadow-md transition-all"
|
||
>
|
||
<div className="flex items-center justify-between mb-2 sm:mb-3">
|
||
<p className="text-xs sm:text-sm text-gray-500">所有计划</p>
|
||
<div className="w-6 h-6 sm:w-8 sm:h-8 bg-violet-50 rounded-lg flex items-center justify-center">
|
||
<FileText className="w-3 h-3 sm:w-4 sm:h-4 text-violet-500" />
|
||
</div>
|
||
</div>
|
||
<p className="text-xl sm:text-3xl font-bold text-gray-900">{stats.all}</p>
|
||
<p className="text-xs text-gray-400 mt-0.5 sm:mt-1">累计接收</p>
|
||
</motion.div>
|
||
|
||
<motion.div
|
||
variants={itemVariants}
|
||
whileHover={isMobile ? {} : { scale: 1.02, y: -2 }}
|
||
onClick={() => navigate('/washing/plans?filter=processing')}
|
||
className="bg-white rounded-xl sm:rounded-2xl p-3 sm:p-5 shadow-sm border border-gray-100 cursor-pointer hover:border-violet-300 hover:shadow-md transition-all"
|
||
>
|
||
<div className="flex items-center justify-between mb-2 sm:mb-3">
|
||
<p className="text-xs sm:text-sm text-gray-500">进行中</p>
|
||
<div className="w-6 h-6 sm:w-8 sm:h-8 bg-violet-50 rounded-lg flex items-center justify-center">
|
||
<TrendingUp className="w-3 h-3 sm:w-4 sm:h-4 text-violet-500" />
|
||
</div>
|
||
</div>
|
||
<p className="text-xl sm:text-3xl font-bold text-gray-900">{stats.producing}</p>
|
||
<p className="text-xs text-gray-400 mt-0.5 sm:mt-1">正在水洗</p>
|
||
</motion.div>
|
||
|
||
<motion.div
|
||
variants={itemVariants}
|
||
whileHover={isMobile ? {} : { scale: 1.02, y: -2 }}
|
||
onClick={() => navigate('/washing/plans?filter=completed')}
|
||
className="bg-white rounded-xl sm:rounded-2xl p-3 sm:p-5 shadow-sm border border-gray-100 cursor-pointer hover:border-violet-300 hover:shadow-md transition-all"
|
||
>
|
||
<div className="flex items-center justify-between mb-2 sm:mb-3">
|
||
<p className="text-xs sm:text-sm text-gray-500">已完成</p>
|
||
<div className="w-6 h-6 sm:w-8 sm:h-8 bg-emerald-50 rounded-lg flex items-center justify-center">
|
||
<Package className="w-3 h-3 sm:w-4 sm:h-4 text-emerald-600" />
|
||
</div>
|
||
</div>
|
||
<p className="text-xl sm:text-3xl font-bold text-gray-900">{stats.completed}</p>
|
||
<p className="text-xs text-gray-400 mt-0.5 sm:mt-1">已完工</p>
|
||
</motion.div>
|
||
</div>
|
||
|
||
{/* 最近计划 - 左右滑动展示 */}
|
||
<motion.div variants={itemVariants} className="bg-white rounded-xl sm:rounded-2xl shadow-sm border border-gray-100 p-3 sm:p-4 md:p-6">
|
||
<div className="flex justify-between items-center mb-3 sm:mb-4">
|
||
<h2 className="text-sm sm:text-base font-semibold text-gray-900 whitespace-nowrap">最近计划</h2>
|
||
<button
|
||
onClick={() => navigate('/washing/plans')}
|
||
className="text-xs sm:text-sm text-violet-500 hover:text-violet-600 font-medium flex items-center gap-1 whitespace-nowrap"
|
||
>
|
||
查看全部
|
||
<ArrowLeft className="w-3 h-3 sm:w-4 sm:h-4 rotate-180" />
|
||
</button>
|
||
</div>
|
||
|
||
{processingPlans.length === 0 ? (
|
||
<div className="text-center py-8 sm:py-12 text-gray-400">
|
||
<Package className="w-10 h-10 sm:w-12 sm:h-12 mx-auto mb-3 text-gray-300" />
|
||
<p className="text-sm">暂无进行中的计划</p>
|
||
<button
|
||
onClick={() => navigate('/washing/plans')}
|
||
className="mt-3 px-4 py-2 bg-violet-400 text-white rounded-lg text-sm hover:bg-violet-500 transition-colors"
|
||
>
|
||
查看计划
|
||
</button>
|
||
</div>
|
||
) : (
|
||
<div className="relative">
|
||
{/* 滑动容器 */}
|
||
<div className="overflow-hidden">
|
||
<motion.div
|
||
className="flex"
|
||
animate={{ x: -currentPlanIndex * 100 + '%' }}
|
||
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
|
||
>
|
||
{processingPlans.map((plan, i) => {
|
||
const status = statusMap[plan.status] || statusMap.pending;
|
||
const StatusIcon = status.icon;
|
||
|
||
return (
|
||
<div
|
||
key={plan.id}
|
||
className="w-full flex-shrink-0 px-1"
|
||
onClick={() => navigate(`/washing/plans?id=${plan.id}`)}
|
||
>
|
||
<div className="group p-3 sm:p-4 rounded-xl border border-gray-100 hover:border-purple-200 hover:bg-purple-50/30 cursor-pointer transition-all">
|
||
{/* 计划标题行 */}
|
||
<div className="flex items-center gap-2 sm:gap-3">
|
||
<div className={`w-8 h-8 sm:w-10 sm:h-10 rounded-lg flex items-center justify-center ${status.bgColor}`}>
|
||
<StatusIcon className={`w-4 h-4 sm:w-5 sm:h-5 ${status.color}`} />
|
||
</div>
|
||
<div className="flex-1 min-w-0">
|
||
<div className="flex items-center gap-1.5 sm:gap-2">
|
||
<h3 className="font-medium text-gray-900 text-sm sm:text-base truncate">{plan.plan_code}</h3>
|
||
<span className={`px-1.5 py-0.5 text-xs rounded-full whitespace-nowrap ${status.bgColor} ${status.color}`}>
|
||
{status.label}
|
||
</span>
|
||
</div>
|
||
<p className="text-xs text-gray-500">{plan.planned_meters}米</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</motion.div>
|
||
</div>
|
||
|
||
{/* 左右滑动按钮 */}
|
||
{processingPlans.length > 1 && (
|
||
<>
|
||
<button
|
||
onClick={() => setCurrentPlanIndex(prev => Math.max(0, prev - 1))}
|
||
disabled={currentPlanIndex === 0}
|
||
className="absolute left-0 top-1/2 -translate-y-1/2 -translate-x-2 w-8 h-8 bg-white shadow-lg rounded-full flex items-center justify-center disabled:opacity-30 disabled:cursor-not-allowed hover:bg-gray-50 transition-colors z-10"
|
||
>
|
||
<ArrowLeft className="w-4 h-4 text-gray-600" />
|
||
</button>
|
||
<button
|
||
onClick={() => setCurrentPlanIndex(prev => Math.min(processingPlans.length - 1, prev + 1))}
|
||
disabled={currentPlanIndex === processingPlans.length - 1}
|
||
className="absolute right-0 top-1/2 -translate-y-1/2 translate-x-2 w-8 h-8 bg-white shadow-lg rounded-full flex items-center justify-center disabled:opacity-30 disabled:cursor-not-allowed hover:bg-gray-50 transition-colors z-10"
|
||
>
|
||
<ArrowLeft className="w-4 h-4 text-gray-600 rotate-180" />
|
||
</button>
|
||
</>
|
||
)}
|
||
|
||
{/* 指示器 */}
|
||
{processingPlans.length > 1 && (
|
||
<div className="flex items-center justify-center gap-1.5 mt-4">
|
||
{processingPlans.map((_, i) => (
|
||
<button
|
||
key={i}
|
||
onClick={() => setCurrentPlanIndex(i)}
|
||
className={`w-2 h-2 rounded-full transition-all ${
|
||
i === currentPlanIndex ? 'bg-purple-500 w-4' : 'bg-gray-300 hover:bg-gray-400'
|
||
}`}
|
||
/>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
</motion.div>
|
||
|
||
{/* 快捷操作 - 与采购商一致 */}
|
||
<motion.div variants={itemVariants} className="bg-white rounded-xl sm:rounded-2xl shadow-sm border border-gray-100 p-3 sm:p-4 md:p-6">
|
||
<h2 className="text-sm sm:text-base font-semibold text-gray-900 mb-3 sm:mb-4 whitespace-nowrap">快捷操作</h2>
|
||
<div className="grid grid-cols-3 sm:grid-cols-4 gap-2 sm:gap-3">
|
||
{getQuickActions(auth.user?.is_master || false).map((action, index) => (
|
||
<motion.button
|
||
key={action.id}
|
||
whileHover={isMobile ? {} : { scale: 1.03, y: -2 }}
|
||
whileTap={{ scale: 0.97 }}
|
||
onClick={() => navigate(action.path)}
|
||
className="group flex flex-col items-center p-2 sm:p-3 rounded-lg sm:rounded-xl border border-gray-100 hover:border-transparent hover:shadow-md transition-all bg-gray-50/50 hover:bg-white"
|
||
>
|
||
<div className={`w-8 h-8 sm:w-10 sm:h-10 rounded-lg sm:rounded-xl flex items-center justify-center mb-1 sm:mb-2 bg-gradient-to-br ${action.gradient} shadow-sm group-hover:shadow-md transition-shadow`}>
|
||
<action.icon className="w-4 h-4 sm:w-5 sm:h-5 text-white" />
|
||
</div>
|
||
<span className="text-[10px] sm:text-xs font-medium text-gray-700 group-hover:text-gray-900 text-center whitespace-nowrap">{action.label}</span>
|
||
</motion.button>
|
||
))}
|
||
</div>
|
||
</motion.div>
|
||
</motion.div>
|
||
)}
|
||
</div>
|
||
|
||
{/* 帮助中心弹窗 */}
|
||
<HelpCenter isOpen={showHelp} onClose={() => setShowHelp(false)} />
|
||
|
||
{/* 新手引导 */}
|
||
<OnboardingGuide
|
||
role="washing"
|
||
isOpen={showOnboarding}
|
||
onClose={() => setShowOnboarding(false)}
|
||
/>
|
||
|
||
{/* 用户手册 */}
|
||
<UserManual isOpen={showUserManual} onClose={() => setShowUserManual(false)} />
|
||
|
||
{/* 反馈弹窗 */}
|
||
<FeedbackModal isOpen={showFeedback} onClose={() => setShowFeedback(false)} />
|
||
|
||
{/* 关于窗口 */}
|
||
<AboutModal isOpen={showAbout} onClose={() => setShowAbout(false)} />
|
||
|
||
{/* 版本更新弹窗 */}
|
||
<VersionUpdate
|
||
isOpen={showUpdate}
|
||
onClose={() => setShowUpdate(false)}
|
||
/>
|
||
|
||
{/* 崩溃报告 */}
|
||
<CrashReporter />
|
||
|
||
{/* 账户卡片弹窗 */}
|
||
{showAccountCard && (
|
||
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4" onClick={() => setShowAccountCard(false)}>
|
||
<motion.div
|
||
initial={{ opacity: 0, scale: 0.95 }}
|
||
animate={{ opacity: 1, scale: 1 }}
|
||
exit={{ opacity: 0, scale: 0.95 }}
|
||
className="bg-white rounded-2xl w-full max-w-sm shadow-2xl overflow-hidden"
|
||
onClick={e => e.stopPropagation()}
|
||
>
|
||
{/* 头部 */}
|
||
<div className="bg-gradient-to-r from-violet-400 to-fuchsia-400 p-6 text-white">
|
||
<div className="flex items-center justify-between">
|
||
<h2 className="text-xl font-bold">账户信息</h2>
|
||
<button onClick={() => setShowAccountCard(false)} className="p-2 hover:bg-white/20 rounded-lg transition-colors">
|
||
<X className="w-5 h-5" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 账户详情 */}
|
||
<div className="p-6 space-y-4">
|
||
{/* 用户信息 */}
|
||
<div className="flex items-center gap-4">
|
||
<label className="w-16 h-16 bg-gradient-to-br from-violet-400 to-fuchsia-400 rounded-xl flex items-center justify-center shadow-lg cursor-pointer hover:opacity-90 transition-opacity overflow-hidden relative group">
|
||
{(auth.user as any)?.image_url ? (
|
||
<img src={(auth.user as any).image_url} alt="头像" className="w-full h-full object-cover relative z-10" />
|
||
) : (
|
||
<Droplets className="w-8 h-8 text-white relative z-10" />
|
||
)}
|
||
<div className="absolute inset-0 bg-black/30 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity z-20">
|
||
<Camera className="w-5 h-5 text-white" />
|
||
</div>
|
||
<input
|
||
type="file"
|
||
accept="image/*"
|
||
className="hidden"
|
||
onChange={async (e) => {
|
||
const file = e.target.files?.[0];
|
||
if (!file) return;
|
||
const { data: { user } } = await supabase.auth.getUser();
|
||
if (!user) return;
|
||
try {
|
||
const fileExt = file.name.split('.').pop();
|
||
const fileName = `${user.id}-${Date.now()}.${fileExt}`;
|
||
const arrayBuffer = await file.arrayBuffer();
|
||
const { error: uploadError } = await supabase.storage
|
||
.from('avatars')
|
||
.upload(fileName, arrayBuffer, {
|
||
contentType: file.type,
|
||
upsert: true
|
||
});
|
||
if (uploadError) {
|
||
console.error('头像上传失败:', uploadError);
|
||
alert('头像上传失败: ' + uploadError.message);
|
||
return;
|
||
}
|
||
const { data: { publicUrl } } = supabase.storage
|
||
.from('avatars')
|
||
.getPublicUrl(fileName);
|
||
const { error: updateError } = await supabase
|
||
.from('profiles')
|
||
.update({ image_url: publicUrl } as any)
|
||
.eq('id', user.id);
|
||
if (updateError) {
|
||
console.error('头像更新失败:', updateError);
|
||
alert('头像更新失败: ' + updateError.message);
|
||
} else {
|
||
window.location.reload();
|
||
}
|
||
} catch (err) {
|
||
console.error('头像上传出错:', err);
|
||
alert('头像上传出错,请重试');
|
||
}
|
||
}}
|
||
/>
|
||
</label>
|
||
<div>
|
||
<p className="font-semibold text-gray-900 text-lg truncate max-w-[200px]">{(auth.user as any)?.display_name || auth.user?.username || '用户'}</p>
|
||
<p className="text-sm text-gray-500">{auth.company?.name || '水洗厂'}</p>
|
||
<span className="inline-flex items-center gap-1 text-xs bg-violet-100 text-violet-600 px-2 py-0.5 rounded-full mt-1">
|
||
{auth.user?.is_master ? '主账号' : '子账号'}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 功能菜单 */}
|
||
<div className="space-y-2 pt-4 border-t border-gray-100">
|
||
<button
|
||
onClick={() => { setShowAccountCard(false); navigate('/members'); }}
|
||
className="w-full flex items-center gap-3 p-3 rounded-xl hover:bg-gray-50 transition-colors text-left"
|
||
>
|
||
<div className="w-10 h-10 bg-violet-100 rounded-lg flex items-center justify-center">
|
||
<Users className="w-5 h-5 text-violet-500" />
|
||
</div>
|
||
<div>
|
||
<p className="font-medium text-gray-900">账号管理</p>
|
||
<p className="text-xs text-gray-500">管理团队成员和账户设置</p>
|
||
</div>
|
||
</button>
|
||
|
||
<button
|
||
onClick={() => { setShowAccountCard(false); setShowUserManual(true); }}
|
||
className="w-full flex items-center gap-3 p-3 rounded-xl hover:bg-gray-50 transition-colors text-left"
|
||
>
|
||
<div className="w-10 h-10 bg-violet-100 rounded-lg flex items-center justify-center">
|
||
<BookOpen className="w-5 h-5 text-violet-500" />
|
||
</div>
|
||
<div>
|
||
<p className="font-medium text-gray-900">用户手册</p>
|
||
<p className="text-xs text-gray-500">查看详细使用指南</p>
|
||
</div>
|
||
</button>
|
||
|
||
<button
|
||
onClick={() => { setShowAccountCard(false); setShowFeedback(true); }}
|
||
className="w-full flex items-center gap-3 p-3 rounded-xl hover:bg-gray-50 transition-colors text-left"
|
||
>
|
||
<div className="w-10 h-10 bg-violet-100 rounded-lg flex items-center justify-center">
|
||
<MessageSquare className="w-5 h-5 text-violet-500" />
|
||
</div>
|
||
<div>
|
||
<p className="font-medium text-gray-900">用户反馈</p>
|
||
<p className="text-xs text-gray-500">提交问题或建议</p>
|
||
</div>
|
||
</button>
|
||
|
||
<button
|
||
onClick={() => { setShowAccountCard(false); setShowAbout(true); }}
|
||
className="w-full flex items-center gap-3 p-3 rounded-xl hover:bg-gray-50 transition-colors text-left"
|
||
>
|
||
<div className="w-10 h-10 bg-gray-100 rounded-lg flex items-center justify-center">
|
||
<Info className="w-5 h-5 text-gray-600" />
|
||
</div>
|
||
<div>
|
||
<p className="font-medium text-gray-900">关于</p>
|
||
<p className="text-xs text-gray-500">版本信息与版权</p>
|
||
</div>
|
||
</button>
|
||
</div>
|
||
|
||
{/* 退出登录 */}
|
||
<button
|
||
onClick={async () => { await logout(); navigate('/login'); }}
|
||
className="w-full py-3 bg-red-50 hover:bg-red-100 text-red-600 rounded-xl transition-colors flex items-center justify-center gap-2 font-medium"
|
||
>
|
||
<LogOut className="w-5 h-5" />
|
||
退出登录
|
||
</button>
|
||
</div>
|
||
</motion.div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|