Files
soul/app/my/settings/page.tsx
卡若 b60edb3d47 feat: 完整重构小程序匹配功能 + 修复UI对齐 + 文章数据API
主要更新:
1. 按H5网页端完全重构匹配功能(match页面)
   - 4种匹配类型: 创业合伙/资源对接/导师顾问/团队招募
   - 资源对接等类型弹出手机号/微信号输入框
   - 去掉重新匹配按钮,改为返回按钮

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

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

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

5. 新增文章数据API
   - /api/db/chapters - 获取完整书籍结构
   - 支持按ID获取单篇文章内容
2026-01-21 15:49:12 +08:00

260 lines
9.6 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 { 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>
)
}