<template>
|
|
<view class="page__view">
|
|
|
|
<view class="header">
|
|
<view class="filter">
|
|
<view class="filter-header">
|
|
<view class="bar">
|
|
<view>
|
|
<button :class="['btn', isFold ? 'is-fold' : '']" @click="isFold = !isFold">
|
|
<text>筛选</text>
|
|
<image class="btn-icon" :src="isFold ? '/static/image/icon-arrow-down.png' : '/static/image/icon-arrow-up-light.png'" mode="widthFix"></image>
|
|
</button>
|
|
</view>
|
|
<view class="title">分类</view>
|
|
</view>
|
|
</view>
|
|
<view v-if="!isFold" class="filter-content">
|
|
<view class="filter-item" v-for="filter in filters" :key="filter.id">
|
|
<view class="filter-item-label">{{ `${filter.label}:` }}</view>
|
|
<view class="filter-item-content">
|
|
<template v-if="filter.key === 'price'">
|
|
<view class="flex range price">
|
|
<view class="range-item">
|
|
<uv-input
|
|
v-model="priceLow"
|
|
type="number"
|
|
inputAlign="center"
|
|
placeholder="开始价格"
|
|
placeholderStyle="color: #181818; font-size: 28rpx; font-weight: 400;"
|
|
:customStyle="{
|
|
backgroundColor: 'transparent',
|
|
padding: '0',
|
|
boxSizing: 'border-box',
|
|
fontSize: '28rpx',
|
|
border: 'none',
|
|
}"
|
|
fontSize="28rpx"
|
|
:clearable="true"
|
|
@confirm="onStartPriceChange"
|
|
@clear="onStartPriceChange(null)"
|
|
></uv-input>
|
|
</view>
|
|
<view class="split">至</view>
|
|
<view class="range-item">
|
|
<uv-input
|
|
v-model="priceHigh"
|
|
type="number"
|
|
inputAlign="center"
|
|
placeholder="结束价格"
|
|
placeholderStyle="color: #181818; font-size: 28rpx; font-weight: 400;"
|
|
:customStyle="{
|
|
backgroundColor: 'transparent',
|
|
padding: '0',
|
|
boxSizing: 'border-box',
|
|
fontSize: '28rpx',
|
|
border: 'none',
|
|
}"
|
|
fontSize="28rpx"
|
|
:clearable="true"
|
|
@confirm="onEndPriceChange"
|
|
@clear="onEndPriceChange(null)"
|
|
></uv-input>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
<template v-else-if="filter.key === 'time'">
|
|
<view class="flex range time">
|
|
<view class="range-item" @click="openStartDatePicker">
|
|
{{ dateLow ? $dayjs(dateLow).format('YYYY-MM-DD') : '开始日期' }}
|
|
<button v-if="dateLow" class="btn btn-clear" @click.stop="onClearStartDate">
|
|
<uv-icon name="close-circle" color="#B5B5B5" size="28rpx"></uv-icon>
|
|
</button>
|
|
</view>
|
|
<view class="split">至</view>
|
|
<view class="range-item" @click="openEndDatePicker">
|
|
{{ dateHigh ? $dayjs(dateHigh).format('YYYY-MM-DD') : '结束日期' }}
|
|
<button v-if="dateHigh" class="btn btn-clear" @click.stop="onClearEndDate">
|
|
<uv-icon name="close-circle" color="#B5B5B5" size="28rpx"></uv-icon>
|
|
</button>
|
|
</view>
|
|
</view>
|
|
<uv-datetime-picker
|
|
ref="startDatePicker"
|
|
v-model="dateLow"
|
|
mode="date"
|
|
title="开始日期"
|
|
confirmColor="#00A9FF"
|
|
round="32rpx"
|
|
:minDate="minTime"
|
|
@confirm="onStartDateChange"
|
|
></uv-datetime-picker>
|
|
<uv-datetime-picker
|
|
ref="endDatePicker"
|
|
v-model="dateHigh"
|
|
mode="date"
|
|
title="结束日期"
|
|
confirmColor="#00A9FF"
|
|
round="32rpx"
|
|
:minDate="dateLow || minTime"
|
|
@confirm="onEndDateChange"
|
|
></uv-datetime-picker>
|
|
</template>
|
|
<template v-else>
|
|
<view class="option">
|
|
<view
|
|
v-for="option in filter.options"
|
|
:key="option.id"
|
|
:class="['option-item', option.id == queryParams[filter.key] ? 'is-active' : '']"
|
|
@click="onClickFilter(filter.key, option.id)"
|
|
>
|
|
{{ option.label }}
|
|
</view>
|
|
</view>
|
|
</template>
|
|
</view>
|
|
</view>
|
|
|
|
<button class="flex btn btn-fold" @click="isFold = false">
|
|
<image class="btn-icon" src="@/static/image/icon-arrow-up.png" mode="widthFix"></image>
|
|
</button>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="sort">
|
|
<sortBar v-model="queryParams.sort" @change="onSortChange"></sortBar>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 分类商品列表 -->
|
|
<view class="main" >
|
|
|
|
<uv-vtabs
|
|
:list="categoryList"
|
|
keyName="name"
|
|
:current="current"
|
|
:chain="true"
|
|
@change="change"
|
|
|
|
barWidth="177rpx"
|
|
barBgColor="#F5F5F5"
|
|
:barItemStyle="{
|
|
color: '#1D2129',
|
|
fontSize: '28rpx',
|
|
fontWeight: 400,
|
|
}"
|
|
:barItemActiveStyle="{
|
|
color: '#00A9FF',
|
|
fontWeight: 600,
|
|
backgroundColor: '#FFFFFF',
|
|
}"
|
|
:barItemActiveLineStyle="{
|
|
background: '#00A9FF',
|
|
margin: '48rpx 4rpx',
|
|
borderRadius: '4rpx',
|
|
}"
|
|
>
|
|
<uv-vtabs-item v-for="(item, index) in categoryList" :index="index" :key="item.id">
|
|
<template v-if="item.children.length">
|
|
<view class="card" v-for="product in item.children" :key="product.id" >
|
|
<productCard :data="product" ></productCard>
|
|
</view>
|
|
</template>
|
|
<template v-else>
|
|
<uv-empty text="还没有呢"/>
|
|
</template>
|
|
</uv-vtabs-item>
|
|
</uv-vtabs>
|
|
</view>
|
|
|
|
<!-- tabbar -->
|
|
<tabber select="category" />
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
|
|
// import mixinsList from '@/mixins/list.js'
|
|
import { mapState } from 'vuex'
|
|
|
|
import tabber from '@/components/base/tabbar.vue'
|
|
import sortBar from '@/components/product/sortBar.vue'
|
|
import productCard from '@/components/product/productCard.vue'
|
|
|
|
export default {
|
|
// mixins: [mixinsList],
|
|
components: {
|
|
sortBar,
|
|
productCard,
|
|
tabber,
|
|
},
|
|
data() {
|
|
return {
|
|
current: 0,
|
|
priceLow: null,
|
|
priceHigh: null,
|
|
dateLow: null,
|
|
dateHigh: null,
|
|
minTime: new Date().getTime(),
|
|
queryParams: {
|
|
pageNo: 1,
|
|
pageSize: 1000,
|
|
sort: 'comprehensive',
|
|
},
|
|
categoryList: [],
|
|
filters: [],
|
|
addressOptionsConfig: {},
|
|
isFold: true,
|
|
}
|
|
},
|
|
async onLoad({ categoryId }) {
|
|
if(uni.getStorageSync('token')){
|
|
this.$store.commit('getUserInfo')
|
|
}
|
|
|
|
await Promise.allSettled([this.fetchCategoryList(), this.fetchFilters()])
|
|
console.log('categoryList', this.categoryList)
|
|
console.log('filters', this.filters)
|
|
|
|
await this.initList()
|
|
|
|
if(this.categoryList.length > 0 && categoryId){
|
|
setTimeout(() => {
|
|
this.current = this.categoryList.findIndex(item => item.id === categoryId)
|
|
}, 800)
|
|
// this.$nextTick(() => {
|
|
// this.current = this.categoryList.findIndex(item => item.id === categoryId)
|
|
// })
|
|
}
|
|
},
|
|
methods: {
|
|
async fetchCategoryList() {
|
|
try {
|
|
this.categoryList = (await this.$fetch('queryCategoryList', { pageSize: 1000 }))?.records?.map(item => ({ id: item.id, name: item.title, children: [] }))
|
|
} catch(err) {
|
|
this.categoryList = []
|
|
}
|
|
},
|
|
async fetchFilters() {
|
|
|
|
let fetchs = [
|
|
this.$fetch('queryAddressList', { pageNo: 1, pageSize: 1000, pid: '0' }),
|
|
this.$fetch('queryAgeList', { pageNo: 1, pageSize: 1000 }),
|
|
this.$fetch('queryTimeList', { pageNo: 1, pageSize: 1000 }),
|
|
]
|
|
const results = (await Promise.allSettled(fetchs))
|
|
.map(res => {
|
|
return res.value.records.map(item => {
|
|
return {
|
|
id: item.id,
|
|
label: item.title,
|
|
}
|
|
})
|
|
})
|
|
console.log('results', results)
|
|
|
|
fetchs = results[0].map(item => {
|
|
return this.$fetch('queryAddressList', { pageNo: 1, pageSize: 1000, pid: item.id })
|
|
})
|
|
const addressResults = (await Promise.allSettled(fetchs))
|
|
.map(res => {
|
|
return res.value.records.map(item => {
|
|
return {
|
|
id: item.id,
|
|
label: item.title,
|
|
}
|
|
})
|
|
})
|
|
console.log('addressResults', addressResults)
|
|
const addressOptionsConfig = addressResults.reduce((obj, records, index) => {
|
|
obj[results[0][index].id] = records
|
|
return obj
|
|
}, {})
|
|
|
|
this.addressOptionsConfig = addressOptionsConfig
|
|
console.log('addressOptionsConfig', addressOptionsConfig)
|
|
|
|
this.filters = [
|
|
{
|
|
id: '001',
|
|
key: 'frontier',
|
|
label: '国境',
|
|
options: results[0],
|
|
},
|
|
{
|
|
id: '002',
|
|
key: 'addressId',
|
|
label: '目的地',
|
|
options: [
|
|
{
|
|
label: '全部',
|
|
},
|
|
...addressOptionsConfig[results[0][0].id]
|
|
],
|
|
},
|
|
{
|
|
id: '003',
|
|
key: 'ageId',
|
|
label: '适合年龄',
|
|
options: [
|
|
{
|
|
label: '全部',
|
|
},
|
|
...results[1]
|
|
],
|
|
},
|
|
{
|
|
id: '004',
|
|
key: 'timeId',
|
|
label: '活动时长',
|
|
options: [
|
|
{
|
|
label: '全部',
|
|
},
|
|
...results[2],
|
|
],
|
|
},
|
|
{
|
|
id: '005',
|
|
key: 'price',
|
|
label: '价格区间',
|
|
},
|
|
{
|
|
id: '006',
|
|
key: 'time',
|
|
label: '出发日期',
|
|
},
|
|
]
|
|
console.log('filters', this.filters)
|
|
|
|
this.filters.forEach(item => {
|
|
const { key, options } = item
|
|
|
|
if (!options?.length || !options[0]?.id) {
|
|
return
|
|
}
|
|
|
|
this.queryParams[key] = options[0].id
|
|
})
|
|
|
|
},
|
|
async queryProductList(categoryId) {
|
|
try {
|
|
const {
|
|
frontier,
|
|
addressId,
|
|
sort,
|
|
...params
|
|
} = this.queryParams
|
|
|
|
params.addressId = addressId || frontier
|
|
params.categoryId = categoryId
|
|
|
|
switch(sort) {
|
|
// 销量排序(saleOrder):0-从高到低 1-从低到高
|
|
case 'sale-desc': // 销量排序 - 降序
|
|
params.saleOrder = '0'
|
|
break
|
|
case 'sale-asc': // 销量排序 - 升序
|
|
params.saleOrder = '1'
|
|
break
|
|
// 价格排序(priceOrder):0-从高到低 1-从低到高
|
|
case 'price-desc': // 销量排序 - 降序
|
|
params.priceOrder = '0'
|
|
break
|
|
case 'price-asc': // 销量排序 - 升序
|
|
params.priceOrder = '1'
|
|
break
|
|
default:
|
|
break
|
|
}
|
|
|
|
return (await this.$fetch('queryActivityList', params))?.records || []
|
|
} catch (err) {
|
|
return []
|
|
}
|
|
},
|
|
async initList() {
|
|
console.log('queryParams', this.queryParams)
|
|
const results = await Promise.allSettled(this.categoryList.map(category => { return this.queryProductList(category.id) }))
|
|
|
|
results.forEach((result, index) => {
|
|
this.categoryList[index].children = result.value || []
|
|
})
|
|
|
|
console.log('categoryList', this.categoryList)
|
|
},
|
|
change(e) {
|
|
this.current = e
|
|
},
|
|
onClickFilter(key, val) {
|
|
if (val) {
|
|
this.queryParams[key] = val
|
|
} else {
|
|
delete this.queryParams[key]
|
|
}
|
|
|
|
if (key === 'frontier') {
|
|
this.filters[1].options = [
|
|
{
|
|
label: '全部',
|
|
},
|
|
...this.addressOptionsConfig[val]
|
|
]
|
|
delete this.queryParams.addressId
|
|
}
|
|
|
|
this.initList()
|
|
},
|
|
onStartPriceChange(value) {
|
|
if (value) {
|
|
this.queryParams.priceLow = value
|
|
} else {
|
|
delete this.queryParams.priceLow
|
|
}
|
|
this.initList()
|
|
},
|
|
onEndPriceChange(value) {
|
|
if (value) {
|
|
this.queryParams.priceHigh = value
|
|
} else {
|
|
delete this.queryParams.priceHigh
|
|
}
|
|
this.initList()
|
|
},
|
|
openStartDatePicker() {
|
|
this.$refs.startDatePicker?.[0]?.open?.();
|
|
},
|
|
onStartDateChange(e) {
|
|
const date = e.value
|
|
|
|
this.queryParams.dateLow = $dayjs(date).format('YYYY-MM-DD')
|
|
|
|
const { dateHigh } = this.queryParams
|
|
|
|
if (dateHigh && this.$dayjs(date).isAfter(dateHigh, 'day')) {
|
|
this.dateHigh = null
|
|
delete this.queryParams.dateHigh
|
|
}
|
|
|
|
this.initList()
|
|
},
|
|
onClearStartDate() {
|
|
this.dateLow = null
|
|
|
|
delete this.queryParams.dateLow
|
|
|
|
this.initList()
|
|
},
|
|
openEndDatePicker() {
|
|
this.$refs.endDatePicker?.[0]?.open?.();
|
|
},
|
|
onEndDateChange(e) {
|
|
const date = e.value
|
|
|
|
this.queryParams.dateHigh = $dayjs(date).format('YYYY-MM-DD')
|
|
|
|
const { dateLow } = this.queryParams
|
|
|
|
if (dateLow && this.$dayjs(date).isBefore(dateLow, 'day')) {
|
|
this.dateLow = null
|
|
delete this.queryParams.dateLow
|
|
}
|
|
|
|
this.initList()
|
|
},
|
|
onClearEndDate() {
|
|
this.dateHigh = null
|
|
|
|
delete this.queryParams.dateHigh
|
|
|
|
this.initList()
|
|
},
|
|
onSortChange(sort) {
|
|
console.log('onSortChange', sort)
|
|
this.initList()
|
|
},
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.page__view {
|
|
height: 100vh;
|
|
background: linear-gradient(#DAF3FF, #FBFEFF 200rpx, #FBFEFF);
|
|
|
|
/deep/ .uv-popup {
|
|
z-index: 1000000 !important;
|
|
}
|
|
}
|
|
|
|
.header {
|
|
width: 100%;
|
|
padding: 0 32rpx;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.filter {
|
|
|
|
&-header {
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: flex-end;
|
|
height: 176rpx;
|
|
padding-bottom: 12rpx;
|
|
box-sizing: border-box;
|
|
|
|
.bar {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
|
|
.btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
column-gap: 8rpx;
|
|
padding: 8rpx 30rpx;
|
|
font-size: 28rpx;
|
|
font-weight: 500;
|
|
color: #FFFFFF;
|
|
background: #00A9FF;
|
|
border: 2rpx solid #00A9FF;
|
|
border-radius: 64rpx;
|
|
|
|
&-icon {
|
|
width: 32rpx;
|
|
height: auto;
|
|
}
|
|
|
|
&.is-fold {
|
|
font-weight: 400;
|
|
color: #191919;
|
|
background: #D8F2FF;
|
|
border-color: #00A9FF66;
|
|
}
|
|
}
|
|
|
|
.title {
|
|
text-align: center;
|
|
font-size: 32rpx;
|
|
font-weight: 600;
|
|
color: #191919;
|
|
}
|
|
}
|
|
}
|
|
|
|
&-content {
|
|
margin-top: 24rpx;
|
|
|
|
.btn {
|
|
&-fold {
|
|
margin-top: 32rpx;
|
|
width: 100%;
|
|
}
|
|
|
|
&-icon {
|
|
width: 40rpx;
|
|
height: auto;
|
|
}
|
|
}
|
|
}
|
|
|
|
&-item {
|
|
display: flex;
|
|
|
|
& + & {
|
|
margin-top: 32rpx;
|
|
}
|
|
|
|
&-label {
|
|
width: 156rpx;
|
|
min-height: 64rpx;
|
|
line-height: 64rpx;
|
|
flex: none;
|
|
}
|
|
|
|
&-content {
|
|
flex: 1;
|
|
|
|
.option {
|
|
margin-top: 6rpx;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 24rpx;
|
|
|
|
&-item {
|
|
padding: 8rpx 16rpx;
|
|
font-size: 28rpx;
|
|
color: #181818;
|
|
border-radius: 4rpx;
|
|
|
|
&.is-active {
|
|
color: #FFFFFF;
|
|
background: #00A9FF;
|
|
}
|
|
}
|
|
}
|
|
|
|
.range {
|
|
margin: 4rpx 0;
|
|
column-gap: 8rpx;
|
|
border-bottom: 2rpx solid #EEEEEE;
|
|
|
|
&-item {
|
|
width: 220rpx;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.split {
|
|
padding: 0 24rpx;
|
|
font-size: 32rpx;
|
|
line-height: 1;
|
|
color: #8B8B8B;
|
|
}
|
|
|
|
&.price {
|
|
.range-item {
|
|
padding: 4rpx 0;
|
|
}
|
|
}
|
|
|
|
&.time {
|
|
.range-item {
|
|
// width: 220rpx;
|
|
padding: 8rpx 0;
|
|
text-align: center;
|
|
font-size: 28rpx;
|
|
color: #181818;
|
|
|
|
.btn-clear {
|
|
margin: 6rpx 0;
|
|
float: right;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.sort {
|
|
width: 100%;
|
|
height: 116rpx;
|
|
padding: 24rpx 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.main {
|
|
/deep/ .uv-vtabs,
|
|
/deep/ .uv-vtabs__bar,
|
|
/deep/ .uv-vtabs__content {
|
|
height: calc(100vh - 292rpx - #{$tabbar-height} - env(safe-area-inset-bottom)) !important;
|
|
}
|
|
|
|
/deep/ .uv-vtabs__bar {
|
|
background: #F6F6F6 !important;
|
|
}
|
|
|
|
/deep/ .uv-vtabs__bar-item {
|
|
padding: 48rpx 32rpx;
|
|
}
|
|
|
|
/deep/ .uv-vtabs__content {
|
|
padding: 24rpx 24rpx 0 24rpx;
|
|
box-sizing: border-box;
|
|
background: linear-gradient(#DAF3FF, #F4F4F4 250rpx, #F4F4F4);
|
|
}
|
|
}
|
|
|
|
.card {
|
|
& + & {
|
|
margin-top: 32rpx;
|
|
}
|
|
|
|
&:last-child {
|
|
padding-bottom: 24rpx;
|
|
}
|
|
}
|
|
|
|
</style>
|