iloom-flatten/src/pages/MemberManage.tsx

878 lines
34 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
};
// 根据角色获取主题色
const getThemeColors = () => {
const role = companyInfo?.role || 'purchaser';
switch (role) {
case 'textile':
return {
primary: 'emerald',
bg: 'bg-emerald-50',
bgHover: 'hover:bg-emerald-100',
text: 'text-emerald-500',
border: 'border-emerald-200',
ring: 'focus:ring-emerald-400',
button: 'bg-emerald-400 hover:bg-emerald-500',
gradient: 'from-emerald-400 to-teal-400'
};
case 'washing':
return {
primary: 'violet',
bg: 'bg-violet-50',
bgHover: 'hover:bg-violet-100',
text: 'text-violet-500',
border: 'border-violet-200',
ring: 'focus:ring-violet-400',
button: 'bg-violet-400 hover:bg-violet-500',
gradient: 'from-violet-400 to-fuchsia-400'
};
default:
return {
primary: 'amber',
bg: 'bg-amber-50',
bgHover: 'hover:bg-amber-100',
text: 'text-amber-500',
border: 'border-amber-200',
ring: 'focus:ring-amber-400',
button: 'bg-amber-400 hover:bg-amber-500',
gradient: 'from-amber-400 to-orange-400'
};
}
};
const theme = getThemeColors();
// 如果不是主账号,显示无权限
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 ${theme.button} text-white rounded-lg 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 ${theme.button} text-white rounded-lg transition-colors text-sm md:text-base`}
>
<Plus size={18} />
</motion.button>
</div>
{/* 主账号信息卡片 */}
<div className={`bg-gradient-to-r ${theme.gradient} 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-white/70"></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 ${theme.text}`} />
<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 ${theme.bg} rounded-xl ${theme.bgHover} transition-colors text-left`}
>
<div className={`w-10 h-10 ${theme.bg} rounded-lg flex items-center justify-center`}>
<BookOpen className={`w-5 h-5 ${theme.text}`} />
</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 ${theme.bg} rounded-xl ${theme.bgHover} transition-colors text-left`}
>
<div className={`w-10 h-10 ${theme.bg} rounded-lg flex items-center justify-center`}>
<MessageSquare className={`w-5 h-5 ${theme.text}`} />
</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 ${theme.bg} rounded-xl ${theme.bgHover} transition-colors text-left`}
>
<div className={`w-10 h-10 ${theme.bg} rounded-lg flex items-center justify-center`}>
<Info className={`w-5 h-5 ${theme.text}`} />
</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 ${theme.button} text-white rounded-lg 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 ${theme.ring} focus:border-transparent 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 ${theme.ring} focus:border-transparent 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 ${theme.ring} focus:border-transparent 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 ${theme.ring} focus:border-transparent 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 ${theme.button} text-white rounded-lg 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 ${theme.ring} focus:border-transparent 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 ${theme.ring} focus:border-transparent 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 ${theme.ring} focus:border-transparent 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 ${theme.button} text-white rounded-lg 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>
);
}