mall-uniapp/pages/goods/list.vue

458 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<s-layout
navbar="normal"
:leftWidth="0"
:rightWidth="0"
tools="search"
:defaultSearch="state.keyword"
@search="onSearch"
>
<!-- 筛选 -->
<su-sticky bgColor="#fff">
<view class="ss-flex">
<view class="ss-flex-1">
<su-tabs
:list="state.tabList"
:scrollable="false"
@change="onTabsChange"
:current="state.currentTab"
/>
</view>
<view class="list-icon" @tap="state.iconStatus = !state.iconStatus">
<text v-if="state.iconStatus" class="sicon-goods-list" />
<text v-else class="sicon-goods-card" />
</view>
</view>
</su-sticky>
<!-- 弹窗 -->
<su-popup
:show="state.showFilter"
type="top"
round="10"
:space="sys_navBar + 38"
backgroundColor="#F6F6F6"
:zIndex="10"
@close="state.showFilter = false"
>
<view class="filter-list-box">
<view
class="filter-item"
v-for="(item, index) in state.tabList[state.currentTab].list"
:key="item.value"
:class="[{ 'filter-item-active': index === state.curFilter }]"
@tap="onFilterItem(index)"
>
{{ item.label }}
</view>
</view>
</su-popup>
<!-- 情况一:单列布局 -->
<view v-if="state.iconStatus && state.pagination.total > 0" class="goods-list ss-m-t-20">
<view
class="ss-p-l-20 ss-p-r-20 ss-m-b-20"
v-for="item in state.pagination.list"
:key="item.id"
>
<s-goods-column
class=""
size="lg"
:data="item"
:topRadius="10"
:bottomRadius="10"
@click="sheep.$router.go('/pages/goods/index', { id: item.id })"
/>
</view>
</view>
<!-- 情况二:双列布局 -->
<view
v-if="!state.iconStatus && state.pagination.total > 0"
class="ss-flex ss-flex-wrap ss-p-x-20 ss-m-t-20 ss-col-top"
>
<view class="goods-list-box">
<view class="left-list" v-for="item in state.leftGoodsList" :key="item.id">
<s-goods-column
class="goods-md-box"
size="md"
:data="item"
:topRadius="10"
:bottomRadius="10"
@click="sheep.$router.go('/pages/goods/index', { id: item.id })"
@getHeight="mountMasonry($event, 'left')"
>
<template v-slot:cart>
<button class="ss-reset-button cart-btn" />
</template>
</s-goods-column>
</view>
</view>
<view class="goods-list-box">
<view class="right-list" v-for="item in state.rightGoodsList" :key="item.id">
<s-goods-column
class="goods-md-box"
size="md"
:topRadius="10"
:bottomRadius="10"
:data="item"
@click="sheep.$router.go('/pages/goods/index', { id: item.id })"
@getHeight="mountMasonry($event, 'right')"
>
<template v-slot:cart>
<button class="ss-reset-button cart-btn" />
</template>
</s-goods-column>
</view>
</view>
</view>
<uni-load-more
v-if="state.pagination.total > 0"
:status="state.loadStatus"
:content-text="{
contentdown: '上拉加载更多',
}"
@tap="loadMore"
/>
<s-empty v-if="state.pagination.total === 0" icon="/static/soldout-empty.png" text="暂无商品" />
</s-layout>
</template>
<script setup>
import { reactive, ref } from 'vue';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import sheep from '@/sheep';
import _ from 'lodash-es';
import { resetPagination } from '@/sheep/util';
import SpuApi from '@/sheep/api/product/spu';
const sys_navBar = sheep.$platform.navbar;
const emits = defineEmits(['close', 'change']);
const state = reactive({
pagination: {
list: [],
total: 0,
pageNo: 1,
pageSize: 6,
},
currentSort: undefined,
currentOrder: undefined,
currentTab: 0, // 当前选中的 tab
curFilter: 0, // 当前选中的 list 筛选项
showFilter: false,
iconStatus: false, // true - 单列布局false - 双列布局
keyword: '',
categoryId: 0,
tabList: [
{
name: '综合推荐',
list: [
{
label: '综合推荐',
},
{
label: '价格升序',
sort: 'price',
order: true,
},
{
label: '价格降序',
sort: 'price',
order: false,
},
],
},
{
name: '销量',
sort: 'salesCount',
order: false,
},
{
name: '新品优先',
value: 'createTime',
order: false,
},
],
loadStatus: '',
leftGoodsList: [], // 双列布局 - 左侧商品
rightGoodsList: [], // 双列布局 - 右侧商品
});
// 加载瀑布流
let count = 0;
let leftHeight = 0;
let rightHeight = 0;
// 处理双列布局 leftGoodsList + rightGoodsList
function mountMasonry(height = 0, where = 'left') {
if (!state.pagination.list[count]) {
return;
}
if (where === 'left') {
leftHeight += height;
} else {
rightHeight += height;
}
if (leftHeight <= rightHeight) {
state.leftGoodsList.push(state.pagination.list[count]);
} else {
state.rightGoodsList.push(state.pagination.list[count]);
}
count++;
}
// 清空列表
function emptyList() {
resetPagination(state.pagination);
state.leftGoodsList = [];
state.rightGoodsList = [];
count = 0;
leftHeight = 0;
rightHeight = 0;
}
// 搜索
function onSearch(e) {
state.keyword = e;
emptyList();
getList(state.currentSort, state.currentOrder);
}
// 点击
function onTabsChange(e) {
// 如果点击的是【综合推荐】,则直接展开或者收起筛选项
if (state.tabList[e.index].list) {
state.currentTab = e.index;
state.showFilter = !state.showFilter;
return;
}
state.showFilter = false;
// 如果点击的是【销量】或者【新品优先】,则直接切换 tab
if (e.index === state.currentTab) {
return;
}
state.currentTab = e.index;
state.currentSort = e.sort;
state.currentOrder = e.order;
emptyList();
getList(e.sort, e.order);
}
// 点击 tab 的 list 筛选项
const onFilterItem = (val) => {
// 如果点击的是当前的筛选项,则直接收起筛选项,不要加载数据
// 这里选择 tabList[0] 的原因,是目前只有它有 list
if (
state.currentSort === state.tabList[0].list[val].sort &&
state.currentOrder === state.tabList[0].list[val].order
) {
state.showFilter = false;
return;
}
state.showFilter = false;
// 设置筛选条件
state.curFilter = val;
state.tabList[0].name = state.tabList[0].list[val].label;
state.currentSort = state.tabList[0].list[val].sort;
state.currentOrder = state.tabList[0].list[val].order;
// 清空 + 加载数据
emptyList();
getList();
};
async function getList() {
state.loadStatus = 'loading';
const { code, data } = await SpuApi.getSpuPage({
pageNo: state.pagination.pageNo,
pageSize: state.pagination.pageSize,
sortField: state.currentSort,
sortAsc: state.currentOrder,
categoryId: state.categoryId,
keyword: state.keyword,
});
if (code !== 0) {
return;
}
// 使用 map 提取每个对象的 id 属性
const ids = data.list.map((item) => item.id);
// 使用 join 方法将 id 数组连接成一个用逗号分隔的字符串
const idsString = ids.join(',');
// 获取结算信息
settleData.value = await getSettlementByIds(idsString);
// 处理获得的数据
const ms = enrichDataWithSkus(data.list, settleData.value);
state.pagination.list = _.concat(state.pagination.list, ms);
state.pagination.total = data.total;
state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';
mountMasonry();
}
// 加载更多
function loadMore() {
if (state.loadStatus === 'noMore') {
return;
}
state.pagination.pageNo++;
getList(state.currentSort, state.currentOrder);
}
//获取结算信息
const settleData = ref();
async function getSettlementByIds(ids) {
const { data } = await SpuApi.getSettlementProduct(ids);
return data;
}
//计算展示价格的函数
function enrichDataWithSkus(data, array) {
// 创建一个映射,以 id 为键,存储 data 数组中的对象
const dataMap = new Map(data.map((item) => [item.id, { ...item }]));
// 遍历 array 数组
array.forEach((item) => {
// 初始化 discountPrice 和 vipPrice 为 null
let discountPrice = null;
let vipPrice = null;
let foundType4 = false;
let foundType6 = false;
// 遍历 skus 数组,寻找 type 为 4 和 6 的首个条目
item.skus.forEach((sku) => {
debugger;
if (!foundType4 && sku.promotionType === 4) {
discountPrice = sku.payPrice;
foundType4 = true;
}
if (!foundType6 && sku.promotionType === 6) {
vipPrice = sku.payPrice;
foundType6 = true;
}
// 如果已经找到 type 为 4 和 6 的条目,则不需要继续遍历
if (foundType4 && foundType6) {
return;
}
});
// 更新 dataMap 中对应的对象
if (dataMap.has(item.spuId)) {
debugger;
dataMap.get(item.spuId).discountPrice = discountPrice;
dataMap.get(item.spuId).vipPrice = vipPrice;
dataMap.get(item.spuId).reward = item.rewardActivity;
}
});
// 返回更新后的数据数组
return Array.from(dataMap.values());
}
onLoad((options) => {
state.categoryId = options.categoryId;
state.keyword = options.keyword;
getList(state.currentSort, state.currentOrder);
});
// 上拉加载更多
onReachBottom(() => {
loadMore();
});
</script>
<style lang="scss" scoped>
.goods-list-box {
width: 50%;
box-sizing: border-box;
.left-list {
margin-right: 10rpx;
margin-bottom: 20rpx;
}
.right-list {
margin-left: 10rpx;
margin-bottom: 20rpx;
}
}
.goods-box {
&:nth-last-of-type(1) {
margin-bottom: 0 !important;
}
&:nth-child(2n) {
margin-right: 0;
}
}
.list-icon {
width: 80rpx;
.sicon-goods-card {
font-size: 40rpx;
}
.sicon-goods-list {
font-size: 40rpx;
}
}
.goods-card {
margin-left: 20rpx;
}
.list-filter-tabs {
background-color: #fff;
}
.filter-list-box {
padding: 28rpx 52rpx;
.filter-item {
font-size: 28rpx;
font-weight: 500;
color: #333333;
line-height: normal;
margin-bottom: 24rpx;
&:nth-last-child(1) {
margin-bottom: 0;
}
}
.filter-item-active {
color: var(--ui-BG-Main);
}
}
.tab-item {
height: 50px;
position: relative;
z-index: 11;
.tab-title {
font-size: 30rpx;
}
.cur-tab-title {
font-weight: $font-weight-bold;
}
.tab-line {
width: 60rpx;
height: 6rpx;
border-radius: 6rpx;
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 10rpx;
background-color: var(--ui-BG-Main);
z-index: 12;
}
}
</style>