Files
jiapuapp/pages/family/f05-article-detail.vue
T

233 lines
6.1 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.
<!-- 页面编号F-05用途谱文详情 -->
<template>
<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">
<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="handleStateAction" />
</view>
</view>
</view>
</template>
<script setup>
import { computed, ref } from "vue";
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 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();
});
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">
@import "../../styles/adaptive-frame-profiles.scss";
.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 {
flex: 1;
padding: 18rpx 24rpx 72rpx;
}
.article-card,
.article-state-card {
@include 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: clamp(14px, 23rpx, 17px);
font-weight: 700;
}
.article-card__title {
margin-top: 14rpx;
color: $ink;
font-family: STKaiti, KaiTi, serif;
font-size: clamp(20px, 40rpx, 26px);
font-weight: 700;
line-height: 1.32;
}
.article-card__meta {
margin-top: 16rpx;
color: $ink-muted;
font-size: clamp(14px, 22rpx, 17px);
}
.article-card__summary {
margin-top: 22rpx;
color: $ink-muted;
font-size: clamp(15px, 25rpx, 18px);
line-height: 1.6;
}
.article-card__divider {
height: 1rpx;
margin: 26rpx 0;
background: rgba(128, 89, 49, 0.22);
}
.article-card__content {
color: $ink;
font-size: clamp(16px, 28rpx, 20px);
line-height: 1.85;
white-space: pre-wrap;
}
.article-card__views {
margin-top: 30rpx;
color: $ink-muted;
font-size: clamp(13px, 21rpx, 16px);
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: clamp(19px, 35rpx, 24px);
font-weight: 700;
}
.article-state-card > text:nth-child(2) {
margin-top: 18rpx;
color: $ink-muted;
font-size: clamp(15px, 24rpx, 18px);
line-height: 1.65;
}
.article-state-card .app-button {
margin-top: 28rpx;
}
</style>