mall-uniapp/pages/goods/list.vue

399 lines
9.5 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 } 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;
}
state.pagination.list = _.concat(state.pagination.list, data.list);
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);
}
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>