Files
cunkebao_v3/SuperAdmin/app/dashboard/projects/page.tsx
2025-04-21 14:36:06 +08:00

274 lines
9.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useState, useEffect } from "react"
import Link from "next/link"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { Plus, Search, MoreHorizontal, Edit, Eye, Trash, ChevronLeft, ChevronRight } from "lucide-react"
import { toast } from "sonner"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { Badge } from "@/components/ui/badge"
interface Project {
id: number
name: string
status: number
tenantId: number
companyId: number
memo: string | null
userCount: number
createTime: string
deviceCount: number
}
export default function ProjectsPage() {
const [searchTerm, setSearchTerm] = useState("")
const [projects, setProjects] = useState<Project[]>([])
const [isLoading, setIsLoading] = useState(true)
const [currentPage, setCurrentPage] = useState(1)
const [totalPages, setTotalPages] = useState(1)
const [pageSize] = useState(10)
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
const [deletingProjectId, setDeletingProjectId] = useState<number | null>(null)
const [isDeleting, setIsDeleting] = useState(false)
// 获取项目列表
useEffect(() => {
const fetchProjects = async () => {
setIsLoading(true)
try {
const response = await fetch(`http://yishi.com/company/list?page=${currentPage}&limit=${pageSize}`)
const data = await response.json()
if (data.code === 200) {
setProjects(data.data.list)
setTotalPages(Math.ceil(data.data.total / pageSize))
} else {
toast.error(data.msg || "获取项目列表失败")
}
} catch (error) {
toast.error("获取项目列表失败")
} finally {
setIsLoading(false)
}
}
fetchProjects()
}, [currentPage, pageSize])
// 切换页码
const handlePageChange = (page: number) => {
if (page >= 1 && page <= totalPages) {
setCurrentPage(page)
}
}
const handleDeleteClick = (projectId: number) => {
setDeletingProjectId(projectId)
setDeleteDialogOpen(true)
}
const handleConfirmDelete = async () => {
if (!deletingProjectId) return
setIsDeleting(true)
try {
const response = await fetch("http://yishi.com/company/delete", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
id: deletingProjectId
}),
})
const data = await response.json()
if (data.code === 200) {
toast.success("删除成功")
// 刷新项目列表
window.location.reload()
} else {
toast.error(data.msg || "删除失败")
}
} catch (error) {
toast.error("网络错误,请稍后重试")
} finally {
setIsDeleting(false)
setDeleteDialogOpen(false)
setDeletingProjectId(null)
}
}
return (
<div className="space-y-6">
<div className="flex justify-between">
<h1 className="text-2xl font-bold"></h1>
<Button asChild>
<Link href="/dashboard/projects/new">
<Plus className="mr-2 h-4 w-4" />
</Link>
</Button>
</div>
<div className="flex items-center gap-2">
<div className="relative flex-1">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
type="search"
placeholder="搜索项目名称..."
className="pl-8"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
</div>
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead className="text-right"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{isLoading ? (
<TableRow>
<TableCell colSpan={5} className="h-24 text-center">
...
</TableCell>
</TableRow>
) : projects.length > 0 ? (
projects.map((project) => (
<TableRow key={project.id}>
<TableCell className="font-medium">{project.name}</TableCell>
<TableCell>
<Badge variant={project.status === 1 ? "default" : "secondary"}>
{project.status === 1 ? "启用" : "禁用"}
</Badge>
</TableCell>
<TableCell className="text-center">{project.userCount}</TableCell>
<TableCell className="text-center">{project.deviceCount}</TableCell>
<TableCell className="text-center">{project.createTime}</TableCell>
<TableCell className="text-right">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only"></span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem asChild>
<Link href={`/dashboard/projects/${project.id}`}>
<Eye className="mr-2 h-4 w-4" />
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link href={`/dashboard/projects/${project.id}/edit`}>
<Edit className="mr-2 h-4 w-4" />
</Link>
</DropdownMenuItem>
<DropdownMenuItem
className="text-red-600"
onClick={() => handleDeleteClick(project.id)}
>
<Trash className="mr-2 h-4 w-4" />
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={5} className="h-24 text-center">
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
{/* 分页控件 */}
{!isLoading && projects.length > 0 && (
<div className="flex items-center justify-center gap-2">
<Button
variant="outline"
size="icon"
onClick={() => handlePageChange(currentPage - 1)}
disabled={currentPage === 1}
>
<ChevronLeft className="h-4 w-4" />
</Button>
{Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (
<Button
key={page}
variant={page === currentPage ? "default" : "outline"}
size="icon"
onClick={() => handlePageChange(page)}
>
{page}
</Button>
))}
<Button
variant="outline"
size="icon"
onClick={() => handlePageChange(currentPage + 1)}
disabled={currentPage === totalPages}
>
<ChevronRight className="h-4 w-4" />
</Button>
</div>
)}
{/* 删除确认对话框 */}
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription>
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
variant="outline"
onClick={() => setDeleteDialogOpen(false)}
disabled={isDeleting}
>
</Button>
<Button
variant="destructive"
onClick={handleConfirmDelete}
disabled={isDeleting}
>
{isDeleting ? "删除中..." : "确认删除"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
)
}