feat: 首页接口对接

This commit is contained in:
zoujing
2026-09-17 17:05:35 +08:00
parent 56c419140e
commit e675ecc3d7
2 changed files with 66 additions and 44 deletions

View File

@@ -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();
};
/**

View File

@@ -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'">