|
|
@@ -114,7 +114,7 @@
|
|
|
<!-- 网格视图 -->
|
|
|
<div v-if="viewMode === 'grid'" class="grid-view" ref="gridViewRef">
|
|
|
<div
|
|
|
- v-for="folder in folderList"
|
|
|
+ v-for="folder in filteredFolderList"
|
|
|
:key="'folder-' + folder.folderId"
|
|
|
class="file-item folder-type"
|
|
|
:class="{ selected: isSelected('folder', folder.folderId), 'drag-over': dragOverFolderId === folder.folderId }"
|
|
|
@@ -137,7 +137,7 @@
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
|
- v-for="file in fileList"
|
|
|
+ v-for="file in filteredFileList"
|
|
|
:key="'file-' + file.fileId"
|
|
|
class="file-item file-type"
|
|
|
:class="{ selected: isSelected('file', file.fileId) }"
|
|
|
@@ -159,7 +159,7 @@
|
|
|
</div>
|
|
|
|
|
|
<!-- 列表视图 -->
|
|
|
- <el-table v-else :data="[...folderList, ...fileList]" @selection-change="handleSelectionChange">
|
|
|
+ <el-table v-else :data="[...filteredFolderList, ...filteredFileList]" @selection-change="handleSelectionChange">
|
|
|
<el-table-column type="selection" width="55" />
|
|
|
<el-table-column label="名称" min-width="300">
|
|
|
<template #default="scope">
|
|
|
@@ -192,9 +192,12 @@
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
|
|
|
- <el-empty v-if="!loading && folderList.length === 0 && fileList.length === 0" description="此文件夹为空">
|
|
|
- <el-button type="primary" @click="handleUpload">上传文件</el-button>
|
|
|
- <el-button @click="handleAddFolder">新建文件夹</el-button>
|
|
|
+ <el-empty v-if="!loading && filteredFolderList.length === 0 && filteredFileList.length === 0"
|
|
|
+ :description="searchKeyword ? '未找到匹配项' : '此文件夹为空'">
|
|
|
+ <template v-if="!searchKeyword">
|
|
|
+ <el-button type="primary" @click="handleUpload">上传文件</el-button>
|
|
|
+ <el-button @click="handleAddFolder">新建文件夹</el-button>
|
|
|
+ </template>
|
|
|
</el-empty>
|
|
|
</div>
|
|
|
|
|
|
@@ -242,9 +245,10 @@
|
|
|
<el-dialog
|
|
|
v-model="previewDialogVisible"
|
|
|
:title="previewFile?.fileName || '文件预览'"
|
|
|
- width="80%"
|
|
|
+ width="85%"
|
|
|
:close-on-click-modal="true"
|
|
|
destroy-on-close
|
|
|
+ @close="handlePreviewClose"
|
|
|
>
|
|
|
<div class="preview-container">
|
|
|
<!-- 图片预览 -->
|
|
|
@@ -263,13 +267,51 @@
|
|
|
<div v-else-if="previewType === 'pdf'" class="preview-pdf">
|
|
|
<iframe :src="previewUrl" style="width: 100%; height: 70vh; border: none;" />
|
|
|
</div>
|
|
|
- <!-- 文本预览 -->
|
|
|
- <div v-else-if="previewType === 'text'" class="preview-text">
|
|
|
- <pre style="max-height: 70vh; overflow: auto; background: #f5f7fa; padding: 16px; border-radius: 4px;">{{ previewContent }}</pre>
|
|
|
+ <!-- 代码/文本预览 -->
|
|
|
+ <div v-else-if="previewType === 'text' || previewType === 'code'" class="preview-code">
|
|
|
+ <div class="code-toolbar">
|
|
|
+ <el-button v-if="!previewEditing" size="small" @click="previewEditing = true">
|
|
|
+ <el-icon><Edit /></el-icon> 编辑
|
|
|
+ </el-button>
|
|
|
+ <template v-else>
|
|
|
+ <el-button size="small" type="primary" @click="saveTextContent">保存</el-button>
|
|
|
+ <el-button size="small" @click="previewEditing = false">取消</el-button>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ <textarea
|
|
|
+ v-if="previewEditing"
|
|
|
+ v-model="previewContent"
|
|
|
+ class="code-editor"
|
|
|
+ ></textarea>
|
|
|
+ <pre v-else class="code-preview"><code v-html="highlightedCode"></code></pre>
|
|
|
+ </div>
|
|
|
+ <!-- Excel预览 -->
|
|
|
+ <div v-else-if="previewType === 'excel'" class="preview-excel">
|
|
|
+ <div class="excel-tabs" v-if="excelSheets.length > 1">
|
|
|
+ <el-radio-group v-model="currentSheet" size="small">
|
|
|
+ <el-radio-button v-for="(sheet, idx) in excelSheets" :key="idx" :value="idx">
|
|
|
+ {{ sheet }}
|
|
|
+ </el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ </div>
|
|
|
+ <div class="excel-table-wrapper">
|
|
|
+ <table class="excel-table" v-if="excelData[currentSheet]">
|
|
|
+ <thead>
|
|
|
+ <tr>
|
|
|
+ <th v-for="(cell, idx) in excelData[currentSheet][0]" :key="idx">{{ cell }}</th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody>
|
|
|
+ <tr v-for="(row, rowIdx) in excelData[currentSheet].slice(1)" :key="rowIdx">
|
|
|
+ <td v-for="(cell, cellIdx) in row" :key="cellIdx">{{ cell }}</td>
|
|
|
+ </tr>
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
<!-- 不支持预览 -->
|
|
|
<div v-else class="preview-unsupported">
|
|
|
- <el-empty description="该文件类型暂不支持预览">
|
|
|
+ <el-empty :description="previewType === 'office' ? 'Word/PPT请下载后查看' : '该文件类型暂不支持预览'">
|
|
|
<el-button type="primary" @click="handleDownloadFile(previewFile)">下载文件</el-button>
|
|
|
</el-empty>
|
|
|
</div>
|
|
|
@@ -301,41 +343,11 @@
|
|
|
<div class="menu-item" @click="handleShare(contextMenuItem)">
|
|
|
<el-icon><Share /></el-icon> 分享
|
|
|
</div>
|
|
|
- <div class="menu-item" @click="handleCopy(contextMenuItem)">
|
|
|
- <el-icon><Folder /></el-icon> 复制到...
|
|
|
- </div>
|
|
|
- <div class="menu-item" @click="handleMove(contextMenuItem)">
|
|
|
- <el-icon><FolderRemove /></el-icon> 移动到...
|
|
|
- </div>
|
|
|
<div class="menu-item danger" @click="handleDeleteItem(contextMenuItem)">
|
|
|
<el-icon><Delete /></el-icon> 删除
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 移动/复制目标文件夹选择对话框 -->
|
|
|
- <el-dialog :title="folderSelectTitle" v-model="folderSelectDialogVisible" width="500px">
|
|
|
- <el-tree
|
|
|
- ref="folderTreeRef"
|
|
|
- :data="folderTree"
|
|
|
- :props="{ label: 'label', children: 'children' }"
|
|
|
- node-key="id"
|
|
|
- default-expand-all
|
|
|
- highlight-current
|
|
|
- @node-click="handleFolderTreeClick"
|
|
|
- >
|
|
|
- <template #default="{ node, data }">
|
|
|
- <span class="folder-tree-node">
|
|
|
- <el-icon color="#f9a825"><Folder /></el-icon>
|
|
|
- <span>{{ data.label }}</span>
|
|
|
- </span>
|
|
|
- </template>
|
|
|
- </el-tree>
|
|
|
- <template #footer>
|
|
|
- <el-button @click="folderSelectDialogVisible = false">取消</el-button>
|
|
|
- <el-button type="primary" @click="submitFolderSelect" :disabled="!selectedTargetFolderId">确定</el-button>
|
|
|
- </template>
|
|
|
- </el-dialog>
|
|
|
-
|
|
|
<!-- 分享对话框 -->
|
|
|
<el-dialog
|
|
|
v-model="shareDialogVisible"
|
|
|
@@ -467,16 +479,18 @@
|
|
|
import {
|
|
|
HomeFilled, Folder, Document, FolderAdd, Upload, UploadFilled,
|
|
|
ArrowDown, ArrowLeft, ArrowRight, Top, Menu, List, Edit, Download, Delete, Search,
|
|
|
- Share, CopyDocument, FolderRemove, Scissor, DocumentAdd, Refresh, CircleCheck
|
|
|
+ Share, CopyDocument, Scissor, DocumentAdd, Refresh, CircleCheck
|
|
|
} from '@element-plus/icons-vue'
|
|
|
import {
|
|
|
- listFolder, addFolder, updateFolder, delFolder, getFolderTree,
|
|
|
- listFile, downloadFile, delFile, updateFile,
|
|
|
- moveFile, copyFile, copyFolder,
|
|
|
+ listFolder, addFolder, updateFolder, delFolder,
|
|
|
+ listFile, downloadFile, delFile, updateFile, updateFileContent,
|
|
|
createFileShare, createFolderShare, cancelFileShare,
|
|
|
getFileShare, getFolderShare
|
|
|
} from '@/api/system/file'
|
|
|
import { getToken } from '@/utils/auth'
|
|
|
+import hljs from 'highlight.js'
|
|
|
+import 'highlight.js/styles/github.css'
|
|
|
+import * as XLSX from 'xlsx'
|
|
|
|
|
|
const { proxy } = getCurrentInstance()
|
|
|
|
|
|
@@ -496,6 +510,19 @@ const isIndeterminate = computed(() => {
|
|
|
return selectedItems.value.length > 0 && selectedItems.value.length < totalItems
|
|
|
})
|
|
|
|
|
|
+// 过滤后的列表(搜索)
|
|
|
+const filteredFolderList = computed(() => {
|
|
|
+ if (!searchKeyword.value.trim()) return folderList.value
|
|
|
+ const keyword = searchKeyword.value.toLowerCase()
|
|
|
+ return folderList.value.filter(f => f.folderName?.toLowerCase().includes(keyword))
|
|
|
+})
|
|
|
+
|
|
|
+const filteredFileList = computed(() => {
|
|
|
+ if (!searchKeyword.value.trim()) return fileList.value
|
|
|
+ const keyword = searchKeyword.value.toLowerCase()
|
|
|
+ return fileList.value.filter(f => f.fileName?.toLowerCase().includes(keyword))
|
|
|
+})
|
|
|
+
|
|
|
// 剪贴板
|
|
|
const clipboard = ref([])
|
|
|
const clipboardAction = ref('') // 'copy' or 'cut'
|
|
|
@@ -541,15 +568,10 @@ const previewFile = ref(null)
|
|
|
const previewType = ref('')
|
|
|
const previewUrl = ref('')
|
|
|
const previewContent = ref('')
|
|
|
-
|
|
|
-// 移动/复制相关
|
|
|
-const folderSelectDialogVisible = ref(false)
|
|
|
-const folderSelectTitle = ref('')
|
|
|
-const folderSelectMode = ref('') // 'move' or 'copy'
|
|
|
-const folderTree = ref([])
|
|
|
-const folderTreeRef = ref(null)
|
|
|
-const selectedTargetFolderId = ref(null)
|
|
|
-const operatingItem = ref(null)
|
|
|
+const previewEditing = ref(false)
|
|
|
+const excelData = ref([])
|
|
|
+const excelSheets = ref([])
|
|
|
+const currentSheet = ref(0)
|
|
|
|
|
|
// 分享相关
|
|
|
const shareDialogVisible = ref(false)
|
|
|
@@ -844,14 +866,22 @@ function handleFilePreview(file) {
|
|
|
if (!file) return
|
|
|
|
|
|
previewFile.value = file
|
|
|
+ previewEditing.value = false
|
|
|
+ excelData.value = []
|
|
|
+ excelSheets.value = []
|
|
|
+ currentSheet.value = 0
|
|
|
+
|
|
|
const fileName = file.fileName || ''
|
|
|
const ext = fileName.split('.').pop()?.toLowerCase() || ''
|
|
|
|
|
|
// 判断文件类型
|
|
|
- const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg']
|
|
|
- const videoExts = ['mp4', 'webm', 'ogg', 'mov', 'avi']
|
|
|
- const audioExts = ['mp3', 'wav', 'ogg', 'aac', 'flac']
|
|
|
- const textExts = ['txt', 'json', 'xml', 'html', 'css', 'js', 'ts', 'vue', 'md', 'yaml', 'yml', 'ini', 'conf', 'log']
|
|
|
+ const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'ico']
|
|
|
+ const videoExts = ['mp4', 'webm', 'ogg', 'mov', 'avi', 'mkv', 'flv', 'm4v']
|
|
|
+ const audioExts = ['mp3', 'wav', 'ogg', 'aac', 'flac', 'm4a', 'wma']
|
|
|
+ const codeExts = ['js', 'ts', 'jsx', 'tsx', 'vue', 'json', 'html', 'htm', 'css', 'scss', 'less', 'xml', 'sql', 'java', 'py', 'php', 'c', 'cpp', 'h', 'hpp', 'go', 'rs', 'sh', 'bat', 'ps1', 'dockerfile', 'makefile']
|
|
|
+ const textExts = ['txt', 'md', 'markdown', 'yaml', 'yml', 'ini', 'conf', 'log', 'csv', 'env', 'gitignore']
|
|
|
+ const excelExts = ['xls', 'xlsx']
|
|
|
+ const officeExts = ['doc', 'docx', 'ppt', 'pptx']
|
|
|
|
|
|
if (imageExts.includes(ext)) {
|
|
|
previewType.value = 'image'
|
|
|
@@ -861,19 +891,47 @@ function handleFilePreview(file) {
|
|
|
previewType.value = 'audio'
|
|
|
} else if (ext === 'pdf') {
|
|
|
previewType.value = 'pdf'
|
|
|
+ } else if (codeExts.includes(ext)) {
|
|
|
+ previewType.value = 'code'
|
|
|
+ loadTextContent(file)
|
|
|
} else if (textExts.includes(ext)) {
|
|
|
previewType.value = 'text'
|
|
|
- // 加载文本内容
|
|
|
loadTextContent(file)
|
|
|
+ } else if (excelExts.includes(ext)) {
|
|
|
+ previewType.value = 'excel'
|
|
|
+ loadExcelContent(file)
|
|
|
+ } else if (officeExts.includes(ext)) {
|
|
|
+ previewType.value = 'office'
|
|
|
} else {
|
|
|
previewType.value = 'unsupported'
|
|
|
}
|
|
|
|
|
|
- // 设置预览URL
|
|
|
- previewUrl.value = import.meta.env.VITE_APP_BASE_API + '/system/file/preview/' + file.fileId
|
|
|
+ // 设置预览URL(带token)
|
|
|
+ previewUrl.value = import.meta.env.VITE_APP_BASE_API + '/system/file/preview/' + file.fileId + '?Authorization=' + getToken()
|
|
|
previewDialogVisible.value = true
|
|
|
}
|
|
|
|
|
|
+/** 获取代码高亮 */
|
|
|
+const highlightedCode = computed(() => {
|
|
|
+ if (!previewContent.value) return ''
|
|
|
+ const ext = previewFile.value?.fileName?.split('.').pop()?.toLowerCase() || ''
|
|
|
+ const langMap = {
|
|
|
+ 'js': 'javascript', 'ts': 'typescript', 'jsx': 'javascript', 'tsx': 'typescript',
|
|
|
+ 'vue': 'xml', 'json': 'json', 'html': 'xml', 'htm': 'xml', 'xml': 'xml',
|
|
|
+ 'css': 'css', 'scss': 'scss', 'less': 'less', 'sql': 'sql',
|
|
|
+ 'java': 'java', 'py': 'python', 'php': 'php', 'c': 'c', 'cpp': 'cpp',
|
|
|
+ 'h': 'c', 'hpp': 'cpp', 'go': 'go', 'rs': 'rust', 'sh': 'bash',
|
|
|
+ 'bat': 'dos', 'ps1': 'powershell', 'dockerfile': 'dockerfile', 'makefile': 'makefile',
|
|
|
+ 'md': 'markdown', 'yaml': 'yaml', 'yml': 'yaml'
|
|
|
+ }
|
|
|
+ const lang = langMap[ext] || 'plaintext'
|
|
|
+ try {
|
|
|
+ return hljs.highlight(previewContent.value, { language: lang }).value
|
|
|
+ } catch {
|
|
|
+ return hljs.highlightAuto(previewContent.value).value
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
/** 加载文本内容 */
|
|
|
async function loadTextContent(file) {
|
|
|
try {
|
|
|
@@ -885,6 +943,44 @@ async function loadTextContent(file) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/** 加载Excel内容 */
|
|
|
+async function loadExcelContent(file) {
|
|
|
+ try {
|
|
|
+ const response = await downloadFile(file.fileId)
|
|
|
+ const arrayBuffer = await response.arrayBuffer()
|
|
|
+ const workbook = XLSX.read(arrayBuffer, { type: 'array' })
|
|
|
+
|
|
|
+ excelSheets.value = workbook.SheetNames
|
|
|
+ excelData.value = workbook.SheetNames.map(name => {
|
|
|
+ const sheet = workbook.Sheets[name]
|
|
|
+ return XLSX.utils.sheet_to_json(sheet, { header: 1 })
|
|
|
+ })
|
|
|
+ } catch (error) {
|
|
|
+ proxy.$modal.msgError('Excel加载失败')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/** 保存文本内容 */
|
|
|
+async function saveTextContent() {
|
|
|
+ if (!previewFile.value) return
|
|
|
+
|
|
|
+ try {
|
|
|
+ await updateFileContent(previewFile.value.fileId, previewContent.value)
|
|
|
+ proxy.$modal.msgSuccess('保存成功')
|
|
|
+ previewEditing.value = false
|
|
|
+ } catch (error) {
|
|
|
+ proxy.$modal.msgError('保存失败')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/** 预览关闭 */
|
|
|
+function handlePreviewClose() {
|
|
|
+ previewEditing.value = false
|
|
|
+ previewContent.value = ''
|
|
|
+ excelData.value = []
|
|
|
+ excelSheets.value = []
|
|
|
+}
|
|
|
+
|
|
|
/** 删除项目 */
|
|
|
function handleDeleteItem(item) {
|
|
|
closeContextMenu()
|
|
|
@@ -1246,78 +1342,6 @@ function copyShareAll() {
|
|
|
proxy.$modal.msgSuccess('已复制到剪贴板')
|
|
|
}
|
|
|
|
|
|
-/** 复制 */
|
|
|
-async function handleCopy(item) {
|
|
|
- closeContextMenu()
|
|
|
- if (!item) return
|
|
|
-
|
|
|
- operatingItem.value = item
|
|
|
- folderSelectMode.value = 'copy'
|
|
|
- folderSelectTitle.value = '复制到'
|
|
|
- selectedTargetFolderId.value = null
|
|
|
-
|
|
|
- try {
|
|
|
- const response = await getFolderTree()
|
|
|
- folderTree.value = [{ id: 0, label: '根目录', children: response.data || [] }]
|
|
|
- folderSelectDialogVisible.value = true
|
|
|
- } catch (error) {
|
|
|
- proxy.$modal.msgError('加载文件夹失败')
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-/** 移动 */
|
|
|
-async function handleMove(item) {
|
|
|
- closeContextMenu()
|
|
|
- if (!item) return
|
|
|
-
|
|
|
- operatingItem.value = item
|
|
|
- folderSelectMode.value = 'move'
|
|
|
- folderSelectTitle.value = '移动到'
|
|
|
- selectedTargetFolderId.value = null
|
|
|
-
|
|
|
- try {
|
|
|
- const response = await getFolderTree()
|
|
|
- folderTree.value = [{ id: 0, label: '根目录', children: response.data || [] }]
|
|
|
- folderSelectDialogVisible.value = true
|
|
|
- } catch (error) {
|
|
|
- proxy.$modal.msgError('加载文件夹失败')
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-function handleFolderTreeClick(data) {
|
|
|
- selectedTargetFolderId.value = data.id
|
|
|
-}
|
|
|
-
|
|
|
-async function submitFolderSelect() {
|
|
|
- if (selectedTargetFolderId.value === null || !operatingItem.value) return
|
|
|
-
|
|
|
- const isFolder = !!operatingItem.value.folderId
|
|
|
- const targetId = selectedTargetFolderId.value
|
|
|
-
|
|
|
- try {
|
|
|
- if (folderSelectMode.value === 'copy') {
|
|
|
- if (isFolder) {
|
|
|
- await copyFolder(operatingItem.value.folderId, targetId)
|
|
|
- } else {
|
|
|
- await copyFile(operatingItem.value.fileId, targetId)
|
|
|
- }
|
|
|
- proxy.$modal.msgSuccess('复制成功')
|
|
|
- } else {
|
|
|
- if (isFolder) {
|
|
|
- proxy.$modal.msgError('暂不支持移动文件夹')
|
|
|
- return
|
|
|
- }
|
|
|
- await moveFile(operatingItem.value.fileId, targetId)
|
|
|
- proxy.$modal.msgSuccess('移动成功')
|
|
|
- }
|
|
|
-
|
|
|
- folderSelectDialogVisible.value = false
|
|
|
- loadData()
|
|
|
- } catch (error) {
|
|
|
- proxy.$modal.msgError(folderSelectMode.value === 'copy' ? '复制失败' : '移动失败')
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
/** 关闭右键菜单 */
|
|
|
function closeContextMenu() {
|
|
|
contextMenuVisible.value = false
|
|
|
@@ -1619,6 +1643,12 @@ onUnmounted(() => {
|
|
|
gap: 12px;
|
|
|
flex: 1;
|
|
|
min-width: 0;
|
|
|
+ flex-wrap: nowrap;
|
|
|
+
|
|
|
+ .el-button-group,
|
|
|
+ > .el-button {
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
.toolbar-right {
|
|
|
@@ -1633,29 +1663,46 @@ onUnmounted(() => {
|
|
|
background: #f5f7fa;
|
|
|
border-radius: 8px;
|
|
|
flex: 1;
|
|
|
- min-width: 200px;
|
|
|
- max-width: 500px;
|
|
|
+ min-width: 120px;
|
|
|
+ overflow: hidden;
|
|
|
|
|
|
- .el-breadcrumb {
|
|
|
+ :deep(.el-breadcrumb) {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: nowrap;
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
line-height: 1;
|
|
|
font-size: 14px;
|
|
|
- }
|
|
|
-
|
|
|
- :deep(.el-breadcrumb__item) {
|
|
|
- cursor: pointer;
|
|
|
|
|
|
- .el-breadcrumb__inner {
|
|
|
- transition: color 0.2s;
|
|
|
+ .el-breadcrumb__item {
|
|
|
+ cursor: pointer;
|
|
|
+ flex-shrink: 0;
|
|
|
+ max-width: 120px;
|
|
|
+
|
|
|
+ .el-breadcrumb__inner {
|
|
|
+ display: inline-block;
|
|
|
+ max-width: 100px;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ vertical-align: middle;
|
|
|
+ transition: color 0.2s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ color: #409eff;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- &:hover {
|
|
|
- color: #409eff;
|
|
|
+ .el-breadcrumb__separator {
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.search-input {
|
|
|
- width: 220px;
|
|
|
+ width: 180px;
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
|
:deep(.el-input__wrapper) {
|
|
|
border-radius: 8px;
|
|
|
@@ -1883,8 +1930,7 @@ onUnmounted(() => {
|
|
|
|
|
|
.preview-container {
|
|
|
display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
+ flex-direction: column;
|
|
|
min-height: 400px;
|
|
|
background: #fafafa;
|
|
|
border-radius: 8px;
|
|
|
@@ -1893,8 +1939,7 @@ onUnmounted(() => {
|
|
|
.preview-image,
|
|
|
.preview-video,
|
|
|
.preview-audio,
|
|
|
- .preview-pdf,
|
|
|
- .preview-text {
|
|
|
+ .preview-pdf {
|
|
|
width: 100%;
|
|
|
display: flex;
|
|
|
justify-content: center;
|
|
|
@@ -1912,15 +1957,90 @@ onUnmounted(() => {
|
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
|
}
|
|
|
|
|
|
- .preview-text pre {
|
|
|
+ .preview-code {
|
|
|
width: 100%;
|
|
|
- white-space: pre-wrap;
|
|
|
- word-wrap: break-word;
|
|
|
- background: #ffffff;
|
|
|
- border-radius: 8px;
|
|
|
- font-family: 'Monaco', 'Menlo', monospace;
|
|
|
- font-size: 13px;
|
|
|
- line-height: 1.6;
|
|
|
+
|
|
|
+ .code-toolbar {
|
|
|
+ margin-bottom: 12px;
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .code-editor {
|
|
|
+ width: 100%;
|
|
|
+ height: 65vh;
|
|
|
+ padding: 16px;
|
|
|
+ font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
|
|
|
+ font-size: 13px;
|
|
|
+ line-height: 1.6;
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
+ border-radius: 8px;
|
|
|
+ resize: none;
|
|
|
+ outline: none;
|
|
|
+ background: #fff;
|
|
|
+
|
|
|
+ &:focus {
|
|
|
+ border-color: #409eff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .code-preview {
|
|
|
+ width: 100%;
|
|
|
+ max-height: 65vh;
|
|
|
+ overflow: auto;
|
|
|
+ margin: 0;
|
|
|
+ padding: 16px;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 8px;
|
|
|
+ border: 1px solid #eee;
|
|
|
+
|
|
|
+ code {
|
|
|
+ font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
|
|
|
+ font-size: 13px;
|
|
|
+ line-height: 1.6;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .preview-excel {
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ .excel-tabs {
|
|
|
+ margin-bottom: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .excel-table-wrapper {
|
|
|
+ max-height: 65vh;
|
|
|
+ overflow: auto;
|
|
|
+ border: 1px solid #eee;
|
|
|
+ border-radius: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .excel-table {
|
|
|
+ width: 100%;
|
|
|
+ border-collapse: collapse;
|
|
|
+ background: #fff;
|
|
|
+ font-size: 13px;
|
|
|
+
|
|
|
+ th, td {
|
|
|
+ padding: 8px 12px;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ text-align: left;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ th {
|
|
|
+ background: #f5f7fa;
|
|
|
+ font-weight: 600;
|
|
|
+ position: sticky;
|
|
|
+ top: 0;
|
|
|
+ z-index: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ tr:hover td {
|
|
|
+ background: #f5f7fa;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -1988,34 +2108,6 @@ onUnmounted(() => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// 文件夹树选择
|
|
|
-.folder-tree-node {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 8px;
|
|
|
-
|
|
|
- .el-icon {
|
|
|
- font-size: 18px;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-:deep(.el-tree) {
|
|
|
- .el-tree-node__content {
|
|
|
- height: 36px;
|
|
|
- border-radius: 6px;
|
|
|
- margin: 2px 0;
|
|
|
-
|
|
|
- &:hover {
|
|
|
- background: #f5f7fa;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .el-tree-node.is-current > .el-tree-node__content {
|
|
|
- background: #ecf5ff;
|
|
|
- color: #409eff;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
// 分享结果
|
|
|
.share-result {
|
|
|
.share-tip {
|