You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

493 lines
12 KiB

<template>
<div class="product-grid-page">
<!-- 筛选排序区 -->
<div class="filter-bar" v-if="showFilter">
<!-- 分类弹框 -->
<el-select
v-model="selectedCategory"
placeholder="全部分类"
class="filter-select"
@change="handleCategoryChange"
>
<el-option
v-for="cat in categories"
:key="cat.id"
:label="cat.name"
:value="cat.id"
></el-option>
</el-select>
<!-- 排序选项 -->
<el-select
v-model="selectedSort"
placeholder="综合排序"
class="filter-select"
@change="handleSortChange"
>
<el-option label="综合排序" value="default"></el-option>
<el-option label="销量↑" value="sales_asc"></el-option>
<el-option label="销量↓" value="sales_desc"></el-option>
<el-option label="价格↑" value="price_asc"></el-option>
<el-option label="价格↓" value="price_desc"></el-option>
</el-select>
</div>
<!-- 搜索结果统计 -->
<div class="result-stats" v-if="type == 'search'">
<span>全部结果 ></span>
<span class="keyword" v-if="searchKeyword">"{{ searchKeyword }}"</span>
<span
v-if="
selectedCategory &&
categories.find((cat) => cat.value === selectedCategory)
"
class="category"
>
"{{ categories.find((cat) => cat.value === selectedCategory).label }}"
</span>
<span>共{{ totalProducts }}个结果</span>
</div>
<!-- 商品网格 -->
<div class="product-grid">
<div
class="product-card"
v-for="(product, index) in visibleProducts"
:key="index"
@click="goToDetail(product.id)"
>
<img
v-lazy="product.headimg"
:alt="product.title"
class="product-img"
fit="cover"
/>
<!-- <div slot="placeholder" class="image-placeholder">
<i class="el-icon-loading"></i>
</div> -->
<!-- </img> -->
<div class="product-info">
<div class="product-name">{{ product.title }}</div>
<div class="price-row">
<span class="current-price">¥{{ product.price / 100 }}</span>
<span class="original-price" v-if="product.market_price"
>¥{{ product.market_price / 100 }}</span
>
</div>
<div class="sales-volume">
<i class="el-icon-shopping-cart"></i> 已售
{{ product.sales_number }}
</div>
<el-button type="primary" size="mini" class="cart-btn">
立即购买
</el-button>
</div>
</div>
<div class="empty-state" v-if="visibleProducts.length === 0">
<el-empty description="暂无符合条件的商品"></el-empty>
</div>
</div>
<!-- 分页组件 -->
<div class="pagination-container" v-if="totalProducts > 0">
<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>
import { mapGetters } from "vuex";
export default {
name: "ProductGridPage",
data() {
return {
type: "",
// 搜索关键词
searchKeyword: "",
// 筛选/排序条件
selectedCategory: "",
selectedSort: "default", // 默认综合排序
showFilter: true,
// 分页控制
currentPage: 1,
pageSize: 12,
// 商品数据
visibleProducts: [],
totalProducts: 0,
// 分类选项
categories: [],
id: "",
keyword: "",
};
},
computed: {
...mapGetters(["getSearchText"]),
// 计算当前页对应的offset
currentOffset() {
return (this.currentPage - 1) * this.pageSize;
},
},
watch: {
// 监听搜索词变化
getSearchText(newVal, oldVal) {
if (newVal !== oldVal) {
this.searchKeyword = newVal;
this.fetchProducts(); // 调用接口获取数据
}
},
},
created() {
this.type = this.$route.query?.type;
this.searchKeyword = this.getSearchText;
this.getTagList();
},
// 组件销毁时移除事件监听(避免内存泄漏)
beforeDestroy() {
// 清空搜索词
this.searchKeyword = "";
// 同时更新Vuex中的搜索词状态
this.$store.commit("setSearchText", "");
// 重置分页和筛选条件(可选)
this.currentPage = 1;
this.selectedCategory = "";
this.selectedSort = "default";
},
methods: {
// 获取分类
getTagList() {
this.post(
{
pid: this.VUE_APP_GLOBAL_TAGS.side_all,
},
"/api/product/tag_list"
).then((res) => {
this.categories = res.data;
let ids = [];
// 先请求pc_all的标签列表
this.post(
{
pid: this.VUE_APP_GLOBAL_TAGS.pc_all,
},
"/api/product/tag_list"
).then((res) => {
// 收集当前层级的id
res.data.forEach((item) => ids.push(item.id));
// 创建所有子标签请求的Promise数组
const subTagPromises = res.data.map((item) =>
this.post({ pid: item.id }, "/api/product/tag_list").then(
(subRes) => {
// 收集子标签的id
subRes.data.forEach((i) => ids.push(i.id));
}
)
);
// 等待所有子标签请求完成
Promise.all(subTagPromises).then(() => {
// 所有id收集完成后再进行后续操作
this.categories[0].id = ids.join(",");
// 获取路由中的id参数
const routeId = this.$route.query.id;
if (routeId) {
// 存储路由id
this.id = routeId;
// 查找匹配的分类并选中
const matchedCategory = this.categories.find(
(cat) => cat.id == routeId
);
console.log(routeId, matchedCategory);
if (matchedCategory) {
this.selectedCategory = matchedCategory.name;
}
this.fetchProducts();
} else {
// 确保在ids获取完成后再赋值并调用接口
this.id = ids.join(",");
this.fetchProducts();
}
});
});
});
},
// 获取列表 - 使用offset和limit参数
async fetchProducts() {
// 构建请求参数
const params = {
tag_id: this.id || this.selectedCategory,
offset: this.currentOffset,
limit: this.pageSize,
title: this.searchKeyword,
...this.parseSortParams(),
};
// 如果有搜索关键词,添加到请求参数中
if (this.searchKeyword) {
params.keyword = this.searchKeyword;
}
const response = await this.post(
params,
"/api/product/get_product_by_tag"
);
// 假设接口返回格式包含list和total字段
this.visibleProducts = response.data.list || [];
this.totalProducts = Number(response.data.total) || 0;
},
// 排序参数
parseSortParams() {
if (this.selectedSort === "default") {
return {}; // 综合排序不需要参数
}
// 拆分排序类型和排序方向
const [sortField, order] = this.selectedSort.split("_");
// 转换为接口需要的字段名
const sortMap = {
sales: "sales_number",
price: "price",
};
return {
sort: sortMap[sortField],
order: order,
};
},
// 分类变更处理
handleCategoryChange(event) {
this.id = event;
this.currentPage = 1; // 切换分类后重置到第一页
this.fetchProducts();
},
// 排序变更处理
handleSortChange() {
this.currentPage = 1; // 切换排序后重置到第一页
this.fetchProducts();
},
// 分页大小改变
handleSizeChange(val) {
this.pageSize = val;
this.currentPage = 1; // 改变每页条数时重置到第一页
this.fetchProducts();
},
// 当前页改变
handleCurrentChange(val) {
this.currentPage = val;
this.fetchProducts();
// 滚动到页面顶部
window.scrollTo(0, 0);
},
// 跳转详情页
goToDetail(id) {
this.$router.push(`/Detail/${id}`);
},
},
};
</script>
<style lang="scss" scoped>
/* 样式部分保持不变 */
.product-grid-page {
padding: 20px;
background-color: #f5f7fa;
max-width: 1400px;
margin: 0 auto;
// 筛选栏样式
.filter-bar {
display: flex;
gap: 15px;
margin-bottom: 20px;
flex-wrap: wrap;
align-items: center;
padding: 15px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
.search-input {
flex: 1;
min-width: 200px;
}
.filter-select {
min-width: 140px;
}
}
// 搜索结果统计样式
.result-stats {
margin: 0 0 15px 5px;
color: #666;
font-size: 14px;
padding: 5px 0;
.keyword,
.category {
color: #ff4d4f;
font-weight: 500;
margin: 0 5px;
}
}
// 商品网格布局
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
// 商品卡片
.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 8px 16px rgba(0, 0, 0, 0.1);
}
.product-img {
width: 100%;
height: 200px;
background-color: #f5f5f5;
}
.image-placeholder {
width: 100%;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
background-color: #f5f5f5;
}
.tag {
position: absolute;
top: 10px;
left: 10px;
padding: 3px 8px;
font-size: 12px;
color: #fff;
border-radius: 4px;
z-index: 1;
}
.product-info {
padding: 15px;
.product-name {
font-size: 14px;
color: #333;
margin-bottom: 10px;
height: 40px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.price-row {
display: flex;
align-items: center;
margin-bottom: 8px;
.current-price {
color: #ff4d4f;
font-weight: bold;
font-size: 16px;
}
.original-price {
color: #999;
font-size: 12px;
text-decoration: line-through;
margin-left: 8px;
}
}
.sales-volume {
font-size: 12px;
color: #666;
margin-bottom: 10px;
display: flex;
align-items: center;
i {
font-size: 12px;
margin-right: 4px;
}
}
.cart-btn {
width: 100%;
}
}
}
// 空状态样式
.empty-state {
grid-column: 1 / -1;
padding: 60px 0;
text-align: center;
}
// 分页容器
.pagination-container {
display: flex;
justify-content: center;
margin-top: 20px;
padding: 10px;
}
}
// 响应式调整
@media (max-width: 768px) {
.product-grid-page {
padding: 10px;
.filter-bar {
padding: 10px;
gap: 10px;
}
.product-grid {
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 10px;
}
.product-card {
.product-img {
height: 140px;
}
.image-placeholder {
height: 140px;
}
}
}
}
</style>