This commit is contained in:
2025-08-14 08:52:56 +08:00
parent 58ac884203
commit c338c5d3d0
15 changed files with 10773 additions and 0 deletions
+824
View File
@@ -0,0 +1,824 @@
<template>
<view class="detail-container">
<!-- 产品信息卡片 -->
<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>
File diff suppressed because it is too large Load Diff