😎优化文件树生成及页面调整

This commit is contained in:
zuohuaijun 2024-11-10 02:11:20 +08:00
parent 8ccd73e268
commit bf1e57bccd
6 changed files with 120 additions and 60 deletions

View File

@ -30,7 +30,7 @@
<PackageReference Include="Magicodes.IE.Word" Version="2.7.5.2" />
<PackageReference Include="MailKit" Version="4.8.0" />
<PackageReference Include="Microsoft.PowerShell.SDK" Version="7.4.6" />
<PackageReference Include="MiniExcel" Version="1.34.2" />
<PackageReference Include="MiniExcel" Version="1.35.0" />
<PackageReference Include="MiniWord" Version="0.8.0" />
<PackageReference Include="MQTTnet" Version="4.3.7.1207" />
<PackageReference Include="MySqlBackup.NET.MySqlConnector" Version="2.3.8" />
@ -45,7 +45,7 @@
<PackageReference Include="SqlSugarCore" Version="5.1.4.170" />
<PackageReference Include="SSH.NET" Version="2024.1.0" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.4.8" />
<PackageReference Include="TencentCloudSDK.Sms" Version="3.0.1119" />
<PackageReference Include="TencentCloudSDK.Sms" Version="3.0.1120" />
<PackageReference Include="UAParser" Version="3.1.47" />
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
</ItemGroup>

View File

@ -286,26 +286,14 @@ public class SysFileService : IDynamicApiController, ITransient
/// </summary>
/// <returns></returns>
[DisplayName("获取文件路径")]
public async Task<dynamic> GetFolder()
public async Task<List<TreeNode>> GetFolder()
{
var files = await _sysFileRep.AsQueryable().ToListAsync();
var folders = files.GroupBy(u => u.FilePath).Select(u => u.First().FilePath).ToList();
var result = folders
.GroupBy(u => u.Split('/').First())
.Select((u, index) => new
{
Id = index + 1, // 组的索引加1作为Id
Pid = 0,
Name = u.Key,
Children = u.Select((item, subIndex) => new
{
Id = (index + 1 * 100) + subIndex + 1, // 子项的索引加1作为Id
Pid = index + 1,
Name = item.Split('/').Last(),
Children = new List<string>()
}).ToList()
}).ToList();
return result;
var pathTreeBuilder = new PathTreeBuilder();
var tree = pathTreeBuilder.BuildTree(folders);
return tree.Children;
}
/// <summary>

View File

@ -0,0 +1,57 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 树形节点
/// </summary>
public class TreeNode
{
public int Id { get; set; }
public int Pid { get; set; }
public string Name { get; set; }
public List<TreeNode> Children { get; set; } = new();
}
/// <summary>
/// 根据路径数组生成树结构
/// </summary>
public class PathTreeBuilder
{
private int _nextId = 1;
public TreeNode BuildTree(List<string> paths)
{
var root = new TreeNode { Id = 1, Pid = 0, Name = "文件目录" }; // 根节点
var dict = new Dictionary<string, TreeNode>();
foreach (var path in paths)
{
var parts = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
TreeNode currentNode = root;
foreach (var part in parts)
{
var key = currentNode.Id + "_" + part; // 生成唯一键
if (!dict.ContainsKey(key))
{
var newNode = new TreeNode
{
Id = _nextId++,
Pid = currentNode.Id,
Name = part
};
currentNode.Children.Add(newNode);
dict[key] = newNode;
}
currentNode = dict[key]; // 更新当前节点
}
}
return root;
}
}

View File

@ -2,7 +2,7 @@
"name": "admin.net.pro",
"type": "module",
"version": "2.4.33",
"lastBuildTime": "2024.11.08",
"lastBuildTime": "2024.11.10",
"description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架",
"author": "zuohuaijun",
"license": "MIT",
@ -42,7 +42,7 @@
"jsplumb": "^2.15.6",
"jwchat": "^2.0.3",
"lodash-es": "^4.17.21",
"md-editor-v3": "^4.21.1",
"md-editor-v3": "^4.21.3",
"mitt": "^3.0.1",
"monaco-editor": "^0.52.0",
"mqtt": "^5.10.1",
@ -71,7 +71,7 @@
"vue-router": "^4.4.5",
"vue-signature-pad": "^3.0.2",
"vue3-tree-org": "^4.2.2",
"vxe-pc-ui": "^4.2.46",
"vxe-pc-ui": "^4.2.49",
"vxe-table": "^4.7.59",
"vxe-table-plugin-element": "^4.0.4",
"vxe-table-plugin-export-xlsx": "^4.0.7",

View File

