Files
chazhi_h5/src/pages/index/index.vue
wangxiaowei ac8212c8f0 完善接口
2025-11-03 18:36:50 +08:00

390 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- 使用 type="home" 属性设置首页其他页面不需要设置默认为page -->
<route lang="jsonc" type="home">{
"layout": "tabbar",
"style": {
// 'custom' 表示开启自定义导航栏,默认 'default'
"navigationStyle": "custom",
"navigationBarTitleText": "首页"
}
}</route>
<template>
<view class="home-bg">
<view class="home-bg sticky top-0 left-0 z-50 pb-16rpx">
<wd-navbar safeAreaInsetTop :bordered="false" custom-style="background-color: transparent !important;">
<template #left>
<view class="flex items-center">
<view class="text-36rpx leading-50rpx text-#303133 mr-38rpx">
预约茶艺师
</view>
<view class="flex items-center line-1 city-picker">
<wd-icon name="location" size="32rpx"></wd-icon>
<wd-picker :columns="cityColumns" v-model="cityValue" @confirm="Index.handleSelectCity" use-default-slot>
<view class="mr-10rpx font-400 leading-44rpx text-32rpx pl-10rpx line-1">{{ defaultCity }}</view>
</wd-picker>
<wd-img width="14rpx" height="9rpx" :src="`${OSS}icon/icon_arrow_down.png`" />
</view>
</view>
</template>
</wd-navbar>
<view class="search-box relative">
<wd-search placeholder="茶艺师名称" cancel-txt="搜索" placeholder-left hide-cancel custom-input-class="!h-72rpx" v-model="teaSpecialistName" light >
</wd-search>
<view
class="absolute top-1/2 -translate-y-1/2 right-34rpx w-142rpx h-64rpx leading-64rpx text-center rounded-32rpx bg-#4C9F44 text-#fff font-400 text-32rpx"
@click="Index.handleSearch">
搜索
</view>
</view>
<view class="mx-30rpx flex items-center">
<view class="flex items-center mr-14rpx">
<wd-img width="160rpx" height="36rpx" :src="`${OSS}images/h5/home/home_image1.png`" />
</view>
<view class="flex items-center mr-14rpx">
<view class="flex items-center">
<wd-img width="36rpx" height="36rpx" :src="`${OSS}icon/icon_safe2.png`" />
</view>
<view class="font-400 text-22rpx leading-32rpx text-#818CA9">专业资质</view>
</view>
<view class="flex items-center">
<view class="flex items-center">
<wd-img width="36rpx" height="36rpx" :src="`${OSS}icon/icon_safe2.png`" />
</view>
<view class="font-400 text-22rpx leading-32rpx text-#818CA9">定制方案</view>
</view>
</view>
</view>
<view>
<view class="mt-16rpx relative w-690rpx h-260rpx mx-30rpx" @click="Index.handleToGroupReserve">
<wd-img width="690rpx" height="260rpx" :src="`${OSS}images/h5/home/home_image2.png`" mode="scaleToFill" />
<view class="h-64rpx absolute bottom-0 right-0 bg-[#4C9F44] text-[#fff] flex items-center px-26rpx rounded">
<text class="mr-8rpx">一键约</text>
<wd-img width="22rpx" height="18.06rpx" :src="`${OSS}icon/icon_arrow_right.png`" mode="aspectFit" />
</view>
</view>
<view class="relative mt-44rpx h-44rpx mx-30rpx">
<view class="absolute ele-center" >
<wd-img width="252.04rpx" height="24.43rpx" :src="`${OSS}images/home/home_image3.png`" mode="aspectFit" />
</view>
<view class="text-32rpx text[#303133] font-500 absolute top-0 ele-center">茶艺师</view>
</view>
<view>
<!-- 茶艺师等级筛选 -->
<view class="flex items-center text-#303133 overflow-x-auto whitespace-nowrap ml-30rpx my-30rpx tea-level-scrollbar">
<view
v-for="(item, index) in TeaSpecialistLevels" :key="index"
class="h-64rpx rounded-12rpx px-24rpx py-12rpx flex items-center justify-center font-400 text-28rpx mr-20rpx"
:class="selectedLevel.includes(item.id) ? 'bg-[#4C9F44] text-[#fff]' : 'bg-[#FFF] text-[#606266]'"
@click="Index.handleToggleTeaSpecialistLevel(item.id)">
{{ item.level_name}}
</view>
</view>
<mescroll-body @init="mescrollInit" @down="downCallback" :down="downOption" @up="Index.upCallback" :up="upOption" fixed>
<view class="flex items-center bg-white p-20rpx rounded-10rpx mx-30rpx mb-20rpx" v-for="(item, index) in list" :key="index" @click="Index.handleToReserveTeaSpecialist(item.id)">
<view class="mr-28rpx relative">
<wd-img width="200rpx" height="200rpx" :src="item.image"></wd-img>
<view class="tea-specialist-time absolute top-6rpx left-0 text-[#fff] font-400 text-18rpx leading-26rpx flex items-center justify-center">
可约9:00
</view>
</view>
<view class="flex-1">
<view class="flex items-center">
<view class="font-bold text-[#303133] text-30rpx leading-42rpx mr-14rpx">
{{ item.name }}
</view>
<view>
<tea-specialist-level :level="TeaSpecialistLevelValue[item.teamasterLevel[0].level_name]"></tea-specialist-level>
</view>
</view>
<view class="flex items-center">
<template v-for="(label, labelIndex) in item.teamasterlabel" :key="labelIndex">
<!-- 上门服务 -->
<view class="mr-12rpx" v-if="label.id == 1">
<wd-tag color="#40AE36" bg-color="#40AE36" plain custom-class="!rounded-4rpx">{{ label.label_name }}</wd-tag>
</view>
<!-- 到点服务 -->
<view class="mr-12rpx" v-if="label.id == 2">
<wd-tag color="#F55726" bg-color="#F55726" plain custom-class="!rounded-4rpx">{{ label.label_name }}</wd-tag>
</view>
</template>
<view class="mr-12rpx">
<wd-tag color="#818CA9" bg-color="#F3F3F3">{{ item.both }}</wd-tag>
</view>
<view class="flex items-center mt-8rpx">
<wd-img :src="item.sex == 1 ? `${OSS}icon/icon_man.png` : `${OSS}icon/icon_woman.png`" width="28rpx" height="28rpx"></wd-img>
</view>
</view>
<view class="flex items-center justify-between mt-18rpx">
<view class="mr-20rpx w-200rpx">
<view class="flex items-center" v-if="item.authent_status == 1">
<view class="flex items-center">
<wd-img :src="`${OSS}icon/icon_store_cert.png`" width="36rpx" height="36rpx"></wd-img>
</view>
<text class="ml-8rpx font-400 text-24rpx leading-4rpx text-#303133">商家认证</text>
</view>
</view>
<view class="flex items-center font-400 text-24rpx leading-34rpx text-#92928C">距您{{ item.distance }}km</view>
</view>
<view class="font-400 text-[#FF5951] text-26rpx leading-40rpx flex items-center justify-between mt-4rpx">
<view class="">
<text class="text-32rpx">{{ item.price }}</text>
<text class="text-24rpx">/小时</text>
</view>
<view class="font-400 text-22rpx leading-32rpx text-#92928C">最快{{ item.speed }}分钟到达</view>
</view>
</view>
</view>
</mescroll-body>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { onPageScroll, onReachBottom } from '@dcloudio/uni-app'
import useMescroll from "@/uni_modules/mescroll-uni/hooks/useMescroll.js"
import TeaSpecialistLevel from '@/components/TeaSpecialistLevel.vue'
import {wxGetLocation} from '@/utils/jwexin'
import {TeaSpecialistLevelValue} from '@/utils/teaSpecialist'
import {getDecorate, getTeaSpecialistLevels, getTeaSpecialist} from '@/api/home'
import {getCity} from '@/api/city'
import type { IIndexListResult } from '@/api/types/home'
import { router } from '@/utils/tools'
const OSS = inject('OSS')
// 茶艺师等级
const TeaSpecialistLevels = reactive<Array<{ id: number, status: number, level_name: string}>>([])
const selectedLevel = ref<Array<any>>([]) // 选择茶艺师的点击等级
// 分页相关
const { mescrollInit, downCallback, getMescroll } = useMescroll(onPageScroll, onReachBottom) // 调用mescroll的hook
const downOption = {
auto: true
}
const upOption = {
auto: true,
textNoMore: '~ 已经到底啦 ~', //无更多数据的提示
}
const latitude = ref<number>(import.meta.env.VITE_DEFAULT_LATITUDE) // 纬度
const longitude = ref<number>(import.meta.env.VITE_DEFAULT_LONGITUDE) // 经度
// 经纬度缓存过期处理1小时
const LOCATION_EXPIRE_KEY = 'location_expire_time'
const LOCATION_EXPIRE_MS = 60 * 60 * 1000 // 1小时
const defaultCity = ref<string>(import.meta.env.VITE_DEFAULT_ADDRESS) // 默认城市
const list = ref<Array<any>>([]) // 茶艺师列表
const teaSpecialistName = ref<string>('') // 茶艺师名称
const cityColumns = ref<Array<{label: string, value: string}>>([])
const cityValue = ref<string>('')
onLoad(async () => {
// 检查缓存是否过期,超时则重新授权
await Index.handleEnsureLocationAuth()
// 获取城市列表
getCity({latitude: latitude.value, longitude: longitude.value}).then((res: any) => {
cityColumns.value = res.area_list.map(item => {
return {
label: item.getArea[0].name,
value: item.getArea[0].lat + ',' + item.getArea[0].lng
}
})
})
// getDecorate({id: 1}).then((res: any) => {
// const data = JSON.parse(res.data)
// console.log('装修数据:', data)
// })
// 等待授权完成后再加载数据
getTeaSpecialistLevels().then((res:Array<any>) => {
TeaSpecialistLevels.push(...res)
})
})
const Index = {
// 设置经纬度缓存
handleSetLocationCache: (lat: number, lng: number) => {
uni.setStorageSync('latitude', lat)
uni.setStorageSync('longitude', lng)
uni.setStorageSync(LOCATION_EXPIRE_KEY, Date.now() + LOCATION_EXPIRE_MS)
},
// 检查经纬度缓存是否过期
handleCheckLocationCache: () => {
const expire = uni.getStorageSync(LOCATION_EXPIRE_KEY)
if (expire && Date.now() > expire) {
uni.removeStorageSync('latitude')
uni.removeStorageSync('longitude')
uni.removeStorageSync(LOCATION_EXPIRE_KEY)
return false
}
return true
},
// 初始化经纬度
handleEnsureLocationAuth: async () => {
if (!Index.handleCheckLocationCache()) {
// 超时,重新获取授权
await wxGetLocation((res) => {
latitude.value = res.latitude
longitude.value = res.longitude
Index.handleSetLocationCache(latitude.value, longitude.value)
Index.handleSearch()
})
} else {
const lat = uni.getStorageSync('latitude')
const lng = uni.getStorageSync('longitude')
if (lat && lng) {
latitude.value = lat
longitude.value = lng
} else {
await wxGetLocation((res) => {
latitude.value = res.latitude
longitude.value = res.longitude
Index.handleSetLocationCache(latitude.value, longitude.value)
Index.handleSearch()
})
}
}
},
// 选择城市
handleSelectCity: (e: any) => {
cityValue.value = e.value
defaultCity.value = e.selectedItems.label
const val = e.value.split(',')
latitude.value = parseFloat(val[0])
longitude.value = parseFloat(val[1])
Index.handleSearch()
},
// 搜索
handleSearch: () => {
list.value = []
getMescroll().resetUpScroll()
},
// 上拉加载的回调: 其中num:当前页 从1开始, size:每页数据条数,默认10
upCallback: (mescroll) => {
const filter = {
level_id: selectedLevel.value.join(',') || '0',
page: mescroll.num,
size: mescroll.size,
latitude: latitude.value,
longitude: longitude.value,
search: teaSpecialistName.value
}
console.log('filter:', filter)
getTeaSpecialist(filter).then((res: IIndexListResult) => {
const curPageData = res.list || [] // 当前页数据
if(mescroll.num == 1) list.value = [] // 第一页需手动制空列表
list.value = list.value.concat(curPageData) //追加新数据
mescroll.endSuccess(curPageData.length, Boolean(res.more))
}).catch(() => {
mescroll.endErr() // 请求失败, 结束加载
})
},
handleClick: (item: any) => {
// 处理点击事件
console.log('Clicked item:', item)
},
// 跳转到预约茶艺师页面
handleToReserveTeaSpecialist: (id: number = 1) => {
router.navigateTo(`/pages/index/detail?id=${id}&lat=${latitude.value}&lng=${longitude.value}`)
},
// 选择茶艺师等级
handleToggleTeaSpecialistLevel: (value: any) => {
const index = selectedLevel.value.indexOf(value);
if (index > -1) {
// 如果已经选择了该等级,则取消选择
selectedLevel.value.splice(index, 1);
} else {
// 如果未选择该等级,则添加选择
selectedLevel.value.push(value);
}
Index.handleSearch()
},
// 跳转到团体预约页面
handleToGroupReserve: () => {
router.navigateTo('/pages/reserve/group-tea-specialist')
}
}
</script>
<style lang="scss">
page {
background-color: $cz-page-background;
}
// 隐藏横向滚动条
.tea-level-scrollbar {
&::-webkit-scrollbar {
display: none;
-ms-overflow-style: none;
scrollbar-width: none;
}
}
.home-bg {
background-color: $cz-page-background;
background-image: url(#{$OSS}images/home/home_bg.png);
background-size: 100%;
background-repeat: no-repeat;
}
.search-box {
:deep() {
.wd-search {
background: transparent !important;
}
}
}
.rounded {
border-radius: 20rpx 0rpx 20rpx 0rpx;
}
.ele-center {
left: 50%;
transform: translateX(-50%);
}
.tea-specialist-time {
width: 106rpx;
height: 38rpx;
background-image: url(#{$OSS}images/collect/collect_image1.png);
background-size: 100% 100%;
}
.city-picker {
:deep() {
.wd-picker__action--cancel {
color: #666666 !important;
}
.wd-picker__action {
color: #4C9F44;
}
}
}
</style>