Files
jiapuapp/pages/profile/m06-help-center.vue
T

329 lines
8.5 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.
<!-- 页面编号M-06用途帮助搜索分类与常见问题 -->
<template>
<view class="help-page">
<ModulePageBackground module="profile" />
<view class="page-layer"><PageHeader title="帮助中心" /></view>
<view class="page-content page-layer">
<view v-if="helpState === 'loading'" class="help-state-card"
><AppLoading text="正在读取帮助内容"
/></view>
<view v-else-if="helpState === 'error'" class="help-state-card">
<text>帮助内容暂时无法读取</text>
<text>请检查网络后重新加载或直接提交问题反馈</text>
<AppButton block type="secondary" label="重新加载" @click="loadHelpArticles" />
<AppButton block label="提交问题反馈" @click="contactSupport" />
</view>
<template v-else>
<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"
/>
</template>
</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 { 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">
@import "../../styles/adaptive-frame-profiles.scss";
.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: clamp(13px, 21rpx, 16px);
line-height: 1.55;
}
.help-state-card {
display: flex;
min-height: 300rpx;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 52rpx 42rpx;
text-align: center;
@include adaptive-profile-content;
}
.help-state-card text {
display: block;
}
.help-state-card text:first-child {
color: $ink;
font-family: STKaiti, KaiTi, serif;
font-size: clamp(18px, 32rpx, 22px);
font-weight: 700;
}
.help-state-card text:nth-child(2) {
margin-top: 16rpx;
color: $ink-muted;
font-size: clamp(14px, 22rpx, 17px);
line-height: 1.6;
}
.help-state-card .app-button {
width: 100%;
margin-top: 20rpx;
}
.search-box {
@include 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: clamp(14px, 23rpx, 17px);
}
.search-box text {
color: $ink-muted;
font-size: clamp(13px, 20rpx, 16px);
}
.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: clamp(14px, 22rpx, 17px);
}
.category-chip.active {
@include 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-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: clamp(15px, 24rpx, 18px);
font-weight: 700;
overflow-wrap: anywhere;
}
.question-heading text:last-child {
color: $brand-red;
font-size: clamp(13px, 20rpx, 16px);
}
.answer {
display: block;
padding: 14rpx 4rpx 6rpx;
border-top: 1px solid rgba(181, 137, 63, 0.32);
color: $ink-muted;
font-size: clamp(14px, 22rpx, 17px);
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: clamp(16px, 29rpx, 20px);
font-weight: 700;
}
.empty-card text:last-child {
margin-top: 12rpx;
color: $ink-muted;
font-size: clamp(14px, 22rpx, 17px);
line-height: 1.5;
}
.page-content > .app-button {
margin-top: 28rpx;
}
@media (max-width: 340px) {
.page-content {
padding-right: 20rpx;
padding-left: 20rpx;
}
}
</style>