Files
SecMPS/api_sqlsugar/VolPro.WebApi/Controllers/Warehouse/Partial/base_deviceController.cs
T
g82tt a0a458bc34 V1.12.2: base_device getPageData 智能分流(支持 Filter)
【问题】
主人反馈: /api/base_device/getPageData 使用 filter: [{name:MapModelId, value:mh7b0xvof7p}] 查询时返回空

【根因】
base_deviceController.cs:159-163 GetPageData override 直接调 GetTreeTableRootData,
而 GetTreeTableRootData 硬编码 x => x.ParentDeviceId==0 || null (查顶层设备),
**完全忽略 options.Filter 条件**。
- 子设备(MapModelId 在子设备上)永远查不到
- 主人用 MapModelId 查设备详情场景完全失效

【修复】
base_deviceController.cs:154-197 GetPageData 智能分流:
1. options.Filter 或 options.Wheres 非空 → GetFilteredPageData → 调 _service.GetPageData(options)
   走框架默认实现(ServiceBase.GetPageData:318),自动处理 Filter/Wheres/Sort/分页
2. 都为空 → 保留原 GetTreeTableRootData 调用(treatable 组件兼容)
3. 新增 GetFilteredPageData 私有方法,统一返回 {total, rows} 结构

【验证】
dotnet build api_sqlsugar 0 错误 1 警告(Program.cs:244 与本次无关)

【效果】
- warehouse 大端 Filter.vue 按 MapModelId 查询设备能正常返回
- web.vite 管理端 tree-table 组件(无 filter 调用)行为不变
- 任意字段(DeviceName/AdapterCode/SourceId 等)都能作为 filter 查询
2026-07-24 05:56:16 +08:00

