2026-05-26 05:15:10 +00:00
|
|
|
|
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';
|
2026-05-27 03:17:46 +00:00
|
|
|
|
import { Import, Warehouse, CreditCard, ArrowLeft, Users, Droplets, Package, CheckCircle, Clock, LogOut, HelpCircle } from 'lucide-react';
|
2026-05-26 05:15:10 +00:00
|
|
|
|
import { useResponsive } from '../../hooks/useResponsive';
|
2026-05-27 03:17:46 +00:00
|
|
|
|
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';
|
2026-05-26 05:15:10 +00:00
|
|
|
|
import type { WashingPlan } from '../../types';
|
|
|
|
|
|
|
|
|
|
|
|
const getQuickActions = (isMaster: boolean) => {
|
|
|
|
|
|
const actions = [
|
|
|
|
|
|
{ id: 'plans', icon: Import, label: '计划总览', path: '/washing/plans', gradient: 'from-purple-500 to-violet-500' },
|
|
|
|
|
|
{ id: 'pending', icon: Package, label: '待处理坯布', path: '/washing/pending', gradient: 'from-amber-500 to-orange-500' },
|
|
|
|
|
|
{ id: 'completed', icon: CheckCircle, label: '已完成坯布', path: '/washing/completed', gradient: 'from-green-500 to-emerald-500' },
|
|
|
|
|
|
{ id: 'finished', icon: Warehouse, label: '成品仓库', path: '/washing/finished-warehouse', gradient: 'from-blue-500 to-cyan-500' },
|
|
|
|
|
|
{ id: 'payment', icon: CreditCard, label: '待结款', path: '/washing/payments', gradient: 'from-rose-500 to-pink-500' }
|
|
|
|
|
|
];
|
|
|
|
|
|
if (isMaster) {
|
|
|
|
|
|
actions.push({ id: 'members', icon: Users, label: '账号管理', path: '/members', gradient: 'from-indigo-500 to-purple-500' });
|
|
|
|
|
|
}
|
|
|
|
|
|
return actions;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const statusMap: Record<string, { label: string; color: string }> = {
|
|
|
|
|
|
pending: { label: '待处理', color: 'bg-amber-100 text-amber-700' },
|
|
|
|
|
|
processing: { label: '处理中', color: 'bg-blue-100 text-blue-700' },
|
|
|
|
|
|
completed: { label: '已完成', color: 'bg-green-100 text-green-700' }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const containerVariants = {
|
|
|
|
|
|
hidden: { opacity: 0 },
|
|
|
|
|
|
visible: {
|
|
|
|
|
|
opacity: 1,
|
|
|
|
|
|
transition: {
|
|
|
|
|
|
staggerChildren: 0.08,
|
|
|
|
|
|
delayChildren: 0.05
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const itemVariants = {
|
|
|
|
|
|
hidden: { opacity: 0, y: 15, scale: 0.98 },
|
|
|
|
|
|
visible: {
|
|
|
|
|
|
opacity: 1,
|
|
|
|
|
|
y: 0,
|
|
|
|
|
|
scale: 1,
|
|
|
|
|
|
transition: {
|
|
|
|
|
|
duration: 0.35,
|
|
|
|
|
|
ease: [0.25, 0.46, 0.45, 0.94]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export function WashingDashboard() {
|
|
|
|
|
|
const navigate = useNavigate();
|
|
|
|
|
|
const { auth, logout } = useAuth();
|
|
|
|
|
|
const { isMobile } = useResponsive();
|
|
|
|
|
|
const [plans, setPlans] = useState<WashingPlan[]>([]);
|
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
|
|
const [pendingCount, setPendingCount] = useState(0);
|
|
|
|
|
|
const [completedCount, setCompletedCount] = useState(0);
|
|
|
|
|
|
|
2026-05-27 03:17:46 +00:00
|
|
|
|
// 帮助中心状态
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否显示新手引导
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (shouldShowOnboarding('washing')) {
|
|
|
|
|
|
setShowOnboarding(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
2026-05-26 05:15:10 +00:00
|
|
|
|
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 });
|
|
|
|
|
|
|
|
|
|
|
|
const plans = washingPlans || [];
|
|
|
|
|
|
setPlans(plans);
|
|
|
|
|
|
setPendingCount(plans.filter(p => p.status === 'pending' || p.status === 'processing').length);
|
|
|
|
|
|
setCompletedCount(plans.filter(p => p.status === 'completed').length);
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const stats = {
|
|
|
|
|
|
all: plans.length,
|
|
|
|
|
|
producing: plans.filter(p => p.status === 'processing').length,
|
|
|
|
|
|
completed: plans.filter(p => p.status === 'completed').length
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const recentPlans = plans.slice(0, 5);
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-purple-50 to-violet-50">
|
|
|
|
|
|
<header className="bg-gradient-to-r from-purple-600 to-violet-600 shadow-lg">
|
|
|
|
|
|
<div className="max-w-7xl mx-auto px-4 md:px-8 py-4 md:py-5 flex justify-between items-center">
|
|
|
|
|
|
<div className="flex items-center gap-2 sm:gap-3">
|
|
|
|
|
|
<div className="w-8 h-8 sm:w-10 sm:h-10 bg-white/20 rounded-xl flex items-center justify-center">
|
|
|
|
|
|
<Droplets className="w-4 h-4 sm:w-5 sm:h-5 text-white" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
2026-05-27 03:17:46 +00:00
|
|
|
|
<h1 className="text-sm sm:text-lg font-bold text-white flex items-center gap-2 whitespace-nowrap">
|
2026-05-26 05:15:10 +00:00
|
|
|
|
{auth.company?.name || '水洗厂'}工作台
|
2026-05-27 03:17:46 +00:00
|
|
|
|
<span className="text-[10px] sm:text-xs bg-white/20 text-white px-1.5 sm:px-2 py-0.5 rounded-full font-medium whitespace-nowrap">限时免费</span>
|
2026-05-26 05:15:10 +00:00
|
|
|
|
</h1>
|
2026-05-27 03:17:46 +00:00
|
|
|
|
<p className="text-xs text-white/70 hidden sm:block">{(auth.user as any)?.display_name || auth.user?.username || '用户'},欢迎回来</p>
|
2026-05-26 05:15:10 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-05-27 03:17:46 +00:00
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<HelpButton onClick={() => setShowHelp(true)} />
|
|
|
|
|
|
<NotificationCenter theme="purple" />
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={async () => { await logout(); navigate('/login'); }}
|
|
|
|
|
|
className="p-2 sm:px-3 sm:py-1 text-white/70 hover:text-white hover:bg-white/10 rounded-lg transition-colors"
|
|
|
|
|
|
title="退出登录"
|
|
|
|
|
|
>
|
|
|
|
|
|
<LogOut className="w-5 h-5 sm:hidden" />
|
|
|
|
|
|
<span className="hidden sm:inline text-xs md:text-sm">退出登录</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-05-26 05:15:10 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="p-4 md:p-8 max-w-7xl mx-auto">
|
|
|
|
|
|
{loading ? (
|
|
|
|
|
|
<div className="text-center py-12 text-gray-400">加载中...</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<motion.div variants={containerVariants} initial="hidden" animate="visible">
|
|
|
|
|
|
<div className="grid grid-cols-3 gap-3 md:gap-6 mb-6 md:mb-8">
|
|
|
|
|
|
<motion.div variants={itemVariants} className="bg-gradient-to-br from-purple-500 to-violet-500 rounded-xl md:rounded-2xl shadow-lg shadow-purple-500/20 p-4 md:p-6 text-white">
|
|
|
|
|
|
<p className="text-xs md:text-sm text-purple-100 mb-1 md:mb-2">所有计划</p>
|
|
|
|
|
|
<p className="text-2xl md:text-4xl font-bold">{stats.all}</p>
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
<motion.div variants={itemVariants} className="bg-gradient-to-br from-blue-500 to-cyan-500 rounded-xl md:rounded-2xl shadow-lg shadow-blue-500/20 p-4 md:p-6 text-white">
|
|
|
|
|
|
<p className="text-xs md:text-sm text-blue-100 mb-1 md:mb-2">进行中</p>
|
|
|
|
|
|
<p className="text-2xl md:text-4xl font-bold">{stats.producing}</p>
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
<motion.div variants={itemVariants} className="bg-gradient-to-br from-green-500 to-emerald-500 rounded-xl md:rounded-2xl shadow-lg shadow-green-500/20 p-4 md:p-6 text-white">
|
|
|
|
|
|
<p className="text-xs md:text-sm text-green-100 mb-1 md:mb-2">已完成</p>
|
|
|
|
|
|
<p className="text-2xl md:text-4xl font-bold">{stats.completed}</p>
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<motion.div variants={itemVariants} className="bg-white/80 backdrop-blur rounded-2xl shadow-sm p-4 md:p-6 mb-6 md:mb-8 border border-white/50">
|
|
|
|
|
|
<h2 className="text-base md:text-lg font-semibold text-gray-800 mb-4 md:mb-6">快捷操作</h2>
|
|
|
|
|
|
<div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-6 gap-3 md:gap-4">
|
|
|
|
|
|
{getQuickActions(auth.user?.is_master || false).map((action, index) => (
|
|
|
|
|
|
<motion.button
|
|
|
|
|
|
key={action.id}
|
|
|
|
|
|
initial={{ opacity: 0, y: 10 }}
|
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
|
|
transition={{ delay: 0.1 + index * 0.05, duration: 0.3 }}
|
|
|
|
|
|
whileHover={isMobile ? {} : { scale: 1.03, y: -3 }}
|
|
|
|
|
|
whileTap={{ scale: 0.97 }}
|
|
|
|
|
|
onClick={() => navigate(action.path)}
|
|
|
|
|
|
className="flex flex-col items-center justify-center p-3 md:p-4 rounded-xl border border-gray-200 hover:border-transparent hover:shadow-md transition-all duration-200 group will-change-transform"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className={`w-10 h-10 md:w-12 md:h-12 rounded-xl flex items-center justify-center mb-2 md:mb-3 bg-gradient-to-br ${action.gradient} shadow-lg transition-shadow duration-200 group-hover:shadow-xl`}>
|
|
|
|
|
|
<action.icon className="w-5 h-5 md:w-6 md:h-6 text-white" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span className="text-xs md:text-sm text-gray-700 group-hover:text-gray-900 transition-colors text-center">{action.label}</span>
|
|
|
|
|
|
</motion.button>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-6">
|
|
|
|
|
|
<motion.div variants={itemVariants} className="bg-white/80 backdrop-blur rounded-2xl shadow-sm p-4 md:p-6 border border-white/50">
|
|
|
|
|
|
<div className="flex justify-between items-center mb-4 md:mb-6">
|
|
|
|
|
|
<h2 className="text-base md:text-lg font-semibold text-gray-800">待处理坯布</h2>
|
|
|
|
|
|
<button onClick={() => navigate('/washing/pending')} className="text-xs md:text-sm text-amber-600 hover:text-amber-700 font-medium">查看全部</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{pendingCount === 0 ? (
|
|
|
|
|
|
<p className="text-center text-gray-400 py-6 md:py-8">暂无待处理坯布</p>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="space-y-3 md:space-y-4">
|
|
|
|
|
|
{plans
|
|
|
|
|
|
.filter(p => p.status === 'pending' || p.status === 'processing')
|
|
|
|
|
|
.slice(0, 3)
|
|
|
|
|
|
.map((plan, i) => (
|
|
|
|
|
|
<motion.div
|
|
|
|
|
|
key={plan.id}
|
|
|
|
|
|
initial={{ opacity: 0, x: -15 }}
|
|
|
|
|
|
animate={{ opacity: 1, x: 0 }}
|
|
|
|
|
|
transition={{ delay: i * 0.08, duration: 0.35, ease: [0.25, 0.46, 0.45, 0.94] }}
|
|
|
|
|
|
whileHover={isMobile ? {} : { x: 4, backgroundColor: 'rgba(254, 243, 199, 0.5)' }}
|
|
|
|
|
|
whileTap={{ scale: 0.99 }}
|
|
|
|
|
|
onClick={() => navigate(`/washing/plans?id=${plan.id}`)}
|
|
|
|
|
|
className="border border-gray-200 rounded-xl p-3 md:p-4 hover:border-amber-300 cursor-pointer transition-all duration-200 will-change-transform"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="flex justify-between items-start mb-2">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="text-xs text-gray-500">{plan.plan_code}</p>
|
|
|
|
|
|
<p className="text-sm font-medium text-gray-800 mt-1">{plan.planned_meters}米</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span className={`px-2 py-1 rounded-lg text-xs font-medium ${statusMap[plan.status]?.color || 'bg-gray-100'}`}>
|
|
|
|
|
|
{statusMap[plan.status]?.label || plan.status}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
|
|
|
|
|
|
<motion.div variants={itemVariants} className="bg-white/80 backdrop-blur rounded-2xl shadow-sm p-4 md:p-6 border border-white/50">
|
|
|
|
|
|
<div className="flex justify-between items-center mb-4 md:mb-6">
|
|
|
|
|
|
<h2 className="text-base md:text-lg font-semibold text-gray-800">已完成坯布</h2>
|
|
|
|
|
|
<button onClick={() => navigate('/washing/completed')} className="text-xs md:text-sm text-green-600 hover:text-green-700 font-medium">查看全部</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{completedCount === 0 ? (
|
|
|
|
|
|
<p className="text-center text-gray-400 py-6 md:py-8">暂无已完成坯布</p>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="space-y-3 md:space-y-4">
|
|
|
|
|
|
{plans
|
|
|
|
|
|
.filter(p => p.status === 'completed')
|
|
|
|
|
|
.slice(0, 3)
|
|
|
|
|
|
.map((plan, i) => (
|
|
|
|
|
|
<motion.div
|
|
|
|
|
|
key={plan.id}
|
|
|
|
|
|
initial={{ opacity: 0, x: -15 }}
|
|
|
|
|
|
animate={{ opacity: 1, x: 0 }}
|
|
|
|
|
|
transition={{ delay: i * 0.08, duration: 0.35, ease: [0.25, 0.46, 0.45, 0.94] }}
|
|
|
|
|
|
whileHover={isMobile ? {} : { x: 4, backgroundColor: 'rgba(209, 250, 229, 0.5)' }}
|
|
|
|
|
|
whileTap={{ scale: 0.99 }}
|
|
|
|
|
|
onClick={() => navigate(`/washing/plans?id=${plan.id}`)}
|
|
|
|
|
|
className="border border-gray-200 rounded-xl p-3 md:p-4 hover:border-green-300 cursor-pointer transition-all duration-200 will-change-transform"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="flex justify-between items-start mb-2">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="text-xs text-gray-500">{plan.plan_code}</p>
|
|
|
|
|
|
<p className="text-sm font-medium text-gray-800 mt-1">
|
|
|
|
|
|
洗前: {plan.planned_meters}米 → 洗后: {plan.actual_washed_meters || plan.estimated_washed_meters}米
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span className="px-2 py-1 rounded-lg text-xs font-medium bg-green-100 text-green-700">
|
|
|
|
|
|
已完成
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p className="text-xs text-gray-500">
|
|
|
|
|
|
缩水率: {plan.actual_shrinkage_rate || plan.estimated_shrinkage_rate}%
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<motion.div variants={itemVariants} className="bg-white/80 backdrop-blur rounded-2xl shadow-sm p-4 md:p-6 mt-4 md:mt-6 border border-white/50">
|
|
|
|
|
|
<div className="flex justify-between items-center mb-4 md:mb-6">
|
|
|
|
|
|
<h2 className="text-base md:text-lg font-semibold text-gray-800">最近计划</h2>
|
|
|
|
|
|
<button onClick={() => navigate('/washing/plans')} className="text-xs md:text-sm text-purple-600 hover:text-purple-700 font-medium">查看全部</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{recentPlans.length === 0 ? (
|
|
|
|
|
|
<p className="text-center text-gray-400 py-6 md:py-8">暂无关联计划</p>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="space-y-3 md:space-y-4">
|
|
|
|
|
|
{recentPlans.map((plan, i) => (
|
|
|
|
|
|
<motion.div
|
|
|
|
|
|
key={plan.id}
|
|
|
|
|
|
initial={{ opacity: 0, x: -15 }}
|
|
|
|
|
|
animate={{ opacity: 1, x: 0 }}
|
|
|
|
|
|
transition={{ delay: i * 0.08, duration: 0.35, ease: [0.25, 0.46, 0.45, 0.94] }}
|
|
|
|
|
|
whileHover={isMobile ? {} : { x: 4, backgroundColor: 'rgba(243, 232, 255, 0.8)' }}
|
|
|
|
|
|
whileTap={{ scale: 0.99 }}
|
|
|
|
|
|
onClick={() => navigate(`/washing/plans?id=${plan.id}`)}
|
|
|
|
|
|
className="border border-gray-200 rounded-xl p-4 md:p-5 hover:border-purple-300 cursor-pointer transition-all duration-200 will-change-transform"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="flex justify-between items-start mb-2 md:mb-3">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="text-sm text-gray-600">{plan.plan_code}</p>
|
|
|
|
|
|
<p className="text-xs text-gray-400 mt-1">计划米数: {plan.planned_meters}米</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span className={`px-2 py-1 md:px-3 md:py-1.5 rounded-lg text-xs md:text-sm font-medium ${statusMap[plan.status]?.color || 'bg-gray-100'}`}>
|
|
|
|
|
|
{statusMap[plan.status]?.label || plan.status}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{plan.status === 'completed' && (
|
|
|
|
|
|
<div className="flex items-center gap-2 md:gap-4 text-xs md:text-sm text-gray-600">
|
|
|
|
|
|
<span>洗后: {plan.actual_washed_meters}米</span>
|
|
|
|
|
|
<span>缩水率: {plan.actual_shrinkage_rate}%</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2026-05-27 03:17:46 +00:00
|
|
|
|
|
|
|
|
|
|
{/* 帮助中心弹窗 */}
|
|
|
|
|
|
<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 />
|
2026-05-26 05:15:10 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|