feat: 修复二级页面头图展示问题
This commit is contained in:
1 parent
d2dab27afa
commit
87cf7dfbaa
4 files changed
+67
-130
No files matched your search
+20
-13
@@ -34,27 +34,34 @@ const buildGroups = (section) => {
|
||||
});
|
||||
};
|
||||
|
||||
/** 板块列表(4 个);coverImage 缺失时用该板块首个条目的主图(只读字段,不拼路径) */
|
||||
export const getSections = async () => {
|
||||
if (!USE_MOCK_CONTENT) return contentApi.fetchSections();
|
||||
return clone(
|
||||
sectionsMock.map((section) => {
|
||||
if (section.coverImage) return section;
|
||||
const firstGroup = (catalogMock[section.collections[0]] || [])[0];
|
||||
const firstId = firstGroup?.item_ids?.[0];
|
||||
return { ...section, coverImage: itemsMock[firstId]?.main_image_url || "" };
|
||||
})
|
||||
);
|
||||
/**
|
||||
* 板块封面兜底:`coverImage` 缺失时取该板块**首个分组首个条目**的主图(只读字段,不拼路径)。
|
||||
*
|
||||
* ⚠️ 兜底必须抽出来给 `getSections` 与 `getSection` **共用**:两处口径一旦不一致,就会出现
|
||||
* 「首页板块卡有图、点进板块列表页头图空白」—— `sections.json` 里只有 `tourism` 自带
|
||||
* `coverImage`,`lodging` / `food` / `specialties` 三个都没这个字段。
|
||||
*/
|
||||
const withCoverFallback = (section) => {
|
||||
if (!section || section.coverImage) return section;
|
||||
const firstGroup = (catalogMock[section.collections[0]] || [])[0];
|
||||
const firstId = firstGroup?.item_ids?.[0];
|
||||
return { ...section, coverImage: itemsMock[firstId]?.main_image_url || "" };
|
||||
};
|
||||
|
||||
/** 板块详情:定义 + 分组(含条目)+ 素材 */
|
||||
/** 板块列表(4 个) */
|
||||
export const getSections = async () => {
|
||||
if (!USE_MOCK_CONTENT) return contentApi.fetchSections();
|
||||
return clone(sectionsMock.map(withCoverFallback));
|
||||
};
|
||||
|
||||
/** 板块详情:定义 + 分组(含条目)+ 素材;封面同样走兜底,供列表页头图使用 */
|
||||
export const getSection = async (sectionKey) => {
|
||||
if (!USE_MOCK_CONTENT) return contentApi.fetchSection(sectionKey);
|
||||
const section = sectionsMock.find((s) => s.key === sectionKey);
|
||||
if (!section) return null;
|
||||
const groups = buildGroups(section);
|
||||
const supportAssets = (assetsMock || []).filter((a) => section.supportAssetIds.includes(a.entity_id));
|
||||
return clone({ ...section, groups, supportAssets });
|
||||
return clone({ ...withCoverFallback(section), groups, supportAssets });
|
||||
};
|
||||
|
||||
/** 条目详情 */
|
||||
|
||||
@@ -105,12 +105,8 @@ const onTap = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view
|
||||
class="relative flex h-[130px] bg-white border-[0.5px] rounded-card mt-[11px]"
|
||||
:class="rootStateCls"
|
||||
hover-class="opacity-90"
|
||||
@click="onTap"
|
||||
>
|
||||
<view class="relative flex h-[130px] bg-white border-[0.5px] rounded-card mt-[11px]" :class="rootStateCls"
|
||||
hover-class="opacity-90" @click="onTap">
|
||||
<!-- 左:信息区(四行固定顺序) -->
|
||||
<view class="flex-1 min-w-0 rounded-l-card px-[14px] pt-[13px] pb-3 flex flex-col" :class="sideStateCls">
|
||||
<!-- ① 类型 -->
|
||||
@@ -126,25 +122,19 @@ const onTap = () => {
|
||||
<text class="text-[15px] font-bold" :class="priceNumCls">¥</text>
|
||||
<text class="text-display font-extrabold tracking-[-1px]" :class="priceNumCls">{{ deal }}</text>
|
||||
<text v-if="original" class="text-[11.5px] font-medium line-through" :class="wasCls">¥{{ original }}</text>
|
||||
<text
|
||||
v-if="pct"
|
||||
class="ml-auto text-[10px] font-bold tracking-[0.2px] rounded-[6px] px-[6px] py-[3px]"
|
||||
:class="pctCls"
|
||||
>
|
||||
<text v-if="pct" class="ml-auto text-[10px] font-bold tracking-[0.2px] rounded-[6px] px-[6px] py-[3px]"
|
||||
:class="pctCls">
|
||||
{{ pct }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- ③ 规格胶囊 + 适用范围 -->
|
||||
<view class="flex items-center gap-[6px] mt-[7px] text-[11px] whitespace-nowrap" :class="scopeCls">
|
||||
<text
|
||||
v-if="spec"
|
||||
class="shrink-0 text-hint font-semibold rounded-[6px] px-[6px] py-[2px] border-[0.5px]"
|
||||
:class="specCls"
|
||||
>
|
||||
<text v-if="spec" class="shrink-0 text-hint font-semibold rounded-[6px] px-[6px] py-[2px] border-[0.5px]"
|
||||
:class="specCls">
|
||||
{{ spec }}
|
||||
</text>
|
||||
<text class="min-w-0 truncate">{{ b.scope_desc || b.merchant_name || "" }}</text>
|
||||
<view class="min-w-0 flex-1 line-clamp-1">{{ b.scope_desc || b.merchant_name || "" }}</view>
|
||||
</view>
|
||||
|
||||
<!-- ④ 有效期 + 第二项随态 -->
|
||||
@@ -153,10 +143,8 @@ const onTap = () => {
|
||||
<AppIcon name="clock" tone="ink3" :size="12" />
|
||||
<text>{{ validText }}</text>
|
||||
</view>
|
||||
<view
|
||||
class="flex items-center gap-[3.5px] shrink-0 whitespace-nowrap"
|
||||
:class="secondMeta.ok ? 'text-[#00A46C]' : ''"
|
||||
>
|
||||
<view class="flex items-center gap-[3.5px] shrink-0 whitespace-nowrap"
|
||||
:class="secondMeta.ok ? 'text-[#00A46C]' : ''">
|
||||
<AppIcon :name="secondMeta.icon" :tone="secondMeta.ok ? 'brand' : 'ink3'" :size="12" />
|
||||
<text>{{ secondMeta.text }}</text>
|
||||
</view>
|
||||
@@ -165,20 +153,18 @@ const onTap = () => {
|
||||
|
||||
<!-- 右:配图区(62%/38% 撕口) -->
|
||||
<view
|
||||
class="relative w-[38%] min-w-[112px] shrink-0 overflow-hidden rounded-r-card border-l border-dashed border-[rgba(255,138,61,0.45)]"
|
||||
>
|
||||
class="relative w-[38%] min-w-[112px] shrink-0 overflow-hidden rounded-r-card border-l border-dashed border-[rgba(255,138,61,0.45)]">
|
||||
<image class="absolute inset-0 w-full h-full" :class="coverImgCls" :src="b.image_url" mode="aspectFill" />
|
||||
<view class="absolute inset-0 scrim-cover" />
|
||||
|
||||
<view class="absolute top-[11px] right-[11px] z-[2] text-right text-[11.5px] font-bold leading-[1.5] text-white tracking-[0.6px] [rotate:-8deg] [text-shadow:0_1px_7px_rgba(0,0,0,0.5)]">
|
||||
<view
|
||||
class="absolute top-[11px] right-[11px] z-[2] text-right text-[11.5px] font-bold leading-[1.5] text-white tracking-[0.6px] [rotate:-8deg] [text-shadow:0_1px_7px_rgba(0,0,0,0.5)]">
|
||||
<text v-for="(line, i) in slogan" :key="i" class="block">{{ line }}</text>
|
||||
</view>
|
||||
|
||||
<view class="absolute left-0 right-0 bottom-3 z-[2] flex justify-center">
|
||||
<view
|
||||
class="h-[30px] px-4 rounded-full text-white text-cap font-semibold flex items-center whitespace-nowrap"
|
||||
:class="ctaStateCls"
|
||||
>
|
||||
<view class="h-[30px] px-4 rounded-full text-white text-cap font-semibold flex items-center whitespace-nowrap"
|
||||
:class="ctaStateCls">
|
||||
{{ ctaText }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -25,17 +25,10 @@ const onTap = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view
|
||||
class="flex gap-[11px] bg-white rounded-card p-[10px] shadow-elev1 border-[0.5px] border-line"
|
||||
hover-class="opacity-90"
|
||||
@click="onTap"
|
||||
>
|
||||
<image
|
||||
class="shrink-0 rounded-[12px] bg-alt"
|
||||
:src="item.main_image_url"
|
||||
mode="aspectFill"
|
||||
:style="{ width: thumbSize + 'px', height: thumbSize + 'px' }"
|
||||
/>
|
||||
<view class="flex gap-[11px] bg-white rounded-card p-[10px] shadow-elev1 border-[0.5px] border-line"
|
||||
hover-class="opacity-90" @click="onTap">
|
||||
<image class="shrink-0 rounded-[12px] bg-alt" :src="item.main_image_url" mode="aspectFill"
|
||||
:style="{ width: thumbSize + 'px', height: thumbSize + 'px' }" />
|
||||
|
||||
<view class="min-w-0 flex-1 flex flex-col">
|
||||
<view class="text-[13.5px] font-semibold leading-[1.35] text-ink line-clamp-2">{{ item.name }}</view>
|
||||
@@ -44,9 +37,10 @@ const onTap = () => {
|
||||
{{ item.description }}
|
||||
</view>
|
||||
|
||||
<view v-if="showAddress && item.address_text" class="mt-auto pt-[5px] flex items-center gap-[3px] text-[10px] text-ink3">
|
||||
<view v-if="showAddress && item.address_text"
|
||||
class="mt-auto pt-[5px] flex items-center gap-[3px] text-[10px] text-ink3">
|
||||
<AppIcon name="pin" tone="ink3" :size="11" />
|
||||
<text class="truncate">{{ item.address_text }}</text>
|
||||
<view class="min-w-0 flex-1 line-clamp-1">{{ item.address_text }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -33,10 +33,6 @@ const businessHours = computed(() => item.value?.business_hours || "");
|
||||
|
||||
/**
|
||||
* 卡片通用底:白卡 + 22px 圆角 + 0.5px 描边 + 双侧柔影
|
||||
* ⚠️ `border-[0.5px]` **必须紧跟颜色类**:Tailwind v4 里 `border-<宽度>` 的默认色是
|
||||
* `currentColor`,只写宽度会让卡片描边吃掉继承的文字色 → 渲染成近黑色。
|
||||
* 普通卡与「到店权益」染色卡写成两套**互斥完整类串**,不走「CARD + 覆盖」拼接 ——
|
||||
* 同类选择器优先级相同时,谁生效取决于样式表顺序而非 class 书写顺序,拼接不可靠。
|
||||
*/
|
||||
const CARD = "bg-white rounded-hero p-[18px] mx-3 mt-3 border-[0.5px] border-line shadow-block";
|
||||
/** 品牌染色卡(到店权益) */
|
||||
@@ -131,33 +127,15 @@ onShareAppMessage(() => ({
|
||||
<view class="absolute inset-0 bg-brand-soft" :style="{ opacity: navProgress }" />
|
||||
<view class="relative">
|
||||
<AppStatusBar />
|
||||
<AppNavBar
|
||||
:theme="navSolid ? 'dark' : 'light'"
|
||||
bare
|
||||
:glass="!navSolid"
|
||||
:title="item.name"
|
||||
:subtitle="item.type"
|
||||
show-back
|
||||
/>
|
||||
<AppNavBar :theme="navSolid ? 'dark' : 'light'" bare :glass="!navSolid" :title="item.name"
|
||||
:subtitle="item.type" show-back />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 头图:多图自动轮播(≥2 张才启用),点击任意位置预览大图。
|
||||
⚠️ 点击必须绑在**最外层容器**上,不能只绑 <image>:上下两条 scrim 渐隐层与
|
||||
指示点都是绝对定位的 <view>,会吃掉落在图片上的 tap,只绑 image 的话
|
||||
只有中段一条窄带能点开预览。tap 默认冒泡,所以只在最外层绑一次即可
|
||||
(image 上不要再绑,否则会连触两次)。 -->
|
||||
<view class="relative overflow-hidden bg-[#0E4432]">
|
||||
<view class="relative h-[316px]" @click="onPreview(current)">
|
||||
<swiper
|
||||
class="w-full h-[316px] bg-[#0E4432]"
|
||||
:current="current"
|
||||
:circular="images.length > 1"
|
||||
:autoplay="images.length > 1"
|
||||
:interval="3500"
|
||||
:duration="500"
|
||||
@change="onSwiperChange"
|
||||
>
|
||||
<swiper class="w-full h-[316px] bg-[#0E4432]" :current="current" :circular="images.length > 1"
|
||||
:autoplay="images.length > 1" :interval="3500" :duration="500" @change="onSwiperChange">
|
||||
<swiper-item v-for="(url, i) in images" :key="i">
|
||||
<image class="block w-full h-[316px]" :src="url" mode="aspectFill" />
|
||||
</swiper-item>
|
||||
@@ -166,20 +144,14 @@ onShareAppMessage(() => ({
|
||||
<view class="absolute left-0 right-0 top-0 h-[140px] scrim-dtop" />
|
||||
<view class="absolute left-0 right-0 bottom-0 h-[170px] scrim-dbottom" />
|
||||
|
||||
<view
|
||||
v-if="images.length > 1"
|
||||
class="absolute right-[14px] bottom-[52px] h-[22px] px-[10px] rounded-full flex items-center text-[11px] text-white bg-[rgba(6,26,19,0.44)] border-[0.5px] border-[rgba(255,255,255,0.24)]"
|
||||
>
|
||||
<view v-if="images.length > 1"
|
||||
class="absolute right-[14px] bottom-[52px] h-[22px] px-[10px] rounded-full flex items-center text-[11px] text-white bg-[rgba(6,26,19,0.44)] border-[0.5px] border-[rgba(255,255,255,0.24)]">
|
||||
{{ current + 1 }} / {{ images.length }}
|
||||
</view>
|
||||
|
||||
<view v-if="images.length > 1" class="absolute left-0 right-0 bottom-[52px] flex justify-center gap-[5px]">
|
||||
<view
|
||||
v-for="(url, i) in images"
|
||||
:key="i"
|
||||
class="rounded-full"
|
||||
:class="i === current ? 'w-4 h-[5px] bg-white' : 'w-[5px] h-[5px] bg-[rgba(255,255,255,0.5)]'"
|
||||
/>
|
||||
<view v-for="(url, i) in images" :key="i" class="rounded-full"
|
||||
:class="i === current ? 'w-4 h-[5px] bg-white' : 'w-[5px] h-[5px] bg-[rgba(255,255,255,0.5)]'" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -189,10 +161,8 @@ onShareAppMessage(() => ({
|
||||
<view class="relative z-[4] -mt-[34px] rounded-t-panel bg-white pt-7 px-4 pb-[18px] shadow-wcard">
|
||||
<view class="flex items-center gap-[9px]">
|
||||
<text class="text-[24px] font-bold tracking-[-0.6px] text-ink">{{ item.name }}</text>
|
||||
<text
|
||||
v-if="badge"
|
||||
class="shrink-0 text-hint font-semibold px-2 py-1 rounded-[7px] bg-[rgba(0,181,120,0.1)] text-brand-deep border-[0.5px] border-[rgba(0,181,120,0.2)]"
|
||||
>
|
||||
<text v-if="badge"
|
||||
class="shrink-0 text-hint font-semibold px-2 py-1 rounded-[7px] bg-[rgba(0,181,120,0.1)] text-brand-deep border-[0.5px] border-[rgba(0,181,120,0.2)]">
|
||||
{{ badge }}
|
||||
</text>
|
||||
</view>
|
||||
@@ -200,22 +170,20 @@ onShareAppMessage(() => ({
|
||||
<view v-if="item.name_en" class="mt-px text-[11.5px] text-ink3">{{ item.name_en }}</view>
|
||||
|
||||
<!-- 营业时间(字段待运营方补充,一期 mock) -->
|
||||
<view v-if="businessHours" class="mt-2 flex items-center gap-[5px] text-[12px] font-medium text-brand-deep tracking-[0.1px]">
|
||||
<view v-if="businessHours"
|
||||
class="mt-2 flex items-center gap-[5px] text-[12px] font-medium text-brand-deep tracking-[0.1px]">
|
||||
<AppIcon name="clock" tone="brand" :size="12" />
|
||||
<text>营业时间 {{ businessHours }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 地址条(无经纬度 → 不做地图导航,点击复制) -->
|
||||
<view
|
||||
v-if="addressText"
|
||||
<view v-if="addressText"
|
||||
class="mt-3 flex items-center gap-[7px] px-[11px] py-[10px] rounded-[14px] grad-address border-[0.5px] border-[rgba(0,181,120,0.14)] text-[12px] leading-[1.55] text-ink2"
|
||||
@click="onCopyAddress"
|
||||
>
|
||||
@click="onCopyAddress">
|
||||
<AppIcon name="pin" tone="ink3" :size="14" />
|
||||
<text class="flex-1 min-w-0">{{ addressText }}</text>
|
||||
<view
|
||||
class="shrink-0 ml-auto w-7 h-7 rounded-full flex items-center justify-center bg-white border-[0.5px] border-[rgba(0,181,120,0.22)] shadow-[0_3px_8px_rgba(0,181,120,0.14)]"
|
||||
>
|
||||
class="shrink-0 ml-auto w-7 h-7 rounded-full flex items-center justify-center bg-white border-[0.5px] border-[rgba(0,181,120,0.22)] shadow-[0_3px_8px_rgba(0,181,120,0.14)]">
|
||||
<AppIcon name="copy" tone="brandDeep" :size="14" />
|
||||
</view>
|
||||
</view>
|
||||
@@ -241,9 +209,7 @@ onShareAppMessage(() => ({
|
||||
</view>
|
||||
<view
|
||||
class="shrink-0 w-11 h-11 rounded-full flex items-center justify-center grad-call shadow-[0_8px_18px_rgba(0,181,120,0.32)]"
|
||||
hover-class="opacity-90"
|
||||
@click="onCall"
|
||||
>
|
||||
hover-class="opacity-90" @click="onCall">
|
||||
<AppIcon name="phone" tone="white" :size="20" />
|
||||
</view>
|
||||
</view>
|
||||
@@ -254,13 +220,8 @@ onShareAppMessage(() => ({
|
||||
<view class="flex items-center gap-2 text-[16px] font-bold tracking-[-0.3px] text-ink deco-titledot">
|
||||
到店权益
|
||||
</view>
|
||||
<BenefitCard
|
||||
v-for="benefit in relatedBenefits"
|
||||
:key="benefit.id"
|
||||
:benefit="benefit"
|
||||
mode="detail"
|
||||
@action="onClaimBenefit"
|
||||
/>
|
||||
<BenefitCard v-for="benefit in relatedBenefits" :key="benefit.id" :benefit="benefit" mode="detail"
|
||||
@action="onClaimBenefit" />
|
||||
</view>
|
||||
|
||||
<!-- 附近同类推荐(图上压字卡) -->
|
||||
@@ -268,27 +229,16 @@ onShareAppMessage(() => ({
|
||||
<view class="flex items-center gap-2 text-[16px] font-bold tracking-[-0.3px] text-ink deco-titledot">
|
||||
附近同类推荐
|
||||
</view>
|
||||
<!-- ⚠️ scroll-x 必须开 enable-flex,否则 display:flex 在 WebView 下不生效 → 子项竖排。
|
||||
内层包裹需 shrink-0,否则成为 flex item 后会被压到容器宽度。
|
||||
scroll-view 内不要用 gap-*(小程序实测不生效),条间距用子项 mr-[11px]。 -->
|
||||
<scroll-view
|
||||
class="mt-[11px] flex flex-row overflow-hidden"
|
||||
scroll-x
|
||||
enable-flex
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<scroll-view class="mt-[11px] flex flex-row overflow-hidden" scroll-x enable-flex :show-scrollbar="false">
|
||||
<view class="flex shrink-0">
|
||||
<view v-for="rec in related" :key="rec.id" class="relative shrink-0 w-[154px] mr-[11px]" @click="goRelated(rec)">
|
||||
<image
|
||||
class="w-[154px] h-[102px] rounded-[16px] shadow-[0_8px_20px_rgba(8,30,22,0.14)]"
|
||||
:src="rec.main_image_url"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<text
|
||||
class="absolute left-0 right-0 bottom-0 px-[10px] pt-[22px] pb-[9px] rounded-b-[16px] text-white text-[11.5px] font-semibold truncate scrim-rec"
|
||||
>
|
||||
<view v-for="rec in related" :key="rec.id" class="relative shrink-0 w-[154px] h-[102px] mr-[11px]"
|
||||
@click="goRelated(rec)">
|
||||
<image class="block w-[154px] h-[102px] rounded-[16px] shadow-[0_8px_20px_rgba(8,30,22,0.14)]"
|
||||
:src="rec.main_image_url" mode="aspectFill" />
|
||||
<view
|
||||
class="absolute left-0 right-0 bottom-0 px-[10px] pt-[22px] pb-[9px] rounded-b-[16px] text-white text-[11.5px] font-semibold line-clamp-1 scrim-rec">
|
||||
{{ rec.name }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
Reference in new issue
Block a user