This commit is contained in:
menxipeng
2025-10-19 13:07:43 +08:00
parent 83f608e71d
commit ac28965614
3 changed files with 275 additions and 246 deletions

View File

@@ -527,7 +527,7 @@ function CreateUserForm({ roles, onClose }: { roles: Role[]; onClose: () => void
}))
}
const shouldShowProvinces = formData.role && formData.role !== "admin"
const shouldShowProvinces = formData.role && formData.role !== "admin" && formData.role !== "merchant"
return (
<form onSubmit={handleSubmit} className="space-y-4">

View File

@@ -94,6 +94,7 @@ export default function MallsPage() {
const [provinces, setProvinces] = useState<Province[]>([])
const [mallUsers, setMallUsers] = useState<MallUser[]>([])
const [loading, setLoading] = useState(false)
const [userRole, setUserRole] = useState<string>("")
const [newMall, setNewMall] = useState({
name: "",
address: "",
@@ -102,6 +103,18 @@ export default function MallsPage() {
description: "",
})
// 获取用户角色
const fetchUserRole = async () => {
try {
const response = await apiGet('/back/getUserRole')
if (response.code === 200) {
setUserRole(response.data || "")
}
} catch (error) {
console.error('获取用户角色失败:', error)
}
}
// 获取商场列表
const fetchMalls = async () => {
try {
@@ -160,6 +173,7 @@ export default function MallsPage() {
// 页面加载时获取数据
useEffect(() => {
fetchUserRole()
fetchProvinces()
fetchMalls()
}, [])
@@ -250,6 +264,7 @@ export default function MallsPage() {
<h1 className="text-2xl font-bold text-gray-900"></h1>
<p className="text-gray-600"></p>
</div>
{userRole !== "mall" && (
<Dialog open={isAddDialogOpen} onOpenChange={setIsAddDialogOpen}>
<DialogTrigger asChild>
<Button>
@@ -352,6 +367,7 @@ export default function MallsPage() {
</DialogFooter>
</DialogContent>
</Dialog>
)}
</div>
{/* 统计卡片 */}
@@ -451,6 +467,7 @@ export default function MallsPage() {
</div>
</div>
{getStatusBadge(mall.status)}
{userRole !== "mall" && (
<div className="flex space-x-1">
<div
className="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 hover:bg-accent hover:text-accent-foreground h-9 px-3 cursor-pointer"
@@ -471,6 +488,7 @@ export default function MallsPage() {
<Trash2 className="h-4 w-4" />
</div>
</div>
)}
</div>
</div>
{expandedMalls.includes(mall.id) && (

View File

@@ -63,6 +63,7 @@ export default function MerchantsPage() {
const [isAddMerchantOpen, setIsAddMerchantOpen] = useState(false)
const [isAddEquipmentOpen, setIsAddEquipmentOpen] = useState(false)
const [selectedMerchant, setSelectedMerchant] = useState<any>(null)
const [isEquipmentDialogOpen, setIsEquipmentDialogOpen] = useState(false)
const [malls, setMalls] = useState<any[]>([])
const [loadingMalls, setLoadingMalls] = useState(false)
const [totalMerchants, setTotalMerchants] = useState<TotalMerchant[]>([])
@@ -74,6 +75,7 @@ export default function MerchantsPage() {
const [total, setTotal] = useState(0)
const [merchantEquipments, setMerchantEquipments] = useState<{[key: string]: any[]}>({})
const [loadingMerchantEquipments, setLoadingMerchantEquipments] = useState<{[key: string]: boolean}>({})
const [userRole, setUserRole] = useState<string>("")
const [newMerchant, setNewMerchant] = useState({
name: "",
@@ -90,6 +92,18 @@ export default function MerchantsPage() {
const [isSubmitting, setIsSubmitting] = useState(false)
// 获取用户角色
const fetchUserRole = async () => {
try {
const response = await apiGet('/back/getUserRole')
if (response.code === 200) {
setUserRole(response.data || "")
}
} catch (error) {
console.error('获取用户角色失败:', error)
}
}
// 处理省份变化
const handleProvinceChange = (province: string) => {
setNewMerchant({
@@ -166,20 +180,20 @@ export default function MerchantsPage() {
}
// 获取商户设备列表
const fetchMerchantEquipments = async (merchantsId: string) => {
setLoadingMerchantEquipments(prev => ({ ...prev, [merchantsId]: true }))
const fetchMerchantEquipments = async (merchantId: string) => {
setLoadingMerchantEquipments(prev => ({ ...prev, [merchantId]: true }))
try {
const response = await apiGet(`/back/equipment/list?pageNum=1&pageSize=10&merchantsId=${merchantsId}`)
const response = await apiGet(`/back/equipment/list?pageNum=1&pageSize=10&merchantId=${merchantId}`)
if (response.code === 200) {
setMerchantEquipments(prev => ({ ...prev, [merchantsId]: response.rows || [] }))
setMerchantEquipments(prev => ({ ...prev, [merchantId]: response.rows || [] }))
return response.rows || []
}
return []
} catch (error) {
console.error(`获取商户${merchantsId}设备列表失败:`, error)
console.error(`获取商户${merchantId}设备列表失败:`, error)
return []
} finally {
setLoadingMerchantEquipments(prev => ({ ...prev, [merchantsId]: false }))
setLoadingMerchantEquipments(prev => ({ ...prev, [merchantId]: false }))
}
}
@@ -218,6 +232,7 @@ export default function MerchantsPage() {
// 组件加载时获取数据
useEffect(() => {
fetchUserRole()
fetchMerchants()
}, [])
@@ -347,6 +362,14 @@ export default function MerchantsPage() {
}
}
// 打开设备对话框
const handleViewEquipment = (merchant: any) => {
setSelectedMerchant(merchant)
setIsEquipmentDialogOpen(true)
const merchantId = merchant.merchantsId || merchant.id
fetchMerchantEquipments(merchantId)
}
const filteredMerchants = merchants.filter((merchant) => {
const matchesSearch =
merchant.merchantName?.toLowerCase().includes(searchTerm.toLowerCase()) ||
@@ -365,118 +388,6 @@ export default function MerchantsPage() {
return matchesSearch && matchesStatus
})
const EquipmentDialog = ({ merchant }: { merchant: any }) => {
const merchantsId = merchant.merchantsId || merchant.id
const equipments = merchantEquipments[merchantsId] || []
const isLoading = loadingMerchantEquipments[merchantsId] || false
const handleOpenChange = (open: boolean) => {
if (open && !merchantEquipments[merchantsId]) {
fetchMerchantEquipments(merchantsId)
}
}
return (
<Dialog onOpenChange={handleOpenChange}>
<DialogTrigger asChild>
<Button variant="outline" size="sm">
<Eye className="h-4 w-4 mr-1" />
({merchant.equipmentCount})
</Button>
</DialogTrigger>
<DialogContent className="!w-[1400px] !max-w-[1400px] max-h-[800px] overflow-hidden flex flex-col">
<DialogHeader className="flex-shrink-0">
<DialogTitle>{merchant.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">{merchant.normalCount}</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">{merchant.expiringCount}</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">{merchant.expiredCount}</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>
{isLoading ? (
<TableRow>
<TableCell colSpan={6} className="text-center py-4">
...
</TableCell>
</TableRow>
) : equipments.length > 0 ? (
equipments.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>
{getStatusBadge(equipment.status)}
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={6} className="text-center py-4">
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
</div>
</div>
</DialogContent>
</Dialog>
)
}
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
@@ -484,10 +395,12 @@ export default function MerchantsPage() {
<h1 className="text-3xl font-bold text-gray-900"></h1>
<p className="text-gray-600"></p>
</div>
{userRole !== "merchant" && (
<Button onClick={() => setIsAddMerchantOpen(true)}>
<Plus className="h-4 w-4 mr-2" />
</Button>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
@@ -664,10 +577,15 @@ export default function MerchantsPage() {
</TableCell>
<TableCell>
<div className="flex space-x-2">
<EquipmentDialog merchant={merchant} />
<Button variant="outline" size="sm" onClick={() => handleViewEquipment(merchant)}>
<Eye className="h-4 w-4 mr-1" />
({merchant.equipmentCount})
</Button>
{userRole !== "merchant" && (
<Button variant="outline" size="sm">
</Button>
)}
</div>
</TableCell>
</TableRow>
@@ -943,6 +861,99 @@ export default function MerchantsPage() {
</div>
</DialogContent>
</Dialog>
{/* 设备详情对话框 */}
<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>
{loadingMerchantEquipments[selectedMerchant?.merchantsId || selectedMerchant?.id] ? (
<TableRow>
<TableCell colSpan={6} className="text-center py-4">
...
</TableCell>
</TableRow>
) : (merchantEquipments[selectedMerchant?.merchantsId || selectedMerchant?.id] || []).length > 0 ? (
(merchantEquipments[selectedMerchant?.merchantsId || selectedMerchant?.id] || []).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>
{getStatusBadge(equipment.status)}
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={6} className="text-center py-4">
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
</div>
</div>
</DialogContent>
</Dialog>
</div>
)
}