703 lines
15 KiB
Vue
703 lines
15 KiB
Vue
<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> |