feat: 完整重构小程序匹配功能 + 修复UI对齐 + 文章数据API

主要更新:
1. 按H5网页端完全重构匹配功能(match页面)
   - 4种匹配类型: 创业合伙/资源对接/导师顾问/团队招募
   - 资源对接等类型弹出手机号/微信号输入框
   - 去掉重新匹配按钮,改为返回按钮

2. 修复所有卡片对齐和宽度问题
   - 目录页附录卡片居中
   - 首页阅读进度卡片满宽度
   - 我的页面菜单卡片对齐
   - 推广中心分享卡片统一宽度

3. 修复目录页图标和文字对齐
   - section-icon固定40rpx宽高
   - section-title与图标垂直居中

4. 更新真实完整文章标题(62篇)
   - 从book目录读取真实markdown文件名
   - 替换之前的简化标题

5. 新增文章数据API
   - /api/db/chapters - 获取完整书籍结构
   - 支持按ID获取单篇文章内容
This commit is contained in:
卡若
2026-01-21 15:49:12 +08:00
parent 1ee25e3dab
commit b60edb3d47
197 changed files with 34430 additions and 7345 deletions

View File

@@ -2,6 +2,7 @@
import { X } from "lucide-react"
import { Button } from "@/components/ui/button"
import { getTotalSectionCount } from "@/lib/book-data"
interface PosterModalProps {
isOpen: boolean
@@ -14,6 +15,9 @@ interface PosterModalProps {
export function PosterModal({ isOpen, onClose, referralLink, referralCode, nickname }: PosterModalProps) {
if (!isOpen) return null
// 动态获取案例数量
const caseCount = getTotalSectionCount()
// Use a public QR code API
const qrCodeUrl = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(referralLink)}`
@@ -29,46 +33,92 @@ export function PosterModal({ isOpen, onClose, referralLink, referralCode, nickn
<X className="w-5 h-5" />
</button>
{/* Poster Content */}
<div className="bg-gradient-to-br from-indigo-900 to-purple-900 text-white p-6 flex flex-col items-center text-center relative overflow-hidden">
{/* Decorative circles */}
<div className="absolute top-0 left-0 w-32 h-32 bg-white/10 rounded-full -translate-x-1/2 -translate-y-1/2 blur-2xl" />
<div className="absolute bottom-0 right-0 w-40 h-40 bg-pink-500/20 rounded-full translate-x-1/3 translate-y-1/3 blur-2xl" />
{/* Poster Content - 销售/商业风格 */}
<div className="bg-gradient-to-br from-[#0a1628] via-[#0f2137] to-[#1a3a5c] text-white p-6 flex flex-col items-center text-center relative overflow-hidden">
{/* 装饰性元素 */}
<div className="absolute top-0 left-0 w-40 h-40 bg-[#00CED1]/10 rounded-full -translate-x-1/2 -translate-y-1/2 blur-3xl" />
<div className="absolute bottom-0 right-0 w-48 h-48 bg-[#FFD700]/10 rounded-full translate-x-1/3 translate-y-1/3 blur-3xl" />
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-64 h-64 border border-[#00CED1]/5 rounded-full" />
<div className="relative z-10 w-full flex flex-col items-center">
{/* Book Title */}
<h2 className="text-xl font-bold mb-1 leading-tight text-white">SOUL的<br/></h2>
<p className="text-white/80 text-xs mb-6"> · 55 · </p>
{/* 顶部标签 */}
<div className="flex items-center gap-2 mb-3">
<span className="px-3 py-1 text-[10px] font-bold bg-[#FFD700]/20 text-[#FFD700] rounded-full border border-[#FFD700]/30">
</span>
<span className="px-3 py-1 text-[10px] font-bold bg-[#00CED1]/20 text-[#00CED1] rounded-full border border-[#00CED1]/30">
</span>
</div>
{/* Cover Image Placeholder */}
<div className="w-32 h-44 bg-gray-200 rounded shadow-lg mb-6 overflow-hidden relative">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src="/images/image.png" alt="Book Cover" className="w-full h-full object-cover" />
{/* Book Title */}
<h2 className="text-2xl font-black mb-1 leading-tight">
<span className="bg-gradient-to-r from-white via-[#00CED1] to-white bg-clip-text text-transparent">
SOUL的
</span>
<br/>
<span className="text-white"></span>
</h2>
<p className="text-white/60 text-xs mb-4">Soul派对房的真实商业故事</p>
{/* 核心数据展示 */}
<div className="w-full grid grid-cols-3 gap-2 mb-4 px-2">
<div className="bg-white/5 rounded-lg p-2 border border-white/10">
<p className="text-2xl font-black text-[#FFD700]">{caseCount}</p>
<p className="text-[10px] text-white/50"></p>
</div>
<div className="bg-white/5 rounded-lg p-2 border border-white/10">
<p className="text-2xl font-black text-[#00CED1]">5%</p>
<p className="text-[10px] text-white/50"></p>
</div>
<div className="bg-white/5 rounded-lg p-2 border border-white/10">
<p className="text-2xl font-black text-[#E91E63]">90%</p>
<p className="text-[10px] text-white/50"></p>
</div>
</div>
{/* 特色标签 */}
<div className="flex flex-wrap justify-center gap-1 mb-4 px-4">
{["人性观察", "行业揭秘", "赚钱逻辑", "创业复盘", "资源对接"].map((tag) => (
<span key={tag} className="px-2 py-0.5 text-[10px] bg-white/5 text-white/70 rounded border border-white/10">
{tag}
</span>
))}
</div>
{/* Recommender Info */}
<div className="flex items-center gap-2 mb-4 bg-white/10 px-3 py-1.5 rounded-full backdrop-blur-sm">
<span className="text-xs text-white">: {nickname}</span>
<div className="flex items-center gap-2 mb-3 bg-[#00CED1]/10 px-4 py-2 rounded-full border border-[#00CED1]/20">
<div className="w-6 h-6 rounded-full bg-[#00CED1]/30 flex items-center justify-center text-[10px] font-bold text-[#00CED1]">
{nickname.charAt(0)}
</div>
<span className="text-xs text-[#00CED1]">{nickname} </span>
</div>
{/* 优惠说明 */}
<div className="w-full p-3 rounded-xl bg-gradient-to-r from-[#FFD700]/10 to-[#E91E63]/10 border border-[#FFD700]/20 mb-4">
<p className="text-center text-xs text-white/80">
<span className="text-[#00CED1] font-bold">5%</span>
</p>
</div>
{/* QR Code Section */}
<div className="bg-white p-2 rounded-lg shadow-lg mb-2">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={qrCodeUrl} alt="QR Code" className="w-32 h-32" />
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={qrCodeUrl} alt="QR Code" className="w-28 h-28" />
</div>
<p className="text-[10px] text-white/60 mb-1"></p>
<p className="text-xs font-mono tracking-wider text-white">: {referralCode}</p>
<p className="text-[10px] text-white/40 mb-1"> · </p>
<p className="text-xs font-mono tracking-wider text-[#00CED1]/80">: {referralCode}</p>
</div>
</div>
{/* Footer Actions */}
<div className="p-4 bg-gray-50 flex flex-col gap-2">
<p className="text-center text-xs text-gray-500 mb-1">
</p>
<Button onClick={onClose} className="w-full" variant="outline">
</Button>
<p className="text-center text-xs text-gray-500 mb-1">
</p>
<Button onClick={onClose} className="w-full" variant="outline">
</Button>
</div>
</div>
</div>

View File

@@ -1,7 +1,7 @@
"use client"
import { useState } from "react"
import { X, Wallet, CheckCircle } from "lucide-react"
import { useState, useEffect } from "react"
import { X, Wallet, CheckCircle, AlertCircle, Phone, MessageCircle, CreditCard } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
@@ -14,7 +14,7 @@ interface WithdrawalModalProps {
}
export function WithdrawalModal({ isOpen, onClose, availableAmount }: WithdrawalModalProps) {
const { requestWithdrawal } = useStore()
const { requestWithdrawal, user } = useStore()
const [amount, setAmount] = useState<string>("")
const [method, setMethod] = useState<"wechat" | "alipay">("wechat")
const [account, setAccount] = useState("")
@@ -22,6 +22,20 @@ export function WithdrawalModal({ isOpen, onClose, availableAmount }: Withdrawal
const [isSubmitting, setIsSubmitting] = useState(false)
const [isSuccess, setIsSuccess] = useState(false)
// 检查是否已绑定支付方式
const hasBindWechat = !!user?.wechat
const hasBindAlipay = !!user?.alipay
const hasAnyPaymentMethod = hasBindWechat || hasBindAlipay
// 自动填充已绑定的账号
useEffect(() => {
if (method === "wechat" && user?.wechat) {
setAccount(user.wechat)
} else if (method === "alipay" && user?.alipay) {
setAccount(user.alipay)
}
}, [method, user])
if (!isOpen) return null
const handleSubmit = async (e: React.FormEvent) => {
@@ -57,43 +71,91 @@ export function WithdrawalModal({ isOpen, onClose, availableAmount }: Withdrawal
onClose()
}
// 未绑定支付方式的提示
if (!hasAnyPaymentMethod) {
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<div className="absolute inset-0 bg-black/80 backdrop-blur-sm" onClick={handleClose} />
<div className="relative w-full max-w-sm bg-[#1c1c1e] rounded-2xl overflow-hidden shadow-2xl animate-in fade-in zoom-in duration-200">
<button
onClick={handleClose}
className="absolute top-3 right-3 p-1.5 bg-white/10 rounded-full text-white/60 hover:bg-white/20 z-10"
>
<X className="w-5 h-5" />
</button>
<div className="p-6 text-center">
<div className="w-16 h-16 bg-orange-500/20 rounded-full flex items-center justify-center mx-auto mb-4">
<AlertCircle className="w-8 h-8 text-orange-400" />
</div>
<h3 className="text-xl font-bold text-white mb-2"></h3>
<p className="text-white/60 text-sm mb-6">
"我的"
</p>
<div className="grid grid-cols-2 gap-3 mb-6">
<div className="p-4 rounded-xl bg-white/5 border border-white/10">
<MessageCircle className="w-6 h-6 text-[#07C160] mx-auto mb-2" />
<p className="text-white/60 text-xs"></p>
</div>
<div className="p-4 rounded-xl bg-white/5 border border-white/10">
<CreditCard className="w-6 h-6 text-[#1677FF] mx-auto mb-2" />
<p className="text-white/60 text-xs"></p>
</div>
</div>
<Button
onClick={handleClose}
className="w-full bg-[#00CED1] hover:bg-[#00CED1]/90 text-black font-medium"
>
</Button>
</div>
</div>
</div>
)
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<div className="absolute inset-0 bg-black/80 backdrop-blur-sm" onClick={handleClose} />
<div className="relative w-full max-w-sm bg-white rounded-xl overflow-hidden shadow-2xl animate-in fade-in zoom-in duration-200">
<div className="relative w-full max-w-sm bg-[#1c1c1e] rounded-2xl overflow-hidden shadow-2xl animate-in fade-in zoom-in duration-200">
<button
onClick={handleClose}
className="absolute top-2 right-2 p-1.5 bg-black/10 rounded-full text-gray-500 hover:bg-black/20 z-10"
className="absolute top-3 right-3 p-1.5 bg-white/10 rounded-full text-white/60 hover:bg-white/20 z-10"
>
<X className="w-5 h-5" />
</button>
{isSuccess ? (
<div className="p-8 flex flex-col items-center text-center">
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mb-4">
<CheckCircle className="w-8 h-8 text-green-600" />
<div className="w-16 h-16 bg-green-500/20 rounded-full flex items-center justify-center mb-4">
<CheckCircle className="w-8 h-8 text-green-400" />
</div>
<h3 className="text-xl font-bold text-gray-900 mb-2"></h3>
<p className="text-sm text-gray-500 mb-6">
<h3 className="text-xl font-bold text-white mb-2"></h3>
<p className="text-sm text-white/60 mb-6">
1-3
</p>
<Button onClick={handleClose} className="w-full bg-green-600 hover:bg-green-700 text-white">
<Button onClick={handleClose} className="w-full bg-green-500 hover:bg-green-600 text-white">
</Button>
</div>
) : (
<form onSubmit={handleSubmit} className="p-6">
<div className="flex items-center gap-2 mb-6">
<Wallet className="w-5 h-5 text-indigo-600" />
<h3 className="text-lg font-bold text-gray-900"></h3>
<Wallet className="w-5 h-5 text-[#FFD700]" />
<h3 className="text-lg font-bold text-white"></h3>
</div>
<div className="space-y-4 mb-6">
<div className="space-y-2">
<Label htmlFor="amount"> (: ¥{availableAmount.toFixed(2)})</Label>
<Label htmlFor="amount" className="text-white/80">
<span className="text-[#00CED1]">(: ¥{availableAmount.toFixed(2)})</span>
</Label>
<div className="relative">
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500">¥</span>
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-white/50">¥</span>
<Input
id="amount"
type="number"
@@ -102,64 +164,77 @@ export function WithdrawalModal({ isOpen, onClose, availableAmount }: Withdrawal
step="0.01"
value={amount}
onChange={(e) => setAmount(e.target.value)}
className="pl-7"
className="pl-7 bg-white/5 border-white/10 text-white placeholder:text-white/30"
placeholder="最低10元"
/>
</div>
</div>
<div className="space-y-2">
<Label></Label>
<div className="flex gap-4">
<button
type="button"
onClick={() => setMethod("wechat")}
className={`flex-1 py-2 px-4 rounded-lg border text-sm font-medium transition-colors ${
method === "wechat"
? "border-green-600 bg-green-50 text-green-700"
: "border-gray-200 hover:bg-gray-50 text-gray-600"
}`}
>
</button>
<button
type="button"
onClick={() => setMethod("alipay")}
className={`flex-1 py-2 px-4 rounded-lg border text-sm font-medium transition-colors ${
method === "alipay"
? "border-blue-600 bg-blue-50 text-blue-700"
: "border-gray-200 hover:bg-gray-50 text-gray-600"
}`}
>
</button>
<Label className="text-white/80"></Label>
<div className="flex gap-3">
{hasBindWechat && (
<button
type="button"
onClick={() => setMethod("wechat")}
className={`flex-1 py-3 px-4 rounded-xl border text-sm font-medium transition-colors flex items-center justify-center gap-2 ${
method === "wechat"
? "border-[#07C160] bg-[#07C160]/10 text-[#07C160]"
: "border-white/10 bg-white/5 text-white/60"
}`}
>
<MessageCircle className="w-4 h-4" />
</button>
)}
{hasBindAlipay && (
<button
type="button"
onClick={() => setMethod("alipay")}
className={`flex-1 py-3 px-4 rounded-xl border text-sm font-medium transition-colors flex items-center justify-center gap-2 ${
method === "alipay"
? "border-[#1677FF] bg-[#1677FF]/10 text-[#1677FF]"
: "border-white/10 bg-white/5 text-white/60"
}`}
>
<CreditCard className="w-4 h-4" />
</button>
)}
</div>
</div>
<div className="space-y-2">
<Label htmlFor="account">{method === "wechat" ? "微信号" : "支付宝账号"}</Label>
<Label htmlFor="account" className="text-white/80">
{method === "wechat" ? "微信号" : "支付宝账号"}
</Label>
<Input
id="account"
value={account}
onChange={(e) => setAccount(e.target.value)}
placeholder={method === "wechat" ? "请输入微信号" : "请输入支付宝账号"}
className="bg-white/5 border-white/10 text-white placeholder:text-white/30"
/>
{((method === "wechat" && user?.wechat) || (method === "alipay" && user?.alipay)) && (
<p className="text-xs text-[#00CED1]"></p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="name"></Label>
<Label htmlFor="name" className="text-white/80"></Label>
<Input
id="name"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="请输入收款人真实姓名"
className="bg-white/5 border-white/10 text-white placeholder:text-white/30"
/>
</div>
</div>
<Button
type="submit"
className="w-full bg-indigo-600 hover:bg-indigo-700 text-white"
className="w-full bg-[#FFD700] hover:bg-[#FFD700]/90 text-black font-bold"
disabled={isSubmitting || !amount || !account || !name}
>
{isSubmitting ? "提交中..." : "确认提现"}