数据统计
This commit is contained in:
@@ -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}
|
||||||
|
|||||||
@@ -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 />
|
||||||
|
|||||||
@@ -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="筛选工人" />
|
||||||
|
|||||||
Reference in New Issue
Block a user