import React, { useEffect, useMemo, useState } from 'react'; import { Avatar, Button, Form, Input, Modal, Popconfirm, Select, Space, Switch, Table, Tag, Typography, message } from 'antd'; import { AppstoreOutlined, EditOutlined, LockOutlined, PlusOutlined, ReloadOutlined, SyncOutlined, DeleteOutlined, SafetyCertificateOutlined, UserOutlined } from '@ant-design/icons'; import dayjs from 'dayjs'; import { SycmCard } from '../../components/Common'; import { createUser, deleteUser, getDepartments, getUserPagePermissions, getUsers, resetPassword, syncDingTalkUsers, updateUser, updateUserAdminRole, updateUserPagePermissions, } from '../../api/admin'; const { Text } = Typography; const { Search } = Input; const ROLE_LABELS = { admin: '管理员', analyst: '分析师', user: '用户', guest: '访客', }; const SOURCE_META = { local: { color: 'default', text: '本地账号' }, dingtalk: { color: 'blue', text: '钉钉' }, google: { color: 'red', text: 'Google' }, }; const UserManagement = () => { const [users, setUsers] = useState([]); const [departments, setDepartments] = useState([]); const [loading, setLoading] = useState(false); const [saving, setSaving] = useState(false); const [syncing, setSyncing] = useState(false); const [switchingAdminId, setSwitchingAdminId] = useState(null); const [modalVisible, setModalVisible] = useState(false); const [resetVisible, setResetVisible] = useState(false); const [permissionVisible, setPermissionVisible] = useState(false); const [editingUser, setEditingUser] = useState(null); const [resetUser, setResetUser] = useState(null); const [permissionUser, setPermissionUser] = useState(null); const [pagePermissions, setPagePermissions] = useState([]); const [permissionSaving, setPermissionSaving] = useState(false); const [keyword, setKeyword] = useState(''); const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 }); const [form] = Form.useForm(); const [resetForm] = Form.useForm(); const departmentOptions = useMemo( () => departments .filter((item) => item.status === 'active') .map((item) => ({ label: item.name, value: item.id, })), [departments] ); const fetchDepartments = async () => { try { const data = await getDepartments(); setDepartments(Array.isArray(data) ? data : []); } catch (error) { message.error(error.response?.data?.error || '获取部门列表失败'); } }; const fetchUsers = async (page = pagination.current, pageSize = pagination.pageSize, nextKeyword = keyword) => { try { setLoading(true); const res = await getUsers({ page, size: pageSize, keyword: nextKeyword, }); setUsers(res.data || []); setPagination({ current: res.page || page, pageSize: res.size || pageSize, total: res.total || 0, }); } catch (error) { message.error(error.response?.data?.error || '获取用户列表失败'); } finally { setLoading(false); } }; useEffect(() => { fetchDepartments(); fetchUsers(1, 10, ''); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const openCreateModal = () => { setEditingUser(null); form.resetFields(); form.setFieldsValue({ role: 'user', status: 'active', }); setModalVisible(true); }; const openEditModal = (record) => { setEditingUser(record); form.setFieldsValue({ username: record.username, name: record.name, email: record.email, mobile: record.mobile, jobTitle: record.jobTitle, role: record.role, departmentId: record.department_id || undefined, status: record.status, }); setModalVisible(true); }; const handleSave = async () => { try { const values = await form.validateFields(); setSaving(true); if (editingUser) { await updateUser(editingUser.id, values); message.success('用户信息已更新'); } else { await createUser(values); message.success('用户已创建'); } setModalVisible(false); fetchUsers(); } catch (error) { if (!error?.errorFields) { message.error(error.response?.data?.error || '保存用户失败'); } } finally { setSaving(false); } }; const handleDelete = async (id) => { try { await deleteUser(id); message.success('用户已删除'); fetchUsers(); } catch (error) { message.error(error.response?.data?.error || '删除用户失败'); } }; const handleToggleAdminRole = async (record) => { const nextIsAdmin = record.role !== 'admin'; try { setSwitchingAdminId(record.id); const res = await updateUserAdminRole(record.id, { isAdmin: nextIsAdmin }); message.success(res.message || (nextIsAdmin ? '已授予管理员权限' : '已取消管理员权限')); fetchUsers(pagination.current, pagination.pageSize, keyword); } catch (error) { message.error(error.response?.data?.error || '更新管理员权限失败'); } finally { setSwitchingAdminId(null); } }; const openPagePermissionModal = async (record) => { try { setPermissionUser(record); const data = await getUserPagePermissions(record.id); setPagePermissions(Array.isArray(data.permissions) ? data.permissions : []); setPermissionVisible(true); } catch (error) { message.error(error.response?.data?.error || '获取用户可见页面失败'); } }; const handlePagePermissionToggle = (pageKey, userAllowed) => { setPagePermissions((prev) => prev.map((item) => (item.pageKey === pageKey ? { ...item, userAllowed, effectiveAllowed: Boolean(item.baseAllowed) || userAllowed } : item)) ); }; const handleSavePagePermissions = async () => { if (!permissionUser) { return; } try { setPermissionSaving(true); const res = await updateUserPagePermissions(permissionUser.id, { permissions: pagePermissions.map((item) => ({ pageKey: item.pageKey, allowed: Boolean(item.userAllowed), })), }); setPagePermissions(Array.isArray(res.permissions) ? res.permissions : []); message.success(res.message || '用户可见页面已更新'); setPermissionVisible(false); } catch (error) { message.error(error.response?.data?.error || '更新用户可见页面失败'); } finally { setPermissionSaving(false); } }; const openResetModal = (record) => { setResetUser(record); resetForm.resetFields(); setResetVisible(true); }; const handleResetPassword = async () => { try { const values = await resetForm.validateFields(); setSaving(true); await resetPassword(resetUser.id, { newPassword: values.newPassword }); message.success('密码已重置'); setResetVisible(false); } catch (error) { if (!error?.errorFields) { message.error(error.response?.data?.error || '重置密码失败'); } } finally { setSaving(false); } }; const handleSync = async () => { try { setSyncing(true); const res = await syncDingTalkUsers(); message.success(`同步完成,共处理 ${res.total || 0} 位钉钉用户`); await Promise.all([fetchUsers(1, pagination.pageSize, keyword), fetchDepartments()]); } catch (error) { message.error(error.response?.data?.error || '同步钉钉用户失败'); } finally { setSyncing(false); } }; const columns = [ { title: '用户', key: 'user', width: 280, render: (_, record) => ( } size={42} />
{record.name || record.username}
{record.username}
{record.email}
), }, { title: '来源', dataIndex: 'authSource', width: 110, render: (value) => { const meta = SOURCE_META[value] || { color: 'default', text: value || '未知' }; return {meta.text}; }, }, { title: '部门信息', key: 'department', width: 220, render: (_, record) => (
{record.departmentNames || record.departmentName || '-'}
{record.jobTitle && {record.jobTitle}}
), }, { title: '角色 / 状态', key: 'roleStatus', width: 160, render: (_, record) => ( {ROLE_LABELS[record.role] || record.role} {record.status === 'active' ? '启用' : '停用'} ), }, { title: '联系信息', key: 'contact', width: 180, render: (_, record) => (
{record.mobile || '-'}
{(record.dingtalkUserId || record.googleSub) && ( {record.dingtalkUserId ? `钉钉ID: ${record.dingtalkUserId}` : `Google: ${record.googleSub}`} )}
), }, { title: '最后登录', dataIndex: 'lastLoginAt', width: 170, render: (value) => (value ? dayjs(value).format('YYYY-MM-DD HH:mm') : '-'), }, { title: '操作', key: 'actions', width: 320, fixed: 'right', render: (_, record) => ( handleToggleAdminRole(record)} > handleDelete(record.id)}> ), }, ]; return (
{ setKeyword(value); fetchUsers(1, pagination.pageSize, value); }} style={{ width: 320 }} />
fetchUsers(page, pageSize), }} /> setModalVisible(false)} onOk={handleSave} confirmLoading={saving} width={560} >
{!editingUser && ( )} ({ value, label, }))} />