Files
jiapuapp/pages/profile/promotions.vue
T

358 lines
13 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>
<text v-if="operationFeedback" class="promotion-feedback" role="status">{{ operationFeedback }}</text>
<view class="referral-card">
<view class="referral-card__heading">
<text>邀请家人注册</text>
<text>推荐关系由注册接口一次性确认前端不会在注册后补绑</text>
</view>
<AppLoading v-if="referralState === 'loading'" text="正在读取我的推荐码" />
<view v-else-if="referralState === 'error'" class="referral-card__state">
<text>{{ referralError || "推荐码暂时无法读取。" }}</text>
<AppButton compact type="secondary" label="重试" @click="loadReferralProfile" />
</view>
<view v-else-if="referralProfile.enabled" class="referral-card__content">
<text class="referral-card__code">{{ referralProfile.referralCode }}</text>
<text>已成功邀请 {{ referralProfile.referredUserCount }} </text>
<text>{{ referralProfile.shareDescription || "家人通过此链接注册后,系统会记录推荐关系。" }}</text>
<view class="referral-card__actions">
<AppButton compact type="secondary" label="复制推荐码" @click="copyReferralCode" />
<AppButton compact type="secondary" label="复制推荐链接" @click="copyReferralLink" />
<AppButton compact label="分享给家人" @click="shareReferral" />
</view>
</view>
<view v-else class="referral-card__state">
<text>{{ referralProfile.disabledReason || "推荐功能暂未开放。" }}</text>
</view>
</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">
<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" @click.stop>
<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 { referralApi } from "@/services/api/referral-service.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 referralState = ref("loading");
const referralError = ref("");
const referralProfile = ref({
enabled: false,
disabledReason: "",
referralCode: "",
shareTitle: "",
shareDescription: "",
shareUrl: "",
referredUserCount: 0,
});
const promotionListRequestController = createRequestController();
const referralRequestController = createRequestController();
let pageActive = true;
const loadReferralProfile = async () => {
referralRequestController.abort();
referralState.value = "loading";
referralError.value = "";
try {
const profile = await referralApi.getMyReferralProfile({
requestController: referralRequestController,
});
if (!pageActive) return;
referralProfile.value = profile;
referralState.value = "ready";
} catch (error) {
if (!pageActive || isRequestCancelled(error)) return;
referralError.value = getRequestErrorMessage(error, "推荐码暂时无法读取,请稍后重试。");
referralState.value = "error";
}
};
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 copyText = (content, successMessage) => {
if (!content || typeof uni?.setClipboardData !== "function") {
operationFeedback.value = "当前设备暂时不能复制。";
return;
}
uni.setClipboardData({
data: content,
success: () => { operationFeedback.value = successMessage; },
fail: () => { operationFeedback.value = "复制失败,请稍后再试。"; },
});
};
const copyReferralCode = () =>
copyText(referralProfile.value.referralCode, "推荐码已复制,可以发给家人。");
const copyReferralLink = () =>
copyText(referralProfile.value.shareUrl, "推荐链接已复制,可以发给家人。");
const shareReferral = () => {
const profile = referralProfile.value;
if (!profile.enabled || !profile.shareUrl) return;
const content = [profile.shareTitle, profile.shareDescription, profile.shareUrl]
.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
copyReferralLink();
};
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(() => {
void loadReferralProfile();
void loadPromotions();
});
onBackPress((event) => handleBackPress(event, requestBack));
onUnload(() => {
pageActive = false;
promotionListRequestController.abort();
referralRequestController.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 calc(72rpx + env(safe-area-inset-bottom));
}
.promotion-hero,
.referral-card,
.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;
}
.referral-card {
margin-top: 20rpx;
padding: 28rpx;
border: 1rpx solid rgba(128, 89, 49, 0.26);
border-radius: 12rpx;
background: rgba(255, 252, 245, 0.82);
}
.referral-card text { display: block; }
.referral-card__heading text:first-child { color: $ink; font-size: clamp(17px, 29rpx, 21px); font-weight: 700; }
.referral-card__heading text:last-child,
.referral-card__content > text:last-of-type,
.referral-card__state > text { margin-top: 8rpx; color: $ink-muted; font-size: clamp(13px, 22rpx, 16px); line-height: 1.55; }
.referral-card__content,
.referral-card__state { margin-top: 22rpx; }
.referral-card__code { color: #9e251b; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: clamp(22px, 40rpx, 30px); font-weight: 700; letter-spacing: 3rpx; }
.referral-card__actions { display: flex; flex-wrap: wrap; justify-content: flex-end; margin-top: 22rpx; gap: 12rpx; }
.referral-card__actions .app-button { width: auto; min-width: 150rpx; }
.promotion-state-card .app-button { margin-top: 28rpx; }
.promotion-list { display: grid; gap: 18rpx; margin-top: 20rpx; }
.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; margin-top: 16rpx; 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); line-height: 1.5; overflow-wrap: anywhere; }
.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;
}
.promotion-card__actions .app-button {
min-width: 0;
flex: 1 1 220rpx;
}
}
</style>