mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-19 08:19:15 +08:00
feat: 文件列表增加排序功能 (#2541)
Refs https://github.com/1Panel-dev/1Panel/issues/2343 Refs https://github.com/1Panel-dev/1Panel/issues/1698 Refs https://github.com/1Panel-dev/1Panel/issues/1032
This commit is contained in:
parent
51ca89daba
commit
8efe0c8bed
@ -24,6 +24,12 @@ func Exec(cmdStr string) (string, error) {
|
||||
return "", buserr.New(constant.ErrCmdTimeout)
|
||||
}
|
||||
if err != nil {
|
||||
return handleErr(stdout, stderr, err)
|
||||
}
|
||||
return stdout.String(), nil
|
||||
}
|
||||
|
||||
func handleErr(stdout, stderr bytes.Buffer, err error) (string, error) {
|
||||
errMsg := ""
|
||||
if len(stderr.String()) != 0 {
|
||||
errMsg = fmt.Sprintf("stderr: %s", stderr.String())
|
||||
@ -37,8 +43,6 @@ func Exec(cmdStr string) (string, error) {
|
||||
}
|
||||
return errMsg, err
|
||||
}
|
||||
return stdout.String(), nil
|
||||
}
|
||||
|
||||
func ExecWithTimeOut(cmdStr string, timeout time.Duration) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
@ -52,18 +56,7 @@ func ExecWithTimeOut(cmdStr string, timeout time.Duration) (string, error) {
|
||||
return "", buserr.New(constant.ErrCmdTimeout)
|
||||
}
|
||||
if err != nil {
|
||||
errMsg := ""
|
||||
if len(stderr.String()) != 0 {
|
||||
errMsg = fmt.Sprintf("stderr: %s", stderr.String())
|
||||
}
|
||||
if len(stdout.String()) != 0 {
|
||||
if len(errMsg) != 0 {
|
||||
errMsg = fmt.Sprintf("%s; stdout: %s", errMsg, stdout.String())
|
||||
} else {
|
||||
errMsg = fmt.Sprintf("stdout: %s", stdout.String())
|
||||
}
|
||||
}
|
||||
return errMsg, err
|
||||
return handleErr(stdout, stderr, err)
|
||||
}
|
||||
return stdout.String(), nil
|
||||
}
|
||||
@ -114,18 +107,7 @@ func Execf(cmdStr string, a ...interface{}) (string, error) {
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
errMsg := ""
|
||||
if len(stderr.String()) != 0 {
|
||||
errMsg = fmt.Sprintf("stderr: %s", stderr.String())
|
||||
}
|
||||
if len(stdout.String()) != 0 {
|
||||
if len(errMsg) != 0 {
|
||||
errMsg = fmt.Sprintf("%s; stdout: %s", errMsg, stdout.String())
|
||||
} else {
|
||||
errMsg = fmt.Sprintf("stdout: %s", stdout.String())
|
||||
}
|
||||
}
|
||||
return errMsg, err
|
||||
return handleErr(stdout, stderr, err)
|
||||
}
|
||||
return stdout.String(), nil
|
||||
}
|
||||
@ -137,18 +119,7 @@ func ExecWithCheck(name string, a ...string) (string, error) {
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
errMsg := ""
|
||||
if len(stderr.String()) != 0 {
|
||||
errMsg = fmt.Sprintf("stderr: %s", stderr.String())
|
||||
}
|
||||
if len(stdout.String()) != 0 {
|
||||
if len(errMsg) != 0 {
|
||||
errMsg = fmt.Sprintf("%s; stdout: %s", errMsg, stdout.String())
|
||||
} else {
|
||||
errMsg = fmt.Sprintf("stdout: %s", stdout.String())
|
||||
}
|
||||
}
|
||||
return errMsg, err
|
||||
return handleErr(stdout, stderr, err)
|
||||
}
|
||||
return stdout.String(), nil
|
||||
}
|
||||
@ -166,22 +137,20 @@ func ExecScript(scriptPath, workDir string) (string, error) {
|
||||
return "", buserr.New(constant.ErrCmdTimeout)
|
||||
}
|
||||
if err != nil {
|
||||
errMsg := ""
|
||||
if len(stderr.String()) != 0 {
|
||||
errMsg = fmt.Sprintf("stderr: %s", stderr.String())
|
||||
}
|
||||
if len(stdout.String()) != 0 {
|
||||
if len(errMsg) != 0 {
|
||||
errMsg = fmt.Sprintf("%s; stdout: %s", errMsg, stdout.String())
|
||||
} else {
|
||||
errMsg = fmt.Sprintf("stdout: %s", stdout.String())
|
||||
}
|
||||
}
|
||||
return errMsg, err
|
||||
return handleErr(stdout, stderr, err)
|
||||
}
|
||||
return stdout.String(), nil
|
||||
}
|
||||
|
||||
func ExecCmd(cmdStr string) error {
|
||||
cmd := exec.Command("bash", "-c", cmdStr)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CheckIllegal(args ...string) bool {
|
||||
if args == nil {
|
||||
return false
|
||||
|
@ -312,83 +312,14 @@ func (f FileOp) CopyDir(src, dst string) error {
|
||||
return err
|
||||
}
|
||||
dstDir := filepath.Join(dst, srcInfo.Name())
|
||||
if err := f.Fs.MkdirAll(dstDir, srcInfo.Mode()); err != nil {
|
||||
if err = f.Fs.MkdirAll(dstDir, srcInfo.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dir, _ := f.Fs.Open(src)
|
||||
obs, err := dir.Readdir(-1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var errs []error
|
||||
|
||||
for _, obj := range obs {
|
||||
fSrc := filepath.Join(src, obj.Name())
|
||||
if obj.IsDir() {
|
||||
err = f.CopyDir(fSrc, dstDir)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
} else {
|
||||
err = f.CopyFile(fSrc, dstDir)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var errString string
|
||||
for _, err := range errs {
|
||||
errString += err.Error() + "\n"
|
||||
}
|
||||
|
||||
if errString != "" {
|
||||
return errors.New(errString)
|
||||
}
|
||||
|
||||
return nil
|
||||
return cmd.ExecCmd(fmt.Sprintf("cp -rf %s %s", src, dst+"/"))
|
||||
}
|
||||
|
||||
func (f FileOp) CopyFile(src, dst string) error {
|
||||
srcFile, err := f.Fs.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
srcInfo, err := f.Fs.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dstPath := path.Join(dst, srcInfo.Name())
|
||||
if src == dstPath {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = f.Fs.MkdirAll(filepath.Dir(dst), 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dstFile, err := f.Fs.OpenFile(dstPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dstFile.Close()
|
||||
|
||||
if _, err = io.Copy(dstFile, srcFile); err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := f.Fs.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = f.Fs.Chmod(dstFile.Name(), info.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return cmd.ExecCmd(fmt.Sprintf("cp -f %s %s", src, dst+"/"))
|
||||
}
|
||||
|
||||
func (f FileOp) GetDirSize(path string) (float64, error) {
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
@ -52,6 +53,8 @@ type FileOption struct {
|
||||
ShowHidden bool `json:"showHidden"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
SortBy string `json:"sortBy" validate:"oneof=name size modTime"`
|
||||
SortOrder string `json:"sortOrder" validate:"oneof=ascending descending"`
|
||||
}
|
||||
|
||||
type FileSearchInfo struct {
|
||||
@ -90,7 +93,7 @@ func NewFileInfo(op FileOption) (*FileInfo, error) {
|
||||
}
|
||||
if op.Expand {
|
||||
if file.IsDir {
|
||||
if err := file.listChildren(op.Dir, op.ShowHidden, op.ContainSub, op.Search, op.Page, op.PageSize); err != nil {
|
||||
if err := file.listChildren(op); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return file, nil
|
||||
@ -139,7 +142,42 @@ func (f *FileInfo) search(search string, count int) (files []FileSearchInfo, tot
|
||||
return
|
||||
}
|
||||
|
||||
func (f *FileInfo) listChildren(dir, showHidden, containSub bool, search string, page, pageSize int) error {
|
||||
func sortFileList(list []FileSearchInfo, sortBy, sortOrder string) {
|
||||
switch sortBy {
|
||||
case "name":
|
||||
if sortOrder == "ascending" {
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
return list[i].Name() < list[j].Name()
|
||||
})
|
||||
} else {
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
return list[i].Name() > list[j].Name()
|
||||
})
|
||||
}
|
||||
case "size":
|
||||
if sortOrder == "ascending" {
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
return list[i].Size() < list[j].Size()
|
||||
})
|
||||
} else {
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
return list[i].Size() > list[j].Size()
|
||||
})
|
||||
}
|
||||
case "modTime":
|
||||
if sortOrder == "ascending" {
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
return list[i].ModTime().Before(list[j].ModTime())
|
||||
})
|
||||
} else {
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
return list[i].ModTime().After(list[j].ModTime())
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FileInfo) listChildren(option FileOption) error {
|
||||
afs := &afero.Afero{Fs: f.Fs}
|
||||
var (
|
||||
files []FileSearchInfo
|
||||
@ -147,8 +185,8 @@ func (f *FileInfo) listChildren(dir, showHidden, containSub bool, search string,
|
||||
total int
|
||||
)
|
||||
|
||||
if search != "" && containSub {
|
||||
files, total, err = f.search(search, page*pageSize)
|
||||
if option.Search != "" && option.ContainSub {
|
||||
files, total, err = f.search(option.Search, option.Page*option.PageSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -157,34 +195,46 @@ func (f *FileInfo) listChildren(dir, showHidden, containSub bool, search string,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
dirs []FileSearchInfo
|
||||
fileList []FileSearchInfo
|
||||
)
|
||||
for _, file := range dirFiles {
|
||||
files = append(files, FileSearchInfo{
|
||||
info := FileSearchInfo{
|
||||
Path: f.Path,
|
||||
FileInfo: file,
|
||||
})
|
||||
}
|
||||
if file.IsDir() {
|
||||
dirs = append(dirs, info)
|
||||
} else {
|
||||
fileList = append(fileList, info)
|
||||
}
|
||||
}
|
||||
sortFileList(dirs, option.SortBy, option.SortOrder)
|
||||
sortFileList(fileList, option.SortBy, option.SortOrder)
|
||||
files = append(dirs, fileList...)
|
||||
}
|
||||
|
||||
var items []*FileInfo
|
||||
for _, df := range files {
|
||||
if dir && !df.IsDir() {
|
||||
if option.Dir && !df.IsDir() {
|
||||
continue
|
||||
}
|
||||
name := df.Name()
|
||||
fPath := path.Join(df.Path, df.Name())
|
||||
if search != "" {
|
||||
if containSub {
|
||||
if option.Search != "" {
|
||||
if option.ContainSub {
|
||||
fPath = df.Path
|
||||
name = strings.TrimPrefix(strings.TrimPrefix(fPath, f.Path), "/")
|
||||
} else {
|
||||
lowerName := strings.ToLower(name)
|
||||
lowerSearch := strings.ToLower(search)
|
||||
lowerSearch := strings.ToLower(option.Search)
|
||||
if !strings.Contains(lowerName, lowerSearch) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if !showHidden && IsHidden(name) {
|
||||
if !option.ShowHidden && IsHidden(name) {
|
||||
continue
|
||||
}
|
||||
f.ItemTotal++
|
||||
@ -228,11 +278,11 @@ func (f *FileInfo) listChildren(dir, showHidden, containSub bool, search string,
|
||||
}
|
||||
items = append(items, file)
|
||||
}
|
||||
if containSub {
|
||||
if option.ContainSub {
|
||||
f.ItemTotal = total
|
||||
}
|
||||
start := (page - 1) * pageSize
|
||||
end := pageSize + start
|
||||
start := (option.Page - 1) * option.PageSize
|
||||
end := option.PageSize + start
|
||||
var result []*FileInfo
|
||||
if start < 0 || start > f.ItemTotal || end < 0 || start > end {
|
||||
result = items
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "panel",
|
||||
"name": "1Panel-Frontend",
|
||||
"private": true,
|
||||
"version": "1.3",
|
||||
"version": "1.7",
|
||||
"description": "1Panel 前端",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
@ -30,6 +30,8 @@ export namespace File {
|
||||
dir?: boolean;
|
||||
showHidden?: boolean;
|
||||
containSub?: boolean;
|
||||
sortBy?: string;
|
||||
sortOrder?: string;
|
||||
}
|
||||
|
||||
export interface SearchUploadInfo extends ReqPage {
|
||||
|
@ -75,9 +75,15 @@ function sort(prop: string, order: string) {
|
||||
function clearSelects() {
|
||||
tableRef.value.refElTable.clearSelection();
|
||||
}
|
||||
|
||||
function clearSort() {
|
||||
tableRef.value.refElTable.clearSort();
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
clearSelects,
|
||||
sort,
|
||||
clearSort,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
|
@ -952,6 +952,7 @@ const message = {
|
||||
currentSelect: 'Current Select: ',
|
||||
unsupportType: 'Unsupported file type',
|
||||
deleteHelper: 'The following resources will be deleted, this operation cannot be rolled back, continue? ',
|
||||
fileHeper: 'Note: 1. Sorting is not supported after searching 2. Folders are not supported by size sorting',
|
||||
},
|
||||
ssh: {
|
||||
sshAlert:
|
||||
|
@ -916,6 +916,7 @@ const message = {
|
||||
currentSelect: '當前選中: ',
|
||||
unsupportType: '不支持的文件類型',
|
||||
deleteHelper: '以下資源將被刪除,此操作不可回滾,是否繼續?',
|
||||
fileHeper: '注意:1.搜尋之後不支援排序 2.依大小排序不支援資料夾',
|
||||
},
|
||||
ssh: {
|
||||
sshAlert: '列表數據根據登錄時間排序,但請註意,切換時區或其他操作可能導致登錄日誌的時間出現偏差。',
|
||||
|
@ -916,6 +916,7 @@ const message = {
|
||||
currentSelect: '当前选中: ',
|
||||
unsupportType: '不支持的文件类型',
|
||||
deleteHelper: '以下资源将被删除,此操作不可回滚,是否继续?',
|
||||
fileHeper: '注意:1.搜索之后不支持排序 2.按大小排序不支持文件夹',
|
||||
},
|
||||
ssh: {
|
||||
sshAlert: '列表数据根据登录时间排序,但请注意,切换时区或其他操作可能导致登录日志的时间出现偏差。',
|
||||
|
@ -40,6 +40,13 @@
|
||||
/>
|
||||
</div>
|
||||
<LayoutContent :title="$t('file.file')" v-loading="loading">
|
||||
<template #prompt>
|
||||
<el-alert type="info" :closable="false">
|
||||
<template #default>
|
||||
<span><span v-html="$t('file.fileHeper')"></span></span>
|
||||
</template>
|
||||
</el-alert>
|
||||
</template>
|
||||
<template #toolbar>
|
||||
<el-dropdown @command="handleCreate">
|
||||
<el-button type="primary">
|
||||
@ -111,9 +118,17 @@
|
||||
ref="tableRef"
|
||||
:data="data"
|
||||
@search="search"
|
||||
@sort-change="changeSort"
|
||||
>
|
||||
<el-table-column type="selection" width="30" />
|
||||
<el-table-column :label="$t('commons.table.name')" min-width="250" fix show-overflow-tooltip>
|
||||
<el-table-column
|
||||
:label="$t('commons.table.name')"
|
||||
min-width="250"
|
||||
fix
|
||||
show-overflow-tooltip
|
||||
sortable
|
||||
prop="name"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<svg-icon v-if="row.isDir" className="table-icon" iconName="p-file-folder"></svg-icon>
|
||||
<svg-icon v-else className="table-icon" :iconName="getIconName(row.extension)"></svg-icon>
|
||||
@ -140,7 +155,7 @@
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('file.size')" prop="size" max-width="50">
|
||||
<el-table-column :label="$t('file.size')" prop="size" max-width="50" sortable>
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.isDir">
|
||||
<el-button type="primary" link small @click="getDirSize(row)">
|
||||
@ -156,15 +171,16 @@
|
||||
<el-table-column
|
||||
:label="$t('file.updateTime')"
|
||||
prop="modTime"
|
||||
min-width="150"
|
||||
width="180"
|
||||
:formatter="dateFormat"
|
||||
show-overflow-tooltip
|
||||
sortable
|
||||
></el-table-column>
|
||||
<fu-table-operations
|
||||
:ellipsis="mobile ? 0 : 3"
|
||||
:buttons="buttons"
|
||||
:label="$t('commons.table.operate')"
|
||||
:min-width="mobile ? 'auto' : 300"
|
||||
:min-width="mobile ? 'auto' : 200"
|
||||
:fixed="mobile ? false : 'right'"
|
||||
fix
|
||||
/>
|
||||
@ -213,7 +229,6 @@ import Detail from './detail/index.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { Back, Refresh } from '@element-plus/icons-vue';
|
||||
import { MsgSuccess, MsgWarning } from '@/utils/message';
|
||||
// import { ElMessageBox } from 'element-plus';
|
||||
import { useSearchable } from './hooks/searchable';
|
||||
import { ResultData } from '@/api/interface';
|
||||
import { GlobalStore } from '@/store';
|
||||
@ -238,6 +253,8 @@ const initData = () => ({
|
||||
pageSize: 100,
|
||||
search: '',
|
||||
containSub: false,
|
||||
sortBy: 'name',
|
||||
sortOrder: 'ascending',
|
||||
});
|
||||
let req = reactive(initData());
|
||||
let loading = ref(false);
|
||||
@ -288,6 +305,12 @@ const mobile = computed(() => {
|
||||
|
||||
const search = async () => {
|
||||
loading.value = true;
|
||||
if (req.search != '') {
|
||||
req.sortBy = 'name';
|
||||
req.sortOrder = 'ascending';
|
||||
tableRef.value.clearSort();
|
||||
}
|
||||
|
||||
req.page = paginationConfig.currentPage;
|
||||
req.pageSize = paginationConfig.pageSize;
|
||||
await GetFilesList(req)
|
||||
@ -584,6 +607,16 @@ const openDetail = (row: File.File) => {
|
||||
detailRef.value.acceptParams({ path: row.path });
|
||||
};
|
||||
|
||||
const changeSort = ({ prop, order }) => {
|
||||
req.sortBy = prop;
|
||||
req.sortOrder = order;
|
||||
req.search = '';
|
||||
req.page = 1;
|
||||
req.pageSize = paginationConfig.pageSize;
|
||||
req.containSub = false;
|
||||
search();
|
||||
};
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('file.open'),
|
||||
|
Loading…
x
Reference in New Issue
Block a user