Browse Source

提交

master
chenkainan 2 months ago
parent
commit
9245ce5b38
  1. 7
      src/App.vue
  2. 4
      src/components/layout/HomeLayout.vue
  3. 165
      src/components/layout/Sidebar.vue
  4. 674
      src/components/product/Evaluate.vue
  5. 1
      src/main.js
  6. 9
      src/router/index.js
  7. 22
      src/views/Detail/Index.vue
  8. 540
      src/views/ProductPage/Index.vue

7
src/App.vue

@ -16,6 +16,9 @@
<router-view />
</main>
<!-- 侧边栏 -->
<Sidebar v-if="$route.name !== 'Login' && $route.name !== 'Register'" />
<!-- 页脚 -->
<Footer v-if="$route.name !== 'Login' && $route.name !== 'Register'" />
@ -26,7 +29,8 @@
<script>
import HeaderNav from "./components/layout/HeaderNav";
import Footer from './components/layout/Footer'
import Footer from './components/layout/Footer';
import Sidebar from './components/layout/Sidebar';
import BackToTop from "./components/common/BackToTop";
import { mapGetters } from "vuex";
@ -36,6 +40,7 @@ export default {
HeaderNav,
Footer,
BackToTop,
Sidebar
},
computed: {
...mapGetters(["getLoadingStatus"]),

4
src/components/layout/HomeLayout.vue

@ -1,7 +1,7 @@
<template>
<div class="home-layout-container">
<!-- 左侧导航栏 -->
<div class="left-nav">
<router-link to="/productList" class="left-nav">
<ul class="nav-list">
<li class="nav-item"><i class="el-icon-sell"></i>全部商品</li>
<li class="nav-item"><i class="el-icon-burger"></i>米面油</li>
@ -11,7 +11,7 @@
<li class="nav-item"><i class="el-icon-grape"></i>鲜果蔬菜</li>
<li class="nav-item"><i class="el-icon-present"></i>组合集市</li>
</ul>
</div>
</router-link>
<!-- 轮播图 -->
<div class="main-content">

165
src/components/layout/Sidebar.vue

@ -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>

674
src/components/product/Evaluate.vue

@ -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>

1
src/main.js

@ -14,7 +14,6 @@ Vue.use(utils)
// 全局配置
Vue.config.productionTip = false
Vue.use(ElementUI)
Vue.prototype.$http = axios
// 配置图片懒加载
Vue.use(VueLazyload, {

9
src/router/index.js

@ -34,6 +34,15 @@ const router = new Router({
},
component: () => import('@/views/Detail/Index.vue')
},
{
path: '/productList',
name: 'ProductList',
meta: {
title: '商品列表',
keepAlive: false
},
component: () => import('@/views/ProductPage/Index.vue')
},
// {
// path: '/category/:id?',
// name: 'Category',

22
src/views/Detail/Index.vue

@ -138,20 +138,35 @@
<!-- 右侧 -->
<div class="product-right">
<div class="product-tabs">
<span class="tab-item active">商品详情</span>
<span class="tab-item">商品评价(125)</span>
<span
:class="['tab-item', { active: !tabIndex }]"
@click="tabIndex = 0"
>商品详情</span
>
<span
:class="['tab-item', { active: tabIndex }]"
@click="tabIndex = 1"
>商品评价(125)</span
>
</div>
<!-- 商品详情 -->
<div class="product-detail-main"></div>
<div class="product-detail-main" v-show="!tabIndex"></div>
<!-- 商品评价 -->
<Evaluate v-show="tabIndex" />
</div>
</div>
</div>
</template>
<script>
import Evaluate from "@/components/product/Evaluate.vue";
export default {
name: "ProductDetail",
components: {
Evaluate,
},
data() {
return {
productImages: [
@ -200,6 +215,7 @@ export default {
price: 359,
},
],
tabIndex: 0,
};
},
methods: {

540
src/views/ProductPage/Index.vue

@ -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…
Cancel
Save