iloom-flatten/src/pages/MemberManage.tsx

836 lines
33 KiB
TypeScript
Raw Normal View History

import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
import { supabase } from '../supabase/client';
import { motion, AnimatePresence } from 'framer-motion';
import { Users, ArrowLeft, Plus, X, User, Phone, Shield, Trash2, AlertCircle, Edit2, Building2, BookOpen, MessageSquare, Info } from 'lucide-react';
import { useResponsive } from '../hooks/useResponsive';
import { encryptPhone, maskPhone } from '../utils/crypto';
import { UserManual } from '../components/UserManual';
import { FeedbackModal } from '../components/FeedbackModal';
import { AboutModal } from '../components/VersionUpdate';
interface Member {
id: string;
username: string;
phone: string | null;
created_at: string;
}
export function MemberManage() {
const navigate = useNavigate();
const { auth } = useAuth();
const { isMobile } = useResponsive();
const [members, setMembers] = useState<Member[]>([]);
const [loading, setLoading] = useState(true);
const [showModal, setShowModal] = useState(false);
const [showDeleteConfirm, setShowDeleteConfirm] = useState<string | null>(null);
// 表单状态
const [formData, setFormData] = useState({
username: '',
displayName: '',
phone: '',
password: ''
});
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState('');
// 主账号编辑状态
const [showEditMasterModal, setShowEditMasterModal] = useState(false);
const [masterFormData, setMasterFormData] = useState({
displayName: '',
phone: '',
password: ''
});
const [editingMaster, setEditingMaster] = useState(false);
// 公司信息
const [companyInfo, setCompanyInfo] = useState<{
name: string;
contact_phone: string | null;
role: string;
} | null>(null);
// 帮助系统弹窗状态
const [showUserManual, setShowUserManual] = useState(false);
const [showFeedback, setShowFeedback] = useState(false);
const [showAbout, setShowAbout] = useState(false);
useEffect(() => {
if (!auth.user) return;
// 只有主账号可以管理子账号
if (!auth.user.is_master) {
navigate('/');
return;
}
fetchMembers();
fetchCompanyInfo();
}, [auth.user]);
const fetchCompanyInfo = async () => {
if (!auth.user?.company_id) return;
const { data } = await supabase
.from('companies')
.select('name, contact_phone, role')
.eq('id', auth.user.company_id)
.maybeSingle();
if (data) {
setCompanyInfo(data);
}
};
const fetchMembers = async () => {
setLoading(true);
try {
// 获取当前公司的所有子账号(非主账号)
const companyId = auth.user?.company_id;
if (!companyId) {
setLoading(false);
return;
}
const { data, error } = await supabase
.from('profiles')
.select('id, username, phone, created_at')
.eq('company_id', companyId)
.eq('is_master', false)
.order('created_at', { ascending: false });
if (error) {
console.error('获取成员列表失败:', error);
} else {
setMembers(data || []);
}
} catch (err) {
console.error('获取成员列表出错:', err);
}
setLoading(false);
};
const handleOpenModal = () => {
setFormData({ username: '', displayName: '', phone: '', password: '' });
setError('');
setShowModal(true);
};
const handleCloseModal = () => {
setShowModal(false);
setFormData({ username: '', displayName: '', phone: '', password: '' });
setError('');
};
const handleOpenEditMaster = () => {
setMasterFormData({
displayName: (auth.user as any)?.display_name || auth.user?.username || '',
phone: auth.user?.phone || '',
password: ''
});
setShowEditMasterModal(true);
};
const handleCloseEditMaster = () => {
setShowEditMasterModal(false);
setMasterFormData({ displayName: '', phone: '', password: '' });
};
const handleUpdateMaster = async (e: React.FormEvent) => {
e.preventDefault();
if (!masterFormData.phone.trim()) {
setError('请输入手机号');
return;
}
if (!/^1[3-9]\d{9}$/.test(masterFormData.phone)) {
setError('请输入正确的手机号');
return;
}
setEditingMaster(true);
try {
// 更新姓名和手机号
const { error: updateError } = await supabase
.from('profiles')
.update({
display_name: masterFormData.displayName,
phone: encryptPhone(masterFormData.phone)
})
.eq('id', auth.user?.id || '');
if (updateError) {
setError('更新失败:' + updateError.message);
setEditingMaster(false);
return;
}
// 如果填写了密码,更新密码
if (masterFormData.password && masterFormData.password.length >= 6) {
const { error: passwordError } = await supabase.auth.updateUser({
password: masterFormData.password
});
if (passwordError) {
setError('密码更新失败:' + passwordError.message);
setEditingMaster(false);
return;
}
}
// 刷新页面以获取更新后的用户信息
window.location.reload();
} catch (err) {
setError('更新时发生错误');
console.error(err);
}
setEditingMaster(false);
};
const validateForm = () => {
if (!formData.username.trim()) return '请输入用户名';
if (formData.username.length < 3) return '用户名至少3个字符';
if (!formData.displayName.trim()) return '请输入姓名';
if (!formData.password.trim()) return '请输入密码';
if (formData.password.length < 6) return '密码至少6个字符';
if (!formData.phone.trim()) return '请输入手机号';
if (!/^1[3-9]\d{9}$/.test(formData.phone)) {
return '请输入正确的手机号';
}
return '';
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const validationError = validateForm();
if (validationError) {
setError(validationError);
return;
}
if (!auth.user?.company_id) {
setError('请先登录');
return;
}
setSubmitting(true);
setError('');
try {
// 检查用户名是否已存在(全局唯一性检查)
const { data: existingUser, error: checkError } = await supabase
.from('profiles')
.select('id')
.eq('username', formData.username.trim())
.maybeSingle();
if (checkError) {
console.error('检查用户名失败:', checkError);
}
if (existingUser) {
setError('该用户名已被使用,请更换其他用户名');
setSubmitting(false);
return;
}
// 1. 创建用户(使用虚拟邮箱格式)
const email = `${formData.username}@meoo.local`;
const { data: authData, error: signUpError } = await supabase.auth.signUp({
email,
password: formData.password,
options: {
data: {
username: formData.username,
phone: formData.phone || null,
company_id: auth.user.company_id,
is_master: false,
master_id: auth.user.id
}
}
});
if (signUpError) {
setError('创建账号失败:' + signUpError.message);
setSubmitting(false);
return;
}
if (!authData.user) {
setError('创建账号失败,请重试');
setSubmitting(false);
return;
}
// 2. 创建 profile 记录(手机号加密存储)
const { error: profileError } = await supabase
.from('profiles')
.insert({
id: authData.user.id,
username: formData.username,
display_name: formData.displayName,
phone: encryptPhone(formData.phone) || null,
company_id: auth.user.company_id,
is_master: false,
master_id: auth.user.id
});
if (profileError) {
console.error('创建 profile 失败:', profileError);
// 继续执行,因为用户已创建成功
}
// 3. 刷新成员列表
await fetchMembers();
handleCloseModal();
} catch (err) {
setError('创建子账号时发生错误');
console.error(err);
}
setSubmitting(false);
};
const handleDelete = async (memberId: string) => {
try {
// 删除 profile 记录
const { error: profileError } = await supabase
.from('profiles')
.delete()
.eq('id', memberId);
if (profileError) {
console.error('删除成员失败:', profileError);
return;
}
// 刷新列表
setMembers(members.filter(m => m.id !== memberId));
setShowDeleteConfirm(null);
} catch (err) {
console.error('删除成员出错:', err);
}
};
// 如果不是主账号,显示无权限
if (!auth.user?.is_master) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center p-4">
<div className="bg-white rounded-lg shadow-sm p-8 text-center max-w-md">
<div className="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-4">
<Shield className="w-8 h-8 text-red-500" />
</div>
<h2 className="text-lg font-bold text-gray-800 mb-2">访</h2>
<p className="text-gray-500 mb-4"></p>
<button
onClick={() => navigate(-1)}
className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
</button>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gray-50 p-4 md:p-6">
<div className="max-w-4xl mx-auto">
{/* 头部 */}
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-3 md:gap-4">
<button
onClick={() => navigate(-1)}
className="p-2 hover:bg-gray-200 rounded-full transition-colors"
>
<ArrowLeft className="w-5 h-5 md:w-6 md:h-6 text-gray-600" />
</button>
<div>
<h1 className="text-xl md:text-2xl font-bold text-gray-800"></h1>
<p className="text-sm text-gray-500"></p>
</div>
</div>
<motion.button
whileHover={{ scale: isMobile ? 1 : 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={handleOpenModal}
className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-sm md:text-base"
>
<Plus size={18} />
</motion.button>
</div>
{/* 主账号信息卡片 */}
<div className="bg-gradient-to-r from-blue-500 to-blue-600 rounded-xl p-4 md:p-6 mb-6 text-white">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-12 h-12 bg-white/20 rounded-full flex items-center justify-center">
<Shield className="w-6 h-6" />
</div>
<div>
<p className="text-sm text-blue-100"></p>
<p className="font-semibold text-lg">{auth.user?.username}</p>
<span className="inline-flex items-center gap-1 text-xs bg-white/20 px-2 py-0.5 rounded mt-1">
<Shield size={12} />
</span>
</div>
</div>
<motion.button
whileTap={{ scale: 0.98 }}
onClick={handleOpenEditMaster}
className="flex items-center gap-1.5 px-3 py-1.5 text-sm text-white bg-white/20 hover:bg-white/30 rounded-lg transition-colors"
>
<Edit2 className="w-4 h-4" />
</motion.button>
</div>
</div>
{/* 帮助与支持卡片 */}
<div className="bg-white rounded-lg shadow-sm p-4 md:p-6 mb-6">
<div className="flex items-center gap-2 mb-4">
<BookOpen className="w-5 h-5 text-blue-600" />
<h2 className="font-semibold text-gray-800"></h2>
</div>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => setShowUserManual(true)}
className="flex items-center gap-3 p-4 bg-blue-50 rounded-xl hover:bg-blue-100 transition-colors text-left"
>
<div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
<BookOpen className="w-5 h-5 text-blue-600" />
</div>
<div>
<p className="font-medium text-gray-800"></p>
<p className="text-xs text-gray-500">使</p>
</div>
</motion.button>
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => setShowFeedback(true)}
className="flex items-center gap-3 p-4 bg-emerald-50 rounded-xl hover:bg-emerald-100 transition-colors text-left"
>
<div className="w-10 h-10 bg-emerald-100 rounded-lg flex items-center justify-center">
<MessageSquare className="w-5 h-5 text-emerald-600" />
</div>
<div>
<p className="font-medium text-gray-800"></p>
<p className="text-xs text-gray-500"></p>
</div>
</motion.button>
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => setShowAbout(true)}
className="flex items-center gap-3 p-4 bg-purple-50 rounded-xl hover:bg-purple-100 transition-colors text-left"
>
<div className="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center">
<Info className="w-5 h-5 text-purple-600" />
</div>
<div>
<p className="font-medium text-gray-800"></p>
<p className="text-xs text-gray-500"></p>
</div>
</motion.button>
</div>
</div>
{/* 子账号列表 */}
{loading ? (
<div className="text-center py-12 text-gray-400">...</div>
) : members.length === 0 ? (
<div className="bg-white rounded-lg shadow-sm p-8 text-center">
<div className="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
<Users className="w-8 h-8 text-gray-400" />
</div>
<p className="text-gray-500 mb-2"></p>
<p className="text-gray-400 text-sm mb-4">"添加子账号"</p>
<motion.button
whileHover={{ scale: isMobile ? 1 : 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={handleOpenModal}
className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
</motion.button>
</div>
) : (
<div className="bg-white rounded-lg shadow-sm overflow-hidden">
<div className="p-4 border-b border-gray-100">
<h2 className="font-semibold text-gray-800">
<span className="text-sm text-gray-500">({members.length})</span>
</h2>
</div>
<div className="divide-y divide-gray-100">
{members.map((member) => (
<div
key={member.id}
className="flex items-center justify-between p-4 hover:bg-gray-50 transition-colors"
>
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-gray-100 rounded-full flex items-center justify-center">
<User className="w-5 h-5 text-gray-500" />
</div>
<div>
<p className="font-medium text-gray-800">{member.username}</p>
<div className="flex items-center gap-2 text-sm text-gray-500">
{member.phone && (
<span className="flex items-center gap-1">
<Phone size={12} />
{maskPhone(member.phone)}
</span>
)}
</div>
</div>
</div>
<div className="flex items-center gap-3">
<span className="text-xs text-gray-400">
{new Date(member.created_at).toLocaleDateString('zh-CN')}
</span>
<button
onClick={() => setShowDeleteConfirm(member.id)}
className="p-2 text-red-500 hover:bg-red-50 rounded-lg transition-colors"
>
<Trash2 size={18} />
</button>
</div>
</div>
))}
</div>
</div>
)}
</div>
{/* 添加子账号弹窗 */}
<AnimatePresence>
{showModal && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 20 }}
transition={{ duration: 0.2 }}
className="bg-white rounded-2xl w-full max-w-md shadow-xl"
>
<div className="flex items-center justify-between p-4 md:p-6 border-b border-gray-100">
<h2 className="text-lg md:text-xl font-bold text-gray-800"></h2>
<button
onClick={handleCloseModal}
className="p-2 hover:bg-gray-100 rounded-full transition-colors"
>
<X size={20} className="text-gray-500" />
</button>
</div>
<form onSubmit={handleSubmit} className="p-4 md:p-6 space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1.5">
<span className="text-red-500">*</span>
</label>
<div className="relative">
<User className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
type="text"
value={formData.username}
onChange={(e) => setFormData({ ...formData, username: e.target.value })}
className="w-full pl-10 pr-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all"
placeholder="请输入用户名至少3个字符"
required
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1.5">
<span className="text-red-500">*</span>
</label>
<div className="relative">
<User className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
type="text"
value={formData.displayName}
onChange={(e) => setFormData({ ...formData, displayName: e.target.value })}
className="w-full pl-10 pr-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all"
placeholder="请输入姓名"
required
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1.5">
<span className="text-red-500">*</span>
</label>
<div className="relative">
<Phone className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
type="tel"
value={formData.phone}
onChange={(e) => setFormData({ ...formData, phone: e.target.value })}
className="w-full pl-10 pr-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all"
placeholder="请输入手机号"
required
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1.5">
<span className="text-red-500">*</span>
</label>
<input
type="password"
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
className="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all"
placeholder="请输入密码至少6个字符"
required
/>
</div>
{error && (
<motion.p
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
className="text-red-500 text-sm text-center bg-red-50 py-2 rounded-lg flex items-center justify-center gap-2"
>
<AlertCircle size={16} />
{error}
</motion.p>
)}
<div className="flex gap-3 pt-2">
<button
type="button"
onClick={handleCloseModal}
className="flex-1 py-2.5 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors font-medium"
>
</button>
<motion.button
whileHover={{ scale: isMobile ? 1 : 1.02 }}
whileTap={{ scale: 0.98 }}
type="submit"
disabled={submitting}
className="flex-1 py-2.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium disabled:opacity-50 disabled:cursor-not-allowed"
>
{submitting ? '创建中...' : '创建子账号'}
</motion.button>
</div>
</form>
</motion.div>
</div>
)}
</AnimatePresence>
{/* 编辑主账号弹窗 */}
<AnimatePresence>
{showEditMasterModal && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 20 }}
transition={{ duration: 0.2 }}
className="bg-white rounded-2xl w-full max-w-md shadow-xl"
>
<div className="flex items-center justify-between p-4 md:p-6 border-b border-gray-100">
<h2 className="text-lg md:text-xl font-bold text-gray-800"></h2>
<button
onClick={handleCloseEditMaster}
className="p-2 hover:bg-gray-100 rounded-full transition-colors"
>
<X size={20} className="text-gray-500" />
</button>
</div>
<form onSubmit={handleUpdateMaster} className="p-4 md:p-6 space-y-4">
{/* 公司信息(只读) */}
<div className="bg-gray-50 rounded-lg p-3 space-y-3">
<p className="text-xs font-medium text-gray-500 uppercase tracking-wider"></p>
{/* 公司名称 - 只读 */}
<div>
<label className="block text-sm font-medium text-gray-500 mb-1">
</label>
<input
type="text"
value={companyInfo?.name || '-'}
disabled
className="w-full px-3 py-2 border border-gray-200 bg-gray-100 text-gray-500 rounded-lg cursor-not-allowed text-sm"
/>
</div>
{/* 角色类型 - 只读 */}
<div>
<label className="block text-sm font-medium text-gray-500 mb-1">
</label>
<input
type="text"
value={companyInfo?.role === 'purchaser' ? '采购商' : companyInfo?.role === 'textile' ? '纺织厂' : companyInfo?.role === 'washing' ? '水洗厂' : '-'}
disabled
className="w-full px-3 py-2 border border-gray-200 bg-gray-100 text-gray-500 rounded-lg cursor-not-allowed text-sm"
/>
</div>
</div>
{/* 账号信息(只读) */}
<div className="bg-gray-50 rounded-lg p-3 space-y-3">
<p className="text-xs font-medium text-gray-500 uppercase tracking-wider"></p>
{/* 用户名 - 只读 */}
<div>
<label className="block text-sm font-medium text-gray-500 mb-1">
</label>
<input
type="text"
value={auth.user?.username || ''}
disabled
className="w-full px-3 py-2 border border-gray-200 bg-gray-100 text-gray-500 rounded-lg cursor-not-allowed text-sm"
/>
</div>
</div>
{/* 可编辑字段 */}
<div className="border-t border-gray-200 pt-4 space-y-4">
{/* 姓名 - 可编辑 */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1.5">
<span className="text-red-500">*</span>
</label>
<div className="relative">
<User className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
type="text"
value={masterFormData.displayName}
onChange={(e) => setMasterFormData({ ...masterFormData, displayName: e.target.value })}
className="w-full pl-10 pr-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all"
placeholder="请输入姓名"
required
/>
</div>
</div>
{/* 手机号 - 可编辑 */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1.5">
<span className="text-red-500">*</span>
</label>
<div className="relative">
<Phone className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
type="tel"
value={masterFormData.phone}
onChange={(e) => setMasterFormData({ ...masterFormData, phone: e.target.value })}
className="w-full pl-10 pr-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all"
placeholder="请输入手机号"
required
/>
</div>
</div>
{/* 登录密码 - 可编辑 */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1.5">
</label>
<input
type="password"
value={masterFormData.password || ''}
onChange={(e) => setMasterFormData({ ...masterFormData, password: e.target.value })}
className="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all"
placeholder="请输入新密码至少6个字符"
/>
<p className="text-xs text-gray-400 mt-1"></p>
</div>
</div>
{error && (
<motion.p
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
className="text-red-500 text-sm text-center bg-red-50 py-2 rounded-lg flex items-center justify-center gap-2"
>
<AlertCircle size={16} />
{error}
</motion.p>
)}
<div className="flex gap-3 pt-2">
<button
type="button"
onClick={handleCloseEditMaster}
className="flex-1 py-2.5 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors font-medium"
>
</button>
<motion.button
whileHover={{ scale: isMobile ? 1 : 1.02 }}
whileTap={{ scale: 0.98 }}
type="submit"
disabled={editingMaster}
className="flex-1 py-2.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium disabled:opacity-50 disabled:cursor-not-allowed"
>
{editingMaster ? '保存中...' : '保存修改'}
</motion.button>
</div>
</form>
</motion.div>
</div>
)}
</AnimatePresence>
{/* 删除确认弹窗 */}
<AnimatePresence>
{showDeleteConfirm && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.2 }}
className="bg-white rounded-2xl w-full max-w-sm shadow-xl p-6"
>
<div className="text-center">
<div className="w-12 h-12 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-4">
<AlertCircle className="w-6 h-6 text-red-500" />
</div>
<h3 className="text-lg font-bold text-gray-800 mb-2"></h3>
<p className="text-gray-500 mb-6"></p>
<div className="flex gap-3">
<button
onClick={() => setShowDeleteConfirm(null)}
className="flex-1 py-2.5 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors font-medium"
>
</button>
<button
onClick={() => handleDelete(showDeleteConfirm)}
className="flex-1 py-2.5 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors font-medium"
>
</button>
</div>
</div>
</motion.div>
</div>
)}
</AnimatePresence>
{/* 用户手册弹窗 */}
<UserManual isOpen={showUserManual} onClose={() => setShowUserManual(false)} />
{/* 用户反馈弹窗 */}
<FeedbackModal isOpen={showFeedback} onClose={() => setShowFeedback(false)} />
{/* 关于窗口 */}
<AboutModal isOpen={showAbout} onClose={() => setShowAbout(false)} />
</div>
);
}