1193 lines
28 KiB
Vue
Raw Normal View History

2025-03-29 16:01:43 +08:00
<template>
<view class="page">
<!-- 商品信息卡片 -->
<view class="product-card">
<!-- 商品头部 -->
<view class="product-header">
<image class="product-image" :src="beerInfo.cover" mode="aspectFill"></image>
<view class="product-info">
<view class="title-row">
<text class="product-title">{{beerInfo.beerName || '--'}}</text>
<image v-if="!isFavor" class="like-btn" src="/static/heart-add.png" mode="aspectFit" @click="favorBeerFun(1)"></image>
<image v-else class="like-btn" src="/static/heart-tick.png" mode="aspectFit" @click="favorBeerFun(2)"></image>
2025-03-29 16:01:43 +08:00
</view>
<text class="product-subtitle">{{beerInfo.beerStyles || '--'}}</text>
<view class="rating">
<text class="rating-score">{{reviewScoreList.avgOverallRating || 0}}/5.0</text>
<uni-rate :value="reviewScoreList.avgOverallRating || 0" :readonly="true" :touchable="false" size="18" />
2025-03-29 16:01:43 +08:00
</view>
<view v-if="beerInfo.breweryId" class="brand" @click="toBrand">
<image class="brand-logo" :src="beerInfo.brandLogo" mode="aspectFill"></image>
<text class="brand-name">{{beerInfo.brandName || ''}}</text>
<image class="arrow-icon" src="/static/arrow-right.png" mode="aspectFit"></image>
2025-03-29 16:01:43 +08:00
</view>
</view>
</view>
<!-- 商品参数 -->
<view class="product-params">
<view class="param-item">
<text class="param-label">ABV</text>
<text class="param-value">{{beerInfo.beerAbv || '--'}}</text>
2025-03-29 16:01:43 +08:00
</view>
<view class="param-item">
<text class="param-label">IBU</text>
<text class="param-value">{{beerInfo.beerIbus || '--'}}</text>
2025-03-29 16:01:43 +08:00
</view>
<view class="param-item">
<text class="param-label">原麦汁</text>
<text class="param-value">{{beerInfo.onSale == 1 ? '在售款' : '停售'}}</text>
2025-03-29 16:01:43 +08:00
</view>
</view>
<!-- 商品描述 -->
<view class="product-description">
<rich-text :nodes="beerInfo.desc"></rich-text>
</view>
</view>
<!-- 评分和评价 -->
<view class="rating-section">
<view class="section-title">评分和评价</view>
<!-- 评分总览 -->
<view class="rating-overview">
<text class="rating-score-large">{{currentTab === 2 ? (reviewScoreList.avgOverallRating || '-') : (myReviewInfo ? myReviewInfo.overallRating : '-')}}</text>
<view class="rating-right">
<uni-rate
:value="currentTab === 2 ? (reviewScoreList.avgOverallRating || 0) : (myReviewInfo ? myReviewInfo.overallRating : 0)"
:readonly="true"
:size="24"
:touchable="false"
color="#ECECEC"
active-color="#FEE034"
/>
<view class="rating-count" :class="{ highlight: currentTab === 1 && !myReviewInfo }">
<template v-if="currentTab === 2">
({{reviewTotal}})
</template>
<template v-else>
<template v-if="myReviewInfo">
{{myReviewInfo.createTime ? '评分于 ' + myReviewInfo.createTime.slice(0,10) : '我的评分'}}
</template>
<template v-else>
首次评分还可以领取品牌啤酒币哦
</template>
</template>
2025-03-29 16:01:43 +08:00
</view>
</view>
</view>
<!-- 添加评分切换按钮 -->
<view class="rating-tabs">
<view class="tab-item" :class="{ active: currentTab === 2 }" @click="currentTab = 2">
<image class="tab-icon" src="/static/users.png" mode="aspectFit"></image>
2025-03-29 16:01:43 +08:00
</view>
<view class="tab-item" :class="{ active: currentTab === 1 }" @click="currentTab = 1">
<image class="tab-icon" src="/static/user.png" mode="aspectFit"></image>
2025-03-29 16:01:43 +08:00
</view>
</view>
2025-03-29 16:01:43 +08:00
<!-- 评分条区域 -->
<view class="rating-bars">
<view class="rating-bar" v-for="(item, index) in [
{label: '外观', key: 'avgColorRating', weight: '10%',
value: currentTab === 2 ? reviewScoreList.avgColorRating : (myReviewInfo ? myReviewInfo.colorRating : 0)},
{label: '香气', key: 'avgAromaRating', weight: '30%',
value: currentTab === 2 ? reviewScoreList.avgAromaRating : (myReviewInfo ? myReviewInfo.aromaRating : 0)},
{label: '口感', key: 'avgTasteRating', weight: '60%',
value: currentTab === 2 ? reviewScoreList.avgTasteRating : (myReviewInfo ? myReviewInfo.tasteRating : 0)}
]" :key="index">
<view class="bar-header">
<text class="bar-label">{{item.label}} ({{item.weight}})</text>
<text class="bar-value">{{item.value ? item.value.toFixed(1) : '0.0'}}</text>
</view>
<view class="bar">
<view class="bar-filled" :style="{width: item.value ? (item.value/5*100 + '%') : '0%'}"></view>
</view>
2025-03-29 16:01:43 +08:00
</view>
</view>
<!-- 评价排序按钮 -->
<view class="review-sort">
<view class="sort-item" :class="{ active: sortType === 'latest' }" @click="sortType = 'latest'">
<text>最新</text>
2025-03-29 16:01:43 +08:00
</view>
<view class="sort-item" :class="{ active: sortType === 'hot' }" @click="sortType = 'hot'">
<text>最热</text>
2025-03-29 16:01:43 +08:00
</view>
</view>
<!-- 评论列表 -->
<view class="reviews" v-if="reviewList.length > 0">
<view class="review-item" v-for="(item, index) in reviewList.slice(0, 3)" :key="index">
<!-- 用户信息头部 -->
<view class="review-header">
<view class="user-info">
<image class="avatar" :src="item.userAvatar || '/static/default-avatar.png'" mode="aspectFill"></image>
<view class="user-meta">
<text class="user-name">{{item.userName || '匿名用户'}}</text>
<text class="review-time">{{item.createTime.slice(0,10)}}</text>
</view>
2025-03-29 16:01:43 +08:00
</view>
</view>
<!-- 评论内容区域 -->
<view class="review-content">
<view class="content-left">
<!-- 评论文本 -->
<view class="review-text">
{{item.reviewContent}}
</view>
<!-- 评论图片区域 -->
<scroll-view v-if="item.reviewImg" class="image-scroll" scroll-x="true">
<view class="image-container">
<image
v-for="(img, imgIndex) in item.reviewImg.split(',')"
:key="imgIndex"
:src="img"
class="review-image"
mode="aspectFill"
@tap="previewImage(item.reviewImg.split(','), imgIndex)"
></image>
</view>
</scroll-view>
<!-- 评分和点赞 -->
<view class="review-footer">
<uni-rate
:value="item.compositeScore"
:readonly="true"
:size="16"
:touchable="false"
color="#ECECEC"
active-color="#FEE034"
/>
<text class="score-text">{{item.compositeScore}}</text>
<view class="like-btn" :class="{ active: item.reviewLike }" @tap.stop="handleLike(item)">
<image class="like-icon" src="/static/like.png" mode="aspectFit"></image>
<text class="like-count">{{item.likeCount || 0}}</text>
</view>
</view>
2025-03-29 16:01:43 +08:00
</view>
</view>
</view>
</view>
<view v-else class="no-review">巧了还没有酒评呢快拿下首评</view>
2025-03-29 16:01:43 +08:00
<!-- 查看全部评价按钮 -->
<view v-if="reviewList.length > 0"
class="view-all-reviews"
:class="{ 'disabled': reviewList.length === 0 }"
@tap="toAllReviews">
查看全部评价
2025-03-29 16:01:43 +08:00
</view>
</view>
<!-- 底部操作栏 -->
<view class="bottom-bar">
<view class="action-box">
<view class="action-item" @click="createPic">
<image class="action-icon" src="@/static/share.png" mode="aspectFit"></image>
<text class="action-text">酒款分享</text>
2025-03-29 16:01:43 +08:00
</view>
<view class="action-item" @click="toWinelist">
<image class="action-icon" src="@/static/map.png" mode="aspectFit"></image>
<text class="action-text">生成酒单</text>
2025-03-29 16:01:43 +08:00
</view>
<view class="write-review" @click="toWrite">
{{ myReviewInfo ? '修改评分' : '为这款酒评分' }}
2025-03-29 16:01:43 +08:00
</view>
</view>
</view>
<!-- 组件 -->
2025-03-29 16:01:43 +08:00
<loginPopup ref="loginRef"></loginPopup>
<createPoster v-if="showShare" ref="createPosterRef" :url="tempUrl" @close="showShare=false"></createPoster>
<canvas type="2d" id="myCanvas" style="width: 360px;height: 570px;position: fixed;left:8888px"></canvas>
</view>
</template>
<script>
import {
getReviewInfo,
getReviewList,
getReviewScoreList,
getMyReviewInfo,
getBeerInfo,
favorBeer,
likeReview,
getBeerFavorStatus
} from '@/api/bar.js'
import loginPopup from '@/components/loginPopup.vue';
import createPoster from '@/components/createPoster.vue'
export default {
components: {
loginPopup,
createPoster
},
data() {
return {
tempUrl: '',
showShare: false,
beerId: '',
beerInfo: {}, // 酒款信息
reviewInfo: {},
reviewList: [], // 酒评列表
reviewScoreList: [], // 酒评评分
myReviewInfo: null,
currentTab: 2,
reviewTotal: 0, // 酒评总数
queryForm: {
beerId:'',
pageNum: 1,
pageSize: 5,
},
isFavor: false, // 是否收藏
sortType: 'latest',
2025-03-29 16:01:43 +08:00
};
},
onLoad({beerId}) {
2025-03-29 16:01:43 +08:00
this.beerId = beerId
this.queryForm.beerId = beerId
this.getBeerInfoFun()
this.getReviewListFun()
this.getReviewScoreListFun()
// 只在登录状态下获取收藏状态和我的评价信息
const token = uni.getStorageSync('token')
if (token) {
this.getBeerFavorStatusFun()
this.getMyReviewInfoFun()
}
2025-03-29 16:01:43 +08:00
},
onShow() {
console.log('show')
this.reviewList = []
this.queryForm.pageNum = 1
this.getReviewListFun()
this.getReviewScoreListFun()
// 只在登录状态下获取收藏状态和我的评价信息
const token = uni.getStorageSync('token')
if (token) {
this.getBeerFavorStatusFun()
2025-03-29 16:01:43 +08:00
this.getMyReviewInfoFun()
}
// 滚动到最顶
uni.pageScrollTo({
scrollTop: 0,
duration: 0,
})
},
methods: {
toPageBottom() {
uni.pageScrollTo({
scrollTop: 9999,
})
},
// 查询酒款收藏状态
getBeerFavorStatusFun() {
const token = uni.getStorageSync('token')
if (!token) {
this.$refs.loginRef.open()
return
}
2025-03-29 16:01:43 +08:00
getBeerFavorStatus(this.beerId).then(res => {
if(res.data) {
this.isFavor = true
}else {
this.isFavor = false
}
})
},
// 获取酒款信息
getBeerInfoFun() {
getBeerInfo(this.beerId).then(res => {
this.beerInfo = res.data
})
},
// 获取酒评信息
getReviewInfoFun() {
getReviewInfo(this.beerId).then(res => {
this.reviewInfo = res.data
})
},
// 获取酒评列表
getReviewListFun() {
getReviewList(this.queryForm).then(res => {
if (res.rows) {
console.log('获取评论列表数据:', res.rows.length)
let arr = res.rows.map(it => ({
...it,
compositeScore: this.getScore(it.aromaRating, it.tasteRating, it.colorRating),
isExpanded: false,
needExpand: it.reviewContent && it.reviewContent.length > 50
}))
2025-03-29 16:01:43 +08:00
if (arr.length > 0) {
console.log('更新评论列表')
this.reviewList = [...this.reviewList, ...arr]
2025-03-29 16:01:43 +08:00
}
}
this.reviewTotal = res.total
})
},
// 酒评列表分页
reviewPageChange() {
this.queryForm.pageNum++
this.getReviewListFun()
},
// 获取酒评评分列表
getReviewScoreListFun() {
getReviewScoreList(this.beerId).then(res => {
this.reviewScoreList = res.data
})
},
// 获取我的酒评信息
getMyReviewInfoFun() {
const token = uni.getStorageSync('token')
if (!token) {
this.$refs.loginRef.open()
return
}
2025-03-29 16:01:43 +08:00
getMyReviewInfo(this.beerId).then(res => {
this.myReviewInfo = res.data
})
},
// 写酒评
toWrite() {
const token = uni.getStorageSync('token')
if (!token) {
2025-03-29 16:01:43 +08:00
this.$refs.loginRef.open()
return
}
// 检查认证状态
const barInfo = uni.getStorageSync('barInfo')
2025-04-05 14:07:39 +08:00
if (!barInfo) {
uni.showModal({
title: '提示',
content: '请先认证门店',
showCancel: true,
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: '/pages/index/registration'
})
}
}
})
return
}
2025-04-05 14:07:39 +08:00
// 处理不同的认证状态
if (barInfo.authState === 0) {
uni.showModal({
title: '提示',
content: '请先认证门店',
showCancel: true,
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: '/pages/index/registration'
})
}
}
})
return
} else if (barInfo.authState === 1) {
uni.showToast({
title: '您的门店正在认证中,请耐心等待',
icon: 'none'
})
return
}
2025-03-29 16:01:43 +08:00
uni.navigateTo({
url: '/pages/index/writeReview?beerId=' + this.beerId
})
},
// 计算综合评分
getScore(a, b, c) {
let score = 0
score = (a + b + c) / 3
return score.toFixed(0)
},
// 跳转 品牌方
toBrand() {
if (!this.beerInfo.breweryId) return
uni.navigateTo({
url: '/pages/index/brandHome?breweryId=' + this.beerInfo.breweryId
})
},
// 收藏酒款
favorBeerFun(status) {
const token = uni.getStorageSync('token')
if (!token) {
2025-03-29 16:01:43 +08:00
this.$refs.loginRef.open()
return
}
// 检查认证状态
const barInfo = uni.getStorageSync('barInfo')
if (!barInfo || barInfo.authState === 0) {
uni.showModal({
title: '提示',
content: '请先认证门店',
showCancel: true,
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: '/pages/index/registration'
})
}
}
})
return
}
if (barInfo.authState === 1) {
2025-04-05 14:07:39 +08:00
uni.showToast({
title: '您的门店正在认证中,请耐心等待',
icon: 'none'
})
return
}
2025-03-29 16:01:43 +08:00
let data = {
beerId: this.beerId,
status
}
favorBeer(data).then(res => {
if (status == 1) {
this.isFavor = true
2025-03-29 16:01:43 +08:00
uni.showToast({
title: '收藏成功',
icon: 'success'
})
} else {
this.isFavor = false
2025-03-29 16:01:43 +08:00
uni.showToast({
title: '取消收藏',
icon: 'none'
})
}
}).catch(err => {
console.error('收藏操作失败:', err)
if (err.code === 500 && err.msg.includes('门店未认证')) {
uni.showModal({
title: '提示',
content: '请先认证门店',
showCancel: true,
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: '/pages/index/registration'
})
}
}
})
} else {
uni.showToast({
title: err.msg || '操作失败',
icon: 'none'
})
}
2025-03-29 16:01:43 +08:00
})
},
// 点赞
handleLike(item) {
const token = uni.getStorageSync('token')
if (!token) {
2025-03-29 16:01:43 +08:00
this.$refs.loginRef.open()
return
}
let data = {
reviewId: item.id,
status: item.reviewLike ? 2 : 1
}
likeReview(data).then(res => {
if (data.status == 1) {
uni.showToast({
title: '点赞成功',
icon: 'none'
})
item.reviewLike = true
item.likeCount = (item.likeCount || 0) + 1
2025-03-29 16:01:43 +08:00
} else {
uni.showToast({
title: '取消点赞',
icon: 'none'
})
item.reviewLike = false
item.likeCount = Math.max(0, (item.likeCount || 1) - 1)
2025-03-29 16:01:43 +08:00
}
})
},
// 生成酒单
toWinelist() {
const token = uni.getStorageSync('token')
if (!token) {
2025-03-29 16:01:43 +08:00
this.$refs.loginRef.open()
return
}
// 检查认证状态
const barInfo = uni.getStorageSync('barInfo')
2025-04-05 14:07:39 +08:00
if (!barInfo) {
uni.showModal({
title: '提示',
content: '请先认证门店',
showCancel: true,
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: '/pages/index/registration'
})
}
}
})
return
}
2025-04-05 14:07:39 +08:00
// 处理不同的认证状态
if (barInfo.authState === 0) {
uni.showModal({
title: '提示',
content: '请先认证门店',
showCancel: true,
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: '/pages/index/registration'
})
}
}
})
return
} else if (barInfo.authState === 1) {
uni.showToast({
title: '您的门店正在认证中,请耐心等待',
icon: 'none'
})
return
}
2025-03-29 16:01:43 +08:00
uni.navigateTo({
url: "/pagesActivity/winelist?beerId=" + this.beerId
})
},
// 分享
createPic() {
uni.showLoading({
title: '加载中'
})
let _this = this
// 获取的画布
uni.createSelectorQuery().select('#myCanvas').fields({
node: true,
size: true
}).exec((res) => {
const ctx = res[0].node.getContext('2d')
const canvas = res[0].node
const dpr = uni.getSystemInfoSync().pixelRatio
// const dpr = uni.getDeviceInfo().devicePixelRatio
// const dpr = uni.getSystemSetting().canvasToTempFilePath()
// 设置画布的宽高
canvas.width = res[0].width * dpr
canvas.height = res[0].height * dpr
ctx.scale(dpr, dpr)
let imageCover = canvas.createImage()
imageCover.src = this.beerInfo.cover
imageCover.onload = () => {
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(imageCover, 0, 0, 360, 460)
// 绘制文字
ctx.font = `12px Arial`
ctx.fillStyle = '#0B0E26'
ctx.fillText(this.beerInfo.brandName, 20, 470)
ctx.font = `12px Arial`
ctx.fillStyle = '#0B0E26'
ctx.fillText(this.beerInfo.beerName, 20, 490)
ctx.font = `12px Arial`
ctx.fillStyle = '#5E5F60'
ctx.fillText(this.beerInfo.beerStyles, 20, 510)
// ctx.font = `${this.els.text3.fontSize}px Arial`
// ctx.fillStyle = this.els.text3.color
// ctx.fillText(this.els.text3.value, this.els.text3.x, this.els.text3.y)
// ctx.font = `${this.els.text4.fontSize}px Arial`
// ctx.fillStyle = this.els.text4.color
// ctx.fillText(this.els.text4.value, this.els.text4.x, this.els.text4.y)
// 保存图片
uni.canvasToTempFilePath({
canvas: canvas,
success: (res) => {
_this.tempUrl = res.tempFilePath
this.showShare = true
setTimeout(() => {
uni.hideLoading()
this.$refs.createPosterRef.open()
}, 1000)
},
fail: (err) => {
console.log(err)
uni.hideLoading()
},
})
}
})
},
// 预览图片
previewImage(imgs, index) {
// 实现图片预览逻辑
console.log('Previewing image:', imgs[index])
},
// 跳转到全部评价页面
toAllReviews() {
uni.navigateTo({
url: '/pages/index/allReviews?beerId=' + this.beerId
})
},
2025-03-29 16:01:43 +08:00
}
}
</script>
<style lang="scss" scoped>
/* 页面容器 */
.page {
min-height: 100vh;
background-color: #F8F9FA;
padding-bottom: 220rpx;
font-family:-apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Roboto, "Segoe UI", "Microsoft YaHei", sans-serif;
}
/* 商品信息卡片 */
.product-card {
background: #fff;
margin: 24rpx;
padding: 32rpx;
border-radius: 24rpx;
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.06);
2025-03-29 16:01:43 +08:00
.product-header {
display: flex;
padding: 0;
.product-image {
width: 242rpx;
height: 342rpx;
border-radius: 16rpx;
margin-right: 32rpx;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.12);
}
.product-info {
flex: 1;
padding-top: 10rpx;
.title-row {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16rpx;
.product-title {
flex: 1;
font-size: 40rpx;
font-weight: bold;
color: #0B0E26;
margin-right: 20rpx;
}
.like-btn {
margin-top: 4rpx;
width: 48rpx;
height: 48rpx;
padding: 4rpx;
}
}
.product-subtitle {
font-size: 28rpx;
color: #606060;
margin: 10rpx 0;
}
.rating {
display: flex;
align-items: center;
gap: 16rpx;
margin: 20rpx 0;
.rating-score {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
}
.brand {
display: flex;
align-items: center;
padding: 20rpx;
background: linear-gradient(to right, #f9f9f9, #ffffff);
border-radius: 16rpx;
margin-top: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
.brand-logo {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
margin-right: 16rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.brand-name {
flex: 1;
font-size: 28rpx;
color: #0B0E26;
}
.arrow-icon {
width: 32rpx;
height: 32rpx;
opacity: 0.6;
}
}
}
2025-03-29 16:01:43 +08:00
}
.product-params {
2025-03-29 16:01:43 +08:00
display: flex;
justify-content: space-between;
padding: 0;
margin: 32rpx 0;
2025-03-29 16:01:43 +08:00
.param-item {
width: 200rpx;
height: 180rpx;
background: linear-gradient(135deg, rgba(245, 197, 24, 0.1), rgba(245, 197, 24, 0.05));
border-radius: 16rpx;
2025-03-29 16:01:43 +08:00
display: flex;
flex-direction: column;
2025-03-29 16:01:43 +08:00
align-items: center;
justify-content: center;
gap: 12rpx;
box-shadow: 0 4rpx 16rpx rgba(245, 197, 24, 0.1);
2025-03-29 16:01:43 +08:00
.param-label {
font-size: 32rpx;
font-weight: 600;
color: #5F5F63;
}
.param-value {
font-size: 32rpx;
font-weight: bold;
color: #FDCA40;
2025-03-29 16:01:43 +08:00
}
}
}
2025-03-29 16:01:43 +08:00
.product-description {
padding: 0;
font-size: 28rpx;
font-weight: normal;
line-height: 1.6;
color: #A1A1A1;
margin-bottom: 32rpx;
}
}
2025-03-29 16:01:43 +08:00
/* 评分区域 */
.rating-section {
background-color: #fff;
margin: 24rpx;
padding: 32rpx;
border-radius: 24rpx;
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.06);
2025-03-29 16:01:43 +08:00
.section-title {
font-size: 40rpx;
font-weight: bold;
line-height: 130%;
color: #030303;
margin-bottom: 36rpx;
}
2025-03-29 16:01:43 +08:00
.rating-tabs {
display: flex;
align-items: center;
justify-content: flex-end;
margin: 56rpx 0;
width: 100%;
2025-03-29 16:01:43 +08:00
.tab-item {
width: 120rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
background: #F9F9F9;
position: relative;
&:first-child {
border-radius: 12rpx 0 0 12rpx;
}
&:last-child {
border-radius: 0 12rpx 12rpx 0;
}
.tab-icon {
width: 60rpx;
height: 40rpx;
opacity: 0.6;
}
&.active {
background: #19367A;
box-shadow: 0 4rpx 12rpx rgba(25, 54, 122, 0.2);
.tab-icon {
opacity: 1;
filter: brightness(0) invert(1);
2025-03-29 16:01:43 +08:00
}
}
}
}
2025-03-29 16:01:43 +08:00
.rating-overview {
display: flex;
align-items: center;
padding-left: 80rpx;
gap: 30rpx;
margin-bottom: 40rpx;
.rating-score-large {
font-size: 104rpx;
font-weight: bold;
color: #030303;
line-height: 1;
min-width: 120rpx;
text-align: center;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
}
.rating-right {
display: flex;
flex-direction: column;
margin-left: 12rpx;
gap: 10rpx;
min-height: 80rpx;
}
.rating-count {
font-size: 24rpx;
margin-left: 12rpx;
color: #606060;
white-space: nowrap;
min-height: 24rpx;
&.highlight {
color: #FDCA40;
2025-03-29 16:01:43 +08:00
font-weight: 500;
}
}
}
2025-03-29 16:01:43 +08:00
.rating-bars {
margin: 32rpx 0;
.rating-bar {
margin-bottom: 16rpx;
2025-03-29 16:01:43 +08:00
.bar-header {
2025-03-29 16:01:43 +08:00
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 8rpx;
2025-03-29 16:01:43 +08:00
.bar-label {
font-size: 24rpx;
color: #606060;
}
2025-03-29 16:01:43 +08:00
.bar-value {
font-size: 32rpx;
color: #030303;
font-weight: 600;
}
}
2025-03-29 16:01:43 +08:00
.bar {
width: 100%;
height: 46rpx;
background: #F9F9F9;
border-radius: 23rpx;
overflow: hidden;
box-shadow: inset 0 2rpx 4rpx rgba(0, 0, 0, 0.05);
.bar-filled {
height: 100%;
background: linear-gradient(to right, #FDCA40, #FEE034);
border-radius: 23rpx;
box-shadow: 0 2rpx 8rpx rgba(253, 202, 64, 0.3);
2025-03-29 16:01:43 +08:00
}
}
}
}
2025-03-29 16:01:43 +08:00
.review-sort {
display: flex;
align-items: center;
margin: 32rpx 0;
gap: 16rpx;
padding-top: 24rpx;
.sort-item {
height: 64rpx;
padding: 0 24rpx;
background: #F9F9F9;
border-radius: 8rpx;
font-size: 24rpx;
color: #606060;
display: flex;
align-items: center;
justify-content: center;
&.active {
background: #19367A;
color: #FFFFFF;
box-shadow: 0 4rpx 12rpx rgba(25, 54, 122, 0.2);
}
}
}
.reviews {
.review-item {
padding: 40rpx 32rpx;
margin: 24rpx 0;
background: #FFFFFF;
border-radius: 16rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
border: none;
&:last-child {
border-bottom: none;
}
.review-header {
margin-bottom: 32rpx;
.user-info {
2025-03-29 16:01:43 +08:00
display: flex;
align-items: center;
gap: 24rpx;
.avatar {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
background: #f5f5f5;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
2025-03-29 16:01:43 +08:00
.user-meta {
flex: 1;
2025-03-29 16:01:43 +08:00
display: flex;
flex-direction: column;
gap: 12rpx;
2025-03-29 16:01:43 +08:00
.user-name {
font-size: 32rpx;
color: #333333;
font-weight: 500;
2025-03-29 16:01:43 +08:00
}
.review-time {
font-size: 24rpx;
color: #999999;
}
2025-03-29 16:01:43 +08:00
}
}
}
.review-content {
.content-left {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 24rpx;
2025-03-29 16:01:43 +08:00
.review-text {
font-size: 28rpx;
line-height: 44rpx;
color: #333333;
word-break: break-all;
}
2025-03-29 16:01:43 +08:00
.image-scroll {
margin-top: 32rpx;
width: 100%;
white-space: nowrap;
.image-container {
display: inline-flex;
gap: 16rpx;
padding-right: 32rpx;
.review-image {
width: 220rpx;
height: 220rpx;
border-radius: 12rpx;
flex-shrink: 0;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}
}
}
2025-03-29 16:01:43 +08:00
.review-footer {
display: flex;
align-items: center;
gap: 24rpx;
margin-top: 24rpx;
padding-top: 24rpx;
border-top: 2rpx solid #f5f5f5;
2025-03-29 16:01:43 +08:00
.score-text {
font-size: 28rpx;
color: #FDCA40;
font-weight: 500;
}
2025-03-29 16:01:43 +08:00
.like-btn {
display: flex;
align-items: center;
gap: 8rpx;
padding: 4rpx 12rpx;
border-radius: 8rpx;
background: rgba(25, 54, 122, 0.05);
.like-icon {
width: 32rpx;
height: 32rpx;
opacity: 0.6;
}
.like-count {
font-size: 24rpx;
color: #666666;
}
&.active {
background: rgba(253, 202, 64, 0.1);
box-shadow: 0 2rpx 8rpx rgba(253, 202, 64, 0.2);
.like-icon {
opacity: 1;
}
.like-count {
color: #FDCA40;
}
}
}
2025-03-29 16:01:43 +08:00
}
}
}
}
}
2025-03-29 16:01:43 +08:00
.view-all-reviews {
width: 100%;
height: 88rpx;
margin: 40rpx 0;
border: 2rpx solid #19367A;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
color: #19367A;
background: #FFFFFF;
box-shadow: 0 4rpx 12rpx rgba(25, 54, 122, 0.08);
&.disabled {
border-color: #CCCCCC;
color: #CCCCCC;
pointer-events: none;
box-shadow: none;
}
}
2025-03-29 16:01:43 +08:00
.no-review {
text-align: center;
padding: 80rpx 0;
font-size: 32rpx;
color: #606060;
background: #FFFFFF;
border-radius: 16rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
}
}
2025-03-29 16:01:43 +08:00
/* 底部操作栏 */
.bottom-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
background: #fff;
padding: 24rpx 32rpx env(safe-area-inset-bottom);
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.1);
.action-box {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12rpx 0;
.action-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
padding: 8rpx 16rpx;
.action-icon {
width: 48rpx;
height: 48rpx;
2025-03-29 16:01:43 +08:00
}
.action-text {
2025-03-29 16:01:43 +08:00
font-size: 24rpx;
color: #333;
margin-top: 4rpx;
2025-03-29 16:01:43 +08:00
}
}
2025-03-29 16:01:43 +08:00
.write-review {
width: 358rpx;
height: 96rpx;
background: linear-gradient(135deg, #19367A, #1E4B9E);
border-radius: 48rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: #fff;
font-weight: 500;
margin: 0 16rpx;
box-shadow: 0 4rpx 16rpx rgba(25, 54, 122, 0.2);
2025-03-29 16:01:43 +08:00
}
}
}
2025-03-29 16:01:43 +08:00
</style>