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

View File

@ -45,6 +45,8 @@ pub struct UpdateCabinetBody {
pub struct OrganizationRow { pub struct OrganizationRow {
pub id: i64, pub id: i64,
pub name: String, 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'; } from '@arco-design/web-react/icon';
import { import {
type TreeNode, type TreeNode,
type Cabinet,
} from '@/api/devices'; } from '@/api/devices';
const Sider = Layout.Sider; const Sider = Layout.Sider;
@ -35,19 +34,19 @@ export interface TreeInternalNode {
_id: number; _id: number;
} }
/** 构建 Arco Tree 数据(只到项目节点,不显示柜子 */ /** 构建 Arco Tree 数据(支持多级组织 -> 项目 */
export function buildTreeData(nodes: TreeNode[], parentKey?: string): TreeInternalNode[] { export function buildTreeData(nodes: TreeNode[], parentKey?: string): TreeInternalNode[] {
const result: TreeInternalNode[] = []; const result: TreeInternalNode[] = [];
for (const n of nodes) { for (const n of nodes) {
const key = parentKey ? `${parentKey}-${n.id}` : `${n.id}`; const key = parentKey ? `${parentKey}-${n.id}` : `${n.id}`;
const nodeType: NodeType = parentKey ? 'project' : 'organization'; // 有children的是组织没有children的是项目
// 只显示组织和项目,不显示柜子 const isOrg = n.children && n.children.length > 0;
const hasChildren = n.children && n.children.some(c => c.children !== undefined); const nodeType: NodeType = isOrg ? 'organization' : 'project';
const children = hasChildren ? buildTreeData(n.children!, key) : undefined; const children = isOrg ? buildTreeData(n.children!, key) : undefined;
result.push({ result.push({
key, key,
title: n.name, title: n.name,
isLeaf: !hasChildren, isLeaf: !isOrg,
children, children,
_type: nodeType, _type: nodeType,
_id: n.id, _id: n.id,
@ -56,37 +55,6 @@ export function buildTreeData(nodes: TreeNode[], parentKey?: string): TreeIntern
return result; 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 { interface OrganizationTreeProps {
treeData: TreeInternalNode[]; treeData: TreeInternalNode[];
loading: boolean; loading: boolean;