06488f0237
功能: - Go后端 (Gin + GORM + PostgreSQL) - UniApp用户端 (iOS/Android/小程序) - DaisyUI5后台管理 - JWT认证 + 微信登录 - 盲选加权算法 - 会员系统 + 优惠券 - 打分评价 + 偏好学习
323 lines
7.3 KiB
Vue
323 lines
7.3 KiB
Vue
<template>
|
||
<view class="page-review">
|
||
<view class="review-header">
|
||
<text class="review-title">⭐ 打分评价</text>
|
||
<text class="review-subtitle">你对这次盲选体验满意吗?</text>
|
||
</view>
|
||
|
||
<!-- 商家名 -->
|
||
<view class="merchant-info card">
|
||
<text class="merchant-name">{{ packageName }}</text>
|
||
<text class="merchant-shop">{{ shopName }}</text>
|
||
</view>
|
||
|
||
<!-- 总体评分 -->
|
||
<view class="overall-rating card">
|
||
<text class="section-label">你打几分?</text>
|
||
<view class="overall-stars">
|
||
<text
|
||
v-for="i in 5" :key="i"
|
||
class="star"
|
||
:class="{ active: overallRating >= i }"
|
||
@click="overallRating = i"
|
||
>★</text>
|
||
</view>
|
||
<text class="rating-label" v-if="overallRating">{{ ratingLabels[overallRating - 1] }}</text>
|
||
</view>
|
||
|
||
<!-- 维度评分 -->
|
||
<view class="dimensions card">
|
||
<text class="section-label">维度评分</text>
|
||
|
||
<view class="dimension-row" v-for="dim in dimensions" :key="dim.key">
|
||
<text class="dim-icon">{{ dim.icon }}</text>
|
||
<text class="dim-label">{{ dim.label }}</text>
|
||
<view class="dim-stars">
|
||
<text
|
||
v-for="i in 5" :key="i"
|
||
class="star small"
|
||
:class="{ active: dimensionScores[dim.key] >= i }"
|
||
@click="setDimScore(dim.key, i)"
|
||
>★</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 标签选择 -->
|
||
<view class="tags-section card">
|
||
<text class="section-label">怎么形容这次体验?</text>
|
||
<view class="tags">
|
||
<view
|
||
class="tag-btn"
|
||
:class="{ active: selectedTags.includes(tag) }"
|
||
v-for="tag in allTags"
|
||
:key="tag"
|
||
@click="toggleTag(tag)"
|
||
>
|
||
<text>{{ tag }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 备注 -->
|
||
<view class="note-section card">
|
||
<text class="section-label">备注(选填)</text>
|
||
<textarea
|
||
class="note-textarea"
|
||
placeholder="写点什么..."
|
||
v-model="noteText"
|
||
maxlength="200"
|
||
></textarea>
|
||
</view>
|
||
|
||
<!-- 再次前往 -->
|
||
<view class="repeat-section card">
|
||
<view class="repeat-option" @click="isRepeat = !isRepeat">
|
||
<text class="repeat-icon">{{ isRepeat ? '🔁' : '○' }}</text>
|
||
<text class="repeat-text">我还会再来!</text>
|
||
<text class="repeat-hint">这个选项对你的推荐权重影响很大</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 提交 -->
|
||
<view class="submit-area">
|
||
<button class="submit-btn" @click="submitReview" :disabled="overallRating === 0">
|
||
提交评价
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref } from 'vue'
|
||
import { useRoute } from '@dcloudio/uni-app'
|
||
import { reviewApi } from '@/api/index.js'
|
||
|
||
const route = useRoute()
|
||
const sessionId = ref(parseInt(route.query.sessionId) || 1)
|
||
const packageId = ref(parseInt(route.query.packageId) || 1)
|
||
const packageName = ref('神秘套餐')
|
||
const shopName = ref('某某商家')
|
||
|
||
const overallRating = ref(0)
|
||
const dimensionScores = ref({ taste: 0, value: 0, distance: 0, match: 0 })
|
||
const selectedTags = ref([])
|
||
const noteText = ref('')
|
||
const isRepeat = ref(false)
|
||
|
||
const ratingLabels = ['太失望了', '不太好', '还行', '还不错', '非常满意!']
|
||
|
||
const dimensions = [
|
||
{ key: 'taste', label: '口味', icon: '🍽️' },
|
||
{ key: 'value', label: '性价比', icon: '💰' },
|
||
{ key: 'distance', label: '距离', icon: '📍' },
|
||
{ key: 'match', label: '符合预期', icon: '🎯' },
|
||
]
|
||
|
||
const allTags = [
|
||
'口味赞', '环境好', '性价比高', '值得再来', '服务棒',
|
||
'踩雷', '太贵', '难吃', '远', '分量少', '排队久', '适合约会',
|
||
]
|
||
|
||
function setDimScore(key, score) {
|
||
dimensionScores.value[key] = score
|
||
}
|
||
|
||
function toggleTag(tag) {
|
||
const idx = selectedTags.value.indexOf(tag)
|
||
if (idx >= 0) {
|
||
selectedTags.value.splice(idx, 1)
|
||
} else {
|
||
selectedTags.value.push(tag)
|
||
}
|
||
}
|
||
|
||
async function submitReview() {
|
||
if (overallRating.value === 0) {
|
||
uni.showToast({ title: '请先打分', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
try {
|
||
await reviewApi.submit({
|
||
session_id: sessionId.value,
|
||
package_id: packageId.value,
|
||
rating: overallRating.value,
|
||
taste: dimensionScores.value.taste,
|
||
value: dimensionScores.value.value,
|
||
distance: dimensionScores.value.distance,
|
||
match: dimensionScores.value.match,
|
||
tags: selectedTags.value,
|
||
text: noteText.value,
|
||
is_repeat: isRepeat.value,
|
||
})
|
||
uni.showToast({ title: '评价已提交!这会影响下次推荐哦~', icon: 'success' })
|
||
setTimeout(() => uni.navigateBack(), 1500)
|
||
} catch (e) {
|
||
uni.showToast({ title: '提交失败', icon: 'none' })
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.page-review {
|
||
min-height: 100vh;
|
||
background: #f5f5f5;
|
||
padding-bottom: 100rpx;
|
||
}
|
||
|
||
.review-header {
|
||
background: linear-gradient(135deg, #FFD700, #FFA500);
|
||
padding: 60rpx 30rpx 40rpx;
|
||
color: #fff;
|
||
text-align: center;
|
||
.review-title {
|
||
font-size: 36rpx;
|
||
font-weight: 700;
|
||
display: block;
|
||
}
|
||
.review-subtitle {
|
||
font-size: 24rpx;
|
||
opacity: 0.9;
|
||
display: block;
|
||
margin-top: 8rpx;
|
||
}
|
||
}
|
||
|
||
.card {
|
||
background: #fff;
|
||
border-radius: 20rpx;
|
||
padding: 24rpx;
|
||
margin: 20rpx 24rpx;
|
||
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.06);
|
||
}
|
||
|
||
.merchant-info {
|
||
text-align: center;
|
||
.merchant-name {
|
||
font-size: 32rpx;
|
||
font-weight: 700;
|
||
display: block;
|
||
}
|
||
.merchant-shop {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
display: block;
|
||
margin-top: 4rpx;
|
||
}
|
||
}
|
||
|
||
.section-label {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
display: block;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.overall-stars {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.star {
|
||
font-size: 64rpx;
|
||
color: #ddd;
|
||
transition: color 0.2s;
|
||
&.active { color: #FFD700; }
|
||
&.small {
|
||
font-size: 36rpx;
|
||
}
|
||
}
|
||
|
||
.rating-label {
|
||
display: block;
|
||
text-align: center;
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
margin-top: 12rpx;
|
||
}
|
||
|
||
.dimension-row {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 16rpx 0;
|
||
border-bottom: 1rpx solid #f0f0f0;
|
||
&:last-child { border: none; }
|
||
.dim-icon { font-size: 32rpx; width: 40rpx; }
|
||
.dim-label {
|
||
flex: 1;
|
||
font-size: 26rpx;
|
||
margin-left: 12rpx;
|
||
}
|
||
.dim-stars {
|
||
display: flex;
|
||
gap: 4rpx;
|
||
}
|
||
}
|
||
|
||
.tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.tag-btn {
|
||
padding: 10rpx 24rpx;
|
||
border: 2rpx solid #eee;
|
||
border-radius: 30rpx;
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
&.active {
|
||
border-color: #FF6B35;
|
||
background: #FFF3E0;
|
||
color: #FF6B35;
|
||
}
|
||
}
|
||
|
||
.note-textarea {
|
||
width: 100%;
|
||
min-height: 160rpx;
|
||
font-size: 26rpx;
|
||
padding: 16rpx;
|
||
background: #f8f8f8;
|
||
border-radius: 12rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.repeat-option {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
padding: 10rpx 0;
|
||
.repeat-icon { font-size: 36rpx; }
|
||
.repeat-text {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: #FF6B35;
|
||
}
|
||
.repeat-hint {
|
||
font-size: 20rpx;
|
||
color: #999;
|
||
display: block;
|
||
}
|
||
}
|
||
|
||
.submit-area {
|
||
padding: 0 40rpx;
|
||
}
|
||
|
||
.submit-btn {
|
||
background: linear-gradient(135deg, #FF6B35, #FF8C42);
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 40rpx;
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
box-shadow: 0 8rpx 30rpx rgba(255,107,53,0.3);
|
||
&:disabled {
|
||
opacity: 0.5;
|
||
}
|
||
}
|
||
</style>
|