2025-08-05 18:00:45 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<a-modal
|
|
|
|
|
|
v-model:open="visible"
|
|
|
|
|
|
width="1200px"
|
|
|
|
|
|
:footer="null"
|
2026-01-15 04:54:53 +08:00
|
|
|
|
:closable="false"
|
2025-09-19 11:52:50 +08:00
|
|
|
|
wrap-class-name="file-detail"
|
2025-08-05 18:00:45 +08:00
|
|
|
|
@after-open-change="afterOpenChange"
|
2025-09-19 11:52:50 +08:00
|
|
|
|
:bodyStyle="{ height: '80vh', padding: '0' }"
|
2025-08-05 18:00:45 +08:00
|
|
|
|
>
|
2025-11-08 17:00:32 +08:00
|
|
|
|
<template #title>
|
|
|
|
|
|
<div class="modal-title-wrapper">
|
2026-01-15 04:54:53 +08:00
|
|
|
|
<!-- 左侧:文件名和图标 -->
|
|
|
|
|
|
<div class="file-title">
|
|
|
|
|
|
<component :is="fileIcon" :style="{ color: fileIconColor, fontSize: '18px' }" />
|
|
|
|
|
|
<span class="file-name">{{ file?.filename || '文件详情' }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="header-controls">
|
|
|
|
|
|
<!-- 字符数/片段数显示在 segment 左边 -->
|
|
|
|
|
|
<span class="view-info">
|
|
|
|
|
|
{{ viewMode === 'chunks' ? chunkCount + ' 个片段' : formatTextLength(charCount) + ' 字符' }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 视图模式切换 -->
|
|
|
|
|
|
<div class="view-controls" v-if="file && hasContent">
|
|
|
|
|
|
<a-segmented
|
|
|
|
|
|
v-model:value="viewMode"
|
|
|
|
|
|
:options="viewModeOptions"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 下载按钮下拉菜单 -->
|
|
|
|
|
|
<a-dropdown trigger="click" v-if="file">
|
|
|
|
|
|
<a-button type="default" class="download-btn">
|
|
|
|
|
|
<template #icon><Download :size="16" /></template>
|
|
|
|
|
|
下载
|
|
|
|
|
|
<ChevronDown :size="16" style="margin-left: 4px;" />
|
|
|
|
|
|
</a-button>
|
|
|
|
|
|
<template #overlay>
|
|
|
|
|
|
<a-menu @click="handleDownloadMenuClick">
|
|
|
|
|
|
<a-menu-item key="original" :disabled="!file.file_id">
|
|
|
|
|
|
<template #icon><Download :size="16" /></template>
|
|
|
|
|
|
下载原文
|
|
|
|
|
|
</a-menu-item>
|
|
|
|
|
|
<a-menu-item key="markdown" :disabled="!((file.lines && file.lines.length > 0) || file.content)">
|
|
|
|
|
|
<template #icon><FileText :size="16" /></template>
|
|
|
|
|
|
下载 Markdown
|
|
|
|
|
|
</a-menu-item>
|
|
|
|
|
|
</a-menu>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</a-dropdown>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 自定义关闭按钮 -->
|
|
|
|
|
|
<button class="custom-close-btn" @click="visible = false">
|
|
|
|
|
|
<X :size="16" />
|
|
|
|
|
|
</button>
|
2025-11-08 17:00:32 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
2025-08-05 18:00:45 +08:00
|
|
|
|
<div class="file-detail-content" v-if="file">
|
2026-01-15 04:54:53 +08:00
|
|
|
|
<!-- Markdown 模式 -->
|
|
|
|
|
|
<div v-if="viewMode === 'markdown'" class="content-panel">
|
|
|
|
|
|
<MdPreview
|
|
|
|
|
|
v-if="mergedContent"
|
|
|
|
|
|
:modelValue="mergedContent"
|
|
|
|
|
|
:theme="theme"
|
|
|
|
|
|
previewTheme="github"
|
|
|
|
|
|
class="markdown-content"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<div v-else class="empty-content">
|
|
|
|
|
|
<p>暂无文件内容</p>
|
|
|
|
|
|
</div>
|
2025-08-05 18:00:45 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-01-15 04:54:53 +08:00
|
|
|
|
<!-- Chunks 模式:使用 Grid 布局 -->
|
|
|
|
|
|
<div v-else-if="viewMode === 'chunks'" class="chunks-panel">
|
|
|
|
|
|
<div class="chunk-grid">
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-for="chunk in mappedChunks"
|
|
|
|
|
|
:key="chunk.id"
|
|
|
|
|
|
class="chunk-card"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="chunk-card-header">
|
|
|
|
|
|
<span class="chunk-order">#{{ chunk.chunk_order_index }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="chunk-card-content">
|
|
|
|
|
|
{{ chunk.content.replace(/\n+/g, ' ') }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-if="mappedChunks.length === 0" class="empty-content">
|
|
|
|
|
|
<p>暂无分块信息</p>
|
|
|
|
|
|
</div>
|
2025-08-05 18:00:45 +08:00
|
|
|
|
</div>
|
2026-01-15 04:54:53 +08:00
|
|
|
|
</div>
|
2025-08-05 18:00:45 +08:00
|
|
|
|
|
2026-01-15 04:54:53 +08:00
|
|
|
|
<div v-else-if="loading" class="loading-container">
|
|
|
|
|
|
<a-spin />
|
2025-08-05 18:00:45 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</a-modal>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2026-01-15 04:54:53 +08:00
|
|
|
|
import { computed, ref, watch } from 'vue';
|
2025-08-05 18:00:45 +08:00
|
|
|
|
import { useDatabaseStore } from '@/stores/database';
|
2026-01-15 04:54:53 +08:00
|
|
|
|
import { useThemeStore } from '@/stores/theme';
|
2025-11-08 17:00:32 +08:00
|
|
|
|
import { message } from 'ant-design-vue';
|
|
|
|
|
|
import { documentApi } from '@/apis/knowledge_api';
|
|
|
|
|
|
import { mergeChunks } from '@/utils/chunkUtils';
|
2026-01-15 04:54:53 +08:00
|
|
|
|
import { getFileIcon, getFileIconColor } from '@/utils/file_utils';
|
|
|
|
|
|
import { MdPreview } from 'md-editor-v3';
|
|
|
|
|
|
import 'md-editor-v3/lib/preview.css';
|
|
|
|
|
|
import { Download, ChevronDown, FileText, X } from 'lucide-vue-next';
|
2025-08-05 18:00:45 +08:00
|
|
|
|
|
|
|
|
|
|
const store = useDatabaseStore();
|
2026-01-15 04:54:53 +08:00
|
|
|
|
const themeStore = useThemeStore();
|
2025-08-05 18:00:45 +08:00
|
|
|
|
|
|
|
|
|
|
const visible = computed({
|
|
|
|
|
|
get: () => store.state.fileDetailModalVisible,
|
|
|
|
|
|
set: (value) => store.state.fileDetailModalVisible = value
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const file = computed(() => store.selectedFile);
|
|
|
|
|
|
const loading = computed(() => store.state.fileDetailLoading);
|
2026-01-15 04:54:53 +08:00
|
|
|
|
|
|
|
|
|
|
// 文件图标
|
|
|
|
|
|
const fileIcon = computed(() => getFileIcon(file.value?.filename));
|
|
|
|
|
|
const fileIconColor = computed(() => getFileIconColor(file.value?.filename));
|
|
|
|
|
|
|
2025-11-08 17:00:32 +08:00
|
|
|
|
const downloadingOriginal = ref(false);
|
|
|
|
|
|
const downloadingMarkdown = ref(false);
|
2025-08-05 18:00:45 +08:00
|
|
|
|
|
2026-01-15 04:54:53 +08:00
|
|
|
|
// 主题设置
|
|
|
|
|
|
const theme = computed(() => themeStore.isDark ? 'dark' : 'light');
|
|
|
|
|
|
|
|
|
|
|
|
// 视图模式
|
|
|
|
|
|
const viewMode = ref('markdown');
|
|
|
|
|
|
const hasIndexed = computed(() => ['done', 'indexed'].includes(file.value?.status));
|
|
|
|
|
|
const hasContent = computed(() => (file.value?.lines && file.value?.lines.length > 0) || file.value?.content);
|
|
|
|
|
|
|
|
|
|
|
|
const viewModeOptions = computed(() => {
|
|
|
|
|
|
const options = [{ label: 'Markdown', value: 'markdown' }];
|
|
|
|
|
|
if (hasIndexed.value) {
|
|
|
|
|
|
options.push({ label: 'Chunks', value: 'chunks' });
|
|
|
|
|
|
}
|
|
|
|
|
|
return options;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 监听文件状态变化,重置视图模式
|
|
|
|
|
|
watch(file, (newFile) => {
|
|
|
|
|
|
if (newFile) {
|
|
|
|
|
|
if (!hasIndexed.value) {
|
|
|
|
|
|
viewMode.value = 'markdown';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 统计信息
|
|
|
|
|
|
const mergeResult = computed(() => mergeChunks(file.value?.lines || []));
|
|
|
|
|
|
const mappedChunks = computed(() => mergeResult.value.chunks);
|
|
|
|
|
|
const mergedContent = computed(() => file.value?.content || mergeResult.value.content || '');
|
|
|
|
|
|
const charCount = computed(() => mergedContent.value.length);
|
|
|
|
|
|
const chunkCount = computed(() => mappedChunks.value.length || file.value?.lines?.length || 0);
|
|
|
|
|
|
|
|
|
|
|
|
// 格式化文本长度
|
|
|
|
|
|
function formatTextLength(length) {
|
|
|
|
|
|
if (!length && length !== 0) return '0 字符';
|
|
|
|
|
|
|
|
|
|
|
|
if (length < 1000) {
|
|
|
|
|
|
return `${length}`;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return `${(length / 1000).toFixed(1)}k`;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-05 18:00:45 +08:00
|
|
|
|
const afterOpenChange = (open) => {
|
|
|
|
|
|
if (!open) {
|
|
|
|
|
|
store.selectedFile = null;
|
2026-01-15 04:54:53 +08:00
|
|
|
|
viewMode.value = 'markdown';
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 下载菜单点击处理
|
|
|
|
|
|
const handleDownloadMenuClick = ({ key }) => {
|
|
|
|
|
|
if (key === 'original') {
|
|
|
|
|
|
handleDownloadOriginal();
|
|
|
|
|
|
} else if (key === 'markdown') {
|
|
|
|
|
|
handleDownloadMarkdown();
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-08 17:00:32 +08:00
|
|
|
|
// 下载原文
|
|
|
|
|
|
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 = () => {
|
2026-01-15 04:54:53 +08:00
|
|
|
|
const content = mergedContent.value;
|
2026-01-04 21:35:25 +08:00
|
|
|
|
|
|
|
|
|
|
if (!content) {
|
2025-11-08 17:00:32 +08:00
|
|
|
|
message.error('没有可下载的 Markdown 内容');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
downloadingMarkdown.value = true;
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 生成文件名(如果原文件没有 .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;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2025-08-05 18:00:45 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.file-detail-content {
|
|
|
|
|
|
height: 100%;
|
2025-09-19 11:52:50 +08:00
|
|
|
|
overflow-y: auto;
|
2025-08-05 18:00:45 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 04:54:53 +08:00
|
|
|
|
.content-panel,
|
|
|
|
|
|
.chunks-panel {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
padding: 16px 0;
|
|
|
|
|
|
background: var(--gray-0);
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.markdown-content {
|
|
|
|
|
|
min-height: 100%;
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.loading-container {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
height: 200px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.empty-content {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
padding: 40px 0;
|
2025-11-23 01:39:44 +08:00
|
|
|
|
color: var(--gray-400);
|
2026-01-15 04:54:53 +08:00
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Chunks 面板样式 */
|
|
|
|
|
|
.chunk-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chunk-card {
|
|
|
|
|
|
background: var(--gray-0);
|
|
|
|
|
|
border: 1px solid var(--gray-200);
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chunk-card:hover {
|
|
|
|
|
|
border-color: var(--main-color);
|
|
|
|
|
|
box-shadow: 0 2px 8px rgba(1, 97, 121, 0.1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chunk-card-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chunk-order {
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: var(--main-color);
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chunk-card-content {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: var(--gray-600);
|
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
display: -webkit-box;
|
|
|
|
|
|
-webkit-line-clamp: 4;
|
|
|
|
|
|
-webkit-box-orient: vertical;
|
2025-08-05 18:00:45 +08:00
|
|
|
|
}
|
2025-09-19 11:52:50 +08:00
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="less">
|
|
|
|
|
|
.file-detail {
|
|
|
|
|
|
.ant-modal {
|
|
|
|
|
|
top: 20px;
|
|
|
|
|
|
}
|
2025-11-08 17:00:32 +08:00
|
|
|
|
|
|
|
|
|
|
.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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 04:54:53 +08:00
|
|
|
|
/* 文件标题样式 */
|
|
|
|
|
|
.file-title {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.file-name {
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
color: var(--gray-900);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.title-info {
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
color: var(--gray-600);
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.header-controls {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 下载按钮样式 */
|
|
|
|
|
|
.download-btn {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 4px 8px;
|
|
|
|
|
|
height: 28px;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
line-height: 1;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
|
|
|
|
|
|
svg {
|
|
|
|
|
|
vertical-align: middle;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 自定义关闭按钮 */
|
|
|
|
|
|
.custom-close-btn {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
width: 28px;
|
|
|
|
|
|
height: 28px;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
color: var(--gray-500);
|
|
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
background: var(--gray-100);
|
|
|
|
|
|
color: var(--gray-700);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 视图切换控件 */
|
|
|
|
|
|
.view-controls {
|
2025-11-08 17:00:32 +08:00
|
|
|
|
display: flex;
|
2026-01-15 04:54:53 +08:00
|
|
|
|
align-items: center;
|
2025-11-08 17:00:32 +08:00
|
|
|
|
gap: 8px;
|
2025-09-19 11:52:50 +08:00
|
|
|
|
}
|
2026-01-15 04:54:53 +08:00
|
|
|
|
|
|
|
|
|
|
.view-info {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: var(--gray-500);
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 下拉菜单样式 */
|
|
|
|
|
|
.ant-dropdown-menu {
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
padding: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ant-dropdown-menu-item {
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 8px 12px;
|
|
|
|
|
|
|
|
|
|
|
|
svg {
|
|
|
|
|
|
margin-right: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="less">
|
|
|
|
|
|
/* MdPreview 覆盖样式 - 非 scoped */
|
|
|
|
|
|
.markdown-content {
|
|
|
|
|
|
.md-editor-preview-wrapper {
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#md-editor-v-2-preview-wrapper {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
line-height: 1.75;
|
|
|
|
|
|
color: var(--gray-1000);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#md-editor-v-2-preview-wrapper h1 {
|
|
|
|
|
|
font-size: 1.5rem;
|
|
|
|
|
|
margin: 16px 0 12px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#md-editor-v-2-preview-wrapper h2 {
|
|
|
|
|
|
font-size: 1.4rem;
|
|
|
|
|
|
margin: 16px 0 12px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#md-editor-v-2-preview-wrapper h3 {
|
|
|
|
|
|
font-size: 1.3rem;
|
|
|
|
|
|
margin: 14px 0 10px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#md-editor-v-2-preview-wrapper h4 {
|
|
|
|
|
|
font-size: 1.2rem;
|
|
|
|
|
|
margin: 14px 0 10px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#md-editor-v-2-preview-wrapper h5 {
|
|
|
|
|
|
font-size: 1.1rem;
|
|
|
|
|
|
margin: 12px 0 8px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#md-editor-v-2-preview-wrapper h6 {
|
|
|
|
|
|
font-size: 1rem;
|
|
|
|
|
|
margin: 12px 0 8px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|