c68dddfc01
新增 ONLYOFFICE 配置签名、内部内容接口、容器编排与反向代理。 打通网页端和桌面端独立预览工作区,完善文档入口、布局及帮助体验。 补充桌面安全发布门禁、开发脚本、使用文档和前后端测试。
578 lines
15 KiB
Vue
578 lines
15 KiB
Vue
<template>
|
|
<div class="page ctms-page-shell page--flush medical-consult-page medical-consult-page--workbench">
|
|
<section class="faq-hero">
|
|
<div class="workbench-toolbar-meta">
|
|
<span>知识条目</span>
|
|
<small>{{ activeCategoryName }} · {{ resultSummary }}</small>
|
|
</div>
|
|
<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>
|
|
<PermissionAction action="faq.create">
|
|
<el-button type="primary" class="new-consult-btn" @click="openForm()">
|
|
<el-icon class="el-icon--left"><Plus /></el-icon>
|
|
新建
|
|
</el-button>
|
|
</PermissionAction>
|
|
</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" class="faq-content-col">
|
|
<div class="faq-main unified-shell">
|
|
<FaqList
|
|
:items="faqs"
|
|
:categories="categories"
|
|
:loading="loading"
|
|
:can-update="canUpdateFaq"
|
|
:can-delete="canDeleteFaq"
|
|
@refresh="loadFaqs"
|
|
/>
|
|
<div class="pagination-wrap" v-if="total > 0">
|
|
<el-pagination
|
|
v-model:current-page="page"
|
|
v-model:page-size="pageSize"
|
|
:page-sizes="[5, 10, 20]"
|
|
:total="total"
|
|
layout="prev, pager, next, sizes, total"
|
|
small
|
|
@current-change="onPageChange"
|
|
@size-change="onPageSizeChange"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</el-col>
|
|
</el-row>
|
|
|
|
<FaqItemForm v-model="showForm" :item="editing" :categories="categories" @success="loadFaqs" />
|
|
</div>
|
|
</template>
|
|
|
|
<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";
|
|
import FaqItemForm from "../components/FaqItemForm.vue";
|
|
import { useAuthStore } from "../store/auth";
|
|
import { useStudyStore } from "../store/study";
|
|
import PermissionAction from "../components/PermissionAction.vue";
|
|
import { usePermission } from "../utils/permission";
|
|
import { TEXT } from "../locales";
|
|
|
|
const auth = useAuthStore();
|
|
const study = useStudyStore();
|
|
|
|
const categories = ref<any[]>([]);
|
|
const faqs = ref<any[]>([]);
|
|
const loading = ref(false);
|
|
const page = ref(1);
|
|
const pageSize = ref(10);
|
|
const total = ref(0);
|
|
const keyword = ref("");
|
|
|
|
const activeCategory = ref("");
|
|
const showForm = ref(false);
|
|
const editing = ref<any | null>(null);
|
|
|
|
const { can } = usePermission();
|
|
const canReadCategories = computed(() => can("faq.category.read"));
|
|
const canUpdateFaq = computed(() => can("faq.update"));
|
|
const canDeleteFaq = computed(() => can("faq.delete"));
|
|
const activeCategoryName = computed(() => {
|
|
if (!activeCategory.value) return TEXT.common.labels.all;
|
|
return categories.value.find((item: any) => item.id === activeCategory.value)?.name || "已筛选分类";
|
|
});
|
|
const resultSummary = computed(() => {
|
|
const suffix = keyword.value.trim() ? ",已应用搜索" : "";
|
|
return `${total.value} 条${suffix}`;
|
|
});
|
|
|
|
const loadCategories = async () => {
|
|
try {
|
|
if (!study.currentStudy) return;
|
|
if (!canReadCategories.value) {
|
|
categories.value = [];
|
|
activeCategory.value = "";
|
|
return;
|
|
}
|
|
const params: Record<string, any> = {};
|
|
if (study.currentStudy) params.study_id = study.currentStudy.id;
|
|
const { data } = await fetchFaqCategories(params);
|
|
categories.value = data.items || data || [];
|
|
} catch (e: any) {
|
|
ElMessage.error(e?.response?.data?.message || TEXT.common.messages.loadFailed);
|
|
}
|
|
};
|
|
|
|
const loadFaqs = async () => {
|
|
if (!study.currentStudy) return;
|
|
loading.value = true;
|
|
try {
|
|
const params: Record<string, any> = {
|
|
skip: (page.value - 1) * pageSize.value,
|
|
limit: pageSize.value,
|
|
study_id: study.currentStudy.id,
|
|
};
|
|
if (activeCategory.value) params.category_id = activeCategory.value;
|
|
if (keyword.value) params.keyword = keyword.value;
|
|
const { data } = await fetchFaqItems(params);
|
|
if (Array.isArray(data)) {
|
|
faqs.value = data;
|
|
total.value = data.length;
|
|
} else {
|
|
faqs.value = data.items || [];
|
|
total.value = data.total || 0;
|
|
}
|
|
} catch (e: any) {
|
|
ElMessage.error(e?.response?.data?.message || TEXT.common.messages.loadFailed);
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
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();
|
|
};
|
|
|
|
const onPageSizeChange = (size: number) => {
|
|
pageSize.value = size;
|
|
page.value = 1;
|
|
loadFaqs();
|
|
};
|
|
|
|
watch(activeCategory, () => {
|
|
page.value = 1;
|
|
loadFaqs();
|
|
});
|
|
|
|
const openForm = (row?: any) => {
|
|
editing.value = row || null;
|
|
showForm.value = true;
|
|
};
|
|
|
|
onMounted(async () => {
|
|
if (!auth.user && auth.token) {
|
|
await auth.fetchMe().catch(() => {});
|
|
}
|
|
await loadCategories();
|
|
loadFaqs();
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.page {
|
|
position: relative;
|
|
padding: 0;
|
|
min-height: calc(100vh - 64px);
|
|
background: linear-gradient(180deg, #f0f4ff 0%, #ffffff 30%, #f8fafc 100%);
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.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-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: 16px 0 24px;
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.workbench-toolbar-meta {
|
|
display: flex;
|
|
position: relative;
|
|
min-width: 160px;
|
|
padding-left: 14px;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
text-align: left;
|
|
}
|
|
|
|
.workbench-toolbar-meta::before {
|
|
content: "";
|
|
position: absolute;
|
|
top: 2px;
|
|
bottom: 2px;
|
|
left: 0;
|
|
width: 4px;
|
|
border-radius: 999px;
|
|
background: linear-gradient(180deg, #2f7be8 0%, #23b7d9 100%);
|
|
box-shadow: 0 0 16px rgba(47, 123, 232, 0.35);
|
|
}
|
|
|
|
.workbench-toolbar-meta span {
|
|
color: #11335a;
|
|
font-size: 15px;
|
|
font-weight: 850;
|
|
}
|
|
|
|
.workbench-toolbar-meta small {
|
|
color: #436785;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.medical-consult-page--workbench {
|
|
display: flex;
|
|
height: 100%;
|
|
min-height: 0;
|
|
flex-direction: column;
|
|
gap: 0 !important;
|
|
overflow: hidden;
|
|
background: linear-gradient(180deg, #eef5ff 0%, #f7fbff 100%);
|
|
}
|
|
|
|
.medical-consult-page--workbench .faq-hero {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
padding: 12px 18px !important;
|
|
border: 1px solid rgba(90, 145, 220, 0.24) !important;
|
|
background:
|
|
radial-gradient(circle at 0% 0%, rgba(42, 132, 255, 0.22) 0%, transparent 34%),
|
|
radial-gradient(circle at 78% 18%, rgba(35, 183, 217, 0.16) 0%, transparent 30%),
|
|
linear-gradient(135deg, #ecf7ff 0%, #edf3ff 46%, #f8fcff 100%) !important;
|
|
box-shadow:
|
|
0 10px 24px rgba(30, 73, 128, 0.08),
|
|
0 1px 0 rgba(255, 255, 255, 0.86) inset !important;
|
|
text-align: left;
|
|
border-radius: 0 !important;
|
|
}
|
|
|
|
.medical-consult-page--workbench .faq-hero::before {
|
|
display: none;
|
|
}
|
|
|
|
.medical-consult-page--workbench .hero-tools {
|
|
width: auto;
|
|
max-width: none;
|
|
margin: 0;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.medical-consult-page--workbench .hero-search {
|
|
width: min(460px, 44vw);
|
|
}
|
|
|
|
.medical-consult-page--workbench .hero-search :deep(.el-input__wrapper) {
|
|
height: 34px;
|
|
padding: 0 12px;
|
|
border: 0;
|
|
border-radius: 8px;
|
|
background: rgba(255, 255, 255, 0.92);
|
|
box-shadow:
|
|
0 8px 20px rgba(48, 92, 150, 0.08),
|
|
0 0 0 1px rgba(93, 146, 214, 0.24) inset;
|
|
transition: border-color 0.2s, box-shadow 0.2s;
|
|
}
|
|
|
|
.medical-consult-page--workbench .hero-search :deep(.el-input__inner) {
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.medical-consult-page--workbench .hero-search :deep(.el-input__prefix) {
|
|
color: #6d93bc;
|
|
font-size: 15px;
|
|
}
|
|
|
|
.medical-consult-page--workbench .new-consult-btn {
|
|
height: 34px;
|
|
padding: 0 16px;
|
|
border-radius: 8px;
|
|
background: linear-gradient(135deg, #2f7be8 0%, #2560bd 100%);
|
|
border: none;
|
|
box-shadow: 0 10px 18px rgba(47, 123, 232, 0.24);
|
|
font-size: 13px;
|
|
font-weight: 800;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.medical-consult-page--workbench .new-consult-btn:hover,
|
|
.medical-consult-page--workbench .new-consult-btn:focus {
|
|
background: linear-gradient(135deg, #3d6bbf 0%, #254d90 100%);
|
|
box-shadow: 0 4px 14px rgba(79, 126, 207, 0.4);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.medical-consult-page--workbench .faq-workspace {
|
|
display: grid;
|
|
grid-template-columns: 236px minmax(0, 1fr);
|
|
flex: 1 1 auto;
|
|
height: 0;
|
|
min-height: 0;
|
|
margin-top: 0;
|
|
border: 1px solid rgba(130, 158, 190, 0.22);
|
|
border-radius: 0;
|
|
background: #ffffff;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.medical-consult-page--workbench .faq-sidebar-col,
|
|
.medical-consult-page--workbench .faq-content-col {
|
|
width: auto;
|
|
max-width: none !important;
|
|
flex: initial !important;
|
|
}
|
|
|
|
.medical-consult-page--workbench .faq-sidebar-col {
|
|
min-height: 0;
|
|
border-right: 1px solid rgba(134, 163, 194, 0.22);
|
|
background:
|
|
radial-gradient(circle at 18% 0%, rgba(47, 123, 232, 0.13) 0%, transparent 34%),
|
|
linear-gradient(180deg, #e9f4ff 0%, #f6faff 44%, #eaf1fa 100%);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.medical-consult-page--workbench .faq-content-col {
|
|
display: flex;
|
|
min-height: 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.medical-consult-page--workbench .faq-main {
|
|
display: flex;
|
|
width: 100%;
|
|
height: 100%;
|
|
min-height: 0;
|
|
flex: 1;
|
|
flex-direction: column;
|
|
border: 0 !important;
|
|
padding: 0;
|
|
overflow: hidden;
|
|
background: #ffffff;
|
|
}
|
|
|
|
.medical-consult-page--workbench .pagination-wrap {
|
|
flex: 0 0 auto;
|
|
padding: 10px 14px;
|
|
border-top: 1px solid #edf3fa;
|
|
background: linear-gradient(180deg, #ffffff 0%, #fbfdff 100%);
|
|
}
|
|
|
|
.medical-consult-page--workbench :deep(.faq-category-panel) {
|
|
height: 100%;
|
|
padding: 12px 10px;
|
|
}
|
|
|
|
.medical-consult-page--workbench :deep(.faq-category-panel .header) {
|
|
padding: 10px 10px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.medical-consult-page--workbench :deep(.faq-category-panel .menu) {
|
|
flex: 1 1 auto;
|
|
max-height: none;
|
|
min-height: 0;
|
|
padding-top: 10px;
|
|
}
|
|
|
|
.medical-consult-page--workbench :deep(.faq-category-panel .menu .el-menu-item) {
|
|
height: 36px;
|
|
margin: 4px 0;
|
|
border-radius: 8px;
|
|
font-size: 13px;
|
|
line-height: 36px;
|
|
}
|
|
|
|
.medical-consult-page--workbench :deep(.faq-category-panel .cat-icon) {
|
|
width: 22px;
|
|
height: 22px;
|
|
border-radius: 7px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
:global([data-ctms-theme="dark"] .medical-consult-page--workbench .faq-hero),
|
|
:global([data-ctms-theme="dark"] .medical-consult-page--workbench .faq-workspace) {
|
|
border-color: #26364a;
|
|
background:
|
|
radial-gradient(circle at 0% 0%, rgba(59, 130, 246, 0.2) 0%, transparent 34%),
|
|
linear-gradient(135deg, #172033 0%, #111827 100%) !important;
|
|
}
|
|
|
|
:global([data-ctms-theme="dark"] .medical-consult-page--workbench .faq-sidebar-col) {
|
|
border-color: #26364a;
|
|
background: #111827;
|
|
}
|
|
|
|
:global([data-ctms-theme="dark"] .medical-consult-page--workbench .workbench-toolbar-meta span) {
|
|
color: #e5edf7;
|
|
}
|
|
|
|
@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;
|
|
}
|
|
|
|
.medical-consult-page--workbench .faq-workspace {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.medical-consult-page--workbench .faq-sidebar-col {
|
|
border-right: 0;
|
|
border-bottom: 1px solid #d9e2ec;
|
|
}
|
|
|
|
.medical-consult-page--workbench .faq-main {
|
|
padding: 0;
|
|
}
|
|
}
|
|
</style>
|