361 lines
10 KiB
Vue
361 lines
10 KiB
Vue
<template>
|
||
<view class="help-page">
|
||
<ModulePageBackground module="profile" />
|
||
<view class="page-layer"
|
||
><PageHeader title="帮助中心" custom-back @back="requestBack"
|
||
/></view>
|
||
<view class="page-content page-layer">
|
||
<view class="help-hero">
|
||
<text>常见问题</text>
|
||
<text>帮助内容会随页面更新。</text>
|
||
</view>
|
||
|
||
<view v-if="helpCenterState === 'loading'" class="help-state-card">
|
||
<AppLoading text="正在读取帮助内容" />
|
||
</view>
|
||
<view v-else-if="helpCenterState === 'error'" class="help-state-card">
|
||
<text>暂时无法读取帮助内容</text>
|
||
<text>{{ helpCenterError || "请检查网络后重新加载。" }}</text>
|
||
<AppButton block type="secondary" label="重新加载" @click="loadHelpArticles" />
|
||
</view>
|
||
<template v-else>
|
||
<view v-if="articles.length" class="help-list-panel">
|
||
<input
|
||
v-model.trim="searchText"
|
||
class="help-search"
|
||
type="text"
|
||
maxlength="80"
|
||
placeholder="搜索问题"
|
||
placeholder-class="help-search__placeholder"
|
||
/>
|
||
<scroll-view class="help-category-list" scroll-x>
|
||
<button
|
||
v-for="category in categories"
|
||
:key="category"
|
||
class="help-category"
|
||
:class="{ 'help-category--active': activeCategory === category }"
|
||
:aria-pressed="activeCategory === category"
|
||
@click="selectCategory(category)"
|
||
>{{ categoryLabel(category) }}</button>
|
||
</scroll-view>
|
||
|
||
<view v-if="visibleArticles.length" class="help-article-list">
|
||
<view v-for="item in visibleArticles" :key="item.key" class="help-article">
|
||
<button
|
||
class="help-article__question"
|
||
:aria-expanded="expandedKey === item.key"
|
||
:aria-controls="`help-answer-${item.key}`"
|
||
@click="toggleArticle(item.key)"
|
||
>
|
||
<view>
|
||
<text class="help-article__category">{{ item.categoryLabel }}</text>
|
||
<text class="help-article__title">{{ item.title }}</text>
|
||
</view>
|
||
<text class="help-article__indicator">{{ expandedKey === item.key ? "收起" : "查看" }}</text>
|
||
</button>
|
||
<text
|
||
v-if="expandedKey === item.key"
|
||
:id="`help-answer-${item.key}`"
|
||
class="help-article__answer"
|
||
>{{ item.content }}</text>
|
||
</view>
|
||
</view>
|
||
<view v-else class="help-empty-state">
|
||
<text>没有符合条件的帮助内容</text>
|
||
<button v-if="searchText || activeCategory !== '全部'" @click="clearFilters">清除筛选</button>
|
||
</view>
|
||
</view>
|
||
<view v-else class="help-state-card">
|
||
<text>当前没有帮助内容</text>
|
||
<text>如有使用问题,可以直接提交反馈。</text>
|
||
</view>
|
||
</template>
|
||
|
||
<view class="help-feedback-card">
|
||
<text>没有找到答案?</text>
|
||
<text>把问题提交给我们,我们会尽快处理。</text>
|
||
<AppButton block label="提交问题反馈" @click="contactSupport" />
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed, ref } from "vue";
|
||
import { onBackPress, onLoad, 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 {
|
||
createRequestController,
|
||
isRequestCancelled
|
||
} from "@/services/api/request-controller.js";
|
||
import { siteContentApi } from "@/services/api/site-content-service.js";
|
||
import { getRequestErrorMessage } from "@/services/api/request-error-message.js";
|
||
import { goBack, handleBackPress, openPage } from "@/utils/navigation/gateway.js";
|
||
|
||
const articles = ref([]);
|
||
const helpCenterState = ref("loading");
|
||
const helpCenterError = ref("");
|
||
const searchText = ref("");
|
||
const activeCategory = ref("全部");
|
||
const expandedKey = ref("");
|
||
const helpArticleRequestController = createRequestController();
|
||
let pageActive = true;
|
||
|
||
const categories = computed(() => ["全部", ...new Set(articles.value.map((item) => item.category))]);
|
||
const categoryLabel = (category) => (
|
||
articles.value.find((item) => item.category === category)?.categoryLabel || category
|
||
);
|
||
const visibleArticles = computed(() => {
|
||
const keyword = searchText.value.trim().toLocaleLowerCase();
|
||
return articles.value.filter((item) => (
|
||
(activeCategory.value === "全部" || item.category === activeCategory.value)
|
||
&& (!keyword || `${item.title}\n${item.content}`.toLocaleLowerCase().includes(keyword))
|
||
));
|
||
});
|
||
|
||
const loadHelpArticles = async () => {
|
||
helpArticleRequestController.abort();
|
||
helpCenterState.value = "loading";
|
||
helpCenterError.value = "";
|
||
expandedKey.value = "";
|
||
try {
|
||
const helpArticles = await siteContentApi.getHelpArticles({
|
||
requestController: helpArticleRequestController,
|
||
});
|
||
if (!pageActive) return;
|
||
articles.value = helpArticles;
|
||
if (!categories.value.includes(activeCategory.value)) activeCategory.value = "全部";
|
||
helpCenterState.value = "ready";
|
||
} catch (error) {
|
||
if (!pageActive || isRequestCancelled(error)) return;
|
||
helpCenterError.value = getRequestErrorMessage(error, "请稍后重试。");
|
||
helpCenterState.value = "error";
|
||
}
|
||
};
|
||
const selectCategory = (category) => {
|
||
activeCategory.value = category;
|
||
expandedKey.value = "";
|
||
};
|
||
const toggleArticle = (key) => {
|
||
expandedKey.value = expandedKey.value === key ? "" : key;
|
||
};
|
||
const clearFilters = () => {
|
||
searchText.value = "";
|
||
activeCategory.value = "全部";
|
||
expandedKey.value = "";
|
||
};
|
||
const contactSupport = () => openPage("M07", {}, "M06");
|
||
const requestBack = () => goBack();
|
||
onLoad(loadHelpArticles);
|
||
onBackPress((event) => handleBackPress(event, requestBack));
|
||
onUnload(() => {
|
||
pageActive = false;
|
||
helpArticleRequestController.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 calc(72rpx + env(safe-area-inset-bottom));
|
||
}
|
||
.help-hero,
|
||
.help-state-card,
|
||
.help-list-panel,
|
||
.help-feedback-card {
|
||
@include adaptive-profile-content;
|
||
}
|
||
.help-hero {
|
||
padding: 34rpx 36rpx;
|
||
text-align: center;
|
||
}
|
||
.help-hero text {
|
||
display: block;
|
||
}
|
||
.help-hero text:first-child,
|
||
.help-state-card text:first-child,
|
||
.help-feedback-card text:first-child {
|
||
color: $ink;
|
||
font-family: STKaiti, KaiTi, serif;
|
||
font-size: clamp(18px, 32rpx, 22px);
|
||
font-weight: 700;
|
||
}
|
||
.help-hero text:last-child,
|
||
.help-state-card text:last-child,
|
||
.help-feedback-card text:nth-child(2) {
|
||
margin-top: 12rpx;
|
||
color: $ink-muted;
|
||
font-size: clamp(14px, 22rpx, 17px);
|
||
line-height: 1.6;
|
||
}
|
||
.help-state-card {
|
||
display: flex;
|
||
min-height: 210rpx;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-top: 20rpx;
|
||
padding: 38rpx;
|
||
text-align: center;
|
||
}
|
||
.help-state-card text {
|
||
display: block;
|
||
}
|
||
.help-state-card .app-button,
|
||
.help-feedback-card .app-button {
|
||
width: 100%;
|
||
margin-top: 24rpx;
|
||
}
|
||
.help-list-panel {
|
||
margin-top: 20rpx;
|
||
padding: 24rpx;
|
||
}
|
||
.help-search {
|
||
width: 100%;
|
||
min-height: 80rpx;
|
||
box-sizing: border-box;
|
||
padding: 0 22rpx;
|
||
border: 1rpx solid rgba(128, 89, 49, 0.28);
|
||
border-radius: 12rpx;
|
||
background: rgba(255, 252, 245, 0.78);
|
||
color: $ink;
|
||
font-size: clamp(14px, 24rpx, 18px);
|
||
}
|
||
.help-search__placeholder {
|
||
color: #a49382;
|
||
}
|
||
.help-category-list {
|
||
width: 100%;
|
||
margin-top: 18rpx;
|
||
white-space: nowrap;
|
||
}
|
||
.help-category {
|
||
display: inline-flex;
|
||
min-width: 112rpx;
|
||
min-height: 80rpx;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 12rpx;
|
||
padding: 0 20rpx;
|
||
border: 1rpx solid rgba(128, 89, 49, 0.24);
|
||
border-radius: 40rpx;
|
||
background: rgba(255, 252, 245, 0.54);
|
||
color: $ink-muted;
|
||
font-size: clamp(13px, 22rpx, 16px);
|
||
line-height: 1.4;
|
||
}
|
||
.help-category::after,
|
||
.help-article__question::after,
|
||
.help-empty-state button::after {
|
||
border: 0;
|
||
}
|
||
.help-category--active {
|
||
border-color: $brand-red;
|
||
background: $brand-red;
|
||
color: #fff7e8;
|
||
}
|
||
.help-article-list {
|
||
display: grid;
|
||
gap: 14rpx;
|
||
width: 100%;
|
||
margin-top: 18rpx;
|
||
}
|
||
.help-article {
|
||
overflow: hidden;
|
||
border: 1rpx solid rgba(128, 89, 49, 0.22);
|
||
border-radius: 12rpx;
|
||
background: rgba(255, 252, 245, 0.88);
|
||
}
|
||
.help-article__question {
|
||
display: flex;
|
||
width: 100%;
|
||
min-height: 100rpx;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 18rpx;
|
||
padding: 20rpx 22rpx;
|
||
border: 0;
|
||
border-radius: 0;
|
||
background: transparent;
|
||
text-align: left;
|
||
}
|
||
.help-article__question view {
|
||
min-width: 0;
|
||
flex: 1;
|
||
}
|
||
.help-article__question text {
|
||
display: block;
|
||
}
|
||
.help-article__category {
|
||
color: #9a6555;
|
||
font-size: clamp(12px, 20rpx, 15px);
|
||
}
|
||
.help-article__title {
|
||
margin-top: 5rpx;
|
||
color: $ink;
|
||
font-size: clamp(15px, 25rpx, 19px);
|
||
font-weight: 700;
|
||
line-height: 1.45;
|
||
}
|
||
.help-article__indicator {
|
||
flex: 0 0 auto;
|
||
color: $brand-red-dark;
|
||
font-size: clamp(12px, 20rpx, 15px);
|
||
}
|
||
.help-article__answer {
|
||
display: block;
|
||
padding: 16rpx 22rpx 24rpx;
|
||
border-top: 1rpx solid rgba(128, 89, 49, 0.16);
|
||
color: $ink-muted;
|
||
font-size: clamp(14px, 23rpx, 17px);
|
||
line-height: 1.7;
|
||
white-space: pre-wrap;
|
||
}
|
||
.help-empty-state {
|
||
padding: 50rpx 22rpx 28rpx;
|
||
color: $ink-muted;
|
||
font-size: clamp(14px, 23rpx, 17px);
|
||
text-align: center;
|
||
}
|
||
.help-empty-state text {
|
||
display: block;
|
||
}
|
||
.help-empty-state button {
|
||
margin-top: 16rpx;
|
||
border: 0;
|
||
background: transparent;
|
||
color: $brand-red-dark;
|
||
font-size: clamp(14px, 23rpx, 17px);
|
||
}
|
||
.help-feedback-card {
|
||
margin-top: 20rpx;
|
||
padding: 32rpx 34rpx;
|
||
text-align: center;
|
||
}
|
||
.help-feedback-card text {
|
||
display: block;
|
||
}
|
||
.help-feedback-card text:first-child {
|
||
font-size: clamp(17px, 28rpx, 20px);
|
||
}
|
||
@media (max-width: 340px) {
|
||
.page-content {
|
||
padding-right: 20rpx;
|
||
padding-left: 20rpx;
|
||
}
|
||
}
|
||
</style>
|