Files
chazhi/src/pages/index/index.vue
wangxiaowei 079a66f287 修改代码
2026-02-02 02:36:40 +08:00

533 lines
17 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="">
<!-- 领取优惠券 -->
<wd-overlay :show="showCoupon" @click="showCoupon = false">
<view class="flex flex-col justify-center items-center h-100%">
<view class="w-652rpx h-590rpx relative" >
<wd-img :src="`${OSS}images/home/home_image10.png`" width="100%" height="100%"></wd-img>
<view class="absolute top-200rpx left-1/2 transform -translate-x-1/2 flex flex-col items-center">
<view class="mt-220rpx" @click.stop="showCoupon = false">
<view class="h-90rpx relative">
<wd-img :src="`${OSS}images/home/home_image9.png`" width="342rpx" height="90rpx"></wd-img>
<view class="absolute top-20rpx left-1/2 transform -translate-x-1/2 flex flex-col items-center" @click.stop="Index.handleToGetCoupon">立即领取</view>
</view>
</view>
</view>
</view>
<view class="mt-80rpx" @click.stop="showCoupon = false">
<wd-img :src="`${OSS}icon/icon_close2.png`" width="72rpx" height="72rpx"></wd-img>
</view>
</view>
</wd-overlay>
<view class="w-[100%] fixed top-0 left-0 z-100">
<wd-navbar safeAreaInsetTop :bordered="false" custom-style="background-color: #F9FAFB !important;">
<template #left>
<view class="flex items-center line-1 w-130rpx" @click="Index.handleToCity">
<view class="mr-10rpx font-400 leading-44rpx text-32rpx pl-10rpx line-1">{{ city || LOCATION_DEFAULT_CITY }}</view>
<wd-img width="14rpx" height="9rpx" :src="`${OSS}icon/icon_arrow_down.png`" />
</view>
</template>
<template #title>
<view class="search-box flex items-center ml-26rpx" @click="Index.handleToSearch">
<wd-search placeholder="搜索高端茶室" hide-cancel disabled :placeholder-left="true"
placeholderStyle="text-align:left;padding-left: 24rpx;line-heigt: 44rpx;color: #C9C9C9; font-size: 32rpx;font-weight: normal;">
</wd-search>
</view>
</template>
</wd-navbar>
</view>
<view :style="{ paddingTop: navbarHeight + 'px' }">
<view class="mt-32rpx mx-30rpx">
<wd-swiper
height="320rpx"
indicatorPosition="bottom-left"
:indicator="{ type: 'dots-bar' }"
:list="swiperList"
v-model:current="current"
mode="aspectFit"
@click="Index.handlePreviewImage"
></wd-swiper>
</view>
<view class="mt-40rpx flex items-center h-36rpx mx-30rpx">
<wd-img width="160rpx" height="36rpx" :src="`${OSS}images/home/home_image1.png`" mode="aspectFit" />
<text class="text-22rpx leading-32rpx text-[#818CA9] ml-36rpx">甄选茗师</text>
<text class="text-22rpx leading-32rpx text-[#818CA9] ml-10rpx">·</text>
<text class="text-22rpx leading-32rpx text-[#818CA9] ml-10rpx">商务雅趣</text>
<text class="text-22rpx leading-32rpx text-[#818CA9] ml-10rpx">·</text>
<text class="text-22rpx leading-32rpx text-[#818CA9] ml-10rpx">尊享奉茶</text>
</view>
<view class="mt-16rpx relative w-690rpx h-224rpx mx-30rpx" @click="router.navigateTo(`/bundle_b/pages/tea-specialist/list`)">
<wd-img width="690rpx" height="224rpx" :src="`${OSS}images/home/home_image14.png`" mode="scaleToFill" />
</view>
<!-- <view class="relative mt-40rpx 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 class="mt-16rpx relative w-690rpx h-180rpx mx-30rpx">
<wd-img width="690rpx" height="180rpx" :src="`${OSS}images/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="mx-30rpx mt-36rpx mb-10rpx">
<scroll-view class="w-[100%] whitespace-nowrap " :scroll-x="true" :show-scrollbar="false" :enhanced="true">
<!-- <view class="scroll-item text-26rpx text-#333 mr-16rpx" :class="distance === 1 ? 'active' : ''" @click="Index.handleSort('distance')">距离优先</view> -->
<view class="scroll-item text-26rpx text-#333 mr-16rpx" :class="sort.sales === 1 ? 'active' : ''" @click="Index.handleSort('sales')">销量优先</view>
<view class="scroll-item text-26rpx text-#333 mr-16rpx" :class="sort.open === 1 ? 'active' : ''" @click="Index.handleSort('open')">24小时营业</view>
<view class="scroll-item text-26rpx text-#333 mr-16rpx" :class="sort.use === 1 ? 'active' : ''" @click="Index.handleSort('use')">常去</view>
</scroll-view>
</view>
<view>
<mescroll-body @init="mescrollInit" :down="downOption" @down="downCallback" :up="upOption" @up="Index.upCallback" top="28rpx"
:fixed="true">
<view class="relative p-20rp mb-24rpx" v-for="(item, index) in list" :key="index" @click="Index.handleToReserveRoom(item.id, item.operation_type)">
<!-- <view class="absolute top--28rpx left-0 z-1" v-if="item.operation_type == 1">
<wd-img width="110rpx" height="110rpx" :src="`${OSS}images/home/home_image6.png`"/>
</view> -->
<view class="mx-30rpx p-30rpx flex bg-white rounded-10rpx">
<view class="relative">
<wd-img width="200rpx" height="200rpx" :src="item.image" radius="10rpx" />
<view class="bg-#34C759 text-#fff text-center w-200rpx font-700 text-20rpx absolute bottom-0 left-0 right-0 hour" v-if="item.operation_type == 1">24小时营业</view>
</view>
<view class="flex-1 ml-28rpx flex justify-between line-1 items-start relative">
<view class="line-1">
<view class="flex items-center justify-between">
<view class="font-bold text-30rpx leading-42rpx line-1 w-400rpx">
{{ item.name }}
</view>
<view class="text-24rpx text-[#92928C] font-400 mt-12rpx ml-20rpx">距您{{ item.distance }}</view>
</view>
<view class="flex items-center mt-20rpx leading-34rpx">
<view class="font-400 bg-#FFF3E0 text-[#F29747] text-24rpx mr-18rpx px-20rpx py-10rpx rounded-8rpx" v-if="item.operation_type == 1">半年预约{{ item.half_year_nums > 300 ? '300+' : item.half_year_nums }}</view>
<view class="font-400 bg-[#E8F5E9] text-[#2E7D32] text-22rpx px-8rpx py-10rpx rounded-8rpx">刚有人预约</view>
</view>
<view class="flex items-center mt-12rpx leading-34rpx">
<view class="font-400 text-[#606266] text-24rpx mr-10rpx flex items-center">
<wd-img width="26rpx" height="26rpx" :src="`${OSS}icon/icon_time3.png`" mode="aspectFit" />
<view class="ml-10rpx">
{{ item.start_time }}-{{ item.end_time }}
</view>
</view>
<view class="font-400 bg-[#FFEEED] text-[#FF5951] text-22rpx px-4rpx rounded-8rpx border-[#F2E2E1]" v-if="item.shop_status == 0">
打烊了
</view>
</view>
<view class="flex items-center mt-20rpx">
<wd-img width="26rpx" height="26rpx" :src="`${OSS}icon/icon_location5.png`"
mode="aspectFit" />
<view class="ml-4rpx line-1 font-400 text-22rpx text-[#606266] leading-32rpx w-300rpx">
{{ item.address }}
</view>
</view>
</view>
<view class="absolute bottom-0 right-0">
<wd-img width="64rpx" height="64rpx" :src="`${OSS}icon/icon_add.png`"
mode="aspectFit" />
<!-- <view class="flex justify-end">
<view class="bg-[#34C759] w-64rpx h-64rpx rounded-64rpx flex items-center justify-center add-box-shadow">
<wd-icon name="add" color="#fff" size="20rpx" custom-style="font-weight: bold;" />
</view>
</view> -->
</view>
</view>
</view>
</view>
</mescroll-body>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { router, previewImage } from '@/utils/tools'
import { onPageScroll, onReachBottom } from '@dcloudio/uni-app'
import useMescroll from "@/uni_modules/mescroll-uni/hooks/useMescroll.js"
import { LOCATION_DENY_TIME_KEY, handleEnsureLocationAuthHooks, LOCATION_DEFAULT_CITY, handleGetLocationCity, LOCATION_CITY_KEY, handleForceGetLocation } from '@/hooks/useLocation'
import { getHomeBannerList, getHomeCouponPopup, claimIndexCoupon, getWxacode } from '@/api/home'
import { getHomeTeaStoreList } from '@/api/tea-room'
import { useUserStore } from '@/store'
import { useToast } from 'wot-design-uni'
const OSS = inject('OSS')
const navbarHeight = inject('navbarHeight')
const toast = useToast()
// 轮播图
const swiperList = ref<string[]>([])
const current = ref<number>(0)
// 分页
const { mescrollInit, downCallback, getMescroll } = useMescroll(onPageScroll, onReachBottom) // 调用mescroll的hook
const downOption = {
auto: true
}
const upOption = {
auto: true,
textNoMore: '~ 已经到底啦 ~', //无更多数据的提示
}
const latitude = ref<number>(0)
const longitude = ref<number>(0)
const city = ref<string>('')
const keywords = ref<string>('')
const list = ref<Array<any>>([])
const city_id = ref<number>(0)
const distance = ref<number>(0)
const sort = ref({
sales: 0, // 销量优先
open: 0, // 24小时
use: 0, // 常去
})
let lastLocation = { lat: 0, lng: 0 }
const canLocation = ref<boolean>(false)
// 显示优惠券弹窗
const showCoupon = ref<boolean>(false)
onShow(async () => {
// if (canLocation) {
// const location = await handleGetLocationCity(latitude.value, longitude.value)
// Index.handleResetSearch()
// }
// canLocation.value = true
// 检测用户授权地理位置的缓存LOCATION_DENY_TIME_KEY
if (uni.getStorageSync(LOCATION_DENY_TIME_KEY)) {
const location = await checkLocationAuthWithModal()
if (location) {
// 只有经纬度变化时才刷新
if (location.lat !== lastLocation.lat || location.lng !== lastLocation.lng) {
const loc = await handleGetLocationCity(location.lat, location.lng)
city.value = loc.city
latitude.value = location.lat
longitude.value = location.lng
lastLocation.lat = location.lat
lastLocation.lng = location.lng
Index.handleResetSearch()
}
}
}
const userStore = useUserStore()
const userId = userStore.userInfo?.id
if (userId) {
// 获取首页优惠券弹窗
const res = await getHomeCouponPopup()
if (Number(res.status) === 0) {
showCoupon.value = true
}
}
})
onLoad(async(args) => {
// 获取小程序码注册来源
// getWxacode().then( res => {})
// 初始化页面数据
Index.handleInit()
// 获取用户经纬度(带缓存和授权逻辑)
const { lat, lng } = await handleEnsureLocationAuthHooks()
latitude.value = lat
longitude.value = lng
// 根据经纬度获取城市信息再重新赋值经纬度和城市
const location = await handleGetLocationCity(lat, lng)
city.value = location.city
latitude.value = location.latitude
longitude.value = location.longitude
Index.handleResetSearch()
})
// 首页分享
onShareAppMessage(async (e) => {
return {
title: '首页',
path: `/pages/index/index`,
}
})
const Index = {
/**
* 茶室门店列表
* @param mescroll
*/
upCallback: async (mescroll) => {
const userStore = useUserStore()
const userId = userStore.userInfo?.id || 0
if (canLocation.value ) {
console.log("🚀 ~ 重置定位:", canLocation)
const location = await handleEnsureLocationAuthHooks()
latitude.value = location.lat
longitude.value = location.lng
}
canLocation.value = true
const filter = {
page: mescroll.num,
size: mescroll.size,
latitude: latitude.value,
longitude: longitude.value,
search: keywords.value,
user_id: userId,
city_area_id: city_id.value || 0,
// 合并排序条件
...sort.value
}
uni.showLoading({ title: '加载中...' })
try {
getHomeTeaStoreList(filter).then( res => {
uni.hideLoading()
const curPageData = res.list || [] // 当前页数据
if(mescroll.num == 1) list.value = [] // 第一页需手动制空列表
list.value = list.value.concat(curPageData) //追加新数据
mescroll.endSuccess(curPageData.length, Boolean(res.more))
}).catch(() => {
uni.hideLoading()
mescroll.endErr() // 请求失败, 结束加载
})
} catch (error) {
uni.hideLoading()
}
},
/**
* 初始化首页数据
*/
handleInit: () => {
getHomeBannerList().then(res => {
swiperList.value = res.list.map(item => item.address)
})
},
/**
* 跳转城市选择
*/
handleToCity: () => {
uni.$on('locationUpdate', params => {
uni.$off('locationUpdate')
city.value = params.city
city_id.value = params.id
// latitude.value = params.latitude
// longitude.value = params.longitude
Index.handleResetSearch()
})
router.navigateTo(`/pages/city/city?lat=${latitude.value}&lng=${longitude.value}`)
},
/**
* 跳转茶室搜索
*/
handleToSearch: () => {
uni.$on('refreshTeaRoomList', params => {
keywords.value = params.keywords
Index.handleResetSearch()
uni.$off('refreshTeaRoomList')
})
router.navigateTo(`/pages/search/search?keywords=${keywords.value}`)
},
/**
* 跳转到预约茶室页面
*/
handleToReserveRoom: (id: number = 0, type: number = 1) => {
router.navigateTo( `/bundle/tea-room/room?id=${id}&type=${type}`)
},
/**
* 重置搜索
*/
handleResetSearch: () => {
list.value = []
getMescroll().resetUpScroll()
},
/**
* 更新定位信息
*/
handleLocationUpdate: (params: any) => {
console.log("🚀 ~ locationUpdate:")
if (
city.value !== params.city ||
latitude.value !== params.latitude ||
longitude.value !== params.longitude
) {
city.value = params.city
latitude.value = params.latitude
longitude.value = params.longitude
Index.handleResetSearch()
}
},
/**
* 跳转领取优惠券页面
*/
handleToGetCoupon: async () => {
await claimIndexCoupon()
showCoupon.value = false
router.navigateTo('/bundle_b/pages/tea-specialist/list')
},
/**
* 轮播图跳转
*/
handleBanner: (e: any) => {
switch(e.index){
case 0:
// 跳转到茶艺师入驻
router.navigateTo('/bundle/settle-in/tea-specialist')
break;
case 1:
// 跳转到茶馆入驻
router.navigateTo('/bundle/settle-in/tea-room')
break;
case 2:
// 跳转到茶艺师列表
router.navigateTo('/bundle_b/pages/tea-specialist/list')
break;
case 3:
// 跳转到充值会员
router.navigateTo('/bundle/vip/benefits')
break;
}
// if (e.index == 2) {
// // 跳转到茶艺师列表
// router.navigateTo(`/bundle_b/pages/tea-specialist/list`)
// }
console.log("🚀 ~ e:", e)
// const index = e.currentTarget.dataset.index
},
handleSort(type: 'distance' | 'sales' | 'open' | 'use') {
const userStore = useUserStore()
const userId = userStore.userInfo?.id
console.log("🚀 ~ userId:", userId)
if (type === 'use' && !userId) {
toast.info('请先登录账号')
router.navigateTo('/pages/login/login', 100)
return
}
if (type === 'distance') {
distance.value = distance.value === 1 ? 0 : 1
} else {
if (sort.value[type] === 1) {
sort.value[type] = 0
} else {
sort.value[type] = 1
}
console.log("🚀 ~ sort.value:", sort.value)
Index.handleResetSearch()
}
},
/**
* 预览图片
*/
handlePreviewImage: (e: {index: number, item: any}) => {
previewImage(swiperList.value[e.index], swiperList.value)
}
}
</script>
<style lang="scss">
page {
background-color: #F9FAFB;
}
.home-bg {
background-color: #F9FAFB;
background-image: url(#{$OSS}images/home/home_bg.png);
background-size: 100%;
background-repeat: no-repeat;
}
.search-box {
display: flex;
height: 100%;
margin-right: 40px;
--wot-search-padding: 0;
--wot-search-side-padding: 0;
:deep() {
.wd-search {
background: transparent !important;
width: 100% !important;
}
.wd-search__block {
background-color: #F3F4F6 !important;
}
.wd-search__input {
// #ifdef MP
padding-left: 32px !important;
padding-right: 32px !important;
// #endif
// #ifndef MP
padding-right: 0 !important;
// #endif
}
}
}
.rounded {
border-radius: 20rpx 0rpx 20rpx 0rpx;
}
.ele-center {
left: 50%;
transform: translateX(-50%);
}
.scroll-item {
display: inline-block;
text-align: center;
background-color: #fff;
border: 2rpx solid #E5E7EB;
border-radius: 20rpx;
padding: 16rpx 32rpx;
}
.active {
border: 2rpx solid #34C759;
color: #34C759;
}
.hour {
height: 44rpx;
line-height: 44rpx;
border-radius: 0 0 16rpx 16rpx;
}
</style>