daShangDao_miniProgram/components/BookProductList.vue
2025-11-24 10:25:20 +08:00

865 lines
22 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view>
<!-- 比价和筛选按钮 -->
<view class="button-group">
<u-popup :show="popupShow" @close="close" @open="open" mode="bottom" round="10">
<view class="filter-popup">
<view class="filter-section">
<view class="section-title">出版社</view>
<view class="tag-group">
<u-tag v-for="(item, index) in publisherOptions" :key="index" v-if="item.visible"
:text="item.publisher" :plain="!item.checked" size="large" :name="index"
@click="togglePublisherFilter(index)" />
</view>
</view>
<view class="filter-section">
<view class="section-title">作者</view>
<view class="tag-group">
<u-tag v-for="(item, index) in authorOptions" :key="index" v-if="item.visible"
:text="item.author" :plain="!item.checked" size="large" :name="index"
@click="toggleAuthorFilter(index)" />
</view>
</view>
<view class="filter-buttons">
<u-button text="重置" type="info" plain @click="resetFilters" />
<u-button text="确定" type="primary" @click="applyFilters" />
</view>
</view>
</u-popup>
</view>
<view class="book-list">
<view class="product-header">
<view class="blue-block"></view>
<view class="title-text">
在售商品(总价/书价/运费)
</view>
<view class="header-buttons">
<view class="btn" :class="{ 'btn-loading': isLoading }" @click="switchCompareType"
v-if="showCompareButton">
<u-loading-icon v-if="isLoading" mode="circle" size="20" color="#ffffff"></u-loading-icon>
<text v-else>{{ compareType === 'isbn' ? '书名比价' : 'ISBN比价' }}</text>
</view>
<view class="btn" :class="{ 'btn-loading': isLoading }" @click="handleCopyrightCompare"
v-if="showCopyrightButton">
<u-loading-icon v-if="isLoading" mode="circle" size="20" color="#ffffff"></u-loading-icon>
<text v-else>版权页比价</text>
</view>
<view class="btn" @click="popupShow = true" v-if="showFilterButton">更多筛选</view>
</view>
</view>
<!-- 在售商品展示区域 -->
<view class="container" v-if="(isFiltered ? filteredOnSaleProducts : displayOnSaleProducts).length > 0">
<view class="product-grid">
<view class="product-card"
v-for="(product, index) in isFiltered ? filteredOnSaleProducts : displayOnSaleProducts"
:key="index">
<view class="product-image-container">
<image class="product-image" :src="product.imageUrl" mode="aspectFit"
@click="previewImage(product.imageUrl, isFiltered ? filteredOnSaleProducts : displayOnSaleProducts)">
</image>
<view class="price-details">
<text>{{product.totalPrice}}/{{product.bookPrice}}/{{product.shippingFee}}</text>
</view>
</view>
<view class="product-info">
<text class="product-price"
style="font-size: 26rpx;">{{product.totalPrice}}/{{product.qualityText}}</text>
</view>
</view>
</view>
</view>
<view class="empty-products" v-else>
<text>暂无在售商品信息</text>
</view>
</view>
</view>
</template>
<script>
import { checkMemberBooksCount } from '@/components/MemberBookCheck.js';
export default {
name: 'BookProductList',
props: {
// 接收初始数据
initialOnSaleProducts: {
type: Array,
default: () => []
},
initialDisplayOnSaleProducts: {
type: Array,
default: () => []
},
initialCompareType: {
type: String,
default: 'isbn'
},
isbn: {
type: String,
default: ''
},
bookName: {
type: String,
default: ''
},
showCompareButton: {
type: Boolean,
default: true // 默认显示比价按钮
},
showCopyrightButton: {
type: Boolean,
default: false
},
publisher: {
type: String,
default: ''
},
author: {
type: String,
default: ''
},
showFilterButton: {
type: Boolean,
default: true
}
},
data() {
return {
// 筛选相关
popupShow: false,
compareType: this.initialCompareType,
isLoading: false,
// 数据相关
onSaleProducts: this.initialOnSaleProducts,
displayOnSaleProducts: this.initialDisplayOnSaleProducts,
filteredOnSaleProducts: [],
// 筛选选项
publisherOptions: [],
authorOptions: [],
isFiltered: false,
// 关联映射
publisherAuthorMap: new Map(),
authorPublisherMap: new Map(),
// 添加排序类型
currentSortType: uni.getStorageSync('sortType') || '7', // 默认按总价排序
};
},
watch: {
initialOnSaleProducts: {
handler(newValue) {
this.onSaleProducts = newValue;
this.extractPublishersAndAuthors();
},
immediate: true
},
initialDisplayOnSaleProducts: {
handler(newValue) {
this.displayOnSaleProducts = newValue;
this.filteredOnSaleProducts = [...newValue];
},
immediate: true
},
initialCompareType: {
handler(newValue) {
this.compareType = newValue;
},
immediate: true
},
// 添加对排序类型的监听
currentSortType: {
handler(newSortType) {
if (this.onSaleProducts && this.onSaleProducts.length > 0) {
this.sortProducts(newSortType);
}
},
immediate: true
}
},
mounted() {
// 组件挂载时提取出版社和作者信息
this.extractPublishersAndAuthors();
},
methods: {
setCompareType(type) {
this.compareType = type;
},
// 打开和关闭筛选弹窗
open() {
this.popupShow = true;
},
close() {
this.popupShow = false;
},
// 切换比价类型
async switchCompareType() {
// 检查用户是否可以上传书籍
const canUpload = await checkMemberBooksCount();
if (!canUpload) {
// 如果不能上传,则直接返回,不继续执行
return;
}
// 如果正在加载中,直接返回
if (this.isLoading) return;
try {
// 设置加载状态
this.isLoading = true;
const newType = this.compareType === 'isbn' ? 'title' : 'isbn';
this.compareType = newType;
// 发出类型切换事件
await this.$emit('compare-type-change', {
type: newType,
value: newType === 'isbn' ? this.isbn : this.bookName
});
} catch (error) {
console.error('切换比价类型失败:', error);
// 检查是否是重复提交错误
if (error.responseData && error.responseData.code === 500 &&
error.responseData.msg === "Repeat submit is not allowed, please try again later") {
uni.showToast({
title: '操作太频繁,请稍后再试',
icon: 'none',
duration: 2000
});
} else {
uni.showToast({
title: '切换失败,请稍后重试',
icon: 'none'
});
}
} finally {
// 延迟关闭加载状态,避免闪烁
setTimeout(() => {
this.isLoading = false;
}, 500);
}
},
// 提取出版社和作者
extractPublishersAndAuthors() {
if (!this.onSaleProducts || this.onSaleProducts.length === 0) return;
// 创建出版社-作者关联映射
const publisherAuthorMap = new Map();
const authorPublisherMap = new Map();
// 遍历商品数据,建立关联关系
this.onSaleProducts.forEach(product => {
if (product.publisher && product.author) {
// 添加出版社-作者关联
if (!publisherAuthorMap.has(product.publisher)) {
publisherAuthorMap.set(product.publisher, new Set());
}
publisherAuthorMap.get(product.publisher).add(product.author);
// 添加作者-出版社关联
if (!authorPublisherMap.has(product.author)) {
authorPublisherMap.set(product.author, new Set());
}
authorPublisherMap.get(product.author).add(product.publisher);
}
});
// 保存关联映射
this.publisherAuthorMap = publisherAuthorMap;
this.authorPublisherMap = authorPublisherMap;
// 提取出版社
const publishers = new Set();
this.onSaleProducts.forEach(product => {
if (product.publisher) {
publishers.add(product.publisher);
}
});
// 提取作者
const authors = new Set();
this.onSaleProducts.forEach(product => {
if (product.author) {
authors.add(product.author);
}
});
// 转换为选项数组
this.publisherOptions = Array.from(publishers).map(publisher => ({
publisher,
checked: false,
visible: true
}));
this.authorOptions = Array.from(authors).map(author => ({
author,
checked: false,
visible: true
}));
},
// 切换出版社筛选
togglePublisherFilter(index) {
// 切换选中状态
this.publisherOptions[index].checked = !this.publisherOptions[index].checked;
// 获取所有选中的出版社
const selectedPublishers = this.publisherOptions
.filter(item => item.checked)
.map(item => item.publisher);
// 如果没有选中任何出版社,显示所有作者
if (selectedPublishers.length === 0) {
this.authorOptions.forEach(item => {
item.visible = true;
});
} else {
// 获取当前选中的出版社
const currentPublisher = this.publisherOptions[index].publisher;
// 如果取消选中,需要重新计算可见的作者
if (!this.publisherOptions[index].checked) {
// 重置所有作者的可见性
this.authorOptions.forEach(item => {
item.visible = false;
});
// 根据剩余选中的出版社更新作者可见性
selectedPublishers.forEach(publisher => {
if (this.publisherAuthorMap.has(publisher)) {
const relatedAuthors = this.publisherAuthorMap.get(publisher);
this.authorOptions.forEach(item => {
if (relatedAuthors.has(item.author)) {
item.visible = true;
}
});
}
});
} else {
// 如果是新选中,只显示与该出版社相关的作者
this.authorOptions.forEach(item => {
// 检查该作者是否与当前选中的出版社相关
if (this.publisherAuthorMap.has(currentPublisher)) {
const relatedAuthors = this.publisherAuthorMap.get(currentPublisher);
item.visible = relatedAuthors.has(item.author);
} else {
item.visible = false;
}
});
}
}
// 取消选中不可见的作者
this.authorOptions.forEach(item => {
if (!item.visible) {
item.checked = false;
}
});
},
// 切换作者筛选
toggleAuthorFilter(index) {
// 切换选中状态
this.authorOptions[index].checked = !this.authorOptions[index].checked;
// 获取所有选中的作者
const selectedAuthors = this.authorOptions
.filter(item => item.checked)
.map(item => item.author);
// 如果没有选中任何作者,显示所有出版社
if (selectedAuthors.length === 0) {
this.publisherOptions.forEach(item => {
item.visible = true;
});
} else {
// 获取当前选中的作者
const currentAuthor = this.authorOptions[index].author;
// 如果取消选中,需要重新计算可见的出版社
if (!this.authorOptions[index].checked) {
// 重置所有出版社的可见性
this.publisherOptions.forEach(item => {
item.visible = false;
});
// 根据剩余选中的作者更新出版社可见性
selectedAuthors.forEach(author => {
if (this.authorPublisherMap.has(author)) {
const relatedPublishers = this.authorPublisherMap.get(author);
this.publisherOptions.forEach(item => {
if (relatedPublishers.has(item.publisher)) {
item.visible = true;
}
});
}
});
} else {
// 如果是新选中,只显示与该作者相关的出版社
this.publisherOptions.forEach(item => {
// 检查该出版社是否与当前选中的作者相关
if (this.authorPublisherMap.has(currentAuthor)) {
const relatedPublishers = this.authorPublisherMap.get(currentAuthor);
item.visible = relatedPublishers.has(item.publisher);
} else {
item.visible = false;
}
});
}
}
// 取消选中不可见的出版社
this.publisherOptions.forEach(item => {
if (!item.visible) {
item.checked = false;
}
});
},
// 重置筛选
resetFilters() {
// 重置出版社选项
this.publisherOptions.forEach(item => {
item.checked = false;
item.visible = true;
});
// 重置作者选项
this.authorOptions.forEach(item => {
item.checked = false;
item.visible = true;
});
// 重置筛选状态
this.isFiltered = false;
// 恢复排序后的数据
this.filteredOnSaleProducts = [...this.displayOnSaleProducts];
// 关闭弹窗
this.popupShow = false;
// 发出重置事件
this.$emit('filters-reset');
},
// 应用筛选
applyFilters() {
// 获取选中的出版社
const selectedPublishers = this.publisherOptions
.filter(item => item.checked)
.map(item => item.publisher);
// 获取选中的作者
const selectedAuthors = this.authorOptions
.filter(item => item.checked)
.map(item => item.author);
// 如果没有选择任何筛选条件,则显示原始数据
if (selectedPublishers.length === 0 && selectedAuthors.length === 0) {
this.filteredOnSaleProducts = [...this.displayOnSaleProducts];
this.isFiltered = false;
} else {
// 筛选在售商品
let filteredProducts = this.onSaleProducts.filter(product => {
const matchPublisher = selectedPublishers.length === 0 ||
(product.publisher && selectedPublishers.includes(product.publisher));
const matchAuthor = selectedAuthors.length === 0 ||
(product.author && selectedAuthors.includes(product.author));
return matchPublisher && matchAuthor;
});
// 对筛选后的结果按总价从低到高排序
filteredProducts = filteredProducts.sort((a, b) => {
const priceA = parseFloat(a.totalPrice) || 0;
const priceB = parseFloat(b.totalPrice) || 0;
return priceA - priceB;
});
// 只保留前10条数据
this.filteredOnSaleProducts = filteredProducts.slice(0, 10);
this.isFiltered = true;
}
// 关闭弹窗
this.popupShow = false;
// 发出筛选结果事件
this.$emit('filters-applied', this.filteredOnSaleProducts);
},
// 预览图片
previewImage(currentUrl, productList) {
// 检查productList是否存在且是数组
let urls = [];
if (productList && Array.isArray(productList)) {
// 提取所有图片 URL
urls = productList.map(item => item.imageUrl);
} else {
// 如果productList不存在或不是数组则只使用当前URL
urls = [currentUrl];
}
// 确保urls中至少有一个有效的URL
if (!urls.length || !urls[0]) {
console.error('没有有效的图片URL可预览');
return;
}
// 调用 UniApp 预览接口,添加长按操作
uni.previewImage({
current: currentUrl, // 当前显示图片
urls: urls, // 所有图片列表
longPressActions: {
itemList: ['保存图片', '分享图片'],
success: function(data) {
console.log('选择了第' + (data.tapIndex + 1) + '个按钮');
},
fail: function(err) {
console.log(err.errMsg);
}
},
success: () => {},
fail: (err) => {
console.error('预览失败:', err);
}
});
},
// 修改 updateProducts 方法
updateProducts(products) {
this.onSaleProducts = products;
this.sortProducts(this.currentSortType);
this.extractPublishersAndAuthors();
},
// 添加更新排序类型的方法
updateSortType(sortType) {
console.log('更新排序类型:', sortType);
this.currentSortType = sortType;
// 直接对现有数据进行排序
if (this.onSaleProducts && this.onSaleProducts.length > 0) {
this.sortProducts(sortType);
}
// 触发父组件重新获取数据
this.$emit('compare-type-change', {
type: this.compareType,
value: this.compareType === 'isbn' ? this.isbn : this.bookName,
sortType: sortType
});
},
// 修改 sortProducts 方法
sortProducts(sortType) {
console.log("123131231", sortType)
if (!this.onSaleProducts || this.onSaleProducts.length === 0) return;
// 根据排序方式选择排序字段
const sortField = sortType === '5' ? 'bookPrice' : 'totalPrice';
// 对商品数据按照选择的字段从低到高排序
const sortedData = [...this.onSaleProducts].sort((a, b) => {
const priceA = parseFloat(a[sortField]) || 0;
const priceB = parseFloat(b[sortField]) || 0;
return priceA - priceB;
});
// 只保留前12条数据用于展示
this.displayOnSaleProducts = sortedData.slice(0, 12);
this.filteredOnSaleProducts = [...this.displayOnSaleProducts];
this.isFiltered = false;
// 发出数据更新事件
this.$emit('products-updated', this.displayOnSaleProducts);
},
// 获取当前显示的商品数据
getCurrentProducts() {
return this.isFiltered ? this.filteredOnSaleProducts : this.displayOnSaleProducts;
},
// 获取筛选状态
getFilterStatus() {
return {
isFiltered: this.isFiltered,
selectedPublishers: this.publisherOptions.filter(item => item.checked).map(item => item.publisher),
selectedAuthors: this.authorOptions.filter(item => item.checked).map(item => item.author)
};
},
// 添加版权页比价方法
async handleCopyrightCompare() {
if (this.isLoading) return;
try {
this.isLoading = true;
// 打印传递参数日志
console.log('【版权页比价按钮参数】', {
bookName: this.bookName,
publisher: this.publisher,
author: this.author
});
// 发出版权页比价事件,传递出版社和作者信息
await this.$emit('copyright-compare', {
type: 'copyright',
bookName: this.bookName,
publisher: this.publisher,
author: this.author
});
} catch (error) {
console.error('版权页比价失败:', error);
uni.showToast({
title: '比价失败,请稍后重试',
icon: 'none'
});
} finally {
setTimeout(() => {
this.isLoading = false;
}, 500);
}
}
}
}
</script>
<style scoped>
.book-list {
margin-top: 20rpx;
display: flex;
flex-wrap: wrap;
}
.book-list>.label {
padding: 10rpx;
display: block;
width: calc(100% - 20rpx);
height: 40rpx;
color: #333;
border-bottom: 2rpx solid #ccc;
}
.book-list>.label>.btn {
display: inline-block;
padding: 10rpx;
text-align: center;
width: 120rpx;
height: 30rpx;
line-height: 30rpx;
color: #fff;
background-color: #3c9cff;
border-radius: 10%;
margin-right: 10rpx;
}
.book-list>.container {
position: relative;
width: 100%;
overflow: hidden;
}
.book-list>.container>.product-grid {
display: flex;
flex-wrap: wrap;
gap: 10rpx;
}
.book-list>.container>.product-grid>.product-card {
width: calc(25% - 8rpx);
background-color: #ffffff;
border-radius: 12rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
overflow: hidden;
flex-shrink: 0;
height: 180rpx;
display: flex;
flex-direction: column;
position: relative;
}
.book-list>.container>.product-grid>.product-card>.product-image-container {
position: relative;
width: 100%;
height: 140rpx;
overflow: hidden;
}
.book-list>.container>.product-grid>.product-card>.product-image-container>.product-image {
width: 100%;
height: 100%;
object-fit: contain;
flex: 0 0 auto;
background-color: #f5f5f5;
}
.book-list>.container>.product-grid>.product-card>.product-image-container>.product-info {
padding: 8rpx;
font-size: 24rpx;
flex: 1;
overflow: hidden;
text-align: center;
}
.book-list>.container>.product-grid>.product-card>.product-image-container>.product-info>.product-price {
font-size: 28rpx;
font-weight: bold;
color: #ff6b00;
display: block;
margin-bottom: 4rpx;
text-align: center;
}
.book-list>.container>.product-grid>.product-card>.product-image-container>.price-details {
font-size: 22rpx;
line-height: 1.2;
overflow: hidden;
text-overflow: ellipsis;
display: flex;
flex-direction: row;
justify-content: center;
gap: 8rpx;
color: #ffffff;
background-color: rgba(0, 0, 0, 0.5);
padding: 4rpx 0;
border-radius: 0 0 8rpx 8rpx;
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: 1;
}
.button-group {
display: flex;
align-items: center;
gap: 20rpx;
margin: 10rpx 0;
}
.filter-section {
margin-bottom: 30rpx;
}
.section-title {
font-size: 28rpx;
color: #333;
}
.filter-popup {
padding: 30rpx;
background-color: #fff;
}
.filter-buttons {
display: flex;
justify-content: space-between;
margin-top: 40rpx;
}
.tag-group {
display: flex;
flex-wrap: wrap;
gap: 10rpx;
margin-top: 10rpx;
}
.empty-products {
padding: 40rpx 0;
text-align: center;
color: #999;
font-size: 28rpx;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
}
/* 响应式布局 */
@media screen and (max-width: 768rpx) {
.book-list>.container>.product-grid>.product-card {
width: calc(25% - 7rpx);
}
}
@media screen and (max-width: 480rpx) {
.book-list>.container>.product-grid>.product-card {
width: calc(50% - 5rpx);
}
.book-list>.container>.product-grid>.product-card>.product-image-container {
height: 200rpx;
}
}
/* 新增商品列表标题栏样式 */
.product-header {
padding: 10rpx;
display: flex;
justify-content: space-between;
align-items: center;
width: calc(100% - 20rpx);
height: 40rpx;
color: #333;
border-bottom: 2rpx solid #ccc;
}
.title-text {
font-size: 28rpx;
flex: 1;
text-align: left;
}
.header-buttons {
display: flex;
gap: 10rpx;
}
.header-buttons .btn {
display: flex;
align-items: center;
justify-content: center;
padding: 6rpx 14rpx;
text-align: center;
width: 120rpx;
height: 30rpx;
line-height: 30rpx;
color: #fff;
background-color: #3c9cff;
border-radius: 10rpx;
font-size: 24rpx;
}
.header-buttons .btn {
display: flex;
align-items: center;
justify-content: center;
padding: 6rpx 14rpx;
text-align: center;
width: 120rpx;
height: 30rpx;
line-height: 30rpx;
color: #fff;
background-color: #3c9cff;
border-radius: 10rpx;
font-size: 24rpx;
transition: all 0.3s ease;
}
/* 添加加载状态的样式 */
.btn-loading {
opacity: 0.7;
pointer-events: none;
}
/* 确保加载图标居中显示 */
.header-buttons .btn .u-loading-icon {
margin: 0 auto;
}
.blue-block {
width: 8rpx;
height: 32rpx;
background-color: #007AFF;
margin-right: 16rpx;
border-radius: 4rpx;
}
</style>