feat(faq): refine category icons and project faq flows
This commit is contained in:
+222
-29
@@ -1,27 +1,45 @@
|
||||
<template>
|
||||
<div class="page ctms-page-shell page--flush">
|
||||
<el-row :gutter="12">
|
||||
<el-col v-if="canReadCategories" :span="5">
|
||||
<div class="page ctms-page-shell page--flush medical-consult-page">
|
||||
<div class="page-bg-dots"></div>
|
||||
<section class="faq-hero">
|
||||
<h1><i class="fas fa-book-medical hero-title-icon"></i>项目知识库</h1>
|
||||
<div class="hero-tools">
|
||||
<el-autocomplete
|
||||
v-model="keyword"
|
||||
:placeholder="TEXT.common.placeholders.keyword"
|
||||
clearable
|
||||
class="hero-search"
|
||||
:fetch-suggestions="fetchSuggestions"
|
||||
:trigger-on-focus="false"
|
||||
highlight-first-item
|
||||
@select="onSuggestionSelect"
|
||||
@keyup.enter="loadFaqs"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Search /></el-icon>
|
||||
</template>
|
||||
</el-autocomplete>
|
||||
<div class="spacer" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<el-row :gutter="0" class="faq-workspace">
|
||||
<el-col v-if="canReadCategories" :span="5" class="faq-sidebar-col">
|
||||
<FaqCategoryPanel
|
||||
v-model="activeCategory"
|
||||
:categories="categories"
|
||||
:faqs="faqs"
|
||||
@refresh="loadCategories"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="canReadCategories ? 19 : 24">
|
||||
<el-col :span="canReadCategories ? 19 : 24" class="faq-content-col">
|
||||
<div class="faq-main unified-shell">
|
||||
<div class="filters unified-action-bar">
|
||||
<el-input
|
||||
v-model="keyword"
|
||||
:placeholder="TEXT.common.placeholders.keyword"
|
||||
clearable
|
||||
@keyup.enter="loadFaqs"
|
||||
style="width: 260px"
|
||||
/>
|
||||
<el-switch v-model="onlyActive" :active-text="TEXT.common.labels.activeOnly" @change="loadFaqs" />
|
||||
<div class="spacer" />
|
||||
<div class="list-toolbar">
|
||||
<PermissionAction action="faq.create">
|
||||
<el-button type="primary" @click="openForm()">{{ TEXT.modules.knowledgeMedicalConsult.newItem }}</el-button>
|
||||
<el-button type="primary" class="new-consult-btn" @click="openForm()">
|
||||
<el-icon class="el-icon--left"><Plus /></el-icon>
|
||||
新建
|
||||
</el-button>
|
||||
</PermissionAction>
|
||||
</div>
|
||||
|
||||
@@ -56,6 +74,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { Plus, Search } from "@element-plus/icons-vue";
|
||||
import { fetchFaqCategories, fetchFaqItems } from "../api/faqs";
|
||||
import FaqCategoryPanel from "../components/FaqCategoryPanel.vue";
|
||||
import FaqList from "../components/FaqList.vue";
|
||||
@@ -76,7 +95,7 @@ const page = ref(1);
|
||||
const pageSize = ref(10);
|
||||
const total = ref(0);
|
||||
const keyword = ref("");
|
||||
const onlyActive = ref(true);
|
||||
|
||||
const activeCategory = ref("");
|
||||
const showForm = ref(false);
|
||||
const editing = ref<any | null>(null);
|
||||
@@ -114,7 +133,6 @@ const loadFaqs = async () => {
|
||||
};
|
||||
if (activeCategory.value) params.category_id = activeCategory.value;
|
||||
if (keyword.value) params.keyword = keyword.value;
|
||||
if (onlyActive.value) params.is_active = true;
|
||||
const { data } = await fetchFaqItems(params);
|
||||
if (Array.isArray(data)) {
|
||||
faqs.value = data;
|
||||
@@ -130,6 +148,30 @@ const loadFaqs = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const fetchSuggestions = async (queryString: string, callback: (results: Array<{ value: string }>) => void) => {
|
||||
if (!queryString || !study.currentStudy) {
|
||||
callback([]);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const params: Record<string, any> = {
|
||||
study_id: study.currentStudy.id,
|
||||
keyword: queryString,
|
||||
limit: 8,
|
||||
};
|
||||
const { data } = await fetchFaqItems(params);
|
||||
const items = Array.isArray(data) ? data : data.items || [];
|
||||
callback(items.slice(0, 8).map((item: any) => ({ value: item.question || item.title || "" })));
|
||||
} catch {
|
||||
callback([]);
|
||||
}
|
||||
};
|
||||
|
||||
const onSuggestionSelect = (item: { value: string }) => {
|
||||
keyword.value = item.value;
|
||||
loadFaqs();
|
||||
};
|
||||
|
||||
const onPageChange = (p: number) => {
|
||||
page.value = p;
|
||||
loadFaqs();
|
||||
@@ -162,26 +204,177 @@ onMounted(async () => {
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
min-height: calc(100vh - 64px);
|
||||
background: linear-gradient(180deg, #f0f4ff 0%, #ffffff 30%, #f8fafc 100%);
|
||||
}
|
||||
.faq-main {
|
||||
background: #fff;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
|
||||
.page-bg-dots {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
opacity: 0.03;
|
||||
background-image: radial-gradient(#1e40af 1px, transparent 1px);
|
||||
background-size: 24px 24px;
|
||||
}
|
||||
|
||||
.faq-hero {
|
||||
position: relative;
|
||||
padding: 28px 24px 36px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #e8eef6;
|
||||
background:
|
||||
radial-gradient(ellipse at 50% 0%, rgba(59, 130, 246, 0.06) 0%, transparent 60%),
|
||||
linear-gradient(180deg, #ffffff 0%, #fafcff 100%);
|
||||
overflow: hidden;
|
||||
}
|
||||
.filters {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
|
||||
.faq-hero::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -80px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 400px;
|
||||
height: 200px;
|
||||
background: radial-gradient(ellipse, rgba(59, 130, 246, 0.08) 0%, transparent 70%);
|
||||
pointer-events: none;
|
||||
}
|
||||
.spacer {
|
||||
flex: 1;
|
||||
|
||||
.faq-hero h1 {
|
||||
margin: 0 0 24px;
|
||||
color: #0f172a;
|
||||
font-size: 32px;
|
||||
font-weight: 900;
|
||||
letter-spacing: -0.02em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.hero-title-icon {
|
||||
font-size: 28px;
|
||||
color: #3b82f6;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.hero-desc {
|
||||
margin: 8px 0 28px;
|
||||
color: #64748b;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.hero-tools {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 14px;
|
||||
max-width: 920px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.hero-search {
|
||||
width: min(620px, 54vw);
|
||||
}
|
||||
.hero-search :deep(.el-input__wrapper) {
|
||||
height: 54px;
|
||||
padding: 0 20px;
|
||||
border-radius: 14px;
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
box-shadow: 0 14px 34px rgba(15, 35, 69, 0.08), 0 0 0 1px #e6edf6 inset;
|
||||
}
|
||||
.hero-search :deep(.el-input__inner) {
|
||||
color: #1f2d3d;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.hero-search :deep(.el-input__prefix) {
|
||||
color: #9aa9bb;
|
||||
font-size: 20px;
|
||||
}
|
||||
.new-consult-btn {
|
||||
flex-shrink: 0;
|
||||
height: 46px;
|
||||
padding: 0 20px;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
|
||||
box-shadow: 0 4px 16px rgba(59, 130, 246, 0.3);
|
||||
font-weight: 800;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.new-consult-btn:hover,
|
||||
.new-consult-btn:focus {
|
||||
background: linear-gradient(135deg, #2563eb 0%, #1e40af 100%);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 24px rgba(59, 130, 246, 0.4);
|
||||
}
|
||||
.faq-workspace {
|
||||
min-height: calc(100vh - 252px);
|
||||
}
|
||||
.faq-sidebar-col {
|
||||
border-right: 1px solid #edf2f8;
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
}
|
||||
.faq-content-col {
|
||||
min-width: 0;
|
||||
}
|
||||
.faq-main {
|
||||
min-height: calc(100vh - 252px);
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
padding: 24px 28px 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.list-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding: 0 0 16px;
|
||||
}
|
||||
|
||||
.list-title strong {
|
||||
min-width: 30px;
|
||||
height: 24px;
|
||||
padding: 0 8px;
|
||||
border-radius: 999px;
|
||||
background: #e8f2ff;
|
||||
color: #1f5fb8;
|
||||
font-size: 13px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.list-meta {
|
||||
color: #7d8da3;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.pagination-wrap {
|
||||
padding: 10px 16px 0;
|
||||
padding: 16px 0 24px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 1080px) {
|
||||
.hero-tools {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.hero-search {
|
||||
width: 100%;
|
||||
}
|
||||
.faq-sidebar-col,
|
||||
.faq-content-col {
|
||||
max-width: 100%;
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
.faq-main {
|
||||
padding: 18px 16px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user