77 lines
6.2 KiB
Vue
77 lines
6.2 KiB
Vue
<!-- 页面编号:M-06;用途:帮助搜索、分类与常见问题。 -->
|
||
<template>
|
||
<view class="help-page">
|
||
<ModulePageBackground module="profile" />
|
||
<view class="page-layer"><PageHeader title="帮助中心" /></view>
|
||
<view class="page-content page-layer">
|
||
<text class="help-source-note">{{ sourceNote }}</text>
|
||
<view class="search-box"><input v-model.trim="keyword" aria-label="搜索帮助" placeholder="搜索问题关键词" /><text>{{ filteredQuestions.length }} 条</text></view>
|
||
<scroll-view scroll-x class="category-scroll" :show-scrollbar="false"><view class="category-row"><view v-for="category in helpCategories" :key="category.value" class="category-chip" :class="{ active: activeCategory === category.value }" role="button" @click="activeCategory = category.value">{{ category.label }}</view></view></scroll-view>
|
||
<view v-if="filteredQuestions.length" class="question-list">
|
||
<view v-for="item in filteredQuestions" :key="item.id" class="question-card">
|
||
<view class="question-heading" role="button" :aria-label="item.question" @click="toggleQuestion(item.id)"><text>{{ item.question }}</text><text>{{ expandedIds.includes(item.id) ? "收起" : "查看" }}</text></view>
|
||
<text v-if="expandedIds.includes(item.id)" class="answer">{{ item.answer }}</text>
|
||
</view>
|
||
</view>
|
||
<view v-else class="empty-card"><text>没有找到相关问题</text><text>换个关键词试试,或直接提交意见反馈。</text></view>
|
||
<AppButton block type="secondary" label="联系家谱助手" @click="contactSupport" />
|
||
</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 ModulePageBackground from "@/components/ModulePageBackground.vue";
|
||
import PageHeader from "@/components/PageHeader.vue";
|
||
import { appApi, createRequestController, isRequestCancelled } from "@/utils/api.js";
|
||
import { openPage } from "@/utils/navigation.js";
|
||
|
||
const questions = ref([]);
|
||
const helpState = ref("loading");
|
||
const controller = createRequestController();
|
||
let active = true;
|
||
const categoryLabels = Object.freeze({ common: "常见问题", member: "成员管理", lineage: "世系关系" });
|
||
const helpCategories = computed(() => [
|
||
{ value: "", label: "全部" },
|
||
...[...new Set(questions.value.map((item) => item.category).filter(Boolean))].map((value) => ({ value, label: categoryLabels[value.toLowerCase()] || value })),
|
||
]);
|
||
const activeCategory = ref("");
|
||
const keyword = ref("");
|
||
const expandedIds = ref([]);
|
||
const sourceNote = computed(() => ({ loading: "正在读取帮助文章…", error: "帮助文章暂时无法读取,请稍后重试。", empty: "暂未收到可展示的帮助文章。", ready: "帮助内容来自服务端,可按分类或关键词查找。" })[helpState.value]);
|
||
const filteredQuestions = computed(() => {
|
||
const query = keyword.value.toLowerCase();
|
||
return questions.value.filter((item) => (!activeCategory.value || item.category === activeCategory.value) && (!query || `${item.question}${item.answer}`.toLowerCase().includes(query)));
|
||
});
|
||
const toggleQuestion = (id) => { expandedIds.value = expandedIds.value.includes(id) ? expandedIds.value.filter((item) => item !== id) : [...expandedIds.value, id]; };
|
||
const contactSupport = () => openPage("M07", {}, "M06");
|
||
const loadHelpArticles = async () => {
|
||
controller.abort();
|
||
helpState.value = "loading";
|
||
try {
|
||
const rows = await appApi.getHelpArticles({ requestController: controller });
|
||
if (!active) return;
|
||
questions.value = rows.map((item) => ({
|
||
id: String(item.helpId),
|
||
category: String(item.helpCategory || "其他"),
|
||
question: String(item.helpTitle || "未命名帮助文章"),
|
||
answer: String(item.helpContent || "暂无正文")
|
||
}));
|
||
helpState.value = questions.value.length ? "ready" : "empty";
|
||
} catch (error) {
|
||
if (!active || isRequestCancelled(error)) return;
|
||
helpState.value = "error";
|
||
}
|
||
};
|
||
onLoad(loadHelpArticles);
|
||
onShow(() => { if (helpState.value !== "loading") loadHelpArticles(); });
|
||
onUnload(() => { active = false; controller.abort(); });
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
|
||
.help-page{display:flex;min-height:100vh;flex-direction:column;background:$paper}.page-layer{z-index:1}.page-content{flex:1;padding:24rpx 28rpx 72rpx}.help-source-note{display:block;margin:0 4rpx 16rpx;color:$ink-muted;font-size:21rpx;line-height:1.55}.search-box{@include adaptive.adaptive-profile-field;display:grid;grid-template-columns:minmax(0,1fr) auto;min-height:86rpx;align-items:center;gap:16rpx;padding:0 30rpx}.search-box input{width:auto;min-width:0;min-height:68rpx;color:$ink;font-size:23rpx}.search-box text{color:$ink-muted;font-size:20rpx}.category-scroll{width:100%;margin-top:18rpx}.category-row{display:flex;width:max-content;gap:12rpx;padding:2rpx}.category-chip{display:flex;min-width:100rpx;min-height:64rpx;align-items:center;justify-content:center;padding:0 22rpx;box-sizing:border-box;color:$ink-muted;font-size:22rpx}.category-chip.active{@include adaptive.adaptive-scroll-button(secondary);color:$brand-red;font-weight:700}.question-list{display:flex;flex-direction:column;gap:14rpx;margin-top:18rpx}.question-card,.empty-card{@include adaptive.adaptive-profile-content}.question-card{min-height:104rpx;padding:24rpx 34rpx}.question-heading{display:grid;grid-template-columns:minmax(0,1fr) auto;min-height:58rpx;align-items:center;gap:18rpx}.question-heading text:first-child{color:$ink;font-size:24rpx;font-weight:700;overflow-wrap:anywhere}.question-heading text:last-child{color:$brand-red;font-size:20rpx}.answer{display:block;padding:14rpx 4rpx 6rpx;border-top:1px solid rgba(181,137,63,.32);color:$ink-muted;font-size:22rpx;line-height:1.65;overflow-wrap:anywhere}.empty-card{min-height:220rpx;padding:58rpx 44rpx;text-align:center}.empty-card text{display:block}.empty-card text:first-child{color:$ink;font-size:29rpx;font-weight:700}.empty-card text:last-child{margin-top:12rpx;color:$ink-muted;font-size:22rpx;line-height:1.5}.page-content>.app-button{margin-top:28rpx}@media(max-width:340px){.page-content{padding-right:20rpx;padding-left:20rpx}}
|
||
</style>
|