199 lines
4.8 KiB
Vue
199 lines
4.8 KiB
Vue
<template>
|
|
<aside class="panel unified-shell faq-category-panel">
|
|
<div class="header">
|
|
<span>{{ TEXT.common.labels.category }}</span>
|
|
<div v-if="canManageCategories">
|
|
<el-button type="primary" size="small" class="category-create-btn" @click="openManager()">编辑</el-button>
|
|
</div>
|
|
</div>
|
|
<el-menu :default-active="activeCategory" @select="onSelect" class="menu">
|
|
<el-menu-item index="">
|
|
<span class="cat-icon"><FaqIcon icon="fa-th-large" /></span>
|
|
<span class="name">{{ TEXT.common.labels.all }}</span>
|
|
<span class="cat-count">{{ totalFaqCount }}</span>
|
|
</el-menu-item>
|
|
<el-menu-item v-for="c in sortedCategories" :key="c.id" :index="c.id">
|
|
<span class="cat-icon">
|
|
<FaqIcon v-if="c.icon" :icon="c.icon" />
|
|
<span v-else>{{ c.name.slice(0, 1) }}</span>
|
|
</span>
|
|
<span class="name" :title="c.name">{{ c.name }}</span>
|
|
<span class="cat-count">{{ categoryCounts[c.id] || 0 }}</span>
|
|
</el-menu-item>
|
|
</el-menu>
|
|
<FaqCategoryManager
|
|
v-model="showForm"
|
|
:categories="props.categories"
|
|
:faqs="props.faqs"
|
|
:can-create="canCreateCategory"
|
|
:can-update="canUpdateCategory"
|
|
:can-delete="canDeleteCategory"
|
|
@success="emit('refresh')"
|
|
/>
|
|
</aside>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, ref } from "vue";
|
|
import FaqIcon from "./FaqIcon.vue";
|
|
import FaqCategoryManager from "./FaqCategoryManager.vue";
|
|
import type { FaqCategory } from "../api/faqs";
|
|
import { usePermission } from "../utils/permission";
|
|
import { TEXT } from "../locales";
|
|
|
|
const props = defineProps<{
|
|
categories: FaqCategory[];
|
|
faqs?: any[];
|
|
modelValue: string;
|
|
}>();
|
|
const emit = defineEmits(["update:modelValue", "refresh"]);
|
|
|
|
const showForm = ref(false);
|
|
|
|
const { can } = usePermission();
|
|
const canCreateCategory = computed(() => can("faq.category.create"));
|
|
const canUpdateCategory = computed(() => can("faq.category.update"));
|
|
const canDeleteCategory = computed(() => can("faq.category.delete"));
|
|
const canManageCategories = computed(() => canCreateCategory.value || canUpdateCategory.value || canDeleteCategory.value);
|
|
const sortedCategories = computed(() =>
|
|
[...props.categories].sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0))
|
|
);
|
|
|
|
const activeCategory = computed(() => props.modelValue || "");
|
|
|
|
const categoryCounts = computed(() => {
|
|
const map: Record<string, number> = {};
|
|
(props.faqs || []).forEach((f: any) => {
|
|
if (f.category_id) {
|
|
map[f.category_id] = (map[f.category_id] || 0) + 1;
|
|
}
|
|
});
|
|
return map;
|
|
});
|
|
|
|
const totalFaqCount = computed(() => (props.faqs || []).length);
|
|
|
|
const onSelect = (id: string) => {
|
|
if (id && id === activeCategory.value) {
|
|
openManager();
|
|
return;
|
|
}
|
|
emit("update:modelValue", id);
|
|
};
|
|
|
|
const openManager = () => {
|
|
showForm.value = true;
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.panel {
|
|
border: 0;
|
|
border-radius: 0;
|
|
box-shadow: none;
|
|
overflow: hidden;
|
|
padding: 24px 14px;
|
|
background: transparent;
|
|
}
|
|
|
|
.header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 0 8px 16px;
|
|
border-bottom: 1px solid #edf2f8;
|
|
color: #8b98aa;
|
|
font-size: 14px;
|
|
font-weight: 800;
|
|
}
|
|
|
|
.menu {
|
|
max-height: calc(100vh - 240px);
|
|
overflow: auto;
|
|
border-right: 0;
|
|
padding: 14px 0 0;
|
|
background: transparent;
|
|
}
|
|
.menu :deep(.el-menu-item) {
|
|
height: 44px;
|
|
margin: 4px 0;
|
|
padding: 0 10px !important;
|
|
border-radius: 10px;
|
|
color: #5f6f7a;
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
line-height: 44px;
|
|
}
|
|
.menu :deep(.el-menu-item:hover) {
|
|
background: rgba(31, 95, 184, 0.06);
|
|
color: #1f5fb8;
|
|
}
|
|
.menu :deep(.el-menu-item.is-active) {
|
|
background: #e7f4ff;
|
|
color: #1f5fb8;
|
|
}
|
|
.cat-icon {
|
|
display: inline-flex;
|
|
flex: 0 0 auto;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 24px;
|
|
height: 24px;
|
|
margin-right: 10px;
|
|
border-radius: 7px;
|
|
background: rgba(95, 111, 122, 0.08);
|
|
color: #697982;
|
|
font-size: 13px;
|
|
font-weight: 800;
|
|
}
|
|
.cat-icon :deep(svg) {
|
|
font-size: 13px;
|
|
width: 1em;
|
|
height: 1em;
|
|
}
|
|
.menu :deep(.el-menu-item.is-active) .cat-icon {
|
|
background: #d7ebff;
|
|
color: #1f5fb8;
|
|
}
|
|
.category-create-btn {
|
|
height: 34px;
|
|
border: 0;
|
|
border-radius: 10px;
|
|
background: #415a77;
|
|
font-weight: 800;
|
|
box-shadow: 0 8px 18px rgba(65, 90, 119, 0.16);
|
|
}
|
|
.category-create-btn:hover,
|
|
.category-create-btn:focus {
|
|
background: #344960;
|
|
}
|
|
.name {
|
|
flex: 1;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.cat-count {
|
|
flex-shrink: 0;
|
|
margin-left: 6px;
|
|
min-width: 20px;
|
|
height: 18px;
|
|
padding: 0 6px;
|
|
border-radius: 999px;
|
|
background: rgba(95, 111, 122, 0.08);
|
|
color: #697982;
|
|
font-size: 10px;
|
|
font-weight: 700;
|
|
line-height: 18px;
|
|
text-align: center;
|
|
}
|
|
|
|
.menu :deep(.el-menu-item.is-active) .cat-count {
|
|
background: #d7ebff;
|
|
color: #1f5fb8;
|
|
}
|
|
</style>
|