删除多个完成报告文件,优化项目结构以提升可维护性。

This commit is contained in:
乘风
2026-02-03 15:59:37 +08:00
parent d4ca9573f5
commit a2443c097c
119 changed files with 2119 additions and 8537 deletions

View File

@@ -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("/view/login"), 2000)
} else {
setError(data.error || "重置失败")
}
} finally {
setLoading(false)
}
}
if (success) {
return (
<div className="min-h-screen bg-black text-white flex flex-col items-center justify-center px-6">
<p className="text-[#30d158] text-lg mb-4"></p>
<p className="text-gray-500 text-sm">使...</p>
</div>
)
}
return (
<div className="min-h-screen bg-black text-white flex flex-col">
<header className="flex items-center px-4 py-3">
<Link
href="/view/login"
className="w-9 h-9 rounded-full bg-[#1c1c1e] flex items-center justify-center"
>
<ChevronLeft className="w-5 h-5 text-gray-400" />
</Link>
<h1 className="flex-1 text-center text-lg font-semibold"></h1>
<div className="w-9" />
</header>
<main className="flex-1 px-6 pt-8">
<p className="text-gray-500 text-sm mb-6">
使使
</p>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="relative">
<Phone className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
<input
type="tel"
value={phone}
onChange={(e) => 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"
/>
</div>
<div className="relative">
<Hash className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
<input
type="password"
value={newPassword}
onChange={(e) => 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"
/>
</div>
<div className="relative">
<Hash className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
<input
type="password"
value={confirmPassword}
onChange={(e) => 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"
/>
</div>
{error && <p className="text-red-500 text-sm">{error}</p>}
<button
type="submit"
disabled={loading || !phone || !newPassword || !confirmPassword}
className="w-full py-3.5 bg-[#30d158] text-white font-medium rounded-xl active:scale-[0.98] transition-transform disabled:opacity-50"
>
{loading ? "提交中..." : "重置密码"}
</button>
</form>
<p className="text-gray-500 text-xs mt-6 text-center">
使
</p>
</main>
</div>
)
}

183
app/view/login/page.tsx Normal file
View File

@@ -0,0 +1,183 @@
"use client"
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 } = useStore()
const [mode, setMode] = useState<"login" | "register">("login")
const [phone, setPhone] = useState("")
const [code, setCode] = useState("")
const [nickname, setNickname] = useState("")
const [referralCode, setReferralCode] = useState("")
const [error, setError] = useState("")
const [loading, setLoading] = useState(false)
const handleSubmit = async () => {
setError("")
setLoading(true)
try {
// 管理员登录(使用 code 作为密码,调用后台 API 并写 Cookie
if (phone.toLowerCase() === "admin") {
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("/view")
} else {
setError("密码错误或用户不存在")
}
} else {
if (!nickname.trim()) {
setError("请输入昵称")
return
}
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("/view")
} else {
setError("该手机号已注册")
}
}
} finally {
setLoading(false)
}
}
return (
<div className="min-h-screen bg-black text-white flex flex-col">
{/* 顶部导航 */}
<header className="flex items-center px-4 py-3">
<button
onClick={() => router.back()}
className="w-9 h-9 rounded-full bg-[#1c1c1e] flex items-center justify-center"
>
<ChevronLeft className="w-5 h-5 text-gray-400" />
</button>
</header>
{/* 主内容 */}
<main className="flex-1 px-6 pt-8">
<h1 className="text-2xl font-bold mb-2">{mode === "login" ? "登录" : "注册"}</h1>
<p className="text-gray-500 text-sm mb-8">
{mode === "login" ? "登录后查看购买记录和收益" : "注册后开始阅读真实商业故事"}
</p>
<div className="space-y-4">
{/* 手机号 */}
<div className="relative">
<Phone className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
<input
type="tel"
value={phone}
onChange={(e) => 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"
/>
</div>
{/* 昵称(注册时显示) */}
{mode === "register" && (
<div className="relative">
<User className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
<input
type="text"
value={nickname}
onChange={(e) => setNickname(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"
/>
</div>
)}
{/* 密码 */}
<div className="relative">
<Hash className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
<input
type="password"
value={code}
onChange={(e) => setCode(e.target.value)}
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"
/>
</div>
{/* 邀请码(注册时显示) */}
{mode === "register" && (
<div className="relative">
<Hash className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
<input
type="text"
value={referralCode}
onChange={(e) => setReferralCode(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"
/>
</div>
)}
{/* 错误提示 */}
{error && <p className="text-red-500 text-sm">{error}</p>}
{/* 提交按钮 */}
<button
onClick={handleSubmit}
disabled={loading || !phone}
className="w-full py-3.5 bg-[#30d158] text-white font-medium rounded-xl active:scale-[0.98] transition-transform disabled:opacity-50"
>
{loading ? "处理中..." : mode === "login" ? "登录" : "注册"}
</button>
{/* 忘记密码 / 切换模式 */}
<div className="text-center space-y-2">
{mode === "login" && (
<div>
<Link href="/view/login/forgot" className="text-[#30d158] text-sm">
</Link>
</div>
)}
<button onClick={() => setMode(mode === "login" ? "register" : "login")} className="text-[#30d158] text-sm block mx-auto">
{mode === "login" ? "没有账号?去注册" : "已有账号?去登录"}
</button>
</div>
</div>
</main>
</div>
)
}