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, AnimatePresence } from 'framer-motion';
|
2026-05-27 03:17:46 +00:00
|
|
|
|
import { Users, ArrowLeft, Plus, X, User, Phone, Shield, Trash2, AlertCircle, Edit2, Building2, BookOpen, MessageSquare, Info } from 'lucide-react';
|
2026-05-26 05:15:10 +00:00
|
|
|
|
import { useResponsive } from '../hooks/useResponsive';
|
|
|
|
|
|
import { encryptPhone, maskPhone } from '../utils/crypto';
|
2026-05-27 03:17:46 +00:00
|
|
|
|
import { UserManual } from '../components/UserManual';
|
|
|
|
|
|
import { FeedbackModal } from '../components/FeedbackModal';
|
|
|
|
|
|
import { AboutModal } from '../components/VersionUpdate';
|
2026-05-26 05:15:10 +00:00
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
2026-05-27 03:17:46 +00:00
|
|
|
|
// 帮助系统弹窗状态
|
|
|
|
|
|
const [showUserManual, setShowUserManual] = useState(false);
|
|
|
|
|
|
const [showFeedback, setShowFeedback] = useState(false);
|
|
|
|
|
|
const [showAbout, setShowAbout] = useState(false);
|
|
|
|
|
|
|
2026-05-26 05:15:10 +00:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-05-28 05:54:23 +00:00
|
|
|
|
// 根据角色获取主题色
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
2026-05-26 05:15:10 +00:00
|
|
|
|
// 如果不是主账号,显示无权限
|
|
|
|
|
|
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)}
|
2026-05-28 05:54:23 +00:00
|
|
|
|
className={`px-6 py-2 ${theme.button} text-white rounded-lg transition-colors`}
|
2026-05-26 05:15:10 +00:00
|
|
|
|
>
|
|
|
|
|
|
返回
|
|
|
|
|
|
</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}
|
2026-05-28 05:54:23 +00:00
|
|
|
|
className={`flex items-center gap-2 px-4 py-2 ${theme.button} text-white rounded-lg transition-colors text-sm md:text-base`}
|
2026-05-26 05:15:10 +00:00
|
|
|
|
>
|
|
|
|
|
|
<Plus size={18} />
|
|
|
|
|
|
添加子账号
|
|
|
|
|
|
</motion.button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 主账号信息卡片 */}
|
2026-05-28 05:54:23 +00:00
|
|
|
|
<div className={`bg-gradient-to-r ${theme.gradient} rounded-xl p-4 md:p-6 mb-6 text-white`}>
|
2026-05-26 05:15:10 +00:00
|
|
|
|
<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>
|
2026-05-28 05:54:23 +00:00
|
|
|
|
<p className="text-sm text-white/70">当前登录账号</p>
|
2026-05-26 05:15:10 +00:00
|
|
|
|
<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>
|
|
|
|
|
|
|
2026-05-27 03:17:46 +00:00
|
|
|
|
{/* 帮助与支持卡片 */}
|
|
|
|
|
|
<div className="bg-white rounded-lg shadow-sm p-4 md:p-6 mb-6">
|
|
|
|
|
|
<div className="flex items-center gap-2 mb-4">
|
2026-05-28 05:54:23 +00:00
|
|
|
|
<BookOpen className={`w-5 h-5 ${theme.text}`} />
|
2026-05-27 03:17:46 +00:00
|
|
|
|
<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)}
|
2026-05-28 05:54:23 +00:00
|
|
|
|
className={`flex items-center gap-3 p-4 ${theme.bg} rounded-xl ${theme.bgHover} transition-colors text-left`}
|
2026-05-27 03:17:46 +00:00
|
|
|
|
>
|
2026-05-28 05:54:23 +00:00
|
|
|
|
<div className={`w-10 h-10 ${theme.bg} rounded-lg flex items-center justify-center`}>
|
|
|
|
|
|
<BookOpen className={`w-5 h-5 ${theme.text}`} />
|
2026-05-27 03:17:46 +00:00
|
|
|
|
</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)}
|
2026-05-28 05:54:23 +00:00
|
|
|
|
className={`flex items-center gap-3 p-4 ${theme.bg} rounded-xl ${theme.bgHover} transition-colors text-left`}
|
2026-05-27 03:17:46 +00:00
|
|
|
|
>
|
2026-05-28 05:54:23 +00:00
|
|
|
|
<div className={`w-10 h-10 ${theme.bg} rounded-lg flex items-center justify-center`}>
|
|
|
|
|
|
<MessageSquare className={`w-5 h-5 ${theme.text}`} />
|
2026-05-27 03:17:46 +00:00
|
|
|
|
</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)}
|
2026-05-28 05:54:23 +00:00
|
|
|
|
className={`flex items-center gap-3 p-4 ${theme.bg} rounded-xl ${theme.bgHover} transition-colors text-left`}
|
2026-05-27 03:17:46 +00:00
|
|
|
|
>
|
2026-05-28 05:54:23 +00:00
|
|
|
|
<div className={`w-10 h-10 ${theme.bg} rounded-lg flex items-center justify-center`}>
|
|
|
|
|
|
<Info className={`w-5 h-5 ${theme.text}`} />
|
2026-05-27 03:17:46 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="font-medium text-gray-800">关于</p>
|
|
|
|
|
|
<p className="text-xs text-gray-500">版本信息与版权</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</motion.button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-26 05:15:10 +00:00
|
|
|
|
{/* 子账号列表 */}
|
|
|
|
|
|
{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}
|
2026-05-28 05:54:23 +00:00
|
|
|
|
className={`px-6 py-2 ${theme.button} text-white rounded-lg transition-colors`}
|
2026-05-26 05:15:10 +00:00
|
|
|
|
>
|
|
|
|
|
|
添加子账号
|
|
|
|
|
|
</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 })}
|
2026-05-28 05:54:23 +00:00
|
|
|
|
className={`w-full pl-10 pr-4 py-2.5 border border-gray-300 rounded-lg ${theme.ring} focus:border-transparent transition-all`}
|
2026-05-26 05:15:10 +00:00
|
|
|
|
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 })}
|
2026-05-28 05:54:23 +00:00
|
|
|
|
className={`w-full pl-10 pr-4 py-2.5 border border-gray-300 rounded-lg ${theme.ring} focus:border-transparent transition-all`}
|
2026-05-26 05:15:10 +00:00
|
|
|
|
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 })}
|
2026-05-28 05:54:23 +00:00
|
|
|
|
className={`w-full pl-10 pr-4 py-2.5 border border-gray-300 rounded-lg ${theme.ring} focus:border-transparent transition-all`}
|
2026-05-26 05:15:10 +00:00
|
|
|
|
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 })}
|
2026-05-28 05:54:23 +00:00
|
|
|
|
className={`w-full px-4 py-2.5 border border-gray-300 rounded-lg ${theme.ring} focus:border-transparent transition-all`}
|
2026-05-26 05:15:10 +00:00
|
|
|
|
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}
|
2026-05-28 05:54:23 +00:00
|
|
|
|
className={`flex-1 py-2.5 ${theme.button} text-white rounded-lg transition-colors font-medium disabled:opacity-50 disabled:cursor-not-allowed`}
|
2026-05-26 05:15:10 +00:00
|
|
|
|
>
|
|
|
|
|
|
{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" />
|
2026-05-28 05:54:23 +00:00
|
|
|
|
<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
|
|
|
|
|
|
/>
|
2026-05-26 05:15:10 +00:00
|
|
|
|
</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" />
|
2026-05-28 05:54:23 +00:00
|
|
|
|
<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
|
|
|
|
|
|
/>
|
2026-05-26 05:15:10 +00:00
|
|
|
|
</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 })}
|
2026-05-28 05:54:23 +00:00
|
|
|
|
className={`w-full px-4 py-2.5 border border-gray-300 rounded-lg ${theme.ring} focus:border-transparent transition-all`}
|
2026-05-26 05:15:10 +00:00
|
|
|
|
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}
|
2026-05-28 05:54:23 +00:00
|
|
|
|
className={`flex-1 py-2.5 ${theme.button} text-white rounded-lg transition-colors font-medium disabled:opacity-50 disabled:cursor-not-allowed`}
|
2026-05-26 05:15:10 +00:00
|
|
|
|
>
|
|
|
|
|
|
{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>
|
2026-05-27 03:17:46 +00:00
|
|
|
|
|
|
|
|
|
|
{/* 用户手册弹窗 */}
|
|
|
|
|
|
<UserManual isOpen={showUserManual} onClose={() => setShowUserManual(false)} />
|
|
|
|
|
|
|
|
|
|
|
|
{/* 用户反馈弹窗 */}
|
|
|
|
|
|
<FeedbackModal isOpen={showFeedback} onClose={() => setShowFeedback(false)} />
|
|
|
|
|
|
|
|
|
|
|
|
{/* 关于窗口 */}
|
|
|
|
|
|
<AboutModal isOpen={showAbout} onClose={() => setShowAbout(false)} />
|
2026-05-26 05:15:10 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|