feat: 增强 Knowledge 页面功能
- 优化文档预览功能 - 添加 CSV 文件解析支持 - 增强知识库详情展示 - 优化样式和交互 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,10 @@ import { ref, computed, onMounted } from 'vue'
|
|||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { useModelSettings } from './settings/useModelSettings'
|
import { useModelSettings } from './settings/useModelSettings'
|
||||||
import { fetchKnowledgeBases, createKnowledgeBase as apiCreateKnowledgeBase, deleteKnowledgeBase as apiDeleteKnowledgeBase, fetchKnowledgeDocuments } from './knowledge/useKnowledge'
|
import { fetchKnowledgeBases, createKnowledgeBase as apiCreateKnowledgeBase, deleteKnowledgeBase as apiDeleteKnowledgeBase, fetchKnowledgeDocuments } from './knowledge/useKnowledge'
|
||||||
|
import VueOfficeDocx from '@vue-office/docx'
|
||||||
|
import VueOfficeExcel from '@vue-office/excel'
|
||||||
|
import Papa from 'papaparse'
|
||||||
|
import { marked } from 'marked'
|
||||||
import './knowledge/knowledge.css'
|
import './knowledge/knowledge.css'
|
||||||
|
|
||||||
// 获取已配置的模型列表
|
// 获取已配置的模型列表
|
||||||
@@ -135,7 +139,12 @@ const loadingDocuments = ref(false)
|
|||||||
const fileInput = ref<HTMLInputElement | null>(null)
|
const fileInput = ref<HTMLInputElement | null>(null)
|
||||||
const uploading = ref(false)
|
const uploading = ref(false)
|
||||||
const previewUrl = ref('') // 文档预览URL (blob URL)
|
const previewUrl = ref('') // 文档预览URL (blob URL)
|
||||||
|
const previewHtml = ref('') // HTML内容预览
|
||||||
|
const previewContentType = ref('') // content_type: url 或 html
|
||||||
const previewDownloadUrl = ref('') // 原始下载链接
|
const previewDownloadUrl = ref('') // 原始下载链接
|
||||||
|
const previewFileType = ref('') // 文件类型: pdf, docx, xlsx, csv
|
||||||
|
const previewCsvData = ref<any[]>([]) // CSV数据
|
||||||
|
const previewCsvHeaders = ref<string[]>([]) // CSV表头
|
||||||
const loadingPreview = ref(false)
|
const loadingPreview = ref(false)
|
||||||
const previewPage = ref(1) // 当前页码
|
const previewPage = ref(1) // 当前页码
|
||||||
const previewTotalPages = ref(1) // 总页数
|
const previewTotalPages = ref(1) // 总页数
|
||||||
@@ -157,6 +166,20 @@ const loadPdfWithProxy = async (doc: any): Promise<string> => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将URL转换为blob URL (用于Excel组件)
|
||||||
|
const loadFileAsBlob = async (url: string): Promise<Blob> => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url)
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch: ${response.status}`)
|
||||||
|
}
|
||||||
|
return await response.blob()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load file as blob:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const newKbForm = ref({
|
const newKbForm = ref({
|
||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
@@ -337,6 +360,11 @@ const enterKnowledge = async (kb: any) => {
|
|||||||
selectedKnowledge.value = kb
|
selectedKnowledge.value = kb
|
||||||
selectedFile.value = null
|
selectedFile.value = null
|
||||||
previewUrl.value = ''
|
previewUrl.value = ''
|
||||||
|
previewHtml.value = ''
|
||||||
|
previewContentType.value = ''
|
||||||
|
previewFileType.value = ''
|
||||||
|
previewCsvData.value = []
|
||||||
|
previewCsvHeaders.value = []
|
||||||
previewDownloadUrl.value = ''
|
previewDownloadUrl.value = ''
|
||||||
|
|
||||||
// 获取文档列表
|
// 获取文档列表
|
||||||
@@ -375,13 +403,126 @@ const selectDocument = async (doc: any) => {
|
|||||||
selectedFile.value = doc.id
|
selectedFile.value = doc.id
|
||||||
selectedDocument.value = doc
|
selectedDocument.value = doc
|
||||||
previewUrl.value = ''
|
previewUrl.value = ''
|
||||||
|
previewHtml.value = ''
|
||||||
|
previewContentType.value = ''
|
||||||
previewDownloadUrl.value = ''
|
previewDownloadUrl.value = ''
|
||||||
|
previewFileType.value = ''
|
||||||
|
previewCsvData.value = []
|
||||||
|
previewCsvHeaders.value = []
|
||||||
previewPage.value = 1
|
previewPage.value = 1
|
||||||
previewTotalPages.value = 1
|
previewTotalPages.value = 1
|
||||||
|
|
||||||
// 优先使用代理接口加载PDF
|
// 检测文件类型
|
||||||
|
const fileName = doc.name || ''
|
||||||
|
const ext = fileName.split('.').pop()?.toLowerCase() || ''
|
||||||
|
console.log('File name:', fileName, 'Ext:', ext)
|
||||||
|
if (ext === 'pdf') {
|
||||||
|
previewFileType.value = 'pdf'
|
||||||
|
} else if (ext === 'docx' || ext === 'doc') {
|
||||||
|
previewFileType.value = 'docx'
|
||||||
|
} else if (ext === 'xlsx' || ext === 'xls') {
|
||||||
|
previewFileType.value = 'xlsx'
|
||||||
|
} else if (ext === 'csv') {
|
||||||
|
previewFileType.value = 'csv'
|
||||||
|
} else if (ext === 'txt' || ext === 'md') {
|
||||||
|
previewFileType.value = 'text'
|
||||||
|
}
|
||||||
|
console.log('Preview file type:', previewFileType.value)
|
||||||
|
|
||||||
|
// 纯文本文件 (txt, md) 直接读取内容显示
|
||||||
|
if (previewFileType.value === 'text') {
|
||||||
|
// 优先使用 file_url(上传时后端返回的)
|
||||||
|
let url = doc.file_url || doc.fileUrl || doc.url || doc.FileURL || ''
|
||||||
|
// 如果没有 file_url,尝试用 file_key 通过代理
|
||||||
|
if (!url && doc.file_key && selectedKnowledge.value) {
|
||||||
|
url = await loadPdfWithProxy(doc)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Text file URL:', url)
|
||||||
|
if (url) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url)
|
||||||
|
console.log('Response status:', response.status)
|
||||||
|
const arrayBuffer = await response.arrayBuffer()
|
||||||
|
const decoder = new TextDecoder('utf-8')
|
||||||
|
const content = decoder.decode(arrayBuffer)
|
||||||
|
console.log('Content length:', content.length, 'ext:', ext)
|
||||||
|
|
||||||
|
// .md 文件用 marked 渲染
|
||||||
|
if (ext === 'md') {
|
||||||
|
// marked.parse 返回 Promise,需要 await
|
||||||
|
const html = await marked.parse(content)
|
||||||
|
previewHtml.value = html as string
|
||||||
|
} else {
|
||||||
|
// .txt 文件直接显示
|
||||||
|
previewHtml.value = '<pre style="white-space: pre-wrap; word-wrap: break-word; color: #e8eaed;">' + content + '</pre>'
|
||||||
|
}
|
||||||
|
console.log('PreviewHtml set, length:', previewHtml.value.length)
|
||||||
|
return
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load text file:', error)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('No URL for text file, file_key:', doc.file_key, 'file_url:', doc.file_url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSV需要特殊处理:用PapaParse解析
|
||||||
|
if (previewFileType.value === 'csv' && (doc.file_key || doc.file_url)) {
|
||||||
|
const url = doc.file_key && selectedKnowledge.value
|
||||||
|
? await loadPdfWithProxy(doc)
|
||||||
|
: doc.file_url || doc.fileUrl || doc.url || ''
|
||||||
|
if (url) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url)
|
||||||
|
const arrayBuffer = await response.arrayBuffer()
|
||||||
|
|
||||||
|
// 尝试多种编码解码
|
||||||
|
let csvText = ''
|
||||||
|
const encodings = ['utf-8', 'gbk', 'gb2312', 'big5']
|
||||||
|
for (const encoding of encodings) {
|
||||||
|
try {
|
||||||
|
const decoder = new TextDecoder(encoding)
|
||||||
|
csvText = decoder.decode(arrayBuffer)
|
||||||
|
// 检查是否成功解码(如果没有乱码字符)
|
||||||
|
if (!csvText.includes('\uFFFD')) {
|
||||||
|
console.log(`CSV decoded with: ${encoding}`)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = Papa.parse(csvText, { header: false, skipEmptyLines: true })
|
||||||
|
if (result.data && result.data.length > 0) {
|
||||||
|
previewCsvHeaders.value = result.data[0] as string[]
|
||||||
|
previewCsvData.value = result.data.slice(1) as any[][]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to parse CSV:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用代理接口加载文件
|
||||||
if (doc.file_key && selectedKnowledge.value) {
|
if (doc.file_key && selectedKnowledge.value) {
|
||||||
previewUrl.value = await loadPdfWithProxy(doc)
|
const proxyUrl = await loadPdfWithProxy(doc)
|
||||||
|
|
||||||
|
// Excel/Word需要blob URL
|
||||||
|
if ((previewFileType.value === 'xlsx' || previewFileType.value === 'docx') && proxyUrl) {
|
||||||
|
try {
|
||||||
|
const blob = await loadFileAsBlob(proxyUrl)
|
||||||
|
const blobUrl = URL.createObjectURL(blob)
|
||||||
|
previewUrl.value = blobUrl
|
||||||
|
return
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to convert to blob:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
previewUrl.value = proxyUrl
|
||||||
if (previewUrl.value) {
|
if (previewUrl.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -397,11 +538,19 @@ const selectDocument = async (doc: any) => {
|
|||||||
// 如果没有file_url,调用预览API获取
|
// 如果没有file_url,调用预览API获取
|
||||||
if (selectedKnowledge.value) {
|
if (selectedKnowledge.value) {
|
||||||
loadingPreview.value = true
|
loadingPreview.value = true
|
||||||
|
previewUrl.value = ''
|
||||||
|
previewHtml.value = ''
|
||||||
|
previewContentType.value = ''
|
||||||
try {
|
try {
|
||||||
const { getDocumentPreview } = await import('./knowledge/useKnowledge')
|
const { getDocumentPreview } = await import('./knowledge/useKnowledge')
|
||||||
const result = await getDocumentPreview(selectedKnowledge.value.id, doc.id)
|
const result = await getDocumentPreview(selectedKnowledge.value.id, doc.id)
|
||||||
if (result.success && result.data?.content) {
|
if (result.success && result.data?.content) {
|
||||||
previewUrl.value = result.data.content
|
previewContentType.value = result.data.content_type || 'url'
|
||||||
|
if (previewContentType.value === 'html') {
|
||||||
|
previewHtml.value = result.data.content
|
||||||
|
} else {
|
||||||
|
previewUrl.value = result.data.content
|
||||||
|
}
|
||||||
previewTotalPages.value = result.data.total_pages || 1
|
previewTotalPages.value = result.data.total_pages || 1
|
||||||
previewPage.value = result.data.current_page || 1
|
previewPage.value = result.data.current_page || 1
|
||||||
}
|
}
|
||||||
@@ -416,6 +565,8 @@ const selectDocument = async (doc: any) => {
|
|||||||
// 翻页
|
// 翻页
|
||||||
const changePreviewPage = async (page: number) => {
|
const changePreviewPage = async (page: number) => {
|
||||||
if (!selectedKnowledge.value || !selectedFile.value || page < 1 || page > previewTotalPages.value) return
|
if (!selectedKnowledge.value || !selectedFile.value || page < 1 || page > previewTotalPages.value) return
|
||||||
|
// HTML内容不支持翻页
|
||||||
|
if (previewContentType.value === 'html') return
|
||||||
|
|
||||||
loadingPreview.value = true
|
loadingPreview.value = true
|
||||||
previewPage.value = page
|
previewPage.value = page
|
||||||
@@ -423,7 +574,12 @@ const changePreviewPage = async (page: number) => {
|
|||||||
const { getDocumentPreview } = await import('./knowledge/useKnowledge')
|
const { getDocumentPreview } = await import('./knowledge/useKnowledge')
|
||||||
const result = await getDocumentPreview(selectedKnowledge.value.id, selectedFile.value, page)
|
const result = await getDocumentPreview(selectedKnowledge.value.id, selectedFile.value, page)
|
||||||
if (result.success && result.data?.content) {
|
if (result.success && result.data?.content) {
|
||||||
previewUrl.value = result.data.content
|
previewContentType.value = result.data.content_type || 'url'
|
||||||
|
if (previewContentType.value === 'html') {
|
||||||
|
previewHtml.value = result.data.content
|
||||||
|
} else {
|
||||||
|
previewUrl.value = result.data.content
|
||||||
|
}
|
||||||
previewTotalPages.value = result.data.total_pages || 1
|
previewTotalPages.value = result.data.total_pages || 1
|
||||||
previewPage.value = result.data.current_page || page
|
previewPage.value = result.data.current_page || page
|
||||||
}
|
}
|
||||||
@@ -459,16 +615,8 @@ const handleFileSelect = async (event: Event) => {
|
|||||||
// 获取刚上传的文档
|
// 获取刚上传的文档
|
||||||
const uploadedDoc = knowledgeDocuments.value.find(d => d.id === result.id)
|
const uploadedDoc = knowledgeDocuments.value.find(d => d.id === result.id)
|
||||||
if (uploadedDoc) {
|
if (uploadedDoc) {
|
||||||
// 选中新上传的文档
|
// 选中新上传的文档,调用 selectDocument 处理预览
|
||||||
selectedFile.value = result.id
|
await selectDocument(uploadedDoc)
|
||||||
selectedDocument.value = uploadedDoc
|
|
||||||
|
|
||||||
// 使用代理接口加载PDF
|
|
||||||
if (uploadedDoc.file_key) {
|
|
||||||
previewUrl.value = await loadPdfWithProxy(uploadedDoc)
|
|
||||||
} else if (uploadedDoc.file_url) {
|
|
||||||
previewUrl.value = uploadedDoc.file_url
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(result.message || 'Failed to upload file')
|
ElMessage.error(result.message || 'Failed to upload file')
|
||||||
@@ -497,7 +645,12 @@ const deleteDocument = async (docId: string) => {
|
|||||||
selectedFile.value = null
|
selectedFile.value = null
|
||||||
selectedDocument.value = null
|
selectedDocument.value = null
|
||||||
previewUrl.value = ''
|
previewUrl.value = ''
|
||||||
previewDownloadUrl.value = ''
|
previewHtml.value = ''
|
||||||
|
previewContentType.value = ''
|
||||||
|
previewFileType.value = ''
|
||||||
|
previewCsvData.value = []
|
||||||
|
previewCsvHeaders.value = []
|
||||||
|
previewDownloadUrl.value = ''
|
||||||
}
|
}
|
||||||
// 刷新文档列表
|
// 刷新文档列表
|
||||||
await changeFileFilter(fileFilter.value)
|
await changeFileFilter(fileFilter.value)
|
||||||
@@ -873,7 +1026,7 @@ const deleteDocument = async (docId: string) => {
|
|||||||
type="file"
|
type="file"
|
||||||
ref="fileInput"
|
ref="fileInput"
|
||||||
style="display: none"
|
style="display: none"
|
||||||
accept=".pdf,.doc,.docx,.txt,.md"
|
accept=".pdf,.doc,.docx,.docx,.txt,.md,.csv,.xlsx,.xls,.pptx,.ppt"
|
||||||
@change="handleFileSelect"
|
@change="handleFileSelect"
|
||||||
/>
|
/>
|
||||||
<button class="btn-primary" @click="triggerFileUpload">
|
<button class="btn-primary" @click="triggerFileUpload">
|
||||||
@@ -987,7 +1140,40 @@ const deleteDocument = async (docId: string) => {
|
|||||||
<i class="fa-solid fa-spinner fa-spin"></i>
|
<i class="fa-solid fa-spinner fa-spin"></i>
|
||||||
<span>Loading preview...</span>
|
<span>Loading preview...</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- 有blob预览URL时显示PDF (使用iframe) -->
|
<!-- Word文件预览 -->
|
||||||
|
<VueOfficeDocx
|
||||||
|
v-else-if="previewUrl && previewFileType === 'docx'"
|
||||||
|
:src="previewUrl"
|
||||||
|
class="office-embed"
|
||||||
|
@rendered="console.log('Docx rendered')"
|
||||||
|
@error="console.error('Docx error', $event)"
|
||||||
|
/>
|
||||||
|
<!-- Excel文件预览 -->
|
||||||
|
<VueOfficeExcel
|
||||||
|
v-else-if="previewUrl && previewFileType === 'xlsx'"
|
||||||
|
:src="previewUrl"
|
||||||
|
class="office-embed"
|
||||||
|
@rendered="console.log('Excel rendered')"
|
||||||
|
@error="console.error('Excel error', $event)"
|
||||||
|
/>
|
||||||
|
<!-- CSV文件预览 -->
|
||||||
|
<div v-else-if="previewFileType === 'csv' && previewCsvData.length > 0" class="csv-preview">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th v-for="(header, index) in previewCsvHeaders" :key="index">{{ header }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(row, rowIndex) in previewCsvData" :key="rowIndex">
|
||||||
|
<td v-for="(cell, cellIndex) in row" :key="cellIndex">{{ cell }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- 文本文件预览 (txt, md) -->
|
||||||
|
<div v-else-if="previewHtml" class="text-preview" v-html="previewHtml"></div>
|
||||||
|
<!-- PDF/其他文件预览 (使用iframe) -->
|
||||||
<iframe
|
<iframe
|
||||||
v-else-if="previewUrl"
|
v-else-if="previewUrl"
|
||||||
:src="previewUrl"
|
:src="previewUrl"
|
||||||
@@ -996,9 +1182,9 @@ const deleteDocument = async (docId: string) => {
|
|||||||
<!-- 无预览但有下载链接时显示下载按钮 -->
|
<!-- 无预览但有下载链接时显示下载按钮 -->
|
||||||
<div v-else-if="previewDownloadUrl" class="preview-no-file">
|
<div v-else-if="previewDownloadUrl" class="preview-no-file">
|
||||||
<i class="fa-solid fa-file-pdf"></i>
|
<i class="fa-solid fa-file-pdf"></i>
|
||||||
<span>Cannot preview PDF directly</span>
|
<span>Cannot preview file directly</span>
|
||||||
<a :href="previewDownloadUrl" target="_blank" class="download-link">
|
<a :href="previewDownloadUrl" target="_blank" class="download-link">
|
||||||
<i class="fa-solid fa-download"></i> Download PDF
|
<i class="fa-solid fa-download"></i> Download File
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<!-- 无预览也无下载链接时显示提示 -->
|
<!-- 无预览也无下载链接时显示提示 -->
|
||||||
|
|||||||
@@ -241,6 +241,7 @@ const showChangePassword = () => {
|
|||||||
description="Configure your model settings"
|
description="Configure your model settings"
|
||||||
icon="fa-solid fa-brain"
|
icon="fa-solid fa-brain"
|
||||||
icon-class="bg-gradient-to-br from-primary-orange to-red-500"
|
icon-class="bg-gradient-to-br from-primary-orange to-red-500"
|
||||||
|
class="add-model-dialog"
|
||||||
>
|
>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -943,6 +943,166 @@
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* HTML内容预览 */
|
||||||
|
.html-preview {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #1e1e24;
|
||||||
|
color: #e8eaed;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.html-preview table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.html-preview table th,
|
||||||
|
.html-preview table td {
|
||||||
|
border: 1px solid #3a3a4a;
|
||||||
|
padding: 8px 12px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.html-preview table th {
|
||||||
|
background-color: #2a2a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.html-preview h1, .html-preview h2, .html-preview h3 {
|
||||||
|
margin-top: 16px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.html-preview pre {
|
||||||
|
background-color: #2a2a3a;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.html-preview code {
|
||||||
|
background-color: #2a2a3a;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Office 组件预览 */
|
||||||
|
.office-embed {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 修复 vue-office 表格黑色背景问题 */
|
||||||
|
.office-embed :deep(table) {
|
||||||
|
background-color: #ffffff !important;
|
||||||
|
color: #333333 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.office-embed :deep(td),
|
||||||
|
.office-embed :deep(th) {
|
||||||
|
background-color: #ffffff !important;
|
||||||
|
color: #333333 !important;
|
||||||
|
border: 1px solid #dddddd !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CSV 表格预览 */
|
||||||
|
.csv-preview {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
background-color: #ffffff;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.csv-preview table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.csv-preview th,
|
||||||
|
.csv-preview td {
|
||||||
|
border: 1px solid #dddddd;
|
||||||
|
padding: 8px 12px;
|
||||||
|
text-align: left;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.csv-preview th {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.csv-preview tr:nth-child(even) {
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 文本文件预览 */
|
||||||
|
.text-preview {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 16px;
|
||||||
|
background-color: #1e1e24;
|
||||||
|
color: #e8eaed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-preview pre {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
color: #e8eaed;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Markdown 渲染样式 */
|
||||||
|
.text-preview h1, .text-preview h2, .text-preview h3,
|
||||||
|
.text-preview h4, .text-preview h5, .text-preview h6 {
|
||||||
|
color: #ffffff;
|
||||||
|
margin-top: 16px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-preview code {
|
||||||
|
background-color: #2a2a3a;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-preview pre {
|
||||||
|
background-color: #2a2a3a;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-preview ul, .text-preview ol {
|
||||||
|
padding-left: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-preview li {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-preview a {
|
||||||
|
color: #f97316;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-preview blockquote {
|
||||||
|
border-left: 4px solid #f97316;
|
||||||
|
padding-left: 16px;
|
||||||
|
margin-left: 0;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
.preview-info {
|
.preview-info {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
|||||||
Reference in New Issue
Block a user