Files
soul/app/match/page.tsx

360 lines
13 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, 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>
)
}