06488f0237
功能: - Go后端 (Gin + GORM + PostgreSQL) - UniApp用户端 (iOS/Android/小程序) - DaisyUI5后台管理 - JWT认证 + 微信登录 - 盲选加权算法 - 会员系统 + 优惠券 - 打分评价 + 偏好学习
367 lines
8.3 KiB
Vue
367 lines
8.3 KiB
Vue
<template>
|
|
<view class="page-blind">
|
|
<!-- 头部 -->
|
|
<view class="blind-header">
|
|
<view class="back-btn" @click="goBack">
|
|
<text>← 返回</text>
|
|
</view>
|
|
<text class="blind-title">{{ categoryIcon }} {{ categoryName }}</text>
|
|
</view>
|
|
|
|
<!-- 价格区间选择 -->
|
|
<view class="price-section card">
|
|
<text class="section-label">选择价格区间</text>
|
|
<view class="price-options">
|
|
<view
|
|
class="price-option"
|
|
:class="{ active: selectedPrice === p.value }"
|
|
v-for="p in priceRanges"
|
|
:key="p.value"
|
|
@click="selectedPrice = p.value"
|
|
>
|
|
<text>{{ p.label }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 盲选卡片 -->
|
|
<view class="blind-card-container">
|
|
<view class="blind-card" :class="{ spinning: isSpinning, revealed: isRevealed }">
|
|
<view class="card-front" v-if="!isRevealed">
|
|
<text class="card-logo">🎲</text>
|
|
<text class="card-hint">点击按钮开始盲选</text>
|
|
</view>
|
|
<view class="card-reveal" v-if="isRevealed && selectedResult">
|
|
<text class="reveal-icon">✨</text>
|
|
<text class="reveal-name">{{ selectedResult.merchant_name }}</text>
|
|
<text class="reveal-desc">{{ selectedResult.description }}</text>
|
|
<text class="reveal-price">¥{{ selectedResult.price_range }}</text>
|
|
<view class="reveal-tags">
|
|
<text class="reveal-tag" v-for="tag in resultTags" :key="tag">{{ tag }}</text>
|
|
</view>
|
|
<text class="reveal-match">🎯 匹配度 {{ (selectedResult.match_score * 100).toFixed(0) }}%</text>
|
|
<view class="reveal-coupon" v-if="selectedResult.has_coupon">
|
|
<text class="coupon-icon">🎁</text>
|
|
<text class="coupon-text">{{ selectedResult.coupon_value || '有优惠券!' }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 盲选按钮 -->
|
|
<view class="action-area" v-if="!isRevealed">
|
|
<button class="blind-btn" :loading="isSpinning" @click="startBlind" :disabled="isSpinning">
|
|
<text class="blind-btn-text">{{ isSpinning ? '正在抽取...' : '🎲 开始盲选' }}</text>
|
|
</button>
|
|
</view>
|
|
|
|
<!-- 结果操作 -->
|
|
<view class="result-actions" v-if="isRevealed && selectedResult">
|
|
<button class="accept-btn" @click="acceptResult">
|
|
<text>✅ 接受 / 前往</text>
|
|
</button>
|
|
<button class="skip-btn" @click="declineResult">
|
|
<text>🔄 换一个</text>
|
|
</button>
|
|
<button class="star-btn" @click="goToReview">
|
|
<text>⭐ 已去过 · 打分</text>
|
|
</button>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref } from 'vue'
|
|
import { useRoute } from '@dcloudio/uni-app'
|
|
import { blindApi } from '@/api/index.js'
|
|
|
|
const route = useRoute()
|
|
const categoryName = ref(route.query.categoryName || '盲选')
|
|
const categoryIcon = ref(route.query.categoryIcon || '🍽️')
|
|
const packageId = ref(route.query.packageId || 0)
|
|
const categoryId = ref(parseInt(route.query.categoryId) || 1)
|
|
|
|
const selectedPrice = ref('100-300')
|
|
const isSpinning = ref(false)
|
|
const isRevealed = ref(false)
|
|
const selectedResult = ref(null)
|
|
|
|
const priceRanges = [
|
|
{ label: '¥100以内', value: '0-100' },
|
|
{ label: '¥100-300', value: '100-300' },
|
|
{ label: '¥300-500', value: '300-500' },
|
|
{ label: '¥500+', value: '500-9999' },
|
|
]
|
|
|
|
const resultTags = ['推荐', '好评', '特色']
|
|
|
|
async function startBlind() {
|
|
isSpinning.value = true
|
|
|
|
// 1秒动画延迟
|
|
await new Promise(r => setTimeout(r, 1000))
|
|
|
|
try {
|
|
// 调用盲选API
|
|
const priceMap = { '100-300': '100-300', '0-100': '0-100', '300-500': '300-500', '500-9999': '500-1000' }
|
|
const data = await blindApi.choose({
|
|
category_id: categoryId.value,
|
|
price_range: priceMap[selectedPrice.value] || '100-300',
|
|
})
|
|
|
|
// 显示结果
|
|
selectedResult.value = data.result
|
|
isRevealed.value = true
|
|
} catch (e) {
|
|
uni.showToast({ title: '盲选失败,请重试', icon: 'none' })
|
|
} finally {
|
|
isSpinning.value = false
|
|
}
|
|
}
|
|
|
|
function acceptResult() {
|
|
uni.showToast({ title: '已前往!', icon: 'success' })
|
|
goBack()
|
|
}
|
|
|
|
function declineResult() {
|
|
// 换一个
|
|
isRevealed.value = false
|
|
selectedResult.value = null
|
|
startBlind()
|
|
}
|
|
|
|
function goToReview() {
|
|
uni.navigateTo({
|
|
url: `/pages/blind/review?sessionId=${selectedResult.value?.session_id || 1}&packageId=${packageId.value || 1}`
|
|
})
|
|
}
|
|
|
|
function goBack() {
|
|
uni.navigateBack()
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.page-blind {
|
|
min-height: 100vh;
|
|
background: #f5f5f5;
|
|
padding-bottom: 40rpx;
|
|
}
|
|
|
|
.blind-header {
|
|
background: linear-gradient(135deg, #FF6B35, #FF8C42);
|
|
padding: 40rpx 30rpx;
|
|
color: #fff;
|
|
.back-btn {
|
|
font-size: 26rpx;
|
|
opacity: 0.8;
|
|
margin-bottom: 10rpx;
|
|
}
|
|
.blind-title {
|
|
font-size: 36rpx;
|
|
font-weight: 700;
|
|
display: block;
|
|
}
|
|
}
|
|
|
|
.card {
|
|
background: #fff;
|
|
border-radius: 20rpx;
|
|
padding: 24rpx;
|
|
margin: 20rpx 24rpx;
|
|
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.06);
|
|
}
|
|
|
|
.section-label {
|
|
font-size: 28rpx;
|
|
font-weight: 600;
|
|
display: block;
|
|
margin-bottom: 16rpx;
|
|
}
|
|
|
|
.price-options {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 12rpx;
|
|
}
|
|
|
|
.price-option {
|
|
padding: 12rpx 24rpx;
|
|
border: 2rpx solid #eee;
|
|
border-radius: 30rpx;
|
|
font-size: 24rpx;
|
|
color: #666;
|
|
&.active {
|
|
border-color: #FF6B35;
|
|
color: #FF6B35;
|
|
background: #FFF3E0;
|
|
}
|
|
}
|
|
|
|
.blind-card-container {
|
|
display: flex;
|
|
justify-content: center;
|
|
padding: 40rpx 0;
|
|
}
|
|
|
|
.blind-card {
|
|
width: 560rpx;
|
|
height: 400rpx;
|
|
border-radius: 30rpx;
|
|
background: linear-gradient(135deg, #667eea, #764ba2);
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: #fff;
|
|
box-shadow: 0 20rpx 60rpx rgba(102,126,234,0.3);
|
|
transition: transform 0.6s, opacity 0.6s;
|
|
|
|
&.spinning {
|
|
animation: spin 1s ease-in-out;
|
|
}
|
|
|
|
&.revealed {
|
|
transform: scale(1);
|
|
}
|
|
}
|
|
|
|
@keyframes spin {
|
|
0% { transform: rotateY(0deg) scale(1); }
|
|
50% { transform: rotateY(90deg) scale(0.9); }
|
|
100% { transform: rotateY(360deg) scale(1); }
|
|
}
|
|
|
|
.card-front {
|
|
text-align: center;
|
|
.card-logo {
|
|
font-size: 100rpx;
|
|
display: block;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
.card-hint {
|
|
font-size: 28rpx;
|
|
opacity: 0.8;
|
|
}
|
|
}
|
|
|
|
.card-reveal {
|
|
text-align: center;
|
|
padding: 30rpx;
|
|
.reveal-icon {
|
|
font-size: 48rpx;
|
|
display: block;
|
|
margin-bottom: 10rpx;
|
|
}
|
|
.reveal-name {
|
|
font-size: 36rpx;
|
|
font-weight: 700;
|
|
display: block;
|
|
margin-bottom: 12rpx;
|
|
}
|
|
.reveal-desc {
|
|
font-size: 24rpx;
|
|
opacity: 0.9;
|
|
display: block;
|
|
margin-bottom: 16rpx;
|
|
line-height: 1.5;
|
|
}
|
|
.reveal-price {
|
|
font-size: 32rpx;
|
|
font-weight: 600;
|
|
background: rgba(255,255,255,0.2);
|
|
padding: 8rpx 24rpx;
|
|
border-radius: 20rpx;
|
|
display: inline-block;
|
|
margin-bottom: 16rpx;
|
|
}
|
|
.reveal-match {
|
|
font-size: 22rpx;
|
|
opacity: 0.8;
|
|
display: block;
|
|
margin-bottom: 12rpx;
|
|
}
|
|
}
|
|
|
|
.reveal-tags {
|
|
display: flex;
|
|
justify-content: center;
|
|
flex-wrap: wrap;
|
|
gap: 8rpx;
|
|
margin-bottom: 12rpx;
|
|
}
|
|
|
|
.reveal-tag {
|
|
background: rgba(255,255,255,0.2);
|
|
padding: 4rpx 14rpx;
|
|
border-radius: 14rpx;
|
|
font-size: 20rpx;
|
|
}
|
|
|
|
.reveal-coupon {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8rpx;
|
|
background: rgba(255,215,0,0.3);
|
|
padding: 10rpx 20rpx;
|
|
border-radius: 12rpx;
|
|
margin-top: 12rpx;
|
|
.coupon-icon { font-size: 24rpx; }
|
|
.coupon-text { font-size: 22rpx; color: #FFD700; }
|
|
}
|
|
|
|
.action-area {
|
|
text-align: center;
|
|
padding: 20rpx;
|
|
}
|
|
|
|
.blind-btn {
|
|
background: linear-gradient(135deg, #FF6B35, #FF8C42);
|
|
color: #fff;
|
|
border: none;
|
|
border-radius: 50rpx;
|
|
padding: 24rpx 60rpx;
|
|
font-size: 36rpx;
|
|
font-weight: 700;
|
|
box-shadow: 0 8rpx 30rpx rgba(255,107,53,0.4);
|
|
}
|
|
|
|
.blind-btn:disabled {
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.result-actions {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16rpx;
|
|
padding: 0 40rpx;
|
|
}
|
|
|
|
.accept-btn {
|
|
background: linear-gradient(135deg, #4CAF50, #66BB6A);
|
|
color: #fff;
|
|
border: none;
|
|
border-radius: 30rpx;
|
|
font-size: 30rpx;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.skip-btn {
|
|
background: #fff;
|
|
color: #666;
|
|
border: 2rpx solid #ddd;
|
|
border-radius: 30rpx;
|
|
font-size: 28rpx;
|
|
}
|
|
|
|
.star-btn {
|
|
background: transparent;
|
|
color: #FFD700;
|
|
border: 2rpx solid #FFD700;
|
|
border-radius: 30rpx;
|
|
font-size: 28rpx;
|
|
}
|
|
</style>
|