From f1c3c2312700bbe47b982950bbf34034b367d421 Mon Sep 17 00:00:00 2001 From: peng Date: Tue, 4 Nov 2025 08:58:07 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=9F=E4=BA=A7=E7=AE=A1=E7=90=86=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=20-=20=E7=94=A8=E6=88=B7=E7=AE=A1=E7=90=86=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E9=9B=86=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/UserDetailDialog.tsx | 340 +++++++++--- .../user-management/components/UserList.tsx | 315 +++++++---- .../components/UserManagementFilters.tsx | 23 +- .../components/UserManagementHeader.tsx | 67 ++- .../components/UserManagementStatsCards.tsx | 64 +-- .../components/userManagementApi.ts | 193 +++++++ .../tenant/user-management/page.tsx | 519 ++++++++++-------- .../tenant/user-management/types.ts | 68 ++- 8 files changed, 1098 insertions(+), 491 deletions(-) create mode 100644 crop-x/src/app/(app)/central-config/tenant/user-management/components/userManagementApi.ts diff --git a/crop-x/src/app/(app)/central-config/tenant/user-management/components/UserDetailDialog.tsx b/crop-x/src/app/(app)/central-config/tenant/user-management/components/UserDetailDialog.tsx index 3ec4a32..5b58708 100644 --- a/crop-x/src/app/(app)/central-config/tenant/user-management/components/UserDetailDialog.tsx +++ b/crop-x/src/app/(app)/central-config/tenant/user-management/components/UserDetailDialog.tsx @@ -1,112 +1,282 @@ -'use client'; +/** + * filekorolheader: 用户详情对话框组件 - 用户详细信息展示界面 + * 功能:用户详细信息展示、多标签页布局、状态和权限信息 + * 路径:/central-config/tenant/user-management/components/UserDetailDialog + * 规范:遵循crop-x/docs/开发项目规范.md,使用shadcn/ui组件,TypeScript类型安全 + */ -import React from 'react'; +import { User } from '../types'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Label } from '@/components/ui/label'; import { Badge } from '@/components/ui/badge'; -import { User, UserStatus, UserType } from '../types'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { Card } from '@/components/ui/card'; +import { User, Mail, Phone, Calendar, Building, Shield, CheckCircle, XCircle, Clock } from 'lucide-react'; interface UserDetailDialogProps { open: boolean; onOpenChange: (open: boolean) => void; - selectedUser: User | null; + user: User | null; } export function UserDetailDialog({ open, onOpenChange, - selectedUser + user }: UserDetailDialogProps) { - const getStatusBadge = (status: UserStatus) => { - switch (status) { - case 'active': - return 正常; - case 'frozen': - return 已冻结; - case 'inactive': - return 停用; - default: - return {status}; + const getStatusBadge = (isActive: boolean) => { + return isActive ? ( + 正常 + ) : ( + 停用 + ); + }; + + const getRoleBadge = (isSuperuser: boolean) => { + return isSuperuser ? ( + 超级管理员 + ) : ( + 普通用户 + ); + }; + + const getVerifiedBadge = (isVerified: boolean) => { + return isVerified ? ( + 已验证 + ) : ( + 未验证 + ); + }; + + const formatDate = (dateString: string) => { + try { + const date = new Date(dateString); + return date.toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + }).replace(/\//g, '-'); + } catch (error) { + return dateString; } }; - const getUserTypeBadge = (type: UserType) => { - switch (type) { - case 'super_admin': - return 超级管理员; - case 'enterprise_admin': - return 企业管理员; - case 'employee': - return 员工; - default: - return {type}; - } - }; - - if (!selectedUser) return null; + if (!user) return null; return ( - + - 用户详情 - - 查看用户的详细信息 - - -
-
-
- -
{selectedUser.username}
-
-
- -
{selectedUser.name}
-
-
- -
{selectedUser.phone}
-
-
- -
{selectedUser.email || '-'}
-
-
- -
{getUserTypeBadge(selectedUser.userType)}
-
-
- -
{selectedUser.enterpriseName || '-'}
-
-
- -
{getStatusBadge(selectedUser.status)}
-
- {selectedUser.lastLoginTime && ( -
- -
- {new Date(selectedUser.lastLoginTime).toLocaleString('zh-CN')} -
-
- )} -
- -
- {new Date(selectedUser.createdAt).toLocaleString('zh-CN')} -
-
-
- -
- {new Date(selectedUser.updatedAt).toLocaleString('zh-CN')} -
+
+ 用户详情 +
+ {getRoleBadge(user.isSuperuser)} + {getVerifiedBadge(user.isVerified)} + {getStatusBadge(user.isActive)}
-
+ + 查看用户的详细信息和权限 + + + + + + + + + 基本信息 + + + + 权限信息 + + + + 活动信息 + + + + {/* 基本信息 */} + +
+ {user.avatarUrl ? ( + {user.username} + ) : ( +
+ +
+ )} +
+

{user.displayName || user.fullName || user.username}

+

@{user.username}

+
+ {getRoleBadge(user.isSuperuser)} + {getVerifiedBadge(user.isVerified)} + {getStatusBadge(user.isActive)} +
+
+
+ +
+
+ +
{user.username}
+
+
+ +
+ {user.displayName || user.fullName || '-'} +
+
+
+ +
+ + {user.email} +
+
+
+ +
+ + {user.phone || '-'} +
+
+
+ +
+ + {user.companyName || '-'} +
+
+
+ +
+ {user.scope === 'tenant' ? '租户级' : user.scope || '-'} +
+
+ {user.departmentName && ( +
+ +
{user.departmentName}
+
+ )} + {user.bio && ( +
+ +
+ {user.bio} +
+
+ )} +
+
+ + {/* 权限信息 */} + +
+
+

系统权限

+
+ +
+ 超级管理员权限 + {user.isSuperuser ? ( + + ) : ( + + )} +
+
+ +
+ 邮箱已验证 + {user.isVerified ? ( + + ) : ( + + )} +
+
+
+
+ +
+

访问状态

+ +
+ 账户状态 + {getStatusBadge(user.isActive)} +
+
+
+ + {user.tenantId && ( +
+

关联信息

+
+ + +
+ {user.tenantId} +
+
+ {user.departmentId && ( + + +
+ {user.departmentId} +
+
+ )} +
+
+ )} +
+
+ + {/* 活动信息 */} + +
+
+

时间信息

+
+ + +
{formatDate(user.createdAt)}
+
+ + +
{formatDate(user.updatedAt)}
+
+ + +
+ {user.lastLoginAt ? formatDate(user.lastLoginAt) : '从未登录'} +
+
+ + +
+ {getStatusBadge(user.isActive)} +
+
+
+
+
+
+
+
+ + )} + {onResetPassword && ( + + )} + {onToggleStatus && ( + + )} + {onDelete && !user.isSuperuser && ( + + )} +
+ + + )) + )} + + + + + {/* 分页 */} + {pagination.total > 0 && ( +
+
+ 显示第 {(pagination.page - 1) * pagination.size + 1} - {Math.min(pagination.page * pagination.size, pagination.total)} 条,共 {pagination.total} 条记录 +
+
+ + +
+ +
+ { + const newPage = parseInt(e.target.value); + if (!isNaN(newPage)) { + onPageChange(newPage); + } + }} + className="w-16 h-8 text-center border rounded-md" + /> +
+ / {pagination.totalPages} 页 +
+ + +
+
+ )} +
); } \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/tenant/user-management/components/UserManagementFilters.tsx b/crop-x/src/app/(app)/central-config/tenant/user-management/components/UserManagementFilters.tsx index a169737..f527b64 100644 --- a/crop-x/src/app/(app)/central-config/tenant/user-management/components/UserManagementFilters.tsx +++ b/crop-x/src/app/(app)/central-config/tenant/user-management/components/UserManagementFilters.tsx @@ -9,16 +9,17 @@ import { UserFilters } from '../types'; interface UserManagementFiltersProps { filters: UserFilters; - onFiltersChange: (filters: UserFilters) => void; + onSearchChange: (value: string) => void; + onStatusFilterChange: (value: string) => void; + onTypeFilterChange: (value: string) => void; } -export function UserManagementFilters({ filters, onFiltersChange }: UserManagementFiltersProps) { - const updateFilter = (key: keyof UserFilters, value: string) => { - onFiltersChange({ - ...filters, - [key]: value - }); - }; +export function UserManagementFilters({ + filters, + onSearchChange, + onStatusFilterChange, + onTypeFilterChange +}: UserManagementFiltersProps) { return ( @@ -29,12 +30,12 @@ export function UserManagementFilters({ filters, onFiltersChange }: UserManageme updateFilter('searchKeyword', e.target.value)} + onChange={(e) => onSearchChange(e.target.value)} className="pl-10" /> - @@ -45,7 +46,7 @@ export function UserManagementFilters({ filters, onFiltersChange }: UserManageme 员工 - diff --git a/crop-x/src/app/(app)/central-config/tenant/user-management/components/UserManagementHeader.tsx b/crop-x/src/app/(app)/central-config/tenant/user-management/components/UserManagementHeader.tsx index ebefe18..b3bb510 100644 --- a/crop-x/src/app/(app)/central-config/tenant/user-management/components/UserManagementHeader.tsx +++ b/crop-x/src/app/(app)/central-config/tenant/user-management/components/UserManagementHeader.tsx @@ -1,31 +1,58 @@ +/** + * filekorolheader: 用户管理页面头部组件 - 页面标题和操作按钮 + * 功能:页面标题显示、刷新功能、统计数据展示 + * 路径:/central-config/tenant/user-management/components/UserManagementHeader + * 规范:遵循crop-x/docs/开发项目规范.md,使用shadcn/ui组件,TypeScript类型安全 + */ + 'use client'; -import React from 'react'; +import { Card } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; -import { Plus, Download } from 'lucide-react'; +import { Users, RefreshCw } from 'lucide-react'; interface UserManagementHeaderProps { - onAddUser: () => void; - onExport: () => void; + stats: Array<{ + label: string; + value: number; + color: string; + bg: string; + }>; + onRefresh: () => void; + loading: boolean; } -export function UserManagementHeader({ onAddUser, onExport }: UserManagementHeaderProps) { +export function UserManagementHeader({ stats, onRefresh, loading }: UserManagementHeaderProps) { return ( -
-
-

用户管理

-

平台所有用户账户的集中管理

+ +
+
+ +
+

用户管理

+

+ 平台所有用户账户的集中管理,支持搜索、筛选和详情查看 +

+
+ + 搜索功能 + + + 状态筛选 + + + 详情查看 + +
+
+
+
+ +
-
- - -
-
+ ); } \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/tenant/user-management/components/UserManagementStatsCards.tsx b/crop-x/src/app/(app)/central-config/tenant/user-management/components/UserManagementStatsCards.tsx index d592e7c..d4fe0c2 100644 --- a/crop-x/src/app/(app)/central-config/tenant/user-management/components/UserManagementStatsCards.tsx +++ b/crop-x/src/app/(app)/central-config/tenant/user-management/components/UserManagementStatsCards.tsx @@ -1,47 +1,49 @@ +/** + * filekorolheader: 用户管理统计卡片组件 - 用户统计数据展示界面 + * 功能:总用户数、活跃用户、管理员、已验证用户统计展示 + * 路径:/central-config/tenant/user-management/components/UserManagementStatsCards + * 规范:遵循crop-x/docs/开发项目规范.md,使用shadcn/ui组件,TypeScript类型安全 + */ + 'use client'; -import React from 'react'; import { Card } from '@/components/ui/card'; -import { UserManagementStats, User } from '../types'; interface UserManagementStatsCardsProps { - users: User[]; + stats: Array<{ + label: string; + value: number; + color: string; + bg: string; + }>; + loading?: boolean; } -export function UserManagementStatsCards({ users }: UserManagementStatsCardsProps) { - const stats: UserManagementStats[] = [ - { - label: '总用户数', - value: users.length, - color: 'text-blue-600', - bg: 'bg-blue-100', - }, - { - label: '超级管理员', - value: users.filter(u => u.userType === 'super_admin').length, - color: 'text-purple-600', - bg: 'bg-purple-100', - }, - { - label: '企业管理员', - value: users.filter(u => u.userType === 'enterprise_admin').length, - color: 'text-blue-600', - bg: 'bg-blue-100', - }, - { - label: '正常用户', - value: users.filter(u => u.status === 'active').length, - color: 'text-green-600', - bg: 'bg-green-100', - }, - ]; +export function UserManagementStatsCards({ + stats, + loading = false +}: UserManagementStatsCardsProps) { + if (loading) { + return ( +
+ {stats.map((_, index) => ( + +
+
+
+
+
+ ))} +
+ ); + } return (
{stats.map((stat, index) => (
{stat.label}
-
{stat.value}
+
{stat.value}
))}
diff --git a/crop-x/src/app/(app)/central-config/tenant/user-management/components/userManagementApi.ts b/crop-x/src/app/(app)/central-config/tenant/user-management/components/userManagementApi.ts new file mode 100644 index 0000000..1acf497 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/tenant/user-management/components/userManagementApi.ts @@ -0,0 +1,193 @@ +/** + * filekorolheader: 用户管理API接口 - 用户数据查询接口服务 + * 功能:API请求封装、数据转换、错误处理、分页查询 + * 路径:/central-config/tenant/user-management/components/userManagementApi + * 规范:遵循crop-x/docs/开发项目规范.md,使用SDK API调用,TypeScript类型安全 + */ + +import { getAuthToken } from "@/utils/token"; +import { + getUsersApiV1UsersGet, +} from "@/lib/api/sdk.gen"; + +// API返回的用户数据类型 +export interface UserData { + id: string; + tenant_id: string; + email: string; + username: string; + full_name: string | null; + phone: string | null; + is_active: boolean; + is_superuser: boolean; + is_verified: boolean; + created_at: string; + updated_at: string; + last_login_at: string | null; + avatar_url: string | null; + bio: string | null; + display_name: string | null; + department_id: string | null; + department_name: string | null; + scope: string; + company_name: string | null; +} + +// API响应接口 +export interface UsersApiResponse { + data: UserData[]; + total: number; + page: number; + size: number; + total_pages: number; + has_next: boolean; + has_prev: boolean; +} + +// 查询参数接口 +export interface UsersQueryParams { + search?: string; + is_active?: boolean; + page?: number; + size?: number; + order_by?: string; + sort_order?: 'asc' | 'desc'; +} + +// 页面使用的用户数据类型(转换后的) +export interface User { + id: string; + username: string; + email: string; + fullName: string | null; + phone: string | null; + isActive: boolean; + isSuperuser: boolean; + isVerified: boolean; + createdAt: string; + updatedAt: string; + lastLoginAt: string | null; + avatarUrl: string | null; + bio: string | null; + displayName: string | null; + departmentId: string | null; + departmentName: string | null; + scope: string; + companyName: string | null; + tenantId: string; +} + +/** + * 获取用户列表数据 + */ +export async function fetchUsers(params: UsersQueryParams = {}): Promise { + try { + // 构建查询参数对象 + const queryParams: any = {}; + + if (params.search) queryParams.search = params.search; + if (params.is_active !== undefined) queryParams.is_active = params.is_active; + if (params.page) queryParams.page = params.page; + if (params.size) queryParams.size = params.size; + if (params.order_by) queryParams.order_by = params.order_by; + if (params.sort_order) queryParams.sort_order = params.sort_order; + + // 默认参数 + if (!params.page) queryParams.page = 1; + if (!params.size) queryParams.size = 10; + if (!params.sort_order) queryParams.sort_order = 'desc'; + + // 使用SDK API调用用户查询接口,添加缓存破坏器和认证头部 + const token = getAuthToken(); + console.log('用户管理API调用参数:', queryParams); + + const response = await getUsersApiV1UsersGet({ + query: { + ...queryParams, + // 添加时间戳防止缓存 + _t: Date.now(), + }, + headers: token ? { + 'Authorization': `Bearer ${token}`, + } : undefined, + }); + + if (response.error) { + throw new Error(`API error: ${response.error.message || 'Unknown error'}`); + } + + const data = response.data as any; + console.log('用户管理API响应:', data); + + // 转换响应数据格式以匹配现有的接口 + // API返回的数据结构: { data: [...], total: 25, page: 1, size: 10, ... } + return { + data: data?.data || [], // 注意:实际数据在 data.data 中 + total: data?.total || 0, + page: data?.page || 1, + size: data?.size || 10, + total_pages: data?.total_pages || 0, + has_next: data?.has_next || false, + has_prev: data?.has_prev || false, + }; + } catch (error) { + console.error('Failed to fetch users:', error); + throw error; + } +} + +/** + * 将API数据转换为页面所需的用户数据格式 + * 优先显示display_name,其次full_name,最后username + */ +export function transformUserData(user: UserData): User { + return { + id: user.id, + username: user.username, + email: user.email, + fullName: user.full_name, + phone: user.phone, + isActive: user.is_active, + isSuperuser: user.is_superuser, + isVerified: user.is_verified, + createdAt: formatDate(user.created_at), + updatedAt: formatDate(user.updated_at), + lastLoginAt: user.last_login_at ? formatDate(user.last_login_at) : null, + avatarUrl: user.avatar_url, + bio: user.bio, + displayName: user.display_name || user.full_name || user.username, + departmentId: user.department_id, + departmentName: user.department_name, + scope: user.scope, + companyName: user.company_name, + tenantId: user.tenant_id, + }; +} + +/** + * 格式化日期 + */ +function formatDate(dateString: string): string { + try { + const date = new Date(dateString); + return date.toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + }).replace(/\//g, '-'); + } catch (error) { + return dateString; + } +} + +// Pagination state interface for page components +export interface PaginationState { + page: number; + size: number; + total: number; + totalPages: number; + hasNext: boolean; + hasPrev: boolean; +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/tenant/user-management/page.tsx b/crop-x/src/app/(app)/central-config/tenant/user-management/page.tsx index 48141d2..0ea2d0f 100644 --- a/crop-x/src/app/(app)/central-config/tenant/user-management/page.tsx +++ b/crop-x/src/app/(app)/central-config/tenant/user-management/page.tsx @@ -1,271 +1,330 @@ +/** + * filekorolheader: 用户管理页面 - 用户查询和管理页面 + * 功能:用户列表查询、搜索筛选、详情查看、用户管理 + * 路径:/central-config/tenant/user-management + * 规范:遵循crop-x/docs/开发项目规范.md,使用useReducer状态管理,API集成,shadcn语义化样式 + */ 'use client'; -import { useState, useEffect } from 'react'; +import { useReducer, useEffect } from 'react'; import { toast } from 'sonner'; +import { fetchUsers, transformUserData, UsersQueryParams, User, UsersApiResponse, PaginationState } from './components/userManagementApi'; import { UserManagementHeader } from './components/UserManagementHeader'; import { UserManagementStatsCards } from './components/UserManagementStatsCards'; import { UserManagementFilters } from './components/UserManagementFilters'; import { UserList } from './components/UserList'; -import { UserFormDialog } from './components/UserFormDialog'; import { UserDetailDialog } from './components/UserDetailDialog'; -import { User, Enterprise, UserFilters, UserFormData } from './types'; +import { Enterprise, UserFilters } from './types'; -export default function TenantUserManagementPage() { - const [users, setUsers] = useState([]); - const [enterprises, setEnterprises] = useState([]); - const [filters, setFilters] = useState({ +// 用户管理状态管理 +interface UserManagementState { + users: User[]; + enterprises: Enterprise[]; + 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_ENTERPRISES'; payload: Enterprise[] } + | { 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_ENTERPRISES': + return { ...state, enterprises: 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: [], + enterprises: [], + loading: false, + error: null, + pagination: { + page: 1, + size: 10, + total: 0, + totalPages: 0, + hasNext: false, + hasPrev: false, + }, + filters: { searchKeyword: '', statusFilter: 'all', typeFilter: 'all' - }); - const [showForm, setShowForm] = useState(false); - const [showDetailDialog, setShowDetailDialog] = useState(false); - const [editingUser, setEditingUser] = useState(null); - const [selectedUser, setSelectedUser] = useState(null); - const [formData, setFormData] = useState({ - userType: 'enterprise_admin', - status: 'active', - roleIds: [], - }); + }, + sortBy: 'created_at', + sortOrder: 'desc', + selectedUser: null, + showDetailDialog: false, +}; +export default function TenantUserManagementPage() { + const [state, dispatch] = useReducer(userManagementReducer, initialState); + + // 加载用户数据 + const loadUsers = async (resetPage = false) => { + try { + dispatch({ type: 'SET_LOADING', payload: true }); + + const params: UsersQueryParams = { + search: state.filters.searchKeyword || undefined, + page: resetPage ? 1 : state.pagination.page, + size: state.pagination.size, + order_by: state.sortBy, + sort_order: state.sortOrder, + }; + + // 根据状态筛选器设置 is_active 参数 + if (state.filters.statusFilter === 'active') { + params.is_active = true; + } else if (state.filters.statusFilter === 'inactive') { + params.is_active = false; + } + + 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 : '加载用户数据失败' + }); + } + }; + + // 加载企业数据(这里暂时使用mock数据,后续可以添加企业API) + const loadEnterprises = () => { + // 这里可以添加企业API调用,现在使用mock数据 + const mockEnterprises: Enterprise[] = [ + { id: 'ent-1', name: '丰收现代农业集团' }, + { id: 'ent-2', name: '绿色种植科技有限公司' }, + { id: 'ent-3', name: '智慧农业示范区' }, + ]; + dispatch({ type: 'SET_ENTERPRISES', payload: mockEnterprises }); + }; + + // 搜索处理 + const handleSearch = (value: string) => { + dispatch({ type: 'SET_FILTERS', payload: { searchKeyword: value } }); + }; + + // 状态筛选 + const handleStatusFilter = (value: string) => { + dispatch({ type: 'SET_FILTERS', payload: { statusFilter: value } }); + }; + + // 类型筛选 + const handleTypeFilter = (value: string) => { + dispatch({ type: 'SET_FILTERS', payload: { typeFilter: value } }); + }; + + // 排序处理 + const handleSort = (sortBy: string) => { + const newSortOrder = state.sortBy === sortBy && state.sortOrder === 'desc' ? 'asc' : 'desc'; + dispatch({ type: 'SET_SORT', payload: { sortBy, sortOrder: newSortOrder } }); + }; + + // 分页处理 + const handlePageChange = (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 } }); + }; + + // 查看详情 + 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 handleDelete = (user: User) => { + if (!confirm(`确定要删除用户 ${user.fullName || user.username} 吗?`)) return; + // 这里可以添加删除逻辑,调用API删除用户 + toast.info('删除功能开发中...'); + }; + + // 切换用户状态 + const handleToggleStatus = (user: User) => { + const newStatus = !user.isActive; + const statusText = newStatus ? '激活' : '停用'; + if (!confirm(`确定要${statusText}用户 ${user.fullName || user.username} 吗?`)) return; + // 这里可以添加状态切换逻辑,调用API更新用户状态 + toast.info(`${statusText}功能开发中...`); + }; + + // 重置密码 + const handleResetPassword = (user: User) => { + if (!confirm(`确定要重置用户 ${user.fullName || user.username} 的密码吗?`)) return; + // 这里可以添加重置密码逻辑,调用API重置密码 + toast.info('重置密码功能开发中...'); + }; + + // 刷新数据 + const handleRefresh = () => { + dispatch({ type: 'REFRESH_DATA' }); + loadUsers(true); + toast.success('数据已刷新'); + }; + + // 统计数据计算 + const stats = [ + { + label: '总用户数', + value: state.pagination.total, + color: 'text-blue-600', + bg: 'bg-blue-100', + }, + { + label: '活跃用户', + value: state.users.filter(u => u.isActive).length, + color: 'text-green-600', + bg: 'bg-green-100', + }, + { + label: '管理员', + value: state.users.filter(u => u.isSuperuser).length, + color: 'text-purple-600', + bg: 'bg-purple-100', + }, + { + label: '已验证', + value: state.users.filter(u => u.isVerified).length, + color: 'text-orange-600', + bg: 'bg-orange-100', + }, + ]; + + // 初始化和监听器 useEffect(() => { loadUsers(); loadEnterprises(); }, []); - const loadEnterprises = () => { - const data = localStorage.getItem('smart_agriculture_enterprises'); - if (data) { - const allEnterprises = JSON.parse(data); - setEnterprises(allEnterprises.map((e: any) => ({ id: e.id, name: e.name }))); + useEffect(() => { + const timer = setTimeout(() => { + loadUsers(); + }, 300); + + return () => clearTimeout(timer); + }, [state.filters.searchKeyword, state.filters.statusFilter, state.filters.typeFilter, state.sortBy, state.sortOrder]); + + useEffect(() => { + if (state.pagination.page > 1) { + loadUsers(); } - }; - - const loadUsers = () => { - const data = localStorage.getItem('smart_agriculture_users'); - if (data) { - setUsers(JSON.parse(data)); - } else { - // 初始化示例数据 - const mockUsers: User[] = [ - { - id: 'user-1', - username: 'admin', - name: '系统管理员', - phone: '13900000000', - email: 'admin@system.com', - userType: 'super_admin', - roleIds: ['role-1'], - roles: ['超级管理员'], - status: 'active', - createdAt: '2024-01-01T00:00:00', - updatedAt: '2024-01-01T00:00:00', - lastLoginTime: '2024-10-14T10:00:00', - }, - { - id: 'user-2', - username: 'ent_admin_1', - name: '李总', - phone: '13900139002', - email: 'litotal@fengshou.com', - enterpriseId: 'ent-2', - enterpriseName: '丰收现代农业集团', - userType: 'enterprise_admin', - roleIds: ['role-2'], - roles: ['企业管理员'], - status: 'active', - createdAt: '2024-10-05T10:00:00', - updatedAt: '2024-10-05T10:00:00', - lastLoginTime: '2024-10-14T09:00:00', - }, - { - id: 'user-3', - username: 'zhangsan', - name: '张三', - phone: '13800138001', - email: 'zhangsan@fengshou.com', - enterpriseId: 'ent-2', - enterpriseName: '丰收现代农业集团', - userType: 'employee', - roleIds: ['role-3'], - roles: ['操作员'], - status: 'active', - createdAt: '2024-10-01T08:00:00', - updatedAt: '2024-10-01T08:00:00', - lastLoginTime: '2024-10-14T08:30:00', - }, - ]; - localStorage.setItem('smart_agriculture_users', JSON.stringify(mockUsers)); - setUsers(mockUsers); - } - }; - - const filteredUsers = users.filter(user => { - const matchKeyword = !filters.searchKeyword || - user.name.includes(filters.searchKeyword) || - user.username.includes(filters.searchKeyword) || - user.phone.includes(filters.searchKeyword) || - (user.enterpriseName && user.enterpriseName.includes(filters.searchKeyword)); - - const matchStatus = filters.statusFilter === 'all' || user.status === filters.statusFilter; - const matchType = filters.typeFilter === 'all' || user.userType === filters.typeFilter; - - return matchKeyword && matchStatus && matchType; - }); - - const handleAddUser = () => { - setEditingUser(null); - setFormData({ - userType: 'enterprise_admin', - status: 'active', - roleIds: [], - }); - setShowForm(true); - }; - - const handleEdit = (user: User) => { - setEditingUser(user); - setFormData(user); - setShowForm(true); - }; - - const handleSave = () => { - if (!formData.username || !formData.name || !formData.phone) { - toast.error('请填写必填项'); - return; - } - - // 如果是企业管理员,必须选择企业 - if (formData.userType === 'enterprise_admin' && !formData.enterpriseId) { - toast.error('企业管理员必须选择所属企业'); - return; - } - - // 根据选择的企业ID设置企业名称 - let enterpriseName = formData.enterpriseName; - if (formData.enterpriseId && !enterpriseName) { - const selectedEnterprise = enterprises.find(e => e.id === formData.enterpriseId); - if (selectedEnterprise) { - enterpriseName = selectedEnterprise.name; - } - } - - if (editingUser) { - const updated = users.map(user => - user.id === editingUser.id - ? { - ...user, - ...formData, - enterpriseName, - updatedAt: new Date().toISOString(), - } - : user - ); - setUsers(updated); - localStorage.setItem('smart_agriculture_users', JSON.stringify(updated)); - toast.success('用户信息更新成功'); - } else { - const newUser: User = { - id: `user-${Date.now()}`, - ...formData as User, - enterpriseName, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }; - const updated = [...users, newUser]; - setUsers(updated); - localStorage.setItem('smart_agriculture_users', JSON.stringify(updated)); - toast.success('用户添加成功'); - } - - setShowForm(false); - }; - - const handleDelete = (id: string) => { - if (!confirm('确定要删除该用户吗?')) return; - - const updated = users.filter(user => user.id !== id); - setUsers(updated); - localStorage.setItem('smart_agriculture_users', JSON.stringify(updated)); - toast.success('用户删除成功'); - }; - - const handleToggleStatus = (user: User) => { - const newStatus = user.status === 'active' ? 'frozen' : 'active'; - const updated = users.map(u => - u.id === user.id - ? { ...u, status: newStatus, updatedAt: new Date().toISOString() } - : u - ); - setUsers(updated); - localStorage.setItem('smart_agriculture_users', JSON.stringify(updated)); - toast.success(newStatus === 'active' ? '账户已激活' : '账户已冻结'); - }; - - const handleResetPassword = (user: User) => { - if (!confirm(`确定要重置 ${user.name} 的密码吗?`)) return; - toast.success('密码已重置为:123456'); - }; - - const handleViewDetail = (user: User) => { - setSelectedUser(user); - setShowDetailDialog(true); - }; - - const handleExport = () => { - const dataStr = JSON.stringify(users, null, 2); - const dataBlob = new Blob([dataStr], { type: 'application/json' }); - const url = URL.createObjectURL(dataBlob); - const link = document.createElement('a'); - link.href = url; - link.download = `users_${new Date().getTime()}.json`; - link.click(); - toast.success('用户数据导出成功'); - }; + }, [state.pagination.page]); return (
- + {/* 页面标题和统计 */} + {/* 统计卡片 */} - + {/* 搜索和筛选 */} + {/* 错误显示 */} + {state.error && ( +
+
+ {state.error} +
+
+ )} + {/* 用户列表 */} - {/* 添加/编辑表单 */} - - - {/* 详情对话框 */} + {/* 用户详情对话框 */} dispatch({ type: 'TOGGLE_DETAIL_DIALOG', payload: open })} + user={state.selectedUser} />
); diff --git a/crop-x/src/app/(app)/central-config/tenant/user-management/types.ts b/crop-x/src/app/(app)/central-config/tenant/user-management/types.ts index ca463b5..d7080c7 100644 --- a/crop-x/src/app/(app)/central-config/tenant/user-management/types.ts +++ b/crop-x/src/app/(app)/central-config/tenant/user-management/types.ts @@ -3,18 +3,37 @@ export interface User { id: string; username: string; - name: string; - phone: string; - email?: string; - enterpriseId?: string; - enterpriseName?: string; - roleIds: string[]; - roles?: string[]; - userType: UserType; - status: UserStatus; + email: string; + fullName: string | null; + phone: string | null; + isActive: boolean; + isSuperuser: boolean; + isVerified: boolean; createdAt: string; updatedAt: string; + lastLoginAt: string | null; + avatarUrl: string | null; + bio: string | null; + displayName: string | null; + departmentId: string | null; + departmentName: string | null; + scope: string; + companyName: string | null; + tenantId: string; +} + +// 为了兼容现有代码,保留一些映射属性 +export interface UserWithLegacyFields extends User { + // 向后兼容的属性 + name: string; + phone: string; + enterpriseId?: string; + enterpriseName?: string; + userType: UserType; + status: UserStatus; lastLoginTime?: string; + roleIds: string[]; + roles?: string[]; } export type UserType = 'super_admin' | 'enterprise_admin' | 'employee'; @@ -40,6 +59,37 @@ export interface UserFilters { typeFilter: string; } +// API响应数据类型 +export interface UsersApiResponse { + data: User[]; + total: number; + page: number; + size: number; + total_pages: number; + has_next: boolean; + has_prev: boolean; +} + +// 分页状态 +export interface PaginationState { + page: number; + size: number; + total: number; + totalPages: number; + hasNext: boolean; + hasPrev: boolean; +} + +// API查询参数 +export interface UsersQueryParams { + search?: string; + is_active?: boolean; + page?: number; + size?: number; + order_by?: string; + sort_order?: 'asc' | 'desc'; +} + // 表单数据 export interface UserFormData { username?: string;