This commit is contained in:
rain
2026-07-24 18:03:02 +08:00
parent c7f278fe79
commit ec8486b035
86 changed files with 7943 additions and 1841 deletions
+89 -21
View File
@@ -1,13 +1,24 @@
<!-- 页面编号F-05用途谱文详情详情 DTO 未声明时不展示本地正文 -->
<!-- 页面编号F-05用途谱文详情 -->
<template>
<view class="article-detail-page">
<view class="article-detail-page" :class="`article-state--${articleState}`">
<ModulePageBackground module="family" />
<view class="article-detail-header"><PageHeader title="谱文详情" custom-back @back="backToArticles" /></view>
<view class="article-detail-content">
<view class="article-state-card">
<AppLoading v-if="articleState === 'loading'" text="正在读取谱文" description="请稍候,正在同步谱文正文。" />
<view v-else-if="articleState === 'ready'" class="article-card">
<text v-if="article.category" class="article-card__category">{{ article.category }}</text>
<text class="article-card__title">{{ article.title }}</text>
<text class="article-card__meta">{{ article.author }} · {{ article.time || '未标注时间' }}</text>
<text v-if="article.summary" class="article-card__summary">{{ article.summary }}</text>
<view class="article-card__divider" />
<text class="article-card__content">{{ article.content || '作者暂未填写正文。' }}</text>
<text class="article-card__views">阅读 {{ article.viewCount }} </text>
</view>
<view v-else class="article-state-card">
<text>{{ stateCopy.title }}</text>
<text>{{ stateCopy.copy }}</text>
<AppButton block :label="stateCopy.action" @click="backToArticles" />
<AppButton block :label="stateCopy.action" @click="handleStateAction" />
</view>
</view>
</view>
@@ -15,44 +26,101 @@
<script setup>
import { computed, ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import { onLoad, onShow, 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 { appApi, createRequestController, isRequestCancelled } from "@/utils/api.js";
import { goBack, returnTo } from "@/utils/navigation.js";
const genealogyId = ref("");
const articleId = ref("");
const hasValidContext = computed(() => /^[1-9]\d*$/.test(genealogyId.value) && /^[1-9]\d*$/.test(articleId.value));
const stateCopy = computed(() => hasValidContext.value
? {
title: "谱文正文待后端字段合同",
copy: "详情接口没有声明文章标题、分类、作者、更新时间或正文段落字段;页面已停止读取本地文章,不能猜测这些字段。",
action: "返回谱文列表",
}
: {
title: "谱文入口无效",
copy: "没有取得有效家谱或文章标识,页面不会回退到其他文章。",
action: "返回上一页",
},
const articleState = ref("loading");
const article = ref(null);
const controller = createRequestController();
let pageActive = true;
const hasValidContext = computed(() =>
/^[1-9]\d*$/.test(genealogyId.value) && /^[1-9]\d*$/.test(articleId.value),
);
const stateCopy = computed(() => {
if (!hasValidContext.value) {
return {
title: "谱文入口无效",
copy: "没有取得有效家谱或文章标识,页面不会展示其他谱文。",
action: "返回上一页",
};
}
if (articleState.value === "missing") {
return {
title: "该谱文已不存在",
copy: "它可能已被作者删除,或当前账号已不再拥有查看权限。",
action: "返回谱文列表",
};
}
return {
title: "谱文暂时无法读取",
copy: "请检查网络后重新读取;本页不会替换为其他谱文。",
action: "重新读取",
};
});
const loadArticle = async () => {
if (!hasValidContext.value) {
articleState.value = "invalid";
return;
}
articleState.value = "loading";
try {
const current = await appApi.getArticleDetail(genealogyId.value, articleId.value, { requestController: controller });
if (!pageActive) return;
article.value = current;
articleState.value = "ready";
} catch (error) {
if (!pageActive || isRequestCancelled(error)) return;
articleState.value = error?.code === "HTTP_ERROR" && error?.httpStatus === 404 ? "missing" : "error";
}
};
onLoad((query) => {
genealogyId.value = String(query?.genealogyId || "");
articleId.value = String(query?.articleId || "");
void loadArticle();
});
const backToArticles = () => /^[1-9]\d*$/.test(genealogyId.value)
onShow(() => {
if (hasValidContext.value) void loadArticle();
});
onUnload(() => {
pageActive = false;
controller.abort();
});
const backToArticles = () => hasValidContext.value
? returnTo("F04", { genealogyId: genealogyId.value })
: goBack();
const handleStateAction = () => articleState.value === "error" ? loadArticle() : backToArticles();
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.article-detail-page { display: flex; min-height: 100vh; flex-direction: column; background: $paper; }
.article-detail-header, .article-detail-content { z-index: 1; }
.article-detail-content { padding: 18rpx 24rpx 72rpx; }
.article-state-card { @include adaptive.adaptive-family-content; width: 100%; min-height: 350rpx; margin-top: 36rpx; padding: 78rpx 52rpx 50rpx; text-align: center; }
.article-detail-content { flex: 1; padding: 18rpx 24rpx 72rpx; }
.article-card, .article-state-card { @include adaptive.adaptive-family-content; box-sizing: border-box; }
.article-card { margin-top: 18rpx; padding: 34rpx 30rpx; }
.article-card__category, .article-card__title, .article-card__meta, .article-card__summary, .article-card__content, .article-card__views { display: block; }
.article-card__category { color: $brand-red; font-size: 23rpx; font-weight: 700; }
.article-card__title { margin-top: 14rpx; color: $ink; font-family: STKaiti, KaiTi, serif; font-size: 40rpx; font-weight: 700; line-height: 1.32; }
.article-card__meta { margin-top: 16rpx; color: $ink-muted; font-size: 22rpx; }
.article-card__summary { margin-top: 22rpx; color: $ink-muted; font-size: 25rpx; line-height: 1.6; }
.article-card__divider { height: 1rpx; margin: 26rpx 0; background: rgba(128, 89, 49, .22); }
.article-card__content { color: $ink; font-size: 28rpx; line-height: 1.85; white-space: pre-wrap; }
.article-card__views { margin-top: 30rpx; color: $ink-muted; font-size: 21rpx; text-align: right; }
.article-state-card { min-height: 350rpx; margin-top: 36rpx; padding: 78rpx 52rpx 50rpx; text-align: center; }
.article-state-card > text { display: block; }
.article-state-card > text:first-child { color: $ink; font-family: STKaiti, KaiTi, serif; font-size: 35rpx; font-weight: 700; }
.article-state-card > text:nth-child(2) { margin-top: 18rpx; color: $ink-muted; font-size: 24rpx; line-height: 1.65; }
.article-state-card .app-button { margin-top: 30rpx; }
.article-state-card .app-button { margin-top: 28rpx; }
</style>