超级管理员后台
This commit is contained in:
137
SuperAdmin/app/dashboard/admins/[id]/edit/page.tsx
Normal file
137
SuperAdmin/app/dashboard/admins/[id]/edit/page.tsx
Normal file
@@ -0,0 +1,137 @@
|
||||
"use client"
|
||||
|
||||
import type React from "react"
|
||||
|
||||
import { useState } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { ArrowLeft } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
|
||||
// Sample admin data for editing
|
||||
const adminData = {
|
||||
id: "2",
|
||||
username: "admin_li",
|
||||
name: "李管理",
|
||||
permissions: ["project_management", "customer_pool"],
|
||||
}
|
||||
|
||||
export default function EditAdminPage({ params }: { params: { id: string } }) {
|
||||
const router = useRouter()
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [username, setUsername] = useState(adminData.username)
|
||||
const [name, setName] = useState(adminData.name)
|
||||
|
||||
const permissions = [
|
||||
{ id: "project_management", label: "项目管理" },
|
||||
{ id: "customer_pool", label: "客户池" },
|
||||
{ id: "admin_management", label: "管理员权限" },
|
||||
]
|
||||
|
||||
const [selectedPermissions, setSelectedPermissions] = useState<string[]>(adminData.permissions)
|
||||
|
||||
const togglePermission = (permissionId: string) => {
|
||||
setSelectedPermissions((prev) =>
|
||||
prev.includes(permissionId) ? prev.filter((id) => id !== permissionId) : [...prev, permissionId],
|
||||
)
|
||||
}
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setIsSubmitting(true)
|
||||
|
||||
// Simulate API call
|
||||
setTimeout(() => {
|
||||
setIsSubmitting(false)
|
||||
router.push("/dashboard/admins")
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" size="icon" asChild>
|
||||
<Link href="/dashboard/admins">
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
<h1 className="text-2xl font-bold">编辑管理员</h1>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>管理员信息</CardTitle>
|
||||
<CardDescription>编辑管理员账号信息和权限</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="username">账号</Label>
|
||||
<Input
|
||||
id="username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
placeholder="请输入账号"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">姓名</Label>
|
||||
<Input
|
||||
id="name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="请输入姓名"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">重置密码</Label>
|
||||
<Input id="password" type="password" placeholder="留空则不修改密码" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="confirmPassword">确认密码</Label>
|
||||
<Input id="confirmPassword" type="password" placeholder="留空则不修改密码" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<Label>权限设置</Label>
|
||||
<div className="grid gap-2">
|
||||
{permissions.map((permission) => (
|
||||
<div key={permission.id} className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id={permission.id}
|
||||
checked={selectedPermissions.includes(permission.id)}
|
||||
onCheckedChange={() => togglePermission(permission.id)}
|
||||
/>
|
||||
<Label htmlFor={permission.id} className="cursor-pointer">
|
||||
{permission.label}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="flex justify-end gap-2">
|
||||
<Button variant="outline" asChild>
|
||||
<Link href="/dashboard/admins">取消</Link>
|
||||
</Button>
|
||||
<Button type="submit" disabled={isSubmitting}>
|
||||
{isSubmitting ? "保存中..." : "保存修改"}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
4
SuperAdmin/app/dashboard/admins/loading.tsx
Normal file
4
SuperAdmin/app/dashboard/admins/loading.tsx
Normal file
@@ -0,0 +1,4 @@
|
||||
export default function Loading() {
|
||||
return null
|
||||
}
|
||||
|
||||
115
SuperAdmin/app/dashboard/admins/new/page.tsx
Normal file
115
SuperAdmin/app/dashboard/admins/new/page.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
"use client"
|
||||
|
||||
import type React from "react"
|
||||
|
||||
import { useState } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { ArrowLeft } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
|
||||
export default function NewAdminPage() {
|
||||
const router = useRouter()
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
|
||||
const permissions = [
|
||||
{ id: "project_management", label: "项目管理" },
|
||||
{ id: "customer_pool", label: "客户池" },
|
||||
{ id: "admin_management", label: "管理员权限" },
|
||||
]
|
||||
|
||||
const [selectedPermissions, setSelectedPermissions] = useState<string[]>([])
|
||||
|
||||
const togglePermission = (permissionId: string) => {
|
||||
setSelectedPermissions((prev) =>
|
||||
prev.includes(permissionId) ? prev.filter((id) => id !== permissionId) : [...prev, permissionId],
|
||||
)
|
||||
}
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setIsSubmitting(true)
|
||||
|
||||
// Simulate API call
|
||||
setTimeout(() => {
|
||||
setIsSubmitting(false)
|
||||
router.push("/dashboard/admins")
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" size="icon" asChild>
|
||||
<Link href="/dashboard/admins">
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
<h1 className="text-2xl font-bold">新增管理员</h1>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>管理员信息</CardTitle>
|
||||
<CardDescription>创建新管理员账号并设置权限</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="username">账号</Label>
|
||||
<Input id="username" placeholder="请输入账号" required />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">姓名</Label>
|
||||
<Input id="name" placeholder="请输入姓名" required />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">密码</Label>
|
||||
<Input id="password" type="password" placeholder="请设置密码" required />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="confirmPassword">确认密码</Label>
|
||||
<Input id="confirmPassword" type="password" placeholder="请再次输入密码" required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<Label>权限设置</Label>
|
||||
<div className="grid gap-2">
|
||||
{permissions.map((permission) => (
|
||||
<div key={permission.id} className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id={permission.id}
|
||||
checked={selectedPermissions.includes(permission.id)}
|
||||
onCheckedChange={() => togglePermission(permission.id)}
|
||||
/>
|
||||
<Label htmlFor={permission.id} className="cursor-pointer">
|
||||
{permission.label}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="flex justify-end gap-2">
|
||||
<Button variant="outline" asChild>
|
||||
<Link href="/dashboard/admins">取消</Link>
|
||||
</Button>
|
||||
<Button type="submit" disabled={isSubmitting}>
|
||||
{isSubmitting ? "创建中..." : "创建管理员"}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
154
SuperAdmin/app/dashboard/admins/page.tsx
Normal file
154
SuperAdmin/app/dashboard/admins/page.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
"use client"
|
||||
|
||||
import { useState } 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 { Search, MoreHorizontal, Edit, Trash, UserPlus } from "lucide-react"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
|
||||
// Sample admin data
|
||||
const adminsData = [
|
||||
{
|
||||
id: "1",
|
||||
username: "admin_zhang",
|
||||
name: "张管理",
|
||||
role: "超级管理员",
|
||||
permissions: ["项目管理", "客户池", "管理员权限"],
|
||||
createdAt: "2023-05-01",
|
||||
lastLogin: "2023-06-28 09:15",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
username: "admin_li",
|
||||
name: "李管理",
|
||||
role: "项目管理员",
|
||||
permissions: ["项目管理", "客户池"],
|
||||
createdAt: "2023-05-10",
|
||||
lastLogin: "2023-06-27 14:30",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
username: "admin_wang",
|
||||
name: "王管理",
|
||||
role: "客户管理员",
|
||||
permissions: ["客户池"],
|
||||
createdAt: "2023-05-15",
|
||||
lastLogin: "2023-06-28 11:45",
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
username: "admin_zhao",
|
||||
name: "赵管理",
|
||||
role: "项目管理员",
|
||||
permissions: ["项目管理"],
|
||||
createdAt: "2023-05-20",
|
||||
lastLogin: "2023-06-26 16:20",
|
||||
},
|
||||
]
|
||||
|
||||
export default function AdminsPage() {
|
||||
const [searchTerm, setSearchTerm] = useState("")
|
||||
|
||||
const filteredAdmins = adminsData.filter(
|
||||
(admin) =>
|
||||
admin.username.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
admin.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
admin.role.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between">
|
||||
<h1 className="text-2xl font-bold">管理员列表</h1>
|
||||
<Button asChild>
|
||||
<Link href="/dashboard/admins/new">
|
||||
<UserPlus 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>最后登录</TableHead>
|
||||
<TableHead className="text-right">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredAdmins.length > 0 ? (
|
||||
filteredAdmins.map((admin) => (
|
||||
<TableRow key={admin.id}>
|
||||
<TableCell className="font-medium">{admin.username}</TableCell>
|
||||
<TableCell>{admin.name}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={admin.role === "超级管理员" ? "default" : "outline"}>{admin.role}</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{admin.permissions.map((permission) => (
|
||||
<Badge key={permission} variant="secondary">
|
||||
{permission}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{admin.createdAt}</TableCell>
|
||||
<TableCell>{admin.lastLogin}</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/admins/${admin.id}/edit`}>
|
||||
<Edit className="mr-2 h-4 w-4" /> 编辑管理员
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="text-destructive">
|
||||
<Trash className="mr-2 h-4 w-4" /> 删除管理员
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={7} className="h-24 text-center">
|
||||
未找到管理员
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
202
SuperAdmin/app/dashboard/customers/[id]/page.tsx
Normal file
202
SuperAdmin/app/dashboard/customers/[id]/page.tsx
Normal file
@@ -0,0 +1,202 @@
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
||||
import { ArrowLeft, MessageSquare, Phone, UserPlus } from "lucide-react"
|
||||
|
||||
// Sample customer data
|
||||
const customerData = {
|
||||
id: "1",
|
||||
name: "张三",
|
||||
avatar: "/placeholder.svg?height=100&width=100",
|
||||
wechatId: "zhangsan123",
|
||||
gender: "男",
|
||||
region: "北京",
|
||||
source: "微信搜索",
|
||||
tags: ["潜在客户", "高消费"],
|
||||
projectName: "电商平台项目",
|
||||
addedDate: "2023-06-10",
|
||||
devices: [
|
||||
{ id: "d1", name: "iPhone 13 Pro", addedDate: "2023-06-10" },
|
||||
{ id: "d2", name: "MacBook Pro", addedDate: "2023-06-12" },
|
||||
],
|
||||
interactions: [
|
||||
{
|
||||
id: "i1",
|
||||
type: "消息",
|
||||
content: "您好,我对您的产品很感兴趣,能详细介绍一下吗?",
|
||||
date: "2023-06-15 14:30",
|
||||
},
|
||||
{
|
||||
id: "i2",
|
||||
type: "电话",
|
||||
content: "讨论产品细节和价格",
|
||||
duration: "15分钟",
|
||||
date: "2023-06-16 10:45",
|
||||
},
|
||||
{
|
||||
id: "i3",
|
||||
type: "消息",
|
||||
content: "谢谢您的详细介绍,我考虑一下再联系您。",
|
||||
date: "2023-06-16 16:20",
|
||||
},
|
||||
{
|
||||
id: "i4",
|
||||
type: "消息",
|
||||
content: "我决定购买您的产品,请问如何下单?",
|
||||
date: "2023-06-18 09:15",
|
||||
},
|
||||
],
|
||||
transactions: [
|
||||
{
|
||||
id: "t1",
|
||||
product: "高级会员套餐",
|
||||
amount: "¥1,299",
|
||||
status: "已完成",
|
||||
date: "2023-06-20",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default function CustomerDetailPage({ params }: { params: { id: string } }) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" size="icon" asChild>
|
||||
<Link href="/dashboard/customers">
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
<h1 className="text-2xl font-bold">客户详情</h1>
|
||||
</div>
|
||||
<Button>
|
||||
<UserPlus className="mr-2 h-4 w-4" /> 分发客户
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-3">
|
||||
<Card className="md:col-span-1">
|
||||
<CardHeader>
|
||||
<CardTitle>基本信息</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col items-center text-center">
|
||||
<Avatar className="h-24 w-24 mb-4">
|
||||
<AvatarImage src={customerData.avatar} alt={customerData.name} />
|
||||
<AvatarFallback>{customerData.name.slice(0, 2)}</AvatarFallback>
|
||||
</Avatar>
|
||||
<h3 className="text-xl font-bold">{customerData.name}</h3>
|
||||
<p className="text-sm text-muted-foreground mb-4">{customerData.wechatId}</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-1 mb-6">
|
||||
{customerData.tags.map((tag) => (
|
||||
<Badge key={tag} variant="outline">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="w-full space-y-2 text-left">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-muted-foreground">性别</span>
|
||||
<span className="text-sm">{customerData.gender}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-muted-foreground">地区</span>
|
||||
<span className="text-sm">{customerData.region}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-muted-foreground">来源</span>
|
||||
<span className="text-sm">{customerData.source}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-muted-foreground">所属项目</span>
|
||||
<span className="text-sm">{customerData.projectName}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-muted-foreground">添加时间</span>
|
||||
<span className="text-sm">{customerData.addedDate}</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="md:col-span-2">
|
||||
<CardHeader>
|
||||
<CardTitle>详细信息</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Tabs defaultValue="interactions" className="space-y-4">
|
||||
<TabsList>
|
||||
<TabsTrigger value="interactions">互动记录</TabsTrigger>
|
||||
<TabsTrigger value="devices">关联设备</TabsTrigger>
|
||||
<TabsTrigger value="transactions">交易记录</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="interactions" className="space-y-4">
|
||||
{customerData.interactions.map((interaction) => (
|
||||
<div key={interaction.id} className="flex gap-4 pb-4 border-b last:border-0">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-muted">
|
||||
{interaction.type === "消息" ? (
|
||||
<MessageSquare className="h-5 w-5" />
|
||||
) : (
|
||||
<Phone className="h-5 w-5" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 space-y-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm font-medium">{interaction.type}</p>
|
||||
<p className="text-xs text-muted-foreground">{interaction.date}</p>
|
||||
</div>
|
||||
<p className="text-sm">{interaction.content}</p>
|
||||
{interaction.duration && (
|
||||
<p className="text-xs text-muted-foreground">通话时长: {interaction.duration}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="devices">
|
||||
{customerData.devices.map((device) => (
|
||||
<div key={device.id} className="flex items-center justify-between py-2 border-b last:border-0">
|
||||
<div>
|
||||
<p className="font-medium">{device.name}</p>
|
||||
<p className="text-xs text-muted-foreground">添加时间: {device.addedDate}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="transactions">
|
||||
{customerData.transactions.length > 0 ? (
|
||||
customerData.transactions.map((transaction) => (
|
||||
<div key={transaction.id} className="flex items-center justify-between py-2 border-b last:border-0">
|
||||
<div>
|
||||
<p className="font-medium">{transaction.product}</p>
|
||||
<p className="text-xs text-muted-foreground">交易日期: {transaction.date}</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="font-medium">{transaction.amount}</p>
|
||||
<Badge variant={transaction.status === "已完成" ? "success" : "outline"}>
|
||||
{transaction.status}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<p className="text-center text-muted-foreground py-4">暂无交易记录</p>
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
4
SuperAdmin/app/dashboard/customers/loading.tsx
Normal file
4
SuperAdmin/app/dashboard/customers/loading.tsx
Normal file
@@ -0,0 +1,4 @@
|
||||
export default function Loading() {
|
||||
return null
|
||||
}
|
||||
|
||||
310
SuperAdmin/app/dashboard/customers/page.tsx
Normal file
310
SuperAdmin/app/dashboard/customers/page.tsx
Normal file
@@ -0,0 +1,310 @@
|
||||
"use client"
|
||||
|
||||
import { useState } 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,
|
||||
DropdownMenuSeparator,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Search, MoreHorizontal, Eye, UserPlus, Filter } from "lucide-react"
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
|
||||
// Sample customer data
|
||||
const customersData = [
|
||||
{
|
||||
id: "1",
|
||||
name: "张三",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
wechatId: "zhangsan123",
|
||||
gender: "男",
|
||||
region: "北京",
|
||||
source: "微信搜索",
|
||||
tags: ["潜在客户", "高消费"],
|
||||
projectName: "电商平台项目",
|
||||
addedDate: "2023-06-10",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "李四",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
wechatId: "lisi456",
|
||||
gender: "男",
|
||||
region: "上海",
|
||||
source: "朋友推荐",
|
||||
tags: ["活跃用户"],
|
||||
projectName: "社交媒体营销",
|
||||
addedDate: "2023-06-12",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "王五",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
wechatId: "wangwu789",
|
||||
gender: "男",
|
||||
region: "广州",
|
||||
source: "广告点击",
|
||||
tags: ["新用户"],
|
||||
projectName: "企业官网推广",
|
||||
addedDate: "2023-06-15",
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "赵六",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
wechatId: "zhaoliu321",
|
||||
gender: "男",
|
||||
region: "深圳",
|
||||
source: "线下活动",
|
||||
tags: ["高消费", "忠诚客户"],
|
||||
projectName: "教育平台项目",
|
||||
addedDate: "2023-06-18",
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
name: "钱七",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
wechatId: "qianqi654",
|
||||
gender: "女",
|
||||
region: "成都",
|
||||
source: "微信群",
|
||||
tags: ["潜在客户"],
|
||||
projectName: "金融服务推广",
|
||||
addedDate: "2023-06-20",
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
name: "孙八",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
wechatId: "sunba987",
|
||||
gender: "女",
|
||||
region: "武汉",
|
||||
source: "微信搜索",
|
||||
tags: ["活跃用户", "高消费"],
|
||||
projectName: "电商平台项目",
|
||||
addedDate: "2023-06-22",
|
||||
},
|
||||
{
|
||||
id: "7",
|
||||
name: "周九",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
wechatId: "zhoujiu135",
|
||||
gender: "女",
|
||||
region: "杭州",
|
||||
source: "朋友推荐",
|
||||
tags: ["新用户"],
|
||||
projectName: "社交媒体营销",
|
||||
addedDate: "2023-06-25",
|
||||
},
|
||||
]
|
||||
|
||||
export default function CustomersPage() {
|
||||
const [searchTerm, setSearchTerm] = useState("")
|
||||
const [selectedRegion, setSelectedRegion] = useState("")
|
||||
const [selectedGender, setSelectedGender] = useState("")
|
||||
const [selectedSource, setSelectedSource] = useState("")
|
||||
const [selectedProject, setSelectedProject] = useState("")
|
||||
|
||||
// Filter customers based on search and filters
|
||||
const filteredCustomers = customersData.filter((customer) => {
|
||||
const matchesSearch =
|
||||
customer.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
customer.wechatId.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
|
||||
const matchesRegion = selectedRegion ? customer.region === selectedRegion : true
|
||||
const matchesGender = selectedGender ? customer.gender === selectedGender : true
|
||||
const matchesSource = selectedSource ? customer.source === selectedSource : true
|
||||
const matchesProject = selectedProject ? customer.projectName === selectedProject : true
|
||||
|
||||
return matchesSearch && matchesRegion && matchesGender && matchesSource && matchesProject
|
||||
})
|
||||
|
||||
// Get unique values for filters
|
||||
const regions = [...new Set(customersData.map((c) => c.region))]
|
||||
const sources = [...new Set(customersData.map((c) => c.source))]
|
||||
const projects = [...new Set(customersData.map((c) => c.projectName))]
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between">
|
||||
<h1 className="text-2xl font-bold">客户池</h1>
|
||||
<Button>
|
||||
<UserPlus className="mr-2 h-4 w-4" /> 批量分发
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<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="搜索客户名称或微信ID..."
|
||||
className="pl-8"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline">
|
||||
<Filter className="mr-2 h-4 w-4" /> 筛选
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-[200px]">
|
||||
<div className="p-2">
|
||||
<p className="mb-2 text-sm font-medium">地区</p>
|
||||
<Select value={selectedRegion} onValueChange={setSelectedRegion}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="所有地区" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">所有地区</SelectItem>
|
||||
{regions.map((region) => (
|
||||
<SelectItem key={region} value={region}>
|
||||
{region}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<DropdownMenuSeparator />
|
||||
<div className="p-2">
|
||||
<p className="mb-2 text-sm font-medium">性别</p>
|
||||
<Select value={selectedGender} onValueChange={setSelectedGender}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="所有性别" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">所有性别</SelectItem>
|
||||
<SelectItem value="男">男</SelectItem>
|
||||
<SelectItem value="女">女</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<DropdownMenuSeparator />
|
||||
<div className="p-2">
|
||||
<p className="mb-2 text-sm font-medium">来源</p>
|
||||
<Select value={selectedSource} onValueChange={setSelectedSource}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="所有来源" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">所有来源</SelectItem>
|
||||
{sources.map((source) => (
|
||||
<SelectItem key={source} value={source}>
|
||||
{source}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<DropdownMenuSeparator />
|
||||
<div className="p-2">
|
||||
<p className="mb-2 text-sm font-medium">所属项目</p>
|
||||
<Select value={selectedProject} onValueChange={setSelectedProject}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="所有项目" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">所有项目</SelectItem>
|
||||
{projects.map((project) => (
|
||||
<SelectItem key={project} value={project}>
|
||||
{project}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>客户信息</TableHead>
|
||||
<TableHead>微信ID</TableHead>
|
||||
<TableHead>标签</TableHead>
|
||||
<TableHead>地区</TableHead>
|
||||
<TableHead>来源</TableHead>
|
||||
<TableHead>所属项目</TableHead>
|
||||
<TableHead>添加时间</TableHead>
|
||||
<TableHead className="text-right">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredCustomers.length > 0 ? (
|
||||
filteredCustomers.map((customer) => (
|
||||
<TableRow key={customer.id}>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar>
|
||||
<AvatarImage src={customer.avatar} alt={customer.name} />
|
||||
<AvatarFallback>{customer.name.slice(0, 2)}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div>
|
||||
<div className="font-medium">{customer.name}</div>
|
||||
<div className="text-xs text-muted-foreground">{customer.gender}</div>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{customer.wechatId}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{customer.tags.map((tag) => (
|
||||
<Badge key={tag} variant="outline">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{customer.region}</TableCell>
|
||||
<TableCell>{customer.source}</TableCell>
|
||||
<TableCell>{customer.projectName}</TableCell>
|
||||
<TableCell>{customer.addedDate}</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/customers/${customer.id}`}>
|
||||
<Eye className="mr-2 h-4 w-4" /> 查看详情
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<UserPlus className="mr-2 h-4 w-4" /> 分发客户
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} className="h-24 text-center">
|
||||
未找到客户
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
101
SuperAdmin/app/dashboard/layout.tsx
Normal file
101
SuperAdmin/app/dashboard/layout.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
"use client"
|
||||
|
||||
import type React from "react"
|
||||
|
||||
import { useState } from "react"
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { LayoutDashboard, Users, Settings, LogOut, Menu, X } from "lucide-react"
|
||||
|
||||
export default function DashboardLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const [sidebarOpen, setSidebarOpen] = useState(true)
|
||||
const pathname = usePathname()
|
||||
|
||||
const navItems = [
|
||||
{
|
||||
title: "项目管理",
|
||||
href: "/dashboard/projects",
|
||||
icon: <LayoutDashboard className="h-5 w-5" />,
|
||||
},
|
||||
{
|
||||
title: "客户池",
|
||||
href: "/dashboard/customers",
|
||||
icon: <Users className="h-5 w-5" />,
|
||||
},
|
||||
{
|
||||
title: "管理员权限",
|
||||
href: "/dashboard/admins",
|
||||
icon: <Settings className="h-5 w-5" />,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden">
|
||||
{/* Mobile sidebar toggle */}
|
||||
<div className="fixed top-4 left-4 z-50 md:hidden">
|
||||
<Button variant="outline" size="icon" onClick={() => setSidebarOpen(!sidebarOpen)}>
|
||||
{sidebarOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Sidebar */}
|
||||
<div
|
||||
className={`bg-primary text-primary-foreground w-64 flex-shrink-0 transition-all duration-300 ease-in-out ${
|
||||
sidebarOpen ? "translate-x-0" : "-translate-x-full"
|
||||
} md:translate-x-0 fixed md:relative z-40 h-full`}
|
||||
>
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex items-center justify-center h-16 border-b border-primary/10">
|
||||
<h1 className="text-xl font-bold">超级管理员后台</h1>
|
||||
</div>
|
||||
<nav className="flex-1 overflow-y-auto py-4">
|
||||
<ul className="space-y-1 px-2">
|
||||
{navItems.map((item) => (
|
||||
<li key={item.href}>
|
||||
<Link
|
||||
href={item.href}
|
||||
className={`flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors hover:bg-primary-foreground hover:text-primary ${
|
||||
pathname.startsWith(item.href) ? "bg-primary-foreground text-primary" : ""
|
||||
}`}
|
||||
>
|
||||
{item.icon}
|
||||
{item.title}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
<div className="border-t border-primary/10 p-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full justify-start gap-2 bg-transparent text-primary-foreground hover:bg-primary-foreground hover:text-primary"
|
||||
onClick={() => {
|
||||
// Handle logout
|
||||
window.location.href = "/login"
|
||||
}}
|
||||
>
|
||||
<LogOut className="h-5 w-5" />
|
||||
退出登录
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main content */}
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<header className="h-16 border-b flex items-center px-6 bg-background">
|
||||
<h2 className="text-lg font-medium">
|
||||
{navItems.find((item) => pathname.startsWith(item.href))?.title || "仪表盘"}
|
||||
</h2>
|
||||
</header>
|
||||
<main className="p-6">{children}</main>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
47
SuperAdmin/app/dashboard/page.tsx
Normal file
47
SuperAdmin/app/dashboard/page.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Users, FolderKanban, UserCog } from "lucide-react"
|
||||
|
||||
export default function DashboardPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<h1 className="text-3xl font-bold">欢迎使用超级管理员后台</h1>
|
||||
<p className="text-muted-foreground">通过此平台,您可以管理项目、客户和管理员权限。</p>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">项目总数</CardTitle>
|
||||
<FolderKanban className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">24</div>
|
||||
<p className="text-xs text-muted-foreground">较上月增长 12%</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">客户总数</CardTitle>
|
||||
<Users className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">1,284</div>
|
||||
<p className="text-xs text-muted-foreground">较上月增长 8%</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">管理员数量</CardTitle>
|
||||
<UserCog className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">8</div>
|
||||
<p className="text-xs text-muted-foreground">较上月增长 2 人</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
159
SuperAdmin/app/dashboard/projects/[id]/edit/page.tsx
Normal file
159
SuperAdmin/app/dashboard/projects/[id]/edit/page.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
"use client"
|
||||
|
||||
import type React from "react"
|
||||
|
||||
import { useState } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { ArrowLeft, Plus, Trash } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
|
||||
// Sample project data for editing
|
||||
const projectData = {
|
||||
id: "1",
|
||||
name: "电商平台项目",
|
||||
phone: "13800138000",
|
||||
account: "ecommerce_admin",
|
||||
description: "这是一个电商平台推广项目,主要针对年轻用户群体,通过微信社交渠道进行产品推广和销售转化。",
|
||||
devices: [
|
||||
{ id: "d1", name: "iPhone 13 Pro" },
|
||||
{ id: "d2", name: "Huawei P40" },
|
||||
{ id: "d3", name: "Samsung S21" },
|
||||
],
|
||||
}
|
||||
|
||||
export default function EditProjectPage({ params }: { params: { id: string } }) {
|
||||
const router = useRouter()
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [projectName, setProjectName] = useState(projectData.name)
|
||||
const [phone, setPhone] = useState(projectData.phone)
|
||||
const [account, setAccount] = useState(projectData.account)
|
||||
const [description, setDescription] = useState(projectData.description)
|
||||
const [devices, setDevices] = useState(projectData.devices)
|
||||
|
||||
const handleAddDevice = () => {
|
||||
setDevices([...devices, { id: Date.now().toString(), name: "" }])
|
||||
}
|
||||
|
||||
const handleRemoveDevice = (id: string) => {
|
||||
setDevices(devices.filter((device) => device.id !== id))
|
||||
}
|
||||
|
||||
const handleDeviceChange = (id: string, value: string) => {
|
||||
setDevices(devices.map((device) => (device.id === id ? { ...device, name: value } : device)))
|
||||
}
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setIsSubmitting(true)
|
||||
|
||||
// Simulate API call
|
||||
setTimeout(() => {
|
||||
setIsSubmitting(false)
|
||||
router.push(`/dashboard/projects/${params.id}`)
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" size="icon" asChild>
|
||||
<Link href={`/dashboard/projects/${params.id}`}>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
<h1 className="text-2xl font-bold">编辑项目</h1>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>项目基本信息</CardTitle>
|
||||
<CardDescription>编辑项目的名称、手机号等基础信息</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="projectName">项目名称</Label>
|
||||
<Input
|
||||
id="projectName"
|
||||
value={projectName}
|
||||
onChange={(e) => setProjectName(e.target.value)}
|
||||
placeholder="请输入项目名称"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="phone">手机号</Label>
|
||||
<Input
|
||||
id="phone"
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
placeholder="请输入手机号"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="account">账号</Label>
|
||||
<Input
|
||||
id="account"
|
||||
value={account}
|
||||
onChange={(e) => setAccount(e.target.value)}
|
||||
placeholder="请输入账号"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>关联设备</Label>
|
||||
<div className="space-y-3">
|
||||
{devices.map((device, index) => (
|
||||
<div key={device.id} className="flex items-center gap-2">
|
||||
<Input
|
||||
placeholder={`设备 ${index + 1} 名称`}
|
||||
value={device.name}
|
||||
onChange={(e) => handleDeviceChange(device.id, e.target.value)}
|
||||
/>
|
||||
<Button type="button" variant="outline" size="icon" onClick={() => handleRemoveDevice(device.id)}>
|
||||
<Trash className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button type="button" variant="outline" onClick={handleAddDevice} className="flex items-center gap-1">
|
||||
<Plus className="h-4 w-4" /> 添加设备
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="description">项目介绍</Label>
|
||||
<Textarea
|
||||
id="description"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder="请输入项目介绍(选填)"
|
||||
rows={4}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="flex justify-end gap-2">
|
||||
<Button variant="outline" asChild>
|
||||
<Link href={`/dashboard/projects/${params.id}`}>取消</Link>
|
||||
</Button>
|
||||
<Button type="submit" disabled={isSubmitting}>
|
||||
{isSubmitting ? "保存中..." : "保存修改"}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
179
SuperAdmin/app/dashboard/projects/[id]/page.tsx
Normal file
179
SuperAdmin/app/dashboard/projects/[id]/page.tsx
Normal file
@@ -0,0 +1,179 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import Link from "next/link"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||
import { ArrowLeft, Edit } from "lucide-react"
|
||||
|
||||
// Sample project data
|
||||
const projectData = {
|
||||
id: "1",
|
||||
name: "电商平台项目",
|
||||
phone: "13800138000",
|
||||
account: "ecommerce_admin",
|
||||
description: "这是一个电商平台推广项目,主要针对年轻用户群体,通过微信社交渠道进行产品推广和销售转化。",
|
||||
createdAt: "2023-05-15",
|
||||
devices: [
|
||||
{ id: "d1", name: "iPhone 13 Pro", wechatFriends: 120 },
|
||||
{ id: "d2", name: "Huawei P40", wechatFriends: 85 },
|
||||
{ id: "d3", name: "Samsung S21", wechatFriends: 40 },
|
||||
],
|
||||
subAccounts: [
|
||||
{ id: "a1", username: "sales_team1", createdAt: "2023-05-16" },
|
||||
{ id: "a2", username: "sales_team2", createdAt: "2023-05-16" },
|
||||
{ id: "a3", username: "marketing_team", createdAt: "2023-05-17" },
|
||||
{ id: "a4", username: "support_team", createdAt: "2023-05-18" },
|
||||
{ id: "a5", username: "admin_assistant", createdAt: "2023-05-20" },
|
||||
],
|
||||
}
|
||||
|
||||
export default function ProjectDetailPage({ params }: { params: { id: string } }) {
|
||||
const [activeTab, setActiveTab] = useState("overview")
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" size="icon" asChild>
|
||||
<Link href="/dashboard/projects">
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
<h1 className="text-2xl font-bold">{projectData.name}</h1>
|
||||
</div>
|
||||
<Button asChild>
|
||||
<Link href={`/dashboard/projects/${params.id}/edit`}>
|
||||
<Edit className="mr-2 h-4 w-4" /> 编辑项目
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-4">
|
||||
<TabsList>
|
||||
<TabsTrigger value="overview">项目概览</TabsTrigger>
|
||||
<TabsTrigger value="devices">关联设备</TabsTrigger>
|
||||
<TabsTrigger value="accounts">子账号</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="overview" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>基本信息</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<dl className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-muted-foreground">项目名称</dt>
|
||||
<dd className="text-sm">{projectData.name}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-muted-foreground">手机号</dt>
|
||||
<dd className="text-sm">{projectData.phone}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-muted-foreground">账号</dt>
|
||||
<dd className="text-sm">{projectData.account}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-muted-foreground">创建时间</dt>
|
||||
<dd className="text-sm">{projectData.createdAt}</dd>
|
||||
</div>
|
||||
<div className="sm:col-span-2">
|
||||
<dt className="text-sm font-medium text-muted-foreground">项目介绍</dt>
|
||||
<dd className="text-sm">{projectData.description}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium">关联设备数</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{projectData.devices.length}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium">子账号数</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{projectData.subAccounts.length}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium">微信好友总数</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">
|
||||
{projectData.devices.reduce((sum, device) => sum + device.wechatFriends, 0)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="devices">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>关联设备列表</CardTitle>
|
||||
<CardDescription>项目关联的所有设备及其微信好友数量</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>设备名称</TableHead>
|
||||
<TableHead className="text-right">微信好友数量</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{projectData.devices.map((device) => (
|
||||
<TableRow key={device.id}>
|
||||
<TableCell className="font-medium">{device.name}</TableCell>
|
||||
<TableCell className="text-right">{device.wechatFriends}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="accounts">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>子账号列表</CardTitle>
|
||||
<CardDescription>项目下的所有子账号</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>账号名称</TableHead>
|
||||
<TableHead className="text-right">创建时间</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{projectData.subAccounts.map((account) => (
|
||||
<TableRow key={account.id}>
|
||||
<TableCell className="font-medium">{account.username}</TableCell>
|
||||
<TableCell className="text-right">{account.createdAt}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
4
SuperAdmin/app/dashboard/projects/loading.tsx
Normal file
4
SuperAdmin/app/dashboard/projects/loading.tsx
Normal file
@@ -0,0 +1,4 @@
|
||||
export default function Loading() {
|
||||
return null
|
||||
}
|
||||
|
||||
129
SuperAdmin/app/dashboard/projects/new/page.tsx
Normal file
129
SuperAdmin/app/dashboard/projects/new/page.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
"use client"
|
||||
|
||||
import type React from "react"
|
||||
|
||||
import { useState } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { ArrowLeft, Plus, Trash } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
|
||||
export default function NewProjectPage() {
|
||||
const router = useRouter()
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [devices, setDevices] = useState([{ id: "1", name: "" }])
|
||||
|
||||
const handleAddDevice = () => {
|
||||
setDevices([...devices, { id: Date.now().toString(), name: "" }])
|
||||
}
|
||||
|
||||
const handleRemoveDevice = (id: string) => {
|
||||
setDevices(devices.filter((device) => device.id !== id))
|
||||
}
|
||||
|
||||
const handleDeviceChange = (id: string, value: string) => {
|
||||
setDevices(devices.map((device) => (device.id === id ? { ...device, name: value } : device)))
|
||||
}
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setIsSubmitting(true)
|
||||
|
||||
// Simulate API call
|
||||
setTimeout(() => {
|
||||
setIsSubmitting(false)
|
||||
router.push("/dashboard/projects")
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" size="icon" asChild>
|
||||
<Link href="/dashboard/projects">
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
<h1 className="text-2xl font-bold">新建项目</h1>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>项目基本信息</CardTitle>
|
||||
<CardDescription>创建新项目需要填写项目名称并设置账号信息</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="projectName">项目名称</Label>
|
||||
<Input id="projectName" placeholder="请输入项目名称" required />
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="phone">手机号</Label>
|
||||
<Input id="phone" placeholder="请输入手机号" required />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="account">账号</Label>
|
||||
<Input id="account" placeholder="请输入账号" required />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">初始密码</Label>
|
||||
<Input id="password" type="password" placeholder="请设置初始密码" required />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="confirmPassword">确认密码</Label>
|
||||
<Input id="confirmPassword" type="password" placeholder="请再次输入密码" required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>关联设备</Label>
|
||||
<div className="space-y-3">
|
||||
{devices.map((device, index) => (
|
||||
<div key={device.id} className="flex items-center gap-2">
|
||||
<Input
|
||||
placeholder={`设备 ${index + 1} 名称`}
|
||||
value={device.name}
|
||||
onChange={(e) => handleDeviceChange(device.id, e.target.value)}
|
||||
/>
|
||||
{devices.length > 1 && (
|
||||
<Button type="button" variant="outline" size="icon" onClick={() => handleRemoveDevice(device.id)}>
|
||||
<Trash className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<Button type="button" variant="outline" onClick={handleAddDevice} className="flex items-center gap-1">
|
||||
<Plus className="h-4 w-4" /> 添加设备
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="description">项目介绍</Label>
|
||||
<Textarea id="description" placeholder="请输入项目介绍(选填)" rows={4} />
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="flex justify-end gap-2">
|
||||
<Button variant="outline" asChild>
|
||||
<Link href="/dashboard/projects">取消</Link>
|
||||
</Button>
|
||||
<Button type="submit" disabled={isSubmitting}>
|
||||
{isSubmitting ? "创建中..." : "创建项目"}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
147
SuperAdmin/app/dashboard/projects/page.tsx
Normal file
147
SuperAdmin/app/dashboard/projects/page.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
"use client"
|
||||
|
||||
import { useState } 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 } from "lucide-react"
|
||||
|
||||
// Sample project data
|
||||
const projectsData = [
|
||||
{
|
||||
id: "1",
|
||||
name: "电商平台项目",
|
||||
phone: "13800138000",
|
||||
accountCount: 5,
|
||||
deviceCount: 12,
|
||||
wechatFriends: 245,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "社交媒体营销",
|
||||
phone: "13900139000",
|
||||
accountCount: 8,
|
||||
deviceCount: 20,
|
||||
wechatFriends: 567,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "企业官网推广",
|
||||
phone: "13700137000",
|
||||
accountCount: 3,
|
||||
deviceCount: 8,
|
||||
wechatFriends: 120,
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "教育平台项目",
|
||||
phone: "13600136000",
|
||||
accountCount: 10,
|
||||
deviceCount: 25,
|
||||
wechatFriends: 780,
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
name: "金融服务推广",
|
||||
phone: "13500135000",
|
||||
accountCount: 6,
|
||||
deviceCount: 15,
|
||||
wechatFriends: 320,
|
||||
},
|
||||
]
|
||||
|
||||
export default function ProjectsPage() {
|
||||
const [searchTerm, setSearchTerm] = useState("")
|
||||
|
||||
const filteredProjects = projectsData.filter(
|
||||
(project) => project.name.toLowerCase().includes(searchTerm.toLowerCase()) || project.phone.includes(searchTerm),
|
||||
)
|
||||
|
||||
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 className="text-center">关联设备数</TableHead>
|
||||
<TableHead className="text-center">子账号数</TableHead>
|
||||
<TableHead className="text-center">微信好友总数</TableHead>
|
||||
<TableHead className="text-right">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredProjects.length > 0 ? (
|
||||
filteredProjects.map((project) => (
|
||||
<TableRow key={project.id}>
|
||||
<TableCell className="font-medium">{project.name}</TableCell>
|
||||
<TableCell>{project.phone}</TableCell>
|
||||
<TableCell className="text-center">{project.deviceCount}</TableCell>
|
||||
<TableCell className="text-center">{project.accountCount}</TableCell>
|
||||
<TableCell className="text-center">{project.wechatFriends}</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-destructive">
|
||||
<Trash className="mr-2 h-4 w-4" /> 删除项目
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6} className="h-24 text-center">
|
||||
未找到项目
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
87
SuperAdmin/app/globals.css
Normal file
87
SuperAdmin/app/globals.css
Normal file
@@ -0,0 +1,87 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
|
||||
--primary: 221.2 83.2% 53.3%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 221.2 83.2% 53.3%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
|
||||
--sidebar: 221.2 83.2% 53.3%;
|
||||
--sidebar-foreground: 210 40% 98%;
|
||||
--sidebar-primary: 0 0% 100%;
|
||||
--sidebar-primary-foreground: 222.2 84% 4.9%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
|
||||
--primary: 217.2 91.2% 59.8%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 224.3 76.3% 48%;
|
||||
|
||||
--sidebar: 222.2 84% 4.9%;
|
||||
--sidebar-foreground: 210 40% 98%;
|
||||
--sidebar-primary: 217.2 91.2% 59.8%;
|
||||
--sidebar-primary-foreground: 222.2 47.4% 11.2%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
33
SuperAdmin/app/layout.tsx
Normal file
33
SuperAdmin/app/layout.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import type React from "react"
|
||||
import type { Metadata } from "next"
|
||||
import { Inter } from "next/font/google"
|
||||
import "./globals.css"
|
||||
import { ThemeProvider } from "@/components/theme-provider"
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] })
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "超级管理员后台系统",
|
||||
description: "管理项目、客户和管理员权限的综合平台",
|
||||
generator: 'v0.dev'
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
return (
|
||||
<html lang="zh-CN" suppressHydrationWarning>
|
||||
<body className={inter.className}>
|
||||
<ThemeProvider attribute="class" defaultTheme="light" enableSystem disableTransitionOnChange>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
import './globals.css'
|
||||
70
SuperAdmin/app/login/page.tsx
Normal file
70
SuperAdmin/app/login/page.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
"use client"
|
||||
|
||||
import type React from "react"
|
||||
|
||||
import { useState } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Label } from "@/components/ui/label"
|
||||
|
||||
export default function LoginPage() {
|
||||
const [username, setUsername] = useState("")
|
||||
const [password, setPassword] = useState("")
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const router = useRouter()
|
||||
|
||||
const handleLogin = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setIsLoading(true)
|
||||
|
||||
// Simulate login API call
|
||||
setTimeout(() => {
|
||||
setIsLoading(false)
|
||||
router.push("/dashboard")
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-screen w-full items-center justify-center bg-gray-50">
|
||||
<Card className="w-[400px]">
|
||||
<CardHeader className="space-y-1">
|
||||
<CardTitle className="text-2xl text-center">超级管理员后台</CardTitle>
|
||||
<CardDescription className="text-center">请输入您的账号和密码登录系统</CardDescription>
|
||||
</CardHeader>
|
||||
<form onSubmit={handleLogin}>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="username">账号</Label>
|
||||
<Input
|
||||
id="username"
|
||||
placeholder="请输入账号"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">密码</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button className="w-full" type="submit" disabled={isLoading}>
|
||||
{isLoading ? "登录中..." : "登录"}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</form>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user