Browse Source

feat: 文件夹树形列表实现

pull/18/head^2
zhengkunwang223 2 years ago
parent
commit
8faff6d386
  1. 14
      backend/app/api/v1/file.go
  2. 6
      backend/app/dto/file.go
  3. 21
      backend/app/service/file.go
  4. 1
      backend/router/ro_file.go
  5. 8
      frontend/src/api/index.ts
  6. 7
      frontend/src/api/interface/file.ts
  7. 4
      frontend/src/api/modules/files.ts
  8. 133
      frontend/src/views/file-management/index.vue

14
backend/app/api/v1/file.go

@ -20,3 +20,17 @@ func (b *BaseApi) ListFiles(c *gin.Context) {
}
helper.SuccessWithData(c, files)
}
func (b *BaseApi) GetFileTree(c *gin.Context) {
var req dto.FileOption
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
tree, err := fileService.GetFileTree(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, tree)
}

6
backend/app/dto/file.go

@ -9,3 +9,9 @@ type FileOption struct {
type FileInfo struct {
files.FileInfo
}
type FileTree struct {
Name string `json:"name"`
Path string `json:"path"`
Children []FileTree `json:"children"`
}

21
backend/app/service/file.go

@ -25,3 +25,24 @@ func (f FileService) GetFileList(op dto.FileOption) (dto.FileInfo, error) {
fileInfo.FileInfo = *info
return fileInfo, nil
}
func (f FileService) GetFileTree(op dto.FileOption) ([]dto.FileTree, error) {
var treeArray []dto.FileTree
info, err := files.NewFileInfo(op.FileOption)
if err != nil {
return nil, err
}
node := dto.FileTree{
Name: info.Name,
Path: info.Path,
}
for _, v := range info.Items {
if v.IsDir {
node.Children = append(node.Children, dto.FileTree{
Name: v.Name,
Path: v.Path,
})
}
}
return append(treeArray, node), nil
}

1
backend/router/ro_file.go

@ -16,6 +16,7 @@ func (f *FileRouter) InitFileRouter(Router *gin.RouterGroup) {
baseApi := v1.ApiGroupApp.BaseApi
{
fileRouter.POST("/search", baseApi.ListFiles)
fileRouter.POST("/tree", baseApi.GetFileTree)
}
}

8
frontend/src/api/index.ts

@ -1,5 +1,5 @@
import axios, { AxiosInstance, AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
import { showFullScreenLoading, tryHideFullScreenLoading } from '@/config/service-loading';
// import { showFullScreenLoading, tryHideFullScreenLoading } from '@/config/service-loading';
import { AxiosCanceler } from './helper/axios-cancel';
import { ResultData } from '@/api/interface';
import { ResultEnum } from '@/enums/http-enum';
@ -31,7 +31,7 @@ class RequestHttp {
};
}
axiosCanceler.addPending(config);
config.headers!.noLoading || showFullScreenLoading();
// config.headers!.noLoading || showFullScreenLoading();
return {
...config,
};
@ -48,7 +48,7 @@ class RequestHttp {
globalStore.setCsrfToken(response.headers['x-csrf-token']);
}
axiosCanceler.removePending(config);
tryHideFullScreenLoading();
// tryHideFullScreenLoading();
if (data.code == ResultEnum.OVERDUE || data.code == ResultEnum.FORBIDDEN) {
ElMessage.error(data.msg);
router.replace({
@ -64,7 +64,7 @@ class RequestHttp {
},
async (error: AxiosError) => {
const { response } = error;
tryHideFullScreenLoading();
// tryHideFullScreenLoading();
if (error.message.indexOf('timeout') !== -1) ElMessage.error('请求超时请您稍后重试');
if (response) checkStatus(response.status);
if (!window.navigator.onLine) router.replace({ path: '/500' });

7
frontend/src/api/interface/file.ts

@ -21,4 +21,11 @@ export namespace File {
search?: string;
expand: boolean;
}
export interface FileTree {
name: string;
isDir: Boolean;
path: string;
children?: FileTree[];
}
}

4
frontend/src/api/modules/files.ts

@ -4,3 +4,7 @@ import http from '@/api';
export const GetFilesList = (params: File.ReqFile) => {
return http.post<File.File>('files/search', params);
};
export const GetFilesTree = (params: File.ReqFile) => {
return http.post<File.FileTree[]>('files/tree', params);
};

133
frontend/src/views/file-management/index.vue

@ -1,19 +1,28 @@
<template>
<LayoutContent :header="$t('menu.files')">
<el-row :gutter="20">
<el-col :span="6">
<el-tree :data="dataSource" node-key="id">
<template #default="{ node }">
<el-icon v-if="node.data.isDir && node.expanded"><FolderOpened /></el-icon>
<el-icon v-if="node.data.isDir && !node.expanded"><Folder /></el-icon>
<el-icon v-if="!node.data.isDir"><Document /></el-icon>
<span class="custom-tree-node">
<span>{{ node.data.label }}</span>
</span>
</template>
</el-tree>
<el-col :span="5">
<el-scrollbar height="800px">
<el-tree
:data="fileTree"
:props="defaultProps"
:load="loadNode"
lazy
node-key="id"
v-loading="treeLoading"
>
<template #default="{ node }">
<el-icon v-if="node.expanded"><FolderOpened /></el-icon>
<el-icon v-else><Folder /></el-icon>
<span class="custom-tree-node">
<span>{{ node.data.name }}</span>
</span>
</template>
</el-tree>
</el-scrollbar>
</el-col>
<el-col :span="18">
<el-col :span="19">
<div class="path">
<BreadCrumbs>
<BreadCrumbItem @click="jump(-1)" :right="paths.length == 0">root</BreadCrumbItem>
@ -30,7 +39,7 @@
:pagination-config="paginationConfig"
v-model:selects="selects"
:data="data"
:loading="loading"
v-loading="loading"
>
<template #toolbar>
<el-dropdown split-button type="primary">
@ -89,26 +98,29 @@
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from '@vue/runtime-core';
import { reactive, ref } from '@vue/runtime-core';
import LayoutContent from '@/layout/layout-content.vue';
import ComplexTable from '@/components/complex-table/index.vue';
import i18n from '@/lang';
import { GetFilesList } from '@/api/modules/files';
import { GetFilesList, GetFilesTree } from '@/api/modules/files';
import { dateFromat } from '@/utils/util';
import { File } from '@/api/interface/file';
import BreadCrumbs from '@/components/bread-crumbs/index.vue';
import BreadCrumbItem from '@/components/bread-crumbs/bread-crumbs-item.vue';
interface Tree {
id: number;
label: string;
isDir: Boolean;
children?: Tree[];
}
let data = ref();
let selects = ref<any>([]);
let req = reactive({ path: '/', expand: true });
let loading = ref<boolean>(false);
let treeLoading = ref<boolean>(false);
let paths = ref<string[]>([]);
let fileTree = ref<File.FileTree[]>([]);
const defaultProps = {
children: 'children',
label: 'name',
};
const paginationConfig = reactive({
page: 1,
pageSize: 5,
@ -135,9 +147,9 @@ const buttons = [
},
];
const search = (req: File.ReqFile) => {
const search = async (req: File.ReqFile) => {
loading.value = true;
GetFilesList(req)
await GetFilesList(req)
.then((res) => {
data.value = res.data.items;
req.path = res.data.path;
@ -176,53 +188,36 @@ const jump = async (index: number) => {
search(req);
};
const dataSource = ref<Tree[]>([
{
id: 1,
label: 'var',
isDir: true,
children: [
{
id: 4,
label: 'log',
isDir: true,
children: [
{
id: 9,
isDir: false,
label: 'ko.log',
},
{
id: 10,
isDir: false,
label: 'kubepi.log',
},
],
},
],
},
{
id: 2,
label: 'opt',
isDir: true,
children: [
{
id: 5,
isDir: false,
label: 'app.conf',
},
{
id: 6,
isDir: false,
label: 'test.txt',
},
],
},
]);
const getTree = async (req: File.ReqFile, node: File.FileTree | null) => {
treeLoading.value = true;
await GetFilesTree(req)
.then((res) => {
if (node) {
if (res.data.length > 0) {
node.children = res.data[0].children;
}
} else {
fileTree.value = res.data;
}
search(req);
})
.finally(() => {
treeLoading.value = false;
});
};
onMounted(() => {
search(req);
});
const loadNode = (node: any, resolve: (data: File.FileTree[]) => void) => {
console.log(node.id);
if (!node.hasChildNodes) {
if (node.data.path) {
req.path = node.data.path;
getTree(req, node.data);
} else {
getTree(req, null);
}
}
resolve([]);
};
</script>
<style>

Loading…
Cancel
Save