mirror of
https://github.com/ccmjga/zhilu-admin
synced 2026-03-13 21:27:19 +08:00
新增知识库相关组件,包括文档卡片、知识库卡片、状态徽章和分段卡片,优化日期格式化工具函数,更新文档管理和知识库管理页面以使用新组件。
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div class="bg-white border border-gray-200 rounded-lg shadow-sm hover:shadow-md transition-shadow">
|
||||
<div class="p-4">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h5 class="text-xl font-semibold tracking-tight text-gray-900 mb-1 truncate">{{ doc.name }}</h5>
|
||||
<div class="flex items-center mb-2">
|
||||
<KnowledgeStatusBadge :status="doc.status" type="status" class="mr-2" />
|
||||
<KnowledgeStatusBadge :enabled="doc.enable" type="enabled" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex space-x-2">
|
||||
<slot name="toggle-switch"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-xs text-gray-500">
|
||||
上传时间: {{ formatDateString(doc.createTime) }}
|
||||
</span>
|
||||
<div class="flex space-x-2">
|
||||
<slot name="actions"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { KnowledgeStatusBadge } from '@/components/common/knowledge';
|
||||
import type { LibraryDoc } from "@/types/KnowledgeTypes";
|
||||
import { formatDateString } from '@/utils/dateUtil';
|
||||
|
||||
const props = defineProps<{
|
||||
doc: LibraryDoc;
|
||||
}>();
|
||||
</script>
|
||||
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div class="bg-white border border-gray-200 rounded-lg shadow-sm hover:shadow-md transition-shadow">
|
||||
<div class="p-4">
|
||||
<div class="flex justify-between items-start">
|
||||
<h5 class="text-xl font-semibold tracking-tight text-gray-900 mb-1 truncate">{{ library.name }}</h5>
|
||||
<div class="flex space-x-2">
|
||||
<slot name="actions-top"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 mb-3 line-clamp-2">
|
||||
{{ library.description || '暂无描述' }}
|
||||
</p>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-xs text-gray-500">
|
||||
创建时间: {{ formatDateString(library.createTime) }}
|
||||
</span>
|
||||
<slot name="actions-bottom"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Library } from "@/types/KnowledgeTypes";
|
||||
import { formatDateString } from '@/utils/dateUtil';
|
||||
|
||||
const props = defineProps<{
|
||||
library: Library;
|
||||
}>();
|
||||
</script>
|
||||
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<span :class="`inline-flex items-center px-2 py-1 text-xs font-medium rounded-full ${
|
||||
getStatusClass()
|
||||
}`">
|
||||
{{ getStatusText() }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DocStatus } from "@/types/KnowledgeTypes";
|
||||
|
||||
const props = defineProps<{
|
||||
status?: string;
|
||||
enabled?: boolean;
|
||||
type: 'status' | 'enabled';
|
||||
}>();
|
||||
|
||||
const getStatusClass = () => {
|
||||
if (props.type === 'status') {
|
||||
return props.status === DocStatus.SUCCESS
|
||||
? 'bg-green-100 text-green-800'
|
||||
: 'bg-yellow-100 text-yellow-800';
|
||||
}
|
||||
|
||||
return props.enabled
|
||||
? 'bg-blue-100 text-blue-800'
|
||||
: 'bg-gray-100 text-gray-800';
|
||||
};
|
||||
|
||||
const getStatusText = () => {
|
||||
if (props.type === 'status') {
|
||||
return props.status === DocStatus.SUCCESS ? '解析完成' : '解析中';
|
||||
}
|
||||
|
||||
return props.enabled ? '已启用' : '已禁用';
|
||||
};
|
||||
</script>
|
||||
34
frontend/src/components/common/knowledge/SegmentCard.vue
Normal file
34
frontend/src/components/common/knowledge/SegmentCard.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div class="bg-white border border-gray-200 rounded-lg shadow-sm p-4">
|
||||
<div class="flex justify-between items-start mb-2">
|
||||
<h5 class="text-lg font-semibold text-gray-900">分段 #{{ index + 1 }}</h5>
|
||||
<div class="text-xs text-gray-500">
|
||||
ID: {{ segment.id }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-sm text-gray-500 mb-2">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="inline-flex items-center px-2 py-1 text-xs font-medium rounded-full bg-blue-100 text-blue-800">
|
||||
Embedding ID: {{ segment.embeddingId || '无' }}
|
||||
</span>
|
||||
<span class="inline-flex items-center px-2 py-1 text-xs font-medium rounded-full bg-green-100 text-green-800">
|
||||
Token 使用量: {{ segment.tokenUsage || 0 }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-200 pt-3 mt-3">
|
||||
<h6 class="text-sm font-medium text-gray-900 mb-2">内容:</h6>
|
||||
<pre
|
||||
class="text-sm text-gray-700 whitespace-pre-wrap bg-gray-50 p-3 rounded-lg max-h-60 overflow-y-auto">{{ segment.content }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { LibraryDocSegment } from "@/types/KnowledgeTypes";
|
||||
|
||||
const props = defineProps<{
|
||||
segment: LibraryDocSegment;
|
||||
index: number;
|
||||
}>();
|
||||
</script>
|
||||
11
frontend/src/components/common/knowledge/index.ts
Normal file
11
frontend/src/components/common/knowledge/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import KnowledgeDocCard from "./KnowledgeDocCard.vue";
|
||||
import KnowledgeLibraryCard from "./KnowledgeLibraryCard.vue";
|
||||
import KnowledgeStatusBadge from "./KnowledgeStatusBadge.vue";
|
||||
import SegmentCard from "./SegmentCard.vue";
|
||||
|
||||
export {
|
||||
KnowledgeStatusBadge,
|
||||
KnowledgeDocCard,
|
||||
KnowledgeLibraryCard,
|
||||
SegmentCard,
|
||||
};
|
||||
@@ -13,4 +13,9 @@ const formatDate = (date?: Date) => {
|
||||
return dayjs(date).format("YYYY-MM-DDTHH:mm:ss.SSSZ");
|
||||
};
|
||||
|
||||
export { dayjs, formatDate };
|
||||
const formatDateString = (dateString?: string, format = "YYYY-MM-DD HH:mm") => {
|
||||
if (!dateString) return "未知";
|
||||
return dayjs(dateString).format(format);
|
||||
};
|
||||
|
||||
export { dayjs, formatDate, formatDateString };
|
||||
|
||||
@@ -5,63 +5,36 @@
|
||||
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl">{{ currentLibrary?.name || '知识库' }} - 文档管理</h1>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<div v-for="doc in docs" :key="doc.id"
|
||||
class="bg-white border border-gray-200 rounded-lg shadow-sm hover:shadow-md transition-shadow">
|
||||
<div class="p-4">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h5 class="text-xl font-semibold tracking-tight text-gray-900 mb-1 truncate">{{ doc.name }}</h5>
|
||||
<div class="flex items-center mb-2">
|
||||
<span :class="`inline-flex items-center px-2 py-1 text-xs font-medium rounded-full ${
|
||||
doc.status === DocStatus.SUCCESS ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800'
|
||||
} mr-2`">
|
||||
{{ doc.status === DocStatus.SUCCESS ? '解析完成' : '解析中' }}
|
||||
</span>
|
||||
<span :class="`inline-flex items-center px-2 py-1 text-xs font-medium rounded-full ${
|
||||
doc.enable ? 'bg-blue-100 text-blue-800' : 'bg-gray-100 text-gray-800'
|
||||
}`">
|
||||
{{ doc.enable ? '已启用' : '已禁用' }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- 文档列表 -->
|
||||
<div v-if="docs.length > 0" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<KnowledgeDocCard v-for="doc in docs" :key="doc.id" :doc="doc">
|
||||
<template #toggle-switch>
|
||||
<label class="inline-flex items-center mb-5"
|
||||
:class="!doc.enable ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'">
|
||||
<input type="checkbox" class="sr-only peer" :checked="doc.enable" @change="handleToggleDocStatus(doc)">
|
||||
<div
|
||||
class="relative w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:w-5 after:h-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600">
|
||||
</div>
|
||||
<div class="flex space-x-2">
|
||||
<label class="inline-flex items-center mb-5"
|
||||
:class="!doc.enable ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'">
|
||||
<input type="checkbox" class="sr-only peer" :checked="doc.enable" @change="handleToggleDocStatus(doc)">
|
||||
<div
|
||||
class="relative w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:w-5 after:h-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600">
|
||||
</div>
|
||||
</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="text-sm text-gray-600 mb-3">
|
||||
<div class="truncate">{{ doc.path || '无' }}</div>
|
||||
</div> -->
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-xs text-gray-500">
|
||||
上传时间: {{ formatDate(doc.createTime) }}
|
||||
</span>
|
||||
<div class="flex space-x-2">
|
||||
<button @click="navigateToDocSegments(doc)" :class="
|
||||
['text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-xs px-3 py-content',
|
||||
doc.status !== DocStatus.SUCCESS ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer']"
|
||||
:disabled="doc.status !== DocStatus.SUCCESS">
|
||||
查看内容
|
||||
</button>
|
||||
<button @click="handleDeleteDoc(doc)"
|
||||
class="text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-xs px-3 py-1.5">
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</template>
|
||||
<template #actions>
|
||||
<button @click="navigateToDocSegments(doc)" :class="
|
||||
['text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-xs px-3 py-content',
|
||||
doc.status !== DocStatus.SUCCESS ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer']"
|
||||
:disabled="doc.status !== DocStatus.SUCCESS">
|
||||
查看内容
|
||||
</button>
|
||||
<button @click="handleDeleteDoc(doc)"
|
||||
class="text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-xs px-3 py-1.5">
|
||||
删除
|
||||
</button>
|
||||
</template>
|
||||
</KnowledgeDocCard>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center py-10">
|
||||
<div v-if="docs.length === 0" class="text-gray-500 text-lg mb-4">暂无文档</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else class="flex flex-col items-center justify-center py-10">
|
||||
<div class="text-gray-500 text-lg mb-4">暂无文档</div>
|
||||
<div>
|
||||
<input ref="fileInputRef" class="hidden" id="doc_file_input" type="file" @change="handleFileChange">
|
||||
<TableButton variant="primary" size="md" @click="triggerFileInput">
|
||||
@@ -84,11 +57,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import dayjs from "dayjs";
|
||||
import { Modal, type ModalInterface, initFlowbite } from "flowbite";
|
||||
import { onMounted, ref, watchEffect } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
|
||||
import { KnowledgeDocCard, KnowledgeStatusBadge } from "@/components/common/knowledge";
|
||||
import { PlusIcon } from "@/components/icons";
|
||||
import Breadcrumbs from "@/components/layout/Breadcrumbs.vue";
|
||||
import ConfirmationDialog from "@/components/modals/ConfirmationDialog.vue";
|
||||
@@ -98,6 +71,7 @@ import { useKnowledgeQuery } from "@/composables/knowledge/useKnowledgeQuery";
|
||||
import { useKnowledgeUpsert } from "@/composables/knowledge/useKnowledgeUpsert";
|
||||
import useAlertStore from "@/composables/store/useAlertStore";
|
||||
import { Routes } from "@/router/constants";
|
||||
import { formatDateString } from '@/utils/dateUtil';
|
||||
|
||||
import type { Library, LibraryDoc } from "@/types/KnowledgeTypes";
|
||||
import { DocStatus } from "@/types/KnowledgeTypes";
|
||||
@@ -129,12 +103,6 @@ const selectedDoc = ref<LibraryDoc | undefined>();
|
||||
// 提示store
|
||||
const alertStore = useAlertStore();
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateString?: string) => {
|
||||
if (!dateString) return "未知";
|
||||
return dayjs(dateString).format("YYYY-MM-DD HH:mm");
|
||||
};
|
||||
|
||||
// 触发文件选择
|
||||
const triggerFileInput = () => {
|
||||
fileInputRef.value?.click();
|
||||
|
||||
@@ -5,48 +5,38 @@
|
||||
<h1 class="text-2xl font-semibold text-gray-900">知识库管理</h1>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<div v-for="library in libraries" :key="library.id"
|
||||
class="bg-white border border-gray-200 rounded-lg shadow-sm hover:shadow-md transition-shadow">
|
||||
<div class="p-4">
|
||||
<div class="flex justify-between items-start">
|
||||
<h5 class="text-xl font-semibold tracking-tight text-gray-900 mb-1 truncate">{{ library.name }}</h5>
|
||||
<div class="flex space-x-2">
|
||||
<button @click="handleEditLibrary(library)" class="text-gray-500 hover:text-blue-700 focus:outline-none">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z">
|
||||
</path>
|
||||
</svg>
|
||||
</button>
|
||||
<button @click="handleDeleteLibrary(library)" class="text-gray-500 hover:text-red-700 focus:outline-none">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16">
|
||||
</path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 mb-3 line-clamp-2">
|
||||
{{ library.description || '暂无描述' }}
|
||||
</p>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-xs text-gray-500">
|
||||
创建时间: {{ formatDate(library.createTime) }}
|
||||
</span>
|
||||
<button @click="navigateToLibraryDocs(library)"
|
||||
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-xs px-3 py-1.5">
|
||||
查看知识库
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 知识库列表 -->
|
||||
<div v-if="libraries.length > 0" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<KnowledgeLibraryCard v-for="library in libraries" :key="library.id" :library="library">
|
||||
<template #actions-top>
|
||||
<button @click="handleEditLibrary(library)" class="text-gray-500 hover:text-blue-700 focus:outline-none">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z">
|
||||
</path>
|
||||
</svg>
|
||||
</button>
|
||||
<button @click="handleDeleteLibrary(library)" class="text-gray-500 hover:text-red-700 focus:outline-none">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16">
|
||||
</path>
|
||||
</svg>
|
||||
</button>
|
||||
</template>
|
||||
<template #actions-bottom>
|
||||
<button @click="navigateToLibraryDocs(library)"
|
||||
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-xs px-3 py-1.5">
|
||||
查看知识库
|
||||
</button>
|
||||
</template>
|
||||
</KnowledgeLibraryCard>
|
||||
</div>
|
||||
|
||||
<div v-if="libraries.length === 0" class="flex flex-col items-center justify-center py-10">
|
||||
<!-- 空状态 -->
|
||||
<div v-else class="flex flex-col items-center justify-center py-10">
|
||||
<div class="text-gray-500 text-lg mb-4">暂无知识库</div>
|
||||
<div>
|
||||
<button @click="handleCreateLibraryClick"
|
||||
@@ -75,6 +65,7 @@ import { Modal, type ModalInterface, initFlowbite } from "flowbite";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
import { KnowledgeLibraryCard } from "@/components/common/knowledge";
|
||||
import Breadcrumbs from "@/components/layout/Breadcrumbs.vue";
|
||||
import ConfirmationDialog from "@/components/modals/ConfirmationDialog.vue";
|
||||
import LibraryFormDialog from "@/components/modals/LibraryFormDialog.vue";
|
||||
|
||||
@@ -9,36 +9,14 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 分段列表 -->
|
||||
<!-- 空状态 -->
|
||||
<div v-if="segments.length === 0" class="flex flex-col items-center justify-center py-10">
|
||||
<div class="text-gray-500 text-lg">暂无分段内容</div>
|
||||
</div>
|
||||
|
||||
<!-- 分段列表 -->
|
||||
<div v-else class="space-y-4">
|
||||
<div v-for="(segment, index) in segments" :key="segment.id"
|
||||
class="bg-white border border-gray-200 rounded-lg shadow-sm p-4">
|
||||
<div class="flex justify-between items-start mb-2">
|
||||
<h5 class="text-lg font-semibold text-gray-900">分段 #{{ index + 1 }}</h5>
|
||||
<div class="text-xs text-gray-500">
|
||||
ID: {{ segment.id }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-sm text-gray-500 mb-2">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="inline-flex items-center px-2 py-1 text-xs font-medium rounded-full bg-blue-100 text-blue-800">
|
||||
Embedding ID: {{ segment.embeddingId || '无' }}
|
||||
</span>
|
||||
<span
|
||||
class="inline-flex items-center px-2 py-1 text-xs font-medium rounded-full bg-green-100 text-green-800">
|
||||
Token 使用量: {{ segment.tokenUsage || 0 }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-200 pt-3 mt-3">
|
||||
<h6 class="text-sm font-medium text-gray-900 mb-2">内容:</h6>
|
||||
<pre
|
||||
class="text-sm text-gray-700 whitespace-pre-wrap bg-gray-50 p-3 rounded-lg max-h-60 overflow-y-auto">{{ segment.content }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<SegmentCard v-for="(segment, index) in segments" :key="segment.id" :segment="segment" :index="index" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -47,6 +25,7 @@
|
||||
import { onMounted, ref, watchEffect } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
import { SegmentCard } from "@/components/common/knowledge";
|
||||
import Breadcrumbs from "@/components/layout/Breadcrumbs.vue";
|
||||
import { useKnowledgeQuery } from "@/composables/knowledge/useKnowledgeQuery";
|
||||
import { Routes } from "@/router/constants";
|
||||
|
||||
Reference in New Issue
Block a user