diff --git a/app/Http/Controllers/Api/FileController.php b/app/Http/Controllers/Api/FileController.php
index d6a009003..40de674e0 100755
--- a/app/Http/Controllers/Api/FileController.php
+++ b/app/Http/Controllers/Api/FileController.php
@@ -41,6 +41,7 @@ class FileController extends AbstractController
* @apiName lists
*
* @apiParam {Number} [pid] 父级ID
+ * @apiParam {String} [scope] 板块范围(根目录生效):mine=我的文件、shared=共享文件、all=全部(默认)
*
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
* @apiSuccess {String} msg 返回信息(错误描述)
@@ -51,8 +52,12 @@ class FileController extends AbstractController
$user = User::auth();
//
$pid = intval(Request::input('pid'));
+ $scope = Request::input('scope', 'all');
+ if (!in_array($scope, ['mine', 'shared', 'all'])) {
+ $scope = 'all';
+ }
//
- return Base::retSuccess('success', (new File)->getFileList($user, $pid));
+ return Base::retSuccess('success', (new File)->getFileList($user, $pid, 'all', true, $scope));
}
/**
diff --git a/app/Models/File.php b/app/Models/File.php
index 9879b11f6..2c782f07d 100644
--- a/app/Models/File.php
+++ b/app/Models/File.php
@@ -178,28 +178,26 @@ class File extends AbstractModel
* @param int $pid
* @param string $type
* @param bool $isGetparent
+ * @param string $scope 板块范围(根目录生效):mine=仅我的私有文件;shared=共享文件(别人共享给我的+我共享出去的);all=全部(默认,兼容旧调用)
* @return array
*/
- public function getFileList($user, int $pid, $type = "all", $isGetparent = true)
+ public function getFileList($user, int $pid, $type = "all", $isGetparent = true, $scope = "all")
{
$permission = 1000;
$userids = $user->isTemp() ? [$user->userid] : [0, $user->userid];
- $builder = File::wherePid($pid)
- ->when($type == 'dir', function ($q) {
- $q->whereType('folder');
- });
+ //
if ($pid > 0) {
+ // 目录内:按权限返回子级(不区分板块)
File::permissionFind($pid, $userids, 0, $permission);
- } else {
- $builder->whereUserid($user->userid);
- }
- //
- $array = $builder->take(500)->get()->toArray();
- foreach ($array as &$item) {
- $item['permission'] = $permission;
- }
- //
- if ($pid > 0) {
+ $array = File::wherePid($pid)
+ ->when($type == 'dir', function ($q) {
+ $q->whereType('folder');
+ })
+ ->take(500)->get()->toArray();
+ foreach ($array as &$item) {
+ $item['permission'] = $permission;
+ }
+ unset($item);
// 遍历获取父级
if ($isGetparent) {
while ($pid > 0) {
@@ -230,24 +228,61 @@ class File extends AbstractModel
$array = array_values($array);
}
} else {
- // 获取共享相关
- DB::statement("SET SQL_MODE=''");
- $pre = DB::connection()->getTablePrefix();
- $list = File::select(["files.*", DB::raw("MAX({$pre}file_users.permission) as permission")])
- ->join('file_users', 'files.id', '=', 'file_users.file_id')
- ->where('files.userid', '!=', $user->userid)
- ->whereIn('file_users.userid', $userids)
- ->groupBy('files.id')
- ->take(100)
- ->when($type == 'dir', function ($q) {
- $q->where('files.type', 'folder');
- })
- ->get();
- if ($list->isNotEmpty()) {
- foreach ($list as $file) {
- $temp = $file->toArray();
- $temp['pid'] = 0;
- $array[] = $temp;
+ // 根目录:按板块拆分
+ $array = [];
+ // 我的文件(mine 仅私有 share=0;all 含全部我的)
+ if ($scope === 'mine' || $scope === 'all') {
+ $mine = File::wherePid(0)
+ ->whereUserid($user->userid)
+ ->when($scope === 'mine', function ($q) {
+ $q->where('share', 0);
+ })
+ ->when($type == 'dir', function ($q) {
+ $q->whereType('folder');
+ })
+ ->take(500)->get()->toArray();
+ foreach ($mine as &$item) {
+ $item['permission'] = $permission;
+ }
+ unset($item);
+ $array = array_merge($array, $mine);
+ }
+ // 共享文件
+ if ($scope === 'shared' || $scope === 'all') {
+ // 别人共享给我的
+ DB::statement("SET SQL_MODE=''");
+ $pre = DB::connection()->getTablePrefix();
+ $list = File::select(["files.*", DB::raw("MAX({$pre}file_users.permission) as permission")])
+ ->join('file_users', 'files.id', '=', 'file_users.file_id')
+ ->where('files.userid', '!=', $user->userid)
+ ->whereIn('file_users.userid', $userids)
+ ->groupBy('files.id')
+ ->take(100)
+ ->when($type == 'dir', function ($q) {
+ $q->where('files.type', 'folder');
+ })
+ ->get();
+ if ($list->isNotEmpty()) {
+ foreach ($list as $file) {
+ $temp = $file->toArray();
+ $temp['pid'] = 0;
+ $array[] = $temp;
+ }
+ }
+ // 我共享出去的(仅 shared 板块补充;all 板块已包含在“我的文件”里)
+ if ($scope === 'shared') {
+ $mineShared = File::wherePid(0)
+ ->whereUserid($user->userid)
+ ->where('share', 1)
+ ->when($type == 'dir', function ($q) {
+ $q->whereType('folder');
+ })
+ ->take(500)->get()->toArray();
+ foreach ($mineShared as &$item) {
+ $item['permission'] = $permission;
+ }
+ unset($item);
+ $array = array_merge($array, $mineShared);
}
}
}
diff --git a/language/original-web.txt b/language/original-web.txt
index 9022a4d44..7d4e8e7d6 100644
--- a/language/original-web.txt
+++ b/language/original-web.txt
@@ -2527,3 +2527,7 @@ AI任务分析
确定授权
当前任务所属项目已被删除
当前任务所属项目已归档
+我的文件
+共享文件
+我共享的
+共享给我的
diff --git a/resources/ai-kb/zh/menu-map/file/entry.md b/resources/ai-kb/zh/menu-map/file/entry.md
index 4ecf0ce73..829a41e4d 100644
--- a/resources/ai-kb/zh/menu-map/file/entry.md
+++ b/resources/ai-kb/zh/menu-map/file/entry.md
@@ -17,7 +17,7 @@ prerequisites: []
negative:
- 文件入口不在「应用」二级页面,是左侧栏一级菜单
- 移动端目前没有专门的「文件」Tabbar,需要从「更多」进
-last_verified: v1.7.90
+last_verified: v1.8.64
---
# 文件入口在哪
@@ -30,9 +30,12 @@ DooTask 的「文件」是一级导航,相当于个人网盘 + 团队共享盘
- 移动端:底部 Tabbar「更多」→「文件」
## 默认视图
-- 顶部面包屑显示当前路径(根目录 / 文件夹层级)
+- 顶部一行是板块 Tab:「我的文件」/「共享文件」,同一行最右是视图切换(宫格 / 列表),窄屏自动换行
+- 「我的文件」:自己创建的私有文件(未共享)
+- 「共享文件」:双向共享——包含「我共享出去的」和「别人共享给我的」,可用「全部 / 我共享的 / 共享给我的」二次筛选
+- 进入子文件夹后,Tab 下方出现面包屑路径(以板块名为起点,可点击回到板块根目录);根目录不显示重复标题
- 主区域列出当前文件夹下的文件与文件夹(最多 500 条,超出滚动加载)
-- 自己创建的在「我的文件」段,他人共享给我的在「共享」段
+- 板块选择会记住上次停留的 Tab
## 权限要求
- 所有登录用户可见
diff --git a/resources/assets/js/pages/manage/file.vue b/resources/assets/js/pages/manage/file.vue
index 8870e2717..491235b6b 100644
--- a/resources/assets/js/pages/manage/file.vue
+++ b/resources/assets/js/pages/manage/file.vue
@@ -36,10 +36,27 @@
-
+
+
+
{{$L('我的文件')}}
+
{{$L('共享文件')}}
+
+
+ {{$L('全部')}}
+ {{$L('我共享的')}}
+ {{$L('共享给我的')}}
+
+
+
+
+
+
import('./components/FilePreview');
const FileContent = () => import('./components/FileContent');
-const FileObject = {sort: null, mode: null, shared: null};
+const FileObject = {sort: null, mode: null, board: null};
export default {
components: {Forwarder, UserAvatarTip, UserSelect, FilePreview, DrawerOverlay, FileContent},
@@ -563,7 +571,8 @@ export default {
],
tableMode: "",
- hideShared: false,
+ board: "mine", // 当前板块:mine=我的文件、shared=共享文件
+ sharedSrc: "all", // 共享板块二次筛选:all=全部、byme=我共享的、tome=共享给我的
columns: [],
shareShow: false,
@@ -623,14 +632,14 @@ export default {
async beforeRouteEnter(to, from, next) {
FileObject.sort = await $A.IDBJson("cacheFileSort")
FileObject.mode = await $A.IDBString("fileTableMode")
- FileObject.shared = await $A.IDBBoolean("fileHideShared")
+ FileObject.board = await $A.IDBString("fileBoard")
next()
},
created() {
this.tableMode = FileObject.mode
- this.hideShared = FileObject.shared
+ this.board = FileObject.board === 'shared' ? 'shared' : 'mine'
this.columns = [
{
type: 'selection',
@@ -879,15 +888,32 @@ export default {
},
fileList() {
- const {fileLists, searchKey, hideShared, pid, selectedItems, userId} = this;
+ const {fileLists, searchKey, board, sharedSrc, pid, selectedItems, userId} = this;
const list = $A.cloneJSON(sortBy(fileLists.filter(file => {
- if (hideShared && file.userid != userId && file.created_id != userId) {
- return false
- }
if (searchKey) {
return file.name.indexOf(searchKey) !== -1;
}
- return file.pid == pid;
+ if (file.pid != pid) {
+ return false;
+ }
+ // 根目录按板块区分:我的=我的私有文件(share=0),共享=其余(双向共享)
+ if (pid == 0) {
+ const isMinePrivate = file.userid == userId && !file.share;
+ if (board === 'mine') {
+ return isMinePrivate;
+ }
+ if (isMinePrivate) {
+ return false;
+ }
+ // 共享板块二次筛选:我共享的(我拥有) / 共享给我的(他人拥有)
+ if (sharedSrc === 'byme') {
+ return file.userid == userId;
+ }
+ if (sharedSrc === 'tome') {
+ return file.userid != userId;
+ }
+ }
+ return true;
}), file => {
return (file.type == 'folder' ? 'a' : 'b') + file.name;
}));
@@ -906,11 +932,6 @@ export default {
})
},
- hasShareFile() {
- const {fileLists, userId} = this;
- return fileLists.findIndex(file => file.share && file.userid != userId) !== -1
- },
-
shearFirst() {
const {fileLists, shearIds} = this;
if (shearIds.length === 0) {
@@ -919,6 +940,11 @@ export default {
return fileLists.find(item => item.id == shearIds[0])
},
+ showNavigator() {
+ // 仅在进入子目录、搜索、选中或剪切时显示导航行;根目录空闲时隐藏(板块名已由 Tab 标示)
+ return this.pid > 0 || !!this.searchKey || this.selectedItems.length > 0 || !!this.shearFirst;
+ },
+
navigator() {
let {pid, fileLists} = this;
let array = [];
@@ -1009,8 +1035,8 @@ export default {
}
},
- hideShared(val) {
- $A.IDBSave("fileHideShared", val)
+ board(val) {
+ $A.IDBSave("fileBoard", val)
},
fileShow(val) {
@@ -1147,7 +1173,7 @@ export default {
return;
}
this.loadIng++;
- this.$store.dispatch("getFiles", this.pid).then(async () => {
+ this.$store.dispatch("getFiles", {pid: this.pid, scope: this.board}).then(async () => {
this.loadIng--;
this.openFileJudge()
this.shakeFile(this.$route.params.shakeId);
@@ -1478,6 +1504,22 @@ export default {
})
},
+ switchBoard(board) {
+ if (this.board === board) {
+ return;
+ }
+ this.board = board;
+ this.sharedSrc = 'all';
+ this.selectedItems = [];
+ this.clearShear();
+ if (this.pid > 0) {
+ // 退回板块根目录,由 pid 变化触发重载
+ this.browseFolder(0);
+ } else {
+ this.getFileList();
+ }
+ },
+
browseFolder(id, shakeId = null) {
if (this.pid == id && this.fid == 0 && shakeId) {
this.shakeFile(shakeId);
diff --git a/resources/assets/js/store/actions.js b/resources/assets/js/store/actions.js
index 7c1709068..716a1862d 100644
--- a/resources/assets/js/store/actions.js
+++ b/resources/assets/js/store/actions.js
@@ -1491,13 +1491,13 @@ export default {
* @param pid
* @returns {Promise}
*/
- getFiles({commit, state, dispatch}, pid) {
+ getFiles({commit, state, dispatch}, payload) {
+ const pid = typeof payload === 'object' && payload !== null ? payload.pid : payload;
+ const scope = typeof payload === 'object' && payload !== null ? payload.scope : undefined;
return new Promise(function (resolve, reject) {
dispatch("call", {
url: 'file/lists',
- data: {
- pid
- },
+ data: scope ? {pid, scope} : {pid},
}).then((result) => {
const ids = result.data.map(({id}) => id)
commit("file/save", state.fileLists.filter((item) => item.pid != pid || ids.includes(item.id)));
diff --git a/resources/assets/sass/pages/page-file.scss b/resources/assets/sass/pages/page-file.scss
index fcc7458a1..fd9de13fd 100644
--- a/resources/assets/sass/pages/page-file.scss
+++ b/resources/assets/sass/pages/page-file.scss
@@ -82,6 +82,121 @@
}
}
}
+ .file-tabs {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ margin: 0 32px 4px;
+ gap: 8px 20px;
+ .file-tabs-nav {
+ display: flex;
+ align-items: flex-end;
+ gap: 24px;
+ .file-tab {
+ position: relative;
+ padding: 4px 2px 8px;
+ font-size: 15px;
+ font-weight: 500;
+ color: $primary-text-color;
+ cursor: pointer;
+ transition: color 0.2s;
+ &:hover {
+ color: $primary-title-color;
+ }
+ &.active {
+ color: $primary-title-color;
+ &:after {
+ content: "";
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ height: 2px;
+ border-radius: 2px;
+ background-color: $primary-color;
+ }
+ }
+ }
+ }
+ .file-shared-src {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ > span {
+ font-size: 12.5px;
+ color: $primary-text-color;
+ padding: 3px 12px;
+ border-radius: 14px;
+ border: 1px solid transparent;
+ background-color: rgba(0, 0, 0, 0.03);
+ cursor: pointer;
+ transition: all 0.2s;
+ &.on {
+ color: $primary-color;
+ border-color: rgba($primary-color, 0.5);
+ background-color: rgba($primary-color, 0.08);
+ }
+ }
+ }
+ .file-tabs-full {
+ flex: 1;
+ min-width: 12px;
+ }
+ .switch-button {
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ background-color: #ffffff;
+ border-radius: 6px;
+ position: relative;
+ transition: box-shadow 0.2s;
+ &:hover {
+ box-shadow: 0 0 10px #e6ecfa;
+ }
+ &:before {
+ content: "";
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 50%;
+ height: 100%;
+ z-index: 0;
+ color: $primary-color;
+ border-radius: 6px;
+ border: 1px solid $primary-color;
+ background-color: rgba($primary-color, 0.1);
+ transition: left 0.2s;
+ }
+ > div {
+ z-index: 1;
+ width: 32px;
+ height: 30px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 6px;
+ cursor: pointer;
+ color: $primary-text-color;
+ > i {
+ font-size: 17px;
+ }
+ &:first-child {
+ color: $primary-color;
+ }
+ }
+ &.table {
+ &:before {
+ left: 50%;
+ }
+ > div:first-child {
+ color: $primary-text-color;
+ }
+ > div:last-child {
+ color: $primary-color;
+ }
+ }
+ }
+ }
.file-navigator {
display: flex;
align-items: center;
@@ -183,72 +298,6 @@
}
.flex-full {
flex: 1;
- margin-right: 24px;
- }
- .only-checkbox {
- flex-shrink: 0;
- display: flex;
- align-items: center;
- margin-right: 14px;
- opacity: 0.9;
- height: 30px;
- .ivu-checkbox-focus {
- box-shadow: none;
- }
- }
- .switch-button {
- flex-shrink: 0;
- display: flex;
- align-items: center;
- background-color: #ffffff;
- border-radius: 6px;
- position: relative;
- transition: box-shadow 0.2s;
- &:hover {
- box-shadow: 0 0 10px #e6ecfa;
- }
- &:before {
- content: "";
- position: absolute;
- top: 0;
- left: 0;
- width: 50%;
- height: 100%;
- z-index: 0;
- color: $primary-color;
- border-radius: 6px;
- border: 1px solid $primary-color;
- background-color: rgba($primary-color, 0.1);
- transition: left 0.2s;
- }
- > div {
- z-index: 1;
- width: 32px;
- height: 30px;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 6px;
- cursor: pointer;
- color: $primary-text-color;
- > i {
- font-size: 17px;
- }
- &:first-child {
- color: $primary-color;
- }
- }
- &.table {
- &:before {
- left: 50%;
- }
- > div:first-child {
- color: $primary-text-color;
- }
- > div:last-child {
- color: $primary-color;
- }
- }
}
}
.file-drag {
@@ -747,14 +796,25 @@ body.window-portrait {
.file-head {
margin: 24px 16px 16px;
}
+ .file-tabs {
+ margin: 0 16px 6px;
+ flex-wrap: wrap;
+ gap: 10px 16px;
+ .file-tabs-nav {
+ gap: 20px;
+ }
+ .file-shared-src {
+ flex-wrap: wrap;
+ > span {
+ padding: 2px 10px;
+ }
+ }
+ }
.file-navigator {
- margin: 0 24px 0;
+ margin: 0 16px 0;
.flex-full {
margin-right: 10px;
}
- .only-checkbox {
- margin-right: 0;
- }
}
.file-drag {
.file-list {