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

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

@@ -107,7 +107,7 @@ export default function AdminLayout({ children }: { children: React.ReactNode })
<span className="text-sm">退</span>
</button>
<Link
href="/"
href="/view"
className="flex items-center gap-3 px-4 py-3 text-gray-400 hover:text-white rounded-lg hover:bg-gray-700/50 transition-colors"
>
<span className="text-sm"></span>

View File

@@ -3,7 +3,7 @@ import type { Metadata } from "next"
import { Geist, Geist_Mono } from "next/font/google"
import { Analytics } from "@vercel/analytics/next"
import "./globals.css"
import { LayoutWrapper } from "@/components/layout-wrapper"
import { LayoutWrapper } from "@/components/view/layout/layout-wrapper"
const _geist = Geist({ subsets: ["latin"] })
const _geistMono = Geist_Mono({ subsets: ["latin"] })

View File

@@ -1,223 +1,6 @@
/**
* 一场SOUL的创业实验 - 首页
* 开发: 卡若
* 技术支持: 存客宝
*/
"use client"
import { redirect } from "next/navigation"
import { useState, useEffect } from "react"
import { useRouter } from "next/navigation"
import { Search, ChevronRight, BookOpen } from "lucide-react"
import { useStore } from "@/lib/store"
import { bookData, getTotalSectionCount } from "@/lib/book-data"
import { SearchModal } from "@/components/search-modal"
import { BottomNav } from "@/components/bottom-nav"
export default function HomePage() {
const router = useRouter()
const { user } = useStore()
const [mounted, setMounted] = useState(false)
const [searchOpen, setSearchOpen] = useState(false)
// 计算数据(必须在所有 hooks 之后)
const totalSections = getTotalSectionCount()
const hasFullBook = user?.hasFullBook || false
const purchasedCount = hasFullBook ? totalSections : user?.purchasedSections?.length || 0
// 推荐章节
const featuredSections = [
{ id: "1.1", title: "荷包:电动车出租的被动收入模式", tag: "免费", part: "真实的人" },
{ id: "3.1", title: "3000万流水如何跑出来", tag: "热门", part: "真实的行业" },
{ id: "8.1", title: "流量杠杆:抖音、Soul、飞书", tag: "推荐", part: "真实的赚钱" },
]
// 最新更新
const latestSection = {
id: "9.14",
title: "大健康私域一个月150万的70后",
part: "真实的赚钱",
}
useEffect(() => {
setMounted(true)
}, [])
if (!mounted) {
return null
}
return (
<div className="min-h-screen bg-black text-white pb-24">
{/* 顶部区域 */}
<header className="px-4 pt-6 pb-4">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-[#00CED1] to-[#20B2AA] flex items-center justify-center shadow-lg shadow-[#00CED1]/30">
<span className="text-white font-bold text-lg">S</span>
</div>
<div>
<h1 className="text-lg font-bold text-white">Soul<span className="text-[#00CED1]"></span></h1>
<p className="text-xs text-gray-500"></p>
</div>
</div>
<div className="flex items-center gap-2">
<span className="text-xs text-[#00CED1] bg-[#00CED1]/10 px-2 py-1 rounded-full">{totalSections}</span>
</div>
</div>
{/* 搜索栏 */}
<div
onClick={() => setSearchOpen(true)}
className="flex items-center gap-3 px-4 py-3 rounded-xl bg-[#1c1c1e] border border-white/5 cursor-pointer hover:border-[#00CED1]/30 transition-colors"
>
<Search className="w-4 h-4 text-gray-500" />
<span className="text-gray-500 text-sm">...</span>
</div>
</header>
{/* 搜索弹窗 */}
<SearchModal open={searchOpen} onOpenChange={setSearchOpen} />
<main className="px-4 space-y-5">
{/* Banner卡片 - 最新章节 */}
<div
onClick={() => router.push(`/read/${latestSection.id}`)}
className="relative p-5 rounded-2xl overflow-hidden cursor-pointer"
style={{
background: "linear-gradient(135deg, #0d3331 0%, #1a1a2e 50%, #16213e 100%)",
}}
>
<div className="absolute top-0 right-0 w-32 h-32 opacity-20">
<div className="w-full h-full bg-[#00CED1] rounded-full blur-3xl" />
</div>
<span className="inline-block px-2 py-1 rounded text-xs bg-[#00CED1] text-black font-medium mb-3">
</span>
<h2 className="text-lg font-bold text-white mb-2 pr-8">{latestSection.title}</h2>
<p className="text-sm text-gray-400 mb-3">{latestSection.part}</p>
<div className="flex items-center gap-2 text-[#00CED1] text-sm font-medium">
<ChevronRight className="w-4 h-4" />
</div>
</div>
{/* 阅读进度卡 */}
<div className="p-4 rounded-2xl bg-[#1c1c1e] border border-white/5">
<div className="flex items-center justify-between mb-3">
<h3 className="text-sm font-medium text-white"></h3>
<span className="text-xs text-gray-500">
{purchasedCount}/{totalSections}
</span>
</div>
<div className="w-full h-2 bg-[#2c2c2e] rounded-full overflow-hidden mb-3">
<div
className="h-full bg-gradient-to-r from-[#00CED1] to-[#20B2AA] rounded-full transition-all"
style={{ width: `${(purchasedCount / totalSections) * 100}%` }}
/>
</div>
<div className="grid grid-cols-4 gap-3">
<div className="text-center">
<p className="text-[#00CED1] text-lg font-bold">{purchasedCount}</p>
<p className="text-gray-500 text-xs"></p>
</div>
<div className="text-center">
<p className="text-white text-lg font-bold">{totalSections - purchasedCount}</p>
<p className="text-gray-500 text-xs"></p>
</div>
<div className="text-center">
<p className="text-white text-lg font-bold">5</p>
<p className="text-gray-500 text-xs"></p>
</div>
<div className="text-center">
<p className="text-white text-lg font-bold">11</p>
<p className="text-gray-500 text-xs"></p>
</div>
</div>
</div>
{/* 精选推荐 */}
<div>
<div className="flex items-center justify-between mb-3">
<h3 className="text-base font-semibold text-white"></h3>
<button onClick={() => router.push("/chapters")} className="text-xs text-[#00CED1] flex items-center gap-1">
<ChevronRight className="w-3 h-3" />
</button>
</div>
<div className="space-y-3">
{featuredSections.map((section) => (
<div
key={section.id}
onClick={() => router.push(`/read/${section.id}`)}
className="p-4 rounded-xl bg-[#1c1c1e] border border-white/5 cursor-pointer active:scale-[0.98] transition-transform"
>
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
<span className="text-[#00CED1] text-xs font-medium">{section.id}</span>
<span
className={`text-xs px-2 py-0.5 rounded ${
section.tag === "免费"
? "bg-[#00CED1]/10 text-[#00CED1]"
: section.tag === "热门"
? "bg-pink-500/10 text-pink-400"
: "bg-purple-500/10 text-purple-400"
}`}
>
{section.tag}
</span>
</div>
<h4 className="text-white font-medium text-sm mb-1">{section.title}</h4>
<p className="text-gray-500 text-xs">{section.part}</p>
</div>
<ChevronRight className="w-4 h-4 text-gray-600 mt-1" />
</div>
</div>
))}
</div>
</div>
<div>
<h3 className="text-base font-semibold text-white mb-3"></h3>
<div className="space-y-3">
{bookData.map((part) => (
<div
key={part.id}
onClick={() => router.push("/chapters")}
className="p-4 rounded-xl bg-[#1c1c1e] border border-white/5 cursor-pointer active:scale-[0.98] transition-transform"
>
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-[#00CED1]/20 to-[#20B2AA]/10 flex items-center justify-center shrink-0">
<span className="text-[#00CED1] font-bold text-sm">{part.number}</span>
</div>
<div className="flex-1 min-w-0">
<h4 className="text-white font-medium text-sm mb-0.5">{part.title}</h4>
<p className="text-gray-500 text-xs truncate">{part.subtitle}</p>
</div>
<ChevronRight className="w-4 h-4 text-gray-600 shrink-0" />
</div>
</div>
))}
</div>
</div>
{/* 序言入口 */}
<div
onClick={() => router.push("/read/preface")}
className="p-4 rounded-xl bg-gradient-to-r from-[#00CED1]/10 to-transparent border border-[#00CED1]/20 cursor-pointer"
>
<div className="flex items-center justify-between">
<div>
<h4 className="text-white font-medium text-sm mb-1"></h4>
<p className="text-gray-400 text-xs">6Soul开播?</p>
</div>
<span className="text-xs text-[#00CED1] bg-[#00CED1]/10 px-2 py-1 rounded"></span>
</div>
</div>
</main>
{/* 使用统一的底部导航组件 */}
<BottomNav />
</div>
)
/** 根路径重定向到移动端首页 */
export default function RootPage() {
redirect("/view")
}

View File

@@ -6,7 +6,6 @@ import { ChevronRight, Lock, Unlock, Book, BookOpen, Sparkles, Zap, Crown, Searc
import { useStore } from "@/lib/store"
import { bookData, getTotalSectionCount, specialSections, getPremiumBookPrice, getExtraSectionsCount, BASE_SECTIONS_COUNT } from "@/lib/book-data"
import { SearchModal } from "@/components/search-modal"
import { BottomNav } from "@/components/bottom-nav"
export default function ChaptersPage() {
const router = useRouter()
@@ -22,7 +21,7 @@ export default function ChaptersPage() {
const extraSections = getExtraSectionsCount()
const handleSectionClick = (sectionId: string) => {
router.push(`/read/${sectionId}`)
router.push(`/view/read/${sectionId}`)
}
return (
@@ -210,7 +209,6 @@ export default function ChaptersPage() {
</div>
</main>
<BottomNav />
</div>
)
}

View File

@@ -8,7 +8,7 @@ export default function DocsPage() {
<main className="min-h-screen bg-[#0a1628] text-white pb-20">
<div className="sticky top-0 z-10 bg-[#0a1628]/95 backdrop-blur-md border-b border-gray-700/50">
<div className="max-w-2xl mx-auto flex items-center gap-4 p-4">
<Link href="/" className="p-2 -ml-2">
<Link href="/view" className="p-2 -ml-2">
<ArrowLeft className="w-5 h-5" />
</Link>
<h1 className="text-lg font-semibold"></h1>

View File

@@ -46,7 +46,7 @@ export default function ForgotPasswordPage() {
if (data.success) {
setSuccess(true)
setTimeout(() => router.push("/login"), 2000)
setTimeout(() => router.push("/view/login"), 2000)
} else {
setError(data.error || "重置失败")
}
@@ -68,7 +68,7 @@ export default function ForgotPasswordPage() {
<div className="min-h-screen bg-black text-white flex flex-col">
<header className="flex items-center px-4 py-3">
<Link
href="/login"
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" />

View File

@@ -50,7 +50,7 @@ export default function LoginPage() {
}
const success = await login(phone, code)
if (success) {
router.push("/")
router.push("/view")
} else {
setError("密码错误或用户不存在")
}
@@ -69,7 +69,7 @@ export default function LoginPage() {
}
const success = await register(phone, nickname, code, referralCode || undefined)
if (success) {
router.push("/")
router.push("/view")
} else {
setError("该手机号已注册")
}
@@ -167,7 +167,7 @@ export default function LoginPage() {
<div className="text-center space-y-2">
{mode === "login" && (
<div>
<Link href="/login/forgot" className="text-[#30d158] text-sm">
<Link href="/view/login/forgot" className="text-[#30d158] text-sm">
</Link>
</div>

View File

@@ -4,7 +4,6 @@ import { useState, useEffect } from "react"
import { motion, AnimatePresence } from "framer-motion"
import { Users, X, CheckCircle, Loader2, Lock, Zap } from "lucide-react"
import { useRouter } from "next/navigation"
import { BottomNav } from "@/components/bottom-nav"
import { useStore } from "@/lib/store"
interface MatchUser {
@@ -367,7 +366,7 @@ export default function MatchPage() {
</span>
{matchesRemaining <= 0 && !user?.hasFullBook && (
<button
onClick={() => router.push('/chapters')}
onClick={() => router.push('/view/chapters')}
className="px-3 py-1.5 rounded-full bg-[#FFD700]/20 text-[#FFD700] text-xs font-medium"
>
+1
@@ -495,7 +494,7 @@ export default function MatchPage() {
<p className="text-gray-400 text-sm mt-1">9.93</p>
</div>
<button
onClick={() => router.push('/chapters')}
onClick={() => router.push('/view/chapters')}
className="px-4 py-2 rounded-lg bg-[#00E5FF] text-black text-sm font-medium"
>
@@ -706,7 +705,7 @@ export default function MatchPage() {
<button
onClick={() => {
setShowUnlockModal(false)
router.push('/chapters')
router.push('/view/chapters')
}}
className="w-full py-3 rounded-xl bg-[#FFD700] text-black font-medium"
>
@@ -845,7 +844,6 @@ export default function MatchPage() {
)}
</AnimatePresence>
<BottomNav />
</div>
)
}

View File

@@ -8,7 +8,7 @@ import { useStore } from "@/lib/store"
export default function EditAddressPage() {
const router = useRouter()
const params = useParams()
const id = params.id as string
const id = params?.id as string
const { user } = useStore()
const [loading, setLoading] = useState(false)
const [fetching, setFetching] = useState(true)
@@ -21,7 +21,7 @@ export default function EditAddressPage() {
const [isDefault, setIsDefault] = useState(false)
useEffect(() => {
if (!id) {
if (!id || !user?.id) {
setFetching(false)
return
}
@@ -29,19 +29,18 @@ export default function EditAddressPage() {
.then((res) => res.json())
.then((data) => {
if (data.success && data.item) {
const item = data.item
setName(item.name)
setPhone(item.phone)
setProvince(item.province)
setCity(item.city)
setDistrict(item.district)
setDetail(item.detail)
setIsDefault(item.isDefault)
const a = data.item
setName(a.name || "")
setPhone(a.phone || "")
setProvince(a.province || "")
setCity(a.city || "")
setDistrict(a.district || "")
setDetail(a.detail || "")
setIsDefault(!!a.isDefault)
}
setFetching(false)
})
.catch(() => setFetching(false))
}, [id])
.finally(() => setFetching(false))
}, [id, user?.id])
if (!user?.id) {
return (
@@ -61,7 +60,6 @@ export default function EditAddressPage() {
alert("请输入正确的手机号")
return
}
// 省/市/区为选填
if (!detail.trim()) {
alert("请输入详细地址")
return
@@ -83,7 +81,7 @@ export default function EditAddressPage() {
})
const data = await res.json()
if (data.success) {
router.push("/my/addresses")
router.push("/view/my/addresses")
} else {
alert(data.message || "保存失败")
}
@@ -97,7 +95,7 @@ export default function EditAddressPage() {
if (fetching) {
return (
<div className="min-h-screen bg-black text-white flex items-center justify-center">
<div className="text-white/40 text-sm">...</div>
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-[#00CED1]" />
</div>
)
}
@@ -140,54 +138,21 @@ export default function EditAddressPage() {
<div className="flex items-center justify-between p-4 border-b border-white/5">
<label className="text-white text-sm w-24"></label>
<div className="flex-1 flex gap-2 justify-end">
<input
type="text"
value={province}
onChange={(e) => setProvince(e.target.value)}
placeholder="省"
className="flex-1 max-w-24 bg-transparent text-white text-sm text-right placeholder-white/30 outline-none"
/>
<input
type="text"
value={city}
onChange={(e) => setCity(e.target.value)}
placeholder="市"
className="flex-1 max-w-24 bg-transparent text-white text-sm text-right placeholder-white/30 outline-none"
/>
<input
type="text"
value={district}
onChange={(e) => setDistrict(e.target.value)}
placeholder="区"
className="flex-1 max-w-24 bg-transparent text-white text-sm text-right placeholder-white/30 outline-none"
/>
<input type="text" value={province} onChange={(e) => setProvince(e.target.value)} placeholder="省" className="flex-1 max-w-24 bg-transparent text-white text-sm text-right placeholder-white/30 outline-none" />
<input type="text" value={city} onChange={(e) => setCity(e.target.value)} placeholder="市" className="flex-1 max-w-24 bg-transparent text-white text-sm text-right placeholder-white/30 outline-none" />
<input type="text" value={district} onChange={(e) => setDistrict(e.target.value)} placeholder="区" className="flex-1 max-w-24 bg-transparent text-white text-sm text-right placeholder-white/30 outline-none" />
</div>
</div>
<div className="flex items-start justify-between p-4">
<label className="text-white text-sm w-24 pt-2"></label>
<textarea
value={detail}
onChange={(e) => setDetail(e.target.value)}
placeholder="街道、楼栋、门牌号等"
rows={3}
className="flex-1 bg-transparent text-white text-sm text-right placeholder-white/30 outline-none resize-none"
/>
<textarea value={detail} onChange={(e) => setDetail(e.target.value)} placeholder="街道、楼栋、门牌号等" rows={3} className="flex-1 bg-transparent text-white text-sm text-right placeholder-white/30 outline-none resize-none" />
</div>
<div className="flex items-center justify-between p-4 border-t border-white/5">
<span className="text-white text-sm"></span>
<input
type="checkbox"
checked={isDefault}
onChange={(e) => setIsDefault(e.target.checked)}
className="w-5 h-5 rounded accent-[#00CED1]"
/>
<input type="checkbox" checked={isDefault} onChange={(e) => setIsDefault(e.target.checked)} className="w-5 h-5 rounded accent-[#00CED1]" />
</div>
</div>
<button
type="submit"
disabled={loading}
className="w-full py-3 rounded-xl bg-[#00CED1] text-black font-medium disabled:opacity-50"
>
<button type="submit" disabled={loading} className="w-full py-3 rounded-xl bg-[#00CED1] text-black font-medium disabled:opacity-50">
{loading ? "保存中..." : "保存"}
</button>
</form>

View File

@@ -58,7 +58,7 @@ export default function NewAddressPage() {
})
const data = await res.json()
if (data.success) {
router.push("/my/addresses")
router.push("/view/my/addresses")
} else {
alert(data.message || "添加失败")
}

View File

@@ -59,7 +59,7 @@ export default function AddressesPage() {
<div className="text-center">
<p className="text-white/60 mb-4"></p>
<button
onClick={() => router.push("/my")}
onClick={() => router.push("/view/my")}
className="px-4 py-2 rounded-xl bg-[#00CED1] text-black font-medium"
>
@@ -112,7 +112,7 @@ export default function AddressesPage() {
<p className="text-white/60 text-sm leading-relaxed">{item.fullAddress}</p>
<div className="flex justify-end gap-4 mt-3 pt-3 border-t border-white/5">
<button
onClick={() => router.push(`/my/addresses/${item.id}`)}
onClick={() => router.push(`/view/my/addresses/${item.id}`)}
className="flex items-center gap-1 text-[#00CED1] text-sm"
>
<Pencil className="w-4 h-4" />
@@ -130,7 +130,7 @@ export default function AddressesPage() {
)}
<button
onClick={() => router.push("/my/addresses/new")}
onClick={() => router.push("/view/my/addresses/new")}
className="mt-6 w-full py-3 rounded-xl bg-[#00CED1] text-black font-medium flex items-center justify-center gap-2"
>
<Plus className="w-5 h-5" />

View File

@@ -5,7 +5,6 @@ import { useRouter } from "next/navigation"
import { User, Users, ChevronRight, Gift, Star, Info, Wallet, Footprints, Eye, BookOpen, Clock, ArrowUpRight, Phone, MessageCircle, CreditCard, X, Check, Loader2, Settings } from "lucide-react"
import { useStore } from "@/lib/store"
import { AuthModal } from "@/components/modules/auth/auth-modal"
import { BottomNav } from "@/components/bottom-nav"
import { getFullBookPrice, getTotalSectionCount } from "@/lib/book-data"
export default function MyPage() {
@@ -190,7 +189,7 @@ export default function MyPage() {
<ChevronRight className="w-5 h-5 text-white/30" />
</button>
<button
onClick={() => router.push("/about")}
onClick={() => router.push("/view/about")}
className="w-full flex items-center justify-between p-4 active:bg-white/5"
>
<div className="flex items-center gap-3">
@@ -203,7 +202,6 @@ export default function MyPage() {
</button>
</div>
<BottomNav />
<AuthModal isOpen={showAuthModal} onClose={() => setShowAuthModal(false)} />
</main>
)
@@ -275,7 +273,7 @@ export default function MyPage() {
<span className="text-white font-medium"></span>
</div>
<button
onClick={() => router.push("/my/referral")}
onClick={() => router.push("/view/my/referral")}
className="text-[#00CED1] text-xs flex items-center gap-1"
>
广
@@ -299,7 +297,7 @@ export default function MyPage() {
</div>
<button
onClick={() => router.push("/my/referral")}
onClick={() => router.push("/view/my/referral")}
className="w-full py-2.5 rounded-xl bg-gradient-to-r from-[#FFD700]/80 to-[#FFA500]/80 text-black text-sm font-bold flex items-center justify-center gap-2"
>
<Gift className="w-4 h-4" />
@@ -338,7 +336,7 @@ export default function MyPage() {
{/* 菜单列表 */}
<div className="mx-4 mt-4 rounded-2xl bg-[#1c1c1e] border border-white/5 overflow-hidden">
<button
onClick={() => router.push("/my/purchases")}
onClick={() => router.push("/view/my/purchases")}
className="w-full flex items-center justify-between p-4 border-b border-white/5 active:bg-white/5"
>
<div className="flex items-center gap-3">
@@ -351,7 +349,7 @@ export default function MyPage() {
</div>
</button>
<button
onClick={() => router.push("/my/referral")}
onClick={() => router.push("/view/my/referral")}
className="w-full flex items-center justify-between p-4 border-b border-white/5 active:bg-white/5"
>
<div className="flex items-center gap-3">
@@ -366,7 +364,7 @@ export default function MyPage() {
</div>
</button>
<button
onClick={() => router.push("/about")}
onClick={() => router.push("/view/about")}
className="w-full flex items-center justify-between p-4 active:bg-white/5"
>
<div className="flex items-center gap-3">
@@ -378,7 +376,7 @@ export default function MyPage() {
<ChevronRight className="w-5 h-5 text-white/30" />
</button>
<button
onClick={() => router.push("/my/settings")}
onClick={() => router.push("/view/my/settings")}
className="w-full flex items-center justify-between p-4 active:bg-white/5"
>
<div className="flex items-center gap-3">
@@ -438,7 +436,7 @@ export default function MyPage() {
<span className="text-white text-sm"> {sectionId}</span>
</div>
<button
onClick={() => router.push(`/read/${sectionId}`)}
onClick={() => router.push(`/view/read/${sectionId}`)}
className="text-[#00CED1] text-xs"
>
@@ -451,7 +449,7 @@ export default function MyPage() {
<BookOpen className="w-8 h-8 mx-auto mb-2 opacity-50" />
<p className="text-sm"></p>
<button
onClick={() => router.push("/chapters")}
onClick={() => router.push("/view/chapters")}
className="mt-2 text-[#00CED1] text-sm"
>
@@ -471,7 +469,7 @@ export default function MyPage() {
<Users className="w-8 h-8 mx-auto mb-2 opacity-50" />
<p className="text-sm"></p>
<button
onClick={() => router.push("/match")}
onClick={() => router.push("/view/match")}
className="mt-2 text-[#00CED1] text-sm"
>
@@ -483,7 +481,6 @@ export default function MyPage() {
</>
)}
<BottomNav />
{/* 绑定弹窗 */}
{showBindModal && (

View File

@@ -13,7 +13,7 @@ export default function MyPurchasesPage() {
<div className="min-h-screen bg-[#0a1628] text-white flex items-center justify-center">
<div className="text-center">
<p className="text-gray-400 mb-4"></p>
<Link href="/" className="text-[#38bdac] hover:underline">
<Link href="/view" className="text-[#38bdac] hover:underline">
</Link>
</div>
@@ -29,7 +29,7 @@ export default function MyPurchasesPage() {
{/* Header */}
<header className="sticky top-0 z-50 bg-[#0a1628]/90 backdrop-blur-md border-b border-gray-800">
<div className="max-w-4xl mx-auto px-4 py-4 flex items-center">
<Link href="/" className="flex items-center gap-2 text-gray-400 hover:text-white transition-colors">
<Link href="/view" className="flex items-center gap-2 text-gray-400 hover:text-white transition-colors">
<ChevronLeft className="w-5 h-5" />
<span></span>
</Link>
@@ -66,7 +66,7 @@ export default function MyPurchasesPage() {
<div className="text-center py-12">
<BookOpen className="w-16 h-16 text-gray-600 mx-auto mb-4" />
<p className="text-gray-400 mb-4"></p>
<Link href="/chapters" className="text-[#38bdac] hover:underline">
<Link href="/view/chapters" className="text-[#38bdac] hover:underline">
</Link>
</div>
@@ -89,7 +89,7 @@ export default function MyPurchasesPage() {
{purchasedInPart.map((section) => (
<Link
key={section.id}
href={`/read/${section.id}`}
href={`/view/read/${section.id}`}
className="flex items-center gap-3 px-4 py-3 hover:bg-[#0f2137]/40 transition-colors"
>
<CheckCircle className="w-4 h-4 text-[#38bdac]" />

View File

@@ -132,7 +132,7 @@ export default function ReferralPage() {
<div className="min-h-screen bg-black text-white flex items-center justify-center pb-20">
<div className="text-center glass-card p-8">
<p className="text-[var(--app-text-secondary)] mb-4"></p>
<Link href="/" className="btn-ios inline-block">
<Link href="/view" className="btn-ios inline-block">
</Link>
</div>
@@ -211,7 +211,7 @@ export default function ReferralPage() {
{/* Header - iOS风格 */}
<header className="sticky top-0 z-50 glass-nav safe-top">
<div className="max-w-md mx-auto px-4 py-3 flex items-center">
<Link href="/my" className="w-8 h-8 rounded-full bg-[var(--app-bg-secondary)] flex items-center justify-center touch-feedback">
<Link href="/view/my" className="w-8 h-8 rounded-full bg-[var(--app-bg-secondary)] flex items-center justify-center touch-feedback">
<ChevronLeft className="w-5 h-5 text-[var(--app-text-secondary)]" />
</Link>
<h1 className="flex-1 text-center font-semibold"></h1>

View File

@@ -176,7 +176,7 @@ export default function SettingsPage() {
{/* 收货地址 */}
<button
onClick={() => router.push("/my/addresses")}
onClick={() => router.push("/view/my/addresses")}
className="w-full flex items-center justify-between p-4 active:bg-white/5"
>
<div className="flex items-center gap-3">
@@ -205,7 +205,7 @@ export default function SettingsPage() {
<button
onClick={() => {
logout()
router.push("/")
router.push("/view")
}}
className="w-full py-3 rounded-xl bg-[#1c1c1e] text-red-400 font-medium border border-red-400/30"
>

221
app/view/page.tsx Normal file
View File

@@ -0,0 +1,221 @@
/**
* 一场SOUL的创业实验 - 首页
* 开发: 卡若
* 技术支持: 存客宝
*/
"use client"
import { useState, useEffect } from "react"
import { useRouter } from "next/navigation"
import { Search, ChevronRight, BookOpen } from "lucide-react"
import { useStore } from "@/lib/store"
import { bookData, getTotalSectionCount } from "@/lib/book-data"
import { SearchModal } from "@/components/search-modal"
export default function HomePage() {
const router = useRouter()
const { user } = useStore()
const [mounted, setMounted] = useState(false)
const [searchOpen, setSearchOpen] = useState(false)
// 计算数据(必须在所有 hooks 之后)
const totalSections = getTotalSectionCount()
const hasFullBook = user?.hasFullBook || false
const purchasedCount = hasFullBook ? totalSections : user?.purchasedSections?.length || 0
// 推荐章节
const featuredSections = [
{ id: "1.1", title: "荷包:电动车出租的被动收入模式", tag: "免费", part: "真实的人" },
{ id: "3.1", title: "3000万流水如何跑出来", tag: "热门", part: "真实的行业" },
{ id: "8.1", title: "流量杠杆:抖音、Soul、飞书", tag: "推荐", part: "真实的赚钱" },
]
// 最新更新
const latestSection = {
id: "9.14",
title: "大健康私域一个月150万的70后",
part: "真实的赚钱",
}
useEffect(() => {
setMounted(true)
}, [])
if (!mounted) {
return null
}
return (
<div className="min-h-screen bg-black text-white pb-24">
{/* 顶部区域 */}
<header className="px-4 pt-6 pb-4">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-[#00CED1] to-[#20B2AA] flex items-center justify-center shadow-lg shadow-[#00CED1]/30">
<span className="text-white font-bold text-lg">S</span>
</div>
<div>
<h1 className="text-lg font-bold text-white">Soul<span className="text-[#00CED1]"></span></h1>
<p className="text-xs text-gray-500"></p>
</div>
</div>
<div className="flex items-center gap-2">
<span className="text-xs text-[#00CED1] bg-[#00CED1]/10 px-2 py-1 rounded-full">{totalSections}</span>
</div>
</div>
{/* 搜索栏 */}
<div
onClick={() => setSearchOpen(true)}
className="flex items-center gap-3 px-4 py-3 rounded-xl bg-[#1c1c1e] border border-white/5 cursor-pointer hover:border-[#00CED1]/30 transition-colors"
>
<Search className="w-4 h-4 text-gray-500" />
<span className="text-gray-500 text-sm">...</span>
</div>
</header>
{/* 搜索弹窗 */}
<SearchModal open={searchOpen} onOpenChange={setSearchOpen} />
<main className="px-4 space-y-5">
{/* Banner卡片 - 最新章节 */}
<div
onClick={() => router.push(`/view/read/${latestSection.id}`)}
className="relative p-5 rounded-2xl overflow-hidden cursor-pointer"
style={{
background: "linear-gradient(135deg, #0d3331 0%, #1a1a2e 50%, #16213e 100%)",
}}
>
<div className="absolute top-0 right-0 w-32 h-32 opacity-20">
<div className="w-full h-full bg-[#00CED1] rounded-full blur-3xl" />
</div>
<span className="inline-block px-2 py-1 rounded text-xs bg-[#00CED1] text-black font-medium mb-3">
</span>
<h2 className="text-lg font-bold text-white mb-2 pr-8">{latestSection.title}</h2>
<p className="text-sm text-gray-400 mb-3">{latestSection.part}</p>
<div className="flex items-center gap-2 text-[#00CED1] text-sm font-medium">
<ChevronRight className="w-4 h-4" />
</div>
</div>
{/* 阅读进度卡 */}
<div className="p-4 rounded-2xl bg-[#1c1c1e] border border-white/5">
<div className="flex items-center justify-between mb-3">
<h3 className="text-sm font-medium text-white"></h3>
<span className="text-xs text-gray-500">
{purchasedCount}/{totalSections}
</span>
</div>
<div className="w-full h-2 bg-[#2c2c2e] rounded-full overflow-hidden mb-3">
<div
className="h-full bg-gradient-to-r from-[#00CED1] to-[#20B2AA] rounded-full transition-all"
style={{ width: `${(purchasedCount / totalSections) * 100}%` }}
/>
</div>
<div className="grid grid-cols-4 gap-3">
<div className="text-center">
<p className="text-[#00CED1] text-lg font-bold">{purchasedCount}</p>
<p className="text-gray-500 text-xs"></p>
</div>
<div className="text-center">
<p className="text-white text-lg font-bold">{totalSections - purchasedCount}</p>
<p className="text-gray-500 text-xs"></p>
</div>
<div className="text-center">
<p className="text-white text-lg font-bold">5</p>
<p className="text-gray-500 text-xs"></p>
</div>
<div className="text-center">
<p className="text-white text-lg font-bold">11</p>
<p className="text-gray-500 text-xs"></p>
</div>
</div>
</div>
{/* 精选推荐 */}
<div>
<div className="flex items-center justify-between mb-3">
<h3 className="text-base font-semibold text-white"></h3>
<button onClick={() => router.push("/view/chapters")} className="text-xs text-[#00CED1] flex items-center gap-1">
<ChevronRight className="w-3 h-3" />
</button>
</div>
<div className="space-y-3">
{featuredSections.map((section) => (
<div
key={section.id}
onClick={() => router.push(`/view/read/${section.id}`)}
className="p-4 rounded-xl bg-[#1c1c1e] border border-white/5 cursor-pointer active:scale-[0.98] transition-transform"
>
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
<span className="text-[#00CED1] text-xs font-medium">{section.id}</span>
<span
className={`text-xs px-2 py-0.5 rounded ${
section.tag === "免费"
? "bg-[#00CED1]/10 text-[#00CED1]"
: section.tag === "热门"
? "bg-pink-500/10 text-pink-400"
: "bg-purple-500/10 text-purple-400"
}`}
>
{section.tag}
</span>
</div>
<h4 className="text-white font-medium text-sm mb-1">{section.title}</h4>
<p className="text-gray-500 text-xs">{section.part}</p>
</div>
<ChevronRight className="w-4 h-4 text-gray-600 mt-1" />
</div>
</div>
))}
</div>
</div>
<div>
<h3 className="text-base font-semibold text-white mb-3"></h3>
<div className="space-y-3">
{bookData.map((part) => (
<div
key={part.id}
onClick={() => router.push("/view/chapters")}
className="p-4 rounded-xl bg-[#1c1c1e] border border-white/5 cursor-pointer active:scale-[0.98] transition-transform"
>
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-[#00CED1]/20 to-[#20B2AA]/10 flex items-center justify-center shrink-0">
<span className="text-[#00CED1] font-bold text-sm">{part.number}</span>
</div>
<div className="flex-1 min-w-0">
<h4 className="text-white font-medium text-sm mb-0.5">{part.title}</h4>
<p className="text-gray-500 text-xs truncate">{part.subtitle}</p>
</div>
<ChevronRight className="w-4 h-4 text-gray-600 shrink-0" />
</div>
</div>
))}
</div>
</div>
{/* 序言入口 */}
<div
onClick={() => router.push("/view/read/preface")}
className="p-4 rounded-xl bg-gradient-to-r from-[#00CED1]/10 to-transparent border border-[#00CED1]/20 cursor-pointer"
>
<div className="flex items-center justify-between">
<div>
<h4 className="text-white font-medium text-sm mb-1"></h4>
<p className="text-gray-400 text-xs">6Soul开播?</p>
</div>
<span className="text-xs text-[#00CED1] bg-[#00CED1]/10 px-2 py-1 rounded"></span>
</div>
</div>
</main>
{/* 使用统一的底部导航组件 */}
</div>
)
}

View File

@@ -67,7 +67,7 @@ export default async function HomePage() {
{/* 立即阅读按钮 */}
<div className="px-6 mb-6">
<Link href="/read/preface" className="btn-ios w-full py-4 text-lg shadow-[0_0_20px_rgba(48,209,88,0.2)]">
<Link href="/view/read/preface" className="btn-ios w-full py-4 text-lg shadow-[0_0_20px_rgba(48,209,88,0.2)]">
<div className="flex items-center gap-2">
<Home className="w-5 h-5" />
<span></span>
@@ -126,15 +126,15 @@ export default async function HomePage() {
{/* 底部导航 */}
<nav className="fixed bottom-0 left-0 right-0 h-20 bg-black/80 backdrop-blur-xl border-t border-white/5 flex items-center justify-around px-6 z-50">
<Link href="/" className="flex flex-col items-center gap-1 text-[#30D158]">
<Link href="/view" className="flex flex-col items-center gap-1 text-[#30D158]">
<Home className="w-6 h-6" />
<span className="text-[10px] font-medium"></span>
</Link>
<Link href="/match" className="flex flex-col items-center gap-1 text-white/40">
<Link href="/view/match" className="flex flex-col items-center gap-1 text-white/40">
<Sparkles className="w-6 h-6" />
<span className="text-[10px] font-medium"></span>
</Link>
<Link href="/my" className="flex flex-col items-center gap-1 text-white/40">
<Link href="/view/my" className="flex flex-col items-center gap-1 text-white/40">
<User className="w-6 h-6" />
<span className="text-[10px] font-medium"></span>
</Link>