- 在 FileDetailModal 组件中,新增下载原文和下载 Markdown 按钮。
- 实现下载原文和 Markdown 的逻辑,包括文件名处理和错误提示。
- 优化模态框标题样式,增强用户体验。
This commit is contained in:
Wenjie Zhang 2025-11-08 17:00:32 +08:00
parent 2f64a4b597
commit 47bf8f41fe
2 changed files with 164 additions and 5 deletions

View File

@ -19,6 +19,7 @@
### 新增
- 优化知识库详情页面,更加简洁清晰
- 新增对于上传文件的智能体中间件
- 增强文件下载功能
### 修复
- 修复重排序模型实际未生效的问题

View File

@ -1,13 +1,39 @@
<template>
<a-modal
v-model:open="visible"
:title="file?.filename || '文件详情'"
width="1200px"
:footer="null"
wrap-class-name="file-detail"
@after-open-change="afterOpenChange"
:bodyStyle="{ height: '80vh', padding: '0' }"
>
<template #title>
<div class="modal-title-wrapper">
<span>{{ file?.filename || '文件详情' }}</span>
<div class="download-buttons" v-if="file">
<a-button
type="text"
size="small"
@click="handleDownloadOriginal"
:loading="downloadingOriginal"
:disabled="!file.file_id"
:icon="h(DownloadOutlined)"
>
下载原文
</a-button>
<a-button
type="text"
size="small"
@click="handleDownloadMarkdown"
:loading="downloadingMarkdown"
:disabled="!file.lines || file.lines.length === 0"
:icon="h(DownloadOutlined)"
>
下载 Markdown
</a-button>
</div>
</div>
</template>
<div class="file-detail-content" v-if="file">
<div class="file-content-section" v-if="file.lines && file.lines.length > 0">
<MarkdownContentViewer :chunks="file.lines" />
@ -25,8 +51,13 @@
</template>
<script setup>
import { computed } from 'vue';
import { computed, ref, h } from 'vue';
import { useDatabaseStore } from '@/stores/database';
import { message } from 'ant-design-vue';
import { DownloadOutlined } from '@ant-design/icons-vue';
import { documentApi } from '@/apis/knowledge_api';
import { mergeChunks } from '@/utils/chunkUtils';
import MarkdownContentViewer from './MarkdownContentViewer.vue';
const store = useDatabaseStore();
@ -37,6 +68,8 @@ const visible = computed({
const file = computed(() => store.selectedFile);
const loading = computed(() => store.state.fileDetailLoading);
const downloadingOriginal = ref(false);
const downloadingMarkdown = ref(false);
const afterOpenChange = (open) => {
if (!open) {
@ -44,9 +77,113 @@ const afterOpenChange = (open) => {
}
};
//
import { getStatusText, formatStandardTime } from '@/utils/file_utils';
import MarkdownContentViewer from './MarkdownContentViewer.vue';
//
const handleDownloadOriginal = async () => {
if (!file.value || !file.value.file_id) {
message.error('文件信息不完整');
return;
}
const dbId = store.databaseId;
if (!dbId) {
message.error('无法获取数据库ID请刷新页面后重试');
return;
}
downloadingOriginal.value = true;
try {
const response = await documentApi.downloadDocument(dbId, file.value.file_id);
//
const contentDisposition = response.headers.get('content-disposition');
let filename = file.value.filename;
if (contentDisposition) {
// RFC 2231 filename*=UTF-8''...
const rfc2231Match = contentDisposition.match(/filename\*=UTF-8''([^;]+)/);
if (rfc2231Match) {
try {
filename = decodeURIComponent(rfc2231Match[1]);
} catch (error) {
console.warn('Failed to decode RFC2231 filename:', rfc2231Match[1], error);
}
} else {
// 退 filename="..."
const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
if (filenameMatch && filenameMatch[1]) {
filename = filenameMatch[1].replace(/['"]/g, '');
// URL
try {
filename = decodeURIComponent(filename);
} catch (error) {
console.warn('Failed to decode filename:', filename, error);
}
}
}
}
// blob
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
message.success('下载成功');
} catch (error) {
console.error('下载文件时出错:', error);
message.error(error.message || '下载文件失败');
} finally {
downloadingOriginal.value = false;
}
};
// Markdown
const handleDownloadMarkdown = () => {
if (!file.value || !file.value.lines || file.value.lines.length === 0) {
message.error('没有可下载的 Markdown 内容');
return;
}
downloadingMarkdown.value = true;
try {
// chunks Markdown
const { content } = mergeChunks(file.value.lines);
// .md
let filename = file.value.filename || 'document.md';
if (!filename.toLowerCase().endsWith('.md')) {
// .md
const lastDotIndex = filename.lastIndexOf('.');
if (lastDotIndex > 0) {
filename = filename.substring(0, lastDotIndex) + '.md';
} else {
filename = filename + '.md';
}
}
// blob
const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
message.success('下载成功');
} catch (error) {
console.error('下载 Markdown 时出错:', error);
message.error(error.message || '下载 Markdown 失败');
} finally {
downloadingMarkdown.value = false;
}
};
</script>
<style scoped>
@ -82,5 +219,26 @@ import MarkdownContentViewer from './MarkdownContentViewer.vue';
.ant-modal {
top: 20px;
}
.ant-modal-header {
.ant-modal-title {
width: 100%;
}
}
}
.modal-title-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding-right: 8px;
}
.download-buttons {
display: flex;
gap: 8px;
margin: 0 16px;
flex-shrink: 0;
}
</style>