roamfuding-xcx/page_user/meishi/must-eat.vue

703 lines
15 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.

<template>
<view class="page">
<!-- 顶部导航栏 -->
<u-navbar title="必吃版" :border-bottom="false" :background="bgc" back-icon-color="#000" title-color='#000'
title-size='36' height='46' id="navbar">
</u-navbar>
<!-- 轮播图区域 -->
<view class="banner-section" v-if="bannerList.length > 0">
<u-swiper :list="bannerList" height="400" :indicator="true" indicator-active-color="#4CAF50"
bg-color="#E8F5E9" radius="16"></u-swiper>
</view>
<!-- 美食分类标签栏 -->
<view class="category-tabs-wrapper">
<scroll-view class="category-tabs" scroll-x scroll-with-animation :show-scrollbar="false">
<view
class="category-tab-item"
:class="{ active: selectedCategoryId === item.id }"
v-for="(item, index) in categoryList"
:key="index"
@click="selectCategory(item)">
<text>{{ item.name }}</text>
</view>
</scroll-view>
<view class="tabs-arrow" @click="toggleFilterPanel">
<view class="arrow-button" :class="{ 'arrow-rotated': showFilterPanel }">
<u-icon name="arrow-right" color="#fff" size="28"></u-icon>
</view>
</view>
<!-- 筛选面板 -->
<view class="filter-panel" v-if="showFilterPanel">
<view class="dropdown-grid">
<view class="grid-item"
:class="{active: selectedCategoryId === item.id}"
v-for="(item, index) in categoryList"
:key="index"
@tap="selectFilterFromPanel(item)">
<text>{{ item.name }}</text>
</view>
</view>
</view>
</view>
<!-- 主内容区 -->
<scroll-view class="content-scroll" scroll-y @scrolltolower="loadMoreStores">
<!-- 食物详情卡片 -->
<view class="dish-detail-card" v-if="currentDish.id">
<!-- 食物图片 -->
<view class="dish-image-wrapper">
<image :src="getDishImage(currentDish)" mode="aspectFill" class="dish-image"></image>
</view>
<!-- 标题横幅 -->
<view class="dish-title-banner">
<view class="banner-triangle"></view>
<text class="dish-name">{{ currentDish.name }}</text>
</view>
<!-- 描述文字 -->
<view class="dish-description">
<text class="description-text">{{ currentDish.description || currentDish.introduction || '暂无描述' }}</text>
</view>
</view>
<!-- 相关餐厅/店铺列表 -->
<view class="store-list-section">
<view
class="store-item"
v-for="(store, index) in storeList"
:key="index"
@click="goToStoreDetail(store)">
<view class="store-image-placeholder">
<image
v-if="getFirstImage(store.picture)"
:src="getFirstImage(store.picture)"
mode="aspectFill"
class="store-image">
</image>
<view v-else class="placeholder-bg"></view>
</view>
<view class="store-info">
<text class="store-name">{{ store.name }}</text>
<view class="store-meta">
<text class="store-price" v-if="store.capita">人均¥{{ store.capita }}</text>
<text class="store-distance" v-if="store.distance">距离{{ formatDistance(store.distance) }}</text>
</view>
<view class="store-address">
<text class="address-text">{{ store.address || '暂无地址' }}</text>
</view>
</view>
<view class="store-arrow">
<text class="arrow-icon"></text>
</view>
</view>
</view>
<!-- 查看更多按钮 -->
<view class="load-more-btn" v-if="hasMore" @click="loadMoreStores">
<text class="load-more-text">查看更多></text>
</view>
<view class="no-more" v-else>
<text>没有更多了</text>
</view>
<!-- 遮罩层 -->
<view class="dropdown-mask" v-if="showFilterPanel" @tap="closeFilterPanel"></view>
</scroll-view>
</view>
</template>
<script>
export default {
data() {
return {
bgc: {
backgroundColor: "#E8F5E9",
},
bannerList: [], // 轮播图列表
categoryList: [], // 美食分类列表
selectedCategoryId: '', // 当前选中的分类ID
currentDish: {}, // 当前显示的美食详情
storeList: [], // 相关店铺列表
pageNum: 1,
pageSize: 20,
total: 0,
hasMore: true,
lat: '',
lng: '',
showFilterPanel: false // 是否显示筛选面板
}
},
onLoad() {
this.getLocation()
this.getBannerList()
this.getCategoryList()
},
methods: {
// 获取轮播图
getBannerList() {
this.$u.get(`/app/carousel/list?pageNum=1&pageSize=99&type=2`).then((res) => {
if(res.code == 200 && res.rows && res.rows.length > 0) {
res.rows.forEach(item => {
this.bannerList.push({
image: item.imageUrl,
id: item.id
})
})
}
}).catch(err => {
console.error('获取轮播图失败', err)
})
},
// 获取位置信息
getLocation() {
uni.getLocation({
type: 'gcj02',
success: (res) => {
this.lat = res.latitude
this.lng = res.longitude
},
fail: (err) => {
console.log('获取位置失败', err)
}
})
},
// 获取美食分类列表
getCategoryList() {
this.$u.get(`/app/dish/list?pageNum=1&pageSize=100`).then((res) => {
if(res.code == 200 && res.rows && res.rows.length > 0) {
this.categoryList = res.rows
// 默认选中第一个
if(this.categoryList.length > 0) {
this.selectedCategoryId = this.categoryList[0].id
this.currentDish = this.categoryList[0]
// 获取该美食的详细信息
this.getDishDetail(this.categoryList[0].id)
this.getStoreList()
}
}
}).catch(err => {
console.error('获取美食列表失败', err)
uni.showToast({
title: '加载失败,请重试',
icon: 'none'
})
})
},
// 获取美食详情
getDishDetail(dishId) {
if(!dishId) return
this.$u.get(`/app/dish/${dishId}`).then((res) => {
if(res.code == 200 && res.data) {
// 合并详情数据到当前美食
this.currentDish = Object.assign({}, this.currentDish, res.data)
}
}).catch(err => {
console.error('获取美食详情失败', err)
})
},
// 选择分类
selectCategory(item) {
this.selectedCategoryId = item.id
this.currentDish = item
this.pageNum = 1
this.storeList = []
this.hasMore = true
// 获取该美食的详细信息
this.getDishDetail(item.id)
this.getStoreList()
},
// 切换筛选面板
toggleFilterPanel() {
this.showFilterPanel = !this.showFilterPanel
},
// 从筛选面板选择分类
selectFilterFromPanel(item) {
this.selectedCategoryId = item.id
this.currentDish = item
this.showFilterPanel = false
this.pageNum = 1
this.storeList = []
this.hasMore = true
// 获取该美食的详细信息
this.getDishDetail(item.id)
this.getStoreList()
},
// 关闭筛选面板
closeFilterPanel() {
this.showFilterPanel = false
},
// 获取相关店铺列表
getStoreList() {
if(!this.selectedCategoryId) return
let url = `/app/store/list?pageNum=${this.pageNum}&pageSize=${this.pageSize}&dishIds=${this.selectedCategoryId}`
// 如果有位置信息,添加距离参数
if(this.lat && this.lng) {
url += `&currLat=${this.lat}&currLon=${this.lng}`
}
this.$u.get(url).then((res) => {
if(res.code == 200) {
this.total = res.total || 0
if(this.pageNum == 1) {
this.storeList = res.rows || []
} else {
this.storeList = this.storeList.concat(res.rows || [])
}
// 判断是否还有更多
this.hasMore = this.storeList.length < this.total
if(this.hasMore) {
this.pageNum++
}
}
}).catch(err => {
console.error('获取店铺列表失败', err)
})
},
// 加载更多店铺
loadMoreStores() {
if(this.hasMore && this.storeList.length < this.total && this.selectedCategoryId) {
this.getStoreList()
}
},
// 获取美食图片
getDishImage(dish) {
if(!dish) return ''
const imageUrl = dish.imageUrl || dish.picture || ''
if(!imageUrl) return ''
// 如果包含逗号,取第一张
if(imageUrl.includes(',')) {
return imageUrl.split(',')[0].trim()
}
return imageUrl.trim()
},
// 获取第一张图片
getFirstImage(pictureString) {
if (!pictureString) return ''
if (pictureString.includes(',')) {
return pictureString.split(',')[0].trim()
}
return pictureString.trim()
},
// 格式化距离
formatDistance(distance) {
if(!distance) return ''
if(distance < 1000) {
return Math.round(distance) + 'm'
} else {
return (distance / 1000).toFixed(1) + 'km'
}
},
// 返回上一页
goBack() {
uni.navigateBack()
},
// 跳转到店铺详情
goToStoreDetail(store) {
uni.navigateTo({
url: '/page_user/meishi/meishixq?storeId=' + store.id
})
}
}
}
</script>
<style lang="scss">
.page {
width: 100%;
min-height: 100vh;
background: #FFFFFF;
}
// 顶部导航栏
.navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
background: #FFFFFF;
padding-top: var(--status-bar-height, 0);
.navbar-content {
display: flex;
align-items: center;
justify-content: space-between;
height: 88rpx;
padding: 0 30rpx;
.nav-left {
width: 60rpx;
display: flex;
align-items: center;
justify-content: flex-start;
.back-icon {
font-size: 48rpx;
color: #000000;
line-height: 1;
font-weight: 300;
}
}
.nav-title {
flex: 1;
text-align: center;
font-size: 36rpx;
font-weight: 600;
color: #000000;
}
.nav-right {
width: 60rpx;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 20rpx;
.more-icon {
font-size: 32rpx;
color: #000000;
line-height: 1;
}
.user-icon {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
background: #E0E0E0;
}
}
}
}
// 轮播图区域
.banner-section {
width: 100%;
background: #E8F5E9;
padding: 20rpx 20rpx 0;
::v-deep .u-swiper {
border-radius: 16rpx;
overflow: hidden;
}
::v-deep .u-swiper-item {
border-radius: 16rpx;
overflow: hidden;
}
::v-deep .u-swiper-image {
border-radius: 16rpx;
}
}
// 美食分类标签栏
.category-tabs-wrapper {
background: #E8F5E9;
display: flex;
align-items: center;
padding: 20rpx 0;
position: relative;
z-index: 101;
.category-tabs {
flex: 1;
white-space: nowrap;
padding-left: 20rpx;
overflow: hidden;
.category-tab-item {
display: inline-block;
padding: 12rpx 24rpx;
margin-right: 16rpx;
border-radius: 10rpx;
background: #FFFFFF;
border: none;
font-size: 28rpx;
color: #333333;
transition: all 0.3s;
white-space: nowrap;
&.active {
background: #CAF4E6;
border: none;
color: #004D34;
font-weight: 500;
}
}
}
.tabs-arrow {
padding: 0 20rpx;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
.arrow-button {
width: 48rpx;
height: 48rpx;
border-radius: 50%;
background: #2E7D32;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2rpx 8rpx rgba(46, 125, 50, 0.3);
transition: transform 0.3s;
&.arrow-rotated {
transform: rotate(90deg);
}
}
}
// 筛选面板样式
.filter-panel {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: #fff;
z-index: 102;
border-top: 1rpx solid #F5F5F5;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
.dropdown-grid {
padding: 30rpx;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20rpx;
.grid-item {
padding: 12rpx 24rpx;
border-radius: 10rpx;
font-size: 26rpx;
background: #F5F5F5;
color: #666;
transition: all 0.3s;
white-space: nowrap;
text-align: center;
text {
display: block;
}
&.active {
background: #4CAF50;
color: #fff;
font-weight: bold;
}
}
}
}
}
// 遮罩层样式
.dropdown-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.3);
z-index: 99;
height: 100vh;
}
// 主内容区
.content-scroll {
width: 100%;
padding-bottom: 40rpx;
}
// 食物详情卡片
.dish-detail-card {
background: #FFFFFF;
margin: 20rpx;
border-radius: 16rpx;
overflow: hidden;
.dish-image-wrapper {
width: 100%;
height: 400rpx;
overflow: hidden;
.dish-image {
width: 100%;
height: 100%;
}
}
.dish-title-banner {
position: relative;
background: #4CAF50;
padding: 20rpx 30rpx 20rpx 50rpx;
margin-top: -4rpx;
border-radius: 0 0 20rpx 20rpx;
.banner-triangle {
position: absolute;
left: 0;
top: 0;
width: 0;
height: 0;
border-style: solid;
border-width: 0 0 60rpx 40rpx;
border-color: transparent transparent #4CAF50 transparent;
}
.dish-name {
font-size: 32rpx;
font-weight: 600;
color: #FFFFFF;
}
}
.dish-description {
padding: 24rpx 30rpx;
display: flex;
align-items: flex-start;
justify-content: space-between;
.description-text {
flex: 1;
font-size: 28rpx;
color: #666666;
line-height: 1.6;
}
.check-icon {
margin-left: 16rpx;
font-size: 32rpx;
color: #4CAF50;
flex-shrink: 0;
}
}
}
// 店铺列表区域
.store-list-section {
padding: 0 20rpx;
.store-item {
display: flex;
align-items: center;
background: #FFFFFF;
border-radius: 12rpx;
padding: 20rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
.store-image-placeholder {
width: 120rpx;
height: 120rpx;
border-radius: 8rpx;
overflow: hidden;
flex-shrink: 0;
background: #F5F5F5;
.store-image {
width: 100%;
height: 100%;
}
.placeholder-bg {
width: 100%;
height: 100%;
background: #E0E0E0;
}
}
.store-info {
flex: 1;
padding-left: 20rpx;
display: flex;
flex-direction: column;
gap: 8rpx;
.store-name {
font-size: 30rpx;
font-weight: 600;
color: #000000;
}
.store-meta {
display: flex;
align-items: center;
gap: 20rpx;
.store-price {
font-size: 26rpx;
color: #333333;
}
.store-distance {
font-size: 26rpx;
color: #333333;
}
}
.store-address {
display: flex;
align-items: flex-start;
.address-dot {
font-size: 24rpx;
color: #999999;
margin-right: 8rpx;
flex-shrink: 0;
}
.address-text {
flex: 1;
font-size: 24rpx;
color: #999999;
line-height: 1.5;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
line-clamp: 1;
-webkit-box-orient: vertical;
}
}
}
.store-arrow {
padding-left: 16rpx;
flex-shrink: 0;
.arrow-icon {
font-size: 32rpx;
color: #CCCCCC;
line-height: 1;
}
}
}
}
// 查看更多按钮
.load-more-btn {
text-align: center;
padding: 40rpx 0;
.load-more-text {
font-size: 28rpx;
color: #4CAF50;
}
}
.no-more {
text-align: center;
padding: 40rpx 0;
font-size: 24rpx;
color: #999999;
}
</style>