zdtap-uniapp-main/pages/index/writeReview.vue

861 lines
18 KiB
Vue
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.

<template>
<view class="page">
<!-- 酒款信息 -->
<view class="beer-info">
<view class="info-card">
<image :src="beerInfo.cover" mode="aspectFill" class="cover"></image>
<view class="info">
<view class="name-row">
<text class="name">{{ beerInfo.beerName || '--' }}</text>
<view class="rating" v-if="beerInfo.avgOverallRating">
<uni-rate :readonly="true" :value="beerInfo.avgOverallRating" size="12" color="#FEE034"/>
<text class="rating-text">{{ beerInfo.avgOverallRating }}/5.0</text>
</view>
</view>
<view class="sub-info">
<view class="brewery-tag">
<image :src="beerInfo.brandLogo" class="brewery-logo" v-if="beerInfo.brandLogo"></image>
<text>{{ beerInfo.brandName || '--' }}</text>
</view>
<!-- <text class="divider">|</text> -->
<text class="style">{{ beerInfo.beerStyles || '--' }}</text>
</view>
</view>
</view>
</view>
<!-- 步骤进度 -->
<view class="steps">
<view class="step-item" :class="{'active': currentStep >= 1, 'done': currentStep > 1}">
<view class="step-number">1</view>
<view class="step-content">
<view class="step-title">酒款评分</view>
<view class="step-desc">为这款啤酒打分</view>
</view>
</view>
<view class="step-line" :class="{'active': currentStep >= 2}"></view>
<view class="step-item" :class="{'active': currentStep >= 2}">
<view class="step-number">2</view>
<view class="step-content">
<view class="step-title">评价内容</view>
<view class="step-desc">分享你的品鉴体验</view>
</view>
</view>
</view>
<!-- 步骤内容区 -->
<view class="step-panels">
<!-- 步骤1评分面板 -->
<view class="panel" v-show="currentStep === 1">
<view class="section">
<view class="section-title">
<text class="title">评分方式</text>
<view class="tab-group">
<view class="tab"
:class="{'active': currentTab == 1}"
@click="changeTab(1)">
快速评分
</view>
<view class="tab"
:class="{'active': currentTab == 2}"
@click="changeTab(2)">
细节评分
</view>
</view>
</view>
<!-- 快速评分模式 -->
<view v-if="currentTab == 1" class="quick-rating">
<view class="score-display">
<view class="score">{{compositeScore.toFixed(1)}}</view>
<view class="stars-wrap">
<uni-rate
:value="compositeScore"
:readonly="true"
:size="36"
:touchable="false"
color="#ECECEC"
active-color="#FEE034"
/>
</view>
</view>
<view class="slider-box">
<slider
:min="0"
:max="5"
:step="0.5"
:value="compositeScore"
@change="handleQuickRating"
activeColor="#FEE034"
backgroundColor="#ECECEC"
block-color="#FEE034"
block-size="28"
/>
<text class="hint">左右滑动为啤酒打分0-5分</text>
</view>
</view>
<!-- 细节评分模式 -->
<view v-if="currentTab == 2" class="detailed-rating">
<view class="rating-item" v-for="(item, index) in ratingItems" :key="index">
<view class="rating-header">
<view class="label-group">
<text class="label">{{item.label}}</text>
<text class="weight">{{item.weight}}%</text>
</view>
<text class="value">{{form[item.key].toFixed(1)}}</text>
</view>
<view class="slider-box">
<slider :min="0"
:max="5"
:step="0.1"
:value="form[item.key]"
@change="e => handleDetailRating(e, item)"
activeColor="#FEE034"
backgroundColor="#F5F5F5"
block-color="#FEE034"
block-size="24" />
</view>
</view>
<view class="composite-score">
<view class="score-title">
<text>综合评分</text>
<text class="hint">根据权重自动计算</text>
</view>
<view class="score-display">
<view class="score">{{weightedScore.toFixed(1)}}</view>
<view class="stars">
<uni-rate
:value="weightedScore"
:readonly="true"
:size="36"
:touchable="false"
color="#ECECEC"
active-color="#FEE034"
/>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 步骤2评价内容面板 -->
<view class="panel" v-show="currentStep === 2">
<view class="section">
<view class="section-title">
<text class="title">评价内容</text>
<text class="subtitle">分享你对这款啤酒的感受</text>
</view>
<view class="review-content">
<textarea v-model="form.reviewContent"
maxlength="100"
placeholder="这款啤酒怎么样?写下你的品鉴体验..."
class="textarea-box">
</textarea>
<view class="word-count">{{form.reviewContent.length}}/100</view>
</view>
</view>
<view class="section">
<view class="section-title">
<text class="title">上传图片</text>
<text class="subtitle">上传图片更容易获得点赞</text>
</view>
<view class="upload-content">
<view class="img-box" @click="handleUpload">
<image v-if="form.reviewImg"
:src="form.reviewImg"
mode="aspectFill"
class="preview-image">
</image>
<view v-else class="upload-placeholder">
<text class="cuIcon-camerafill"></text>
<text class="upload-text">添加图片</text>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 底部按钮区 -->
<view class="bottom-actions">
<template v-if="currentStep === 1">
<button class="btn next"
@click="nextStep"
:disabled="!isRatingValid">
下一步
</button>
</template>
<template v-else>
<button class="btn prev" @click="prevStep">
<text class="cuIcon-back"></text>
<text>上一步</text>
</button>
<button class="btn submit"
@click="submitForm"
:disabled="!isFormValid"
:class="{'disabled': !isFormValid}">
发布评价
</button>
</template>
</view>
</view>
</template>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background: #F7F7F7;
padding-bottom: 180rpx;
}
.beer-info {
margin-top: 24rpx;
padding: 0 32rpx;
margin-bottom: 24rpx;
.info-card {
background: #FFFFFF;
border-radius: 20rpx;
padding: 20rpx;
display: flex;
align-items: flex-start;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
.cover {
width: 208rpx;
height: 300rpx;
border-radius: 12rpx;
margin-right: 20rpx;
background: #F7F7F7;
object-fit: cover;
}
.info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
height: 300rpx;
padding: 8rpx 0;
.name-row {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 12rpx;
.name {
font-size: 32rpx;
font-weight: 600;
color: #333;
flex: 1;
margin-right: 16rpx;
line-height: 1.4;
}
.rating {
display: flex;
align-items: center;
gap: 6rpx;
.rating-text {
font-size: 22rpx;
color: #666;
}
}
}
.sub-info {
display: flex;
flex-direction: column;
gap: 12rpx;
.brewery-tag {
display: flex;
align-items: center;
background: #F8F8F8;
padding: 10rpx 16rpx;
border-radius: 10rpx;
.brewery-logo {
width: 36rpx;
height: 36rpx;
border-radius: 50%;
margin-right: 10rpx;
}
text {
font-size: 26rpx;
color: #333;
font-weight: 500;
}
}
.style {
font-size: 26rpx;
color: #666;
background: #F7F7F7;
padding: 10rpx 16rpx;
border-radius: 10rpx;
}
}
}
}
}
.steps {
margin: 24rpx 32rpx;
padding: 32rpx;
background: #FFFFFF;
border-radius: 12rpx;
display: flex;
align-items: flex-start;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
.step-item {
flex: 1;
display: flex;
align-items: flex-start;
opacity: 0.5;
transition: all 0.3s;
&.active {
opacity: 1;
}
&.done .step-number {
background: #19367A;
}
.step-number {
width: 48rpx;
height: 48rpx;
border-radius: 24rpx;
background: #19367A;
color: #FFFFFF;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
margin-right: 16rpx;
}
.step-content {
flex: 1;
.step-title {
font-size: 28rpx;
color: #333;
font-weight: 500;
margin-bottom: 4rpx;
}
.step-desc {
font-size: 24rpx;
color: #999;
}
}
}
.step-line {
width: 60rpx;
height: 2rpx;
background: #DEDEDE;
margin: 24rpx 16rpx 0;
transition: all 0.3s;
&.active {
background: #19367A;
}
}
}
.section {
background: #FFFFFF;
border-radius: 20rpx;
margin: 0 32rpx 24rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
.section-title {
padding: 32rpx;
border-bottom: 2rpx solid #F7F7F7;
.title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
}
.subtitle {
font-size: 24rpx;
color: #999;
}
.tab-group {
display: flex;
background: #F7F7F7;
border-radius: 12rpx;
padding: 4rpx;
margin-top: 24rpx;
.tab {
flex: 1;
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
color: #666;
border-radius: 8rpx;
transition: all 0.3s;
&.active {
background: #FFFFFF;
color: #19367A;
font-weight: 500;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
}
}
}
}
}
.quick-rating {
padding: 32rpx;
.score-display {
text-align: center;
margin-bottom: 48rpx;
.score {
font-size: 72rpx;
font-weight: 600;
color: #333;
margin-bottom: 24rpx;
}
.stars-wrap {
display: flex;
justify-content: center;
margin-bottom: 32rpx;
}
}
.slider-box {
padding: 0 48rpx;
.hint {
font-size: 24rpx;
color: #999;
text-align: center;
margin-top: 16rpx;
display: block;
}
}
}
.detailed-rating {
padding: 32rpx;
.rating-item {
margin-bottom: 32rpx;
.rating-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
.label-group {
display: flex;
align-items: center;
gap: 8rpx;
.label {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
.weight {
font-size: 24rpx;
color: #999;
}
}
.value {
font-size: 32rpx;
color: #19367A;
font-weight: 600;
}
}
.slider-box {
padding: 0 24rpx;
}
}
.composite-score {
margin-top: 48rpx;
padding: 24rpx;
background: #F8F8F8;
border-radius: 12rpx;
.score-title {
font-size: 28rpx;
color: #666;
margin-bottom: 16rpx;
.hint {
font-size: 24rpx;
color: #999;
margin-left: 8rpx;
}
}
.score-display {
display: flex;
align-items: center;
gap: 24rpx;
.score {
font-size: 48rpx;
font-weight: 600;
color: #333;
}
.stars {
flex: 1;
display: flex;
align-items: center;
}
}
}
}
.review-content {
padding: 32rpx;
position: relative;
.textarea-box {
width: 100%;
height: 240rpx;
font-size: 28rpx;
color: #333;
line-height: 1.6;
padding: 24rpx;
background: #F7F7F7;
border-radius: 12rpx;
box-sizing: border-box;
&::placeholder {
color: #999;
}
}
.word-count {
position: absolute;
right: 48rpx;
bottom: 48rpx;
font-size: 24rpx;
color: #999;
}
}
.upload-content {
padding: 32rpx;
.img-box {
width: 200rpx;
height: 200rpx;
background: #F7F7F7;
border-radius: 12rpx;
overflow: hidden;
transition: all 0.3s ease;
&:active {
transform: scale(0.98);
}
.preview-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.upload-placeholder {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #19367A;
.cuIcon-camerafill {
font-size: 48rpx;
margin-bottom: 12rpx;
}
.upload-text {
font-size: 24rpx;
}
}
}
}
.bottom-actions {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 24rpx 32rpx env(safe-area-inset-bottom);
background: #FFFFFF;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.1);
display: flex;
gap: 24rpx;
.btn {
flex: 1;
height: 96rpx;
border-radius: 48rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
font-weight: 500;
transition: all 0.3s ease;
&:active {
transform: scale(0.98);
opacity: 0.9;
}
&.prev {
background: #F7F7F7;
color: #666;
.cuIcon-back {
margin-right: 8rpx;
}
}
&.next,
&.submit {
background: linear-gradient(135deg, #19367A, #1E4B9E);
color: #FFFFFF;
box-shadow: 0 4rpx 16rpx rgba(25, 54, 122, 0.2);
&:disabled,
&.disabled {
background: #CCCCCC;
opacity: 0.7;
pointer-events: none;
box-shadow: none;
}
}
}
}
</style>
<script>
import {
addReview,
getBeerInfo
} from '@/api/bar.js'
import {
base_url
} from '@/api/config.js'
export default {
data() {
return {
currentStep: 1,
currentTab: 1,
beerInfo: {
cover: '',
beerName: '',
brandName: '',
beerStyles: '',
brandLogo: '',
avgOverallRating: 0
},
form: {
beerId: '',
reviewContent: '',
reviewImg: '',
colorRating: 0,
aromaRating: 0,
tasteRating: 0,
},
compositeScore: 0,
ratingItems: [
{ label: '外观', key: 'colorRating', weight: 10 },
{ label: '香味', key: 'aromaRating', weight: 30 },
{ label: '口感', key: 'tasteRating', weight: 60 }
],
};
},
computed: {
weightedScore() {
const { colorRating, aromaRating, tasteRating } = this.form;
return Number(((colorRating * 0.1 + aromaRating * 0.3 + tasteRating * 0.6)).toFixed(1));
},
isRatingValid() {
if (this.currentTab === 1) {
return this.compositeScore > 0;
} else {
return this.weightedScore > 0;
}
},
isFormValid() {
// 检查文字评价和图片是否都已填写/上传
const hasContent = this.form.reviewContent.trim().length > 0;
const hasImage = !!this.form.reviewImg;
console.log('表单验证状态:', { hasContent, hasImage, isValid: hasContent && hasImage });
return hasContent && hasImage;
}
},
onLoad(options) {
if(options.beerId) {
this.form.beerId = options.beerId;
this.getBeerInfo();
} else {
uni.showToast({
title: '缺少酒款信息',
icon: 'none'
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
},
methods: {
async getBeerInfo() {
try {
const res = await getBeerInfo(this.form.beerId);
if(res.code === 200 && res.data) {
this.beerInfo = {
cover: res.data.cover || '',
beerName: res.data.beerName || '--',
brandName: res.data.brandName || '--',
beerStyles: res.data.beerStyles || '--',
brandLogo: res.data.brandLogo || '',
avgOverallRating: res.data.avgOverallRating || 0
};
} else {
uni.showToast({
title: res.msg || '获取酒款信息失败',
icon: 'none'
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
} catch (error) {
console.error('获取酒款信息失败:', error);
uni.showToast({
title: '获取酒款信息失败',
icon: 'none'
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
},
handleQuickRating(e) {
const score = Number(e.detail.value);
this.compositeScore = score;
// 同步更新所有细节评分
this.form.colorRating = score;
this.form.aromaRating = score;
this.form.tasteRating = score;
},
handleDetailRating(e, item) {
this.form[item.key] = Number(e.detail.value);
},
changeTab(tab) {
this.currentTab = tab;
if (tab === 1) {
// 切换到快速评分时,使用加权平均分
this.compositeScore = this.weightedScore;
} else {
// 切换到细节评分时,如果有快速评分,则同步到各项
if (this.compositeScore > 0) {
this.form.colorRating = this.compositeScore;
this.form.aromaRating = this.compositeScore;
this.form.tasteRating = this.compositeScore;
}
}
},
nextStep() {
if (!this.isRatingValid) {
uni.showToast({
title: '请完成评分',
icon: 'none'
});
return;
}
this.currentStep = 2;
},
prevStep() {
this.currentStep = 1;
},
handleUpload() {
uni.chooseImage({
count: 1,
success: (res) => {
this.form.reviewImg = res.tempFilePaths[0];
console.log('图片上传成功:', this.form.reviewImg);
}
});
},
async submitForm() {
if (!this.isFormValid) {
if (!this.form.reviewImg) {
uni.showToast({
title: '请上传图片',
icon: 'none'
});
} else if (this.form.reviewContent.trim().length === 0) {
uni.showToast({
title: '请填写评价内容',
icon: 'none'
});
}
return;
}
try {
const data = {
...this.form,
overallRating: this.currentTab === 1 ? this.compositeScore : this.weightedScore
};
const res = await addReview(data);
if (res.code === 200) {
uni.showToast({
title: '发布成功',
icon: 'success'
});
setTimeout(() => {
// 跳转回酒款详情页
uni.redirectTo({
url: `/pages/index/review?beerId=${this.form.beerId}`
});
}, 1500);
}
} catch (error) {
uni.showToast({
title: '发布失败',
icon: 'none'
});
}
}
}
}
</script>