243 lines
8.3 KiB
Vue
243 lines
8.3 KiB
Vue
<template>
|
||
<view class="promotion-page">
|
||
<ModulePageBackground module="profile" />
|
||
<view class="page-layer"><PageHeader title="推广中心" custom-back @back="requestBack" /></view>
|
||
<view class="page-content page-layer">
|
||
<view class="promotion-hero">
|
||
<image
|
||
src="/static/assets/foundation/transparent/brand-seal.png"
|
||
mode="aspectFit"
|
||
/>
|
||
<text>家谱服务推荐</text>
|
||
<text>这里会展示家谱相关服务和活动。</text>
|
||
</view>
|
||
<view v-if="promotionListState === 'loading'" class="promotion-state-card">
|
||
<AppLoading text="正在读取推广内容" />
|
||
</view>
|
||
<view v-else-if="promotionListState === 'error'" class="promotion-state-card">
|
||
<text>暂时无法读取推广内容</text>
|
||
<text>{{ promotionListError || '请检查网络后重新加载。' }}</text>
|
||
<AppButton block type="secondary" label="重新加载" @click="loadPromotions" />
|
||
</view>
|
||
<view v-else-if="!promotions.length" class="promotion-state-card">
|
||
<text>当前没有推广内容</text>
|
||
<text>后续正式发布的家谱服务推荐会在这里展示。</text>
|
||
</view>
|
||
<view v-else class="promotion-list">
|
||
<text v-if="operationFeedback" class="promotion-feedback" role="status">{{ operationFeedback }}</text>
|
||
<view
|
||
v-for="item in promotions"
|
||
:key="item.id"
|
||
class="promotion-card"
|
||
:class="{ 'promotion-card--linked': item.targetUrl }"
|
||
:role="item.targetUrl ? 'button' : undefined"
|
||
:aria-label="item.targetUrl ? `${item.title},查看详情` : undefined"
|
||
@click="openPromotion(item)"
|
||
>
|
||
<image
|
||
v-if="item.coverFile?.accessUrl"
|
||
class="promotion-card__cover"
|
||
:src="item.coverFile.accessUrl"
|
||
mode="aspectFill"
|
||
/>
|
||
<text class="promotion-card__title">{{ item.title }}</text>
|
||
<text v-if="item.description" class="promotion-card__description">{{ item.description }}</text>
|
||
<view v-if="item.targetUrl" class="promotion-card__actions">
|
||
<AppButton compact type="secondary" label="复制链接" @click.stop="copyPromotionLink(item)" />
|
||
<AppButton compact label="分享给家人" @click.stop="sharePromotion(item)" />
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref } from "vue";
|
||
import { onLoad, onBackPress, onUnload } from "@dcloudio/uni-app";
|
||
import AppButton from "@/components/AppButton.vue";
|
||
import AppLoading from "@/components/AppLoading.vue";
|
||
import ModulePageBackground from "@/components/ModulePageBackground.vue";
|
||
import PageHeader from "@/components/PageHeader.vue";
|
||
import {
|
||
createRequestController,
|
||
isRequestCancelled
|
||
} from "@/services/api/request-controller.js";
|
||
import { siteContentApi } from "@/services/api/site-content-service.js";
|
||
import { getRequestErrorMessage } from "@/services/api/request-error-message.js";
|
||
import { goBack, handleBackPress, openSiteContentTarget } from "@/utils/navigation/gateway.js";
|
||
|
||
const promotions = ref([]);
|
||
const promotionListState = ref("loading");
|
||
const promotionListError = ref("");
|
||
const operationFeedback = ref("");
|
||
const promotionListRequestController = createRequestController();
|
||
let pageActive = true;
|
||
|
||
const loadPromotions = async () => {
|
||
promotionListRequestController.abort();
|
||
promotionListState.value = "loading";
|
||
promotionListError.value = "";
|
||
try {
|
||
const promotionRows = await siteContentApi.getPromotions({
|
||
requestController: promotionListRequestController,
|
||
});
|
||
if (!pageActive) return;
|
||
promotions.value = promotionRows;
|
||
promotionListState.value = "ready";
|
||
} catch (error) {
|
||
if (!pageActive || isRequestCancelled(error)) return;
|
||
promotionListError.value = getRequestErrorMessage(error, "推广内容暂时无法加载,请稍后再试。");
|
||
promotionListState.value = "error";
|
||
}
|
||
};
|
||
|
||
const openPromotion = async (item) => {
|
||
const targetUrl = item?.targetUrl || "";
|
||
if (!targetUrl) return;
|
||
const showExternalLinkError = () => {
|
||
operationFeedback.value = "链接暂时打不开,请稍后再试。";
|
||
};
|
||
try {
|
||
await openSiteContentTarget(targetUrl, showExternalLinkError);
|
||
} catch {
|
||
operationFeedback.value = targetUrl.startsWith("/")
|
||
? "这个页面暂时打不开,请稍后再试。"
|
||
: "链接暂时打不开,请稍后再试。";
|
||
}
|
||
};
|
||
const copyPromotionLink = (item) => {
|
||
if (!item?.targetUrl || typeof uni?.setClipboardData !== "function") {
|
||
operationFeedback.value = "当前设备暂时不能复制链接。";
|
||
return;
|
||
}
|
||
uni.setClipboardData({
|
||
data: item.targetUrl,
|
||
success: () => { operationFeedback.value = "推广链接已复制,可以发给家人。"; },
|
||
fail: () => { operationFeedback.value = "复制失败,请稍后再试。"; },
|
||
});
|
||
};
|
||
const sharePromotion = (item) => {
|
||
if (!item?.targetUrl) return;
|
||
const content = [item.title, item.description, item.targetUrl].filter(Boolean).join("\n");
|
||
// #ifdef APP-PLUS
|
||
if (typeof plus?.share?.sendWithSystem === "function") {
|
||
plus.share.sendWithSystem(
|
||
{ type: "text", content },
|
||
() => { operationFeedback.value = "已打开系统分享。"; },
|
||
() => { operationFeedback.value = "已取消分享。"; },
|
||
);
|
||
return;
|
||
}
|
||
// #endif
|
||
// #ifdef H5
|
||
if (typeof navigator?.share === "function") {
|
||
navigator.share({ title: item.title, text: item.description, url: item.targetUrl })
|
||
.then(() => { operationFeedback.value = "分享已完成。"; })
|
||
.catch(() => { operationFeedback.value = "已取消分享。"; });
|
||
return;
|
||
}
|
||
// #endif
|
||
copyPromotionLink(item);
|
||
};
|
||
|
||
const requestBack = () => goBack();
|
||
onLoad(loadPromotions);
|
||
onBackPress((event) => handleBackPress(event, requestBack));
|
||
onUnload(() => {
|
||
pageActive = false;
|
||
promotionListRequestController.abort();
|
||
});
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
@import "../../styles/adaptive-frame-profiles.scss";
|
||
.promotion-page {
|
||
display: flex;
|
||
min-height: 100vh;
|
||
flex-direction: column;
|
||
background: $paper;
|
||
}
|
||
.page-layer {
|
||
z-index: 1;
|
||
}
|
||
.page-content {
|
||
flex: 1;
|
||
padding: 28rpx 30rpx 72rpx;
|
||
}
|
||
.promotion-hero,
|
||
.promotion-state-card,
|
||
.promotion-list {
|
||
@include adaptive-profile-content;
|
||
}
|
||
.promotion-hero {
|
||
display: flex;
|
||
min-height: 250rpx;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 38rpx 48rpx;
|
||
text-align: center;
|
||
}
|
||
.promotion-hero image {
|
||
width: 90rpx;
|
||
height: 102rpx;
|
||
}
|
||
.promotion-hero text,
|
||
.promotion-state-card text {
|
||
display: block;
|
||
}
|
||
.promotion-hero text:nth-child(2),
|
||
.promotion-state-card text:first-child {
|
||
color: $ink;
|
||
font-family: STKaiti, KaiTi, serif;
|
||
font-size: clamp(19px, 34rpx, 24px);
|
||
font-weight: 700;
|
||
}
|
||
.promotion-hero text:last-child,
|
||
.promotion-state-card text:last-child {
|
||
margin-top: 12rpx;
|
||
color: $ink-muted;
|
||
font-size: clamp(14px, 22rpx, 17px);
|
||
line-height: 1.55;
|
||
}
|
||
.promotion-state-card {
|
||
display: flex;
|
||
min-height: 210rpx;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-top: 20rpx;
|
||
padding: 38rpx;
|
||
text-align: center;
|
||
}
|
||
.promotion-state-card .app-button { margin-top: 28rpx; }
|
||
.promotion-list { display: grid; gap: 16rpx; }
|
||
.promotion-card {
|
||
overflow: hidden;
|
||
padding: 26rpx 28rpx;
|
||
border: 1rpx solid rgba(128, 89, 49, 0.26);
|
||
border-radius: 12rpx;
|
||
background: rgba(255, 252, 245, 0.78);
|
||
}
|
||
.promotion-card--linked { cursor: pointer; }
|
||
.promotion-card__cover {
|
||
display: block;
|
||
width: calc(100% + 56rpx);
|
||
height: 260rpx;
|
||
margin: -26rpx -28rpx 22rpx;
|
||
}
|
||
.promotion-card text { display: block; }
|
||
.promotion-card__title { color: $ink; font-size: clamp(16px, 28rpx, 20px); font-weight: 700; }
|
||
.promotion-card__description { margin-top: 10rpx; color: $ink-muted; font-size: clamp(14px, 23rpx, 17px); line-height: 1.6; }
|
||
.promotion-feedback { display: block; padding: 16rpx 20rpx; border: 1rpx solid rgba(66, 107, 88, .32); border-radius: 10rpx; background: rgba(66, 107, 88, .08); color: #426b58; font-size: clamp(14px, 22rpx, 17px); }
|
||
.promotion-card__actions { display: flex; flex-wrap: wrap; justify-content: flex-end; margin-top: 16rpx; gap: 12rpx; }
|
||
.promotion-card__actions .app-button { width: auto; min-width: 142rpx; }
|
||
@media (max-width: 340px) {
|
||
.page-content {
|
||
padding-right: 22rpx;
|
||
padding-left: 22rpx;
|
||
}
|
||
}
|
||
</style>
|