mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2026-02-03 20:06:18 +00:00
优化构建流v0.1.9b6b
This commit is contained in:
993
web-front/src/components/DirectoryTree.vue
Normal file
993
web-front/src/components/DirectoryTree.vue
Normal file
@@ -0,0 +1,993 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user