diff --git a/fastbee-open-api/src/main/java/com/fastbee/controller/report/ReportController.java b/fastbee-open-api/src/main/java/com/fastbee/controller/report/ReportController.java index e44bf8ba..a5e0e64f 100644 --- a/fastbee-open-api/src/main/java/com/fastbee/controller/report/ReportController.java +++ b/fastbee-open-api/src/main/java/com/fastbee/controller/report/ReportController.java @@ -71,6 +71,17 @@ public class ReportController extends BaseController { return AjaxResult.success("报表生成任务已提交,请稍后查询状态", reportNo); } + /** + * 重新生成失败的报表(沿用首次生成的设备口径与时间窗口,生成新的报告编号) + */ + @ApiOperation("重新生成失败报表") + @PreAuthorize("@ss.hasPermi('iot:report:generate')") + @PostMapping("/regenerate/{reportNo}") + public AjaxResult regenerate(@PathVariable("reportNo") String reportNo) { + String newReportNo = reportAsyncExecutor.resubmitFailed(reportNo); + return AjaxResult.success("报表生成任务已重新提交,请稍后查询状态", newReportNo); + } + /** * 查询报表生成状态 */ diff --git a/fastbee-report/src/main/java/com/fastbee/report/executor/ReportAsyncExecutor.java b/fastbee-report/src/main/java/com/fastbee/report/executor/ReportAsyncExecutor.java index 148c12bd..3f234cd8 100644 --- a/fastbee-report/src/main/java/com/fastbee/report/executor/ReportAsyncExecutor.java +++ b/fastbee-report/src/main/java/com/fastbee/report/executor/ReportAsyncExecutor.java @@ -1,5 +1,6 @@ package com.fastbee.report.executor; +import com.alibaba.fastjson2.JSON; import com.fastbee.common.core.redis.RedisCache; import com.fastbee.common.enums.MonitorCategory; import com.fastbee.common.utils.StringUtils; @@ -33,6 +34,7 @@ import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.Map; import java.time.LocalDate; @@ -136,6 +138,63 @@ public class ReportAsyncExecutor { return reportNo; } + /** + * 重新生成失败的报告 + * + *
按落库的原始请求参数重新提交异步任务,返回新报告编号(原失败记录保留,便于追溯)。 + * 时间窗口固定回放首次生成时已解析出来的 start/end,避免"日报/周报"这类周期报告 + * 隔天重生成时被重新解析成另一个统计区间。
+ * + * @param reportNo 失败记录的报告编号 + * @return 新报告编号 + */ + public String resubmitFailed(String reportNo) { + ReportRecord record = reportRecordService.findByReportNo(reportNo); + if (record == null) { + throw new ReportException("报告不存在:" + reportNo); + } + if (!ReportRecord.STATUS_FAILED.equals(record.getStatus())) { + throw new ReportException("仅生成失败的报告可以重新生成"); + } + return submitAsyncTask(restoreRequest(record)); + } + + /** + * 还原首次生成时的请求参数 + * + *优先使用落库的 request_json(保留 groupId / deviceIds 多测点口径), + * 历史记录缺该列时退化为记录中的设备ID 单设备重建。
+ */ + private ReportRequest restoreRequest(ReportRecord record) { + ReportRequest request = null; + if (StringUtils.isNotEmpty(record.getRequestJson())) { + try { + request = JSON.parseObject(record.getRequestJson(), ReportRequest.class); + } catch (Exception e) { + log.warn("报告原始请求解析失败,退化为单设备重生成 reportNo={} json={}", + record.getReportNo(), record.getRequestJson(), e); + } + } + if (request == null) { + request = new ReportRequest(); + request.setDeviceId(record.getDeviceId()); + } + request.setStartTime(formatGmt8(record.getStartTime())); + request.setEndTime(formatGmt8(record.getEndTime())); + // 清空周期参数:start/end 已确定,若保留 period 会在 start/end 缺失时被按"今天"重新解析 + request.setPeriod(null); + request.setAnchorDate(null); + return request; + } + + private String formatGmt8(Date time) { + if (time == null) { + return null; + } + return LocalDateTime.ofInstant(time.toInstant(), ZoneOffset.ofHours(8)) + .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } + /** * 异步执行生成逻辑(支持多测点:网关子设备 / 直连多选 / 设备分组) */ diff --git a/fastbee-report/src/main/java/com/fastbee/report/record/ReportRecord.java b/fastbee-report/src/main/java/com/fastbee/report/record/ReportRecord.java index 5445a44a..e7ce453a 100644 --- a/fastbee-report/src/main/java/com/fastbee/report/record/ReportRecord.java +++ b/fastbee-report/src/main/java/com/fastbee/report/record/ReportRecord.java @@ -51,5 +51,11 @@ public class ReportRecord extends BaseEntity { private String errorMessage; + /** + * 首次生成时的原始请求 JSON(ReportRequest),供失败重生成回放, + * 保证 groupId / deviceIds 多测点口径与首次一致 + */ + private String requestJson; + private Long tenantId; } diff --git a/fastbee-report/src/main/java/com/fastbee/report/record/ReportRecordServiceImpl.java b/fastbee-report/src/main/java/com/fastbee/report/record/ReportRecordServiceImpl.java index 96dc3348..7cffb59a 100644 --- a/fastbee-report/src/main/java/com/fastbee/report/record/ReportRecordServiceImpl.java +++ b/fastbee-report/src/main/java/com/fastbee/report/record/ReportRecordServiceImpl.java @@ -1,5 +1,6 @@ package com.fastbee.report.record; +import com.alibaba.fastjson2.JSON; import com.fastbee.common.utils.DateUtils; import com.fastbee.report.core.ReportContext; import com.fastbee.report.mapper.ReportRecordMapper; @@ -34,6 +35,10 @@ public class ReportRecordServiceImpl implements ReportRecordService { record.setStartTime(toGmt8Date(context.getTimeWindow().getStartTime())); record.setEndTime(toGmt8Date(context.getTimeWindow().getEndTime())); record.setStatus(ReportRecord.STATUS_CREATING); + // 落库原始请求,失败后可用同一口径(含 groupId / deviceIds)重新生成 + if (context.getRequest() != null) { + record.setRequestJson(JSON.toJSONString(context.getRequest())); + } record.setTenantId(context.getTenantId()); record.setCreateBy(context.getCreateBy()); record.setCreateTime(now); diff --git a/sql/fastbee-report.sql b/sql/fastbee-report.sql index 78bdf81b..fd3c45bd 100644 --- a/sql/fastbee-report.sql +++ b/sql/fastbee-report.sql @@ -14,6 +14,18 @@ -- ------------------------------------------------------------ ALTER TABLE `iot_product` ADD COLUMN `monitor_category_code` VARCHAR(64) DEFAULT NULL COMMENT '监测分类编码(CRACK_METER/DEEP_DISPLACEMENT/AXIAL_FORCE/STATIC_LEVEL/VIBRATING_WIRE/STRAIN_GAUGE/WATER_LEVEL/RANGING_METER/INCLINOMETER/THERMOMETER/RAIN_GAUGE)' AFTER `category_name`; +-- ============================================================ +-- 1. 设备级基准点(相对测量用) +-- 静力水准等设备的读数是相对基准点的,累计变化量需按 +-- 「(当前读数 − 基准点当前读数) − (初始读数 − 基准点初始读数)」计算。 +-- deviceId 全局唯一,因此天然支持跨网关引用 +-- (如 GW2 的监测点引用 GW1 下的参考点)。 +-- 留空表示该设备为绝对量测量,不参与基准点换算。 +-- ============================================================ +ALTER TABLE iot_device + ADD COLUMN baseline_device_id BIGINT NULL COMMENT '基准点设备ID(相对测量用,留空=绝对量)' AFTER serial_number; + + -- 存量产品按产品名回填监测分类 UPDATE `iot_product` SET `monitor_category_code` = 'CRACK_METER' WHERE (`monitor_category_code` IS NULL OR `monitor_category_code` = '') @@ -107,6 +119,7 @@ CREATE TABLE `report_record` ( `file_name` varchar(256) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '文件名称', `file_url` varchar(512) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '文件地址', `error_message` varchar(1024) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '失败原因', + `request_json` varchar(2048) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '原始生成请求JSON(失败重生成用,保留 groupId/deviceIds 口径)', `tenant_id` bigint(20) DEFAULT NULL COMMENT '租户ID', `create_by` varchar(64) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '创建者', `create_time` datetime DEFAULT NULL COMMENT '创建时间', @@ -117,6 +130,22 @@ CREATE TABLE `report_record` ( UNIQUE KEY `uk_report_no` (`report_no`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='报告生成记录'; +-- ------------------------------------------------------------ +-- 2.4 report_record 原始请求列(失败重生成) +-- 全新库:上面 CREATE TABLE 已含 request_json 列; +-- 存量库:由下面的语句补列。用 information_schema 做存在性判断,可重复执行 +-- ------------------------------------------------------------ +SET @exist_col := (SELECT COUNT(1) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'report_record' + AND COLUMN_NAME = 'request_json'); +SET @ddl_sql := IF(@exist_col = 0, + 'ALTER TABLE `report_record` ADD COLUMN `request_json` varchar(2048) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT ''原始生成请求JSON(失败重生成用,保留 groupId/deviceIds 口径)'' AFTER `error_message`', + 'SELECT ''skip: report_record.request_json already exists'' AS info'); +PREPARE ddl_stmt FROM @ddl_sql; +EXECUTE ddl_stmt; +DEALLOCATE PREPARE ddl_stmt; + -- ------------------------------------------------------------ -- 3. oss_config:MinIO 默认配置(未购买云 OSS → 自建 MinIO) -- endpoint:后端连接 MinIO 的 S3 API 地址(若后端与 MinIO 同机可改为 http://127.0.0.1:9000) @@ -125,7 +154,7 @@ CREATE TABLE `report_record` ( -- ------------------------------------------------------------ UPDATE `oss_config` SET `status` = '1' WHERE `config_key` IN ('aliyun', 'qiniu'); -INSERT IGNORE INTO `oss_config` +INSERT INTO `oss_config` (tenant_id, tenant_name, config_key, access_key, secret_key, bucket_name, prefix, endpoint, domain, is_https, region, access_policy, status, del_flag, create_by, create_time, update_by, update_time, remark) VALUES @@ -135,12 +164,26 @@ VALUES -- ------------------------------------------------------------ -- 4. 历史报表菜单与权限(数据中心 3343 下新增) -- ------------------------------------------------------------ -INSERT IGNORE INTO `sys_menu` +INSERT INTO `sys_menu` (menu_id, menu_name, parent_id, order_num, `path`, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES (3610, '历史报表', 3343, 4, 'reportHistory', 'dataCenter/reportHistory', NULL, 1, 0, 'C', '0', '0', 'iot:report:list', 'documentation', 'admin', NOW(), 'admin', NOW(), '设备监测报告历史报表'), (3611, '报表生成', 3610, 1, '', NULL, NULL, 1, 0, 'F', '0', '0', 'iot:report:generate', '#', 'admin', NOW(), 'admin', NOW(), '生成并导出分析报告'); -INSERT IGNORE INTO `sys_menu_translate` (id, zh_cn, en_us) VALUES +INSERT INTO `sys_menu_translate` (id, zh_cn, en_us) VALUES (3610, '历史报表', 'Report history'), (3611, '报表生成', 'Generate report'); + +-- 初始数据 +INSERT INTO fastbee.iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, warning_rate, alarm_rate, control_rate, unit, create_by, create_time) VALUES(0, 0, 'DEEP_DISPLACEMENT', 'displacement', 20.000000, 30.000000, 50.000000, 2.000000, 3.000000, 5.000000, 'mm', 'admin', NOW()); +INSERT INTO fastbee.iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, warning_rate, alarm_rate, control_rate, unit, create_by, create_time) VALUES(0, 0, 'STATIC_LEVEL', 'settlement', 15.000000, 25.000000, 40.000000, 1.500000, 2.000000, 3.000000, 'mm', 'admin', NOW()); +INSERT INTO fastbee.iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, warning_rate, alarm_rate, control_rate, unit, create_by, create_time) VALUES(0, 0, 'CRACK_METER', 'crack_width', 0.300000, 0.500000, 1.000000, 0.050000, 0.100000, 0.200000, 'mm', 'admin', NOW()); +INSERT INTO fastbee.iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, warning_rate, alarm_rate, control_rate, unit, create_by, create_time) VALUES(0, 0, 'AXIAL_FORCE', 'axial_force', NULL, NULL, NULL, NULL, NULL, NULL, 'kN', 'admin', NOW()); +INSERT INTO fastbee.iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, warning_rate, alarm_rate, control_rate, unit, create_by, create_time) VALUES(0, 0, 'RANGING_METER', 'distance', 15.000000, 25.000000, 40.000000, 1.000000, 2.000000, 3.000000, 'mm', 'admin', NOW()); +INSERT INTO fastbee.iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, warning_rate, alarm_rate, control_rate, unit, create_by, create_time) VALUES(0, 0, 'INCLINOMETER', 'inclination', 1.000000, 2.000000, 3.000000, 0.100000, 0.200000, 0.500000, '°', 'admin', NOW()); +INSERT INTO fastbee.iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, warning_rate, alarm_rate, control_rate, unit, create_by, create_time) VALUES(0, 0, 'WATER_LEVEL', 'water_level', 0.500000, 1.000000, 2.000000, 0.500000, 1.000000, 1.500000, 'm', 'admin', NOW()); +INSERT INTO fastbee.iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, warning_rate, alarm_rate, control_rate, unit, create_by, create_time) VALUES(0, 0, 'STRAIN_GAUGE', 'strain', 500.000000, 800.000000, 1000.000000, 50.000000, 100.000000, 200.000000, 'με', 'admin', NOW()); +INSERT INTO fastbee.iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, warning_rate, alarm_rate, control_rate, unit, create_by, create_time) VALUES(0, 0, 'VIBRATING_WIRE', 'frequency', 30.000000, 40.000000, 50.000000, NULL, NULL, NULL, 'Hz', 'admin', NOW()); +INSERT INTO fastbee.iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, warning_rate, alarm_rate, control_rate, unit, create_by, create_time) VALUES(0, 0, 'THERMOMETER', 'temperature', 10.000000, 15.000000, 20.000000, 2.000000, 3.000000, 5.000000, '℃', 'admin', NOW()); +INSERT INTO fastbee.iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, warning_rate, alarm_rate, control_rate, unit, create_by, create_time) VALUES(0, 0, 'RAIN_GAUGE', 'rainfall', 25.000000, 50.000000, 100.000000, 25.000000, 50.000000, 100.000000, 'mm', 'admin', NOW()); + diff --git a/sql/init_things_threshold.sql b/sql/init_things_threshold.sql deleted file mode 100644 index f2596e94..00000000 --- a/sql/init_things_threshold.sql +++ /dev/null @@ -1,110 +0,0 @@ --- 报告阈值初始化脚本(基于 JGJ 8-2016、GB 50497-2019 等行业标准) --- 使用方式:在 fastbee 数据库中执行此脚本 --- 说明:product_id=0 表示全局默认,可被产品级/设备级配置覆盖 - --- 1. 深部位移(累计位移控制值通常 30-100mm,速率预警值 2mm/d) -INSERT INTO iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time) -VALUES (0, 0, 'DEEP_DISPLACEMENT', 'displacement', 20.00, 30.00, 50.00, 'mm', 'admin', NOW()); - --- 2. 静力水准(累计沉降控制值通常 20-60mm,速率预警值 2mm/d) -INSERT INTO iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time) -VALUES (0, 0, 'STATIC_LEVEL', 'settlement', 15.00, 25.00, 40.00, 'mm', 'admin', NOW()); - --- 3. 裂缝计(裂缝宽度控制值通常 0.3-1.0mm,变化量预警值 0.2mm) -INSERT INTO iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time) -VALUES (0, 0, 'CRACK_METER', 'crack_width', 0.30, 0.50, 1.00, 'mm', 'admin', NOW()); - --- 4. 轴力计(轴力控制值按设计值的 60%-80%,这里设为相对值) --- 注:轴力阈值通常需根据设计值动态计算,此处仅作示例 -INSERT INTO iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time) -VALUES (0, 0, 'AXIAL_FORCE', 'axial_force', NULL, NULL, NULL, 'kN', 'admin', NOW()); - --- 5. 测距仪(收敛变形控制值通常 20-50mm) -INSERT INTO iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time) -VALUES (0, 0, 'RANGING_METER', 'distance', 15.00, 25.00, 40.00, 'mm', 'admin', NOW()); - --- 6. 倾角计(倾斜角度控制值通常 1°-3°) -INSERT INTO iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time) -VALUES (0, 0, 'INCLINOMETER', 'inclination', 1.00, 2.00, 3.00, '°', 'admin', NOW()); - --- 7. 水位计(水位变化控制值通常 0.5-2.0m) -INSERT INTO iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time) -VALUES (0, 0, 'WATER_LEVEL', 'water_level', 0.50, 1.00, 2.00, 'm', 'admin', NOW()); - --- 8. 应变计(应变控制值通常 ±1000με) -INSERT INTO iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time) -VALUES (0, 0, 'STRAIN_GAUGE', 'strain', 500.00, 800.00, 1000.00, 'με', 'admin', NOW()); - --- 9. 振弦式传感器(频率变化控制值通常 ±50Hz) -INSERT INTO iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time) -VALUES (0, 0, 'VIBRATING_WIRE', 'frequency', 30.00, 40.00, 50.00, 'Hz', 'admin', NOW()); - --- 10. 温度计(温度变化控制值通常 ±15℃) -INSERT INTO iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time) -VALUES (0, 0, 'THERMOMETER', 'temperature', 10.00, 15.00, 20.00, '℃', 'admin', NOW()); - --- 11. 雨量计(累计降雨量预警值通常 50mm/d) -INSERT INTO iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time) -VALUES (0, 0, 'RAIN_GAUGE', 'rainfall', 25.00, 50.00, 100.00, 'mm', 'admin', NOW()); - --- 说明: --- 1. 以上阈值基于 JGJ 8-2016《建筑变形测量规范》、GB 50497-2019《建筑基坑工程监测标准》 --- 等行业标准,适用于一般建筑工程。实际项目应根据设计要求调整。 --- 2. product_id=0 表示全局默认,如需针对特定产品,请复制以上记录并修改 product_id。 --- 3. metric_identifier 需与物模型标识符匹配,如不匹配则阈值不生效。 --- 4. 预警值 < 报警值 < 控制值,系统会按当前值与阈值的比较自动判断安全等级。 - - --- 结构安全监测改造:设备级基准点 + 阈值速率维度(双控) --- 执行前请确认已备份,且字段尚未存在(MySQL 不支持 ADD COLUMN IF NOT EXISTS,重复执行会报错) - --- ============================================================ --- 1. 设备级基准点(相对测量用) --- 静力水准等设备的读数是相对基准点的,累计变化量需按 --- 「(当前读数 − 基准点当前读数) − (初始读数 − 基准点初始读数)」计算。 --- deviceId 全局唯一,因此天然支持跨网关引用 --- (如 GW2 的监测点引用 GW1 下的参考点)。 --- 留空表示该设备为绝对量测量,不参与基准点换算。 --- ============================================================ -ALTER TABLE iot_device - ADD COLUMN baseline_device_id BIGINT NULL COMMENT '基准点设备ID(相对测量用,留空=绝对量)' AFTER serial_number; - --- ============================================================ --- 2. 阈值表:变化速率维度 --- 依据 GB 50497-2019 第 8.0.4 条(强制性条文): --- 监测报警按「累计变化量」与「变化速率」双控,任一超限即取较严重等级。 --- 单位统一为「单位/天」,例如 mm/d、kN/d。 --- ============================================================ -ALTER TABLE iot_things_threshold - ADD COLUMN warning_rate DECIMAL(18, 6) NULL COMMENT '变化速率预警值(单位/天)' AFTER control_value, - ADD COLUMN alarm_rate DECIMAL(18, 6) NULL COMMENT '变化速率报警值(单位/天)' AFTER warning_rate, - ADD COLUMN control_rate DECIMAL(18, 6) NULL COMMENT '变化速率控制值(单位/天)' AFTER alarm_rate; - --- ============================================================ --- 附:各行业规范推荐取值(供配置产品阈值时参考,不写入全局默认值) --- 未配置阈值的产品将输出「待评估」,不会伪造为「安全」。 --- --- 监测项目 累计控制值 速率预警值 依据 --- 深部位移(测斜) 40~50mm (0.4%~0.5%H) 3~5 mm/d GB 50497-2019 --- 支护结构顶部位移 25~30mm (0.2%~0.3%H) 3~5 mm/d GB 50497-2019 --- 周边地表沉降 25~35mm 3~5 mm/d GB 50497-2019 --- 管片结构沉降 硬土 10~20 / 软土 20~30mm 2~3 mm/d GB 50911-2013 --- 管片结构隆起 10mm 1 mm/d GB 50911-2013 --- 净空收敛(测距仪) 0.2%D 或 10mm 3 mm/d GB 50911-2013 --- 差异沉降 0.04%La 3 mm/d GB 50911-2013 --- 地下水位变化 1000~2000mm(常年变幅外) 500 mm/d GB 50497-2019 表8.0.5 --- 裂缝(结构性/既有) 1.5~3mm 持续发展 GB 50497-2019 表8.0.5 --- 裂缝(地表/新增) 1~3mm 持续发展 GB 50497-2019 表8.0.5 --- 锚索/支撑轴力 构件承载力设计值的 80% ≥80% 且持续增长 GB 50497-2019 8.0.6 --- 管线沉降(刚性/压力) 10~20mm 2 mm/d GB 50497-2019 表8.0.5 --- 构筑物及无压管线 20~25mm 2 mm/d GB 50157-2013 --- 建筑物倾斜 — 2°/d GB 50157-2013 --- --- 三级取值比例(设计允许值为基准): --- 预警值 ≈ 60%~70% 报警值 ≈ 80% 控制值 = 100% --- 速率细则: --- 达到规定值、或连续 3 次超过该值的 70%,即应预警; --- 底板浇筑完成后,速率不宜超过速率预警值的 70%。 --- 注:雨量计不是安全指标,规范中是工况触发因素(雨季/暴雨后须加测), --- 不应参与预警判定。 --- ============================================================ diff --git a/yack-web/docs/data-center-history-chart-solution.md b/yack-web/docs/data-center-history-chart-solution.md deleted file mode 100644 index 2f0a0692..00000000 --- a/yack-web/docs/data-center-history-chart-solution.md +++ /dev/null @@ -1,165 +0,0 @@ -# 数据中心 · 历史记录 · 曲线趋势图改造方案 - -## 1. 背景与问题 - -### 1.1 业务场景 - -路径:**数据中心 → 历史记录**([`history.vue`](../src/views/dataCenter/history.vue))。 - -页面含三个 Tab:**设备**、**网关设备**、**场景**,各自有「曲线趋势图」或网关下的计算量 / 多设备对比 / 单子设备折线图。 - -### 1.2 原始痛点 - -| 问题 | 说明 | -|------|------| -| 多曲线「压成直线」 | 多物模型量级差异大(如位移 168、角度 3、电压 12),共用单 Y 轴时范围被拉大,小幅波动在图上不可见。 | -| 多 Y 轴布局异常 | 为每条曲线建隐藏 Y 轴时,ECharts 会为多轴计算偏移,曲线一多时绘图区被挤到一侧。 | -| 图例占用空间 | 网关多设备对比图例项极多,默认换行占满高度,挤压 grid。 | -| X 轴可读性 | 时间点密集时标签重叠。 | -| 垂直空间不足 | 固定 480px 高度时,几十条归一化曲线仍显拥挤。 | - ---- - -## 2. 总体思路 - -1. **独立 Y 轴模式**:不再使用「每 series 一个 Y 轴」,改为 **按每条曲线自身 min-max 归一化到 [-100, 100]**,**共用一条数值 Y 轴**,避免多轴布局副作用;**Tooltip 始终展示原始值**。 -2. **共享 Y 轴模式**:保留原逻辑,单 Y 轴 + `scale: true`,便于对比绝对量级。 -3. **归一化坐标展示**:独立模式下 Y 轴可见,刻度约 -100%~100%,并附说明性网格线。 -4. **网关图**:图例与 grid 与设备 Tab 对齐策略;X 轴旋转 + 对齐刻度;必要时配合底部留白与 dataZoom。 -5. **动态高度**:按 **series 数量** 增加图表容器高度(有上下限),并在 DOM 更新后 `resize`。 - ---- - -## 3. 功能开关 - -- **数据字段**:`independentYAxis`(默认 `true`)。 -- **UI**:设备 / 网关 / 场景三个图表卡片头部 **「独立Y轴 | 共享Y轴」** 开关。 -- **切换行为**:`handleYAxisModeChange` 在 `$nextTick` 内按当前 Tab 与已有数据调用对应 `draw*Line`,无需重新请求接口。 - ---- - -## 4. 核心实现 - -### 4.1 归一化(独立模式) - -**方法**:`applyIndependentScale(series)` - -- 对每条 `series.data` 中可解析为数字的点,取该 series 全体有效点的 `min`、`max`。 -- 映射公式:`value = ((num - min) / (max - min)) * 200 - 100`,落在 **[-100, 100]**。 -- **常量序列**(`max === min`):映射为 **0**(中间水平线)。 -- 每个数据点存为 `{ value, _original }`,`_original` 为归一化前的原始值(兼容负数与非数字展示)。 - -**返回的 Y 轴配置**(单轴): - -- `min: -100`, `max: 100` -- `axisLine` / `axisTick` 显示 -- `axisLabel.formatter`: 显示为 `xx%`(表示相对本曲线量程的位置,非物理单位) -- `splitLine`:虚线网格,便于读趋势 - -### 4.2 Tooltip - -**方法**:`independentScaleTooltip()` - -- `trigger: 'axis'` -- `formatter`:时间轴标签 + 各 series **原始值**(从 `p.data._original` 读取) - -### 4.3 共享模式 - -- `yAxis`: `{ type: 'value', scale: true }` -- `tooltip`: `{ trigger: 'axis' }`(默认) - -### 4.4 涉及的绘图方法 - -| 方法 | Tab / 场景 | -|------|------------| -| `drawDevLine` | 设备 | -| `drawGwCalcLine` | 网关 · 计算量 | -| `drawGwSingleLine` | 网关 · 单子设备 | -| `drawSubCompareLine` | 网关 · 多设备同一变量对比 | -| `drawSceneLine` | 场景 | - -以上方法在 `independentYAxis === true` 时均走 `applyIndependentScale` + `independentScaleTooltip`。 - -### 4.5 网关图布局要点 - -- `legend`: `align: 'right'`, `left: '3%'`, `top: '3%'`(与多行图例协调) -- `grid.top: '30%'`,为图例留出区域,避免与曲线重叠 -- `xAxis`: `boundaryGap: true`, `axisTick.alignWithLabel: true`, `axisLabel.rotate: 30` -- `dataZoom`: `inside` + 滑块,便于时间范围缩放 -- `toolbox`: 数据视图、还原、保存图片(与设备 Tab 能力对齐) - -### 4.6 动态图表高度 - -**数据字段**(像素): - -- `devChartHeight`, `gwCalcChartHeight`, `gwSingleChartHeight`, `subCompareChartHeight`, `sceneChartHeight`(默认 480) - -**方法**:`calcChartHeight(seriesCount)` - -```text -height = clamp(480, seriesCount * 14 + 360, 1200) -``` - -即:最少 480px,随曲线数量增加,**最多 1200px**。 - -**模板**:各图表容器使用 `:style="{ height: xxxChartHeight + 'px', ... }"`。 - -**绘制流程**: - -1. 先算好 `series` 与 `series.length`,赋值对应 `xxxChartHeight` -2. `this.$nextTick(() => { init/clear → setOption → chart.resize() })` - 保证先根据新高度更新 DOM,再初始化/重绘 ECharts。 - ---- - -## 5. 数据流(简图) - -```mermaid -flowchart LR - subgraph input [数据] - API[历史接口] - end - subgraph mode [模式] - IND[独立Y轴] - SHR[共享Y轴] - end - subgraph render [渲染] - NORM[逐条 min-max 到 -100~100] - ONEAXIS[单 Y 轴] - TT[Tooltip 原始值] - end - API --> IND - API --> SHR - IND --> NORM --> ONEAXIS - ONEAXIS --> TT - SHR --> ONEAXIS -``` - ---- - -## 6. 使用说明(给产品/测试) - -1. **独立 Y 轴(推荐多曲线看趋势)**:纵轴为各曲线**自身量程内的相对位置**(-100%~100%),对比不同物理量时看形状;**真实数值以 Tooltip 与下方表格为准**。 -2. **共享 Y 轴**:纵轴为**真实数值**,适合量级接近、需比大小的变量;量级差很大时仍可能出现「看起来像直线」。 -3. **曲线很多时**:图会自动变高(上限约 1200px),页面可纵向滚动查看全图。 - ---- - -## 7. 相关文件 - -| 文件 | 说明 | -|------|------| -| [`yack-web/src/views/dataCenter/history.vue`](../src/views/dataCenter/history.vue) | 全部图表逻辑、开关、高度、归一化 | -| [`yack-web/src/api/data/center.js`](../src/api/data/center.js) | 历史数据接口(未因本方案变更协议) | - ---- - -## 8. 版本与维护建议 - -- 若需调整「多高算够」,改 `calcChartHeight` 中的系数 `14`、基础 `360`、上下限 `480`/`1200` 即可。 -- 若产品要求 Y 轴显示「归一化」文案而非 `%`,可在 `applyIndependentScale` 返回的 `axisLabel` 中改为固定文案或 `name`/`nameLocation`。 -- 新增同类折线图时,复用:`applyIndependentScale` / `independentScaleTooltip` / `calcChartHeight` + `$nextTick` + `resize` 模式。 - ---- - -*文档对应实现:历史记录页曲线趋势图(多 Tab、独立/共享 Y 轴、归一化 -100~100、动态高度)。* diff --git a/yack-web/src/api/iot/report.js b/yack-web/src/api/iot/report.js index 42927ac3..28aaf6e1 100644 --- a/yack-web/src/api/iot/report.js +++ b/yack-web/src/api/iot/report.js @@ -9,6 +9,14 @@ export function generateDeviceReport(data) { }); } +// 重新生成失败的报告(沿用首次口径,返回新的报告编号) +export function regenerateReport(reportNo) { + return request({ + url: '/iot/report/regenerate/' + reportNo, + method: 'post', + }); +} + // 历史报表分页列表(仅未过期) export function listReportRecords(query) { return request({ diff --git a/yack-web/src/assets/icons/svg/WORD.svg b/yack-web/src/assets/icons/svg/word.svg similarity index 100% rename from yack-web/src/assets/icons/svg/WORD.svg rename to yack-web/src/assets/icons/svg/word.svg diff --git a/yack-web/src/views/dataCenter/reportHistory.vue b/yack-web/src/views/dataCenter/reportHistory.vue index 1f099d86..ffa7b429 100644 --- a/yack-web/src/views/dataCenter/reportHistory.vue +++ b/yack-web/src/views/dataCenter/reportHistory.vue @@ -47,7 +47,7 @@