From 8485ff5139b5cfa23496c502cdcb3e9583a2e404 Mon Sep 17 00:00:00 2001 From: Asuka <15019597+asuka-civil@user.noreply.gitee.com> Date: Fri, 20 Jun 2025 20:18:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9A=82=E6=97=B6=E4=BF=9D=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../management/actions/changePassword.ts | 42 +++++ .../(app)/management/actions/getUserInfo.ts | 19 +++ src/app/(app)/management/actions/index.ts | 4 + .../management/actions/updateUserInfo.ts | 25 +++ .../(app)/management/change-password/page.tsx | 125 +++++++++++++++ src/app/(app)/management/page.tsx | 90 +++++++++++ src/app/(app)/management/profile/page.tsx | 150 ++++++++++++++++++ .../dashboard/layout.tsx} | 34 ++-- src/app/(protected)/dashboard/page.tsx | 3 + .../management-sidebar/manage-form.tsx | 28 ++++ .../management-sidebar/manage-sidebar.tsx | 97 +++++++++++ .../management-sidebar/manage-switcher.tsx | 64 ++++++++ src/components/nav-user.tsx | 21 ++- src/components/sidebar/admin-sidebar.tsx | 108 +++++++++++++ src/components/sidebar/app-sidebar.tsx | 84 +++++++--- src/components/sidebar/teacher-sidebar.tsx | 8 +- src/components/ui/donut-chart.tsx | 58 ------- src/lib/auth.ts | 5 + 18 files changed, 856 insertions(+), 109 deletions(-) create mode 100644 src/app/(app)/management/actions/changePassword.ts create mode 100644 src/app/(app)/management/actions/getUserInfo.ts create mode 100644 src/app/(app)/management/actions/index.ts create mode 100644 src/app/(app)/management/actions/updateUserInfo.ts create mode 100644 src/app/(app)/management/change-password/page.tsx create mode 100644 src/app/(app)/management/page.tsx create mode 100644 src/app/(app)/management/profile/page.tsx rename src/app/{dashboard/page.tsx => (protected)/dashboard/layout.tsx} (61%) create mode 100644 src/app/(protected)/dashboard/page.tsx create mode 100644 src/components/management-sidebar/manage-form.tsx create mode 100644 src/components/management-sidebar/manage-sidebar.tsx create mode 100644 src/components/management-sidebar/manage-switcher.tsx create mode 100644 src/components/sidebar/admin-sidebar.tsx delete mode 100644 src/components/ui/donut-chart.tsx diff --git a/src/app/(app)/management/actions/changePassword.ts b/src/app/(app)/management/actions/changePassword.ts new file mode 100644 index 0000000..018db7a --- /dev/null +++ b/src/app/(app)/management/actions/changePassword.ts @@ -0,0 +1,42 @@ +// changePassword.ts +"use server"; + +import prisma from "@/lib/prisma"; +import bcrypt from "bcryptjs"; + +export async function changePassword(formData: FormData) { + const oldPassword = formData.get("oldPassword") as string; + const newPassword = formData.get("newPassword") as string; + + if (!oldPassword || !newPassword) { + throw new Error("旧密码和新密码不能为空"); + } + + try { + const user = await prisma.user.findUnique({ + where: { id: '1' }, + }); + + if (!user) throw new Error("用户不存在"); + + if (!user.password) { + throw new Error("用户密码未设置"); + } + + const passwordHash: string = user.password as string; + const isMatch = await bcrypt.compare(oldPassword, passwordHash); + if (!isMatch) throw new Error("旧密码错误"); + + const hashedPassword = await bcrypt.hash(newPassword, 10); + + await prisma.user.update({ + where: { id: '1' }, + data: { password: hashedPassword }, + }); + + return { success: true }; + } catch (error) { + console.error("修改密码失败:", error); + throw new Error("修改密码失败"); + } +} \ No newline at end of file diff --git a/src/app/(app)/management/actions/getUserInfo.ts b/src/app/(app)/management/actions/getUserInfo.ts new file mode 100644 index 0000000..dd83390 --- /dev/null +++ b/src/app/(app)/management/actions/getUserInfo.ts @@ -0,0 +1,19 @@ +// getUserInfo.ts +"use server"; + +import prisma from "@/lib/prisma"; + +export async function getUserInfo() { + try { + const user = await prisma.user.findUnique({ + where: { id: 'user_001' }, + }); + + if (!user) throw new Error("用户不存在"); + + return user; + } catch (error) { + console.error("获取用户信息失败:", error); + throw new Error("获取用户信息失败"); + } +} \ No newline at end of file diff --git a/src/app/(app)/management/actions/index.ts b/src/app/(app)/management/actions/index.ts new file mode 100644 index 0000000..5c599e5 --- /dev/null +++ b/src/app/(app)/management/actions/index.ts @@ -0,0 +1,4 @@ +// index.ts +export { getUserInfo } from "./getUserInfo"; +export { updateUserInfo } from "./updateUserInfo"; +export { changePassword } from "./changePassword"; \ No newline at end of file diff --git a/src/app/(app)/management/actions/updateUserInfo.ts b/src/app/(app)/management/actions/updateUserInfo.ts new file mode 100644 index 0000000..201284e --- /dev/null +++ b/src/app/(app)/management/actions/updateUserInfo.ts @@ -0,0 +1,25 @@ +// updateUserInfo.ts +"use server"; + +import prisma from "@/lib/prisma"; + +export async function updateUserInfo(formData: FormData) { + const name = formData.get("name") as string; + const email = formData.get("email") as string; + + if (!name || !email) { + throw new Error("缺少必要字段:name, email"); + } + + try { + const updatedUser = await prisma.user.update({ + where: { id: 'user_001' }, + data: { name, email }, + }); + + return updatedUser; + } catch (error) { + console.error("更新用户信息失败:", error); + throw new Error("更新用户信息失败"); + } +} \ No newline at end of file diff --git a/src/app/(app)/management/change-password/page.tsx b/src/app/(app)/management/change-password/page.tsx new file mode 100644 index 0000000..3b9b770 --- /dev/null +++ b/src/app/(app)/management/change-password/page.tsx @@ -0,0 +1,125 @@ +// src/app/(app)/management/change-password/page.tsx +"use client"; + +import { useState } from "react"; +import { changePassword } from "@/app/(app)/management/actions"; + +export default function ChangePasswordPage() { + const [oldPassword, setOldPassword] = useState(""); + const [newPassword, setNewPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [showSuccess, setShowSuccess] = useState(false); + + const getPasswordStrength = (password: string) => { + if (password.length < 6) return "weak"; + if (/[A-Za-z]/.test(password) && /\d/.test(password)) return "medium"; + return "strong"; + }; + + const strengthText = getPasswordStrength(newPassword); + let strengthColor = ""; + let strengthLabel = ""; + + switch (strengthText) { + case "weak": + strengthColor = "bg-red-500"; + strengthLabel = "弱"; + break; + case "medium": + strengthColor = "bg-yellow-500"; + strengthLabel = "中等"; + break; + case "strong": + strengthColor = "bg-green-500"; + strengthLabel = "强"; + break; + } + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (newPassword !== confirmPassword) { + alert("两次输入的密码不一致!"); + return; + } + + const formData = new FormData(); + formData.append("oldPassword", oldPassword); + formData.append("newPassword", newPassword); + + try { + await changePassword(formData); + setShowSuccess(true); + setTimeout(() => setShowSuccess(false), 3000); + } catch (error: any) { + alert(error.message); + } + }; + + return ( +
+
+

