- 将KbDocumentVO的fileSize类型由数字改为字符串,并新增fileUrl字段 - 引入vue-pdf-embed组件实现PDF预览 - 新增预览相关响应式状态变量及控制方法 - 支持PDF分页显示、全部页显示、旋转和打印功能 - 支持文本和Markdown文件通过iframe预览 - 对不支持直接预览的文件类型显示提示并提供打开下载链接 - 在操作栏新增“预览”按钮,符合文档状态才显示 - 添加预览对话框及配套样式,提升用户体验
This commit is contained in:
@@ -54,8 +54,9 @@ export interface KbDocumentVO {
|
|||||||
title: string;
|
title: string;
|
||||||
docType: string; // report/document/text/data/other
|
docType: string; // report/document/text/data/other
|
||||||
fileType: string; // pdf/doc/txt/md等
|
fileType: string; // pdf/doc/txt/md等
|
||||||
fileSize: number; // 字节
|
fileSize: string; // 字节
|
||||||
filePath: string;
|
filePath: string;
|
||||||
|
fileUrl?: string; // 文件访问URL
|
||||||
sourceType: string; // upload/project/risk等
|
sourceType: string; // upload/project/risk等
|
||||||
chunkCount: number; // 分块数量
|
chunkCount: number; // 分块数量
|
||||||
status: "pending" | "processing" | "active" | "error";
|
status: "pending" | "processing" | "active" | "error";
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { ref, onMounted, watch } from "vue";
|
import { ref, onMounted, watch } from "vue";
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||||
|
import VuePdfEmbed from "vue-pdf-embed";
|
||||||
import {
|
import {
|
||||||
getDocuments,
|
getDocuments,
|
||||||
uploadDocument,
|
uploadDocument,
|
||||||
@@ -25,6 +26,7 @@ import DocIcon from "~icons/ri/file-word-line";
|
|||||||
import TxtIcon from "~icons/ri/file-text-line";
|
import TxtIcon from "~icons/ri/file-text-line";
|
||||||
import ViewIcon from "~icons/ri/eye-line";
|
import ViewIcon from "~icons/ri/eye-line";
|
||||||
import ChunkIcon from "~icons/ri/file-list-3-line";
|
import ChunkIcon from "~icons/ri/file-list-3-line";
|
||||||
|
import PreviewIcon from "~icons/ri/file-search-line";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "KnowledgeBase"
|
name: "KnowledgeBase"
|
||||||
@@ -51,6 +53,17 @@ const currentChunkDoc = ref<KbDocumentVO | null>(null);
|
|||||||
const selectedChunk = ref<DocumentChunkVO | null>(null);
|
const selectedChunk = ref<DocumentChunkVO | null>(null);
|
||||||
const chunkDetailVisible = ref(false);
|
const chunkDetailVisible = ref(false);
|
||||||
|
|
||||||
|
// 预览相关
|
||||||
|
const previewDialogVisible = ref(false);
|
||||||
|
const previewDoc = ref<KbDocumentVO | null>(null);
|
||||||
|
const previewLoading = ref(false);
|
||||||
|
const previewPageCount = ref(1);
|
||||||
|
const previewCurrentPage = ref(1);
|
||||||
|
const previewRotation = ref(0);
|
||||||
|
const previewPdfRef = ref<any>(null);
|
||||||
|
const previewAllPages = ref(false);
|
||||||
|
const previewRotations = [0, 90, 180, 270];
|
||||||
|
|
||||||
// 加载项目列表
|
// 加载项目列表
|
||||||
async function loadProjects() {
|
async function loadProjects() {
|
||||||
projectLoading.value = true;
|
projectLoading.value = true;
|
||||||
@@ -234,6 +247,62 @@ function handleViewChunkDetail(chunk: DocumentChunkVO) {
|
|||||||
chunkDetailVisible.value = true;
|
chunkDetailVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 预览文档
|
||||||
|
function handlePreview(doc: KbDocumentVO) {
|
||||||
|
previewDoc.value = doc;
|
||||||
|
previewDialogVisible.value = true;
|
||||||
|
previewLoading.value = true;
|
||||||
|
previewCurrentPage.value = 1;
|
||||||
|
previewRotation.value = 0;
|
||||||
|
previewAllPages.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PDF渲染完成
|
||||||
|
function handlePdfRender() {
|
||||||
|
previewLoading.value = false;
|
||||||
|
if (previewPdfRef.value?.doc) {
|
||||||
|
previewPageCount.value = previewPdfRef.value.doc.numPages;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换显示所有页面
|
||||||
|
function handlePreviewAllPagesChange() {
|
||||||
|
previewCurrentPage.value = previewAllPages.value ? null : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 旋转PDF
|
||||||
|
function handleRotatePdf() {
|
||||||
|
previewRotation.value =
|
||||||
|
previewRotation.value === 3 ? 0 : previewRotation.value + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打印PDF
|
||||||
|
function handlePrintPdf() {
|
||||||
|
previewPdfRef.value?.print();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开文件链接
|
||||||
|
function handleOpenFile(url: string) {
|
||||||
|
window.open(url, "_blank");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否可预览
|
||||||
|
function canPreview(doc: KbDocumentVO): boolean {
|
||||||
|
return !!doc.fileUrl && doc.status === "active";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取文件类型显示名
|
||||||
|
function getFileTypeName(fileType: string): string {
|
||||||
|
const typeMap: Record<string, string> = {
|
||||||
|
pdf: "PDF",
|
||||||
|
doc: "Word",
|
||||||
|
docx: "Word",
|
||||||
|
txt: "文本",
|
||||||
|
md: "Markdown"
|
||||||
|
};
|
||||||
|
return typeMap[fileType.toLowerCase()] || fileType.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
// 获取状态标签类型
|
// 获取状态标签类型
|
||||||
function getStatusType(
|
function getStatusType(
|
||||||
status: DocumentStatus
|
status: DocumentStatus
|
||||||
@@ -402,8 +471,17 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column label="操作" width="240" align="center" fixed="right">
|
<el-table-column label="操作" width="300" align="center" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
|
<el-button
|
||||||
|
v-if="canPreview(row)"
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="handlePreview(row)"
|
||||||
|
>
|
||||||
|
<component :is="useRenderIcon(PreviewIcon)" class="mr-1" />
|
||||||
|
预览
|
||||||
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="row.status === 'active'"
|
v-if="row.status === 'active'"
|
||||||
link
|
link
|
||||||
@@ -579,6 +657,109 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 文档预览对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="previewDialogVisible"
|
||||||
|
:title="`预览 - ${previewDoc?.title || ''}`"
|
||||||
|
width="900px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
destroy-on-close
|
||||||
|
>
|
||||||
|
<template v-if="previewDoc">
|
||||||
|
<!-- PDF预览 -->
|
||||||
|
<template v-if="previewDoc.fileType?.toLowerCase() === 'pdf'">
|
||||||
|
<div
|
||||||
|
v-loading="previewLoading"
|
||||||
|
class="pdf-preview-container"
|
||||||
|
element-loading-text="加载中..."
|
||||||
|
>
|
||||||
|
<div class="pdf-toolbar">
|
||||||
|
<div v-if="previewAllPages" class="page-info">
|
||||||
|
共 {{ previewPageCount }} 页
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="previewCurrentPage"
|
||||||
|
background
|
||||||
|
layout="prev, slot, next"
|
||||||
|
:page-size="1"
|
||||||
|
:total="previewPageCount"
|
||||||
|
>
|
||||||
|
{{ previewCurrentPage }} / {{ previewPageCount }}
|
||||||
|
</el-pagination>
|
||||||
|
</div>
|
||||||
|
<div class="pdf-actions">
|
||||||
|
<el-checkbox
|
||||||
|
v-model="previewAllPages"
|
||||||
|
@change="handlePreviewAllPagesChange"
|
||||||
|
>
|
||||||
|
显示所有页面
|
||||||
|
</el-checkbox>
|
||||||
|
<el-button link @click="handleRotatePdf">
|
||||||
|
<component
|
||||||
|
:is="useRenderIcon('ri/anticlockwise-line')"
|
||||||
|
:size="18"
|
||||||
|
/>
|
||||||
|
旋转
|
||||||
|
</el-button>
|
||||||
|
<el-button link @click="handlePrintPdf">
|
||||||
|
<component
|
||||||
|
:is="useRenderIcon('ri/printer-line')"
|
||||||
|
:size="18"
|
||||||
|
/>
|
||||||
|
打印
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-scrollbar class="pdf-scrollbar">
|
||||||
|
<vue-pdf-embed
|
||||||
|
ref="previewPdfRef"
|
||||||
|
:rotation="previewRotations[previewRotation]"
|
||||||
|
:page="previewCurrentPage"
|
||||||
|
:source="previewDoc.fileUrl"
|
||||||
|
class="pdf-viewer"
|
||||||
|
@rendered="handlePdfRender"
|
||||||
|
/>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 文本/Markdown预览 -->
|
||||||
|
<template
|
||||||
|
v-else-if="['txt', 'md'].includes(previewDoc.fileType?.toLowerCase())"
|
||||||
|
>
|
||||||
|
<div class="text-preview-container">
|
||||||
|
<iframe :src="previewDoc.fileUrl" class="text-preview-iframe" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Word/其他文件 - 使用iframe尝试预览 -->
|
||||||
|
<template v-else>
|
||||||
|
<div class="other-preview-container">
|
||||||
|
<div class="preview-tip">
|
||||||
|
<component
|
||||||
|
:is="useRenderIcon('ri/file-info-line')"
|
||||||
|
:size="48"
|
||||||
|
class="tip-icon"
|
||||||
|
/>
|
||||||
|
<p>{{ getFileTypeName(previewDoc.fileType) }} 文件预览</p>
|
||||||
|
<p class="tip-desc">此文件类型不支持直接预览,您可以下载后查看</p>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="handleOpenFile(previewDoc.fileUrl)"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="useRenderIcon('ri/download-line')"
|
||||||
|
class="mr-1"
|
||||||
|
/>
|
||||||
|
打开文件
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -658,4 +839,78 @@ onMounted(() => {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PDF预览样式
|
||||||
|
.pdf-preview-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 70vh;
|
||||||
|
|
||||||
|
.pdf-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 8px 0;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||||
|
|
||||||
|
.page-info {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-scrollbar {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-viewer {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文本预览样式
|
||||||
|
.text-preview-container {
|
||||||
|
height: 70vh;
|
||||||
|
|
||||||
|
.text-preview-iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他文件预览样式
|
||||||
|
.other-preview-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 50vh;
|
||||||
|
|
||||||
|
.preview-tip {
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.tip-icon {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 8px 0;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-desc {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user