From 401b85b87ef111041a6944ea73860fd5176d3be0 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 06:43:26 +0800 Subject: [PATCH] feat: add on-demand folder size calculation in file manager (#1201) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial plan * feat: add folder size calculation feature with "Calculate" link - Backend: Add Size API to calculate directory size - Backend: Modify formatDir to return empty size for directories - Frontend: Show "Calculate" link for directories instead of size - Frontend: Add loading spinner during calculation - Frontend: Cache calculated sizes until path changes - Add translations for Calculate and Failed to calculate size Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com> * fix: lint * fix: 优化 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com> Co-authored-by: 耗子 --- internal/route/http.go | 1 + internal/service/file.go | 39 +++++- web/src/api/panel/file/index.ts | 2 + web/src/locales/en.po | 8 ++ web/src/locales/frontend.pot | 207 +++++++++++++++---------------- web/src/locales/zh_CN.po | 8 ++ web/src/locales/zh_TW.po | 8 ++ web/src/views/file/ListTable.vue | 56 ++++++++- 8 files changed, 222 insertions(+), 107 deletions(-) diff --git a/internal/route/http.go b/internal/route/http.go index 5f26e549..242bb651 100644 --- a/internal/route/http.go +++ b/internal/route/http.go @@ -403,6 +403,7 @@ func (route *Http) Register(r *chi.Mux) { r.Get("/download", route.file.Download) r.Post("/remote_download", route.file.RemoteDownload) r.Get("/info", route.file.Info) + r.Get("/size", route.file.Size) r.Post("/permission", route.file.Permission) r.Post("/compress", route.file.Compress) r.Post("/un_compress", route.file.UnCompress) diff --git a/internal/service/file.go b/internal/service/file.go index 2bea5cc4..341d2a47 100644 --- a/internal/service/file.go +++ b/internal/service/file.go @@ -18,6 +18,7 @@ import ( "github.com/leonelquinteros/gotext" "github.com/libtnb/chix" "github.com/libtnb/utils/file" + "github.com/spf13/cast" "github.com/acepanel/panel/internal/app" "github.com/acepanel/panel/internal/biz" @@ -327,6 +328,37 @@ func (s *FileService) Info(w http.ResponseWriter, r *http.Request) { }) } +// Size 计算大小 +func (s *FileService) Size(w http.ResponseWriter, r *http.Request) { + req, err := Bind[request.FilePath](r) + if err != nil { + Error(w, http.StatusInternalServerError, "%v", err) + return + } + + info, err := stdos.Stat(req.Path) + if err != nil { + Error(w, http.StatusInternalServerError, "%v", err) + return + } + if !info.IsDir() { + // 如果不是目录,直接返回文件大小 + Success(w, chix.M{ + "size": tools.FormatBytes(float64(info.Size())), + }) + return + } + + // 计算目录大小 + output, err := shell.Execf("du -sb '%s' | awk '{print $1}'", req.Path) + if err != nil { + Error(w, http.StatusInternalServerError, "%v", err) + return + } + + Success(w, tools.FormatBytes(cast.ToFloat64(output))) +} + func (s *FileService) Permission(w http.ResponseWriter, r *http.Request) { req, err := Bind[request.FilePermission](r) if err != nil { @@ -458,10 +490,15 @@ func (s *FileService) formatDir(base string, entries []stdos.DirEntry) []any { } stat := info.Sys().(*syscall.Stat_t) + // 对于目录,size 返回空字符串,需要用户手动计算 + size := "" + if !info.IsDir() { + size = tools.FormatBytes(float64(info.Size())) + } paths = append(paths, map[string]any{ "name": info.Name(), "full": filepath.Join(base, info.Name()), - "size": tools.FormatBytes(float64(info.Size())), + "size": size, "mode_str": info.Mode().String(), "mode": fmt.Sprintf("%04o", info.Mode().Perm()), "owner": os.GetUser(stat.Uid), diff --git a/web/src/api/panel/file/index.ts b/web/src/api/panel/file/index.ts index cf5ba67c..17d4a1c4 100644 --- a/web/src/api/panel/file/index.ts +++ b/web/src/api/panel/file/index.ts @@ -22,6 +22,8 @@ export default { http.Post('/file/remote_download', { path, url }), // 获取文件信息 info: (path: string): any => http.Get('/file/info', { params: { path } }), + // 获取目录/文件大小 + size: (path: string): any => http.Get('/file/size', { params: { path } }), // 修改文件权限 permission: (path: string, mode: string, owner: string, group: string): any => http.Post('/file/permission', { path, mode, owner, group }), diff --git a/web/src/locales/en.po b/web/src/locales/en.po index 0b4b2b57..b7735c3a 100644 --- a/web/src/locales/en.po +++ b/web/src/locales/en.po @@ -3001,6 +3001,10 @@ msgstr "Uncompress" msgid "Paste" msgstr "Paste" +#: src/views/file/ListTable.vue:209 +msgid "Calculate" +msgstr "Calculate" + #: src/views/file/ListTable.vue:281 msgid "Are you sure you want to delete %{ name }?" msgstr "Are you sure you want to delete %{ name }?" @@ -3011,6 +3015,10 @@ msgstr "Are you sure you want to delete %{ name }?" msgid "Marked successfully, please navigate to the destination path to paste" msgstr "Marked successfully, please navigate to the destination path to paste" +#: src/views/file/ListTable.vue:447 +msgid "Failed to calculate size" +msgstr "Failed to calculate size" + #: src/views/file/ListTable.vue:415 src/views/file/ListTable.vue:502 #: src/views/file/ToolBar.vue:128 msgid "Warning" diff --git a/web/src/locales/frontend.pot b/web/src/locales/frontend.pot index 5d1958cb..bb4e6121 100644 --- a/web/src/locales/frontend.pot +++ b/web/src/locales/frontend.pot @@ -209,7 +209,7 @@ msgstr "" #: src/views/apps/supervisor/IndexView.vue:284 #: src/views/environment/PHPView.vue:137 #: src/views/environment/PHPView.vue:143 -#: src/views/setting/IndexView.vue:50 +#: src/views/setting/IndexView.vue:53 #: src/views/toolbox/SystemView.vue:48 #: src/views/toolbox/SystemView.vue:54 #: src/views/toolbox/SystemView.vue:63 @@ -240,7 +240,7 @@ msgstr "" #: src/views/database/CreateServerModal.vue:55 #: src/views/database/ServerList.vue:39 #: src/views/database/UpdateServerModal.vue:55 -#: src/views/file/ListTable.vue:105 +#: src/views/file/ListTable.vue:110 #: src/views/file/ToolBar.vue:257 #: src/views/ssh/CreateModal.vue:59 #: src/views/ssh/UpdateModal.vue:65 @@ -258,31 +258,31 @@ msgid "Permissions" msgstr "" #: src/components/common/PathSelector.vue:95 -#: src/views/file/ListTable.vue:159 +#: src/views/file/ListTable.vue:164 msgid "Owner / Group" msgstr "" #: src/components/common/PathSelector.vue:107 #: src/views/backup/ListView.vue:42 #: src/views/container/ImageView.vue:53 -#: src/views/file/ListTable.vue:171 +#: src/views/file/ListTable.vue:176 #: src/views/toolbox/DiskView.vue:275 msgid "Size" msgstr "" #: src/components/common/PathSelector.vue:115 -#: src/views/file/ListTable.vue:179 +#: src/views/file/ListTable.vue:214 msgid "Modification Time" msgstr "" #: src/components/common/PathSelector.vue:149 -#: src/views/file/ListTable.vue:459 +#: src/views/file/ListTable.vue:509 #: src/views/file/PathInput.vue:28 msgid "Invalid path" msgstr "" #: src/components/common/PathSelector.vue:209 -#: src/views/file/ListTable.vue:408 +#: src/views/file/ListTable.vue:458 #: src/views/file/ToolBar.vue:37 #: src/views/file/ToolBar.vue:51 msgid "Invalid name" @@ -597,8 +597,8 @@ msgid "Confirm" msgstr "" #: src/layout/header/components/UserAvatar.vue:30 -#: src/views/file/ListTable.vue:418 -#: src/views/file/ListTable.vue:513 +#: src/views/file/ListTable.vue:468 +#: src/views/file/ListTable.vue:563 #: src/views/file/ToolBar.vue:139 #: src/views/file/ToolBar.vue:226 #: src/views/home/UpdateView.vue:27 @@ -632,27 +632,6 @@ msgstr "" msgid "Unknown" msgstr "" -#: src/layout/sidebar/components/SideSetting.vue:66 -#: src/layout/sidebar/components/SideSetting.vue:71 -msgid "Menu Settings" -msgstr "" - -#: src/layout/sidebar/components/SideSetting.vue:83 -msgid "Settings are saved in the browser and will be reset after clearing the browser cache" -msgstr "" - -#: src/layout/sidebar/components/SideSetting.vue:88 -msgid "Custom Logo" -msgstr "" - -#: src/layout/sidebar/components/SideSetting.vue:91 -msgid "Please enter the complete URL" -msgstr "" - -#: src/layout/sidebar/components/SideSetting.vue:94 -msgid "Hide Menu" -msgstr "" - #: src/layout/tab/components/ContextMenu.vue:28 msgid "Close" msgstr "" @@ -843,7 +822,7 @@ msgstr "" #: src/views/database/ServerList.vue:134 #: src/views/database/UserList.vue:151 #: src/views/environment/PHPView.vue:50 -#: src/views/file/ListTable.vue:191 +#: src/views/file/ListTable.vue:226 #: src/views/firewall/ForwardView.vue:79 #: src/views/firewall/IpRuleView.vue:121 #: src/views/firewall/RuleView.vue:158 @@ -995,8 +974,8 @@ msgstr "" #: src/views/environment/PHPView.vue:210 #: src/views/environment/PHPView.vue:228 #: src/views/file/EditModal.vue:32 -#: src/views/file/ListTable.vue:746 -#: src/views/setting/IndexView.vue:97 +#: src/views/file/ListTable.vue:799 +#: src/views/setting/IndexView.vue:104 #: src/views/task/CronView.vue:259 #: src/views/toolbox/SshView.vue:239 #: src/views/toolbox/SystemView.vue:100 @@ -1095,8 +1074,8 @@ msgstr "" #: src/views/database/ServerList.vue:214 #: src/views/database/UserList.vue:189 #: src/views/environment/PHPView.vue:101 -#: src/views/file/ListTable.vue:87 -#: src/views/file/ListTable.vue:291 +#: src/views/file/ListTable.vue:92 +#: src/views/file/ListTable.vue:326 #: src/views/file/ToolBar.vue:238 #: src/views/firewall/ForwardView.vue:103 #: src/views/firewall/IpRuleView.vue:145 @@ -1137,8 +1116,8 @@ msgstr "" #: src/views/database/DatabaseList.vue:121 #: src/views/database/ServerList.vue:238 #: src/views/database/UserList.vue:213 -#: src/views/file/ListTable.vue:274 -#: src/views/file/ListTable.vue:618 +#: src/views/file/ListTable.vue:309 +#: src/views/file/ListTable.vue:668 #: src/views/file/ToolBar.vue:189 #: src/views/firewall/ForwardView.vue:129 #: src/views/firewall/ForwardView.vue:147 @@ -1265,7 +1244,7 @@ msgstr "" #: src/views/apps/rsync/IndexView.vue:272 #: src/views/apps/rsync/IndexView.vue:326 #: src/views/database/IndexView.vue:28 -#: src/views/setting/IndexView.vue:83 +#: src/views/setting/IndexView.vue:90 #: src/views/toolbox/ProcessView.vue:129 #: src/views/toolbox/ProcessView.vue:389 #: src/views/toolbox/WebHookView.vue:334 @@ -1552,7 +1531,7 @@ msgstr "" #: src/views/database/CreateUserModal.vue:55 #: src/views/database/IndexView.vue:42 #: src/views/setting/CreateModal.vue:32 -#: src/views/setting/IndexView.vue:89 +#: src/views/setting/IndexView.vue:96 msgid "Create User" msgstr "" @@ -2336,8 +2315,8 @@ msgid "Creation Time" msgstr "" #: src/views/container/ComposeView.vue:96 -#: src/views/file/ListTable.vue:70 -#: src/views/file/ListTable.vue:222 +#: src/views/file/ListTable.vue:75 +#: src/views/file/ListTable.vue:257 #: src/views/ssh/IndexView.vue:70 #: src/views/task/CronView.vue:128 #: src/views/toolbox/WebHookView.vue:157 @@ -2507,9 +2486,9 @@ msgid "Container Directory" msgstr "" #: src/views/container/ContainerCreate.vue:255 -#: src/views/file/ListTable.vue:75 -#: src/views/file/ListTable.vue:147 -#: src/views/file/ListTable.vue:302 +#: src/views/file/ListTable.vue:80 +#: src/views/file/ListTable.vue:152 +#: src/views/file/ListTable.vue:337 #: src/views/file/PermissionModal.vue:113 #: src/views/file/ToolBar.vue:235 msgid "Permission" @@ -2596,8 +2575,8 @@ msgstr "" #: src/views/container/ContainerView.vue:121 #: src/views/container/ContainerView.vue:456 -#: src/views/file/ListTable.vue:86 -#: src/views/file/ListTable.vue:266 +#: src/views/file/ListTable.vue:91 +#: src/views/file/ListTable.vue:301 msgid "Rename" msgstr "" @@ -2617,7 +2596,7 @@ msgid "Resume" msgstr "" #: src/views/container/ContainerView.vue:199 -#: src/views/file/ListTable.vue:366 +#: src/views/file/ListTable.vue:401 msgid "More" msgstr "" @@ -2685,7 +2664,7 @@ msgid "Cleanup Containers" msgstr "" #: src/views/container/ContainerView.vue:463 -#: src/views/file/ListTable.vue:742 +#: src/views/file/ListTable.vue:795 msgid "New Name" msgstr "" @@ -2933,7 +2912,7 @@ msgstr "" #: src/views/database/UpdateServerModal.vue:76 #: src/views/firewall/ForwardView.vue:31 #: src/views/firewall/RuleView.vue:48 -#: src/views/setting/SettingBase.vue:42 +#: src/views/setting/SettingBase.vue:85 #: src/views/ssh/CreateModal.vue:70 #: src/views/ssh/UpdateModal.vue:76 #: src/views/website/CreateModal.vue:129 @@ -2990,8 +2969,8 @@ msgstr "" #: src/views/database/ServerList.vue:75 #: src/views/database/UserList.vue:69 -#: src/views/file/ListTable.vue:519 -#: src/views/file/ListTable.vue:539 +#: src/views/file/ListTable.vue:569 +#: src/views/file/ListTable.vue:589 #: src/views/file/ToolBar.vue:145 #: src/views/file/ToolBar.vue:165 #: src/views/setting/TokenModal.vue:163 @@ -3000,8 +2979,8 @@ msgstr "" #: src/views/database/ServerList.vue:79 #: src/views/database/UserList.vue:73 -#: src/views/file/ListTable.vue:73 -#: src/views/file/ListTable.vue:300 +#: src/views/file/ListTable.vue:78 +#: src/views/file/ListTable.vue:335 #: src/views/file/ToolBar.vue:232 msgid "Copy" msgstr "" @@ -3108,9 +3087,9 @@ msgstr "" #: src/views/file/CompressModal.vue:73 #: src/views/file/CompressModal.vue:105 -#: src/views/file/ListTable.vue:77 -#: src/views/file/ListTable.vue:247 -#: src/views/file/ListTable.vue:303 +#: src/views/file/ListTable.vue:82 +#: src/views/file/ListTable.vue:282 +#: src/views/file/ListTable.vue:338 #: src/views/file/ToolBar.vue:234 msgid "Compress" msgstr "" @@ -3136,112 +3115,120 @@ msgstr "" msgid "Refresh" msgstr "" -#: src/views/file/ListTable.vue:67 -#: src/views/file/ListTable.vue:224 +#: src/views/file/ListTable.vue:72 +#: src/views/file/ListTable.vue:259 msgid "Open" msgstr "" -#: src/views/file/ListTable.vue:69 -#: src/views/file/ListTable.vue:222 +#: src/views/file/ListTable.vue:74 +#: src/views/file/ListTable.vue:257 msgid "Preview" msgstr "" -#: src/views/file/ListTable.vue:74 -#: src/views/file/ListTable.vue:301 +#: src/views/file/ListTable.vue:79 +#: src/views/file/ListTable.vue:336 #: src/views/file/ToolBar.vue:233 msgid "Move" msgstr "" -#: src/views/file/ListTable.vue:77 -#: src/views/file/ListTable.vue:249 +#: src/views/file/ListTable.vue:82 +#: src/views/file/ListTable.vue:284 #: src/views/toolbox/SshView.vue:301 msgid "Download" msgstr "" -#: src/views/file/ListTable.vue:81 -#: src/views/file/ListTable.vue:305 -#: src/views/file/ListTable.vue:764 +#: src/views/file/ListTable.vue:86 +#: src/views/file/ListTable.vue:340 +#: src/views/file/ListTable.vue:817 msgid "Uncompress" msgstr "" -#: src/views/file/ListTable.vue:91 +#: src/views/file/ListTable.vue:96 #: src/views/file/ToolBar.vue:229 msgid "Paste" msgstr "" -#: src/views/file/ListTable.vue:281 +#: src/views/file/ListTable.vue:209 +msgid "Calculate" +msgstr "" + +#: src/views/file/ListTable.vue:316 msgid "Are you sure you want to delete %{ name }?" msgstr "" -#: src/views/file/ListTable.vue:322 -#: src/views/file/ListTable.vue:337 -#: src/views/file/ListTable.vue:578 -#: src/views/file/ListTable.vue:591 +#: src/views/file/ListTable.vue:357 +#: src/views/file/ListTable.vue:372 +#: src/views/file/ListTable.vue:628 +#: src/views/file/ListTable.vue:641 #: src/views/file/ToolBar.vue:77 #: src/views/file/ToolBar.vue:94 msgid "Marked successfully, please navigate to the destination path to paste" msgstr "" -#: src/views/file/ListTable.vue:415 -#: src/views/file/ListTable.vue:502 +#: src/views/file/ListTable.vue:447 +msgid "Failed to calculate size" +msgstr "" + +#: src/views/file/ListTable.vue:465 +#: src/views/file/ListTable.vue:552 #: src/views/file/ToolBar.vue:128 msgid "Warning" msgstr "" -#: src/views/file/ListTable.vue:416 +#: src/views/file/ListTable.vue:466 msgid "There are items with the same name. Do you want to overwrite?" msgstr "" -#: src/views/file/ListTable.vue:417 -#: src/views/file/ListTable.vue:512 +#: src/views/file/ListTable.vue:467 +#: src/views/file/ListTable.vue:562 #: src/views/file/ToolBar.vue:138 msgid "Overwrite" msgstr "" -#: src/views/file/ListTable.vue:424 -#: src/views/file/ListTable.vue:440 +#: src/views/file/ListTable.vue:474 +#: src/views/file/ListTable.vue:490 msgid "Renamed %{ source } to %{ target } successfully" msgstr "" -#: src/views/file/ListTable.vue:462 +#: src/views/file/ListTable.vue:512 msgid "Uncompressing..." msgstr "" -#: src/views/file/ListTable.vue:469 +#: src/views/file/ListTable.vue:519 msgid "Uncompressed successfully" msgstr "" -#: src/views/file/ListTable.vue:478 +#: src/views/file/ListTable.vue:528 #: src/views/file/ToolBar.vue:104 msgid "Please mark the files/folders to copy or move first" msgstr "" -#: src/views/file/ListTable.vue:503 +#: src/views/file/ListTable.vue:553 #: src/views/file/ToolBar.vue:129 msgid "There are items with the same name %{ items } Do you want to overwrite?" msgstr "" -#: src/views/file/ListTable.vue:525 -#: src/views/file/ListTable.vue:545 +#: src/views/file/ListTable.vue:575 +#: src/views/file/ListTable.vue:595 #: src/views/file/ToolBar.vue:151 #: src/views/file/ToolBar.vue:171 msgid "Moved successfully" msgstr "" -#: src/views/file/ListTable.vue:531 +#: src/views/file/ListTable.vue:581 #: src/views/file/ToolBar.vue:157 msgid "Canceled" msgstr "" -#: src/views/file/ListTable.vue:734 +#: src/views/file/ListTable.vue:787 msgid "Rename - %{ source }" msgstr "" -#: src/views/file/ListTable.vue:752 +#: src/views/file/ListTable.vue:805 msgid "Uncompress - %{ file }" msgstr "" -#: src/views/file/ListTable.vue:760 +#: src/views/file/ListTable.vue:813 msgid "Uncompress to" msgstr "" @@ -3994,15 +3981,15 @@ msgstr "" msgid "Enter user email" msgstr "" -#: src/views/setting/IndexView.vue:59 +#: src/views/setting/IndexView.vue:66 msgid "Panel is restarting, page will refresh in 5 seconds" msgstr "" -#: src/views/setting/IndexView.vue:81 +#: src/views/setting/IndexView.vue:88 msgid "Basic" msgstr "" -#: src/views/setting/IndexView.vue:82 +#: src/views/setting/IndexView.vue:89 msgid "Safe" msgstr "" @@ -4013,47 +4000,59 @@ msgstr "" msgid "Updated successfully" msgstr "" -#: src/views/setting/SettingBase.vue:20 +#: src/views/setting/SettingBase.vue:26 msgid "Stable" msgstr "" -#: src/views/setting/SettingBase.vue:24 +#: src/views/setting/SettingBase.vue:30 msgid "Beta" msgstr "" -#: src/views/setting/SettingBase.vue:33 -#: src/views/setting/SettingBase.vue:34 +#: src/views/setting/SettingBase.vue:76 +#: src/views/setting/SettingBase.vue:77 msgid "Panel Name" msgstr "" -#: src/views/setting/SettingBase.vue:36 +#: src/views/setting/SettingBase.vue:79 msgid "Language" msgstr "" -#: src/views/setting/SettingBase.vue:39 +#: src/views/setting/SettingBase.vue:82 msgid "Update Channel" msgstr "" -#: src/views/setting/SettingBase.vue:43 +#: src/views/setting/SettingBase.vue:86 msgid "8888" msgstr "" -#: src/views/setting/SettingBase.vue:45 +#: src/views/setting/SettingBase.vue:88 msgid "Default Website Directory" msgstr "" -#: src/views/setting/SettingBase.vue:46 +#: src/views/setting/SettingBase.vue:89 msgid "/opt/ace/sites" msgstr "" -#: src/views/setting/SettingBase.vue:48 +#: src/views/setting/SettingBase.vue:91 msgid "Default Backup Directory" msgstr "" -#: src/views/setting/SettingBase.vue:49 +#: src/views/setting/SettingBase.vue:92 msgid "/opt/ace/backup" msgstr "" +#: src/views/setting/SettingBase.vue:94 +msgid "Custom Logo" +msgstr "" + +#: src/views/setting/SettingBase.vue:97 +msgid "Please enter the complete URL" +msgstr "" + +#: src/views/setting/SettingBase.vue:100 +msgid "Hide Menu" +msgstr "" + #: src/views/setting/SettingSafe.vue:34 msgid "Disabled" msgstr "" diff --git a/web/src/locales/zh_CN.po b/web/src/locales/zh_CN.po index 50b1a624..49a114b8 100644 --- a/web/src/locales/zh_CN.po +++ b/web/src/locales/zh_CN.po @@ -2941,6 +2941,10 @@ msgstr "解压" msgid "Paste" msgstr "粘贴" +#: src/views/file/ListTable.vue:209 +msgid "Calculate" +msgstr "计算" + #: src/views/file/ListTable.vue:281 msgid "Are you sure you want to delete %{ name }?" msgstr "您确定要删除 %{ name } 吗?" @@ -2951,6 +2955,10 @@ msgstr "您确定要删除 %{ name } 吗?" msgid "Marked successfully, please navigate to the destination path to paste" msgstr "标记成功,请导航到目标路径进行粘贴" +#: src/views/file/ListTable.vue:447 +msgid "Failed to calculate size" +msgstr "计算大小失败" + #: src/views/file/ListTable.vue:415 src/views/file/ListTable.vue:502 #: src/views/file/ToolBar.vue:128 msgid "Warning" diff --git a/web/src/locales/zh_TW.po b/web/src/locales/zh_TW.po index 47e99484..05db6fcf 100644 --- a/web/src/locales/zh_TW.po +++ b/web/src/locales/zh_TW.po @@ -2939,6 +2939,10 @@ msgstr "解壓縮" msgid "Paste" msgstr "貼上" +#: src/views/file/ListTable.vue:209 +msgid "Calculate" +msgstr "計算" + #: src/views/file/ListTable.vue:281 msgid "Are you sure you want to delete %{ name }?" msgstr "您確定要刪除 %{ name } 嗎?" @@ -2949,6 +2953,10 @@ msgstr "您確定要刪除 %{ name } 嗎?" msgid "Marked successfully, please navigate to the destination path to paste" msgstr "標記成功,請導航到目標路徑進行粘貼" +#: src/views/file/ListTable.vue:447 +msgid "Failed to calculate size" +msgstr "計算大小失敗" + #: src/views/file/ListTable.vue:415 src/views/file/ListTable.vue:502 #: src/views/file/ToolBar.vue:128 msgid "Warning" diff --git a/web/src/views/file/ListTable.vue b/web/src/views/file/ListTable.vue index b1c56836..0a2b45cf 100644 --- a/web/src/views/file/ListTable.vue +++ b/web/src/views/file/ListTable.vue @@ -7,6 +7,7 @@ import { NInput, NPopconfirm, NPopselect, + NSpin, NTag } from 'naive-ui' import { useGettext } from 'vue3-gettext' @@ -48,6 +49,10 @@ const selectedRow = ref() const dropdownX = ref(0) const dropdownY = ref(0) +// 目录大小计算状态 +const sizeLoading = ref>(new Map()) +const sizeCache = ref>(new Map()) + const renameModal = ref(false) const renameModel = ref({ source: '', @@ -170,9 +175,42 @@ const columns: DataTableColumns = [ { title: $gettext('Size'), key: 'size', - minWidth: 80, + minWidth: 100, render(row: any): any { - return h(NTag, { type: 'info', size: 'small', bordered: false }, { default: () => row.size }) + // 文件 + if (!row.dir) { + return h( + NTag, + { type: 'info', size: 'small', bordered: false }, + { default: () => row.size } + ) + } + // 目录 + const cachedSize = sizeCache.value.get(row.full) + if (cachedSize) { + return h( + NTag, + { type: 'info', size: 'small', bordered: false }, + { default: () => cachedSize } + ) + } + const isLoading = sizeLoading.value.get(row.full) + if (isLoading) { + return h(NSpin, { size: 16, style: { paddingTop: '4px' } }) + } + return h( + 'a', + { + href: 'javascript:void(0)', + style: { color: '#18a058', cursor: 'pointer', fontSize: '12px' }, + onClick: (e: MouseEvent) => { + e.preventDefault() + e.stopPropagation() + calculateDirSize(row.full) + } + }, + $gettext('Calculate') + ) } }, { @@ -401,6 +439,18 @@ const { loading, data, page, total, pageSize, pageCount, refresh } = usePaginati } ) +// 计算目录大小 +const calculateDirSize = (dirPath: string) => { + sizeLoading.value.set(dirPath, true) + useRequest(file.size(dirPath)) + .onSuccess(({ data }) => { + sizeCache.value.set(dirPath, data) + }) + .onComplete(() => { + sizeLoading.value.set(dirPath, false) + }) +} + const handleRename = () => { const source = path.value + '/' + renameModel.value.source const target = path.value + '/' + renameModel.value.target @@ -665,6 +715,8 @@ onMounted(() => { selected.value = [] keyword.value = '' sub.value = false + sizeCache.value.clear() + sizeLoading.value.clear() nextTick(() => { refresh() })