mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2025-12-17 12:53:02 +00:00
993 lines
23 KiB
Vue
993 lines
23 KiB
Vue
<template>
|
||
<div class="main-container">
|
||
<div class="directory-tree" :class="{ 'dark-theme': isDarkTheme }">
|
||
<template v-if="viewMode === 'pane'">
|
||
<!-- 窗格模式(原有) -->
|
||
<div class="breadcrumb">
|
||
<div
|
||
v-for="(item, index) in pathStack"
|
||
:key="index"
|
||
class="breadcrumb-item"
|
||
:class="{ 'active': index === pathStack.length - 1 }"
|
||
@click="goToDirectory(index)"
|
||
>
|
||
<i class="fas fa-folder" v-if="index === 0"></i>
|
||
<i class="fas fa-chevron-right" v-else-if="index > 0"></i>
|
||
{{ item.name }}
|
||
</div>
|
||
</div>
|
||
<div class="file-grid" v-loading="loading">
|
||
<div
|
||
v-for="file in currentFileList"
|
||
:key="file.fileName"
|
||
class="file-item"
|
||
:class="getFileTypeClass(file)"
|
||
@click="handleFileClick(file)"
|
||
>
|
||
<div class="file-icon">
|
||
<i :class="getFileIcon(file)"></i>
|
||
</div>
|
||
<div class="file-name">{{ file.fileName }}</div>
|
||
<div class="file-meta">
|
||
{{ file.sizeStr || '0B' }} · {{ formatDate(file.createTime) }}
|
||
</div>
|
||
</div>
|
||
<div v-if="!loading && (!currentFileList || currentFileList.length === 0)" class="empty-state">
|
||
<i class="fas fa-folder-open"></i>
|
||
<h3>此文件夹为空</h3>
|
||
<p>暂无文件或文件夹</p>
|
||
</div>
|
||
</div>
|
||
<div class="action-bar">
|
||
<el-button
|
||
type="primary"
|
||
@click="goBack"
|
||
:disabled="pathStack.length <= 1"
|
||
icon="el-icon-arrow-left"
|
||
>
|
||
返回上一级
|
||
</el-button>
|
||
<div class="stats">
|
||
<span class="stat-item">
|
||
<i class="fas fa-folder"></i> {{ folderCount }} 个文件夹
|
||
</span>
|
||
<span class="stat-item">
|
||
<i class="fas fa-file"></i> {{ fileCount }} 个文件
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<template v-else-if="viewMode === 'tree'">
|
||
<div class="content-card">
|
||
<splitpanes class="split-theme custom-splitpanes" style="height:100%;">
|
||
<pane>
|
||
<div class="tree-sidebar">
|
||
<el-tree
|
||
:data="treeData"
|
||
:props="treeProps"
|
||
node-key="id"
|
||
lazy
|
||
:load="loadNode"
|
||
highlight-current
|
||
@node-click="onNodeClick"
|
||
:default-expand-all="false"
|
||
:default-expanded-keys="['root']"
|
||
:render-content="renderContent"
|
||
style="background:transparent;"
|
||
/>
|
||
</div>
|
||
</pane>
|
||
<pane>
|
||
<div class="tree-content">
|
||
<div v-if="selectedNode">
|
||
<div class="file-detail-icon-wrap">
|
||
<i :class="getFileIcon(selectedNode)" class="file-detail-icon"></i>
|
||
</div>
|
||
<h4>{{ selectedNode.fileName }}</h4>
|
||
<div v-if="selectedNode.fileType === 'folder'">
|
||
<ul>
|
||
<li v-for="file in selectedNode.children || []" :key="file.id">
|
||
<i :class="getFileIcon(file)"></i> {{ file.fileName }}
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
<div v-else>
|
||
<p>类型: {{ getFileTypeClass(selectedNode) }}</p>
|
||
<p>大小: {{ selectedNode.sizeStr || '0B' }}</p>
|
||
<p>创建时间: {{ formatDate(selectedNode.createTime) }}</p>
|
||
<!-- 文件详情区下载按钮 -->
|
||
<el-button v-if="selectedNode && selectedNode.parserUrl" @click="previewFile(selectedNode)">打开</el-button>
|
||
<a
|
||
v-if="selectedNode && selectedNode.parserUrl"
|
||
:href="selectedNode.parserUrl"
|
||
download
|
||
target="_blank"
|
||
class="el-button el-button--success"
|
||
style="margin-left: 8px;"
|
||
>
|
||
下载
|
||
</a>
|
||
</div>
|
||
</div>
|
||
<div v-else style="color: #888;">请选择左侧文件或文件夹</div>
|
||
</div>
|
||
</pane>
|
||
</splitpanes>
|
||
</div>
|
||
</template>
|
||
<!-- 文件操作对话框(窗格模式下) -->
|
||
<el-dialog
|
||
v-if="viewMode === 'pane'"
|
||
title="文件操作"
|
||
v-model="fileDialogVisible"
|
||
width="400px"
|
||
:before-close="closeFileDialog"
|
||
>
|
||
<div class="file-dialog-content">
|
||
<p><strong>{{ selectedFile?.fileName || '未命名文件' }}</strong></p>
|
||
<p class="file-info">
|
||
大小: {{ selectedFile?.sizeStr || '0B' }}<br>
|
||
创建时间: {{ formatDate(selectedFile?.createTime) }}
|
||
</p>
|
||
</div>
|
||
|
||
<span slot="footer" class="dialog-footer">
|
||
<el-button type="primary" @click="previewFile(selectedFile)">打开</el-button>
|
||
<!-- 弹窗下载按钮 -->
|
||
<a
|
||
v-if="selectedFile && selectedFile.parserUrl"
|
||
:href="selectedFile.parserUrl"
|
||
download
|
||
target="_blank"
|
||
class="el-button el-button--success"
|
||
style="margin-left: 8px;"
|
||
>
|
||
下载
|
||
</a>
|
||
</span>
|
||
</el-dialog>
|
||
<div v-if="isPreviewing" class="preview-mask">
|
||
<div class="preview-toolbar">
|
||
<el-button size="small" @click="closePreview">关闭预览</el-button>
|
||
<el-button size="small" type="primary" @click="openPreviewInNewTab">新窗口打开</el-button>
|
||
</div>
|
||
<iframe :src="previewUrl" frameborder="0" class="preview-iframe"></iframe>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import axios from 'axios'
|
||
import { ElTree } from 'element-plus'
|
||
import { Splitpanes, Pane } from 'splitpanes'
|
||
import 'splitpanes/dist/splitpanes.css'
|
||
import fileTypeUtils from '@/utils/fileTypeUtils'
|
||
|
||
export default {
|
||
name: 'DirectoryTree',
|
||
components: { ElTree, Splitpanes, Pane },
|
||
props: {
|
||
fileList: {
|
||
type: Array,
|
||
default: () => []
|
||
},
|
||
shareUrl: {
|
||
type: String,
|
||
required: true
|
||
},
|
||
password: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
viewMode: {
|
||
type: String,
|
||
default: 'pane' // 'pane' or 'tree'
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
loading: false,
|
||
pathStack: [{ name: '全部文件', url: '' }],
|
||
currentFileList: [],
|
||
fileDialogVisible: false,
|
||
selectedFile: null,
|
||
isDarkTheme: false,
|
||
initialized: false,
|
||
// 文件树模式相关
|
||
treeData: [],
|
||
selectedNode: null,
|
||
isPreviewing: false,
|
||
previewUrl: '',
|
||
treeProps: {
|
||
label: 'fileName',
|
||
children: 'children',
|
||
isLeaf: 'isLeaf'
|
||
}
|
||
}
|
||
},
|
||
computed: {
|
||
folderCount() {
|
||
return this.currentFileList.filter(file => file.fileType === 'folder').length
|
||
},
|
||
fileCount() {
|
||
return this.currentFileList.filter(file => file.fileType !== 'folder').length
|
||
}
|
||
},
|
||
watch: {
|
||
fileList: {
|
||
immediate: true,
|
||
handler(newList) {
|
||
// 根节点children为当前目录下所有文件/文件夹
|
||
this.treeData = [
|
||
{
|
||
id: 'root',
|
||
fileName: '全部文件',
|
||
fileType: 'folder',
|
||
children: (newList || []).map(item => ({
|
||
...item,
|
||
isLeaf: item.fileType !== 'folder'
|
||
})),
|
||
isLeaf: false
|
||
}
|
||
]
|
||
this.currentFileList = newList
|
||
}
|
||
}
|
||
},
|
||
methods: {
|
||
...fileTypeUtils,
|
||
// 构建API URL
|
||
buildApiUrl() {
|
||
const baseUrl = `${window.location.origin}/v2/getFileList`
|
||
const params = new URLSearchParams({
|
||
url: this.shareUrl
|
||
})
|
||
if (this.password) {
|
||
params.append('pwd', this.password)
|
||
}
|
||
return `${baseUrl}?${params.toString()}`
|
||
},
|
||
// 文件树与窗格同源:直接返回当前目录数据
|
||
buildTree(list) {
|
||
return list || []
|
||
},
|
||
// 懒加载子节点
|
||
loadNode(node, resolve) {
|
||
if (node.level === 0) {
|
||
// 根节点
|
||
resolve(this.treeData[0].children)
|
||
} else if (node.data.fileType === 'folder' && node.data.parserUrl) {
|
||
axios.get(node.data.parserUrl).then(res => {
|
||
if (res.data.code === 200) {
|
||
const children = (res.data.data || []).map(item => ({
|
||
...item,
|
||
isLeaf: item.fileType !== 'folder'
|
||
}))
|
||
resolve(children)
|
||
} else {
|
||
resolve([])
|
||
}
|
||
}).catch(() => resolve([]))
|
||
} else {
|
||
resolve([])
|
||
}
|
||
},
|
||
onNodeClick(data) {
|
||
this.selectedNode = data
|
||
},
|
||
// 处理文件点击
|
||
handleFileClick(file) {
|
||
console.log('点击文件', file, this.viewMode)
|
||
if (file.fileType === 'folder') {
|
||
this.enterFolder(file)
|
||
} else if (this.viewMode === 'pane') {
|
||
this.selectedFile = file
|
||
this.fileDialogVisible = true
|
||
}
|
||
},
|
||
// 进入文件夹
|
||
async enterFolder(folder) {
|
||
if (!folder.parserUrl) {
|
||
this.$message.error('无法进入该文件夹,缺少访问链接')
|
||
return
|
||
}
|
||
try {
|
||
this.loading = true
|
||
const response = await axios.get(folder.parserUrl)
|
||
if (response.data.code === 200) {
|
||
const newDir = {
|
||
url: folder.parserUrl,
|
||
name: folder.fileName || '未命名文件夹'
|
||
}
|
||
this.pathStack.push(newDir)
|
||
this.currentFileList = response.data.data || []
|
||
} else {
|
||
this.$message.error(response.data.msg || '获取文件夹内容失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('进入文件夹失败:', error)
|
||
this.$message.error('进入文件夹失败')
|
||
} finally {
|
||
this.loading = false
|
||
}
|
||
},
|
||
goBack() {
|
||
if (this.pathStack.length > 1) {
|
||
this.pathStack.pop()
|
||
this.loadCurrentDirectory()
|
||
}
|
||
},
|
||
goToDirectory(index) {
|
||
this.pathStack.splice(index + 1)
|
||
this.loadCurrentDirectory()
|
||
},
|
||
async loadCurrentDirectory() {
|
||
const currentDir = this.pathStack[this.pathStack.length - 1]
|
||
if (!currentDir.url) {
|
||
this.currentFileList = this.fileList
|
||
return
|
||
}
|
||
try {
|
||
this.loading = true
|
||
const response = await axios.get(currentDir.url)
|
||
if (response.data.code === 200) {
|
||
this.currentFileList = response.data.data || []
|
||
} else {
|
||
this.$message.error(response.data.msg || '加载目录失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('加载目录失败:', error)
|
||
this.$message.error('加载目录失败')
|
||
} finally {
|
||
this.loading = false
|
||
}
|
||
},
|
||
// 预览文件
|
||
previewFile(file) {
|
||
if (file?.previewUrl || file?.parserUrl) {
|
||
this.previewUrl = file.previewUrl || file.parserUrl
|
||
this.isPreviewing = true
|
||
} else {
|
||
this.$message.warning('该文件暂无预览链接')
|
||
}
|
||
this.closeFileDialog()
|
||
},
|
||
// 下载文件
|
||
downloadFile(file) {
|
||
if (file?.parserUrl) {
|
||
const iframe = document.createElement('iframe')
|
||
iframe.style.display = 'none'
|
||
iframe.src = file.parserUrl
|
||
document.body.appendChild(iframe)
|
||
setTimeout(() => {
|
||
document.body.removeChild(iframe)
|
||
}, 1000)
|
||
this.$message.success('开始下载文件')
|
||
} else {
|
||
this.$message.warning('该文件暂无下载链接')
|
||
}
|
||
this.closeFileDialog()
|
||
},
|
||
closeFileDialog() {
|
||
this.fileDialogVisible = false
|
||
this.selectedFile = null
|
||
},
|
||
closePreview() {
|
||
this.isPreviewing = false
|
||
this.previewUrl = ''
|
||
},
|
||
openPreviewInNewTab() {
|
||
if (this.previewUrl) {
|
||
window.open(this.previewUrl, '_blank')
|
||
}
|
||
},
|
||
formatDate(timestamp) {
|
||
if (!timestamp) return '未知时间'
|
||
const date = new Date(timestamp)
|
||
return date.toLocaleString('zh-CN')
|
||
},
|
||
checkTheme() {
|
||
this.isDarkTheme = document.body.classList.contains('dark-theme') ||
|
||
document.documentElement.classList.contains('dark-theme')
|
||
},
|
||
renderContent(h, { node, data, store }) {
|
||
const isFolder = data.fileType === 'folder'
|
||
return h('div', {
|
||
class: 'custom-tree-node'
|
||
}, [
|
||
h('i', {
|
||
class: [this.getFileIcon(data), { 'folder-icon': isFolder, 'file-icon': !isFolder }]
|
||
}),
|
||
h('span', {
|
||
class: ['node-label', { 'folder-text': isFolder, 'file-text': !isFolder }]
|
||
}, node.label)
|
||
])
|
||
}
|
||
},
|
||
mounted() {
|
||
this.checkTheme()
|
||
this.initialized = true
|
||
|
||
// 监听主题变化
|
||
this._observer = new MutationObserver(() => {
|
||
this.checkTheme()
|
||
})
|
||
this._observer.observe(document.body, {
|
||
attributes: true,
|
||
attributeFilter: ['class']
|
||
})
|
||
},
|
||
beforeUnmount() {
|
||
if (this._observer) {
|
||
this._observer.disconnect()
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
html, body, #app, .main-container, .directory-tree, .content-card {
|
||
/* overflow: hidden; */
|
||
/* overflow: auto; */
|
||
/* position: relative; */
|
||
}
|
||
.main-container {
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.directory-tree {
|
||
background: white;
|
||
border-radius: 12px;
|
||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
|
||
overflow: hidden;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.directory-tree.dark-theme {
|
||
background: #2d2d2d;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.breadcrumb {
|
||
background: #f8f9fa;
|
||
padding: 16px 24px;
|
||
border-bottom: 1px solid #eaeaea;
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.dark-theme .breadcrumb {
|
||
background: #404040;
|
||
border-bottom-color: #555555;
|
||
}
|
||
|
||
.breadcrumb-item {
|
||
display: flex;
|
||
align-items: center;
|
||
font-size: 0.95rem;
|
||
color: #7f8c8d;
|
||
cursor: pointer;
|
||
transition: color 0.2s;
|
||
margin-right: 8px;
|
||
}
|
||
|
||
.breadcrumb-item:hover {
|
||
color: #3498db;
|
||
}
|
||
|
||
.breadcrumb-item.active {
|
||
color: #2c3e50;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.dark-theme .breadcrumb-item.active {
|
||
color: #ffffff;
|
||
}
|
||
|
||
.breadcrumb-item i {
|
||
margin: 0 8px;
|
||
font-size: 0.8rem;
|
||
color: #bdc3c7;
|
||
}
|
||
|
||
.file-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||
gap: 12px;
|
||
padding: 12px;
|
||
min-height: 200px;
|
||
}
|
||
|
||
.file-item {
|
||
background: #fff;
|
||
border-radius: 10px;
|
||
overflow: hidden;
|
||
box-shadow: 0 3px 15px rgba(0, 0, 0, 0.05);
|
||
transition: all 0.3s ease;
|
||
cursor: pointer;
|
||
text-align: center;
|
||
padding: 10px 4px;
|
||
min-height: 80px;
|
||
border: 2px solid transparent;
|
||
}
|
||
|
||
.dark-theme .file-item {
|
||
background: #404040;
|
||
box-shadow: 0 3px 15px rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
.file-item:hover {
|
||
transform: translateY(-5px);
|
||
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
|
||
border-color: #3498db;
|
||
}
|
||
|
||
.dark-theme .file-item:hover {
|
||
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.file-icon {
|
||
font-size: 2.2rem;
|
||
margin-bottom: 8px;
|
||
transition: transform 0.3s;
|
||
}
|
||
|
||
.file-item:hover .file-icon {
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
.folder .file-icon {
|
||
color: #3498db;
|
||
}
|
||
|
||
.image .file-icon {
|
||
color: #e74c3c;
|
||
}
|
||
|
||
.document .file-icon {
|
||
color: #f39c12;
|
||
}
|
||
|
||
.archive .file-icon {
|
||
color: #9b59b6;
|
||
}
|
||
|
||
.audio .file-icon {
|
||
color: #1abc9c;
|
||
}
|
||
|
||
.video .file-icon {
|
||
color: #d35400;
|
||
}
|
||
|
||
.code .file-icon {
|
||
color: #27ae60;
|
||
}
|
||
|
||
.file-name {
|
||
font-weight: 500;
|
||
font-size: 0.85rem;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
line-height: 1.4;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.file-meta {
|
||
font-size: 0.75rem;
|
||
color: #95a5a6;
|
||
}
|
||
|
||
.dark-theme .file-meta {
|
||
color: #bdc3c7;
|
||
}
|
||
|
||
.empty-state {
|
||
text-align: center;
|
||
padding: 30px 10px;
|
||
color: #7f8c8d;
|
||
grid-column: 1 / -1;
|
||
}
|
||
|
||
.dark-theme .empty-state {
|
||
color: #bdc3c7;
|
||
}
|
||
|
||
.empty-state i {
|
||
font-size: 3rem;
|
||
margin-bottom: 10px;
|
||
color: #bdc3c7;
|
||
}
|
||
|
||
.dark-theme .empty-state i {
|
||
color: #555555;
|
||
}
|
||
|
||
.empty-state h3 {
|
||
font-size: 1.1rem;
|
||
margin-bottom: 6px;
|
||
color: #2c3e50;
|
||
}
|
||
|
||
.dark-theme .empty-state h3 {
|
||
color: #ffffff;
|
||
}
|
||
|
||
.action-bar {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: 10px 12px;
|
||
background: #f8f9fa;
|
||
border-top: 1px solid #eaeaea;
|
||
}
|
||
|
||
.dark-theme .action-bar {
|
||
background: #404040;
|
||
border-top-color: #555555;
|
||
}
|
||
|
||
.stats {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
color: #7f8c8d;
|
||
font-size: 0.85rem;
|
||
}
|
||
|
||
.dark-theme .stats {
|
||
color: #bdc3c7;
|
||
}
|
||
|
||
.stat-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
}
|
||
|
||
.file-dialog-content {
|
||
text-align: center;
|
||
}
|
||
|
||
.file-info {
|
||
color: #7f8c8d;
|
||
font-size: 0.9rem;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.dark-theme .file-info {
|
||
color: #bdc3c7;
|
||
}
|
||
|
||
.tree-layout {
|
||
display: flex;
|
||
height: 500px;
|
||
}
|
||
.tree-sidebar {
|
||
width: 220px;
|
||
background: #f8f9fa;
|
||
border-right: 1px solid #eaeaea;
|
||
overflow-y: auto;
|
||
}
|
||
.directory-tree.dark-theme .tree-sidebar {
|
||
background: #232323;
|
||
border-right: 1px solid #404040;
|
||
}
|
||
.file-tree-root, .tree-node ul {
|
||
list-style: none;
|
||
padding-left: 12px;
|
||
margin: 0;
|
||
}
|
||
.tree-node {
|
||
margin-bottom: 2px;
|
||
}
|
||
.tree-node.selected > .tree-node-label {
|
||
background: #e6f7ff;
|
||
color: #409eff;
|
||
}
|
||
.directory-tree.dark-theme .tree-node.selected > .tree-node-label {
|
||
background: #333c4d;
|
||
color: #4a9eff;
|
||
}
|
||
.tree-node-label {
|
||
cursor: pointer;
|
||
padding: 3px 6px;
|
||
border-radius: 4px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
transition: background 0.2s;
|
||
font-size: 0.95em;
|
||
}
|
||
.tree-content {
|
||
flex: 1;
|
||
padding: 16px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
/* 自定义树节点样式 */
|
||
.custom-tree-node {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 6px 0;
|
||
width: 100%;
|
||
}
|
||
|
||
.custom-tree-node i {
|
||
font-size: 16px;
|
||
width: 20px;
|
||
text-align: center;
|
||
color: #606266;
|
||
}
|
||
|
||
.dark-theme .custom-tree-node i {
|
||
color: #bdc3c7;
|
||
}
|
||
|
||
.custom-tree-node .node-label {
|
||
flex: 1;
|
||
font-size: 14px;
|
||
color: #303133;
|
||
}
|
||
|
||
.dark-theme .custom-tree-node .node-label {
|
||
color: #e1e1e1;
|
||
}
|
||
|
||
/* 文件夹样式 */
|
||
.custom-tree-node .folder-icon {
|
||
color: #409eff !important;
|
||
}
|
||
|
||
.dark-theme .custom-tree-node .folder-icon {
|
||
color: #4a9eff !important;
|
||
}
|
||
|
||
.custom-tree-node .folder-text {
|
||
color: #409eff !important;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.dark-theme .custom-tree-node .folder-text {
|
||
color: #4a9eff !important;
|
||
}
|
||
|
||
/* 文件样式 */
|
||
.custom-tree-node .file-icon {
|
||
color: #95a5a6 !important;
|
||
}
|
||
|
||
.dark-theme .custom-tree-node .file-icon {
|
||
color: #bdc3c7 !important;
|
||
}
|
||
|
||
.custom-tree-node .file-text {
|
||
color: #606266 !important;
|
||
}
|
||
|
||
.dark-theme .custom-tree-node .file-text {
|
||
color: #e1e1e1 !important;
|
||
}
|
||
|
||
/* 特殊文件类型图标颜色 */
|
||
.custom-tree-node i.fa-file-image {
|
||
color: #e74c3c !important;
|
||
}
|
||
|
||
.custom-tree-node i.fa-file-pdf {
|
||
color: #e74c3c !important;
|
||
}
|
||
|
||
.custom-tree-node i.fa-file-word {
|
||
color: #3498db !important;
|
||
}
|
||
|
||
.custom-tree-node i.fa-file-excel {
|
||
color: #27ae60 !important;
|
||
}
|
||
|
||
.custom-tree-node i.fa-file-powerpoint {
|
||
color: #f39c12 !important;
|
||
}
|
||
|
||
.custom-tree-node i.fa-file-archive {
|
||
color: #9b59b6 !important;
|
||
}
|
||
|
||
.custom-tree-node i.fa-file-audio {
|
||
color: #1abc9c !important;
|
||
}
|
||
|
||
.custom-tree-node i.fa-file-video {
|
||
color: #d35400 !important;
|
||
}
|
||
|
||
.custom-tree-node i.fa-file-code {
|
||
color: #27ae60 !important;
|
||
}
|
||
|
||
/* 树节点悬停效果 */
|
||
.el-tree-node__content:hover .custom-tree-node {
|
||
background-color: #f5f7fa;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.dark-theme .el-tree-node__content:hover .custom-tree-node {
|
||
background-color: #2c2c2c;
|
||
}
|
||
|
||
/* 选中节点样式 */
|
||
.el-tree-node.is-current > .el-tree-node__content .custom-tree-node {
|
||
background-color: #e6f7ff;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.dark-theme .el-tree-node.is-current > .el-tree-node__content .custom-tree-node {
|
||
background-color: #333c4d;
|
||
}
|
||
|
||
.preview-mask { position: fixed; z-index: 9999; left: 0; top: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.85); display: flex; flex-direction: column; }
|
||
.preview-toolbar { padding: 12px; background: #232323; text-align: right; }
|
||
.preview-iframe { flex: 1; width: 100vw; border: none; background: #222; }
|
||
|
||
.content-card {
|
||
min-height: 500px;
|
||
height: 100%;
|
||
background: #fff;
|
||
border-radius: 12px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
|
||
margin: 0 0 12px 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.dark-theme .content-card {
|
||
background: #232323;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.18);
|
||
}
|
||
|
||
.split-theme {
|
||
flex: 1 1 0;
|
||
height: 100%;
|
||
}
|
||
|
||
.tree-sidebar, .tree-content {
|
||
width: 100%;
|
||
height: 100%;
|
||
min-height: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: auto;
|
||
}
|
||
|
||
.tree-content {
|
||
padding: 40px 16px 16px 16px;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.file-detail-icon-wrap {
|
||
width: 100%;
|
||
display: flex;
|
||
justify-content: center;
|
||
margin-bottom: 12px;
|
||
}
|
||
.file-detail-icon {
|
||
font-size: 48px;
|
||
color: #409eff;
|
||
display: block;
|
||
}
|
||
.dark-theme .file-detail-icon {
|
||
color: #4a9eff;
|
||
}
|
||
|
||
/* splitpanes 拖拽条自定义按钮 */
|
||
.custom-splitpanes .splitpanes__splitter {
|
||
position: relative;
|
||
background: #e0e0e0;
|
||
transition: background 0.2s;
|
||
touch-action: pan-x pan-y;
|
||
}
|
||
.custom-splitpanes .splitpanes__splitter:hover {
|
||
background: #b3b3b3;
|
||
}
|
||
.custom-splitpanes .splitpanes__splitter:after {
|
||
content: '';
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 50%;
|
||
transform: translate(-50%, -50%);
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: 50%;
|
||
background: #fff;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||
border: 1.5px solid #d0d0d0;
|
||
z-index: 2;
|
||
display: block;
|
||
}
|
||
.dark-theme .custom-splitpanes .splitpanes__splitter:after {
|
||
background: #232323;
|
||
border-color: #444;
|
||
}
|
||
|
||
.feedback-bar {
|
||
width: 100%;
|
||
text-align: right;
|
||
padding: 12px 18px 0 0;
|
||
}
|
||
.feedback-link {
|
||
color: #e74c3c;
|
||
font-weight: bold;
|
||
font-size: 1.08rem;
|
||
text-decoration: none;
|
||
border: 1px solid #e74c3c;
|
||
border-radius: 6px;
|
||
padding: 4px 14px;
|
||
background: #fff5f5;
|
||
transition: background 0.2s, color 0.2s;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
margin-left: 10px;
|
||
}
|
||
.feedback-link:first-child { margin-left: 0; }
|
||
.feedback-link:hover {
|
||
background: #e74c3c;
|
||
color: #fff;
|
||
}
|
||
.dark-theme .feedback-link {
|
||
background: #2d2d2d;
|
||
color: #ff7675;
|
||
border-color: #ff7675;
|
||
}
|
||
.dark-theme .feedback-link:hover {
|
||
background: #ff7675;
|
||
color: #232323;
|
||
}
|
||
.feedback-icon {
|
||
font-size: 1.15em;
|
||
color: #e74c3c;
|
||
margin-right: 2px;
|
||
}
|
||
.feedback-link:hover .feedback-icon {
|
||
color: #fff;
|
||
}
|
||
.feedback-link:nth-child(2) .feedback-icon { color: #333; }
|
||
.feedback-link:nth-child(3) .feedback-icon { color: #f39c12; }
|
||
.dark-theme .feedback-icon {
|
||
color: #ff7675;
|
||
}
|
||
.dark-theme .feedback-link:nth-child(2) .feedback-icon { color: #fff; }
|
||
.dark-theme .feedback-link:nth-child(3) .feedback-icon { color: #f7ca77; }
|
||
|
||
@media (max-width: 768px) {
|
||
.file-grid {
|
||
grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
|
||
gap: 8px;
|
||
padding: 6px;
|
||
}
|
||
.file-item {
|
||
padding: 6px 2px;
|
||
min-height: 60px;
|
||
}
|
||
.file-icon {
|
||
font-size: 1.5rem;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 480px) {
|
||
.file-grid {
|
||
grid-template-columns: repeat(auto-fill, minmax(70px, 1fr));
|
||
gap: 4px;
|
||
}
|
||
.action-bar {
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
}
|
||
}
|
||
</style> |