业务员等

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) => { 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 = const matchesSearch =
item.equipmentName.toLowerCase().includes(searchTerm.toLowerCase()) || equipmentName.toLowerCase().includes(searchLower) ||
item.merchantName.toLowerCase().includes(searchTerm.toLowerCase()) || merchantName.toLowerCase().includes(searchLower) ||
item.mallName.toLowerCase().includes(searchTerm.toLowerCase()) mallName.toLowerCase().includes(searchLower)
// 修正状态筛选逻辑 // 修正状态筛选逻辑
let matchesStatus = true let matchesStatus = true

View File

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

View File

@@ -3,8 +3,8 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui
import { Button } from "../ui/button" import { Button } from "../ui/button"
import { Input } from "../ui/input" import { Input } from "../ui/input"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../ui/table" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../ui/table"
import { Search, Download, User, ChevronLeft, ChevronRight, Plus, Edit } from "lucide-react" import { Search, Download, User, ChevronLeft, ChevronRight, Plus, Edit, Trash2 } from "lucide-react"
import { apiGet, apiPost, apiPut } from "../../services/api" import { apiGet, apiPost, apiPut, apiDelete } from "../../services/api"
import { getUserData } from "../../utils/storage" import { getUserData } from "../../utils/storage"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select"
import { import {
@@ -46,6 +46,8 @@ export default function SalePage() {
// 新增业务员对话框状态 // 新增业务员对话框状态
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false) const [isAddDialogOpen, setIsAddDialogOpen] = useState(false)
const [isEditDialogOpen, setIsEditDialogOpen] = 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 [isSubmitting, setIsSubmitting] = useState(false)
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
userName: "", 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 () => { const handleSubmitAdd = async () => {
// 表单验证 // 表单验证
@@ -400,14 +436,27 @@ export default function SalePage() {
<div className="text-sm">{user.createTime}</div> <div className="text-sm">{user.createTime}</div>
</TableCell> </TableCell>
<TableCell className="text-center"> <TableCell className="text-center">
<Button <div className="flex items-center justify-center space-x-2">
variant="ghost" <Button
size="sm" variant="ghost"
onClick={() => handleOpenEditDialog(user)} size="sm"
> onClick={() => handleOpenEditDialog(user)}
<Edit className="h-4 w-4 mr-1" /> disabled={loading}
>
</Button> <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> </TableCell>
</TableRow> </TableRow>
)) ))
@@ -436,15 +485,26 @@ export default function SalePage() {
<div className="text-xs text-gray-500 truncate">{user.nickName}</div> <div className="text-xs text-gray-500 truncate">{user.nickName}</div>
</div> </div>
</div> </div>
<Button <div className="flex items-center gap-2 flex-shrink-0">
variant="ghost" <Button
size="sm" variant="ghost"
onClick={() => handleOpenEditDialog(user)} size="sm"
className="flex-shrink-0 h-8 px-2 sm:px-3 text-xs sm:text-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" /> >
<Edit className="h-3.5 w-3.5 sm:h-4 sm:w-4 mr-1" />
</Button>
</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>
<div className="border-t pt-3 space-y-2"> <div className="border-t pt-3 space-y-2">
@@ -711,6 +771,67 @@ export default function SalePage() {
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </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> </div>
) )
} }