鸿宇研学生前端代码
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.
 
 
 

845 lines
22 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="startPrice"
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"
></uv-input>
</view>
<view class="split">至</view>
<view class="range-item">
<uv-input
v-model="endPrice"
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"
></uv-input>
</view>
</view>
</template>
<template v-else-if="filter.key === 'time'">
<view class="flex range time">
<view class="range-item" @click="openStartDatePicker">
{{ startDate ? $dayjs(startDate).format('YYYY-MM-DD') : '开始日期' }}
<button v-if="startDate" 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">
{{ endDate ? $dayjs(endDate).format('YYYY-MM-DD') : '结束日期' }}
<button v-if="endDate" 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="startDate"
mode="date"
title="开始日期"
confirmColor="#00A9FF"
round="32rpx"
:minDate="minTime"
@confirm="onStartDateChange"
></uv-datetime-picker>
<uv-datetime-picker
ref="endDatePicker"
v-model="endDate"
mode="date"
title="结束日期"
confirmColor="#00A9FF"
round="32rpx"
:minDate="startDate || 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,
startPrice: null,
endPrice: null,
startDate: null,
endDate: null,
minTime: new Date().getTime(),
queryParams: {
pageNo: 1,
pageSize: 1000,
// todo
sort: 'comprehensive',
},
categoryList: [],
filters: [],
isFold: true,
}
},
async onLoad({ categoryId }) {
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() {
this.categoryList = [
{
"key": "1962345168240185345",
"title": "国际游",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
},
{
"key": "1962345225345634305",
"title": "夏令营",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
},
{
"key": "1962345290571255810",
"title": "周末营",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
},
{
"key": "1962345372007862273",
"title": "周边游",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
},
{
"key": "1962345497681793025",
"title": "定制游",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
},
{
"key": "1962345589524467714",
"title": "周末活动",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
},
{
"key": "1962345642188148737",
"title": "亲子活动",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
},
{
"key": "1962345709817106434",
"title": "主题研学",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
},
{
"key": "1962346300198948866",
"title": "社会实践",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
},
{
"key": "1962346769759670273",
"title": "研学交流",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
},
{
"key": "1962346834884628481",
"title": "周末研学",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
},
{
"key": "1962346960097185793",
"title": "假期专享",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
},
{
"key": "1962347024639135745",
"title": "本地研学",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
}
].map(item => {
const { key, title } = item
return {
id: key,
name: title,
children: []
}
})
return
try {
this.categoryList = (await this.$fetch('getCategoryList', { pageSize: 1000 }))?.records?.map(item => ({ id: item.id, name: item.name, children: [] }))
} catch(err) {
this.categoryList = []
}
},
async fetchFilters() {
this.filters = [
{
id: '001',
key: 'frontier',
label: '国境',
options: [
{
id: '00101',
label: '国内',
},
{
id: '00102',
label: '国外',
},
],
},
{
id: '002',
key: 'addressId',
label: '目的地',
options: [
{
label: '全部',
},
{
id: '00201',
label: '上海',
},
{
id: '00202',
label: '北京',
},
{
id: '00203',
label: '浙江省',
},
{
id: '00204',
label: '广东省',
},
{
id: '00205',
label: '广西省',
},
{
id: '00206',
label: '云南省',
},
],
},
{
id: '003',
key: 'ageId',
label: '适合年龄',
options: [
{
label: '全部',
},
{
id: '00301',
label: '6-10岁',
},
{
id: '00302',
label: '11-14岁',
},
{
id: '00303',
label: '15-16岁',
},
{
id: '00304',
label: '17-18岁',
},
],
},
{
id: '004',
key: 'timeId',
label: '活动时长',
options: [
{
label: '全部',
},
{
id: '00401',
label: '1日',
},
{
id: '00402',
label: '多日',
},
{
id: '00403',
label: '寒假',
},
{
id: '00404',
label: '暑假',
},
],
},
{
id: '005',
key: 'price',
label: '价格区间',
},
{
id: '006',
key: 'time',
label: '出发日期',
},
]
this.filters.forEach(item => {
const { key, options } = item
if (!options?.length || !options[0]?.id) {
return
}
this.queryParams[key] = options[0].id
})
// todo: fetch
},
async queryProductList(categoryId) {
try {
return (await this.$fetch('queryActivityList', { ...this.queryParams, categoryId }))?.records || []
} catch (err) {
return []
}
return [
{
id: '001',
image: '/static/image/temp-20.png',
title: '新疆天山行7/9日丨醉美伊犁&吐鲁番双套餐',
tagList: ['国内游','7-9天','12岁+'],
priceDiscount: 688.99,
priceOrigin: 1200,
applyNum: 4168,
},
{
id: '002',
image: '/static/image/temp-20.png',
title: '坝上双草原6日|乌兰布统+锡林郭勒+长城',
tagList: ['国内游','7-9天','12岁+'],
priceDiscount: 688.99,
priceOrigin: 1200,
applyNum: 4168,
},
{
id: '003',
image: '/static/image/temp-20.png',
title: '牛湖线探秘 | 清远牛湖线徒步,探秘天坑与大草原',
tagList: ['国内游','7-9天','12岁+'],
priceDiscount: 688.99,
priceOrigin: 1200,
applyNum: 4168,
},
{
id: '004',
image: '/static/image/temp-20.png',
title: '低海拔藏区草原,汉藏文化大穿越',
tagList: ['国内游','7-9天','12岁+'],
priceDiscount: 688.99,
priceOrigin: 1200,
applyNum: 4168,
},
{
id: '005',
image: '/static/image/temp-20.png',
title: '新丝路到敦煌7日 | 甘青轻松穿越,沙漠+草原',
tagList: ['国内游','7-9天','12岁+'],
priceDiscount: 688.99,
priceOrigin: 1200,
applyNum: 4168,
},
{
id: '006',
image: '/static/image/temp-20.png',
title: '呼伦贝尔6/8日|经典or环线双套餐可选',
tagList: ['国内游','7-9天','12岁+'],
priceDiscount: 688.99,
priceOrigin: 1200,
applyNum: 4168,
},
]
},
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
},
search(){
// todo: set filter
this.initList()
},
onClickFilter(key, val) {
if (val) {
this.queryParams[key] = val
} else {
delete this.queryParams[key]
}
this.initList()
},
openStartDatePicker() {
this.$refs.startDatePicker?.[0]?.open?.();
},
onStartDateChange(e) {
const date = e.value
this.queryParams.startDate = date
const { endDate } = this.queryParams
if (endDate && this.$dayjs(date).isAfter(endDate, 'day')) {
this.endDate = null
delete this.queryParams.endDate
}
this.initList()
},
onClearStartDate() {
this.startDate = null
delete this.queryParams.startDate
this.initList()
},
openEndDatePicker() {
this.$refs.endDatePicker?.[0]?.open?.();
},
onEndDateChange(e) {
const date = e.value
this.queryParams.endDate = date
const { startDate } = this.queryParams
if (startDate && this.$dayjs(date).isBefore(startDate, 'day')) {
this.startDate = null
delete this.queryParams.startDate
}
this.initList()
},
onClearEndDate() {
this.endDate = null
delete this.queryParams.endDate
this.initList()
},
onSortChange(sort) {
console.log('onSortChange', sort)
// todo set sort
this.getData()
},
}
}
</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>