码云:一个简单的网盘项目 · jiongyou/jsDemo - 码云 - 开源中国
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue网盘系统 - 增强版</title>
<!-- 引入Element UI样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入Vue和Element UI -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<!-- 引入图标 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: #f5f7fa;
color: #333;
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;
}
.cloud-app {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.app-header {
background: linear-gradient(135deg, #3a7bd5, #00d2ff);
color: white;
padding: 15px 30px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.logo {
display: flex;
align-items: center;
font-size: 22px;
font-weight: bold;
}
.logo i {
margin-right: 10px;
font-size: 28px;
}
.user-info {
display: flex;
align-items: center;
}
.user-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
margin-left: 15px;
font-weight: bold;
}
.clipboard-status {
background-color: #e6f7ff;
padding: 8px 15px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #91d5ff;
}
.clipboard-info {
display: flex;
align-items: center;
}
.clipboard-actions {
display: flex;
gap: 10px;
}
.downloads-panel {
background-color: #f5f7fa;
padding: 10px 15px;
border-bottom: 1px solid #ebeef5;
max-height: 200px;
overflow-y: auto;
}
.download-item {
display: flex;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #ebeef5;
}
.download-item:last-child {
border-bottom: none;
}
.download-icon {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 10px;
border-radius: 4px;
background-color: #e6f7ff;
color: #3a7bd5;
}
.download-info {
flex: 1;
}
.download-name {
font-size: 14px;
margin-bottom: 4px;
}
.download-status {
font-size: 12px;
color: #909399;
}
.download-progress {
width: 100px;
margin-left: 10px;
}
.download-actions {
margin-left: 10px;
}
.main-container {
display: flex;
flex: 1;
}
.sidebar {
width: 220px;
background-color: #304156;
color: #fff;
padding: 20px 0;
}
.menu-item {
padding: 14px 20px;
cursor: pointer;
transition: background-color 0.3s;
display: flex;
align-items: center;
}
.menu-item i {
margin-right: 10px;
width: 20px;
text-align: center;
}
.menu-item:hover, .menu-item.active {
background-color: #263445;
}
.content {
flex: 1;
padding: 20px;
overflow-y: auto;
}
.action-bar {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
align-items: center;
}
.breadcrumb {
margin-bottom: 20px;
padding: 15px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.file-list {
background-color: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.file-item {
display: flex;
align-items: center;
padding: 12px 20px;
border-bottom: 1px solid #ebeef5;
cursor: pointer;
transition: background-color 0.3s;
position: relative;
}
.file-item:hover {
background-color: #f5f7fa;
}
.file-item.selected {
background-color: #ecf5ff;
}
.file-icon {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 15px;
border-radius: 4px;
}
.folder-icon {
background-color: #ffe7cc;
color: #ff9426;
}
.file-info {
flex: 1;
}
.file-name {
font-weight: 500;
margin-bottom: 5px;
}
.file-meta {
font-size: 12px;
color: #909399;
}
.file-actions {
display: flex;
gap: 10px;
opacity: 0;
transition: opacity 0.3s;
}
.file-item:hover .file-actions {
opacity: 1;
}
.action-btn {
padding: 5px;
border-radius: 4px;
cursor: pointer;
color: #606266;
}
.action-btn:hover {
background-color: #f5f7fa;
color: #3a7bd5;
}
.upload-area {
margin-top: 30px;
border: 2px dashed #dcdfe6;
border-radius: 6px;
padding: 30px;
text-align: center;
background-color: rgba(58, 123, 213, 0.03);
transition: all 0.3s;
}
.upload-area:hover {
border-color: #3a7bd5;
background-color: rgba(58, 123, 213, 0.08);
}
.upload-icon {
font-size: 48px;
color: #3a7bd5;
margin-bottom: 15px;
}
.upload-text {
margin-bottom: 20px;
}
.upload-text h3 {
margin-bottom: 8px;
color: #3a7bd5;
}
.upload-text p {
color: #909399;
font-size: 14px;
}
.empty-state {
text-align: center;
padding: 40px;
color: #909399;
}
.empty-icon {
font-size: 60px;
margin-bottom: 20px;
color: #dcdfe6;
}
.storage-info {
margin-top: 20px;
padding: 20px;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 4px;
margin: 20px;
}
.storage-title {
margin-bottom: 10px;
font-size: 14px;
}
.storage-progress {
margin-bottom: 10px;
}
.storage-size {
font-size: 12px;
}
.context-menu {
position: fixed;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
z-index: 1000;
padding: 5px 0;
}
.context-menu-item {
padding: 8px 20px;
cursor: pointer;
display: flex;
align-items: center;
}
.context-menu-item i {
margin-right: 10px;
width: 16px;
text-align: center;
}
.context-menu-item:hover {
background-color: #f5f7fa;
color: #3a7bd5;
}
.selection-actions {
background-color: #ecf5ff;
padding: 10px 15px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #b3d8ff;
}
.selection-count {
display: flex;
align-items: center;
}
.selection-buttons {
display: flex;
gap: 10px;
}
</style>
</head>
<body>
<div id="app" class="cloud-app">
<!-- 顶部导航 -->
<header class="app-header">
<div class="logo">
<i class="fas fa-cloud"></i>
<span>云端网盘</span>
</div>
<div class="user-info">
<i class="fas fa-bell"></i>
<div class="user-avatar">JS</div>
</div>
</header>
<!-- 剪贴板状态栏 -->
<div class="clipboard-status" v-if="clipboard.item">
<div class="clipboard-info">
<i class="fas fa-clipboard" style="margin-right: 10px;"></i>
<span>已{{ clipboard.operation === 'copy' ? '复制' : '移动' }}: {{ clipboard.item.name }}</span>
</div>
<div class="clipboard-actions">
<el-button size="mini" @click="pasteItem">粘贴到当前文件夹</el-button>
<el-button size="mini" @click="clearClipboard">取消</el-button>
</div>
</div>
<!-- 下载面板 -->
<div class="downloads-panel" v-if="downloads.length > 0">
<div class="download-item" v-for="download in downloads" :key="download.id">
<div class="download-icon">
<i :class="getFileIcon(download)"></i>
</div>
<div class="download-info">
<div class="download-name">{{ download.name }}</div>
<div class="download-status">
<span v-if="download.status === 'downloading'">下载中...</span>
<span v-else-if="download.status === 'completed'">下载完成</span>
<span v-else-if="download.status === 'error'">下载失败</span>
<span v-else>等待下载</span>
</div>
</div>
<div class="download-progress">
<el-progress
:percentage="download.progress"
:status="download.status === 'error' ? 'exception' : (download.status === 'completed' ? 'success' : '')"
:show-text="false"
v-if="download.status !== 'completed' && download.status !== 'error'">
</el-progress>
<span v-else>{{ download.status === 'completed' ? '完成' : '失败' }}</span>
</div>
<div class="download-actions">
<i class="fas fa-times" v-if="download.status !== 'downloading'" @click="removeDownload(download.id)" style="cursor: pointer;"></i>
</div>
</div>
</div>
<!-- 选择操作栏 -->
<div class="selection-actions" v-if="selectedItems.length > 0">
<div class="selection-count">
<i class="fas fa-check-circle" style="color: #3a7bd5; margin-right: 8px;"></i>
<span>已选择 {{ selectedItems.length }} 个项目</span>
</div>
<div class="selection-buttons">
<el-button size="mini" @click="downloadSelected">下载所选</el-button>
<el-button size="mini" @click="clearSelection">取消选择</el-button>
</div>
</div>
<div class="main-container">
<!-- 侧边栏 -->
<div class="sidebar">
<div class="menu-item active">
<i class="fas fa-home"></i>
<span>首页</span>
</div>
<div class="menu-item">
<i class="fas fa-file"></i>
<span>我的文件</span>
</div>
<div class="menu-item">
<i class="fas fa-share-alt"></i>
<span>共享</span>
</div>
<div class="menu-item">
<i class="fas fa-clock"></i>
<span>最近</span>
</div>
<div class="menu-item">
<i class="fas fa-star"></i>
<span>收藏</span>
</div>
<div class="menu-item">
<i class="fas fa-trash"></i>
<span>回收站</span>
</div>
<div class="menu-item">
<i class="fas fa-cog"></i>
<span>设置</span>
</div>
<div class="storage-info">
<div class="storage-title">存储空间</div>
<el-progress class="storage-progress" :percentage="65" :show-text="false"></el-progress>
<div class="storage-size">6.5 GB / 10 GB 已使用</div>
</div>
</div>
<!-- 主内容区 -->
<div class="content">
<!-- 面包屑导航 -->
<el-breadcrumb class="breadcrumb" separator="/">
<el-breadcrumb-item v-for="(item, index) in breadcrumb" :key="index">
<a href="#" @click.prevent="navigateTo(item.id)">{{ item.name }}</a>
</el-breadcrumb-item>
</el-breadcrumb>
<!-- 操作栏 -->
<div class="action-bar">
<h2 v-if="currentFolderId === 0">我的文件夹</h2>
<h2 v-else>{{ currentFolderName }}</h2>
<div>
<el-button type="primary" icon="el-icon-folder-add" @click="createFolder">新建文件夹</el-button>
<el-button icon="el-icon-upload" @click="uploadFile">上传文件</el-button>
<el-button icon="el-icon-document-copy" :disabled="!clipboard.item" @click="pasteItem">粘贴</el-button>
<el-button icon="el-icon-download" @click="downloadAllVisible">下载全部</el-button>
</div>
</div>
<!-- 文件列表 -->
<div class="file-list">
<!-- 文件夹列表 -->
<div v-if="folders.length > 0">
<div class="file-item" v-for="folder in folders" :key="'folder-'+folder.id"
@click="toggleSelect(folder, 'folder')"
@dblclick="enterFolder(folder)"
@contextmenu.prevent="showContextMenu($event, folder, 'folder')"
:class="{ selected: isSelected(folder.id, 'folder') }">
<div class="file-icon folder-icon">
<i class="fas fa-folder"></i>
</div>
<div class="file-info">
<div class="file-name">{{ folder.name }}</div>
<div class="file-meta">{{ folder.items }}个项目 · {{ folder.createdAt }}</div>
</div>
<div class="file-actions">
<div class="action-btn" @click.stop="copyItem(folder, 'folder')">
<i class="fas fa-copy"></i>
</div>
<div class="action-btn" @click.stop="cutItem(folder, 'folder')">
<i class="fas fa-cut"></i>
</div>
<div class="action-btn" @click.stop="downloadItem(folder, 'folder')">
<i class="fas fa-download"></i>
</div>
<div class="action-btn" @click.stop="renameFolder(folder)">
<i class="fas fa-pen"></i>
</div>
<div class="action-btn" @click.stop="deleteFolder(folder)">
<i class="fas fa-trash"></i>
</div>
</div>
</div>
</div>
<!-- 文件列表 -->
<div v-if="files.length > 0">
<div class="file-item" v-for="file in files" :key="'file-'+file.id"
@click="toggleSelect(file, 'file')"
@contextmenu.prevent="showContextMenu($event, file, 'file')"
:class="{ selected: isSelected(file.id, 'file') }">
<div class="file-icon" :class="getFileIconClass(file)">
<i :class="getFileIcon(file)"></i>
</div>
<div class="file-info">
<div class="file-name">{{ file.name }}</div>
<div class="file-meta">{{ file.size }} · {{ file.createdAt }}</div>
</div>
<div class="file-actions">
<div class="action-btn" @click.stop="downloadItem(file, 'file')">
<i class="fas fa-download"></i>
</div>
<div class="action-btn" @click.stop="copyItem(file, 'file')">
<i class="fas fa-copy"></i>
</div>
<div class="action-btn" @click.stop="cutItem(file, 'file')">
<i class="fas fa-cut"></i>
</div>
<div class="action-btn" @click.stop="renameFile(file)">
<i class="fas fa-pen"></i>
</div>
<div class="action-btn" @click.stop="deleteFile(file)">
<i class="fas fa-trash"></i>
</div>
</div>
</div>
</div>
<!-- 空状态 -->
<div class="empty-state" v-if="folders.length === 0 && files.length === 0">
<div class="empty-icon">
<i class="fas fa-folder-open"></i>
</div>
<h3>当前文件夹为空</h3>
<p>上传文件或创建新文件夹</p>
</div>
</div>
<!-- 上传区域 -->
<div class="upload-area" @drop="onDrop" @dragover="onDragOver" @dragleave="onDragLeave">
<div class="upload-icon">
<i class="fas fa-cloud-upload-alt"></i>
</div>
<div class="upload-text">
<h3>拖放文件到此处上传</h3>
<p>支持单个或多个文件上传,最大文件大小2GB</p>
</div>
<el-button type="primary" icon="el-icon-upload" @click="uploadFile">选择文件</el-button>
<input type="file" ref="fileInput" style="display: none" @change="onFileSelected" multiple>
</div>
</div>
</div>
<!-- 上下文菜单 -->
<div class="context-menu" v-if="contextMenu.visible" :style="{ top: contextMenu.y + 'px', left: contextMenu.x + 'px' }">
<div class="context-menu-item" @click="downloadItem(contextMenu.item, contextMenu.type)">
<i class="fas fa-download"></i>
<span>下载</span>
</div>
<div class="context-menu-item" @click="copyItem(contextMenu.item, contextMenu.type)">
<i class="fas fa-copy"></i>
<span>复制</span>
</div>
<div class="context-menu-item" @click="cutItem(contextMenu.item, contextMenu.type)">
<i class="fas fa-cut"></i>
<span>剪切</span>
</div>
<div class="context-menu-item" @click="pasteItem" v-if="clipboard.item">
<i class="fas fa-paste"></i>
<span>粘贴</span>
</div>
<el-divider></el-divider>
<div class="context-menu-item" @click="contextMenu.type === 'folder' ? renameFolder(contextMenu.item) : renameFile(contextMenu.item)">
<i class="fas fa-pen"></i>
<span>重命名</span>
</div>
<div class="context-menu-item" @click="contextMenu.type === 'folder' ? deleteFolder(contextMenu.item) : deleteFile(contextMenu.item)">
<i class="fas fa-trash"></i>
<span>删除</span>
</div>
</div>
<!-- 选择目标文件夹对话框 -->
<el-dialog :title="'选择目标文件夹 - ' + (clipboard.operation === 'copy' ? '复制' : '移动')" :visible.sync="targetFolderDialogVisible" width="50%">
<el-tree
:data="folderTree"
:props="treeProps"
node-key="id"
default-expand-all
:expand-on-click-node="false"
@node-click="handleFolderSelect"
:highlight-current="true">
<span class="custom-tree-node" slot-scope="{ node, data }">
<i class="fas fa-folder" style="color: #ff9426; margin-right: 8px;"></i>
<span>{{ node.label }}</span>
</span>
</el-tree>
<span slot="footer" class="dialog-footer">
<el-button @click="targetFolderDialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmTargetFolder">确定</el-button>
</span>
</el-dialog>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
// 当前文件夹ID(0表示根目录)
currentFolderId: 0,
currentFolderName: '',
// 面包屑导航
breadcrumb: [{ id: 0, name: '全部文件' }],
// 文件夹数据
folders: [],
// 文件数据
files: [],
// 剪贴板状态
clipboard: {
item: null,
type: null,
operation: null, // 'copy' or 'cut'
originalParentId: null
},
// 上下文菜单
contextMenu: {
visible: false,
x: 0,
y: 0,
item: null,
type: null
},
// 目标文件夹对话框
targetFolderDialogVisible: false,
selectedTargetFolder: null,
folderTree: [],
treeProps: {
children: 'children',
label: 'name'
},
// 下载相关
downloads: [],
// 选择相关
selectedItems: []
};
},
mounted() {
this.loadAllData();
this.loadFolderContent();
// 点击其他地方关闭上下文菜单
document.addEventListener('click', () => {
this.contextMenu.visible = false;
});
// 加载下载历史
this.loadDownloadHistory();
},
methods: {
// 加载所有数据
loadAllData() {
this.allFolders = JSON.parse(localStorage.getItem('cloudFolders')) || [
{ id: 1, name: '工作文档', items: 5, createdAt: '2023-06-15', parentId: 0 },
{ id: 2, name: '个人照片', items: 23, createdAt: '2023-05-22', parentId: 0 },
{ id: 3, name: '学习资料', items: 12, createdAt: '2023-07-10', parentId: 0 },
{ id: 4, name: '项目文件', items: 8, createdAt: '2023-08-01', parentId: 1 },
{ id: 5, name: '会议记录', items: 3, createdAt: '2023-08-05', parentId: 1 }
];
this.allFiles = JSON.parse(localStorage.getItem('cloudFiles')) || [
{ id: 1, name: '项目报告.pdf', size: '2.4 MB', type: 'pdf', createdAt: '2023-08-05', parentId: 0 },
{ id: 2, name: 'vacation.jpg', size: '4.2 MB', type: 'image', createdAt: '2023-08-10', parentId: 0 },
{ id: 3, name: '论文草稿.docx', size: '1.8 MB', type: 'doc', createdAt: '2023-08-12', parentId: 0 },
{ id: 4, name: '演示视频.mp4', size: '86.5 MB', type: 'video', createdAt: '2023-08-15', parentId: 0 },
{ id: 5, name: 'Q3规划.xlsx', size: '0.8 MB', type: 'excel', createdAt: '2023-08-18', parentId: 1 },
{ id: 6, name: '需求文档.pdf', size: '3.1 MB', type: 'pdf', createdAt: '2023-08-20', parentId: 4 }
];
// 构建文件夹树
this.buildFolderTree();
},
// 构建文件夹树
buildFolderTree() {
// 复制文件夹数据
const folders = JSON.parse(JSON.stringify(this.allFolders));
// 创建根节点
const rootFolders = folders.filter(f => f.parentId === 0);
// 递归构建树
const buildTree = (parentId) => {
const children = folders.filter(f => f.parentId === parentId);
for (const child of children) {
child.children = buildTree(child.id);
}
return children;
};
this.folderTree = buildTree(0);
},
// 加载当前文件夹内容
loadFolderContent() {
this.folders = this.allFolders.filter(f => f.parentId === this.currentFolderId);
this.files = this.allFiles.filter(f => f.parentId === this.currentFolderId);
// 更新当前文件夹名称
if (this.currentFolderId !== 0) {
const folder = this.allFolders.find(f => f.id === this.currentFolderId);
this.currentFolderName = folder ? folder.name : '';
}
// 清空选择
this.selectedItems = [];
},
// 进入文件夹
enterFolder(folder) {
this.currentFolderId = folder.id;
this.breadcrumb.push({ id: folder.id, name: folder.name });
this.loadFolderContent();
},
// 导航到指定文件夹
navigateTo(folderId) {
const index = this.breadcrumb.findIndex(item => item.id === folderId);
if (index !== -1) {
this.breadcrumb = this.breadcrumb.slice(0, index + 1);
this.currentFolderId = folderId;
this.loadFolderContent();
}
},
// 创建文件夹
createFolder() {
this.$prompt('请输入文件夹名称', '创建文件夹', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /.+/,
inputErrorMessage: '文件夹名称不能为空'
}).then(({ value }) => {
const newFolder = {
id: Date.now(),
name: value,
items: 0,
createdAt: new Date().toLocaleDateString(),
parentId: this.currentFolderId
};
// 保存到本地存储
this.allFolders.push(newFolder);
localStorage.setItem('cloudFolders', JSON.stringify(this.allFolders));
// 更新当前视图
this.folders = this.allFolders.filter(f => f.parentId === this.currentFolderId);
this.$message({
type: 'success',
message: '文件夹创建成功'
});
}).catch(() => {
// 用户取消操作
});
},
// 重命名文件夹
renameFolder(folder) {
this.$prompt('请输入新的文件夹名称', '重命名文件夹', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputValue: folder.name,
inputPattern: /.+/,
inputErrorMessage: '文件夹名称不能为空'
}).then(({ value }) => {
// 更新本地存储
const index = this.allFolders.findIndex(f => f.id === folder.id);
if (index !== -1) {
this.allFolders[index].name = value;
localStorage.setItem('cloudFolders', JSON.stringify(this.allFolders));
// 更新当前视图
this.folders = this.allFolders.filter(f => f.parentId === this.currentFolderId);
this.$message({
type: 'success',
message: '文件夹重命名成功'
});
}
}).catch(() => {
// 用户取消操作
});
},
// 删除文件夹
deleteFolder(folder) {
this.$confirm(`确定要删除文件夹 "${folder.name}" 吗?此操作将删除文件夹内的所有内容。`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 递归删除文件夹及其内容
this.deleteFolderRecursively(folder.id);
// 更新视图
this.loadFolderContent();
this.$message({
type: 'success',
message: '删除成功'
});
}).catch(() => {
// 用户取消操作
});
},
// 递归删除文件夹及其内容
deleteFolderRecursively(folderId) {
// 删除文件夹
this.allFolders = this.allFolders.filter(f => f.id !== folderId);
// 删除文件夹内的文件
this.allFiles = this.allFiles.filter(f => f.parentId !== folderId);
// 查找并删除子文件夹
const childFolders = this.allFolders.filter(f => f.parentId === folderId);
for (const child of childFolders) {
this.deleteFolderRecursively(child.id);
}
// 保存到本地存储
localStorage.setItem('cloudFolders', JSON.stringify(this.allFolders));
localStorage.setItem('cloudFiles', JSON.stringify(this.allFiles));
// 更新数据
this.folders = this.allFolders.filter(f => f.parentId === this.currentFolderId);
this.files = this.allFiles.filter(f => f.parentId === this.currentFolderId);
},
// 上传文件
uploadFile() {
this.$refs.fileInput.click();
},
// 文件选择处理
onFileSelected(event) {
const files = event.target.files;
if (files.length > 0) {
this.handleFileUpload(files);
}
// 重置input
event.target.value = '';
},
// 处理文件上传
handleFileUpload(fileList) {
for (let i = 0; i < fileList.length; i++) {
const file = fileList[i];
const fileSize = (file.size / (1024 * 1024)).toFixed(1) + ' MB';
// 获取文件类型
let fileType = 'file';
if (file.type.includes('image')) fileType = 'image';
else if (file.type.includes('pdf')) fileType = 'pdf';
else if (file.type.includes('word')) fileType = 'doc';
else if (file.type.includes('video')) fileType = 'video';
else if (file.type.includes('excel') || file.type.includes('sheet')) fileType = 'excel';
const newFile = {
id: Date.now() + i,
name: file.name,
size: fileSize,
type: fileType,
createdAt: new Date().toLocaleDateString(),
parentId: this.currentFolderId
};
this.allFiles.push(newFile);
}
// 保存到本地存储
localStorage.setItem('cloudFiles', JSON.stringify(this.allFiles));
// 更新当前视图
this.files = this.allFiles.filter(f => f.parentId === this.currentFolderId);
this.$message({
type: 'success',
message: `成功上传 ${fileList.length} 个文件`
});
},
// 下载文件
downloadItem(item, type) {
// 创建下载任务
const downloadId = Date.now();
const downloadTask = {
id: downloadId,
name: item.name,
type: type === 'file' ? item.type : 'folder',
progress: 0,
status: 'pending'
};
this.downloads.unshift(downloadTask);
this.saveDownloadHistory();
// 模拟下载过程
this.simulateDownload(downloadId, item, type);
this.contextMenu.visible = false;
},
// 模拟下载过程
simulateDownload(downloadId, item, type) {
// 更新状态为下载中
const downloadIndex = this.downloads.findIndex(d => d.id === downloadId);
if (downloadIndex !== -1) {
this.downloads[downloadIndex].status = 'downloading';
// 模拟进度更新
let progress = 0;
const interval = setInterval(() => {
progress += Math.random() * 15;
if (progress >= 100) {
progress = 100;
clearInterval(interval);
// 下载完成
this.downloads[downloadIndex].progress = 100;
this.downloads[downloadIndex].status = 'completed';
this.saveDownloadHistory();
// 实际下载文件
this.performDownload(item, type);
} else {
this.downloads[downloadIndex].progress = Math.round(progress);
}
}, 200);
}
},
// 执行实际下载
performDownload(item, type) {
// 创建文件内容
let content = '';
let fileName = item.name;
if (type === 'file') {
// 根据文件类型生成不同的内容
switch (item.type) {
case 'pdf':
content = `%PDF-1.4\n模拟PDF文件内容: ${item.name}\n生成时间: ${new Date().toLocaleString()}`;
break;
case 'image':
content = `模拟图像文件: ${item.name}\n生成时间: ${new Date().toLocaleString()}`;
if (!fileName.endsWith('.jpg')) fileName += '.jpg';
break;
case 'doc':
content = `模拟Word文档: ${item.name}\n生成时间: ${new Date().toLocaleString()}`;
if (!fileName.endsWith('.docx')) fileName += '.docx';
break;
case 'video':
content = `模拟视频文件: ${item.name}\n生成时间: ${new Date().toLocaleString()}`;
if (!fileName.endsWith('.mp4')) fileName += '.mp4';
break;
case 'excel':
content = `模拟Excel文件: ${item.name}\n生成时间: ${new Date().toLocaleString()}`;
if (!fileName.endsWith('.xlsx')) fileName += '.xlsx';
break;
default:
content = `文件: ${item.name}\n大小: ${item.size}\n生成时间: ${new Date().toLocaleString()}`;
}
} else {
// 文件夹下载 - 创建ZIP文件(模拟)
content = `压缩文件夹: ${item.name}\n包含文件: ${item.items}个\n生成时间: ${new Date().toLocaleString()}`;
if (!fileName.endsWith('.zip')) fileName += '.zip';
}
// 创建Blob对象
const blob = new Blob([content], { type: 'application/octet-stream' });
const url = URL.createObjectURL(blob);
// 创建下载链接
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
// 清理
setTimeout(() => {
document.body.removeChild(link);
URL.revokeObjectURL(url);
}, 100);
},
// 移除下载任务
removeDownload(downloadId) {
this.downloads = this.downloads.filter(d => d.id !== downloadId);
this.saveDownloadHistory();
},
// 保存下载历史
saveDownloadHistory() {
// 只保存最近10条下载记录
const history = this.downloads.slice(0, 10);
localStorage.setItem('downloadHistory', JSON.stringify(history));
},
// 加载下载历史
loadDownloadHistory() {
const history = JSON.parse(localStorage.getItem('downloadHistory')) || [];
this.downloads = history;
},
// 重命名文件
renameFile(file) {
this.$prompt('请输入新的文件名称', '重命名文件', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputValue: file.name,
inputPattern: /.+/,
inputErrorMessage: '文件名称不能为空'
}).then(({ value }) => {
// 更新本地存储
const index = this.allFiles.findIndex(f => f.id === file.id);
if (index !== -1) {
this.allFiles[index].name = value;
localStorage.setItem('cloudFiles', JSON.stringify(this.allFiles));
// 更新当前视图
this.files = this.allFiles.filter(f => f.parentId === this.currentFolderId);
this.$message({
type: 'success',
message: '文件重命名成功'
});
}
}).catch(() => {
// 用户取消操作
});
},
// 删除文件
deleteFile(file) {
this.$confirm(`确定要删除文件 "${file.name}" 吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 更新本地存储
this.allFiles = this.allFiles.filter(f => f.id !== file.id);
localStorage.setItem('cloudFiles', JSON.stringify(this.allFiles));
// 更新当前视图
this.files = this.allFiles.filter(f => f.parentId === this.currentFolderId);
this.$message({
type: 'success',
message: '删除成功'
});
}).catch(() => {
// 用户取消操作
});
},
// 复制项目
copyItem(item, type) {
this.clipboard = {
item: item,
type: type,
operation: 'copy',
originalParentId: item.parentId
};
this.contextMenu.visible = false;
this.$message({
type: 'success',
message: `已复制: ${item.name}`
});
},
// 剪切项目
cutItem(item, type) {
this.clipboard = {
item: item,
type: type,
operation: 'cut',
originalParentId: item.parentId
};
this.contextMenu.visible = false;
this.$message({
type: 'success',
message: `已剪切: ${item.name}`
});
},
// 粘贴项目
pasteItem() {
if (!this.clipboard.item) return;
// 如果目标是文件夹,需要选择目标文件夹
if (this.clipboard.type === 'folder' && this.clipboard.operation === 'cut') {
this.targetFolderDialogVisible = true;
return;
}
// 直接粘贴到当前文件夹
this.performPaste(this.currentFolderId);
},
// 执行粘贴操作
performPaste(targetFolderId) {
if (this.clipboard.operation === 'copy') {
this.copyToTarget(targetFolderId);
} else {
this.moveToTarget(targetFolderId);
}
// 清空剪贴板
this.clearClipboard();
// 重新加载内容
this.loadFolderContent();
},
// 复制到目标文件夹
copyToTarget(targetFolderId) {
if (this.clipboard.type === 'file') {
// 复制文件
const newFile = {
...this.clipboard.item,
id: Date.now(),
parentId: targetFolderId
};
this.allFiles.push(newFile);
} else {
// 复制文件夹
const newFolder = {
...this.clipboard.item,
id: Date.now(),
parentId: targetFolderId
};
this.allFolders.push(newFolder);
}
// 保存到本地存储
localStorage.setItem('cloudFolders', JSON.stringify(this.allFolders));
localStorage.setItem('cloudFiles', JSON.stringify(this.allFiles));
this.$message({
type: 'success',
message: '复制成功'
});
},
// 移动到目标文件夹
moveToTarget(targetFolderId) {
if (this.clipboard.type === 'file') {
// 移动文件
const index = this.allFiles.findIndex(f => f.id === this.clipboard.item.id);
if (index !== -1) {
this.allFiles[index].parentId = targetFolderId;
}
} else {
// 移动文件夹
const index = this.allFolders.findIndex(f => f.id === this.clipboard.item.id);
if (index !== -1) {
this.allFolders[index].parentId = targetFolderId;
}
}
// 保存到本地存储
localStorage.setItem('cloudFolders', JSON.stringify(this.allFolders));
localStorage.setItem('cloudFiles', JSON.stringify(this.allFiles));
this.$message({
type: 'success',
message: '移动成功'
});
},
// 清空剪贴板
clearClipboard() {
this.clipboard = {
item: null,
type: null,
operation: null,
originalParentId: null
};
},
// 显示上下文菜单
showContextMenu(event, item, type) {
this.contextMenu = {
visible: true,
x: event.pageX,
y: event.pageY,
item: item,
type: type
};
},
// 处理文件夹选择
handleFolderSelect(data) {
this.selectedTargetFolder = data;
},
// 确认目标文件夹
confirmTargetFolder() {
if (!this.selectedTargetFolder) {
this.$message.warning('请选择目标文件夹');
return;
}
this.performPaste(this.selectedTargetFolder.id);
this.targetFolderDialogVisible = false;
},
// 选择/取消选择项目
toggleSelect(item, type) {
const itemId = `${type}-${item.id}`;
const index = this.selectedItems.findIndex(i => i.id === itemId);
if (index === -1) {
this.selectedItems.push({
id: itemId,
type: type,
item: item
});
} else {
this.selectedItems.splice(index, 1);
}
},
// 检查项目是否被选择
isSelected(itemId, type) {
return this.selectedItems.some(i => i.id === `${type}-${itemId}`);
},
// 清空选择
clearSelection() {
this.selectedItems = [];
},
// 下载所选项目
downloadSelected() {
if (this.selectedItems.length === 0) return;
// 批量下载
this.selectedItems.forEach(selected => {
this.downloadItem(selected.item, selected.type);
});
this.$message({
type: 'success',
message: `开始下载 ${this.selectedItems.length} 个项目`
});
},
// 下载全部可见项目
downloadAllVisible() {
const itemsToDownload = [
...this.folders.map(f => ({ item: f, type: 'folder' })),
...this.files.map(f => ({ item: f, type: 'file' }))
];
if (itemsToDownload.length === 0) return;
itemsToDownload.forEach(({ item, type }) => {
this.downloadItem(item, type);
});
this.$message({
type: 'success',
message: `开始下载 ${itemsToDownload.length} 个项目`
});
},
// 获取文件图标
getFileIcon(file) {
switch (file.type) {
case 'pdf': return 'fas fa-file-pdf';
case 'image': return 'fas fa-file-image';
case 'doc': return 'fas fa-file-word';
case 'video': return 'fas fa-file-video';
case 'excel': return 'fas fa-file-excel';
case 'folder': return 'fas fa-folder';
default: return 'fas fa-file';
}
},
// 获取文件图标类
getFileIconClass(file) {
switch (file.type) {
case 'pdf': return 'pdf-icon';
case 'image': return 'image-icon';
case 'doc': return 'doc-icon';
case 'video': return 'video-icon';
case 'excel': return 'excel-icon';
default: return 'file-icon';
}
},
// 拖放相关方法
onDragOver(e) {
e.preventDefault();
e.currentTarget.style.borderColor = '#3a7bd5';
e.currentTarget.style.backgroundColor = 'rgba(58, 123, 213, 0.15)';
},
onDragLeave(e) {
e.preventDefault();
e.currentTarget.style.borderColor = '#dcdfe6';
e.currentTarget.style.backgroundColor = 'rgba(58, 123, 213, 0.03)';
},
onDrop(e) {
e.preventDefault();
e.currentTarget.style.borderColor = '#dcdfe6';
e.currentTarget.style.backgroundColor = 'rgba(58, 123, 213, 0.03)';
const files = e.dataTransfer.files;
if (files.length > 0) {
this.handleFileUpload(files);
}
}
}
});
</script>
</body>
</html>