统计 优化

This commit is contained in:
menxipeng
2025-10-26 20:15:41 +08:00
parent c1c5826f6d
commit c7b20dbfed
4 changed files with 67 additions and 194 deletions

View File

@@ -22,8 +22,6 @@ import {
Plus, Plus,
Search, Search,
MapPin, MapPin,
Users,
Shield,
ChevronDown, ChevronDown,
ChevronRight, ChevronRight,
Store, Store,
@@ -507,54 +505,6 @@ export default function MallsPage() {
)} )}
</div> </div>
{/* 统计卡片 */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium"></CardTitle>
<Building2 className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{malls.length}</div>
<p className="text-xs text-muted-foreground">
{malls.filter((m) => m.status === "1").length}
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium"></CardTitle>
<Store className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">0</div>
<p className="text-xs text-muted-foreground"> {malls.length} </p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium"></CardTitle>
<Shield className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">0</div>
<p className="text-xs text-muted-foreground">
0
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium"></CardTitle>
<Users className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{new Set(malls.map((m) => m.mallUser)).size}</div>
<p className="text-xs text-muted-foreground"></p>
</CardContent>
</Card>
</div>
{/* 搜索和筛选 */} {/* 搜索和筛选 */}
<Card> <Card>
<CardHeader> <CardHeader>

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react" import { useState, useEffect } from "react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card"
import { Button } from "../ui/button" import { Button } from "../ui/button"
import { Input } from "../ui/input" import { Input } from "../ui/input"
@@ -13,9 +13,8 @@ import {
DialogDescription, DialogDescription,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
DialogTrigger,
} from "../ui/dialog" } from "../ui/dialog"
import { Plus, Search, Filter, Download, Calendar, AlertTriangle, Store, Eye, Building2, MapPin, ChevronLeft, ChevronRight } from "lucide-react" import { Plus, Search, Filter, Download, Store, Eye, Building2, MapPin, ChevronLeft, ChevronRight } from "lucide-react"
import { apiGet, apiPost, apiPut } from "../../lib/services/api" import { apiGet, apiPost, apiPut } from "../../lib/services/api"
// 总商户数据类型 // 总商户数据类型
@@ -382,16 +381,6 @@ export default function MerchantsPage() {
} }
} }
const getMerchantStatusBadge = (merchant: any) => {
if (merchant.expiredCount > 0) {
return <Badge variant="destructive"></Badge>
} else if (merchant.expiringCount > 0) {
return <Badge className="bg-yellow-100 text-yellow-800"></Badge>
} else {
return <Badge className="bg-green-100 text-green-800"></Badge>
}
}
// 打开编辑对话框 // 打开编辑对话框
const handleEditMerchant = (merchant: any) => { const handleEditMerchant = (merchant: any) => {
setEditMerchant({ setEditMerchant({
@@ -529,64 +518,6 @@ export default function MerchantsPage() {
)} )}
</div> </div>
<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">{total}</p>
</div>
<div className="h-8 w-8 bg-blue-100 rounded-full flex items-center justify-center">
<Store className="h-4 w-4 text-blue-600" />
</div>
</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">128</p>
</div>
<div className="h-8 w-8 bg-green-100 rounded-full flex items-center justify-center">
<Calendar className="h-4 w-4 text-green-600" />
</div>
</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-yellow-600">23</p>
</div>
<div className="h-8 w-8 bg-yellow-100 rounded-full flex items-center justify-center">
<AlertTriangle className="h-4 w-4 text-yellow-600" />
</div>
</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-red-600">5</p>
</div>
<div className="h-8 w-8 bg-red-100 rounded-full flex items-center justify-center">
<AlertTriangle className="h-4 w-4 text-red-600" />
</div>
</div>
</CardContent>
</Card>
</div>
{/* Filters and Search */} {/* Filters and Search */}
<Card> <Card>
<CardHeader> <CardHeader>

View File

@@ -56,9 +56,25 @@ interface DealerDistributionData {
totalCount: number totalCount: number
} }
// 定义设备状态分布数据接口
interface DeviceDistribution {
name: string
value: number
}
// 定义月度工单趋势数据接口
interface WorkOrderTrend {
month: string
completed: number
pending: number
total: number | string // total 可能是字符串或数字
}
export default function StatisticsPage() { export default function StatisticsPage() {
const [statisticsData, setStatisticsData] = useState<StatisticsData | null>(null) const [statisticsData, setStatisticsData] = useState<StatisticsData | null>(null)
const [dealerDistributionData, setDealerDistributionData] = useState<DealerDistribution[]>([]) const [dealerDistributionData, setDealerDistributionData] = useState<DealerDistribution[]>([])
const [deviceDistributionData, setDeviceDistributionData] = useState<DeviceDistribution[]>([])
const [workOrderTrendData, setWorkOrderTrendData] = useState<WorkOrderTrend[]>([])
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const userData = getUserData() const userData = getUserData()
@@ -68,6 +84,8 @@ export default function StatisticsPage() {
useEffect(() => { useEffect(() => {
fetchStatistics() fetchStatistics()
fetchDealerDistribution() fetchDealerDistribution()
fetchDeviceDistribution()
fetchWorkOrderTrend()
}, []) }, [])
const fetchStatistics = async () => { const fetchStatistics = async () => {
@@ -95,6 +113,28 @@ export default function StatisticsPage() {
} }
} }
const fetchDeviceDistribution = async () => {
try {
const response = await apiGet<{ code: number; msg: string; data: DeviceDistribution[] }>('/back/statistics/deviceDistribution')
if (response.code === 200) {
setDeviceDistributionData(response.data)
}
} catch (error) {
console.error('获取设备状态分布数据失败:', error)
}
}
const fetchWorkOrderTrend = async () => {
try {
const response = await apiGet<{ code: number; msg: string; data: WorkOrderTrend[] }>('/back/statistics/workOrderCompletionTrend')
if (response.code === 200) {
setWorkOrderTrendData(response.data)
}
} catch (error) {
console.error('获取月度工单趋势数据失败:', error)
}
}
const stats = { const stats = {
totalEquipment: statisticsData?.equipmentTotal || 0, totalEquipment: statisticsData?.equipmentTotal || 0,
activeWorkOrders: statisticsData?.completedWorkOrders || 0, activeWorkOrders: statisticsData?.completedWorkOrders || 0,
@@ -153,22 +193,32 @@ export default function StatisticsPage() {
], ],
} }
// 设备状态分布数据 // 根据状态名称获取颜色
const equipmentStatusData = [ const getColorByStatusName = (name: string): string => {
{ name: "正常运行", value: 1156, color: "#10B981" }, const colorMap: { [key: string]: string } = {
{ name: "即将到期", value: 67, color: "#F59E0B" }, '正常': '#10B981',
{ name: "已过期", value: 25, color: "#EF4444" }, '正常运行': '#10B981',
] '即将到期': '#F59E0B',
'已过期': '#EF4444',
'过期': '#EF4444',
}
return colorMap[name] || '#6B7280' // 默认灰色
}
// 月度工单完成情况 // 设备状态分布数据 - 从接口获取并添加颜色
const monthlyWorkOrderData = [ const equipmentStatusData = deviceDistributionData.map(item => ({
{ month: "1月", completed: 145, pending: 23, total: 168 }, name: item.name,
{ month: "2月", completed: 132, pending: 18, total: 150 }, value: item.value,
{ month: "3月", completed: 156, pending: 25, total: 181 }, color: getColorByStatusName(item.name)
{ month: "4月", completed: 142, pending: 19, total: 161 }, }))
{ month: "5月", completed: 167, pending: 22, total: 189 },
{ month: "6月", completed: 158, pending: 27, total: 185 }, // 月度工单完成情况 - 从接口获取
] const monthlyWorkOrderData = workOrderTrendData.map(item => ({
month: item.month,
completed: typeof item.completed === 'string' ? Number(item.completed) : item.completed,
pending: typeof item.pending === 'string' ? Number(item.pending) : item.pending,
total: typeof item.total === 'string' ? Number(item.total) : item.total,
}))
// provinceData 已从接口获取,存储在 dealerDistributionData 中 // provinceData 已从接口获取,存储在 dealerDistributionData 中

View File

@@ -16,20 +16,16 @@ import { Label } from "../ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../ui/table" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../ui/table"
import { import {
Users,
Plus, Plus,
Search, Search,
MapPin, MapPin,
Shield,
ChevronDown, ChevronDown,
ChevronRight, ChevronRight,
User, User,
Edit, Edit,
Trash2, Trash2,
Phone, Phone,
Award,
Wrench, Wrench,
CheckCircle,
} from "lucide-react" } from "lucide-react"
import { apiPost, apiGet, apiPut, apiDelete } from "../../lib/services/api" import { apiPost, apiGet, apiPut, apiDelete } from "../../lib/services/api"
import { getProvinces } from '../../lib/services/region' import { getProvinces } from '../../lib/services/region'
@@ -805,60 +801,6 @@ export default function WorkersPage() {
</Dialog> </Dialog>
</div> </div>
{/* 统计卡片 */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium"></CardTitle>
<Users className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{workers.length}</div>
<p className="text-xs text-muted-foreground">
{workers.filter((w) => w.status === "active").length}
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium"></CardTitle>
<Award className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{workers.filter((w) => w.skillLevel === "高级技师").length}</div>
<p className="text-xs text-muted-foreground">
{workers.length > 0 ? Math.round((workers.filter((w) => w.skillLevel === "高级技师").length / workers.length) * 100) : 0}%
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium"></CardTitle>
<CheckCircle className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{workers.reduce((sum, w) => sum + w.completedOrders, 0)}</div>
<p className="text-xs text-muted-foreground">
{workers.length > 0 ? Math.round(workers.reduce((sum, w) => sum + w.completedOrders, 0) / workers.length) : 0}
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium"></CardTitle>
<Shield className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{workers.filter((w) => w.rating > 0).length > 0
? (workers.reduce((sum, w) => sum + w.rating, 0) / workers.filter((w) => w.rating > 0).length).toFixed(1)
: '-'}
</div>
<p className="text-xs text-muted-foreground"> 5.0 </p>
</CardContent>
</Card>
</div>
{/* 搜索和筛选 */} {/* 搜索和筛选 */}
<Card> <Card>
<CardHeader> <CardHeader>