フロントエンドの開発では、すべてのメニュー データが同じレベルにある場合があり、バックエンドはデータに対して階層的な処理を実行しませんが、フロントエンドのレンダリングはツリー構造のデータである必要があり、どのように実現するかデータのツリーのような構造? 配列内の親ノードの ID は、再帰関数によって子ノードの parentId に関連付けられます。
フロントエンドのフレームワークは、ここでは element-ui のツリー コントロールを使用して実装されています. わからない場合は、公式 Web サイトにアクセスしてドキュメントを参照してください。
地址:Element - 世界で最も人気のある Vue UI フレームワーク
1. ページを作成する
ここでは Vue プロジェクトの構築については触れませんが、基盤がよくない場合は、公式 Web サイトにアクセスしてドキュメントを参照できます。
まず、src/pages ディレクトリに element-trees フォルダーを作成し、次に index.vue を作成します。コードは次のようになります。
<template>
<div class="tree-container">
</div>
</template>
<script>
export default {
data(){
return {}
},
created() {},
methods: {}
}
</script>
<style lang="scss">
</style>
クラス セレクターのツリー コンテナー コンテナーで、関連するボタンとツリー コントロールを追加し、装飾用にいくつかのスタイルをコンテナーに追加します。コードは次のとおりです。
<template>
<div class="tree-container">
<div class="top-box">
<el-button type="primary" size="mini" @click="appendNode">添加菜单</el-button>
<el-button type="info" size="mini" @click="removeSelected">删除选中项</el-button>
</div>
<el-tree
ref="tree"
:data="treeData"
:props="props"
accordion
show-checkbox
node-key="id"
default-expand-all
:expand-on-click-node="false">
<span class="custom-tree-node" slot-scope="{ node, data }">
<span>{
{ node.label }}</span>
<span>
<el-button type="text" size="mini" @click="append(data)">添加</el-button>
<el-button type="text" size="mini" @click="editor(data)">修改</el-button>
<el-button type="text" size="mini" @click="remove(node, data)">删除</el-button>
</span>
</span>
</el-tree>
</div>
</template>
<script>
export default {
data(){
return {
props: {
label: 'name',
children: 'children'
},
treeData: []
}
},
created() {},
methods: {
//添加父节点数据
appendNode(){},
//添加子项数据
append(data){},
//编辑数据
editor(data){},
//移出项目
remove(node, data){},
//删除选中的节点
removeSelected(){}
}
}
</script>
<style lang="scss">
.tree-container{ width: 360px; font-size: 12px;
.top-box{ text-align: right; padding-bottom: 20px; }
}
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
}
</style>
このとき、ページ効果は次のようになります。
2. シミュレーションデータ
element-trees フォルダーに data.js ファイルを追加して、シミュレーション データを保存します。コードは以下のように表示されます:
/**
* 模拟数据
*/
const dataList = [
{
"name": "网络安全渗透工程师体系大纲",
"pid": 0,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 1
},
{
"name": "WEB通信、前后端原理",
"pid": 1,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 2
},
{
"name": "信息收集",
"pid": 1,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 3
},
{
"name": "注入全方位利用+数据库注入",
"pid": 1,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 4
},
{
"name": "前端渗透、文件上传解析漏洞",
"pid": 1,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 5
},
{
"name": "漏洞利用",
"pid": 1,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 6
},
{
"name": "漏洞挖掘",
"pid": 1,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 7
},
{
"name": "Web服务器通信原理",
"pid": 2,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 8
},
{
"name": "后端基础SQL",
"pid": 2,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 9
},
{
"name": "数据库简介及SQL语法",
"pid": 2,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 10
},
{
"name": "后端基础SQL高级查询与子查询",
"pid": 2,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 11
},
{
"name": "后端基础PHP简介及基本函数上",
"pid": 2,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 12
},
{
"name": "后端基础PHP—表单验证",
"pid": 2,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 13
},
{
"name": "正则表达式",
"pid": 2,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 14
},
{
"name": "信息搜集的意义 — 渗透测试的灵魂",
"pid": 3,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 15
},
{
"name": "信息收集(一)",
"pid": 3,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 16
},
{
"name": "网络架构-信息收集",
"pid": 3,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 17
},
{
"name": "前端-信息收集",
"pid": 3,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 18
},
{
"name": "系统-信息收集",
"pid": 3,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 19
},
{
"name": "SQL注入的原理",
"pid": 4,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 20
},
{
"name": "渗透测试常用工具讲解",
"pid": 4,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 21
},
{
"name": "POST注入/HEAD注入",
"pid": 4,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 22
},
{
"name": "盲注",
"pid": 4,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 23
},
{
"name": "XSS的原理分析与解剖",
"pid": 5,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 24
},
{
"name": "存储型XSS",
"pid": 5,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 25
},
{
"name": " Dom Based XSS",
"pid": 5,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 26
},
{
"name": "XXE实体注入",
"pid": 6,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 27
},
{
"name": "SSRF-服务器端请求伪造",
"pid": 6,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 28
},
{
"name": "验证码绕过、密码找回漏洞",
"pid": 7,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 29
},
{
"name": "平行越权、垂直越权",
"pid": 7,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 30
},
{
"name": "支付漏洞",
"pid": 7,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 31
},
{
"name": "Sql注入",
"pid": 7,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 32
},
{
"name": "变量覆盖漏洞",
"pid": 6,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 33
},
{
"name": "本地包含与远程包含",
"pid": 6,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 34
},
{
"name": "unserialize反序列化漏洞",
"pid": 6,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 35
},
{
"name": "CSRF",
"pid": 5,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 36
},
{
"name": "文件上传解析漏洞",
"pid": 5,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 37
},
{
"name": "宽字节注入",
"pid": 4,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 38
},
{
"name": "Accsess注入 — Cookie注入",
"pid": 4,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 39
},
{
"name": "Accsess注入 — 偏移注入",
"pid": 4,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 40
},
{
"name": "MySQL注入 — Dns 注入",
"pid": 4,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 41
},
{
"name": "MSSQL注入 — 反弹注入",
"pid": 4,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 42
},
{
"name": "Oracle注入 — 报错注入",
"pid": 4,
"createtime": "2023-03-29",
"updatetime": "2023-03-29",
"id": 43
}
];
export default dataList;
ファイルが作成されたら、それを index.vue ファイルにインポートします。コードは次のとおりです。
<script>
import DataList from './data'
export default {
data(){
return {
props: {
label: 'name',
children: 'children'
},
treeData: []
}
},
created() {
this.loadNode();
},
methods: {
//加载节点数据
loadNode(){
this.treeData = DataList.map(item => item);
},
//略...
}
}
</script>
loadNode() 関数を使用して上記のコードを実行すると、シミュレートされたデータが Tree コントロールにバインドされます。ただし、ページに表示されるデータはすべて同じレベルで表示されます。データの分類と分類については後述する。ページ効果は次のとおりです。
3. 再帰関数
この時点で、成功まであと 1 歩です.まず、data.js のデータ構造を分析しましょう。以下に示すように:
データをどのように分類して分類するかはループによって判断でき、各レベルの id はデータ内の pid に関連付けられます (たとえば、最初のデータ id は 1 であり、配列を走査して pid が 1 であることを判別します)。つまり、子アイテム データがあり、その子アイテム データが children 配列に追加されます。0 の pid は第 1 レベルのデータであるため、再帰関数を定義する場合、渡される pid はデフォルトで 0 です。
ここでのレベルは、1 層、2 層、3 層、... n 層であり、層ごとに判断するのは面倒なので、ここでは n レベルのデータ処理を実現するために再帰的な方法を使用する必要があります。
src の下に utils/utils.js ファイルを作成し、utils.js で再帰関数を定義します。コードは次のとおりです。
/**
* 递归函数
* @param arr 数组
* @param pid 父ID,不传默认为0
*/
export const DGFilterTrees = (arr, pid = 0) => {
let newArr = [];
//循环数组
arr.forEach((item, i) => {
//判断pid与遍历元素中pid相同的数据,相等的为同级数据,追加到该级数组中
if(item.pid == pid){
newArr.push(item);
/**
* 判断该item元素是否有子项
* 当数组中有元素的pid与item.id相等,some函数会返回true,表示该元素有子项数据
*/
if(arr.some(ele => ele.pid == item.id)){
//此时,通过重新调用本函数进行递归处理
item['children'] = DGFilterTrees(arr, item.id);
}
}
});
return newArr;
}
注: 再帰アルゴリズムは、独自の関数またはメソッドを直接的または間接的に呼び出すアルゴリズムです。
再帰アルゴリズムの特徴:
- 再帰とは、メソッドが自分自身を呼び出すことです。
- 増分再帰戦略を使用する場合、再帰終了と呼ばれる明確な再帰終了条件が必要です。
- 通常、問題を解く再帰アルゴリズムは非常に簡潔ですが、問題を解く再帰アルゴリズムの操作効率は低くなります。したがって、再帰アルゴリズムを使用してプログラムを設計することは一般的に推奨されません。
- 再帰呼び出しの過程で、システムは各レイヤーのリターン ポイントとローカル数量を格納するためのスタックを開きます。再帰回数が多すぎると、スタック オーバーフローなどが発生しやすくなるため、通常、再帰アルゴリズムを使用してプログラムを設計することはお勧めしません。
そのため、再帰アルゴリズムを使用する場合は、判定条件が正常に終了できるかどうかに注意する必要があります。そうしないと、独自の制御で終了できないループが発生し、スタック オーバーフローやプログラムのスタックなどの問題が発生します。
DGFilterTrees() 再帰関数を index.vue に導入して、シミュレートされたデータを再編成します。コードは次のとおりです。
<script>
import DataList from './data'
import DGFilterTrees from '@/utils/utils.js'
export default {
data(){
return {
props: {
label: 'name',
children: 'children'
},
treeData: []
}
},
created() {
this.loadNode();
},
methods: {
//加载节点数据
loadNode(){
this.treeData = DGFilterTrees(DataList);
},
//略...
}
}
</script>
この時点で、すべてのデータが分類され、階層的に表示されます。ページのレンダリングは次のようになります。
4. 選択後の表示ボタン
上の図に示すように、各項目の後続の追加、変更、および削除がすべて表示され、混雑して操作が不便になることがわかります。ここで、ノードを出力した後、 isCurrent を使用して判断し、現在のアイテムを選択するボタンのみを表示できることがわかります。
v-if を span タグに追加して、node.isCurrent が true かどうかを判断します。コードは次のとおりです。
<el-tree
ref="tree"
:data="treeData"
:props="props"
accordion
show-checkbox
node-key="id"
default-expand-all
:expand-on-click-node="false">
<span class="custom-tree-node" slot-scope="{ node, data }">
<span>{
{ node.label }}</span>
<span v-if="node.isCurrent">
<el-button type="text" size="mini" @click="append(data)">添加</el-button>
<el-button type="text" size="mini" @click="editor(data)">修改</el-button>
<el-button type="text" size="mini" @click="remove(node, data)">删除</el-button>
</span>
</span>
</el-tree>
下図のように「Backend Basic SQL」をクリックして選択すると、以下の操作ボタンが表示されます。
5.要素を削除する
ここでは、ローカルに定義されたシミュレートされたデータを使用して操作するため、実際のプロジェクトでインターフェイスを介してデータを追加、削除、変更、および確認する部分はありません。コードは以下のように表示されます:
//移出项目
remove(node, data){
if(Array.isArray(data['children'])&&data.children.length>0){
this.$message.error('当前数据有子项,无法删除~');
return;
}
//查询数据位置索引
let index = DataList.findIndex(item => item.id==data.id);
//删除指定位置数据
DataList.splice(index, 1);
//重新加载数据
this.loadNode();
}
効果は次のとおりです。
5. 複数のオプションを削除する
ここでは、Tree コントロールの getCheckedNodes() 関数を使用して、選択した項目を取得し、選択した各要素をループで削除する必要があります。コードは次のとおりです。
//删除选中的节点
removeSelected(){
//获取选中的数据
let checks = this.$refs.tree.getCheckedNodes();
//循环删除选中
checks.forEach(item => {
DataList.splice(DataList.findIndex(sub => sub.id == item.id), 1);
});
//重新加载数据
this.loadNode();
}
効果は次のとおりです。
この号ではまずこれを紹介します。追加、変更、およびその他の機能は比較的単純であり、element-ui の $prompt 箇条書きボックス コントロールによって実現できます。