This commit is contained in:
jeremygan2021
2026-01-10 01:38:34 +08:00
parent 7a716e7f5e
commit 20c280405a
5 changed files with 843 additions and 41 deletions

View File

@@ -0,0 +1,377 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>资源匹配 - 供需对接</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
:root {
--primary-color: #00f2ff; /* Cyan from the AI sphere/text */
--secondary-color: #0066ff; /* Rich blue */
--bg-color: #050814; /* Deep space blue/black */
--card-bg: rgba(12, 24, 50, 0.5); /* Glassy blue tint */
--text-color: #ffffff;
--text-muted: #b0c4de; /* Light blue-grey */
--accent-glow: rgba(0, 242, 255, 0.6);
}
* {
box-sizing: border-box;
/* margin: 0; padding: 0; - Tailwind resets this but good to keep in mind */
}
body {
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
background-image:
radial-gradient(circle at 50% 0%, #1a3a75 0%, #050814 60%),
radial-gradient(circle at 85% 30%, rgba(0, 242, 255, 0.1) 0%, transparent 40%),
radial-gradient(circle at 15% 70%, rgba(0, 102, 255, 0.15) 0%, transparent 40%);
background-attachment: fixed;
min-height: 100vh;
overflow-x: hidden;
}
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
linear-gradient(rgba(255,255,255,0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,0.03) 1px, transparent 1px);
background-size: 50px 50px;
pointer-events: none;
z-index: -1;
}
.container {
width: 100%;
max-width: 600px;
margin: 0 auto;
padding: 20px;
position: relative;
z-index: 1;
}
.header-img {
width: 100%;
max-height: 220px;
object-fit: cover;
border-radius: 12px;
margin-bottom: 25px;
box-shadow: 0 0 20px rgba(0, 242, 255, 0.2);
border: 1px solid rgba(0, 242, 255, 0.3);
}
.glass-card {
background: var(--card-bg);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(0, 242, 255, 0.2);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.5), inset 0 0 20px rgba(0, 242, 255, 0.05);
border-radius: 16px;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
h1, h2, h3 {
color: #fff;
}
h1 {
background: linear-gradient(180deg, #ffffff 0%, #d0eaff 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 0 30px rgba(0, 242, 255, 0.4);
}
.text-primary { color: var(--primary-color); }
.text-muted { color: var(--text-muted); }
/* Custom Input Style to match index.html */
.custom-input {
width: 100%;
padding: 14px 16px;
background: rgba(0, 0, 0, 0.4);
border: 1px solid rgba(0, 242, 255, 0.3);
border-radius: 8px;
color: white;
font-size: 1rem;
outline: none;
transition: all 0.3s;
box-shadow: inset 0 2px 5px rgba(0,0,0,0.3);
}
.custom-input:focus {
border-color: var(--primary-color);
box-shadow: 0 0 15px rgba(0, 242, 255, 0.3), inset 0 2px 5px rgba(0,0,0,0.3);
background: rgba(0, 0, 0, 0.6);
}
/* Custom Button Style */
.custom-btn {
background: linear-gradient(90deg, #0051ff 0%, #00f2ff 100%);
color: #fff;
border: none;
box-shadow: 0 4px 15px rgba(0, 242, 255, 0.3);
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
transition: all 0.3s;
}
.custom-btn:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 242, 255, 0.5);
background: linear-gradient(90deg, #0040cc 0%, #00d0dd 100%);
}
.custom-btn:disabled {
background: #2a3b55;
cursor: not-allowed;
opacity: 0.6;
box-shadow: none;
}
/* Tab Buttons */
.tab-btn {
background: rgba(255, 255, 255, 0.05);
color: var(--text-muted);
border: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.3s;
}
.tab-btn.active {
background: rgba(0, 242, 255, 0.15);
color: #fff;
border-color: var(--primary-color);
box-shadow: 0 0 15px rgba(0, 242, 255, 0.2);
}
/* Avatar */
.avatar-circle {
background: linear-gradient(135deg, var(--secondary-color), var(--primary-color));
color: white;
box-shadow: 0 0 10px rgba(0, 242, 255, 0.3);
}
/* Tags */
.tag {
background: rgba(0, 242, 255, 0.1);
color: var(--primary-color);
border: 1px solid rgba(0, 242, 255, 0.2);
}
/* Mobile specific adjustments */
@media (max-width: 640px) {
.container { padding: 15px; }
h1 { font-size: 1.5rem; }
}
</style>
</head>
<body>
<div id="app" class="container pb-20">
<!-- Header Image -->
<img src="/static/image.jpg" alt="Header Image" class="header-img" onerror="this.style.display='none'">
<!-- Header Info -->
<div class="glass-card p-6 mb-6 text-center relative overflow-hidden">
<!-- Background Decoration -->
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-blue-600 to-cyan-400"></div>
<h1 class="text-2xl font-bold mb-2 tracking-wide">资源雷达 📡</h1>
<p class="text-sm text-muted">发现全场潜在商机与合作伙伴</p>
<!-- User Stats -->
<div v-if="user" class="mt-6 flex items-center justify-between bg-black/20 rounded-lg p-4 border border-white/10">
<div class="flex items-center text-left">
<div class="avatar-circle w-10 h-10 rounded-full flex items-center justify-center mr-3 text-lg font-bold shrink-0">
<i class="fas fa-user"></i>
</div>
<div>
<div class="font-bold text-white" v-text="user.name"></div>
<div class="text-xs text-muted truncate max-w-[120px]" v-text="user.industry_company"></div>
</div>
</div>
<div class="text-right">
<div class="text-xs text-muted mb-1">社交点数</div>
<div class="text-xl font-bold text-yellow-300 drop-shadow-md"><span v-text="user.points"></span> 💎</div>
</div>
</div>
</div>
<!-- Login / Search Form -->
<div v-if="!user">
<div class="glass-card p-8">
<h2 class="text-xl font-bold mb-6 text-center border-b border-white/10 pb-4">开启资源匹配</h2>
<div class="mb-6">
<label class="block text-primary text-sm font-bold mb-3 pl-1">手机号</label>
<input v-model="phone" type="tel" class="custom-input" placeholder="请输入您的手机号">
</div>
<button @click="login" :disabled="loading" class="w-full custom-btn py-3 px-4 rounded-lg font-bold flex items-center justify-center">
<span v-if="loading"><i class="fas fa-spinner fa-spin mr-2"></i>匹配中...</span>
<span v-else>开始匹配</span>
</button>
</div>
</div>
<!-- Results -->
<div v-else class="space-y-6">
<!-- Category Tabs -->
<div class="flex space-x-3 overflow-x-auto pb-2 scrollbar-hide">
<button @click="activeTab = 'customers'" :class="['px-5 py-2 rounded-full text-sm font-bold whitespace-nowrap tab-btn', activeTab === 'customers' ? 'active' : '']">
全场客户 (<span v-text="matches.customers.length"></span>)
</button>
<button @click="activeTab = 'partners'" :class="['px-5 py-2 rounded-full text-sm font-bold whitespace-nowrap tab-btn', activeTab === 'partners' ? 'active' : '']">
合作伙伴 (<span v-text="matches.partners.length"></span>)
</button>
<button @click="activeTab = 'peers'" :class="['px-5 py-2 rounded-full text-sm font-bold whitespace-nowrap tab-btn', activeTab === 'peers' ? 'active' : '']">
同行 (<span v-text="matches.peers.length"></span>)
</button>
</div>
<!-- List -->
<div class="space-y-4">
<div v-if="matches[activeTab].length === 0" class="text-center py-16 text-muted glass-card">
<i class="fas fa-satellite text-4xl mb-4 opacity-50"></i>
<p>暂无匹配结果</p>
</div>
<div v-for="(person, index) in matches[activeTab]" :key="index" class="glass-card p-5 relative overflow-hidden group hover:border-cyan-400/50 transition-colors">
<!-- Match Badge -->
<div class="absolute top-0 right-0 bg-gradient-to-bl from-yellow-500/20 to-transparent text-yellow-300 text-xs px-3 py-1 rounded-bl-lg font-bold border-l border-b border-yellow-500/20">
匹配度 <span v-text="Math.round(person.score * 100)"></span>%
</div>
<div class="flex items-start mb-4">
<div class="avatar-circle w-12 h-12 rounded-full flex items-center justify-center mr-4 text-xl font-bold shrink-0">
<span v-text="person.name[0]"></span>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-bold text-lg text-white mb-1" v-text="person.name"></h3>
<p class="text-sm text-muted flex items-center truncate">
<i class="fas fa-building mr-2 opacity-70"></i>
<span v-text="person.company_name || '暂无公司信息'"></span>
</p>
<p class="text-xs text-muted/70 mt-1 pl-6 truncate" v-text="person.position || '职位未知'"></p>
</div>
</div>
<!-- Tags -->
<div class="flex flex-wrap gap-2 mb-4">
<span class="tag text-xs px-2 py-1 rounded flex items-center">
<i class="fas fa-briefcase mr-1 opacity-70"></i><span v-text="person.industry"></span>
</span>
<span class="tag text-xs px-2 py-1 rounded flex items-center">
<i class="fas fa-bullseye mr-1 opacity-70"></i>
<span class="truncate max-w-[200px]" v-text="person.vision_2026 || '暂无愿景'"></span>
</span>
</div>
<!-- Contact Action -->
<div class="border-t border-white/10 pt-4 mt-2">
<div v-if="person.unlocked" class="bg-green-900/20 border border-green-500/30 p-3 rounded-lg text-sm text-green-400">
<p class="mb-1"><i class="fas fa-phone mr-2"></i><span v-text="person.phone"></span></p>
<p><i class="fas fa-map-marker-alt mr-2"></i><span v-text="person.location"></span></p>
</div>
<button v-else @click="unlockContact(person)" class="w-full bg-white/5 hover:bg-white/10 text-white text-sm py-2 rounded-lg transition border border-white/10 flex items-center justify-center group-hover:border-cyan-400/30">
<i class="fas fa-lock mr-2 text-muted group-hover:text-cyan-400"></i> 查看联系方式 (-1 💎)
</button>
</div>
</div>
</div>
</div>
<!-- Toast -->
<div v-if="toast.show" class="fixed bottom-10 left-1/2 transform -translate-x-1/2 bg-gray-900 border border-cyan-500/50 text-white px-6 py-3 rounded-lg shadow-[0_0_20px_rgba(0,242,255,0.3)] text-sm transition-opacity duration-300 z-50 flex items-center" :class="{'opacity-0': !toast.show}">
<i class="fas fa-info-circle text-primary mr-2"></i>
<span v-text="toast.message"></span>
</div>
</div>
<script>
const { createApp, ref, reactive } = Vue
createApp({
setup() {
const phone = ref('')
const user = ref(null)
const loading = ref(false)
const activeTab = ref('customers')
const matches = reactive({
customers: [],
partners: [],
peers: []
})
const toast = reactive({ show: false, message: '' })
const showToast = (msg) => {
toast.message = msg
toast.show = true
setTimeout(() => toast.show = false, 3000)
}
const login = async () => {
if (!phone.value) return showToast('请输入手机号')
loading.value = true
try {
const res = await fetch('/api/resource-match', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ phone: phone.value })
})
const data = await res.json()
if (data.success) {
user.value = data.user
matches.customers = data.matches.customers
matches.partners = data.matches.partners
matches.peers = data.matches.peers
} else {
showToast(data.message || '匹配失败')
}
} catch (e) {
showToast('网络错误')
} finally {
loading.value = false
}
}
const unlockContact = async (person) => {
if (user.value.points <= 0) {
return showToast('社交点数不足,请联系工作人员充值')
}
if (!confirm(`确定消耗1点数查看 ${person.name} 的联系方式吗?`)) return
try {
const res = await fetch('/api/unlock-contact', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
my_phone: user.value.phone,
target_id: person.new_id
})
})
const data = await res.json()
if (data.success) {
person.phone = data.contact.phone
person.location = data.contact.location
person.unlocked = true
user.value.points = data.remaining_points
showToast('解锁成功!')
} else {
showToast(data.message)
}
} catch (e) {
showToast('解锁失败')
}
}
return {
phone, user, loading, activeTab, matches, login, unlockContact, toast
}
}
}).mount('#app')
</script>
</body>
</html>