/** * filekorolheader: 用户管理页面 - 用户查询和管理页面 * 功能:用户列表查询、搜索筛选、详情查看、用户管理 * 路径:/central-config/tenant/user-management * 规范:遵循crop-x/docs/开发项目规范.md,使用SearchFormPagination公共组件,shadcn语义化样式 */ 'use client'; import { useReducer, useEffect, useState, useCallback, useMemo } from 'react'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { Eye, Edit, Lock, UserX, UserCheck } from 'lucide-react'; import { UserDetailDialog } from './components/UserDetailDialog'; import { SearchFormPagination, SearchFieldConfig, TableColumnConfig } from '@/components/common/searchFormPagination'; import { fetchUsers, transformUserData, UsersQueryParams, User, UsersApiResponse, PaginationState } from './components/userManagementApi'; import { UserManagementHeader } from './components/UserManagementHeader'; import { UserManagementStatsCards } from './components/UserManagementStatsCards'; import { UserFilters } from './types'; // 移除了Enterprise的引用,因为新实现中不再需要 // 用户管理状态管理 interface UserManagementState { users: User[]; loading: boolean; error: string | null; pagination: PaginationState; filters: UserFilters; sortBy?: string; sortOrder: 'asc' | 'desc'; selectedUser: User | null; showDetailDialog: boolean; } type UserManagementAction = | { type: 'SET_USERS'; payload: { data: User[]; pagination: PaginationState } } | { type: 'SET_LOADING'; payload: boolean } | { type: 'SET_ERROR'; payload: string | null } | { type: 'SET_FILTERS'; payload: Partial } | { type: 'SET_SORT'; payload: { sortBy?: string; sortOrder: 'asc' | 'desc' } } | { type: 'SET_PAGINATION'; payload: Partial } | { type: 'SET_SELECTED_USER'; payload: User | null } | { type: 'TOGGLE_DETAIL_DIALOG'; payload: boolean } | { type: 'REFRESH_DATA' }; const userManagementReducer = (state: UserManagementState, action: UserManagementAction): UserManagementState => { switch (action.type) { case 'SET_USERS': return { ...state, users: action.payload.data, pagination: action.payload.pagination, loading: false, error: null, }; case 'SET_LOADING': return { ...state, loading: action.payload }; case 'SET_ERROR': return { ...state, error: action.payload, loading: false }; case 'SET_FILTERS': return { ...state, filters: { ...state.filters, ...action.payload } }; case 'SET_SORT': return { ...state, sortBy: action.payload.sortBy, sortOrder: action.payload.sortOrder }; case 'SET_PAGINATION': return { ...state, pagination: { ...state.pagination, ...action.payload } }; case 'SET_SELECTED_USER': return { ...state, selectedUser: action.payload }; case 'TOGGLE_DETAIL_DIALOG': return { ...state, showDetailDialog: !state.showDetailDialog }; case 'REFRESH_DATA': return { ...state, error: null }; default: return state; } }; const initialState: UserManagementState = { users: [], loading: false, error: null, pagination: { page: 1, size: 10, total: 0, totalPages: 0, hasNext: false, hasPrev: false, }, filters: { searchKeyword: '', statusFilter: 'all', typeFilter: 'all' }, sortBy: 'created_at', sortOrder: 'desc', selectedUser: null, showDetailDialog: false, }; export default function TenantUserManagementPage() { const [state, dispatch] = useReducer(userManagementReducer, initialState); const [searchFilters, setSearchFilters] = useState>({}); // 搜索字段配置 const searchFields: SearchFieldConfig[] = useMemo(() => [ { key: 'search', label: '搜索', type: 'text', placeholder: '搜索用户名、姓名、邮箱...', }, { key: 'status', label: '用户状态', type: 'select', defaultValue: 'all', options: [ { value: 'all', label: '全部状态' }, { value: 'active', label: '活跃' }, { value: 'inactive', label: '未激活' }, ], }, { key: 'type', label: '用户类型', type: 'select', defaultValue: 'all', options: [ { value: 'all', label: '全部类型' }, { value: 'admin', label: '管理员' }, { value: 'user', label: '普通用户' }, { value: 'staff', label: '员工' }, ], }, ], []); // 表格列配置 const columns: TableColumnConfig[] = useMemo(() => [ { key: 'username', label: '用户名', sortable: true, render: (value: string, user: User) => (
{value}
), }, { key: 'fullName', label: '姓名', sortable: true, render: (value: string) => value || '-', }, { key: 'email', label: '邮箱', sortable: true, render: (value: string) => value || '-', }, { key: 'isActive', label: '状态', sortable: true, render: (value: boolean) => (
{value ? '活跃' : '未激活'}
), }, { key: 'isSuperuser', label: '角色', sortable: true, render: (value: boolean, user: User) => { if (value) { return (
超级管理员
); } return (
普通用户
); }, }, { key: 'isVerified', label: '验证', sortable: true, render: (value: boolean) => (
{value ? '已验证' : '未验证'}
), }, { key: 'lastLoginAt', label: '最后登录', sortable: true, render: (value: string) => { if (!value) return '-'; try { const date = new Date(value); return date.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', }); } catch { return value; } }, }, { key: 'actions', label: '操作', render: (_, user: User) => (
), }, ], []); // 加载用户数据 const loadUsers = useCallback(async (resetPage = false) => { try { dispatch({ type: 'SET_LOADING', payload: true }); const params: UsersQueryParams = { page: resetPage ? 1 : state.pagination.page, size: state.pagination.size, is_active: true, }; // 添加搜索条件 if (searchFilters.search) { params.search = searchFilters.search; } if (searchFilters.status && searchFilters.status !== 'all') { params.is_active = searchFilters.status === 'active'; } if (searchFilters.type && searchFilters.type !== 'all') { // For user type filtering, we'll need to handle this differently based on the API // For now, we'll filter on the client side if needed } if (state.sortBy) { params.order_by = state.sortBy; params.sort_order = state.sortOrder; } const response: UsersApiResponse = await fetchUsers(params); const transformedUsers = response.data.map(transformUserData); dispatch({ type: 'SET_USERS', payload: { data: transformedUsers, pagination: { page: response.page, size: response.size, total: response.total, totalPages: response.total_pages, hasNext: response.has_next, hasPrev: response.has_prev, } } }); } catch (error) { console.error('Failed to load users:', error); dispatch({ type: 'SET_ERROR', payload: error instanceof Error ? error.message : '加载用户数据失败' }); } }, [state.pagination.page, state.pagination.size, state.sortBy, state.sortOrder, searchFilters]); // 搜索处理 const handleSearch = useCallback((filters: Record) => { setSearchFilters(filters); dispatch({ type: 'SET_PAGINATION', payload: { page: 1 } }); }, []); // 排序处理 const handleSort = useCallback((sortBy: string, sortOrder: 'asc' | 'desc') => { dispatch({ type: 'SET_SORT', payload: { sortBy, sortOrder } }); }, []); // 分页处理 const handlePageChange = useCallback((page: number) => { if (page < 1) { page = 1; } else if (page > state.pagination.totalPages && state.pagination.totalPages > 0) { page = state.pagination.totalPages; } dispatch({ type: 'SET_PAGINATION', payload: { page } }); }, [state.pagination.totalPages]); // 每页条数变化处理 const handleSizeChange = useCallback((size: number) => { dispatch({ type: 'SET_PAGINATION', payload: { size, page: 1 } }); }, []); // 查看详情 const handleViewDetail = (user: User) => { dispatch({ type: 'SET_SELECTED_USER', payload: user }); dispatch({ type: 'TOGGLE_DETAIL_DIALOG', payload: true }); }; // 编辑用户 const handleEdit = (user: User) => { toast.info('编辑功能开发中...'); }; // 切换用户状态 const handleToggleStatus = (user: User) => { const newStatus = !user.isActive; const statusText = newStatus ? '激活' : '停用'; if (!confirm(`确定要${statusText}用户 ${user.fullName || user.username} 吗?`)) return; toast.info(`${statusText}功能开发中...`); }; // 重置密码 const handleResetPassword = (user: User) => { if (!confirm(`确定要重置用户 ${user.fullName || user.username} 的密码吗?`)) return; toast.info('重置密码功能开发中...'); }; // 统计数据计算 const stats = useMemo(() => [ { label: '总用户数', value: state.pagination.total, color: 'text-blue-600 dark:text-blue-400', bg: 'bg-blue-50 dark:bg-blue-950', }, { label: '活跃用户', value: state.users.filter(u => u.isActive).length, color: 'text-green-600 dark:text-green-400', bg: 'bg-green-50 dark:bg-green-950', }, { label: '管理员', value: state.users.filter(u => u.isSuperuser).length, color: 'text-purple-600 dark:text-purple-400', bg: 'bg-purple-50 dark:bg-purple-950', }, { label: '已验证', value: state.users.filter(u => u.isVerified).length, color: 'text-orange-600 dark:text-orange-400', bg: 'bg-orange-50 dark:bg-orange-950', }, ], [state.users, state.pagination.total]); // 加载数据 useEffect(() => { loadUsers(); }, []); return (
{/* 页面标题 */} {/* 统计卡片 */} {/* 搜索表单、数据表格和分页 */} toast.info('新建用户功能开发中...')}> 新建用户 } searchFields={searchFields} columns={columns} data={state.users} loading={state.loading} error={state.error} pagination={state.pagination} sortBy={state.sortBy} sortOrder={state.sortOrder} onPageChange={handlePageChange} onSizeChange={handleSizeChange} onSearch={handleSearch} onSort={handleSort} emptyText="暂无用户数据" sizeOptions={[10, 20, 50, 100]} /> {/* 用户详情对话框 */} dispatch({ type: 'TOGGLE_DETAIL_DIALOG', payload: open })} user={state.selectedUser} />
); }