2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 14:57:16 +08:00

feat: add file overwrite option to upload modal and display upload errors (#1322)

* Initial plan

* feat: add file overwrite upload option and display error messages

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: lint

---------

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: 耗子 <haozi@loli.email>
This commit is contained in:
Copilot
2026-02-03 00:24:51 +08:00
committed by GitHub
parent 9ab01dc64b
commit 30bf14c40c
8 changed files with 38 additions and 9 deletions

View File

@@ -73,6 +73,7 @@ type ChunkUploadStart struct {
FileName string `json:"file_name" validate:"required"` // 文件名
FileHash string `json:"file_hash" validate:"required|len:64"` // 文件SHA256
ChunkCount int `json:"chunk_count" validate:"required|min:1"` // 分块总数
Force bool `json:"force"` // 是否覆盖已存在文件
}
// ChunkUploadFinish 分块上传完成请求
@@ -81,4 +82,5 @@ type ChunkUploadFinish struct {
FileName string `json:"file_name" validate:"required"` // 文件名
FileHash string `json:"file_hash" validate:"required|len:64"` // 文件SHA256
ChunkCount int `json:"chunk_count" validate:"required|min:1"` // 分块总数
Force bool `json:"force"` // 是否覆盖已存在文件
}

View File

@@ -155,12 +155,13 @@ func (s *FileService) Upload(w http.ResponseWriter, r *http.Request) {
}
path := r.FormValue("path")
force := r.FormValue("force") == "true"
_, handler, err := r.FormFile("file")
if err != nil {
Error(w, http.StatusInternalServerError, s.t.Get("upload file error: %v", err))
return
}
if io.Exists(path) {
if io.Exists(path) && !force {
Error(w, http.StatusForbidden, s.t.Get("target path %s already exists", path))
return
}
@@ -565,7 +566,7 @@ func (s *FileService) ChunkUploadStart(w http.ResponseWriter, r *http.Request) {
}
targetPath := filepath.Join(req.Path, req.FileName)
if io.Exists(targetPath) {
if io.Exists(targetPath) && !req.Force {
Error(w, http.StatusForbidden, s.t.Get("target path %s already exists", targetPath))
return
}
@@ -680,7 +681,7 @@ func (s *FileService) ChunkUploadFinish(w http.ResponseWriter, r *http.Request)
targetPath := filepath.Join(req.Path, req.FileName)
// 检查目标文件是否已存在
if io.Exists(targetPath) {
if io.Exists(targetPath) && !req.Force {
Error(w, http.StatusForbidden, s.t.Get("target path %s already exists", targetPath))
return
}

View File

@@ -10,8 +10,7 @@ export default {
// 删除文件
delete: (path: string): any => http.Post('/file/delete', { path }),
// 上传文件
upload: (formData: FormData): any =>
http.Post('/file/upload', formData, { meta: { noAlert: true } }),
upload: (formData: FormData): any => http.Post('/file/upload', formData),
// 检查文件是否存在
exist: (paths: string[]): any => http.Post('/file/exist', paths),
// 移动文件
@@ -48,15 +47,16 @@ export default {
file_name: string
file_hash: string
chunk_count: number
force?: boolean
}): any => http.Post('/file/chunk/start', data),
// 上传分块
chunkUpload: (formData: FormData): any =>
http.Post('/file/chunk/upload', formData, { meta: { noAlert: true } }),
chunkUpload: (formData: FormData): any => http.Post('/file/chunk/upload', formData),
// 完成分块上传
chunkFinish: (data: {
path: string
file_name: string
file_hash: string
chunk_count: number
force?: boolean
}): any => http.Post('/file/chunk/finish', data)
}

View File

@@ -4618,6 +4618,10 @@ msgstr "There are items with the same name. Do you want to overwrite?"
msgid "Overwrite"
msgstr "Overwrite"
#: src/views/file/UploadModal.vue
msgid "Overwrite existing files"
msgstr "Overwrite existing files"
#: src/views/file/ListView.vue:744 src/views/file/ListView.vue:763
msgid "Renamed %{ source } to %{ target } successfully"
msgstr "Renamed %{ source } to %{ target } successfully"

View File

@@ -4705,6 +4705,10 @@ msgstr ""
msgid "Overwrite"
msgstr ""
#: src/views/file/UploadModal.vue
msgid "Overwrite existing files"
msgstr ""
#: src/views/file/ListView.vue:744
#: src/views/file/ListView.vue:763
msgid "Renamed %{ source } to %{ target } successfully"

View File

@@ -4395,6 +4395,10 @@ msgstr "存在同名项目。您要覆盖吗?"
msgid "Overwrite"
msgstr "覆盖"
#: src/views/file/UploadModal.vue
msgid "Overwrite existing files"
msgstr "覆盖已存在的文件"
#: src/views/file/ListView.vue:744 src/views/file/ListView.vue:763
msgid "Renamed %{ source } to %{ target } successfully"
msgstr "成功将 %{ source } 重命名为 %{ target }"

View File

@@ -4396,6 +4396,10 @@ msgstr "存在同名項目。您要覆蓋嗎?"
msgid "Overwrite"
msgstr "覆蓋"
#: src/views/file/UploadModal.vue
msgid "Overwrite existing files"
msgstr "覆蓋已存在的檔案"
#: src/views/file/ListView.vue:744 src/views/file/ListView.vue:763
msgid "Renamed %{ source } to %{ target } successfully"
msgstr "成功將 %{ source } 重命名為 %{ target }"

View File

@@ -17,6 +17,9 @@ const props = defineProps<{
const upload = ref<UploadInst | null>(null)
const fileList = ref<UploadFileInfo[]>([])
// 覆盖上传选项
const forceOverwrite = ref(false)
// 文件数量阈值,超过此数量需要二次确认
const FILE_COUNT_THRESHOLD = 100
// 大文件阈值,超过此大小使用分块上传 (100MB)
@@ -203,7 +206,8 @@ const chunkedUpload = async (
path: path.value,
file_name: file.name,
file_hash: fileHash,
chunk_count: chunkCount
chunk_count: chunkCount,
force: forceOverwrite.value
})
task.activeRequests.push(startMethod)
const startRes = await startMethod
@@ -287,7 +291,8 @@ const chunkedUpload = async (
path: path.value,
file_name: file.name,
file_hash: fileHash,
chunk_count: chunkCount
chunk_count: chunkCount,
force: forceOverwrite.value
})
task.activeRequests.push(finishMethod)
await finishMethod
@@ -390,6 +395,7 @@ const uploadRequest = ({ file, onFinish, onError, onProgress }: UploadCustomRequ
const formData = new FormData()
formData.append('path', `${path.value}/${file.name}`)
formData.append('file', fileObj)
formData.append('force', forceOverwrite.value.toString())
const method = api.upload(formData)
task.activeRequests.push(method)
@@ -463,6 +469,10 @@ const handleChange = (data: { fileList: UploadFileInfo[] }) => {
:segmented="false"
>
<n-flex vertical>
<!-- 覆盖上传选项 -->
<n-checkbox v-model:checked="forceOverwrite">
{{ $gettext('Overwrite existing files') }}
</n-checkbox>
<!-- 上传速度显示 -->
<n-flex
v-for="[key, progress] in uploadProgressMap"