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

703 lines
15 KiB
Vue
Raw Permalink Normal View History

2026-01-15 14:44:11 +08:00
<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>