统计 优化

This commit is contained in:
menxipeng
2025-10-26 22:32:25 +08:00
parent c7b20dbfed
commit 3117e040f5
6 changed files with 151 additions and 117 deletions

View File

@@ -85,6 +85,28 @@ export default function EquipmentPage() {
pageSize: 10,
total: 0
})
const [equipmentStats, setEquipmentStats] = useState({
count: 0,
totalCount: 0,
countEnd: 0,
countExpire: 0
})
const [loadingStats, setLoadingStats] = useState(false)
// 获取设备统计数据
const fetchEquipmentStats = async () => {
setLoadingStats(true)
try {
const response = await apiGet('/back/equipment/user/count')
if (response.code === 200) {
setEquipmentStats(response.data)
}
} catch (error) {
console.error('获取设备统计数据失败:', error)
} finally {
setLoadingStats(false)
}
}
// 获取设备类型列表
const fetchEquipmentTypes = async () => {
@@ -167,6 +189,7 @@ export default function EquipmentPage() {
}
useEffect(() => {
fetchEquipmentStats()
fetchEquipmentTypes()
fetchEquipmentList()
}, [])
@@ -669,6 +692,57 @@ export default function EquipmentPage() {
</div>
</div>
{/* 统计卡片 */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Card>
<CardContent className="pt-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600 mb-1"></p>
<h3 className="text-3xl font-bold text-gray-900">
{loadingStats ? '-' : equipmentStats.count}
</h3>
</div>
<div className="w-12 h-12 bg-green-100 rounded-lg flex items-center justify-center">
<Shield className="h-6 w-6 text-green-600" />
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="pt-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600 mb-1"></p>
<h3 className="text-3xl font-bold text-gray-900">
{loadingStats ? '-' : equipmentStats.countEnd}
</h3>
</div>
<div className="w-12 h-12 bg-yellow-100 rounded-lg flex items-center justify-center">
<Calendar className="h-6 w-6 text-yellow-600" />
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="pt-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600 mb-1"></p>
<h3 className="text-3xl font-bold text-gray-900">
{loadingStats ? '-' : equipmentStats.countExpire}
</h3>
</div>
<div className="w-12 h-12 bg-red-100 rounded-lg flex items-center justify-center">
<Shield className="h-6 w-6 text-red-600" />
</div>
</div>
</CardContent>
</Card>
</div>
<Card>
<CardHeader className="pb-4">
<div className="flex items-center justify-between">

View File

@@ -132,61 +132,6 @@ export default function UsersPage() {
</Dialog>
</div>
{/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card>
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600"></p>
<p className="text-2xl font-bold text-blue-600">{users.length}</p>
</div>
<User className="h-8 w-8 text-blue-600" />
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600"></p>
<p className="text-2xl font-bold text-green-600">{users.filter((u) => u.status === "active").length}</p>
</div>
<User className="h-8 w-8 text-green-600" />
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600"></p>
<p className="text-2xl font-bold text-purple-600">
{users.reduce((sum, user) => sum + user.orderCount, 0)}
</p>
</div>
<User className="h-8 w-8 text-purple-600" />
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600"></p>
<p className="text-2xl font-bold text-orange-600">
¥{users.reduce((sum, user) => sum + user.totalSpent, 0)}
</p>
</div>
<User className="h-8 w-8 text-orange-600" />
</div>
</CardContent>
</Card>
</div>
{/* Filters and Search */}
<Card>
<CardHeader>

View File

@@ -6,7 +6,7 @@ import { Badge } from "../ui/badge";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "../ui/dialog";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../ui/table";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { Search, Eye, Download, FileText, CheckCircle, User, Building, ChevronLeft, ChevronRight, FileDown } from "lucide-react";
import { Search, Eye, Download, FileText, CheckCircle, XCircle, ChevronLeft, ChevronRight, FileDown } from "lucide-react";
import { apiGet, API_BASE_URL } from "../../lib/services/api";
import { getUserToken } from "../../lib/utils/storage";
@@ -50,12 +50,33 @@ export default function WorkOrderArchivePage() {
pageSize: 10,
total: 0
});
const [archiveCount, setArchiveCount] = useState({
count: 0,
hcount: 0,
ncount: 0
});
// 获取归档统计数据
const fetchArchiveCount = async () => {
try {
const response = await apiGet('/back/workers/archiveCount');
if (response.code === 200 && response.data) {
setArchiveCount({
count: response.data.count || 0,
hcount: response.data.hcount || 0,
ncount: response.data.ncount || 0
});
}
} catch (error) {
console.error('获取归档统计数据失败:', error);
}
};
// 获取归档工单列表
const fetchArchivedOrders = async (pageNum = 1, pageSize = 10) => {
setLoading(true);
try {
const response = await apiGet(`/client/work/list?queryStaus=6&pageNum=${pageNum}&pageSize=${pageSize}`);
const response = await apiGet(`/client/work/list?queryStaus=7&pageNum=${pageNum}&pageSize=${pageSize}`);
if (response.code === 200) {
setArchivedOrders(response.data || []);
setPagination({
@@ -72,6 +93,7 @@ export default function WorkOrderArchivePage() {
};
useEffect(() => {
fetchArchiveCount();
fetchArchivedOrders();
}, []);
@@ -165,13 +187,13 @@ export default function WorkOrderArchivePage() {
</div>
{/* Statistics Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<Card>
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600"></p>
<p className="text-3xl font-bold">{pagination.total}</p>
<p className="text-3xl font-bold">{archiveCount.count}</p>
</div>
<FileText className="h-8 w-8 text-blue-500" />
</div>
@@ -182,8 +204,8 @@ export default function WorkOrderArchivePage() {
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600"></p>
<p className="text-3xl font-bold">{archivedOrders.length}</p>
<p className="text-sm font-medium text-gray-600"></p>
<p className="text-3xl font-bold">{archiveCount.hcount}</p>
</div>
<CheckCircle className="h-8 w-8 text-green-500" />
</div>
@@ -194,22 +216,10 @@ export default function WorkOrderArchivePage() {
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600"></p>
<p className="text-3xl font-bold">{pagination.pageNum}/{Math.ceil(pagination.total / pagination.pageSize)}</p>
<p className="text-sm font-medium text-gray-600"></p>
<p className="text-3xl font-bold">{archiveCount.ncount}</p>
</div>
<User className="h-8 w-8 text-purple-500" />
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600"></p>
<p className="text-3xl font-bold">{pagination.pageSize}</p>
</div>
<Building className="h-8 w-8 text-orange-500" />
<XCircle className="h-8 w-8 text-red-500" />
</div>
</CardContent>
</Card>

View File

@@ -21,13 +21,13 @@ import {
Download,
Clock,
CheckCircle,
AlertCircle,
User,
Calendar,
MapPin,
MoreHorizontal,
ChevronLeft,
ChevronRight,
Wrench,
} from "lucide-react"
import { apiGet, apiPost } from "../../lib/services/api"
@@ -106,9 +106,6 @@ interface Worker {
jobNum: string
}
// 优先级数据类型接口 - API返回键值对格式
type PriorityData = Record<string, string>
// 工单状态枚举
enum WorkOrderStatus {
PENDING_ACCEPT = 1,
@@ -165,6 +162,23 @@ export default function WorkOrdersPage() {
const [totalWorkOrders, setTotalWorkOrders] = useState(0)
const [currentPage, setCurrentPage] = useState(1)
const [pageSize, setPageSize] = useState(10)
const [statusCounts, setStatusCounts] = useState<Record<number, number>>({})
// 获取工单状态统计
const fetchStatusCounts = async () => {
try {
const response = await apiGet('/back/orders/statusCount')
if (response.code === 200 && response.data) {
const counts: Record<number, number> = {}
response.data.forEach((item: { num: string; status: number }) => {
counts[item.status] = parseInt(item.num) || 0
})
setStatusCounts(counts)
}
} catch (error) {
console.error('获取工单状态统计失败:', error)
}
}
// 获取工单列表
const fetchWorkOrders = async (page = currentPage, size = pageSize) => {
@@ -196,9 +210,10 @@ export default function WorkOrdersPage() {
fetchWorkOrders(1, size)
}
// 组件加载时获取工单列表
// 组件加载时获取工单列表和状态统计
useEffect(() => {
fetchWorkOrders()
fetchStatusCounts()
}, [])
const getStatusBadge = (status: string | number) => {
@@ -224,29 +239,6 @@ export default function WorkOrdersPage() {
}
}
const getPriorityBadge = (priority: string) => {
switch (priority) {
case "1":
return <Badge variant="destructive"></Badge>
case "2":
return <Badge className="bg-orange-100 text-orange-800"></Badge>
case "3":
return <Badge className="bg-blue-100 text-blue-800"></Badge>
case "4":
return <Badge className="bg-green-100 text-green-800"></Badge>
case "urgent":
return <Badge variant="destructive"></Badge>
case "high":
return <Badge className="bg-orange-100 text-orange-800"></Badge>
case "medium":
return <Badge className="bg-blue-100 text-blue-800"></Badge>
case "low":
return <Badge className="bg-green-100 text-green-800"></Badge>
default:
return <Badge variant="outline"></Badge>
}
}
const getTypeBadge = (type: string) => {
switch (type) {
case "设备检测":
@@ -306,6 +298,7 @@ export default function WorkOrdersPage() {
<CreateWorkOrderForm onClose={() => {
setIsCreateDialogOpen(false)
fetchWorkOrders(currentPage, pageSize)
fetchStatusCounts()
}} />
</DialogContent>
</Dialog>
@@ -317,8 +310,10 @@ export default function WorkOrdersPage() {
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600"></p>
<p className="text-2xl font-bold text-gray-600">2</p>
<p className="text-sm text-gray-600"></p>
<p className="text-2xl font-bold text-gray-600">
{statusCounts[1] || 0}
</p>
</div>
<div className="h-8 w-8 bg-gray-100 rounded-full flex items-center justify-center">
<Clock className="h-4 w-4 text-gray-600" />
@@ -332,7 +327,9 @@ export default function WorkOrdersPage() {
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600"></p>
<p className="text-2xl font-bold text-blue-600">1</p>
<p className="text-2xl font-bold text-blue-600">
{statusCounts[3] || 0}
</p>
</div>
<div className="h-8 w-8 bg-blue-100 rounded-full flex items-center justify-center">
<User className="h-4 w-4 text-blue-600" />
@@ -345,11 +342,13 @@ export default function WorkOrdersPage() {
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600"></p>
<p className="text-2xl font-bold text-green-600">1</p>
<p className="text-sm text-gray-600"></p>
<p className="text-2xl font-bold text-orange-600">
{statusCounts[4] || 0}
</p>
</div>
<div className="h-8 w-8 bg-green-100 rounded-full flex items-center justify-center">
<CheckCircle className="h-4 w-4 text-green-600" />
<div className="h-8 w-8 bg-orange-100 rounded-full flex items-center justify-center">
<Wrench className="h-4 w-4 text-orange-600" />
</div>
</div>
</CardContent>
@@ -359,11 +358,13 @@ export default function WorkOrdersPage() {
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600"></p>
<p className="text-2xl font-bold text-red-600">0</p>
<p className="text-sm text-gray-600"></p>
<p className="text-2xl font-bold text-green-600">
{statusCounts[7] || 0}
</p>
</div>
<div className="h-8 w-8 bg-red-100 rounded-full flex items-center justify-center">
<AlertCircle className="h-4 w-4 text-red-600" />
<div className="h-8 w-8 bg-green-100 rounded-full flex items-center justify-center">
<CheckCircle className="h-4 w-4 text-green-600" />
</div>
</div>
</CardContent>

View File

@@ -1,7 +1,9 @@
import { getUserToken, removeStorageItem, runOnClient } from '@/lib/utils/storage'
// API 客户端工具,统一处理请求和认证
const API_BASE_URL = 'http://localhost:8080/api'
const API_BASE_URL = 'http://116.204.124.80:8080/api'
//const API_BASE_URL = 'http://localhost:8080/api'
// 获取存储的 token
function getToken(): string | null {

View File

@@ -1,7 +1,9 @@
import { getUserToken, removeStorageItem, runOnClient } from '@/utils/storage'
// API 客户端工具,统一处理请求和认证
const API_BASE_URL = 'http://localhost:8080/api'
const API_BASE_URL = 'http://116.204.124.80:8080/api'
//const API_BASE_URL = 'http://localhost:8080/api'
// 获取存储的 token
function getToken(): string | null {