修改密码

+
+
+ + setOldPassword(e.target.value)} + className="w-full border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" + required + /> +
+ +
+ + setNewPassword(e.target.value)} + className="w-full border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" + required + /> + {newPassword && ( +

+ 密码强度: + +   + {strengthLabel} +

+ )} +
+ +
+ + setConfirmPassword(e.target.value)} + className="w-full border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" + required + /> + {newPassword && confirmPassword && newPassword !== confirmPassword && ( +

密码不一致

+ )} +
+ +
+ +
+
+
+ + {showSuccess && ( +
+ ✅ 密码修改成功! +
+ )} +
+ ); +} \ No newline at end of file diff --git a/src/app/(app)/management/page.tsx b/src/app/(app)/management/page.tsx new file mode 100644 index 0000000..03a8f8c --- /dev/null +++ b/src/app/(app)/management/page.tsx @@ -0,0 +1,90 @@ +"use client" +import React, { useState } from "react" +import { AppSidebar } from "@/components/management-sidebar/manage-sidebar" +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator, +} from "@/components/ui/breadcrumb" +import { Separator } from "@/components/ui/separator" +import { + SidebarInset, + SidebarProvider, + SidebarTrigger, +} from "@/components/ui/sidebar" +import ProfilePage from "./profile/page" +import ChangePasswordPage from "./change-password/page" + +// 模拟菜单数据 +const menuItems = [ + { title: "登录信息", key: "profile" }, + { title: "修改密码", key: "change-password" }, +] + +export default function ManagementDefaultPage() { + const [activePage, setActivePage] = useState("profile") + const [isCollapsed, setIsCollapsed] = useState(false) + + const renderContent = () => { + switch (activePage) { + case "profile": + return + case "change-password": + return + default: + return + } + } + + const toggleSidebar = () => { + setIsCollapsed((prev) => !prev) + } + + return ( + +
+ {/* 左侧侧边栏 */} + {!isCollapsed && ( +
+ +
+ )} + + {/* 右侧主内容区域 */} + +
+ {/* 折叠按钮 */} + + + + {/* 面包屑导航 */} + + + + 管理面板 + + + + + {menuItems.find((item) => item.key === activePage)?.title} + + + + +
+ {/* 主体内容:根据 isCollapsed 切换样式 */} +
+ {renderContent()} +
+
+
+
+ ) +} \ No newline at end of file diff --git a/src/app/(app)/management/profile/page.tsx b/src/app/(app)/management/profile/page.tsx new file mode 100644 index 0000000..4659aba --- /dev/null +++ b/src/app/(app)/management/profile/page.tsx @@ -0,0 +1,150 @@ +// src/app/(app)/management/profile/page.tsx +"use client"; + +import { useEffect, useState } from "react"; +import { getUserInfo, updateUserInfo } from "@/app/(app)/management/actions"; + +interface User { + id: string; // TEXT 类型 + name: string | null; // 可能为空 + email: string; // NOT NULL + emailVerified: Date | null; // TIMESTAMP 转换为字符串 + image: string | null; + role: "GUEST" | "USER" | "ADMIN"; // 枚举类型 + createdAt: Date; // TIMESTAMP 转换为字符串 + updatedAt: Date; // TIMESTAMP 转换为字符串 +} + +export default function ProfilePage() { + const [user, setUser] = useState(null); + const [isEditing, setIsEditing] = useState(false); + + useEffect(() => { + async function fetchUser() { + try { + const data = await getUserInfo(); + setUser(data); + } catch (error) { + console.error("获取用户信息失败:", error); + } + } + + fetchUser(); + }, []); + + const handleSave = async () => { + const nameInput = document.getElementById("name") as HTMLInputElement | null; + const emailInput = document.getElementById("email") as HTMLInputElement | null; + + if (!nameInput || !emailInput) { + alert("表单元素缺失"); + return; + } + + const formData = new FormData(); + formData.append("name", nameInput.value); + formData.append("email", emailInput.value); + + try { + const updatedUser = await updateUserInfo(formData); + setUser(updatedUser); + setIsEditing(false); + } catch (error: any) { + alert(error.message); + } +}; + + if (!user) return

