feat: 支持多级组织机构,组织树可展开显示下级
This commit is contained in:
parent
2453f680ac
commit
69faa75310
@ -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 {
|
||||||
|
|||||||
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 项目表行
|
/// 项目表行
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user