Files
cheflinkuser/src/pages/home/components/tabbar-browse/tabbar-browse.vue
T

395 lines
8.8 KiB
Vue

<script setup lang="ts">
import Search from "../tabbar-home/components/search.vue";
import { useConfigStore, useUserStore } from "@/store";
import BrowseSkeleton from "./components/browse-skeleton.vue";
import {
appMerchantNearbyListPost,
appSearchSearchRecipePost,
} from "@/service";
import {thumbnailImg} from "@/utils/utils";
const configStore = useConfigStore();
const userStore = useUserStore();
const loading = ref(false);
const recipePreviewLimit = 4;
const { t } = useI18n();
function navigateTo(url: string) {
if(userStore.checkLogin()) {
uni.navigateTo({
url,
});
}
}
async function initData() {
if(!recipeData.value) {
loading.value = true;
}
getRecipeData()
getNearbyStoreList()
}
// 获取菜谱数据
const recipeData = ref<any[]>([]);
function getRecipeData() {
appSearchSearchRecipePost({
params: {
pageNum: 1,
pageSize: 10,
},
body: {}
}).then(res=> {
console.log('菜谱数据', res)
recipeData.value = res.rows;
}).finally(()=> {
loading.value = false;
})
}
function navigateToRecipeDetail(id: string | number) {
navigateTo(`/pages-user/pages/recipe/index?id=${id}`)
}
function navigateToRecipeList() {
navigateTo('/pages-user/pages/recipe/list')
}
// 获取附近店铺
const nearbyStoreList = ref<any[]>([]);
const nearbyStoreLoaded = ref(false);
function getNearbyStoreList() {
nearbyStoreLoaded.value = false;
appMerchantNearbyListPost({
body: {
lat: userStore.userLocation.latitude,
lng: userStore.userLocation.longitude,
},
})
.then((res) => {
console.log("附近店铺", res);
nearbyStoreList.value = res.data || [];
})
.catch(() => {
nearbyStoreList.value = [];
})
.finally(() => {
nearbyStoreLoaded.value = true;
});
}
function handleClickStore(item: any) {
navigateTo(`/pages-store/pages/store/index?id=${item.id}`);
}
function getStoreName(item: any) {
return item?.merchantName || "--";
}
function getStoreLogo(item: any) {
return item?.logo || "";
}
function getStoreRate(item: any) {
const rating = Number(item?.rating ?? 0);
return Number.isFinite(rating) && rating > 0 ? rating.toFixed(1) : "5.0";
}
function getPreviewRecipeList() {
return recipeData.value.slice(0, recipePreviewLimit)
}
function showRecipeMore() {
return recipeData.value.length > recipePreviewLimit
}
async function getPlatformDefaultStoreInfo() {}
defineExpose({
initData,
init: getPlatformDefaultStoreInfo,
});
</script>
<template>
<view
class="bg-#fff"
:style="[
{
height: configStore.windowHeight + 'px',
},
]"
>
<z-paging ref="paging">
<template #top>
<status-bar />
</template>
<view
v-show="loading"
class="animate-in fade-in animate-ease-out animate-duration-300"
>
<browse-skeleton />
</view>
<view
v-show="!loading"
class="animate-in fade-in animate-ease-in animate-duration-300"
>
<view class="px-24rpx pt-16rpx">
<search />
</view>
<view class="browse-wrap px-24rpx">
<view class="section-title mt-36rpx">{{ t("pages.browse.titleRecipes") }}</view>
<view class="mt-28rpx">
<scroll-view scroll-x class="recipe-scroll" :show-scrollbar="false" :enable-flex="true">
<view class="recipe-track">
<view
v-for="item in getPreviewRecipeList()"
:key="item.id"
class="recipe-item"
@click="navigateToRecipeDetail(item.id)"
>
<image
:src="thumbnailImg(item?.recipeImage?.split(',')[0])"
class="recipe-avatar"
mode="aspectFill"
/>
<text class="recipe-name line-clamp-1">{{ item.recipeName }}</text>
</view>
<view v-if="showRecipeMore()" class="recipe-more" @click="navigateToRecipeList">
<view class="recipe-more__text-wrap">
<text class="recipe-more__text">{{ t("pages.browse.moreRecipes") }}</text>
</view>
<i class="i-carbon:chevron-right recipe-more__icon"></i>
</view>
</view>
</scroll-view>
</view>
<view class="section-title mt-54rpx">{{ t("pages.browse.titleCuisine") }}</view>
<scroll-view
v-if="nearbyStoreList.length > 0"
scroll-x
class="store-scroll mt-28rpx pb-40rpx"
:show-scrollbar="false"
:enable-flex="true"
>
<view class="store-track">
<view
v-for="item in nearbyStoreList"
:key="item.id"
@click="handleClickStore(item)"
class="store-card"
>
<image
:src="thumbnailImg(getStoreLogo(item))"
class="store-card__cover"
mode="aspectFill"
/>
<view class="store-card__right">
<view class="store-card__name line-clamp-2">{{ getStoreName(item) }}</view>
<view class="store-card__rating">★★★★★ {{ getStoreRate(item) }}</view>
<view class="store-card__brand">{{ t("pages.browse.brandTag") }}</view>
<view class="store-card__arrow center">
<i class="i-carbon:chevron-right text-30rpx text-white"></i>
</view>
</view>
</view>
</view>
</scroll-view>
<view
v-else-if="nearbyStoreLoaded && !loading"
class="store-empty mt-28rpx pb-40rpx"
>
<view class="store-empty__icon-wrap center">
<i class="i-carbon:location-off store-empty__icon"></i>
</view>
<text class="store-empty__text">{{ t("pages.browse.nearbyEmpty") }}</text>
</view>
</view>
</view>
<template #bottom>
<view class="h-50px"></view>
<view :style="[configStore.iosSafeBottomPlaceholder]"></view>
</template>
</z-paging>
</view>
</template>
<style scoped lang="scss">
.browse-wrap {
background: #fff;
}
.section-title {
font-size: 32rpx;
line-height: 48rpx;
// font-weight: 700;
color: #1c1c1c;
}
.recipe-scroll,
.store-scroll {
width: 100%;
}
.recipe-track {
display: flex;
align-items: flex-start;
gap: 22rpx;
padding-right: 24rpx;
}
.recipe-item {
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: center;
}
.recipe-avatar {
width: 112rpx;
height: 112rpx;
border-radius: 56rpx;
background: #f1f1f1;
}
.recipe-name {
max-width: 140rpx;
margin-top: 14rpx;
text-align: center;
font-size: 26rpx;
line-height: 32rpx;
color: #222;
font-weight: 500;
}
.recipe-more {
flex-shrink: 0;
height: 112rpx;
display: flex;
align-items: center;
justify-content: space-between;
gap: 4rpx;
padding: 4rpx 8rpx;
}
.recipe-more__text-wrap {
display: flex;
flex-direction: column;
justify-content: center;
gap: 6rpx;
}
.recipe-more__text {
font-size: 24rpx;
line-height: 28rpx;
color: #8a8a8a;
font-weight: 500;
writing-mode: vertical-rl;
text-orientation: upright;
letter-spacing: 2rpx;
white-space: nowrap;
}
.recipe-more__icon {
font-size: 20rpx;
color: #8a8a8a;
}
.store-track {
display: flex;
align-items: stretch;
gap: 22rpx;
padding-right: 24rpx;
}
.store-card {
width: 404rpx;
height: 220rpx;
border-radius: 20rpx;
overflow: hidden;
background: #fff;
flex-shrink: 0;
display: flex;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
}
.store-card__cover {
width: 184rpx;
height: 220rpx;
background: #f2f2f2;
flex-shrink: 0;
}
.store-card__right {
flex: 1;
padding: 16rpx 14rpx 14rpx 16rpx;
position: relative;
}
.store-card__name {
font-size: 28rpx;
line-height: 34rpx;
color: #191919;
font-weight: 600;
}
.store-card__rating {
margin-top: 8rpx;
color: #111;
font-size: 26rpx;
line-height: 30rpx;
font-weight: 500;
}
.store-card__brand {
margin-top: 12rpx;
color: #d39a48;
font-size: 24rpx;
line-height: 28rpx;
font-weight: 500;
}
.store-card__arrow {
position: absolute;
right: 10rpx;
bottom: 12rpx;
width: 52rpx;
height: 52rpx;
border-radius: 50%;
background: #111;
}
.store-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 220rpx;
padding: 48rpx 32rpx;
border-radius: 20rpx;
background: #f7f7f7;
}
.store-empty__icon-wrap {
width: 96rpx;
height: 96rpx;
border-radius: 50%;
background: #ececec;
}
.store-empty__icon {
font-size: 44rpx;
color: #b0b0b0;
}
.store-empty__text {
margin-top: 24rpx;
font-size: 28rpx;
line-height: 40rpx;
color: #8a8a8a;
text-align: center;
}
</style>