2026-07-02 05:38:01 +08:00
|
|
|
|
//! 组织树 API
|
|
|
|
|
|
//!
|
2026-07-02 21:20:06 +08:00
|
|
|
|
//! 两个独立接口:
|
2026-07-02 21:32:06 +08:00
|
|
|
|
//! 1. /api/organization-tree — 返回多级组织->项目树
|
2026-07-02 21:20:06 +08:00
|
|
|
|
//! 2. /api/cabinets/filter — 根据组织/项目筛选设备列表
|
2026-07-02 05:38:01 +08:00
|
|
|
|
|
2026-07-02 21:20:06 +08:00
|
|
|
|
use axum::extract::{State, Query};
|
2026-07-02 05:38:01 +08:00
|
|
|
|
use axum::Json;
|
2026-07-02 21:20:06 +08:00
|
|
|
|
use serde::Deserialize;
|
2026-07-02 05:38:01 +08:00
|
|
|
|
use serde_json::{json, Value};
|
|
|
|
|
|
|
|
|
|
|
|
use super::organizations::{CabinetRow, OrganizationRow, ProjectRow, TreeNode};
|
|
|
|
|
|
use crate::error::AppError;
|
|
|
|
|
|
use crate::middleware::auth::{self, CurrentUser};
|
|
|
|
|
|
use crate::commands::AppState;
|
|
|
|
|
|
|
2026-07-02 21:32:06 +08:00
|
|
|
|
/// 组织树接口 — 返回多级组织->项目树
|
2026-07-02 05:38:01 +08:00
|
|
|
|
pub async fn organization_tree(
|
|
|
|
|
|
user: CurrentUser,
|
|
|
|
|
|
State(state): State<AppState>,
|
|
|
|
|
|
) -> Result<Json<Value>, AppError> {
|
|
|
|
|
|
auth::check_permission(&user, "device:view")?;
|
|
|
|
|
|
|
|
|
|
|
|
let db = &state.mysql;
|
|
|
|
|
|
|
2026-07-02 21:32:06 +08:00
|
|
|
|
// 获取所有组织(带parent_id)
|
|
|
|
|
|
let orgs: Vec<OrganizationRow> = sqlx::query_as(
|
|
|
|
|
|
"SELECT id, name, parent_id FROM organizations ORDER BY id"
|
|
|
|
|
|
)
|
|
|
|
|
|
.fetch_all(db)
|
|
|
|
|
|
.await?;
|
2026-07-02 05:38:01 +08:00
|
|
|
|
|
2026-07-02 21:20:06 +08:00
|
|
|
|
// 获取所有项目
|
2026-07-02 05:38:01 +08:00
|
|
|
|
let projects: Vec<ProjectRow> = sqlx::query_as(
|
2026-07-02 21:20:06 +08:00
|
|
|
|
"SELECT id, organization_id, name FROM projects ORDER BY id"
|
2026-07-02 05:38:01 +08:00
|
|
|
|
)
|
|
|
|
|
|
.fetch_all(db)
|
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
2026-07-02 21:32:06 +08:00
|
|
|
|
// 递归构建树
|
|
|
|
|
|
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 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) },
|
2026-07-02 21:20:06 +08:00
|
|
|
|
abstract_id: None,
|
|
|
|
|
|
imei: None,
|
|
|
|
|
|
status: None,
|
2026-07-02 21:32:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.collect()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let tree = build_tree(&orgs, &projects, None);
|
2026-07-02 05:38:01 +08:00
|
|
|
|
|
|
|
|
|
|
Ok(Json(json!(tree)))
|
|
|
|
|
|
}
|
2026-07-02 21:20:06 +08:00
|
|
|
|
|
|
|
|
|
|
/// 设备列表筛选参数
|
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
|
|
pub struct CabinetFilterParams {
|
|
|
|
|
|
pub organization_id: Option<i64>,
|
|
|
|
|
|
pub project_id: Option<i64>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 设备列表接口 — 根据组织/项目筛选
|
|
|
|
|
|
pub async fn filter_cabinets(
|
|
|
|
|
|
user: CurrentUser,
|
|
|
|
|
|
State(state): State<AppState>,
|
|
|
|
|
|
Query(params): Query<CabinetFilterParams>,
|
|
|
|
|
|
) -> Result<Json<Value>, AppError> {
|
|
|
|
|
|
auth::check_permission(&user, "device:view")?;
|
|
|
|
|
|
|
|
|
|
|
|
let db = &state.mysql;
|
|
|
|
|
|
|
|
|
|
|
|
let rows = if let Some(proj_id) = params.project_id {
|
|
|
|
|
|
// 按项目筛选
|
|
|
|
|
|
sqlx::query_as::<_, CabinetRow>(
|
|
|
|
|
|
"SELECT id, project_id, abstract_id, imei, iccid, name, address, status
|
|
|
|
|
|
FROM cabinets WHERE project_id = ? ORDER BY id"
|
|
|
|
|
|
)
|
|
|
|
|
|
.bind(proj_id)
|
|
|
|
|
|
.fetch_all(db)
|
|
|
|
|
|
.await?
|
|
|
|
|
|
} else if let Some(org_id) = params.organization_id {
|
2026-07-02 21:32:06 +08:00
|
|
|
|
// 按组织筛选(包括下级组织)
|
2026-07-02 21:20:06 +08:00
|
|
|
|
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
|
2026-07-02 21:32:06 +08:00
|
|
|
|
WHERE p.organization_id IN (
|
|
|
|
|
|
SELECT id FROM organizations WHERE id = ? OR parent_id = ?
|
|
|
|
|
|
) ORDER BY c.id"
|
2026-07-02 21:20:06 +08:00
|
|
|
|
)
|
|
|
|
|
|
.bind(org_id)
|
2026-07-02 21:32:06 +08:00
|
|
|
|
.bind(org_id)
|
2026-07-02 21:20:06 +08:00
|
|
|
|
.fetch_all(db)
|
|
|
|
|
|
.await?
|
|
|
|
|
|
} else if user.role_level >= 2 {
|
|
|
|
|
|
// 总管理员看所有
|
|
|
|
|
|
sqlx::query_as::<_, CabinetRow>(
|
|
|
|
|
|
"SELECT id, project_id, abstract_id, imei, iccid, name, address, status
|
|
|
|
|
|
FROM cabinets ORDER BY id"
|
|
|
|
|
|
)
|
|
|
|
|
|
.fetch_all(db)
|
|
|
|
|
|
.await?
|
|
|
|
|
|
} else if let Some(org_id) = user.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"
|
|
|
|
|
|
)
|
|
|
|
|
|
.bind(org_id)
|
|
|
|
|
|
.fetch_all(db)
|
|
|
|
|
|
.await?
|
|
|
|
|
|
} else {
|
|
|
|
|
|
vec![]
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Ok(Json(json!(rows)))
|
|
|
|
|
|
}
|