300 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
*设备管理扩展 — 区域树 + 点位设备列表
*所有改动在 Partial 目录,不破坏框架可升级性
*/
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using VolPro.Core.BaseProvider;
using VolPro.Core.Configuration;
using VolPro.Core.Enums;
using VolPro.Core.Extensions;
using VolPro.Core.Filters;
using VolPro.Core.ManageUser;
using VolPro.Entity.DomainModels;
using VolPro.Sys.IRepositories;
using VolPro.Sys.Repositories;
using Warehouse.IRepositories;
using Warehouse.IServices;
using Warehouse.Repositories;
namespace Warehouse.Controllers
{
public partial class base_deviceController
{
private readonly Ibase_deviceService _service;//访问业务代码
private readonly Iwarehouse_regionsService _regionsService;
private readonly Iwarehouse_devicepointService _pointService;
private readonly Ibase_deviceRepository _repository;
private readonly IHttpContextAccessor _httpContextAccessor;
[ActivatorUtilitiesConstructor]
public base_deviceController(
Ibase_deviceService service,
Iwarehouse_regionsService regionsService,
Iwarehouse_devicepointService pointService,
Ibase_deviceRepository repository,
IHttpContextAccessor httpContextAccessor
)
: base(service)
{
_service = service;
_regionsService = regionsService;
_pointService = pointService;
_repository = repository;
_httpContextAccessor = httpContextAccessor;
}
/// <summary>
/// 获取区域→点位→设备树。
/// 用于管理端左侧树形控件展示层级结构。
/// 格式: [{ id, label, type:"region", children: [{ id, label, type:"point", deviceCount }] }]
/// </summary>
[HttpGet]
[Route("/api/DeviceManager/GetRegionTree")]
public async Task<IActionResult> GetRegionTree()
{
// 查所有区域
var regions = await _regionsService.FindAsIQueryable(x => true).ToListAsync();
// 查所有点位
var points = await _pointService.FindAsIQueryable(x => true).ToListAsync();
// 统计每个点位下的设备数量
//var deviceCounts = new Dictionary<int, int>();
//var allDevices = await _service.FindAsIQueryable(x => true)
// .Where(x => x.PointId != null)
// .GroupBy(x => x.PointId!.Value)
// .Select(g => new { PointId = g.Key, Count = g.Count() })
// .ToListAsync();
var deviceCounts = new Dictionary<int, int>();
var devices = await _service.FindAsIQueryable(x => x.PointId != null)
.Select(x => new { x.PointId })
.ToListAsync();
deviceCounts = devices
.Where(x => x.PointId.HasValue)
.GroupBy(x => x.PointId!.Value)
.ToDictionary(g => g.Key, g => g.Count());
// 构建树形结构
var tree = new List<object>();
foreach (var region in regions)
{
var regionChildren = points
.Where(p => p.RegionId == region.Id)
.Select(p => new
{
id = $"p_{p.PointID}",
label = p.PointName ?? $"点位{p.PointID}",
type = "point",
deviceCount = deviceCounts.TryGetValue(p.PointID, out var c) ? c : 0
})
.ToList<object>();
tree.Add(new
{
id = $"r_{region.Id}",
label = region.RegionName ?? $"区域{region.Id}",
type = "region",
deviceCount = regionChildren.Count,
children = regionChildren
});
}
return Ok(tree);
}
/// <summary>
/// 获取指定点位下的设备列表(含子设备)。
/// 支持分页参数 page 和 size。
/// </summary>
[HttpGet]
[Route("/api/DeviceManager/GetDevicesByPoint")]
public async Task<IActionResult> GetDevicesByPoint(int pointId, int page = 1, int size = 20)
{
var query = _service.FindAsIQueryable(x => x.PointId == pointId);
var total = await query.CountAsync();
var items = await query
.Skip((page - 1) * size)
.Take(size)
.OrderBy(x => x.DeviceId)
.Select(x => new
{
x.DeviceId,
x.DeviceName,
x.AdapterCode,
x.SourceId,
x.DeviceCategory,
x.DeviceGroup,
x.IsParent,
x.ParentDeviceId,
x.IsOnline,
x.IpAddress,
x.Port,
x.Location,
x.ExtraData,
x.LastSyncTime,
x.MapModelId,
x.MapModelScale,
x.MapModelRotation,
x.Enable
})
.ToListAsync();
return Ok(new { items, total });
}
/// <summary>
/// V1.12.2 修复:通用分页查询(带 Filter/Wheres/Sort 支持)
///
/// **历史**V1.5 之前此端点 override 直接调 GetTreeTableRootData(硬编码 ParentDeviceId==0
/// 或 null 查顶层设备),**完全忽略 options.Filter**。这导致 warehouse 大端 Filter.vue
/// 按 MapModelId 查询设备时永远返回空(子设备查不到)。
///
/// **修复**:智能分流
/// - options.Filter 不为空 或 options.Wheres 不为空 → 走通用分页(支持任意字段过滤)
/// - 都为空 → 走原 GetTreeTableRootData 逻辑(保持 tree-table 组件兼容)
///
/// 注意:基类 ApiBaseController<T>.GetPageData 是 virtual ActionResult 同步方法,
/// 这里用 override 保留二进制兼容;内部用 .Result 包装 async 调用(不会死锁,
/// 因为 GetFilteredPageData 内部是纯查询不涉及同步上下文切换)。
/// </summary>
[ApiActionPermission(ActionPermissionOptions.Search)]
[HttpPost, Route("GetPageData")]
public override ActionResult GetPageData([FromBody] PageDataOptions loadData)
{
// 1) 智能分流:有 filter/wheres → 通用分页;无 → 走原 tree-table 根节点
var hasFilter = (loadData?.Filter != null && loadData.Filter.Count > 0)
|| !string.IsNullOrEmpty(loadData?.Wheres);
if (hasFilter)
{
return GetFilteredPageData(loadData);
}
return GetTreeTableRootData(loadData).Result;
}
/// <summary>
/// V1.12.2 新增:通用分页查询(不限制 ParentDeviceId,支持任意字段 Filter)。
/// 用于 warehouse 大端按 MapModelId 等任意字段查设备列表。
/// </summary>
private ActionResult GetFilteredPageData(PageDataOptions options)
{
// 直接用 _service 调用 IService<T>.GetPageData 默认实现
// 默认实现会自动处理 options.Filter(按 name 字段名+value 值+displayType 比较类型)
// 并按 options.Page/Rows 分页、按 options.Sort 排序
var pageData = _service.GetPageData(options);
// 统一响应结构:{ total, rows }(与 GetTreeTableRootData 一致)
return JsonNormal(new { total = pageData.total, rows = pageData.rows });
}
/// <summary>
/// treetable 获取一级(根)节点数据
/// </summary>
/// <returns></returns>
[HttpPost, Route("getTreeTableRootData")]
[ApiActionPermission(ActionPermissionOptions.Search)]
public async Task<ActionResult> GetTreeTableRootData([FromBody] PageDataOptions options)
{
//页面加载根节点数据条件x => x.ParentId == 0,自己根据需要设置
var dbServiceId = UserContext.CurrentServiceId;
// V1.8 增量补遗:过滤条件兼容 ParentDeviceId=0 和 =null 两种情况
// 原因:历史数据 + 网关未给 IsParent 赋值时顶层设备 ParentDeviceId 可能为 NULL
// 框架默认条件 x => x.ParentId == 0 会漏掉所有 NULL 记录
var query = _repository.FindAsIQueryable(x => x.ParentDeviceId == 0 || x.ParentDeviceId == null || x.DeviceId == 1);
var queryChild = _repository.FindAsIQueryable(x => true);
// 先获取总数
var total = await query.CountAsync();
// 按需先排序,再 Skip/Take 分页
var rows = await query
.OrderBy(x => x.DeviceId)
.Skip((options.Page - 1) * options.Rows)
.Take(options.Rows)
.Select(s => new
{
s.DeviceId,
s.ParentDeviceId,
s.DeviceName,
s.AdapterCode,
s.SourceId,
s.DeviceCategory,
s.DeviceGroup,
s.IsParent,
s.IsOnline,
s.IpAddress,
s.Port,
s.Location,
s.ExtraData,
s.LastSyncTime,
s.MapModelId,
s.MapModelScale,
s.MapModelRotation,
s.Enable,
s.CreateDate,
s.Creator,
s.Modifier,
s.ModifyDate,
hasChildren = SqlSugar.SqlFunc.Subqueryable<base_device>().Where(x => x.ParentDeviceId == s.DeviceId).Any()
}).ToListAsync();
return JsonNormal(new { total, rows });
}
/// <summary>
///treetable 获取子节点数据
/// </summary>
/// <returns></returns>
[HttpPost, Route("getTreeTableChildrenData")]
[ApiActionPermission(ActionPermissionOptions.Search)]
public async Task<ActionResult> GetTreeTableChildrenData(int deviceId)
{
//点击节点时,加载子节点数据
var basedeviceRepository = base_deviceRepository.Instance.FindAsIQueryable(x => true);
var query = basedeviceRepository.Where(x => x.ParentDeviceId == deviceId);
//if (AppSetting.UseDynamicShareDB)
//{
// query = query.Where(x => x.DbServiceId == UserContext.CurrentServiceId);
//}
var rows = await query
.Select(s => new
{
s.DeviceId,
s.ParentDeviceId,
s.DeviceName,
s.AdapterCode,
s.SourceId,
s.DeviceCategory,
s.DeviceGroup,
s.IsParent,
s.IsOnline,
s.IpAddress,
s.Port,
s.Location,
s.ExtraData,
s.LastSyncTime,
s.MapModelId,
s.MapModelScale,
s.MapModelRotation,
s.Enable,
s.CreateDate,
s.Creator,
s.Modifier,
s.ModifyDate,
hasChildren = SqlSugar.SqlFunc.Subqueryable<base_device>().Where(x => x.ParentDeviceId == s.DeviceId).Any()
}).ToListAsync();
return JsonNormal(new { rows });
}
}
}