diff --git a/tone-page-web/app/blog/page.tsx b/tone-page-web/app/(with-header-footer)/blog/page.tsx
similarity index 100%
rename from tone-page-web/app/blog/page.tsx
rename to tone-page-web/app/(with-header-footer)/blog/page.tsx
diff --git a/tone-page-web/app/(with-header-footer)/layout.tsx b/tone-page-web/app/(with-header-footer)/layout.tsx
new file mode 100644
index 0000000..587bf07
--- /dev/null
+++ b/tone-page-web/app/(with-header-footer)/layout.tsx
@@ -0,0 +1,18 @@
+import Header from "../components/Header";
+import Footer from "../components/Footer";
+
+export default function LayoutWithHeaderFooter({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+ <>
+
+
+ {children}
+
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/tone-page-web/app/page.tsx b/tone-page-web/app/(with-header-footer)/page.tsx
similarity index 96%
rename from tone-page-web/app/page.tsx
rename to tone-page-web/app/(with-header-footer)/page.tsx
index 5fdb354..f549371 100644
--- a/tone-page-web/app/page.tsx
+++ b/tone-page-web/app/(with-header-footer)/page.tsx
@@ -1,5 +1,5 @@
'use client';
-import favicon from './favicon.ico';
+import favicon from '../favicon.ico';
import Image from 'next/image';
export default function Home() {
diff --git a/tone-page-web/app/resource/components/ResourceCard.tsx b/tone-page-web/app/(with-header-footer)/resource/components/ResourceCard.tsx
similarity index 100%
rename from tone-page-web/app/resource/components/ResourceCard.tsx
rename to tone-page-web/app/(with-header-footer)/resource/components/ResourceCard.tsx
diff --git a/tone-page-web/app/resource/page.tsx b/tone-page-web/app/(with-header-footer)/resource/page.tsx
similarity index 100%
rename from tone-page-web/app/resource/page.tsx
rename to tone-page-web/app/(with-header-footer)/resource/page.tsx
diff --git a/tone-page-web/app/components/Header.tsx b/tone-page-web/app/components/Header.tsx
index 0875e94..6c153e7 100644
--- a/tone-page-web/app/components/Header.tsx
+++ b/tone-page-web/app/components/Header.tsx
@@ -24,7 +24,7 @@ export default function Header() {
{ name: '特恩(TONE)', href: '/' },
{ name: '资源', href: '/resource' },
{ name: '博客', href: '/blog' },
- { name: '控制台', href: '/console' },
+ { name: '控制台', href: '/console/login' },
]
return (
diff --git a/tone-page-web/app/console/account/page.tsx b/tone-page-web/app/console/account/page.tsx
deleted file mode 100644
index a9c8911..0000000
--- a/tone-page-web/app/console/account/page.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function Account() {
- return (
-
-
-
- )
-}
\ No newline at end of file
diff --git a/tone-page-web/app/console/login/components/EmailLoginMode.tsx b/tone-page-web/app/console/login/components/EmailLoginMode.tsx
new file mode 100644
index 0000000..15d2a27
--- /dev/null
+++ b/tone-page-web/app/console/login/components/EmailLoginMode.tsx
@@ -0,0 +1,67 @@
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp";
+import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp";
+import { useState, useCallback } from "react";
+import { toast } from "sonner";
+import LoginHeader from "./LoginHeader";
+import { SendCodeFormData } from "./types";
+import { Label } from "@/components/ui/label";
+
+export default function EmailLoginMode({ onSendCode }: { onSendCode: (data: SendCodeFormData) => Promise }) {
+ const [email, setEmail] = useState("");
+ const handleSendCode = useCallback(() => {
+ if (!email.trim().match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
+ toast.error('请输入正确的邮箱地址');
+ return;
+ }
+ onSendCode({
+ type: 'email',
+ email,
+ })
+ }, [email, onSendCode]);
+
+ return (
+ <>
+
+
+
+ setEmail(e.target.value)}
+ required />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/tone-page-web/app/console/login/components/LoginHeader.tsx b/tone-page-web/app/console/login/components/LoginHeader.tsx
new file mode 100644
index 0000000..ff55f7e
--- /dev/null
+++ b/tone-page-web/app/console/login/components/LoginHeader.tsx
@@ -0,0 +1,12 @@
+export default function LoginHeader() {
+ return (
+ <>
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/tone-page-web/app/console/login/components/PasswordLoginMode.tsx b/tone-page-web/app/console/login/components/PasswordLoginMode.tsx
new file mode 100644
index 0000000..2b0ee47
--- /dev/null
+++ b/tone-page-web/app/console/login/components/PasswordLoginMode.tsx
@@ -0,0 +1,41 @@
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import LoginHeader from "./LoginHeader";
+import { Label } from "@/components/ui/label"
+
+export default function PasswordLoginMode({ forgetPassword }: { forgetPassword: () => void }) {
+ return (
+ <>
+
+
+
+
+
+
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/tone-page-web/app/console/login/components/PhoneLoginMode.tsx b/tone-page-web/app/console/login/components/PhoneLoginMode.tsx
new file mode 100644
index 0000000..771c1f9
--- /dev/null
+++ b/tone-page-web/app/console/login/components/PhoneLoginMode.tsx
@@ -0,0 +1,67 @@
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp";
+import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp";
+import { useState, useCallback } from "react";
+import { toast } from "sonner";
+import LoginHeader from "./LoginHeader";
+import { SendCodeFormData } from "./types";
+import { Label } from "@/components/ui/label"
+
+export default function PhoneLoginMode({ onSendCode }: { onSendCode: (data: SendCodeFormData) => Promise }) {
+ const [phone, setPhone] = useState("");
+ const handleSendCode = useCallback(() => {
+ if (phone.trim().length !== 11) {
+ toast.error('请输入正确的手机号');
+ return;
+ }
+ onSendCode({
+ type: 'phone',
+ phone,
+ })
+ }, [phone, onSendCode]);
+
+ return (
+ <>
+
+
+
+ setPhone(e.target.value)}
+ required />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/tone-page-web/components/login-bg.jpg b/tone-page-web/app/console/login/components/login-bg.jpg
similarity index 100%
rename from tone-page-web/components/login-bg.jpg
rename to tone-page-web/app/console/login/components/login-bg.jpg
diff --git a/tone-page-web/app/console/login/components/types.ts b/tone-page-web/app/console/login/components/types.ts
new file mode 100644
index 0000000..8a8a754
--- /dev/null
+++ b/tone-page-web/app/console/login/components/types.ts
@@ -0,0 +1,16 @@
+export type SubmitMode = 'password' | 'phone' | 'email';
+export type LoginFormData = {
+ type: SubmitMode;
+ account?: string;
+ password?: string;
+ phone?: string;
+ email?: string;
+ code?: string;
+}
+
+export type SendCodeMode = 'phone' | 'email';
+export type SendCodeFormData = {
+ type: SendCodeMode;
+ phone?: string;
+ email?: string;
+}
\ No newline at end of file
diff --git a/tone-page-web/app/console/login/page.tsx b/tone-page-web/app/console/login/page.tsx
new file mode 100644
index 0000000..bdc3384
--- /dev/null
+++ b/tone-page-web/app/console/login/page.tsx
@@ -0,0 +1,140 @@
+'use client';
+import { authApi, verificationApi } from "@/lib/api";
+import { useRouter } from "next/navigation";
+import { toast } from "sonner";
+import Header from "@/app/components/Header";
+import Footer from "@/app/components/Footer";
+import { Button } from "@/components/ui/button";
+import { Card, CardContent } from "@/components/ui/card";
+import { cn } from "@/lib/utils";
+import { KeyRound, Phone, Mail } from "lucide-react";
+import EmailLoginMode from "./components/EmailLoginMode";
+import PasswordLoginMode from "./components/PasswordLoginMode";
+import PhoneLoginMode from "./components/PhoneLoginMode";
+import { LoginFormData, SendCodeFormData, SubmitMode } from "./components/types";
+import { useCallback, useState } from "react";
+import LoginBG from './components/login-bg.jpg';
+import Image from "next/image";
+
+export default function Login() {
+ const router = useRouter();
+ const [loginMode, setLoginMode] = useState('password');
+
+ const handleForgetPassword = useCallback(() => {
+ toast.warning('开发中,敬请期待!暂时可通过发送邮件至网站管理员进行密码重置。');
+ }, []);
+
+ const handleSendCode = async (data: SendCodeFormData) => {
+ const res = await verificationApi.send({
+ type: 'login',
+ targetType: data.type,
+ phone: data.phone,
+ email: data.email,
+ })
+
+ if (res.statusCode === 200) {
+ toast.success('验证码已发送,请注意查收');
+ return true;
+ } else {
+ toast.error(res.message || '验证码发送失败,请稍后再试');
+ return false;
+ }
+ }
+
+ const handleSubmit = async (data: LoginFormData) => {
+ const res = await authApi.login({
+ ...data,
+ });
+
+ if (res.statusCode === 200 && res.data) {
+ toast.success('登录成功');
+ localStorage.setItem('token', res.data.token);
+ router.replace('/console');
+ return true;
+ } else {
+ toast.error(res.message || '登录失败,请稍后再试');
+ return false;
+ }
+ }
+
+ return (
+ <>
+
+
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/tone-page-web/app/console/page.tsx b/tone-page-web/app/console/page.tsx
index 714b9df..240524a 100644
--- a/tone-page-web/app/console/page.tsx
+++ b/tone-page-web/app/console/page.tsx
@@ -1,21 +1,52 @@
-'use client';
+import { AppSidebar } from "@/components/app-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 { LoginForm, LoginFormData } from "@/components/login-form";
-
-const handleSubmit = async (data: LoginFormData) => {
- console.log(data)
-}
-
-const handleSendCode = async (data: LoginFormData) => {
- console.log(data)
-}
-
-export default function Console() {
+export default function Page() {
return (
-
+
+
+
+
+
+
+
)
-}
\ No newline at end of file
+}
diff --git a/tone-page-web/app/dashboard/page.tsx b/tone-page-web/app/dashboard/page.tsx
new file mode 100644
index 0000000..18a85b1
--- /dev/null
+++ b/tone-page-web/app/dashboard/page.tsx
@@ -0,0 +1,55 @@
+import { AppSidebar } from "@/components/app-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"
+
+export default function Page() {
+ return (
+
+
+
+
+
+
+
+ )
+}
diff --git a/tone-page-web/app/layout.tsx b/tone-page-web/app/layout.tsx
index 7fd9a77..fa61fc0 100644
--- a/tone-page-web/app/layout.tsx
+++ b/tone-page-web/app/layout.tsx
@@ -35,12 +35,10 @@ export default function RootLayout({
enableSystem
disableTransitionOnChange
>
-
{children}
-