pet/pages/review/detail.vue

825 lines
17 KiB
Vue
Raw Permalink 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="detail-container page-container-with-bg">
<!-- 产品信息卡片 -->
<view class="product-info-card">
<view class="product-images">
<swiper class="product-swiper" indicator-dots circular>
<swiper-item v-for="(image, index) in product.images" :key="index">
<image class="swiper-image" :src="image" mode="aspectFill" />
</swiper-item>
</swiper>
</view>
<view class="product-details">
<view class="product-header">
<view class="product-brand">{{ product.brand }}</view>
<view class="product-favorite" @click="toggleFavorite">
<text class="favorite-icon" :class="{ active: product.isFavorite }">♥</text>
</view>
</view>
<view class="product-name">{{ product.name }}</view>
<view class="product-rating">
<view class="rating-stars">
<text
class="star"
v-for="i in 5"
:key="i"
:class="{ active: i <= product.rating }"
>★</text>
</view>
<text class="rating-score">{{ product.rating }}</text>
<text class="rating-count">({{ product.reviewCount }}条评价)</text>
</view>
<view class="product-price">
<text class="price-label">参考价格:</text>
<text class="price-range">¥{{ product.priceRange }}</text>
</view>
<view class="product-tags">
<text
class="product-tag"
v-for="tag in product.tags"
:key="tag"
>{{ tag }}</text>
</view>
</view>
</view>
<!-- 专业评测卡片 -->
<view class="review-card">
<view class="card-header">
<text class="card-title">专业评测</text>
<view class="expert-info">
<text class="expert-name">宠物营养师 · 李医生</text>
</view>
</view>
<view class="review-content">
<view class="review-section">
<text class="section-title">营养成分分析</text>
<view class="nutrition-list">
<view class="nutrition-item" v-for="item in product.nutrition" :key="item.name">
<text class="nutrition-name">{{ item.name }}</text>
<text class="nutrition-value">{{ item.value }}</text>
</view>
</view>
</view>
<view class="review-section">
<text class="section-title">适用对象</text>
<text class="section-content">{{ product.suitableFor }}</text>
</view>
<view class="review-section">
<text class="section-title">优点</text>
<view class="pros-list">
<text class="pros-item" v-for="pro in product.pros" :key="pro">• {{ pro }}</text>
</view>
</view>
<view class="review-section">
<text class="section-title">缺点</text>
<view class="cons-list">
<text class="cons-item" v-for="con in product.cons" :key="con"> {{ con }}</text>
</view>
</view>
<view class="review-section">
<text class="section-title">推荐指数</text>
<view class="recommendation">
<view class="recommendation-stars">
<text
class="rec-star"
v-for="i in 5"
:key="i"
:class="{ active: i <= product.recommendation }"
>★</text>
</view>
<text class="recommendation-text">{{ getRecommendationText(product.recommendation) }}</text>
</view>
</view>
</view>
</view>
<!-- 用户评价卡片 -->
<view class="user-reviews-card">
<view class="card-header">
<text class="card-title">用户评价</text>
<text class="reviews-count">{{ product.reviewCount }}条</text>
</view>
<view class="reviews-list">
<view class="review-item" v-for="review in userReviews" :key="review.id">
<view class="review-header">
<view class="user-info">
<image class="user-avatar" :src="review.avatar" mode="aspectFill" />
<text class="user-name">{{ review.userName }}</text>
</view>
<view class="review-rating">
<text
class="review-star"
v-for="i in 5"
:key="i"
:class="{ active: i <= review.rating }"
>★</text>
</view>
</view>
<view class="review-content">
<text class="review-text">{{ review.content }}</text>
</view>
<view class="review-images" v-if="review.images && review.images.length > 0">
<image
class="review-image"
v-for="(image, index) in review.images"
:key="index"
:src="image"
mode="aspectFill"
@click="previewImage(review.images, index)"
/>
</view>
<view class="review-footer">
<text class="review-time">{{ formatTime(review.time) }}</text>
</view>
</view>
</view>
</view>
<!-- 底部操作栏 -->
<view class="bottom-actions">
<view class="action-button secondary" @click="shareProduct">
<text class="action-icon">📤</text>
<text class="action-text">分享</text>
</view>
<view class="action-button primary" @click="buyProduct">
<text class="action-text">去购买</text>
</view>
</view>
</view>
</template>
<script>
import { reactive, ref, onMounted } from 'vue'
export default {
name: 'ReviewDetailPage',
setup() {
// 响应式数据
const product = reactive({
id: '',
name: '',
brand: '',
rating: 0,
reviewCount: 0,
priceRange: '',
images: [],
tags: [],
nutrition: [],
suitableFor: '',
pros: [],
cons: [],
recommendation: 0,
isFavorite: false
})
const userReviews = ref([])
// 生命周期
onMounted(() => {
loadProductDetail()
loadUserReviews()
})
// 方法定义
const loadProductDetail = () => {
// 获取产品ID
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
const productId = currentPage.options.id || 'p1'
// 模拟产品详情数据
const mockProduct = {
id: productId,
name: '成猫粮 室内猫配方',
brand: '皇家',
rating: 4.8,
reviewCount: 1256,
priceRange: '89-156',
images: [
'/static/product1.jpg',
'/static/product1-2.jpg',
'/static/product1-3.jpg'
],
tags: ['室内猫', '营养均衡', '毛球护理'],
nutrition: [
{ name: '粗蛋白', value: '≥32%' },
{ name: '粗脂肪', value: '≥13%' },
{ name: '粗纤维', value: '≤5.1%' },
{ name: '粗灰分', value: '≤7.5%' },
{ name: '水分', value: '≤10%' }
],
suitableFor: '1岁以上成年室内猫特别适合运动量较少、容易发胖的室内猫咪',
pros: [
'营养配比科学,适合室内猫需求',
'添加毛球护理配方,减少毛球形成',
'颗粒大小适中,适口性好',
'品牌信誉度高,质量有保障'
],
cons: [
'价格相对较高',
'部分猫咪可能需要适应期',
'包装密封性有待改进'
],
recommendation: 4,
isFavorite: false
}
Object.assign(product, mockProduct)
// 检查收藏状态
const favorites = uni.getStorageSync('reviewFavorites') || []
product.isFavorite = favorites.includes(product.id)
}
const loadUserReviews = () => {
// 模拟用户评价数据
const mockReviews = [
{
id: 'r1',
userName: '铲屎官小王',
avatar: '/static/avatar1.jpg',
rating: 5,
content: '我家猫咪很喜欢吃,而且吃了之后毛色变得更亮了,推荐!',
images: ['/static/review1.jpg'],
time: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString()
},
{
id: 'r2',
userName: '猫奴一枚',
avatar: '/static/avatar2.jpg',
rating: 4,
content: '质量不错,就是价格有点贵,不过为了猫主子的健康还是值得的。',
images: [],
time: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString()
},
{
id: 'r3',
userName: '宠物达人',
avatar: '/static/avatar3.jpg',
rating: 5,
content: '用了半年了,猫咪身体状况很好,体重控制得也不错,会继续购买。',
images: ['/static/review2.jpg', '/static/review3.jpg'],
time: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString()
}
]
userReviews.value = mockReviews
}
const getRecommendationText = (score) => {
const textMap = {
5: '强烈推荐',
4: '推荐',
3: '一般推荐',
2: '不太推荐',
1: '不推荐'
}
return textMap[score] || '一般推荐'
}
const toggleFavorite = () => {
let favorites = uni.getStorageSync('reviewFavorites') || []
const index = favorites.indexOf(product.id)
if (index > -1) {
favorites.splice(index, 1)
product.isFavorite = false
} else {
favorites.push(product.id)
product.isFavorite = true
}
uni.setStorageSync('reviewFavorites', favorites)
uni.showToast({
title: product.isFavorite ? '已收藏' : '已取消收藏',
icon: 'success'
})
}
const previewImage = (images, current) => {
uni.previewImage({
urls: images,
current: current
})
}
const formatTime = (timeString) => {
if (!timeString) return ''
const date = new Date(timeString)
const now = new Date()
const diffTime = now - date
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24))
if (diffDays === 0) {
return '今天'
} else if (diffDays === 1) {
return '昨天'
} else if (diffDays < 30) {
return `${diffDays}天前`
} else {
return date.toLocaleDateString('zh-CN')
}
}
const shareProduct = () => {
uni.showActionSheet({
itemList: ['分享到微信好友', '分享到朋友圈', '复制链接'],
success: (res) => {
const actions = ['微信好友', '朋友圈', '复制链接']
uni.showToast({
title: `已分享到${actions[res.tapIndex]}`,
icon: 'success'
})
}
})
}
const buyProduct = () => {
uni.showActionSheet({
itemList: ['淘宝', '京东', '天猫', '拼多多'],
success: (res) => {
const platforms = ['淘宝', '京东', '天猫', '拼多多']
uni.showToast({
title: `即将跳转到${platforms[res.tapIndex]}`,
icon: 'success'
})
}
})
}
return {
product,
userReviews,
getRecommendationText,
toggleFavorite,
previewImage,
formatTime,
shareProduct,
buyProduct
}
}
}
</script>
<style lang="scss" scoped>
.detail-container {
background: linear-gradient(135deg, #FF8A80 0%, #FFB6C1 25%, #FECFEF 50%, #F8BBD9 100%);
min-height: 100vh;
padding-bottom: 120rpx;
}
/* 通用卡片样式 */
.product-info-card,
.review-card,
.user-reviews-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
margin: 0 30rpx 24rpx 30rpx;
border-radius: 24rpx;
overflow: hidden;
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
border: 1rpx solid rgba(255, 255, 255, 0.3);
}
/* 产品信息卡片 */
.product-info-card {
.product-images {
.product-swiper {
height: 400rpx;
.swiper-image {
width: 100%;
height: 100%;
}
}
}
.product-details {
padding: 32rpx;
.product-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12rpx;
.product-brand {
font-size: 24rpx;
color: #999999;
}
.product-favorite {
width: 48rpx;
height: 48rpx;
background: rgba(255, 138, 128, 0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
.favorite-icon {
font-size: 28rpx;
color: #CCCCCC;
transition: all 0.3s ease;
&.active {
color: #FF8A80;
}
}
}
}
.product-name {
font-size: 36rpx;
font-weight: 700;
color: #333333;
margin-bottom: 16rpx;
line-height: 1.3;
}
.product-rating {
display: flex;
align-items: center;
margin-bottom: 16rpx;
.rating-stars {
margin-right: 12rpx;
.star {
font-size: 24rpx;
color: #DDDDDD;
&.active {
color: #FFD700;
}
}
}
.rating-score {
font-size: 28rpx;
color: #333333;
font-weight: 600;
margin-right: 8rpx;
}
.rating-count {
font-size: 24rpx;
color: #999999;
}
}
.product-price {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.price-label {
font-size: 26rpx;
color: #666666;
margin-right: 8rpx;
}
.price-range {
font-size: 32rpx;
color: #FF8A80;
font-weight: 700;
}
}
.product-tags {
display: flex;
flex-wrap: wrap;
gap: 8rpx;
.product-tag {
padding: 8rpx 16rpx;
background: rgba(255, 138, 128, 0.1);
border-radius: 16rpx;
font-size: 22rpx;
color: #FF8A80;
}
}
}
}
/* 评测卡片 */
.review-card {
padding: 32rpx;
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
.card-title {
font-size: 32rpx;
font-weight: 600;
color: #333333;
}
.expert-info {
.expert-name {
font-size: 22rpx;
color: #666666;
}
}
}
.review-content {
.review-section {
margin-bottom: 32rpx;
&:last-child {
margin-bottom: 0;
}
.section-title {
display: block;
font-size: 28rpx;
font-weight: 600;
color: #333333;
margin-bottom: 16rpx;
}
.section-content {
font-size: 26rpx;
color: #666666;
line-height: 1.6;
}
}
.nutrition-list {
.nutrition-item {
display: flex;
justify-content: space-between;
padding: 12rpx 0;
border-bottom: 1rpx solid rgba(255, 255, 255, 0.3);
&:last-child {
border-bottom: none;
}
.nutrition-name {
font-size: 24rpx;
color: #666666;
}
.nutrition-value {
font-size: 24rpx;
color: #333333;
font-weight: 500;
}
}
}
.pros-list,
.cons-list {
.pros-item,
.cons-item {
display: block;
font-size: 24rpx;
color: #666666;
line-height: 1.6;
margin-bottom: 8rpx;
&:last-child {
margin-bottom: 0;
}
}
}
.recommendation {
display: flex;
align-items: center;
.recommendation-stars {
margin-right: 16rpx;
.rec-star {
font-size: 28rpx;
color: #DDDDDD;
&.active {
color: #FF8A80;
}
}
}
.recommendation-text {
font-size: 26rpx;
color: #FF8A80;
font-weight: 600;
}
}
}
}
/* 用户评价卡片 */
.user-reviews-card {
padding: 32rpx;
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
.card-title {
font-size: 32rpx;
font-weight: 600;
color: #333333;
}
.reviews-count {
font-size: 24rpx;
color: #666666;
}
}
.reviews-list {
.review-item {
padding: 24rpx 0;
border-bottom: 1rpx solid rgba(255, 255, 255, 0.3);
&:last-child {
border-bottom: none;
}
.review-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12rpx;
.user-info {
display: flex;
align-items: center;
.user-avatar {
width: 48rpx;
height: 48rpx;
border-radius: 50%;
margin-right: 12rpx;
}
.user-name {
font-size: 26rpx;
color: #333333;
font-weight: 500;
}
}
.review-rating {
.review-star {
font-size: 20rpx;
color: #DDDDDD;
&.active {
color: #FFD700;
}
}
}
}
.review-content {
margin-bottom: 16rpx;
.review-text {
font-size: 26rpx;
color: #666666;
line-height: 1.6;
}
}
.review-images {
display: flex;
gap: 8rpx;
margin-bottom: 12rpx;
.review-image {
width: 120rpx;
height: 120rpx;
border-radius: 8rpx;
}
}
.review-footer {
.review-time {
font-size: 22rpx;
color: #999999;
}
}
}
}
}
/* 底部操作栏 */
.bottom-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
padding: 20rpx 30rpx;
border-top: 1rpx solid rgba(255, 255, 255, 0.3);
display: flex;
gap: 20rpx;
z-index: 100;
.action-button {
flex: 1;
height: 80rpx;
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
&.secondary {
background: rgba(255, 138, 128, 0.1);
border: 2rpx solid #FF8A80;
.action-icon {
font-size: 24rpx;
margin-right: 8rpx;
}
.action-text {
font-size: 28rpx;
color: #FF8A80;
font-weight: 600;
}
}
&.primary {
background: linear-gradient(135deg, #FF8A80, #FFB6C1);
.action-text {
font-size: 28rpx;
color: white;
font-weight: 600;
}
}
&:active {
transform: scale(0.98);
}
}
}
/* 响应式设计 */
@media (max-width: 375px) {
.detail-container {
.product-info-card,
.review-card,
.user-reviews-card {
margin: 0 20rpx 20rpx 20rpx;
}
.product-details,
.review-card,
.user-reviews-card {
padding: 24rpx;
}
.bottom-actions {
padding: 16rpx 20rpx;
gap: 16rpx;
}
}
}
/* 动画效果 */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.detail-container > view {
animation: fadeIn 0.5s ease-out;
}
.detail-container > view:nth-child(1) { animation-delay: 0.1s; }
.detail-container > view:nth-child(2) { animation-delay: 0.2s; }
.detail-container > view:nth-child(3) { animation-delay: 0.3s; }
/* 交互反馈 */
.product-favorite:active {
transform: scale(0.9);
}
.review-image:active {
transform: scale(0.95);
}
</style>