From 1fb128ede58bb1f39e154964fa39430e846748aa Mon Sep 17 00:00:00 2001 From: peng Date: Wed, 5 Nov 2025 17:18:25 +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=E5=91=98=E5=B7=A5=E7=AE=A1=E7=90=86=E3=80=81?= =?UTF-8?q?=E4=BC=81=E4=B8=9A=E4=BF=A1=E6=81=AF=E8=81=94=E8=B0=83=E5=AE=8C?= =?UTF-8?q?=E6=AF=95=E4=BB=A5=E5=8F=8A=E4=B8=80=E4=BA=9B=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E4=B8=8A=E7=9A=84=E6=A0=B7=E5=BC=8F=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crop-x/next-env.d.ts | 2 +- .../tenant/audit-history/page.tsx | 144 +++++-- .../components/BasicInfoForm.tsx | 5 +- .../components/enterpriseInfoApi.ts | 64 +++- .../tenant/enterprise-info/page.tsx | 58 ++- .../components/CreateEnterpriseDialog.tsx | 212 +++++++++++ .../components/enterpriseApi.ts | 46 ++- .../tenant/enterprise-management/page.tsx | 26 +- .../components/DepartmentHeader.tsx | 41 +- .../components/DepartmentInstructions.tsx | 12 +- .../components/EmployeeFormDialog.tsx | 86 ++++- .../user/employee/components/EmployeeList.tsx | 132 +++++-- .../components/EmployeeManagementHeader.tsx | 4 +- .../user/employee/components/employeeApi.ts | 280 +++++++++++++- .../central-config/user/employee/page.tsx | 360 +++++++++++++++--- .../central-config/user/employee/types.ts | 1 + 16 files changed, 1266 insertions(+), 207 deletions(-) create mode 100644 crop-x/src/app/(app)/central-config/tenant/enterprise-management/components/CreateEnterpriseDialog.tsx diff --git a/crop-x/next-env.d.ts b/crop-x/next-env.d.ts index c4b7818..9edff1c 100644 --- a/crop-x/next-env.d.ts +++ b/crop-x/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/dev/types/routes.d.ts"; +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/crop-x/src/app/(app)/central-config/tenant/audit-history/page.tsx b/crop-x/src/app/(app)/central-config/tenant/audit-history/page.tsx index 59ee3a6..1b4817f 100644 --- a/crop-x/src/app/(app)/central-config/tenant/audit-history/page.tsx +++ b/crop-x/src/app/(app)/central-config/tenant/audit-history/page.tsx @@ -136,7 +136,6 @@ export default function AuditHistoryPage() { dispatch({ type: 'SET_LOADING', payload: true }); const params: AuditLogsQueryParams = { - search: state.filters.search_keyword || undefined, page: state.pagination.page, size: state.pagination.size }; @@ -244,10 +243,10 @@ export default function AuditHistoryPage() { // 工具函数 const getActionBadge = (action: string) => { switch (action) { - case 'register': - return 注册审核; - case 'update': - return 变更审核; + case 'SUBMIT': + return 提交审核; + case 'AUDIT': + return 审核操作; default: return {action}; } @@ -258,9 +257,11 @@ export default function AuditHistoryPage() { case 'approved': return 已通过; case 'rejected': - return 已驳回; + return 已拒绝; case 'pending': return 待审核; + case 'draft': + return 草稿; default: return {result}; } @@ -328,8 +329,8 @@ export default function AuditHistoryPage() { 全部类型 - 注册审核 - 变更审核 + 提交审核 + 审核操作 @@ -343,8 +344,9 @@ export default function AuditHistoryPage() { 全部结果 已通过 - 已驳回 + 已拒绝 待审核 + 草稿 @@ -413,22 +415,15 @@ export default function AuditHistoryPage() { className="cursor-pointer hover:bg-muted" onClick={() => handleSort('action')} > - 操作类型 + 审核类型 {state.sortBy === 'action' && ( {state.sortOrder === 'asc' ? '↑' : '↓'} )} - 操作人 - handleSort('action_time')} - > - 操作时间 - {state.sortBy === 'action_time' && ( - {state.sortOrder === 'asc' ? '↑' : '↓'} - )} - - 结果 + 提交时间 + 审核时间 + 审核人 + 审核结果 操作 @@ -445,18 +440,23 @@ export default function AuditHistoryPage() {
- {record.snapshot_company_name} + {record.enterpriseName}
{getActionBadge(record.action)} + + {record.action === 'SUBMIT' ? record.submitTime : '-'} + + + {record.action === 'AUDIT' ? record.actionTime : '-'} +
- {record.action_by} + {record.actionBy || '-'}
- {record.action_time} - {getResultBadge(record.result)} + {getResultBadge(record.auditStatus)} + + + + + ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/tenant/enterprise-management/components/enterpriseApi.ts b/crop-x/src/app/(app)/central-config/tenant/enterprise-management/components/enterpriseApi.ts index 7bbe519..51005ea 100644 --- a/crop-x/src/app/(app)/central-config/tenant/enterprise-management/components/enterpriseApi.ts +++ b/crop-x/src/app/(app)/central-config/tenant/enterprise-management/components/enterpriseApi.ts @@ -10,7 +10,8 @@ import { getAuthToken } from "@/utils/token.ts"; import { listTenantsApiV1TenantsGet, enableTenantApiV1TenantsTenantIdEnablePatch, - disableTenantApiV1TenantsTenantIdDisablePatch + disableTenantApiV1TenantsTenantIdDisablePatch, + createTenantApiV1TenantsPost } from "@/lib/api/sdk.gen"; export interface TenantData { id: string; @@ -65,6 +66,13 @@ export interface TenantsQueryParams { sort_order?: 'asc' | 'desc'; } +// 新建企业请求参数接口 +export interface CreateEnterpriseRequest { + company_name: string; + tenant_code: string; + company_type: string; +} + // 企业页面数据类型(转换后的) export interface Enterprise { id: string; @@ -289,6 +297,42 @@ function mapAuditStatus(status: string): Enterprise['auditStatus'] { } } +/** + * 创建新企业 + * @param data 企业创建数据 + * @returns 创建结果 + */ +export async function createEnterprise(data: CreateEnterpriseRequest): Promise { + try { + console.log('🏢 创建企业API调用:', data); + + // 获取认证token + const token = getAuthToken(); + + // 使用SDK API调用创建企业接口 + const response = await createTenantApiV1TenantsPost({ + body: data, + headers: token ? { + 'Authorization': `Bearer ${token}`, + } : undefined, + }); + + if (response.error) { + console.error('🏢 创建企业API错误:', response.error); + throw new Error(`创建失败: ${response.error.message || '未知错误'}`); + } + + const result = response.data as TenantData; + console.log('🏢 创建企业API成功:', result); + + return result; + + } catch (error) { + console.error('🏢 创建企业失败:', error); + throw error; + } +} + /** * 格式化日期 */ diff --git a/crop-x/src/app/(app)/central-config/tenant/enterprise-management/page.tsx b/crop-x/src/app/(app)/central-config/tenant/enterprise-management/page.tsx index 2ca9024..22ed77d 100644 --- a/crop-x/src/app/(app)/central-config/tenant/enterprise-management/page.tsx +++ b/crop-x/src/app/(app)/central-config/tenant/enterprise-management/page.tsx @@ -18,11 +18,12 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; -import { Building2, Eye, Power, PowerOff, Search, FileText, CreditCard, User, RefreshCw, AlertCircle, ChevronLeft, ChevronRight } from 'lucide-react'; +import { Building2, Eye, Power, PowerOff, Search, FileText, CreditCard, User, RefreshCw, AlertCircle, ChevronLeft, ChevronRight, Plus } from 'lucide-react'; import { toast } from 'sonner'; import { enterpriseReducer, initialState, EnterpriseState, EnterpriseAction } from './components/enterpriseReducer'; -import { fetchTenants, transformTenantData, enableTenant, disableTenant, TenantsQueryParams, Enterprise } from './components/enterpriseApi'; +import { fetchTenants, transformTenantData, enableTenant, disableTenant, createEnterprise, TenantsQueryParams, Enterprise } from './components/enterpriseApi'; +import { CreateEnterpriseDialog } from './components/CreateEnterpriseDialog'; // Utility functions const getStatusBadge = (status: 'active' | 'inactive') => { @@ -152,6 +153,16 @@ export default function EnterpriseManagement() { dispatch({ type: 'TOGGLE_STATUS_DIALOG', payload: true }); }; + const handleCreateNew = () => { + dispatch({ type: 'RESET_FORM_DATA' }); + dispatch({ type: 'TOGGLE_ADD_DIALOG', payload: true }); + }; + + const handleCreateSuccess = () => { + // 创建成功后刷新数据 + loadEnterprises(true); + }; + const confirmStatusChange = async () => { if (!state.selectedEnterprise) return; @@ -226,6 +237,10 @@ export default function EnterpriseManagement() {
+
); } \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user/department/components/DepartmentHeader.tsx b/crop-x/src/app/(app)/central-config/user/department/components/DepartmentHeader.tsx index e168a18..9a83d51 100644 --- a/crop-x/src/app/(app)/central-config/user/department/components/DepartmentHeader.tsx +++ b/crop-x/src/app/(app)/central-config/user/department/components/DepartmentHeader.tsx @@ -7,9 +7,8 @@ 'use client'; -import { Card } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; -import { Building2, Plus, RefreshCw } from 'lucide-react'; +import { Plus } from 'lucide-react'; interface DepartmentHeaderProps { onAdd: () => void; @@ -19,35 +18,15 @@ interface DepartmentHeaderProps { export function DepartmentHeader({ onAdd }: DepartmentHeaderProps) { return ( - -
-
- -
-

部门管理

-

- 树形结构管理企业部门信息,支持拖动排序 -

-
- - 树形结构 - - - 拖动排序 - - - 层级管理 - -
-
-
-
- -
+
+
+

部门管理

+

树形结构管理企业部门信息,支持拖动排序

- + +
); } \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user/department/components/DepartmentInstructions.tsx b/crop-x/src/app/(app)/central-config/user/department/components/DepartmentInstructions.tsx index 0ee54bd..59b146d 100644 --- a/crop-x/src/app/(app)/central-config/user/department/components/DepartmentInstructions.tsx +++ b/crop-x/src/app/(app)/central-config/user/department/components/DepartmentInstructions.tsx @@ -6,15 +6,11 @@ */ import { Card } from '@/components/ui/card'; -import { Building2, GripVertical, AlertCircle, Users } from 'lucide-react'; export function DepartmentInstructions() { return ( -
- -

部门管理说明

-
+

部门管理说明

  • @@ -26,7 +22,7 @@ export function DepartmentInstructions() {
  • - +
    拖动排序: 按住部门左侧的 ⋮⋮ 图标拖动,可调整同级部门的顺序 @@ -42,7 +38,7 @@ export function DepartmentInstructions() {
  • - +
    员工关联: 在员工管理中新增员工时,可选择此处维护的部门 @@ -50,7 +46,7 @@ export function DepartmentInstructions() {
  • - +
    删除限制: 删除部门前请先删除其所有子部门 diff --git a/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeFormDialog.tsx b/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeFormDialog.tsx index a705eee..1b088b4 100644 --- a/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeFormDialog.tsx +++ b/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeFormDialog.tsx @@ -1,6 +1,7 @@ 'use client'; -import React from 'react'; +import React, { useState, useEffect } from 'react'; +import { toast } from 'sonner'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -8,6 +9,7 @@ import { Label } from '@/components/ui/label'; import { Card } from '@/components/ui/card'; import { Checkbox } from '@/components/ui/checkbox'; import { Employee, Role, EmployeeFormData } from '../types'; +import { fetchEmployeeDetail } from './employeeApi'; interface EmployeeFormDialogProps { open: boolean; @@ -17,6 +19,9 @@ interface EmployeeFormDialogProps { onFormDataChange: (data: EmployeeFormData) => void; onSave: () => void; roles: Role[]; + creating?: boolean; + updating?: boolean; + onClearForm?: () => void; } export function EmployeeFormDialog({ @@ -26,13 +31,73 @@ export function EmployeeFormDialog({ formData, onFormDataChange, onSave, - roles + roles, + creating = false, + updating = false, + onClearForm }: EmployeeFormDialogProps) { + const [loadingDetail, setLoadingDetail] = useState(false); + + // 当编辑员工时,根据ID获取用户详情 + useEffect(() => { + if (open && editingEmployee && editingEmployee.id) { + loadEmployeeDetail(editingEmployee.id); + } + }, [open, editingEmployee]); + + const loadEmployeeDetail = async (userId: string) => { + setLoadingDetail(true); + try { + const employeeDetail = await fetchEmployeeDetail(userId); + + // 将API数据转换为表单数据格式 + const formDetailData: EmployeeFormData = { + username: employeeDetail.username, + name: employeeDetail.displayName || employeeDetail.fullName || employeeDetail.username, + phone: employeeDetail.phone || '', + email: employeeDetail.email || '', + department: employeeDetail.departmentName || '', + position: '', // API返回中没有position字段 + enterpriseId: employeeDetail.tenantId, + enterpriseName: employeeDetail.companyName || '', + status: employeeDetail.isActive ? 'active' : 'inactive', + roleIds: [], // 需要单独获取角色信息 + idCard: '', // API返回中没有idCard字段 + address: '', // API返回中没有address字段 + auditStatus: 'approved', // 默认值 + isSuperuser: employeeDetail.isSuperuser, + }; + + // 更新表单数据 + onFormDataChange(formDetailData); + } catch (error) { + console.error('获取员工详情失败:', error); + toast.error('接口调用失败,请稍后重试'); + } finally { + setLoadingDetail(false); + } + }; return ( - + { + if (!isOpen && onClearForm) { + onClearForm(); + } + onOpenChange(isOpen); + }}> - {editingEmployee ? '编辑员工' : '添加员工'} + + {editingEmployee ? ( +
    + 编辑员工 + {loadingDetail && ( +
    + )} +
    + ) : ( + '添加员工' + )} + {editingEmployee ? '编辑员工信息' : '添加新员工'} @@ -49,6 +114,7 @@ export function EmployeeFormDialog({ value={formData.username || ''} onChange={(e) => onFormDataChange({ ...formData, username: e.target.value })} placeholder="登录用户名" + disabled={editingEmployee && loadingDetail} />
    @@ -58,6 +124,7 @@ export function EmployeeFormDialog({ value={formData.name || ''} onChange={(e) => onFormDataChange({ ...formData, name: e.target.value })} placeholder="真实姓名" + disabled={editingEmployee && loadingDetail} />
    @@ -67,6 +134,7 @@ export function EmployeeFormDialog({ value={formData.phone || ''} onChange={(e) => onFormDataChange({ ...formData, phone: e.target.value })} placeholder="11位手机号码" + disabled={editingEmployee && loadingDetail} />
    @@ -77,6 +145,7 @@ export function EmployeeFormDialog({ value={formData.email || ''} onChange={(e) => onFormDataChange({ ...formData, email: e.target.value })} placeholder="电子邮箱" + disabled={editingEmployee && loadingDetail} />
    @@ -86,6 +155,7 @@ export function EmployeeFormDialog({ value={formData.idCard || ''} onChange={(e) => onFormDataChange({ ...formData, idCard: e.target.value })} placeholder="18位身份证号码" + disabled={editingEmployee && loadingDetail} />
    @@ -95,6 +165,7 @@ export function EmployeeFormDialog({ value={formData.address || ''} onChange={(e) => onFormDataChange({ ...formData, address: e.target.value })} placeholder="详细住址" + disabled={editingEmployee && loadingDetail} />
    @@ -111,6 +182,7 @@ export function EmployeeFormDialog({ value={formData.department || ''} onChange={(e) => onFormDataChange({ ...formData, department: e.target.value })} placeholder="所属部门" + disabled={editingEmployee && loadingDetail} />
@@ -164,10 +236,12 @@ export function EmployeeFormDialog({ - - + diff --git a/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeList.tsx b/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeList.tsx index 45557dd..34728f5 100644 --- a/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeList.tsx +++ b/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeList.tsx @@ -6,6 +6,7 @@ import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { Pagination, PaginationContent, @@ -31,6 +32,7 @@ interface EmployeeListProps { onToggleStatus: (employee: Employee) => void; onDelete: (id: string) => void; onAudit?: (employee: Employee, action: 'approve' | 'reject') => void; + togglingId?: string | null; } export function EmployeeList({ @@ -44,7 +46,8 @@ export function EmployeeList({ onResetPassword, onToggleStatus, onDelete, - onAudit + onAudit, + togglingId }: EmployeeListProps) { const getStatusBadge = (isActive: boolean, status?: UserStatus) => { // 优先使用isActive字段(来自API),其次使用status字段(兼容旧数据) @@ -76,7 +79,8 @@ export function EmployeeList({ }; return ( - + + @@ -142,45 +146,88 @@ export function EmployeeList({ )} - - - - - + + + + + +

查看详情

+
+
+ + + + + +

编辑员工

+
+
+ + + + + +

重置密码

+
+
+ + + + + +

{(employee.isActive || employee.status === 'active') ? '停用员工' : '激活员工'}

+
+
+ + + + + +

删除员工

+
+
@@ -268,6 +315,7 @@ export function EmployeeList({ )} - + + ); } \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeManagementHeader.tsx b/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeManagementHeader.tsx index 6190f64..c1ae891 100644 --- a/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeManagementHeader.tsx +++ b/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeManagementHeader.tsx @@ -8,7 +8,9 @@ interface EmployeeManagementHeaderProps { onAddEmployee: () => void; } -export function EmployeeManagementHeader({ onAddEmployee }: EmployeeManagementHeaderProps) { +export function EmployeeManagementHeader({ + onAddEmployee +}: EmployeeManagementHeaderProps) { return (
diff --git a/crop-x/src/app/(app)/central-config/user/employee/components/employeeApi.ts b/crop-x/src/app/(app)/central-config/user/employee/components/employeeApi.ts index b7b4cb9..0d1ef2d 100644 --- a/crop-x/src/app/(app)/central-config/user/employee/components/employeeApi.ts +++ b/crop-x/src/app/(app)/central-config/user/employee/components/employeeApi.ts @@ -6,7 +6,7 @@ */ import { getAuthToken } from "@/utils/token"; -import { getUsersApiV1UsersGet } from "@/lib/api/sdk.gen"; +import { getUsersApiV1UsersGet, createUserApiV1UsersPost, getUserApiV1UsersUserIdGet, updateUserApiV1UsersUserIdPut, activateUserApiV1UsersUserIdActivatePost, deactivateUserApiV1UsersUserIdDeactivatePost, deleteUserApiV1UsersUserIdDelete } from "@/lib/api/sdk.gen"; // API返回的员工数据类型 export interface EmployeeApiData { @@ -50,6 +50,32 @@ export interface EmployeesQueryParams { sort_order?: 'asc' | 'desc'; } +// 创建用户请求参数接口 +export interface CreateEmployeeRequest { + email: string; + username: string; + full_name?: string; + phone: string; + password: string; + tenant_id?: string; + scope?: string; + department_id?: string; + is_superuser?: boolean; +} + +// 更新用户请求参数接口 +export interface UpdateEmployeeRequest { + email?: string; + username?: string; + full_name?: string; + phone?: string; + password?: string; + tenant_id?: string; + scope?: string; + department_id?: string; + is_superuser?: boolean; +} + // 页面使用的员工数据类型(转换后的) export interface Employee { id: string; @@ -209,6 +235,258 @@ function formatDate(dateString: string): string { } } +/** + * 创建员工用户 + */ +export async function createEmployee(employeeData: CreateEmployeeRequest): Promise { + try { + // 获取认证token + const token = getAuthToken(); + console.log('创建员工API调用参数:', employeeData); + + // 使用SDK API调用创建用户接口 + const response = await createUserApiV1UsersPost({ + body: employeeData, + headers: token ? { + 'Authorization': `Bearer ${token}`, + } : undefined, + }); + + if (response.error) { + // 处理API错误响应 + const errorData = response.error as any; + + // 检查是否是409冲突错误(用户已存在) + if (errorData.status === 409 && errorData.data) { + const conflictError = errorData.data as { + code?: string; + message?: string; + domain?: string; + detail?: { + field?: string; + value?: string; + }; + }; + + // 抛出包含详细错误信息的异常 + throw new Error(conflictError.message || '用户创建失败'); + } + + // 其他HTTP错误 + if (errorData.data && errorData.data.message) { + throw new Error(errorData.data.message); + } + + // 通用错误处理 + throw new Error(errorData.message || `API error: ${errorData.status || 'Unknown error'}`); + } + + const data = response.data as any; + console.log('创建员工API响应:', data); + + // 转换并返回新创建的员工数据 + return transformEmployeeData(data); + } catch (error) { + console.error('Failed to create employee:', error); + throw error; + } +} + +/** + * 获取用户详情信息 + */ +export async function fetchEmployeeDetail(userId: string): Promise { + try { + // 获取认证token + const token = getAuthToken(); + console.log('获取用户详情API调用参数:', { userId }); + + // 使用SDK API调用用户详情查询接口 + const response = await getUserApiV1UsersUserIdGet({ + path: { + user_id: userId, + }, + 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); + + // 转换并返回员工详情数据 + return transformEmployeeData(data); + } catch (error) { + console.error('Failed to fetch employee detail:', error); + throw error; + } +} + +/** + * 更新员工用户信息 + */ +export async function updateEmployee(userId: string, employeeData: UpdateEmployeeRequest): Promise { + try { + // 获取认证token + const token = getAuthToken(); + console.log('更新员工API调用参数:', { userId, employeeData }); + + // 使用SDK API调用更新用户接口 + const response = await updateUserApiV1UsersUserIdPut({ + path: { + user_id: userId, + }, + body: employeeData as any, // 使用any类型绕过类型检查,因为API类型定义与实际需求不匹配 + headers: token ? { + 'Authorization': `Bearer ${token}`, + } : undefined, + }); + + if (response.error) { + // 处理API错误响应 + const errorData = response.error as any; + + // 检查是否有详细的错误信息 + if (errorData.data && errorData.data.message) { + throw new Error(errorData.data.message); + } + + // 通用错误处理 + throw new Error(errorData.message || `API error: ${errorData.status || 'Unknown error'}`); + } + + const data = response.data as any; + console.log('更新员工API响应:', data); + + // 转换并返回更新后的员工数据 + return transformEmployeeData(data); + } catch (error) { + console.error('Failed to update employee:', error); + throw error; + } +} + +/** + * 激活用户 + */ +export async function activateUser(userId: string): Promise { + try { + // 获取认证token + const token = getAuthToken(); + console.log('激活用户API调用参数:', { userId }); + + // 使用SDK API调用激活用户接口 + const response = await activateUserApiV1UsersUserIdActivatePost({ + path: { + user_id: userId, + }, + headers: token ? { + 'Authorization': `Bearer ${token}`, + } : undefined, + }); + + if (response.error) { + // 处理API错误响应 + const errorData = response.error as any; + + // 检查是否有详细的错误信息 + if (errorData.data && errorData.data.message) { + throw new Error(errorData.data.message); + } + + // 通用错误处理 + throw new Error(errorData.message || `API error: ${errorData.status || 'Unknown error'}`); + } + + console.log('激活用户API响应:', response.data); + } catch (error) { + console.error('Failed to activate user:', error); + throw error; + } +} + +/** + * 停用用户 + */ +export async function deactivateUser(userId: string): Promise { + try { + // 获取认证token + const token = getAuthToken(); + console.log('停用用户API调用参数:', { userId }); + + // 使用SDK API调用停用用户接口 + const response = await deactivateUserApiV1UsersUserIdDeactivatePost({ + path: { + user_id: userId, + }, + headers: token ? { + 'Authorization': `Bearer ${token}`, + } : undefined, + }); + + if (response.error) { + // 处理API错误响应 + const errorData = response.error as any; + + // 检查是否有详细的错误信息 + if (errorData.data && errorData.data.message) { + throw new Error(errorData.data.message); + } + + // 通用错误处理 + throw new Error(errorData.message || `API error: ${errorData.status || 'Unknown error'}`); + } + + console.log('停用用户API响应:', response.data); + } catch (error) { + console.error('Failed to deactivate user:', error); + throw error; + } +} + +/** + * 删除用户 + */ +export async function deleteUser(userId: string): Promise { + try { + // 获取认证token + const token = getAuthToken(); + console.log('删除用户API调用参数:', { userId }); + + // 使用SDK API调用删除用户接口 + const response = await deleteUserApiV1UsersUserIdDelete({ + path: { + user_id: userId, + }, + headers: token ? { + 'Authorization': `Bearer ${token}`, + } : undefined, + }); + + if (response.error) { + // 处理API错误响应 + const errorData = response.error as any; + + // 检查是否有详细的错误信息 + if (errorData.data && errorData.data.message) { + throw new Error(errorData.data.message); + } + + // 通用错误处理 + throw new Error(errorData.message || `API error: ${errorData.status || 'Unknown error'}`); + } + + console.log('删除用户API响应:', response.data); + } catch (error) { + console.error('Failed to delete user:', error); + throw error; + } +} + // Pagination state interface for page components export interface PaginationState { page: number; diff --git a/crop-x/src/app/(app)/central-config/user/employee/page.tsx b/crop-x/src/app/(app)/central-config/user/employee/page.tsx index d173dce..25e71ea 100644 --- a/crop-x/src/app/(app)/central-config/user/employee/page.tsx +++ b/crop-x/src/app/(app)/central-config/user/employee/page.tsx @@ -9,10 +9,28 @@ import { EmployeeManagementFilters } from './components/EmployeeManagementFilter import { EmployeeList } from './components/EmployeeList'; import { EmployeeFormDialog } from './components/EmployeeFormDialog'; import { EmployeeDetailDialog } from './components/EmployeeDetailDialog'; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from '@/components/ui/alert-dialog'; import { Employee, Role, EmployeeFilters, EmployeeFormData } from './types'; import { fetchEmployees, transformEmployeesList, + createEmployee, + updateEmployee, + activateUser, + deactivateUser, + deleteUser, + CreateEmployeeRequest, + UpdateEmployeeRequest, PaginationState, EmployeesQueryParams } from './components/employeeApi'; @@ -21,6 +39,15 @@ export default function EmployeeManagementPage() { const [employees, setEmployees] = useState([]); const [roles, setRoles] = useState([]); const [loading, setLoading] = useState(false); + const [creating, setCreating] = useState(false); + const [updating, setUpdating] = useState(false); + const [toggling, setToggling] = useState(null); // 记录正在操作的用户ID + + // 确认对话框状态 + const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); + const [userToDelete, setUserToDelete] = useState(null); + const [deactivateConfirmOpen, setDeactivateConfirmOpen] = useState(false); + const [userToDeactivate, setUserToDeactivate] = useState(null); const [pagination, setPagination] = useState({ page: 1, size: 10, @@ -35,6 +62,7 @@ export default function EmployeeManagementPage() { }); const [showForm, setShowForm] = useState(false); const [showDetailDialog, setShowDetailDialog] = useState(false); + const [formKey, setFormKey] = useState(0); // 添加key来强制重新渲染表单 const [editingEmployee, setEditingEmployee] = useState(null); const [selectedEmployee, setSelectedEmployee] = useState(null); const [formData, setFormData] = useState({ @@ -135,16 +163,36 @@ export default function EmployeeManagementPage() { const handleAddEmployee = () => { setEditingEmployee(null); - setFormData({ + clearForm(); + setFormKey(prev => prev + 1); // 增加key强制重新渲染 + setShowForm(true); + }; + + const clearForm = () => { + // 先设置一个空的表单对象 + const emptyForm = { enterpriseId: 'ent-2', enterpriseName: '丰收现代农业集团', - status: 'active', - auditStatus: 'pending', + status: 'active' as const, + auditStatus: 'pending' as const, roleIds: [], idCard: '', address: '', - }); - setShowForm(true); + username: '', + name: '', + phone: '', + email: '', + department: '', + position: '', + }; + + // 强制清空表单 + setFormData(emptyForm); + + // 使用setTimeout确保状态更新完成 + setTimeout(() => { + setFormData({...emptyForm}); + }, 0); }; const handleEdit = (employee: Employee) => { @@ -153,7 +201,7 @@ export default function EmployeeManagementPage() { setShowForm(true); }; - const handleSave = () => { + const handleSave = async () => { if (!formData.username || !formData.name || !formData.phone) { toast.error('请填写必填项'); return; @@ -170,58 +218,195 @@ export default function EmployeeManagementPage() { const roleNames = selectedRoles.map(r => r.name); if (editingEmployee) { - // 更新 - const updated = employees.map(emp => - emp.id === editingEmployee.id - ? { - ...emp, - ...formData, - roles: roleNames, - updatedAt: new Date().toISOString(), - } - : emp - ); - setEmployees(updated); - localStorage.setItem('smart_agriculture_employees', JSON.stringify(updated)); - toast.success('员工信息更新成功'); - } else { - // 新增 - const newEmployee: Employee = { - id: `emp-${Date.now()}`, - ...formData as Employee, - roles: roleNames, - auditStatus: 'pending', - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }; - const updated = [...employees, newEmployee]; - setEmployees(updated); - localStorage.setItem('smart_agriculture_employees', JSON.stringify(updated)); - toast.success('员工添加成功'); - } + // 更新 - 调用API + setUpdating(true); + try { + // 构建API请求参数 + const updateRequest: UpdateEmployeeRequest = { + email: formData.email || '', + username: formData.username, + full_name: formData.name, + phone: formData.phone, + password: '', // 编辑时不传密码 + tenant_id: formData.enterpriseId, + scope: 'tenant', + department_id: formData.departmentId || '', + is_superuser: formData.isSuperuser || false, + }; - setShowForm(false); + // 调用API更新用户 + const updatedEmployee = await updateEmployee(editingEmployee.id, updateRequest); + + // 更新本地列表中的员工数据 + const updated = employees.map(emp => + emp.id === editingEmployee.id + ? { + ...emp, + ...updatedEmployee, + roles: roleNames, + updatedAt: new Date().toISOString(), + } + : emp + ); + setEmployees(updated); + + toast.success('员工信息更新成功'); + setShowForm(false); + clearForm(); + + // 刷新员工列表数据 + await loadEmployees(); + } catch (error) { + console.error('更新员工失败:', error); + + // 处理错误,显示具体的错误消息 + const errorMessage = error instanceof Error ? error.message : '员工信息更新失败'; + toast.error(errorMessage); + } finally { + setUpdating(false); + } + } else { + // 新增 - 调用API + setCreating(true); + try { + // 构建API请求参数 + const createRequest: CreateEmployeeRequest = { + email: formData.email || '', // 没有邮箱就传空字符串 + username: formData.username, + full_name: formData.name, + phone: formData.phone, + password: '', // 传递空字符串给后端 + tenant_id: formData.enterpriseId, + scope: 'tenant', + department_id: formData.departmentId || '', + is_superuser: formData.isSuperuser || false, + }; + + // 调用API创建用户 + const newEmployee = await createEmployee(createRequest); + + // 将新员工添加到列表 + const updated = [newEmployee, ...employees]; + setEmployees(updated); + + toast.success('员工添加成功'); + setShowForm(false); + clearForm(); + + // 刷新员工列表数据 + await loadEmployees(); + } catch (error) { + console.error('创建员工失败:', error); + + // 处理错误,显示具体的错误消息 + const errorMessage = error instanceof Error ? error.message : '员工添加失败'; + toast.error(errorMessage); + } finally { + setCreating(false); + } + } }; - const handleDelete = (id: string) => { - if (!confirm('确定要删除该员工吗?')) return; + const handleDelete = (userId: string) => { + // 防止重复操作 + if (toggling) { + toast.warning('操作进行中,请稍候...'); + return; + } - const updated = employees.filter(emp => emp.id !== id); - setEmployees(updated); - localStorage.setItem('smart_agriculture_employees', JSON.stringify(updated)); - toast.success('员工删除成功'); + // 查找要删除的员工信息 + const employeeToDelete = employees.find(emp => emp.id === userId); + if (!employeeToDelete) { + toast.error('未找到要删除的用户'); + return; + } + + // 设置要删除的用户并显示确认对话框 + setUserToDelete(employeeToDelete); + setDeleteConfirmOpen(true); + }; + + const executeDelete = async (userId: string) => { + setToggling(userId); + + try { + // 调用API删除用户 + await deleteUser(userId); + + // 成功后从本地列表中移除 + const updated = employees.filter(emp => emp.id !== userId); + setEmployees(updated); + + toast.success('用户删除成功'); + + // 刷新列表确保数据同步 + await loadEmployees(); + + // 关闭确认对话框 + setDeleteConfirmOpen(false); + setUserToDelete(null); + } catch (error) { + console.error('删除用户失败:', error); + + // 处理错误,显示具体的错误消息 + const errorMessage = error instanceof Error ? error.message : '删除失败,请稍后重试'; + toast.error(errorMessage); + + // 失败时不关闭确认对话框,用户可以重试 + } finally { + setToggling(null); + } }; const handleToggleStatus = (employee: Employee) => { - const newStatus = employee.status === 'active' ? 'frozen' : 'active'; - const updated = employees.map(emp => - emp.id === employee.id - ? { ...emp, status: newStatus, updatedAt: new Date().toISOString() } - : emp - ); - setEmployees(updated); - localStorage.setItem('smart_agriculture_employees', JSON.stringify(updated)); - toast.success(newStatus === 'active' ? '账户已激活' : '账户已冻结'); + if (toggling) { + toast.warning('操作进行中,请稍候...'); + return; + } + + if (employee.isActive) { + // 当前是激活状态,进行停用操作,需要二次确认 + setUserToDeactivate(employee); + setDeactivateConfirmOpen(true); + } else { + // 当前是停用状态,进行激活操作(不需要确认) + executeToggleStatus(employee); + } + }; + + const executeToggleStatus = async (employee: Employee) => { + setToggling(employee.id); + + try { + if (employee.isActive) { + // 当前是激活状态,进行停用操作 + await deactivateUser(employee.id); + toast.success('账户已停用'); + } else { + // 当前是停用状态,进行激活操作 + await activateUser(employee.id); + toast.success('账户已激活'); + } + + // 成功后刷新列表 + await loadEmployees(); + + // 关闭停用确认对话框 + if (deactivateConfirmOpen) { + setDeactivateConfirmOpen(false); + setUserToDeactivate(null); + } + } catch (error) { + console.error('切换用户状态失败:', error); + + // 处理错误,显示具体的错误消息 + const errorMessage = error instanceof Error ? error.message : '操作失败,请稍后重试'; + toast.error(errorMessage); + + // 失败时不关闭确认对话框,用户可以重试 + } finally { + setToggling(null); + } }; const handleResetPassword = (employee: Employee) => { @@ -301,10 +486,12 @@ export default function EmployeeManagementPage() { onToggleStatus={handleToggleStatus} onDelete={handleDelete} onAudit={handleAudit} + togglingId={toggling} /> {/* 添加/编辑表单 */} {/* 详情对话框 */} @@ -320,6 +510,74 @@ export default function EmployeeManagementPage() { onOpenChange={setShowDetailDialog} selectedEmployee={selectedEmployee} /> + + {/* 删除确认对话框 */} + + + + 确认删除用户 + + 确定要删除用户 " + {userToDelete?.displayName || userToDelete?.fullName || userToDelete?.username || ''} + " 吗? +

+ + 删除后该用户将无法恢复,所有相关数据将被清除。 + +
+
+ + + 取消 + + { + if (userToDelete) { + executeDelete(userToDelete.id); + } + }} + disabled={toggling !== null} + className="bg-red-600 hover:bg-red-700" + > + {toggling ? '删除中...' : '确认删除'} + + +
+
+ + {/* 停用确认对话框 */} + + + + 确认停用用户 + + 确定要停用用户 " + {userToDeactivate?.displayName || userToDeactivate?.fullName || userToDeactivate?.username || ''} + " 吗? +

+ + 停用后,该用户将无法登录系统。 + +
+
+ + + 取消 + + { + if (userToDeactivate) { + executeToggleStatus(userToDeactivate); + } + }} + disabled={toggling !== null} + className="bg-orange-600 hover:bg-orange-700" + > + {toggling ? '停用中...' : '确认停用'} + + +
+
); } \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user/employee/types.ts b/crop-x/src/app/(app)/central-config/user/employee/types.ts index d967bfc..41bf91b 100644 --- a/crop-x/src/app/(app)/central-config/user/employee/types.ts +++ b/crop-x/src/app/(app)/central-config/user/employee/types.ts @@ -75,6 +75,7 @@ export interface EmployeeFormData { name?: string; phone?: string; email?: string; + password?: string; department?: string; position?: string; enterpriseId?: string;