@ -1,16 +1,16 @@
<template lang="">
<el-card class="box-card" shadow="hover" body-style="height:100%; overflow:auto;padding:5px;">
<template>
<el-card class="box-card" shadow="hover" body-style="height:100%;overflow:auto;padding:5px;width:100%;">
<template #header>
<div class="card-header">
<div class="tree-h-flex">
<div class="tree-h-left">
<el-input :prefix-icon="Search" v-model="filterText" placeholder="文件夹名称" />
<el-input :prefix-icon="Search" v-model.lazy="filterText" clearable placeholder="机构名称" />
</div>
<div class="tree-h-right">
<el-dropdown @command="handleCommand">
<el-button style="margin-left: 8px; width: 34px">
<el-icon class="el-icon--center">
<MoreFilled />
<more-filled />
</el-icon>
</el-button>
<template #dropdown>
@ -23,24 +23,32 @@
</template>
</el-dropdown>
</div>
<el-checkbox v-if="!props.checkStrictly && state.isShowCheckbox" v-model="state.strictly" label="联动" style="margin-left: 8px" border />
</div>
</div>
</template>
<div style="margin-bottom: 45px" v-loading="state.loading">
<el-tree
ref="treeRef"
class="filter-tree"
:data="state.folderData"
node-key="id"
:props="{ children: 'children', label: 'name' }"
:filter-node-method="filterNode"
@node-click="nodeClick"
highlight-current
check-strictly
accordion
lazy
:load="loadNode"
/>
<el-scrollbar>
<el-tree
ref="treeRef"
class="filter-tree"
:data="state.folderData"
node-key="id"
:props="{ children: 'children', label: 'name' }"
:filter-node-method="filterNode"
@node-click="nodeClick"
:show-checkbox="state.isShowCheckbox"
default-expand-all
highlight-current
:check-strictly="!state.strictly"
>
<template #default="{ node }">
<el-icon v-if="node.level < 4" size="16" style="margin-right: 3px; display: inline; vertical-align: middle"><ele-FolderOpened /></el-icon>
<el-icon v-else size="16" style="margin-right: 3px; display: inline; vertical-align: middle"><ele-Folder /></el-icon>
{{ node.label }}
</template>
</el-tree>
</el-scrollbar>
</div>
</el-card>
</template>
@ -52,38 +60,38 @@ import { Search, MoreFilled } from '@element-plus/icons-vue';
import { getAPI } from '/@/utils/axios-utils';
import { SysFileApi } from '/@/api-services/api';
import { TreeKey } from 'element-plus/es/components/tree/src/tree.type';
const props = defineProps({
checkStrictly: { type: Boolean, defaul: true },
});
const filterText = ref('');
const treeRef = ref<InstanceType<typeof ElTree>>();
const state = reactive({
loading: false,
folderData: [] as any,
isShowCheckbox: false,
strictly: false,
});
onMounted(() => {
initTreeData();
//
onMounted(async () => {
await fetchTreeData();
});
//
watch(filterText, (val) => {
treeRef.value!.filter(val);
});
const initTreeData = async () => {
state.loading = true;
//
const fetchTreeData = async (showLoading: boolean = true) => {
if (showLoading) state.loading = true;
var res = await getAPI(SysFileApi).apiSysFileFolderGet();
state.folderData = res.data.result ?? [];
state.loading = false;
};
const loadNode = async (node: any, resolve: any) => {
console.log(node);
if (node.data == undefined || Array.isArray(node.data)) return;
state.loading = true;
var data = state.folderData.find((u) => u.id == node.data.id);
state.loading = false;
console.log(data);
if (data) resolve(data.children);
else resolve([]);
if (showLoading) state.loading = false;
return res.data.result ?? [];
};
//
@ -91,11 +99,13 @@ const getCheckedKeys = () => {
return treeRef.value!.getCheckedKeys();
};
//
const filterNode = (value: string, data: any) => {
if (!value) return true;
return data.name.includes(value);
};
//
const handleCommand = async (command: string | number | object) => {
if ('expandAll' == command) {
for (let i = 0; i < treeRef.value!.store._getAllNodes().length; i++) {
@ -106,21 +116,26 @@ const handleCommand = async (command: string | number | object) => {
treeRef.value!.store._getAllNodes()[i].expanded = false;
}
} else if ('refresh' == command) {
initTreeData();
fetchTreeData();
} else if ('rootNode' == command) {
treeRef.value?.setCurrentKey();
emits('node-click', { id: 0, name: '' });
}
};
//
const emits = defineEmits(['node-click']);
const nodeClick = (node: any) => {
emits('node-click', { id: node.id, name: node.name });
};
//
const setCurrentKey = (key?: TreeKey | undefined, shouldAutoExpandParent?: boolean | undefined) => {
treeRef.value?.setCurrentKey(key, shouldAutoExpandParent);
};
//
defineExpose({ initTreeData, getCheckedKeys });
defineExpose({ fetchTreeData, getCheckedKeys, setCurrentKey });
</script>
<style lang="scss" scoped>

View File

@ -1,7 +1,7 @@
<template>
<div class="sys-file-container">
<splitpanes class="default-theme">
<pane size="20" style="display: flex">
<pane size="15" style="display: flex">
<FolderTree ref="folderTreeRef" @node-click="handleNodeChange" />
</pane>