加载中...

; + + return ( +
+
+

用户信息

+ +
+
+
+ 👤 +
+
+
+ {isEditing ? ( + + ) : ( +

{user?.name || "未提供"}

+ )} +

角色:{user?.role}

+

邮箱验证时间:{user.emailVerified ? new Date(user.emailVerified).toLocaleString() : "未验证"}

+
+
+ +
+ +
+
+ +

{user.id}

+
+ +
+ + {isEditing ? ( + + ) : ( +

{user.email}

+ )} +
+ +
+ +

{new Date(user.createdAt).toLocaleString()}

+
+ +
+ +

{new Date(user.updatedAt).toLocaleString()}

+
+
+ +
+ {isEditing ? ( + <> + + + + ) : ( + + )} +
+
+
+ ); +} \ No newline at end of file diff --git a/src/app/dashboard/page.tsx b/src/app/(protected)/dashboard/layout.tsx similarity index 61% rename from src/app/dashboard/page.tsx rename to src/app/(protected)/dashboard/layout.tsx index a6876ff..565b2a7 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/(protected)/dashboard/layout.tsx @@ -1,4 +1,4 @@ -import { AppSidebar } from "@/components/sidebar/app-sidebar" +import { AppSidebar } from "@/components/sidebar/app-sidebar"; import { Breadcrumb, BreadcrumbItem, @@ -6,18 +6,29 @@ import { BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, -} from "@/components/ui/breadcrumb" -import { Separator } from "@/components/ui/separator" +} from "@/components/ui/breadcrumb"; +import { Separator } from "@/components/ui/separator"; import { SidebarInset, SidebarProvider, SidebarTrigger, -} from "@/components/ui/sidebar" +} from "@/components/ui/sidebar"; +import { auth } from "@/lib/auth"; +import { notFound } from "next/navigation"; -export default function Page() { +interface LayoutProps { + children: React.ReactNode; +} + +export default async function Layout({ children }: LayoutProps) { + const session = await auth(); + const user = session?.user; + if (!user) { + notFound(); + } return ( - +
@@ -38,15 +49,8 @@ export default function Page() {
-
-
-
-
-
-
-
-
+
{children}
- ) + ); } diff --git a/src/app/(protected)/dashboard/page.tsx b/src/app/(protected)/dashboard/page.tsx new file mode 100644 index 0000000..80efbfb --- /dev/null +++ b/src/app/(protected)/dashboard/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
Dashboard
+} diff --git a/src/components/management-sidebar/manage-form.tsx b/src/components/management-sidebar/manage-form.tsx new file mode 100644 index 0000000..a120602 --- /dev/null +++ b/src/components/management-sidebar/manage-form.tsx @@ -0,0 +1,28 @@ +import { Search } from "lucide-react" + +import { Label } from "@/components/ui/label" +import { + SidebarGroup, + SidebarGroupContent, + SidebarInput, +} from "@/components/ui/sidebar" + +export function SearchForm({ ...props }: React.ComponentProps<"form">) { + return ( +
+ + + + + + + +
+ ) +} \ No newline at end of file diff --git a/src/components/management-sidebar/manage-sidebar.tsx b/src/components/management-sidebar/manage-sidebar.tsx new file mode 100644 index 0000000..fb026d3 --- /dev/null +++ b/src/components/management-sidebar/manage-sidebar.tsx @@ -0,0 +1,97 @@ +import * as React from "react"; +import { ChevronRight } from "lucide-react"; + +import { VersionSwitcher } from "@/components//management-sidebar/manage-switcher"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible"; +import { + Sidebar, + SidebarContent, + SidebarGroup, + SidebarGroupContent, + SidebarGroupLabel, + SidebarHeader, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + SidebarRail, +} from "@/components/ui/sidebar"; + +// 自定义数据:包含用户相关菜单项 +const data = { + versions: ["1.0.1", "1.1.0-alpha", "2.0.0-beta1"], + navUser: [ + { + title: "个人中心", + url: "#", + items: [ + { title: "登录信息", url: "#", key: "profile" }, + { title: "修改密码", url: "#", key: "change-password" }, + ], + }, + ], +}; + +// 显式定义 props 类型 +interface AppSidebarProps { + onItemClick?: (key: string) => void; +} + +export function AppSidebar({ onItemClick = (key: string) => {}, ...props }: AppSidebarProps) { + return ( + + + + + + {/* 渲染用户相关的侧边栏菜单 */} + {data.navUser.map((item) => ( + + + + + {item.title} + + + + + + + {item.items.map((subItem) => ( + + { + e.preventDefault(); + onItemClick(subItem.key); + }} + > + {subItem.title} + + + ))} + + + + + + ))} + + + + ); +} \ No newline at end of file diff --git a/src/components/management-sidebar/manage-switcher.tsx b/src/components/management-sidebar/manage-switcher.tsx new file mode 100644 index 0000000..054995b --- /dev/null +++ b/src/components/management-sidebar/manage-switcher.tsx @@ -0,0 +1,64 @@ +"use client" + +import * as React from "react" +import { Check, ChevronsUpDown, GalleryVerticalEnd } from "lucide-react" + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, +} from "@/components/ui/sidebar" + +export function VersionSwitcher({ + versions, + defaultVersion, +}: { + versions: string[] + defaultVersion: string +}) { + const [selectedVersion, setSelectedVersion] = React.useState(defaultVersion) + + return ( + + + + + +
+ +
+
+ Documentation + v{selectedVersion} +
+ +
+
+ + {versions.map((version) => ( + setSelectedVersion(version)} + > + v{version}{" "} + {version === selectedVersion && } + + ))} + +
+
+
+ ) +} \ No newline at end of file diff --git a/src/components/nav-user.tsx b/src/components/nav-user.tsx index ec6f1f6..1fa7a82 100644 --- a/src/components/nav-user.tsx +++ b/src/components/nav-user.tsx @@ -43,6 +43,19 @@ export function NavUser({ const { isMobile } = useSidebar() const router = useRouter() + async function handleLogout() { + await fetch("/api/auth/signout", { method: "POST" }); + router.replace("/sign-in"); + } + + function handleAccount() { + if (user && user.email) { + router.replace("/user/profile"); + } else { + router.replace("/sign-in"); + } + } + return ( @@ -90,11 +103,11 @@ export function NavUser({ - + Account - router.push("/sign-in")}> + router.push("/sign-in")}> Switch User @@ -104,9 +117,7 @@ export function NavUser({ - { - router.replace("/"); - }}> + Log out diff --git a/src/components/sidebar/admin-sidebar.tsx b/src/components/sidebar/admin-sidebar.tsx new file mode 100644 index 0000000..b5ead92 --- /dev/null +++ b/src/components/sidebar/admin-sidebar.tsx @@ -0,0 +1,108 @@ +"use client" +import { siteConfig } from "@/config/site" +import * as React from "react" +import { + LifeBuoy, + Send, + Shield, +} from "lucide-react" + +import { NavMain } from "@/components/nav-main" +import { NavProjects } from "@/components/nav-projects" +import { NavSecondary } from "@/components/nav-secondary" +import { NavUser } from "@/components/nav-user" +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarHeader, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, +} from "@/components/ui/sidebar" + +import { useEffect, useState } from "react" + +const adminData = { + navMain: [ + { + title: "管理面板", + url: "#", + icon: Shield, + isActive: true, + items: [ + { title: "管理员管理", url: "/usermanagement/admin" }, + { title: "用户管理", url: "/usermanagement/guest" }, + { title: "教师管理", url: "/usermanagement/teacher" }, + { title: "题目管理", url: "/usermanagement/problem" }, + ], + }, + + ], + navSecondary: [ + { title: "帮助", url: "/", icon: LifeBuoy }, + { title: "反馈", url: siteConfig.url.repo.github, icon: Send }, + ], + wrongProblems: [], +} + +async function fetchCurrentUser() { + try { + const res = await fetch("/api/auth/session"); + if (!res.ok) return null; + const session = await res.json(); + return { + name: session?.user?.name ?? "未登录管理员", + email: session?.user?.email ?? "", + avatar: session?.user?.image ?? "/avatars/default.jpg", + }; + } catch { + return { + name: "未登录管理员", + email: "", + avatar: "/avatars/default.jpg", + }; + } +} + +export function AdminSidebar(props: React.ComponentProps) { + const [user, setUser] = useState({ + name: "未登录管理员", + email: "", + avatar: "/avatars/default.jpg", + }); + + useEffect(() => { + fetchCurrentUser().then(u => u && setUser(u)); + }, []); + + return ( + + + + + + +
+ +
+
+ Admin + 管理后台 +
+
+
+
+
+
+ + + + + + + + +
+ ) +} diff --git a/src/components/sidebar/app-sidebar.tsx b/src/components/sidebar/app-sidebar.tsx index ff41c19..9038c9e 100644 --- a/src/components/sidebar/app-sidebar.tsx +++ b/src/components/sidebar/app-sidebar.tsx @@ -1,6 +1,7 @@ -"use client" -import { siteConfig } from "@/config/site" -import * as React from "react" +"use client"; + +import { siteConfig } from "@/config/site"; +import * as React from "react"; import { BookOpen, Command, @@ -8,12 +9,12 @@ import { Send, Settings2, SquareTerminal, -} from "lucide-react" +} from "lucide-react"; -import { NavMain } from "@/components/nav-main" -import { NavProjects } from "@/components/nav-projects" -import { NavSecondary } from "@/components/nav-secondary" -import { NavUser } from "@/components/nav-user" +import { NavMain } from "@/components/nav-main"; +import { NavProjects } from "@/components/nav-projects"; +import { NavSecondary } from "@/components/nav-secondary"; +import { NavUser } from "@/components/nav-user"; import { Sidebar, SidebarContent, @@ -22,15 +23,13 @@ import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, -} from "@/components/ui/sidebar" +} from "@/components/ui/sidebar"; +import { User } from "next-auth"; +// import { useEffect, useState } from "react" +// import { auth, signIn } from "@/lib/auth" const data = { - user: { - name: "shadcn", - email: "m@example.com", - avatar: "/avatars/shadcn.jpg", - }, navMain: [ { title: "页面", @@ -52,7 +51,7 @@ const data = { }, ], }, - + { title: "已完成事项", url: "#", @@ -66,7 +65,7 @@ const data = { title: "错题集", url: "#", }, - { + { title: "收藏集", url: "#", }, @@ -77,10 +76,6 @@ const data = { url: "#", icon: Settings2, items: [ - { - title: "一般设置", - url: "#", - }, { title: "语言", url: "#", @@ -117,11 +112,50 @@ const data = { status: "TLE", }, ], +}; + +// // 获取当前登录用户信息的 API +// async function fetchCurrentUser() { +// try { +// const res = await fetch("/api/auth/session"); +// if (!res.ok) return null; +// const session = await res.json(); +// return { +// name: session?.user?.name ?? "未登录用户", +// email: session?.user?.email ?? "", +// avatar: session?.user?.image ?? "/avatars/default.jpg", +// }; +// } catch { +// return { +// name: "未登录用户", +// email: "", +// avatar: "/avatars/default.jpg", +// }; +// } +// } + +interface AppSidebarProps{ + user:User } -export function AppSidebar({ ...props }: React.ComponentProps) { +export function AppSidebar({ user, ...props }: AppSidebarProps) { + // const [user, setUser] = useState({ + // name: "未登录用户", + // email: "", + // avatar: "/avatars/default.jpg", + // }); + + // useEffect(() => { + // fetchCurrentUser().then(u => u && setUser(u)); + // }, []); + const userInfo = { + name: user.name ?? "", + email: user.email ?? "", + avatar: user.image ?? "", + }; + return ( - + @@ -145,8 +179,8 @@ export function AppSidebar({ ...props }: React.ComponentProps) { - + - ) -} \ No newline at end of file + ); +} diff --git a/src/components/sidebar/teacher-sidebar.tsx b/src/components/sidebar/teacher-sidebar.tsx index 49babac..2eecd71 100644 --- a/src/components/sidebar/teacher-sidebar.tsx +++ b/src/components/sidebar/teacher-sidebar.tsx @@ -36,10 +36,6 @@ const data = { icon: SquareTerminal, isActive: true, items: [ - { - title: "课程管理", - url: "/teacher/courses", - }, { title: "学生管理", url: "/teacher/students", @@ -56,11 +52,11 @@ const data = { icon: PieChart, items: [ { - title: "成绩统计", + title: "完成情况", url: "/teacher/statistics/grades", }, { - title: "错题分析", + title: "错题统计", url: "/teacher/statistics/activity", }, ], diff --git a/src/components/ui/donut-chart.tsx b/src/components/ui/donut-chart.tsx deleted file mode 100644 index c6f3296..0000000 --- a/src/components/ui/donut-chart.tsx +++ /dev/null @@ -1,58 +0,0 @@ -// 简单的环形图组件,使用 SVG 实现 -import React from "react"; - -interface DonutChartProps { - percent: number; // 完成比例 0-100 - size?: number; // 图表直径 - strokeWidth?: number; // 圆环宽度 - color?: string; // 完成部分颜色 - bgColor?: string; // 未完成部分颜色 -} - -export function DonutChart({ - percent, - size = 120, - strokeWidth = 16, - color = "#3b82f6", - bgColor = "#e5e7eb", -}: DonutChartProps) { - const radius = (size - strokeWidth) / 2; - const circumference = 2 * Math.PI * radius; - const offset = circumference * (1 - percent / 100); - - return ( - - - - - {percent}% - - - ); -} diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 0643271..96ba401 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -247,3 +247,8 @@ export const { auth, handlers, signIn, signOut } = NextAuth({ }, }, }); + +export const getCurrentUser=async ()=>{ + const session=await auth(); + return session?.user +}