Files
longli-mp/src/pages-sub/catalog/index.vue
T
2026-09-18 12:02:09 +08:00

132 lines
5.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
/**
* 二级 · 板块列表页(需求 §5)
* 数据链路:GET /v1/categories?section_key= → 左列分类导航;
* GET /v1/merchants?section_key= → 全部商家,前端按 category_id 归组到各分类下。
* 250px 头图 + 白面板上浮 30px;面板内「左侧 86px 分类导航 + 右侧卡片列表」双列。
* 头图与大标题**前端自持**:头图取首页同 section_key 的板块卡图,大标题走 SECTION_TITLE。
*/
import { computed, ref } from "vue";
import { onLoad, onShareAppMessage } from "@dcloudio/uni-app";
import contentApi from "@/api/content";
import { REGION_TEXT, SECTION_TITLE } from "@/constant";
import { previewImages } from "@/utils/nav";
const sectionKey = ref("");
const categories = ref([]);
const merchants = ref([]);
const cover = ref("");
const bannerImages = ref([]);
const activeIndex = ref(0);
const scrollIntoView = ref("");
/** 左列分类 + 右列该分类下的商家卡片 */
const groups = computed(() =>
categories.value.map((c) => ({
id: c.id,
name: c.name,
items: merchants.value.filter((m) => m.category_id === c.id),
}))
);
const title = computed(() => SECTION_TITLE[sectionKey.value] || "");
onLoad(async (options) => {
sectionKey.value = options?.section || "scenic";
const [cats, list, banners, coverUrl] = await Promise.all([
contentApi.getCategories(sectionKey.value),
contentApi.getMerchants({ section_key: sectionKey.value }),
contentApi.getSectionBannerImages(sectionKey.value),
contentApi.getSectionCover(sectionKey.value),
]);
categories.value = cats?.items || [];
merchants.value = list?.items || [];
bannerImages.value = banners || [];
cover.value = coverUrl || "";
if (groups.value.length) {
scrollIntoView.value = `g-${groups.value[0].id}`;
}
});
/** 点左侧分类:右侧列表滚动到对应锚点 */
const onGroupTap = (index) => {
activeIndex.value = index;
const group = groups.value[index];
if (group) scrollIntoView.value = "";
setTimeout(() => {
if (group) scrollIntoView.value = `g-${group.id}`;
}, 0);
};
/** 点头图预览:优先看板块图集,没有图集时退回预览头图本身 */
const onCoverPreview = () => {
const list = bannerImages.value.length
? bannerImages.value
: [cover.value].filter(Boolean);
if (!list.length) return;
previewImages(list, list[0]);
};
onShareAppMessage(() => ({
title: `${title.value} · 龙里文旅`,
path: `/pages-sub/catalog/index?section=${sectionKey.value}`,
imageUrl: cover.value,
}));
</script>
<template>
<view class="relative min-h-screen w-full overflow-hidden">
<!-- 头图(点击直接预览大图,原「全景图」胶囊按钮已去除) -->
<view class="relative h-[250px] overflow-hidden">
<image class="absolute inset-0 w-full h-full" :src="cover" mode="aspectFill" @click="onCoverPreview" />
<view class="absolute inset-0 scrim-hd" />
<view class="relative z-[5]">
<AppStatusBar />
<AppNavBar theme="light" />
</view>
<view class="absolute left-5 right-5 bottom-[46px] z-[5]">
<text class="text-h1 font-bold text-white tracking-[0.3px]">{{ title }}</text>
<view class="mt-[7px] text-cap text-[rgba(255,255,255,0.86)]">{{ REGION_TEXT }}</view>
</view>
</view>
<!-- 内容面板:分类导航 + 卡片列表(双列) -->
<view
class="relative -mt-[30px] rounded-t-panel bg-white h-[calc(100vh_-_220px)] min-h-[460px] overflow-hidden shadow-pan">
<view class="flex h-full">
<scroll-view class="w-[86px] shrink-0 bg-alt overflow-hidden pt-3" scroll-y :show-scrollbar="false">
<view v-for="(group, i) in groups" :key="group.id"
class="h-16 flex items-center justify-center text-cap relative text-center leading-[1.3]"
:class="i === activeIndex ? 'bg-white text-brand-deep font-semibold deco-mark' : 'text-ink2'"
@click="onGroupTap(i)">
<text>{{ group.name }}</text>
<text class="ml-[3px] text-[10px]" :class="i === activeIndex ? 'text-brand' : 'text-ink3'">
({{ group.items.length }})
</text>
</view>
</scroll-view>
<scroll-view class="flex-1 min-w-0 bg-white px-3 pt-1 pb-5" scroll-y :show-scrollbar="false"
:scroll-into-view="scrollIntoView" :scroll-with-animation="true">
<view v-for="group in groups" :id="`g-${group.id}`" :key="group.id">
<view class="text-[16px] font-semibold mt-3 mb-[9px] text-ink">{{ group.name }}</view>
<view v-for="(item, i) in group.items" :key="item.id" :class="i ? 'mt-[10px]' : ''">
<ContentCard :item="item" show-address show-benefit />
</view>
<view v-if="!group.items.length"
class="h-[92px] rounded-card bg-alt border-[0.5px] border-line border-dashed flex flex-col items-center justify-center gap-[6px]">
<AppIcon name="info" tone="ink3" :size="22" />
<text class="text-hint text-ink3">暂无商家</text>
</view>
</view>
<EmptyBox v-if="!groups.length" text="该板块暂无内容" />
<view class="h-5" />
</scroll-view>
</view>
</view>
</view>
</template>