8 changed files with 1415 additions and 7 deletions
@ -0,0 +1,165 @@ |
|||
<template> |
|||
<div class="sidebar-container"> |
|||
<!-- 购物车 --> |
|||
<div |
|||
class="sidebar-item" |
|||
@click="handleCartClick" |
|||
> |
|||
<el-badge :value="cartCount" class="item-badge"> |
|||
<i class="el-icon-shopping-cart-full"></i> |
|||
</el-badge> |
|||
<div class="item-text">购物车</div> |
|||
</div> |
|||
|
|||
<!-- 在线客服 --> |
|||
<div |
|||
class="sidebar-item" |
|||
@click="handleServiceClick" |
|||
> |
|||
<i class="el-icon-headset"></i> |
|||
<div class="item-text">在线客服</div> |
|||
</div> |
|||
|
|||
<!-- 回到顶部 --> |
|||
<div |
|||
class="sidebar-item back-to-top" |
|||
@click="handleBackToTop" |
|||
:class="{ active: isShowBackTop }" |
|||
> |
|||
<i class="el-icon-caret-top"></i> |
|||
<div class="item-text">回到顶部</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'Sidebar', |
|||
data() { |
|||
return { |
|||
cartCount: 3, // 购物车数量,可根据实际情况动态获取 |
|||
isShowBackTop: false, // 是否显示回到顶部激活态 |
|||
scrollTimer: null // 节流定时器 |
|||
}; |
|||
}, |
|||
mounted() { |
|||
// 监听滚动事件 |
|||
window.addEventListener('scroll', this.handleScroll); |
|||
}, |
|||
beforeDestroy() { |
|||
// 移除滚动监听 |
|||
window.removeEventListener('scroll', this.handleScroll); |
|||
if (this.scrollTimer) { |
|||
clearTimeout(this.scrollTimer); |
|||
} |
|||
}, |
|||
methods: { |
|||
// 处理购物车点击 |
|||
handleCartClick() { |
|||
// 这里可跳转到购物车页面 |
|||
this.$router.push('/cart'); |
|||
}, |
|||
|
|||
// 处理在线客服点击 |
|||
handleServiceClick() { |
|||
// 这里可实现客服功能,如唤起客服弹窗或跳转到客服页面 |
|||
this.$message.info('正在唤起在线客服...'); |
|||
}, |
|||
|
|||
// 处理回到顶部 |
|||
handleBackToTop() { |
|||
window.scrollTo({ top: 0, behavior: 'smooth' }); |
|||
}, |
|||
|
|||
// 处理滚动事件 |
|||
handleScroll() { |
|||
// 节流处理 |
|||
if (this.scrollTimer) { |
|||
clearTimeout(this.scrollTimer); |
|||
} |
|||
this.scrollTimer = setTimeout(() => { |
|||
// 滚动超过 300px 显示激活态 |
|||
this.isShowBackTop = window.pageYOffset > 300; |
|||
}, 200); |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.sidebar-container { |
|||
position: fixed; |
|||
right: 20px; |
|||
top: 50%; |
|||
transform: translateY(-50%); |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
background-color: #fff; |
|||
border-radius: 8px; |
|||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); |
|||
padding: 15px 10px; |
|||
z-index: 999; |
|||
|
|||
.sidebar-item { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
cursor: pointer; |
|||
margin-bottom: 20px; |
|||
color: #666; |
|||
transition: all 0.3s; |
|||
|
|||
&:last-child { |
|||
margin-bottom: 0; |
|||
} |
|||
|
|||
i { |
|||
font-size: 20px; |
|||
margin-bottom: 5px; |
|||
} |
|||
|
|||
.item-text { |
|||
font-size: 12px; |
|||
} |
|||
|
|||
&:hover { |
|||
color: #409eff; // 悬停高亮 |
|||
} |
|||
} |
|||
|
|||
// 购物车徽章样式 |
|||
.item-badge { |
|||
.el-badge__content { |
|||
background-color: #ff4d4f; |
|||
top: -5px; |
|||
right: -10px; |
|||
} |
|||
} |
|||
|
|||
// 回到顶部激活态 |
|||
.back-to-top.active { |
|||
color: #409eff; |
|||
} |
|||
} |
|||
|
|||
// 响应式适配 |
|||
@media (max-width: 768px) { |
|||
.sidebar-container { |
|||
right: 10px; |
|||
padding: 10px 5px; |
|||
|
|||
.sidebar-item { |
|||
margin-bottom: 15px; |
|||
|
|||
i { |
|||
font-size: 18px; |
|||
} |
|||
|
|||
.item-text { |
|||
font-size: 10px; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,674 @@ |
|||
<template> |
|||
<div class="product-review-section"> |
|||
<!-- 评分与筛选 --> |
|||
<div class="review-header"> |
|||
<div class="rating-box"> |
|||
<div class="rating-score">{{ averageScore }} 分</div> |
|||
<div class="rating-text">综合评分 (共 {{ totalReviews }} 条)</div> |
|||
</div> |
|||
|
|||
<div class="filter-tabs"> |
|||
<div |
|||
class="tab-item" |
|||
:class="{ active: activeFilter === 'all' }" |
|||
@click="handleFilterChange('all')" |
|||
> |
|||
全部({{ totalReviews }}) |
|||
</div> |
|||
<div |
|||
class="tab-item" |
|||
:class="{ active: activeFilter === 'image' }" |
|||
@click="handleFilterChange('image')" |
|||
> |
|||
有图({{ hasImageCount }}) |
|||
</div> |
|||
<div |
|||
class="tab-item" |
|||
:class="{ active: activeFilter === 'good' }" |
|||
@click="handleFilterChange('good')" |
|||
> |
|||
好评({{ goodReviewsCount }}) |
|||
</div> |
|||
<div |
|||
class="tab-item" |
|||
:class="{ active: activeFilter === 'medium' }" |
|||
@click="handleFilterChange('medium')" |
|||
> |
|||
中评({{ mediumReviewsCount }}) |
|||
</div> |
|||
<div |
|||
class="tab-item" |
|||
:class="{ active: activeFilter === 'bad' }" |
|||
@click="handleFilterChange('bad')" |
|||
> |
|||
差评({{ badReviewsCount }}) |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 评价列表 - 使用v-for循环 --> |
|||
<div class="review-list"> |
|||
<div |
|||
class="review-item" |
|||
v-for="(review, index) in currentPageReviews" |
|||
:key="review.id" |
|||
> |
|||
<div class="user-info"> |
|||
<img |
|||
class="avatar" |
|||
:src="review.avatar" |
|||
:alt="review.userName + '的头像'" |
|||
/> |
|||
<div class="user-name">{{ review.userName }}</div> |
|||
<div class="rating-stars"> |
|||
<!-- 动态渲染星级 --> |
|||
<span v-for="star in 5" :key="star"> |
|||
{{ star <= review.rating ? "★" : "☆" }} |
|||
</span> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="review-content" :class="{ empty: !review.content }"> |
|||
{{ review.content || "该用户没有填写评价内容" }} |
|||
</div> |
|||
|
|||
<!-- 评价图片 - 有图片才显示 --> |
|||
<div class="review-images" v-if="review.images && review.images.length"> |
|||
<img |
|||
v-for="(img, imgIndex) in review.images" |
|||
:key="imgIndex" |
|||
:src="img" |
|||
:alt="`${review.userName}的评价图片${imgIndex + 1}`" |
|||
@click="openPreview(review.images, imgIndex)" |
|||
/> |
|||
</div> |
|||
|
|||
<div class="review-time">{{ review.time }}</div> |
|||
</div> |
|||
|
|||
<!-- 空状态提示 --> |
|||
<div class="empty-state" v-if="filteredReviews.length === 0"> |
|||
<el-empty description="暂无符合条件的评价"></el-empty> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Element UI 分页组件 --> |
|||
<div class="pagination-container" v-if="filteredReviews.length > 0"> |
|||
<el-pagination |
|||
@size-change="handleSizeChange" |
|||
@current-change="handleCurrentChange" |
|||
:current-page="currentPage" |
|||
:page-sizes="[5, 10, 20]" |
|||
:page-size="pageSize" |
|||
layout="total, sizes, prev, pager, next, jumper" |
|||
:total="filteredReviews.length" |
|||
></el-pagination> |
|||
</div> |
|||
|
|||
<!-- 图片预览弹窗 --> |
|||
<el-dialog |
|||
:visible.sync="previewVisible" |
|||
:modal="true" |
|||
:title="`查看图片 ${previewIndex + 1}/${previewImages.length}`" |
|||
width="80%" |
|||
height="80%" |
|||
custom-class="image-preview-dialog" |
|||
:close-on-click-modal="true" |
|||
> |
|||
<div class="preview-container"> |
|||
<!-- 上一张按钮 --> |
|||
<el-button |
|||
icon="el-icon-arrow-left" |
|||
circle |
|||
class="preview-btn prev-btn" |
|||
@click="changePreview('prev')" |
|||
:disabled="previewIndex === 0" |
|||
></el-button> |
|||
|
|||
<!-- 预览图片 --> |
|||
<div class="preview-img-wrapper"> |
|||
<img |
|||
:src="previewImages[previewIndex]" |
|||
:alt="`预览图片 ${previewIndex + 1}`" |
|||
class="preview-img" |
|||
/> |
|||
</div> |
|||
|
|||
<!-- 下一张按钮 --> |
|||
<el-button |
|||
icon="el-icon-arrow-right" |
|||
circle |
|||
class="preview-btn next-btn" |
|||
@click="changePreview('next')" |
|||
:disabled="previewIndex === previewImages.length - 1" |
|||
></el-button> |
|||
</div> |
|||
</el-dialog> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { Empty, Pagination } from "element-ui"; |
|||
|
|||
export default { |
|||
name: "ProductReviews", |
|||
components: { |
|||
ElEmpty: Empty, |
|||
ElPagination: Pagination, |
|||
}, |
|||
data() { |
|||
return { |
|||
// 评价筛选状态 |
|||
activeFilter: "all", |
|||
// 分页参数 |
|||
currentPage: 1, // 当前页码 |
|||
pageSize: 10, // 每页条数,默认10条 |
|||
|
|||
// 评价原始数据(模拟15条数据) |
|||
reviews: [ |
|||
{ |
|||
id: 1, |
|||
userName: "张**", |
|||
avatar: "https://picsum.photos/id/64/40/40", |
|||
rating: 5, |
|||
content: |
|||
"商品质量很好,超出预期,非常满意的一次购物体验!包装很用心,完全没有损坏,会回购的。", |
|||
images: [ |
|||
"https://picsum.photos/id/26/80/80", |
|||
"https://picsum.photos/id/292/80/80", |
|||
], |
|||
time: "2023-10-15 14:30:22", |
|||
}, |
|||
{ |
|||
id: 2, |
|||
userName: "李**", |
|||
avatar: "https://picsum.photos/id/65/40/40", |
|||
rating: 4, |
|||
content: "总体不错,就是物流有点慢,其他都还好,给个四星吧", |
|||
images: [], |
|||
time: "2023-10-14 09:15:47", |
|||
}, |
|||
{ |
|||
id: 3, |
|||
userName: "王**", |
|||
avatar: "https://picsum.photos/id/66/40/40", |
|||
rating: 5, |
|||
content: "", // 空评价 |
|||
images: ["https://picsum.photos/id/431/80/80"], |
|||
time: "2023-10-13 20:45:11", |
|||
}, |
|||
{ |
|||
id: 4, |
|||
userName: "赵**", |
|||
avatar: "https://picsum.photos/id/67/40/40", |
|||
rating: 2, |
|||
content: "不太满意,和描述的有点差距,希望商家能改进", |
|||
images: [], |
|||
time: "2023-10-12 16:20:33", |
|||
}, |
|||
{ |
|||
id: 5, |
|||
userName: "陈**", |
|||
avatar: "https://picsum.photos/id/68/40/40", |
|||
rating: 5, |
|||
content: "第二次购买了,品质一如既往的好,推荐给大家!", |
|||
images: [ |
|||
"https://picsum.photos/id/102/80/80", |
|||
"https://picsum.photos/id/103/80/80", |
|||
"https://picsum.photos/id/104/80/80", |
|||
], |
|||
time: "2023-10-11 11:05:59", |
|||
}, |
|||
{ |
|||
id: 6, |
|||
userName: "刘**", |
|||
avatar: "https://picsum.photos/id/69/40/40", |
|||
rating: 3, |
|||
content: "一般般吧,没有特别惊艳的地方,价格还算合理", |
|||
images: [], |
|||
time: "2023-10-10 18:30:15", |
|||
}, |
|||
{ |
|||
id: 7, |
|||
userName: "黄**", |
|||
avatar: "https://picsum.photos/id/70/40/40", |
|||
rating: 5, |
|||
content: "包装精美,送礼自用都合适,性价比很高", |
|||
images: ["https://picsum.photos/id/105/80/80"], |
|||
time: "2023-10-09 08:45:27", |
|||
}, |
|||
{ |
|||
id: 8, |
|||
userName: "周**", |
|||
avatar: "https://picsum.photos/id/71/40/40", |
|||
rating: 4, |
|||
content: "东西不错,物流很快,客服态度也好", |
|||
images: ["https://picsum.photos/id/106/80/80"], |
|||
time: "2023-10-08 15:22:10", |
|||
}, |
|||
{ |
|||
id: 9, |
|||
userName: "吴**", |
|||
avatar: "https://picsum.photos/id/72/40/40", |
|||
rating: 5, |
|||
content: "非常满意,推荐购买!", |
|||
images: [], |
|||
time: "2023-10-07 10:15:33", |
|||
}, |
|||
{ |
|||
id: 10, |
|||
userName: "郑**", |
|||
avatar: "https://picsum.photos/id/73/40/40", |
|||
rating: 1, |
|||
content: "很差的购物体验,不会再买了", |
|||
images: [], |
|||
time: "2023-10-06 19:40:25", |
|||
}, |
|||
{ |
|||
id: 11, |
|||
userName: "孙**", |
|||
avatar: "https://picsum.photos/id/74/40/40", |
|||
rating: 5, |
|||
content: "质量很好,和图片描述一致,值得购买", |
|||
images: [ |
|||
"https://picsum.photos/id/107/80/80", |
|||
"https://picsum.photos/id/108/80/80", |
|||
], |
|||
time: "2023-10-05 09:20:18", |
|||
}, |
|||
{ |
|||
id: 12, |
|||
userName: "徐**", |
|||
avatar: "https://picsum.photos/id/75/40/40", |
|||
rating: 4, |
|||
content: "不错的商品,推荐给朋友了", |
|||
images: [], |
|||
time: "2023-10-04 16:50:47", |
|||
}, |
|||
{ |
|||
id: 13, |
|||
userName: "马**", |
|||
avatar: "https://picsum.photos/id/76/40/40", |
|||
rating: 5, |
|||
content: "很好用,已经回购多次", |
|||
images: ["https://picsum.photos/id/109/80/80"], |
|||
time: "2023-10-03 14:30:55", |
|||
}, |
|||
{ |
|||
id: 14, |
|||
userName: "朱**", |
|||
avatar: "https://picsum.photos/id/77/40/40", |
|||
rating: 3, |
|||
content: "还行吧,没有想象中好", |
|||
images: [], |
|||
time: "2023-10-02 11:15:32", |
|||
}, |
|||
{ |
|||
id: 15, |
|||
userName: "胡**", |
|||
avatar: "https://picsum.photos/id/78/40/40", |
|||
rating: 5, |
|||
content: "非常满意的一次购物,五星推荐!", |
|||
images: [ |
|||
"https://picsum.photos/id/110/80/80", |
|||
"https://picsum.photos/id/111/80/80", |
|||
], |
|||
time: "2023-10-01 08:40:17", |
|||
}, |
|||
], |
|||
// 图片预览相关状态 |
|||
previewVisible: false, // 预览弹窗是否可见 |
|||
previewImages: [], // 当前预览的图片数组 |
|||
previewIndex: 0, // 当前预览的图片索引 |
|||
}; |
|||
}, |
|||
computed: { |
|||
// 总评价数 |
|||
totalReviews() { |
|||
return this.reviews.length; |
|||
}, |
|||
|
|||
// 有图评价数 |
|||
hasImageCount() { |
|||
return this.reviews.filter( |
|||
(review) => review.images && review.images.length |
|||
).length; |
|||
}, |
|||
|
|||
// 好评数(4-5星) |
|||
goodReviewsCount() { |
|||
return this.reviews.filter((review) => review.rating >= 4).length; |
|||
}, |
|||
|
|||
// 中评数(3星) |
|||
mediumReviewsCount() { |
|||
return this.reviews.filter((review) => review.rating === 3).length; |
|||
}, |
|||
|
|||
// 差评数(1-2星) |
|||
badReviewsCount() { |
|||
return this.reviews.filter((review) => review.rating <= 2).length; |
|||
}, |
|||
|
|||
// 平均评分 |
|||
averageScore() { |
|||
const sum = this.reviews.reduce( |
|||
(total, review) => total + review.rating, |
|||
0 |
|||
); |
|||
return (sum / this.totalReviews).toFixed(1); |
|||
}, |
|||
|
|||
// 根据筛选条件过滤评价 |
|||
filteredReviews() { |
|||
let result = [...this.reviews]; |
|||
|
|||
// 根据当前筛选条件过滤 |
|||
switch (this.activeFilter) { |
|||
case "image": |
|||
result = result.filter( |
|||
(review) => review.images && review.images.length |
|||
); |
|||
break; |
|||
case "good": |
|||
result = result.filter((review) => review.rating >= 4); |
|||
break; |
|||
case "medium": |
|||
result = result.filter((review) => review.rating === 3); |
|||
break; |
|||
case "bad": |
|||
result = result.filter((review) => review.rating <= 2); |
|||
break; |
|||
default: |
|||
// 全部评价 |
|||
break; |
|||
} |
|||
|
|||
return result; |
|||
}, |
|||
|
|||
// 当前页显示的评价 |
|||
currentPageReviews() { |
|||
const startIndex = (this.currentPage - 1) * this.pageSize; |
|||
const endIndex = startIndex + this.pageSize; |
|||
return this.filteredReviews.slice(startIndex, endIndex); |
|||
}, |
|||
}, |
|||
methods: { |
|||
// 筛选条件改变 |
|||
handleFilterChange(filterType) { |
|||
this.activeFilter = filterType; |
|||
this.currentPage = 1; // 切换筛选条件时重置到第一页 |
|||
}, |
|||
|
|||
// 每页条数改变 |
|||
handleSizeChange(val) { |
|||
this.pageSize = val; |
|||
this.currentPage = 1; // 改变每页条数时重置到第一页 |
|||
}, |
|||
|
|||
// 当前页码改变 |
|||
handleCurrentChange(val) { |
|||
this.currentPage = val; |
|||
// 滚动到评价列表顶部 |
|||
document.querySelector(".review-list").scrollIntoView({ |
|||
behavior: "smooth", |
|||
}); |
|||
}, |
|||
|
|||
// 打开图片预览 |
|||
openPreview(images, index) { |
|||
this.previewImages = images; // 保存当前评价的所有图片 |
|||
this.previewIndex = index; // 记录当前点击的图片索引 |
|||
this.previewVisible = true; // 显示预览弹窗 |
|||
}, |
|||
|
|||
// 切换预览图片(上一张/下一张) |
|||
changePreview(type) { |
|||
if (type === "prev" && this.previewIndex > 0) { |
|||
this.previewIndex--; |
|||
} else if ( |
|||
type === "next" && |
|||
this.previewIndex < this.previewImages.length - 1 |
|||
) { |
|||
this.previewIndex++; |
|||
} |
|||
// 更新弹窗标题中的图片计数 |
|||
this.$nextTick(() => { |
|||
document.querySelector(".el-dialog__title").textContent = `查看图片 ${ |
|||
this.previewIndex + 1 |
|||
}/${this.previewImages.length}`; |
|||
}); |
|||
}, |
|||
}, |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.product-review-section { |
|||
padding: 20px; |
|||
background: #fff; |
|||
border: 1px solid #eee; |
|||
border-radius: 4px; |
|||
|
|||
.review-header { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
margin-bottom: 20px; |
|||
flex-wrap: wrap; |
|||
|
|||
.rating-box { |
|||
display: flex; |
|||
align-items: center; |
|||
|
|||
.rating-score { |
|||
font-size: 24px; |
|||
color: #ff4d4f; |
|||
font-weight: bold; |
|||
margin-right: 10px; |
|||
} |
|||
|
|||
.rating-text { |
|||
font-size: 14px; |
|||
color: #999; |
|||
} |
|||
} |
|||
|
|||
.filter-tabs { |
|||
display: flex; |
|||
gap: 10px; |
|||
flex-wrap: wrap; |
|||
|
|||
.tab-item { |
|||
padding: 6px 12px; |
|||
border: 1px solid #ddd; |
|||
border-radius: 4px; |
|||
color: #666; |
|||
cursor: pointer; |
|||
transition: all 0.3s ease; |
|||
|
|||
&.active, |
|||
&:hover { |
|||
border-color: #ff4d4f; |
|||
color: #ff4d4f; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.review-list { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 20px; |
|||
margin-bottom: 30px; |
|||
|
|||
.review-item { |
|||
padding: 15px; |
|||
border: 1px solid #f5f5f5; |
|||
border-radius: 4px; |
|||
background: #fdfdfd; |
|||
|
|||
.user-info { |
|||
display: flex; |
|||
align-items: center; |
|||
margin-bottom: 10px; |
|||
|
|||
.avatar { |
|||
width: 40px; |
|||
height: 40px; |
|||
border-radius: 50%; |
|||
margin-right: 10px; |
|||
} |
|||
|
|||
.user-name { |
|||
font-size: 14px; |
|||
color: #333; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.rating-stars { |
|||
margin-left: auto; |
|||
color: #ff4d4f; |
|||
font-size: 14px; |
|||
} |
|||
} |
|||
|
|||
.review-content { |
|||
font-size: 14px; |
|||
color: #666; |
|||
line-height: 1.6; |
|||
margin-bottom: 10px; |
|||
|
|||
&.empty { |
|||
color: #999; |
|||
font-style: italic; |
|||
} |
|||
} |
|||
|
|||
.review-images { |
|||
display: flex; |
|||
gap: 8px; |
|||
margin-bottom: 10px; |
|||
|
|||
img { |
|||
width: 80px; |
|||
height: 80px; |
|||
border-radius: 4px; |
|||
object-fit: cover; |
|||
border: 1px solid #eee; |
|||
cursor: pointer; |
|||
transition: transform 0.2s; |
|||
|
|||
&:hover { |
|||
transform: scale(1.05); |
|||
} |
|||
} |
|||
} |
|||
|
|||
.review-time { |
|||
font-size: 12px; |
|||
color: #999; |
|||
margin-top: 5px; |
|||
} |
|||
} |
|||
|
|||
.empty-state { |
|||
padding: 50px 0; |
|||
text-align: center; |
|||
} |
|||
} |
|||
|
|||
// 分页组件样式 |
|||
.pagination-container { |
|||
display: flex; |
|||
justify-content: center; |
|||
margin-top: 20px; |
|||
padding-top: 10px; |
|||
border-top: 1px solid #f5f5f5; |
|||
} |
|||
} |
|||
|
|||
// 响应式适配 |
|||
@media (max-width: 768px) { |
|||
.product-review-section { |
|||
.review-header { |
|||
flex-direction: column; |
|||
align-items: flex-start; |
|||
gap: 15px; |
|||
} |
|||
|
|||
.pagination-container { |
|||
padding: 0 10px; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 图片预览相关样式 |
|||
.image-preview-dialog { |
|||
.el-dialog__body { |
|||
padding: 0; |
|||
height: 80vh; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
} |
|||
|
|||
.preview-container { |
|||
position: relative; |
|||
width: 100%; |
|||
height: 100%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.preview-img-wrapper { |
|||
width: fit-content; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.preview-img { |
|||
min-height: 50vh; |
|||
max-height: 70vh; |
|||
object-fit: contain; |
|||
// box-shadow: 0 0 20px rgba(0, 0, 0, 0.3); |
|||
} |
|||
|
|||
.preview-btn { |
|||
position: absolute; |
|||
width: 40px; |
|||
height: 40px; |
|||
background-color: rgba(0, 0, 0, 0.5); |
|||
color: white; |
|||
border: none; |
|||
z-index: 10; |
|||
opacity: 0.7; |
|||
transition: opacity 0.3s; |
|||
|
|||
&:hover { |
|||
opacity: 1; |
|||
color: white; |
|||
background-color: rgba(0, 0, 0, 0.7); |
|||
} |
|||
} |
|||
|
|||
.prev-btn { |
|||
left: 20px; |
|||
} |
|||
|
|||
.next-btn { |
|||
right: 20px; |
|||
} |
|||
|
|||
// 评价图片添加鼠标指针样式 |
|||
.review-images { |
|||
img { |
|||
cursor: zoom-in; // 鼠标悬停显示放大图标 |
|||
} |
|||
} |
|||
</style> |
|||
|
@ -0,0 +1,540 @@ |
|||
<template> |
|||
<div class="product-grid-page"> |
|||
<!-- 筛选排序区(可根据需求扩展,设计稿未体现时可隐藏) --> |
|||
<div class="filter-bar" v-if="showFilter"> |
|||
<el-select |
|||
v-model="selectedCategory" |
|||
placeholder="选择分类" |
|||
class="filter-select" |
|||
> |
|||
<el-option |
|||
v-for="cat in categories" |
|||
:key="cat.value" |
|||
:label="cat.label" |
|||
:value="cat.value" |
|||
></el-option> |
|||
</el-select> |
|||
|
|||
<el-select |
|||
v-model="selectedSort" |
|||
placeholder="排序方式" |
|||
class="filter-select" |
|||
> |
|||
<el-option |
|||
v-for="sort in sortOptions" |
|||
:key="sort.value" |
|||
:label="sort.label" |
|||
:value="sort.value" |
|||
></el-option> |
|||
</el-select> |
|||
|
|||
<el-input |
|||
v-model="searchKeyword" |
|||
placeholder="搜索商品" |
|||
class="search-input" |
|||
clearable |
|||
@keyup.enter.native="handleSearch" |
|||
> |
|||
<i slot="suffix" class="el-icon-search" @click="handleSearch" style="line-height: 3;"></i> |
|||
</el-input> |
|||
</div> |
|||
|
|||
<!-- 商品网格 --> |
|||
<div class="product-grid"> |
|||
<div |
|||
class="product-card" |
|||
v-for="(product, index) in visibleProducts" |
|||
:key="index" |
|||
@click="goToDetail(product.id)" |
|||
> |
|||
<!-- 商品图片(懒加载 + 占位) --> |
|||
<el-image |
|||
v-lazy="product.image" |
|||
:alt="product.name" |
|||
class="product-img" |
|||
lazy |
|||
fit="cover" |
|||
> |
|||
<div slot="placeholder" class="image-placeholder"> |
|||
<i class="el-icon-loading"></i> |
|||
</div> |
|||
</el-image> |
|||
|
|||
<!-- 标签(如新品、折扣等,设计稿未体现时可注释) --> |
|||
<div |
|||
class="tag" |
|||
v-if="product.tag" |
|||
:style="{ backgroundColor: product.tagColor }" |
|||
> |
|||
{{ product.tag }} |
|||
</div> |
|||
|
|||
<!-- 商品信息区 --> |
|||
<div class="product-info"> |
|||
<div class="product-name">{{ product.name }}</div> |
|||
<div class="price-row"> |
|||
<span class="current-price">¥{{ product.price }}</span> |
|||
<span class="original-price" v-if="product.originalPrice" |
|||
>¥{{ product.originalPrice }}</span |
|||
> |
|||
</div> |
|||
<el-button |
|||
type="primary" |
|||
size="mini" |
|||
class="cart-btn" |
|||
@click.prevent="addToCart(product)" |
|||
> |
|||
加入购物车 |
|||
</el-button> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 空状态 --> |
|||
<div class="empty-state" v-if="visibleProducts.length === 0"> |
|||
<el-empty description="暂无商品"></el-empty> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 分页组件 --> |
|||
<div class="pagination-container"> |
|||
<el-pagination |
|||
@size-change="handleSizeChange" |
|||
@current-change="handleCurrentChange" |
|||
:current-page="currentPage" |
|||
:page-sizes="[12, 24, 36]" |
|||
:page-size="pageSize" |
|||
layout="total, sizes, prev, pager, next, jumper" |
|||
:total="totalProducts" |
|||
></el-pagination> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: "ProductGridPage", |
|||
data() { |
|||
return { |
|||
// 筛选/排序条件(可根据需求扩展) |
|||
selectedCategory: "", |
|||
selectedSort: "", |
|||
searchKeyword: "", |
|||
showFilter: true, // 控制是否显示筛选栏,设计稿无则设为 false |
|||
|
|||
// 分页控制 |
|||
currentPage: 1, |
|||
pageSize: 12, // 设计稿每行 4 个,共 4 行,所以 12 个/页 |
|||
|
|||
// 商品数据(模拟,替换成真实接口) |
|||
products: [ |
|||
{ |
|||
id: 1, |
|||
name: "产品名称产品名称1", |
|||
image: "https://picsum.photos/id/101/200/200", |
|||
price: 359, |
|||
originalPrice: 499, |
|||
tag: "限时折扣", |
|||
tagColor: "#FF6B6B", |
|||
}, |
|||
{ |
|||
id: 2, |
|||
name: "产品名称产品名称", |
|||
image: "https://picsum.photos/id/102/200/200", |
|||
price: 259, |
|||
originalPrice: 329, |
|||
tag: "新品", |
|||
tagColor: "#4ECDC4", |
|||
}, |
|||
{ |
|||
id: 3, |
|||
name: "产品名称产品名称", |
|||
image: "https://picsum.photos/id/103/200/200", |
|||
price: 159, |
|||
originalPrice: 229, |
|||
tag: "爆款", |
|||
tagColor: "#FFD166", |
|||
}, |
|||
{ |
|||
id: 4, |
|||
name: "产品名称产品名称", |
|||
image: "https://picsum.photos/id/104/200/200", |
|||
price: 215, |
|||
originalPrice: 299, |
|||
tag: "推荐", |
|||
tagColor: "#06D6A0", |
|||
}, |
|||
{ |
|||
id: 5, |
|||
name: "产品名称产品名称", |
|||
image: "https://picsum.photos/id/105/200/200", |
|||
price: 299, |
|||
originalPrice: 399, |
|||
tag: "限时折扣", |
|||
tagColor: "#FF6B6B", |
|||
}, |
|||
{ |
|||
id: 6, |
|||
name: "产品名称产品名称", |
|||
image: "https://picsum.photos/id/106/200/200", |
|||
price: 159, |
|||
originalPrice: 229, |
|||
tag: "新品", |
|||
tagColor: "#4ECDC4", |
|||
}, |
|||
{ |
|||
id: 7, |
|||
name: "产品名称产品名称", |
|||
image: "https://picsum.photos/id/107/200/200", |
|||
price: 229, |
|||
originalPrice: 329, |
|||
tag: "爆款", |
|||
tagColor: "#FFD166", |
|||
}, |
|||
{ |
|||
id: 8, |
|||
name: "产品名称产品名称", |
|||
image: "https://picsum.photos/id/108/200/200", |
|||
price: 289, |
|||
originalPrice: 399, |
|||
tag: "推荐", |
|||
tagColor: "#06D6A0", |
|||
}, |
|||
{ |
|||
id: 9, |
|||
name: "产品名称产品名称", |
|||
image: "https://picsum.photos/id/109/200/200", |
|||
price: 359, |
|||
originalPrice: 499, |
|||
tag: "限时折扣", |
|||
tagColor: "#FF6B6B", |
|||
}, |
|||
{ |
|||
id: 10, |
|||
name: "产品名称产品名称", |
|||
image: "https://picsum.photos/id/110/200/200", |
|||
price: 159, |
|||
originalPrice: 229, |
|||
tag: "新品", |
|||
tagColor: "#4ECDC4", |
|||
}, |
|||
{ |
|||
id: 11, |
|||
name: "产品名称产品名称", |
|||
image: "https://picsum.photos/id/111/200/200", |
|||
price: 259, |
|||
originalPrice: 329, |
|||
tag: "爆款", |
|||
tagColor: "#FFD166", |
|||
}, |
|||
{ |
|||
id: 12, |
|||
name: "产品名称产品名称", |
|||
image: "https://picsum.photos/id/112/200/200", |
|||
price: 299, |
|||
originalPrice: 399, |
|||
tag: "推荐", |
|||
tagColor: "#06D6A0", |
|||
}, |
|||
{ |
|||
id: 13, |
|||
name: "产品名称产品名称", |
|||
image: "https://picsum.photos/id/113/200/200", |
|||
price: 359, |
|||
originalPrice: 499, |
|||
tag: "限时折扣", |
|||
tagColor: "#FF6B6B", |
|||
}, |
|||
{ |
|||
id: 14, |
|||
name: "产品名称产品名称", |
|||
image: "https://picsum.photos/id/114/200/200", |
|||
price: 159, |
|||
originalPrice: 229, |
|||
tag: "新品", |
|||
tagColor: "#4ECDC4", |
|||
}, |
|||
{ |
|||
id: 15, |
|||
name: "产品名称产品名称", |
|||
image: "https://picsum.photos/id/115/200/200", |
|||
price: 259, |
|||
originalPrice: 329, |
|||
tag: "爆款", |
|||
tagColor: "#FFD166", |
|||
}, |
|||
{ |
|||
id: 16, |
|||
name: "产品名称产品名称", |
|||
image: "https://picsum.photos/id/116/200/200", |
|||
price: 299, |
|||
originalPrice: 399, |
|||
tag: "推荐", |
|||
tagColor: "#06D6A0", |
|||
}, |
|||
{ |
|||
id: 17, |
|||
name: "产品名称产品名称", |
|||
image: "https://picsum.photos/id/117/200/200", |
|||
price: 359, |
|||
originalPrice: 499, |
|||
tag: "限时折扣", |
|||
tagColor: "#FF6B6B", |
|||
}, |
|||
{ |
|||
id: 18, |
|||
name: "产品名称产品名称", |
|||
image: "https://picsum.photos/id/118/200/200", |
|||
price: 159, |
|||
originalPrice: 229, |
|||
tag: "新品", |
|||
tagColor: "#4ECDC4", |
|||
}, |
|||
{ |
|||
id: 19, |
|||
name: "产品名称产品名称", |
|||
image: "https://picsum.photos/id/119/200/200", |
|||
price: 259, |
|||
originalPrice: 329, |
|||
tag: "爆款", |
|||
tagColor: "#FFD166", |
|||
}, |
|||
{ |
|||
id: 20, |
|||
name: "产品名称产品名称", |
|||
image: "https://picsum.photos/id/120/200/200", |
|||
price: 299, |
|||
originalPrice: 399, |
|||
tag: "推荐", |
|||
tagColor: "#06D6A0", |
|||
}, |
|||
], |
|||
|
|||
// 分页控制 |
|||
currentPage: 1, |
|||
pageSize: 12, |
|||
|
|||
// 筛选选项(设计稿未体现时可简化) |
|||
categories: [ |
|||
{ label: "全部", value: "" }, |
|||
{ label: "美食", value: "food" }, |
|||
{ label: "生活", value: "life" }, |
|||
{ label: "数码", value: "digital" }, |
|||
], |
|||
sortOptions: [ |
|||
{ label: "默认排序", value: "" }, |
|||
{ label: "价格 ascending", value: "price_asc" }, |
|||
{ label: "价格 descending", value: "price_desc" }, |
|||
], |
|||
}; |
|||
}, |
|||
computed: { |
|||
// 筛选后商品 |
|||
filteredProducts() { |
|||
return this.products.filter((product) => { |
|||
const categoryMatch = this.selectedCategory |
|||
? product.category === this.selectedCategory |
|||
: true; |
|||
const keywordMatch = this.searchKeyword |
|||
? product.name.includes(this.searchKeyword) |
|||
: true; |
|||
return categoryMatch && keywordMatch; |
|||
}); |
|||
}, |
|||
|
|||
// 排序处理 |
|||
sortedProducts() { |
|||
if (this.selectedSort === "price_asc") { |
|||
return this.filteredProducts.sort((a, b) => a.price - b.price); |
|||
} else if (this.selectedSort === "price_desc") { |
|||
return this.filteredProducts.sort((a, b) => b.price - a.price); |
|||
} |
|||
return this.filteredProducts; |
|||
}, |
|||
|
|||
// 分页后商品 |
|||
visibleProducts() { |
|||
const start = (this.currentPage - 1) * this.pageSize; |
|||
const end = start + this.pageSize; |
|||
return this.sortedProducts.slice(start, end); |
|||
}, |
|||
|
|||
// 商品总数 |
|||
totalProducts() { |
|||
return this.sortedProducts.length; |
|||
}, |
|||
}, |
|||
methods: { |
|||
// 搜索 |
|||
handleSearch() { |
|||
this.currentPage = 1; |
|||
}, |
|||
|
|||
// 分页大小改变 |
|||
handleSizeChange(val) { |
|||
this.pageSize = val; |
|||
this.currentPage = 1; |
|||
}, |
|||
|
|||
// 当前页改变 |
|||
handleCurrentChange(val) { |
|||
this.currentPage = val; |
|||
}, |
|||
|
|||
// 加入购物车 |
|||
addToCart(product) { |
|||
this.$message.success(`${product.name} 已加入购物车`); |
|||
}, |
|||
|
|||
// 跳转详情 |
|||
goToDetail(id) { |
|||
this.$router.push(`/product/${id}`); |
|||
}, |
|||
}, |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.product-grid-page { |
|||
padding: 20px; |
|||
background-color: #fff; |
|||
|
|||
// 筛选栏样式(设计稿无则隐藏) |
|||
.filter-bar { |
|||
display: flex; |
|||
gap: 15px; |
|||
margin-bottom: 20px; |
|||
flex-wrap: wrap; |
|||
|
|||
.filter-select { |
|||
min-width: 150px; |
|||
} |
|||
|
|||
.search-input { |
|||
flex: 1; |
|||
max-width: 300px; |
|||
} |
|||
} |
|||
|
|||
// 商品网格布局(4列响应式) |
|||
.product-grid { |
|||
display: grid; |
|||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); |
|||
gap: 20px; |
|||
} |
|||
|
|||
// 商品卡片 |
|||
.product-card { |
|||
background: #fff; |
|||
border-radius: 8px; |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); |
|||
overflow: hidden; |
|||
transition: transform 0.3s, box-shadow 0.3s; |
|||
cursor: pointer; |
|||
position: relative; |
|||
|
|||
&:hover { |
|||
transform: translateY(-5px); |
|||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
// 商品图片容器 |
|||
.product-img { |
|||
width: 100%; |
|||
height: 200px; |
|||
border-radius: 8px 8px 0 0; |
|||
} |
|||
|
|||
// 标签(设计稿无则注释) |
|||
.tag { |
|||
position: absolute; |
|||
top: 10px; |
|||
left: 10px; |
|||
padding: 4px 8px; |
|||
border-radius: 4px; |
|||
color: #fff; |
|||
font-size: 12px; |
|||
z-index: 1; |
|||
} |
|||
|
|||
// 商品信息区 |
|||
.product-info { |
|||
padding: 15px; |
|||
position: relative; |
|||
|
|||
.product-name { |
|||
font-size: 14px; |
|||
font-weight: 500; |
|||
margin-bottom: 8px; |
|||
line-height: 1.4; |
|||
height: 36px; |
|||
overflow: hidden; |
|||
display: -webkit-box; |
|||
-webkit-line-clamp: 2; |
|||
-webkit-box-orient: vertical; |
|||
} |
|||
|
|||
.price-row { |
|||
display: flex; |
|||
align-items: baseline; |
|||
gap: 8px; |
|||
margin-bottom: 10px; |
|||
|
|||
.current-price { |
|||
font-size: 16px; |
|||
color: #ff4d4f; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
.original-price { |
|||
font-size: 12px; |
|||
color: #999; |
|||
text-decoration: line-through; |
|||
} |
|||
} |
|||
|
|||
.cart-btn { |
|||
position: absolute; |
|||
bottom: 15px; |
|||
right: 15px; |
|||
padding: 4px 12px; |
|||
font-size: 12px; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 加载占位 |
|||
.image-placeholder { |
|||
width: 100%; |
|||
height: 200px; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
background-color: #f8f8f8; |
|||
color: #ccc; |
|||
} |
|||
|
|||
// 空状态 |
|||
.empty-state { |
|||
text-align: center; |
|||
padding: 50px 0; |
|||
} |
|||
|
|||
// 分页容器 |
|||
.pagination-container { |
|||
display: flex; |
|||
justify-content: center; |
|||
margin-top: 20px; |
|||
} |
|||
} |
|||
|
|||
// 响应式适配(平板/手机) |
|||
@media (max-width: 768px) { |
|||
.product-grid { |
|||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); |
|||
} |
|||
|
|||
.product-card .product-img { |
|||
height: 160px; |
|||
} |
|||
} |
|||
</style> |
Loading…
Reference in new issue