diff --git a/app/admin/layout.tsx b/app/admin/layout.tsx index 55fec8ac..2cb29b5f 100644 --- a/app/admin/layout.tsx +++ b/app/admin/layout.tsx @@ -3,17 +3,43 @@ import type React from "react" import { useState, useEffect } from "react" import Link from "next/link" -import { usePathname } from "next/navigation" +import { usePathname, useRouter } from "next/navigation" import { LayoutDashboard, FileText, Users, CreditCard, Settings, LogOut, Wallet, Globe, BookOpen } from "lucide-react" export default function AdminLayout({ children }: { children: React.ReactNode }) { const pathname = usePathname() + const router = useRouter() const [mounted, setMounted] = useState(false) + const [authChecked, setAuthChecked] = useState(false) useEffect(() => { setMounted(true) }, []) + // 非登录页时校验 Cookie,未登录则跳转登录页 + useEffect(() => { + if (!mounted || pathname === "/admin/login") return + setAuthChecked(false) + let cancelled = false + fetch("/api/admin", { credentials: "include" }) + .then((res) => { + if (cancelled) return + if (res.status === 401) router.replace("/admin/login") + else setAuthChecked(true) + }) + .catch(() => { + if (!cancelled) setAuthChecked(true) + }) + return () => { + cancelled = true + } + }, [mounted, pathname, router]) + + const handleLogout = async () => { + await fetch("/api/admin/logout", { method: "POST", credentials: "include" }) + router.replace("/admin/login") + } + // 简化菜单:按功能归类,保留核心功能 // PDF需求:分账管理、分销管理、订单管理三合一 → 交易中心 const menuItems = [ @@ -25,8 +51,13 @@ export default function AdminLayout({ children }: { children: React.ReactNode }) { icon: Settings, label: "系统设置", href: "/admin/settings" }, ] - // 避免hydration错误,等待客户端mount - if (!mounted) { + // 登录页:不渲染侧栏,只渲染子页面 + if (pathname === "/admin/login") { + return
{children}
+ } + + // 避免 hydration 错误,等待客户端 mount 并完成鉴权 + if (!mounted || !authChecked) { return (
@@ -66,12 +97,19 @@ export default function AdminLayout({ children }: { children: React.ReactNode }) })} -
+
+ - 返回前台
diff --git a/app/admin/login/page.tsx b/app/admin/login/page.tsx index 8b9235d0..b92ad3c4 100644 --- a/app/admin/login/page.tsx +++ b/app/admin/login/page.tsx @@ -5,11 +5,9 @@ import { useRouter } from "next/navigation" import { Lock, User, ShieldCheck } from "lucide-react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" -import { useStore } from "@/lib/store" export default function AdminLoginPage() { const router = useRouter() - const { adminLogin } = useStore() const [username, setUsername] = useState("") const [password, setPassword] = useState("") const [error, setError] = useState("") @@ -18,14 +16,22 @@ export default function AdminLoginPage() { const handleLogin = async () => { setError("") setLoading(true) - - await new Promise((resolve) => setTimeout(resolve, 500)) - - const success = adminLogin(username, password) - if (success) { - router.push("/admin") - } else { - setError("用户名或密码错误") + try { + const res = await fetch("/api/admin", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ username: username.trim(), password }), + credentials: "include", + }) + const data = await res.json() + if (res.ok && data.success) { + router.push("/admin") + return + } + setError(data.error || "用户名或密码错误") + } catch { + setError("网络错误,请重试") + } finally { setLoading(false) } } @@ -95,12 +101,6 @@ export default function AdminLoginPage() {
-
-

- 默认账号: admin /{" "} - key123456 -

-
{/* Footer */} diff --git a/app/api/admin/chapters/route.ts b/app/api/admin/chapters/route.ts index a8ef4a11..90f16f07 100644 --- a/app/api/admin/chapters/route.ts +++ b/app/api/admin/chapters/route.ts @@ -6,6 +6,7 @@ import { NextResponse } from 'next/server' import fs from 'fs' import path from 'path' +import { requireAdminResponse } from '@/lib/admin-auth' // 获取书籍目录 const BOOK_DIR = path.join(process.cwd(), 'book') @@ -14,6 +15,8 @@ const BOOK_DIR = path.join(process.cwd(), 'book') * GET - 获取所有章节列表 */ export async function GET(request: Request) { + const authErr = requireAdminResponse(request) + if (authErr) return authErr try { const { searchParams } = new URL(request.url) const includeContent = searchParams.get('content') === 'true' @@ -274,6 +277,8 @@ export async function GET(request: Request) { * POST - 更新章节设置 */ export async function POST(request: Request) { + const authErr = requireAdminResponse(request) + if (authErr) return authErr try { const body = await request.json() const { action, chapterId, data } = body diff --git a/app/api/admin/content/route.ts b/app/api/admin/content/route.ts index 84d54cc1..4d156f21 100644 --- a/app/api/admin/content/route.ts +++ b/app/api/admin/content/route.ts @@ -5,11 +5,14 @@ import { NextRequest, NextResponse } from 'next/server' import fs from 'fs' import path from 'path' import matter from 'gray-matter' +import { requireAdminResponse } from '@/lib/admin-auth' const BOOK_DIR = path.join(process.cwd(), 'book') // GET: 获取所有章节列表 export async function GET(req: NextRequest) { + const authErr = requireAdminResponse(req) + if (authErr) return authErr try { const chapters = getAllChapters() @@ -28,6 +31,8 @@ export async function GET(req: NextRequest) { // POST: 创建新章节 export async function POST(req: NextRequest) { + const authErr = requireAdminResponse(req) + if (authErr) return authErr try { const body = await req.json() const { title, content, category, tags } = body @@ -70,6 +75,8 @@ export async function POST(req: NextRequest) { // PUT: 更新章节 export async function PUT(req: NextRequest) { + const authErr = requireAdminResponse(req) + if (authErr) return authErr try { const body = await req.json() const { id, title, content, category, tags } = body @@ -97,6 +104,8 @@ export async function PUT(req: NextRequest) { // DELETE: 删除章节 export async function DELETE(req: NextRequest) { + const authErr = requireAdminResponse(req) + if (authErr) return authErr try { const { searchParams } = new URL(req.url) const id = searchParams.get('id') diff --git a/app/api/admin/logout/route.ts b/app/api/admin/logout/route.ts new file mode 100644 index 00000000..9d287345 --- /dev/null +++ b/app/api/admin/logout/route.ts @@ -0,0 +1,9 @@ +import { NextRequest, NextResponse } from 'next/server' +import { getAdminCookieName, getAdminCookieOptions } from '@/lib/admin-auth' + +export async function POST(_req: NextRequest) { + const res = NextResponse.json({ success: true }) + const opts = getAdminCookieOptions() + res.cookies.set(getAdminCookieName(), '', { ...opts, maxAge: 0 }) + return res +} diff --git a/app/api/admin/payment/route.ts b/app/api/admin/payment/route.ts index cde1bea1..3aaba3c5 100644 --- a/app/api/admin/payment/route.ts +++ b/app/api/admin/payment/route.ts @@ -2,6 +2,7 @@ // 付费模块管理API import { NextRequest, NextResponse } from 'next/server' +import { requireAdminResponse } from '@/lib/admin-auth' // 模拟订单数据 let orders = [ @@ -29,6 +30,8 @@ let orders = [ // GET: 获取订单列表 export async function GET(req: NextRequest) { + const authErr = requireAdminResponse(req) + if (authErr) return authErr const { searchParams } = new URL(req.url) const status = searchParams.get('status') const page = parseInt(searchParams.get('page') || '1') @@ -71,6 +74,8 @@ export async function GET(req: NextRequest) { // POST: 创建订单(手动) export async function POST(req: NextRequest) { + const authErr = requireAdminResponse(req) + if (authErr) return authErr try { const body = await req.json() const { userId, userName, amount, note } = body @@ -110,6 +115,8 @@ export async function POST(req: NextRequest) { // PUT: 更新订单状态 export async function PUT(req: NextRequest) { + const authErr = requireAdminResponse(req) + if (authErr) return authErr try { const body = await req.json() const { orderId, status, note } = body @@ -148,6 +155,8 @@ export async function PUT(req: NextRequest) { // DELETE: 删除订单 export async function DELETE(req: NextRequest) { + const authErr = requireAdminResponse(req) + if (authErr) return authErr try { const { searchParams } = new URL(req.url) const orderId = searchParams.get('id') diff --git a/app/api/admin/referral/route.ts b/app/api/admin/referral/route.ts index 832229a4..771c82ad 100644 --- a/app/api/admin/referral/route.ts +++ b/app/api/admin/referral/route.ts @@ -2,6 +2,7 @@ // 分销模块管理API import { NextRequest, NextResponse } from 'next/server' +import { requireAdminResponse } from '@/lib/admin-auth' // 模拟分销数据 let referralRecords = [ @@ -52,6 +53,8 @@ let commissionRecords = [ // GET: 获取分销概览或列表 export async function GET(req: NextRequest) { + const authErr = requireAdminResponse(req) + if (authErr) return authErr const { searchParams } = new URL(req.url) const type = searchParams.get('type') || 'list' const page = parseInt(searchParams.get('page') || '1') @@ -95,6 +98,8 @@ export async function GET(req: NextRequest) { // POST: 创建分销记录或处理佣金 export async function POST(req: NextRequest) { + const authErr = requireAdminResponse(req) + if (authErr) return authErr try { const body = await req.json() const { action, data } = body @@ -170,6 +175,8 @@ export async function POST(req: NextRequest) { // PUT: 更新分销记录 export async function PUT(req: NextRequest) { + const authErr = requireAdminResponse(req) + if (authErr) return authErr try { const body = await req.json() const { referrerId, status, commissionRate, note } = body @@ -205,6 +212,8 @@ export async function PUT(req: NextRequest) { // DELETE: 删除分销记录 export async function DELETE(req: NextRequest) { + const authErr = requireAdminResponse(req) + if (authErr) return authErr try { const { searchParams } = new URL(req.url) const referrerId = searchParams.get('id') diff --git a/app/api/admin/route.ts b/app/api/admin/route.ts index ae960d77..75ee978f 100644 --- a/app/api/admin/route.ts +++ b/app/api/admin/route.ts @@ -1,25 +1,27 @@ // app/api/admin/route.ts -// 后台管理API入口 +// 后台管理API入口:登录与鉴权(账号密码从环境变量读取,默认 admin / admin123) import { NextRequest, NextResponse } from 'next/server' +import { + verifyAdminToken, + getAdminTokenFromRequest, + verifyAdminCredentials, + getAdminCredentials, + createAdminToken, + getAdminCookieName, + getAdminCookieOptions, +} from '@/lib/admin-auth' -// 验证管理员权限 -function verifyAdmin(req: NextRequest) { - const token = req.headers.get('Authorization')?.replace('Bearer ', '') - - // TODO: 实现真实的token验证 - if (!token || token !== 'admin-token-secret') { - return false - } - - return true +function requireAdmin(req: NextRequest): boolean { + const token = getAdminTokenFromRequest(req) + return verifyAdminToken(token) } -// GET: 获取后台概览数据 +// GET: 获取后台概览数据(需已登录) export async function GET(req: NextRequest) { - if (!verifyAdmin(req)) { + if (!requireAdmin(req)) { return NextResponse.json( - { error: '未授权访问' }, + { error: '未授权访问,请先登录' }, { status: 401 } ) } @@ -58,27 +60,31 @@ export async function GET(req: NextRequest) { return NextResponse.json(overview) } -// POST: 管理员登录 +// POST: 管理员登录(账号密码从环境变量 ADMIN_USERNAME / ADMIN_PASSWORD 读取,默认 admin / admin123) export async function POST(req: NextRequest) { const body = await req.json() const { username, password } = body - // TODO: 实现真实的登录验证 - if (username === 'admin' && password === 'admin123') { - return NextResponse.json({ - success: true, - token: 'admin-token-secret', - user: { - id: 'admin', - username: 'admin', - role: 'admin', - name: '卡若' - } - }) + if (!username || !password) { + return NextResponse.json( + { error: '请输入用户名和密码' }, + { status: 400 } + ) } - return NextResponse.json( - { error: '用户名或密码错误' }, - { status: 401 } - ) + if (!verifyAdminCredentials(String(username).trim(), String(password))) { + return NextResponse.json( + { error: '用户名或密码错误' }, + { status: 401 } + ) + } + + const token = createAdminToken() + const res = NextResponse.json({ + success: true, + user: { id: 'admin', username: getAdminCredentials().username, role: 'admin', name: '卡若' }, + }) + const opts = getAdminCookieOptions() + res.cookies.set(getAdminCookieName(), token, opts) + return res } diff --git a/app/api/admin/withdrawals/route.ts b/app/api/admin/withdrawals/route.ts index 867c5695..29db3002 100644 --- a/app/api/admin/withdrawals/route.ts +++ b/app/api/admin/withdrawals/route.ts @@ -6,9 +6,12 @@ import { NextResponse } from 'next/server' import { query } from '@/lib/db' import { createTransfer } from '@/lib/wechat-transfer' +import { requireAdminResponse } from '@/lib/admin-auth' // 获取所有提现记录 export async function GET(request: Request) { + const authErr = requireAdminResponse(request) + if (authErr) return authErr try { const { searchParams } = new URL(request.url) const status = searchParams.get('status') // pending, success, failed, all @@ -84,6 +87,8 @@ export async function GET(request: Request) { // 处理提现(审批/拒绝) export async function PUT(request: Request) { + const authErr = requireAdminResponse(request) + if (authErr) return authErr try { const body = await request.json() const { id, action, reason } = body // action: approve, reject diff --git a/app/api/auth/login/route.ts b/app/api/auth/login/route.ts new file mode 100644 index 00000000..a578784e --- /dev/null +++ b/app/api/auth/login/route.ts @@ -0,0 +1,73 @@ +/** + * Web 端登录:手机号 + 密码 + * POST { phone, password } -> 校验后返回用户信息(不含密码) + */ + +import { NextRequest, NextResponse } from 'next/server' +import { query } from '@/lib/db' +import { verifyPassword } from '@/lib/password' + +function mapRowToUser(r: any) { + return { + id: r.id, + phone: r.phone || '', + nickname: r.nickname || '', + isAdmin: !!r.is_admin, + purchasedSections: Array.isArray(r.purchased_sections) + ? r.purchased_sections + : (r.purchased_sections ? JSON.parse(String(r.purchased_sections)) : []) || [], + hasFullBook: !!r.has_full_book, + referralCode: r.referral_code || '', + referredBy: r.referred_by || undefined, + earnings: parseFloat(String(r.earnings || 0)), + pendingEarnings: parseFloat(String(r.pending_earnings || 0)), + withdrawnEarnings: parseFloat(String(r.withdrawn_earnings || 0)), + referralCount: Number(r.referral_count) || 0, + createdAt: r.created_at || '', + } +} + +export async function POST(request: NextRequest) { + try { + const body = await request.json() + const { phone, password } = body + + if (!phone || !password) { + return NextResponse.json( + { success: false, error: '请输入手机号和密码' }, + { status: 400 } + ) + } + + const rows = await query( + 'SELECT id, phone, nickname, password, is_admin, has_full_book, referral_code, referred_by, earnings, pending_earnings, withdrawn_earnings, referral_count, purchased_sections, created_at FROM users WHERE phone = ?', + [String(phone).trim()] + ) as any[] + + if (!rows || rows.length === 0) { + return NextResponse.json( + { success: false, error: '用户不存在或密码错误' }, + { status: 401 } + ) + } + + const row = rows[0] + const storedPassword = row.password == null ? '' : String(row.password) + + if (!verifyPassword(String(password), storedPassword)) { + return NextResponse.json( + { success: false, error: '密码错误' }, + { status: 401 } + ) + } + + const user = mapRowToUser(row) + return NextResponse.json({ success: true, user }) + } catch (e) { + console.error('[Auth Login] error:', e) + return NextResponse.json( + { success: false, error: '登录失败' }, + { status: 500 } + ) + } +} diff --git a/app/api/auth/reset-password/route.ts b/app/api/auth/reset-password/route.ts new file mode 100644 index 00000000..3ba84902 --- /dev/null +++ b/app/api/auth/reset-password/route.ts @@ -0,0 +1,54 @@ +/** + * 忘记密码 / 重置密码(Web 端) + * POST { phone, newPassword } -> 按手机号更新密码(无验证码版本,适合内测/内部使用) + */ + +import { NextRequest, NextResponse } from 'next/server' +import { query } from '@/lib/db' +import { hashPassword } from '@/lib/password' + +export async function POST(request: NextRequest) { + try { + const body = await request.json() + const { phone, newPassword } = body + + if (!phone || !newPassword) { + return NextResponse.json( + { success: false, error: '请输入手机号和新密码' }, + { status: 400 } + ) + } + + const trimmedPhone = String(phone).trim() + const trimmedPassword = String(newPassword).trim() + + if (trimmedPassword.length < 6) { + return NextResponse.json( + { success: false, error: '密码至少 6 位' }, + { status: 400 } + ) + } + + const rows = await query('SELECT id FROM users WHERE phone = ?', [trimmedPhone]) as any[] + if (!rows || rows.length === 0) { + return NextResponse.json( + { success: false, error: '该手机号未注册' }, + { status: 404 } + ) + } + + const hashed = hashPassword(trimmedPassword) + await query('UPDATE users SET password = ?, updated_at = NOW() WHERE phone = ?', [ + hashed, + trimmedPhone, + ]) + + return NextResponse.json({ success: true, message: '密码已重置,请使用新密码登录' }) + } catch (e) { + console.error('[Auth ResetPassword] error:', e) + return NextResponse.json( + { success: false, error: '重置失败' }, + { status: 500 } + ) + } +} diff --git a/app/api/db/users/route.ts b/app/api/db/users/route.ts index ff24f1ff..e6eb1421 100644 --- a/app/api/db/users/route.ts +++ b/app/api/db/users/route.ts @@ -5,6 +5,7 @@ import { NextRequest, NextResponse } from 'next/server' import { query } from '@/lib/db' +import { hashPassword } from '@/lib/password' // 生成用户ID function generateUserId(): string { @@ -32,29 +33,35 @@ export async function GET(request: NextRequest) { const openId = searchParams.get('openId') try { - // 获取单个用户 + const omitPassword = (u: any) => { + if (!u) return u + const { password: _, ...rest } = u + return rest + } + + // 获取单个用户(不返回 password) if (id) { const users = await query('SELECT * FROM users WHERE id = ?', [id]) as any[] if (users.length > 0) { - return NextResponse.json({ success: true, user: users[0] }) + return NextResponse.json({ success: true, user: omitPassword(users[0]) }) } return NextResponse.json({ success: false, error: '用户不存在' }, { status: 404 }) } - + // 通过手机号查询 if (phone) { const users = await query('SELECT * FROM users WHERE phone = ?', [phone]) as any[] if (users.length > 0) { - return NextResponse.json({ success: true, user: users[0] }) + return NextResponse.json({ success: true, user: omitPassword(users[0]) }) } return NextResponse.json({ success: false, error: '用户不存在' }, { status: 404 }) } - + // 通过openId查询 if (openId) { const users = await query('SELECT * FROM users WHERE open_id = ?', [openId]) as any[] if (users.length > 0) { - return NextResponse.json({ success: true, user: users[0] }) + return NextResponse.json({ success: true, user: omitPassword(users[0]) }) } return NextResponse.json({ success: false, error: '用户不存在' }, { status: 404 }) } @@ -95,13 +102,18 @@ export async function POST(request: NextRequest) { const body = await request.json() const { openId, phone, nickname, password, wechatId, avatar, referredBy, is_admin } = body + // 密码:确保非空字符串才存储(bcrypt 哈希) + const rawPassword = typeof password === 'string' ? password.trim() : '' + const passwordToStore = rawPassword.length >= 6 ? hashPassword(rawPassword) : null + // 检查openId或手机号是否已存在 if (openId) { const existing = await query('SELECT id FROM users WHERE open_id = ?', [openId]) as any[] if (existing.length > 0) { - // 已存在,返回现有用户 const users = await query('SELECT * FROM users WHERE open_id = ?', [openId]) as any[] - return NextResponse.json({ success: true, user: users[0], isNew: false }) + const u = users[0] + const { password: _p2, ...userSafe } = u || {} + return NextResponse.json({ success: true, user: userSafe, isNew: false }) } } @@ -115,7 +127,7 @@ export async function POST(request: NextRequest) { // 生成用户ID和推荐码 const userId = generateUserId() const referralCode = generateReferralCode(openId || phone || userId) - + // 创建用户 await query(` INSERT INTO users ( @@ -128,7 +140,7 @@ export async function POST(request: NextRequest) { openId || null, phone || null, nickname || '用户' + userId.slice(-4), - password || null, + passwordToStore, wechatId || null, avatar || null, referralCode, @@ -136,12 +148,13 @@ export async function POST(request: NextRequest) { is_admin || false ]) - // 返回新用户 + // 返回新用户(不返回 password) const users = await query('SELECT * FROM users WHERE id = ?', [userId]) as any[] - + const u = users[0] + const { password: _p, ...userSafe } = u || {} return NextResponse.json({ success: true, - user: users[0], + user: userSafe, isNew: true, message: '用户创建成功' }) @@ -189,7 +202,7 @@ export async function PUT(request: NextRequest) { } if (password !== undefined) { updates.push('password = ?') - values.push(password) + values.push(password === '' || password == null ? null : hashPassword(String(password).trim())) } if (has_full_book !== undefined) { updates.push('has_full_book = ?') diff --git a/app/login/forgot/page.tsx b/app/login/forgot/page.tsx new file mode 100644 index 00000000..42a5c3f3 --- /dev/null +++ b/app/login/forgot/page.tsx @@ -0,0 +1,136 @@ +"use client" + +import { useState } from "react" +import { useRouter } from "next/navigation" +import Link from "next/link" +import { ChevronLeft, Phone, Hash } from "lucide-react" + +export default function ForgotPasswordPage() { + const router = useRouter() + const [phone, setPhone] = useState("") + const [newPassword, setNewPassword] = useState("") + const [confirmPassword, setConfirmPassword] = useState("") + const [error, setError] = useState("") + const [success, setSuccess] = useState(false) + const [loading, setLoading] = useState(false) + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + setError("") + setLoading(true) + + try { + if (!phone.trim()) { + setError("请输入手机号") + return + } + if (!newPassword.trim()) { + setError("请输入新密码") + return + } + if (newPassword.trim().length < 6) { + setError("密码至少 6 位") + return + } + if (newPassword !== confirmPassword) { + setError("两次输入的密码不一致") + return + } + + const res = await fetch("/api/auth/reset-password", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ phone: phone.trim(), newPassword: newPassword.trim() }), + }) + const data = await res.json() + + if (data.success) { + setSuccess(true) + setTimeout(() => router.push("/login"), 2000) + } else { + setError(data.error || "重置失败") + } + } finally { + setLoading(false) + } + } + + if (success) { + return ( +
+

密码已重置

+

请使用新密码登录,正在跳转...

+
+ ) + } + + return ( +
+
+ + + +

找回密码

+
+
+ +
+

+ 请输入注册时使用的手机号和新密码,重置后请使用新密码登录。 +

+ +
+
+ + setPhone(e.target.value)} + placeholder="手机号" + className="w-full pl-12 pr-4 py-3.5 bg-[#1c1c1e] rounded-xl text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[#30d158]/50" + /> +
+ +
+ + setNewPassword(e.target.value)} + placeholder="新密码(至少 6 位)" + className="w-full pl-12 pr-4 py-3.5 bg-[#1c1c1e] rounded-xl text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[#30d158]/50" + /> +
+ +
+ + setConfirmPassword(e.target.value)} + placeholder="再次输入新密码" + className="w-full pl-12 pr-4 py-3.5 bg-[#1c1c1e] rounded-xl text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[#30d158]/50" + /> +
+ + {error &&

{error}

} + + +
+ +

