数据统计

This commit is contained in:
menxipeng
2025-11-18 22:19:49 +08:00
parent f01f721a29
commit bf26caee84
3 changed files with 93 additions and 61 deletions

View File

@@ -70,7 +70,8 @@ interface Equipment {
}
export default function EquipmentPage() {
const [searchTerm, setSearchTerm] = useState("")
const [equipmentIdSearch, setEquipmentIdSearch] = useState("")
const [equipmentNameSearch, setEquipmentNameSearch] = useState("")
const [statusFilter, setStatusFilter] = useState("all")
const [isAddEquipmentOpen, setIsAddEquipmentOpen] = useState(false)
const [isEditEquipmentOpen, setIsEditEquipmentOpen] = useState(false)
@@ -125,10 +126,26 @@ export default function EquipmentPage() {
}
// 获取设备列表
const fetchEquipmentList = async (pageNum = 1, pageSize = 10) => {
const fetchEquipmentList = async (pageNum = 1, pageSize = 10, equipmentId = "", equipmentName = "", status = "") => {
setLoadingEquipmentList(true)
try {
const response = await apiGet(`/back/equipment/list?pageNum=${pageNum}&pageSize=${pageSize}`)
// 构建查询参数
const params = new URLSearchParams({
pageNum: pageNum.toString(),
pageSize: pageSize.toString(),
})
if (equipmentId.trim()) {
params.append('equipmentId', equipmentId.trim())
}
if (equipmentName.trim()) {
params.append('equipmentName', equipmentName.trim())
}
if (status && status !== "all") {
params.append('status', status)
}
const response = await apiGet(`/back/equipment/list?${params.toString()}`)
if (response.code === 200) {
setEquipmentList(response.rows || [])
setPagination({
@@ -195,6 +212,25 @@ export default function EquipmentPage() {
fetchEquipmentList()
}, [])
// 搜索条件变化时调用API使用防抖
useEffect(() => {
const timer = setTimeout(() => {
// 重置到第一页
const newPageNum = 1
fetchEquipmentList(
newPageNum,
pagination.pageSize,
equipmentIdSearch,
equipmentNameSearch,
statusFilter
)
setPagination(prev => ({ ...prev, pageNum: newPageNum }))
}, 500) // 500ms 防抖
return () => clearTimeout(timer)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [equipmentIdSearch, equipmentNameSearch, statusFilter])
const [newEquipment, setNewEquipment] = useState({
name: "",
type: "",
@@ -345,7 +381,13 @@ export default function EquipmentPage() {
console.log('编辑设备成功:', result)
alert('编辑设备成功!')
setIsEditEquipmentOpen(false)
fetchEquipmentList(pagination.pageNum, pagination.pageSize)
fetchEquipmentList(
pagination.pageNum,
pagination.pageSize,
equipmentIdSearch,
equipmentNameSearch,
statusFilter
)
} else {
console.error('编辑设备失败:', result)
alert('编辑设备失败:' + (result.msg || '未知错误'))
@@ -415,7 +457,13 @@ export default function EquipmentPage() {
setIsAddEquipmentOpen(false)
// 刷新设备列表
fetchEquipmentList(pagination.pageNum, pagination.pageSize)
fetchEquipmentList(
pagination.pageNum,
pagination.pageSize,
equipmentIdSearch,
equipmentNameSearch,
statusFilter
)
} else {
console.error('添加设备失败:', result)
alert('添加设备失败:' + (result.msg || '未知错误'))
@@ -444,30 +492,8 @@ export default function EquipmentPage() {
}
}
const filteredEquipment = equipmentList.filter((item) => {
// 安全地处理可能为 null 或 undefined 的字段
const equipmentName = item.equipmentName || ""
const merchantName = item.merchantName || ""
const mallName = item.mallName || ""
const searchLower = searchTerm.toLowerCase()
const matchesSearch =
equipmentName.toLowerCase().includes(searchLower) ||
merchantName.toLowerCase().includes(searchLower) ||
mallName.toLowerCase().includes(searchLower)
// 修正状态筛选逻辑
let matchesStatus = true
if (statusFilter === "1" || statusFilter === "normal") {
matchesStatus = item.status === "1"
} else if (statusFilter === "2" || statusFilter === "expiring") {
matchesStatus = item.status === "2"
} else if (statusFilter === "3" || statusFilter === "expired") {
matchesStatus = item.status === "3"
}
return matchesSearch && matchesStatus
})
// 直接使用从API返回的数据不再进行前端过滤
const filteredEquipment = equipmentList
return (
<div className="space-y-4 sm:space-y-6 p-4 sm:p-6">
@@ -931,18 +957,28 @@ export default function EquipmentPage() {
<CardContent>
{/* 筛选区域 */}
<div className="flex flex-col sm:flex-row gap-4 mb-6">
<div className="flex-1">
<div className="flex-1 min-w-0">
<div className="relative">
<MapPin className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
<Shield className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
<Input
placeholder="搜索设备名称、商户或商场..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="搜索设备编号..."
value={equipmentIdSearch}
onChange={(e) => setEquipmentIdSearch(e.target.value)}
className="pl-10"
/>
</div>
</div>
<div className="flex-1 min-w-0">
<div className="relative">
<MapPin className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
<Input
placeholder="搜索设备名称..."
value={equipmentNameSearch}
onChange={(e) => setEquipmentNameSearch(e.target.value)}
className="pl-10"
/>
</div>
</div>
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="w-full sm:w-40">
<SelectValue placeholder="筛选状态" />
@@ -954,8 +990,7 @@ export default function EquipmentPage() {
<SelectItem value="3"></SelectItem>
</SelectContent>
</Select>
<Button variant="outline">
<Button variant="outline" className="w-full sm:w-auto">
<Download className="h-4 w-4 mr-2" />
</Button>
@@ -1144,7 +1179,13 @@ export default function EquipmentPage() {
size="sm"
onClick={() => {
if (pagination.pageNum > 1) {
fetchEquipmentList(pagination.pageNum - 1, pagination.pageSize)
fetchEquipmentList(
pagination.pageNum - 1,
pagination.pageSize,
equipmentIdSearch,
equipmentNameSearch,
statusFilter
)
}
}}
disabled={pagination.pageNum <= 1 || loadingEquipmentList}
@@ -1161,7 +1202,13 @@ export default function EquipmentPage() {
size="sm"
onClick={() => {
if (pagination.pageNum < Math.ceil(pagination.total / pagination.pageSize)) {
fetchEquipmentList(pagination.pageNum + 1, pagination.pageSize)
fetchEquipmentList(
pagination.pageNum + 1,
pagination.pageSize,
equipmentIdSearch,
equipmentNameSearch,
statusFilter
)
}
}}
disabled={pagination.pageNum >= Math.ceil(pagination.total / pagination.pageSize) || loadingEquipmentList}

View File

@@ -76,22 +76,23 @@ export default function StatisticsPage() {
const [deviceDistributionData, setDeviceDistributionData] = useState<DeviceDistribution[]>([])
const [workOrderTrendData, setWorkOrderTrendData] = useState<WorkOrderTrend[]>([])
const [loading, setLoading] = useState(true)
const [timeScope, setTimeScope] = useState<string>('month')
const userData = getUserData()
// 判断是否是总公司账号(根据实际业务逻辑调整)
const isHeadquarters = userData?.role === 'admin' || userData?.roleType === 'headquarters'
useEffect(() => {
fetchStatistics()
fetchStatistics(timeScope)
fetchDealerDistribution()
fetchDeviceDistribution()
fetchWorkOrderTrend()
}, [])
}, [timeScope])
const fetchStatistics = async () => {
const fetchStatistics = async (scope: string = 'month') => {
try {
setLoading(true)
const response = await apiGet<{ code: number; msg: string; data: StatisticsData }>('/back/statistics/planTop')
const response = await apiGet<{ code: number; msg: string; data: StatisticsData }>(`/back/statistics/planTop?scope=${scope}`)
if (response.code === 200) {
setStatisticsData(response.data)
}
@@ -246,7 +247,7 @@ export default function StatisticsPage() {
<p className="text-gray-600"></p>
</div>
<div className="flex space-x-2">
<Select defaultValue="month">
<Select value={timeScope} onValueChange={setTimeScope}>
<SelectTrigger className="w-32">
<Filter className="h-4 w-4 mr-2" />
<SelectValue />

View File

@@ -39,7 +39,6 @@ export default function WorkersPage() {
const [provinces, setProvinces] = useState<Province[]>([])
const [dealers, setDealers] = useState<Dealer[]>([])
const [searchTerm, setSearchTerm] = useState("")
const [provinceFilter, setProvinceFilter] = useState("all")
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false)
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)
@@ -262,9 +261,7 @@ export default function WorkersPage() {
worker.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
worker.workerId.toLowerCase().includes(searchTerm.toLowerCase()) ||
worker.dealerName.toLowerCase().includes(searchTerm.toLowerCase())
const matchesProvince = provinceFilter === "all" || worker.province === provinceFilter ||
provinces.find(p => p.code === provinceFilter)?.name === worker.province
return matchesSearch && matchesProvince
return matchesSearch
})
const handleAddWorker = async () => {
@@ -820,19 +817,6 @@ export default function WorkersPage() {
className="pl-8 w-full sm:w-80"
/>
</div>
<Select value={provinceFilter} onValueChange={setProvinceFilter}>
<SelectTrigger className="w-full sm:w-32">
<SelectValue placeholder="省份" />
</SelectTrigger>
<SelectContent className="max-h-[300px]">
<SelectItem value="all"></SelectItem>
{provinces.map((province) => (
<SelectItem key={province.code} value={province.code}>
{province.name}
</SelectItem>
))}
</SelectContent>
</Select>
<Select value={selectedWorkOrder} onValueChange={setSelectedWorkOrder}>
<SelectTrigger className="w-full sm:w-64">
<SelectValue placeholder="筛选工人" />