Compare commits
2 Commits
bb517741b9
...
a2fed17b48
| Author | SHA1 | Date | |
|---|---|---|---|
| a2fed17b48 | |||
| f6b253e6ef |
@@ -23,7 +23,7 @@
|
|||||||
* filekorolheader: 物联设备数据接入页面 - IoT设备数据管理中心
|
* filekorolheader: 物联设备数据接入页面 - IoT设备数据管理中心
|
||||||
* 功能:设备列表管理、实时数据监控、数据对比分析、报告生成
|
* 功能:设备列表管理、实时数据监控、数据对比分析、报告生成
|
||||||
* 路径:/ai-crop-model/data-sense-center/iot
|
* 路径:/ai-crop-model/data-sense-center/iot
|
||||||
* 规范:遵循crop-x/docs/开发项目规范.md,使用useReducer状态管理,shadcn语义化样式
|
* 规范:遵循crop-x-new/docs/开发项目规范.md,使用useReducer状态管理,shadcn语义化样式
|
||||||
*/
|
*/
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -216,7 +216,7 @@ src/app/(app)/land-information/archive/statistics/
|
|||||||
### 9.注意乱码原则
|
### 9.注意乱码原则
|
||||||
生成的代码注意看看有没有乱码,必须遵守utf-8编码
|
生成的代码注意看看有没有乱码,必须遵守utf-8编码
|
||||||
### 10.接口调用原则。
|
### 10.接口调用原则。
|
||||||
接口必须调用 D:\code\repotest\smart-crop-ui\crop-x\src\lib\api\sdk.gen.ts 这个里面的接口
|
接口必须调用 D:\code\repotest\smart-crop-ui\crop-x-new\src\lib\api\sdk.gen.ts 这个里面的接口
|
||||||
比如 /api/v1/departments/tree 这个路径,就是要调用export const getDepartmentTreeApiV1DepartmentsDepartmentsTreeGet = <ThrowOnError extends boolean = false>(options?: Options<GetDepartmentTreeApiV1DepartmentsDepartmentsTreeGetData, ThrowOnError>) => {
|
比如 /api/v1/departments/tree 这个路径,就是要调用export const getDepartmentTreeApiV1DepartmentsDepartmentsTreeGet = <ThrowOnError extends boolean = false>(options?: Options<GetDepartmentTreeApiV1DepartmentsDepartmentsTreeGetData, ThrowOnError>) => {
|
||||||
return (options?.client ?? client).get<GetDepartmentTreeApiV1DepartmentsDepartmentsTreeGetResponses, unknown, ThrowOnError>({
|
return (options?.client ?? client).get<GetDepartmentTreeApiV1DepartmentsDepartmentsTreeGetResponses, unknown, ThrowOnError>({
|
||||||
security: [
|
security: [
|
||||||
@@ -229,7 +229,7 @@ src/app/(app)/land-information/archive/statistics/
|
|||||||
...options
|
...options
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
实际使用的时候,要参考D:\code\repotest\smart-crop-ui\crop-x\src\app\(app)\central-config\tenant\audit-history\components\auditHistoryApi.ts 里面
|
实际使用的时候,要参考D:\code\repotest\smart-crop-ui\crop-x-new\src\app\(app)\central-config\tenant\audit-history\components\auditHistoryApi.ts 里面
|
||||||
import {
|
import {
|
||||||
getTenantAuditLogsApiV1TenantsAuditLogsGet,
|
getTenantAuditLogsApiV1TenantsAuditLogsGet,
|
||||||
} from "@/lib/api/sdk.gen"; 这个引入和用法。
|
} from "@/lib/api/sdk.gen"; 这个引入和用法。
|
||||||
|
|||||||
2
crop-x-new/next-env.d.ts
vendored
2
crop-x-new/next-env.d.ts
vendored
@@ -1,6 +1,6 @@
|
|||||||
/// <reference types="next" />
|
/// <reference types="next" />
|
||||||
/// <reference types="next/image-types/global" />
|
/// <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
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ export async function fetchAuditLogs(params: AuditLogsQueryParams = {}): Promise
|
|||||||
if (params.size) queryParams.size = params.size;
|
if (params.size) queryParams.size = params.size;
|
||||||
if (params.order_by) queryParams.order_by = params.order_by;
|
if (params.order_by) queryParams.order_by = params.order_by;
|
||||||
if (params.sort_order) queryParams.sort_order = params.sort_order;
|
if (params.sort_order) queryParams.sort_order = params.sort_order;
|
||||||
|
if (params.search_keyword) queryParams.search = params.search_keyword;
|
||||||
// 默认参数
|
// 默认参数
|
||||||
if (!params.page) queryParams.page = 1;
|
if (!params.page) queryParams.page = 1;
|
||||||
if (!params.size) queryParams.size = 10;
|
if (!params.size) queryParams.size = 10;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,525 +0,0 @@
|
|||||||
/**
|
|
||||||
* filekorolheader: 审核历史 - 企业审核记录查询与详情查看页面
|
|
||||||
* 功能:审核历史查询、筛选过滤、详情查看、数据导出、分页控制
|
|
||||||
* 路径:/central-config/tenant/audit-history
|
|
||||||
* 规范:遵循crop-x/docs/开发项目规范.md,使用useReducer状态管理,API集成,shadcn语义化样式
|
|
||||||
*/
|
|
||||||
'use client';
|
|
||||||
|
|
||||||
import { useReducer, useEffect, useMemo } from 'react';
|
|
||||||
import { Card } from '@/components/ui/card';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Badge } from '@/components/ui/badge';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { Label } from '@/components/ui/label';
|
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
|
|
||||||
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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
||||||
import {
|
|
||||||
History,
|
|
||||||
Download,
|
|
||||||
Search,
|
|
||||||
Eye,
|
|
||||||
AlertCircle,
|
|
||||||
ChevronLeft,
|
|
||||||
ChevronRight,
|
|
||||||
Calendar,
|
|
||||||
Clock,
|
|
||||||
User,
|
|
||||||
FileText,
|
|
||||||
Building2,
|
|
||||||
MapPin,
|
|
||||||
CreditCard,
|
|
||||||
Phone
|
|
||||||
} from 'lucide-react';
|
|
||||||
import { toast } from 'sonner';
|
|
||||||
|
|
||||||
import { auditHistoryReducer, initialState, AuditHistoryState, AuditHistoryAction } from './components/auditHistoryReducer';
|
|
||||||
import { fetchAuditLogs, transformAuditLogData, AuditLogsQueryParams } from './components/auditHistoryApi';
|
|
||||||
import { AuditRecord, FilterOptions, AuditStatus } from './types';
|
|
||||||
|
|
||||||
// Utility functions
|
|
||||||
const getStatusBadge = (status: AuditStatus) => {
|
|
||||||
switch (status) {
|
|
||||||
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-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-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-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-50 dark:bg-gray-950 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-800 font-light">未知</Badge>;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getActionBadge = (action: string) => {
|
|
||||||
if (action === 'SUBMIT') {
|
|
||||||
return <Badge className="bg-blue-50 dark:bg-blue-950 text-blue-600 dark:text-blue-400 border-blue-200 dark:border-blue-800 font-light">提交</Badge>;
|
|
||||||
} else if (action === 'AUDIT') {
|
|
||||||
return <Badge className="bg-purple-50 dark:bg-purple-950 text-purple-600 dark:text-purple-400 border-purple-200 dark:border-purple-800 font-light">审核</Badge>;
|
|
||||||
}
|
|
||||||
return <Badge variant="outline" className="font-light">{action}</Badge>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function AuditHistoryPage() {
|
|
||||||
const [state, dispatch] = useReducer(auditHistoryReducer, initialState);
|
|
||||||
|
|
||||||
// 加载审核历史数据
|
|
||||||
const loadAuditHistory = async (resetPage = false) => {
|
|
||||||
try {
|
|
||||||
dispatch({ type: 'SET_LOADING', payload: true });
|
|
||||||
|
|
||||||
const params: AuditLogsQueryParams = {
|
|
||||||
page: resetPage ? 1 : state.pagination.page,
|
|
||||||
size: state.pagination.size,
|
|
||||||
order_by: state.sortBy,
|
|
||||||
sort_order: state.sortOrder,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await fetchAuditLogs(params);
|
|
||||||
const transformedData = response.data.map(transformAuditLogData);
|
|
||||||
|
|
||||||
console.log('Audit Logs API Response:', response);
|
|
||||||
console.log('Transformed Audit Data:', transformedData);
|
|
||||||
|
|
||||||
dispatch({
|
|
||||||
type: 'SET_RECORDS',
|
|
||||||
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 audit history:', error);
|
|
||||||
const errorMessage = error instanceof Error ? error.message : '加载审核历史失败';
|
|
||||||
dispatch({ type: 'SET_ERROR', payload: errorMessage });
|
|
||||||
toast.error(errorMessage);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 初始加载
|
|
||||||
useEffect(() => {
|
|
||||||
loadAuditHistory(true);
|
|
||||||
}, [state.sortBy, state.sortOrder]);
|
|
||||||
|
|
||||||
// 分页加载
|
|
||||||
useEffect(() => {
|
|
||||||
if (state.pagination.page > 1) {
|
|
||||||
loadAuditHistory(false);
|
|
||||||
}
|
|
||||||
}, [state.pagination.page]);
|
|
||||||
|
|
||||||
// 计算统计数据 - 按照参考组件的顺序
|
|
||||||
const stats = useMemo(() => [
|
|
||||||
{
|
|
||||||
label: '审核总数',
|
|
||||||
value: state.records.length,
|
|
||||||
color: 'text-blue-600 dark:text-blue-400',
|
|
||||||
bg: 'bg-blue-50 dark:bg-blue-950',
|
|
||||||
borderColor: 'border-blue-200 dark:border-blue-800',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '已通过',
|
|
||||||
value: state.records.filter(r => r.result === 'approved').length,
|
|
||||||
color: 'text-green-600 dark:text-green-400',
|
|
||||||
bg: 'bg-green-50 dark:bg-green-950',
|
|
||||||
borderColor: 'border-green-200 dark:border-green-800',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '已驳回',
|
|
||||||
value: state.records.filter(r => r.result === 'rejected').length,
|
|
||||||
color: 'text-red-600 dark:text-red-400',
|
|
||||||
bg: 'bg-red-50 dark:bg-red-950',
|
|
||||||
borderColor: 'border-red-200 dark:border-red-800',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '待审核',
|
|
||||||
value: state.records.filter(r => r.result === 'pending').length,
|
|
||||||
color: 'text-yellow-600 dark:text-yellow-400',
|
|
||||||
bg: 'bg-yellow-50 dark:bg-yellow-950',
|
|
||||||
borderColor: 'border-yellow-200 dark:border-yellow-800',
|
|
||||||
},
|
|
||||||
], [state.records]);
|
|
||||||
|
|
||||||
// 筛选记录
|
|
||||||
const filteredRecords = useMemo(() => {
|
|
||||||
return state.records.filter(record => {
|
|
||||||
const matchKeyword = !state.filters.searchKeyword ||
|
|
||||||
record.enterpriseName.toLowerCase().includes(state.filters.searchKeyword.toLowerCase()) ||
|
|
||||||
record.changeSummary.toLowerCase().includes(state.filters.searchKeyword.toLowerCase());
|
|
||||||
|
|
||||||
const matchResult = state.filters.resultFilter === 'all' || record.result === state.filters.resultFilter;
|
|
||||||
const matchType = state.filters.typeFilter === 'all' || record.auditType === state.filters.typeFilter;
|
|
||||||
|
|
||||||
// 日期筛选
|
|
||||||
let matchDate = true;
|
|
||||||
if (state.filters.dateRange !== 'all' && record.actionTime) {
|
|
||||||
const auditDate = new Date(record.actionTime);
|
|
||||||
const now = new Date();
|
|
||||||
const diffDays = Math.floor((now.getTime() - auditDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
||||||
|
|
||||||
switch (state.filters.dateRange) {
|
|
||||||
case 'today':
|
|
||||||
matchDate = diffDays === 0;
|
|
||||||
break;
|
|
||||||
case 'week':
|
|
||||||
matchDate = diffDays <= 7;
|
|
||||||
break;
|
|
||||||
case 'month':
|
|
||||||
matchDate = diffDays <= 30;
|
|
||||||
break;
|
|
||||||
case 'quarter':
|
|
||||||
matchDate = diffDays <= 90;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return matchKeyword && matchResult && matchType && matchDate;
|
|
||||||
});
|
|
||||||
}, [state.records, state.filters]);
|
|
||||||
|
|
||||||
// 事件处理器
|
|
||||||
const handleSearch = (value: string) => {
|
|
||||||
dispatch({ type: 'SET_FILTERS', payload: { searchKeyword: value } });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleResultFilter = (value: string) => {
|
|
||||||
dispatch({ type: 'SET_FILTERS', payload: { resultFilter: value } });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTypeFilter = (value: string) => {
|
|
||||||
dispatch({ type: 'SET_FILTERS', payload: { typeFilter: value } });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDateFilter = (value: string) => {
|
|
||||||
dispatch({ type: 'SET_FILTERS', payload: { dateRange: value } });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSort = (sortBy?: string) => {
|
|
||||||
const newSortOrder = state.sortBy === sortBy && state.sortOrder === 'desc' ? 'asc' : 'desc';
|
|
||||||
dispatch({ type: 'SET_SORT', payload: { sortBy, sortOrder: newSortOrder } });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePageChange = (page: number) => {
|
|
||||||
// 边界检查
|
|
||||||
if (page < 1) {
|
|
||||||
page = 1;
|
|
||||||
} else if (page > state.pagination.totalPages && state.pagination.totalPages > 0) {
|
|
||||||
page = state.pagination.totalPages;
|
|
||||||
}
|
|
||||||
dispatch({ type: 'SET_PAGINATION', payload: { page } });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleViewDetail = (record: AuditRecord) => {
|
|
||||||
dispatch({ type: 'SET_SELECTED_RECORD', payload: record });
|
|
||||||
dispatch({ type: 'TOGGLE_DETAIL_DIALOG', payload: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleExport = () => {
|
|
||||||
const exportData = filteredRecords.map(record => ({
|
|
||||||
企业名称: record.enterpriseName,
|
|
||||||
操作类型: record.action === 'SUBMIT' ? '提交' : '审核',
|
|
||||||
审核类型: record.auditType === 'register' ? '注册' : '更新',
|
|
||||||
操作时间: record.actionTime,
|
|
||||||
操作人: record.actionBy,
|
|
||||||
审核结果: record.result === 'approved' ? '审核通过' :
|
|
||||||
record.result === 'rejected' ? '审核拒绝' :
|
|
||||||
record.result === 'pending' ? '待审核' : '草稿',
|
|
||||||
变更摘要: record.changeSummary,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const dataStr = JSON.stringify(exportData, null, 2);
|
|
||||||
const dataBlob = new Blob([dataStr], { type: 'application/json' });
|
|
||||||
const url = URL.createObjectURL(dataBlob);
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = url;
|
|
||||||
link.download = `audit_history_${new Date().getTime()}.json`;
|
|
||||||
link.click();
|
|
||||||
toast.success('审核历史数据导出成功');
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-6">
|
|
||||||
{/* Page Header */}
|
|
||||||
<Card className="p-6 bg-gradient-to-r from-purple-50 dark:from-purple-950 to-pink-50 dark:to-pink-950 border-purple-200 dark:border-purple-800">
|
|
||||||
<div className="flex items-start justify-between">
|
|
||||||
<div className="flex items-start gap-3">
|
|
||||||
<History className="w-6 h-6 text-purple-600 dark:text-purple-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">
|
|
||||||
<Calendar className="w-3 h-3 mr-1" />
|
|
||||||
时间筛选
|
|
||||||
</Badge>
|
|
||||||
<Badge variant="outline" className="bg-white dark:bg-gray-800 font-light">
|
|
||||||
<FileText className="w-3 h-3 mr-1" />
|
|
||||||
详情查看
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Button onClick={handleExport}>
|
|
||||||
<Download className="w-4 h-4 mr-2" />
|
|
||||||
导出记录
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Statistics Cards */}
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
||||||
{stats.map((stat, index) => (
|
|
||||||
<Card key={index} className={`p-6 bg-card hover:bg-muted transition-colors ${stat.borderColor} border-2`}>
|
|
||||||
<div className="flex items-center justify-between mb-2">
|
|
||||||
<div className="text-sm text-muted-foreground">{stat.label}</div>
|
|
||||||
<History className="w-5 h-5 text-purple-500" />
|
|
||||||
</div>
|
|
||||||
<div className={`text-3xl font-bold mb-1 ${stat.color}`}>{stat.value}</div>
|
|
||||||
<div className="text-xs text-muted-foreground">
|
|
||||||
条记录
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Filters */}
|
|
||||||
<Card className="p-6 bg-card">
|
|
||||||
<div className="flex flex-col lg:flex-row gap-4 mb-4">
|
|
||||||
<div className="flex-1">
|
|
||||||
<Label className="text-sm">搜索关键词</Label>
|
|
||||||
<div className="relative mt-2">
|
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
|
||||||
<Input
|
|
||||||
placeholder="搜索企业名称、变更摘要..."
|
|
||||||
value={state.filters.searchKeyword}
|
|
||||||
onChange={(e) => handleSearch(e.target.value)}
|
|
||||||
className="pl-10"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex gap-4">
|
|
||||||
<div>
|
|
||||||
<Label className="text-sm">全部类型</Label>
|
|
||||||
<Select value={state.filters.typeFilter} onValueChange={handleTypeFilter}>
|
|
||||||
<SelectTrigger className="w-32 mt-2">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="all">全部类型</SelectItem>
|
|
||||||
<SelectItem value="register">注册审核</SelectItem>
|
|
||||||
<SelectItem value="update">变更审核</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Label className="text-sm">全部结果</Label>
|
|
||||||
<Select value={state.filters.resultFilter} onValueChange={handleResultFilter}>
|
|
||||||
<SelectTrigger className="w-32 mt-2">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="all">全部结果</SelectItem>
|
|
||||||
<SelectItem value="pending">待审核</SelectItem>
|
|
||||||
<SelectItem value="approved">已通过</SelectItem>
|
|
||||||
<SelectItem value="rejected">已驳回</SelectItem>
|
|
||||||
<SelectItem value="draft">草稿</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Time Range Filter */}
|
|
||||||
<Card className="p-4 bg-card">
|
|
||||||
<Label className="text-sm text-muted-foreground mb-2 block">时间范围</Label>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
{[
|
|
||||||
{ value: 'all', label: '全部' },
|
|
||||||
{ value: 'today', label: '今天' },
|
|
||||||
{ value: 'week', label: '近7天' },
|
|
||||||
{ value: 'month', label: '近30天' },
|
|
||||||
{ value: 'quarter', label: '近90天' },
|
|
||||||
].map((option) => (
|
|
||||||
<Button
|
|
||||||
key={option.value}
|
|
||||||
variant={state.filters.dateRange === option.value ? 'default' : 'outline'}
|
|
||||||
size="sm"
|
|
||||||
onClick={() => handleDateFilter(option.value)}
|
|
||||||
>
|
|
||||||
{option.label}
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* 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>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 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>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 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('enterprise_name')}
|
|
||||||
>
|
|
||||||
企业名称
|
|
||||||
{state.sortBy === 'enterprise_name' && (
|
|
||||||
<span className="ml-1">{state.sortOrder === 'asc' ? '↑' : '↓'}</span>
|
|
||||||
)}
|
|
||||||
</TableHead>
|
|
||||||
<TableHead
|
|
||||||
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>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{filteredRecords.map((record) => (
|
|
||||||
<TableRow key={record.id}>
|
|
||||||
<TableCell>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Building2 className="w-4 h-4 text-purple-500" />
|
|
||||||
<span className="font-medium">{record.enterpriseName}</span>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{getActionBadge(record.action)}
|
|
||||||
{record.auditType === 'register' && (
|
|
||||||
<Badge variant="outline" className="font-light">注册</Badge>
|
|
||||||
)}
|
|
||||||
{record.auditType === 'update' && (
|
|
||||||
<Badge variant="outline" className="font-light">更新</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<User className="w-4 h-4 text-gray-500" />
|
|
||||||
<span className="text-sm">{record.actionBy}</span>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="text-sm">{record.actionTime}</TableCell>
|
|
||||||
<TableCell>{getStatusBadge(record.result)}</TableCell>
|
|
||||||
<TableCell className="text-sm max-w-xs truncate" title={record.changeSummary}>
|
|
||||||
{record.changeSummary}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => handleViewDetail(record)}
|
|
||||||
>
|
|
||||||
<Eye className="w-3 h-3 mr-1" />
|
|
||||||
查看
|
|
||||||
</Button>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{filteredRecords.length === 0 && (
|
|
||||||
<div className="text-center py-12 text-muted-foreground">
|
|
||||||
<History 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>
|
|
||||||
@@ -271,19 +271,6 @@ export default function EnterpriseAuditPage() {
|
|||||||
}
|
}
|
||||||
}, []); // 移除所有依赖,使用参数传递状态变化
|
}, []); // 移除所有依赖,使用参数传递状态变化
|
||||||
|
|
||||||
// 首次加载数据 - 使用事件驱动,避免useEffect
|
|
||||||
const initializeData = useCallback(() => {
|
|
||||||
if (isFirstLoad.current) {
|
|
||||||
isFirstLoad.current = false;
|
|
||||||
loadEnterprises({ resetPage: true });
|
|
||||||
}
|
|
||||||
}, [loadEnterprises]);
|
|
||||||
|
|
||||||
// 页面加载时初始化数据
|
|
||||||
useEffect(() => {
|
|
||||||
initializeData();
|
|
||||||
}, []); // 只在组件挂载时执行一次
|
|
||||||
|
|
||||||
// 计算统计数据
|
// 计算统计数据
|
||||||
const stats = useMemo(() => ({
|
const stats = useMemo(() => ({
|
||||||
total: state.pagination.total,
|
total: state.pagination.total,
|
||||||
|
|||||||
Reference in New Issue
Block a user