- 将KbDocumentVO的fileSize类型由数字改为字符串,并新增fileUrl字段 - 引入vue-pdf-embed组件实现PDF预览 - 新增预览相关响应式状态变量及控制方法 - 支持PDF分页显示、全部页显示、旋转和打印功能 - 支持文本和Markdown文件通过iframe预览 - 对不支持直接预览的文件类型显示提示并提供打开下载链接 - 在操作栏新增“预览”按钮,符合文档状态才显示 - 添加预览对话框及配套样式,提升用户体验
This commit is contained in:
@@ -54,8 +54,9 @@ export interface KbDocumentVO {
|
||||
title: string;
|
||||
docType: string; // report/document/text/data/other
|
||||
fileType: string; // pdf/doc/txt/md等
|
||||
fileSize: number; // 字节
|
||||
fileSize: string; // 字节
|
||||
filePath: string;
|
||||
fileUrl?: string; // 文件访问URL
|
||||
sourceType: string; // upload/project/risk等
|
||||
chunkCount: number; // 分块数量
|
||||
status: "pending" | "processing" | "active" | "error";
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { ref, onMounted, watch } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import VuePdfEmbed from "vue-pdf-embed";
|
||||
import {
|
||||
getDocuments,
|
||||
uploadDocument,
|
||||
@@ -25,6 +26,7 @@ import DocIcon from "~icons/ri/file-word-line";
|
||||
import TxtIcon from "~icons/ri/file-text-line";
|
||||
import ViewIcon from "~icons/ri/eye-line";
|
||||
import ChunkIcon from "~icons/ri/file-list-3-line";
|
||||
import PreviewIcon from "~icons/ri/file-search-line";
|
||||
|
||||
defineOptions({
|
||||
name: "KnowledgeBase"
|
||||
@@ -51,6 +53,17 @@ const currentChunkDoc = ref<KbDocumentVO | null>(null);
|
||||
const selectedChunk = ref<DocumentChunkVO | null>(null);
|
||||
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() {
|
||||
projectLoading.value = true;
|
||||
@@ -234,6 +247,62 @@ function handleViewChunkDetail(chunk: DocumentChunkVO) {
|
||||
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(
|
||||
status: DocumentStatus
|
||||
@@ -402,8 +471,17 @@ onMounted(() => {
|
||||
</template>
|
||||
</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 }">
|
||||
<el-button
|
||||
v-if="canPreview(row)"
|
||||
link
|
||||
type="primary"
|
||||
@click="handlePreview(row)"
|
||||
>
|
||||
<component :is="useRenderIcon(PreviewIcon)" class="mr-1" />
|
||||
预览
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.status === 'active'"
|
||||
link
|
||||
@@ -579,6 +657,109 @@ onMounted(() => {
|
||||
</div>
|
||||
</template>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
@@ -658,4 +839,78 @@ onMounted(() => {
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user