pom.xml
@@ -16,7 +16,7 @@ <ruoyi.version>3.8.6</ruoyi.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <java.version>17</java.version> <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version> <druid.version>1.2.16</druid.version> <bitwalker.version>1.21</bitwalker.version> ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/DashboardStatisticsController.java
@@ -9,6 +9,8 @@ import com.ruoyi.system.model.*; import com.ruoyi.system.query.StoryListQuery; import com.ruoyi.system.service.*; import com.ruoyi.system.vo.ClientDashboardQuery; import com.ruoyi.system.vo.ClientDashboardVO; import com.ruoyi.system.vo.ScreenUserVo; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -125,7 +127,18 @@ return R.ok(accidentBrief); } /** * 获取委托方大屏首页统计数据 * * @param query 查询参数 * @return 统计数据 */ @PostMapping("/index") @ApiOperation("获取委托方大屏首页统计数据") public R<ClientDashboardVO> index(@RequestBody ClientDashboardQuery query) { ClientDashboardVO dashboard = clientService.getClientDashboard(query); return R.ok(dashboard); } ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/ProjectController.java
@@ -12,7 +12,6 @@ import com.ruoyi.system.vo.*; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.var; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.parsing.Problem; ruoyi-system/src/main/java/com/ruoyi/system/enums/BigGiftPackageOrderStatusEnum.java
File was deleted ruoyi-system/src/main/java/com/ruoyi/system/enums/ICBCRespOrderErrorEnum.java
File was deleted ruoyi-system/src/main/java/com/ruoyi/system/enums/IOptionVo.java
New file @@ -0,0 +1,17 @@ package com.ruoyi.system.enums; public interface IOptionVo { /** * 获取选项编码 * @return */ String getOptionCode(); /** * 获取选项名称 * @return */ String getOptionName(); } ruoyi-system/src/main/java/com/ruoyi/system/enums/PhysicalGoodsOrderStatusEnum.java
File was deleted ruoyi-system/src/main/java/com/ruoyi/system/enums/ProjectType.java
New file @@ -0,0 +1,74 @@ package com.ruoyi.system.enums; import io.swagger.annotations.ApiModel; import lombok.Getter; import java.util.ArrayList; import java.util.List; @ApiModel("项目类型") @Getter public enum ProjectType implements IOptionVo { HOUSE_CONSTRUCTION("房建工程"), // 房建工程 MUNICIPAL_ADMINISTRATION("市政工程"), // 市政工程 FIXED_ASSETS("固定资产"); // 固定资产 final String desc; private List<ProjectUsageType> projectUsageTypes; ProjectType(String desc) { this.desc = desc; this.projectUsageTypes = new ArrayList<>(); } public static ProjectType getByDesc(String desc) { for (ProjectType projectType : ProjectType.values()) { if (projectType.getDesc().equals(desc)) { return projectType; } } return null; } public void linkPurposeType(ProjectUsageType projectUsageType) { this.projectUsageTypes.add(projectUsageType); } static { // 房建工程关联用途类型 HOUSE_CONSTRUCTION.linkPurposeType(ProjectUsageType.RESIDENTIAL); HOUSE_CONSTRUCTION.linkPurposeType(ProjectUsageType.COMMERCIAL); // 市政工程关联用途类型 MUNICIPAL_ADMINISTRATION.linkPurposeType(ProjectUsageType.ROAD); MUNICIPAL_ADMINISTRATION.linkPurposeType(ProjectUsageType.BRIDGE); MUNICIPAL_ADMINISTRATION.linkPurposeType(ProjectUsageType.TUNNEL_WORKS); // 固定资产关联用途类型 FIXED_ASSETS.linkPurposeType(ProjectUsageType.RETAIL); FIXED_ASSETS.linkPurposeType(ProjectUsageType.FOOD); FIXED_ASSETS.linkPurposeType(ProjectUsageType.EDUCATION); FIXED_ASSETS.linkPurposeType(ProjectUsageType.MEDICAL_BEAUTY); FIXED_ASSETS.linkPurposeType(ProjectUsageType.ENTERTAINMENT); FIXED_ASSETS.linkPurposeType(ProjectUsageType.HOTEL); FIXED_ASSETS.linkPurposeType(ProjectUsageType.REFIT); FIXED_ASSETS.linkPurposeType(ProjectUsageType.PUBLIC_PLACE); FIXED_ASSETS.linkPurposeType(ProjectUsageType.ENERGY_STATION); FIXED_ASSETS.linkPurposeType(ProjectUsageType.MULTI_FUNCTION_COMPLEX); } @Override public String getOptionCode() { return name(); } @Override public String getOptionName() { return desc; } } ruoyi-system/src/main/java/com/ruoyi/system/enums/ProjectUsageType.java
New file @@ -0,0 +1,54 @@ package com.ruoyi.system.enums; import io.swagger.annotations.ApiModel; import lombok.Getter; @ApiModel("项目用途类型") @Getter public enum ProjectUsageType implements IOptionVo { // 房建工程用途类型 RESIDENTIAL("住宅"), COMMERCIAL("商业"), // 市政工程用途类型 ROAD("道路"), BRIDGE("桥梁"), TUNNEL_WORKS("隧道"), // 固定资产用途类型 RETAIL("仓储"), FOOD("餐饮"), EDUCATION("教育"), MEDICAL_BEAUTY("医疗美容"), ENTERTAINMENT("娱乐"), HOTEL("酒店"), REFIT("翻新改造"), PUBLIC_PLACE("公共场所"), ENERGY_STATION("能源站"), MULTI_FUNCTION_COMPLEX("综合体"); private final String desc; ProjectUsageType(String desc) { this.desc = desc; } public static ProjectUsageType getByDesc(String desc) { for (ProjectUsageType projectUsageType : ProjectUsageType.values()) { if (projectUsageType.getDesc().equals(desc)) { return projectUsageType; } } return null; } @Override public String getOptionCode() { return name(); } @Override public String getOptionName() { return desc; } } ruoyi-system/src/main/java/com/ruoyi/system/enums/UnionPayStatusEnum.java
File was deleted ruoyi-system/src/main/java/com/ruoyi/system/enums/UserTypeEnum.java
File was deleted ruoyi-system/src/main/java/com/ruoyi/system/mapper/EvalResultMapper.java
New file @@ -0,0 +1,14 @@ package com.ruoyi.system.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ruoyi.system.model.EvalResult; /** * 评估结果 Mapper 接口 * * @author MiTao * @date 2026-01-21 */ public interface EvalResultMapper extends BaseMapper<EvalResult> { // 继承 BaseMapper 即可,MyBatis-Plus 提供基础 CRUD } ruoyi-system/src/main/java/com/ruoyi/system/mapper/PlanMapper.java
@@ -1,6 +1,9 @@ package com.ruoyi.system.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ruoyi.system.model.Plan; /** * <p> * Mapper 接口 ruoyi-system/src/main/java/com/ruoyi/system/model/Check.java
@@ -1,10 +1,7 @@ package com.ruoyi.system.model; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.extension.activerecord.Model; import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.annotations.ApiModelProperty; @@ -81,7 +78,17 @@ @ApiModelProperty("检查标题") private String title; /** * 创建者 */ @TableField("creator_id") @ApiModelProperty("创建人id") private Integer creatorId; @TableField("deleted") @TableLogic @ApiModelProperty("是否删除 false正常true删除") private Boolean deleted; @TableField(exist = false) @ApiModelProperty("计划名称") ruoyi-system/src/main/java/com/ruoyi/system/model/EvalResult.java
New file @@ -0,0 +1,106 @@ package com.ruoyi.system.model; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.extension.activerecord.Model; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; import java.io.Serializable; import java.util.Date; /** * 评估结果实体类 * * @author MiTao * @date 2026-01-21 */ @Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @TableName("eval_result") @ApiModel(value = "EvalResult对象", description = "评估结果") public class EvalResult extends Model<EvalResult> { private static final long serialVersionUID = 1L; @TableId(value = "id", type = IdType.AUTO) private Integer id; @ApiModelProperty(value = "评价类型:fuzzy-模糊评价,score-打分评价") @TableField("type") private String type; @ApiModelProperty(value = "安全检查得分") @TableField("score") private Float score; @ApiModelProperty(value = "设备评估结果映射(JSON字符串)") @TableField("device_res_map") private String deviceResMap; @ApiModelProperty(value = "系统评估结果(JSON字符串)") @TableField("system_res") private String systemRes; @ApiModelProperty(value = "关联的检查任务ID") @TableField("check_task_id") private Integer checkTaskId; @ApiModelProperty(value = "关联的委托方ID") @TableField("client_id") private Integer clientId; @ApiModelProperty(value = "关联的计划ID") @TableField("plan_id") private Integer planId; @ApiModelProperty(value = "关联的项目ID") @TableField("project_id") private Integer projectId; @ApiModelProperty(value = "创建时间") @TableField("create_time") private Date createTime; @ApiModelProperty(value = "更新时间") @TableField("update_time") private Date updateTime; @ApiModelProperty(value = "逻辑删除标记") @TableField("deleted") private Boolean deleted; @ApiModelProperty(value = "名称") @TableField("name") private String name; @ApiModelProperty(value = "是否包含设备评估结果") @TableField("contain_device_res") private Boolean containDeviceRes; @ApiModelProperty(value = "是否包含系统评估结果") @TableField("contain_system_res") private Boolean containSystemRes; @ApiModelProperty(value = "显示分数") @TableField("display_score") private Float displayScore; @ApiModelProperty(value = "风险等级") @TableField("risk_level") private Integer riskLevel; @ApiModelProperty(value = "创建者ID") @TableField("creator_id") private Integer creatorId; @ApiModelProperty(value = "错误计数") @TableField("error_counts") private String errorCounts; } ruoyi-system/src/main/java/com/ruoyi/system/model/OnDuty.java
@@ -1,11 +1,9 @@ package com.ruoyi.system.model; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.*; import java.time.LocalDate; import com.baomidou.mybatisplus.annotation.TableId; import java.time.LocalDateTime; import com.baomidou.mybatisplus.annotation.TableField; import java.io.Serializable; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @@ -75,5 +73,12 @@ @TableField("create_time") private LocalDateTime createTime; @TableField("creator_id") private Integer creatorId; @TableField("deleted") @TableLogic private Boolean deleted; } ruoyi-system/src/main/java/com/ruoyi/system/model/Project.java
@@ -14,7 +14,7 @@ /** * <p> * * * </p> * * @author administrator @@ -65,11 +65,5 @@ */ private String permission; private String scale; } ruoyi-system/src/main/java/com/ruoyi/system/service/ClientService.java
@@ -2,6 +2,8 @@ import com.baomidou.mybatisplus.extension.service.IService; import com.ruoyi.system.model.Client; import com.ruoyi.system.vo.ClientDashboardQuery; import com.ruoyi.system.vo.ClientDashboardVO; /** * <p> @@ -13,4 +15,11 @@ */ public interface ClientService extends IService<Client> { /** * 获取委托方大屏首页统计数据 * * @param query 查询参数 * @return 统计数据 */ ClientDashboardVO getClientDashboard(ClientDashboardQuery query); } ruoyi-system/src/main/java/com/ruoyi/system/service/ProjectCategoryCalculator.java
New file @@ -0,0 +1,567 @@ package com.ruoyi.system.service; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.ruoyi.system.mapper.*; import com.ruoyi.system.model.*; import com.ruoyi.system.model.Record; import com.ruoyi.system.vo.ProjectScoreData; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.util.*; import java.util.stream.Collectors; /** * 项目类别计算服务 * * @author MiTao * @date 2026-01-21 */ @Service @RequiredArgsConstructor @Slf4j public class ProjectCategoryCalculator { private final EvalResultMapper evalResultMapper; private final ClientParamMapper clientParamMapper; private final CheckMapper checkMapper; private final RecordMapper recordMapper; /** * 计算在建工程项目类别 * * @param projects 项目列表 * @param clientId 委托方ID * @param startDate 开始日期 * @param endDate 结束日期 * @return 项目ID -> 类别(A/B/C)的映射 */ public Map<Integer, String> calculateOngoingProjectCategories( List<Project> projects, Long clientId, Date startDate, Date endDate) { Map<Integer, String> categoryMap = new HashMap<>(); if (projects == null || projects.isEmpty()) { return categoryMap; } try { // 1. 获取委托方配置 ClientParam clientParam = getClientParam(clientId); if (clientParam == null) { log.warn("委托方配置不存在,clientId: {}", clientId); return categoryMap; } // 2. 解析在建工程配置 JSONObject config = parseJsonConfig( clientParam.getUnderConstructionProject(), "在建工程配置"); if (config == null) { return categoryMap; } // 3. 查询项目关联的检查任务 List<Integer> projectIds = projects.stream() .map(Project::getId) .collect(Collectors.toList()); LambdaQueryWrapper<Check> checkWrapper = new LambdaQueryWrapper<>(); checkWrapper.in(Check::getProjectId, projectIds) .ge(Check::getTime, startDate) .le(Check::getTime, endDate); List<Check> checks = checkMapper.selectList(checkWrapper); if (checks.isEmpty()) { log.info("项目没有检查记录,projectIds: {}", projectIds); return categoryMap; } List<Integer> checkTaskIds = checks.stream() .map(Check::getId) .collect(Collectors.toList()); // 4. 根据评价类型查询评估结果 Integer commentType = clientParam.getCommentType(); String evalType = (commentType != null && commentType == 1) ? "fuzzy" : "score"; LambdaQueryWrapper<EvalResult> evalWrapper = new LambdaQueryWrapper<>(); evalWrapper.eq(EvalResult::getType, evalType) .in(EvalResult::getCheckTaskId, checkTaskIds) .eq(EvalResult::getDeleted, false); List<EvalResult> evalResults = evalResultMapper.selectList(evalWrapper); if (evalResults.isEmpty()) { log.info("项目没有评估结果,projectIds: {}", projectIds); return categoryMap; } // 5. 计算项目平均分 Map<Integer, ProjectScoreData> projectScoreMap = calculateProjectScores( checks, evalResults); // 6. 根据配置判断项目类别 for (Map.Entry<Integer, ProjectScoreData> entry : projectScoreMap.entrySet()) { Integer projectId = entry.getKey(); ProjectScoreData scoreData = entry.getValue(); try { String category = determineOngoingCategory(scoreData, config); if (category != null) { categoryMap.put(projectId, category); } } catch (Exception e) { log.error("判断项目类别失败,projectId: {}", projectId, e); } } } catch (Exception e) { log.error("计算在建工程项目类别失败,clientId: {}", clientId, e); } return categoryMap; } /** * 计算固定资产项目类别 * * @param projects 项目列表 * @param clientId 委托方ID * @param startDate 开始日期 * @param endDate 结束日期 * @return 项目ID -> 类别(A/B/C)的映射 */ public Map<Integer, String> calculateFixedAssetCategories( List<Project> projects, Long clientId, Date startDate, Date endDate) { Map<Integer, String> categoryMap = new HashMap<>(); if (projects == null || projects.isEmpty()) { return categoryMap; } try { // 1. 获取委托方配置 ClientParam clientParam = getClientParam(clientId); if (clientParam == null) { log.warn("委托方配置不存在,clientId: {}", clientId); return categoryMap; } // 2. 解析固定资产配置 JSONObject config = parseJsonConfig( clientParam.getAssetProject(), "固定资产配置"); if (config == null) { return categoryMap; } // 3. 查询项目关联的检查任务 List<Integer> projectIds = projects.stream() .map(Project::getId) .collect(Collectors.toList()); LambdaQueryWrapper<Check> checkWrapper = new LambdaQueryWrapper<>(); checkWrapper.in(Check::getProjectId, projectIds) .ge(Check::getTime, startDate) .le(Check::getTime, endDate); List<Check> checks = checkMapper.selectList(checkWrapper); if (checks.isEmpty()) { log.info("项目没有检查记录,projectIds: {}", projectIds); return categoryMap; } List<Integer> checkTaskIds = checks.stream() .map(Check::getId) .collect(Collectors.toList()); // 4. 查询隐患记录(record_type='error') LambdaQueryWrapper<Record> recordWrapper = new LambdaQueryWrapper<>(); recordWrapper.eq(Record::getRecordType, "error") .in(Record::getCheckTaskId, checkTaskIds) .eq(Record::getDeleted, false); List<Record> hazardRecords = recordMapper.selectList(recordWrapper); if (hazardRecords.isEmpty()) { log.info("项目没有隐患记录,projectIds: {}", projectIds); return categoryMap; } // 5. 按项目分组统计隐患等级数量 Map<Integer, Integer> checkToProjectMap = checks.stream() .collect(Collectors.toMap(Check::getId, Check::getProjectId)); Map<Integer, Map<Integer, Integer>> projectHazardMap = new HashMap<>(); for (Record record : hazardRecords) { Integer projectId = checkToProjectMap.get(record.getCheckTaskId()); if (projectId == null) { continue; } Integer level = record.getLevel(); if (level == null) { continue; } projectHazardMap .computeIfAbsent(projectId, k -> new HashMap<>()) .merge(level, 1, Integer::sum); } // 6. 根据配置判断项目类别 for (Map.Entry<Integer, Map<Integer, Integer>> entry : projectHazardMap.entrySet()) { Integer projectId = entry.getKey(); Map<Integer, Integer> hazardCounts = entry.getValue(); try { String category = determineFixedAssetCategory(hazardCounts, config); if (category != null) { categoryMap.put(projectId, category); } } catch (Exception e) { log.error("判断项目类别失败,projectId: {}", projectId, e); } } } catch (Exception e) { log.error("计算固定资产项目类别失败,clientId: {}", clientId, e); } return categoryMap; } /** * 获取委托方配置 * * @param clientId 委托方ID * @return 委托方配置 */ private ClientParam getClientParam(Long clientId) { if (clientId == null) { return null; } LambdaQueryWrapper<ClientParam> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(ClientParam::getClientId, clientId.intValue()); return clientParamMapper.selectOne(wrapper); } /** * 解析 JSON 配置 * * @param jsonStr JSON 字符串 * @param configName 配置名称 * @return JSON 对象 */ private JSONObject parseJsonConfig(String jsonStr, String configName) { if (!StringUtils.hasText(jsonStr)) { log.warn("{}为空", configName); return null; } try { return JSON.parseObject(jsonStr); } catch (Exception e) { log.error("解析{}失败: {}", configName, jsonStr, e); return null; } } /** * 计算项目评分数据 * * @param checks 检查任务列表 * @param evalResults 评估结果列表 * @return 项目ID -> 评分数据的映射 */ private Map<Integer, ProjectScoreData> calculateProjectScores( List<Check> checks, List<EvalResult> evalResults) { Map<Integer, ProjectScoreData> projectScoreMap = new HashMap<>(); // 按 check_task_id 关联到 project_id Map<Integer, Integer> checkToProjectMap = checks.stream() .collect(Collectors.toMap(Check::getId, Check::getProjectId)); // 按项目分组评估结果 Map<Integer, List<EvalResult>> projectEvalMap = evalResults.stream() .filter(e -> checkToProjectMap.containsKey(e.getCheckTaskId())) .collect(Collectors.groupingBy(e -> checkToProjectMap.get(e.getCheckTaskId()))); // 计算每个项目的平均分 for (Map.Entry<Integer, List<EvalResult>> entry : projectEvalMap.entrySet()) { Integer projectId = entry.getKey(); List<EvalResult> results = entry.getValue(); // 计算安全检查平均分 double avgSafetyScore = results.stream() .filter(e -> e.getScore() != null) .mapToDouble(EvalResult::getScore) .average() .orElse(0.0); // 计算设备检查平均分(需要解析 device_res_map) List<Float> deviceScores = results.stream() .filter(e -> StringUtils.hasText(e.getDeviceResMap())) .map(e -> this.calculateDeviceScore(e.getDeviceResMap())) .filter(score -> score != null && score > 0) .collect(Collectors.toList()); ProjectScoreData scoreData = new ProjectScoreData(); scoreData.setProjectId(projectId); scoreData.setSafetyScore((float) avgSafetyScore); if (!deviceScores.isEmpty()) { double avgDeviceScore = deviceScores.stream() .mapToDouble(Float::doubleValue) .average() .orElse(0.0); scoreData.setDeviceScore((float) avgDeviceScore); scoreData.setHasDeviceScore(true); } else { scoreData.setHasDeviceScore(false); } projectScoreMap.put(projectId, scoreData); } return projectScoreMap; } /** * 计算设备得分 * <p> * device_res_map 字段的 JSON 结构(来自 estate_expert 项目): * <pre> * { * "设备ID1": { * "content": { * "data": { * "score": 85.5, * "weight": 100.0, * "total": 100.0, * "totalDeduct": 14.5, * "totalErrorCounts": [1, 2, 0, 0] * }, * "children": [...] * } * }, * "设备ID2": { * "content": { * "data": { * "score": 90.0, * ... * }, * "children": [...] * } * } * } * </pre> * <p> * 解析路径: deviceMap[deviceId].content.data.score * <p> * 计算逻辑: 提取所有设备的 score 字段并计算平均值 * * @param deviceResMapJson device_res_map 字段的 JSON 字符串 * @return 设备平均分,如果无法计算则返回 null */ private static Float calculateDeviceScore(String deviceResMapJson) { try { // 1. 解析 JSON 字符串 JSONObject deviceMap = JSON.parseObject(deviceResMapJson); if (deviceMap == null || deviceMap.isEmpty()) { log.debug("设备评估结果映射为空"); return null; } // 2. 提取所有设备的分数 List<Float> scores = new ArrayList<>(); for (String deviceId : deviceMap.keySet()) { try { // 获取设备数据对象 JSONObject deviceData = deviceMap.getJSONObject(deviceId); if (deviceData == null) { log.debug("设备数据为空,deviceId: {}", deviceId); continue; } // 获取 content 对象 JSONObject content = deviceData.getJSONObject("content"); if (content == null) { log.debug("设备 content 为空,deviceId: {}", deviceId); continue; } // 获取 data 对象 JSONObject data = content.getJSONObject("data"); if (data == null) { log.debug("设备 data 为空,deviceId: {}", deviceId); continue; } // 获取 score 字段 if (!data.containsKey("score")) { log.debug("设备 data 中不包含 score 字段,deviceId: {}", deviceId); continue; } Float score = data.getFloat("score"); if (score != null && score > 0) { scores.add(score); log.debug("成功提取设备得分,deviceId: {}, score: {}", deviceId, score); } } catch (Exception e) { log.warn("提取设备得分失败,deviceId: {}", deviceId, e); } } // 3. 计算平均分 if (scores.isEmpty()) { log.debug("没有有效的设备得分数据"); return null; } float avgScore = (float) scores.stream() .mapToDouble(Float::doubleValue) .average() .orElse(0.0); log.debug("设备平均得分计算完成,设备数量: {}, 平均分: {}", scores.size(), avgScore); return avgScore; } catch (Exception e) { log.warn("解析设备得分失败,deviceResMapJson: {}", deviceResMapJson, e); return null; } } public static void main(String[] args) { String json = "{\"47\": {\"content\": {\"id\": 0, \"data\": {\"score\": 83.111336, \"weight\": 1.0, \"include\": true, \"riskLevel\": \"P3\", \"membershipList\": [0.025, 0.5, 0.3917, 0.0833, 0.0], \"totalErrorCounts\": [0, 1, 0, 0]}, \"name\": \"设备体系-标准-层级\", \"path\": \"/\", \"children\": {\"0\": {\"id\": 0, \"data\": {\"score\": 83.111336, \"weight\": 1.0, \"include\": true, \"riskLevel\": \"P3\", \"membershipList\": [0.025, 0.5, 0.3917, 0.0833, 0.0], \"totalErrorCounts\": [0, 1, 0, 0]}, \"name\": \"塔式起重机\", \"path\": \"/0\", \"children\": {\"1\": {\"id\": 1, \"data\": {\"score\": 83.111336, \"weight\": 1.0, \"include\": true, \"riskLevel\": \"P3\", \"membershipList\": [0.025, 0.5, 0.3917, 0.0833, 0.0], \"totalErrorCounts\": [0, 1, 0, 0]}, \"name\": \"塔式起重机实体(市站)\", \"path\": \"/0/1\", \"children\": {\"0\": {\"id\": 0, \"data\": {\"score\": 83.111336, \"weight\": 1.0, \"include\": true, \"riskLevel\": \"P3\", \"membershipList\": [0.025, 0.5, 0.3917, 0.0833, 0.0], \"totalErrorCounts\": [0, 1, 0, 0]}, \"name\": \"一般项目\", \"path\": \"/0/1/0\", \"children\": {\"0\": {\"id\": 0, \"data\": {\"score\": 0.0, \"weight\": 1.0, \"include\": true, \"riskLevel\": null, \"membershipList\": null, \"totalErrorCounts\": [0, 1, 0, 0]}, \"name\": \"基础与轨道\", \"path\": \"/0/1/0/0\", \"children\": {}, \"childIdCnt\": 0}}, \"childIdCnt\": 0}}, \"childIdCnt\": 0}}, \"childIdCnt\": 0}}, \"childIdCnt\": 0}}}"; Float v = calculateDeviceScore(json); System.out.println(v); } /** * 根据在建工程配置确定项目类别 * * @param scoreData 项目评分数据 * @param config 在建工程配置 * @return 项目类别(A/B/C) */ private String determineOngoingCategory(ProjectScoreData scoreData, JSONObject config) { if (config == null) { log.warn("在建工程配置为空,无法计算项目类别"); return null; } // 解析配置 Float thresholdA = config.getFloat("a"); Float thresholdB = config.getFloat("b"); Float thresholdC = config.getFloat("c"); Float safeProp = config.getFloat("safe_prop"); Float deviceProp = config.getFloat("device_prop"); if (thresholdA == null || thresholdB == null || thresholdC == null || safeProp == null || deviceProp == null) { log.warn("在建工程配置不完整: {}", config.toJSONString()); return null; } // 计算综合得分 float finalScore; if (scoreData.isHasDeviceScore()) { // 有设备得分:按比例计算 finalScore = scoreData.getSafetyScore() * (safeProp / 100.0f) + scoreData.getDeviceScore() * (deviceProp / 100.0f); } else { // 无设备得分:安全检查得分按 100% 计算 finalScore = scoreData.getSafetyScore(); } // 根据阈值判断类别(从高到低判断) if (finalScore >= thresholdC) { return "A"; } else if (finalScore >= thresholdB) { return "B"; } else if (finalScore >= thresholdA) { return "C"; } else { return null; // 不满足任何类别 } } /** * 根据固定资产配置确定项目类别 * * @param hazardCounts 隐患等级统计 Map<level, count> * @param config 固定资产配置 JSON * @return 项目类别(A/B/C) */ private String determineFixedAssetCategory( Map<Integer, Integer> hazardCounts, JSONObject config) { if (config == null) { log.warn("固定资产配置为空,无法计算项目类别"); return null; } // 按优先级顺序判断:A -> B -> C String[] categories = {"a", "b", "c"}; for (String category : categories) { JSONArray categoryRules = config.getJSONArray(category); if (categoryRules == null || categoryRules.isEmpty()) { continue; } // 检查是否满足该类别的任一规则(OR 条件) boolean matchesCategory = false; for (int i = 0; i < categoryRules.size(); i++) { JSONArray rule = categoryRules.getJSONArray(i); if (rule == null) { continue; } // 检查规则中的所有条件(AND 条件) boolean matchesRule = true; for (int j = 0; j < rule.size(); j++) { JSONObject condition = rule.getJSONObject(j); if (condition == null) { continue; } // 条件格式:{"0": 5} 表示 level=0 的隐患数量 >= 5 for (String levelStr : condition.keySet()) { Integer level = Integer.parseInt(levelStr); Integer requiredCount = condition.getInteger(levelStr); Integer actualCount = hazardCounts.getOrDefault(level, 0); if (actualCount < requiredCount) { matchesRule = false; break; } } if (!matchesRule) { break; } } if (matchesRule) { matchesCategory = true; break; } } if (matchesCategory) { return category.toUpperCase(); // 返回 "A", "B", "C" } } return null; // 不满足任何类别 } } ruoyi-system/src/main/java/com/ruoyi/system/service/impl/ClientServiceImpl.java
@@ -1,11 +1,37 @@ package com.ruoyi.system.service.impl; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.system.mapper.CheckMapper; import com.ruoyi.system.mapper.ClientMapper; import com.ruoyi.system.mapper.DeviceUsageMapper; import com.ruoyi.system.mapper.PlanMapper; import com.ruoyi.system.mapper.ProjectMapper; import com.ruoyi.system.mapper.RecordMapper; import com.ruoyi.system.model.Check; import com.ruoyi.system.model.Client; import com.ruoyi.system.model.DeviceUsage; import com.ruoyi.system.model.Plan; import com.ruoyi.system.model.Project; import com.ruoyi.system.model.Record; import com.ruoyi.system.service.ClientService; import com.ruoyi.system.service.ProjectCategoryCalculator; import com.ruoyi.system.vo.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.time.LocalDate; import java.time.YearMonth; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; /** * <p> @@ -16,6 +42,475 @@ * @since 2026-01-12 */ @Service @RequiredArgsConstructor @Slf4j public class ClientServiceImpl extends ServiceImpl<ClientMapper, Client> implements ClientService { private final ProjectMapper projectMapper; private final PlanMapper planMapper; private final CheckMapper checkMapper; private final DeviceUsageMapper deviceUsageMapper; private final RecordMapper recordMapper; private final ProjectCategoryCalculator projectCategoryCalculator; @Override public ClientDashboardVO getClientDashboard(ClientDashboardQuery query) { ClientDashboardVO vo = new ClientDashboardVO(); vo.setProjectCategory(query.getProjectCategory()); // 获取委托方ID(如果未传则需要从当前登录用户获取,这里暂时使用传入的值) Long clientId = query.getClientId(); // 获取日期范围 Date startDate = getStartDate(query.getStartDate()); Date endDate = getEndDate(query.getEndDate()); // 根据项目类型进行不同的统计 if ("ongoing".equals(query.getProjectCategory())) { // 在建工程统计 OngoingProjectDashboardVO ongoingVO = calculateOngoingProjectStatistics(clientId, startDate, endDate); vo.setOngoingProject(ongoingVO); } else if ("fixed".equals(query.getProjectCategory())) { // 固定资产统计 FixedAssetDashboardVO fixedVO = calculateFixedAssetStatistics(clientId, startDate, endDate); vo.setFixedAsset(fixedVO); } return vo; } /** * 在建工程统计 */ private OngoingProjectDashboardVO calculateOngoingProjectStatistics(Long clientId, Date monthStart, Date monthEnd) { OngoingProjectDashboardVO vo = new OngoingProjectDashboardVO(); // 查询在建工程项目(usage_type 表示项目用途类型,假设 0 表示在建工程) LambdaQueryWrapper<Project> projectWrapper = new LambdaQueryWrapper<>(); projectWrapper.eq(Project::getDeleted, false) .eq(Project::getUsageType, 0); // 在建工程 if (clientId != null) { projectWrapper.eq(Project::getClientId, clientId); } List<Project> projects = projectMapper.selectList(projectWrapper); // 1. 统计项目总数及分类 vo.setProjectTotal(projects.size()); long housingCount = projects.stream().filter(p -> p.getType() != null && p.getType() == 0).count(); long municipalCount = projects.stream().filter(p -> p.getType() != null && p.getType() == 1).count(); vo.setHousingProjectCount((int) housingCount); vo.setMunicipalProjectCount((int) municipalCount); // 计算检查覆盖率 List<Integer> projectIds = projects.stream().map(Project::getId).collect(Collectors.toList()); if (!projectIds.isEmpty()) { LambdaQueryWrapper<Check> checkWrapper = new LambdaQueryWrapper<>(); checkWrapper.in(Check::getProjectId, projectIds); List<Check> checks = checkMapper.selectList(checkWrapper); List<Integer> checkedProjectIds = checks.stream() .map(Check::getProjectId) .distinct() .collect(Collectors.toList()); // 房建检查覆盖率 List<Integer> housingProjectIds = projects.stream() .filter(p -> p.getType() != null && p.getType() == 0) .map(Project::getId) .toList(); long checkedHousingCount = checkedProjectIds.stream() .filter(housingProjectIds::contains) .count(); vo.setHousingInspectionCoverage( housingCount > 0 ? (int) Math.round(checkedHousingCount * 100.0 / housingCount) : 0 ); // 市政检查覆盖率 List<Integer> municipalProjectIds = projects.stream() .filter(p -> p.getType() != null && p.getType() == 1) .map(Project::getId) .toList(); long checkedMunicipalCount = checkedProjectIds.stream() .filter(municipalProjectIds::contains) .count(); vo.setMunicipalInspectionCoverage( municipalCount > 0 ? (int) Math.round(checkedMunicipalCount * 100.0 / municipalCount) : 0 ); } else { vo.setHousingInspectionCoverage(0); vo.setMunicipalInspectionCoverage(0); } // 2. 统计本月检查情况 calculateMonthlyInspectionForOngoing(vo, projects, projectIds, monthStart, monthEnd); // 3. 统计大型机械设备数 vo.setMachineryStatistics(calculateMachineryStatistics(projectIds)); // 4. 统计重大隐患数 calculateHazardStatistics(vo, projectIds); // 5. 获取地图标点数据 vo.setProjectMapPoints(getProjectMapPoints(projects, clientId, monthStart, monthEnd)); return vo; } /** * 固定资产统计 */ private FixedAssetDashboardVO calculateFixedAssetStatistics(Long clientId, Date monthStart, Date monthEnd) { FixedAssetDashboardVO vo = new FixedAssetDashboardVO(); // 查询固定资产项目(usage_type 表示项目用途类型,假设 1 表示固定资产) LambdaQueryWrapper<Project> projectWrapper = new LambdaQueryWrapper<>(); projectWrapper.eq(Project::getDeleted, false) .eq(Project::getUsageType, 1); // 固定资产 if (clientId != null) { projectWrapper.eq(Project::getClientId, clientId); } List<Project> projects = projectMapper.selectList(projectWrapper); // 1. 统计项目总数 vo.setProjectTotal(projects.size()); // 计算检查覆盖率 List<Integer> projectIds = projects.stream().map(Project::getId).collect(Collectors.toList()); if (!projectIds.isEmpty()) { LambdaQueryWrapper<Check> checkWrapper = new LambdaQueryWrapper<>(); checkWrapper.in(Check::getProjectId, projectIds); List<Check> checks = checkMapper.selectList(checkWrapper); long checkedProjectCount = checks.stream() .map(Check::getProjectId) .distinct() .count(); vo.setInspectionCoverage( projects.size() > 0 ? (int) Math.round(checkedProjectCount * 100.0 / projects.size()) : 0 ); } else { vo.setInspectionCoverage(0); } // 2. 统计本月检查情况 calculateMonthlyInspectionForFixed(vo, projects, projectIds, monthStart, monthEnd); // 3. 统计重大隐患数 calculateHazardStatisticsForFixed(vo, projectIds); // 4. 获取地图标点数据 vo.setProjectMapPoints(getProjectMapPoints(projects, clientId, monthStart, monthEnd)); return vo; } /** * 统计在建工程本月检查情况 */ private void calculateMonthlyInspectionForOngoing(OngoingProjectDashboardVO vo, List<Project> projects, List<Integer> projectIds, Date monthStart, Date monthEnd) { if (projectIds.isEmpty()) { vo.setInspectedCount(0); vo.setInspectedHousingCount(0); vo.setInspectedMunicipalCount(0); vo.setUninspectedCount(projects.size()); vo.setUninspectedHousingCount(vo.getHousingProjectCount()); vo.setUninspectedMunicipalCount(vo.getMunicipalProjectCount()); return; } // 查询本月有检查记录的项目 LambdaQueryWrapper<Check> checkWrapper = new LambdaQueryWrapper<>(); checkWrapper.in(Check::getProjectId, projectIds) .ge(Check::getTime, monthStart) .le(Check::getTime, monthEnd); List<Check> monthlyChecks = checkMapper.selectList(checkWrapper); List<Integer> inspectedProjectIds = monthlyChecks.stream() .map(Check::getProjectId) .distinct() .toList(); // 已检查项目统计 List<Project> inspectedProjects = projects.stream() .filter(p -> inspectedProjectIds.contains(p.getId())) .toList(); vo.setInspectedCount(inspectedProjects.size()); vo.setInspectedHousingCount((int) inspectedProjects.stream() .filter(p -> p.getType() != null && p.getType() == 0).count()); vo.setInspectedMunicipalCount((int) inspectedProjects.stream() .filter(p -> p.getType() != null && p.getType() == 1).count()); // 未检查项目统计 List<Project> uninspectedProjects = projects.stream() .filter(p -> !inspectedProjectIds.contains(p.getId())) .toList(); vo.setUninspectedCount(uninspectedProjects.size()); vo.setUninspectedHousingCount((int) uninspectedProjects.stream() .filter(p -> p.getType() != null && p.getType() == 0).count()); vo.setUninspectedMunicipalCount((int) uninspectedProjects.stream() .filter(p -> p.getType() != null && p.getType() == 1).count()); } /** * 统计固定资产本月检查情况 */ private void calculateMonthlyInspectionForFixed(FixedAssetDashboardVO vo, List<Project> projects, List<Integer> projectIds, Date monthStart, Date monthEnd) { if (projectIds.isEmpty()) { vo.setInspectedCount(0); vo.setUninspectedCount(projects.size()); return; } // 查询本月有检查记录的项目 LambdaQueryWrapper<Check> checkWrapper = new LambdaQueryWrapper<>(); checkWrapper.in(Check::getProjectId, projectIds) .ge(Check::getTime, monthStart) .le(Check::getTime, monthEnd); List<Check> monthlyChecks = checkMapper.selectList(checkWrapper); long inspectedCount = monthlyChecks.stream() .map(Check::getProjectId) .distinct() .count(); vo.setInspectedCount((int) inspectedCount); vo.setUninspectedCount(projects.size() - (int) inspectedCount); } /** * 统计大型机械设备数(含已检查/未检查) */ private List<MachineryStatisticsVO> calculateMachineryStatistics(List<Integer> projectIds) { if (projectIds.isEmpty()) { return new ArrayList<>(); } // 查询设备使用记录 LambdaQueryWrapper<DeviceUsage> deviceWrapper = new LambdaQueryWrapper<>(); deviceWrapper.eq(DeviceUsage::getDeleted, false) .in(DeviceUsage::getProjectId, projectIds); List<DeviceUsage> devices = deviceUsageMapper.selectList(deviceWrapper); // 按设备名称分组统计 Map<String, List<DeviceUsage>> deviceGroupMap = devices.stream() .filter(d -> d.getName() != null && !d.getName().isEmpty()) .collect(Collectors.groupingBy(DeviceUsage::getName)); // 查询设备检查记录 List<Integer> deviceIds = devices.stream().map(DeviceUsage::getId).collect(Collectors.toList()); Map<Integer, Boolean> deviceInspectionMap = new HashMap<>(); if (!deviceIds.isEmpty()) { LambdaQueryWrapper<Record> recordWrapper = new LambdaQueryWrapper<>(); recordWrapper.eq(Record::getDeleted, false) .in(Record::getDeviceId, deviceIds); List<Record> records = recordMapper.selectList(recordWrapper); Set<Integer> inspectedDeviceIds = records.stream() .map(Record::getDeviceId) .collect(Collectors.toSet()); for (Integer deviceId : deviceIds) { deviceInspectionMap.put(deviceId, inspectedDeviceIds.contains(deviceId)); } } // 构建统计结果 List<MachineryStatisticsVO> result = new ArrayList<>(); for (Map.Entry<String, List<DeviceUsage>> entry : deviceGroupMap.entrySet()) { MachineryStatisticsVO vo = new MachineryStatisticsVO(); vo.setName(entry.getKey()); vo.setTotal(entry.getValue().size()); long inspectedCount = entry.getValue().stream() .filter(d -> deviceInspectionMap.getOrDefault(d.getId(), false)) .count(); vo.setInspectedCount((int) inspectedCount); vo.setUninspectedCount(entry.getValue().size() - (int) inspectedCount); result.add(vo); } return result; } /** * 统计在建工程重大隐患数 */ private void calculateHazardStatistics(OngoingProjectDashboardVO vo, List<Integer> projectIds) { if (projectIds.isEmpty()) { vo.setMajorHazardTotal(0); vo.setRectifiedHazardCount(0); vo.setUnrectifiedHazardCount(0); return; } // 查询检查任务 LambdaQueryWrapper<Check> checkWrapper = new LambdaQueryWrapper<>(); checkWrapper.in(Check::getProjectId, projectIds); List<Check> checks = checkMapper.selectList(checkWrapper); List<Integer> checkTaskIds = checks.stream().map(Check::getId).collect(Collectors.toList()); if (checkTaskIds.isEmpty()) { vo.setMajorHazardTotal(0); vo.setRectifiedHazardCount(0); vo.setUnrectifiedHazardCount(0); return; } // 查询重大隐患记录(level 表示隐患等级,假设 3 表示重大隐患) LambdaQueryWrapper<Record> recordWrapper = new LambdaQueryWrapper<>(); recordWrapper.eq(Record::getDeleted, false) .in(Record::getCheckTaskId, checkTaskIds) .eq(Record::getLevel, 3); // 重大隐患等级 List<Record> majorHazards = recordMapper.selectList(recordWrapper); vo.setMajorHazardTotal(majorHazards.size()); // 统计已整改和未整改数量 long rectifiedCount = majorHazards.stream() .filter(r -> r.getReformed() != null && r.getReformed()) .count(); long unrectifiedCount = majorHazards.stream() .filter(r -> r.getReformed() == null || !r.getReformed()) .count(); vo.setRectifiedHazardCount((int) rectifiedCount); vo.setUnrectifiedHazardCount((int) unrectifiedCount); } /** * 统计固定资产重大隐患数 */ private void calculateHazardStatisticsForFixed(FixedAssetDashboardVO vo, List<Integer> projectIds) { if (projectIds.isEmpty()) { vo.setMajorHazardTotal(0); vo.setRectifiedHazardCount(0); vo.setUnrectifiedHazardCount(0); return; } // 查询检查任务 LambdaQueryWrapper<Check> checkWrapper = new LambdaQueryWrapper<>(); checkWrapper.in(Check::getProjectId, projectIds); List<Check> checks = checkMapper.selectList(checkWrapper); List<Integer> checkTaskIds = checks.stream().map(Check::getId).collect(Collectors.toList()); if (checkTaskIds.isEmpty()) { vo.setMajorHazardTotal(0); vo.setRectifiedHazardCount(0); vo.setUnrectifiedHazardCount(0); return; } // 查询重大隐患记录(level 表示隐患等级,假设 3 表示重大隐患) LambdaQueryWrapper<Record> recordWrapper = new LambdaQueryWrapper<>(); recordWrapper.eq(Record::getDeleted, false) .in(Record::getCheckTaskId, checkTaskIds) .eq(Record::getLevel, 3); // 重大隐患等级 List<Record> majorHazards = recordMapper.selectList(recordWrapper); vo.setMajorHazardTotal(majorHazards.size()); // 统计已整改和未整改数量 long rectifiedCount = majorHazards.stream() .filter(r -> r.getReformed() != null && r.getReformed()) .count(); long unrectifiedCount = majorHazards.stream() .filter(r -> r.getReformed() == null || !r.getReformed()) .count(); vo.setRectifiedHazardCount((int) rectifiedCount); vo.setUnrectifiedHazardCount((int) unrectifiedCount); } /** * 获取项目地图标点数据 */ private List<ProjectMapPointVO> getProjectMapPoints( List<Project> projects, Long clientId, Date startDate, Date endDate) { List<ProjectMapPointVO> result = new ArrayList<>(); // 计算项目类别 Map<Integer, String> categoryMap; if (!projects.isEmpty()) { // 判断项目类型:在建工程 or 固定资产 Integer usageType = projects.get(0).getUsageType(); if (usageType != null && usageType == 0) { // 在建工程 categoryMap = projectCategoryCalculator.calculateOngoingProjectCategories( projects, clientId, startDate, endDate); } else if (usageType != null && usageType == 1) { // 固定资产 categoryMap = projectCategoryCalculator.calculateFixedAssetCategories( projects, clientId, startDate, endDate); } else { categoryMap = new HashMap<>(); } } else { categoryMap = new HashMap<>(); } // 构建地图标点数据 for (Project p : projects) { if (!StringUtils.hasText(p.getAddressInfo())) { continue; } try { // 解析 address_info JSON 字符串 JSONObject addressJson = JSON.parseObject(p.getAddressInfo()); JSONArray gps = addressJson.getJSONArray("gps"); if (gps != null && gps.size() >= 2) { ProjectMapPointVO vo = new ProjectMapPointVO(); vo.setProjectId(p.getId()); vo.setProjectName(p.getName()); vo.setProjectType(p.getType()); // gps 数组格式:[经度, 纬度] vo.setLongitude(gps.getString(0)); vo.setLatitude(gps.getString(1)); // 设置项目类别 vo.setCategory(categoryMap.get(p.getId())); result.add(vo); } } catch (Exception e) { // 解析失败则跳过该项目 log.warn("解析项目地址信息失败,项目ID: {}, address_info: {}", p.getId(), p.getAddressInfo(), e); } } return result; } /** * 获取开始日期 */ private Date getStartDate(Date startDate) { if (startDate != null) { return startDate; } // 默认为当前月份第一天 YearMonth currentMonth = YearMonth.now(); return Date.from(currentMonth.atDay(1).atStartOfDay(ZoneId.systemDefault()).toInstant()); } /** * 获取结束日期 */ private Date getEndDate(Date endDate) { if (endDate != null) { return endDate; } // 默认为当前月份最后一天 YearMonth currentMonth = YearMonth.now(); return Date.from(currentMonth.atEndOfMonth().atTime(23, 59, 59).atZone(ZoneId.systemDefault()).toInstant()); } } ruoyi-system/src/main/java/com/ruoyi/system/service/impl/PlanServiceImpl.java
@@ -1,6 +1,8 @@ package com.ruoyi.system.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.system.mapper.PlanMapper; import com.ruoyi.system.model.Plan; import com.ruoyi.system.service.PlanService; import org.springframework.stereotype.Service; ruoyi-system/src/main/java/com/ruoyi/system/vo/ClientDashboardQuery.java
New file @@ -0,0 +1,33 @@ package com.ruoyi.system.vo; import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.util.Date; /** * 委托方大屏首页查询参数 * * @author MiTao * @date 2026-01-20 15:30 */ @Data @ApiModel("委托方大屏首页查询参数") public class ClientDashboardQuery { @ApiModelProperty("委托方ID(可选,不传则查询当前登录用户的委托方数据)") private Long clientId; @ApiModelProperty("开始日期(格式:yyyy-MM-dd,不传则默认当前月份第一天)") @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") private Date startDate; @ApiModelProperty("结束日期(格式:yyyy-MM-dd,不传则默认当前月份最后一天)") @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") private Date endDate; @ApiModelProperty("项目类型(ongoing-在建工程,fixed-固定资产,必填)") private String projectCategory; } ruoyi-system/src/main/java/com/ruoyi/system/vo/ClientDashboardVO.java
New file @@ -0,0 +1,26 @@ package com.ruoyi.system.vo; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; /** * 委托方大屏首页统计数据(包装类) * * @author MiTao * @date 2026-01-20 15:01 */ @Data @ApiModel("委托方大屏首页统计数据") public class ClientDashboardVO { @ApiModelProperty("项目类型(ongoing-在建工程,fixed-固定资产)") private String projectCategory; @ApiModelProperty("在建工程统计数据(当 projectCategory=ongoing 时返回)") private OngoingProjectDashboardVO ongoingProject; @ApiModelProperty("固定资产统计数据(当 projectCategory=fixed 时返回)") private FixedAssetDashboardVO fixedAsset; } ruoyi-system/src/main/java/com/ruoyi/system/vo/FixedAssetDashboardVO.java
New file @@ -0,0 +1,46 @@ package com.ruoyi.system.vo; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.util.List; /** * 固定资产大屏统计数据 * * @author MiTao * @date 2026-01-20 15:45 */ @Data @ApiModel("固定资产大屏统计数据") public class FixedAssetDashboardVO { // ==================== 项目总数模块 ==================== @ApiModelProperty("项目总数") private Integer projectTotal; @ApiModelProperty("检查覆盖率(百分比,如:77 表示 77%)") private Integer inspectionCoverage; // ==================== 本月检查情况 ==================== @ApiModelProperty("已检查项目数") private Integer inspectedCount; @ApiModelProperty("未检查项目数") private Integer uninspectedCount; // ==================== 重大隐患数 ==================== @ApiModelProperty("重大隐患总数") private Integer majorHazardTotal; @ApiModelProperty("已整改隐患数") private Integer rectifiedHazardCount; @ApiModelProperty("未整改隐患数") private Integer unrectifiedHazardCount; // ==================== 地图标点数据 ==================== @ApiModelProperty("项目地图标点列表") private List<ProjectMapPointVO> projectMapPoints; } ruoyi-system/src/main/java/com/ruoyi/system/vo/MachineryStatisticsVO.java
New file @@ -0,0 +1,28 @@ package com.ruoyi.system.vo; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; /** * 机械设备统计数据 * * @author MiTao * @date 2026-01-20 15:30 */ @Data @ApiModel("机械设备统计数据") public class MachineryStatisticsVO { @ApiModelProperty("设备名称") private String name; @ApiModelProperty("设备总数") private Integer total; @ApiModelProperty("已检查数") private Integer inspectedCount; @ApiModelProperty("未检查数") private Integer uninspectedCount; } ruoyi-system/src/main/java/com/ruoyi/system/vo/OngoingProjectDashboardVO.java
New file @@ -0,0 +1,71 @@ package com.ruoyi.system.vo; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.util.List; /** * 在建工程大屏统计数据 * * @author MiTao * @date 2026-01-20 15:40 */ @Data @ApiModel("在建工程大屏统计数据") public class OngoingProjectDashboardVO { // ==================== 项目总数模块 ==================== @ApiModelProperty("项目总数") private Integer projectTotal; @ApiModelProperty("房建项目数") private Integer housingProjectCount; @ApiModelProperty("市政项目数") private Integer municipalProjectCount; @ApiModelProperty("房建检查覆盖率(百分比,如:77 表示 77%)") private Integer housingInspectionCoverage; @ApiModelProperty("市政检查覆盖率(百分比,如:77 表示 77%)") private Integer municipalInspectionCoverage; // ==================== 本月检查情况 ==================== @ApiModelProperty("已检查项目总数") private Integer inspectedCount; @ApiModelProperty("已检查房建项目数") private Integer inspectedHousingCount; @ApiModelProperty("已检查市政项目数") private Integer inspectedMunicipalCount; @ApiModelProperty("未检查项目总数") private Integer uninspectedCount; @ApiModelProperty("未检查房建项目数") private Integer uninspectedHousingCount; @ApiModelProperty("未检查市政项目数") private Integer uninspectedMunicipalCount; // ==================== 大型机械设备数 ==================== @ApiModelProperty("大型机械设备统计列表") private List<MachineryStatisticsVO> machineryStatistics; // ==================== 重大隐患数 ==================== @ApiModelProperty("重大隐患总数") private Integer majorHazardTotal; @ApiModelProperty("已整改隐患数") private Integer rectifiedHazardCount; @ApiModelProperty("未整改隐患数") private Integer unrectifiedHazardCount; // ==================== 地图标点数据 ==================== @ApiModelProperty("项目地图标点列表") private List<ProjectMapPointVO> projectMapPoints; } ruoyi-system/src/main/java/com/ruoyi/system/vo/ProjectMapPointVO.java
New file @@ -0,0 +1,34 @@ package com.ruoyi.system.vo; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; /** * 项目地图标点数据 * * @author MiTao * @date 2026-01-20 15:35 */ @Data @ApiModel("项目地图标点数据") public class ProjectMapPointVO { @ApiModelProperty("项目ID") private Integer projectId; @ApiModelProperty("项目名称") private String projectName; @ApiModelProperty("项目类型(0-房建,1-市政)") private Integer projectType; @ApiModelProperty("项目类别(A类、B类、C类)") private String category; @ApiModelProperty("纬度") private String latitude; @ApiModelProperty("经度") private String longitude; } ruoyi-system/src/main/java/com/ruoyi/system/vo/ProjectScoreData.java
New file @@ -0,0 +1,28 @@ package com.ruoyi.system.vo; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; /** * 项目评分数据 * * @author MiTao * @date 2026-01-21 */ @Data @ApiModel("项目评分数据") public class ProjectScoreData { @ApiModelProperty("项目ID") private Integer projectId; @ApiModelProperty("安全检查平均分") private Float safetyScore; @ApiModelProperty("设备检查平均分") private Float deviceScore; @ApiModelProperty("是否有设备得分") private boolean hasDeviceScore; } sql/jiashida.sql
File was deleted