一、文件删除
(1)选中了之后才可以删除,没有选中时就显示暗调删除按钮
(2)实现选中高亮功能
(3)单个删除
(4)批量删除
Main.vue中
<!-- 按钮3 -->
<!-- 如果selectFileIdList数组的长度为0(即数组为空),则HTML元素的disabled属性将被设置为true,从而使该元素变为禁用状态 -->
<el-button type="danger" @click="delFileBatch" :disabled="selectFileIdList.length == 0">
<span class="iconfont icon-del"></span>
批量删除
</el-button>
.....
<span class="iconfont icon-del" @click="delFile(row)">
删除
</span>
......
// 定义多选文件夹列表
const selectFileIdList = ref([]);
// 多选
const rowSelected = (rows) => {
// 重置为一个空数组,清空数据
selectFileIdList.value = [];
// 遍历 rows 数组中的每一个元素
rows.forEach((item) => {
// 经遍历到的fileId添加到selectFileIdList中
selectFileIdList.value.push(item.fileId);
});
};
// 删除单个文件
const delFile = (row) => {
// 调用Confirm
proxy.Confirm(
`你确定要删除【$row.fileName】吗?删除的文件可在 10 天内通过回收站还原`,
async () => {
let result = await proxy.Request({
url: api.delFile,
params: {
fileIds: row.fileId,
},
});
if (!result) {
return;
}
// 重新获取数据
loadDataList();
}
);
};
// 批量删除文件
const delFileBatch = () => {
// 如果要删除的长度为0就不执行
if (selectFileIdList.value.length == 0) {
return;
}
// 调用Confirm
proxy.Confirm(
// 第一个参数是一个字符串,用于显示给用户的确认消息
`你确定要删除这些文件吗?删除的文件可在 10 天内通过回收站还原`,
// 第二个参数,当用户点击确认按钮后,这个异步函数会被执行
async () => {
// 使用await关键字调用proxy.Request方法,发送一个HTTP请求到服务器
// 接收proxy.Request方法的响应并将其存储在result变量中
let result = await proxy.Request({
// 请求的URL来自api.delFile,这可能是一个常量或配置对象中的属性,指向删除文件的API端点。
url: api.delFile,
// join将这个数组转换为一个由逗号分隔的字符串(因为批量删除多个ids所以要分开),作为查询参数发送的文件ID列表
params: {
fileIds: selectFileIdList.value.join(","),
},
});
// 处理响应,
if (!result) {
return;
}
// 重新获取数据
loadDataList();
}
);
};
二、文件移动
(1)定义全局组件,FolderSelect.vue
(因为文件移动到哪个文件夹,文件保存到哪个文件夹都要用到,所以封装一个全局组件)
别忘记在Main.js中引入
src/components/FolderSelect.vue(不全面,未添加导航)
<!-- 移动/保存到哪个文件夹组件 -->
<template>
<div>
<!-- 弹出的对话框组件Dialog -->
<Dialog
:show="dialogConfig.show"
:title="dialogConfig.title"
:buttons="dialogConfig.buttons"
width="600px"
:showCancel="true"
@close="dialogConfig.show = false">
<!-- 目录导航 -->
<div class="navigation-panel"></div>
<!-- 文件夹列表 -->
<div class="folder-list" v-if="folderList.length > 0">
<!-- 每一项文件夹 -->
<div
class="folder-item"
v-for="item in folderList"
@click="selectFolder(item)">
<!-- 文件类型为0时即文件夹就显示文件夹的图标 -->
<Icon :fileType="0"></Icon>
<!-- 每个文件夹的名字 -->
<span class="file-name">{{ item.fileName }}</span>
</div>
</div>
<!-- 判断 -->
<div v-else class="tips">
移动到 <span>{{ currentFolder.fileName }}</span> 文件夹
</div>
</Dialog>
</div>
</template>
<script setup>
import { ref, reactive, getCurrentInstance } from "vue";
import { useRouter, useRoute } from "vue-router";
const { proxy } = getCurrentInstance();
const router = useRouter();
const route = useRoute();
const api = {
loadAllFolder: "/file/loadAllFolder",
};
// 定义弹出框的属性
const dialogConfig = ref({
show: false,
// 对话框的标题
title: "移动到",
buttons: [
{
type: "primary",
text: "移动到此",// 按钮上的文字
// 当按钮被点击时触发的回调函数。在这个例子中,点击按钮会调用 folderSelect() 函数。
click: (e) => {
folderSelect();
},
},
],
});
// 目录列表
const folderList = ref([]);
// 父级ID
const filePid = ref("0");
// 当前目录ID
const currentFileIds = ref([]);
// 当前文件夹
const currentFolder = ref({});
// 获取所有的目录文件夹列表
const loadAllFolder = async () => {
// API请求
// 使用 await 等待 proxy.Request 方法的执行结果存储到result里
let result = await proxy.Request({
// 指定请求的url
url: api.loadAllFolder,
// 传递父id和当前文件夹id
params: {
filePid: filePid.value,
currentFileIds: currentFileIds.value,
},
});
// 判断结果
if (!result) {
return;
}
folderList.value = result.data;
};
// 展示弹出框对外的方法
const showFolderDialog = (currentFolder) => {
dialogConfig.value.show = true;
// 更新当前文件id数组
currentFileIds.value = currentFolder;
// 在加载一次获取到的目录文件夹列表
loadAllFolder();
};
// 关闭弹出框
const close = () => {
dialogConfig.value.show = false;
};
// 向外暴露这两个函数,使得父组件Main可以调用这两个函数
defineExpose({ showFolderDialog, close });
// 选择目录(目录导航)
const selectFolder = (data) => {
navigationRef.value.openFolder(data);
};
// 确定选择要移动到的目录
// 将选定的文件目录参数传递给父组件 Main 中的 folderSelect 函数
const emit = defineEmits(["folderSelect"]);
// 此方法回调在父组件中
const folderSelect = () => {
emit("folderSelect", filePid.value);
};
</script>
<style lang="scss" scoped>
.navigation-panel {
padding-left: 10px;
background: #f1f1f1;
}
.folder-list {
.folder-item {
cursor: pointer;
display: flex;
align-items: center;
padding: 10px;
.file-name {
display: inline-block;
margin-left: 10px;
}
&:hover {
background: #f8f8f8;
}
}
max-height: calc(100vh - 200px);
min-height: 200px;
}
.tips {
text-align: center;
line-height: 200px;
span {
color: #06a7ff;
}
}</style>
(2)Main.vue中调用
(不全面,未添加导航栏)
<!-- 引入组件 -->
<FolderSelect
ref="folderSelectRef"
@folderSelect="moveFolderDone"
></FolderSelect>
// 移动目录
const folderSelectRef = ref();
// 当前要移动的文件(单个文件)
const currentMoveFile = ref({});
// 移动单个文件
const moveFolder = (data) => {
// 存储当前要移动的单个文件的信息
currentMoveFile.value = data;
// 把当前文件id给showFolderDialog方法
folderSelectRef.value.showFolderDialog(currentFolder.value.fileId);
};
// 移动批量文件
const moveFolderBatch = () => {
// 清空当前要移动的文件数据
currentMoveFile.value = {};
// 把当前的文件夹id给showFolderDialog
folderSelectRef.value.showFolderDialog(currentFolder.value.fileId);
};
// 点击按钮之后,移动文件操作
const moveFolderDone = async (folderId) => {
// 如果要移动到当前目录,提醒无需移动
if (
// 如果当前移动的文件父级id等于此时要移动到的文件夹id或者当前文件夹的id等于要移动到的文件夹id
currentMoveFile.value.filePid == folderId ||
currentFolder.value.fileId == folderId
) {
// 就提示无需移动
proxy.Message.warning("文件正在当前目录,无需移动");
return;
}
// 定义一个数组存放要移动的文件或者文件夹信息
let fileIdsArray = [];
// 如果是单个文件移动
if (currentMoveFile.value.fileId) {
// 就把当前移动的文件id传给这个数组
fileIdsArray.push(currentMoveFile.value.fileId);
} else {
// 如果是多个文件移动
// concat 连接多个数组
// selectFileIdList 是指批量选择时选择的文件ID
filedIdsArray = filedIdsArray.concat(selectFileIdList.value);
}
// 发请求并将结果存储
let result = await proxy.Request({
// 请求的url
url: api.changeFileFolder,
// 携带的参数
params: {
// 将 fileIdsArray 数组中的所有元素转换为一个由逗号分隔的字符串,赋值给fileIds
fileIds: fileIdsArray.join(","),
// 把folderId传到父文件id里面
filePid: folderId,
},
});
if (!result) {
return;
}
// 调用子组件暴露的close方法,实现当前弹出框页面的关闭
folderSelectRef.value.close();
// 更新当前文件列表
loadDataList();
};
三、目录导航(难点)
(1)导航栏组件(全局)
src/components/Navigation.vue
template结构js
1.设置点击目录事件 openFolder:
2.暴露此事件供父组件使用:defineExpose({ openFolder });
3.设置当前路径 setpath:当点击后目录改变,路径也随之改变
4.获取当前路径的目录 getNavigationFolder
5.doCallback
6.监听路由
7.初始化设置init
8.setCurrentFolder 设置当前目录,点击导航跳转到所点击的目录
9.返回上一级 backParent
src/components/Navigation.vue
<template>
<!-- 导航 -->
<div class="top-navigation">
<!-- 返回上一级 -->
<template v-if="folderList.length > 0">
<span class="back link" @click="backParent">返回上一级</span>
<!-- 竖线 -->
<el-divider direction="vertical" />
</template>
<!-- 全部文件:外面粗体的不能点 -->
<span v-if="folderList.length == 0" class="all-file">全部文件</span>
<!-- 全部文件:能点的 -->
<span
class="link"
v-if="folderList.length > 0"
@click="setCurrentFolder(-1)"
>全部文件
</span>
<!-- 遍历文件列表 -->
<template v-for="(item, index) in folderList">
<!-- 图标 -->
<span class="iconfont icon-right"></span>
<!-- 文件名字可以点击 -->
<span
class="link"
v-if="index < folderList.length - 1"
@click="setCurrentFolder(index)"
>{{ item.fileName }}
</span>
<!-- 文件名字不可以点击 -->
<span class="text" v-if="index == folderList.length - 1">
{{item.fileName}}
</span>
</template>
</div>
</template>
<script setup>
import { ref, reactive, getCurrentInstance, watch } from "vue";
import { useRouter, useRoute } from "vue-router";
const { proxy } = getCurrentInstance();
const router = useRouter();
const route = useRoute();
// 定义父组件Main.vue传递过来的值
const props = defineProps({
// 默认开启路由监听
watchPath: {
// 是否监听路由变化
type: Boolean,
default: true,
},
shareId: {
type: String,
},
adminShow: {
type: Boolean,
default: false,
},
});
const api = {
// 首页 获取当前目录 获取列表 参数(path:完整路径)
getFolderInfo: "/file/getFolderInfo",
// 外部分享 获取目录信息 参数(shareId:分享id / path:完整路径)
getFolderInfo4Share: "/showShare/getFolderInfo",
// 管理员 获取当前目录 参数(path:完整路径)
getFolderInfo4Admin: "/admin/getFolderInfo",
};
// 分类
const category = ref();
// 目录的集合
const folderList = ref([]);
// 当前目录
const currentFolder = ref({ fileId: "0" });
// 初始化
const init = () => {
// 初始目录集合 设置为一个空数组
folderList.value = [];
// 初始当前目录设置
currentFolder.value = { fileId: "0" };
// 调用
doCallback();
};
// 点击目录openFolder
// 父组件 Main/FolderSelect 中调用该方法,实现目录(文件及)的预览
const openFolder = (data) => {
// 把data赋值给文件id和文件name
const { fileId, fileName } = data;
// 定义folder
const folder = {
fileName: fileName,
fileId: fileId,
};
// 把folder push进目录集合
folderList.value.push(folder);
// 更新当前目录
currentFolder.value = folder;
// 设置当前路径,当点击后目录改变,路径也随之改变,调用setPath
setPath();
};
defineExpose({ openFolder });
// 返回上一级
const backParent = () => {
// 查找当前文件夹的索引
let currentIndex = null;
for (let i = 0; i < folderList.value.length; i++) {
if (folderList.value[i].fileId == currentFolder.value.fileId) {
currentIndex = i;
break;
}
}
// 设置当前文件夹为上一级目录
setCurrentFolder(currentIndex - 1);
};
// 点击导航 设置当前目录(点击目录,跳转到所点击的目录)
const setCurrentFolder = (index) => {
// 如果点的是全部文件
if (index == -1) {
// 初始化数组
currentFolder.value = { fileId: "0" };
folderList.value = [];
} else {
// 当前目录的值更新为目录集合数组为index的值
currentFolder.value = folderList.value[index];
// 删除从index+1开始的长度为目录集合数组的长度 的数组
// 对于 splice(start, deleteCount, ...items) 方法:
// start(必需):开始更改数组的位置的索引。
// deleteCount(必需):要删除的元素数量。如果设置为 0,则不会删除任何元素。
// ...items(可选):要添加到数组中的新元素
folderList.value.splice(index + 1, folderList.value.length);
}
setPath();
};
// 设置当前路径,当点击后目录改变,路径也随之改变
const setPath = () => {
if (!props.watchPath) {
// 设置不监听路由回调方法
doCallback();
return;
}
// 定义路径数组
let pathArray = [];
// 遍历目录集合的每一项
folderList.value.forEach((item) => {
// 把每一项的fileId push 到 路径数组里
pathArray.push(item.fileId);
});
// 设置路由
router.push({
// 当前路径path
path: route.path,
// 参数
query:
// 如果pathArray长度为0,参数就为空,否则就把路径用/隔开加入到pathArray里面,更新path,添加到参数里面
pathArray.length == 0
? ""
: {
path: pathArray.join("/"),
},
});
};
// 获取当前路径的目录
const getNavigationFolder = async (path) => {
// 根据给定的 path 和一些其他属性(如 props.shareId 和 props.adminShow)来确定请求哪个 API,然后发送一个请求来获取该路径下的目录信息,并将获取到的目录信息存储在 folderList.value 中。
let url = api.getFolderInfo;
if (props.shareId) {
url = api.getFolderInfo4Share;
}
if (props.adminShow) {
url = api.getFolderInfo4Admin;
}
let result = await proxy.Request({
url: url,
showLoading: false,
params: {
path: path,
shareId: props.shareId,
},
});
if (!result) {
return;
}
folderList.value = result.data;
};
// 回调 将当前的参数传递给父组件 Main
// 定义了一个名为 navChange 的事件。这允许子组件在需要时触发这个事件,并传递一些数据给父组件。
const emit = defineEmits(["navChange"]);
const doCallback = () => {
emit("navChange", {
categoryId: category.value,
curFolder: currentFolder.value,
});
};
// 监听路由
watch(
() => route,
// 它会在route的值变化时被调用。
// newVal是route的新值,oldVal是route的旧值
(newVal, oldVal) => {
if (!props.watchPath) {
return;
}
if (
// 如果不在main路径里面就不用管
newVal.path.indexOf("/main") === -1 &&
newVal.path.indexOf("/settings/fileList") === -1 &&
newVal.path.indexOf("/share") === -1
) {
return;
}
// 把新携带的路径赋值给path,可以拿到?后面的一坨,query参数是?后面的一截
const path = newVal.query.path;
// params是route路由,category是在router里面定义的
const categoryId = newVal.params.category;
category.value = categoryId;
if (path == undefined) {
// 调用
init();
} else {
// 调用
getNavigationFolder(path);
// 刷新的时候要把当前目录设置进来
// 使用split("/")方法将path字符串分割为一个数组pathArray
let pathArray = path.split("/");
// fileId被赋值给currentFolder.value
currentFolder.value = {
// 它使用数组的最后一个元素(即path中的最后一个部分)作为fileId
fileId: pathArray[pathArray.length - 1],
};
doCallback();
}
},
{ immediate: true, deep: true }
);
</script>
<style lang="scss" scoped>
.top-navigation {
font-size: 13px;
display: flex;
align-items: center;
line-height: 40px;
.all-file {
font-weight: bold;
}
.link {
color: #06a7ff;
cursor: pointer;
}
.icon-right {
color: #06a7ff;
padding: 0px 5px;
font-size: 13px;
}
}
</style>
(2)main.js中引入
import Navigation from '@/components/Navigation.vue'; app.component('Navigation', Navigation);
(3)Main.vue中使用组件
<!-- 导航 --> <Navigation ref="navigationRef" @navChange="navChange"></Navigation>
Main.vue中绑定点击事件
<span @click="preview(row)">{{ row.fileName }}</span>
<span @click="preview(row)">{{ row.fileName }}</span>
preview回调,预览
// 预览 const previewRef = ref(); const preview = (data) => { // 如果是文件夹 if (data.folderType == 1) { // 就调用Navigation组件中的openFolder方法,实现预览 navigationRef.value.openFolder(data); return; } if (data.status != 2) { proxy.Message.warning("文件未完成转码,无法预览"); return; } previewRef.value.showPreview(data, 0); };
navChange回调
// 目录,展示目录 const navChange = (data) => { // 从传入的 data 对象中解构出 curFolder 和 categoryId 两个属性,并将它们的值分别赋给新定义的常量 curFolder 和 categoryId const { curFolder, categoryId } = data; // 将当前文件夹的值更新为传过来的文件夹 currentFolder.value = curFolder; // 展示 showLoading.value = true; // 更新category category.value = categoryId; loadDataList(); };
无文件上传时,Main.vue展示
<!-- 判断没有文件时 --> <div class="no-data" v-else> <div class="no-data-inner"> <!-- 图片 --> <Icon iconName="no_data" :width="120" fit="fill"></Icon> <!-- 文字提示 --> <div class="tips">当前目录为空,上传你的第一个文件吧</div> <div class="op-list"> <!-- 上传 --> <el-upload :show-file-list="false" :with-credentials="true" :multiple="true" :http-request="addFile" :accept="fileAccept" > <div class="op-item"> <Icon iconName="file" :width="60"></Icon> <div>上传文件</div> </div> </el-upload> <!-- 新建目录 --> <div class="op-item" v-if="category == 'all'" @click="newFolder"> <Icon iconName="folder" :width="60"></Icon> <div>新建目录</div> </div> </div> </div> </div>
四、 上传优化(列表自动刷新)
(1)上传回调
Main.vue中
// 添加文件回调
const reload = () => {
showLoading.value = false;
// 刷新列表
loadDataList();
};
defineExpose({ reload });
Framework.vue中
<component @addFile="addFile" ref="routerViewRef" :is="Component"></component>
// 上传文件回调
// 上传文件后的刷新列表(调用Uploader子组件中的函数)
const routerViewRef = ref();
const uploadCallbackHandler = () => {
nextTick(() => {
// 它首先等待DOM更新完成(通过nextTick)
// 然后重新加载一个组件(可能是router-view)
routerViewRef.value.reload();
// 并最后调用一个函数来获取空间使用情况。
getUseSpace();
});
};
五、文件选择(过滤)
组件:分类文件类型
src/js/CategoryInfo.js
export default {
"all": {
accept: "*"
},
"video": {
accept: ".mp4,.avi,.rmvb,.mkv,.mov"
},
"music": {
accept: ".mp3,.wav,.wma,.mp2,.flac,.midi,.ra,.ape,.aac,.cda"
},
"image": {
accept: ".jpeg,.jpg,.png,.gif,.bmp,.dds,.psd,.pdt,.webp,.xmp,.svg,.tiff"
},
"doc": {
accept: ".pdf,.doc,.docx,.xls,.xlsx,.txt"
},
"others": {
accept: "*"
},
}
Main.vue中上传按钮
:accept="fileAccept"
// 实现文件选择
const fileAccept = computed(() => {
const categoryItem = CategoryInfo[category.value];
return categoryItem ? categoryItem.accept : "*";
});
六、搜索功能实现
Main.vue中搜索输入框
<!-- 搜索输入框 -->
<div class="search-panel">
<el-input
clearable
placeholder="请输入文件名搜索"
v-model="fileNameFuzzy"
@keyup.enter="search">
<template #suffix>
<i class="iconfont icon-search" @click="search"></i>
</template>
</el-input>
</div>
<!-- 搜索图标 -->
<div class="iconfont icon-refresh" @click="loadDataList"></div>
回调
// 搜索功能
const search = () => {
showLoading.value = true;
loadDataList();
};
七、文件移动的目录导航
FolderSelect.vue中
<!-- 目录导航 -->
<div class="navigation-panel">
<Navigation
ref="navigationRef"
@navChange="navChange"
:watchPath="false"
></Navigation>
</div>
// 绑定导航栏
const navigationRef = ref();
// 调用Navigation子组件中的navChange,使得参数传递给该组件
const navChange = (data) => {
const { curFolder } = data;
currentFolder.value = curFolder;
filePid.value = curFolder.fileId;
loadAllFolder();
};
// 选择目录(目录导航)
const selectFolder = (data) => {
navigationRef.value.openFolder(data);
};