wo 1
This commit is contained in:
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -16,6 +16,11 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
|
host: true, // 允许外部访问
|
||||||
|
allowedHosts: [
|
||||||
|
'pc687bfe.natappfree.cc',
|
||||||
|
'.natappfree.cc', // 允许所有 natappfree.cc 子域名
|
||||||
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user