生产管理系统 - 用户管理接口集成

This commit is contained in:
2025-11-04 08:58:07 +08:00
parent 394e6d8342
commit f1c3c23127
8 changed files with 1098 additions and 491 deletions

View File

@@ -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<User[]>([]);
const [enterprises, setEnterprises] = useState<Enterprise[]>([]);
const [filters, setFilters] = useState<UserFilters>({
// 用户管理状态管理
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<UserFilters> }
| { type: 'SET_SORT'; payload: { sortBy?: string; sortOrder: 'asc' | 'desc' } }
| { type: 'SET_PAGINATION'; payload: Partial<PaginationState> }
| { 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<User | null>(null);
const [selectedUser, setSelectedUser] = useState<User | null>(null);
const [formData, setFormData] = useState<UserFormData>({
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 (
<div className="space-y-6">
<UserManagementHeader
onAddUser={handleAddUser}
onExport={handleExport}
/>
{/* 页面标题和统计 */}
<UserManagementHeader stats={stats} onRefresh={handleRefresh} loading={state.loading} />
{/* 统计卡片 */}
<UserManagementStatsCards users={users} />
<UserManagementStatsCards stats={stats} />
{/* 搜索和筛选 */}
<UserManagementFilters
filters={filters}
onFiltersChange={setFilters}
filters={state.filters}
onSearchChange={handleSearch}
onStatusFilterChange={handleStatusFilter}
onTypeFilterChange={handleTypeFilter}
/>
{/* 错误显示 */}
{state.error && (
<div className="p-4 bg-red-50 dark:bg-red-950 border border-red-200 dark:border-red-800 rounded-lg">
<div className="flex items-center gap-2 text-red-600 dark:text-red-400">
<span>{state.error}</span>
</div>
</div>
)}
{/* 用户列表 */}
<UserList
users={filteredUsers}
users={state.users}
loading={state.loading}
pagination={state.pagination}
onPageChange={handlePageChange}
onViewDetail={handleViewDetail}
onEdit={handleEdit}
onResetPassword={handleResetPassword}
onToggleStatus={handleToggleStatus}
onDelete={handleDelete}
onToggleStatus={handleToggleStatus}
onResetPassword={handleResetPassword}
/>
{/* 添加/编辑表单 */}
<UserFormDialog
open={showForm}
onOpenChange={setShowForm}
editingUser={editingUser}
formData={formData}
onFormDataChange={setFormData}
onSave={handleSave}
enterprises={enterprises}
/>
{/* 详情对话框 */}
{/* 用户详情对话框 */}
<UserDetailDialog
open={showDetailDialog}
onOpenChange={setShowDetailDialog}
selectedUser={selectedUser}
open={state.showDetailDialog}
onOpenChange={(open) => dispatch({ type: 'TOGGLE_DETAIL_DIALOG', payload: open })}
user={state.selectedUser}
/>
</div>
);