60%
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user