06488f0237
功能: - Go后端 (Gin + GORM + PostgreSQL) - UniApp用户端 (iOS/Android/小程序) - DaisyUI5后台管理 - JWT认证 + 微信登录 - 盲选加权算法 - 会员系统 + 优惠券 - 打分评价 + 偏好学习
348 lines
8.6 KiB
Vue
348 lines
8.6 KiB
Vue
<template>
|
||
<view class="page-home">
|
||
<!-- 顶部欢迎区 -->
|
||
<view class="header" :style="{ background: headerGradient }">
|
||
<view class="header-content">
|
||
<view class="greeting">
|
||
<text class="greeting-label">你好, {{ userName }} 👋</text>
|
||
<text class="greeting-message">{{ welcomeMessage }}</text>
|
||
</view>
|
||
<view class="member-badge" v-if="hasMember">
|
||
<text>👑 VIP</text>
|
||
</view>
|
||
<view class="member-badge free" @click="goToMember">
|
||
<text>升级VIP</text>
|
||
</view>
|
||
</view>
|
||
<view class="daily-usage">
|
||
<text>今日盲选: {{ usedToday }}/10</text>
|
||
<view class="usage-bar">
|
||
<view class="usage-fill" :style="{ width: usagePercent + '%' }"></view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- AI推荐 -->
|
||
<view class="ai-recommend card" v-if="aiRecommend">
|
||
<text class="ai-label">✨ AI推荐</text>
|
||
<text class="ai-text">{{ aiRecommend }}</text>
|
||
</view>
|
||
|
||
<!-- 盲选分类卡片 -->
|
||
<view class="categories">
|
||
<text class="section-title">选择类别</text>
|
||
<scroll-view scroll-x class="category-scroll" show-scrollbar="false">
|
||
<view
|
||
class="category-card"
|
||
v-for="cat in categories"
|
||
:key="cat.id"
|
||
@click="selectCategory(cat)"
|
||
>
|
||
<text class="category-icon">{{ cat.icon }}</text>
|
||
<text class="category-name">{{ cat.name }}</text>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<!-- 热门套餐推荐(模糊展示) -->
|
||
<view class="pools">
|
||
<text class="section-title">附近精选</text>
|
||
<view class="pool-grid">
|
||
<view class="pool-card card" v-for="pkg in poolList" :key="pkg.id" @click="goToBlind(pkg)">
|
||
<view class="pool-header">
|
||
<text class="pool-merchant">{{ pkg.merchant }}</text>
|
||
<text class="pool-rating">⭐ {{ pkg.rating.toFixed(1) }}</text>
|
||
</view>
|
||
<text class="pool-name">{{ pkg.name }}</text>
|
||
<text class="pool-cat">{{ pkg.cat_name }}</text>
|
||
<text class="pool-price">{{ pkg.price_range }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部TabBar -->
|
||
<view class="tabbar">
|
||
<view class="tab-item" @click="goPage('pages/index/index')">
|
||
<text class="tab-icon">🏠</text>
|
||
<text :class="['tab-label', currentTab === 0 ? 'active' : '']">首页</text>
|
||
</view>
|
||
<view class="tab-item" @click="goPage('pages/coupon/list')">
|
||
<text class="tab-icon">🎫</text>
|
||
<text :class="['tab-label', currentTab === 1 ? 'active' : '']">优惠券</text>
|
||
</view>
|
||
<view class="tab-item" @click="goPage('pages/member/member')">
|
||
<text class="tab-icon">👑</text>
|
||
<text :class="['tab-label', currentTab === 2 ? 'active' : '']">会员</text>
|
||
</view>
|
||
<view class="tab-item" @click="goPage('pages/user/profile')">
|
||
<text class="tab-icon">👤</text>
|
||
<text :class="['tab-label', currentTab === 3 ? 'active' : '']">我的</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted, computed } from 'vue'
|
||
import { blindApi, memberApi } from '@/api/index.js'
|
||
import { useUserStore } from '@/store/user.js'
|
||
|
||
const userStore = useUserStore()
|
||
const categories = ref([])
|
||
const poolList = ref([])
|
||
const aiRecommend = ref('')
|
||
const usedToday = ref(0)
|
||
|
||
const userName = computed(() => userStore.userInfo.nickname || '朋友')
|
||
const hasMember = computed(() => userStore.hasMember)
|
||
const usagePercent = computed(() => Math.min((usedToday.value / 10) * 100, 100))
|
||
const headerGradient = computed(() =>
|
||
hasMember.value ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' : 'linear-gradient(135deg, #FF6B35 0%, #FF8C42 100%)'
|
||
)
|
||
|
||
const welcomeMessages = [
|
||
'今天想带你吃点惊喜的~',
|
||
'来一场未知的味蕾冒险?',
|
||
'准备好遇见新味道了吗?',
|
||
'你的盲选管家已上线 ✨',
|
||
]
|
||
const welcomeMessage = ref(welcomeMessages[Math.floor(Math.random() * welcomeMessages.length)])
|
||
|
||
const aiTexts = [
|
||
'看你最近爱吃日料,今天推荐一家隐藏好评店?',
|
||
'好久没吃辣了,要不要来场火锅盲选?',
|
||
'根据你的口味,今天适合一场甜品之旅 🍰',
|
||
'探索型用户!今天试试没去过的类型?',
|
||
]
|
||
aiRecommend.value = aiTexts[Math.floor(Math.random() * aiTexts.length)]
|
||
|
||
onMounted(async () => {
|
||
// Load categories
|
||
try {
|
||
categories.value = await blindApi.getCategories()
|
||
} catch(e) {}
|
||
|
||
// Load pool
|
||
try {
|
||
poolList.value = await blindApi.getPool({ limit: 6 })
|
||
} catch(e) {}
|
||
|
||
// Check member status
|
||
try {
|
||
const status = await memberApi.getStatus()
|
||
userStore.setHasMember(status.active)
|
||
usedToday.value = status.used_today || 0
|
||
} catch(e) {}
|
||
})
|
||
|
||
function selectCategory(cat) {
|
||
uni.navigateTo({
|
||
url: `/pages/blind/blind?categoryId=${cat.id}&categoryName=${cat.name}&categoryIcon=${cat.icon}`
|
||
})
|
||
}
|
||
|
||
function goToBlind(pkg) {
|
||
uni.navigateTo({
|
||
url: `/pages/blind/blind?packageId=${pkg.id}&merchant=${pkg.merchant}`
|
||
})
|
||
}
|
||
|
||
function goPage(path) {
|
||
if (path === 'pages/index/index') {
|
||
uni.switchTab({ url: '/pages/index/index' })
|
||
} else {
|
||
uni.switchTab({ url: path })
|
||
}
|
||
}
|
||
|
||
function goToMember() {
|
||
uni.navigateTo({ url: '/pages/member/member' })
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.page-home {
|
||
min-height: 100vh;
|
||
padding-bottom: 120rpx;
|
||
background: #f5f5f5;
|
||
}
|
||
|
||
.header {
|
||
padding: 60rpx 30rpx 40rpx;
|
||
color: #fff;
|
||
.header-content {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
.greeting {
|
||
.greeting-label {
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
display: block;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
.greeting-message {
|
||
font-size: 26rpx;
|
||
opacity: 0.9;
|
||
}
|
||
}
|
||
.member-badge {
|
||
background: rgba(255,255,255,0.25);
|
||
padding: 8rpx 20rpx;
|
||
border-radius: 20rpx;
|
||
font-size: 22rpx;
|
||
backdrop-filter: blur(10rpx);
|
||
&.free {
|
||
background: rgba(255,255,255,0.15);
|
||
}
|
||
}
|
||
}
|
||
.daily-usage {
|
||
margin-top: 24rpx;
|
||
font-size: 22rpx;
|
||
opacity: 0.8;
|
||
.usage-bar {
|
||
height: 6rpx;
|
||
background: rgba(255,255,255,0.2);
|
||
border-radius: 3rpx;
|
||
margin-top: 8rpx;
|
||
overflow: hidden;
|
||
.usage-fill {
|
||
height: 100%;
|
||
background: #fff;
|
||
border-radius: 3rpx;
|
||
transition: width 0.3s;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.card {
|
||
background: #fff;
|
||
border-radius: 20rpx;
|
||
padding: 24rpx;
|
||
margin: 20rpx 30rpx;
|
||
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.06);
|
||
}
|
||
|
||
.ai-recommend {
|
||
.ai-label {
|
||
font-size: 22rpx;
|
||
color: #999;
|
||
display: block;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
.ai-text {
|
||
font-size: 28rpx;
|
||
font-weight: 500;
|
||
line-height: 1.6;
|
||
}
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 32rpx;
|
||
font-weight: 700;
|
||
color: #333;
|
||
padding: 20rpx 30rpx 10rpx;
|
||
display: block;
|
||
}
|
||
|
||
.category-scroll {
|
||
white-space: nowrap;
|
||
padding: 10rpx 30rpx;
|
||
}
|
||
|
||
.category-card {
|
||
display: inline-flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
background: #fff;
|
||
border-radius: 20rpx;
|
||
padding: 24rpx 30rpx;
|
||
margin-right: 16rpx;
|
||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.06);
|
||
.category-icon {
|
||
font-size: 48rpx;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
.category-name {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
}
|
||
}
|
||
|
||
.pool-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 16rpx;
|
||
padding: 10rpx 20rpx;
|
||
}
|
||
|
||
.pool-card {
|
||
background: #fff;
|
||
border-radius: 16rpx;
|
||
padding: 20rpx;
|
||
margin-bottom: 0;
|
||
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06);
|
||
.pool-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
.pool-merchant {
|
||
font-size: 22rpx;
|
||
color: #999;
|
||
}
|
||
.pool-rating {
|
||
font-size: 20rpx;
|
||
}
|
||
.pool-name {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
margin-top: 8rpx;
|
||
display: block;
|
||
}
|
||
.pool-cat {
|
||
font-size: 20rpx;
|
||
color: #FF6B35;
|
||
margin-top: 4rpx;
|
||
display: inline-block;
|
||
background: #FFF3E0;
|
||
padding: 2rpx 10rpx;
|
||
border-radius: 8rpx;
|
||
}
|
||
.pool-price {
|
||
font-size: 24rpx;
|
||
color: #FF6B35;
|
||
font-weight: 600;
|
||
margin-top: 12rpx;
|
||
display: block;
|
||
}
|
||
}
|
||
|
||
.tabbar {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
display: flex;
|
||
background: #fff;
|
||
padding: 10rpx 0 30rpx;
|
||
box-shadow: 0 -4rpx 20rpx rgba(0,0,0,0.06);
|
||
z-index: 100;
|
||
.tab-item {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
.tab-icon { font-size: 36rpx; }
|
||
.tab-label {
|
||
font-size: 20rpx;
|
||
color: #999;
|
||
margin-top: 4rpx;
|
||
&.active { color: #FF6B35; }
|
||
}
|
||
}
|
||
}
|
||
</style>
|