feat: 什么是票根经济?介绍

This commit is contained in:
zoujing
2026-09-23 12:29:48 +08:00
parent 2f35e953df
commit 2ccff41066
6 changed files with 97 additions and 5 deletions

View File

@@ -8,7 +8,7 @@
* 2)公共内容(协议 / 隐私 / 关于 / 客服)
* GET /v1/content(列表,登录同意版本号取这里) + GET /v1/content/:key(单条详情)
*/
import { AGREEMENT_DATE, CUSTOMER_SERVICE_PHONE, CUSTOMER_SERVICE_TIME } from "@/constant";
import { AGREEMENT_DATE, CUSTOMER_SERVICE_PHONE, CUSTOMER_SERVICE_TIME, TICKET_ECONOMY_KEY } from "@/constant";
import { clone, cnDate, unique } from "@/utils";
import { toPublicCard } from "@/api/benefit";
import * as contentApi from "@/request/api/content";
@@ -166,6 +166,21 @@ export const getCustomerService = async () => {
return { phone: (info && info.phone) || CUSTOMER_SERVICE_PHONE };
};
/* ---------------------------------------------------------------------------
* 票根经济介绍页(GET /v1/content/ticket_economy)
* 口径与公共内容一致:title 作标题、body 作正文(纯文本按行拆段,`一、` 行作小标题)。
* 内容全部取自接口,接口无内容时正文为空(无本地兜底)。
* ------------------------------------------------------------------------- */
/** 票根经济介绍页:标题 + 正文段落 */
export const getTicketEconomy = async () => {
const info = await getContent(TICKET_ECONOMY_KEY);
return {
title: (info && info.title) || "票根经济",
blocks: toContentBlocks(info && info.body),
};
};
export default {
getHomepage,
getCategories,
@@ -178,4 +193,5 @@ export default {
getAgreement,
getAbout,
getCustomerService,
getTicketEconomy,
};

View File

@@ -188,5 +188,9 @@ export const ROUTES = {
DETAIL: "/pages-sub/detail/index",
AGREEMENT: "/pages-sub/agreement/index",
ABOUT: "/pages-sub/about/index",
INTRO: "/pages-sub/intro/index",
ACCOUNT_CANCEL: "/pages-sub/account/cancel",
};
/** 票根经济介绍页的内容 key(GET /v1/content/ticket_economy) */
export const TICKET_ECONOMY_KEY = "ticket_economy";

View File

@@ -0,0 +1,42 @@
<script setup>
/**
* 票根经济介绍页(首页品牌名同行小胶囊进入)
* ---------------------------------------------------------------------------
* 内容:GET /v1/content/ticket_economy —— 与公共内容同口径,title 作标题、body 作正文。
* 布局:根 `h-screen flex flex-col` —— 状态栏 + 导航栏(无底色)为固定区 `shrink-0`,
* 正文在导航栏下方的独立 scroll-view 里滚动,正文不会窜到导航栏底下。
*/
import { ref } from "vue";
import contentApi from "@/api/content";
const title = ref("票根经济");
const blocks = ref([]);
const load = async () => {
const res = await contentApi.getTicketEconomy();
title.value = res.title;
blocks.value = res.blocks || [];
};
load();
</script>
<template>
<view class="relative h-screen w-full flex flex-col skin-benefit">
<!-- 固定区:状态栏 + 导航栏(无底色、不随内容滚动),标题常显 -->
<view class="relative z-[60] shrink-0">
<AppStatusBar />
<AppNavBar bare :title="title" />
</view>
<!-- 滚动区:只有导航栏下方这一块滚动 -->
<scroll-view class="flex-1 min-h-0" scroll-y :show-scrollbar="false">
<view class="px-5 pt-4 pb-10">
<template v-for="(block, i) in blocks" :key="i">
<view v-if="block.type === 'h5'" class="text-[14px] font-semibold mt-5 mb-2 text-ink">{{ block.text }}</view>
<view v-else class="text-[13px] leading-[1.9] text-[#4B5563]">{{ block.text }}</view>
</template>
<view class="h-10" />
</view>
</scroll-view>
</view>
</template>

View File

@@ -68,6 +68,14 @@
"enablePullDownRefresh": false
}
},
{
"path": "intro/index",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "票根经济",
"enablePullDownRefresh": false
}
},
{
"path": "account/cancel",
"style": {

View File

@@ -220,6 +220,9 @@ export const goAgreement = (type) => {
};
export const goAbout = () => uni.navigateTo({ url: ROUTES.ABOUT });
/** 跳票根经济介绍页 */
export const goIntro = () => uni.navigateTo({ url: ROUTES.INTRO });
export const goAccountCancel = () => uni.navigateTo({ url: ROUTES.ACCOUNT_CANCEL });
/** 拨打电话 */

View File

@@ -14,10 +14,12 @@
*/
import { computed, ref } from "vue";
import contentApi from "@/api/content";
import { goCatalog, previewImages } from "@/utils/nav";
import { goCatalog, goIntro, previewImages } from "@/utils/nav";
const home = ref({});
const featured = ref([]);
/** 票根经济入口文案:取接口 /v1/content/ticket_economy 的 title(与首页首屏并行前置请求) */
const introTitle = ref("什么是票根经济?");
/** 首屏数据是否已回来(骨架屏开关;请求失败也置真,避免骨架常驻) */
const loaded = ref(false);
@@ -44,9 +46,15 @@ const load = async () => {
if (loading) return;
loading = true;
try {
const data = await contentApi.getHomepage();
// 票根经济介绍内容与首屏一起前置请求:入口文案首屏即为接口值,
// 且二级页进入时命中内容缓存(不再等请求)。
const [data, intro] = await Promise.all([
contentApi.getHomepage(),
contentApi.getTicketEconomy(),
]);
home.value = data || {};
featured.value = data?.featured?.items || [];
if (intro?.title) introTitle.value = intro.title;
} catch (e) {
home.value = {};
featured.value = [];
@@ -140,8 +148,19 @@ const onMapTap = () => {
<view class="w-[5px] h-[5px] rounded-full bg-brand shadow-[0_0_0_4px_rgba(0,181,120,0.14)]" />
<text>{{ brand.sub_name }}</text>
</view>
<view class="mt-[9px] text-display font-bold tracking-[-1px] leading-[1.1] text-ink">
{{ brandHead }}<text class="not-italic gradtext-brand">{{ brandTail }}</text>
<view class="flex items-center justify-between gap-2 mt-[9px]">
<view class="min-w-0 truncate text-display font-bold tracking-[-1px] leading-[1.1] text-ink">
{{ brandHead }}<text class="not-italic gradtext-brand">{{ brandTail }}</text>
</view>
<!-- 票根经济介绍入口:贴品牌名右端,半透明白底,不增加固定区高度;相对品牌名基线下移 8px -->
<view
class="shrink-0 relative top-2 flex items-center gap-[3px] h-[24px] px-[9px] rounded-full bg-[rgba(255,255,255,0.5)] border-[0.5px] border-brand-line"
hover-class="opacity-70"
@click="goIntro"
>
<text class="text-[11px] font-semibold text-brand-deep">{{ introTitle }}</text>
<AppIcon name="chev" tone="brandDeep" :size="11" />
</view>
</view>
<view class="mt-2 text-[11.5px] leading-[1.55] text-ink2 tracking-[0.1px]">
{{ brand.slogan }}