diff --git a/app/Http/Controllers/Api/FileController.php b/app/Http/Controllers/Api/FileController.php index 3caa085e..a1545dae 100755 --- a/app/Http/Controllers/Api/FileController.php +++ b/app/Http/Controllers/Api/FileController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api; use App\Models\File; use App\Models\FileContent; +use App\Models\FileUser; use App\Models\User; use App\Module\Base; use Arr; @@ -21,7 +22,6 @@ class FileController extends AbstractController * 获取文件列表 * * @apiParam {Number} [pid] 父级ID - * @apiParam {String} [key] 关键词 */ public function lists() { @@ -29,28 +29,66 @@ class FileController extends AbstractController // $data = Request::all(); $pid = intval($data['pid']); - $key = trim($data['key']); // - $builder = File::whereUserid($user->userid); - if (Arr::exists($data, 'pid')) { - $builder->wherePid($pid); + if ($pid > 0) { + $file = File::find($pid); + if (empty($file)) { + return Base::retError('Not exist'); + } + $file->chackAllow($user->userid); + // + $builder = File::wherePid($pid); + } else { + $builder = File::whereUserid($user->userid); } - if (Arr::exists($data, 'key')) { - $builder->where('name', 'like', '%' . $key . '%'); - } - $list = $builder->take(500)->get(); - $array = $list->toArray(); + $array = $builder->take(500)->get()->toArray(); // - while ($pid > 0) { - $file = File::whereUserid($user->userid)->whereId($pid)->first(); - if ($file) { + if ($pid > 0) { + // 遍历获取父级 + while ($pid > 0) { + $file = File::whereId($pid)->first(); + if (empty($file)) { + break; + } $array[] = $file->toArray(); $pid = $file->pid; } + } else { + // 获取共享相关 + $list = File::where('userid', '!=', $user->userid)->where(function ($query) use ($user) { + $query->where('share', 1)->orWhere(function ($q2) use ($user) { + $q2->where('share', 2)->whereIn('id', function ($q3) use ($user) { + $q3->select('file_id')->from('file_users')->where('userid', $user->userid); + }); + }); + })->get(); + if ($list->isNotEmpty()) { + $array = array_merge($array, $list->toArray()); + } } return Base::retSuccess('success', $array); } + /** + * 搜索文件列表 + * + * @apiParam {String} [key] 关键词 + */ + public function search() + { + $user = User::auth(); + // + $key = trim(Request::input('key')); + if (empty($key)) { + return Base::retError('请输入关键词'); + } + // + $builder = File::whereUserid($user->userid)->where('name', 'like', '%' . $key . '%'); + $list = $builder->take(50)->get(); + // + return Base::retSuccess('success', $list); + } + /** * 添加、修改文件(夹) * @@ -292,4 +330,99 @@ class FileController extends AbstractController $content->content = $content->formatContent($file->type, $content->content); return Base::retSuccess('保存成功', $content); } + + /** + * 获取共享信息 + * + * @apiParam {Number} id 文件ID + */ + public function share() + { + $user = User::auth(); + // + $id = intval(Request::input('id')); + // + $file = File::whereId($id)->first(); + if (empty($file)) { + return Base::retError('文件不存在或已被删除'); + } + // + if ($file->userid != $user->userid) { + return Base::retError('仅限所有者操作'); + } + // + $userids = FileUser::whereFileId($file->id)->pluck('userid')->toArray(); + // + return Base::retSuccess('success', [ + 'id' => $file->id, + 'userids' => $userids + ]); + } + + /** + * 获取共享信息 + * + * @apiParam {Number} id 文件ID + * @apiParam {String} action 动作 + * - share: 设置共享 + * - unshare: 取消共享 + * @apiParam {Number} [share] 共享方式 + * - 1: 共享给所有人 + * - 2: 共享给指定成员 + * @apiParam {Array} [userids] 共享成员,格式: [userid1, userid2, userid3] + */ + public function share__update() + { + $user = User::auth(); + // + $id = intval(Request::input('id')); + $action = Request::input('action'); + $share = intval(Request::input('share')); + $userids = Request::input('userids'); + // + $file = File::whereId($id)->first(); + if (empty($file)) { + return Base::retError('文件不存在或已被删除'); + } + // + if ($file->userid != $user->userid) { + return Base::retError('仅限所有者操作'); + } + // + if ($file->isNnShare()) { + return Base::retError('已经处于共享目录中'); + } + // + if ($action == 'unshare') { + // 取消共享 + $file->setShare(0); + return Base::retSuccess('取消成功', $file); + } else { + // 设置共享 + if (!in_array($share, [1, 2])) { + return Base::retError('请选择共享类型'); + } + $file->setShare($share); + if ($share == 2) { + $array = []; + if (is_array($userids)) { + foreach ($userids as $userid) { + if (!intval($userid)) continue; + if (!User::whereUserid($userid)->exists()) continue; + FileUser::updateInsert([ + 'file_id' => $file->id, + 'userid' => $userid, + ]); + $array[] = $userid; + } + } + if (empty($array)) { + FileUser::whereFileId($file->id)->delete(); + } else { + FileUser::whereFileId($file->id)->whereNotIn('userid', $array)->delete(); + } + } + return Base::retSuccess('设置成功', $file); + } + } } diff --git a/app/Models/File.php b/app/Models/File.php index f9485f72..ce8257e6 100644 --- a/app/Models/File.php +++ b/app/Models/File.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Exceptions\ApiException; use Illuminate\Database\Eloquent\SoftDeletes; /** @@ -15,6 +16,8 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property string|null $type 类型 * @property int|null $size 大小(B) * @property int|null $userid 拥有者ID + * @property int|null $share 是否共享(1:共享所有人,2:指定成员) + * @property int|null $created_id 创建者ID * @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $updated_at * @property \Illuminate\Support\Carbon|null $deleted_at @@ -24,10 +27,12 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @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 whereCreatedId($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 whereShare($value) * @method static \Illuminate\Database\Eloquent\Builder|File whereSize($value) * @method static \Illuminate\Database\Eloquent\Builder|File whereType($value) * @method static \Illuminate\Database\Eloquent\Builder|File whereUpdatedAt($value) @@ -40,6 +45,97 @@ class File extends AbstractModel { use SoftDeletes; + /** + * 是否有访问权限 + * ① 自己的目录 + * ② 共享所有人的目录 + * ③ 在指定共享人员内 + * @param $userid + */ + public function chackAllow($userid) + { + if ($userid == $this->userid) { + // ① 自己的目录 + return; + } + $row = $this->getShareInfo(); + if ($row) { + if ($row->share == 1) { + // ② 共享所有人的目录 + return; + } elseif ($row->share == 2) { + // ③ 在指定共享人员内 + if (FileUser::whereFileId($row->id)->whereUserid($userid)->exists()) { + return; + } + } + } + throw new ApiException('没有访问权限'); + } + + /** + * 获取共享数据(含自身) + * @return $this|null + */ + public function getShareInfo() + { + if ($this->share > 0) { + return $this; + } + $pid = $this->pid; + while ($pid > 0) { + $row = self::whereId($pid)->first(); + if (empty($row)) { + break; + } + if ($row->share > 0) { + return $row; + } + $pid = $row->pid; + } + return null; + } + + /** + * 是否处于共享目录内(不含自身) + * @return bool + */ + public function isNnShare() + { + $pid = $this->pid; + while ($pid > 0) { + $row = self::whereId($pid)->first(); + if (empty($row)) { + break; + } + if ($row->share > 0) { + return true; + } + $pid = $row->pid; + } + return false; + } + + /** + * 设置/关闭 共享(同时遍历取消里面的共享) + * @param $share + * @return bool + */ + public function setShare($share) + { + AbstractModel::transaction(function () use ($share) { + $this->share = $share; + $this->save(); + $list = self::wherePid($this->id)->get(); + if ($list->isNotEmpty()) { + foreach ($list as $item) { + $item->setShare(0); + } + } + }); + return true; + } + /** * 遍历删除文件(夹) * @return bool diff --git a/app/Models/FileUser.php b/app/Models/FileUser.php new file mode 100644 index 00000000..22ecf12f --- /dev/null +++ b/app/Models/FileUser.php @@ -0,0 +1,27 @@ +string('type', 20)->nullable()->default('')->comment('类型'); $table->bigInteger('size')->nullable()->default(0)->comment('大小(B)'); $table->bigInteger('userid')->nullable()->default(0)->comment('拥有者ID'); + $table->tinyInteger('share')->nullable()->default(0)->comment('是否共享'); + $table->bigInteger('created_id')->nullable()->default(0)->comment('创建者'); $table->timestamps(); $table->softDeletes(); }); diff --git a/database/migrations/2021_07_07_085317_create_file_users_table.php b/database/migrations/2021_07_07_085317_create_file_users_table.php new file mode 100644 index 00000000..921b653e --- /dev/null +++ b/database/migrations/2021_07_07_085317_create_file_users_table.php @@ -0,0 +1,33 @@ +bigIncrements('id'); + $table->bigInteger('file_id')->nullable()->default(0)->comment('项目ID'); + $table->bigInteger('userid')->nullable()->default(0)->comment('成员ID'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('file_users'); + } +} diff --git a/database/seeders/FilesTableSeeder.php b/database/seeders/FilesTableSeeder.php index 32e3e10f..5e79dd15 100644 --- a/database/seeders/FilesTableSeeder.php +++ b/database/seeders/FilesTableSeeder.php @@ -30,6 +30,8 @@ class FilesTableSeeder extends Seeder 'type' => 'folder', 'size' => 0, 'userid' => 1, + 'share' => 0, + 'created_id' => 1, 'created_at' => seeders_at('2021-07-01 14:03:29'), 'updated_at' => seeders_at('2021-07-01 14:03:29'), 'deleted_at' => NULL, @@ -43,6 +45,8 @@ class FilesTableSeeder extends Seeder 'type' => 'document', 'size' => 16976, 'userid' => 1, + 'share' => 0, + 'created_id' => 1, 'created_at' => seeders_at('2021-07-01 14:03:37'), 'updated_at' => seeders_at('2021-07-01 14:17:28'), 'deleted_at' => NULL, @@ -56,6 +60,8 @@ class FilesTableSeeder extends Seeder 'type' => 'document', 'size' => 11971, 'userid' => 1, + 'share' => 0, + 'created_id' => 1, 'created_at' => seeders_at('2021-07-01 15:46:59'), 'updated_at' => seeders_at('2021-07-01 15:49:14'), 'deleted_at' => NULL, @@ -69,6 +75,8 @@ class FilesTableSeeder extends Seeder 'type' => 'folder', 'size' => 0, 'userid' => 1, + 'share' => 0, + 'created_id' => 1, 'created_at' => seeders_at('2021-07-01 15:49:43'), 'updated_at' => seeders_at('2021-07-01 15:49:43'), 'deleted_at' => NULL, @@ -82,6 +90,8 @@ class FilesTableSeeder extends Seeder 'type' => 'document', 'size' => 285, 'userid' => 1, + 'share' => 0, + 'created_id' => 1, 'created_at' => seeders_at('2021-07-01 15:49:50'), 'updated_at' => seeders_at('2021-07-01 15:53:09'), 'deleted_at' => NULL, @@ -95,6 +105,8 @@ class FilesTableSeeder extends Seeder 'type' => 'mind', 'size' => 1947, 'userid' => 1, + 'share' => 0, + 'created_id' => 1, 'created_at' => seeders_at('2021-07-01 15:53:50'), 'updated_at' => seeders_at('2021-07-01 16:11:39'), 'deleted_at' => NULL, @@ -108,6 +120,8 @@ class FilesTableSeeder extends Seeder 'type' => 'sheet', 'size' => 0, 'userid' => 1, + 'share' => 0, + 'created_id' => 1, 'created_at' => seeders_at('2021-07-01 15:54:58'), 'updated_at' => seeders_at('2021-07-01 15:54:58'), 'deleted_at' => NULL, @@ -121,6 +135,8 @@ class FilesTableSeeder extends Seeder 'type' => 'document', 'size' => 8088, 'userid' => 1, + 'share' => 0, + 'created_id' => 1, 'created_at' => seeders_at('2021-07-01 15:56:09'), 'updated_at' => seeders_at('2021-07-01 16:11:13'), 'deleted_at' => NULL, @@ -134,6 +150,8 @@ class FilesTableSeeder extends Seeder 'type' => 'document', 'size' => 23266, 'userid' => 1, + 'share' => 0, + 'created_id' => 1, 'created_at' => seeders_at('2021-07-01 15:57:39'), 'updated_at' => seeders_at('2021-07-01 15:57:56'), 'deleted_at' => NULL, @@ -147,6 +165,8 @@ class FilesTableSeeder extends Seeder 'type' => 'sheet', 'size' => 1883759, 'userid' => 1, + 'share' => 0, + 'created_id' => 1, 'created_at' => seeders_at('2021-07-01 15:58:30'), 'updated_at' => seeders_at('2021-07-01 16:10:17'), 'deleted_at' => NULL, @@ -160,6 +180,8 @@ class FilesTableSeeder extends Seeder 'type' => 'sheet', 'size' => 1771363, 'userid' => 1, + 'share' => 0, + 'created_id' => 1, 'created_at' => seeders_at('2021-07-01 15:59:59'), 'updated_at' => seeders_at('2021-07-01 16:00:28'), 'deleted_at' => NULL, @@ -173,6 +195,8 @@ class FilesTableSeeder extends Seeder 'type' => 'flow', 'size' => 5418, 'userid' => 1, + 'share' => 0, + 'created_id' => 1, 'created_at' => seeders_at('2021-07-01 16:01:27'), 'updated_at' => seeders_at('2021-07-01 16:03:06'), 'deleted_at' => NULL, @@ -186,6 +210,8 @@ class FilesTableSeeder extends Seeder 'type' => 'folder', 'size' => 0, 'userid' => 1, + 'share' => 0, + 'created_id' => 1, 'created_at' => seeders_at('2021-07-01 16:10:08'), 'updated_at' => seeders_at('2021-07-01 16:10:08'), 'deleted_at' => NULL, @@ -199,6 +225,8 @@ class FilesTableSeeder extends Seeder 'type' => 'folder', 'size' => 0, 'userid' => 1, + 'share' => 0, + 'created_id' => 1, 'created_at' => seeders_at('2021-07-01 16:11:08'), 'updated_at' => seeders_at('2021-07-01 16:11:08'), 'deleted_at' => NULL, diff --git a/resources/assets/js/pages/manage/file.vue b/resources/assets/js/pages/manage/file.vue index c103a422..d347a840 100644 --- a/resources/assets/js/pages/manage/file.vue +++ b/resources/assets/js/pages/manage/file.vue @@ -27,7 +27,10 @@ + + + + import {mapState} from "vuex"; import {sortBy} from "lodash"; +import UserInput from "../../components/UserInput"; const FileContent = () => import('./components/FileContent'); export default { - components: {FileContent}, + components: {UserInput, FileContent}, data() { return { loadIng: 0, @@ -133,6 +168,10 @@ export default { tableMode: this.$store.state.method.getStorageBoolean("fileTableMode"), columns: [], + shareShow: false, + shareInfo: {}, + shareLoad: 0, + editShow: false, editShowNum: 0, editHeight: 0, @@ -146,7 +185,7 @@ export default { }, computed: { - ...mapState(['userInfo', 'files']), + ...mapState(['userId', 'userInfo', 'files']), shearFile() { const {files, shearId} = this; @@ -191,9 +230,7 @@ export default { pid: { handler() { this.loadIng++; - this.$store.dispatch("getFiles", { - pid: this.pid - }).then(() => { + this.$store.dispatch("getFiles", this.pid).then(() => { this.loadIng--; this.$store.state.method.setStorage("fileOpenPid", this.pid) }).catch(({msg}) => { @@ -400,6 +437,18 @@ export default { this.shearId = item.id; break; + case 'share': + this.shareInfo = { + id: item.id, + name: item.name, + userid: item.userid, + share: item.share, + _share: item.share, + }; + this.shareShow = true; + this.getShare(); + break; + case 'delete': let typeName = item.type == 'folder' ? '文件夹' : '文件'; $A.modalConfirm({ @@ -509,15 +558,61 @@ export default { if (this.searchKey.trim() != '') { this.searchTimeout = setTimeout(() => { this.loadIng++; - this.$store.dispatch("getFiles", { - key: this.searchKey, - }).then(() => { + this.$store.dispatch("searchFiles", this.searchKey).then(() => { this.loadIng--; }).catch(() => { this.loadIng--; }); }, 600) } + }, + + getShare() { + this.shareLoad++; + this.$store.dispatch("call", { + url: 'file/share', + data: { + id: this.shareInfo.id + }, + }).then(({data}) => { + this.shareLoad--; + if (data.id == this.shareInfo.id) { + this.shareInfo = Object.assign({userTmpHide: true}, this.shareInfo, data); + this.$nextTick(() => { + this.$set(this.shareInfo, 'userTmpHide', false); + }) + } + }).catch(({msg}) => { + this.shareLoad--; + this.shareShow = false; + $A.modalError(msg) + }) + }, + + onShare(share) { + if (!share && !this.shareInfo._share) { + this.shareShow = false; + return; + } + if (![1, 2].includes(this.shareInfo.share)) { + $A.messageWarning("请选择共享类型") + return; + } + this.shareLoad++; + this.$store.dispatch("call", { + url: 'file/share/update', + data: Object.assign(this.shareInfo, { + action: share ? 'share' : 'unshare' + }), + }).then(({data, msg}) => { + this.shareLoad--; + this.shareShow = false; + $A.messageSuccess(msg) + this.$store.dispatch("saveFile", data); + }).catch(({msg}) => { + this.shareLoad--; + $A.modalError(msg) + }) } } } diff --git a/resources/assets/js/store/actions.js b/resources/assets/js/store/actions.js index 8fef3335..345831a4 100644 --- a/resources/assets/js/store/actions.js +++ b/resources/assets/js/store/actions.js @@ -318,17 +318,19 @@ export default { * 获取文件 * @param state * @param dispatch - * @param data {?pid, ?key} + * @param pid * @returns {Promise} */ - getFiles({state, dispatch}, data) { + getFiles({state, dispatch}, pid) { return new Promise(function (resolve, reject) { dispatch("call", { url: 'file/lists', - data, + data: { + pid + }, }).then((result) => { const ids = result.data.map(({id}) => id) - state.files = state.files.filter((item) => item.pid != data.pid || ids.includes(item.id)); + state.files = state.files.filter((item) => item.pid != pid || ids.includes(item.id)); dispatch("saveFile", result.data); resolve(result) }).catch(e => { @@ -338,6 +340,29 @@ export default { }); }, + /** + * 搜索文件 + * @param state + * @param dispatch + * @param key + * @returns {Promise} + */ + searchFiles({state, dispatch}, key) { + return new Promise(function (resolve, reject) { + dispatch("call", { + url: 'file/search', + data: { + key, + }, + }).then((result) => { + dispatch("saveFile", result.data); + resolve(result) + }).catch(e => { + console.error(e); + reject(e) + }); + }); + }, /** *****************************************************************************************/ /** ************************************** 项目 **********************************************/ diff --git a/resources/assets/sass/pages/page-file.scss b/resources/assets/sass/pages/page-file.scss index 8ed1a76d..587e9836 100644 --- a/resources/assets/sass/pages/page-file.scss +++ b/resources/assets/sass/pages/page-file.scss @@ -93,6 +93,9 @@ font-weight: 500; font-family: system-ui, sans-serif; } + .taskfont { + padding-right: 2px; + } > span { display: inline-block; max-width: 180px; @@ -325,12 +328,24 @@ background-repeat: no-repeat; background-size: contain; margin-top: 12px; + position: relative; + .taskfont { + position: absolute; + right: 10px; + bottom: -1px; + font-size: 16px; + color: #ffffff; + } } &.shear { opacity: 0.38; } &.folder .file-icon { background-image: url("../images/file/folder.svg"); + .taskfont { + right: 7px; + bottom: 3px; + } } &.document .file-icon { background-image: url("../images/file/document.svg");