数据统计

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() { export default function EquipmentPage() {
const [searchTerm, setSearchTerm] = useState("") const [equipmentIdSearch, setEquipmentIdSearch] = useState("")
const [equipmentNameSearch, setEquipmentNameSearch] = useState("")
const [statusFilter, setStatusFilter] = useState("all") const [statusFilter, setStatusFilter] = useState("all")
const [isAddEquipmentOpen, setIsAddEquipmentOpen] = useState(false) const [isAddEquipmentOpen, setIsAddEquipmentOpen] = useState(false)
const [isEditEquipmentOpen, setIsEditEquipmentOpen] = 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) setLoadingEquipmentList(true)
try { 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) { if (response.code === 200) {
setEquipmentList(response.rows || []) setEquipmentList(response.rows || [])
setPagination({ setPagination({
@@ -195,6 +212,25 @@ export default function EquipmentPage() {
fetchEquipmentList() 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({ const [newEquipment, setNewEquipment] = useState({
name: "", name: "",
type: "", type: "",
@@ -345,7 +381,13 @@ export default function EquipmentPage() {
console.log('编辑设备成功:', result) console.log('编辑设备成功:', result)
alert('编辑设备成功!') alert('编辑设备成功!')
setIsEditEquipmentOpen(false) setIsEditEquipmentOpen(false)
fetchEquipmentList(pagination.pageNum, pagination.pageSize) fetchEquipmentList(
pagination.pageNum,
pagination.pageSize,
equipmentIdSearch,
equipmentNameSearch,
statusFilter
)
} else { } else {
console.error('编辑设备失败:', result) console.error('编辑设备失败:', result)
alert('编辑设备失败:' + (result.msg || '未知错误')) alert('编辑设备失败:' + (result.msg || '未知错误'))
@@ -415,7 +457,13 @@ export default function EquipmentPage() {
setIsAddEquipmentOpen(false) setIsAddEquipmentOpen(false)
// 刷新设备列表 // 刷新设备列表
fetchEquipmentList(pagination.pageNum, pagination.pageSize) fetchEquipmentList(
pagination.pageNum,
pagination.pageSize,
equipmentIdSearch,
equipmentNameSearch,
statusFilter
)
} else { } else {
console.error('添加设备失败:', result) console.error('添加设备失败:', result)
alert('添加设备失败:' + (result.msg || '未知错误')) alert('添加设备失败:' + (result.msg || '未知错误'))
@@ -444,30 +492,8 @@ export default function EquipmentPage() {
} }
} }
const filteredEquipment = equipmentList.filter((item) => { // 直接使用从API返回的数据不再进行前端过滤
// 安全地处理可能为 null 或 undefined 的字段 const filteredEquipment = equipmentList
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
})
return ( return (
<div className="space-y-4 sm:space-y-6 p-4 sm:p-6"> <div className="space-y-4 sm:space-y-6 p-4 sm:p-6">
@@ -931,18 +957,28 @@ export default function EquipmentPage() {
<CardContent> <CardContent>
{/* 筛选区域 */} {/* 筛选区域 */}
<div className="flex flex-col sm:flex-row gap-4 mb-6"> <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"> <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 <Input
placeholder="搜索设备名称、商户或商场..." placeholder="搜索设备编号..."
value={searchTerm} value={equipmentIdSearch}
onChange={(e) => setSearchTerm(e.target.value)} 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" className="pl-10"
/> />
</div> </div>
</div> </div>
<Select value={statusFilter} onValueChange={setStatusFilter}> <Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="w-full sm:w-40"> <SelectTrigger className="w-full sm:w-40">
<SelectValue placeholder="筛选状态" /> <SelectValue placeholder="筛选状态" />
@@ -954,8 +990,7 @@ export default function EquipmentPage() {
<SelectItem value="3"></SelectItem> <SelectItem value="3"></SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
<Button variant="outline" className="w-full sm:w-auto">
<Button variant="outline">
<Download className="h-4 w-4 mr-2" /> <Download className="h-4 w-4 mr-2" />
</Button> </Button>
@@ -1144,7 +1179,13 @@ export default function EquipmentPage() {
size="sm" size="sm"
onClick={() => { onClick={() => {
if (pagination.pageNum > 1) { if (pagination.pageNum > 1) {
fetchEquipmentList(pagination.pageNum - 1, pagination.pageSize) fetchEquipmentList(
pagination.pageNum - 1,
pagination.pageSize,
equipmentIdSearch,
equipmentNameSearch,
statusFilter
)
} }
}} }}
disabled={pagination.pageNum <= 1 || loadingEquipmentList} disabled={pagination.pageNum <= 1 || loadingEquipmentList}
@@ -1161,7 +1202,13 @@ export default function EquipmentPage() {
size="sm" size="sm"
onClick={() => { onClick={() => {
if (pagination.pageNum < Math.ceil(pagination.total / pagination.pageSize)) { 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} 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 [deviceDistributionData, setDeviceDistributionData] = useState<DeviceDistribution[]>([])
const [workOrderTrendData, setWorkOrderTrendData] = useState<WorkOrderTrend[]>([]) const [workOrderTrendData, setWorkOrderTrendData] = useState<WorkOrderTrend[]>([])
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [timeScope, setTimeScope] = useState<string>('month')
const userData = getUserData() const userData = getUserData()
// 判断是否是总公司账号(根据实际业务逻辑调整) // 判断是否是总公司账号(根据实际业务逻辑调整)
const isHeadquarters = userData?.role === 'admin' || userData?.roleType === 'headquarters' const isHeadquarters = userData?.role === 'admin' || userData?.roleType === 'headquarters'
useEffect(() => { useEffect(() => {
fetchStatistics() fetchStatistics(timeScope)
fetchDealerDistribution() fetchDealerDistribution()
fetchDeviceDistribution() fetchDeviceDistribution()
fetchWorkOrderTrend() fetchWorkOrderTrend()
}, []) }, [timeScope])
const fetchStatistics = async () => { const fetchStatistics = async (scope: string = 'month') => {
try { try {
setLoading(true) 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) { if (response.code === 200) {
setStatisticsData(response.data) setStatisticsData(response.data)
} }
@@ -246,7 +247,7 @@ export default function StatisticsPage() {
<p className="text-gray-600"></p> <p className="text-gray-600"></p>
</div> </div>
<div className="flex space-x-2"> <div className="flex space-x-2">
<Select defaultValue="month"> <Select value={timeScope} onValueChange={setTimeScope}>
<SelectTrigger className="w-32"> <SelectTrigger className="w-32">
<Filter className="h-4 w-4 mr-2" /> <Filter className="h-4 w-4 mr-2" />
<SelectValue /> <SelectValue />

View File

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