今天讲基于jeecgboot的个人网盘功能开发的前端部分,如下图:
之前讲了数据库部分,为了快速方便,通过online在线开发建好后,自动生成相应的前后端代码,前端部分modules与下面很多文件都是自动生成的,但有些目前也没有去用它,以后需要管理的话可以用。
上面中netdisk两个主要文件file.vue与share.vue
其中file.vue为网盘的主界面,share.vue主要是分享用
主界面主要是分两个部分,左边是菜单 AsideMenu,右边是文件列表FileList
其中AsideMenu相对比较简单,就是分成8种不同的文件类型,显示不同的内容而已,相对比较简单。
<template> <div class="side-menu-wrapper"> <!-- 768px 下,以抽屉形式展示 --> <el-drawer :visible.sync="isDrawer" :with-header="false" :size="210" direction="ltr" v-if="screenWidth <= 768" > <!-- collapse 属性:控制菜单收缩展开 --> <el-menu class="side-menu" :default-active="activeIndex" :router="true" :collapse="isCollapse" > <el-submenu index="myFile" class="my-file"> <template slot="title"> <!-- 图标均来自 Element UI 官方图标库 https://element.eleme.cn/#/zh-CN/component/icon --> <i class="el-icon-files"></i> <span slot="title">我的文件</span> </template> <el-menu-item index="0" :route="{ name: 'File', query: { fileType: 0, filePath: '/' } }" > <i class="el-icon-menu"></i> <span slot="title">全部</span> </el-menu-item> <el-menu-item index="1" :route="{ name: 'File', query: { fileType: 1 } }" > <i class="el-icon-picture-outline"></i> <span slot="title">图片</span> </el-menu-item> <el-menu-item index="2" :route="{ name: 'File', query: { fileType: 2 } }" > <i class="el-icon-document"></i> <span slot="title">文档</span> </el-menu-item> <el-menu-item index="3" :route="{ name: 'File', query: { fileType: 3 } }" > <i class="el-icon-video-camera"></i> <span slot="title">视频</span> </el-menu-item> <el-menu-item index="4" :route="{ name: 'File', query: { fileType: 4 } }" > <i class="el-icon-headset"></i> <span slot="title">音乐</span> </el-menu-item> <el-menu-item index="5" :route="{ name: 'File', query: { fileType: 5 } }" > <i class="el-icon-takeaway-box"></i> <span slot="title">其他</span> </el-menu-item> </el-submenu> <el-menu-item index="6" :route="{ name: 'File', query: { fileType: 6 } }" class="recovery" > <i class="el-icon-delete"></i> <span slot="title">回收站</span> </el-menu-item> <el-menu-item index="8" :route="{ name: 'File', query: { fileType: 8, filePath: '/' } }" class="my-share" > <i class="el-icon-share"></i> <span slot="title">我的分享</span> </el-menu-item> </el-menu> <!-- 存储信息显示 --> <div class="storage-wrapper" :class="{ fold: isCollapse }"> <el-progress :percentage="storagePercentage" :color="storageColor" :show-text="false" :type="isCollapse ? 'circle' : 'line'" :width="32" :stroke-width="isCollapse ? 4 : 6" stroke-linecap="square" ></el-progress> <div class="text" v-show="!isCollapse"> <span class="label">存储</span> <span> {{ $file.calculateFileSize(storageValue) }} / {{ $file.calculateFileSize(totalStorageValue) }} </span> </div> <div class="text" v-show="isCollapse"> <span>{{ $file.calculateFileSize(storageValue) }}</span> </div> </div> </el-drawer> <!-- 768px 以上,平铺展示 --> <template v-else> <!-- collapse 属性:控制菜单收缩展开 --> <el-menu class="side-menu" :default-active="activeIndex" :router="true" :collapse="isCollapse" > <el-submenu index="myFile" class="my-file"> <template slot="title"> <!-- 图标均来自 Element UI 官方图标库 https://element.eleme.cn/#/zh-CN/component/icon --> <i class="el-icon-files"></i> <span slot="title">我的文件</span> </template> <el-menu-item index="0" :route="{ name: 'netdiskfile', query: { fileType: 0, filePath: '/' } }" > <i class="el-icon-menu"></i> <span slot="title">全部</span> </el-menu-item> <el-menu-item index="1" :route="{ name: 'netdiskfile', query: { fileType: 1 } }" > <i class="el-icon-picture-outline"></i> <span slot="title">图片</span> </el-menu-item> <el-menu-item index="2" :route="{ name: 'netdiskfile', query: { fileType: 2 } }" > <i class="el-icon-document"></i> <span slot="title">文档</span> </el-menu-item> <el-menu-item index="3" :route="{ name: 'netdiskfile', query: { fileType: 3 } }" > <i class="el-icon-video-camera"></i> <span slot="title">视频</span> </el-menu-item> <el-menu-item index="4" :route="{ name: 'netdiskfile', query: { fileType: 4 } }" > <i class="el-icon-headset"></i> <span slot="title">音乐</span> </el-menu-item> <el-menu-item index="5" :route="{ name: 'netdiskfile', query: { fileType: 5 } }" > <i class="el-icon-takeaway-box"></i> <span slot="title">其他</span> </el-menu-item> </el-submenu> <el-menu-item index="6" :route="{ name: 'netdiskfile', query: { fileType: 6 } }" class="recovery" > <i class="el-icon-delete"></i> <span slot="title">回收站</span> </el-menu-item> <el-menu-item index="8" :route="{ name: 'netdiskfile', query: { fileType: 8, filePath: '/' } }" class="my-share" > <i class="el-icon-share"></i> <span slot="title">我的分享</span> </el-menu-item> </el-menu> <!-- 存储信息显示 --> <div class="storage-wrapper" :class="{ fold: isCollapse }"> <el-progress :percentage="storagePercentage" :color="storageColor" :show-text="false" :type="isCollapse ? 'circle' : 'line'" :width="32" :stroke-width="isCollapse ? 4 : 6" stroke-linecap="square" ></el-progress> <div class="text" v-show="!isCollapse"> <span class="label">存储</span> <span >{{ $file.calculateFileSize(storageValue) }} / {{ $file.calculateFileSize(totalStorageValue) }}</span > </div> <div class="text" v-show="isCollapse"> <span>{{ $file.calculateFileSize(storageValue) }}</span> </div> </div> </template> <!-- 展开 & 收缩分类栏 --> <el-tooltip effect="dark" :content="isCollapse ? '展开' : '收起'" placement="right" > <div class="aside-title" @click="isCollapse ? (isCollapse = false) : (isCollapse = true)" > <i class="icon" :class="isCollapse ? 'el-icon-d-arrow-right' : 'el-icon-d-arrow-left'" :title="isCollapse ? '展开' : '收起'" ></i> </div> </el-tooltip> </div> </template> <script> export default { name: 'SideMenu', data() { return { isDrawer: false, // 控制移动端菜单抽屉是否显示 isCollapse: false, // 控制菜单收缩展开 // 菜单 index 和名称 Map myFileMenuMap: { 0: '全部', 1: '图片', 2: '文档', 3: '视频', 4: '音乐', 5: '其他', 6: '回收站', 8: '我的分享' }, // 自定义进度条颜色,不同占比,进度条颜色不同 storageColor: [ { color: '#67C23A', percentage: 50 }, { color: '#E6A23C', percentage: 80 }, { color: '#F56C6C', percentage: 100 } ] } }, computed: { // 当前激活菜单的 index activeIndex() { return String(this.$route.query.fileType) // 获取当前路由参数中包含的文件类型 }, // 存储容量 storageValue() { console.log("storageValue =",this.$store.state.netdisk.storageValue) return this.$store.state.netdisk.storageValue }, totalStorageValue() { console.log("totalStorageValue =",this.$store.state.netdisk.totalStorageValue) return this.$store.state.netdisk.totalStorageValue }, // 存储百分比 storagePercentage() { return this.totalStorageValue ? (this.storageValue / this.totalStorageValue) * 100 : 0 }, // 屏幕宽度 screenWidth() { return this.$store.state.netdisk.screenWidth } }, watch: { // 监听左侧菜单切换,修改浏览器标签标题 activeIndex(newValue) { document.title = `${this.myFileMenuMap[Number(newValue)]} - ${ window._CONFIG['siteName'] }` this.isDrawer = false }, // 监听收缩状态变化,存储在 localStorage 中,保证页面刷新时仍然保存设置的状态 isCollapse(newValue) { localStorage.setItem('estar_is_collapse', newValue) if (this.screenWidth <= 768 && newValue) { this.isDrawer = true this.isCollapse = false } } }, created() { this.isCollapse = localStorage.getItem('estar_is_collapse') === 'true' // 读取保存的状态 }, mounted() { document.title = `${this.myFileMenuMap[Number(this.activeIndex)]} - ${ window._CONFIG['siteName'] }` } } </script> <style lang="stylus" scoped> @import '~@/assets/nd/styles/varibles.styl' @import '~@/assets/nd/styles/mixins.styl' .side-menu-wrapper { position: relative; height: calc(100vh - 61px); padding-right: 11px; .side-menu { // 高度设置为屏幕高度减去顶部导航栏的高度 height: calc(100vh - 127px); overflow: auto; // 调整滚动条样式 setScrollbar(6px, transparent, #C0C4CC); .el-menu-item.is-active { background: #ecf5ff; } .my-file, .recovery { box-shadow: 0 4px 12px 0 $BorderExtralight; } } >>> .el-menu { background: transparent; } // 对展开状态下的菜单设置宽度 .side-menu:not(.el-menu--collapse) { width: 210px; } // 存储空间展示区 .storage-wrapper { position: absolute; bottom: 0; left: 0; box-shadow: 0 -2px 12px 0 $BorderExtralight; border-right: solid 1px #e6e6e6; box-sizing: border-box; width: calc(100% - 11px); height: 66px; padding: 16px; z-index: 2; color: $PrimaryText; .text { margin-top: 8px; display: flex; justify-content: space-between; align-items: center; font-size: 12px; flex-wrap: wrap; } } .storage-wrapper.fold { padding: 0; >>> .el-progress--circle { margin: 0 auto; width: 32px; display: block; } .text { font-size: 12px; justify-content: center; } } // 折叠图标调整样式 .aside-title { position: absolute; top: calc(50% - 50px); right: 0; z-index: 2; background: $BorderBase; color: #fff; width: 12px; height: 100px; line-height: 100px; cursor: pointer; border-radius: 0 16px 16px 0; &:hover { opacity: 0.7; } .icon { font-size: 12px; } } } </style>
右边的文件列表主要就是显示界面上方的上传数据及批量操作行为,同时提供三种显示模式
<template> <div class="file-list-wrapper"> <!-- 操作按钮 --> <el-header height="auto"> <OperationMenu :fileType="fileType" :filePath="filePath" @getSearchFileList="getSearchFileList" @getTableDataByType="getTableDataByType" ></OperationMenu> </el-header> <div class="middle-wrapper"> <!-- 面包屑导航栏 --> <BreadCrumb class="breadcrumb" :fileType="fileType" :filePath="filePath" @getTableDataByType="getTableDataByType" ></BreadCrumb> </div> <!-- 文件列表-表格模式 --> <FileTable :fileType="fileType" :filePath="filePath" :fileList="fileList" :loading.sync="loading" v-if="fileModel === 0" @getTableDataByType="getTableDataByType" @click.native.right="handleClickRight" ></FileTable> <!-- 文件列表-网格模式 --> <FileGrid :fileType="fileType" :filePath="filePath" :fileList="fileList" :loading="loading" v-if="fileModel === 1" @getTableDataByType="getTableDataByType" @click.native.right="handleClickRight" ></FileGrid> <!-- 图片-时间线模式 --> <FileTimeLine class="image-model" :fileList="fileList" :loading.sync="loading" v-if="fileModel === 2 && fileType === 1" @getTableDataByType="getTableDataByType" @click.native.right="handleClickRight" ></FileTimeLine> <div class="pagination-wrapper"> <div class="current-page-count">当前页{{ fileList.length }}条</div> <!-- 回收站不展示分页组件 --> <el-pagination :current-page="pageData.currentPage" :page-size="pageData.pageCount" :total="pageData.total" :page-sizes="[10, 50, 100, 200]" :layout=" screenWidth <= 768 ? 'total, prev, next, jumper' : 'sizes, total, prev, pager, next' " @current-change="handleCurrentChange" @size-change="handleSizeChange" v-if="fileType !== 6" > </el-pagination> </div> </div> </template> <script> import OperationMenu from './components/OperationMenu.vue' import BreadCrumb from '../common/BreadCrumb.vue' import FileTable from '../common/FileTable.vue' import FileGrid from './components/FileGrid.vue' import FileTimeLine from './components/FileTimeLine.vue' import { getFileListByPath, getFileListByType, getRecoveryFile, getMyShareFileList, searchFile } from '@/api/netdisk/file.js' export default { name: 'FileList', components: { OperationMenu, BreadCrumb, FileTable, FileGrid, FileTimeLine }, data() { return { fileNameSearch: '', loading: true, // 表格数据-loading fileList: [], // 表格数据-文件列表 // 分页数据 pageData: { currentPage: 1, pageCount: 50, total: 0 } } }, computed: { // 左侧菜单选中的文件类型 fileType() { return this.$route.query.fileType ? Number(this.$route.query.fileType) : 0 }, // 当前所在路径 filePath() { return this.$route.query.filePath ? this.$route.query.filePath : '/' }, // 文件查看模式 0列表模式 1网格模式 2 时间线模式 fileModel() { return this.$store.getters.fileModel }, // 屏幕宽度 screenWidth() { return this.$store.state.netdisk.screenWidth } }, watch: { filePath() { // 当左侧菜单选择“全部”或“我的分享”,文件路径发生变化时,再重新获取文件列表 if (this.$route.name === 'netdiskfile' && [0, 8].includes(this.fileType)) { this.setPageCount() this.getTableDataByType() } }, fileType() { if (this.$route.name === 'netdiskfile') { this.setPageCount() this.getTableDataByType() } }, // 监听文件查看模式 fileModel() { this.setPageCount() } }, created() { this.setPageCount() this.getTableDataByType() }, methods: { /** * 文件展示区域的空白处右键事件 * @param {Document} event 右键事件对象 */ handleClickRight(event) { event.preventDefault() // 只有在全部页面才可以进行以下操作 if (![6, 8].includes(this.fileType)) { this.$openBox .contextMenu({ selectedFile: undefined, domEvent: event, serviceEl: this }) .then((res) => { if (res === 'confirm') { this.getTableDataByType() // 刷新文件列表 this.$store.dispatch('showStorage') // 刷新存储容量 } }) } }, /** * 表格数据获取相关事件 | 调整分页大小 */ setPageCount() { this.pageData.currentPage = 1 if (this.fileModel === 0) { this.pageData.pageCount = 50 } if (this.fileModel === 1) { this.pageData.pageCount = 100 } }, /** * 表格数据获取相关事件 | 获取文件列表数据 */ getTableDataByType() { this.loading = true console.log("getTableDataByType this.fileType=",this.fileType) // 分类型 if (Number(this.fileType)) { switch (Number(this.fileType)) { case 6: { this.showFileRecovery() // 回收站 break } case 8: { this.showMyShareFile() // 我的分享 break } default: { this.showFileList() break } } } else { // 全部文件 this.showFileList() } this.$store.dispatch('showStorage') }, /** * 表格数据获取相关事件 | 获取当前路径下的文件列表 */ showFileList() { let data = { fileType: this.fileType, filePath: this.filePath, currentPage: this.pageData.currentPage, pageCount: this.pageData.pageCount } getFileListByPath(data).then((res) => { if (res.success) { console.log("getFileListByPath res=",res); this.fileList = res.result.records this.pageData.total = Number(res.result.total) this.loading = false } else { this.$message.error(res.message) } }) }, /** * 表格数据获取相关事件 | 分页组件 | 当前页码改变 */ handleCurrentChange(currentPage) { this.pageData.currentPage = currentPage this.getTableDataByType() }, /** * 表格数据获取相关事件 | 分页组件 | 页大小改变时 */ handleSizeChange(pageCount) { this.pageData.pageCount = pageCount this.getTableDataByType() }, /** * 表格数据获取相关事件 | 获取回收站文件列表 */ showFileRecovery() { getRecoveryFile().then((res) => { console.log("getRecoveryFile res",res) if (res.success) { this.fileList = res.result this.loading = false } else { this.$message.error(res.message) } }) }, /** * 表格数据获取相关事件 | 获取我的分享列表 */ showMyShareFile() { let data = { shareFilePath: this.filePath, shareBatchNum: this.$route.query.shareBatchNum, currentPage: this.pageData.currentPage, pageCount: this.pageData.pageCount } getMyShareFileList(data).then((res) => { if (res.success) { this.fileList = res.result this.pageData.total = Number(res.total) this.loading = false } else { this.$message.error(res.message) } }) }, /** * 表格数据获取相关事件 | 根据文件类型展示文件列表 */ showFileListByType() { // 分类型 let data = { fileType: this.fileType, currentPage: this.pageData.currentPage, pageCount: this.pageData.pageCount } getFileListByType(data).then((res) => { if (res.success) { this.fileList = res.result.records this.pageData.total = Number(res.total) this.loading = false } else { this.$message.error(res.message) } }) }, /** * 获取搜索文件结果列表 * @param {string} fileName 文件名称 */ getSearchFileList(fileName) { this.loading = true let data = { fileName: fileName, filePath: this.filePath, currentPage: this.pageData.currentPage, pageCount: this.pageData.pageCount } searchFile(data).then((res) => { this.loading = false if (res.success) { this.fileList = res.result.records this.pageData.total = Number(res.total) this.loading = false } else { this.$message.error(res.message) } }) } } } </script> <style lang="stylus" scoped> @import '~@/assets/nd/styles/varibles.styl' @import '~@/assets/nd/styles/mixins.styl' .file-list-wrapper { >>> .el-header { padding: 0; } .middle-wrapper { margin-bottom: 8px; } .pagination-wrapper { position: relative; border-top: 1px solid $BorderBase; height: 44px; line-height: 44px; text-align: center; .current-page-count { position: absolute; left: 16px; height: 32px; line-height: 32px; font-size: 13px; color: $RegularText; } } } </style>
其中,文件列表模式如下
<template> <div class="file-table-wrapper"> <!-- 文件表格 --> <el-table class="file-table" :class="['file-type-' + fileType, routeName === 'Share' ? 'share' : '']" ref="multipleTable" fit v-loading="loading" element-loading-text="文件加载中……" tooltip-effect="dark" :data="fileList" :highlight-current-row="true" @selection-change="handleSelectRow" @sort-change="handleSortChange" @row-contextmenu="handleContextMenu" > <el-table-column type="selection" key="selection" width="56" align="center" v-if="fileType !== 8" ></el-table-column> <el-table-column label prop="isDir" key="isDir" :width="screenWidth <= 768 ? 40 : 56" align="center" class-name="file-icon-column" > <template slot-scope="scope"> <video style="width: 30px; max-height: 30px; cursor: pointer" v-if="$file.isVideoFile(scope.row)" :src="$file.setFileImg(scope.row)" ></video> <img :src="$file.setFileImg(scope.row)" :title="`${scope.row.isDir ? '' : '点击预览'}`" style="width: 30px; max-height: 30px; cursor: pointer" @click=" $file.handleFileNameClick(scope.row, scope.$index, sortedFileList) " v-else /> </template> </el-table-column> <el-table-column prop="fileName" key="fileName" :sort-by="['isDir', 'fileName']" sortable show-overflow-tooltip > <template slot="header"> <span>文件名</span> </template> <template slot-scope="scope"> <span @click=" $file.handleFileNameClick(scope.row, scope.$index, sortedFileList) " > <span class="file-name" style="cursor: pointer" :title="`${scope.row.isDir ? '' : '点击预览'}`" v-html="$file.getFileNameComplete(scope.row, true)" ></span> <div class="file-info" v-if="screenWidth <= 768"> {{ scope.row.uploadTime }} <span class="file-size"> {{ scope.row.isDir === 0 ? $file.calculateFileSize(scope.row.fileSize) : '' }} </span> </div> </span> </template> </el-table-column> <el-table-column :label="fileType === 6 ? '原路径' : '路径'" prop="filePath" key="filePath" show-overflow-tooltip v-if=" ![8].includes(Number($route.query.fileType)) && routeName !== 'Share' && screenWidth > 768 " > <template slot-scope="scope"> <span style="cursor: pointer" title="点击跳转" @click=" $router.push({ query: { filePath: scope.row.filePath, fileType: 0 } }) " >{{ scope.row.filePath }}</span > </template> </el-table-column> <el-table-column label="类型" width="80" prop="extendName" key="extendName" :sort-by="['isDir', 'extendName']" sortable show-overflow-tooltip v-if="selectedColumnList.includes('extendName') && screenWidth > 768" > <template slot-scope="scope"> <span>{{ $file.getFileType(scope.row) }}</span> </template> </el-table-column> <el-table-column label="大小" width="100" prop="fileSize" key="fileSize" :sort-by="['isDir', 'fileSize']" sortable align="right" v-if="selectedColumnList.includes('fileSize') && screenWidth > 768" > <template slot-scope="scope"> {{ scope.row.isDir === 0 ? $file.calculateFileSize(scope.row.fileSize) : '' }} </template> </el-table-column> <el-table-column label="修改日期" prop="uploadTime" key="uploadTime" width="160" :sort-by="['isDir', 'uploadTime']" sortable align="center" v-if=" selectedColumnList.includes('uploadTime') && ![7, 8].includes(fileType) && !['Share'].includes(this.routeName) && screenWidth > 768 " ></el-table-column> <el-table-column label="删除日期" prop="deleteTime" key="deleteTime" width="160" :sort-by="['isDir', 'deleteTime']" sortable align="center" v-if=" fileType === 6 && selectedColumnList.includes('deleteTime') && screenWidth > 768 " ></el-table-column> <el-table-column label="分享类型" prop="shareType" key="shareType" width="100" align="center" v-if="fileType === 8 && screenWidth > 768" > <template slot-scope="scope"> {{ scope.row.shareType === 1 ? '私密' : '公共' }} </template> </el-table-column> <el-table-column label="分享时间" prop="shareTime" key="shareTime" width="160" :sort-by="['isDir', 'shareTime']" show-overflow-tooltip sortable align="center" v-if="fileType === 8 && screenWidth > 768" ></el-table-column> <el-table-column label="过期时间" prop="endTime" key="endTime" width="190" :sort-by="['isDir', 'endTime']" show-overflow-tooltip sortable align="center" v-if="fileType === 8 && screenWidth > 768" > <template slot-scope="scope"> <div> <i class="el-icon-warning" v-if="$file.getFileShareStatus(scope.row.endTime)" ></i> <i class="el-icon-time" v-else></i> {{ scope.row.endTime }} </div> </template> </el-table-column> <el-table-column label="" key="operation" width="48" v-if="screenWidth <= 768" > <template slot-scope="scope"> <i class="file-operate el-icon-more" :class="`operate-more-${scope.$index}`" @click="handleClickMore(scope.row, $event)" ></i> </template> </el-table-column> </el-table> </div> </template> <script> import '@/assets/nd/styles/varibles.styl' import '@/assets/nd/styles/mixins.styl' import { mapGetters } from 'vuex' export default { name: 'FileTable', props: { // 文件类型 fileType: { required: true, type: Number }, // 文件路径 filePath: { required: true, type: String }, // 文件列表 fileList: { required: true, type: Array }, // 文件加载状态 loading: { required: true, type: Boolean } }, data() { return { officeFileType: ['ppt', 'pptx', 'doc', 'docx', 'xls', 'xlsx'], sortedFileList: [] // 排序后的表格数据 } }, computed: { // selectedColumnList: 判断当前用户设置的左侧栏是否折叠 ...mapGetters(['selectedColumnList']), // 路由名称 routeName() { return this.$route.name }, // 屏幕宽度 screenWidth() { return this.$store.state.netdisk.screenWidth } }, watch: { /** * 文件路径变化时清空表格已选行 */ filePath() { this.clearSelectedTable() this.$refs.multipleTable.clearSort() }, /** * 文件类型变化时清空表格已选行 */ fileType() { this.clearSelectedTable() this.$refs.multipleTable.clearSort() }, /** * 文件列表变化时清空表格已选行 */ fileList() { this.clearSelectedTable() this.$refs.multipleTable.clearSort() this.sortedFileList = this.fileList } }, methods: { /** * 当表格的排序条件发生变化的时候会触发该事件 */ handleSortChange() { this.sortedFileList = this.$refs.multipleTable.tableData }, /** * 表格某一行右键事件 * @description 打开右键菜单 * @param {object} row 当前行数据 * @param {object} column 当前列数据 * @param {object} event 当前右键元素 */ handleContextMenu(row, column, event) { // 阻止右键事件冒泡 event.cancelBubble = true // xs 以上的屏幕 if (this.screenWidth > 768) { event.preventDefault() this.$refs.multipleTable.setCurrentRow(row) // 选中当前行 this.$openBox .contextMenu({ selectedFile: row, domEvent: event }) .then((res) => { this.$refs.multipleTable.setCurrentRow() // 取消当前选中行 if (res === 'confirm') { this.$emit('getTableDataByType') // 刷新文件列表 this.$store.dispatch('showStorage') // 刷新存储容量 } }) } }, /** * 清空表格已选行 * @description 用于父组件调用 | 本组件调用,请勿删除 */ clearSelectedTable() { this.$refs.multipleTable.clearSelection() }, /** * 表格选择项发生变化时的回调函数 * @param {[]} selection 勾选的行数据 */ handleSelectRow(selection) { this.$store.commit('changeSelectedFiles', selection) this.$store.commit('changeIsBatchOperation', selection.length !== 0) }, /** * 更多图标点击事件 * @description 打开右键菜单 * @param {object} row 当前行数据 * @param {object} event 当前右键元素 */ handleClickMore(row, event) { this.$refs.multipleTable.setCurrentRow(row) // 选中当前行 this.$openBox .contextMenu({ selectedFile: row, domEvent: event }) .then((res) => { this.$refs.multipleTable.setCurrentRow() // 取消当前选中行 if (res === 'confirm') { this.$emit('getTableDataByType') // 刷新文件列表 this.$store.dispatch('showStorage') // 刷新存储容量 } }) } } } </script> <style lang="stylus" scoped> .file-table-wrapper { margin-top: 2px; .file-type-0 { height: calc(100vh - 206px) !important; >>> .el-table__body-wrapper { height: calc(100vh - 262px) !important; } } .file-type-6 { height: calc(100vh - 211px) !important; >>> .el-table__body-wrapper { height: calc(100vh - 263px) !important; } } .file-table.share { height: calc(100vh - 109px) !important; >>> .el-table__body-wrapper { height: calc(100vh - 161px) !important; } } .file-table { width: 100% !important; height: calc(100vh - 203px); >>> .el-table__header-wrapper { th { // background: $tabBackColor; padding: 4px 0; color: $RegularText; } .el-icon-circle-plus, .el-icon-remove { margin-left: 6px; cursor: pointer; font-size: 16px; &:hover { color: $Primary; } } } >>> .el-table__body-wrapper { height: calc(100vh - 255px); overflow-y: auto; setScrollbar(6px, transparent, #C0C4CC); td { padding: 8px 0; .file-name { .keyword { color: $Danger; } } } .el-icon-warning { font-size: 16px; color: $Warning; } .el-icon-time { font-size: 16px; color: $Success; } } } } .right-menu-list { position: fixed; display: flex; flex-direction: column; background: #fff; border: 1px solid $BorderLighter; border-radius: 4px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); z-index: 2; padding: 4px 0; color: $RegularText; .right-menu-item, .unzip-item { padding: 0 16px; height: 36px; line-height: 36px; cursor: pointer; &:hover { background: $PrimaryHover; color: $Primary; } i { margin-right: 8px; } } .unzip-menu-item { position: relative; &:hover { .unzip-list { display: block; } } .unzip-list { position: absolute; display: none; .unzip-item { width: 200px; setEllipsis(1) } } } } .right-menu-list, .unzip-list { background: #fff; border: 1px solid $BorderLighter; border-radius: 4px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); z-index: 2; padding: 4px 0; color: $RegularText; } </style>
上面主要是一个对文件或目录的右键菜单操作
<template> <!-- 右键列表 --> <transition name="el-fade-in-linear"> <!-- 在某个文件上右键 --> <ul class="right-menu-list" id="rightMenuList" v-show="visible" v-if="selectedFile !== undefined" :style="`top: ${rightMenu.top};right: ${rightMenu.right};bottom: ${rightMenu.bottom};left: ${rightMenu.left};`" > <li class="right-menu-item" @click="$file.handleFileNameClick(selectedFile, 0, [selectedFile])" v-if="seeBtnShow" > <i class="el-icon-view"></i> 查看 </li> <li class="right-menu-item" @click="handleDeleteFileBtnClick(selectedFile)" v-if="deleteBtnShow" > <i class="el-icon-delete"></i> 删除 </li> <li class="right-menu-item" @click="handleRestoreFileBtnClick(selectedFile)" v-if="restoreBtnShow" > <i class="el-icon-refresh-left"></i> 还原 </li> <li class="right-menu-item" @click="handleCopyFileBtnClick(selectedFile)" v-if="copyBtnShow" > <i class="el-icon-copy-document"></i> 复制到 </li> <li class="right-menu-item" @click="handleMoveFileBtnClick(selectedFile)" v-if="moveBtnShow" > <i class="el-icon-s-promotion"></i> 移动 </li> <li class="right-menu-item" @click="handleRenameFileBtnClick(selectedFile)" v-if="renameBtnShow" > <i class="el-icon-edit-outline"></i> 重命名 </li> <li class="right-menu-item" @click="handleShareFileBtnClick(selectedFile)" v-if="shareBtnShow" > <i class="el-icon-share"></i> 分享 </li> <li class="right-menu-item" @click="visible = false" v-if="downloadBtnShow" > <a target="_blank" style="display: block; color: inherit" :href="$file.getDownloadFilePath(selectedFile)" :download="selectedFile.fileName + '.' + selectedFile.extendName" > <i class="el-icon-download"></i> 下载 </a> </li> <!-- 0-解压到当前文件夹, 1-自动创建该文件名目录,并解压到目录里, 3-手动选择解压目录 --> <li class="right-menu-item unzip-menu-item" v-if="unzipBtnShow"> <i class="el-icon-files"></i> 解压缩 <i class="el-icon-arrow-right"></i> <ul class="unzip-list" :style="`top: ${unzipMenu.top};bottom: ${unzipMenu.bottom};left: ${unzipMenu.left};right: ${unzipMenu.right};`" > <li class="unzip-item" @click="handleUnzipFileBtnClick(selectedFile, 0)" > <i class="el-icon-files"></i> 解压到当前文件夹 </li> <li class="unzip-item" @click="handleUnzipFileBtnClick(selectedFile, 1)" :title="`解压到"${selectedFile.fileName}"`" > <i class="el-icon-files"></i> 解压到"{{ selectedFile.fileName }}" </li> <li class="unzip-item" @click="handleUnzipFileBtnClick(selectedFile, 2)" > <i class="el-icon-files"></i> 解压到目标文件夹 </li> </ul> </li> <!-- <li class="right-menu-item" @click="handleClickFolderEdit" v-if="folderEditBtnShow" > <i class="el-icon-edit"></i> 编辑文件夹 </li> --> <li class="right-menu-item" @click="handleClickFileEdit(selectedFile)" v-if="onlineEditBtnShow" > <i class="el-icon-edit"></i> 在线编辑 </li> <li class="right-menu-item" @click=" $file.copyShareLink( selectedFile.shareBatchNum, selectedFile.extractionCode ) " v-if="copyLinkBtnShow" > <i class="el-icon-edit"></i> 复制链接 </li> <li class="right-menu-item" @click="handleShowDetailInfo(selectedFile)" v-if="detailInfoBtnShow" > <i class="el-icon-document"></i> 文件详情 </li> </ul> <!-- 在空白处右键,右键列表展示新建文件夹、新建文件等操作按钮 --> <ul class="right-menu-list add" id="rightMenuList" v-show="visible" v-else :style="`top: ${rightMenu.top};right: ${rightMenu.right};bottom: ${rightMenu.bottom};left: ${rightMenu.left};`" > <li class="right-menu-item" @click="callback('confirm')"> <i class="el-icon-refresh"></i> 刷新 </li> <template v-if="fileType === 0"> <el-divider /> <li class="right-menu-item" @click="handleClickAddFolderBtn"> <i class="el-icon-folder-add"></i> 新建文件夹 </li> <li class="right-menu-item" @click="handleCreateFile('docx')"> <img :src="wordImg" />新建 Word 文档 </li> <li class="right-menu-item" @click="handleCreateFile('xlsx')"> <img :src="excelImg" />新建 Excel 工作表 </li> <li class="right-menu-item" @click="handleCreateFile('pptx')"> <img :src="pptImg" />新建 PPT 演示文稿 </li> <el-divider /> <li class="right-menu-item" @click="handleUploadFileBtnClick(1)"> <i class="el-icon-upload2"></i> 上传文件 </li> <li class="right-menu-item" @click="handleUploadFileBtnClick(2)"> <i class="el-icon-folder-opened"></i> 上传文件夹 </li> <li class="right-menu-item" @click="handleUploadFileBtnClick(3)"> <i class="el-icon-thumb"></i> 拖拽上传 </li> </template> </ul> </transition> </template> <script> import router from '@/router/index.js' import { officeFileType, fileSuffixCodeModeMap, markdownFileType } from '@/libs/map.js' export default { name: 'ContextMenu', data() { return { officeFileType, fileSuffixCodeModeMap, markdownFileType, visible: false, // 右键菜单是否显示 sortedFileList: [], // 排序后的表格数据 // 右键菜单 rightMenu: { top: 0, left: 0, bottom: 'auto', right: 'auto' }, // 右键解压缩菜单 unzipMenu: { top: 0, bottom: 'auto', left: '126px', right: 'auto' }, dirImg: require('@/assets/nd/images/file/dir.png'), wordImg: require('@/assets/nd/images/file/file_word.svg'), excelImg: require('@/assets/nd/images/file/file_excel.svg'), pptImg: require('@/assets/nd/images/file/file_ppt.svg') } }, computed: { // 路由名称 routeName() { return router.currentRoute.name }, // 左侧菜单选中的文件类型 fileType() { return router.currentRoute.query.fileType ? Number(router.currentRoute.query.fileType) : 0 }, // 当前路径 filePath() { return router.currentRoute.query.filePath }, // 查看按钮是否显示 seeBtnShow() { return this.fileType !== 6 }, // 删除按钮是否显示 deleteBtnShow() { return this.fileType !== 8 && !['Share'].includes(this.routeName) }, // 还原按钮是否显示 restoreBtnShow() { return this.fileType === 6 && !['Share'].includes(this.routeName) }, // 复制按钮是否显示 copyBtnShow() { return ( ![6, 8].includes(this.fileType) && !['Share'].includes(this.routeName) ) }, // 移动按钮是否显示 moveBtnShow() { return ( ![6, 8].includes(this.fileType) && !['Share'].includes(this.routeName) ) }, // 重命名按钮是否显示 renameBtnShow() { return ( ![6, 8].includes(this.fileType) && !['Share'].includes(this.routeName) ) }, // 分享按钮是否显示 shareBtnShow() { return ( ![6, 8].includes(this.fileType) && !['Share'].includes(this.routeName) ) }, // 下载按钮是否显示 downloadBtnShow() { return ![6, 8].includes(this.fileType) }, // 解压缩按钮是否显示 unzipBtnShow() { return ( ![6, 8].includes(this.fileType) && !['Share'].includes(this.routeName) && ['zip', 'rar', '7z', 'tar', 'gz'].includes(this.selectedFile.extendName) ) }, // 编辑文件夹按钮是否显示 folderEditBtnShow() { return ( ![6, 8].includes(this.fileType) && this.selectedFile.isDir === 1 && !['Share'].includes(this.routeName) ) }, // 在线编辑按钮是否显示 onlineEditBtnShow() { return ( ![6, 8].includes(this.fileType) && (this.officeFileType.includes(this.selectedFile.extendName) || this.markdownFileType.includes(this.selectedFile.extendName) || this.fileSuffixCodeModeMap.has(this.selectedFile.extendName)) && !['Share'].includes(this.routeName) ) }, // 复制链接按钮是否显示 copyLinkBtnShow() { return this.fileType === 8 }, // 文件详情按钮是否显示 detailInfoBtnShow() { return true }, // 上传文件组件参数 uploadFileParams() { return { filePath: this.filePath, isDir: 0 } } }, watch: { /** * 监听右键列表状态 * @description 右键列表打开时,body 添加点击事件的监听 */ visible(newValue) { if (newValue === true) { document.body.addEventListener('click', this.closeRightMenu) this.handleOpenContextMenu() } else { document.body.removeEventListener('click', this.closeRightMenu) } } }, methods: { /** * 打开右键菜单 */ handleOpenContextMenu() { // 纵坐标设置 if ( document.body.clientHeight - this.domEvent.clientY < document.querySelectorAll('#rightMenuList > .right-menu-item').length * 36 + 10 ) { // 如果到底部的距离小于元素总高度 this.rightMenu.top = 'auto' this.rightMenu.bottom = `${ document.body.clientHeight - this.domEvent.clientY }px` this.unzipMenu.top = 'auto' this.unzipMenu.bottom = '0px' } else { this.rightMenu.top = `${this.domEvent.clientY}px` this.rightMenu.bottom = 'auto' this.unzipMenu.top = '0px' this.unzipMenu.bottom = 'auto' } // 横坐标设置 if (document.body.clientWidth - this.domEvent.clientX < 138) { // 如果到右边的距离小于元素总宽度 this.rightMenu.left = 'auto' this.rightMenu.right = `${ document.body.clientWidth - this.domEvent.clientX }px` this.unzipMenu.left = '-200px' this.unzipMenu.right = 'auto' } else { this.rightMenu.left = `${this.domEvent.clientX + 8}px` this.rightMenu.right = 'auto' this.unzipMenu.left = '126px' this.unzipMenu.right = 'auto' } this.visible = true }, /** * 关闭右键列表 */ closeRightMenu(event) { if ( !event.target.className.includes('operate-more-') && !event.target.className.includes('unzip-menu-item') ) { this.visible = false if (this.selectedFile !== undefined) { // 不是在空白处右键时 this.callback('cancel') } } }, /** * 复制按钮点击事件 * @description 向父组件传递当前操作的文件信息,并打开“复制文件对话框” * @param {object} fileInfo 文件信息 */ handleCopyFileBtnClick(fileInfo) { this.visible = false this.$openDialog .copyFile({ fileInfo }) .then((res) => { this.callback(res) }) }, /** * 移动按钮点击事件 * @description 向父组件传递当前操作的文件信息,并打开“移动文件对话框” * @param {object} fileInfo 文件信息 */ handleMoveFileBtnClick(fileInfo) { this.visible = false this.$openDialog .moveFile({ isBatchMove: false, fileInfo }) .then((res) => { this.callback(res) }) }, /** * 解压缩按钮点击事件 * @description 调用解压缩文件接口,并展示新的文件列表 * @param {object} fileInfo 文件信息 * @param {number} unzipMode 解压模式 0-解压到当前文件夹, 1-自动创建该文件名目录,并解压到目录里, 2-手动选择解压目录 */ handleUnzipFileBtnClick(fileInfo, unzipMode) { this.visible = false this.$openDialog .unzipFile({ unzipMode: unzipMode, userFileId: fileInfo.userFileId }) .then((res) => { this.callback(res) }) }, /** * 删除按钮点击事件 * @description 区分 删除到回收站中 | 在回收站中彻底删除,打开确认对话框 * @param {object} fileInfo 文件信息 */ handleDeleteFileBtnClick(fileInfo) { this.visible = false this.$openDialog .deleteFile({ isBatchOperation: false, fileInfo, deleteMode: this.fileType === 6 ? 2 : 1 // 删除类型:1-删除到回收站 2-彻底删除 }) .then((res) => { this.callback(res) }) }, /** * 还原按钮点击事件 * @description 调用接口,在回收站中还原文件 * @param {object} fileInfo 文件信息 */ handleRestoreFileBtnClick(fileInfo) { this.visible = false this.$openDialog .restoreFile({ deleteBatchNum: fileInfo.deleteBatchNum, filePath: fileInfo.filePath }) .then((res) => { this.callback(res) }) }, /** * 文件重命名按钮点击事件 * @description 打开确认对话框让用户输入新的文件名 * @param {object} fileInfo 文件信息 */ handleRenameFileBtnClick(fileInfo) { this.visible = false this.$openDialog .renameFile({ oldFileName: fileInfo.fileName, userFileId: fileInfo.userFileId }) .then((res) => { this.callback(res) }) }, /** * 文件分享按钮点击事件 * @description 打开对话框让用户选择过期时间和提取码 * @param {object} fileInfo 文件信息 */ handleShareFileBtnClick(fileInfo) { this.visible = false this.$openDialog.shareFile({ fileInfo: [ { userFileId: fileInfo.userFileId } ] }) }, /** * 编辑文件夹按钮点击事件 */ handleClickFolderEdit() { router.push({ name: 'WebIDE', query: { filePath: this.selectedFile.filePath } }) }, /** * 文件在线编辑按钮点击事件 * @description 打开 代码预览对话框 或 office 编辑页面 * @param {object} fileInfo 文件信息 */ handleClickFileEdit(fileInfo) { if (this.officeFileType.includes(fileInfo.extendName)) { // office 编辑页面 this.$file.getFileOnlineEditPathByOffice(fileInfo) } else if (this.markdownFileType.includes(fileInfo.extendName)) { // markdown 编辑浮层 this.$openBox.markdownPreview({ fileInfo: fileInfo, editable: true }) } else { // 代码编辑对话框 this.$openBox.codePreview({ fileInfo: fileInfo, isEdit: true }) } }, /** * 文件详情按钮点击事件 * @description 打开对话框展示文件完整信息 * @param {object} fileInfo 文件信息 */ handleShowDetailInfo(fileInfo) { this.visible = false this.$openDialog.showFileDetail({ fileInfo }) }, /** * 新建文件夹按钮点击事件 * @description 调用新建文件夹服务,并在弹窗确认回调事件中刷新文件列表 */ handleClickAddFolderBtn() { this.$openDialog .addFolder({ filePath: router.currentRoute.query.filePath || '/' }) .then((res) => { this.callback(res) }) }, /** * 新建 office 文件 * @description 调用新建 office 文件服务,并在弹窗确认回调事件中刷新文件列表 * @param {string} 文件扩展名 docx xlsx pptx */ handleCreateFile(extendName) { this.$openDialog .addFile({ extendName: extendName }) .then((res) => { this.callback(res) }) }, /** * 上传文件按钮点击事件 * @description 通过Bus通信,开启全局上传文件流程 * @param {boolean} uploadWay 上传方式 0-文件上传 1-文件夹上传 2-粘贴图片或拖拽上传 */ handleUploadFileBtnClick(uploadWay) { this.$openBox.uploadFile({ params: this.uploadFileParams, uploadWay, serviceEl: this.serviceEl, callType: true // callType 调用此服务的方式:1 - 顶部栏,2 - 右键菜单 }) } } } </script> <style lang="stylus" scoped> @import '~@/assets/nd/styles/varibles.styl' @import '~@/assets/nd/styles/mixins.styl' .right-menu-list { position: fixed; display: flex; flex-direction: column; background: #fff; border: 1px solid $BorderLighter; border-radius: 4px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); z-index: 2; padding: 4px 0; color: $RegularText; list-style: none; .right-menu-item, .unzip-item { padding: 0 16px; height: 36px; line-height: 36px; cursor: pointer; &:hover { background: $PrimaryHover; color: $Primary; } i { margin-right: 8px; } } &.add { .right-menu-item { display: flex; align-items: center; img { margin-right: 4px; height: 20px; } i { margin-right: 4px; font-size: 18px; } } } .unzip-menu-item { position: relative; &:hover { .unzip-list { display: block; } } .unzip-list { position: absolute; display: none; list-style: none; .unzip-item { width: 200px; setEllipsis(1) } } } } .right-menu-list, .unzip-list { background: #fff; border: 1px solid $BorderLighter; border-radius: 6px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); z-index: 2; padding: 8px 0; color: $RegularText; font-size: 14px; .el-divider { margin: 2px 0; } } </style>
整个前端的主要操作与代码就是这样。