This commit is contained in:
menxipeng
2025-10-20 20:50:12 +08:00
parent ff2b80d639
commit 8551f0a572
2 changed files with 245 additions and 31 deletions

View File

@@ -4,6 +4,7 @@ import { Button } from "../ui/button"
import { Input } from "../ui/input" import { Input } from "../ui/input"
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card" import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"
import { Badge } from "../ui/badge" import { Badge } from "../ui/badge"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../ui/table"
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@@ -28,6 +29,7 @@ import {
Store, Store,
Edit, Edit,
Trash2, Trash2,
Eye,
} from "lucide-react" } from "lucide-react"
// 省份数据类型 // 省份数据类型
@@ -97,6 +99,10 @@ export default function MallsPage() {
const [userRole, setUserRole] = useState<string>("") const [userRole, setUserRole] = useState<string>("")
const [mallMerchants, setMallMerchants] = useState<{[key: string]: any[]}>({}) const [mallMerchants, setMallMerchants] = useState<{[key: string]: any[]}>({})
const [loadingMallMerchants, setLoadingMallMerchants] = useState<{[key: string]: boolean}>({}) const [loadingMallMerchants, setLoadingMallMerchants] = useState<{[key: string]: boolean}>({})
const [selectedMerchant, setSelectedMerchant] = useState<any>(null)
const [isEquipmentDialogOpen, setIsEquipmentDialogOpen] = useState(false)
const [merchantEquipments, setMerchantEquipments] = useState<any[]>([])
const [loadingEquipments, setLoadingEquipments] = useState(false)
const [newMall, setNewMall] = useState({ const [newMall, setNewMall] = useState({
name: "", name: "",
address: "", address: "",
@@ -181,9 +187,24 @@ export default function MallsPage() {
try { try {
const response = await apiGet(`/back/merchants/list?mallId=${mallId}`) const response = await apiGet(`/back/merchants/list?mallId=${mallId}`)
if (response.code === 200) { if (response.code === 200) {
const merchants = response.rows || [] const merchantsData = response.rows || []
setMallMerchants(prev => ({ ...prev, [mallId]: merchants }))
return merchants // 为每个商户获取设备数量
const merchantsWithCounts = await Promise.all(
merchantsData.map(async (merchant: any) => {
const equipmentCount = await fetchMerchantEquipmentCount(merchant.merchantsId || merchant.id)
return {
...merchant,
equipmentCount: equipmentCount?.totalCount || 0,
normalCount: equipmentCount?.count || 0,
expiringCount: equipmentCount?.countEnd || 0,
expiredCount: equipmentCount?.countExpire || 0
}
})
)
setMallMerchants(prev => ({ ...prev, [mallId]: merchantsWithCounts }))
return merchantsWithCounts
} }
return [] return []
} catch (error) { } catch (error) {
@@ -194,6 +215,64 @@ export default function MallsPage() {
} }
} }
// 获取单个商户的设备数量
const fetchMerchantEquipmentCount = async (merchantId: string) => {
try {
const response = await apiGet(`/back/equipment/merchant/${merchantId}/count`)
if (response.code === 200) {
return response.data
}
return null
} catch (error) {
console.error(`获取商户${merchantId}设备数量失败:`, error)
return null
}
}
// 获取商户设备列表
const fetchMerchantEquipments = async (merchantId: string) => {
setLoadingEquipments(true)
try {
const response = await apiGet(`/back/equipment/list?pageNum=1&pageSize=100&merchantId=${merchantId}`)
if (response.code === 200) {
setMerchantEquipments(response.rows || [])
return response.rows || []
}
return []
} catch (error) {
console.error(`获取商户${merchantId}设备列表失败:`, error)
setMerchantEquipments([])
return []
} finally {
setLoadingEquipments(false)
}
}
// 打开设备查看对话框
const handleViewEquipment = (merchant: any) => {
setSelectedMerchant(merchant)
setIsEquipmentDialogOpen(true)
const merchantId = merchant.merchantsId || merchant.id
fetchMerchantEquipments(merchantId)
}
// 获取设备状态徽章
const getEquipmentStatusBadge = (status: string) => {
switch (status) {
case "1":
case "normal":
return <Badge className="bg-green-100 text-green-800"></Badge>
case "2":
case "expiring":
return <Badge className="bg-yellow-100 text-yellow-800"></Badge>
case "3":
case "expired":
return <Badge variant="destructive"></Badge>
default:
return <Badge variant="outline"></Badge>
}
}
// 页面加载时获取数据 // 页面加载时获取数据
useEffect(() => { useEffect(() => {
fetchUserRole() fetchUserRole()
@@ -535,39 +614,76 @@ export default function MallsPage() {
<p className="mt-2">...</p> <p className="mt-2">...</p>
</div> </div>
) : (mallMerchants[mall.mallId || ''] || []).length > 0 ? ( ) : (mallMerchants[mall.mallId || ''] || []).length > 0 ? (
<div className="space-y-3"> <div className="rounded-md border bg-white">
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{(mallMerchants[mall.mallId || ''] || []).map((merchant: any) => ( {(mallMerchants[mall.mallId || ''] || []).map((merchant: any) => (
<div key={merchant.id} className="bg-white p-4 rounded-lg border hover:shadow-sm transition-shadow"> <TableRow key={merchant.id}>
<div className="flex items-start justify-between"> <TableCell className="font-medium">{merchant.id}</TableCell>
<div className="flex-1"> <TableCell>
<div className="flex items-center space-x-2 mb-2"> <div className="flex items-center">
<Store className="h-4 w-4 text-blue-600" /> <Store className="h-4 w-4 mr-2 text-gray-400" />
<span className="font-medium">{merchant.merchantName}</span> {merchant.merchantName}
</div>
</TableCell>
<TableCell>
<div>
<p className="font-medium">{merchant.contactPerson || '无'}</p>
<p className="text-sm text-gray-500">{merchant.contactPhone || '无'}</p>
</div>
</TableCell>
<TableCell>
<div className="flex items-start">
<MapPin className="h-4 w-4 mr-1 text-gray-400 mt-0.5 flex-shrink-0" />
<div>
<p className="text-sm">{merchant.detailedAddress || '无'}</p>
<p className="text-xs text-gray-500">{merchant.fullAddress || ''}</p>
</div>
</div>
</TableCell>
<TableCell>
<div className="text-center">
<p className="font-medium">{merchant.equipmentCount || 0}</p>
<div className="flex justify-center space-x-1 text-xs">
<span className="text-green-600">{merchant.normalCount || 0}</span>
<span className="text-yellow-600">{merchant.expiringCount || 0}</span>
<span className="text-red-600">{merchant.expiredCount || 0}</span>
</div>
</div>
</TableCell>
<TableCell>
<Badge className={merchant.status === "1" ? "bg-green-100 text-green-800" : "bg-red-100 text-red-800"}> <Badge className={merchant.status === "1" ? "bg-green-100 text-green-800" : "bg-red-100 text-red-800"}>
{merchant.status === "1" ? "正常" : "停用"} {merchant.status === "1" ? "正常" : "停用"}
</Badge> </Badge>
</div> </TableCell>
<div className="grid grid-cols-2 gap-2 text-sm text-gray-600"> <TableCell>
<div> <Button
<span className="font-medium"></span> variant="outline"
{merchant.contactPerson || '无'} size="sm"
</div> onClick={() => handleViewEquipment(merchant)}
<div> >
<span className="font-medium"></span> <Eye className="h-4 w-4 mr-1" />
{merchant.contactPhone || '无'} ({merchant.equipmentCount || 0})
</div> </Button>
<div className="col-span-2 flex items-start"> </TableCell>
<MapPin className="h-3 w-3 mr-1 mt-0.5 flex-shrink-0" /> </TableRow>
<span>{merchant.fullAddress || merchant.detailedAddress || '无'}</span>
</div>
</div>
</div>
</div>
</div>
))} ))}
</TableBody>
</Table>
</div> </div>
) : ( ) : (
<div className="text-center py-8 text-gray-500"> <div className="text-center py-8 text-gray-500 bg-white rounded-md border">
<Store className="h-8 w-8 mx-auto mb-2 opacity-50" /> <Store className="h-8 w-8 mx-auto mb-2 opacity-50" />
<p></p> <p></p>
</div> </div>
@@ -580,6 +696,99 @@ export default function MallsPage() {
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
{/* 设备详情对话框 */}
<Dialog open={isEquipmentDialogOpen} onOpenChange={setIsEquipmentDialogOpen}>
<DialogContent className="!w-[1400px] !max-w-[1400px] max-h-[800px] overflow-hidden flex flex-col">
<DialogHeader className="flex-shrink-0">
<DialogTitle>{selectedMerchant?.merchantName} - </DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
<div className="flex-1 overflow-y-auto space-y-4">
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<Card>
<CardContent className="p-4">
<div className="text-center">
<p className="text-2xl font-bold text-green-600">{selectedMerchant?.normalCount || 0}</p>
<p className="text-sm text-gray-600"></p>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="text-center">
<p className="text-2xl font-bold text-yellow-600">{selectedMerchant?.expiringCount || 0}</p>
<p className="text-sm text-gray-600"></p>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="text-center">
<p className="text-2xl font-bold text-red-600">{selectedMerchant?.expiredCount || 0}</p>
<p className="text-sm text-gray-600"></p>
</div>
</CardContent>
</Card>
</div>
<div className="rounded-md border overflow-hidden">
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead className="min-w-[120px]"></TableHead>
<TableHead className="min-w-[150px]"></TableHead>
<TableHead className="min-w-[120px]"></TableHead>
<TableHead className="min-w-[120px]"></TableHead>
<TableHead className="min-w-[150px]"></TableHead>
<TableHead className="min-w-[100px]"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{loadingEquipments ? (
<TableRow>
<TableCell colSpan={6} className="text-center py-4">
...
</TableCell>
</TableRow>
) : merchantEquipments.length > 0 ? (
merchantEquipments.map((equipment: any) => (
<TableRow key={equipment.id}>
<TableCell className="font-medium truncate max-w-[120px]" title={equipment.equipmentId}>
{equipment.equipmentId}
</TableCell>
<TableCell className="truncate max-w-[150px]" title={equipment.equipmentName}>
{equipment.equipmentName}
</TableCell>
<TableCell className="truncate max-w-[120px]" title={equipment.equipmentType}>
{equipment.equipmentType}
</TableCell>
<TableCell className="truncate max-w-[120px]" title={equipment.installationDate}>
{equipment.installationDate}
</TableCell>
<TableCell className="truncate max-w-[150px]" title={equipment.installationLocation}>
{equipment.installationLocation}
</TableCell>
<TableCell>
{getEquipmentStatusBadge(equipment.status)}
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={6} className="text-center py-4">
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
</div>
</div>
</DialogContent>
</Dialog>
</div> </div>
) )
} }

View File

@@ -16,6 +16,11 @@ export default defineConfig({
}, },
server: { server: {
port: 3000, port: 3000,
host: true, // 允许外部访问
allowedHosts: [
'pc687bfe.natappfree.cc',
'.natappfree.cc', // 允许所有 natappfree.cc 子域名
],
}, },
}) })