wo 1
This commit is contained in:
@@ -4,6 +4,7 @@ import { Button } from "../ui/button"
|
||||
import { Input } from "../ui/input"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"
|
||||
import { Badge } from "../ui/badge"
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../ui/table"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -28,6 +29,7 @@ import {
|
||||
Store,
|
||||
Edit,
|
||||
Trash2,
|
||||
Eye,
|
||||
} from "lucide-react"
|
||||
|
||||
// 省份数据类型
|
||||
@@ -97,6 +99,10 @@ export default function MallsPage() {
|
||||
const [userRole, setUserRole] = useState<string>("")
|
||||
const [mallMerchants, setMallMerchants] = useState<{[key: string]: any[]}>({})
|
||||
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({
|
||||
name: "",
|
||||
address: "",
|
||||
@@ -181,9 +187,24 @@ export default function MallsPage() {
|
||||
try {
|
||||
const response = await apiGet(`/back/merchants/list?mallId=${mallId}`)
|
||||
if (response.code === 200) {
|
||||
const merchants = response.rows || []
|
||||
setMallMerchants(prev => ({ ...prev, [mallId]: merchants }))
|
||||
return merchants
|
||||
const merchantsData = response.rows || []
|
||||
|
||||
// 为每个商户获取设备数量
|
||||
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 []
|
||||
} 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(() => {
|
||||
fetchUserRole()
|
||||
@@ -535,39 +614,76 @@ export default function MallsPage() {
|
||||
<p className="mt-2">加载中...</p>
|
||||
</div>
|
||||
) : (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) => (
|
||||
<div key={merchant.id} className="bg-white p-4 rounded-lg border hover:shadow-sm transition-shadow">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center space-x-2 mb-2">
|
||||
<Store className="h-4 w-4 text-blue-600" />
|
||||
<span className="font-medium">{merchant.merchantName}</span>
|
||||
<TableRow key={merchant.id}>
|
||||
<TableCell className="font-medium">{merchant.id}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center">
|
||||
<Store className="h-4 w-4 mr-2 text-gray-400" />
|
||||
{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"}>
|
||||
{merchant.status === "1" ? "正常" : "停用"}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2 text-sm text-gray-600">
|
||||
<div>
|
||||
<span className="font-medium">联系人:</span>
|
||||
{merchant.contactPerson || '无'}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium">电话:</span>
|
||||
{merchant.contactPhone || '无'}
|
||||
</div>
|
||||
<div className="col-span-2 flex items-start">
|
||||
<MapPin className="h-3 w-3 mr-1 mt-0.5 flex-shrink-0" />
|
||||
<span>{merchant.fullAddress || merchant.detailedAddress || '无'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handleViewEquipment(merchant)}
|
||||
>
|
||||
<Eye className="h-4 w-4 mr-1" />
|
||||
查看设备({merchant.equipmentCount || 0})
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</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" />
|
||||
<p>该商场暂无商户入驻</p>
|
||||
</div>
|
||||
@@ -580,6 +696,99 @@ export default function MallsPage() {
|
||||
</div>
|
||||
</CardContent>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
@@ -16,6 +16,11 @@ export default defineConfig({
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
host: true, // 允许外部访问
|
||||
allowedHosts: [
|
||||
'pc687bfe.natappfree.cc',
|
||||
'.natappfree.cc', // 允许所有 natappfree.cc 子域名
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
|
Reference in New Issue
Block a user