feat: migrate app routes and business modules
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user