修改完成

This commit is contained in:
2026-07-21 07:53:03 +08:00
parent 01d246c47b
commit ff60119277
172 changed files with 7982 additions and 2999 deletions
+111 -3
View File
@@ -1,5 +1,113 @@
<!-- 页面编号F-05用途谱文详情 -->
<template><ModulePage page-id="f05" /></template>
<!-- 页面编号F-05用途谱文正文收藏编辑与受控状态 -->
<template>
<view class="article-detail-page" :class="articleStateClasses">
<ModulePageBackground module="family" />
<view class="article-detail-header"><PageHeader title="谱文详情" /></view>
<view v-if="articleState === 'loading'" class="article-detail-loading">
<AppLoading text="正在读取谱文" description="请稍候,正在整理正文与收录信息。" />
</view>
<view v-else class="article-detail-content">
<template v-if="articleState === 'ready'">
<view class="article-paper">
<text class="article-paper__category">{{ article.category }}</text>
<text class="article-paper__title">{{ article.title }}</text>
<view class="article-paper__meta"><text>{{ article.author }}</text><text>{{ article.updatedAt }}</text></view>
<view class="article-paper__body">
<text v-for="(paragraph, index) in articleParagraphs" :key="index">{{ paragraph }}</text>
</view>
</view>
<view class="article-actions">
<AppButton block :label="favorite ? '已收藏谱文' : '收藏谱文'" @click="toggleFavorite" />
<AppButton type="secondary" block label="编辑谱文" @click="editArticle" />
</view>
</template>
<view v-else class="article-state-card">
<text>{{ stateCopy.title }}</text>
<text>{{ stateCopy.copy }}</text>
<AppButton :type="articleState === 'error' ? 'secondary' : 'primary'" block :label="stateCopy.action" @click="handleStateAction" />
</view>
</view>
<AppToast :visible="toastVisible" :message="favorite ? '已收藏谱文' : '已取消收藏'" />
</view>
</template>
<script setup>
import ModulePage from "@/components/ModulePage.vue";
import { computed, onUnmounted, reactive, ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import AppButton from "@/components/AppButton.vue";
import AppLoading from "@/components/AppLoading.vue";
import AppToast from "@/components/AppToast.vue";
import ModulePageBackground from "@/components/ModulePageBackground.vue";
import PageHeader from "@/components/PageHeader.vue";
const articleRecords = [
{ id: "101", category: "家风家训", title: "孝友传家的日常", author: "汤文正", updatedAt: "2024 年 5 月 12 日", paragraphs: ["孝友传家,不只在族谱序言里,也在一家人每日的言行中。长辈以宽厚待晚辈,晚辈以耐心照料长辈,亲友之间守信互助,便是最朴素也最长久的家风。", "勤俭并非一味节省,而是珍惜所得、量入为出,也愿意在家人需要时伸出援手。家中每一代人都可以用自己的方式,把这份分寸与担当继续传下去。", "敬祖睦宗,最终是为了让今天的家人彼此认识、彼此关心。记录姓名与世代之外,也应留下真实的生活、共同经历和温暖记忆。"] },
{ id: "102", category: "家族往事", title: "祖居门前的那棵桂花树", author: "汤淑华", updatedAt: "2024 年 5 月 10 日", paragraphs: ["祖居门前曾有一棵桂花树。每到中秋,院里都是清甜的香气,远道回来的家人也总能循着那股味道找到家门。", "后来房屋几经修缮,桂花树仍被大家小心保留下来。它见过孩子长大,也见过长辈把往事一遍遍讲给后来人。"] },
{ id: "103", category: "族谱序言", title: "续修族谱序", author: "谱主", updatedAt: "2024 年 5 月 8 日", paragraphs: ["本次续修以旧谱、碑记、户籍资料和长辈口述为基础,由家人共同核对补充。凡暂不能确认之处,均保留来源和疑问,留待后续查证。", "愿这份记录不仅理清世系,也能保存家风、人物与共同记忆。"] },
];
const articleId = ref("101");
const article = reactive({ ...articleRecords[0] });
const articleState = ref("loading");
const favorite = ref(false);
const toastVisible = ref(false);
let toastTimer = null;
const articleParagraphs = computed(() => article.paragraphs || []);
const articleStateClasses = computed(() => ({
[`article-state--${articleState.value}`]: true,
"article-state--expired": articleState.value === "expired",
"article-state--privacy": articleState.value === "privacy",
"article-state--error": articleState.value === "error",
}));
const stateCopy = computed(() => ({
expired: { title: "这篇谱文已无法查看", copy: "内容可能已被作者删除或取消公开,请返回谱文列表查看其他内容。", action: "返回谱文列表" },
privacy: { title: "这篇谱文暂未公开", copy: "作者仅向有权限的家人开放正文,请返回谱文列表查看其他内容。", action: "返回谱文列表" },
error: { title: "谱文暂不可用", copy: "请稍后重新查看,已有谱文不会受到影响。", action: "重新查看" },
}[articleState.value] || {}));
onLoad((query) => {
articleId.value = String(query.articleId || "101");
const selected = articleRecords.find((item) => item.id === articleId.value);
if (selected) Object.assign(article, selected);
articleState.value = ["loading", "error", "expired", "privacy"].includes(query.state)
? query.state
: selected
? "ready"
: "expired";
});
const toggleFavorite = () => {
favorite.value = !favorite.value;
toastVisible.value = true;
if (toastTimer) clearTimeout(toastTimer);
toastTimer = setTimeout(() => { toastVisible.value = false; }, 1800);
};
const editArticle = () => uni.navigateTo({ url: `/pages/family/f06-article-editor?mode=edit&articleId=${articleId.value}` });
const backToArticles = () => uni.redirectTo({ url: "/pages/family/f04-article-list" });
const handleStateAction = () => { if (articleState.value === "error") articleState.value = "ready"; else backToArticles(); };
onUnmounted(() => { if (toastTimer) clearTimeout(toastTimer); });
</script>
<style scoped lang="scss">
.article-detail-page { display: flex; min-height: 100vh; flex-direction: column; background: $paper; }
.article-detail-header, .article-detail-loading, .article-detail-content { z-index: 1; }
.article-detail-loading { min-height: calc(100vh - 100rpx); }
.article-detail-content { padding: 18rpx 24rpx 72rpx; }
.article-paper, .article-state-card { width: 100%; box-sizing: border-box; background: url("/static/assets/modules/family/transparent/module-content-frame.png") center / 100% 100% no-repeat; }
.article-paper { padding: 50rpx 48rpx 54rpx; }
.article-paper > text { display: block; }
.article-paper__category { color: $brand-red; font-size: 22rpx; font-weight: 700; letter-spacing: 3rpx; }
.article-paper__title { margin-top: 12rpx; color: $ink; font-family: STKaiti, KaiTi, serif; font-size: 39rpx; font-weight: 700; line-height: 1.35; }
.article-paper__meta { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 6rpx 18rpx; margin-top: 16rpx; color: $ink-muted; font-size: 21rpx; }
.article-paper__body { margin-top: 28rpx; }
.article-paper__body text { display: block; margin-top: 18rpx; color: $ink; font-size: 25rpx; line-height: 1.85; text-align: justify; }
.article-actions { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14rpx; margin-top: 18rpx; }
.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; }
@media (max-width: 340px) { .article-actions { grid-template-columns: minmax(0, 1fr); } }
</style>