统计 优化
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user