mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 06:47:20 +08:00
fix: 文件管理bug
This commit is contained in:
@@ -25,13 +25,7 @@ type FileSave struct {
|
||||
Content string `form:"content" json:"content"`
|
||||
}
|
||||
|
||||
type FileMove struct {
|
||||
Source string `form:"source" json:"source" validate:"required"`
|
||||
Target string `form:"target" json:"target" validate:"required"`
|
||||
Force bool `form:"force" json:"force"`
|
||||
}
|
||||
|
||||
type FileCopy struct {
|
||||
type FileControl struct {
|
||||
Source string `form:"source" json:"source" validate:"required"`
|
||||
Target string `form:"target" json:"target" validate:"required"`
|
||||
Force bool `form:"force" json:"force"`
|
||||
|
||||
@@ -218,6 +218,7 @@ func Http(r chi.Router) {
|
||||
r.Post("/save", file.Save)
|
||||
r.Post("/delete", file.Delete)
|
||||
r.Post("/upload", file.Upload)
|
||||
r.Post("/exist", file.Exist)
|
||||
r.Post("/move", file.Move)
|
||||
r.Post("/copy", file.Copy)
|
||||
r.Get("/download", file.Download)
|
||||
|
||||
@@ -166,51 +166,77 @@ func (s *FileService) Upload(w http.ResponseWriter, r *http.Request) {
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *FileService) Exist(w http.ResponseWriter, r *http.Request) {
|
||||
binder := chix.NewBind(r)
|
||||
defer binder.Release()
|
||||
|
||||
var paths []string
|
||||
if err := binder.Body(&paths); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var results []bool
|
||||
for item := range slices.Values(paths) {
|
||||
results = append(results, io.Exists(item))
|
||||
}
|
||||
|
||||
Success(w, results)
|
||||
}
|
||||
|
||||
func (s *FileService) Move(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.FileMove](r)
|
||||
if err != nil {
|
||||
binder := chix.NewBind(r)
|
||||
defer binder.Release()
|
||||
|
||||
var req []request.FileControl
|
||||
if err := binder.Body(&req); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if io.Exists(req.Target) && !req.Force {
|
||||
Error(w, http.StatusForbidden, "target path already exists") // no translate, frontend will use it to determine whether to continue
|
||||
return
|
||||
}
|
||||
for item := range slices.Values(req) {
|
||||
if io.Exists(item.Target) && !item.Force {
|
||||
continue
|
||||
}
|
||||
|
||||
if io.IsDir(req.Source) && strings.HasPrefix(req.Target, req.Source) {
|
||||
Error(w, http.StatusForbidden, "you can't do this, it will be broken")
|
||||
return
|
||||
}
|
||||
if io.IsDir(item.Source) && strings.HasPrefix(item.Target, item.Source) {
|
||||
Error(w, http.StatusForbidden, "you can't do this, it will be broken")
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Mv(req.Source, req.Target); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
if err := io.Mv(item.Source, item.Target); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *FileService) Copy(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.FileCopy](r)
|
||||
if err != nil {
|
||||
binder := chix.NewBind(r)
|
||||
defer binder.Release()
|
||||
|
||||
var req []request.FileControl
|
||||
if err := binder.Body(&req); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if io.Exists(req.Target) && !req.Force {
|
||||
Error(w, http.StatusForbidden, "target path already exists") // no translate, frontend will use it to determine whether to continue
|
||||
return
|
||||
}
|
||||
for item := range slices.Values(req) {
|
||||
if io.Exists(item.Target) && !item.Force {
|
||||
continue
|
||||
}
|
||||
|
||||
if io.IsDir(req.Source) && strings.HasPrefix(req.Target, req.Source) {
|
||||
Error(w, http.StatusForbidden, "you can't do this, it will be broken")
|
||||
return
|
||||
}
|
||||
if io.IsDir(item.Source) && strings.HasPrefix(item.Target, item.Source) {
|
||||
Error(w, http.StatusForbidden, "you can't do this, it will be broken")
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Cp(req.Source, req.Target); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
if err := io.Cp(item.Source, item.Target); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
@@ -415,7 +441,7 @@ func (s *FileService) List(w http.ResponseWriter, r *http.Request) {
|
||||
// formatDir 格式化目录信息
|
||||
func (s *FileService) formatDir(base string, entries []stdos.DirEntry) []any {
|
||||
var paths []any
|
||||
for _, file := range entries {
|
||||
for file := range slices.Values(entries) {
|
||||
info, err := file.Info()
|
||||
if err != nil {
|
||||
continue
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { AxiosResponse } from 'axios'
|
||||
|
||||
import { request } from '@/utils'
|
||||
import type { RequestConfig } from '~/types/axios'
|
||||
|
||||
export default {
|
||||
// 创建文件/文件夹
|
||||
@@ -25,12 +24,12 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
// 检查文件是否存在
|
||||
exist: (paths: string[]): Promise<AxiosResponse<any>> => request.post('/file/exist', paths),
|
||||
// 移动文件
|
||||
move: (source: string, target: string, force: boolean): Promise<AxiosResponse<any>> =>
|
||||
request.post('/file/move', { source, target, force }, { noNeedTip: true } as RequestConfig),
|
||||
move: (paths: any[]): Promise<AxiosResponse<any>> => request.post('/file/move', paths),
|
||||
// 复制文件
|
||||
copy: (source: string, target: string, force: boolean): Promise<AxiosResponse<any>> =>
|
||||
request.post('/file/copy', { source, target, force }, { noNeedTip: true } as RequestConfig),
|
||||
copy: (paths: any[]): Promise<AxiosResponse<any>> => request.post('/file/copy', paths),
|
||||
// 远程下载
|
||||
remoteDownload: (path: string, url: string): Promise<AxiosResponse<any>> =>
|
||||
request.post('/file/remoteDownload', { path, url }),
|
||||
|
||||
@@ -13,6 +13,7 @@ import type { Marked } from '@/views/file/types'
|
||||
const path = ref('/www')
|
||||
const selected = ref<string[]>([])
|
||||
const marked = ref<Marked[]>([])
|
||||
const markedType = ref<string>('copy')
|
||||
|
||||
const compress = ref(false)
|
||||
const permission = ref(false)
|
||||
@@ -26,6 +27,7 @@ const permission = ref(false)
|
||||
v-model:path="path"
|
||||
v-model:selected="selected"
|
||||
v-model:marked="marked"
|
||||
v-model:markedType="markedType"
|
||||
v-model:compress="compress"
|
||||
v-model:permission="permission"
|
||||
/>
|
||||
@@ -33,6 +35,7 @@ const permission = ref(false)
|
||||
v-model:path="path"
|
||||
v-model:selected="selected"
|
||||
v-model:marked="marked"
|
||||
v-model:markedType="markedType"
|
||||
v-model:compress="compress"
|
||||
v-model:permission="permission"
|
||||
/>
|
||||
|
||||
@@ -15,6 +15,7 @@ const sort = ref<string>('')
|
||||
const path = defineModel<string>('path', { type: String, required: true })
|
||||
const selected = defineModel<any[]>('selected', { type: Array, default: () => [] })
|
||||
const marked = defineModel<Marked[]>('marked', { type: Array, default: () => [] })
|
||||
const markedType = defineModel<string>('markedType', { type: String, required: true })
|
||||
const compress = defineModel<boolean>('compress', { type: Boolean, required: true })
|
||||
const permission = defineModel<boolean>('permission', { type: Boolean, required: true })
|
||||
const editorModal = ref(false)
|
||||
@@ -275,21 +276,23 @@ const columns: DataTableColumns<RowData> = [
|
||||
onUpdateValue: (value) => {
|
||||
switch (value) {
|
||||
case 'copy':
|
||||
markedType.value = 'copy'
|
||||
marked.value = [
|
||||
{
|
||||
name: row.name,
|
||||
source: row.full,
|
||||
type: 'copy'
|
||||
force: false
|
||||
}
|
||||
]
|
||||
window.$message.success('标记成功,请前往目标路径粘贴')
|
||||
break
|
||||
case 'move':
|
||||
markedType.value = 'move'
|
||||
marked.value = [
|
||||
{
|
||||
name: row.name,
|
||||
source: row.full,
|
||||
type: 'move'
|
||||
force: false
|
||||
}
|
||||
]
|
||||
window.$message.success('标记成功,请前往目标路径粘贴')
|
||||
@@ -385,7 +388,7 @@ const getList = async (path: string, page: number, limit: number) => {
|
||||
})
|
||||
}
|
||||
|
||||
const handleRename = () => {
|
||||
const handleRename = async () => {
|
||||
const source = path.value + '/' + renameModel.value.source
|
||||
const target = path.value + '/' + renameModel.value.target
|
||||
if (!checkName(renameModel.value.source) || !checkName(renameModel.value.target)) {
|
||||
@@ -393,43 +396,30 @@ const handleRename = () => {
|
||||
return
|
||||
}
|
||||
|
||||
file
|
||||
.move(source, target, false)
|
||||
.then(() => {
|
||||
await file.exist([source]).then(async (res) => {
|
||||
if (res.data[0]) {
|
||||
window.$dialog.warning({
|
||||
title: '警告',
|
||||
content: `存在同名项,是否强制覆盖?`,
|
||||
positiveText: '覆盖',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
await file.move([{ source, target, force: true }])
|
||||
window.$message.success(
|
||||
`重命名 ${renameModel.value.source} 为 ${renameModel.value.target} 成功`
|
||||
)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
await file.move([{ source, target, force: true }])
|
||||
window.$message.success(
|
||||
`重命名 ${renameModel.value.source} 为 ${renameModel.value.target} 成功`
|
||||
)
|
||||
renameModal.value = false
|
||||
window.$bus.emit('file:refresh')
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.message == 'target path already exists') {
|
||||
window.$dialog.warning({
|
||||
title: '警告',
|
||||
content: `存在同名项,是否强制覆盖?`,
|
||||
positiveText: '覆盖',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
file
|
||||
.move(source, target, true)
|
||||
.then(() => {
|
||||
window.$message.success(
|
||||
`重命名 ${renameModel.value.source} 为 ${renameModel.value.target} 成功`
|
||||
)
|
||||
renameModal.value = false
|
||||
window.$bus.emit('file:refresh')
|
||||
})
|
||||
.catch((err) => {
|
||||
window.$message.error(err.message)
|
||||
})
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
renameModal.value = false
|
||||
window.$bus.emit('file:refresh')
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
renameModal.value = false
|
||||
window.$bus.emit('file:refresh')
|
||||
}
|
||||
|
||||
const handleUnCompress = () => {
|
||||
@@ -468,76 +458,66 @@ const handlePaste = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
for (const { name, source, type } of marked.value) {
|
||||
const target = path.value + '/' + name
|
||||
if (type === 'copy') {
|
||||
file
|
||||
.copy(source, target, false)
|
||||
.then(() => {
|
||||
window.$message.success(`复制 ${source} 到 ${target} 成功`)
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.message == 'target path already exists') {
|
||||
window.$dialog.warning({
|
||||
title: '警告',
|
||||
content: `目标 ${target} 已存在,是否覆盖?`,
|
||||
positiveText: '覆盖',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
file
|
||||
.copy(source, target, true)
|
||||
.then(() => {
|
||||
window.$message.success(`复制 ${source} 到 ${target} 成功`)
|
||||
})
|
||||
.catch((err) => {
|
||||
window.$message.error(err.message)
|
||||
})
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
marked.value = []
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
window.$bus.emit('file:refresh')
|
||||
})
|
||||
} else {
|
||||
file
|
||||
.move(source, target, false)
|
||||
.then(() => {
|
||||
window.$message.success(`移动 ${source} 到 ${target} 成功`)
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.message == 'target path already exists') {
|
||||
window.$dialog.warning({
|
||||
title: '警告',
|
||||
content: `目标 ${target} 已存在,是否覆盖?`,
|
||||
positiveText: '覆盖',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
file
|
||||
.move(source, target, true)
|
||||
.then(() => {
|
||||
window.$message.success(`移动 ${source} 到 ${target} 成功`)
|
||||
})
|
||||
.catch((err) => {
|
||||
window.$message.error(err.message)
|
||||
})
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
marked.value = []
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
window.$bus.emit('file:refresh')
|
||||
})
|
||||
// 查重
|
||||
let flag = false
|
||||
let paths = marked.value.map((item) => {
|
||||
return {
|
||||
name: item.name,
|
||||
source: item.source,
|
||||
target: path.value + '/' + item.name,
|
||||
force: false
|
||||
}
|
||||
}
|
||||
|
||||
marked.value = []
|
||||
})
|
||||
const sources = paths.map((item: any) => item.target)
|
||||
await file.exist(sources).then(async (res) => {
|
||||
for (let i = 0; i < res.data.length; i++) {
|
||||
if (res.data[i]) {
|
||||
flag = true
|
||||
paths[i].force = true
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
window.$dialog.warning({
|
||||
title: '警告',
|
||||
content: `存在同名项
|
||||
${paths
|
||||
.filter((item) => item.force)
|
||||
.map((item) => item.name)
|
||||
.join(', ')} 是否覆盖?`,
|
||||
positiveText: '覆盖',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
if (markedType.value == 'copy') {
|
||||
await file.copy(paths).then(() => {
|
||||
window.$message.success('复制成功')
|
||||
})
|
||||
} else {
|
||||
await file.move(paths).then(() => {
|
||||
window.$message.success('移动成功')
|
||||
})
|
||||
}
|
||||
marked.value = []
|
||||
window.$bus.emit('file:refresh')
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
marked.value = []
|
||||
window.$message.info('已取消')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if (markedType.value == 'copy') {
|
||||
await file.copy(paths).then(() => {
|
||||
window.$message.success('复制成功')
|
||||
})
|
||||
} else {
|
||||
await file.move(paths).then(() => {
|
||||
window.$message.success('移动成功')
|
||||
})
|
||||
}
|
||||
marked.value = []
|
||||
window.$bus.emit('file:refresh')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleSelect = (key: string) => {
|
||||
@@ -553,21 +533,23 @@ const handleSelect = (key: string) => {
|
||||
editorModal.value = true
|
||||
break
|
||||
case 'copy':
|
||||
markedType.value = 'copy'
|
||||
marked.value = [
|
||||
{
|
||||
name: selectedRow.value.name,
|
||||
source: selectedRow.value.full,
|
||||
type: 'copy'
|
||||
force: false
|
||||
}
|
||||
]
|
||||
window.$message.success('标记成功,请前往目标路径粘贴')
|
||||
break
|
||||
case 'move':
|
||||
markedType.value = 'move'
|
||||
marked.value = [
|
||||
{
|
||||
name: selectedRow.value.name,
|
||||
source: selectedRow.value.full,
|
||||
type: 'move'
|
||||
force: false
|
||||
}
|
||||
]
|
||||
window.$message.success('标记成功,请前往目标路径粘贴')
|
||||
|
||||
@@ -17,17 +17,12 @@ const checkbox = ref({
|
||||
|
||||
const handlePermission = async () => {
|
||||
for (const path of selected.value) {
|
||||
await file
|
||||
.permission(path, `0${mode.value}`, owner.value, group.value)
|
||||
.then(() => {
|
||||
window.$message.success(`修改 ${path} 成功`)
|
||||
show.value = false
|
||||
selected.value = []
|
||||
})
|
||||
.catch(() => {
|
||||
window.$message.error(`修改 ${path} 失败`)
|
||||
})
|
||||
await file.permission(path, `0${mode.value}`, owner.value, group.value).then(() => {
|
||||
window.$message.success(`修改 ${path} 成功`)
|
||||
})
|
||||
}
|
||||
show.value = false
|
||||
selected.value = []
|
||||
window.$bus.emit('file:refresh')
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { Marked } from '@/views/file/types'
|
||||
const path = defineModel<string>('path', { type: String, required: true })
|
||||
const selected = defineModel<string[]>('selected', { type: Array, default: () => [] })
|
||||
const marked = defineModel<Marked[]>('marked', { type: Array, default: () => [] })
|
||||
const markedType = defineModel<string>('markedType', { type: String, required: true })
|
||||
const compress = defineModel<boolean>('compress', { type: Boolean, required: true })
|
||||
const permission = defineModel<boolean>('permission', { type: Boolean, required: true })
|
||||
|
||||
@@ -62,11 +63,13 @@ const handleCopy = () => {
|
||||
window.$message.error('请选择要复制的文件/文件夹')
|
||||
return
|
||||
}
|
||||
markedType.value = 'copy'
|
||||
marked.value = selected.value.map((path) => ({
|
||||
name: lastDirectory(path),
|
||||
source: path,
|
||||
type: 'copy'
|
||||
force: false
|
||||
}))
|
||||
selected.value = []
|
||||
window.$message.success('标记成功,请前往目标路径粘贴')
|
||||
}
|
||||
|
||||
@@ -75,11 +78,13 @@ const handleMove = () => {
|
||||
window.$message.error('请选择要移动的文件/文件夹')
|
||||
return
|
||||
}
|
||||
markedType.value = 'move'
|
||||
marked.value = selected.value.map((path) => ({
|
||||
name: lastDirectory(path),
|
||||
source: path,
|
||||
type: 'move'
|
||||
force: false
|
||||
}))
|
||||
selected.value = []
|
||||
window.$message.success('标记成功,请前往目标路径粘贴')
|
||||
}
|
||||
|
||||
@@ -93,90 +98,82 @@ const handlePaste = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
for (const { name, source, type } of marked.value) {
|
||||
const target = path.value + '/' + name
|
||||
if (type === 'copy') {
|
||||
file
|
||||
.copy(source, target, false)
|
||||
.then(() => {
|
||||
window.$message.success(`复制 ${source} 到 ${target} 成功`)
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.message == 'target path already exists') {
|
||||
window.$dialog.warning({
|
||||
title: '警告',
|
||||
content: `目标 ${target} 已存在,是否覆盖?`,
|
||||
positiveText: '覆盖',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
file
|
||||
.copy(source, target, true)
|
||||
.then(() => {
|
||||
window.$message.success(`复制 ${source} 到 ${target} 成功`)
|
||||
})
|
||||
.catch((err) => {
|
||||
window.$message.error(err.message)
|
||||
})
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
handleCancel()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
window.$bus.emit('file:refresh')
|
||||
})
|
||||
} else {
|
||||
file
|
||||
.move(source, target, false)
|
||||
.then(() => {
|
||||
window.$message.success(`移动 ${source} 到 ${target} 成功`)
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.message == 'target path already exists') {
|
||||
window.$dialog.warning({
|
||||
title: '警告',
|
||||
content: `目标 ${target} 已存在,是否覆盖?`,
|
||||
positiveText: '覆盖',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
file
|
||||
.move(source, target, true)
|
||||
.then(() => {
|
||||
window.$message.success(`移动 ${source} 到 ${target} 成功`)
|
||||
})
|
||||
.catch((err) => {
|
||||
window.$message.error(err.message)
|
||||
})
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
handleCancel()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
window.$bus.emit('file:refresh')
|
||||
})
|
||||
// 查重
|
||||
let flag = false
|
||||
let paths = marked.value.map((item) => {
|
||||
return {
|
||||
name: item.name,
|
||||
source: item.source,
|
||||
target: path.value + '/' + item.name,
|
||||
force: false
|
||||
}
|
||||
}
|
||||
|
||||
marked.value = []
|
||||
})
|
||||
const sources = paths.map((item: any) => item.target)
|
||||
await file.exist(sources).then(async (res) => {
|
||||
for (let i = 0; i < res.data.length; i++) {
|
||||
if (res.data[i]) {
|
||||
flag = true
|
||||
paths[i].force = true
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
window.$dialog.warning({
|
||||
title: '警告',
|
||||
content: `存在同名项
|
||||
${paths
|
||||
.filter((item) => item.force)
|
||||
.map((item) => item.name)
|
||||
.join(', ')} 是否覆盖?`,
|
||||
positiveText: '覆盖',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
if (markedType.value == 'copy') {
|
||||
await file.copy(paths).then(() => {
|
||||
window.$message.success('复制成功')
|
||||
})
|
||||
} else {
|
||||
await file.move(paths).then(() => {
|
||||
window.$message.success('移动成功')
|
||||
})
|
||||
}
|
||||
marked.value = []
|
||||
window.$bus.emit('file:refresh')
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
marked.value = []
|
||||
window.$message.info('已取消')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if (markedType.value == 'copy') {
|
||||
await file.copy(paths).then(() => {
|
||||
window.$message.success('复制成功')
|
||||
})
|
||||
} else {
|
||||
await file.move(paths).then(() => {
|
||||
window.$message.success('移动成功')
|
||||
})
|
||||
}
|
||||
marked.value = []
|
||||
window.$bus.emit('file:refresh')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const bulkDelete = () => {
|
||||
const bulkDelete = async () => {
|
||||
if (!selected.value.length) {
|
||||
window.$message.error('请选择要删除的文件/文件夹')
|
||||
return
|
||||
}
|
||||
|
||||
for (const path of selected.value) {
|
||||
file.delete(path).then(() => {
|
||||
await file.delete(path).then(() => {
|
||||
window.$message.success(`删除 ${path} 成功`)
|
||||
window.$bus.emit('file:refresh')
|
||||
})
|
||||
}
|
||||
|
||||
selected.value = []
|
||||
}
|
||||
|
||||
// 自动填充下载文件名
|
||||
|
||||
@@ -33,21 +33,24 @@ const uploadRequest = ({ file, onFinish, onError, onProgress }: UploadCustomRequ
|
||||
:bordered="false"
|
||||
:segmented="false"
|
||||
>
|
||||
<n-upload
|
||||
ref="upload"
|
||||
directory-dnd
|
||||
multiple
|
||||
action="/api/panel/file/upload"
|
||||
:custom-request="uploadRequest"
|
||||
>
|
||||
<n-upload-dragger>
|
||||
<div style="margin-bottom: 12px">
|
||||
<the-icon :size="48" icon="bi:arrow-up-square" />
|
||||
</div>
|
||||
<NText style="font-size: 16px"> 点击或者拖动文件到该区域来上传</NText>
|
||||
<NP depth="3" style="margin: 8px 0 0 0"> 不支持断点续传,大文件建议使用 FTP 上传 </NP>
|
||||
</n-upload-dragger>
|
||||
</n-upload>
|
||||
<n-flex vertical>
|
||||
<n-alert type="info">若上传报网络错误,请开启面板 HTTPS 后重试</n-alert>
|
||||
<n-upload
|
||||
ref="upload"
|
||||
directory-dnd
|
||||
multiple
|
||||
action="/api/panel/file/upload"
|
||||
:custom-request="uploadRequest"
|
||||
>
|
||||
<n-upload-dragger>
|
||||
<div style="margin-bottom: 12px">
|
||||
<the-icon :size="48" icon="bi:arrow-up-square" />
|
||||
</div>
|
||||
<NText text-18> 点击或者拖动文件到该区域来上传</NText>
|
||||
<NP depth="3" m-10> 大文件建议使用 SFTP 上传 </NP>
|
||||
</n-upload-dragger>
|
||||
</n-upload>
|
||||
</n-flex>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export interface Marked {
|
||||
name: string
|
||||
source: string
|
||||
type: string
|
||||
force: boolean
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user