2026-01-14 05:24:13 +00:00
|
|
|
|
"use client"
|
2026-01-09 11:58:08 +08:00
|
|
|
|
|
2026-01-14 06:39:23 +00:00
|
|
|
|
import { useState, useEffect, useRef, useCallback } from "react"
|
2026-01-14 05:24:13 +00:00
|
|
|
|
import { useRouter } from "next/navigation"
|
2026-01-14 06:39:23 +00:00
|
|
|
|
import { ChevronRight, Sparkles, Lock, Share2 } from "lucide-react"
|
2026-01-14 05:24:13 +00:00
|
|
|
|
import { useStore } from "@/lib/store"
|
2026-01-14 06:39:23 +00:00
|
|
|
|
import { getFullBookPrice, getTotalSectionCount } from "@/lib/book-data"
|
|
|
|
|
|
import { AuthModal } from "@/components/modules/auth/auth-modal"
|
|
|
|
|
|
import { PaymentModal } from "@/components/payment-modal"
|
2026-01-09 11:58:08 +08:00
|
|
|
|
|
2026-01-14 05:24:13 +00:00
|
|
|
|
export default function HomePage() {
|
|
|
|
|
|
const router = useRouter()
|
|
|
|
|
|
const { user, isLoggedIn, hasPurchased } = useStore()
|
2026-01-14 06:39:23 +00:00
|
|
|
|
const [content, setContent] = useState<string>("")
|
|
|
|
|
|
const [isLoading, setIsLoading] = useState(true)
|
|
|
|
|
|
const [showPaywall, setShowPaywall] = useState(false)
|
|
|
|
|
|
const [readingProgress, setReadingProgress] = useState(0)
|
|
|
|
|
|
const [isAuthOpen, setIsAuthOpen] = useState(false)
|
|
|
|
|
|
const [isPaymentOpen, setIsPaymentOpen] = useState(false)
|
|
|
|
|
|
const [paymentType, setPaymentType] = useState<"section" | "fullbook">("section")
|
|
|
|
|
|
const contentRef = useRef<HTMLDivElement>(null)
|
2026-01-14 05:24:13 +00:00
|
|
|
|
|
2026-01-14 06:39:23 +00:00
|
|
|
|
const fullBookPrice = getFullBookPrice()
|
2026-01-14 05:24:13 +00:00
|
|
|
|
const totalSections = getTotalSectionCount()
|
|
|
|
|
|
const hasFullBook = user?.hasFullBook || false
|
|
|
|
|
|
|
2026-01-14 06:39:23 +00:00
|
|
|
|
// 最新章节 - 使用1.1作为示例
|
|
|
|
|
|
const latestSection = {
|
|
|
|
|
|
id: "1.1",
|
|
|
|
|
|
title: "荷包:电动车出租的被动收入模式",
|
|
|
|
|
|
price: 1,
|
|
|
|
|
|
isFree: true,
|
|
|
|
|
|
filePath: "book/第一篇|真实的人/第1章|人与人之间的底层逻辑/1.1 荷包:电动车出租的被动收入模式.md",
|
2026-01-14 05:24:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-14 06:39:23 +00:00
|
|
|
|
// 加载最新章节内容
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
async function loadContent() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`/api/content?path=${encodeURIComponent(latestSection.filePath)}`)
|
|
|
|
|
|
if (response.ok) {
|
|
|
|
|
|
const data = await response.json()
|
|
|
|
|
|
setContent(data.content || "")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("Failed to load content:", error)
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setIsLoading(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
loadContent()
|
|
|
|
|
|
}, [])
|
2026-01-14 05:24:13 +00:00
|
|
|
|
|
2026-01-14 06:39:23 +00:00
|
|
|
|
// 监听滚动进度
|
|
|
|
|
|
const handleScroll = useCallback(() => {
|
|
|
|
|
|
if (!contentRef.current) return
|
2026-01-14 05:24:13 +00:00
|
|
|
|
|
2026-01-14 06:39:23 +00:00
|
|
|
|
const scrollTop = window.scrollY
|
|
|
|
|
|
const docHeight = document.documentElement.scrollHeight - window.innerHeight
|
|
|
|
|
|
const progress = docHeight > 0 ? Math.min((scrollTop / docHeight) * 100, 100) : 0
|
|
|
|
|
|
setReadingProgress(progress)
|
2026-01-14 05:24:13 +00:00
|
|
|
|
|
2026-01-14 06:39:23 +00:00
|
|
|
|
// 滚动超过20%时触发付费墙(非免费章节或未登录)
|
|
|
|
|
|
if (progress >= 20 && !hasFullBook && !latestSection.isFree) {
|
|
|
|
|
|
setShowPaywall(true)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [hasFullBook])
|
2026-01-14 05:24:13 +00:00
|
|
|
|
|
2026-01-14 06:39:23 +00:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
window.addEventListener("scroll", handleScroll)
|
|
|
|
|
|
return () => window.removeEventListener("scroll", handleScroll)
|
|
|
|
|
|
}, [handleScroll])
|
2026-01-14 05:24:13 +00:00
|
|
|
|
|
2026-01-14 06:39:23 +00:00
|
|
|
|
const handlePurchaseClick = (type: "section" | "fullbook") => {
|
|
|
|
|
|
if (!isLoggedIn) {
|
|
|
|
|
|
setIsAuthOpen(true)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
setPaymentType(type)
|
|
|
|
|
|
setIsPaymentOpen(true)
|
|
|
|
|
|
}
|
2026-01-14 05:24:13 +00:00
|
|
|
|
|
2026-01-14 06:39:23 +00:00
|
|
|
|
const contentLines = content.split("\n").filter((line) => line.trim())
|
|
|
|
|
|
// 如果需要付费墙,只显示前20%内容
|
|
|
|
|
|
const displayContent =
|
|
|
|
|
|
showPaywall && !hasFullBook && !latestSection.isFree
|
|
|
|
|
|
? contentLines.slice(0, Math.ceil(contentLines.length * 0.2)).join("\n")
|
|
|
|
|
|
: content
|
2026-01-14 05:24:13 +00:00
|
|
|
|
|
2026-01-14 06:39:23 +00:00
|
|
|
|
return (
|
|
|
|
|
|
<div className="min-h-screen bg-black text-white pb-24">
|
|
|
|
|
|
{/* 阅读进度条 */}
|
|
|
|
|
|
<div className="fixed top-0 left-0 right-0 z-50 h-0.5 bg-[#1c1c1e]">
|
|
|
|
|
|
<div
|
|
|
|
|
|
className="h-full bg-gradient-to-r from-[#ff3b5c] to-[#ff6b8a] transition-all duration-150"
|
|
|
|
|
|
style={{ width: `${readingProgress}%` }}
|
|
|
|
|
|
/>
|
2026-01-14 05:24:13 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-01-14 06:39:23 +00:00
|
|
|
|
{/* 顶部标题区 */}
|
|
|
|
|
|
<header className="sticky top-0 z-40 bg-black/90 backdrop-blur-xl border-b border-white/5">
|
|
|
|
|
|
<div className="px-4 py-3">
|
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<span className="text-[#ffd700] text-lg font-semibold">Soul派对·创业实验</span>
|
|
|
|
|
|
<span className="text-[10px] text-[#ff3b5c] bg-[#ff3b5c]/10 px-2 py-0.5 rounded">最新</span>
|
2026-01-14 05:24:13 +00:00
|
|
|
|
</div>
|
2026-01-14 06:39:23 +00:00
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
const url = window.location.href
|
|
|
|
|
|
navigator.clipboard.writeText(url)
|
|
|
|
|
|
alert("链接已复制")
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="w-8 h-8 rounded-full bg-[#1c1c1e] flex items-center justify-center"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Share2 className="w-4 h-4 text-gray-400" />
|
|
|
|
|
|
</button>
|
2026-01-14 05:24:13 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-01-14 06:39:23 +00:00
|
|
|
|
</header>
|
2026-01-14 05:24:13 +00:00
|
|
|
|
|
2026-01-14 06:39:23 +00:00
|
|
|
|
{/* 章节标题 */}
|
|
|
|
|
|
<div className="px-4 pt-6 pb-4">
|
|
|
|
|
|
<div className="flex items-center gap-2 mb-2">
|
|
|
|
|
|
<span className="text-[#ff3b5c] text-sm font-medium bg-[#ff3b5c]/10 px-3 py-1 rounded-full">
|
|
|
|
|
|
{latestSection.id}
|
2026-01-14 05:24:13 +00:00
|
|
|
|
</span>
|
2026-01-14 06:39:23 +00:00
|
|
|
|
{latestSection.isFree && (
|
|
|
|
|
|
<span className="text-xs text-[#00E5FF] bg-[#00E5FF]/10 px-2 py-0.5 rounded">免费</span>
|
|
|
|
|
|
)}
|
2026-01-14 05:24:13 +00:00
|
|
|
|
</div>
|
2026-01-14 06:39:23 +00:00
|
|
|
|
<h1 className="text-xl font-bold text-white leading-tight">{latestSection.title}</h1>
|
|
|
|
|
|
<p className="text-gray-500 text-sm mt-2">第一篇|真实的人</p>
|
2026-01-14 05:24:13 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-01-14 06:39:23 +00:00
|
|
|
|
{/* 内容区域 */}
|
|
|
|
|
|
<main ref={contentRef} className="px-4 pb-8">
|
|
|
|
|
|
{isLoading ? (
|
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
|
{[...Array(8)].map((_, i) => (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={i}
|
|
|
|
|
|
className="h-4 bg-[#1c1c1e] rounded animate-pulse"
|
|
|
|
|
|
style={{ width: `${Math.random() * 40 + 60}%` }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
))}
|
2026-01-14 05:24:13 +00:00
|
|
|
|
</div>
|
2026-01-14 06:39:23 +00:00
|
|
|
|
) : (
|
|
|
|
|
|
<article className="text-gray-300 leading-[1.9] text-[16px]">
|
|
|
|
|
|
{displayContent.split("\n").map(
|
|
|
|
|
|
(paragraph, index) =>
|
|
|
|
|
|
paragraph.trim() && (
|
|
|
|
|
|
<p key={index} className="mb-5">
|
|
|
|
|
|
{paragraph}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
),
|
|
|
|
|
|
)}
|
|
|
|
|
|
</article>
|
|
|
|
|
|
)}
|
2026-01-14 05:24:13 +00:00
|
|
|
|
|
2026-01-14 06:39:23 +00:00
|
|
|
|
{/* 付费墙 - 只在非免费章节且滚动超过20%时显示 */}
|
|
|
|
|
|
{showPaywall && !hasFullBook && !latestSection.isFree && (
|
|
|
|
|
|
<div className="relative mt-4">
|
|
|
|
|
|
{/* 渐变遮罩 */}
|
|
|
|
|
|
<div className="absolute -top-32 left-0 right-0 h-32 bg-gradient-to-t from-black to-transparent pointer-events-none" />
|
|
|
|
|
|
|
|
|
|
|
|
{/* 付费卡片 */}
|
|
|
|
|
|
<div className="p-6 rounded-2xl bg-gradient-to-b from-[#1c1c1e] to-[#2c2c2e] border border-white/10">
|
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
|
<div className="w-14 h-14 mx-auto mb-3 rounded-2xl bg-[#ff3b5c]/10 flex items-center justify-center">
|
|
|
|
|
|
<Lock className="w-7 h-7 text-[#ff3b5c]" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<h3 className="text-lg font-semibold text-white mb-2">解锁完整内容</h3>
|
|
|
|
|
|
<p className="text-gray-400 text-sm mb-5">{isLoggedIn ? "支付1元继续阅读" : "登录并支付1元继续阅读"}</p>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="space-y-3 mb-4">
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => handlePurchaseClick("section")}
|
|
|
|
|
|
className="w-full py-3 px-5 rounded-xl bg-[#ff3b5c] text-white font-medium active:scale-[0.98] transition-transform"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="flex items-center justify-center gap-2">
|
|
|
|
|
|
<span>立即解锁</span>
|
|
|
|
|
|
<span className="text-lg font-bold">¥1</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => handlePurchaseClick("fullbook")}
|
|
|
|
|
|
className="w-full py-3 px-5 rounded-xl bg-[#2c2c2e] border border-white/10 text-white font-medium active:scale-[0.98] transition-transform"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<Sparkles className="w-4 h-4 text-[#ffd700]" />
|
|
|
|
|
|
<span>解锁全部 {totalSections} 章</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span className="text-[#ffd700]">¥{fullBookPrice}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<p className="text-xs text-gray-500">分享给好友购买,你可获得90%佣金</p>
|
|
|
|
|
|
</div>
|
2026-01-14 05:24:13 +00:00
|
|
|
|
</div>
|
2026-01-14 06:39:23 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* 底部引导 - 免费章节或已购买时显示 */}
|
|
|
|
|
|
{!showPaywall && (
|
|
|
|
|
|
<div className="mt-8 p-4 rounded-xl bg-[#1c1c1e] border border-white/5">
|
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="text-white font-medium">想看更多内容?</p>
|
|
|
|
|
|
<p className="text-gray-500 text-sm mt-1">共 {totalSections} 章精彩内容</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => router.push("/chapters")}
|
|
|
|
|
|
className="px-4 py-2 rounded-lg bg-[#ff3b5c] text-white text-sm font-medium flex items-center gap-1"
|
|
|
|
|
|
>
|
|
|
|
|
|
查看目录
|
|
|
|
|
|
<ChevronRight className="w-4 h-4" />
|
|
|
|
|
|
</button>
|
2026-01-14 05:24:13 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-01-14 06:39:23 +00:00
|
|
|
|
)}
|
|
|
|
|
|
</main>
|
2026-01-14 05:24:13 +00:00
|
|
|
|
|
2026-01-14 06:39:23 +00:00
|
|
|
|
{/* 底部导航 */}
|
|
|
|
|
|
<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-3">
|
|
|
|
|
|
<div className="flex items-center justify-between gap-2">
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => router.push("/chapters")}
|
|
|
|
|
|
className="flex-1 py-2.5 rounded-xl bg-[#2c2c2e] text-white text-sm font-medium text-center active:bg-[#3c3c3e]"
|
|
|
|
|
|
>
|
|
|
|
|
|
目录
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => router.push("/about")}
|
|
|
|
|
|
className="flex-1 py-2.5 rounded-xl bg-[#2c2c2e] text-white text-sm font-medium text-center active:bg-[#3c3c3e]"
|
|
|
|
|
|
>
|
|
|
|
|
|
作者
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => router.push("/my")}
|
|
|
|
|
|
className="flex-1 py-2.5 rounded-xl bg-[#2c2c2e] text-white text-sm font-medium text-center active:bg-[#3c3c3e]"
|
|
|
|
|
|
>
|
|
|
|
|
|
我的
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => handlePurchaseClick("fullbook")}
|
|
|
|
|
|
className="flex-1 py-2.5 rounded-xl bg-[#ff3b5c] text-white text-sm font-medium text-center active:scale-[0.98]"
|
|
|
|
|
|
>
|
|
|
|
|
|
购买
|
|
|
|
|
|
</button>
|
2026-01-14 05:24:13 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-01-14 06:39:23 +00:00
|
|
|
|
</nav>
|
2026-01-14 05:24:13 +00:00
|
|
|
|
|
2026-01-14 06:39:23 +00:00
|
|
|
|
{/* 登录弹窗 */}
|
|
|
|
|
|
<AuthModal isOpen={isAuthOpen} onClose={() => setIsAuthOpen(false)} />
|
|
|
|
|
|
|
|
|
|
|
|
{/* 支付弹窗 */}
|
|
|
|
|
|
<PaymentModal
|
|
|
|
|
|
isOpen={isPaymentOpen}
|
|
|
|
|
|
onClose={() => setIsPaymentOpen(false)}
|
|
|
|
|
|
type={paymentType}
|
|
|
|
|
|
sectionId={latestSection.id}
|
|
|
|
|
|
sectionTitle={latestSection.title}
|
|
|
|
|
|
amount={paymentType === "section" ? latestSection.price : fullBookPrice}
|
|
|
|
|
|
onSuccess={() => window.location.reload()}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
2026-01-14 05:24:13 +00:00
|
|
|
|
)
|
2026-01-09 11:58:08 +08:00
|
|
|
|
}
|