diff --git a/app/Http/Controllers/Api/FileController.php b/app/Http/Controllers/Api/FileController.php index 15695999..8130e670 100755 --- a/app/Http/Controllers/Api/FileController.php +++ b/app/Http/Controllers/Api/FileController.php @@ -26,16 +26,25 @@ class FileController extends AbstractController // $pid = intval(Request::input('pid')); // - $list = File::whereUserid($user->userid)->wherePid($pid)->orderBy('name')->take(500)->get(); + $list = File::whereUserid($user->userid)->wherePid($pid)->take(500)->get(); + $array = $list->toArray(); // - return Base::retSuccess('success', $list); + while ($pid > 0) { + $file = File::whereUserid($user->userid)->whereId($pid)->first(); + if ($file) { + $array[] = $file->toArray(); + $pid = $file->pid; + } + } + return Base::retSuccess('success', $array); } /** - * 添加项目 + * 添加、修改文件(夹) * * @apiParam {String} name 项目名称 * @apiParam {String} type 文件类型 + * @apiParam {Number} [id] 文件ID(赋值修改文件名称) * @apiParam {Number} [pid] 父级ID */ public function add() @@ -44,6 +53,7 @@ class FileController extends AbstractController // 文件名称 $name = trim(Request::input('name')); $type = trim(Request::input('type')); + $id = intval(Request::input('id')); $pid = intval(Request::input('pid')); if (mb_strlen($name) < 2) { return Base::retError('文件名称不可以少于2个字'); @@ -51,34 +61,137 @@ class FileController extends AbstractController return Base::retError('文件名称最多只能设置32个字'); } // - if (!in_array($type, [ - 'folder', - 'document', - 'mind', - 'sheet', - 'flow', - ])) { - return Base::retError('类型错误'); + if ($id > 0) { + // 修改 + $file = File::whereUserid($user->userid)->whereId($id)->first(); + if (empty($file)) { + return Base::retError('文件不存在或已被删除'); + } + $file->name = $name; + $file->save(); + return Base::retSuccess('修改成功', $file); + } else { + // 添加 + if (!in_array($type, [ + 'folder', + 'document', + 'mind', + 'sheet', + 'flow', + ])) { + return Base::retError('类型错误'); + } + // + if ($pid > 0) { + if (!File::whereUserid($user->userid)->whereId($pid)->exists()) { + return Base::retError('参数错误'); + } + } + if (File::whereUserid($user->userid)->wherePid($pid)->count() >= 300) { + return Base::retError('每个文件夹里最多只能创建300个文件或文件夹'); + } + // 开始创建 + $file = File::createInstance([ + 'pid' => $pid, + 'name' => $name, + 'type' => $type, + 'userid' => $user->userid, + ]); + $file->save(); + // + $data = File::find($file->id); + return Base::retSuccess('添加成功', $data); + } + } + + /** + * 复制文件(夹) + * + * @apiParam {Number} id 文件ID + */ + public function copy() + { + $user = User::auth(); + // + $id = intval(Request::input('id')); + // + $row = File::whereUserid($user->userid)->whereId($id)->first(); + if (empty($row)) { + return Base::retError('文件不存在或已被删除'); + } + if ($row->type == 'folder') { + return Base::retError('不支持复制文件夹'); + } + $num = File::whereCid($row->id)->count() + 1; + $name = $row->name . " ({$num})"; + // 开始复制 + $file = File::createInstance([ + 'cid' => $row->id, + 'pid' => $row->pid, + 'name' => $name, + 'type' => $row->type, + 'userid' => $user->userid, + ]); + $file->save(); + // + $data = File::find($file->id); + return Base::retSuccess('复制成功', $data); + } + + /** + * 移动文件(夹) + * + * @apiParam {Number} id 文件ID + * @apiParam {Number} pid 移动到的文件夹ID + */ + public function move() + { + $user = User::auth(); + // + $id = intval(Request::input('id')); + $pid = intval(Request::input('pid')); + // + $file = File::whereUserid($user->userid)->whereId($id)->first(); + if (empty($file)) { + return Base::retError('文件不存在或已被删除'); } // 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('位置错误'); + } } - if (File::whereUserid($user->userid)->wherePid($pid)->count() >= 300) { - return Base::retError('每个文件夹里最多只能创建300个文件或文件夹'); - } - // 开始创建 - $file = File::createInstance([ - 'pid' => $pid, - 'name' => $name, - 'type' => $type, - 'userid' => $user->userid, - ]); - $file->save(); // - $data = File::find($file->id); - return Base::retSuccess('添加成功', $data); + $file->pid = $pid; + $file->save(); + return Base::retSuccess('操作成功', $file); + } + + /** + * 删除文件(夹) + * + * @apiParam {Number} id 文件ID + */ + public function remove() + { + $user = User::auth(); + // + $id = intval(Request::input('id')); + // + $file = File::whereUserid($user->userid)->whereId($id)->first(); + if (empty($file)) { + return Base::retError('文件不存在或已被删除'); + } + $file->deleteFile(); + return Base::retSuccess('删除成功', $file); } } diff --git a/app/Models/File.php b/app/Models/File.php index 9e78d8eb..5ef53784 100644 --- a/app/Models/File.php +++ b/app/Models/File.php @@ -2,30 +2,57 @@ namespace App\Models; +use Illuminate\Database\Eloquent\SoftDeletes; + /** * Class File * * @package App\Models * @property int $id * @property int|null $pid 上级ID + * @property int|null $cid 复制ID * @property string|null $name 名称 * @property string|null $type 类型 * @property int|null $userid 拥有者ID * @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $updated_at + * @property \Illuminate\Support\Carbon|null $deleted_at * @method static \Illuminate\Database\Eloquent\Builder|File newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|File newQuery() + * @method static \Illuminate\Database\Query\Builder|File onlyTrashed() * @method static \Illuminate\Database\Eloquent\Builder|File query() + * @method static \Illuminate\Database\Eloquent\Builder|File whereCid($value) * @method static \Illuminate\Database\Eloquent\Builder|File whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|File whereDeletedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|File whereId($value) * @method static \Illuminate\Database\Eloquent\Builder|File whereName($value) * @method static \Illuminate\Database\Eloquent\Builder|File wherePid($value) * @method static \Illuminate\Database\Eloquent\Builder|File whereType($value) * @method static \Illuminate\Database\Eloquent\Builder|File whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|File whereUserid($value) + * @method static \Illuminate\Database\Query\Builder|File withTrashed() + * @method static \Illuminate\Database\Query\Builder|File withoutTrashed() * @mixin \Eloquent */ class File extends AbstractModel { + use SoftDeletes; + /** + * 遍历删除文件(夹) + * @return bool + */ + public function deleteFile() + { + AbstractModel::transaction(function () { + $this->delete(); + $list = self::wherePid($this->id)->get(); + if ($list->isNotEmpty()) { + foreach ($list as $item) { + $item->deleteFile(); + } + } + }); + return true; + } } diff --git a/app/Models/FileContent.php b/app/Models/FileContent.php new file mode 100644 index 00000000..905209c4 --- /dev/null +++ b/app/Models/FileContent.php @@ -0,0 +1,31 @@ +
-

