rust 递归菜单

rust 递归菜单

数据库语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
CREATE TABLE `sys_menu` (
`menu_id` bigint NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
`menu_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '菜单名称',
`parent_id` bigint DEFAULT '0' COMMENT '父菜单ID',
`order_num` int DEFAULT '0' COMMENT '显示顺序',
`url` varchar(254) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '请求地址',
`target` tinyint DEFAULT '1' COMMENT '打开方式(1内部,2新窗口)',
`menu_type` tinyint NOT NULL COMMENT '菜单类型(1目录 2菜单 3按钮)',
`visible` tinyint DEFAULT '1' COMMENT '菜单状态(1显示 0隐藏)',
`perms` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '权限标识',
`icon` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '菜单图标',
`create_by` varchar(254) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(254) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(254) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
`del_flag` tinyint NOT NULL DEFAULT '1' COMMENT '1正常 0删除',
PRIMARY KEY (`menu_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2033 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='菜单权限表'

实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};
use sqlx::FromRow;

//菜单
#[allow(dead_code)]
#[derive(FromRow, Debug,Clone,Serialize,Deserialize)]
pub struct SysMenu {

#[serde(rename = "id")]
pub menu_id: i64,

#[serde(rename = "title")]
pub menu_name: String,

pub parent_id: i64,

pub order_num: i32,

#[serde(rename = "href")]
pub url: Option<String>,


#[serde(rename = "openType")]
pub target: Option<i8>,

//菜单类型(1目录 2菜单 3按钮)
#[serde(rename = "type")]
pub menu_type: i8,

pub visible: Option<i8>,
pub perms: Option<String>,

pub icon: Option<String>,
pub create_by: Option<String>,
pub create_time: Option<NaiveDateTime>,
pub update_by: Option<String>,
pub update_time: Option<NaiveDateTime>,
pub remark: Option<String>,
pub del_flag: i8,

#[sqlx(skip)]
pub children: Vec<SysMenu>,
}

actix-web 控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
pub async fn menu(app_state: web::Data<AppState>) -> Result<ApiResponse<Vec<SysMenu>>, AppError> {

let sql = r#"
SELECT * FROM sys_menu WHERE del_flag = 1 and visible = 1 order by order_num
"#;


let menus: Vec<SysMenu> = sqlx::query_as::<_, SysMenu>(sql)
.fetch_all(&app_state.db) // fetch_all 来获取所有行
.await
.map_err(|e| AppError::db(e.into()))?;

// 构建树形结构
let menu_tree = build_menu_tree(0, &menus); // 从根菜单(parent_id = 0)开始构建树
Ok(ApiResponse::okay(menu_tree))
}// 构建树形结构

fn build_menu_tree(parent_id: i64, menus: &[SysMenu]) -> Vec<SysMenu> {
let mut result = Vec::new();
for menu in menus.iter() {
if menu.parent_id == parent_id {
let mut menu_with_children = menu.clone();
menu_with_children.children = build_menu_tree(menu.menu_id, menus);
result.push(menu_with_children);
}
}

result
}

路由配置

1
2
3
4
5
6
7
8
9
10
11
//需要登陆的路由
pub fn secure_route(config: &mut web::ServiceConfig) {
config.service(
web::scope("")
//首页
.route("/", get().to(index::index))
//获取菜单
.route("/menu", get().to(index::menu))
);
}

测试