Files
soul/app/chapters/page.tsx
卡若 4dd2f9f4a7 feat: 完善后台管理+搜索功能+分销系统
主要更新:
- 后台菜单精简(9项→6项)
- 新增搜索功能(敏感信息过滤)
- 分销绑定和提现系统完善
- 数据库初始化API(自动修复表结构)
- 用户管理:显示绑定关系详情
- 小程序:上下章导航优化、匹配页面重构
- 修复hydration和数据类型问题
2026-01-25 19:37:59 +08:00

240 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useState } from "react"
import { useRouter } from "next/navigation"
import { ChevronRight, Lock, Unlock, Book, BookOpen, Home, List, Sparkles, User, Users, Zap, Crown, Search } from "lucide-react"
import { useStore } from "@/lib/store"
import { bookData, getTotalSectionCount, specialSections, getPremiumBookPrice, getExtraSectionsCount, BASE_SECTIONS_COUNT } from "@/lib/book-data"
import { SearchModal } from "@/components/search-modal"
export default function ChaptersPage() {
const router = useRouter()
const { user, hasPurchased } = useStore()
const [expandedPart, setExpandedPart] = useState<string | null>("part-1")
const [bookVersion, setBookVersion] = useState<"basic" | "premium">("basic")
const [showPremiumTab, setShowPremiumTab] = useState(false) // 控制是否显示最新完整版标签
const [searchOpen, setSearchOpen] = useState(false)
const totalSections = getTotalSectionCount()
const hasFullBook = user?.hasFullBook || false
const premiumPrice = getPremiumBookPrice()
const extraSections = getExtraSectionsCount()
const handleSectionClick = (sectionId: string) => {
router.push(`/read/${sectionId}`)
}
return (
<div className="min-h-screen bg-black text-white pb-24">
<header className="sticky top-0 z-40 bg-black/90 backdrop-blur-xl border-b border-white/5">
<div className="px-4 py-3 flex items-center justify-between">
<div className="w-8" /> {/* 占位 */}
<h1 className="text-lg font-semibold text-[#00CED1]"></h1>
<button
onClick={() => setSearchOpen(true)}
className="w-8 h-8 rounded-full bg-[#2c2c2e] flex items-center justify-center hover:bg-[#3c3c3e] transition-colors"
>
<Search className="w-4 h-4 text-gray-400" />
</button>
</div>
</header>
{/* 搜索弹窗 */}
<SearchModal open={searchOpen} onOpenChange={setSearchOpen} />
<div className="mx-4 mt-4 p-4 rounded-2xl bg-gradient-to-br from-[#1c1c1e] to-[#2c2c2e] border border-[#00CED1]/20">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-[#00CED1] to-[#20B2AA] flex items-center justify-center">
<Book className="w-6 h-6 text-white" />
</div>
<div className="flex-1">
<h2 className="text-white font-semibold">SOUL的创业实验场</h2>
<p className="text-gray-500 text-xs mt-0.5">Soul派对房的真实商业故事</p>
</div>
<div className="text-right">
<div className="text-xl font-bold text-[#00CED1]">{totalSections}</div>
<div className="text-[10px] text-gray-500"></div>
</div>
</div>
</div>
{/* 版本标签切换 - 仅在showPremiumTab为true时显示 */}
{showPremiumTab && extraSections > 0 && (
<div className="mx-4 mt-3 flex gap-2">
<button
onClick={() => setBookVersion("basic")}
className={`flex-1 py-2.5 rounded-xl text-sm font-medium transition-all ${
bookVersion === "basic"
? "bg-[#00CED1]/20 text-[#00CED1] border border-[#00CED1]/30"
: "bg-[#1c1c1e] text-white/60 border border-white/5"
}`}
>
¥9.9
</button>
<button
onClick={() => setBookVersion("premium")}
className={`flex-1 py-2.5 rounded-xl text-sm font-medium transition-all flex items-center justify-center gap-1 ${
bookVersion === "premium"
? "bg-[#FFD700]/20 text-[#FFD700] border border-[#FFD700]/30"
: "bg-[#1c1c1e] text-white/60 border border-white/5"
}`}
>
<Crown className="w-4 h-4" />
¥{premiumPrice.toFixed(1)}
</button>
</div>
)}
{/* 目录内容 */}
<main className="px-4 py-4">
<button
onClick={() => handleSectionClick("preface")}
className="w-full mb-3 p-3 rounded-xl bg-[#1c1c1e] flex items-center justify-between active:bg-[#2c2c2e] transition-colors border border-white/5"
>
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-[#00CED1]/20 flex items-center justify-center">
<BookOpen className="w-4 h-4 text-[#00CED1]" />
</div>
<span className="text-sm font-medium text-white">6Soul开播?</span>
</div>
<div className="flex items-center gap-2">
<span className="text-xs text-[#00CED1]"></span>
<ChevronRight className="w-4 h-4 text-gray-500" />
</div>
</button>
{bookData.map((part) => (
<div key={part.id} className="mb-3">
<button
onClick={() => setExpandedPart(expandedPart === part.id ? null : part.id)}
className="w-full p-3 rounded-xl bg-[#1c1c1e] flex items-center justify-between active:bg-[#2c2c2e] transition-colors border border-white/5"
>
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-[#00CED1] to-[#20B2AA] flex items-center justify-center text-sm font-bold text-white">
{part.number}
</div>
<div className="text-left">
<div className="text-sm font-semibold text-white">{part.title}</div>
<div className="text-[10px] text-gray-500">{part.subtitle}</div>
</div>
</div>
<div className="flex items-center gap-2">
<span className="text-xs text-gray-500">
{part.chapters.reduce((acc, ch) => acc + ch.sections.length, 0)}
</span>
<ChevronRight
className={`w-4 h-4 text-gray-500 transition-transform ${expandedPart === part.id ? "rotate-90" : ""}`}
/>
</div>
</button>
{expandedPart === part.id && (
<div className="mt-2 ml-2 space-y-1">
{part.chapters.map((chapter) => (
<div key={chapter.id} className="rounded-lg bg-[#1c1c1e]/50 overflow-hidden border border-white/5">
<div className="px-3 py-2 text-xs font-medium text-gray-400 border-b border-white/5">
{chapter.title}
</div>
{chapter.sections.map((section) => {
const isPurchased = hasFullBook || hasPurchased(section.id)
const canRead = section.isFree || isPurchased
return (
<button
key={section.id}
onClick={() => handleSectionClick(section.id)}
className="w-full px-3 py-2.5 flex items-center justify-between border-b border-white/5 last:border-0 active:bg-white/5 transition-colors"
>
<div className="flex items-center gap-2 flex-1 min-w-0">
{canRead ? (
<Unlock className="w-3.5 h-3.5 text-[#00CED1] flex-shrink-0" />
) : (
<Lock className="w-3.5 h-3.5 text-gray-500 flex-shrink-0" />
)}
<span className={`text-xs truncate ${canRead ? "text-white" : "text-gray-400"}`}>
{section.id} {section.title}
</span>
</div>
<div className="flex items-center gap-2 flex-shrink-0 ml-2">
{section.isFree ? (
<span className="text-[10px] text-[#00CED1] px-1.5 py-0.5 rounded bg-[#00CED1]/10">
</span>
) : isPurchased ? (
<span className="text-[10px] text-[#00CED1]"></span>
) : (
<span className="text-[10px] text-gray-500">¥{section.price}</span>
)}
<ChevronRight className="w-3.5 h-3.5 text-gray-600" />
</div>
</button>
)
})}
</div>
))}
</div>
)}
</div>
))}
<button
onClick={() => handleSectionClick("epilogue")}
className="w-full mb-3 p-3 rounded-xl bg-[#1c1c1e] flex items-center justify-between active:bg-[#2c2c2e] transition-colors border border-white/5"
>
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-[#00CED1]/20 flex items-center justify-center">
<BookOpen className="w-4 h-4 text-[#00CED1]" />
</div>
<span className="text-sm font-medium text-white"></span>
</div>
<div className="flex items-center gap-2">
<span className="text-xs text-[#00CED1]"></span>
<ChevronRight className="w-4 h-4 text-gray-500" />
</div>
</button>
{/* 附录 */}
<div className="mb-3 p-3 rounded-xl bg-[#1c1c1e] border border-white/5">
<div className="text-xs font-medium text-gray-400 mb-2"></div>
{specialSections.appendix.map((item) => (
<button
key={item.id}
onClick={() => handleSectionClick(item.id)}
className="w-full py-2 flex items-center justify-between border-b border-white/5 last:border-0 active:bg-white/5"
>
<span className="text-xs text-gray-300">{item.title}</span>
<ChevronRight className="w-3.5 h-3.5 text-gray-600" />
</button>
))}
</div>
</main>
<nav className="fixed bottom-0 left-0 right-0 bg-[#1c1c1e]/95 backdrop-blur-xl border-t border-white/5 pb-safe-bottom">
<div className="px-4 py-2">
<div className="flex items-center justify-around">
<button onClick={() => router.push("/")} className="flex flex-col items-center py-2 px-4">
<Home className="w-5 h-5 text-gray-500 mb-1" />
<span className="text-gray-500 text-xs"></span>
</button>
<button className="flex flex-col items-center py-2 px-4">
<List className="w-5 h-5 text-[#00CED1] mb-1" />
<span className="text-[#00CED1] text-xs font-medium"></span>
</button>
{/* 找伙伴按钮 */}
<button onClick={() => router.push("/match")} className="flex flex-col items-center py-2 px-6 -mt-4">
<div className="w-14 h-14 rounded-full bg-gradient-to-br from-[#00CED1] to-[#20B2AA] flex items-center justify-center shadow-lg shadow-[#00CED1]/30">
<Users className="w-7 h-7 text-white" />
</div>
<span className="text-gray-500 text-xs mt-1"></span>
</button>
<button onClick={() => router.push("/my")} className="flex flex-col items-center py-2 px-4">
<User className="w-5 h-5 text-gray-500 mb-1" />
<span className="text-gray-500 text-xs"></span>
</button>
</div>
</div>
</nav>
</div>
)
}