Files
jiapuapp/pages/profile/promotions.vue
T

243 lines
8.3 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.
<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>