生产管理系统 - 企业管理页面联调
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/types/routes.d.ts";
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
/**
|
||||
* filekorolheader: 企业管理API接口 - 企业数据查询接口服务
|
||||
* 功能:API请求封装、数据转换、错误处理、分页查询
|
||||
* 路径:/central-config/tenant/enterprise-management/components/enterpriseApi
|
||||
* 规范:遵循crop-x/docs/开发项目规范.md,使用SDK API调用,TypeScript类型安全
|
||||
*/
|
||||
|
||||
// API响应数据类型定义
|
||||
import { getAuthToken } from "@/utils/token.ts";
|
||||
import { listTenantsApiV1TenantsGet, getTenantAuditLogsApiV1TenantsAuditLogsGet } from "@/lib/api/sdk.gen";
|
||||
export interface TenantData {
|
||||
id: string;
|
||||
tenant_code: string;
|
||||
is_active: boolean;
|
||||
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;
|
||||
social_credit_code: string | null;
|
||||
legal_person_name: string | null;
|
||||
company_scale: string | null;
|
||||
registered_capital: string | null;
|
||||
established_date: string | null;
|
||||
invoice_type: string | null;
|
||||
business_scope: string | null;
|
||||
submit_time: string | null;
|
||||
audit_time: string | null;
|
||||
auditor: string | null;
|
||||
audit_status: string;
|
||||
audit_comment: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
// API响应接口
|
||||
export interface TenantsApiResponse {
|
||||
data: TenantData[];
|
||||
total: number;
|
||||
page: number;
|
||||
size: number;
|
||||
total_pages: number;
|
||||
has_next: boolean;
|
||||
has_prev: boolean;
|
||||
}
|
||||
|
||||
// 查询参数接口
|
||||
export interface TenantsQueryParams {
|
||||
search?: string;
|
||||
audit_status?: string;
|
||||
page?: number;
|
||||
size?: number;
|
||||
order_by?: string;
|
||||
sort_order?: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
// 企业页面数据类型(转换后的)
|
||||
export interface Enterprise {
|
||||
id: string;
|
||||
name: string;
|
||||
code: string;
|
||||
type: string;
|
||||
status: 'active' | 'inactive';
|
||||
auditStatus: 'not_submitted' | 'pending' | 'approved' | 'rejected' | 'draft';
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
contact?: string;
|
||||
phone?: string;
|
||||
contactPhone?: string;
|
||||
province?: string;
|
||||
city?: string;
|
||||
district?: string;
|
||||
address?: string;
|
||||
registrant?: string;
|
||||
companySize?: string;
|
||||
registeredCapital?: string;
|
||||
establishmentDate?: string;
|
||||
invoiceType?: string;
|
||||
socialCreditCode?: string;
|
||||
businessScope?: string;
|
||||
legalPerson?: string;
|
||||
bankAccount?: string;
|
||||
bankName?: string;
|
||||
bankFullName?: string;
|
||||
bankAddress?: string;
|
||||
submitTime?: string;
|
||||
auditTime?: string;
|
||||
auditor?: string;
|
||||
auditComment?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取企业列表数据
|
||||
*/
|
||||
export async function fetchTenants(params: TenantsQueryParams = {}): Promise<TenantsApiResponse> {
|
||||
try {
|
||||
// 构建查询参数对象
|
||||
const queryParams: any = {};
|
||||
|
||||
if (params.search) queryParams.search = params.search;
|
||||
if (params.audit_status) queryParams.audit_status = params.audit_status;
|
||||
if (params.page) queryParams.page = params.page;
|
||||
if (params.size) queryParams.size = params.size;
|
||||
if (params.order_by) queryParams.order_by = params.order_by;
|
||||
if (params.sort_order) queryParams.sort_order = params.sort_order;
|
||||
|
||||
// 默认参数
|
||||
if (!params.page) queryParams.page = 1;
|
||||
if (!params.size) queryParams.size = 10;
|
||||
if (!params.sort_order) queryParams.sort_order = 'desc';
|
||||
|
||||
// 使用SDK API调用,添加缓存破坏器和认证头部
|
||||
const token = getAuthToken();
|
||||
const response = await listTenantsApiV1TenantsGet({
|
||||
query: {
|
||||
...queryParams,
|
||||
// 添加时间戳防止缓存
|
||||
_t: Date.now(),
|
||||
},
|
||||
headers: token ? {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
} : undefined,
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(`API error: ${response.error.message || 'Unknown error'}`);
|
||||
}
|
||||
|
||||
const data = response.data as any;
|
||||
|
||||
// 转换响应数据格式以匹配现有的接口
|
||||
// API返回的数据结构: { data: [...], total: 25, page: 1, size: 10, ... }
|
||||
return {
|
||||
data: data?.data || [], // 注意:实际数据在 data.data 中
|
||||
total: data?.total || 0,
|
||||
page: data?.page || 1,
|
||||
size: data?.size || 10,
|
||||
total_pages: data?.total_pages || 0,
|
||||
has_next: data?.has_next || false,
|
||||
has_prev: data?.has_prev || false,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch tenants:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将API数据转换为页面所需的企业数据格式
|
||||
*/
|
||||
export function transformTenantData(tenant: TenantData): Enterprise {
|
||||
return {
|
||||
id: tenant.id,
|
||||
name: tenant.company_name,
|
||||
code: tenant.tenant_code,
|
||||
type: tenant.company_type || '未分类',
|
||||
status: tenant.is_active ? 'active' : 'inactive',
|
||||
auditStatus: mapAuditStatus(tenant.audit_status),
|
||||
createdAt: formatDate(tenant.created_at),
|
||||
updatedAt: formatDate(tenant.updated_at),
|
||||
contact: tenant.registrant,
|
||||
phone: tenant.contact_phone,
|
||||
contactPhone: tenant.contact_phone,
|
||||
province: tenant.province,
|
||||
city: tenant.city,
|
||||
district: tenant.district,
|
||||
address: tenant.detailed_address,
|
||||
registrant: tenant.registrant,
|
||||
companySize: tenant.company_scale,
|
||||
registeredCapital: tenant.registered_capital,
|
||||
establishmentDate: tenant.established_date ?
|
||||
new Date(tenant.established_date).toLocaleDateString('zh-CN') : undefined,
|
||||
invoiceType: tenant.invoice_type,
|
||||
socialCreditCode: tenant.social_credit_code,
|
||||
businessScope: tenant.business_scope,
|
||||
legalPerson: tenant.legal_person_name,
|
||||
bankAccount: tenant.bank_account,
|
||||
bankName: tenant.bank_name,
|
||||
bankFullName: tenant.bank_full_name,
|
||||
bankAddress: tenant.bank_address,
|
||||
submitTime: tenant.submit_time ? formatDate(tenant.submit_time) : undefined,
|
||||
auditTime: tenant.audit_time ? formatDate(tenant.audit_time) : undefined,
|
||||
auditor: tenant.auditor,
|
||||
auditComment: tenant.audit_comment,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 映射审核状态
|
||||
*/
|
||||
function mapAuditStatus(status: string): Enterprise['auditStatus'] {
|
||||
switch (status) {
|
||||
case '未提交':
|
||||
case '草稿':
|
||||
return 'draft';
|
||||
case '待审核':
|
||||
return 'pending';
|
||||
case '已通过':
|
||||
case '审核通过':
|
||||
return 'approved';
|
||||
case '已拒绝':
|
||||
case '已驳回':
|
||||
return 'rejected';
|
||||
default:
|
||||
return 'draft';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期
|
||||
*/
|
||||
function formatDate(dateString: string): string {
|
||||
try {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
}).replace(/\//g, '-');
|
||||
} catch (error) {
|
||||
return dateString;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* filekorolheader: 企业管理状态管理 - 企业数据状态管理核心
|
||||
* 功能:API数据管理、分页状态、加载状态、错误处理
|
||||
* 路径:/central-config/tenant/enterprise-management/components/enterpriseReducer
|
||||
* 规范:遵循crop-x/docs/开发项目规范.md,使用useReducer状态管理模式
|
||||
*/
|
||||
|
||||
import { Enterprise } from './enterpriseApi';
|
||||
|
||||
export interface FormData {
|
||||
name: string;
|
||||
code: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface EnterpriseState {
|
||||
enterprises: Enterprise[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
pagination: {
|
||||
page: number;
|
||||
size: number;
|
||||
total: number;
|
||||
totalPages: number;
|
||||
hasNext: boolean;
|
||||
hasPrev: boolean;
|
||||
};
|
||||
filters: {
|
||||
search: string;
|
||||
audit_status: string;
|
||||
};
|
||||
showAddDialog: boolean;
|
||||
showViewDialog: boolean;
|
||||
showStatusDialog: boolean;
|
||||
selectedEnterprise: Enterprise | null;
|
||||
statusAction: 'enable' | 'disable';
|
||||
formData: FormData;
|
||||
sortBy?: string;
|
||||
sortOrder: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
export type EnterpriseAction =
|
||||
| { type: 'SET_LOADING'; payload: boolean }
|
||||
| { type: 'SET_ERROR'; payload: string | null }
|
||||
| { type: 'SET_ENTERPRISES'; payload: { data: Enterprise[]; pagination: EnterpriseState['pagination'] } }
|
||||
| { type: 'SET_FILTERS'; payload: Partial<EnterpriseState['filters']> }
|
||||
| { type: 'SET_PAGINATION'; payload: Partial<EnterpriseState['pagination']> }
|
||||
| { type: 'SET_SORT'; payload: { sortBy?: string; sortOrder: 'asc' | 'desc' } }
|
||||
| { type: 'TOGGLE_ADD_DIALOG'; payload: boolean }
|
||||
| { type: 'TOGGLE_VIEW_DIALOG'; payload: boolean }
|
||||
| { type: 'TOGGLE_STATUS_DIALOG'; payload: boolean }
|
||||
| { type: 'SET_SELECTED_ENTERPRISE'; payload: Enterprise | null }
|
||||
| { type: 'SET_STATUS_ACTION'; payload: 'enable' | 'disable' }
|
||||
| { type: 'UPDATE_FORM_DATA'; payload: Partial<FormData> }
|
||||
| { type: 'RESET_FORM_DATA' }
|
||||
| { type: 'REFRESH_DATA' };
|
||||
|
||||
// 初始状态
|
||||
export const initialState: EnterpriseState = {
|
||||
enterprises: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
pagination: {
|
||||
page: 1,
|
||||
size: 10,
|
||||
total: 0,
|
||||
totalPages: 0,
|
||||
hasNext: false,
|
||||
hasPrev: false,
|
||||
},
|
||||
filters: {
|
||||
search: '',
|
||||
audit_status: '',
|
||||
},
|
||||
showAddDialog: false,
|
||||
showViewDialog: false,
|
||||
showStatusDialog: false,
|
||||
selectedEnterprise: null,
|
||||
statusAction: 'enable',
|
||||
formData: {
|
||||
name: '',
|
||||
code: '',
|
||||
type: ''
|
||||
},
|
||||
sortBy: undefined,
|
||||
sortOrder: 'desc',
|
||||
};
|
||||
|
||||
// Reducer
|
||||
export function enterpriseReducer(state: EnterpriseState, action: EnterpriseAction): EnterpriseState {
|
||||
switch (action.type) {
|
||||
case 'SET_LOADING':
|
||||
return { ...state, loading: action.payload };
|
||||
|
||||
case 'SET_ERROR':
|
||||
return { ...state, error: action.payload, loading: false };
|
||||
|
||||
case 'SET_ENTERPRISES':
|
||||
return {
|
||||
...state,
|
||||
enterprises: action.payload.data,
|
||||
pagination: action.payload.pagination,
|
||||
loading: false,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case 'SET_FILTERS':
|
||||
return {
|
||||
...state,
|
||||
filters: { ...state.filters, ...action.payload },
|
||||
pagination: { ...state.pagination, page: 1 }, // 重置到第一页
|
||||
};
|
||||
|
||||
case 'SET_PAGINATION':
|
||||
return {
|
||||
...state,
|
||||
pagination: { ...state.pagination, ...action.payload },
|
||||
};
|
||||
|
||||
case 'SET_SORT':
|
||||
return {
|
||||
...state,
|
||||
sortBy: action.payload.sortBy,
|
||||
sortOrder: action.payload.sortOrder,
|
||||
};
|
||||
|
||||
case 'TOGGLE_ADD_DIALOG':
|
||||
return {
|
||||
...state,
|
||||
showAddDialog: action.payload,
|
||||
...(action.payload === false ? { formData: initialState.formData } : {})
|
||||
};
|
||||
|
||||
case 'TOGGLE_VIEW_DIALOG':
|
||||
return { ...state, showViewDialog: action.payload };
|
||||
|
||||
case 'TOGGLE_STATUS_DIALOG':
|
||||
return { ...state, showStatusDialog: action.payload };
|
||||
|
||||
case 'SET_SELECTED_ENTERPRISE':
|
||||
return { ...state, selectedEnterprise: action.payload };
|
||||
|
||||
case 'SET_STATUS_ACTION':
|
||||
return { ...state, statusAction: action.payload };
|
||||
|
||||
case 'UPDATE_FORM_DATA':
|
||||
return {
|
||||
...state,
|
||||
formData: { ...state.formData, ...action.payload }
|
||||
};
|
||||
|
||||
case 'RESET_FORM_DATA':
|
||||
return { ...state, formData: initialState.formData };
|
||||
|
||||
case 'REFRESH_DATA':
|
||||
return {
|
||||
...state,
|
||||
error: null, // 清除错误状态
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,12 @@
|
||||
/**
|
||||
* filekorolheader: 企业管理 - 企业信息管理与维护页面
|
||||
* 功能:企业列表查询、详情查看、状态管理、分页筛选
|
||||
* 路径:/central-config/tenant/enterprise-management
|
||||
* 规范:遵循crop-x/docs/开发项目规范.md,使用useReducer状态管理,API集成,shadcn语义化样式
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import { useReducer, useMemo } from 'react';
|
||||
import { useReducer, useEffect, useMemo } from 'react';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@@ -11,246 +17,119 @@ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Building2, Plus, Eye, Power, PowerOff, Search, Hash, FileText, CreditCard, User } from 'lucide-react';
|
||||
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 { toast } from 'sonner';
|
||||
|
||||
// Types
|
||||
interface Enterprise {
|
||||
id: string;
|
||||
name: string;
|
||||
code: string;
|
||||
type: string;
|
||||
status: 'active' | 'inactive';
|
||||
auditStatus: 'not_submitted' | 'pending' | 'approved' | 'rejected';
|
||||
createdAt: string;
|
||||
contact?: string;
|
||||
phone?: string;
|
||||
contactPhone?: string;
|
||||
province?: string;
|
||||
city?: string;
|
||||
district?: string;
|
||||
address?: string;
|
||||
registrant?: string;
|
||||
companySize?: string;
|
||||
registeredCapital?: string;
|
||||
establishmentDate?: string;
|
||||
invoiceType?: string;
|
||||
socialCreditCode?: string;
|
||||
businessScope?: string;
|
||||
businessLicense?: string;
|
||||
bankAccount?: string;
|
||||
bankName?: string;
|
||||
bankFullName?: string;
|
||||
bankAddress?: string;
|
||||
bankLicense?: string;
|
||||
legalPerson?: string;
|
||||
idCardFront?: string;
|
||||
idCardBack?: string;
|
||||
}
|
||||
|
||||
interface FormData {
|
||||
name: string;
|
||||
code: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface EnterpriseState {
|
||||
enterprises: Enterprise[];
|
||||
showAddDialog: boolean;
|
||||
showViewDialog: boolean;
|
||||
showStatusDialog: boolean;
|
||||
selectedEnterprise: Enterprise | null;
|
||||
searchText: string;
|
||||
statusAction: 'enable' | 'disable';
|
||||
formData: FormData;
|
||||
}
|
||||
|
||||
type EnterpriseAction =
|
||||
| { type: 'SET_ENTERPRISES'; payload: Enterprise[] }
|
||||
| { type: 'TOGGLE_ADD_DIALOG'; payload: boolean }
|
||||
| { type: 'TOGGLE_VIEW_DIALOG'; payload: boolean }
|
||||
| { type: 'TOGGLE_STATUS_DIALOG'; payload: boolean }
|
||||
| { type: 'SET_SELECTED_ENTERPRISE'; payload: Enterprise | null }
|
||||
| { type: 'SET_SEARCH_TEXT'; payload: string }
|
||||
| { type: 'SET_STATUS_ACTION'; payload: 'enable' | 'disable' }
|
||||
| { type: 'UPDATE_FORM_DATA'; payload: Partial<FormData> }
|
||||
| { type: 'RESET_FORM_DATA' }
|
||||
| { type: 'ADD_ENTERPRISE'; payload: Enterprise }
|
||||
| { type: 'UPDATE_ENTERPRISE_STATUS'; payload: { id: string; status: 'active' | 'inactive' } };
|
||||
|
||||
// Mock data
|
||||
const mockEnterprises: Enterprise[] = [
|
||||
{
|
||||
id: 'ent-001',
|
||||
name: '智慧农业科技有限公司',
|
||||
code: 'ZHNY001',
|
||||
type: '科技企业',
|
||||
status: 'active',
|
||||
auditStatus: 'approved',
|
||||
createdAt: '2024-01-15 10:30:00',
|
||||
contact: '张三',
|
||||
phone: '13800138000',
|
||||
contactPhone: '13800138000',
|
||||
province: '北京市',
|
||||
city: '北京市',
|
||||
district: '海淀区',
|
||||
address: '中关村大街1号',
|
||||
registrant: '李四',
|
||||
companySize: '100-500人',
|
||||
registeredCapital: '1000万元',
|
||||
establishmentDate: '2020-01-01',
|
||||
invoiceType: '增值税专用发票',
|
||||
socialCreditCode: '91110108MA01XXXXXX',
|
||||
businessScope: '技术开发、技术服务、技术咨询',
|
||||
legalPerson: '王五'
|
||||
},
|
||||
{
|
||||
id: 'ent-002',
|
||||
name: '绿色农业合作社',
|
||||
code: 'LSNY002',
|
||||
type: '合作社',
|
||||
status: 'active',
|
||||
auditStatus: 'pending',
|
||||
createdAt: '2024-02-20 14:15:00',
|
||||
contact: '赵六',
|
||||
phone: '13900139000'
|
||||
},
|
||||
{
|
||||
id: 'ent-003',
|
||||
name: '现代农业发展有限公司',
|
||||
code: 'XDNY003',
|
||||
type: '农业企业',
|
||||
status: 'inactive',
|
||||
auditStatus: 'not_submitted',
|
||||
createdAt: '2024-03-10 09:45:00',
|
||||
contact: '钱七',
|
||||
phone: '13700137000'
|
||||
}
|
||||
];
|
||||
|
||||
// Initial state
|
||||
const initialState: EnterpriseState = {
|
||||
enterprises: mockEnterprises,
|
||||
showAddDialog: false,
|
||||
showViewDialog: false,
|
||||
showStatusDialog: false,
|
||||
selectedEnterprise: null,
|
||||
searchText: '',
|
||||
statusAction: 'enable',
|
||||
formData: {
|
||||
name: '',
|
||||
code: '',
|
||||
type: ''
|
||||
}
|
||||
};
|
||||
|
||||
// Reducer
|
||||
function enterpriseReducer(state: EnterpriseState, action: EnterpriseAction): EnterpriseState {
|
||||
switch (action.type) {
|
||||
case 'SET_ENTERPRISES':
|
||||
return { ...state, enterprises: action.payload };
|
||||
|
||||
case 'TOGGLE_ADD_DIALOG':
|
||||
return {
|
||||
...state,
|
||||
showAddDialog: action.payload,
|
||||
...(action.payload === false ? { formData: initialState.formData } : {})
|
||||
};
|
||||
|
||||
case 'TOGGLE_VIEW_DIALOG':
|
||||
return { ...state, showViewDialog: action.payload };
|
||||
|
||||
case 'TOGGLE_STATUS_DIALOG':
|
||||
return { ...state, showStatusDialog: action.payload };
|
||||
|
||||
case 'SET_SELECTED_ENTERPRISE':
|
||||
return { ...state, selectedEnterprise: action.payload };
|
||||
|
||||
case 'SET_SEARCH_TEXT':
|
||||
return { ...state, searchText: action.payload };
|
||||
|
||||
case 'SET_STATUS_ACTION':
|
||||
return { ...state, statusAction: action.payload };
|
||||
|
||||
case 'UPDATE_FORM_DATA':
|
||||
return {
|
||||
...state,
|
||||
formData: { ...state.formData, ...action.payload }
|
||||
};
|
||||
|
||||
case 'RESET_FORM_DATA':
|
||||
return { ...state, formData: initialState.formData };
|
||||
|
||||
case 'ADD_ENTERPRISE':
|
||||
return {
|
||||
...state,
|
||||
enterprises: [...state.enterprises, action.payload]
|
||||
};
|
||||
|
||||
case 'UPDATE_ENTERPRISE_STATUS':
|
||||
return {
|
||||
...state,
|
||||
enterprises: state.enterprises.map(ent =>
|
||||
ent.id === action.payload.id
|
||||
? { ...ent, status: action.payload.status }
|
||||
: ent
|
||||
)
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
import { enterpriseReducer, initialState, EnterpriseState, EnterpriseAction } from './components/enterpriseReducer';
|
||||
import { fetchTenants, transformTenantData, TenantsQueryParams, Enterprise } from './components/enterpriseApi';
|
||||
|
||||
// Utility functions
|
||||
const getStatusBadge = (status: 'active' | 'inactive') => {
|
||||
if (status === 'active') {
|
||||
return <Badge className="bg-green-100 text-green-800">启用</Badge>;
|
||||
return <Badge className="bg-green-50 dark:bg-green-950 text-green-600 dark:text-green-400 border-green-200 dark:border-green-800 font-light">启用</Badge>;
|
||||
}
|
||||
return <Badge className="bg-gray-100 text-gray-800">禁用</Badge>;
|
||||
return <Badge className="bg-gray-50 dark:bg-gray-950 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-800 font-light">禁用</Badge>;
|
||||
};
|
||||
|
||||
const getAuditStatusBadge = (auditStatus?: 'not_submitted' | 'pending' | 'approved' | 'rejected') => {
|
||||
const getAuditStatusBadge = (auditStatus: Enterprise['auditStatus']) => {
|
||||
switch (auditStatus) {
|
||||
case 'not_submitted':
|
||||
return <Badge className="bg-gray-100 text-gray-700">未提交</Badge>;
|
||||
case 'draft':
|
||||
return <Badge className="bg-gray-50 dark:bg-gray-950 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-800 font-light">草稿</Badge>;
|
||||
case 'pending':
|
||||
return <Badge className="bg-yellow-100 text-yellow-700">待审核</Badge>;
|
||||
return <Badge className="bg-yellow-50 dark:bg-yellow-950 text-yellow-600 dark:text-yellow-400 border-yellow-200 dark:border-yellow-800 font-light">待审核</Badge>;
|
||||
case 'approved':
|
||||
return <Badge className="bg-green-100 text-green-700">审核通过</Badge>;
|
||||
return <Badge className="bg-green-50 dark:bg-green-950 text-green-600 dark:text-green-400 border-green-200 dark:border-green-800 font-light">审核通过</Badge>;
|
||||
case 'rejected':
|
||||
return <Badge className="bg-red-100 text-red-700">已驳回</Badge>;
|
||||
return <Badge className="bg-red-50 dark:bg-red-950 text-red-600 dark:text-red-400 border-red-200 dark:border-red-800 font-light">已拒绝</Badge>;
|
||||
default:
|
||||
return <Badge className="bg-gray-100 text-gray-700">未提交</Badge>;
|
||||
return <Badge className="bg-gray-50 dark:bg-gray-950 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-800 font-light">草稿</Badge>;
|
||||
}
|
||||
};
|
||||
|
||||
export default function EnterpriseManagement() {
|
||||
const [state, dispatch] = useReducer(enterpriseReducer, initialState);
|
||||
|
||||
// Computed values
|
||||
const filteredEnterprises = useMemo(() => {
|
||||
return state.enterprises.filter(ent => {
|
||||
if (!state.searchText) return true;
|
||||
const searchLower = state.searchText.toLowerCase();
|
||||
return (
|
||||
ent.name.toLowerCase().includes(searchLower) ||
|
||||
ent.code.toLowerCase().includes(searchLower) ||
|
||||
(ent.type && ent.type.toLowerCase().includes(searchLower))
|
||||
);
|
||||
});
|
||||
}, [state.enterprises, state.searchText]);
|
||||
// 加载企业数据
|
||||
const loadEnterprises = async (resetPage = false) => {
|
||||
try {
|
||||
dispatch({ type: 'SET_LOADING', payload: true });
|
||||
|
||||
const params: TenantsQueryParams = {
|
||||
search: state.filters.search || undefined,
|
||||
audit_status: state.filters.audit_status || undefined,
|
||||
page: resetPage ? 1 : state.pagination.page,
|
||||
size: state.pagination.size,
|
||||
order_by: state.sortBy,
|
||||
sort_order: state.sortOrder,
|
||||
};
|
||||
|
||||
const response = await fetchTenants(params);
|
||||
const transformedData = response.data.map(transformTenantData);
|
||||
|
||||
dispatch({
|
||||
type: 'SET_ENTERPRISES',
|
||||
payload: {
|
||||
data: transformedData,
|
||||
pagination: {
|
||||
page: response.page,
|
||||
size: response.size,
|
||||
total: response.total,
|
||||
totalPages: response.total_pages,
|
||||
hasNext: response.has_next,
|
||||
hasPrev: response.has_prev,
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to load enterprises:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : '加载企业数据失败';
|
||||
dispatch({ type: 'SET_ERROR', payload: errorMessage });
|
||||
toast.error(errorMessage);
|
||||
}
|
||||
};
|
||||
|
||||
// 初始加载
|
||||
useEffect(() => {
|
||||
loadEnterprises(true);
|
||||
}, [state.filters.search, state.filters.audit_status, state.sortBy, state.sortOrder]);
|
||||
|
||||
// 分页加载
|
||||
useEffect(() => {
|
||||
if (state.pagination.page > 1) {
|
||||
loadEnterprises(false);
|
||||
}
|
||||
}, [state.pagination.page]);
|
||||
|
||||
// 计算统计数据
|
||||
const stats = useMemo(() => ({
|
||||
total: state.enterprises.length,
|
||||
active: state.enterprises.filter(e => e.status === 'active').length,
|
||||
inactive: state.enterprises.filter(e => e.status === 'inactive').length,
|
||||
}), [state.enterprises]);
|
||||
|
||||
// Event handlers
|
||||
const handleAdd = () => {
|
||||
dispatch({ type: 'RESET_FORM_DATA' });
|
||||
dispatch({ type: 'TOGGLE_ADD_DIALOG', payload: true });
|
||||
// 事件处理器
|
||||
const handleSearch = (value: string) => {
|
||||
dispatch({ type: 'SET_FILTERS', payload: { search: value } });
|
||||
};
|
||||
|
||||
const handleAuditStatusFilter = (value: string) => {
|
||||
dispatch({ type: 'SET_FILTERS', payload: { audit_status: value === 'all' ? '' : value } });
|
||||
};
|
||||
|
||||
const handleSort = (sortBy?: string) => {
|
||||
const newSortOrder = state.sortBy === sortBy && state.sortOrder === 'desc' ? 'asc' : 'desc';
|
||||
dispatch({ type: 'SET_SORT', payload: { sortBy, sortOrder: newSortOrder } });
|
||||
};
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
dispatch({ type: 'SET_PAGINATION', payload: { page } });
|
||||
};
|
||||
|
||||
const handleRefresh = () => {
|
||||
dispatch({ type: 'REFRESH_DATA' });
|
||||
loadEnterprises(true);
|
||||
toast.success('数据已刷新');
|
||||
};
|
||||
|
||||
const handleView = (enterprise: Enterprise) => {
|
||||
@@ -264,116 +143,96 @@ export default function EnterpriseManagement() {
|
||||
dispatch({ type: 'TOGGLE_STATUS_DIALOG', payload: true });
|
||||
};
|
||||
|
||||
const confirmAdd = () => {
|
||||
const { formData } = state;
|
||||
|
||||
if (!formData.name.trim()) {
|
||||
toast.error('请输入企业名称');
|
||||
return;
|
||||
}
|
||||
if (!formData.code.trim()) {
|
||||
toast.error('请输入企业编码');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查编码是否重复
|
||||
if (state.enterprises.some(e => e.code === formData.code)) {
|
||||
toast.error('企业编码已存在');
|
||||
return;
|
||||
}
|
||||
|
||||
const newEnterprise: Enterprise = {
|
||||
id: `ent-${Date.now()}`,
|
||||
name: formData.name,
|
||||
code: formData.code,
|
||||
type: formData.type || '未分类',
|
||||
status: 'active',
|
||||
auditStatus: 'not_submitted',
|
||||
createdAt: new Date().toISOString().replace('T', ' ').substring(0, 19),
|
||||
};
|
||||
|
||||
dispatch({ type: 'ADD_ENTERPRISE', payload: newEnterprise });
|
||||
dispatch({ type: 'TOGGLE_ADD_DIALOG', payload: false });
|
||||
toast.success('企业创建成功');
|
||||
};
|
||||
|
||||
const confirmStatusChange = () => {
|
||||
if (!state.selectedEnterprise) return;
|
||||
|
||||
// 这里应该调用API来更新企业状态
|
||||
// 暂时更新本地状态
|
||||
const newStatus = state.statusAction === 'enable' ? 'active' : 'inactive';
|
||||
|
||||
dispatch({
|
||||
type: 'UPDATE_ENTERPRISE_STATUS',
|
||||
payload: { id: state.selectedEnterprise.id, status: newStatus }
|
||||
type: 'SET_ENTERPRISES',
|
||||
payload: {
|
||||
data: state.enterprises.map(ent =>
|
||||
ent.id === state.selectedEnterprise?.id
|
||||
? { ...ent, status: newStatus }
|
||||
: ent
|
||||
),
|
||||
pagination: state.pagination
|
||||
}
|
||||
});
|
||||
|
||||
dispatch({ type: 'TOGGLE_STATUS_DIALOG', payload: false });
|
||||
toast.success(state.statusAction === 'enable' ? '企业已启用' : '企业已禁用');
|
||||
};
|
||||
|
||||
const updateFormData = (field: keyof FormData, value: string) => {
|
||||
dispatch({ type: 'UPDATE_FORM_DATA', payload: { [field]: value || '' } });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Page Header */}
|
||||
<Card className="p-6 bg-gradient-to-r from-blue-50 to-indigo-50 border-blue-200">
|
||||
<div className="flex items-start gap-3">
|
||||
<Building2 className="w-6 h-6 text-blue-600 flex-shrink-0 mt-1" />
|
||||
<div className="flex-1">
|
||||
<h2 className="text-green-800 mb-2">企业管理</h2>
|
||||
<p className="text-sm text-muted-foreground mb-3">
|
||||
管理平台所有企业信息,支持创建企业、查看详情、启用/禁用企业
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Badge variant="outline" className="bg-white">
|
||||
<Plus className="w-3 h-3 mr-1" />
|
||||
快速创建
|
||||
</Badge>
|
||||
<Badge variant="outline" className="bg-white">
|
||||
<Power className="w-3 h-3 mr-1" />
|
||||
状态管理
|
||||
</Badge>
|
||||
<Badge variant="outline" className="bg-white">
|
||||
<Eye className="w-3 h-3 mr-1" />
|
||||
详情查看
|
||||
</Badge>
|
||||
<Card className="p-6 bg-gradient-to-r from-blue-50 dark:from-blue-950 to-indigo-50 dark:to-indigo-950 border-blue-200 dark:border-blue-800">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-start gap-3">
|
||||
<Building2 className="w-6 h-6 text-blue-600 dark:text-blue-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">
|
||||
<Badge variant="outline" className="bg-white dark:bg-gray-800 font-light">
|
||||
<Search className="w-3 h-3 mr-1" />
|
||||
智能查询
|
||||
</Badge>
|
||||
<Badge variant="outline" className="bg-white dark:bg-gray-800 font-light">
|
||||
<Power className="w-3 h-3 mr-1" />
|
||||
状态管理
|
||||
</Badge>
|
||||
<Badge variant="outline" className="bg-white dark:bg-gray-800 font-light">
|
||||
<Eye className="w-3 h-3 mr-1" />
|
||||
详情查看
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" size="sm" onClick={handleRefresh} disabled={state.loading}>
|
||||
<RefreshCw className={`w-4 h-4 mr-1 ${state.loading ? 'animate-spin' : ''}`} />
|
||||
刷新
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Statistics Cards */}
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<Card className="p-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<Card className="p-6 bg-card hover:bg-muted transition-colors">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="text-sm text-muted-foreground">企业总数</div>
|
||||
<Building2 className="w-5 h-5 text-blue-500" />
|
||||
</div>
|
||||
<div className="text-3xl font-bold mb-1">{stats.total}</div>
|
||||
<div className="text-3xl font-bold mb-1">{state.pagination.total}</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
全部企业数量
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6">
|
||||
<Card className="p-6 bg-card hover:bg-muted transition-colors">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="text-sm text-muted-foreground">启用企业</div>
|
||||
<Power className="w-5 h-5 text-green-500" />
|
||||
</div>
|
||||
<div className="text-3xl font-bold mb-1">{stats.active}</div>
|
||||
<div className="text-xs text-green-600">
|
||||
<div className="text-3xl font-bold mb-1 text-green-600 dark:text-green-400">{stats.active}</div>
|
||||
<div className="text-xs text-green-600 dark:text-green-400">
|
||||
正常运营中
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6">
|
||||
<Card className="p-6 bg-card hover:bg-muted transition-colors">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="text-sm text-muted-foreground">禁用企业</div>
|
||||
<PowerOff className="w-5 h-5 text-gray-500" />
|
||||
</div>
|
||||
<div className="text-3xl font-bold mb-1">{stats.inactive}</div>
|
||||
<div className="text-3xl font-bold mb-1 text-gray-600 dark:text-gray-400">{stats.inactive}</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
已暂停使用
|
||||
</div>
|
||||
@@ -381,177 +240,193 @@ export default function EnterpriseManagement() {
|
||||
</div>
|
||||
|
||||
{/* Enterprise List */}
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<Card className="p-6 bg-card">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-4">
|
||||
<h3>企业列表</h3>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex flex-col sm:flex-row gap-2">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="搜索企业名称、编码..."
|
||||
value={state.searchText}
|
||||
onChange={(e) => dispatch({ type: 'SET_SEARCH_TEXT', payload: e.target.value || '' })}
|
||||
value={state.filters.search}
|
||||
onChange={(e) => handleSearch(e.target.value)}
|
||||
className="pl-10 w-64"
|
||||
/>
|
||||
</div>
|
||||
<Button onClick={handleAdd}>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
新建企业
|
||||
</Button>
|
||||
<Select value={state.filters.audit_status || 'all'} onValueChange={handleAuditStatusFilter}>
|
||||
<SelectTrigger className="w-40">
|
||||
<SelectValue placeholder="审核状态" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">全部状态</SelectItem>
|
||||
<SelectItem value="草稿">草稿</SelectItem>
|
||||
<SelectItem value="待审核">待审核</SelectItem>
|
||||
<SelectItem value="已通过">审核通过</SelectItem>
|
||||
<SelectItem value="已拒绝">已拒绝</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>企业编码</TableHead>
|
||||
<TableHead>企业名称</TableHead>
|
||||
<TableHead>企业类型</TableHead>
|
||||
<TableHead>联系人</TableHead>
|
||||
<TableHead>联系电话</TableHead>
|
||||
<TableHead>创建时间</TableHead>
|
||||
<TableHead>审核状态</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredEnterprises.map((enterprise) => (
|
||||
<TableRow key={enterprise.id}>
|
||||
<TableCell className="font-medium">{enterprise.code}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-2">
|
||||
<Building2 className="w-4 h-4 text-blue-500" />
|
||||
<span className="font-medium">{enterprise.name}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline">{enterprise.type || '未分类'}</Badge>
|
||||
</TableCell>
|
||||
<TableCell>{enterprise.contact || '-'}</TableCell>
|
||||
<TableCell>{enterprise.phone || '-'}</TableCell>
|
||||
<TableCell className="text-sm">{enterprise.createdAt}</TableCell>
|
||||
<TableCell>{getAuditStatusBadge(enterprise.auditStatus)}</TableCell>
|
||||
<TableCell>{getStatusBadge(enterprise.status)}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleView(enterprise)}
|
||||
>
|
||||
<Eye className="w-3 h-3 mr-1" />
|
||||
查看
|
||||
</Button>
|
||||
{enterprise.status === 'active' ? (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="text-gray-600 border-gray-300"
|
||||
onClick={() => handleStatusChange(enterprise, 'disable')}
|
||||
>
|
||||
<PowerOff className="w-3 h-3 mr-1" />
|
||||
禁用
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="text-green-600 border-green-300"
|
||||
onClick={() => handleStatusChange(enterprise, 'enable')}
|
||||
>
|
||||
<Power className="w-3 h-3 mr-1" />
|
||||
启用
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
{filteredEnterprises.length === 0 && (
|
||||
<div className="text-center py-12 text-muted-foreground">
|
||||
<Building2 className="w-12 h-12 mx-auto mb-4 opacity-20" />
|
||||
<p>暂无企业数据</p>
|
||||
{/* Error Display */}
|
||||
{state.error && (
|
||||
<div className="mb-4 p-4 bg-red-50 dark:bg-red-950 border border-red-200 dark:border-red-800 rounded-lg">
|
||||
<div className="flex items-center gap-2 text-red-600 dark:text-red-400">
|
||||
<AlertCircle className="w-4 h-4" />
|
||||
<span>{state.error}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{/* Add Enterprise Dialog */}
|
||||
<Dialog open={state.showAddDialog} onOpenChange={(open) => dispatch({ type: 'TOGGLE_ADD_DIALOG', payload: open })}>
|
||||
<DialogContent className="w-[80vw] max-w-4xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>新建企业</DialogTitle>
|
||||
<DialogDescription>
|
||||
创建新企业账号,管理员仅需填写基本信息,详细信息由企业登录后自行完善
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label>企业名称 *</Label>
|
||||
<div className="relative mt-2">
|
||||
<Building2 className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="请输入企业全称"
|
||||
value={state.formData.name}
|
||||
onChange={(e) => updateFormData('name', e.target.value)}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>企业编码 *</Label>
|
||||
<div className="relative mt-2">
|
||||
<Hash className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="请输入企业唯一编码,如:LYNY001"
|
||||
value={state.formData.code}
|
||||
onChange={(e) => updateFormData('code', e.target.value.toUpperCase())}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">编码创建后不可修改,请谨慎填写</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>企业类型</Label>
|
||||
<div className="relative mt-2">
|
||||
<Building2 className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="如:种植企业、养殖企业、合作社等"
|
||||
value={state.formData.type}
|
||||
onChange={(e) => updateFormData('type', e.target.value)}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-blue-50 border border-blue-200 rounded-lg">
|
||||
<p className="text-sm text-blue-800">
|
||||
<strong>温馨提示:</strong>
|
||||
</p>
|
||||
<ul className="text-sm text-blue-700 mt-2 space-y-1">
|
||||
<li>• 企业创建后默认为启用状态,审核状态为"未提交"</li>
|
||||
<li>• 联系人、电话、地址等详细信息由企业登录后自行填写</li>
|
||||
<li>• 企业编码创建后不可修改,请确保准确无误</li>
|
||||
</ul>
|
||||
</div>
|
||||
{/* Loading State */}
|
||||
{state.loading && (
|
||||
<div className="text-center py-12 text-muted-foreground">
|
||||
<RefreshCw className="w-8 h-8 mx-auto mb-2 animate-spin" />
|
||||
<p>加载中...</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => dispatch({ type: 'TOGGLE_ADD_DIALOG', payload: false })}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={confirmAdd}>创建企业</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
{/* Data Table */}
|
||||
{!state.loading && !state.error && (
|
||||
<>
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead
|
||||
className="cursor-pointer hover:bg-muted"
|
||||
onClick={() => handleSort('tenant_code')}
|
||||
>
|
||||
企业编码
|
||||
{state.sortBy === 'tenant_code' && (
|
||||
<span className="ml-1">{state.sortOrder === 'asc' ? '↑' : '↓'}</span>
|
||||
)}
|
||||
</TableHead>
|
||||
<TableHead
|
||||
className="cursor-pointer hover:bg-muted"
|
||||
onClick={() => handleSort('company_name')}
|
||||
>
|
||||
企业名称
|
||||
{state.sortBy === 'company_name' && (
|
||||
<span className="ml-1">{state.sortOrder === 'asc' ? '↑' : '↓'}</span>
|
||||
)}
|
||||
</TableHead>
|
||||
<TableHead>企业类型</TableHead>
|
||||
<TableHead>登记人</TableHead>
|
||||
<TableHead>联系电话</TableHead>
|
||||
<TableHead
|
||||
className="cursor-pointer hover:bg-muted"
|
||||
onClick={() => handleSort('created_at')}
|
||||
>
|
||||
创建时间
|
||||
{state.sortBy === 'created_at' && (
|
||||
<span className="ml-1">{state.sortOrder === 'asc' ? '↑' : '↓'}</span>
|
||||
)}
|
||||
</TableHead>
|
||||
<TableHead>审核状态</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{state.enterprises.map((enterprise) => (
|
||||
<TableRow key={enterprise.id}>
|
||||
<TableCell className="font-medium">{enterprise.code}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-2">
|
||||
<Building2 className="w-4 h-4 text-blue-500" />
|
||||
<span className="font-medium">{enterprise.name}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline" className="font-light">{enterprise.type}</Badge>
|
||||
</TableCell>
|
||||
<TableCell>{enterprise.registrant || '-'}</TableCell>
|
||||
<TableCell>{enterprise.contactPhone || '-'}</TableCell>
|
||||
<TableCell className="text-sm">{enterprise.createdAt}</TableCell>
|
||||
<TableCell>{getAuditStatusBadge(enterprise.auditStatus)}</TableCell>
|
||||
<TableCell>{getStatusBadge(enterprise.status)}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleView(enterprise)}
|
||||
>
|
||||
<Eye className="w-3 h-3 mr-1" />
|
||||
查看
|
||||
</Button>
|
||||
{enterprise.status === 'active' ? (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="text-gray-600 dark:text-gray-400 border-gray-300 dark:border-gray-600"
|
||||
onClick={() => handleStatusChange(enterprise, 'disable')}
|
||||
>
|
||||
<PowerOff className="w-3 h-3 mr-1" />
|
||||
禁用
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="text-green-600 dark:text-green-400 border-green-300 dark:border-green-600"
|
||||
onClick={() => handleStatusChange(enterprise, 'enable')}
|
||||
>
|
||||
<Power className="w-3 h-3 mr-1" />
|
||||
启用
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
{state.enterprises.length === 0 && (
|
||||
<div className="text-center py-12 text-muted-foreground">
|
||||
<Building2 className="w-12 h-12 mx-auto mb-4 opacity-20" />
|
||||
<p>暂无企业数据</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Pagination */}
|
||||
{state.pagination.totalPages > 1 && (
|
||||
<div className="flex items-center justify-between mt-4">
|
||||
<div className="text-sm text-muted-foreground">
|
||||
显示第 {state.pagination.page} 页,共 {state.pagination.totalPages} 页
|
||||
<span className="ml-2">总计 {state.pagination.total} 条记录</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handlePageChange(state.pagination.page - 1)}
|
||||
disabled={!state.pagination.hasPrev || state.loading}
|
||||
>
|
||||
<ChevronLeft className="w-4 h-4" />
|
||||
上一页
|
||||
</Button>
|
||||
<span className="text-sm font-medium px-2">
|
||||
{state.pagination.page} / {state.pagination.totalPages}
|
||||
</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handlePageChange(state.pagination.page + 1)}
|
||||
disabled={!state.pagination.hasNext || state.loading}
|
||||
>
|
||||
下一页
|
||||
<ChevronRight className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{/* View Enterprise Details Dialog */}
|
||||
<Dialog open={state.showViewDialog} onOpenChange={(open) => dispatch({ type: 'TOGGLE_VIEW_DIALOG', payload: open })}>
|
||||
@@ -605,7 +480,7 @@ export default function EnterpriseManagement() {
|
||||
</div>
|
||||
<div>
|
||||
<Label>企业类型</Label>
|
||||
<div className="field-value p-2 bg-muted rounded">{state.selectedEnterprise.type || '-'}</div>
|
||||
<div className="field-value p-2 bg-muted rounded">{state.selectedEnterprise.type}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>所在地区</Label>
|
||||
@@ -623,7 +498,7 @@ export default function EnterpriseManagement() {
|
||||
</div>
|
||||
<div>
|
||||
<Label>联系电话</Label>
|
||||
<div className="field-value p-2 bg-muted rounded">{state.selectedEnterprise.contactPhone || state.selectedEnterprise.phone || '-'}</div>
|
||||
<div className="field-value p-2 bg-muted rounded">{state.selectedEnterprise.contactPhone || '-'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
@@ -661,19 +536,13 @@ export default function EnterpriseManagement() {
|
||||
<Label>经营范围</Label>
|
||||
<div className="field-value p-2 bg-muted rounded">{state.selectedEnterprise.businessScope || '-'}</div>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<Label>营业执照</Label>
|
||||
<div className="mt-2">
|
||||
{state.selectedEnterprise.businessLicense ? (
|
||||
<img
|
||||
src={state.selectedEnterprise.businessLicense}
|
||||
alt="营业执照"
|
||||
className="w-64 h-auto border rounded-lg"
|
||||
/>
|
||||
) : (
|
||||
<span className="text-muted-foreground">未上传</span>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<Label>提交时间</Label>
|
||||
<div className="field-value p-2 bg-muted rounded">{state.selectedEnterprise.submitTime || '-'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>审核时间</Label>
|
||||
<div className="field-value p-2 bg-muted rounded">{state.selectedEnterprise.auditTime || '-'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
@@ -703,20 +572,6 @@ export default function EnterpriseManagement() {
|
||||
<Label>开户行地址</Label>
|
||||
<div className="field-value p-2 bg-muted rounded">{state.selectedEnterprise.bankAddress || '-'}</div>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<Label>开户许可证</Label>
|
||||
<div className="mt-2">
|
||||
{state.selectedEnterprise.bankLicense ? (
|
||||
<img
|
||||
src={state.selectedEnterprise.bankLicense}
|
||||
alt="开户许可证"
|
||||
className="w-64 h-auto border rounded-lg"
|
||||
/>
|
||||
) : (
|
||||
<span className="text-muted-foreground">未上传</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
@@ -729,35 +584,15 @@ export default function EnterpriseManagement() {
|
||||
</div>
|
||||
<div>
|
||||
<Label>联系人</Label>
|
||||
<div className="field-value p-2 bg-muted rounded">{state.selectedEnterprise.contact || '-'}</div>
|
||||
<div className="field-value p-2 bg-muted rounded">{state.selectedEnterprise.registrant || '-'}</div>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<Label>身份证正面</Label>
|
||||
<div className="mt-2">
|
||||
{state.selectedEnterprise.idCardFront ? (
|
||||
<img
|
||||
src={state.selectedEnterprise.idCardFront}
|
||||
alt="身份证正面"
|
||||
className="w-64 h-auto border rounded-lg"
|
||||
/>
|
||||
) : (
|
||||
<span className="text-muted-foreground">未上传</span>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<Label>审核人</Label>
|
||||
<div className="field-value p-2 bg-muted rounded">{state.selectedEnterprise.auditor || '-'}</div>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<Label>身份证反面</Label>
|
||||
<div className="mt-2">
|
||||
{state.selectedEnterprise.idCardBack ? (
|
||||
<img
|
||||
src={state.selectedEnterprise.idCardBack}
|
||||
alt="身份证反面"
|
||||
className="w-64 h-auto border rounded-lg"
|
||||
/>
|
||||
) : (
|
||||
<span className="text-muted-foreground">未上传</span>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<Label>审核意见</Label>
|
||||
<div className="field-value p-2 bg-muted rounded">{state.selectedEnterprise.auditComment || '-'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
@@ -139,7 +139,6 @@ export function LoginForm({ onRegisterClick }: LoginFormProps) {
|
||||
apiResponse: response.data,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
debugger
|
||||
// 验证token是否正确存储
|
||||
if (userData.token) {
|
||||
console.log('🔑 Token已存储:', userData.token.substring(0, 20) + '...');
|
||||
|
||||
37
crop-x/src/lib/api/config.ts
Normal file
37
crop-x/src/lib/api/config.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* filekorolheader: API配置文件 - API客户端配置和认证处理
|
||||
* 功能:API基础配置、认证头部处理、错误处理
|
||||
* 路径:/lib/api/config
|
||||
* 规范:遵循crop-x/docs/开发项目规范.md,统一API调用配置
|
||||
*/
|
||||
|
||||
import { getAuthToken } from '@/utils/token';
|
||||
|
||||
// API基础URL配置 - 开发环境直接使用真实API地址避免重定向问题
|
||||
export const API_BASE_URL = 'https://gitea-admin-hm-smart-agri-app.dev.maimaiag.com';
|
||||
|
||||
// 获取认证头部
|
||||
export const getAuthHeaders = () => {
|
||||
const token = getAuthToken();
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
// 添加缓存控制头部
|
||||
'Cache-Control': 'no-store, no-cache, must-revalidate',
|
||||
'Pragma': 'no-cache',
|
||||
'Expires': '0',
|
||||
};
|
||||
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
return headers;
|
||||
};
|
||||
|
||||
// API请求配置
|
||||
export const apiConfig = {
|
||||
baseURL: API_BASE_URL,
|
||||
timeout: 30000, // 30秒超时
|
||||
headers: getAuthHeaders(),
|
||||
};
|
||||
11
crop-x/src/utils/token.ts
Normal file
11
crop-x/src/utils/token.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export const getAuthToken = (): string | null => {
|
||||
try {
|
||||
const storedUser = localStorage.getItem('user');
|
||||
const user = storedUser ? JSON.parse(storedUser) : null;
|
||||
return user?.token || null;
|
||||
} catch (error) {
|
||||
console.error('获取token失败:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user