feat: 完成前端业务闭环与后端联调

This commit is contained in:
2026-08-24 23:37:56 +08:00
parent c59e36f933
commit 9ad572907b
175 changed files with 10711 additions and 1740 deletions
+121 -6
View File
@@ -11,6 +11,31 @@
<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>
@@ -24,7 +49,6 @@
<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"
@@ -42,7 +66,7 @@
/>
<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">
<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>
@@ -63,6 +87,7 @@ 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";
@@ -71,9 +96,39 @@ 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";
@@ -117,6 +172,39 @@ const copyPromotionLink = (item) => {
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");
@@ -142,11 +230,15 @@ const sharePromotion = (item) => {
};
const requestBack = () => goBack();
onLoad(loadPromotions);
onLoad(() => {
void loadReferralProfile();
void loadPromotions();
});
onBackPress((event) => handleBackPress(event, requestBack));
onUnload(() => {
pageActive = false;
promotionListRequestController.abort();
referralRequestController.abort();
});
</script>
@@ -163,9 +255,10 @@ onUnload(() => {
}
.page-content {
flex: 1;
padding: 28rpx 30rpx 72rpx;
padding: 28rpx 30rpx calc(72rpx + env(safe-area-inset-bottom));
}
.promotion-hero,
.referral-card,
.promotion-state-card,
.promotion-list {
@include adaptive-profile-content;
@@ -211,8 +304,25 @@ onUnload(() => {
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: 16rpx; }
.promotion-list { display: grid; gap: 18rpx; margin-top: 20rpx; }
.promotion-card {
overflow: hidden;
padding: 26rpx 28rpx;
@@ -230,7 +340,7 @@ onUnload(() => {
.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-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) {
@@ -238,5 +348,10 @@ onUnload(() => {
padding-right: 22rpx;
padding-left: 22rpx;
}
.promotion-card__actions .app-button {
min-width: 0;
flex: 1 1 220rpx;
}
}
</style>