{{$L('文件')}}

- -
+
+
    +
  • {{$L('全部文件')}}
  • +
  • {{$L('搜索')}} "{{searchKey}}"
  • +
  • {{item.name}}
  • +
+ +
+

{{$L('没有任何文件')}}

    -
  • +
  • + + + + {{$L('打开')}} + {{$L('重命名')}} + {{$L('复制')}} + {{$L('剪切')}} + {{$L('删除')}} + +
    -
    {{item.name}}
    -
  • -
  • -
    -
    - -
    -
    -
    {{item.name}}
    +
    {{item.name}}
@@ -68,7 +85,10 @@ export default { data() { return { loadIng: 0, + searchKey: '', + pid: 0, + shearId: 0, types: [ {value: 'folder', name: "目录"}, @@ -77,6 +97,7 @@ export default { {value: 'sheet', name: "表格"}, {value: 'flow', name: "流程图"}, ], + files: [], } }, @@ -92,12 +113,40 @@ export default { computed: { ...mapState(['userInfo']), - folderList() { - return sortBy(this.files.filter(({type, pid}) => pid == this.pid && type == 'folder'), 'name') + shearFile() { + const {files, shearId} = this; + if (shearId > 0) { + let file = files.find(({id}) => id == shearId); + if (file) { + return file; + } + } + return null; }, fileList() { - return sortBy(this.files.filter(({type, pid}) => pid == this.pid && type != 'folder'), 'name') + const {files, searchKey, pid} = this; + return 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; + }) + }, + + navigator() { + let {pid, files} = this; + let array = []; + while (pid > 0) { + let file = files.find(({id}) => id == pid); + if (file) { + array.unshift(file); + pid = file.pid; + } + } + return array; } }, @@ -160,44 +209,144 @@ export default { }, openFile(item) { + if (item._edit || item._load) { + return; + } if (item.type == 'folder') { + this.searchKey = ''; this.pid = item.id; } }, - onBlur(item) { - if (!item.newname) { - this.files = this.files.filter(({id}) => id != item.id); + dropFile(item, command) { + switch (command) { + case 'open': + this.openFile(item); + break; + + case 'rename': + this.$set(item, 'newname', item.name); + this.$set(item, '_edit', true); + this.$nextTick(() => { + this.$refs['input_' + item.id][0].focus({ + cursor: 'all' + }) + }) + break; + + case 'copy': + this.$store.dispatch("call", { + url: 'file/copy', + data: { + id: item.id, + }, + }).then(({data, msg}) => { + $A.messageSuccess(msg); + this.saveFile(data); + }).catch(({msg}) => { + $A.modalError(msg); + }); + break; + + case 'shear': + this.shearId = item.id; + 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.removeFile(item.id); + }).catch(({msg}) => { + $A.modalError(msg); + this.$Modal.remove(); + }); + } + }); + break; } }, - onEnter(item) { - if (!item.newname) { - this.files = this.files.filter(({id}) => id != item.id); - } else { - if (item._load) { - return; - } - this.$set(item, '_load', true); - this.$store.dispatch("call", { - url: 'file/add', - data: { - pid: item.pid, - name: item.newname, - type: item.type, - }, - }).then(({data, msg}) => { - $A.messageSuccess(msg) - this.$set(item, '_load', false); - this.$set(item, '_edit', false); - this.files = this.files.filter(({id}) => id != item.id); - this.saveFile(data); - }).catch(({msg}) => { - $A.modalError(msg) - this.$set(item, '_edit', false); - this.files = this.files.filter(({id}) => id != item.id); - }) + shearTo() { + if (!this.shearFile) { + return; } + this.$store.dispatch("call", { + url: 'file/move', + data: { + id: this.shearFile.id, + pid: this.pid, + }, + }).then(({data, msg}) => { + $A.messageSuccess(msg); + this.shearId = 0; + this.saveFile(data); + }).catch(({msg}) => { + $A.modalError(msg); + }); + }, + + removeFile(id) { + this.files = this.files.filter((file) => file.id != id); + this.files.forEach((file) => { + if (file.pid == id) { + this.removeFile(file.id); + } + }); + }, + + onBlur(item) { + this.onEnter(item); + }, + + onEnter(item) { + let isCreate = !/^\d+$/.test(item.id); + if (!item.newname) { + if (isCreate) { + this.files = this.files.filter(({id}) => id != item.id); + } else { + this.$set(item, '_edit', false); + } + return; + } + if (item._load) { + return; + } + this.$set(item, '_load', true); + this.$store.dispatch("call", { + url: 'file/add', + data: { + id: isCreate ? 0 : item.id, + pid: item.pid, + name: item.newname, + type: item.type, + }, + }).then(({data, msg}) => { + $A.messageSuccess(msg) + this.$set(item, '_load', false); + this.$set(item, '_edit', false); + this.saveFile(data); + if (isCreate) { + this.files = this.files.filter(({id}) => id != item.id); + } + }).catch(({msg}) => { + $A.modalError(msg) + this.$set(item, '_load', false); + if (isCreate) { + this.files = this.files.filter(({id}) => id != item.id); + } + }) } } } diff --git a/resources/assets/sass/iconfont.scss b/resources/assets/sass/iconfont.scss index 292d4997..4b341b7b 100644 --- a/resources/assets/sass/iconfont.scss +++ b/resources/assets/sass/iconfont.scss @@ -1,8 +1,8 @@ @font-face { font-family: 'iconfont'; /* Project id 2583385 */ - src: url('//at.alicdn.com/t/font_2583385_9cbmdxw5tl.woff2?t=1624963755686') format('woff2'), - url('//at.alicdn.com/t/font_2583385_9cbmdxw5tl.woff?t=1624963755686') format('woff'), - url('//at.alicdn.com/t/font_2583385_9cbmdxw5tl.ttf?t=1624963755686') format('truetype'); + src: url('//at.alicdn.com/t/font_2583385_0n9xtibhf9vq.woff2?t=1625032849662') format('woff2'), + url('//at.alicdn.com/t/font_2583385_0n9xtibhf9vq.woff?t=1625032849662') format('woff'), + url('//at.alicdn.com/t/font_2583385_0n9xtibhf9vq.ttf?t=1625032849662') format('truetype'); } .iconfont { diff --git a/resources/assets/sass/pages/page-file.scss b/resources/assets/sass/pages/page-file.scss index 73ecbf02..e34bb3a7 100644 --- a/resources/assets/sass/pages/page-file.scss +++ b/resources/assets/sass/pages/page-file.scss @@ -13,23 +13,29 @@ padding-bottom: 16px; margin: 32px 32px 16px; border-bottom: 1px solid #F4F4F5; - > h1 { + .file-nav { flex: 1; - color: #333333; - font-size: 28px; - font-weight: 600; + display: flex; + align-items: center; + > h1 { + color: #333333; + font-size: 28px; + font-weight: 600; + } } .file-search { flex-shrink: 0; - margin-left: 24px; + margin-left: 22px; cursor: pointer; - .iconfont { - font-size: 18px; + width: 140px; + transition: width 0.3s; + &.has-value { + width: 180px; } } .file-add { flex-shrink: 0; - margin-left: 24px; + margin-left: 22px; cursor: pointer; .iconfont { font-size: 18px; @@ -54,6 +60,59 @@ line-height: 1; } } + .file-navigator { + display: flex; + align-items: center; + height: 24px; + line-height: 24px; + margin: 0 32px 0; + > ul { + display: flex; + align-items: center; + > li { + display: flex; + list-style: none; + align-items: center; + padding-left: 8px; + font-size: 14px; + color: #09aaff; + cursor: pointer; + &:last-child { + color: #515a6e; + cursor: default; + } + &+li:before { + content: "\203a"; + margin-top: -2px; + padding-right: 8px; + color: #515a6e; + line-height: 1; + font-size: 16px; + font-weight: 500; + font-family: system-ui, sans-serif; + } + } + } + .ivu-btn { + font-size: 12px; + margin-left: 12px; + } + .file-shear { + display: flex; + align-items: center; + > span { + padding-right: 3px; + } + > em { + display: inline-block; + max-width: 120px; + font-style: normal; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + } + } .file-list { flex: 1; padding: 0 20px 20px; @@ -98,7 +157,7 @@ width: 100%; height: 20px; line-height: 20px; - color: #666; + color: #515a6e; font-size: 12px; text-align: center; margin-top: 5px; @@ -108,13 +167,33 @@ white-space: nowrap; text-overflow: ellipsis; } + .file-menu { + opacity: 0; + position: absolute; + top: 0; + right: 0; + transition: opacity 0.2s; + display: flex; + .ivu-icon { + font-size: 16px; + color: #aaaaaa; + transition: color 0.2s; + padding: 2px 5px; + &:hover { + color: #515a6e; + } + } + } .file-icon { display: inline-block; - width: 38px; - height: 38px; - background: no-repeat center center; + width: 46px; + height: 46px; + background-repeat: no-repeat; background-size: contain; - margin-top: 14px; + margin-top: 12px; + } + &.shear { + opacity: 0.38; } &.folder .file-icon { background-image: url("../images/file/folder.svg"); @@ -133,6 +212,9 @@ } &:hover { background-color: #f4f5f7; + .file-menu { + opacity: 1; + } } } } @@ -147,11 +229,11 @@ position: relative; &:before { content: ""; - width: 14px; - height: 14px; - background: no-repeat center center; + width: 18px; + height: 18px; + background-repeat: no-repeat; background-size: contain; - margin-right: 6px; + margin-right: 8px; } &.folder:before { background-image: url("../images/file/folder.svg"); diff --git a/resources/assets/statics/public/images/file/document.svg b/resources/assets/statics/public/images/file/document.svg index 315a87c0..2dac4e83 100644 --- a/resources/assets/statics/public/images/file/document.svg +++ b/resources/assets/statics/public/images/file/document.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/resources/assets/statics/public/images/file/flow.svg b/resources/assets/statics/public/images/file/flow.svg index 689783f7..f32e101d 100644 --- a/resources/assets/statics/public/images/file/flow.svg +++ b/resources/assets/statics/public/images/file/flow.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/resources/assets/statics/public/images/file/folder.svg b/resources/assets/statics/public/images/file/folder.svg index 98016999..60788d5b 100644 --- a/resources/assets/statics/public/images/file/folder.svg +++ b/resources/assets/statics/public/images/file/folder.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/resources/assets/statics/public/images/file/mind.svg b/resources/assets/statics/public/images/file/mind.svg index 855ccb65..a6636648 100644 --- a/resources/assets/statics/public/images/file/mind.svg +++ b/resources/assets/statics/public/images/file/mind.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/resources/assets/statics/public/images/file/sheet.svg b/resources/assets/statics/public/images/file/sheet.svg index c67506f5..77ced9bd 100644 --- a/resources/assets/statics/public/images/file/sheet.svg +++ b/resources/assets/statics/public/images/file/sheet.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file