feat: 支持多级组织机构,组织树可展开显示下级

This commit is contained in:
12451 2026-07-02 21:32:06 +08:00
parent 2453f680ac
commit 69faa75310
3 changed files with 60 additions and 82 deletions

View File

@ -1,7 +1,7 @@
//! 组织树 API
//!
//! 两个独立接口:
//! 1. /api/organization-tree — 返回组织->项目树(不含设备)
//! 1. /api/organization-tree — 返回多级组织->项目树
//! 2. /api/cabinets/filter — 根据组织/项目筛选设备列表
use axum::extract::{State, Query};
@ -14,7 +14,7 @@ use crate::error::AppError;
use crate::middleware::auth::{self, CurrentUser};
use crate::commands::AppState;
/// 组织树接口 — 只返回组织->项目(不含设备)
/// 组织树接口 — 返回多级组织->项目树
pub async fn organization_tree(
user: CurrentUser,
State(state): State<AppState>,
@ -23,19 +23,12 @@ pub async fn organization_tree(
let db = &state.mysql;
// 根据角色过滤组织
let orgs: Vec<OrganizationRow> = if user.role_level >= 2 {
sqlx::query_as("SELECT id, name FROM organizations ORDER BY id")
.fetch_all(db)
.await?
} else if let Some(org_id) = user.organization_id {
sqlx::query_as("SELECT id, name FROM organizations WHERE id = ? ORDER BY id")
.bind(org_id)
.fetch_all(db)
.await?
} else {
return Ok(Json(json!([])));
};
// 获取所有组织带parent_id
let orgs: Vec<OrganizationRow> = sqlx::query_as(
"SELECT id, name, parent_id FROM organizations ORDER BY id"
)
.fetch_all(db)
.await?;
// 获取所有项目
let projects: Vec<ProjectRow> = sqlx::query_as(
@ -44,38 +37,50 @@ pub async fn organization_tree(
.fetch_all(db)
.await?;
// 构建树(只到项目节点)
let tree: Vec<TreeNode> = orgs
.into_iter()
.map(|org| {
let org_projects: Vec<ProjectRow> = projects
.iter()
.filter(|p| p.organization_id == Some(org.id))
.cloned()
.collect();
// 递归构建树
fn build_tree(
orgs: &[OrganizationRow],
projects: &[ProjectRow],
parent_id: Option<i64>,
) -> Vec<TreeNode> {
orgs
.iter()
.filter(|o| o.parent_id == parent_id)
.map(|org| {
// 递归获取子组织
let children = build_tree(orgs, projects, Some(org.id));
// 获取该组织下的项目
let org_projects: Vec<TreeNode> = projects
.iter()
.filter(|p| p.organization_id == Some(org.id))
.map(|p| TreeNode {
id: p.id,
name: p.name.clone(),
children: None,
abstract_id: None,
imei: None,
status: None,
})
.collect();
let children: Vec<TreeNode> = org_projects
.into_iter()
.map(|proj| TreeNode {
id: proj.id,
name: proj.name,
children: None,
// 合并子组织和项目
let mut all_children = children;
all_children.extend(org_projects);
TreeNode {
id: org.id,
name: org.name.clone(),
children: if all_children.is_empty() { None } else { Some(all_children) },
abstract_id: None,
imei: None,
status: None,
})
.collect();
}
})
.collect()
}
TreeNode {
id: org.id,
name: org.name,
children: Some(children),
abstract_id: None,
imei: None,
status: None,
}
})
.collect();
let tree = build_tree(&orgs, &projects, None);
Ok(Json(json!(tree)))
}
@ -107,14 +112,17 @@ pub async fn filter_cabinets(
.fetch_all(db)
.await?
} else if let Some(org_id) = params.organization_id {
// 按组织筛选(通过项目关联
// 按组织筛选(包括下级组织
sqlx::query_as::<_, CabinetRow>(
"SELECT c.id, c.project_id, c.abstract_id, c.imei, c.iccid, c.name, c.address, c.status
FROM cabinets c
JOIN projects p ON c.project_id = p.id
WHERE p.organization_id = ? ORDER BY c.id"
WHERE p.organization_id IN (
SELECT id FROM organizations WHERE id = ? OR parent_id = ?
) ORDER BY c.id"
)
.bind(org_id)
.bind(org_id)
.fetch_all(db)
.await?
} else if user.role_level >= 2 {

View File

@ -45,6 +45,8 @@ pub struct UpdateCabinetBody {
pub struct OrganizationRow {
pub id: i64,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub parent_id: Option<i64>,
}
/// 项目表行

View File

@ -17,7 +17,6 @@ import {
} from '@arco-design/web-react/icon';
import {
type TreeNode,
type Cabinet,
} from '@/api/devices';
const Sider = Layout.Sider;
@ -35,19 +34,19 @@ export interface TreeInternalNode {
_id: number;
}
/** 构建 Arco Tree 数据(只到项目节点,不显示柜子 */
/** 构建 Arco Tree 数据(支持多级组织 -> 项目 */
export function buildTreeData(nodes: TreeNode[], parentKey?: string): TreeInternalNode[] {
const result: TreeInternalNode[] = [];
for (const n of nodes) {
const key = parentKey ? `${parentKey}-${n.id}` : `${n.id}`;
const nodeType: NodeType = parentKey ? 'project' : 'organization';
// 只显示组织和项目,不显示柜子
const hasChildren = n.children && n.children.some(c => c.children !== undefined);
const children = hasChildren ? buildTreeData(n.children!, key) : undefined;
// 有children的是组织没有children的是项目
const isOrg = n.children && n.children.length > 0;
const nodeType: NodeType = isOrg ? 'organization' : 'project';
const children = isOrg ? buildTreeData(n.children!, key) : undefined;
result.push({
key,
title: n.name,
isLeaf: !hasChildren,
isLeaf: !isOrg,
children,
_type: nodeType,
_id: n.id,
@ -56,37 +55,6 @@ export function buildTreeData(nodes: TreeNode[], parentKey?: string): TreeIntern
return result;
}
/** 从树中收集所有柜子 */
export function collectCabinets(nodes: TreeNode[]): Cabinet[] {
const result: Cabinet[] = [];
for (const n of nodes) {
if (n.children) {
result.push(...collectCabinets(n.children));
} else if (n.abstract_id) {
result.push({
id: n.id,
abstract_id: n.abstract_id || '',
imei: n.imei || '',
name: n.name,
status: n.status || 0,
});
}
}
return result;
}
/** 在原始树中按 ID 查找组织节点 */
export function findOrgInTree(nodes: TreeNode[], id: number): TreeNode | undefined {
for (const n of nodes) {
if (n.id === id) return n;
if (n.children) {
const found = findOrgInTree(n.children, id);
if (found) return found;
}
}
return undefined;
}
interface OrganizationTreeProps {
treeData: TreeInternalNode[];
loading: boolean;