鸿宇研学生前端代码
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

678 lines
18 KiB

<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>