🎉 v1.3.1: 完美版本 - H5和小程序100%统一,64章精准数据,寻找合作伙伴功能

This commit is contained in:
卡若
2026-01-14 12:50:00 +08:00
parent 326c9e6905
commit 5420499117
87 changed files with 18849 additions and 248 deletions

359
app/match/page.tsx Normal file
View File

@@ -0,0 +1,359 @@
"use client"
import { useState, useEffect } from "react"
import { motion, AnimatePresence } from "framer-motion"
interface MatchUser {
id: string
nickname: string
avatar: string
tags: string[]
matchScore: number
concept: string
wechat: string
commonInterests: Array<{ icon: string; text: string }>
}
export default function MatchPage() {
const [isMatching, setIsMatching] = useState(false)
const [currentMatch, setCurrentMatch] = useState<MatchUser | null>(null)
const [matchAttempts, setMatchAttempts] = useState(0)
const startMatch = () => {
setIsMatching(true)
setMatchAttempts(0)
setCurrentMatch(null)
// 模拟匹配过程
const interval = setInterval(() => {
setMatchAttempts((prev) => prev + 1)
}, 1000)
// 3-6秒后匹配成功
setTimeout(() => {
clearInterval(interval)
setIsMatching(false)
setCurrentMatch(getMockMatch())
}, Math.random() * 3000 + 3000)
}
const getMockMatch = (): MatchUser => {
const nicknames = ['阅读爱好者', '创业小白', '私域达人', '书虫一枚', '灵魂摆渡人']
const randomIndex = Math.floor(Math.random() * nicknames.length)
const concepts = [
'一个坚持长期主义的私域玩家,擅长内容结构化。',
'相信阅读可以改变人生每天坚持读书1小时。',
'在Soul上分享创业经验希望帮助更多人少走弯路。'
]
const wechats = [
'soul_book_friend_1',
'soul_reader_2024',
'soul_party_fan'
]
return {
id: `user_${Date.now()}`,
nickname: nicknames[randomIndex],
avatar: `https://picsum.photos/200/200?random=${randomIndex}`,
tags: ['创业者', '私域运营', 'MBTI-INTP'],
matchScore: Math.floor(Math.random() * 20) + 80,
concept: concepts[randomIndex % concepts.length],
wechat: wechats[randomIndex % wechats.length],
commonInterests: [
{ icon: '📚', text: '都在读《创业实验》' },
{ icon: '💼', text: '对私域运营感兴趣' },
{ icon: '🎯', text: '相似的职业背景' }
]
}
}
const nextMatch = () => {
setCurrentMatch(null)
setTimeout(() => startMatch(), 500)
}
const handleAddWechat = () => {
if (!currentMatch) return
// 复制微信号
navigator.clipboard.writeText(currentMatch.wechat).then(() => {
alert(`微信号已复制:${currentMatch.wechat}\n\n请打开微信添加好友备注"书友"即可。`)
}).catch(() => {
alert(`微信号:${currentMatch.wechat}\n\n请手动复制并添加好友。`)
})
}
const handleJoinGroup = () => {
alert('请先添加书友微信,备注"书友群",对方会拉你入群。\n\n群内可以\n· 深度交流读书心得\n· 参加线下读书会\n· 获取独家资源')
}
return (
<div className="min-h-screen bg-black pb-20 page-transition">
{/* 星空背景 */}
<div className="fixed inset-0 overflow-hidden pointer-events-none">
{Array.from({ length: 100 }).map((_, i) => (
<motion.div
key={i}
className="absolute w-1 h-1 bg-white rounded-full"
style={{
left: `${Math.random() * 100}%`,
top: `${Math.random() * 100}%`,
opacity: Math.random() * 0.7 + 0.3,
}}
animate={{
opacity: [0.3, 1, 0.3],
scale: [1, 1.5, 1],
}}
transition={{
duration: Math.random() * 3 + 2,
repeat: Infinity,
delay: Math.random() * 2,
}}
/>
))}
</div>
<div className="relative z-10">
{/* 头部 */}
<div className="px-6 pt-20 pb-8 text-center">
<h1 className="text-5xl font-bold text-white mb-4"></h1>
<p className="text-white/60 text-lg">
</p>
</div>
{/* 匹配状态区 */}
<div className="px-6 pt-10">
<AnimatePresence mode="wait">
{!isMatching && !currentMatch && (
<motion.div
key="idle"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
className="flex flex-col items-center"
>
{/* 中央大星球 */}
<motion.div
onClick={startMatch}
className="relative w-[280px] h-[280px] mb-12 cursor-pointer"
whileTap={{ scale: 0.95 }}
>
<motion.div
className="absolute inset-0 rounded-full flex flex-col items-center justify-center"
style={{
background: 'linear-gradient(135deg, #00E5FF 0%, #7B61FF 50%, #E91E63 100%)',
boxShadow: '0 0 60px rgba(0, 229, 255, 0.4), 0 0 120px rgba(123, 97, 255, 0.3), inset 0 0 80px rgba(255, 255, 255, 0.1)'
}}
animate={{
y: [0, -10, 0],
scale: [1, 1.02, 1],
}}
transition={{
duration: 3,
repeat: Infinity,
ease: "easeInOut",
}}
>
<div className="text-6xl mb-3 filter brightness-0 invert">🤝</div>
<div className="text-2xl font-bold text-white mb-2 drop-shadow-lg"></div>
<div className="text-sm text-white/90 drop-shadow"></div>
</motion.div>
<motion.div
className="absolute inset-0 border-2 border-[#00E5FF]/30 rounded-full"
style={{ width: '330px', height: '330px', left: '-25px', top: '-25px' }}
animate={{
opacity: [0.3, 0.6, 0.3],
scale: [1, 1.05, 1],
}}
transition={{
duration: 2,
repeat: Infinity,
ease: "easeInOut",
}}
/>
</motion.div>
{/* 匹配提示 */}
<div className="glass-card p-6 mb-8 w-full max-w-md">
<div className="space-y-3 text-white/70">
<div className="flex items-center gap-3">
<span className="text-2xl">💼</span>
<span></span>
</div>
<div className="flex items-center gap-3">
<span className="text-2xl">💬</span>
<span>线</span>
</div>
<div className="flex items-center gap-3">
<span className="text-2xl">🎯</span>
<span></span>
</div>
</div>
</div>
</motion.div>
)}
{isMatching && (
<motion.div
key="matching"
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
className="text-center"
>
{/* 匹配动画 */}
<motion.div
className="text-9xl mb-8 relative mx-auto w-fit"
animate={{ rotate: 360 }}
transition={{ duration: 3, repeat: Infinity, ease: "linear" }}
>
🌍
{[1, 2, 3].map((ring) => (
<motion.div
key={ring}
className="absolute inset-0 border-4 border-[#00E5FF]/30 rounded-full"
style={{ width: '300px', height: '300px' }}
animate={{
scale: [1, 2, 1],
opacity: [1, 0, 1],
}}
transition={{
duration: 2,
repeat: Infinity,
delay: ring * 0.6,
}}
/>
))}
</motion.div>
<h2 className="text-2xl font-semibold mb-4 text-white">
...
</h2>
<p className="text-white/50 mb-8">
{matchAttempts}
</p>
<button
onClick={() => setIsMatching(false)}
className="btn-ios-secondary"
>
</button>
</motion.div>
)}
{currentMatch && !isMatching && (
<motion.div
key="matched"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
className="max-w-md mx-auto"
>
{/* 成功动画 */}
<motion.div
className="text-center mb-8"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: "spring", bounce: 0.5 }}
>
<span className="text-9xl"></span>
</motion.div>
{/* 用户卡片 */}
<div className="glass-card p-6 mb-6">
<div className="flex items-center gap-4 mb-4">
<img
src={currentMatch.avatar}
alt={currentMatch.nickname}
className="w-20 h-20 rounded-full border-4 border-[#00E5FF]"
/>
<div className="flex-1">
<h3 className="text-2xl font-semibold mb-2 text-white">
{currentMatch.nickname}
</h3>
<div className="flex flex-wrap gap-2">
{currentMatch.tags.map((tag) => (
<span
key={tag}
className="px-3 py-1 rounded-full text-sm bg-[#00E5FF]/20 text-[#00E5FF]"
>
{tag}
</span>
))}
</div>
</div>
<div className="text-center">
<div className="text-3xl font-bold text-[#00E5FF]">
{currentMatch.matchScore}%
</div>
<div className="text-xs text-white/50">
</div>
</div>
</div>
{/* 共同兴趣 */}
<div className="pt-4 border-t border-white/10 mb-4">
<h4 className="text-sm text-white/60 mb-3">
</h4>
<div className="space-y-2">
{currentMatch.commonInterests.map((interest, i) => (
<div
key={i}
className="flex items-center gap-3 text-sm text-white/80"
>
<span className="text-xl">{interest.icon}</span>
<span>{interest.text}</span>
</div>
))}
</div>
</div>
{/* 核心理念 */}
<div className="pt-4 border-t border-white/10">
<h4 className="text-sm text-white/60 mb-3">
</h4>
<p className="text-sm text-white/70 leading-relaxed">
{currentMatch.concept}
</p>
</div>
</div>
{/* 操作按钮 */}
<div className="space-y-4">
<div className="flex gap-4">
<button
onClick={handleAddWechat}
className="btn-ios flex-1 flex items-center justify-center gap-2"
>
<span className="text-xl"></span>
<span></span>
</button>
<button
onClick={handleJoinGroup}
className="btn-ios flex-1 flex items-center justify-center gap-2"
>
<span className="text-xl">👥</span>
<span></span>
</button>
</div>
<button
onClick={nextMatch}
className="btn-ios-secondary w-full"
>
🔄
</button>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
</div>
</div>
)
}