mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-11-27 20:49:03 +08:00
feat: 文件增加回收站功能 (#2586)
Refs https://github.com/1Panel-dev/1Panel/issues/1137 Refs https://github.com/1Panel-dev/1Panel/issues/624
This commit is contained in:
parent
eeb80749a2
commit
743e7d0b59
@ -53,4 +53,6 @@ var (
|
||||
processService = service.NewIProcessService()
|
||||
|
||||
hostToolService = service.NewIHostToolService()
|
||||
|
||||
recycleBinService = service.NewIRecycleBinService()
|
||||
)
|
||||
|
70
backend/app/api/v1/recycle_bin.go
Normal file
70
backend/app/api/v1/recycle_bin.go
Normal file
@ -0,0 +1,70 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags RecycleBin
|
||||
// @Summary List RecycleBin files
|
||||
// @Description 获取回收站文件列表
|
||||
// @Accept json
|
||||
// @Param request body dto.PageInfo true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /recycle/search [post]
|
||||
func (b *BaseApi) SearchRecycleBinFile(c *gin.Context) {
|
||||
var req dto.PageInfo
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, list, err := recycleBinService.Page(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Items: list,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags RecycleBin
|
||||
// @Summary Reduce RecycleBin files
|
||||
// @Description 还原回收站文件
|
||||
// @Accept json
|
||||
// @Param request body request.RecycleBinReduce true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /recycle/reduce [post]
|
||||
// @x-panel-log {"bodyKeys":["name],"paramKeys":[],"BeforeFunctions":[],"formatZH":"还原回收站文件 [name]","formatEN":"Reduce RecycleBin file [name]"}
|
||||
func (b *BaseApi) ReduceRecycleBinFile(c *gin.Context) {
|
||||
var req request.RecycleBinReduce
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := recycleBinService.Reduce(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags RecycleBin
|
||||
// @Summary Clear RecycleBin files
|
||||
// @Description 清空回收站文件
|
||||
// @Accept json
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /recycle/clear [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清空回收站","formatEN":"清空回收站"}
|
||||
func (b *BaseApi) ClearRecycleBinFile(c *gin.Context) {
|
||||
if err := recycleBinService.Clear(); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
@ -26,8 +26,9 @@ type FileCreate struct {
|
||||
}
|
||||
|
||||
type FileDelete struct {
|
||||
Path string `json:"path" validate:"required"`
|
||||
IsDir bool `json:"isDir"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
IsDir bool `json:"isDir"`
|
||||
ForceDelete bool `json:"forceDelete"`
|
||||
}
|
||||
|
||||
type FileBatchDelete struct {
|
||||
|
11
backend/app/dto/request/recycle_bin.go
Normal file
11
backend/app/dto/request/recycle_bin.go
Normal file
@ -0,0 +1,11 @@
|
||||
package request
|
||||
|
||||
type RecycleBinCreate struct {
|
||||
SourcePath string `json:"sourcePath" validate:"required"`
|
||||
}
|
||||
|
||||
type RecycleBinReduce struct {
|
||||
From string `json:"from" validate:"required"`
|
||||
RName string `json:"rName" validate:"required"`
|
||||
Name string `json:"name"`
|
||||
}
|
14
backend/app/dto/response/recycle_bin.go
Normal file
14
backend/app/dto/response/recycle_bin.go
Normal file
@ -0,0 +1,14 @@
|
||||
package response
|
||||
|
||||
import "time"
|
||||
|
||||
type RecycleBinDTO struct {
|
||||
Name string `json:"name"`
|
||||
Size int `json:"size"`
|
||||
Type string `json:"type"`
|
||||
DeleteTime time.Time `json:"deleteTime"`
|
||||
RName string `json:"rName"`
|
||||
SourcePath string `json:"sourcePath"`
|
||||
IsDir bool `json:"isDir"`
|
||||
From string `json:"from"`
|
||||
}
|
@ -133,11 +133,14 @@ func (f *FileService) Create(op request.FileCreate) error {
|
||||
|
||||
func (f *FileService) Delete(op request.FileDelete) error {
|
||||
fo := files.NewFileOp()
|
||||
if op.IsDir {
|
||||
return fo.DeleteDir(op.Path)
|
||||
} else {
|
||||
return fo.DeleteFile(op.Path)
|
||||
if op.ForceDelete {
|
||||
if op.IsDir {
|
||||
return fo.DeleteDir(op.Path)
|
||||
} else {
|
||||
return fo.DeleteFile(op.Path)
|
||||
}
|
||||
}
|
||||
return NewIRecycleBinService().Create(request.RecycleBinCreate{SourcePath: op.Path})
|
||||
}
|
||||
|
||||
func (f *FileService) BatchDelete(op request.FileBatchDelete) error {
|
||||
|
208
backend/app/service/recycle_bin.go
Normal file
208
backend/app/service/recycle_bin.go
Normal file
@ -0,0 +1,208 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/shirou/gopsutil/v3/disk"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RecycleBinService struct {
|
||||
}
|
||||
|
||||
type IRecycleBinService interface {
|
||||
Page(search dto.PageInfo) (int64, []response.RecycleBinDTO, error)
|
||||
Create(create request.RecycleBinCreate) error
|
||||
Reduce(reduce request.RecycleBinReduce) error
|
||||
Clear() error
|
||||
}
|
||||
|
||||
func NewIRecycleBinService() IRecycleBinService {
|
||||
return &RecycleBinService{}
|
||||
}
|
||||
|
||||
func (r RecycleBinService) Page(search dto.PageInfo) (int64, []response.RecycleBinDTO, error) {
|
||||
var (
|
||||
result []response.RecycleBinDTO
|
||||
)
|
||||
partitions, err := disk.Partitions(false)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
op := files.NewFileOp()
|
||||
for _, p := range partitions {
|
||||
dir := path.Join(p.Mountpoint, ".1panel_clash")
|
||||
if !op.Stat(dir) {
|
||||
continue
|
||||
}
|
||||
clashFiles, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
for _, file := range clashFiles {
|
||||
if strings.HasPrefix(file.Name(), "_1p_") {
|
||||
recycleDTO, err := getRecycleBinDTOFromName(file.Name())
|
||||
recycleDTO.IsDir = file.IsDir()
|
||||
recycleDTO.From = dir
|
||||
if err == nil {
|
||||
result = append(result, *recycleDTO)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
startIndex := (search.Page - 1) * search.PageSize
|
||||
endIndex := startIndex + search.PageSize
|
||||
|
||||
if startIndex > len(result) {
|
||||
return int64(len(result)), result, nil
|
||||
}
|
||||
if endIndex > len(result) {
|
||||
endIndex = len(result)
|
||||
}
|
||||
return int64(len(result)), result[startIndex:endIndex], nil
|
||||
}
|
||||
|
||||
func (r RecycleBinService) Create(create request.RecycleBinCreate) error {
|
||||
op := files.NewFileOp()
|
||||
if !op.Stat(create.SourcePath) {
|
||||
return buserr.New(constant.ErrLinkPathNotFound)
|
||||
}
|
||||
clashDir, err := getClashDir(create.SourcePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
paths := strings.Split(create.SourcePath, "/")
|
||||
rNamePre := strings.Join(paths, "_1p_")
|
||||
deleteTime := time.Now()
|
||||
openFile, err := op.OpenFile(create.SourcePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileInfo, err := openFile.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
size := 0
|
||||
if fileInfo.IsDir() {
|
||||
sizeF, err := op.GetDirSize(create.SourcePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
size = int(sizeF)
|
||||
} else {
|
||||
size = int(fileInfo.Size())
|
||||
}
|
||||
|
||||
rName := fmt.Sprintf("_1p_%s%s_p_%d_%d", "file", rNamePre, size, deleteTime.Unix())
|
||||
return op.Rename(create.SourcePath, path.Join(clashDir, rName))
|
||||
}
|
||||
|
||||
func (r RecycleBinService) Reduce(reduce request.RecycleBinReduce) error {
|
||||
filePath := path.Join(reduce.From, reduce.RName)
|
||||
op := files.NewFileOp()
|
||||
if !op.Stat(filePath) {
|
||||
return buserr.New(constant.ErrLinkPathNotFound)
|
||||
}
|
||||
recycleBinDTO, err := getRecycleBinDTOFromName(reduce.RName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !op.Stat(path.Dir(recycleBinDTO.SourcePath)) {
|
||||
return buserr.New("ErrSourcePathNotFound")
|
||||
}
|
||||
if op.Stat(recycleBinDTO.SourcePath) {
|
||||
if err = op.RmRf(recycleBinDTO.SourcePath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return op.Rename(filePath, recycleBinDTO.SourcePath)
|
||||
}
|
||||
|
||||
func (r RecycleBinService) Clear() error {
|
||||
partitions, err := disk.Partitions(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
op := files.NewFileOp()
|
||||
for _, p := range partitions {
|
||||
dir := path.Join(p.Mountpoint, ".1panel_clash")
|
||||
if !op.Stat(dir) {
|
||||
continue
|
||||
}
|
||||
newDir := path.Join(p.Mountpoint, "1panel_clash")
|
||||
if err := op.Rename(dir, newDir); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
_ = op.DeleteDir(newDir)
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getClashDir(realPath string) (string, error) {
|
||||
trimmedPath := strings.Trim(realPath, "/")
|
||||
parts := strings.Split(trimmedPath, "/")
|
||||
dir := ""
|
||||
if len(parts) > 0 {
|
||||
dir = parts[0]
|
||||
partitions, err := disk.Partitions(false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, p := range partitions {
|
||||
if p.Mountpoint == dir {
|
||||
if err = createClashDir(path.Join(p.Mountpoint, ".1panel_clash")); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return constant.RecycleBinDir, createClashDir(constant.RecycleBinDir)
|
||||
}
|
||||
|
||||
func createClashDir(clashDir string) error {
|
||||
op := files.NewFileOp()
|
||||
if !op.Stat(clashDir) {
|
||||
if err := op.CreateDir(clashDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getRecycleBinDTOFromName(filename string) (*response.RecycleBinDTO, error) {
|
||||
r := regexp.MustCompile(`_1p_file_1p_(.+)_p_(\d+)_(\d+)`)
|
||||
matches := r.FindStringSubmatch(filename)
|
||||
if len(matches) != 4 {
|
||||
return nil, fmt.Errorf("invalid filename format")
|
||||
}
|
||||
sourcePath := "/" + strings.ReplaceAll(matches[1], "_1p_", "/")
|
||||
size, err := strconv.ParseInt(matches[2], 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deleteTime, err := strconv.ParseInt(matches[3], 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &response.RecycleBinDTO{
|
||||
Name: path.Base(sourcePath),
|
||||
Size: int(size),
|
||||
Type: "file",
|
||||
DeleteTime: time.Unix(deleteTime, 0),
|
||||
SourcePath: sourcePath,
|
||||
RName: filename,
|
||||
}, nil
|
||||
}
|
@ -15,4 +15,5 @@ var (
|
||||
LocalAppInstallDir = path.Join(AppInstallDir, "local")
|
||||
RemoteAppResourceDir = path.Join(AppResourceDir, "remote")
|
||||
RuntimeDir = path.Join(DataDir, "runtime")
|
||||
RecycleBinDir = "/.1panel_clash"
|
||||
)
|
||||
|
@ -63,6 +63,7 @@ ErrFileIsExit: "File already exists!"
|
||||
ErrFileUpload: "Failed to upload file {{.name}} {{.detail}}"
|
||||
ErrFileDownloadDir: "Download folder not supported"
|
||||
ErrCmdNotFound: "{{ .name}} command does not exist, please install this command on the host first"
|
||||
ErrSourcePathNotFound: "Source directory does not exist"
|
||||
|
||||
#website
|
||||
ErrDomainIsExist: "Domain is already exist"
|
||||
|
@ -63,6 +63,7 @@ ErrFileIsExit: "文件已存在!"
|
||||
ErrFileUpload: "{{ .name }} 上傳文件失敗 {{ .detail}}"
|
||||
ErrFileDownloadDir: "不支持下載文件夾"
|
||||
ErrCmdNotFound: "{{ .name}} 命令不存在,請先在宿主機安裝此命令"
|
||||
ErrSourcePathNotFound: "源目錄不存在"
|
||||
|
||||
#website
|
||||
ErrDomainIsExist: "域名已存在"
|
||||
|
@ -63,6 +63,7 @@ ErrFileIsExit: "文件已存在!"
|
||||
ErrFileUpload: "{{ .name }} 上传文件失败 {{ .detail}}"
|
||||
ErrFileDownloadDir: "不支持下载文件夹"
|
||||
ErrCmdNotFound: "{{ .name}} 命令不存在,请先在宿主机安装此命令"
|
||||
ErrSourcePathNotFound: "源目录不存在"
|
||||
|
||||
#website
|
||||
ErrDomainIsExist: "域名已存在"
|
||||
|
@ -37,5 +37,9 @@ func (f *FileRouter) InitFileRouter(Router *gin.RouterGroup) {
|
||||
fileRouter.POST("/size", baseApi.Size)
|
||||
fileRouter.GET("/ws", baseApi.Ws)
|
||||
fileRouter.GET("/keys", baseApi.Keys)
|
||||
|
||||
fileRouter.POST("/recycle/search", baseApi.SearchRecycleBinFile)
|
||||
fileRouter.POST("/recycle/reduce", baseApi.ReduceRecycleBinFile)
|
||||
fileRouter.POST("/recycle/clear", baseApi.ClearRecycleBinFile)
|
||||
}
|
||||
}
|
||||
|
@ -84,6 +84,14 @@ func (f FileOp) DeleteFile(dst string) error {
|
||||
return f.Fs.Remove(dst)
|
||||
}
|
||||
|
||||
func (f FileOp) Delete(dst string) error {
|
||||
return os.RemoveAll(dst)
|
||||
}
|
||||
|
||||
func (f FileOp) RmRf(dst string) error {
|
||||
return cmd.ExecCmd(fmt.Sprintf("rm -rf %s", dst))
|
||||
}
|
||||
|
||||
func (f FileOp) WriteFile(dst string, in io.Reader, mode fs.FileMode) error {
|
||||
file, err := f.Fs.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
|
||||
if err != nil {
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Code generated by swaggo/swag. DO NOT EDIT.
|
||||
|
||||
// Package docs GENERATED BY SWAG; DO NOT EDIT
|
||||
// This file was generated by swaggo/swag
|
||||
package docs
|
||||
|
||||
import "github.com/swaggo/swag"
|
||||
@ -7826,6 +7826,94 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/recycle/clear": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "清空回收站文件",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"RecycleBin"
|
||||
],
|
||||
"summary": "Clear RecycleBin files",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/recycle/reduce": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "还原回收站文件",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"RecycleBin"
|
||||
],
|
||||
"summary": "Reduce RecycleBin files",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.RecycleBinReduce"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/recycle/search": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取回收站文件列表",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"RecycleBin"
|
||||
],
|
||||
"summary": "List RecycleBin files",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.PageInfo"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/runtimes": {
|
||||
"post": {
|
||||
"security": [
|
||||
@ -16228,6 +16316,9 @@ const docTemplate = `{
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"forceDelete": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isDir": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@ -16837,6 +16928,21 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.RecycleBinReduce": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"from",
|
||||
"rName"
|
||||
],
|
||||
"properties": {
|
||||
"from": {
|
||||
"type": "string"
|
||||
},
|
||||
"rName": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.RuntimeCreate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -7819,6 +7819,94 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/recycle/clear": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "清空回收站文件",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"RecycleBin"
|
||||
],
|
||||
"summary": "Clear RecycleBin files",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/recycle/reduce": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "还原回收站文件",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"RecycleBin"
|
||||
],
|
||||
"summary": "Reduce RecycleBin files",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.RecycleBinReduce"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/recycle/search": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取回收站文件列表",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"RecycleBin"
|
||||
],
|
||||
"summary": "List RecycleBin files",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.PageInfo"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/runtimes": {
|
||||
"post": {
|
||||
"security": [
|
||||
@ -16221,6 +16309,9 @@
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"forceDelete": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isDir": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@ -16830,6 +16921,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.RecycleBinReduce": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"from",
|
||||
"rName"
|
||||
],
|
||||
"properties": {
|
||||
"from": {
|
||||
"type": "string"
|
||||
},
|
||||
"rName": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.RuntimeCreate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -2778,6 +2778,8 @@ definitions:
|
||||
type: object
|
||||
request.FileDelete:
|
||||
properties:
|
||||
forceDelete:
|
||||
type: boolean
|
||||
isDir:
|
||||
type: boolean
|
||||
path:
|
||||
@ -3190,6 +3192,16 @@ definitions:
|
||||
required:
|
||||
- PID
|
||||
type: object
|
||||
request.RecycleBinReduce:
|
||||
properties:
|
||||
from:
|
||||
type: string
|
||||
rName:
|
||||
type: string
|
||||
required:
|
||||
- from
|
||||
- rName
|
||||
type: object
|
||||
request.RuntimeCreate:
|
||||
properties:
|
||||
appDetailId:
|
||||
@ -9147,6 +9159,59 @@ paths:
|
||||
formatEN: 结束进程 [PID]
|
||||
formatZH: 结束进程 [PID]
|
||||
paramKeys: []
|
||||
/recycle/clear:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 清空回收站文件
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Clear RecycleBin files
|
||||
tags:
|
||||
- RecycleBin
|
||||
/recycle/reduce:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 还原回收站文件
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.RecycleBinReduce'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Reduce RecycleBin files
|
||||
tags:
|
||||
- RecycleBin
|
||||
/recycle/search:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 获取回收站文件列表
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.PageInfo'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: List RecycleBin files
|
||||
tags:
|
||||
- RecycleBin
|
||||
/runtimes:
|
||||
post:
|
||||
consumes:
|
||||
|
@ -65,6 +65,7 @@ export namespace File {
|
||||
export interface FileDelete {
|
||||
path: string;
|
||||
isDir: boolean;
|
||||
forceDelete: boolean;
|
||||
}
|
||||
|
||||
export interface FileBatchDelete {
|
||||
@ -145,4 +146,20 @@ export namespace File {
|
||||
export interface FilePath {
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface RecycleBin {
|
||||
sourcePath: string;
|
||||
name: string;
|
||||
isDir: boolean;
|
||||
size: number;
|
||||
deleteTime: string;
|
||||
rName: string;
|
||||
from: string;
|
||||
}
|
||||
|
||||
export interface RecycleBinReduce {
|
||||
rName: string;
|
||||
from: string;
|
||||
name: string;
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import http from '@/api';
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
import { ResPage } from '../interface';
|
||||
import { TimeoutEnum } from '@/enums/http-enum';
|
||||
import { ReqPage } from '@/api/interface';
|
||||
|
||||
export const GetFilesList = (params: File.ReqFile) => {
|
||||
return http.post<File.File>('files/search', params, TimeoutEnum.T_5M);
|
||||
@ -87,3 +88,15 @@ export const ComputeDirSize = (params: File.DirSizeReq) => {
|
||||
export const FileKeys = () => {
|
||||
return http.get<File.FileKeys>('files/keys');
|
||||
};
|
||||
|
||||
export const getRecycleList = (params: ReqPage) => {
|
||||
return http.post<ResPage<File.RecycleBin>>('files/recycle/search', params);
|
||||
};
|
||||
|
||||
export const reduceFile = (params: File.RecycleBinReduce) => {
|
||||
return http.post<any>('files/recycle/reduce', params);
|
||||
};
|
||||
|
||||
export const clearRecycle = () => {
|
||||
return http.post<any>('files/recycle/clear');
|
||||
};
|
||||
|
@ -957,8 +957,18 @@ const message = {
|
||||
fileUploadStart: 'Uploading [{0}]....',
|
||||
currentSelect: 'Current Select: ',
|
||||
unsupportType: 'Unsupported file type',
|
||||
deleteHelper: 'The following resources will be deleted, this operation cannot be rolled back, continue? ',
|
||||
deleteHelper:
|
||||
'Are you sure you want to delete the following files? By default, it will enter the recycle bin after deletion',
|
||||
fileHeper: 'Note: 1. Sorting is not supported after searching 2. Folders are not supported by size sorting',
|
||||
forceDeleteHelper: 'Permanently delete the file (without entering the recycle bin, delete it directly)',
|
||||
recycleBin: 'Recycle bin',
|
||||
sourcePath: 'Original path',
|
||||
deleteTime: 'Delete time',
|
||||
reduce: 'Reduction',
|
||||
reduceHelper:
|
||||
'Restore the file to its original path. If a file or directory with the same name exists at the original address of the file, it will be overwritten. Do you want to continue?',
|
||||
clearRecycleBin: 'Clear the recycle bin',
|
||||
clearRecycleBinHelper: 'Do you want to clear the recycle bin? ',
|
||||
},
|
||||
ssh: {
|
||||
autoStart: 'Auto Start',
|
||||
|
@ -920,8 +920,16 @@ const message = {
|
||||
fileUploadStart: '正在上傳【{0}】....',
|
||||
currentSelect: '當前選中: ',
|
||||
unsupportType: '不支持的文件類型',
|
||||
deleteHelper: '以下資源將被刪除,此操作不可回滾,是否繼續?',
|
||||
deleteHelper: '確定刪除所選檔案? 預設刪除之後將進入回收站?',
|
||||
fileHeper: '注意:1.搜尋之後不支援排序 2.依大小排序不支援資料夾',
|
||||
forceDeleteHelper: '永久刪除檔案(不進入回收站,直接刪除)',
|
||||
recycleBin: '回收站',
|
||||
sourcePath: '原路徑',
|
||||
deleteTime: '刪除時間',
|
||||
reduce: '還原',
|
||||
reduceHelper: '恢復檔案到原路徑,如果檔案原始位址,存在同名檔案或目錄,將會覆蓋,是否繼續? ',
|
||||
clearRecycleBin: '清空回收站',
|
||||
clearRecycleBinHelper: '是否清空回收站? ',
|
||||
},
|
||||
ssh: {
|
||||
autoStart: '開機自啟',
|
||||
|
@ -921,8 +921,16 @@ const message = {
|
||||
fileUploadStart: '正在上传【{0}】....',
|
||||
currentSelect: '当前选中: ',
|
||||
unsupportType: '不支持的文件类型',
|
||||
deleteHelper: '以下资源将被删除,此操作不可回滚,是否继续?',
|
||||
deleteHelper: '确定删除所选文件? 默认删除之后将进入回收站',
|
||||
fileHeper: '注意:1.搜索之后不支持排序 2.按大小排序不支持文件夹',
|
||||
forceDeleteHelper: '永久删除文件(不进入回收站,直接删除)',
|
||||
recycleBin: '回收站',
|
||||
sourcePath: '原路径',
|
||||
deleteTime: '删除时间',
|
||||
reduce: '还原',
|
||||
reduceHelper: '恢复文件到原路径,如果文件原地址,存在同名文件或目录,将会覆盖,是否继续?',
|
||||
clearRecycleBin: '清空回收站',
|
||||
clearRecycleBinHelper: '是否清空回收站?',
|
||||
},
|
||||
ssh: {
|
||||
autoStart: '开机自启',
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-drawer v-model="open" :before-close="handleClose" :close-on-click-modal="false" width="50%">
|
||||
<el-drawer v-model="open" :before-close="handleClose" :close-on-click-modal="false" size="50%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('file.setRole')" :resource="name" :back="handleClose" />
|
||||
</template>
|
||||
@ -32,7 +32,7 @@ import { MsgSuccess } from '@/utils/message';
|
||||
|
||||
const open = ref(false);
|
||||
const form = ref<File.FileCreate>({ path: '', isDir: false, mode: 0o755 });
|
||||
const loading = ref<Boolean>(false);
|
||||
const loading = ref(false);
|
||||
const mode = ref('0755');
|
||||
const name = ref('');
|
||||
|
||||
|
@ -18,6 +18,9 @@
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<el-checkbox v-model="forceDelete">{{ $t('file.forceDeleteHelper') }}</el-checkbox>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
@ -45,6 +48,7 @@ const open = ref(false);
|
||||
const files = ref();
|
||||
const loading = ref(false);
|
||||
const em = defineEmits(['close']);
|
||||
const forceDelete = ref(false);
|
||||
|
||||
const acceptParams = (props: File.File[]) => {
|
||||
files.value = props;
|
||||
@ -54,7 +58,7 @@ const acceptParams = (props: File.File[]) => {
|
||||
const onConfirm = () => {
|
||||
const pros = [];
|
||||
for (const s of files.value) {
|
||||
pros.push(DeleteFile({ path: s['path'], isDir: s['isDir'] }));
|
||||
pros.push(DeleteFile({ path: s['path'], isDir: s['isDir'], forceDelete: forceDelete.value }));
|
||||
}
|
||||
loading.value = true;
|
||||
Promise.all(pros)
|
||||
|
@ -48,67 +48,78 @@
|
||||
</el-alert>
|
||||
</template>
|
||||
<template #toolbar>
|
||||
<el-dropdown @command="handleCreate">
|
||||
<el-button type="primary">
|
||||
{{ $t('commons.button.create') }}
|
||||
<el-icon><arrow-down /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="dir">
|
||||
<svg-icon iconName="p-file-folder"></svg-icon>
|
||||
{{ $t('file.dir') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="file">
|
||||
<svg-icon iconName="p-file-normal"></svg-icon>
|
||||
{{ $t('file.file') }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-button-group style="margin-left: 10px">
|
||||
<el-button plain @click="openUpload">{{ $t('file.upload') }}</el-button>
|
||||
<el-button plain @click="openWget">{{ $t('file.remoteFile') }}</el-button>
|
||||
<el-button plain @click="openMove('copy')" :disabled="selects.length === 0">
|
||||
{{ $t('file.copy') }}
|
||||
</el-button>
|
||||
<el-button plain @click="openMove('cut')" :disabled="selects.length === 0">
|
||||
{{ $t('file.move') }}
|
||||
</el-button>
|
||||
<el-button plain @click="openCompress(selects)" :disabled="selects.length === 0">
|
||||
{{ $t('file.compress') }}
|
||||
</el-button>
|
||||
<el-button plain @click="batchDelFiles" :disabled="selects.length === 0">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
<el-button-group class="copy-button" v-if="moveOpen">
|
||||
<el-tooltip class="box-item" effect="dark" :content="$t('file.paste')" placement="bottom">
|
||||
<el-button plain @click="openPaste">{{ $t('file.paste') }}</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="box-item" effect="dark" :content="$t('file.cancel')" placement="bottom">
|
||||
<el-button plain class="close" @click="closeMove">
|
||||
<el-icon class="close-icon"><Close /></el-icon>
|
||||
<div class="btn-container">
|
||||
<div class="left-section">
|
||||
<el-dropdown @command="handleCreate">
|
||||
<el-button type="primary">
|
||||
{{ $t('commons.button.create') }}
|
||||
<el-icon><arrow-down /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="dir">
|
||||
<svg-icon iconName="p-file-folder"></svg-icon>
|
||||
{{ $t('file.dir') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="file">
|
||||
<svg-icon iconName="p-file-normal"></svg-icon>
|
||||
{{ $t('file.file') }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-button-group>
|
||||
<el-button plain @click="openUpload">{{ $t('file.upload') }}</el-button>
|
||||
<el-button plain @click="openWget">{{ $t('file.remoteFile') }}</el-button>
|
||||
<el-button plain @click="openMove('copy')" :disabled="selects.length === 0">
|
||||
{{ $t('file.copy') }}
|
||||
</el-button>
|
||||
<el-button plain @click="openMove('cut')" :disabled="selects.length === 0">
|
||||
{{ $t('file.move') }}
|
||||
</el-button>
|
||||
<el-button plain @click="openCompress(selects)" :disabled="selects.length === 0">
|
||||
{{ $t('file.compress') }}
|
||||
</el-button>
|
||||
<el-button plain @click="batchDelFiles" :disabled="selects.length === 0">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
|
||||
<el-button-group class="copy-button" v-if="moveOpen">
|
||||
<el-tooltip class="box-item" effect="dark" :content="$t('file.paste')" placement="bottom">
|
||||
<el-button plain @click="openPaste">{{ $t('file.paste') }}</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="box-item" effect="dark" :content="$t('file.cancel')" placement="bottom">
|
||||
<el-button plain class="close" @click="closeMove">
|
||||
<el-icon class="close-icon"><Close /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</el-button-group>
|
||||
</div>
|
||||
|
||||
<div class="right-section">
|
||||
<el-button class="btn" @click="openRecycleBin" :icon="Delete">
|
||||
{{ $t('file.recycleBin') }}
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</el-button-group>
|
||||
<div class="search search-button">
|
||||
<el-input
|
||||
v-model="req.search"
|
||||
clearable
|
||||
@clear="search()"
|
||||
@keydown.enter="search()"
|
||||
:placeholder="$t('file.search')"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-checkbox v-model="req.containSub">
|
||||
{{ $t('file.sub') }}
|
||||
</el-checkbox>
|
||||
</template>
|
||||
<template #append>
|
||||
<el-button icon="Search" @click="search" round />
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="search-button">
|
||||
<el-input
|
||||
v-model="req.search"
|
||||
clearable
|
||||
@clear="search()"
|
||||
@keydown.enter="search()"
|
||||
:placeholder="$t('file.search')"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-checkbox v-model="req.containSub">
|
||||
{{ $t('file.sub') }}
|
||||
</el-checkbox>
|
||||
</template>
|
||||
<template #append>
|
||||
<el-button icon="Search" @click="search" round />
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #main>
|
||||
@ -200,7 +211,8 @@
|
||||
<Process :open="processPage.open" @close="closeProcess" />
|
||||
<Owner ref="chownRef" @close="search"></Owner>
|
||||
<Detail ref="detailRef" />
|
||||
<Delete ref="deleteRef" @close="search" />
|
||||
<DeleteFile ref="deleteRef" @close="search" />
|
||||
<RecycleBin ref="recycleBinRef" @close="search" />
|
||||
</LayoutContent>
|
||||
</div>
|
||||
</template>
|
||||
@ -209,6 +221,7 @@
|
||||
import { nextTick, onMounted, reactive, ref, computed } from '@vue/runtime-core';
|
||||
import { GetFilesList, GetFileContent, ComputeDirSize } from '@/api/modules/files';
|
||||
import { computeSize, dateFormat, downloadFile, getIcon, getRandomStr } from '@/utils/util';
|
||||
import { Delete } from '@element-plus/icons-vue';
|
||||
import { File } from '@/api/interface/file';
|
||||
import i18n from '@/lang';
|
||||
import CreateFile from './create/index.vue';
|
||||
@ -222,10 +235,11 @@ import Wget from './wget/index.vue';
|
||||
import Move from './move/index.vue';
|
||||
import Download from './download/index.vue';
|
||||
import Owner from './chown/index.vue';
|
||||
import Delete from './delete/index.vue';
|
||||
import DeleteFile from './delete/index.vue';
|
||||
import { Mimetypes, Languages } from '@/global/mimetype';
|
||||
import Process from './process/index.vue';
|
||||
import Detail from './detail/index.vue';
|
||||
import RecycleBin from './recycle-bin/index.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { Back, Refresh } from '@element-plus/icons-vue';
|
||||
import { MsgSuccess, MsgWarning } from '@/utils/message';
|
||||
@ -288,6 +302,7 @@ const breadCrumbRef = ref();
|
||||
const chownRef = ref();
|
||||
const moveOpen = ref(false);
|
||||
const deleteRef = ref();
|
||||
const recycleBinRef = ref();
|
||||
|
||||
// editablePath
|
||||
const { searchableStatus, searchablePath, searchableInputRef, searchableInputBlur } = useSearchable(paths);
|
||||
@ -608,6 +623,10 @@ const openDetail = (row: File.File) => {
|
||||
detailRef.value.acceptParams({ path: row.path });
|
||||
};
|
||||
|
||||
const openRecycleBin = () => {
|
||||
recycleBinRef.value.acceptParams();
|
||||
};
|
||||
|
||||
const changeSort = ({ prop, order }) => {
|
||||
req.sortBy = prop;
|
||||
req.sortOrder = order;
|
||||
@ -713,12 +732,6 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.search {
|
||||
display: inline;
|
||||
width: 400px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.copy-button {
|
||||
margin-left: 10px;
|
||||
.close {
|
||||
@ -728,4 +741,25 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.left-section,
|
||||
.right-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.left-section > *:not(:first-child) {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.right-section > *:not(:last-child) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
</style>
|
||||
|
155
frontend/src/views/host/file-management/recycle-bin/index.vue
Normal file
155
frontend/src/views/host/file-management/recycle-bin/index.vue
Normal file
@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<el-drawer v-model="open" :before-close="handleClose" :close-on-click-modal="false" size="50%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('file.recycleBin')" :back="handleClose" />
|
||||
</template>
|
||||
<el-button @click="clear" type="primary" :disabled="data == null || data.length == 0">
|
||||
{{ $t('file.clearRecycleBin') }}
|
||||
</el-button>
|
||||
<ComplexTable
|
||||
:pagination-config="paginationConfig"
|
||||
v-model:selects="selects"
|
||||
:data="data"
|
||||
@search="search"
|
||||
class="mt-5"
|
||||
>
|
||||
<el-table-column type="selection" fix />
|
||||
<el-table-column
|
||||
:label="$t('commons.table.name')"
|
||||
min-width="100"
|
||||
fix
|
||||
show-overflow-tooltip
|
||||
prop="name"
|
||||
></el-table-column>
|
||||
<el-table-column :label="$t('file.sourcePath')" show-overflow-tooltip prop="sourcePath"></el-table-column>
|
||||
<el-table-column :label="$t('file.size')" prop="size" max-width="50">
|
||||
<template #default="{ row }">
|
||||
{{ getFileSize(row.size) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('file.deleteTime')"
|
||||
prop="deleteTime"
|
||||
:formatter="dateFormat"
|
||||
show-overflow-tooltip
|
||||
></el-table-column>
|
||||
<fu-table-operations :buttons="buttons" :label="$t('commons.table.operate')" fix />
|
||||
</ComplexTable>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DeleteFile, clearRecycle, getRecycleList, reduceFile } from '@/api/modules/files';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { dateFormat, computeSize } from '@/utils/util';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
|
||||
const open = ref(false);
|
||||
const req = reactive({
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
});
|
||||
const data = ref([]);
|
||||
const em = defineEmits(['close']);
|
||||
const selects = ref([]);
|
||||
const loading = ref(false);
|
||||
const files = ref([]);
|
||||
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'recycle-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 100,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
const handleClose = () => {
|
||||
open.value = false;
|
||||
em('close', false);
|
||||
};
|
||||
|
||||
const getFileSize = (size: number) => {
|
||||
return computeSize(size);
|
||||
};
|
||||
|
||||
const acceptParams = () => {
|
||||
search();
|
||||
};
|
||||
|
||||
const search = async () => {
|
||||
try {
|
||||
const res = await getRecycleList(req);
|
||||
data.value = res.data.items;
|
||||
paginationConfig.total = res.data.total;
|
||||
open.value = true;
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
const singleDel = (row: any) => {
|
||||
ElMessageBox.confirm(i18n.global.t('commons.msg.delete'), i18n.global.t('commons.button.delete'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
}).then(async () => {
|
||||
files.value = [];
|
||||
files.value.push(row);
|
||||
deleteFile();
|
||||
});
|
||||
};
|
||||
|
||||
const deleteFile = async () => {
|
||||
const pros = [];
|
||||
for (const s of files.value) {
|
||||
pros.push(DeleteFile({ path: s.from + '/' + s.rName, isDir: s.isDir, forceDelete: true }));
|
||||
}
|
||||
loading.value = true;
|
||||
Promise.all(pros)
|
||||
.then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
|
||||
search();
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const rdFile = async (row: any) => {
|
||||
ElMessageBox.confirm(i18n.global.t('file.reduceHelper'), i18n.global.t('file.reduce'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
}).then(async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
await reduceFile({ from: row.from, rName: row.rName, name: row.name });
|
||||
loading.value = false;
|
||||
search();
|
||||
} catch (error) {}
|
||||
});
|
||||
};
|
||||
|
||||
const clear = async () => {
|
||||
ElMessageBox.confirm(i18n.global.t('commons.msg.delete'), i18n.global.t('file.clearRecycleBinHelper'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
}).then(async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
await clearRecycle();
|
||||
loading.value = false;
|
||||
search();
|
||||
} catch (error) {}
|
||||
});
|
||||
};
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('file.reduce'),
|
||||
click: rdFile,
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
click: singleDel,
|
||||
},
|
||||
];
|
||||
|
||||
defineExpose({ acceptParams });
|
||||
</script>
|
Loading…
Reference in New Issue
Block a user