diff --git a/app/Http/Controllers/Api/FileController.php b/app/Http/Controllers/Api/FileController.php index 75752f32..fb5be709 100755 --- a/app/Http/Controllers/Api/FileController.php +++ b/app/Http/Controllers/Api/FileController.php @@ -318,7 +318,7 @@ class FileController extends AbstractController * @apiGroup file * @apiName move * - * @apiParam {Number} id 文件ID + * @apiParam {Numbers} ids 文件ID(格式:[id1, id2]) * @apiParam {Number} pid 移动到的文件夹ID * * @apiSuccess {Number} ret 返回状态码(1正确、0错误) @@ -329,30 +329,47 @@ class FileController extends AbstractController { $user = User::auth(); // - $id = intval(Request::input('id')); + $ids = Request::input('ids'); $pid = intval(Request::input('pid')); // - $file = File::permissionFind($id, 1000); - // + if (!is_array($ids) || empty($ids)) { + return Base::retError('请选择移动的文件或文件夹'); + } + if (count($ids) > 100) { + return Base::retError('一次最多只能移动100个文件或文件夹'); + } if ($pid > 0) { if (!File::whereUserid($user->userid)->whereId($pid)->exists()) { return Base::retError('参数错误'); } - $arr = []; - $tid = $pid; - while ($tid > 0) { - $arr[] = $tid; - $tid = intval(File::whereId($tid)->value('pid')); - } - if (in_array($id, $arr)) { - return Base::retError('位置错误'); - } } // - $file->pid = $pid; - $file->save(); - $file->pushMsg('update', $file); - return Base::retSuccess('操作成功', $file); + $files = []; + AbstractModel::transaction(function() use ($pid, $ids, &$files) { + foreach ($ids as $id) { + $file = File::permissionFind($id, 1000); + // + if ($pid > 0) { + $arr = []; + $tid = $pid; + while ($tid > 0) { + $arr[] = $tid; + $tid = intval(File::whereId($tid)->value('pid')); + } + if (in_array($id, $arr)) { + throw new ApiException('移动位置错误'); + } + } + // + $file->pid = $pid; + $file->save(); + $files[] = $file; + } + }); + foreach ($files as $file) { + $file->pushMsg('update', $file); + } + return Base::retSuccess('操作成功', $files); } /** @@ -363,7 +380,7 @@ class FileController extends AbstractController * @apiGroup file * @apiName remove * - * @apiParam {Number} id 文件ID + * @apiParam {Numbers} ids 文件ID(格式:[id1, id2]) * * @apiSuccess {Number} ret 返回状态码(1正确、0错误) * @apiSuccess {String} msg 返回信息(错误描述) @@ -373,12 +390,25 @@ class FileController extends AbstractController { User::auth(); // - $id = intval(Request::input('id')); + $ids = Request::input('ids'); // - $file = File::permissionFind($id, 1000); + if (!is_array($ids) || empty($ids)) { + return Base::retError('请选择删除的文件或文件夹'); + } + if (count($ids) > 100) { + return Base::retError('一次最多只能删除100个文件或文件夹'); + } // - $file->deleteFile(); - return Base::retSuccess('删除成功', $file); + $files = []; + AbstractModel::transaction(function() use ($ids, &$files) { + foreach ($ids as $id) { + $file = File::permissionFind($id, 1000); + $file->deleteFile(); + $files[] = $file; + } + }); + // + return Base::retSuccess('删除成功', $files); } /** diff --git a/resources/assets/js/pages/manage/file.vue b/resources/assets/js/pages/manage/file.vue index f1de3758..3149cdae 100644 --- a/resources/assets/js/pages/manage/file.vue +++ b/resources/assets/js/pages/manage/file.vue @@ -31,26 +31,24 @@ {{$L('只读')}} - + {{$L('粘贴')}} - "{{shearFiles[0].name}}" - {{$L('等')}}{{shearFiles.length}}{{$L('个文件')}} + "{{shearFirst.name}}" + {{$L('等')}}{{shearIds.length}}{{$L('个文件')}} - - - - - {{$L('剪切')}} - - - + + + + {{$L('剪切')}} + + {{$L('删除')}} + {{$L('取消选择')}} - {{$L('取消选择')}} @@ -61,7 +59,6 @@ - - + + - - + + @@ -136,6 +136,7 @@ {{$L('打开')}} + {{$L(selectIds.includes(contextMenuItem.id) ? '取消选择' : '选择')}} {{$L('新建')}} @@ -154,7 +155,6 @@ {{$L('重命名')}} {{$L('复制')}} {{$L('剪切')}} - {{showMultipleChoice?$L('取消多选'):$L('多选')}} {{$L('共享')}} {{$L('链接')}} @@ -349,7 +349,6 @@ export default { searchTimeout: null, pid: $A.getStorageInt("fileOpenPid"), - shearId: 0, types: [ { @@ -453,10 +452,8 @@ export default { left: 0 }, - selectFile: [], - fileChecked: [], - shearFiles: [], - showMultipleChoice: false + shearIds: [], + selectIds: [], } }, @@ -486,17 +483,6 @@ export default { } }, - shearFile() { - const {files, shearId} = this; - if (shearId > 0) { - let file = files.find(({id}) => id == shearId); - if (file) { - return file; - } - } - return null; - }, - shareAlready() { let data = this.shareList ? this.shareList.map(({userid}) => userid) : []; if (this.shareInfo.userid) { @@ -506,17 +492,29 @@ export default { }, fileList() { - const {files, searchKey, pid} = this; - return sortBy(files.filter((file) => { + const {files, searchKey, pid, selectIds} = this; + const list = $A.cloneJSON(sortBy(files.filter((file) => { if (searchKey) { return file.name.indexOf(searchKey) !== -1; } return file.pid == pid; }), (file) => { return (file.type == 'folder' ? 'a' : 'b') + file.name; + })); + return list.map(item => { + item._checked = selectIds.includes(item.id) + return item; }) }, + shearFirst() { + const {files, shearIds} = this; + if (shearIds.length === 0) { + return null; + } + return files.find(item => item.id == shearIds[0]) + }, + navigator() { let {pid, files} = this; let array = []; @@ -540,30 +538,12 @@ export default { watch: { pid() { + this.selectIds = []; this.getFileList(); }, tableMode(val) { $A.setStorage("fileTableMode", val) - // 切换显示模式时把选中的数据转移 - if ( val === true ) { - if ( this.fileChecked.length > 0 ) { - for (let i = 0; i < this.fileList.length; i++) { - if ( this.fileChecked[this.fileList[i].id] === true ) - this.fileList[i]["_checked"] = true; - else - this.fileList[i]["_checked"] = false; - } - } - } else { - this.fileChecked = []; // 清空 - if ( this.selectFile.length > 0 ) { - for (let i = 0; i < this.selectFile.length; i++) { - this.fileChecked[this.selectFile[i].id] = true; - } - } - } - }, fileShow(val) { @@ -575,6 +555,24 @@ export default { } }, + selectIds: { + handler(ids) { + if (ids.length > 0) { + this.shearIds = []; + } + }, + deep: true + }, + + shearIds: { + handler(list) { + if (list.length > 0) { + this.selectIds = []; + } + }, + deep: true + }, + wsOpenNum(num) { if (num <= 1) return this.wsOpenTimeout && clearTimeout(this.wsOpenTimeout) @@ -591,8 +589,8 @@ export default { this.columns = [ { type: 'selection', - width: 60, - align: 'center' + width: 50, + align: 'right' }, { title: this.$L('文件名'), @@ -704,7 +702,7 @@ export default { ])) } return h('div', { - class: 'file-nbox' + class: `file-nbox ${this.shearIds.includes(row.id) ? 'shear' : ''}`, }, [ h('div', { class: `no-dark-mode-before file-name file-icon ${row.type}`, @@ -843,8 +841,6 @@ export default { this.searchKey = ''; this.pid = item.id; } else { - // 清空已选项 - this.clearSelect() if (this.$Electron) { this.openSingle(item); } else { @@ -870,10 +866,12 @@ export default { }); }, - clickRow(row) { - // 清空已选择的行 - this.clearSelect(); - this.dropFile(row, 'open'); + clickRow(row, column) { + if (column.type == "selection") { + this.dropFile(row, 'select'); + } else { + this.dropFile(row, 'open'); + } }, handleContextMenu(row, event) { @@ -904,6 +902,15 @@ export default { this.openFile(item, false); break; + case 'select': + let index = this.selectIds.findIndex(id => id == item.id) + if (index > -1) { + this.selectIds.splice(index, 1) + } else { + this.selectIds.push(item.id) + } + break; + case 'rename': this.$set(item, 'newname', item.name); this.setEdit(item.id, true) @@ -925,42 +932,11 @@ export default { break; case 'shear': - this.showMultipleChoice = true; - this.fileChecked[item.id] = true; - this.onFileCheckClick(item); - for (const item of this.selectFile) { - let selected = false; - for (const shearFile of this.shearFiles) { - if ( shearFile.id === item.id ) { - selected = true; - break; - } - } - if (!selected) - this.shearFiles.push(item); - } + this.shearIds = [item.id]; break; - case 'multipleChoice': - this.showMultipleChoice = !this.showMultipleChoice; - if(this.showMultipleChoice === false){ - this.clearSelect(); - } - break; - - case 'batchShear': - // 排除目录 - for (const item of this.selectFile) { - let selected = false; - for (const shearFile of this.shearFiles) { - if ( shearFile.id === item.id ) { - selected = true; - break; - } - } - if (!selected) - this.shearFiles.push(item); - } + case 'shearSelect': + this.shearIds = $A.cloneJSON(this.selectIds); break; case 'share': @@ -1019,27 +995,7 @@ export default { break; case 'delete': - let typeName = item.type == 'folder' ? '文件夹' : '文件'; - $A.modalConfirm({ - title: '删除' + typeName, - content: '你确定要删除' + typeName +'【' + item.name + '】吗?', - loading: true, - onOk: () => { - this.$store.dispatch("call", { - url: 'file/remove', - data: { - id: item.id, - }, - }).then(({msg}) => { - $A.messageSuccess(msg); - this.$Modal.remove(); - this.$store.dispatch("forgetFile", item.id); - }).catch(({msg}) => { - $A.modalError(msg, 301); - this.$Modal.remove(); - }); - } - }); + this.deleteFile([item.id]) break; } }, @@ -1080,6 +1036,61 @@ export default { this.$refs.linkInput.focus({cursor:'all'}); }, + shearTo() { + if (this.shearIds.length == 0) { + return; + } + this.$store.dispatch("call", { + url: 'file/move', + data: { + ids: this.shearIds, + pid: this.pid, + }, + }).then(({data, msg}) => { + $A.messageSuccess(msg); + this.shearIds = []; + this.$store.dispatch("saveFile", data); + }).catch(({msg}) => { + $A.modalError(msg); + }); + }, + + deleteFile(ids) { + if (ids.length === 0) { + return + } + const firstFile = this.files.find(item => item.id == ids[0]) || {}; + const allFolder = !ids.find(id => { + return this.files.find(item => item.type != 'folder' && item.id == id) + }); + let typeName = allFolder ? "文件夹" : "文件" + let fileName = `【${firstFile.name}】等${ids.length}个${typeName}` + if (ids.length === 1) { + fileName = `【${firstFile.name}】${typeName}` + } + $A.modalConfirm({ + title: '删除' + typeName, + content: '你确定要删除' + fileName + '吗?', + loading: true, + onOk: () => { + this.$store.dispatch("call", { + url: 'file/remove', + data: { + ids, + }, + }).then(({msg}) => { + $A.messageSuccess(msg); + this.$Modal.remove(); + this.$store.dispatch("forgetFile", ids); + this.selectIds = this.selectIds.filter(id => !ids.includes(id)) + }).catch(({msg}) => { + $A.modalError(msg, 301); + this.$Modal.remove(); + }); + } + }); + }, + autoBlur(id) { this.$nextTick(() => { if (this.$refs['input_' + id]) { @@ -1256,6 +1267,14 @@ export default { return $A.getObject(item, 'response.data.full_name') || item.name }, + handleTableSelect(selection) { + this.selectIds = selection.map(item => item.id); + }, + + clearSelect() { + this.selectIds = []; + }, + /********************文件上传部分************************/ uploadUpdate(fileList) { @@ -1330,111 +1349,6 @@ export default { this.uploadShow = true; return true; }, - - handleTableSelect(selection, row) { - this.selectFile = selection; - this.fileChecked = []; - if (this.selectFile.length > 0) { - for (let i = 0; i < this.selectFile.length; i++) { - this.fileChecked[this.selectFile[i].id] = true; - } - } - for (let i = 0; i < this.fileList.length; i++) { - if (this.fileChecked[this.fileList[i].id] === true) - this.fileList[i]["_checked"] = true; - else - this.fileList[i]["_checked"] = false; - } - // 需要清空剪切的文件 - this.shearFiles = []; - }, - - deleteSelectFile() { - if ( this.selectFile.length <= 0 ) { - $A.messageError("未选择任何文件或文件夹"); - return false; - } - let s_ids = this.selectFile.map( (item, index) => { - return item.id; - } ); - $A.modalConfirm({ - title: '批量删除', - content: '你确定要删除这些文件吗?', - loading: true, - onOk: () => { - this.$store.dispatch("call", { - url: 'file/batch/remove', - data: { - ids: s_ids, - }, - }).then(() => { - this.$Modal.remove(); - this.selectFile = []; - this.fileChecked = []; - this.showMultipleChoice = false; - $A.messageSuccess("已提交至后台处理,请稍后再回来查看结果吧"); - }).catch(({msg}) => { - $A.modalError(msg, 301); - this.$Modal.remove(); - }); - } - }); - }, - - onFileCheckClick(file) { - if (this.fileChecked[file.id] === true && !$A.inArray(file.id, this.selectFile)) - this.selectFile.push(file); - else if (this.fileChecked[file.id] === false) { - let index = -1; - for (let i = 0; i < this.selectFile.length; i++) { - if (parseInt(this.selectFile[i].id) === parseInt(file.id)) { - index = i; - break; - } - } - // 删除对应Id - if (index >= 0) - this.selectFile.splice(index, 1); - } - if (this.selectFile.length === 0) { - this.showMultipleChoice = false; - } - // 需要清空剪切的文件 - this.shearFiles = []; - }, - batchShearTo() { - if (!this.shearFiles) { - return; - } - this.$store.dispatch("call", { - url: 'file/batch/move', - data: { - ids: this.shearFiles.map( (item,index) => item.id ), - pid: this.pid, - }, - }).then(({data, msg}) => { - $A.messageSuccess(msg); - // 清空数据 - this.shearId = 0; - this.shearFiles = []; - this.selectFile = []; - this.fileChecked = []; - for (const item of data) { - this.$store.dispatch("saveFile", item); - } - }).catch(({msg}) => { - $A.modalError(msg); - }); - }, - - clearSelect() { - this.shearFiles = []; - this.selectFile = []; - this.fileChecked = []; - this.showMultipleChoice = false; - if ( this.tableMode ) // 如果是表格模式,则将表格取消全选 - this.$refs.fileListTable.selectAll(false); - }, } } diff --git a/resources/assets/sass/dark.scss b/resources/assets/sass/dark.scss index 3960e961..8321901b 100644 --- a/resources/assets/sass/dark.scss +++ b/resources/assets/sass/dark.scss @@ -288,4 +288,14 @@ body.dark-mode-reverse { } } } + + .page-file { + .file-wrapper { + .file-navigator { + .ivu-btn { + color: #000000; + } + } + } + } } diff --git a/resources/assets/sass/pages/page-file.scss b/resources/assets/sass/pages/page-file.scss index 1362eeb9..64ef75ab 100644 --- a/resources/assets/sass/pages/page-file.scss +++ b/resources/assets/sass/pages/page-file.scss @@ -284,6 +284,9 @@ display: flex; align-items: center; position: relative; + &.shear { + opacity: 0.38; + } .file-name { flex: 1; width: 0; @@ -364,6 +367,16 @@ white-space: nowrap; text-overflow: ellipsis; } + .file-check { + opacity: 0; + position: absolute; + top: 1px; + left: 4px; + transition: opacity 0.2s; + &.file-checked { + opacity: 1; + } + } .file-menu { opacity: 0; position: absolute; @@ -422,18 +435,8 @@ } &:hover { background-color: #f4f5f7; - .file-menu, .file-check { - opacity: 1; - } - } - .file-check { - opacity: 0; - position: absolute; - top: 2px; - left: 2px; - transition: opacity 0.2s; - - &.file-checked { + .file-menu, + .file-check { opacity: 1; } }