业务员等

This commit is contained in:
menxipeng
2025-11-17 22:20:01 +08:00
parent 535bbe2b54
commit f01f721a29
3 changed files with 544 additions and 343 deletions

View File

@@ -445,10 +445,16 @@ export default function EquipmentPage() {
}
const filteredEquipment = equipmentList.filter((item) => {
// 安全地处理可能为 null 或 undefined 的字段
const equipmentName = item.equipmentName || ""
const merchantName = item.merchantName || ""
const mallName = item.mallName || ""
const searchLower = searchTerm.toLowerCase()
const matchesSearch =
item.equipmentName.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.merchantName.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.mallName.toLowerCase().includes(searchTerm.toLowerCase())
equipmentName.toLowerCase().includes(searchLower) ||
merchantName.toLowerCase().includes(searchLower) ||
mallName.toLowerCase().includes(searchLower)
// 修正状态筛选逻辑
let matchesStatus = true

View File

@@ -24,27 +24,26 @@ import type { User } from "@/lib/types/user"
export default function ValueAddedServicesPage() {
const [services, setServices] = useState<ValueAddedService[]>([])
const [adminUsers, setAdminUsers] = useState<User[]>([])
const [commonUsers, setCommonUsers] = useState<User[]>([])
const [users, setUsers] = useState<User[]>([])
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false)
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)
const [serviceToDelete, setServiceToDelete] = useState<ValueAddedService | null>(null)
const [selectedService, setSelectedService] = useState<ValueAddedService | null>(null)
const [loading, setLoading] = useState(false)
const [pageNum, setPageNum] = useState(1)
const [pageSize, setPageSize] = useState(10)
const [pageSize] = useState(10)
const [total, setTotal] = useState(0)
const [formData, setFormData] = useState({
serviceTitle: "",
description: "",
status: "1",
assignedAdmin: "",
assignedCommon: "",
assignedUsers: "",
})
useEffect(() => {
loadServices()
loadUsers()
}, [pageNum, pageSize])
const loadServices = async () => {
@@ -64,17 +63,10 @@ export default function ValueAddedServicesPage() {
const loadUsers = async () => {
try {
const [adminResponse, commonResponse] = await Promise.all([
getUserByRoleKey()
// getUserByRoleKey("common"),
])
if (adminResponse.code === 200 && adminResponse.data) {
setAdminUsers(adminResponse.data)
}
if (commonResponse.code === 200 && commonResponse.data) {
setCommonUsers(commonResponse.data)
// 传空字符串获取所有用户,或者根据实际需求传入特定的 roleKey
const response = await getUserByRoleKey("")
if (response.code === 200 && response.data) {
setUsers(response.data)
}
} catch (error) {
console.error("加载用户列表失败:", error)
@@ -84,10 +76,17 @@ export default function ValueAddedServicesPage() {
const handleCreateService = async () => {
setLoading(true)
try {
const response = await createService(formData)
// 将 assignedUsers 同时赋值给 assignedAdmin 和 assignedCommon 以保持 API 兼容性
const response = await createService({
serviceTitle: formData.serviceTitle,
description: formData.description,
status: formData.status,
assignedAdmin: formData.assignedUsers,
assignedCommon: formData.assignedUsers,
})
if (response.code === 200) {
setIsCreateDialogOpen(false)
resetForm()
setIsCreateDialogOpen(false)
resetForm()
loadServices()
} else {
alert(response.msg || "创建失败")
@@ -105,13 +104,18 @@ export default function ValueAddedServicesPage() {
setLoading(true)
try {
// 将 assignedUsers 同时赋值给 assignedAdmin 和 assignedCommon 以保持 API 兼容性
const response = await updateService({
id: selectedService.id,
...formData,
serviceTitle: formData.serviceTitle,
description: formData.description,
status: formData.status,
assignedAdmin: formData.assignedUsers,
assignedCommon: formData.assignedUsers,
})
if (response.code === 200) {
setIsEditDialogOpen(false)
resetForm()
setIsEditDialogOpen(false)
resetForm()
loadServices()
} else {
alert(response.msg || "更新失败")
@@ -124,13 +128,20 @@ export default function ValueAddedServicesPage() {
}
}
const handleDeleteService = async (id: string) => {
if (!confirm("确定要删除该服务吗?")) return
const handleDeleteService = (service: ValueAddedService) => {
setServiceToDelete(service)
setIsDeleteDialogOpen(true)
}
const handleConfirmDelete = async () => {
if (!serviceToDelete) return
setLoading(true)
try {
const response = await deleteService(id)
const response = await deleteService(serviceToDelete.id)
if (response.code === 200) {
setIsDeleteDialogOpen(false)
setServiceToDelete(null)
loadServices()
} else {
alert(response.msg || "删除失败")
@@ -147,13 +158,18 @@ export default function ValueAddedServicesPage() {
setLoading(true)
try {
const newStatus = service.status === "1" ? "0" : "1"
// 合并 assignedAdmin 和 assignedCommon 为 assignedUsers然后同时赋值给两个字段以保持 API 兼容性
const assignedUsers = [
...(service.assignedAdmin || "").split(",").filter(Boolean),
...(service.assignedCommon || "").split(",").filter(Boolean),
].join(",")
const response = await updateService({
id: service.id,
serviceTitle: service.serviceTitle,
description: service.description,
status: newStatus,
assignedAdmin: service.assignedAdmin || "",
assignedCommon: service.assignedCommon || "",
assignedAdmin: assignedUsers,
assignedCommon: assignedUsers,
})
if (response.code === 200) {
loadServices()
@@ -173,26 +189,31 @@ export default function ValueAddedServicesPage() {
serviceTitle: "",
description: "",
status: "1",
assignedAdmin: "",
assignedCommon: "",
assignedUsers: "",
})
setSelectedService(null)
}
const openEditDialog = (service: ValueAddedService) => {
const openEditDialog = async (service: ValueAddedService) => {
setSelectedService(service)
// 合并 assignedAdmin 和 assignedCommon 为 assignedUsers
const assignedUsers = [
...(service.assignedAdmin || "").split(",").filter(Boolean),
...(service.assignedCommon || "").split(",").filter(Boolean),
].join(",")
setFormData({
serviceTitle: service.serviceTitle,
description: service.description,
status: service.status,
assignedAdmin: service.assignedAdmin || "",
assignedCommon: service.assignedCommon || "",
assignedUsers: assignedUsers,
})
// 打开编辑对话框时加载用户列表
await loadUsers()
setIsEditDialogOpen(true)
}
const getUserName = (userId: string, userList: User[]) => {
const user = userList.find((u) => u.userId === userId)
const getUserName = (userId: string) => {
const user = users.find((u) => u.userId === userId)
return user ? (user.nickName || user.userName) : "未知用户"
}
@@ -201,6 +222,11 @@ export default function ValueAddedServicesPage() {
return ids.split(",").filter(Boolean)
}
const handleOpenCreateDialog = async () => {
await loadUsers()
setIsCreateDialogOpen(true)
}
const totalPages = Math.ceil(total / pageSize)
const handlePrevPage = () => {
@@ -216,133 +242,99 @@ export default function ValueAddedServicesPage() {
}
return (
<div className="space-y-6">
<div className="space-y-4 sm:space-y-6 p-4 sm:p-6">
{/* Page Header */}
<div className="flex justify-between items-center">
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<div>
<h1 className="text-3xl font-bold text-gray-900"></h1>
<p className="text-gray-600"></p>
<h1 className="text-2xl sm:text-3xl font-bold text-gray-900"></h1>
<p className="text-sm sm:text-base text-gray-600"></p>
</div>
<Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
<DialogTrigger asChild>
<Button>
<Button onClick={handleOpenCreateDialog} className="w-full sm:w-auto">
<Plus className="h-4 w-4 mr-2" />
</Button>
</DialogTrigger>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription></DialogDescription>
<DialogContent className="max-w-2xl max-h-[95vh] overflow-y-auto w-[95vw] sm:w-full p-3 sm:p-6">
<DialogHeader className="pb-2 sm:pb-4">
<DialogTitle className="text-base sm:text-lg"></DialogTitle>
<DialogDescription className="text-xs sm:text-sm"></DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div>
<Label htmlFor="title"></Label>
<div className="space-y-3 sm:space-y-4">
<div className="space-y-1.5 sm:space-y-2">
<Label htmlFor="title" className="text-sm sm:text-base"></Label>
<Input
id="title"
value={formData.serviceTitle}
onChange={(e) => setFormData({ ...formData, serviceTitle: e.target.value })}
placeholder="请输入服务标题"
className="text-sm sm:text-base"
/>
</div>
<div>
<Label htmlFor="description"></Label>
<div className="space-y-1.5 sm:space-y-2">
<Label htmlFor="description" className="text-sm sm:text-base"></Label>
<Textarea
id="description"
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
placeholder="请输入服务详细描述"
rows={3}
className="text-sm sm:text-base resize-none"
/>
</div>
<div>
<Label></Label>
<div className="grid grid-cols-1 gap-2 mt-2 max-h-48 overflow-y-auto border rounded p-2">
{adminUsers.map((user) => {
const assignedIds = formData.assignedAdmin.split(",").filter(Boolean)
const userId = user.userId
return (
<div key={userId} className="flex items-center space-x-2 p-2 border rounded">
<input
type="checkbox"
id={`admin-${userId}`}
checked={assignedIds.includes(userId)}
onChange={(e) => {
const ids = formData.assignedAdmin.split(",").filter(Boolean)
if (e.target.checked) {
setFormData({
...formData,
assignedAdmin: [...ids, userId].join(","),
})
} else {
setFormData({
...formData,
assignedAdmin: ids.filter((id) => id !== userId).join(","),
})
}
}}
/>
<label htmlFor={`admin-${userId}`} className="flex-1 cursor-pointer">
<div className="flex justify-between">
<span className="font-medium">{user.nickName || user.userName}</span>
<span className="text-sm text-gray-500 flex items-center">
<Phone className="h-3 w-3 mr-1" />
{user.phonenumber}
</span>
</div>
</label>
</div>
)
})}
</div>
</div>
<div>
<Label></Label>
<div className="grid grid-cols-1 gap-2 mt-2 max-h-48 overflow-y-auto border rounded p-2">
{commonUsers.map((user) => {
const assignedIds = formData.assignedCommon.split(",").filter(Boolean)
const userId = user.userId
return (
<div key={userId} className="flex items-center space-x-2 p-2 border rounded">
<input
type="checkbox"
id={`common-${userId}`}
checked={assignedIds.includes(userId)}
onChange={(e) => {
const ids = formData.assignedCommon.split(",").filter(Boolean)
if (e.target.checked) {
setFormData({
...formData,
assignedCommon: [...ids, userId].join(","),
})
} else {
setFormData({
...formData,
assignedCommon: ids.filter((id) => id !== userId).join(","),
})
}
}}
/>
<label htmlFor={`common-${userId}`} className="flex-1 cursor-pointer">
<div className="flex justify-between">
<span className="font-medium">{user.nickName || user.userName}</span>
<span className="text-sm text-gray-500 flex items-center">
<Phone className="h-3 w-3 mr-1" />
{user.phonenumber}
</span>
<div className="space-y-1.5 sm:space-y-2">
<Label className="text-sm sm:text-base"></Label>
<div className="grid grid-cols-1 gap-2 mt-2 max-h-48 sm:max-h-64 overflow-y-auto border rounded p-2">
{users.length === 0 ? (
<div className="text-center py-4 text-sm text-gray-500">...</div>
) : (
users.map((user) => {
const assignedIds = formData.assignedUsers.split(",").filter(Boolean)
const userId = user.userId
return (
<div key={userId} className="flex items-center space-x-2 p-2 sm:p-3 border rounded hover:bg-gray-50">
<input
type="checkbox"
id={`user-${userId}`}
checked={assignedIds.includes(userId)}
onChange={(e) => {
const ids = formData.assignedUsers.split(",").filter(Boolean)
if (e.target.checked) {
setFormData({
...formData,
assignedUsers: [...ids, userId].join(","),
})
} else {
setFormData({
...formData,
assignedUsers: ids.filter((id) => id !== userId).join(","),
})
}
}}
className="flex-shrink-0"
/>
<label htmlFor={`user-${userId}`} className="flex-1 cursor-pointer min-w-0">
<div className="flex flex-col sm:flex-row sm:justify-between gap-1 sm:gap-0">
<span className="font-medium text-sm sm:text-base truncate">{user.nickName || user.userName}</span>
<span className="text-xs sm:text-sm text-gray-500 flex items-center">
<Phone className="h-3 w-3 mr-1 flex-shrink-0" />
<span className="truncate">{user.phonenumber}</span>
</span>
</div>
</label>
</div>
</label>
</div>
)
})}
)
})
)}
</div>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setIsCreateDialogOpen(false)} disabled={loading}>
<DialogFooter className="flex-col-reverse sm:flex-row gap-2 pt-2">
<Button variant="outline" onClick={() => setIsCreateDialogOpen(false)} disabled={loading} className="w-full sm:w-auto">
</Button>
<Button onClick={handleCreateService} disabled={loading}>
<Button onClick={handleCreateService} disabled={loading} className="w-full sm:w-auto">
{loading ? "创建中..." : "创建服务"}
</Button>
</DialogFooter>
@@ -353,120 +345,186 @@ export default function ValueAddedServicesPage() {
{/* Services List */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
<CardTitle className="text-lg sm:text-xl"></CardTitle>
<CardDescription className="text-xs sm:text-sm"></CardDescription>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{loading ? (
{/* Desktop Table View */}
<div className="hidden md:block rounded-md border overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableCell colSpan={7} className="text-center py-8">
...
</TableCell>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
) : services.length === 0 ? (
<TableRow>
<TableCell colSpan={7} className="text-center py-8 text-gray-500">
</TableCell>
</TableRow>
) : (
services.map((service) => (
<TableRow key={service.id}>
<TableCell className="font-medium">{service.serviceTitle}</TableCell>
<TableCell className="max-w-xs truncate">{service.description}</TableCell>
<TableCell>
<div className="flex items-center space-x-2">
<Switch
checked={service.status === "1"}
onCheckedChange={() => toggleServiceStatus(service)}
disabled={loading}
/>
<Badge variant={service.status === "1" ? "default" : "secondary"}>
{service.status === "1" ? "上架" : "下架"}
</Badge>
</div>
</TableCell>
<TableCell>
<div className="flex flex-wrap gap-1">
{getUsersByIds(service.assignedAdmin || "").length > 0 ? (
<>
{getUsersByIds(service.assignedAdmin).slice(0, 2).map((userId) => (
<Badge key={userId} variant="outline" className="text-xs">
{getUserName(userId, adminUsers)}
</Badge>
))}
{getUsersByIds(service.assignedAdmin).length > 2 && (
<Badge variant="outline" className="text-xs">
+{getUsersByIds(service.assignedAdmin).length - 2}
</Badge>
)}
</>
) : (
<span className="text-xs text-gray-400">-</span>
)}
</div>
</TableHeader>
<TableBody>
{loading ? (
<TableRow>
<TableCell colSpan={6} className="text-center py-8">
...
</TableCell>
<TableCell>
<div className="flex flex-wrap gap-1">
{getUsersByIds(service.assignedCommon || "").length > 0 ? (
<>
{getUsersByIds(service.assignedCommon).slice(0, 2).map((userId) => (
<Badge key={userId} variant="outline" className="text-xs">
{getUserName(userId, commonUsers)}
</Badge>
))}
{getUsersByIds(service.assignedCommon).length > 2 && (
<Badge variant="outline" className="text-xs">
+{getUsersByIds(service.assignedCommon).length - 2}
</Badge>
)}
</>
) : (
<span className="text-xs text-gray-400">-</span>
)}
</div>
</TableRow>
) : services.length === 0 ? (
<TableRow>
<TableCell colSpan={6} className="text-center py-8 text-gray-500">
</TableCell>
<TableCell>{service.createdTime || "-"}</TableCell>
<TableCell>
<div className="flex space-x-2">
<Button variant="outline" size="sm" onClick={() => openEditDialog(service)} disabled={loading}>
<Edit className="h-4 w-4" />
</Button>
<Button variant="outline" size="sm" onClick={() => handleDeleteService(service.id)} disabled={loading}>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</TableRow>
) : (
services.map((service) => {
// 合并 assignedAdmin 和 assignedCommon
const allAssignedUsers = [
...getUsersByIds(service.assignedAdmin || ""),
...getUsersByIds(service.assignedCommon || ""),
]
return (
<TableRow key={service.id}>
<TableCell className="font-medium">{service.serviceTitle}</TableCell>
<TableCell className="max-w-xs truncate">{service.description}</TableCell>
<TableCell>
<div className="flex items-center space-x-2">
<Switch
checked={service.status === "1"}
onCheckedChange={() => toggleServiceStatus(service)}
disabled={loading}
/>
<Badge variant={service.status === "1" ? "default" : "secondary"}>
{service.status === "1" ? "上架" : "下架"}
</Badge>
</div>
</TableCell>
<TableCell>
<div className="flex flex-wrap gap-1">
{allAssignedUsers.length > 0 ? (
<>
{allAssignedUsers.slice(0, 2).map((userId) => (
<Badge key={userId} variant="outline" className="text-xs">
{getUserName(userId)}
</Badge>
))}
{allAssignedUsers.length > 2 && (
<Badge variant="outline" className="text-xs">
+{allAssignedUsers.length - 2}
</Badge>
)}
</>
) : (
<span className="text-xs text-gray-400">-</span>
)}
</div>
</TableCell>
<TableCell>{service.createTime || "-"}</TableCell>
<TableCell>
<div className="flex space-x-2">
<Button variant="outline" size="sm" onClick={() => openEditDialog(service)} disabled={loading}>
<Edit className="h-4 w-4" />
</Button>
<Button variant="outline" size="sm" onClick={() => handleDeleteService(service)} disabled={loading}>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
)
})
)}
</TableBody>
</Table>
</div>
{/* Mobile Card View */}
<div className="md:hidden space-y-4">
{loading ? (
<div className="text-center py-8 text-gray-500">...</div>
) : services.length === 0 ? (
<div className="text-center py-8 text-gray-500"></div>
) : (
services.map((service) => {
// 合并 assignedAdmin 和 assignedCommon
const allAssignedUsers = [
...getUsersByIds(service.assignedAdmin || ""),
...getUsersByIds(service.assignedCommon || ""),
]
return (
<Card key={service.id}>
<CardContent className="p-4 space-y-3">
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0">
<div className="font-medium text-sm truncate">{service.serviceTitle}</div>
<div className="text-xs text-gray-500 mt-1 line-clamp-2">{service.description}</div>
</div>
<div className="flex items-center space-x-2 flex-shrink-0">
<Switch
checked={service.status === "1"}
onCheckedChange={() => toggleServiceStatus(service)}
disabled={loading}
/>
<Badge variant={service.status === "1" ? "default" : "secondary"} className="text-xs">
{service.status === "1" ? "上架" : "下架"}
</Badge>
</div>
</div>
<div className="space-y-2 text-sm">
<div className="flex items-start justify-between">
<span className="text-gray-500">:</span>
<div className="flex flex-wrap gap-1 justify-end flex-1 ml-2">
{allAssignedUsers.length > 0 ? (
<>
{allAssignedUsers.slice(0, 2).map((userId) => (
<Badge key={userId} variant="outline" className="text-xs">
{getUserName(userId)}
</Badge>
))}
{allAssignedUsers.length > 2 && (
<Badge variant="outline" className="text-xs">
+{allAssignedUsers.length - 2}
</Badge>
)}
</>
) : (
<span className="text-xs text-gray-400">-</span>
)}
</div>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-500">:</span>
<span className="font-medium text-xs truncate ml-2">{service.createTime || "-"}</span>
</div>
</div>
<div className="flex space-x-2 pt-2 border-t">
<Button variant="outline" size="sm" onClick={() => openEditDialog(service)} disabled={loading} className="flex-1">
<Edit className="h-4 w-4 mr-1" />
</Button>
<Button variant="outline" size="sm" onClick={() => handleDeleteService(service)} disabled={loading} className="flex-1">
<Trash2 className="h-4 w-4 mr-1" />
</Button>
</div>
</CardContent>
</Card>
)
})
)}
</div>
{/* Pagination */}
<div className="flex items-center justify-between mt-4">
<div className="text-sm text-gray-600">
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4 mt-4 pt-4 border-t">
<div className="text-xs sm:text-sm text-gray-600">
{total} {pageNum} / {totalPages || 1}
</div>
<div className="flex gap-2">
<div className="flex gap-2 w-full sm:w-auto">
<Button
variant="outline"
size="sm"
onClick={handlePrevPage}
disabled={pageNum === 1 || loading}
className="flex-1 sm:flex-initial text-xs sm:text-sm"
>
<ChevronLeft className="h-4 w-4 mr-1" />
@@ -476,6 +534,7 @@ export default function ValueAddedServicesPage() {
size="sm"
onClick={handleNextPage}
disabled={pageNum >= totalPages || loading}
className="flex-1 sm:flex-initial text-xs sm:text-sm"
>
<ChevronRight className="h-4 w-4 ml-1" />
@@ -487,124 +546,139 @@ export default function ValueAddedServicesPage() {
{/* Edit Dialog */}
<Dialog open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription></DialogDescription>
<DialogContent className="max-w-2xl max-h-[95vh] overflow-y-auto w-[95vw] sm:w-full p-3 sm:p-6">
<DialogHeader className="pb-2 sm:pb-4">
<DialogTitle className="text-base sm:text-lg"></DialogTitle>
<DialogDescription className="text-xs sm:text-sm"></DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div>
<Label htmlFor="edit-title"></Label>
<div className="space-y-3 sm:space-y-4">
<div className="space-y-1.5 sm:space-y-2">
<Label htmlFor="edit-title" className="text-sm sm:text-base"></Label>
<Input
id="edit-title"
value={formData.serviceTitle}
onChange={(e) => setFormData({ ...formData, serviceTitle: e.target.value })}
placeholder="请输入服务标题"
className="text-sm sm:text-base"
/>
</div>
<div>
<Label htmlFor="edit-description"></Label>
<div className="space-y-1.5 sm:space-y-2">
<Label htmlFor="edit-description" className="text-sm sm:text-base"></Label>
<Textarea
id="edit-description"
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
placeholder="请输入服务详细描述"
rows={3}
className="text-sm sm:text-base resize-none"
/>
</div>
<div>
<Label></Label>
<div className="grid grid-cols-1 gap-2 mt-2 max-h-48 overflow-y-auto border rounded p-2">
{adminUsers.map((user) => {
const assignedIds = formData.assignedAdmin.split(",").filter(Boolean)
const userId = user.userId
return (
<div key={userId} className="flex items-center space-x-2 p-2 border rounded">
<input
type="checkbox"
id={`edit-admin-${userId}`}
checked={assignedIds.includes(userId)}
onChange={(e) => {
const ids = formData.assignedAdmin.split(",").filter(Boolean)
if (e.target.checked) {
setFormData({
...formData,
assignedAdmin: [...ids, userId].join(","),
})
} else {
setFormData({
...formData,
assignedAdmin: ids.filter((id) => id !== userId).join(","),
})
}
}}
/>
<label htmlFor={`edit-admin-${userId}`} className="flex-1 cursor-pointer">
<div className="flex justify-between">
<span className="font-medium">{user.nickName || user.userName}</span>
<span className="text-sm text-gray-500 flex items-center">
<Phone className="h-3 w-3 mr-1" />
{user.phonenumber}
</span>
</div>
</label>
</div>
)
})}
</div>
</div>
<div>
<Label></Label>
<div className="grid grid-cols-1 gap-2 mt-2 max-h-48 overflow-y-auto border rounded p-2">
{commonUsers.map((user) => {
const assignedIds = formData.assignedCommon.split(",").filter(Boolean)
const userId = user.userId
return (
<div key={userId} className="flex items-center space-x-2 p-2 border rounded">
<input
type="checkbox"
id={`edit-common-${userId}`}
checked={assignedIds.includes(userId)}
onChange={(e) => {
const ids = formData.assignedCommon.split(",").filter(Boolean)
if (e.target.checked) {
setFormData({
...formData,
assignedCommon: [...ids, userId].join(","),
})
} else {
setFormData({
...formData,
assignedCommon: ids.filter((id) => id !== userId).join(","),
})
}
}}
/>
<label htmlFor={`edit-common-${userId}`} className="flex-1 cursor-pointer">
<div className="flex justify-between">
<span className="font-medium">{user.nickName || user.userName}</span>
<span className="text-sm text-gray-500 flex items-center">
<Phone className="h-3 w-3 mr-1" />
{user.phonenumber}
</span>
<div className="space-y-1.5 sm:space-y-2">
<Label className="text-sm sm:text-base"></Label>
<div className="grid grid-cols-1 gap-2 mt-2 max-h-48 sm:max-h-64 overflow-y-auto border rounded p-2">
{users.length === 0 ? (
<div className="text-center py-4 text-sm text-gray-500">...</div>
) : (
users.map((user) => {
const assignedIds = formData.assignedUsers.split(",").filter(Boolean)
const userId = user.userId
return (
<div key={userId} className="flex items-center space-x-2 p-2 sm:p-3 border rounded hover:bg-gray-50">
<input
type="checkbox"
id={`edit-user-${userId}`}
checked={assignedIds.includes(userId)}
onChange={(e) => {
const ids = formData.assignedUsers.split(",").filter(Boolean)
if (e.target.checked) {
setFormData({
...formData,
assignedUsers: [...ids, userId].join(","),
})
} else {
setFormData({
...formData,
assignedUsers: ids.filter((id) => id !== userId).join(","),
})
}
}}
className="flex-shrink-0"
/>
<label htmlFor={`edit-user-${userId}`} className="flex-1 cursor-pointer min-w-0">
<div className="flex flex-col sm:flex-row sm:justify-between gap-1 sm:gap-0">
<span className="font-medium text-sm sm:text-base truncate">{user.nickName || user.userName}</span>
<span className="text-xs sm:text-sm text-gray-500 flex items-center">
<Phone className="h-3 w-3 mr-1 flex-shrink-0" />
<span className="truncate">{user.phonenumber}</span>
</span>
</div>
</label>
</div>
</label>
</div>
)
})}
)
})
)}
</div>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setIsEditDialogOpen(false)} disabled={loading}>
<DialogFooter className="flex-col-reverse sm:flex-row gap-2 pt-2">
<Button variant="outline" onClick={() => setIsEditDialogOpen(false)} disabled={loading} className="w-full sm:w-auto">
</Button>
<Button onClick={handleEditService} disabled={loading}>
<Button onClick={handleEditService} disabled={loading} className="w-full sm:w-auto">
{loading ? "保存中..." : "保存修改"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* Delete Confirmation Dialog */}
<Dialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
<DialogContent className="sm:max-w-[400px] max-h-[95vh] overflow-y-auto w-[95vw] sm:w-full p-3 sm:p-6">
<DialogHeader className="pb-2 sm:pb-4">
<DialogTitle className="text-base sm:text-lg"></DialogTitle>
<DialogDescription className="text-xs sm:text-sm">
"{serviceToDelete?.serviceTitle}"
</DialogDescription>
</DialogHeader>
<div className="py-4">
{serviceToDelete && (
<div className="space-y-2 text-sm">
<div>
<span className="text-gray-500"></span>
<span className="font-medium">{serviceToDelete.serviceTitle}</span>
</div>
{serviceToDelete.description && (
<div>
<span className="text-gray-500"></span>
<span className="font-medium line-clamp-2">{serviceToDelete.description}</span>
</div>
)}
</div>
)}
</div>
<DialogFooter className="flex-col-reverse sm:flex-row gap-2 pt-2">
<Button
variant="outline"
onClick={() => {
setIsDeleteDialogOpen(false)
setServiceToDelete(null)
}}
disabled={loading}
className="w-full sm:w-auto"
>
</Button>
<Button
variant="destructive"
onClick={handleConfirmDelete}
disabled={loading}
className="w-full sm:w-auto"
>
{loading ? "删除中..." : "确认删除"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
)
}

View File

@@ -3,8 +3,8 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui
import { Button } from "../ui/button"
import { Input } from "../ui/input"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../ui/table"
import { Search, Download, User, ChevronLeft, ChevronRight, Plus, Edit } from "lucide-react"
import { apiGet, apiPost, apiPut } from "../../services/api"
import { Search, Download, User, ChevronLeft, ChevronRight, Plus, Edit, Trash2 } from "lucide-react"
import { apiGet, apiPost, apiPut, apiDelete } from "../../services/api"
import { getUserData } from "../../utils/storage"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select"
import {
@@ -46,6 +46,8 @@ export default function SalePage() {
// 新增业务员对话框状态
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false)
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)
const [userToDelete, setUserToDelete] = useState<SaleUser | null>(null)
const [isSubmitting, setIsSubmitting] = useState(false)
const [formData, setFormData] = useState({
userName: "",
@@ -248,6 +250,40 @@ export default function SalePage() {
}
}
// 打开删除确认对话框
const handleDeleteUser = (user: SaleUser) => {
setUserToDelete(user)
setIsDeleteDialogOpen(true)
}
// 确认删除业务员
const handleConfirmDelete = async () => {
if (!userToDelete) return
setIsSubmitting(true)
try {
// 调用删除接口
const response: any = await apiDelete(`/back/sale/${userToDelete.userId}`)
console.log("删除业务员响应:", response)
if (response.code === 200) {
alert("删除业务员成功")
setIsDeleteDialogOpen(false)
setUserToDelete(null)
// 刷新列表
fetchSalesUsers(currentPage, pageSize)
} else {
alert(`删除业务员失败: ${response.msg || "未知错误"}`)
}
} catch (error) {
console.error("删除业务员请求失败:", error)
alert("删除业务员失败,请重试")
} finally {
setIsSubmitting(false)
}
}
// 提交新增业务员
const handleSubmitAdd = async () => {
// 表单验证
@@ -400,14 +436,27 @@ export default function SalePage() {
<div className="text-sm">{user.createTime}</div>
</TableCell>
<TableCell className="text-center">
<Button
variant="ghost"
size="sm"
onClick={() => handleOpenEditDialog(user)}
>
<Edit className="h-4 w-4 mr-1" />
</Button>
<div className="flex items-center justify-center space-x-2">
<Button
variant="ghost"
size="sm"
onClick={() => handleOpenEditDialog(user)}
disabled={loading}
>
<Edit className="h-4 w-4 mr-1" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => handleDeleteUser(user)}
disabled={loading}
className="text-red-600 hover:text-red-700 hover:bg-red-50"
>
<Trash2 className="h-4 w-4 mr-1" />
</Button>
</div>
</TableCell>
</TableRow>
))
@@ -436,15 +485,26 @@ export default function SalePage() {
<div className="text-xs text-gray-500 truncate">{user.nickName}</div>
</div>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => handleOpenEditDialog(user)}
className="flex-shrink-0 h-8 px-2 sm:px-3 text-xs sm:text-sm"
>
<Edit className="h-3.5 w-3.5 sm:h-4 sm:w-4 mr-1" />
</Button>
<div className="flex items-center gap-2 flex-shrink-0">
<Button
variant="ghost"
size="sm"
onClick={() => handleOpenEditDialog(user)}
className="h-8 px-2 sm:px-3 text-xs sm:text-sm"
>
<Edit className="h-3.5 w-3.5 sm:h-4 sm:w-4 mr-1" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => handleDeleteUser(user)}
className="h-8 px-2 sm:px-3 text-xs sm:text-sm text-red-600 hover:text-red-700 hover:bg-red-50"
>
<Trash2 className="h-3.5 w-3.5 sm:h-4 sm:w-4 mr-1" />
</Button>
</div>
</div>
<div className="border-t pt-3 space-y-2">
@@ -711,6 +771,67 @@ export default function SalePage() {
</DialogFooter>
</DialogContent>
</Dialog>
{/* 删除确认对话框 */}
<Dialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
<DialogContent className="sm:max-w-[400px] max-h-[95vh] overflow-y-auto w-[95vw] sm:w-full p-3 sm:p-6">
<DialogHeader className="pb-2 sm:pb-4">
<DialogTitle className="text-base sm:text-lg"></DialogTitle>
<DialogDescription className="text-xs sm:text-sm">
"{userToDelete?.userName || userToDelete?.nickName}"
</DialogDescription>
</DialogHeader>
<div className="py-4">
{userToDelete && (
<div className="space-y-2 text-sm">
<div>
<span className="text-gray-500">ID</span>
<span className="font-medium">{userToDelete.userId}</span>
</div>
<div>
<span className="text-gray-500"></span>
<span className="font-medium">{userToDelete.userName}</span>
</div>
<div>
<span className="text-gray-500"></span>
<span className="font-medium">{userToDelete.nickName}</span>
</div>
<div>
<span className="text-gray-500"></span>
<span className="font-medium">{userToDelete.phonenumber}</span>
</div>
{userToDelete.email && (
<div>
<span className="text-gray-500"></span>
<span className="font-medium">{userToDelete.email}</span>
</div>
)}
</div>
)}
</div>
<DialogFooter className="flex-col-reverse sm:flex-row gap-2 pt-2">
<Button
variant="outline"
onClick={() => {
setIsDeleteDialogOpen(false)
setUserToDelete(null)
}}
disabled={isSubmitting}
className="w-full sm:w-auto"
>
</Button>
<Button
variant="destructive"
onClick={handleConfirmDelete}
disabled={isSubmitting}
className="w-full sm:w-auto"
>
{isSubmitting ? "删除中..." : "确认删除"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
)
}