生产管理系统 - 员工管理、企业信息联调完毕以及一些页面上的样式修改
This commit is contained in:
2
crop-x/next-env.d.ts
vendored
2
crop-x/next-env.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
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.
|
||||
|
||||
@@ -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 <Badge className="bg-blue-100 text-blue-700">注册审核</Badge>;
|
||||
case 'update':
|
||||
return <Badge className="bg-orange-100 text-orange-700">变更审核</Badge>;
|
||||
case 'SUBMIT':
|
||||
return <Badge className="bg-blue-100 text-blue-700">提交审核</Badge>;
|
||||
case 'AUDIT':
|
||||
return <Badge className="bg-orange-100 text-orange-700">审核操作</Badge>;
|
||||
default:
|
||||
return <Badge variant="outline">{action}</Badge>;
|
||||
}
|
||||
@@ -258,9 +257,11 @@ export default function AuditHistoryPage() {
|
||||
case 'approved':
|
||||
return <Badge className="bg-green-100 text-green-700">已通过</Badge>;
|
||||
case 'rejected':
|
||||
return <Badge className="bg-red-100 text-red-700">已驳回</Badge>;
|
||||
return <Badge className="bg-red-100 text-red-700">已拒绝</Badge>;
|
||||
case 'pending':
|
||||
return <Badge className="bg-yellow-100 text-yellow-700">待审核</Badge>;
|
||||
case 'draft':
|
||||
return <Badge className="bg-gray-100 text-gray-700">草稿</Badge>;
|
||||
default:
|
||||
return <Badge variant="outline">{result}</Badge>;
|
||||
}
|
||||
@@ -328,8 +329,8 @@ export default function AuditHistoryPage() {
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">全部类型</SelectItem>
|
||||
<SelectItem value="register">注册审核</SelectItem>
|
||||
<SelectItem value="update">变更审核</SelectItem>
|
||||
<SelectItem value="SUBMIT">提交审核</SelectItem>
|
||||
<SelectItem value="AUDIT">审核操作</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
@@ -343,8 +344,9 @@ export default function AuditHistoryPage() {
|
||||
<SelectContent>
|
||||
<SelectItem value="all">全部结果</SelectItem>
|
||||
<SelectItem value="approved">已通过</SelectItem>
|
||||
<SelectItem value="rejected">已驳回</SelectItem>
|
||||
<SelectItem value="rejected">已拒绝</SelectItem>
|
||||
<SelectItem value="pending">待审核</SelectItem>
|
||||
<SelectItem value="draft">草稿</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
@@ -413,22 +415,15 @@ export default function AuditHistoryPage() {
|
||||
className="cursor-pointer hover:bg-muted"
|
||||
onClick={() => handleSort('action')}
|
||||
>
|
||||
操作类型
|
||||
审核类型
|
||||
{state.sortBy === 'action' && (
|
||||
<span className="ml-1">{state.sortOrder === 'asc' ? '↑' : '↓'}</span>
|
||||
)}
|
||||
</TableHead>
|
||||
<TableHead>操作人</TableHead>
|
||||
<TableHead
|
||||
className="cursor-pointer hover:bg-muted"
|
||||
onClick={() => handleSort('action_time')}
|
||||
>
|
||||
操作时间
|
||||
{state.sortBy === 'action_time' && (
|
||||
<span className="ml-1">{state.sortOrder === 'asc' ? '↑' : '↓'}</span>
|
||||
)}
|
||||
</TableHead>
|
||||
<TableHead>结果</TableHead>
|
||||
<TableHead>提交时间</TableHead>
|
||||
<TableHead>审核时间</TableHead>
|
||||
<TableHead>审核人</TableHead>
|
||||
<TableHead>审核结果</TableHead>
|
||||
<TableHead>操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
@@ -445,18 +440,23 @@ export default function AuditHistoryPage() {
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-2">
|
||||
<Building className="w-4 h-4 text-blue-500" />
|
||||
<span className="font-medium">{record.snapshot_company_name}</span>
|
||||
<span className="font-medium">{record.enterpriseName}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{getActionBadge(record.action)}</TableCell>
|
||||
<TableCell className="text-sm text-muted-foreground">
|
||||
{record.action === 'SUBMIT' ? record.submitTime : '-'}
|
||||
</TableCell>
|
||||
<TableCell className="text-sm text-muted-foreground">
|
||||
{record.action === 'AUDIT' ? record.actionTime : '-'}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-2">
|
||||
<User className="w-4 h-4 text-gray-500" />
|
||||
<span>{record.action_by}</span>
|
||||
<span>{record.actionBy || '-'}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-sm">{record.action_time}</TableCell>
|
||||
<TableCell>{getResultBadge(record.result)}</TableCell>
|
||||
<TableCell>{getResultBadge(record.auditStatus)}</TableCell>
|
||||
<TableCell>
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -542,28 +542,42 @@ export default function AuditHistoryPage() {
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<div>
|
||||
<Label>企业名称</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">{state.selectedRecord.snapshot_company_name}</div>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">{state.selectedRecord.enterpriseName}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>操作类型</Label>
|
||||
<Label>审核类型</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">{getActionBadge(state.selectedRecord.action)}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>操作人</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">{state.selectedRecord.action_by}</div>
|
||||
<Label>提交时间</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">
|
||||
{state.selectedRecord.action === 'SUBMIT' ? state.selectedRecord.submitTime : '-'}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>操作时间</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">{state.selectedRecord.action_time}</div>
|
||||
<Label>审核时间</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">
|
||||
{state.selectedRecord.action === 'AUDIT' ? state.selectedRecord.actionTime : '-'}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>操作结果</Label>
|
||||
<Label>审核人</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">{state.selectedRecord.actionBy || '-'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>审核结果</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">{getResultBadge(state.selectedRecord.result)}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>操作摘要</Label>
|
||||
<Label>变更摘要</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md min-h-[80px] whitespace-pre-wrap">
|
||||
{state.selectedRecord.action_summary || '-'}
|
||||
{state.selectedRecord.changeSummary || '-'}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>审核备注</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md min-h-[80px] whitespace-pre-wrap">
|
||||
{state.selectedRecord.auditComment || '-'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -574,25 +588,61 @@ export default function AuditHistoryPage() {
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<div>
|
||||
<Label>企业类型</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">{state.selectedRecord.snapshot_company_type || '-'}</div>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">{state.selectedRecord.snapshot.companyType || '-'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>所在地区</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">
|
||||
{state.selectedRecord.snapshot_province} {state.selectedRecord.snapshot_city}
|
||||
{state.selectedRecord.snapshot.province} {state.selectedRecord.snapshot.city}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<Label>详细地址</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">{state.selectedRecord.snapshot_address || '-'}</div>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">{state.selectedRecord.snapshot.detailedAddress || '-'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>登记人</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">{state.selectedRecord.snapshot_registrant || '-'}</div>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">{state.selectedRecord.snapshot.registrant || '-'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>联系电话</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">{state.selectedRecord.snapshot_contact_phone || '-'}</div>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">{state.selectedRecord.snapshot.contactPhone || '-'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>企业规模</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">{state.selectedRecord.snapshot.companyScale || '-'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>注册资本</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">{state.selectedRecord.snapshot.registeredCapital || '-'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>社会信用代码</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">
|
||||
<code className="text-sm">{state.selectedRecord.snapshot.socialCreditCode || '-'}</code>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>法人名称</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">{state.selectedRecord.snapshot.legalPersonName || '-'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>银行账号</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">
|
||||
<code className="text-sm">{state.selectedRecord.snapshot.bankAccount || '-'}</code>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>开户行</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">{state.selectedRecord.snapshot.bankName || '-'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>开户行全称</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">{state.selectedRecord.snapshot.bankFullName || '-'}</div>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<Label>开户行地址</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">{state.selectedRecord.snapshot.bankAddress || '-'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
@@ -609,19 +659,29 @@ export default function AuditHistoryPage() {
|
||||
<div>
|
||||
<Label>企业ID</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">
|
||||
<code className="text-sm">{state.selectedRecord.tenant_id}</code>
|
||||
<code className="text-sm">{state.selectedRecord.enterpriseId || '-'}</code>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>IP地址</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">{state.selectedRecord.ip_address || '-'}</div>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">{state.selectedRecord.ipAddress || '-'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>用户代理</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md text-sm">
|
||||
{state.selectedRecord.user_agent || '-'}
|
||||
{state.selectedRecord.userAgent || '-'}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>请求ID</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">
|
||||
<code className="text-sm">{state.selectedRecord.requestId || '-'}</code>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>创建时间</Label>
|
||||
<div className="mt-1.5 p-3 bg-gray-50 rounded-md">{state.selectedRecord.createdAt}</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
@@ -49,12 +49,11 @@ export function BasicInfoForm({
|
||||
<SelectValue placeholder="选择企业类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="个体工商户">个体工商户</SelectItem>
|
||||
<SelectItem value="有限责任公司">有限责任公司</SelectItem>
|
||||
<SelectItem value="股份有限公司">股份有限公司</SelectItem>
|
||||
<SelectItem value="个人独资企业">个人独资企业</SelectItem>
|
||||
<SelectItem value="合伙企业">合伙企业</SelectItem>
|
||||
<SelectItem value="个体工商户">个体工商户</SelectItem>
|
||||
<SelectItem value="农民专业合作社">农民专业合作社</SelectItem>
|
||||
<SelectItem value="其他">其他</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
) : (
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import { getAuthToken } from "@/utils/token";
|
||||
import { getTenantApiV1TenantsTenantIdGet } from "@/lib/api/sdk.gen";
|
||||
import { getCurrentTenantApiV1TenantsMeGet, submitTenantAuditApiV1TenantsSubmitPost } from "@/lib/api/sdk.gen";
|
||||
import { Enterprise } from '../types';
|
||||
|
||||
// API返回的租户数据类型(根据实际API返回定义)
|
||||
@@ -42,6 +42,33 @@ export interface TenantApiData {
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
// 提交审核请求参数接口
|
||||
export interface SubmitAuditRequest {
|
||||
company_name: string;
|
||||
company_type: string | null;
|
||||
province: string | null;
|
||||
city: string | null;
|
||||
district: string | null;
|
||||
detailed_address: string | null;
|
||||
registrant: string | null;
|
||||
contact_phone: string | null;
|
||||
bank_account: string | null;
|
||||
bank_name: string | null;
|
||||
bank_full_name: string | null;
|
||||
bank_address: string | null;
|
||||
bank_permit_image: string | null;
|
||||
social_credit_code: string | null;
|
||||
business_license_image: string | null;
|
||||
legal_person_name: string | null;
|
||||
id_card_front_image: string | null;
|
||||
id_card_back_image: string | null;
|
||||
company_scale: string | null;
|
||||
registered_capital: number | null;
|
||||
established_date: string | null;
|
||||
invoice_type: string | null;
|
||||
business_scope: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取企业详细信息
|
||||
*/
|
||||
@@ -50,7 +77,7 @@ export async function fetchEnterpriseInfo(tenantId: string): Promise<Enterprise
|
||||
const token = getAuthToken();
|
||||
console.log('🏢 获取企业信息API调用,租户ID:', tenantId);
|
||||
|
||||
const response = await getTenantApiV1TenantsTenantIdGet({
|
||||
const response = await getCurrentTenantApiV1TenantsMeGet({
|
||||
path: {
|
||||
tenant_id: tenantId,
|
||||
},
|
||||
@@ -84,6 +111,39 @@ export async function updateEnterpriseInfo(tenantId: string, formData: Partial<E
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交企业审核
|
||||
* @param tenantId 租户ID
|
||||
* @param data 提交审核的数据
|
||||
* @returns 提交结果
|
||||
*/
|
||||
export async function submitEnterpriseAudit(tenantId: string, data: SubmitAuditRequest): Promise<void> {
|
||||
try {
|
||||
const token = getAuthToken();
|
||||
console.log('🏢 提交企业审核API调用,租户ID:', tenantId, '数据:', data);
|
||||
|
||||
const response = await submitTenantAuditApiV1TenantsSubmitPost({
|
||||
path: {
|
||||
tenant_id: tenantId,
|
||||
},
|
||||
body: data,
|
||||
headers: token ? {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
} : undefined,
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
console.error('🏢 提交企业审核API错误:', response.error);
|
||||
throw new Error(`提交审核失败: ${response.error.message || '未知错误'}`);
|
||||
}
|
||||
|
||||
console.log('🏢 提交企业审核API成功');
|
||||
} catch (error) {
|
||||
console.error('🏢 提交企业审核失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将API数据转换为页面所需的企业数据格式
|
||||
*/
|
||||
|
||||
@@ -16,7 +16,7 @@ import { SystemInfo } from './components/SystemInfo';
|
||||
import { OperationTips } from './components/OperationTips';
|
||||
import { Enterprise } from './types';
|
||||
import { getAuthUser } from '@/stores/modules/auth';
|
||||
import { fetchEnterpriseInfo, updateEnterpriseInfo } from './components/enterpriseInfoApi';
|
||||
import { fetchEnterpriseInfo, updateEnterpriseInfo, submitEnterpriseAudit, SubmitAuditRequest } from './components/enterpriseInfoApi';
|
||||
|
||||
export default function EnterpriseInfoPage() {
|
||||
const [enterprise, setEnterprise] = useState<Enterprise | null>(null);
|
||||
@@ -88,30 +88,56 @@ export default function EnterpriseInfoPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.name || !formData.type || !formData.socialCreditCode) {
|
||||
toast.error('请填写必填项');
|
||||
// 验证必填字段
|
||||
if (!formData.name?.trim()) {
|
||||
toast.error('请填写企业名称');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
console.log('🏢 开始更新企业信息');
|
||||
console.log('🏢 开始提交企业审核');
|
||||
|
||||
// 调用API更新企业信息
|
||||
const updatedData = await updateEnterpriseInfo(currentUser.tenant_id, formData);
|
||||
// 构建提交审核的数据
|
||||
const submitData: SubmitAuditRequest = {
|
||||
company_name: formData.name || '',
|
||||
company_type: formData.type || null,
|
||||
province: formData.province || null,
|
||||
city: formData.city || null,
|
||||
district: formData.district || null,
|
||||
detailed_address: formData.address || null,
|
||||
registrant: formData.registrant || null,
|
||||
contact_phone: formData.contactPhone || null,
|
||||
bank_account: formData.bankAccount || null,
|
||||
bank_name: formData.bankName || null,
|
||||
bank_full_name: formData.bankFullName || null,
|
||||
bank_address: formData.bankAddress || null,
|
||||
bank_permit_image: null, // 暂时设为null,等待图片上传功能
|
||||
social_credit_code: formData.socialCreditCode || null,
|
||||
business_license_image: null, // 暂时设为null,等待图片上传功能
|
||||
legal_person_name: formData.legalPerson || null,
|
||||
id_card_front_image: null, // 暂时设为null,等待图片上传功能
|
||||
id_card_back_image: null, // 暂时设为null,等待图片上传功能
|
||||
company_scale: formData.companySize || null,
|
||||
registered_capital: formData.registeredCapital ? parseFloat(formData.registeredCapital.toString()) : null,
|
||||
established_date: formData.establishmentDate || null,
|
||||
invoice_type: formData.invoiceType || null,
|
||||
business_scope: formData.businessScope || null,
|
||||
};
|
||||
|
||||
if (updatedData) {
|
||||
setEnterprise(updatedData);
|
||||
setFormData(updatedData);
|
||||
// 调用API提交审核
|
||||
await submitEnterpriseAudit(currentUser.tenant_id, submitData);
|
||||
|
||||
// 提交成功,退出编辑模式
|
||||
setIsEditing(false);
|
||||
toast.success('企业信息已更新,等待管理员审核');
|
||||
} else {
|
||||
toast.info('企业信息保存功能待开发');
|
||||
}
|
||||
toast.success('企业信息已提交审核,请等待管理员审核');
|
||||
|
||||
// 重新加载企业信息以获取最新状态
|
||||
await loadEnterpriseInfo(currentUser.tenant_id);
|
||||
|
||||
} catch (error) {
|
||||
console.error('🏢 更新企业信息失败:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : '更新企业信息失败';
|
||||
console.error('🏢 提交企业审核失败:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : '提交审核失败';
|
||||
toast.error(errorMessage);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* filekorolheader: 新建企业弹窗组件 - 企业创建表单弹窗
|
||||
* 功能:企业信息表单、数据验证、API调用、状态管理
|
||||
* 路径:/central-config/tenant/enterprise-management/components/CreateEnterpriseDialog
|
||||
* 规范:遵循crop-x/docs/开发项目规范.md,使用shadcn语义化样式,TypeScript类型安全
|
||||
*/
|
||||
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Building2, Hash } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { createEnterprise, CreateEnterpriseRequest } from './enterpriseApi';
|
||||
|
||||
interface CreateEnterpriseDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onSuccess: () => void;
|
||||
}
|
||||
|
||||
export function CreateEnterpriseDialog({ open, onOpenChange, onSuccess }: CreateEnterpriseDialogProps) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [formData, setFormData] = useState<CreateEnterpriseRequest>({
|
||||
company_name: '',
|
||||
tenant_code: '',
|
||||
company_type: '',
|
||||
});
|
||||
|
||||
// 企业类型选项
|
||||
const companyTypes = [
|
||||
'个体工商户',
|
||||
'有限责任公司',
|
||||
'股份有限公司',
|
||||
'合伙企业',
|
||||
'其他',
|
||||
];
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
setFormData({
|
||||
company_name: '',
|
||||
tenant_code: '',
|
||||
company_type: '',
|
||||
});
|
||||
};
|
||||
|
||||
// 关闭弹窗
|
||||
const handleClose = () => {
|
||||
if (!loading) {
|
||||
onOpenChange(false);
|
||||
resetForm();
|
||||
}
|
||||
};
|
||||
|
||||
// 处理输入变化
|
||||
const handleInputChange = (field: keyof CreateEnterpriseRequest, value: string) => {
|
||||
setFormData(prev => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
// 表单验证
|
||||
const validateForm = (): boolean => {
|
||||
if (!formData.company_name.trim()) {
|
||||
toast.error('请输入企业名称');
|
||||
return false;
|
||||
}
|
||||
if (!formData.tenant_code.trim()) {
|
||||
toast.error('请输入企业编码');
|
||||
return false;
|
||||
}
|
||||
if (!formData.company_type.trim()) {
|
||||
toast.error('请选择企业类型');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
if (!validateForm()) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
console.log('🏢 开始创建企业:', formData);
|
||||
|
||||
// 调用API创建企业
|
||||
const result = await createEnterprise(formData);
|
||||
|
||||
console.log('🏢 企业创建成功:', result);
|
||||
toast.success('企业创建成功!');
|
||||
|
||||
// 关闭弹窗并重置表单
|
||||
handleClose();
|
||||
|
||||
// 调用成功回调,刷新主页面数据
|
||||
onSuccess();
|
||||
|
||||
} catch (error) {
|
||||
console.error('🏢 创建企业失败:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : '创建企业失败,请稍后重试';
|
||||
toast.error(errorMessage);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={handleClose}>
|
||||
<DialogContent className="sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Building2 className="w-5 h-5 text-blue-600 dark:text-blue-400" />
|
||||
新建企业
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
创建新企业账号,填写基本信息后系统将自动生成企业实例
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-6 py-4">
|
||||
{/* 企业名称 */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="company_name">企业名称 *</Label>
|
||||
<div className="relative">
|
||||
<Building2 className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||
<Input
|
||||
id="company_name"
|
||||
placeholder="请输入企业全称"
|
||||
value={formData.company_name}
|
||||
onChange={(e) => handleInputChange('company_name', e.target.value)}
|
||||
className="pl-10"
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 企业编码 */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="tenant_code">企业编码 *</Label>
|
||||
<div className="relative">
|
||||
<Hash className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||
<Input
|
||||
id="tenant_code"
|
||||
placeholder="请输入企业唯一编码,如:SHNY001"
|
||||
value={formData.tenant_code}
|
||||
onChange={(e) => handleInputChange('tenant_code', e.target.value.toUpperCase())}
|
||||
className="pl-10"
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
编码创建后不可修改,请谨慎填写
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 企业类型 */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="company_type">企业类型 *</Label>
|
||||
<Select
|
||||
value={formData.company_type}
|
||||
onValueChange={(value) => handleInputChange('company_type', value)}
|
||||
disabled={loading}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="请选择企业类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{companyTypes.map((type) => (
|
||||
<SelectItem key={type} value={type}>
|
||||
{type}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 温馨提示 */}
|
||||
<div className="p-4 bg-blue-50 dark:bg-blue-950 border border-blue-200 dark:border-blue-800 rounded-lg">
|
||||
<p className="text-sm text-blue-800 dark:text-blue-200 font-medium mb-2">温馨提示:</p>
|
||||
<ul className="text-sm text-blue-700 dark:text-blue-300 space-y-1">
|
||||
<li>• 企业创建后默认为待审核状态</li>
|
||||
<li>• 企业编码创建后不可修改,请确保准确无误</li>
|
||||
<li>• 创建成功后需要等待管理员审核</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleClose}
|
||||
disabled={loading}
|
||||
className="font-light"
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
disabled={loading}
|
||||
className="font-light"
|
||||
>
|
||||
{loading ? '创建中...' : '创建企业'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -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<TenantData> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期
|
||||
*/
|
||||
|
||||
@@ -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() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button onClick={handleCreateNew} disabled={state.loading}>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
新建企业
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={handleRefresh} disabled={state.loading}>
|
||||
<RefreshCw className={`w-4 h-4 mr-1 ${state.loading ? 'animate-spin' : ''}`} />
|
||||
刷新
|
||||
@@ -668,6 +683,13 @@ export default function EnterpriseManagement() {
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{/* Create Enterprise Dialog */}
|
||||
<CreateEnterpriseDialog
|
||||
open={state.showAddDialog}
|
||||
onOpenChange={(open) => dispatch({ type: 'TOGGLE_ADD_DIALOG', payload: open })}
|
||||
onSuccess={handleCreateSuccess}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<Card className="p-6 bg-gradient-to-r from-green-50 dark:from-green-950 to-emerald-50 dark:to-emerald-950 border-green-200 dark:border-green-800">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-start gap-3">
|
||||
<Building2 className="w-6 h-6 text-green-600 dark:text-green-400 flex-shrink-0 mt-1" />
|
||||
<div className="flex-1">
|
||||
<h2 className="mb-2">部门管理</h2>
|
||||
<p className="text-sm text-muted-foreground mb-3">
|
||||
树形结构管理企业部门信息,支持拖动排序
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<span className="inline-flex items-center text-sm bg-white dark:bg-gray-800 px-3 py-1 rounded-full">
|
||||
树形结构
|
||||
</span>
|
||||
<span className="inline-flex items-center text-sm bg-white dark:bg-gray-800 px-3 py-1 rounded-full">
|
||||
拖动排序
|
||||
</span>
|
||||
<span className="inline-flex items-center text-sm bg-white dark:bg-gray-800 px-3 py-1 rounded-full">
|
||||
层级管理
|
||||
</span>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-green-800 dark:text-green-400">部门管理</h2>
|
||||
<p className="text-muted-foreground">树形结构管理企业部门信息,支持拖动排序</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button onClick={onAdd} className="bg-green-600 hover:bg-green-700 dark:bg-green-600 dark:hover:bg-green-700">
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
添加一级部门
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -6,15 +6,11 @@
|
||||
*/
|
||||
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { Building2, GripVertical, AlertCircle, Users } from 'lucide-react';
|
||||
|
||||
export function DepartmentInstructions() {
|
||||
return (
|
||||
<Card className="p-4 bg-blue-50 dark:bg-blue-950/30 border-blue-200 dark:border-blue-900">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Building2 className="w-5 h-5 text-blue-900 dark:text-blue-400" />
|
||||
<h4 className="text-blue-900 dark:text-blue-400 font-medium">部门管理说明</h4>
|
||||
</div>
|
||||
<h4 className="text-blue-900 dark:text-blue-400 font-medium mb-3">部门管理说明</h4>
|
||||
|
||||
<ul className="space-y-2 text-sm text-blue-800 dark:text-blue-300">
|
||||
<li className="flex items-start gap-2">
|
||||
@@ -26,7 +22,7 @@ export function DepartmentInstructions() {
|
||||
</li>
|
||||
|
||||
<li className="flex items-start gap-2">
|
||||
<GripVertical className="w-4 h-4 text-blue-600 dark:text-blue-400 mt-0.5 flex-shrink-0" />
|
||||
<span className="text-blue-600 dark:text-blue-400 mt-0.5">•</span>
|
||||
<div>
|
||||
<strong className="text-blue-900 dark:text-blue-400">拖动排序:</strong>
|
||||
按住部门左侧的 ⋮⋮ 图标拖动,可调整同级部门的顺序
|
||||
@@ -42,7 +38,7 @@ export function DepartmentInstructions() {
|
||||
</li>
|
||||
|
||||
<li className="flex items-start gap-2">
|
||||
<Users className="w-4 h-4 text-blue-600 dark:text-blue-400 mt-0.5 flex-shrink-0" />
|
||||
<span className="text-blue-600 dark:text-blue-400 mt-0.5">•</span>
|
||||
<div>
|
||||
<strong className="text-blue-900 dark:text-blue-400">员工关联:</strong>
|
||||
在员工管理中新增员工时,可选择此处维护的部门
|
||||
@@ -50,7 +46,7 @@ export function DepartmentInstructions() {
|
||||
</li>
|
||||
|
||||
<li className="flex items-start gap-2">
|
||||
<AlertCircle className="w-4 h-4 text-blue-600 dark:text-blue-400 mt-0.5 flex-shrink-0" />
|
||||
<span className="text-blue-600 dark:text-blue-400 mt-0.5">•</span>
|
||||
<div>
|
||||
<strong className="text-blue-900 dark:text-blue-400">删除限制:</strong>
|
||||
删除部门前请先删除其所有子部门
|
||||
|
||||
@@ -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 (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<Dialog open={open} onOpenChange={(isOpen) => {
|
||||
if (!isOpen && onClearForm) {
|
||||
onClearForm();
|
||||
}
|
||||
onOpenChange(isOpen);
|
||||
}}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{editingEmployee ? '编辑员工' : '添加员工'}</DialogTitle>
|
||||
<DialogTitle>
|
||||
{editingEmployee ? (
|
||||
<div className="flex items-center gap-2">
|
||||
编辑员工
|
||||
{loadingDetail && (
|
||||
<div className="w-4 h-4 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
'添加员工'
|
||||
)}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="sr-only">
|
||||
{editingEmployee ? '编辑员工信息' : '添加新员工'}
|
||||
</DialogDescription>
|
||||
@@ -49,6 +114,7 @@ export function EmployeeFormDialog({
|
||||
value={formData.username || ''}
|
||||
onChange={(e) => onFormDataChange({ ...formData, username: e.target.value })}
|
||||
placeholder="登录用户名"
|
||||
disabled={editingEmployee && loadingDetail}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -58,6 +124,7 @@ export function EmployeeFormDialog({
|
||||
value={formData.name || ''}
|
||||
onChange={(e) => onFormDataChange({ ...formData, name: e.target.value })}
|
||||
placeholder="真实姓名"
|
||||
disabled={editingEmployee && loadingDetail}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -67,6 +134,7 @@ export function EmployeeFormDialog({
|
||||
value={formData.phone || ''}
|
||||
onChange={(e) => onFormDataChange({ ...formData, phone: e.target.value })}
|
||||
placeholder="11位手机号码"
|
||||
disabled={editingEmployee && loadingDetail}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -77,6 +145,7 @@ export function EmployeeFormDialog({
|
||||
value={formData.email || ''}
|
||||
onChange={(e) => onFormDataChange({ ...formData, email: e.target.value })}
|
||||
placeholder="电子邮箱"
|
||||
disabled={editingEmployee && loadingDetail}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -86,6 +155,7 @@ export function EmployeeFormDialog({
|
||||
value={formData.idCard || ''}
|
||||
onChange={(e) => onFormDataChange({ ...formData, idCard: e.target.value })}
|
||||
placeholder="18位身份证号码"
|
||||
disabled={editingEmployee && loadingDetail}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -95,6 +165,7 @@ export function EmployeeFormDialog({
|
||||
value={formData.address || ''}
|
||||
onChange={(e) => onFormDataChange({ ...formData, address: e.target.value })}
|
||||
placeholder="详细住址"
|
||||
disabled={editingEmployee && loadingDetail}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -111,6 +182,7 @@ export function EmployeeFormDialog({
|
||||
value={formData.department || ''}
|
||||
onChange={(e) => onFormDataChange({ ...formData, department: e.target.value })}
|
||||
placeholder="所属部门"
|
||||
disabled={editingEmployee && loadingDetail}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -164,10 +236,12 @@ export function EmployeeFormDialog({
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={creating || updating || loadingDetail}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={onSave}>保存</Button>
|
||||
<Button onClick={onSave} disabled={creating || updating || loadingDetail}>
|
||||
{creating ? '创建中...' : updating ? '更新中...' : loadingDetail ? '加载中...' : '保存'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -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,6 +79,7 @@ export function EmployeeList({
|
||||
};
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Card>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
@@ -142,6 +146,8 @@ export function EmployeeList({
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@@ -149,6 +155,13 @@ export function EmployeeList({
|
||||
>
|
||||
<Eye className="w-4 h-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>查看详情</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@@ -156,6 +169,13 @@ export function EmployeeList({
|
||||
>
|
||||
<Edit className="w-4 h-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>编辑员工</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@@ -163,24 +183,51 @@ export function EmployeeList({
|
||||
>
|
||||
<Lock className="w-4 h-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>重置密码</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onToggleStatus(employee)}
|
||||
disabled={togglingId === employee.id}
|
||||
>
|
||||
{(employee.isActive || employee.status === 'active') ? (
|
||||
{togglingId === employee.id ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
) : (employee.isActive || employee.status === 'active') ? (
|
||||
<UserX className="w-4 h-4 text-orange-600" />
|
||||
) : (
|
||||
<UserCheck className="w-4 h-4 text-green-600" />
|
||||
)}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{(employee.isActive || employee.status === 'active') ? '停用员工' : '激活员工'}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onDelete(employee.id)}
|
||||
disabled={togglingId === employee.id}
|
||||
>
|
||||
{togglingId === employee.id ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
) : (
|
||||
<Trash2 className="w-4 h-4 text-destructive" />
|
||||
)}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>删除员工</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -269,5 +316,6 @@ export function EmployeeList({
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
@@ -8,7 +8,9 @@ interface EmployeeManagementHeaderProps {
|
||||
onAddEmployee: () => void;
|
||||
}
|
||||
|
||||
export function EmployeeManagementHeader({ onAddEmployee }: EmployeeManagementHeaderProps) {
|
||||
export function EmployeeManagementHeader({
|
||||
onAddEmployee
|
||||
}: EmployeeManagementHeaderProps) {
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
|
||||
@@ -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<Employee> {
|
||||
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<Employee> {
|
||||
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<Employee> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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;
|
||||
|
||||
@@ -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<Employee[]>([]);
|
||||
const [roles, setRoles] = useState<Role[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [creating, setCreating] = useState(false);
|
||||
const [updating, setUpdating] = useState(false);
|
||||
const [toggling, setToggling] = useState<string | null>(null); // 记录正在操作的用户ID
|
||||
|
||||
// 确认对话框状态
|
||||
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
|
||||
const [userToDelete, setUserToDelete] = useState<Employee | null>(null);
|
||||
const [deactivateConfirmOpen, setDeactivateConfirmOpen] = useState(false);
|
||||
const [userToDeactivate, setUserToDeactivate] = useState<Employee | null>(null);
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
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<Employee | null>(null);
|
||||
const [selectedEmployee, setSelectedEmployee] = useState<Employee | null>(null);
|
||||
const [formData, setFormData] = useState<EmployeeFormData>({
|
||||
@@ -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) {
|
||||
// 更新
|
||||
// 更新 - 调用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,
|
||||
};
|
||||
|
||||
// 调用API更新用户
|
||||
const updatedEmployee = await updateEmployee(editingEmployee.id, updateRequest);
|
||||
|
||||
// 更新本地列表中的员工数据
|
||||
const updated = employees.map(emp =>
|
||||
emp.id === editingEmployee.id
|
||||
? {
|
||||
...emp,
|
||||
...formData,
|
||||
...updatedEmployee,
|
||||
roles: roleNames,
|
||||
updatedAt: new Date().toISOString(),
|
||||
}
|
||||
: emp
|
||||
);
|
||||
setEmployees(updated);
|
||||
localStorage.setItem('smart_agriculture_employees', JSON.stringify(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 {
|
||||
// 新增
|
||||
const newEmployee: Employee = {
|
||||
id: `emp-${Date.now()}`,
|
||||
...formData as Employee,
|
||||
roles: roleNames,
|
||||
auditStatus: 'pending',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
// 新增 - 调用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,
|
||||
};
|
||||
const updated = [...employees, newEmployee];
|
||||
|
||||
// 调用API创建用户
|
||||
const newEmployee = await createEmployee(createRequest);
|
||||
|
||||
// 将新员工添加到列表
|
||||
const updated = [newEmployee, ...employees];
|
||||
setEmployees(updated);
|
||||
localStorage.setItem('smart_agriculture_employees', JSON.stringify(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 = (userId: string) => {
|
||||
// 防止重复操作
|
||||
if (toggling) {
|
||||
toast.warning('操作进行中,请稍候...');
|
||||
return;
|
||||
}
|
||||
|
||||
setShowForm(false);
|
||||
// 查找要删除的员工信息
|
||||
const employeeToDelete = employees.find(emp => emp.id === userId);
|
||||
if (!employeeToDelete) {
|
||||
toast.error('未找到要删除的用户');
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置要删除的用户并显示确认对话框
|
||||
setUserToDelete(employeeToDelete);
|
||||
setDeleteConfirmOpen(true);
|
||||
};
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
if (!confirm('确定要删除该员工吗?')) return;
|
||||
const executeDelete = async (userId: string) => {
|
||||
setToggling(userId);
|
||||
|
||||
const updated = employees.filter(emp => emp.id !== id);
|
||||
try {
|
||||
// 调用API删除用户
|
||||
await deleteUser(userId);
|
||||
|
||||
// 成功后从本地列表中移除
|
||||
const updated = employees.filter(emp => emp.id !== userId);
|
||||
setEmployees(updated);
|
||||
localStorage.setItem('smart_agriculture_employees', JSON.stringify(updated));
|
||||
toast.success('员工删除成功');
|
||||
|
||||
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}
|
||||
/>
|
||||
|
||||
{/* 添加/编辑表单 */}
|
||||
<EmployeeFormDialog
|
||||
key={formKey} // 使用key强制重新渲染,清除浏览器缓存
|
||||
open={showForm}
|
||||
onOpenChange={setShowForm}
|
||||
editingEmployee={editingEmployee}
|
||||
@@ -312,6 +499,9 @@ export default function EmployeeManagementPage() {
|
||||
onFormDataChange={setFormData}
|
||||
onSave={handleSave}
|
||||
roles={roles}
|
||||
creating={creating}
|
||||
updating={updating}
|
||||
onClearForm={clearForm}
|
||||
/>
|
||||
|
||||
{/* 详情对话框 */}
|
||||
@@ -320,6 +510,74 @@ export default function EmployeeManagementPage() {
|
||||
onOpenChange={setShowDetailDialog}
|
||||
selectedEmployee={selectedEmployee}
|
||||
/>
|
||||
|
||||
{/* 删除确认对话框 */}
|
||||
<AlertDialog open={deleteConfirmOpen} onOpenChange={setDeleteConfirmOpen}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>确认删除用户</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
确定要删除用户 "
|
||||
{userToDelete?.displayName || userToDelete?.fullName || userToDelete?.username || ''}
|
||||
" 吗?
|
||||
<br /><br />
|
||||
<span className="text-red-600 font-semibold">
|
||||
删除后该用户将无法恢复,所有相关数据将被清除。
|
||||
</span>
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={toggling !== null}>
|
||||
取消
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={() => {
|
||||
if (userToDelete) {
|
||||
executeDelete(userToDelete.id);
|
||||
}
|
||||
}}
|
||||
disabled={toggling !== null}
|
||||
className="bg-red-600 hover:bg-red-700"
|
||||
>
|
||||
{toggling ? '删除中...' : '确认删除'}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{/* 停用确认对话框 */}
|
||||
<AlertDialog open={deactivateConfirmOpen} onOpenChange={setDeactivateConfirmOpen}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>确认停用用户</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
确定要停用用户 "
|
||||
{userToDeactivate?.displayName || userToDeactivate?.fullName || userToDeactivate?.username || ''}
|
||||
" 吗?
|
||||
<br /><br />
|
||||
<span className="text-orange-600 font-semibold">
|
||||
停用后,该用户将无法登录系统。
|
||||
</span>
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={toggling !== null}>
|
||||
取消
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={() => {
|
||||
if (userToDeactivate) {
|
||||
executeToggleStatus(userToDeactivate);
|
||||
}
|
||||
}}
|
||||
disabled={toggling !== null}
|
||||
className="bg-orange-600 hover:bg-orange-700"
|
||||
>
|
||||
{toggling ? '停用中...' : '确认停用'}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -75,6 +75,7 @@ export interface EmployeeFormData {
|
||||
name?: string;
|
||||
phone?: string;
|
||||
email?: string;
|
||||
password?: string;
|
||||
department?: string;
|
||||
position?: string;
|
||||
enterpriseId?: string;
|
||||
|
||||
Reference in New Issue
Block a user