refactor: overhaul homepage and app structure

Simplify homepage, show chapter counts, display directory, trim bottom nav, in-page match feature, move marketing content, and enhance "My" page.

#VERCEL_SKIP

Co-authored-by: undefined <undefined+undefined@users.noreply.github.com>
This commit is contained in:
v0
2026-01-14 05:10:32 +00:00
parent c1953b89c1
commit f3195d9331
79 changed files with 1391 additions and 1077 deletions

View File

@@ -2,60 +2,56 @@
import Link from "next/link"
import { usePathname } from "next/navigation"
import { Home, User } from "lucide-react"
import { Home, User, Handshake } from "lucide-react"
import { useState } from "react"
import { MatchModal } from "@/components/match-modal"
export function BottomNav() {
const pathname = usePathname()
const [showMatch, setShowMatch] = useState(false)
// 在文档页面和管理后台不显示底部导航
if (pathname.startsWith("/documentation") || pathname.startsWith("/admin")) {
// 在管理后台不显示底部导航
if (pathname.startsWith("/admin")) {
return null
}
const navItems = [
{ href: "/", icon: Home, label: "首页" },
{ href: "/match", emoji: "🤝", label: "匹配合作" },
{ href: "/my", icon: User, label: "我的" },
]
return (
<>
{/* iOS风格底部导航 - 只有3个按钮 */}
<nav className="fixed bottom-0 left-0 right-0 z-40 glass-nav safe-bottom">
{/* iOS风格底部导航 - 只有3个按钮,匹配在当前页面弹窗 */}
<nav className="fixed bottom-0 left-0 right-0 z-40 bg-black/80 backdrop-blur-xl border-t border-white/[0.06] safe-bottom">
<div className="flex items-center justify-around py-2 max-w-lg mx-auto">
{navItems.map((item, index) => {
const isActive = pathname === item.href || (item.href === '/match' && pathname.startsWith('/match'))
const Icon = item.icon
{/* 首页 */}
<Link href="/" className="flex flex-col items-center py-2 px-6 transition-all">
<Home
className={`w-5 h-5 mb-0.5 ${pathname === "/" ? "text-[var(--app-brand)]" : "text-white/40"}`}
strokeWidth={pathname === "/" ? 2.5 : 1.5}
/>
<span className={`text-[10px] ${pathname === "/" ? "text-[var(--app-brand)]" : "text-white/40"}`}>
</span>
</Link>
return (
<Link
key={index}
href={item.href!}
className="flex flex-col items-center py-2 px-4 sm:px-6 touch-feedback transition-all duration-200"
>
<div className={`w-7 h-7 flex items-center justify-center mb-1 transition-colors ${
isActive ? "text-[var(--app-brand)]" : "text-[var(--app-text-tertiary)]"
}`}>
{item.emoji ? (
<span className="text-2xl">{item.emoji}</span>
) : (
<Icon className="w-6 h-6" strokeWidth={isActive ? 2.5 : 1.5} />
)}
</div>
<span className={`text-[10px] font-medium transition-colors ${
isActive ? "text-[var(--app-brand)]" : "text-[var(--app-text-tertiary)]"
}`}>
{item.label}
</span>
{/* 激活指示器 */}
{isActive && (
<div className="absolute -bottom-0.5 w-1 h-1 rounded-full bg-[var(--app-brand)]" />
)}
</Link>
)
})}
{/* 匹配合作 - 点击弹出弹窗而不是跳转 */}
<button onClick={() => setShowMatch(true)} className="flex flex-col items-center py-2 px-6 transition-all">
<Handshake className="w-5 h-5 mb-0.5 text-white/40" strokeWidth={1.5} />
<span className="text-[10px] text-white/40"></span>
</button>
{/* 我的 */}
<Link href="/my" className="flex flex-col items-center py-2 px-6 transition-all">
<User
className={`w-5 h-5 mb-0.5 ${pathname.startsWith("/my") ? "text-[var(--app-brand)]" : "text-white/40"}`}
strokeWidth={pathname.startsWith("/my") ? 2.5 : 1.5}
/>
<span className={`text-[10px] ${pathname.startsWith("/my") ? "text-[var(--app-brand)]" : "text-white/40"}`}>
</span>
</Link>
</div>
</nav>
{/* 匹配弹窗 */}
<MatchModal isOpen={showMatch} onClose={() => setShowMatch(false)} />
</>
)
}

215
components/match-modal.tsx Normal file
View File

@@ -0,0 +1,215 @@
"use client"
import { useState, useEffect } from "react"
import { X, Copy, Check, RefreshCw } from "lucide-react"
import { motion, AnimatePresence } from "framer-motion"
interface MatchUser {
id: string
nickname: string
tags: string[]
matchScore: number
concept: string
wechat: string
}
interface MatchModalProps {
isOpen: boolean
onClose: () => void
}
export function MatchModal({ isOpen, onClose }: MatchModalProps) {
const [isMatching, setIsMatching] = useState(false)
const [currentMatch, setCurrentMatch] = useState<MatchUser | null>(null)
const [copied, setCopied] = useState(false)
const getMockMatch = (): MatchUser => {
const data = [
{ nickname: "创业小白", tags: ["电商", "私域"], concept: "想找供应链合作伙伴", wechat: "soul_biz_001" },
{ nickname: "私域达人", tags: ["内容", "直播"], concept: "擅长内容运营,找技术合伙人", wechat: "soul_biz_002" },
{ nickname: "供应链老兵", tags: ["供应链", "跨境"], concept: "有工厂资源,找流量渠道", wechat: "soul_biz_003" },
{ nickname: "技术宅", tags: ["AI", "开发"], concept: "会写代码,想找商业方向", wechat: "soul_biz_004" },
]
const item = data[Math.floor(Math.random() * data.length)]
return {
id: `user_${Date.now()}`,
...item,
matchScore: Math.floor(Math.random() * 20) + 80,
}
}
const startMatch = () => {
setIsMatching(true)
setCurrentMatch(null)
setCopied(false)
setTimeout(() => {
setIsMatching(false)
setCurrentMatch(getMockMatch())
}, 2000)
}
const copyWechat = () => {
if (currentMatch) {
navigator.clipboard.writeText(currentMatch.wechat)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
}
useEffect(() => {
if (!isOpen) {
setCurrentMatch(null)
setIsMatching(false)
}
}, [isOpen])
if (!isOpen) return null
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={onClose} />
{/* 弹窗内容 */}
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
className="relative w-full max-w-sm bg-[#111] rounded-3xl overflow-hidden border border-white/10"
>
{/* 关闭按钮 */}
<button
onClick={onClose}
className="absolute top-4 right-4 w-8 h-8 rounded-full bg-white/10 flex items-center justify-center z-10"
>
<X className="w-4 h-4 text-white/60" />
</button>
<div className="p-6 pt-12">
<AnimatePresence mode="wait">
{/* 初始状态 - 开始匹配 */}
{!isMatching && !currentMatch && (
<motion.div
key="idle"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="text-center"
>
<h2 className="text-xl font-bold text-white mb-2"></h2>
<p className="text-white/50 text-sm mb-8"></p>
{/* 匹配按钮 - 美化的圆形按钮 */}
<button onClick={startMatch} className="relative w-40 h-40 mx-auto mb-8 group">
{/* 外圈光环 */}
<div className="absolute inset-0 rounded-full bg-gradient-to-br from-[var(--app-brand)]/20 to-purple-500/20 animate-pulse" />
<div className="absolute inset-2 rounded-full bg-gradient-to-br from-[var(--app-brand)]/30 to-purple-500/30" />
{/* 主按钮 */}
<div className="absolute inset-4 rounded-full bg-gradient-to-br from-[var(--app-brand)] to-purple-500 flex flex-col items-center justify-center shadow-lg shadow-[var(--app-brand)]/30 group-active:scale-95 transition-transform">
<span className="text-4xl mb-1">🤝</span>
<span className="text-white font-medium text-sm"></span>
</div>
</button>
<div className="space-y-2 text-left">
<div className="flex items-center gap-2 text-white/50 text-xs">
<span>💼</span>
<span></span>
</div>
<div className="flex items-center gap-2 text-white/50 text-xs">
<span>🔒</span>
<span></span>
</div>
</div>
</motion.div>
)}
{/* 匹配中 */}
{isMatching && (
<motion.div
key="matching"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="text-center py-12"
>
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 2, repeat: Number.POSITIVE_INFINITY, ease: "linear" }}
className="w-20 h-20 mx-auto mb-6 rounded-full border-4 border-[var(--app-brand)]/30 border-t-[var(--app-brand)]"
/>
<p className="text-white font-medium">...</p>
<p className="text-white/40 text-sm mt-1"></p>
</motion.div>
)}
{/* 匹配成功 */}
{currentMatch && !isMatching && (
<motion.div
key="matched"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0 }}
>
<div className="text-center mb-4">
<span className="text-3xl"></span>
<h3 className="text-lg font-bold text-white mt-2">!</h3>
</div>
{/* 用户卡片 */}
<div className="bg-white/5 rounded-2xl p-4 mb-4">
<div className="flex items-center justify-between mb-3">
<div>
<h4 className="text-white font-medium">{currentMatch.nickname}</h4>
<div className="flex gap-1 mt-1">
{currentMatch.tags.map((tag) => (
<span
key={tag}
className="px-2 py-0.5 rounded-full text-[10px] bg-[var(--app-brand)]/20 text-[var(--app-brand)]"
>
{tag}
</span>
))}
</div>
</div>
<div className="text-right">
<span className="text-2xl font-bold text-[var(--app-brand)]">{currentMatch.matchScore}%</span>
<p className="text-white/40 text-[10px]"></p>
</div>
</div>
<p className="text-white/60 text-sm">{currentMatch.concept}</p>
</div>
{/* 微信号 */}
<div className="bg-white/5 rounded-xl p-3 mb-4">
<p className="text-white/40 text-xs mb-1"></p>
<div className="flex items-center justify-between">
<code className="text-[var(--app-brand)] font-mono">{currentMatch.wechat}</code>
<button
onClick={copyWechat}
className="flex items-center gap-1 px-3 py-1.5 rounded-lg bg-[var(--app-brand)] text-white text-xs"
>
{copied ? <Check className="w-3 h-3" /> : <Copy className="w-3 h-3" />}
{copied ? "已复制" : "复制"}
</button>
</div>
</div>
{/* 重新匹配 */}
<button
onClick={startMatch}
className="w-full py-3 rounded-xl bg-white/5 text-white/60 text-sm flex items-center justify-center gap-2"
>
<RefreshCw className="w-4 h-4" />
</button>
</motion.div>
)}
</AnimatePresence>
</div>
</motion.div>
</div>
)
}