This commit is contained in:
menxipeng
2025-11-29 17:34:10 +08:00
parent de3aa54bd2
commit 5164d2ec46
4 changed files with 330 additions and 124 deletions

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react" import React, { useState, useEffect, useRef } from "react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card"
import { Button } from "../ui/button" import { Button } from "../ui/button"
import { Input } from "../ui/input" import { Input } from "../ui/input"
@@ -74,6 +74,22 @@ export default function CompanyPermissionsPage() {
const [selectedUser, setSelectedUser] = useState<UserData | null>(null) const [selectedUser, setSelectedUser] = useState<UserData | null>(null)
const [isSubmitting, setIsSubmitting] = useState(false) const [isSubmitting, setIsSubmitting] = useState(false)
const [currentUserRole, setCurrentUserRole] = useState<string>("") const [currentUserRole, setCurrentUserRole] = useState<string>("")
const [userRoleFromApi, setUserRoleFromApi] = useState<string>("")
const hasInitializedCommonRole = useRef(false)
const lastFetchedRoleFilter = useRef<string>("")
const lastFetchedPage = useRef<number>(0)
// 获取当前用户角色
const fetchUserRole = async () => {
try {
const result = await apiGet('/back/getUserRole')
if (result.code === 200 && result.data) {
setUserRoleFromApi(result.data)
}
} catch (error) {
console.error('获取用户角色失败:', error)
}
}
// 获取角色列表 // 获取角色列表
const fetchRoles = async () => { const fetchRoles = async () => {
@@ -157,12 +173,41 @@ export default function CompanyPermissionsPage() {
// 初始化数据 // 初始化数据
useEffect(() => { useEffect(() => {
fetchUserRole()
fetchRoles() fetchRoles()
}, []) }, [])
// 初始化 common 角色的过滤器设置(只执行一次)
useEffect(() => {
if (userRoleFromApi === "common" && roles.length > 0 && !hasInitializedCommonRole.current) {
hasInitializedCommonRole.current = true
setRoleFilter("mall")
setActiveTab("mall")
}
}, [userRoleFromApi, roles])
// 当角色列表加载完成后,获取用户数据 // 当角色列表加载完成后,获取用户数据
useEffect(() => { useEffect(() => {
if (roles.length > 0) { if (roles.length > 0) {
// 如果用户角色是 common强制显示商场管理员
if (userRoleFromApi === "common") {
const mallRole = roles.find(r => r.roleKey === "mall")
if (mallRole) {
// 避免重复请求:检查是否与上次请求的参数相同
const currentFilter = "mall"
if (lastFetchedRoleFilter.current !== currentFilter || lastFetchedPage.current !== currentPage) {
lastFetchedRoleFilter.current = currentFilter
lastFetchedPage.current = currentPage
fetchUsersByRole(mallRole.roleId, currentPage)
}
}
} else if (userRoleFromApi !== "") {
// 正常情况:根据 roleFilter 获取用户数据
// 只有当 userRoleFromApi 已经获取到值时才执行
// 避免重复请求:检查是否与上次请求的参数相同
if (lastFetchedRoleFilter.current !== roleFilter || lastFetchedPage.current !== currentPage) {
lastFetchedRoleFilter.current = roleFilter
lastFetchedPage.current = currentPage
if (roleFilter === "all") { if (roleFilter === "all") {
fetchAllUsers() fetchAllUsers()
} else { } else {
@@ -172,7 +217,9 @@ export default function CompanyPermissionsPage() {
} }
} }
} }
}, [roles, roleFilter, currentPage]) }
}
}, [roles, roleFilter, currentPage, userRoleFromApi])
// 当选择的角色改变时,重置页码并获取数据 // 当选择的角色改变时,重置页码并获取数据
const handleRoleFilterChange = (value: string) => { const handleRoleFilterChange = (value: string) => {
@@ -389,7 +436,7 @@ export default function CompanyPermissionsPage() {
<h1 className="text-3xl font-bold text-gray-900"></h1> <h1 className="text-3xl font-bold text-gray-900"></h1>
<p className="text-gray-600"></p> <p className="text-gray-600"></p>
</div> </div>
{isAdmin && ( {(isAdmin || userRoleFromApi === "common") && (
<Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}> <Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button> <Button>
@@ -402,7 +449,11 @@ export default function CompanyPermissionsPage() {
<DialogTitle></DialogTitle> <DialogTitle></DialogTitle>
<DialogDescription></DialogDescription> <DialogDescription></DialogDescription>
</DialogHeader> </DialogHeader>
<CreateUserForm roles={roles} onClose={() => setIsCreateDialogOpen(false)} /> <CreateUserForm
roles={roles}
userRoleFromApi={userRoleFromApi}
onClose={() => setIsCreateDialogOpen(false)}
/>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
)} )}
@@ -410,7 +461,15 @@ export default function CompanyPermissionsPage() {
{/* Stats Cards */} {/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-5 gap-4"> <div className="grid grid-cols-1 md:grid-cols-5 gap-4">
{roles.map((role) => { {roles
.filter((role) => {
// 如果用户角色是 common只显示商场管理员mall
if (userRoleFromApi === "common") {
return role.roleKey === "mall"
}
return true
})
.map((role) => {
const roleInfo = getRoleInfo(role.roleKey) const roleInfo = getRoleInfo(role.roleKey)
const RoleIcon = roleInfo.icon const RoleIcon = roleInfo.icon
const userCount = groupedUsers[role.roleKey]?.length || 0 const userCount = groupedUsers[role.roleKey]?.length || 0
@@ -456,8 +515,18 @@ export default function CompanyPermissionsPage() {
<SelectValue placeholder="角色" /> <SelectValue placeholder="角色" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{userRoleFromApi !== "common" && (
<SelectItem value="all"></SelectItem> <SelectItem value="all"></SelectItem>
{roles.map((role) => ( )}
{roles
.filter((role) => {
// 如果用户角色是 common只显示商场管理员mall
if (userRoleFromApi === "common") {
return role.roleKey === "mall"
}
return true
})
.map((role) => (
<SelectItem key={role.roleId} value={role.roleKey}> <SelectItem key={role.roleId} value={role.roleKey}>
{role.roleName} {role.roleName}
</SelectItem> </SelectItem>
@@ -486,6 +555,7 @@ export default function CompanyPermissionsPage() {
{/* Tabs */} {/* Tabs */}
<div className="space-y-4"> <div className="space-y-4">
<div className="flex space-x-1 overflow-x-auto"> <div className="flex space-x-1 overflow-x-auto">
{userRoleFromApi !== "common" && (
<button <button
className={`px-4 py-2 text-sm font-medium transition-colors whitespace-nowrap ${ className={`px-4 py-2 text-sm font-medium transition-colors whitespace-nowrap ${
activeTab === "all" activeTab === "all"
@@ -496,7 +566,16 @@ export default function CompanyPermissionsPage() {
> >
</button> </button>
{roles.map((role) => ( )}
{roles
.filter((role) => {
// 如果用户角色是 common只显示商场管理员mall
if (userRoleFromApi === "common") {
return role.roleKey === "mall"
}
return true
})
.map((role) => (
<button <button
key={role.roleId} key={role.roleId}
className={`px-4 py-2 text-sm font-medium transition-colors whitespace-nowrap ${ className={`px-4 py-2 text-sm font-medium transition-colors whitespace-nowrap ${
@@ -650,7 +729,15 @@ export default function CompanyPermissionsPage() {
) )
} }
function CreateUserForm({ roles, onClose }: { roles: Role[]; onClose: () => void }) { function CreateUserForm({
roles,
userRoleFromApi,
onClose
}: {
roles: Role[];
userRoleFromApi: string;
onClose: () => void
}) {
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
username: "", username: "",
name: "", name: "",
@@ -664,6 +751,15 @@ function CreateUserForm({ roles, onClose }: { roles: Role[]; onClose: () => void
const [availableProvinces, setAvailableProvinces] = useState<any[]>([]) const [availableProvinces, setAvailableProvinces] = useState<any[]>([])
const [provincesLoading, setProvincesLoading] = useState(false) const [provincesLoading, setProvincesLoading] = useState(false)
// 如果当前用户是 common 角色,默认选中商场管理员并加载省份数据
useEffect(() => {
if (userRoleFromApi === "common") {
setFormData(prev => ({ ...prev, role: "mall" }))
fetchProvinces("mall")
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [userRoleFromApi])
// 根据角色获取省份数据 // 根据角色获取省份数据
const fetchProvinces = async (roleKey: string) => { const fetchProvinces = async (roleKey: string) => {
try { try {
@@ -685,9 +781,37 @@ function CreateUserForm({ roles, onClose }: { roles: Role[]; onClose: () => void
} }
} }
// 用户名校验函数
const validateUsername = (username: string): string | null => {
if (!username || username.trim() === '') {
return '用户名不能为空'
}
// 检查是否包含空格
if (username.includes(' ')) {
return '用户名不能包含空格'
}
// 检查是否包含其他非法字符(只允许字母、数字、下划线、中划线)
const validPattern = /^[a-zA-Z0-9_-]+$/
if (!validPattern.test(username)) {
return '用户名只能包含字母、数字、下划线和中划线'
}
// 检查长度(根据实际需求调整)
if (username.length < 3 || username.length > 20) {
return '用户名长度应在3-20个字符之间'
}
return null
}
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault() e.preventDefault()
// 校验用户名
const usernameError = validateUsername(formData.username)
if (usernameError) {
alert(usernameError)
return
}
if (!formData.role) { if (!formData.role) {
alert('请选择角色') alert('请选择角色')
return return
@@ -751,6 +875,14 @@ function CreateUserForm({ roles, onClose }: { roles: Role[]; onClose: () => void
})) }))
} }
// 处理用户名输入,过滤非法字符
const handleUsernameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
let value = e.target.value
// 移除空格和其他非法字符(只保留字母、数字、下划线、中划线)
value = value.replace(/[^a-zA-Z0-9_-]/g, '')
setFormData({ ...formData, username: value })
}
const shouldShowProvinces = formData.role && formData.role !== "admin" && formData.role !== "merchant" const shouldShowProvinces = formData.role && formData.role !== "admin" && formData.role !== "merchant"
return ( return (
@@ -761,10 +893,14 @@ function CreateUserForm({ roles, onClose }: { roles: Role[]; onClose: () => void
<Input <Input
id="username" id="username"
value={formData.username} value={formData.username}
onChange={(e) => setFormData({ ...formData, username: e.target.value })} onChange={handleUsernameChange}
placeholder="请输入用户名" placeholder="请输入用户名3-20个字符仅支持字母、数字、下划线、中划线"
required required
maxLength={20}
/> />
{formData.username && formData.username.length > 0 && formData.username.length < 3 && (
<p className="text-sm text-red-500">3</p>
)}
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
@@ -799,18 +935,30 @@ function CreateUserForm({ roles, onClose }: { roles: Role[]; onClose: () => void
fetchProvinces(value) fetchProvinces(value)
} }
}} }}
disabled={userRoleFromApi === "common"}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="选择角色" /> <SelectValue placeholder="选择角色" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{roles.map((role) => ( {roles
.filter((role) => {
// 如果当前用户是 common 角色,只显示商场管理员
if (userRoleFromApi === "common") {
return role.roleKey === "mall"
}
return true
})
.map((role) => (
<SelectItem key={role.roleId} value={role.roleKey}> <SelectItem key={role.roleId} value={role.roleKey}>
{role.roleName} {role.roleName}
</SelectItem> </SelectItem>
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
{userRoleFromApi === "common" && (
<p className="text-sm text-gray-500"></p>
)}
</div> </div>
{shouldShowProvinces && ( {shouldShowProvinces && (
@@ -1039,9 +1187,9 @@ function EditUserForm({
<Input <Input
id="edit-username" id="edit-username"
value={formData.username} value={formData.username}
onChange={(e) => setFormData({ ...formData, username: e.target.value })} disabled
placeholder="请输入用户名" placeholder="用户名不可修改"
required className="bg-gray-100 cursor-not-allowed"
/> />
</div> </div>

View File

@@ -165,43 +165,14 @@ export default function EquipmentPage() {
} }
} }
// 获取单个商户的设备数量 // 获取商户列表(用于添加/编辑设备选择,不获取设备数量
const fetchMerchantEquipmentCount = async (merchantId: string) => { const fetchMerchantsForSelect = async () => {
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 fetchMerchants = async () => {
setLoadingMerchants(true) setLoadingMerchants(true)
try { try {
const response = await apiGet('/back/general/provinceMerchants') const response = await apiGet('/back/general/provinceMerchants')
if (response.code === 200) { if (response.code === 200) {
const merchantsData = response.data || [] const merchantsData = response.data || []
setMerchants(merchantsData)
// 为每个商户获取设备数量
const merchantsWithCounts = await Promise.all(
merchantsData.map(async (merchant: any) => {
const equipmentCount = await fetchMerchantEquipmentCount(merchant.id)
return {
...merchant,
equipmentCount: equipmentCount?.totalCount || 0,
normalCount: equipmentCount?.count || 0,
expiringCount: equipmentCount?.countEnd || 0,
expiredCount: equipmentCount?.countExpire || 0
}
})
)
setMerchants(merchantsWithCounts)
} }
} catch (error) { } catch (error) {
console.error('获取商户列表失败:', error) console.error('获取商户列表失败:', error)
@@ -361,7 +332,7 @@ export default function EquipmentPage() {
setIsEditEquipmentOpen(true) setIsEditEquipmentOpen(true)
// 加载商户和设备类型数据 // 加载商户和设备类型数据
if (merchants.length === 0) { if (merchants.length === 0) {
fetchMerchants() fetchMerchantsForSelect()
} }
if (Object.keys(equipmentTypes).length === 0) { if (Object.keys(equipmentTypes).length === 0) {
fetchEquipmentTypes() fetchEquipmentTypes()
@@ -613,7 +584,7 @@ export default function EquipmentPage() {
disabled={loadingMerchants} disabled={loadingMerchants}
onOpenChange={(open) => { onOpenChange={(open) => {
if (open && merchants.length === 0 && !loadingMerchants) { if (open && merchants.length === 0 && !loadingMerchants) {
fetchMerchants() fetchMerchantsForSelect()
} }
if (!open) { if (!open) {
setMerchantSearchForAdd("") setMerchantSearchForAdd("")
@@ -835,7 +806,7 @@ export default function EquipmentPage() {
disabled={loadingMerchants} disabled={loadingMerchants}
onOpenChange={(open) => { onOpenChange={(open) => {
if (open && merchants.length === 0 && !loadingMerchants) { if (open && merchants.length === 0 && !loadingMerchants) {
fetchMerchants() fetchMerchantsForSelect()
} }
if (!open) { if (!open) {
setMerchantSearchForEdit("") setMerchantSearchForEdit("")

View File

@@ -166,15 +166,25 @@ export default function MerchantsPage() {
// 获取商场列表 // 获取商场列表
const fetchMalls = async (province?: string) => { const fetchMalls = async (province?: string) => {
if (!province) {
setMalls([])
return
}
setLoadingMalls(true) setLoadingMalls(true)
try { try {
const url = `/back/info/list?province=${province}&pageNum=1&pageSize=100` const url = `/back/info/list?province=${province}&pageNum=1&pageSize=100`
const response = await apiGet(url) const response = await apiGet(url)
if (response.code === 200) { if (response.code === 200) {
setMalls(response.rows || []) const mallsData = response.rows || []
console.log('获取商场列表成功:', mallsData)
setMalls(mallsData)
} else {
console.error('获取商场列表失败:', response.msg)
setMalls([])
} }
} catch (error) { } catch (error) {
console.error('获取商场列表失败:', error) console.error('获取商场列表失败:', error)
setMalls([])
} finally { } finally {
setLoadingMalls(false) setLoadingMalls(false)
} }
@@ -343,6 +353,47 @@ export default function MerchantsPage() {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [merchantNameSearch, contactPersonSearch, contactPhoneSearch, statusFilter]) }, [merchantNameSearch, contactPersonSearch, contactPhoneSearch, statusFilter])
// 当添加商户对话框打开且已选择省份时,自动加载商场列表
useEffect(() => {
if (isAddMerchantOpen && newMerchant.province && malls.length === 0 && !loadingMalls) {
fetchMalls(newMerchant.province)
}
}, [isAddMerchantOpen, newMerchant.province])
// 当编辑商户对话框打开且已选择省份时,自动加载商场列表
useEffect(() => {
if (isEditMerchantOpen && editMerchant.province && !loadingMalls) {
// 如果商场列表为空,或者当前商场不在列表中,重新加载
if (malls.length === 0 || (editMerchant.mall && !malls.find(m => m.mallId === editMerchant.mall))) {
fetchMalls(editMerchant.province)
}
}
}, [isEditMerchantOpen, editMerchant.province, editMerchant.mall])
// 当商场列表加载完成且编辑对话框打开时,根据 mallId 设置 mallUserId
useEffect(() => {
if (isEditMerchantOpen && editMerchant.mall && malls.length > 0 && !loadingMalls) {
// 如果 mallUserId 为空,尝试根据 mallId 找到对应的 mallUser
if (!editMerchant.mallUserId) {
const foundMall = malls.find(m =>
m.mallId === editMerchant.mall ||
m.id === editMerchant.mall ||
(m.mallId && editMerchant.mall && m.mallId.toString() === editMerchant.mall.toString())
)
if (foundMall && foundMall.mallUser) {
console.log('找到匹配的商场:', foundMall)
setEditMerchant(prev => ({
...prev,
mallUserId: foundMall.mallUser
}))
} else {
console.log('未找到匹配的商场mallId:', editMerchant.mall, '商场列表:', malls)
}
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isEditMerchantOpen, editMerchant.mall, malls.length, loadingMalls])
const [newEquipment, setNewEquipment] = useState({ const [newEquipment, setNewEquipment] = useState({
name: "", name: "",
type: "", type: "",
@@ -459,6 +510,15 @@ export default function MerchantsPage() {
} }
} }
// 将设备类型代码转换为中文
const getEquipmentTypeName = (type: string) => {
const typeMap: { [key: string]: string } = {
"kitchen_automatic_fire_extinguisher": "厨房自动灭火",
"fire_extinguisher": "动火离人"
}
return typeMap[type] || type || '-'
}
// 打开编辑对话框 // 打开编辑对话框
const handleEditMerchant = (merchant: any) => { const handleEditMerchant = (merchant: any) => {
setEditMerchant({ setEditMerchant({
@@ -629,6 +689,7 @@ export default function MerchantsPage() {
<div className="relative"> <div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" /> <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
<Input <Input
id="search-merchant-name"
placeholder="搜索商户名称..." placeholder="搜索商户名称..."
value={merchantNameSearch} value={merchantNameSearch}
onChange={(e) => setMerchantNameSearch(e.target.value)} onChange={(e) => setMerchantNameSearch(e.target.value)}
@@ -638,6 +699,7 @@ export default function MerchantsPage() {
<div className="relative"> <div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" /> <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
<Input <Input
id="search-contact-person"
placeholder="搜索联系人..." placeholder="搜索联系人..."
value={contactPersonSearch} value={contactPersonSearch}
onChange={(e) => setContactPersonSearch(e.target.value)} onChange={(e) => setContactPersonSearch(e.target.value)}
@@ -647,6 +709,7 @@ export default function MerchantsPage() {
<div className="relative"> <div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" /> <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
<Input <Input
id="search-contact-phone"
placeholder="搜索联系电话..." placeholder="搜索联系电话..."
value={contactPhoneSearch} value={contactPhoneSearch}
onChange={(e) => setContactPhoneSearch(e.target.value)} onChange={(e) => setContactPhoneSearch(e.target.value)}
@@ -958,7 +1021,7 @@ export default function MerchantsPage() {
<div className="space-y-1.5 sm:space-y-2"> <div className="space-y-1.5 sm:space-y-2">
<Label htmlFor="mall" className="text-sm sm:text-base"></Label> <Label htmlFor="mall" className="text-sm sm:text-base"></Label>
<Select <Select
value={newMerchant.mall} value={newMerchant.mallUserId}
onValueChange={(value) => { onValueChange={(value) => {
const selectedMall = malls.find(mall => mall.mallUser === value) const selectedMall = malls.find(mall => mall.mallUser === value)
setNewMerchant({ setNewMerchant({
@@ -979,36 +1042,48 @@ export default function MerchantsPage() {
} /> } />
</SelectTrigger> </SelectTrigger>
<SelectContent className="max-h-[300px]"> <SelectContent className="max-h-[300px]">
{malls.map((mall) => ( {loadingMalls ? (
<SelectItem key={mall.mallUser || mall.id} value={mall.mallUser}> <SelectItem value="loading" disabled>
{mall.mallName} ...
</SelectItem> </SelectItem>
))} ) : malls.length === 0 ? (
<SelectItem value="no-data" disabled>
{!newMerchant.province ? "请先选择省份" : "该省份暂无商场数据"}
</SelectItem>
) : (
malls
.filter(mall => mall.mallUser) // 过滤掉没有 mallUser 的项
.map((mall, index) => (
<SelectItem key={mall.id || mall.mallId || `mall-${index}`} value={mall.mallUser}>
{mall.mallName || '未命名商场'}
</SelectItem>
))
)}
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<div className="space-y-1.5 sm:space-y-2"> <div className="space-y-1.5 sm:space-y-2">
<Label htmlFor="name" className="text-sm sm:text-base"></Label> <Label htmlFor="new-merchant-name" className="text-sm sm:text-base"></Label>
<Input <Input
id="name" id="new-merchant-name"
value={newMerchant.name} value={newMerchant.name}
onChange={(e) => setNewMerchant({ ...newMerchant, name: e.target.value })} onChange={(e) => setNewMerchant({ ...newMerchant, name: e.target.value })}
placeholder="请输入商户名称" placeholder="请输入商户名称"
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="contact"></Label> <Label htmlFor="new-merchant-contact"></Label>
<Input <Input
id="contact" id="new-merchant-contact"
value={newMerchant.contact} value={newMerchant.contact}
onChange={(e) => setNewMerchant({ ...newMerchant, contact: e.target.value })} onChange={(e) => setNewMerchant({ ...newMerchant, contact: e.target.value })}
placeholder="请输入联系人姓名" placeholder="请输入联系人姓名"
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="phone"></Label> <Label htmlFor="new-merchant-phone"></Label>
<Input <Input
id="phone" id="new-merchant-phone"
value={newMerchant.phone} value={newMerchant.phone}
onChange={(e) => setNewMerchant({ ...newMerchant, phone: e.target.value })} onChange={(e) => setNewMerchant({ ...newMerchant, phone: e.target.value })}
placeholder="请输入联系电话" placeholder="请输入联系电话"
@@ -1035,8 +1110,8 @@ export default function MerchantsPage() {
</SelectItem> </SelectItem>
) : ( ) : (
totalMerchants.map((merchant) => ( totalMerchants.map((merchant, index) => (
<SelectItem key={merchant.userId} value={merchant.userId}> <SelectItem key={merchant.userId || `total-merchant-${index}`} value={merchant.userId}>
{merchant.nickName} {merchant.nickName}
</SelectItem> </SelectItem>
)) ))
@@ -1216,11 +1291,23 @@ export default function MerchantsPage() {
} /> } />
</SelectTrigger> </SelectTrigger>
<SelectContent className="max-h-[300px]"> <SelectContent className="max-h-[300px]">
{malls.map((mall) => ( {loadingMalls ? (
<SelectItem key={mall.mallUser || mall.id} value={mall.mallUser}> <SelectItem value="loading" disabled>
{mall.mallName} ...
</SelectItem> </SelectItem>
))} ) : malls.length === 0 ? (
<SelectItem value="no-data" disabled>
{!editMerchant.province ? "请先选择省份" : "该省份暂无商场数据"}
</SelectItem>
) : (
malls
.filter(mall => mall.mallUser) // 过滤掉没有 mallUser 的项
.map((mall, index) => (
<SelectItem key={mall.id || mall.mallId || `mall-${index}`} value={mall.mallUser}>
{mall.mallName || '未命名商场'}
</SelectItem>
))
)}
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
@@ -1243,9 +1330,9 @@ export default function MerchantsPage() {
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="edit-phone"></Label> <Label htmlFor="edit-merchant-phone"></Label>
<Input <Input
id="edit-phone" id="edit-merchant-phone"
value={editMerchant.phone} value={editMerchant.phone}
onChange={(e) => setEditMerchant({ ...editMerchant, phone: e.target.value })} onChange={(e) => setEditMerchant({ ...editMerchant, phone: e.target.value })}
placeholder="请输入联系电话" placeholder="请输入联系电话"
@@ -1386,16 +1473,16 @@ export default function MerchantsPage() {
</TableCell> </TableCell>
</TableRow> </TableRow>
) : (merchantEquipments[selectedMerchant?.merchantsId || selectedMerchant?.id] || []).length > 0 ? ( ) : (merchantEquipments[selectedMerchant?.merchantsId || selectedMerchant?.id] || []).length > 0 ? (
(merchantEquipments[selectedMerchant?.merchantsId || selectedMerchant?.id] || []).map((equipment: any) => ( (merchantEquipments[selectedMerchant?.merchantsId || selectedMerchant?.id] || []).map((equipment: any, index: number) => (
<TableRow key={equipment.id}> <TableRow key={equipment.id || equipment.equipmentId || `equipment-${index}`}>
<TableCell className="font-medium truncate max-w-[120px]" title={equipment.equipmentId}> <TableCell className="font-medium truncate max-w-[120px]" title={equipment.equipmentId}>
{equipment.equipmentId} {equipment.equipmentId}
</TableCell> </TableCell>
<TableCell className="truncate max-w-[150px]" title={equipment.equipmentName}> <TableCell className="truncate max-w-[150px]" title={equipment.equipmentName}>
{equipment.equipmentName} {equipment.equipmentName}
</TableCell> </TableCell>
<TableCell className="truncate max-w-[120px]" title={equipment.equipmentType}> <TableCell className="truncate max-w-[120px]" title={getEquipmentTypeName(equipment.equipmentType)}>
{equipment.equipmentType} {getEquipmentTypeName(equipment.equipmentType)}
</TableCell> </TableCell>
<TableCell className="truncate max-w-[120px]" title={equipment.installationDate}> <TableCell className="truncate max-w-[120px]" title={equipment.installationDate}>
{equipment.installationDate} {equipment.installationDate}
@@ -1425,8 +1512,8 @@ export default function MerchantsPage() {
{loadingMerchantEquipments[selectedMerchant?.merchantsId || selectedMerchant?.id] ? ( {loadingMerchantEquipments[selectedMerchant?.merchantsId || selectedMerchant?.id] ? (
<div className="text-center py-8 text-gray-500 text-sm">...</div> <div className="text-center py-8 text-gray-500 text-sm">...</div>
) : (merchantEquipments[selectedMerchant?.merchantsId || selectedMerchant?.id] || []).length > 0 ? ( ) : (merchantEquipments[selectedMerchant?.merchantsId || selectedMerchant?.id] || []).length > 0 ? (
(merchantEquipments[selectedMerchant?.merchantsId || selectedMerchant?.id] || []).map((equipment: any) => ( (merchantEquipments[selectedMerchant?.merchantsId || selectedMerchant?.id] || []).map((equipment: any, index: number) => (
<Card key={equipment.id} className="overflow-hidden"> <Card key={equipment.id || equipment.equipmentId || `equipment-card-${index}`} className="overflow-hidden">
<CardContent className="p-4 space-y-3"> <CardContent className="p-4 space-y-3">
{/* 设备基本信息 */} {/* 设备基本信息 */}
<div className="flex items-start justify-between gap-3"> <div className="flex items-start justify-between gap-3">
@@ -1441,7 +1528,7 @@ export default function MerchantsPage() {
<div className="border-t pt-3 space-y-2.5"> <div className="border-t pt-3 space-y-2.5">
<div className="space-y-1"> <div className="space-y-1">
<div className="text-xs font-medium text-gray-700"></div> <div className="text-xs font-medium text-gray-700"></div>
<div className="text-sm text-gray-900 break-words">{equipment.equipmentType || '-'}</div> <div className="text-sm text-gray-900 break-words">{getEquipmentTypeName(equipment.equipmentType)}</div>
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
<div className="text-xs font-medium text-gray-700"></div> <div className="text-xs font-medium text-gray-700"></div>

View File

@@ -1006,7 +1006,7 @@ function CreateWorkOrderForm({ onClose }: { onClose: () => void }) {
<SelectContent className="max-h-[300px] overflow-y-auto"> <SelectContent className="max-h-[300px] overflow-y-auto">
{availableEquipment.map((equipment) => ( {availableEquipment.map((equipment) => (
<SelectItem key={equipment.id} value={equipment.equipmentId}> <SelectItem key={equipment.id} value={equipment.equipmentId}>
{equipment.equipmentName} ({equipment.equipmentType}) {equipment.equipmentName}
</SelectItem> </SelectItem>
))} ))}
</SelectContent> </SelectContent>