+ 若该手机号未注册,将提示「该手机号未注册」;重置后请使用新密码在登录页登录。 +

+
+
+ ) +} diff --git a/app/login/page.tsx b/app/login/page.tsx index 08e33836..820c93c6 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -2,12 +2,13 @@ import { useState } from "react" import { useRouter } from "next/navigation" +import Link from "next/link" import { useStore } from "@/lib/store" import { ChevronLeft, Phone, User, Hash } from "lucide-react" export default function LoginPage() { const router = useRouter() - const { login, register, adminLogin } = useStore() + const { login, register } = useStore() const [mode, setMode] = useState<"login" | "register">("login") const [phone, setPhone] = useState("") const [code, setCode] = useState("") @@ -21,30 +22,52 @@ export default function LoginPage() { setLoading(true) try { - // 管理员登录 + // 管理员登录(使用 code 作为密码,调用后台 API 并写 Cookie) if (phone.toLowerCase() === "admin") { - if (adminLogin(phone, code)) { - router.push("/admin") - return - } else { - setError("管理员密码错误") - return + try { + const res = await fetch("/api/admin", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ username: phone, password: code }), + credentials: "include", + }) + const data = await res.json() + if (res.ok && data.success) { + router.push("/admin") + return + } + } catch { + // fallthrough to error } + setError("管理员密码错误") + return } if (mode === "login") { + if (!code.trim()) { + setError("请输入密码") + return + } const success = await login(phone, code) if (success) { router.push("/") } else { - setError("验证码错误或用户不存在") + setError("密码错误或用户不存在") } } else { if (!nickname.trim()) { setError("请输入昵称") return } - const success = await register(phone, nickname, referralCode || undefined) + if (!code.trim()) { + setError("请设置密码(至少 6 位)") + return + } + if (code.trim().length < 6) { + setError("密码至少 6 位") + return + } + const success = await register(phone, nickname, code, referralCode || undefined) if (success) { router.push("/") } else { @@ -102,14 +125,14 @@ export default function LoginPage() {
)} - {/* 验证码/密码 */} + {/* 密码 */}
setCode(e.target.value)} - placeholder={mode === "login" ? "验证码(测试:123456)" : "设置密码"} + placeholder={mode === "login" ? "密码" : "设置密码(至少 6 位)"} className="w-full pl-12 pr-4 py-3.5 bg-[#1c1c1e] rounded-xl text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[#30d158]/50" />
@@ -140,9 +163,16 @@ export default function LoginPage() { {loading ? "处理中..." : mode === "login" ? "登录" : "注册"} - {/* 切换模式 */} -
-
diff --git a/components/modules/auth/auth-modal.tsx b/components/modules/auth/auth-modal.tsx index 948b0175..efe9e9cb 100644 --- a/components/modules/auth/auth-modal.tsx +++ b/components/modules/auth/auth-modal.tsx @@ -1,6 +1,7 @@ "use client" import { useState } from "react" +import Link from "next/link" import { X, Phone, Lock, User, Gift } from "lucide-react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" @@ -60,7 +61,7 @@ export function AuthModal({ isOpen, onClose, defaultTab = "login" }: AuthModalPr setIsLoading(true) // 昵称可选,默认使用手机号后四位 const name = nickname.trim() || `用户${phone.slice(-4)}` - const success = await register(phone, name, referralCode || undefined) + const success = await register(phone, name, password, referralCode || undefined) setIsLoading(false) if (success) { @@ -146,6 +147,16 @@ export function AuthModal({ isOpen, onClose, defaultTab = "login" }: AuthModalPr +
+ + 忘记密码? + +
+ {error &&

{error}

}