feat: 支持多级组织机构,组织树可展开显示下级
This commit is contained in:
parent
2453f680ac
commit
69faa75310
@ -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")
|
||||
// 获取所有组织(带parent_id)
|
||||
let orgs: Vec<OrganizationRow> = sqlx::query_as(
|
||||
"SELECT id, name, parent_id 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!([])));
|
||||
};
|
||||
.await?;
|
||||
|
||||
// 获取所有项目
|
||||
let projects: Vec<ProjectRow> = sqlx::query_as(
|
||||
@ -44,21 +37,26 @@ pub async fn organization_tree(
|
||||
.fetch_all(db)
|
||||
.await?;
|
||||
|
||||
// 构建树(只到项目节点)
|
||||
let tree: Vec<TreeNode> = orgs
|
||||
.into_iter()
|
||||
// 递归构建树
|
||||
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 org_projects: Vec<ProjectRow> = projects
|
||||
// 递归获取子组织
|
||||
let children = build_tree(orgs, projects, Some(org.id));
|
||||
|
||||
// 获取该组织下的项目
|
||||
let org_projects: Vec<TreeNode> = projects
|
||||
.iter()
|
||||
.filter(|p| p.organization_id == Some(org.id))
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let children: Vec<TreeNode> = org_projects
|
||||
.into_iter()
|
||||
.map(|proj| TreeNode {
|
||||
id: proj.id,
|
||||
name: proj.name,
|
||||
.map(|p| TreeNode {
|
||||
id: p.id,
|
||||
name: p.name.clone(),
|
||||
children: None,
|
||||
abstract_id: None,
|
||||
imei: None,
|
||||
@ -66,16 +64,23 @@ pub async fn organization_tree(
|
||||
})
|
||||
.collect();
|
||||
|
||||
// 合并子组织和项目
|
||||
let mut all_children = children;
|
||||
all_children.extend(org_projects);
|
||||
|
||||
TreeNode {
|
||||
id: org.id,
|
||||
name: org.name,
|
||||
children: Some(children),
|
||||
name: org.name.clone(),
|
||||
children: if all_children.is_empty() { None } else { Some(all_children) },
|
||||
abstract_id: None,
|
||||
imei: None,
|
||||
status: None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
.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 {
|
||||
|
||||
@ -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>,
|
||||
}
|
||||
|
||||
/// 项目表行
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user