添加设置模块修复网关信息MAC地址跳转问题,删除冗余的文件和代码
This commit is contained in:
@@ -8,16 +8,13 @@ import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
|
||||
import { Lock, Phone } from 'lucide-react'
|
||||
import { Lock, User } from 'lucide-react'
|
||||
import { useAuthStore } from '@/store/auth'
|
||||
import { toast, Toaster } from 'sonner'
|
||||
|
||||
const formSchema = z.object({
|
||||
phone: z.string()
|
||||
.min(11, '手机号必须是11位')
|
||||
.max(11, '手机号必须是11位')
|
||||
.regex(/^1[3-9]\d{9}$/, '请输入有效的手机号'),
|
||||
password: z.string().min(6, '密码至少需要6个字符'),
|
||||
account: z.string().min(3, '账号至少需要3个字符'),
|
||||
password: z.string().min(6, '密码至少需要6个字符'),
|
||||
})
|
||||
|
||||
export default function LoginPage() {
|
||||
@@ -27,7 +24,7 @@ export default function LoginPage() {
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
phone: '',
|
||||
account: '',
|
||||
password: '',
|
||||
},
|
||||
})
|
||||
@@ -78,18 +75,17 @@ export default function LoginPage() {
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="phone"
|
||||
name="account"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>手机号</FormLabel>
|
||||
<FormLabel>账号</FormLabel>
|
||||
<FormControl>
|
||||
<div className="relative">
|
||||
<Phone className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<User className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="请输入手机号"
|
||||
placeholder="请输入您的账号"
|
||||
className="pl-8"
|
||||
{...field}
|
||||
maxLength={11}
|
||||
/>
|
||||
</div>
|
||||
</FormControl>
|
||||
|
||||
@@ -4,23 +4,22 @@ import { compare } from 'bcryptjs'
|
||||
import { z } from 'zod'
|
||||
|
||||
const loginSchema = z.object({
|
||||
phone: z.string()
|
||||
.min(11, '手机号必须是11位')
|
||||
.max(11, '手机号必须是11位')
|
||||
.regex(/^1[3-9]\d{9}$/, '请输入有效的手机号'),
|
||||
account: z.string().min(3, '账号至少需要3个字符'),
|
||||
password: z.string().min(6, '密码至少需要6个字符'),
|
||||
})
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { phone, password } = loginSchema.parse(body)
|
||||
const { account, password } = loginSchema.parse(body)
|
||||
|
||||
|
||||
// 查找用户 - 使用正确的查询方式
|
||||
const user = await prisma.user.findUnique({
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
phone: phone.trim() // 去除空格
|
||||
OR: [
|
||||
{ account: account.trim() },
|
||||
{ password: account.trim() }
|
||||
]
|
||||
},
|
||||
})
|
||||
|
||||
@@ -57,7 +56,7 @@ export async function POST(request: Request) {
|
||||
success: true,
|
||||
user: {
|
||||
id: user.id,
|
||||
phone: user.phone,
|
||||
account: user.account,
|
||||
name: user.name
|
||||
}
|
||||
})
|
||||
|
||||
132
src/app/api/users/route.tsx
Normal file
132
src/app/api/users/route.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { hash } from 'bcryptjs'
|
||||
|
||||
// 获取所有用户
|
||||
export async function GET() {
|
||||
try {
|
||||
const users = await prisma.user.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
account: true,
|
||||
name: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
users
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取用户列表错误:', error)
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '服务器错误,请稍后重试' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const { account, password, name } = await request.json()
|
||||
|
||||
// 检查用户是否已存在
|
||||
const existingUser = await prisma.user.findUnique({
|
||||
where: { account }
|
||||
})
|
||||
|
||||
if (existingUser) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '用户账号已存在' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// 加密密码
|
||||
const hashedPassword = await hash(password, 10)
|
||||
|
||||
// 创建用户
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
account,
|
||||
password: hashedPassword,
|
||||
name: name || account,
|
||||
},
|
||||
})
|
||||
|
||||
// 不返回密码字段
|
||||
const { password: _, ...userWithoutPassword } = user
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
user: userWithoutPassword
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建用户错误:', error)
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '服务器错误,请稍后重试' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 删除用户
|
||||
export async function DELETE(request: Request) {
|
||||
try {
|
||||
// 从URL中获取查询参数
|
||||
const url = new URL(request.url)
|
||||
const id = url.searchParams.get('id')
|
||||
|
||||
if (!id) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '用户ID不能为空' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const userId = parseInt(id)
|
||||
if (isNaN(userId)) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '无效的用户ID' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// 检查用户是否存在
|
||||
const existingUser = await prisma.user.findUnique({
|
||||
where: { id: userId }
|
||||
})
|
||||
|
||||
if (!existingUser) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '用户不存在' },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
// 删除用户
|
||||
await prisma.user.delete({
|
||||
where: { id: userId }
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '用户删除成功'
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('删除用户错误:', error)
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '服务器错误,请稍后重试' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
333
src/app/dashboard/components/settings.tsx
Normal file
333
src/app/dashboard/components/settings.tsx
Normal file
@@ -0,0 +1,333 @@
|
||||
'use client'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import * as z from 'zod'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
|
||||
import { User, Lock, Search, Trash2, Plus, X } from 'lucide-react'
|
||||
import { toast, Toaster } from 'sonner'
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table"
|
||||
|
||||
// 用户类型定义
|
||||
interface UserData {
|
||||
id: string
|
||||
account: string
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
const formSchema = z.object({
|
||||
account: z.string().min(3, '账号至少需要3个字符'),
|
||||
password: z.string().min(6, '密码至少需要6个字符'),
|
||||
confirmPassword: z.string().min(6, '密码至少需要6个字符'),
|
||||
}).refine((data) => data.password === data.confirmPassword, {
|
||||
message: "密码不匹配",
|
||||
path: ["confirmPassword"],
|
||||
})
|
||||
|
||||
|
||||
export default function Settings() {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [users, setUsers] = useState<UserData[]>([])
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [isCreateMode, setIsCreateMode] = useState(false)
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
account: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
// 获取用户列表
|
||||
const fetchUsers = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/users')
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || '获取用户列表失败')
|
||||
}
|
||||
|
||||
if (data.success) {
|
||||
setUsers(data.users)
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error("获取用户列表失败", {
|
||||
description: error instanceof Error ? error.message : "服务器连接失败,请稍后重试",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 初始加载用户列表
|
||||
useEffect(() => {
|
||||
fetchUsers()
|
||||
}, [])
|
||||
|
||||
// 创建用户
|
||||
async function onSubmit(values: z.infer<typeof formSchema>) {
|
||||
setLoading(true)
|
||||
try {
|
||||
const response = await fetch('/api/users', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
account: values.account,
|
||||
password: values.password,
|
||||
}),
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || '创建用户失败')
|
||||
}
|
||||
|
||||
if (data.success) {
|
||||
toast.success("用户创建成功", {
|
||||
description: "新账户已成功添加",
|
||||
})
|
||||
form.reset()
|
||||
setIsCreateMode(false)
|
||||
fetchUsers() // 刷新用户列表
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error("创建用户失败", {
|
||||
description: error instanceof Error ? error.message : "服务器连接失败,请稍后重试",
|
||||
})
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 删除用户
|
||||
const handleDeleteUser = async (userId: number) => {
|
||||
|
||||
if (!confirm('确定要删除这个用户吗?此操作不可恢复。')) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 使用查询参数传递ID
|
||||
const response = await fetch(`/api/users?id=${userId}`, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || '删除用户失败')
|
||||
}
|
||||
|
||||
if (data.success) {
|
||||
toast.success("用户删除成功", {
|
||||
description: "用户账户已删除",
|
||||
})
|
||||
fetchUsers() // 刷新用户列表
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error("删除用户失败", {
|
||||
description: error instanceof Error ? error.message : "服务器连接失败,请稍后重试",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤用户列表
|
||||
const filteredUsers = users.filter(user =>
|
||||
user.account.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white p-4 md:p-8">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h1 className="text-3xl font-bold">用户管理</h1>
|
||||
<Button onClick={() => setIsCreateMode(!isCreateMode)}>
|
||||
{isCreateMode ? (
|
||||
<>
|
||||
<X className="mr-2 h-4 w-4" /> 取消
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Plus className="mr-2 h-4 w-4" /> 添加用户
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{isCreateMode && (
|
||||
<Card className="mb-6">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl">添加用户账号</CardTitle>
|
||||
<CardDescription>创建新的系统用户账户</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="account"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>账号</FormLabel>
|
||||
<FormControl>
|
||||
<div className="relative">
|
||||
<User className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="请输入需要添加的账号"
|
||||
className="pl-8"
|
||||
{...field}
|
||||
/>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>密码</FormLabel>
|
||||
<FormControl>
|
||||
<div className="relative">
|
||||
<Lock className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
className="pl-8"
|
||||
{...field}
|
||||
/>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="confirmPassword"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>确认密码</FormLabel>
|
||||
<FormControl>
|
||||
<div className="relative">
|
||||
<Lock className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="请再次输入密码"
|
||||
className="pl-8"
|
||||
{...field}
|
||||
/>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
|
||||
创建中...
|
||||
</div>
|
||||
) : (
|
||||
'创建用户'
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setIsCreateMode(false)}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>用户列表</CardTitle>
|
||||
<CardDescription>
|
||||
管理系统中的所有用户账户
|
||||
</CardDescription>
|
||||
<div className="relative mt-4 max-w-sm">
|
||||
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="搜索用户..."
|
||||
className="pl-8"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>账号</TableHead>
|
||||
<TableHead>创建时间</TableHead>
|
||||
<TableHead className="text-right">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredUsers.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} className="text-center py-4">
|
||||
暂无用户数据
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
filteredUsers.map((user) => (
|
||||
<TableRow key={user.id}>
|
||||
<TableCell className="font-medium">{user.account}</TableCell>
|
||||
<TableCell>{new Date(user.createdAt).toLocaleDateString()}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handleDeleteUser(Number(user.id))}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<Toaster richColors />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import Gatewayinfo from './components/gatewayinfo'
|
||||
import GatewayConfig from './components/gatewayConfig'
|
||||
import CityNodeStats from './components/cityNodeStats'
|
||||
import AllocationStatus from './components/allocationStatus'
|
||||
import Settings from './components/settings'
|
||||
import Edge from './components/edge'
|
||||
import { LogOut } from 'lucide-react'
|
||||
|
||||
@@ -14,24 +15,23 @@ const tabs = [
|
||||
{ id: 'gateway', label: '网关配置' },
|
||||
{ id: 'city', label: '城市信息' },
|
||||
{ id: 'allocation', label: '分配状态' },
|
||||
{ id: 'edge', label: '节点信息' }
|
||||
{ id: 'edge', label: '节点信息' },
|
||||
{ id: 'setting', label: '设置'}
|
||||
]
|
||||
|
||||
export default function Dashboard() {
|
||||
const [activeTab, setActiveTab] = useState('gatewayInfo')
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
// 从 URL 中获取 tab 参数
|
||||
// 监听URL参数变化
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const urlTab = urlParams.get('tab')
|
||||
if (urlTab && tabs.some(tab => tab.id === urlTab)) {
|
||||
setActiveTab(urlTab)
|
||||
}
|
||||
const urlTab = searchParams.get('tab')
|
||||
if (urlTab && tabs.some(tab => tab.id === urlTab)) {
|
||||
setActiveTab(urlTab)
|
||||
}
|
||||
}, [])
|
||||
}, [searchParams])
|
||||
|
||||
// 退出登录
|
||||
const handleLogout = async () => {
|
||||
@@ -58,7 +58,9 @@ export default function Dashboard() {
|
||||
const handleTabClick = (tabId: string) => {
|
||||
setActiveTab(tabId)
|
||||
// 更新 URL 参数
|
||||
router.push(`/dashboard?tab=${tabId}`)
|
||||
const params = new URLSearchParams(searchParams.toString())
|
||||
params.set('tab', tabId)
|
||||
router.push(`/dashboard?${params.toString()}`)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -108,6 +110,7 @@ export default function Dashboard() {
|
||||
{activeTab === 'city' && <CityNodeStats />}
|
||||
{activeTab === 'allocation' && <AllocationStatus detailed />}
|
||||
{activeTab === 'edge' && <Edge />}
|
||||
{activeTab === 'setting' && <Settings/>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user