716 lines
18 KiB
Vue
716 lines
18 KiB
Vue
<template>
|
||
<!-- 比价和筛选按钮 -->
|
||
<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="label">
|
||
<view style="display: inline-block;width: 380rpx;text-align: left;">
|
||
在售商品(总价/书价/运费)
|
||
</view>
|
||
|
||
<view class="btn" @click="handleSwitchCompareType">
|
||
{{ compareType === 'isbn' ? '书名比价' : 'ISBN比价' }}
|
||
</view>
|
||
<view class="btn" @click="popupShow = true">更多筛序</view>
|
||
</view>
|
||
<!-- 在售商品展示区域 -->
|
||
<view class="container" v-if="showProducts.length > 0">
|
||
<view class="product-grid">
|
||
<view class="product-card" v-for="(product, index) in showProducts" :key="index">
|
||
<view class="product-image-container">
|
||
<image class="product-image" :src="product.imageUrl" mode="aspectFit"
|
||
@click="previewImage(product.imageUrl, showProducts)">
|
||
</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>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
name: 'price-comparison',
|
||
props: {
|
||
// 当前比价类型
|
||
compareType: {
|
||
type: String,
|
||
default: 'isbn'
|
||
},
|
||
// 书籍标识符(ISBN或书名)
|
||
keyword: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
// 品相值
|
||
conditionValue: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
// 排序类型
|
||
sortType: {
|
||
type: String,
|
||
default: 'price' // 默认按价格排序
|
||
},
|
||
// 阻止自动加载
|
||
preventAutoLoad: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
},
|
||
data() {
|
||
return {
|
||
popupShow: false,
|
||
isFiltered: false,
|
||
publisherOptions: [],
|
||
authorOptions: [],
|
||
allProducts: [], // 存储所有在售商品
|
||
displayProducts: [], // 展示的商品
|
||
filteredProducts: [], // 筛选后的商品
|
||
publisherAuthorMap: {}, // 出版社-作者关系映射
|
||
authorPublisherMap: {}, // 作者-出版社关系映射
|
||
loading: false,
|
||
loadError: null
|
||
};
|
||
},
|
||
computed: {
|
||
// 根据筛选状态决定展示哪些商品
|
||
showProducts() {
|
||
return this.isFiltered ? this.filteredProducts : this.displayProducts;
|
||
}
|
||
},
|
||
watch: {
|
||
keyword: {
|
||
handler(newVal) {
|
||
if (newVal && !this.preventAutoLoad) {
|
||
this.loadProducts();
|
||
}
|
||
},
|
||
immediate: true
|
||
},
|
||
compareType: {
|
||
handler(newVal) {
|
||
if (this.keyword && !this.preventAutoLoad) {
|
||
this.loadProducts();
|
||
}
|
||
}
|
||
}
|
||
},
|
||
methods: {
|
||
// 加载在售商品数据
|
||
async loadProducts() {
|
||
if (!this.keyword) return;
|
||
|
||
this.loading = true;
|
||
this.loadError = null;
|
||
|
||
try {
|
||
// 获取cookies,可以从缓存中读取
|
||
const cookies = uni.getStorageSync('cookies');
|
||
|
||
// 调用API获取在售商品数据
|
||
const response = await this.fetchProductData(this.keyword, this.sortType, this.conditionValue, cookies);
|
||
|
||
// 处理数据
|
||
this.processProductData(response);
|
||
|
||
// 发出加载完成事件
|
||
this.$emit('loaded', {
|
||
products: this.allProducts,
|
||
displayProducts: this.displayProducts
|
||
});
|
||
} catch (error) {
|
||
console.error('获取在售商品信息失败:', error);
|
||
this.loadError = error.message || '加载失败';
|
||
this.$emit('error', error);
|
||
} finally {
|
||
this.loading = false;
|
||
}
|
||
},
|
||
|
||
// 调用API获取数据
|
||
async fetchProductData(keyword, sortType, conditionValue, cookies) {
|
||
return new Promise((resolve, reject) => {
|
||
uni.request({
|
||
url: 'https://api.buzhiyushu.cn/zhishu/baseInfo/getBookList',
|
||
method: 'POST',
|
||
data: {
|
||
keyword,
|
||
sortType,
|
||
conditionValue,
|
||
compareType: this.compareType
|
||
},
|
||
header: {
|
||
'Cookie': cookies || ''
|
||
},
|
||
success: (res) => {
|
||
if (res.statusCode === 200 && res.data) {
|
||
resolve(res.data);
|
||
} else {
|
||
reject(new Error('请求失败: ' + res.statusCode));
|
||
}
|
||
},
|
||
fail: (err) => {
|
||
reject(err);
|
||
}
|
||
});
|
||
});
|
||
},
|
||
|
||
// 处理获取到的数据
|
||
processProductData(data) {
|
||
// 获取屏蔽店铺列表
|
||
const blockedShopsStr = uni.getStorageSync('blockedShops') || '';
|
||
const blockedShops = blockedShopsStr.split(';').filter(shop => shop.trim() !== '');
|
||
|
||
// 过滤掉屏蔽的店铺
|
||
const filteredData = Array.isArray(data) ? data.filter(product => {
|
||
return !blockedShops.some(shop => product.shopName && product.shopName.includes(shop.trim()));
|
||
}) : [];
|
||
|
||
// 保存所有数据用于筛选
|
||
this.allProducts = [...filteredData];
|
||
|
||
// 对商品数据按照总价从低到高排序
|
||
const sortedData = [...filteredData].sort((a, b) => {
|
||
const priceA = parseFloat(a.totalPrice) || 0;
|
||
const priceB = parseFloat(b.totalPrice) || 0;
|
||
return priceA - priceB; // 从低到高排序
|
||
});
|
||
|
||
// 只保留前12条数据用于展示
|
||
this.displayProducts = sortedData.slice(0, 12);
|
||
|
||
// 提取出版社和作者信息,用于筛选选项
|
||
this.extractPublishersAndAuthors();
|
||
|
||
// 计算参考价格
|
||
this.calculateReferencePrice();
|
||
},
|
||
|
||
// 提取出版社和作者信息
|
||
extractPublishersAndAuthors() {
|
||
// 创建映射对象
|
||
const publisherAuthorMap = {};
|
||
const authorPublisherMap = {};
|
||
|
||
// 遍历商品数据,建立关联关系
|
||
this.allProducts.forEach(product => {
|
||
if (product.publisher && product.author) {
|
||
// 添加出版社-作者关联
|
||
if (!publisherAuthorMap[product.publisher]) {
|
||
publisherAuthorMap[product.publisher] = [];
|
||
}
|
||
if (!publisherAuthorMap[product.publisher].includes(product.author)) {
|
||
publisherAuthorMap[product.publisher].push(product.author);
|
||
}
|
||
|
||
// 添加作者-出版社关联
|
||
if (!authorPublisherMap[product.author]) {
|
||
authorPublisherMap[product.author] = [];
|
||
}
|
||
if (!authorPublisherMap[product.author].includes(product.publisher)) {
|
||
authorPublisherMap[product.author].push(product.publisher);
|
||
}
|
||
}
|
||
});
|
||
|
||
// 保存关联映射
|
||
this.publisherAuthorMap = publisherAuthorMap;
|
||
this.authorPublisherMap = authorPublisherMap;
|
||
|
||
// 提取出版社
|
||
const publishers = new Set();
|
||
this.allProducts.forEach(product => {
|
||
if (product.publisher) {
|
||
publishers.add(product.publisher);
|
||
}
|
||
});
|
||
|
||
// 提取作者
|
||
const authors = new Set();
|
||
this.allProducts.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
|
||
}));
|
||
},
|
||
|
||
// 计算参考价格
|
||
calculateReferencePrice() {
|
||
if (!this.displayProducts || this.displayProducts.length === 0) return;
|
||
|
||
// 获取最低价格
|
||
const lowestBookPrice = Math.min(...this.displayProducts.map(p => parseFloat(p.bookPrice) || 0));
|
||
const lowestTotalPrice = Math.min(...this.displayProducts.map(p => parseFloat(p.totalPrice) || 0));
|
||
|
||
// 发出价格计算事件
|
||
this.$emit('price-calculated', {
|
||
lowestBookPrice,
|
||
lowestTotalPrice,
|
||
products: this.displayProducts
|
||
});
|
||
},
|
||
|
||
// 打开和关闭筛选弹窗
|
||
open() {
|
||
this.popupShow = true;
|
||
this.$emit('popup-open');
|
||
},
|
||
|
||
close() {
|
||
this.popupShow = false;
|
||
this.$emit('popup-close');
|
||
},
|
||
|
||
// 切换比价类型
|
||
handleSwitchCompareType() {
|
||
const newType = this.compareType === 'isbn' ? 'title' : 'isbn';
|
||
this.$emit('compare-type-change', newType);
|
||
},
|
||
|
||
// 图片预览
|
||
previewImage(currentUrl, productList) {
|
||
// 检查productList是否存在且是数组
|
||
let urls = [];
|
||
|
||
if (productList && Array.isArray(productList)) {
|
||
// 提取所有图片 URL
|
||
urls = productList.map(item => item.imageUrl);
|
||
} else {
|
||
urls = [currentUrl];
|
||
}
|
||
|
||
// 确保urls中至少有一个有效的URL
|
||
if (!urls.length || !urls[0]) {
|
||
console.error('没有有效的图片URL可预览');
|
||
return;
|
||
}
|
||
|
||
// 调用预览接口
|
||
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);
|
||
}
|
||
});
|
||
},
|
||
|
||
// 筛选相关方法
|
||
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[publisher]) {
|
||
const relatedAuthors = this.publisherAuthorMap[publisher];
|
||
this.authorOptions.forEach(item => {
|
||
if (relatedAuthors.includes(item.author)) {
|
||
item.visible = true;
|
||
}
|
||
});
|
||
}
|
||
});
|
||
} else {
|
||
// 如果是新选中,只显示与该出版社相关的作者
|
||
this.authorOptions.forEach(item => {
|
||
// 检查该作者是否与当前选中的出版社相关
|
||
if (this.publisherAuthorMap[currentPublisher]) {
|
||
const relatedAuthors = this.publisherAuthorMap[currentPublisher];
|
||
item.visible = relatedAuthors.includes(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[author]) {
|
||
const relatedPublishers = this.authorPublisherMap[author];
|
||
this.publisherOptions.forEach(item => {
|
||
if (relatedPublishers.includes(item.publisher)) {
|
||
item.visible = true;
|
||
}
|
||
});
|
||
}
|
||
});
|
||
} else {
|
||
// 如果是新选中,只显示与该作者相关的出版社
|
||
this.publisherOptions.forEach(item => {
|
||
// 检查该出版社是否与当前选中的作者相关
|
||
if (this.authorPublisherMap[currentAuthor]) {
|
||
const relatedPublishers = this.authorPublisherMap[currentAuthor];
|
||
item.visible = relatedPublishers.includes(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.filteredProducts = [];
|
||
|
||
// 关闭弹窗
|
||
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.filteredProducts = [];
|
||
this.isFiltered = false;
|
||
} else {
|
||
// 筛选在售商品
|
||
let filteredProducts = this.allProducts.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.filteredProducts = filteredProducts.slice(0, 10);
|
||
this.isFiltered = true;
|
||
}
|
||
|
||
// 关闭弹窗
|
||
this.popupShow = false;
|
||
|
||
// 触发事件
|
||
this.$emit('filters-applied', {
|
||
filteredProducts: this.filteredProducts,
|
||
selectedPublishers,
|
||
selectedAuthors
|
||
});
|
||
},
|
||
|
||
// 手动刷新数据
|
||
refresh() {
|
||
this.loadProducts();
|
||
}
|
||
}
|
||
}
|
||
</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;
|
||
}
|
||
|
||
@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 {
|
||
height: 200rpx;
|
||
}
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
.empty-products {
|
||
padding: 30rpx;
|
||
text-align: center;
|
||
color: #999;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.button-group {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20rpx;
|
||
margin: 10rpx 0;
|
||
}
|
||
|
||
.filter-section {
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.tag-group {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10rpx;
|
||
margin-top: 10rpx;
|
||
}
|
||
|
||
.filter-popup {
|
||
padding: 30rpx;
|
||
background-color: #fff;
|
||
}
|
||
|
||
.filter-buttons {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-top: 40rpx;
|
||
}
|
||
</style> |