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

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

View File

@ -286,26 +286,14 @@ public class SysFileService : IDynamicApiController, ITransient
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
[DisplayName("获取文件路径")] [DisplayName("获取文件路径")]
public async Task<dynamic> GetFolder() public async Task<List<TreeNode>> GetFolder()
{ {
var files = await _sysFileRep.AsQueryable().ToListAsync(); var files = await _sysFileRep.AsQueryable().ToListAsync();
var folders = files.GroupBy(u => u.FilePath).Select(u => u.First().FilePath).ToList(); var folders = files.GroupBy(u => u.FilePath).Select(u => u.First().FilePath).ToList();
var result = folders
.GroupBy(u => u.Split('/').First()) var pathTreeBuilder = new PathTreeBuilder();
.Select((u, index) => new var tree = pathTreeBuilder.BuildTree(folders);
{ return tree.Children;
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;
} }
/// <summary> /// <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", "name": "admin.net.pro",
"type": "module", "type": "module",
"version": "2.4.33", "version": "2.4.33",
"lastBuildTime": "2024.11.08", "lastBuildTime": "2024.11.10",
"description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架", "description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架",
"author": "zuohuaijun", "author": "zuohuaijun",
"license": "MIT", "license": "MIT",
@ -42,7 +42,7 @@
"jsplumb": "^2.15.6", "jsplumb": "^2.15.6",
"jwchat": "^2.0.3", "jwchat": "^2.0.3",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"md-editor-v3": "^4.21.1", "md-editor-v3": "^4.21.3",
"mitt": "^3.0.1", "mitt": "^3.0.1",
"monaco-editor": "^0.52.0", "monaco-editor": "^0.52.0",
"mqtt": "^5.10.1", "mqtt": "^5.10.1",
@ -71,7 +71,7 @@
"vue-router": "^4.4.5", "vue-router": "^4.4.5",
"vue-signature-pad": "^3.0.2", "vue-signature-pad": "^3.0.2",
"vue3-tree-org": "^4.2.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": "^4.7.59",
"vxe-table-plugin-element": "^4.0.4", "vxe-table-plugin-element": "^4.0.4",
"vxe-table-plugin-export-xlsx": "^4.0.7", "vxe-table-plugin-export-xlsx": "^4.0.7",

View File

@ -1,16 +1,16 @@
<template lang=""> <template>
<el-card class="box-card" shadow="hover" body-style="height:100%; overflow:auto;padding:5px;"> <el-card class="box-card" shadow="hover" body-style="height:100%;overflow:auto;padding:5px;width:100%;">
<template #header> <template #header>
<div class="card-header"> <div class="card-header">
<div class="tree-h-flex"> <div class="tree-h-flex">
<div class="tree-h-left"> <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>
<div class="tree-h-right"> <div class="tree-h-right">
<el-dropdown @command="handleCommand"> <el-dropdown @command="handleCommand">
<el-button style="margin-left: 8px; width: 34px"> <el-button style="margin-left: 8px; width: 34px">
<el-icon class="el-icon--center"> <el-icon class="el-icon--center">
<MoreFilled /> <more-filled />
</el-icon> </el-icon>
</el-button> </el-button>
<template #dropdown> <template #dropdown>
@ -23,24 +23,32 @@
</template> </template>
</el-dropdown> </el-dropdown>
</div> </div>
<el-checkbox v-if="!props.checkStrictly && state.isShowCheckbox" v-model="state.strictly" label="联动" style="margin-left: 8px" border />
</div> </div>
</div> </div>
</template> </template>
<div style="margin-bottom: 45px" v-loading="state.loading"> <div style="margin-bottom: 45px" v-loading="state.loading">
<el-tree <el-scrollbar>
ref="treeRef" <el-tree
class="filter-tree" ref="treeRef"
:data="state.folderData" class="filter-tree"
node-key="id" :data="state.folderData"
:props="{ children: 'children', label: 'name' }" node-key="id"
:filter-node-method="filterNode" :props="{ children: 'children', label: 'name' }"
@node-click="nodeClick" :filter-node-method="filterNode"
highlight-current @node-click="nodeClick"
check-strictly :show-checkbox="state.isShowCheckbox"
accordion default-expand-all
lazy highlight-current
:load="loadNode" :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> </div>
</el-card> </el-card>
</template> </template>
@ -52,38 +60,38 @@ import { Search, MoreFilled } from '@element-plus/icons-vue';
import { getAPI } from '/@/utils/axios-utils'; import { getAPI } from '/@/utils/axios-utils';
import { SysFileApi } from '/@/api-services/api'; 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 filterText = ref('');
const treeRef = ref<InstanceType<typeof ElTree>>(); const treeRef = ref<InstanceType<typeof ElTree>>();
const state = reactive({ const state = reactive({
loading: false, loading: false,
folderData: [] as any, folderData: [] as any,
isShowCheckbox: false,
strictly: false,
}); });
onMounted(() => { //
initTreeData(); onMounted(async () => {
await fetchTreeData();
}); });
//
watch(filterText, (val) => { watch(filterText, (val) => {
treeRef.value!.filter(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(); var res = await getAPI(SysFileApi).apiSysFileFolderGet();
state.folderData = res.data.result ?? []; state.folderData = res.data.result ?? [];
state.loading = false; if (showLoading) state.loading = false;
}; return res.data.result ?? [];
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([]);
}; };
// //
@ -91,11 +99,13 @@ const getCheckedKeys = () => {
return treeRef.value!.getCheckedKeys(); return treeRef.value!.getCheckedKeys();
}; };
//
const filterNode = (value: string, data: any) => { const filterNode = (value: string, data: any) => {
if (!value) return true; if (!value) return true;
return data.name.includes(value); return data.name.includes(value);
}; };
//
const handleCommand = async (command: string | number | object) => { const handleCommand = async (command: string | number | object) => {
if ('expandAll' == command) { if ('expandAll' == command) {
for (let i = 0; i < treeRef.value!.store._getAllNodes().length; i++) { 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; treeRef.value!.store._getAllNodes()[i].expanded = false;
} }
} else if ('refresh' == command) { } else if ('refresh' == command) {
initTreeData(); fetchTreeData();
} else if ('rootNode' == command) { } else if ('rootNode' == command) {
treeRef.value?.setCurrentKey();
emits('node-click', { id: 0, name: '' }); emits('node-click', { id: 0, name: '' });
} }
}; };
// //
const emits = defineEmits(['node-click']); const emits = defineEmits(['node-click']);
const nodeClick = (node: any) => { const nodeClick = (node: any) => {
emits('node-click', { id: node.id, name: node.name }); 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> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

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