feat: 首页接口对接
This commit is contained in:
@@ -12,16 +12,17 @@ import { AGREEMENT_DATE, CUSTOMER_SERVICE_PHONE, CUSTOMER_SERVICE_TIME } from "@
|
||||
import { USE_MOCK_CONTENT } from "@/config/env";
|
||||
import { clone, cnDate, unique } from "@/utils";
|
||||
import { applyLocalState, toPublicCard } from "@/api/benefit";
|
||||
import homepageMock from "@/mock/v1/homepage.json";
|
||||
// 首页已切真实接口,mock 保留注释便于回退
|
||||
// import homepageMock from "@/mock/v1/homepage.json";
|
||||
import categoriesMock from "@/mock/v1/categories.json";
|
||||
import merchantsMock from "@/mock/v1/merchants.json";
|
||||
import detailsMock from "@/mock/v1/merchant-details.json";
|
||||
import * as contentApi from "@/request/api/content";
|
||||
|
||||
/** 首页聚合(品牌 / hero / 板块卡 / 精选推荐 / 导览 / 页脚) */
|
||||
/** 首页聚合(品牌 / hero / 板块卡 / 精选推荐 / 导览 / 页脚)—— 走真实接口 */
|
||||
export const getHomepage = async () => {
|
||||
if (!USE_MOCK_CONTENT) return contentApi.fetchHomepage();
|
||||
return clone(homepageMock);
|
||||
// if (USE_MOCK_CONTENT) return clone(homepageMock);
|
||||
return contentApi.fetchHomepage();
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
/**
|
||||
* Tab 1 · 主页
|
||||
* 数据来自 GET /v1/homepage(mock 阶段读 src/mock/v1/homepage.json)。
|
||||
* 数据来自 GET /v1/homepage。
|
||||
* 纵向顺序:
|
||||
* 品牌开场区(吸顶固定)→ 主视觉大卡(180px)→ 玩转龙里(左一通高 + 右二上下)
|
||||
* → 全域导览 → 精选推荐 → 免责声明
|
||||
@@ -23,16 +23,31 @@ const brand = computed(() => home.value?.brand || {});
|
||||
const hero = computed(() => home.value?.hero || {});
|
||||
const entries = computed(() => home.value?.entries || []);
|
||||
const footerLines = computed(() => home.value?.footer_lines || []);
|
||||
/** 玩转龙里:有板块数据才展示整块 */
|
||||
const hasSections = computed(() => entries.value.length > 0);
|
||||
/** 全域导览:有图或有标题才展示整块 */
|
||||
const hasGuide = computed(() => !!(home.value?.guide_image_url || home.value?.guide_title));
|
||||
/** 精选推荐:有卡片才展示整块(含标题行与「更多」) */
|
||||
const hasFeatured = computed(() => featured.value.length > 0);
|
||||
|
||||
const entryOf = (key) => entries.value.find((e) => e.section_key === key) || {};
|
||||
|
||||
const load = async () => {
|
||||
const data = await contentApi.getHomepage();
|
||||
home.value = data || {};
|
||||
featured.value = data?.featured?.items || [];
|
||||
};
|
||||
let loading = false;
|
||||
|
||||
load();
|
||||
const load = async () => {
|
||||
if (loading) return;
|
||||
loading = true;
|
||||
try {
|
||||
const data = await contentApi.getHomepage();
|
||||
home.value = data || {};
|
||||
featured.value = data?.featured?.items || [];
|
||||
} catch (e) {
|
||||
home.value = {};
|
||||
featured.value = [];
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
};
|
||||
|
||||
onShow(() => {
|
||||
if (!home.value?.brand_name) load();
|
||||
@@ -97,44 +112,50 @@ const onMapTap = () => {
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 玩转龙里:左一通高 + 右二上下 -->
|
||||
<view class="flex items-baseline justify-between mt-5 mb-[10px] mx-[2px]">
|
||||
<text class="text-title font-semibold text-ink">玩转龙里</text>
|
||||
</view>
|
||||
<view class="flex gap-[10px] h-[226px] mt-[2px]">
|
||||
<SectionTile class="flex-[1.06] min-w-0" :section="entryOf('stay')" variant="tall" />
|
||||
<view class="flex-1 min-w-0 flex flex-col gap-[10px]">
|
||||
<SectionTile :section="entryOf('food')" variant="short" />
|
||||
<SectionTile :section="entryOf('specialty')" variant="short" />
|
||||
<!-- 玩转龙里:左一通高 + 右二上下(无数据整块不展示) -->
|
||||
<template v-if="hasSections">
|
||||
<view class="flex items-baseline justify-between mt-5 mb-[10px] mx-[2px]">
|
||||
<text class="text-title font-semibold text-ink">玩转龙里</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex gap-[10px] h-[226px] mt-[2px]">
|
||||
<SectionTile class="flex-[1.06] min-w-0" :section="entryOf('stay')" variant="tall" />
|
||||
<view class="flex-1 min-w-0 flex flex-col gap-[10px]">
|
||||
<SectionTile :section="entryOf('food')" variant="short" />
|
||||
<SectionTile :section="entryOf('specialty')" variant="short" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 全域导览 -->
|
||||
<view class="flex items-baseline justify-between mt-5 mb-[10px] mx-[2px]">
|
||||
<text class="text-title font-semibold text-ink">全域导览</text>
|
||||
</view>
|
||||
<view class="relative h-[112px] rounded-tile overflow-hidden mt-[2px] shadow-elev2" @click="onMapTap">
|
||||
<image class="absolute inset-0 w-full h-full" :src="home.guide_image_url" mode="aspectFill" />
|
||||
<view class="absolute inset-0 scrim-map" />
|
||||
<view
|
||||
class="pointer-events-none absolute inset-0 rounded-tile bg-[radial-gradient(200px_130px_at_88%_112%,rgba(63,223,168,0.4),transparent_72%)]" />
|
||||
<view class="absolute inset-0 z-[2] flex flex-col justify-center items-start px-[18px]">
|
||||
<text class="text-white text-[16px] font-semibold">{{ home.guide_title }}</text>
|
||||
<text class="text-[rgba(255,255,255,0.85)] text-hint mt-1">{{ home.guide_subtitle }}</text>
|
||||
<!-- 全域导览(无数据整块不展示) -->
|
||||
<template v-if="hasGuide">
|
||||
<view class="flex items-baseline justify-between mt-5 mb-[10px] mx-[2px]">
|
||||
<text class="text-title font-semibold text-ink">全域导览</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="relative h-[112px] rounded-tile overflow-hidden mt-[2px] shadow-elev2" @click="onMapTap">
|
||||
<image class="absolute inset-0 w-full h-full" :src="home.guide_image_url" mode="aspectFill" />
|
||||
<view class="absolute inset-0 scrim-map" />
|
||||
<view
|
||||
class="pointer-events-none absolute inset-0 rounded-tile bg-[radial-gradient(200px_130px_at_88%_112%,rgba(63,223,168,0.4),transparent_72%)]" />
|
||||
<view class="absolute inset-0 z-[2] flex flex-col justify-center items-start px-[18px]">
|
||||
<text class="text-white text-[16px] font-semibold">{{ home.guide_title }}</text>
|
||||
<text class="text-[rgba(255,255,255,0.85)] text-hint mt-1">{{ home.guide_subtitle }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 精选推荐(卡片自身完成跳转,页面不再重复绑定 tap) -->
|
||||
<view class="flex items-baseline justify-between mt-5 mb-[10px] mx-[2px]">
|
||||
<text class="text-title font-semibold text-ink">精选推荐</text>
|
||||
<view class="flex items-center gap-[2px] text-[11.5px] text-ink3" @click="onMoreFeatured">
|
||||
<text>更多</text>
|
||||
<AppIcon name="chev" tone="ink3" :size="14" />
|
||||
<!-- 精选推荐(无卡片整块不展示;卡片自身完成跳转,页面不再重复绑定 tap) -->
|
||||
<template v-if="hasFeatured">
|
||||
<view class="flex items-baseline justify-between mt-5 mb-[10px] mx-[2px]">
|
||||
<text class="text-title font-semibold text-ink">精选推荐</text>
|
||||
<view class="flex items-center gap-[2px] text-[11.5px] text-ink3" @click="onMoreFeatured">
|
||||
<text>更多</text>
|
||||
<AppIcon name="chev" tone="ink3" :size="14" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-for="(item, i) in featured" :key="item.id" :class="i ? 'mt-[10px]' : ''">
|
||||
<ContentCard :item="item" />
|
||||
</view>
|
||||
<view v-for="(item, i) in featured" :key="item.id" :class="i ? 'mt-[10px]' : ''">
|
||||
<ContentCard :item="item" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<view v-for="(line, i) in footerLines" :key="i"
|
||||
:class="i ? 'mx-1 text-[10px] leading-[1.7] text-ink3 text-center' : 'mt-5 mx-1 text-[10px] leading-[1.7] text-ink3 text-center'">
|
||||
|
||||
Reference in New Issue
Block a user