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