Files
jiapuapp/pages/family/f04-article-list.vue
T
2026-07-23 08:24:07 +08:00

166 lines
8.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-04用途谱文分类搜索列表与新建入口 -->
<template>
<view class="article-list-page" :class="{ 'article-list-state--loading': listState === 'loading', 'article-list-state--empty': listState === 'empty', 'article-list-state--error': listState === 'error' }">
<ModulePageBackground module="family" />
<view class="article-list-header"><PageHeader title="谱文" :action="hasValidContext ? '新建' : ''" @action="createArticle" /></view>
<view v-if="listState === 'loading'" class="article-list-loading">
<AppLoading text="正在整理家族谱文" description="请稍候,正在读取家训、往事与序言。" />
</view>
<view v-else class="article-list-content">
<template v-if="listState === 'ready'">
<view class="article-search">
<input v-model="keyword" placeholder="搜索标题、作者或正文" confirm-type="search" />
</view>
<view class="article-categories">
<view class="article-categories__row">
<view v-for="category in articleCategories" :key="category" class="article-category" :class="{ 'article-category--active': activeCategory === category }" @click="activeCategory = category"><text>{{ category }}</text></view>
</view>
</view>
<view v-if="filteredArticles.length" class="article-list">
<view v-for="article in filteredArticles" :key="article.id" class="article-card" role="button" :aria-label="`查看谱文${article.title}`" @click="openArticle(article)">
<text class="article-card__category">{{ article.category }}</text>
<text class="article-card__title">{{ article.title }}</text>
<text class="article-card__summary">{{ article.summary }}</text>
<view class="article-card__meta"><text>{{ article.author }}</text><text>{{ article.updatedAt }}</text></view>
</view>
<AppButton block label="新建谱文" @click="createArticle" />
</view>
<view v-else class="article-list-state-card">
<text>{{ keyword || activeCategory !== '全部' ? '没有找到相关谱文' : '还没有谱文' }}</text>
<text>{{ keyword || activeCategory !== '全部' ? '换一个关键词或分类继续查找。' : '从第一篇家训、序言或家族往事开始记录。' }}</text>
<AppButton block :label="keyword || activeCategory !== '全部' ? '清空筛选' : '新建谱文'" @click="keyword || activeCategory !== '全部' ? resetFilters() : createArticle()" />
</view>
</template>
<view v-else class="article-list-state-card">
<text>{{ stateCopy.title }}</text>
<text>{{ stateCopy.copy }}</text>
<AppButton :type="listState === 'error' ? 'secondary' : 'primary'" block :label="stateCopy.action" @click="handleStateAction" />
</view>
</view>
</view>
</template>
<script setup>
import { computed, ref } from "vue";
import { onLoad } 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 {
getGenealogyFixtureAccess,
listFamilyArticleFixtures,
} from "@/data/mock.js";
import { goBack, openPage } from "@/utils/navigation.js";
const articleCategories = ["全部", "家风家训", "家族往事", "族谱序言"];
const genealogyId = ref("");
const hasValidContext = ref(false);
const articles = ref([]);
const activeCategory = ref("全部");
const keyword = ref("");
const listState = ref("loading");
const filteredArticles = computed(() => {
const term = keyword.value.trim().toLowerCase();
return articles.value.filter((article) => {
const categoryMatched = activeCategory.value === "全部" || article.category === activeCategory.value;
const keywordMatched = !term || `${article.title} ${article.summary} ${article.author}`.toLowerCase().includes(term);
return categoryMatched && keywordMatched;
});
});
const stateCopy = computed(() => ({
empty: {
title: "还没有谱文",
copy: "当前家谱尚无可读取谱文,真实写接口接入后才能新增。",
action: "填写谱文预览",
},
error: {
title: "谱文列表暂不可用",
copy: "请稍后重新查看,已有谱文不会受到影响。",
action: "重新查看",
},
invalid: {
title: "谱文入口无效",
copy: "没有找到可访问的成员家谱,页面不会展示其他家谱内容。",
action: "返回上一页",
},
}[listState.value]));
onLoad((query) => {
genealogyId.value = String(query.genealogyId || "");
hasValidContext.value = ["owner", "member"].includes(
getGenealogyFixtureAccess(genealogyId.value).accessRole,
);
if (!genealogyId.value || !hasValidContext.value) {
listState.value = "invalid";
return;
}
articles.value = listFamilyArticleFixtures(genealogyId.value);
listState.value = ["loading", "empty", "error"].includes(query.state)
? query.state
: articles.value.length
? "ready"
: "empty";
});
const openArticle = (article) =>
openPage(
"F05",
{ genealogyId: genealogyId.value, articleId: String(article.id) },
"F04",
);
const createArticle = () =>
hasValidContext.value
? openPage(
"F06",
{ genealogyId: genealogyId.value, mode: "create" },
"F04",
)
: Promise.resolve(false);
const restoreArticles = () => {
articles.value = listFamilyArticleFixtures(genealogyId.value);
listState.value = articles.value.length ? "ready" : "empty";
};
const handleStateAction = () => {
if (listState.value === "invalid") return goBack();
if (listState.value === "error") return restoreArticles();
return createArticle();
};
const resetFilters = () => { keyword.value = ""; activeCategory.value = "全部"; };
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.article-list-page { display: flex; min-height: 100vh; flex-direction: column; background: $paper; }
.article-list-header, .article-list-loading, .article-list-content { z-index: 1; }
.article-list-loading { min-height: calc(100vh - 100rpx); }
.article-list-content { padding: 18rpx 24rpx 72rpx; }
.article-search { @include adaptive.adaptive-family-field; min-height: 82rpx; padding: 12rpx 26rpx; }
.article-search input { width: 100%; min-height: 58rpx; color: $ink; font-size: 24rpx; }
.article-categories { width: 100%; margin-top: 16rpx; }
.article-categories__row { display: flex; flex-wrap: wrap; gap: 12rpx; padding: 2rpx 4rpx 8rpx; }
.article-category { @include adaptive.adaptive-family-field; min-height: 58rpx; padding: 0 28rpx; color: $ink-muted; font-size: 23rpx; }
.article-category text { display: flex; min-height: 58rpx; align-items: center; }
.article-category--active { color: $brand-red; font-weight: 700; }
.article-list { display: flex; flex-direction: column; gap: 16rpx; margin-top: 10rpx; }
.article-card, .article-list-state-card { @include adaptive.adaptive-family-content; width: 100%; }
.article-card { min-height: 196rpx; padding: 34rpx 46rpx 30rpx; }
.article-card > text { display: block; }
.article-card__category { color: $brand-red; font-size: 21rpx; font-weight: 700; letter-spacing: 2rpx; }
.article-card__title { margin-top: 7rpx; color: $ink; font-family: STKaiti, KaiTi, serif; font-size: 31rpx; font-weight: 700; }
.article-card__summary { margin-top: 10rpx; color: $ink-muted; font-size: 23rpx; line-height: 1.55; }
.article-card__meta { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 6rpx 18rpx; margin-top: 12rpx; color: $ink-muted; font-size: 20rpx; }
.article-list > .app-button { margin-top: 8rpx; }
.article-list-state-card { min-height: 340rpx; margin-top: 30rpx; padding: 78rpx 52rpx 50rpx; text-align: center; }
.article-list-state-card > text { display: block; }
.article-list-state-card > text:first-child { color: $ink; font-family: STKaiti, KaiTi, serif; font-size: 35rpx; font-weight: 700; }
.article-list-state-card > text:nth-child(2) { margin-top: 18rpx; color: $ink-muted; font-size: 24rpx; line-height: 1.65; }
.article-list-state-card .app-button { margin-top: 28rpx; }
</style>