简易的仿桌面文件夹上传(vue2)
<el-tab-pane label="项目资料">
<Information :projectId="form.id"></Information>
</el-tab-pane>
Information/index.vue
<template>
<div>
<el-row :gutter="20">
<!--文件夹目录数据-->
<el-col :span="4" :xs="24">
<div class="head-container">
<el-input
v-model="name"
placeholder="请输入文件夹名称"
clearable
size="small"
prefix-icon="el-icon-search"
style="margin-bottom: 20px"
/>
</div>
<div class="head-container">
<el-tree
:data="folderOptions"
:props="defaultProps"
:expand-on-click-node="false"
:filter-node-method="filterNode"
ref="tree"
node-key="id"
default-expand-all
highlight-current
@node-click="handleNodeClick"
/>
</div>
</el-col>
<el-col :span="20" :xs="24">
<div class="top">
<!-- <el-button size="small" type="primary" plain @click="goBack" v-if="currentFolder">-->
<!-- <i class="el-icon-back" style="margin-right: 2px;" /> 返回-->
<!-- </el-button>-->
<el-upload
v-if="currentFolder.id != 0"
multiple
:action="uploadFileUrl"
:file-list="fileList"
:on-success="handleUploadSuccess"
:show-file-list="false"
:headers="headers"
ref="fileUpload">
<el-button slot="trigger" size="small" type="success" plain><i class="el-icon-upload" style="margin-right: 2px;"/>上传文件</el-button>
</el-upload>
<el-button v-if="currentFolder.id != 0" size="small" type="primary" plain @click="createNewFolder"><i class="el-icon-plus" style="margin-right: 2px;" /> 新建文件夹</el-button>
<el-button type="info" plain @click="queryData"><i class="el-icon-refresh-left" style="margin-right: 2px;"></i>主目录</el-button>
</div>
<div class="main">
<ContextMenu
:menu="['重命名', '删除']"
:row="selectedRow"
@action="handleContextMenuAction">
<el-table
ref="singleTable"
:data="files"
highlight-current-row
style="width: 100%"
@row-dblclick="handleRowClick"
@row-contextmenu="handleRightClick">
<el-table-column label="文件名" >
<template slot-scope="scope">
<!-- <svg-icon :icon-class="scope.row.type === 'folder' ? 'folder' : 'fileEmpty'" style="font-size: 26px" />-->
<div v-if="scope.row.type === 'folder'">
<svg-icon :icon-class="'folder'" style="font-size: 26px" />
<span style="margin-left: 10px">{{ scope.row.name }}</span>
</div>
<file-upload v-else :only-list="true" v-model="scope.row.fileUrl"></file-upload>
</template>
</el-table-column>
<el-table-column property="createBy" label="创建者" ></el-table-column>
<el-table-column property="createTime" label="创建时间"></el-table-column>
</el-table>
</ContextMenu>
</div>
</el-col>
</el-row>
<!-- 新建文件夹对话框-->
<el-dialog
title="新建文件夹"
:visible.sync="dialogVisible"
width="30%"
append-to-body>
<el-form :model="form" ref="numberValidateForm" label-width="100px" class="demo-ruleForm">
<el-form-item
label="文件名称"
prop="name"
:rules="[{ required: true, message: '文件名称不能为空'}]"
>
<el-input v-model.number="form.name" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="submitFile">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { getToken } from "@/utils/auth";
import FileUpload from "@/components/FileUpload/index.vue";
import { getFolderFile, addFolder, getFildsList, addFile, delFolder, delFolderFile, updateFolder } from "@/api/system/project";
import ContextMenu from "@/components/ContextMenu/index.vue";
export default {
name: 'Information',
components: {
FileUpload,
ContextMenu
},
props: {
// 项目id
projectId: {
type: String,
default: ""
}
},
data() {
return {
currentFolder: {
id: 0,
}, // 当前所在文件夹的信息
folderOptions: null, // 文件夹树选项
name: null, // 文件夹名称
defaultProps: {
children: "children",
label: "label"
},
invoiceFile: null,
uploadFileUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传文件服务器地址
headers: {
Authorization: "Bearer " + getToken(),
},
fileList: [],
dialogVisible: false,
form: {
name: ''
},
files: [],
contextMenuVisible: false, // 是否显示右键弹框
menuPosition: { x: 0, y: 0 }, // 右键菜单的显示位置
selectedRow: null // 右键选中的行
};
},
watch: {
// 根据名称筛选部门树
name(val) {
this.$refs.tree.filter(val);
}
},
created() {
this.queryData()
},
methods: {
// 初始化列表
queryData() {
this.getDeptTree();
this.getList(0);
},
// 获取列表数据
getList(parentId) {
getFolderFile({ parentId, projectId: this.projectId }).then(response => {
let folders = response.data.map(item => ({
...item,
type: 'folder'
}));
getFildsList(parentId, this.projectId).then(res => {
let files = res.data.map(item => ({
...item,
name: item.fileName,
type: 'fileEmpty'
}));
this.files = [...folders, ...files];
});
});
},
// 文件表格点击事件
handleRowClick(row) {
if (row.type === 'folder') {
this.currentFolder = row;
this.getList(row.id);
// 选中树形结构中的对应节点
this.$nextTick(() => {
this.$refs.tree.setCurrentKey(row.id); // 使用当前文件夹的 id 设置树节点选中
});
}
},
// 右键点击事件
handleRightClick(row, column, event) {
event.preventDefault(); // 阻止默认右键菜单
this.selectedRow = row; // 设置当前右键点击的行
console.log(' this.selectedRow', this.selectedRow)
},
// 鼠标右键的点击事件
handleContextMenuAction(action, row) {
console.log('handleContextMenuAction', action, row)
if (action === "删除") {
this.deleteRow(row); // 删除操作
} else if (action === "重命名") {
this.renameRow(row); // 重命名操作
}
},
// 删除
deleteRow(row) {
this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
if (row.type === 'folder') {
delFolder(row.id).then(response => {
this.getList(this.currentFolder.id);
});
} else {
delFolderFile(row.id).then(response => {
this.getList(this.currentFolder.id);
});
}
this.$message({
type: 'success',
message: '删除成功!'
});
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
// 重命名
renameRow(row) {
this.$prompt('', '重新命名', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputValue: row.name
}).then(({ value }) => {
if(row.type === 'folder') {
updateFolder({id: row.id, name: value}).then(response => {
this.getList(this.currentFolder.id);
this.$message({
type: 'success',
message: '修改成功!'
});
})
}
}).catch(() => {
this.$message({
type: 'info',
message: '取消输入'
});
});
},
/** 查询部门下拉树结构 */
getDeptTree() {
getFolderFile({ projectId: this.projectId}).then(response => {
let originData = response.data.map(item => {
return {
id: item.id,
parentId: item.parentId,
label: item.name,
children: item.children
}
})
this.folderOptions = this.handleTree(originData)
});
},
// 筛选节点
filterNode(value, data) {
if (!value) return true;
return data.label.indexOf(value) !== -1;
},
// 节点单击事件
handleNodeClick(data) {
this.currentFolder = data;
this.getList(data.id);
},
// 文件上传成功的回调
handleUploadSuccess(res, file) {
if (res.code === 200) {
const params = {
folderId: this.currentFolder.id || 0,
fileName: res.originalFilename,
fileUrl: res.url,
projectId: this.projectId
}
// 上传文件
addFile(params).then(res => {
this.getList(this.currentFolder.id)
this.$modal.msgSuccess("上传成功");
})
} else {
this.$modal.msgError(res.msg);
}
},
// 新建文件夹
createNewFolder() {
this.dialogVisible = true;
},
// 提交新建文件夹
submitFile() {
// 在这里实现新建文件夹的逻辑
const params = {
parentId: this.currentFolder.id,
name: this.form.name,
projectId: this.projectId
};
addFolder(params).then(res => {
this.getList(this.currentFolder.id)
this.getDeptTree();
this.form = {
name: undefined
}
})
this.dialogVisible = false;
},
}
};
</script>
<style lang="scss">
.el-popover {
z-index: 9999 !important;
}
::v-deep .svg-icon {
vertical-align: -0.28em;
}
.top {
display: flex;
justify-content: flex-end;
align-items: center;
//margin-bottom: 20px;
height: 56px;
.el-button {
margin-right: 10px;
}
}
.file-name {
font-size: 14px;
margin-left: 10px;
width: 300px;
}
.file-creater {
font-size: 14px;
width: 100px;
}
.file-time {
font-size: 14px;
color: #999;
width: 100px;
}
.file-desc {
color: #999;
font-size: 14px;
}
.main {
border: 1px solid #eee;
border-radius: 4px;
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.1);
margin: 0 10px;
padding: 16px;
box-sizing: border-box;
.h-file-row, .h-file-title {
display: flex;
align-items: center;
border-bottom: 1px solid #eee;
padding: 6px 0;
}
.h-file-row:hover {
background-color: #f0f9ff;
}
}
</style>
ContentMenu/index.vue
<template>
<div ref="contentRef" v-bind="$attrs">
<slot></slot>
<Teleport to="body" v-if="showMenu">
<div class="content-menu" :style="{ left: x + 'px', top: y + 'px' }">
<div class="menu-list">
<div
class="menu-item"
v-for="(item, index) in menu"
:key="index"
@click="handleMenuClick(item, $event)">
{{ item }}
</div>
</div>
</div>
</Teleport>
</div>
</template>
<script>
import Teleport from "../Teleport/index.vue"
export default {
name: 'ContextMenu',
components: {
Teleport
},
props: {
menu: {
type: Array,
default: () => ['重命名', '删除']
},
row: {
type: Object,
default: () => ({})
}
},
data() {
return {
showMenu: false,
x: 0,
y: 0,
mode: process.env.NODE_ENV,
isTeleport: false, // 控制什么时候被传送
};
},
mounted() {
if (this.mode === 'production') {
return;
}
const div = this.$refs.contentRef;
div.addEventListener('contextmenu', this.showContextMenu);
window.addEventListener('click', this.closeMenu, true);
},
beforeDestroy() {
const div = this.$refs.contentRef;
if (div) {
div.removeEventListener('contextmenu', this.showContextMenu);
}
window.removeEventListener('click', this.closeMenu);
},
methods: {
showContextMenu(e) {
e.preventDefault();
e.stopPropagation();
this.x = e.clientX;
this.y = e.pageY;
this.showMenu = true;
},
closeMenu() {
this.showMenu = false;
},
handleMenuClick(item, e) {
e.stopPropagation();
this.showMenu = false;
this.$emit("action", item, this.row);
}
}
};
</script>
<style lang="scss" scoped>
.content-menu {
position: absolute;
cursor: pointer;
z-index: 3001;
width: 180px;
background-color: #fff;
padding: 8px 0;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.menu-list {
text-align: center;
letter-spacing: 4px;
min-width: 100px;
}
.menu-item {
padding: 6px 0;
font-size: 12px;
cursor: pointer;
&:hover {
background-color: #f6f6f6;
}
}
</style>
Teleport/index.vue
<script>
export default {
name: 'teleport',
props: {
/* 移动至哪个标签内,最好使用id */
to: {
type: String,
required: true
}
},
mounted() {
// 把内容移动到指定的标签内this.to渲染
document.querySelector(this.to).appendChild(this.$el)
},
beforeDestroy() {
// 移除内容
const targetElement = document.querySelector(this.to)
if (targetElement && targetElement.contains(this.$el)) {
targetElement.removeChild(this.$el)
}
},
render() {
return this.$scopedSlots.default()
}
}
</script>