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

259
app/my/settings/page.tsx Normal file
View File

@@ -0,0 +1,259 @@
"use client"
import { useState } from "react"
import { useRouter } from "next/navigation"
import { ChevronLeft, Phone, MessageCircle, CreditCard, Check, X, Loader2, Shield } from "lucide-react"
import { useStore } from "@/lib/store"
export default function SettingsPage() {
const router = useRouter()
const { user, updateUser, logout } = useStore()
// 绑定弹窗状态
const [showBindModal, setShowBindModal] = useState(false)
const [bindType, setBindType] = useState<"phone" | "wechat" | "alipay">("phone")
const [bindValue, setBindValue] = useState("")
const [isBinding, setIsBinding] = useState(false)
const [bindError, setBindError] = useState("")
// 绑定账号
const handleBind = async () => {
if (!bindValue.trim()) {
setBindError("请输入内容")
return
}
if (bindType === "phone" && !/^1[3-9]\d{9}$/.test(bindValue)) {
setBindError("请输入正确的手机号")
return
}
if (bindType === "wechat" && bindValue.length < 6) {
setBindError("微信号至少6位")
return
}
if (bindType === "alipay" && !bindValue.includes("@") && !/^1[3-9]\d{9}$/.test(bindValue)) {
setBindError("请输入正确的支付宝账号")
return
}
setIsBinding(true)
setBindError("")
try {
await new Promise(resolve => setTimeout(resolve, 1000))
if (updateUser && user) {
const updates: any = {}
if (bindType === "phone") updates.phone = bindValue
if (bindType === "wechat") updates.wechat = bindValue
if (bindType === "alipay") updates.alipay = bindValue
updateUser(user.id, updates)
}
setShowBindModal(false)
setBindValue("")
alert("绑定成功!")
} catch (error) {
setBindError("绑定失败,请重试")
} finally {
setIsBinding(false)
}
}
const openBindModal = (type: "phone" | "wechat" | "alipay") => {
setBindType(type)
setBindValue("")
setBindError("")
setShowBindModal(true)
}
// 检查是否有绑定任何支付方式
const hasAnyPaymentBound = user?.wechat || user?.alipay
return (
<div className="min-h-screen bg-black text-white pb-24">
{/* Header */}
<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">
<button
onClick={() => router.back()}
className="w-8 h-8 rounded-full bg-white/10 flex items-center justify-center"
>
<ChevronLeft className="w-5 h-5 text-white" />
</button>
<h1 className="flex-1 text-center text-lg font-semibold text-white"></h1>
<div className="w-8" />
</div>
</header>
<main className="px-4 py-4 space-y-4">
{/* 账号绑定 */}
<div className="rounded-2xl bg-[#1c1c1e] border border-white/5 overflow-hidden">
<div className="px-4 py-3 border-b border-white/5">
<div className="flex items-center gap-2">
<Shield className="w-4 h-4 text-[#00CED1]" />
<span className="text-white font-medium"></span>
</div>
<p className="text-white/40 text-xs mt-1"></p>
</div>
{/* 手机号 */}
<button
onClick={() => openBindModal("phone")}
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">
<div className={`w-8 h-8 rounded-full flex items-center justify-center ${
user?.phone ? "bg-[#00CED1]/20" : "bg-white/10"
}`}>
<Phone className={`w-4 h-4 ${user?.phone ? "text-[#00CED1]" : "text-white/40"}`} />
</div>
<div className="text-left">
<p className="text-white text-sm"></p>
<p className="text-white/40 text-xs">
{user?.phone || "未绑定"}
</p>
</div>
</div>
{user?.phone ? (
<Check className="w-5 h-5 text-[#00CED1]" />
) : (
<span className="text-[#00CED1] text-xs"></span>
)}
</button>
{/* 微信号 */}
<button
onClick={() => openBindModal("wechat")}
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">
<div className={`w-8 h-8 rounded-full flex items-center justify-center ${
user?.wechat ? "bg-[#07C160]/20" : "bg-white/10"
}`}>
<MessageCircle className={`w-4 h-4 ${user?.wechat ? "text-[#07C160]" : "text-white/40"}`} />
</div>
<div className="text-left">
<p className="text-white text-sm"></p>
<p className="text-white/40 text-xs">
{user?.wechat || "未绑定"}
</p>
</div>
</div>
{user?.wechat ? (
<Check className="w-5 h-5 text-[#07C160]" />
) : (
<span className="text-[#07C160] text-xs"></span>
)}
</button>
{/* 支付宝 */}
<button
onClick={() => openBindModal("alipay")}
className="w-full flex items-center justify-between p-4 active:bg-white/5"
>
<div className="flex items-center gap-3">
<div className={`w-8 h-8 rounded-full flex items-center justify-center ${
user?.alipay ? "bg-[#1677FF]/20" : "bg-white/10"
}`}>
<CreditCard className={`w-4 h-4 ${user?.alipay ? "text-[#1677FF]" : "text-white/40"}`} />
</div>
<div className="text-left">
<p className="text-white text-sm"></p>
<p className="text-white/40 text-xs">
{user?.alipay || "未绑定"}
</p>
</div>
</div>
{user?.alipay ? (
<Check className="w-5 h-5 text-[#1677FF]" />
) : (
<span className="text-[#1677FF] text-xs"></span>
)}
</button>
</div>
{/* 绑定提示 */}
{!hasAnyPaymentBound && (
<div className="p-4 rounded-xl bg-orange-500/10 border border-orange-500/20">
<p className="text-orange-400 text-xs">
使
</p>
</div>
)}
{/* 退出登录 */}
<button
onClick={() => {
logout()
router.push("/")
}}
className="w-full py-3 rounded-xl bg-[#1c1c1e] text-red-400 font-medium border border-red-400/30"
>
退
</button>
</main>
{/* 绑定弹窗 */}
{showBindModal && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" onClick={() => !isBinding && setShowBindModal(false)} />
<div className="relative w-full max-w-sm bg-[#1c1c1e] rounded-2xl overflow-hidden">
<div className="flex items-center justify-between p-4 border-b border-white/10">
<h3 className="text-lg font-semibold text-white">
{bindType === "phone" ? "手机号" : bindType === "wechat" ? "微信号" : "支付宝"}
</h3>
<button
onClick={() => !isBinding && setShowBindModal(false)}
className="w-8 h-8 rounded-full bg-white/10 flex items-center justify-center"
>
<X className="w-4 h-4 text-white/60" />
</button>
</div>
<div className="p-5">
<div className="mb-4">
<label className="block text-white/40 text-xs mb-2">
{bindType === "phone" ? "手机号" : bindType === "wechat" ? "微信号" : "支付宝账号"}
</label>
<input
type={bindType === "phone" ? "tel" : "text"}
value={bindValue}
onChange={(e) => setBindValue(e.target.value)}
placeholder={
bindType === "phone" ? "请输入11位手机号" :
bindType === "wechat" ? "请输入微信号" :
"请输入支付宝账号"
}
className="w-full px-4 py-3 rounded-xl bg-black/30 border border-white/10 text-white placeholder-white/30 focus:outline-none focus:border-[#00CED1]/50"
disabled={isBinding}
/>
</div>
{bindError && (
<p className="text-red-400 text-sm mb-4">{bindError}</p>
)}
<button
onClick={handleBind}
disabled={isBinding || !bindValue}
className="w-full py-3 rounded-xl bg-[#00CED1] text-black font-medium flex items-center justify-center gap-2 disabled:opacity-50"
>
{isBinding ? (
<>
<Loader2 className="w-4 h-4 animate-spin" />
...
</>
) : (
"确认绑定"
)}
</button>
</div>
</div>
</div>
)}
</div>
)
}