2026-01-09 11:58:08 +08:00
|
|
|
|
"use client"
|
|
|
|
|
|
|
|
|
|
|
|
import { useState, useEffect } from "react"
|
2026-01-14 05:24:13 +00:00
|
|
|
|
import { useRouter } from "next/navigation"
|
2026-01-21 15:49:12 +08:00
|
|
|
|
import { ChevronLeft, Lock, Share2, Sparkles, ChevronRight, X, Copy, Check, QrCode } from "lucide-react"
|
|
|
|
|
|
import { type Section, getFullBookPrice, getTotalSectionCount, getNextSection, getPrevSection } from "@/lib/book-data"
|
2026-01-09 11:58:08 +08:00
|
|
|
|
import { useStore } from "@/lib/store"
|
2026-01-14 05:24:13 +00:00
|
|
|
|
import { PaymentModal } from "./payment-modal"
|
2026-01-14 05:41:44 +00:00
|
|
|
|
import { AuthModal } from "./modules/auth/auth-modal"
|
2026-01-09 11:58:08 +08:00
|
|
|
|
|
|
|
|
|
|
interface ChapterContentProps {
|
|
|
|
|
|
section: Section & { filePath: string }
|
|
|
|
|
|
partTitle: string
|
|
|
|
|
|
chapterTitle: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function ChapterContent({ section, partTitle, chapterTitle }: ChapterContentProps) {
|
2026-01-14 05:24:13 +00:00
|
|
|
|
const router = useRouter()
|
2026-01-09 11:58:08 +08:00
|
|
|
|
const [content, setContent] = useState<string>("")
|
|
|
|
|
|
const [isLoading, setIsLoading] = useState(true)
|
|
|
|
|
|
const [isPaymentOpen, setIsPaymentOpen] = useState(false)
|
2026-01-14 05:41:44 +00:00
|
|
|
|
const [isAuthOpen, setIsAuthOpen] = useState(false)
|
2026-01-09 11:58:08 +08:00
|
|
|
|
const [paymentType, setPaymentType] = useState<"section" | "fullbook">("section")
|
2026-01-09 12:24:15 +08:00
|
|
|
|
const [readingProgress, setReadingProgress] = useState(0)
|
2026-01-14 07:32:08 +00:00
|
|
|
|
const [showPaywall, setShowPaywall] = useState(false)
|
2026-01-21 15:49:12 +08:00
|
|
|
|
const [showShareModal, setShowShareModal] = useState(false)
|
|
|
|
|
|
const [shareCopied, setShareCopied] = useState(false)
|
2026-01-09 11:58:08 +08:00
|
|
|
|
|
2026-01-21 15:49:12 +08:00
|
|
|
|
const { user, isLoggedIn, hasPurchased, settings } = useStore()
|
2026-01-14 05:24:13 +00:00
|
|
|
|
const fullBookPrice = getFullBookPrice()
|
|
|
|
|
|
const totalSections = getTotalSectionCount()
|
2026-01-09 11:58:08 +08:00
|
|
|
|
|
2026-01-14 05:24:13 +00:00
|
|
|
|
const hasFullBook = user?.hasFullBook || false
|
|
|
|
|
|
const canAccess = section.isFree || hasFullBook || (isLoggedIn && hasPurchased(section.id))
|
2026-01-09 11:58:08 +08:00
|
|
|
|
|
2026-01-21 15:49:12 +08:00
|
|
|
|
// 获取下一篇和上一篇
|
|
|
|
|
|
const nextSection = getNextSection(section.id)
|
|
|
|
|
|
const prevSection = getPrevSection(section.id)
|
|
|
|
|
|
|
|
|
|
|
|
// 生成分享链接(带用户邀请码)
|
|
|
|
|
|
const getShareLink = () => {
|
|
|
|
|
|
const baseUrl = typeof window !== 'undefined' ? window.location.origin : ''
|
|
|
|
|
|
const referralCode = user?.referralCode || ''
|
|
|
|
|
|
const shareUrl = `${baseUrl}/read/${section.id}${referralCode ? `?ref=${referralCode}` : ''}`
|
|
|
|
|
|
return shareUrl
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成小程序路径
|
|
|
|
|
|
const getMiniProgramPath = () => {
|
|
|
|
|
|
const referralCode = user?.referralCode || ''
|
|
|
|
|
|
return `/pages/read/read?id=${section.id}${referralCode ? `&ref=${referralCode}` : ''}`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有访问权限,直接显示付费墙
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (!canAccess && !isLoading) {
|
|
|
|
|
|
setShowPaywall(true)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [canAccess, isLoading])
|
|
|
|
|
|
|
2026-01-09 12:24:15 +08:00
|
|
|
|
// 阅读进度追踪
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const handleScroll = () => {
|
|
|
|
|
|
const scrollTop = window.scrollY
|
|
|
|
|
|
const docHeight = document.documentElement.scrollHeight - window.innerHeight
|
2026-01-14 05:24:13 +00:00
|
|
|
|
const progress = docHeight > 0 ? Math.min((scrollTop / docHeight) * 100, 100) : 0
|
2026-01-09 12:24:15 +08:00
|
|
|
|
setReadingProgress(progress)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-14 05:24:13 +00:00
|
|
|
|
window.addEventListener("scroll", handleScroll)
|
|
|
|
|
|
return () => window.removeEventListener("scroll", handleScroll)
|
2026-01-21 15:49:12 +08:00
|
|
|
|
}, [])
|
2026-01-09 12:24:15 +08:00
|
|
|
|
|
2026-01-14 05:24:13 +00:00
|
|
|
|
// 加载内容
|
2026-01-09 11:58:08 +08:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
async function loadContent() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (section.content) {
|
|
|
|
|
|
setContent(section.content)
|
|
|
|
|
|
setIsLoading(false)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const response = await fetch(`/api/content?path=${encodeURIComponent(section.filePath)}`)
|
|
|
|
|
|
if (response.ok) {
|
|
|
|
|
|
const data = await response.json()
|
2026-01-14 05:24:13 +00:00
|
|
|
|
setContent(data.content || "")
|
2026-01-09 11:58:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("Failed to load content:", error)
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setIsLoading(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
loadContent()
|
2026-01-14 05:24:13 +00:00
|
|
|
|
}, [section.filePath, section.content])
|
2026-01-09 11:58:08 +08:00
|
|
|
|
|
|
|
|
|
|
const handlePurchaseClick = (type: "section" | "fullbook") => {
|
|
|
|
|
|
if (!isLoggedIn) {
|
2026-01-14 05:41:44 +00:00
|
|
|
|
setIsAuthOpen(true)
|
2026-01-09 11:58:08 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
setPaymentType(type)
|
|
|
|
|
|
setIsPaymentOpen(true)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-21 15:49:12 +08:00
|
|
|
|
const handleShare = () => {
|
|
|
|
|
|
setShowShareModal(true)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleCopyLink = () => {
|
|
|
|
|
|
const shareLink = getShareLink()
|
|
|
|
|
|
navigator.clipboard.writeText(shareLink)
|
|
|
|
|
|
setShareCopied(true)
|
|
|
|
|
|
setTimeout(() => setShareCopied(false), 2000)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleShareToWechat = () => {
|
|
|
|
|
|
// 生成微信分享文案
|
|
|
|
|
|
const shareText = `📚 推荐阅读《${section.title}》\n\n${content.slice(0, 100)}...\n\n👉 点击阅读:${getShareLink()}`
|
|
|
|
|
|
navigator.clipboard.writeText(shareText)
|
|
|
|
|
|
alert('分享文案已复制,请粘贴到微信发送给好友')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-14 05:24:13 +00:00
|
|
|
|
const contentLines = content.split("\n").filter((line) => line.trim())
|
2026-01-14 07:32:08 +00:00
|
|
|
|
const previewLineCount = Math.ceil(contentLines.length * 0.2) // 改为20%
|
2026-01-14 05:24:13 +00:00
|
|
|
|
const previewContent = contentLines.slice(0, previewLineCount).join("\n")
|
2026-01-09 11:58:08 +08:00
|
|
|
|
|
|
|
|
|
|
return (
|
2026-01-14 05:24:13 +00:00
|
|
|
|
<div className="min-h-screen bg-black text-white">
|
|
|
|
|
|
<div className="fixed top-0 left-0 right-0 z-50 h-0.5 bg-[#1c1c1e]">
|
|
|
|
|
|
<div
|
2026-01-14 07:32:08 +00:00
|
|
|
|
className="h-full bg-gradient-to-r from-[#00CED1] to-[#20B2AA] transition-all duration-150"
|
2026-01-09 12:24:15 +08:00
|
|
|
|
style={{ width: `${readingProgress}%` }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-01-14 05:24:13 +00:00
|
|
|
|
{/* 顶部导航 */}
|
|
|
|
|
|
<header className="sticky top-0 z-40 bg-black/80 backdrop-blur-xl border-b border-white/5">
|
|
|
|
|
|
<div className="max-w-2xl mx-auto px-4 py-3 flex items-center justify-between">
|
|
|
|
|
|
<button
|
2026-01-14 07:32:08 +00:00
|
|
|
|
onClick={() => router.push("/chapters")}
|
2026-01-14 05:24:13 +00:00
|
|
|
|
className="w-9 h-9 rounded-full bg-[#1c1c1e] flex items-center justify-center active:bg-[#2c2c2e]"
|
2026-01-09 12:24:15 +08:00
|
|
|
|
>
|
2026-01-14 05:24:13 +00:00
|
|
|
|
<ChevronLeft className="w-5 h-5 text-gray-400" />
|
|
|
|
|
|
</button>
|
2026-01-09 11:58:08 +08:00
|
|
|
|
<div className="text-center flex-1 px-4">
|
2026-01-14 05:24:13 +00:00
|
|
|
|
{partTitle && <p className="text-[10px] text-gray-500">{partTitle}</p>}
|
|
|
|
|
|
{chapterTitle && <p className="text-xs text-gray-400 truncate">{chapterTitle}</p>}
|
2026-01-09 11:58:08 +08:00
|
|
|
|
</div>
|
2026-01-14 05:24:13 +00:00
|
|
|
|
<button
|
2026-01-21 15:49:12 +08:00
|
|
|
|
onClick={handleShare}
|
2026-01-14 05:24:13 +00:00
|
|
|
|
className="w-9 h-9 rounded-full bg-[#1c1c1e] flex items-center justify-center active:bg-[#2c2c2e]"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Share2 className="w-4 h-4 text-gray-400" />
|
|
|
|
|
|
</button>
|
2026-01-09 11:58:08 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
2026-01-09 12:24:15 +08:00
|
|
|
|
{/* 阅读内容 */}
|
2026-01-14 05:24:13 +00:00
|
|
|
|
<main className="max-w-2xl mx-auto px-5 py-8 pb-32">
|
|
|
|
|
|
<div className="mb-8">
|
|
|
|
|
|
<div className="flex items-center gap-2 mb-3">
|
2026-01-14 07:32:08 +00:00
|
|
|
|
<span className="text-[#00CED1] text-sm font-medium bg-[#00CED1]/10 px-3 py-1 rounded-full">
|
2026-01-09 12:24:15 +08:00
|
|
|
|
{section.id}
|
|
|
|
|
|
</span>
|
2026-01-14 07:32:08 +00:00
|
|
|
|
{section.isFree && <span className="text-xs text-[#00CED1] bg-[#00CED1]/10 px-2 py-0.5 rounded">免费</span>}
|
2026-01-09 11:58:08 +08:00
|
|
|
|
</div>
|
2026-01-14 05:24:13 +00:00
|
|
|
|
<h1 className="text-2xl font-bold text-white leading-tight">{section.title}</h1>
|
2026-01-09 11:58:08 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{isLoading ? (
|
2026-01-09 12:24:15 +08:00
|
|
|
|
<div className="space-y-4">
|
2026-01-21 15:49:12 +08:00
|
|
|
|
{[75, 90, 65, 85, 70, 95, 80, 88].map((width, i) => (
|
2026-01-14 05:24:13 +00:00
|
|
|
|
<div
|
|
|
|
|
|
key={i}
|
|
|
|
|
|
className="h-4 bg-[#1c1c1e] rounded animate-pulse"
|
2026-01-21 15:49:12 +08:00
|
|
|
|
style={{ width: `${width}%` }}
|
2026-01-14 05:24:13 +00:00
|
|
|
|
/>
|
2026-01-09 12:24:15 +08:00
|
|
|
|
))}
|
2026-01-09 11:58:08 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
) : canAccess ? (
|
2026-01-14 05:24:13 +00:00
|
|
|
|
// 完整内容
|
2026-01-21 15:49:12 +08:00
|
|
|
|
<>
|
|
|
|
|
|
<article className="text-gray-300 leading-[1.9] text-[17px]">
|
|
|
|
|
|
{content.split("\n").map(
|
|
|
|
|
|
(paragraph, index) =>
|
|
|
|
|
|
paragraph.trim() && (
|
|
|
|
|
|
<p key={index} className="mb-6">
|
|
|
|
|
|
{paragraph}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
),
|
|
|
|
|
|
)}
|
|
|
|
|
|
</article>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 底部章节导航 */}
|
|
|
|
|
|
<div className="mt-12 pt-8 border-t border-white/10">
|
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
|
{prevSection ? (
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => router.push(`/read/${prevSection.id}`)}
|
|
|
|
|
|
className="flex-1 max-w-[48%] p-3 rounded-xl bg-[#1c1c1e] border border-white/5 text-left hover:bg-[#2c2c2e] transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
<p className="text-[10px] text-gray-500 mb-0.5">上一篇</p>
|
|
|
|
|
|
<p className="text-xs text-white truncate">{prevSection.title}</p>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="flex-1 max-w-[48%]" />
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{nextSection ? (
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => router.push(`/read/${nextSection.id}`)}
|
|
|
|
|
|
className="flex-1 max-w-[48%] p-3 rounded-xl bg-gradient-to-r from-[#00CED1]/10 to-[#20B2AA]/10 border border-[#00CED1]/20 text-left hover:from-[#00CED1]/20 hover:to-[#20B2AA]/20 transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
<p className="text-[10px] text-[#00CED1] mb-0.5">下一篇</p>
|
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
|
<p className="text-xs text-white truncate flex-1">{nextSection.title}</p>
|
|
|
|
|
|
<ChevronRight className="w-3 h-3 text-[#00CED1] flex-shrink-0 ml-1" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="flex-1 max-w-[48%] p-3 rounded-xl bg-[#1c1c1e] border border-white/5 text-center">
|
|
|
|
|
|
<p className="text-xs text-gray-400">已是最后一篇 🎉</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 分享提示 */}
|
|
|
|
|
|
<div className="mt-6 p-4 rounded-xl bg-gradient-to-r from-[#FFD700]/10 to-transparent border border-[#FFD700]/20">
|
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="text-sm text-white font-medium">觉得不错?分享给好友</p>
|
|
|
|
|
|
<p className="text-xs text-gray-400 mt-1">好友购买你获得90%佣金</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={handleShare}
|
|
|
|
|
|
className="px-4 py-2 rounded-lg bg-[#FFD700] text-black text-sm font-medium"
|
|
|
|
|
|
>
|
|
|
|
|
|
分享赚钱
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>
|
2026-01-09 11:58:08 +08:00
|
|
|
|
) : (
|
|
|
|
|
|
<div>
|
2026-01-14 05:24:13 +00:00
|
|
|
|
{/* 免费预览部分 */}
|
|
|
|
|
|
<article className="text-gray-300 leading-[1.9] text-[17px]">
|
|
|
|
|
|
{previewContent.split("\n").map(
|
|
|
|
|
|
(paragraph, index) =>
|
2026-01-09 12:24:15 +08:00
|
|
|
|
paragraph.trim() && (
|
2026-01-14 05:24:13 +00:00
|
|
|
|
<p key={index} className="mb-6">
|
2026-01-09 12:24:15 +08:00
|
|
|
|
{paragraph}
|
|
|
|
|
|
</p>
|
2026-01-14 05:24:13 +00:00
|
|
|
|
),
|
|
|
|
|
|
)}
|
2026-01-09 11:58:08 +08:00
|
|
|
|
</article>
|
|
|
|
|
|
|
2026-01-14 07:32:08 +00:00
|
|
|
|
{showPaywall && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
{/* 渐变遮罩 */}
|
|
|
|
|
|
<div className="relative">
|
|
|
|
|
|
<div className="absolute -top-32 left-0 right-0 h-32 bg-gradient-to-t from-black to-transparent pointer-events-none" />
|
2026-01-09 12:24:15 +08:00
|
|
|
|
</div>
|
2026-01-14 07:32:08 +00:00
|
|
|
|
|
|
|
|
|
|
<div className="mt-8 p-6 rounded-2xl bg-gradient-to-b from-[#1c1c1e] to-[#2c2c2e] border border-[#00CED1]/20">
|
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
|
<div className="w-16 h-16 mx-auto mb-4 rounded-2xl bg-[#00CED1]/10 flex items-center justify-center">
|
|
|
|
|
|
<Lock className="w-8 h-8 text-[#00CED1]" />
|
2026-01-14 05:24:13 +00:00
|
|
|
|
</div>
|
2026-01-14 07:32:08 +00:00
|
|
|
|
<h3 className="text-xl font-semibold text-white mb-2">解锁完整内容</h3>
|
|
|
|
|
|
<p className="text-gray-400 text-sm mb-6">
|
|
|
|
|
|
已阅读20%,{isLoggedIn ? "购买后继续阅读" : "登录并购买后继续阅读"}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 购买选项 */}
|
|
|
|
|
|
<div className="space-y-3 mb-6">
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => handlePurchaseClick("section")}
|
|
|
|
|
|
className="w-full py-3.5 px-6 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">
|
|
|
|
|
|
<span>购买本章</span>
|
|
|
|
|
|
<span className="text-[#00CED1]">¥{section.price}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
2026-01-21 15:49:12 +08:00
|
|
|
|
{/* 只有购买超过3章才显示全书购买选项 */}
|
|
|
|
|
|
{(user?.purchasedSections?.length || 0) >= 3 && (
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => handlePurchaseClick("fullbook")}
|
|
|
|
|
|
className="w-full py-3.5 px-6 rounded-xl bg-gradient-to-r from-[#00CED1] to-[#20B2AA] text-white font-medium active:scale-[0.98] transition-transform shadow-lg shadow-[#00CED1]/20"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<Sparkles className="w-4 h-4" />
|
|
|
|
|
|
<span>解锁全部 {totalSections} 章</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-right">
|
|
|
|
|
|
<span className="text-lg font-bold">¥{fullBookPrice}</span>
|
|
|
|
|
|
<span className="text-xs ml-1 opacity-70">省82%</span>
|
|
|
|
|
|
</div>
|
2026-01-14 07:32:08 +00:00
|
|
|
|
</div>
|
2026-01-21 15:49:12 +08:00
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
2026-01-14 05:24:13 +00:00
|
|
|
|
</div>
|
2026-01-09 11:58:08 +08:00
|
|
|
|
|
2026-01-14 07:32:08 +00:00
|
|
|
|
<p className="text-xs text-gray-500">分享给好友购买,你可获得90%佣金</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
2026-01-09 11:58:08 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</main>
|
|
|
|
|
|
|
2026-01-21 15:49:12 +08:00
|
|
|
|
{/* 分享弹窗 */}
|
|
|
|
|
|
{showShareModal && (
|
|
|
|
|
|
<div className="fixed inset-0 z-50 flex items-end justify-center">
|
|
|
|
|
|
<div
|
|
|
|
|
|
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
|
|
|
|
|
|
onClick={() => setShowShareModal(false)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<div className="relative w-full max-w-lg bg-[#1c1c1e] rounded-t-3xl p-6 pb-8 animate-in slide-in-from-bottom duration-300">
|
|
|
|
|
|
<div className="flex items-center justify-between mb-6">
|
|
|
|
|
|
<h3 className="text-lg font-semibold text-white">分享文章</h3>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => setShowShareModal(false)}
|
|
|
|
|
|
className="w-8 h-8 rounded-full bg-white/10 flex items-center justify-center"
|
|
|
|
|
|
>
|
|
|
|
|
|
<X className="w-4 h-4 text-gray-400" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 分享链接预览 */}
|
|
|
|
|
|
<div className="p-4 rounded-xl bg-black/30 border border-white/10 mb-4">
|
|
|
|
|
|
<p className="text-xs text-gray-500 mb-2">你的专属分享链接</p>
|
|
|
|
|
|
<p className="text-sm text-[#00CED1] break-all font-mono">{getShareLink()}</p>
|
|
|
|
|
|
{user?.referralCode && (
|
|
|
|
|
|
<p className="text-xs text-gray-400 mt-2">
|
|
|
|
|
|
邀请码: <span className="text-[#FFD700]">{user.referralCode}</span> · 好友购买你获得90%佣金
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 分享按钮 */}
|
|
|
|
|
|
<div className="grid grid-cols-4 gap-4 mb-6">
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={handleCopyLink}
|
|
|
|
|
|
className="flex flex-col items-center gap-2 p-3 rounded-xl bg-white/5 hover:bg-white/10 transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="w-12 h-12 rounded-full bg-[#00CED1]/20 flex items-center justify-center">
|
|
|
|
|
|
{shareCopied ? (
|
|
|
|
|
|
<Check className="w-5 h-5 text-[#00CED1]" />
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<Copy className="w-5 h-5 text-[#00CED1]" />
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span className="text-xs text-gray-400">{shareCopied ? '已复制' : '复制链接'}</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={handleShareToWechat}
|
|
|
|
|
|
className="flex flex-col items-center gap-2 p-3 rounded-xl bg-white/5 hover:bg-white/10 transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="w-12 h-12 rounded-full bg-[#07C160]/20 flex items-center justify-center">
|
|
|
|
|
|
<svg className="w-6 h-6 text-[#07C160]" viewBox="0 0 24 24" fill="currentColor">
|
|
|
|
|
|
<path d="M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 01.213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.326.326 0 00.167-.054l1.903-1.114a.864.864 0 01.717-.098 10.16 10.16 0 002.837.403c.276 0 .543-.027.811-.05-.857-2.578.157-4.972 1.932-6.446 1.703-1.415 3.882-1.98 5.853-1.838-.576-3.583-4.196-6.348-8.596-6.348zM5.785 5.991c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 01-1.162 1.178A1.17 1.17 0 014.623 7.17c0-.651.52-1.18 1.162-1.18zm5.813 0c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 01-1.162 1.178 1.17 1.17 0 01-1.162-1.178c0-.651.52-1.18 1.162-1.18zm5.34 2.867c-1.797-.052-3.746.512-5.28 1.786-1.72 1.428-2.687 3.72-1.78 6.22.942 2.453 3.666 4.229 6.884 4.229.826 0 1.622-.12 2.361-.336a.722.722 0 01.598.082l1.584.926a.272.272 0 00.14.045c.133 0 .241-.108.241-.245 0-.06-.023-.118-.039-.177l-.326-1.233a.49.49 0 01.178-.553c1.527-1.122 2.505-2.787 2.505-4.638 0-3.265-2.88-5.958-6.524-6.106h-.542zm-2.054 2.865c.534 0 .967.44.967.982a.975.975 0 01-.967.983.975.975 0 01-.967-.983c0-.542.432-.982.967-.982zm5.058 0c.534 0 .967.44.967.982a.975.975 0 01-.967.983.975.975 0 01-.967-.983c0-.542.432-.982.967-.982z"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span className="text-xs text-gray-400">微信好友</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
const text = `📚 ${section.title}\n${getShareLink()}`
|
|
|
|
|
|
navigator.clipboard.writeText(text)
|
|
|
|
|
|
alert('朋友圈文案已复制')
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="flex flex-col items-center gap-2 p-3 rounded-xl bg-white/5 hover:bg-white/10 transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="w-12 h-12 rounded-full bg-[#07C160]/20 flex items-center justify-center">
|
|
|
|
|
|
<svg className="w-6 h-6 text-[#07C160]" viewBox="0 0 24 24" fill="currentColor">
|
|
|
|
|
|
<path d="M12 2C6.477 2 2 6.477 2 12c0 4.237 2.636 7.855 6.356 9.312l.213-.738A.75.75 0 019.3 20h5.4a.75.75 0 01.732.574l.212.738C19.364 19.855 22 16.237 22 12c0-5.523-4.477-10-10-10zm0 3a3 3 0 110 6 3 3 0 010-6zm-4.5 9a1.5 1.5 0 110 3 1.5 1.5 0 010-3zm9 0a1.5 1.5 0 110 3 1.5 1.5 0 010-3z"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span className="text-xs text-gray-400">朋友圈</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => router.push('/my/referral')}
|
|
|
|
|
|
className="flex flex-col items-center gap-2 p-3 rounded-xl bg-white/5 hover:bg-white/10 transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="w-12 h-12 rounded-full bg-[#FFD700]/20 flex items-center justify-center">
|
|
|
|
|
|
<QrCode className="w-5 h-5 text-[#FFD700]" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span className="text-xs text-gray-400">生成海报</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 小程序路径(开发者调试用) */}
|
|
|
|
|
|
{user?.isAdmin && (
|
|
|
|
|
|
<div className="p-3 rounded-lg bg-black/30 border border-white/5">
|
|
|
|
|
|
<p className="text-xs text-gray-500 mb-1">小程序路径</p>
|
|
|
|
|
|
<p className="text-xs text-gray-400 font-mono break-all">{getMiniProgramPath()}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-01-14 05:41:44 +00:00
|
|
|
|
{/* 登录弹窗 */}
|
|
|
|
|
|
<AuthModal isOpen={isAuthOpen} onClose={() => setIsAuthOpen(false)} />
|
|
|
|
|
|
|
2026-01-14 05:24:13 +00:00
|
|
|
|
{/* 支付弹窗 */}
|
2026-01-09 11:58:08 +08:00
|
|
|
|
<PaymentModal
|
|
|
|
|
|
isOpen={isPaymentOpen}
|
|
|
|
|
|
onClose={() => setIsPaymentOpen(false)}
|
|
|
|
|
|
type={paymentType}
|
|
|
|
|
|
sectionId={section.id}
|
|
|
|
|
|
sectionTitle={section.title}
|
|
|
|
|
|
amount={paymentType === "section" ? section.price : fullBookPrice}
|
2026-01-21 15:49:12 +08:00
|
|
|
|
onSuccess={() => {
|
|
|
|
|
|
setIsPaymentOpen(false)
|
|
|
|
|
|
// 刷新当前页面以显示解锁内容
|
|
|
|
|
|
window.location.reload()
|
|
|
|
|
|
}}
|
2026-01-09 11:58:08 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|