Vue3实战Easy云盘(三):文件删除+文件移动+目录导航+上传优化/文件过滤/搜索

本文主要是介绍Vue3实战Easy云盘(三):文件删除+文件移动+目录导航+上传优化/文件过滤/搜索,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、文件删除

(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) => {// 调用Confirmproxy.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;}// 调用Confirmproxy.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"><!-- 每一项文件夹 --><divclass="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({// 指定请求的urlurl: api.loadAllFolder,// 传递父id和当前文件夹idparams: {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中调用

(不全面,未添加导航栏)

<!-- 引入组件 --><FolderSelectref="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给showFolderDialogfolderSelectRef.value.showFolderDialog(currentFolder.value.fileId);
};// 点击按钮之后,移动文件操作
const moveFolderDone = async (folderId) => {// 如果要移动到当前目录,提醒无需移动if (// 如果当前移动的文件父级id等于此时要移动到的文件夹id或者当前文件夹的id等于要移动到的文件夹idcurrentMoveFile.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 是指批量选择时选择的文件IDfiledIdsArray = filedIdsArray.concat(selectFileIdList.value);}// 发请求并将结果存储let result = await proxy.Request({// 请求的urlurl: api.changeFileFolder,// 携带的参数params: {// 将 fileIdsArray 数组中的所有元素转换为一个由逗号分隔的字符串,赋值给fileIdsfileIds: 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><!-- 全部文件:能点的 --><spanclass="link"v-if="folderList.length > 0"@click="setCurrentFolder(-1)">全部文件</span><!-- 遍历文件列表 --><template v-for="(item, index) in folderList"><!-- 图标 --><span class="iconfont icon-right"></span><!-- 文件名字可以点击 --><spanclass="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和文件nameconst { fileId, fileName } = data;// 定义folderconst folder = {fileName: fileName,fileId: fileId,};// 把folder push进目录集合folderList.value.push(folder);// 更新当前目录currentFolder.value = folder;// 设置当前路径,当点击后目录改变,路径也随之改变,调用setPathsetPath();
};
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({// 当前路径pathpath: 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字符串分割为一个数组pathArraylet pathArray = path.split("/");// fileId被赋值给currentFolder.valuecurrentFolder.value = {// 它使用数组的最后一个元素(即path中的最后一个部分)作为fileIdfileId: 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 和 categoryIdconst { curFolder, categoryId } = data;// 将当前文件夹的值更新为传过来的文件夹currentFolder.value = curFolder;// 展示showLoading.value = true;// 更新categorycategory.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-inputclearableplaceholder="请输入文件名搜索"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"><Navigationref="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);
};

这篇关于Vue3实战Easy云盘(三):文件删除+文件移动+目录导航+上传优化/文件过滤/搜索的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/988419

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

【 html+css 绚丽Loading 】000046 三才归元阵

前言:哈喽,大家好,今天给大家分享html+css 绚丽Loading!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 📚一、效果📚二、信息💡1.简介:💡2.外观描述:💡3.使用方式:💡4.战斗方式:💡5.提升:💡6.传说: 📚三、源代码,上代码,可以直接复制使用🎥效果🗂️目录✍️

HDFS—存储优化(纠删码)

纠删码原理 HDFS 默认情况下,一个文件有3个副本,这样提高了数据的可靠性,但也带来了2倍的冗余开销。 Hadoop3.x 引入了纠删码,采用计算的方式,可以节省约50%左右的存储空间。 此种方式节约了空间,但是会增加 cpu 的计算。 纠删码策略是给具体一个路径设置。所有往此路径下存储的文件,都会执行此策略。 默认只开启对 RS-6-3-1024k

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

hdu1240、hdu1253(三维搜索题)

1、从后往前输入,(x,y,z); 2、从下往上输入,(y , z, x); 3、从左往右输入,(z,x,y); hdu1240代码如下: #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#inc

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于