diff --git a/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysUserSettingController.java b/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysUserSettingController.java index e4f4b7eb..66c41b2e 100644 --- a/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysUserSettingController.java +++ b/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysUserSettingController.java @@ -4,6 +4,7 @@ import com.fastbee.common.annotation.Log; import com.fastbee.common.core.controller.BaseController; import com.fastbee.common.core.domain.AjaxResult; import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.enums.ReportTemplate; import com.fastbee.common.utils.SecurityUtils; import com.fastbee.system.domain.LoginRedirectOption; import com.fastbee.system.domain.SysUserSetting; @@ -92,7 +93,8 @@ public class SysUserSettingController extends BaseController { public AjaxResult update(@RequestBody SysUserSetting setting) { if (setting.getLoginRedirect() == null && setting.getQueryGatewayChild() == null - && setting.getShowChild() == null) { + && setting.getShowChild() == null + && setting.getReportTemplate() == null) { return AjaxResult.error("没有需要保存的配置项"); } // 只有在本次确实要改「登录后显示页面」时才做白名单校验; @@ -100,11 +102,33 @@ public class SysUserSettingController extends BaseController { if (setting.getLoginRedirect() != null && !isValidLoginRedirect(setting.getLoginRedirect())) { return AjaxResult.error("登录后跳转页面不合法,请从系统菜单中选择"); } + // 报告模板版式只接受已注册的枚举编码,挡住脏数据/未发布的模板 + if (setting.getReportTemplate() != null && !isValidReportTemplate(setting.getReportTemplate())) { + return AjaxResult.error("报告导出模板不合法,请从可选项中选择"); + } // 审计字段由服务端填充,不接受前端传入,避免越权写入他人操作名 userSettingService.updatePartial(SecurityUtils.getUserId(), setting, SecurityUtils.getUsername()); return AjaxResult.success(); } + /** + * 校验报告模板编码为已注册的模板版式 + * + *

取值即 {@code ReportTemplate.code},未注册的一律拒绝(而非静默回退默认版式), + * 避免用户选到未发布的模板后导出行为与预期不符。

+ */ + private boolean isValidReportTemplate(String code) { + if (code == null || code.trim().isEmpty()) { + return false; + } + for (ReportTemplate template : ReportTemplate.values()) { + if (template.getCode().equalsIgnoreCase(code.trim())) { + return true; + } + } + return false; + } + /** * 校验跳转目标为系统内已注册的一级页签 * diff --git a/fastbee-admin/src/main/resources/application.yml b/fastbee-admin/src/main/resources/application.yml index 4ea2e8cf..569f8a68 100644 --- a/fastbee-admin/src/main/resources/application.yml +++ b/fastbee-admin/src/main/resources/application.yml @@ -233,12 +233,3 @@ map: gaode_secret: d0561ad20142e74f8c59edc5deafdbe4 tianditu_ak: b920e6076a06aa279c974f5f76cf746e -# 设备监测报告导出配置(全局一刀切,test/prod 环境共用) -report: - # 模板编码(取值即 templates/ 下的模板名,V2 为按分类取默认的版本标记): - # 留空 或 V2 → 完整版,按监测分类走 DEVICE_{CATEGORY}_V2(共 10 节) - # DCPReport → DCP 平台专用三节版式(项目概况 / 测点数据 / 时程曲线), - # 测点表只输出 ThingsThreshold.is_primary=1 的主监测指标 - # 默认保持完整版(V2):切换版式是交付格式变更,需现场确认后由运维显式改为 DCPReport - template-code: V2 - diff --git a/fastbee-common/src/main/java/com/fastbee/common/constant/UserSettingConstants.java b/fastbee-common/src/main/java/com/fastbee/common/constant/UserSettingConstants.java index 5bc2c663..f1e74650 100644 --- a/fastbee-common/src/main/java/com/fastbee/common/constant/UserSettingConstants.java +++ b/fastbee-common/src/main/java/com/fastbee/common/constant/UserSettingConstants.java @@ -1,5 +1,7 @@ package com.fastbee.common.constant; +import com.fastbee.common.enums.ReportTemplate; + import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -80,6 +82,17 @@ public class UserSettingConstants { */ public static final boolean DEFAULT_SHOW_CHILD = false; + /** + * 报告导出模板版式的默认值 + * + *

对应 {@code sys_user_setting.report_template},取值即 {@code ReportTemplate.code}: + * 默认 {@code V2}(完整版,按监测分类取 {@code DEVICE_{CATEGORY}_V2})= 与引入本开关前完全一致。 + * 用户可在「系统设置」页切换为 {@code DCPReport}(DCP 平台三节版式)等其它版式。 + *

维度归属:报告导出由登录用户触发(异步线程已显式透传 SecurityContext,能拿到 userId), + * 故为 per-user 设置;无登录态不允许导出,不做全局兜底。 + */ + public static final String DEFAULT_REPORT_TEMPLATE = ReportTemplate.FULL_VERSION.getCode(); + private UserSettingConstants() { } diff --git a/fastbee-common/src/main/java/com/fastbee/common/enums/ReportTemplate.java b/fastbee-common/src/main/java/com/fastbee/common/enums/ReportTemplate.java new file mode 100644 index 00000000..23164d8b --- /dev/null +++ b/fastbee-common/src/main/java/com/fastbee/common/enums/ReportTemplate.java @@ -0,0 +1,92 @@ +package com.fastbee.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 设备监测报告导出模板版式 + * + *

版式是现场交付物,每种版式对应一套固定的渲染策略(是否只出主指标、是否按分类拆分模板文件), + * 因此用枚举把「模板编码 ↔ 渲染行为」内聚在一起,新增版式只需新增枚举项 + 放置模板文件, + * 渲染层无需再散落字符串比较。

+ * + *

为什么是枚举而不是自由字符串:早期实现把「是否只出主指标」硬绑在 + * {@code templateCode == "DEVICE_SUMMARY"} 上,一旦模板改名判定会静默失效(不报错, + * 辅助量列又冒出来)。枚举把编码与行为绑定,改名只动枚举,判定入口唯一。

+ * + *

模板编码落库语义:{@link #getCode()} 即 {@code sys_user_setting.report_template} + * 的存储值,同时是 {@code templates/} 下模板文件名的决定依据 —— + * {@link #isCategoryScoped()} 为 false 时直接用 code 拼文件名,为 true 时拼分类前缀。

+ * + * @author FastBee + * @date 2026-10-06 + */ +@Getter +@AllArgsConstructor +public enum ReportTemplate { + + /** + * 完整版(V2):按监测分类取 {@code DEVICE_{CATEGORY}_V2},共 10 节 + * + *

默认版式。测点表沿用「主指标完整四列 + 辅助量合并列」原口径, + * 辅助物理量(水温、电压、角度…)保留为「本次值(变化量)」一列用于分析影响量。

+ */ + FULL_VERSION("V2", "完整版", false, true), + + /** + * DCP 平台专用三节版式(项目概况 / 测点数据 / 时程曲线) + * + *

全分类共用一份 {@code templates/DCPReport.docx}(首行标题用 + * {@code {{device.categoryDesc}}} 占位,故不按分类拆分)。测点表只输出 + * {@code iot_things_threshold.is_primary=1} 的主监测指标,非主监测指标整列不出现。

+ * + *

命名对齐 DCP 平台(平台实时数据推送,见 + * {@code com.fastbee.iot.platform.adapter.dcp})要求的交付格式;注意与推送侧的 + * DCP 适配器区分:本枚举属报告导出,彼处属数据推送。

+ */ + DCP_REPORT("DCPReport", "DCP 三节版式", true, false); + + /** + * 模板编码(存库值;非分类拆分时即 templates/ 下模板文件名) + */ + private final String code; + + /** + * 模板版式中文说明(前端下拉展示用) + */ + private final String desc; + + /** + * 测点表是否只输出主监测指标(is_primary=1) + */ + private final boolean primaryOnly; + + /** + * 是否按监测分类拆分模板文件 + * + *

true:模板文件名为 {@code DEVICE_{CATEGORY}_V2}(每分类一份); + * false:全分类共用一份 {@code {code}.docx}。

+ */ + private final boolean categoryScoped; + + /** + * 按编码解析模板版式 + * + *

未知编码回退 {@link #FULL_VERSION}(完整版)而非抛异常:报告生成不能因为 + * 一个脏配置就整体失败,回退到默认版式至少能产出报告,同时由调用方记录告警。

+ * + * @param code 模板编码(可为 null / 空串) + * @return 匹配的模板版式;空值或未识别时返回 FULL_VERSION + */ + public static ReportTemplate fromCode(String code) { + if (code == null || code.trim().isEmpty()) { + return FULL_VERSION; + } + for (ReportTemplate template : values()) { + if (template.code.equalsIgnoreCase(code.trim())) { + return template; + } + } + return FULL_VERSION; + } +} 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 a5e0e64f..48a9e1bc 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 @@ -6,6 +6,7 @@ import com.fastbee.common.core.domain.AjaxResult; import com.fastbee.common.core.page.TableDataInfo; import com.fastbee.common.core.redis.RedisCache; import com.fastbee.common.enums.MonitorCategory; +import com.fastbee.common.enums.ReportTemplate; import com.fastbee.common.exception.ServiceException; import com.fastbee.common.utils.StringUtils; import com.fastbee.oss.service.OssClient; @@ -160,4 +161,21 @@ public class ReportController extends BaseController { .collect(Collectors.toList()); return AjaxResult.success(list); } + + /** + * 报告导出模板版式列表(枚举驱动,供系统设置页下拉选择 per-user 模板) + */ + @ApiOperation("报告导出模板版式列表") + @GetMapping("/templates") + public AjaxResult templates() { + List> list = Arrays.stream(ReportTemplate.values()) + .map(t -> { + Map m = new HashMap<>(); + m.put("code", t.getCode()); + m.put("desc", t.getDesc()); + return m; + }) + .collect(Collectors.toList()); + return AjaxResult.success(list); + } } diff --git a/fastbee-report/pom.xml b/fastbee-report/pom.xml index 9a94506d..ad2eb926 100644 --- a/fastbee-report/pom.xml +++ b/fastbee-report/pom.xml @@ -38,6 +38,12 @@ fastbee-iot-data + + + com.fastbee + fastbee-system-service + + com.deepoove diff --git a/fastbee-report/src/main/java/com/fastbee/report/config/ReportProperties.java b/fastbee-report/src/main/java/com/fastbee/report/config/ReportProperties.java deleted file mode 100644 index 3d4d7413..00000000 --- a/fastbee-report/src/main/java/com/fastbee/report/config/ReportProperties.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.fastbee.report.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -/** - * 报告导出配置 - * - *

只控制「用哪一套模板」,全局一刀切。

- * - *

模板版式(2026-10-04 起只有两种,不要按字面值硬判断):

- *
    - *
  • V2:完整版,按监测分类取 {@code DEVICE_{CATEGORY}_V2},共 10 节;
  • - *
  • DCPReport:DCP 平台专用的三节版式(项目概况 / 测点数据 / 时程曲线), - * 测点表只输出 {@code iot_things_threshold.is_primary=1} 的主监测指标。 - * 全分类共用一份 {@code templates/DCPReport.docx},首行标题用 - * {@code {{device.categoryDesc}}} 占位,因此不再按分类拆分。
  • - *
- * - *

为什么叫 DCPReport:这套三节版式是 DCP 平台(平台实时数据推送,见 - * {@code com.fastbee.iot.platform.adapter.dcp})要求交付的报告格式,命名与用途对齐, - * 便于日后回溯「为什么报告有两套版式」。注意与推送侧的 DCP 适配器区分:本常量属 - * 报告导出,彼处属数据推送,两者是不同业务线。

- * - *

为什么不按分类/产品白名单:报告模板是现场交付物,格式要求由业主一次性约定, - * 不会一部分产品要三节版、一部分要完整版。全局开关让「改配置即全量生效」, - * 也避免白名单长期无人维护、导致部分设备悄悄导出错版本。

- * - * @author FastBee - * @date 2026-10-04 - */ -@Data -@Component -@ConfigurationProperties(prefix = "report") -public class ReportProperties { - - /** 完整版模板的版本标记:配置值等于它(或为空)时按监测分类取默认模板 */ - public static final String FULL_VERSION_CODE = "V2"; - - /** - * DCP 平台三节版式的模板编码,与 {@code templates/DCPReport.docx} 文件名严格一致 - */ - public static final String DCP_REPORT_TEMPLATE_CODE = "DCPReport"; - - /** - * 报告模板编码 - * - *

取值:空 / {@code V2} → 完整版;{@code DCPReport} → DCP 三节版式。

- */ - private String templateCode; - - /** - * 是否启用 DCP 三节版式(只出项目概况、isPrimary 测点表、时程曲线) - * - *

唯一判定入口。装配器与渲染层都只依赖这个方法,不各自比对模板编码字符串 —— - * 早期版本把「是否只出主指标」硬绑在 {@code templateCode == "DEVICE_SUMMARY"} 上, - * 一旦模板改名,判定会静默失效(不报错,辅助量列又冒出来),是必须避免的耦合。

- */ - public boolean isDcpReportMode() { - return DCP_REPORT_TEMPLATE_CODE.equalsIgnoreCase(trimmed()); - } - - /** - * 实际生效的模板编码 - * - *

用于 {@code ReportModel.templateCode} 落值与日志;非 DCP 版式返回 {@code V2}, - * 由调用方拼分类前缀得到具体文件名。

- */ - public String trimmed() { - return templateCode == null ? "" : templateCode.trim(); - } -} diff --git a/fastbee-report/src/main/java/com/fastbee/report/core/ReportAssembler.java b/fastbee-report/src/main/java/com/fastbee/report/core/ReportAssembler.java index 2dfde331..80881d24 100644 --- a/fastbee-report/src/main/java/com/fastbee/report/core/ReportAssembler.java +++ b/fastbee-report/src/main/java/com/fastbee/report/core/ReportAssembler.java @@ -1,12 +1,14 @@ package com.fastbee.report.core; import com.fastbee.common.enums.ReportPeriod; +import com.fastbee.common.enums.ReportTemplate; import com.fastbee.common.utils.DateUtils; import com.fastbee.common.utils.StringUtils; -import com.fastbee.report.config.ReportProperties; +import com.fastbee.report.exception.ReportException; import com.fastbee.report.model.AnalysisResult; import com.fastbee.report.model.ReportModel; import com.fastbee.report.model.Statistics; +import com.fastbee.system.service.ISysUserSettingService; import org.springframework.stereotype.Component; import javax.annotation.Resource; @@ -23,7 +25,7 @@ import java.util.UUID; public class ReportAssembler { @Resource - private ReportProperties reportProperties; + private ISysUserSettingService userSettingService; public ReportModel assemble(ReportContext context, AnalysisResult analysis) { return assemble(context, analysis, 1); @@ -33,8 +35,8 @@ public class ReportAssembler { * @param pointCount 参与分析的测点数(>1 时为多测点报告,设备信息展示测点规模) */ public ReportModel assemble(ReportContext context, AnalysisResult analysis, int pointCount) { - // 模板选择:DCP 版式为全局配置(report.template-code),不拼分类后缀,全分类共用一份模板 - String templateCode = resolveTemplateCode(context); + // 模板选择:per-user(sys_user_setting.report_template),必须能拿到 userId + ReportTemplate template = resolveTemplate(context); ReportPeriod period = context.getTimeWindow().getPeriod(); String serialNumber = context.getDevice().getSerialNumber(); @@ -68,7 +70,7 @@ public class ReportAssembler { return ReportModel.builder() .reportNo(context.getReportNo()) - .templateCode(templateCode) + .templateCode(resolveTemplateFileName(template, context)) .fileName(fileName) .periodDesc(context.getTimeWindow().getDescription()) .period(period) @@ -82,32 +84,45 @@ public class ReportAssembler { .metricName(primary == null ? null : primary.getModelName()) .metricNames(metricNames) .metricUnit(primary == null ? null : primary.getUnit()) - // DCP 版式:测点表只出 is_primary=1 的主监测指标(未勾选时由渲染层输出提示行) - .primaryOnly(ReportProperties.DCP_REPORT_TEMPLATE_CODE.equals(templateCode)) + // 精简版(DCP 三节版式):测点表只出 is_primary=1 的主监测指标 + .primaryOnly(template.isPrimaryOnly()) .analysis(analysis) .build(); } /** - * 解析报告模板编码 + * 解析报告模板版式(per-user) * - *

取值优先级:

- *
    - *
  1. 配置 {@code report.template-code=DCPReport} → DCP 平台三节版式, - * 全分类共用一份 {@code templates/DCPReport.docx},不拼分类后缀;
  2. - *
  3. 配空或配成 {@code V2} → 按监测分类取完整版 {@code DEVICE_{CATEGORY}_V2}。
  4. - *
+ *

报告导出由登录用户触发(异步线程已显式透传 SecurityContext,userId 落在 + * {@code ReportContext.userId}),因此模板版式是 per-user 偏好,从 + * {@code sys_user_setting.report_template} 读取。无登录态(userId 为 null)时直接失败: + * 报告导出本就不允许无登录态触发,不做全局兜底,避免后台任务用错版式。

* *

为什么不读 {@code request.templateCode}:那是历史字段,从未被真正使用过 * (此前本行是硬编码),前端也从未传值。放开它等于给外部一个可任意指定 classpath * 模板文件名的入口,而报告模板与渲染策略强耦合(动态表列、图表位、poi-tl 策略绑定), - * 传错编码会直接渲染失败或产出错版式。格式是现场交付物,由运维改配置决定即可。

+ * 传错编码会直接渲染失败或产出错版式。版式由用户在系统设置页选定即可。

*/ - private String resolveTemplateCode(ReportContext context) { - if (reportProperties != null && reportProperties.isDcpReportMode()) { - return ReportProperties.DCP_REPORT_TEMPLATE_CODE; + private ReportTemplate resolveTemplate(ReportContext context) { + Long userId = context == null ? null : context.getUserId(); + if (userId == null) { + throw new ReportException("无法确定报告导出人,请登录后重试"); } - return "DEVICE_" + context.getCategory().name() + "_" + ReportProperties.FULL_VERSION_CODE; + String code = userSettingService.selectByUserId(userId).getReportTemplate(); + return ReportTemplate.fromCode(code); + } + + /** + * 由版式与分类拼出模板文件名(解析器按它从 classpath 定位 {@code templates/{name}.docx}) + * + *

按分类拆分的版式(完整版 V2)拼 {@code DEVICE_{CATEGORY}_V2}; + * 全分类共用一份的版式(DCP 三节)直接用 {@code code} 拼 {@code DCPReport}。

+ */ + private String resolveTemplateFileName(ReportTemplate template, ReportContext context) { + if (template.isCategoryScoped()) { + return "DEVICE_" + context.getCategory().name() + "_" + template.getCode(); + } + return template.getCode(); } /** diff --git a/fastbee-report/src/main/java/com/fastbee/report/core/ReportContext.java b/fastbee-report/src/main/java/com/fastbee/report/core/ReportContext.java index d45a0dbe..e9c959ec 100644 --- a/fastbee-report/src/main/java/com/fastbee/report/core/ReportContext.java +++ b/fastbee-report/src/main/java/com/fastbee/report/core/ReportContext.java @@ -70,6 +70,9 @@ public class ReportContext { /** 租户ID(从主线程传递) */ private Long tenantId; + /** 创建人用户ID(从主线程传递,报告模板版式 per-user 判定依赖它) */ + private Long userId; + /** * 该物模型是否被用户显式指定为主监测指标 */ diff --git a/fastbee-report/src/main/java/com/fastbee/report/core/ReportContextBuilder.java b/fastbee-report/src/main/java/com/fastbee/report/core/ReportContextBuilder.java index 64f346dc..540c3853 100644 --- a/fastbee-report/src/main/java/com/fastbee/report/core/ReportContextBuilder.java +++ b/fastbee-report/src/main/java/com/fastbee/report/core/ReportContextBuilder.java @@ -148,10 +148,12 @@ public class ReportContextBuilder { // 从主线程获取用户信息,传递到异步线程 String username = null; Long tenantId = null; + Long userId = null; try { LoginUser loginUser = SecurityUtils.getLoginUser(); if (loginUser != null) { username = loginUser.getUsername(); + userId = loginUser.getUserId(); if (loginUser.getUser() != null && loginUser.getUser().getDept() != null) { tenantId = loginUser.getUser().getDept().getDeptUserId(); } @@ -161,6 +163,7 @@ public class ReportContextBuilder { } context.setCreateBy(username); context.setTenantId(tenantId); + context.setUserId(userId); return context; } diff --git a/fastbee-report/src/main/java/com/fastbee/report/model/AnalysisResult.java b/fastbee-report/src/main/java/com/fastbee/report/model/AnalysisResult.java index 8d635097..4e48a8ab 100644 --- a/fastbee-report/src/main/java/com/fastbee/report/model/AnalysisResult.java +++ b/fastbee-report/src/main/java/com/fastbee/report/model/AnalysisResult.java @@ -48,7 +48,7 @@ public class AnalysisResult { * {@code is_primary=1} 的物模型,没配就是空,不做任何推断。 * * - *

精简版报告({@code report.template-code=DCPReport})的测点表只输出本集合中的物模型, + *

精简版报告({@code ReportTemplate.DCP_REPORT},三节版式)的测点表只输出本集合中的物模型, * 因此必须与用户配置严格一致 —— 用关键词推断的结果冒充实配会输出用户没勾的指标。

* *

多测点归并时取代表测点(最不利测点)的集合,与 {@link #primaryIdentifiers} 同源同口径。

diff --git a/fastbee-report/src/main/java/com/fastbee/report/renderer/WordRenderer.java b/fastbee-report/src/main/java/com/fastbee/report/renderer/WordRenderer.java index e11ebcdf..a90f9df9 100644 --- a/fastbee-report/src/main/java/com/fastbee/report/renderer/WordRenderer.java +++ b/fastbee-report/src/main/java/com/fastbee/report/renderer/WordRenderer.java @@ -300,6 +300,12 @@ public class WordRenderer implements ReportRenderer { analysisMap.put("quality", qualityMap); analysisMap.put("warning", warningMap); + // 报告最上方「是否达到预警值」结论(DCP 三节版式头部用 {{warningStatus}}): + // 与正文预警章节同一份 warning 数据,但措辞直接回答「有无达到」,不落状态词 + data.put("warningStatus", warning == null + ? "未配置预警阈值,无法判定是否达到预警值" + : warningReachedText(warning.getStatus())); + // 添加项目概况总结(始终 put,空值兜底) data.put("summary", StringUtils.isNotEmpty(analysis.getSummary()) ? analysis.getSummary() : "统计期内无有效监测数据。"); @@ -797,6 +803,29 @@ public class WordRenderer implements ReportRenderer { return "正常"; } + /** + * 预警状态转「是否达到预警值」结论(报告头部 {{warningStatus}}) + * + *

与 {@link #warningLevelText} 的区别:后者输出状态词(正常/黄色预警…), + * 前者直接回答「有无达到预警值」,并点明达到的是哪一档(预警值/报警值/控制值), + * 措辞与 {@code WarningAnalyzer} 的 description 口径一致(GB 50497-2019 双控)。

+ */ + private String warningReachedText(String status) { + if (WarningAnalysis.STATUS_CONTROL.equals(status)) { + return "已达到红色预警(控制值)"; + } + if (WarningAnalysis.STATUS_ALARM.equals(status)) { + return "已达到橙色预警(报警值)"; + } + if (WarningAnalysis.STATUS_WARNING.equals(status)) { + return "已达到黄色预警(预警值)"; + } + if (WarningAnalysis.STATUS_UNKNOWN.equals(status)) { + return "未配置预警阈值,无法判定是否达到预警值"; + } + return "未达到预警值"; + } + /** 安全等级转中文 */ private String safetyLevelText(String level) { if ("DANGER".equals(level)) { diff --git a/fastbee-report/src/main/resources/templates/DCPReport.docx b/fastbee-report/src/main/resources/templates/DCPReport.docx index d2f13ad2..dba5c0a3 100644 Binary files a/fastbee-report/src/main/resources/templates/DCPReport.docx and b/fastbee-report/src/main/resources/templates/DCPReport.docx differ diff --git a/fastbee-report/src/test/java/com/fastbee/report/renderer/WordRendererDcpReportTemplateTest.java b/fastbee-report/src/test/java/com/fastbee/report/renderer/WordRendererDcpReportTemplateTest.java index 316c532a..735b1404 100644 --- a/fastbee-report/src/test/java/com/fastbee/report/renderer/WordRendererDcpReportTemplateTest.java +++ b/fastbee-report/src/test/java/com/fastbee/report/renderer/WordRendererDcpReportTemplateTest.java @@ -2,8 +2,8 @@ package com.fastbee.report.renderer; import com.fastbee.common.enums.MonitorCategory; import com.fastbee.common.enums.ReportPeriod; +import com.fastbee.common.enums.ReportTemplate; import com.fastbee.report.chart.ChartGenerator; -import com.fastbee.report.config.ReportProperties; import com.fastbee.report.core.ReportAssembler; import com.fastbee.report.data.MetricData; import com.fastbee.report.model.AnalysisResult; @@ -13,6 +13,8 @@ import com.fastbee.report.model.ReportModel; import com.fastbee.report.model.Statistics; import com.fastbee.report.model.TrendAnalysis; import com.fastbee.report.model.WarningAnalysis; +import com.fastbee.system.domain.SysUserSetting; +import com.fastbee.system.service.ISysUserSettingService; import org.junit.jupiter.api.Test; import org.springframework.test.util.ReflectionTestUtils; @@ -60,7 +62,7 @@ public class WordRendererDcpReportTemplateTest { } @Test - public void testRenderDcpReportTemplate() { + public void testRenderDcpReportTemplate() throws Exception { ReportModel model = buildModel(Arrays.asList("crackWidth")); byte[] docx = newRenderer().render(model); assertNotNull(docx); @@ -68,6 +70,11 @@ public class WordRendererDcpReportTemplateTest { String magic = new String(new byte[]{docx[0], docx[1]}, StandardCharsets.ISO_8859_1); assertTrue("PK".equals(magic), "渲染结果应为有效 docx(zip)"); + // 头部「是否达到预警值」结论:主指标为正常状态 → 应输出「未达到预警值」 + String xml = readDocumentXml(docx); + assertTrue(xml.contains("预警状态"), "DCP 头部应包含「预警状态」标签"); + assertTrue(xml.contains("未达到预警值"), "正常状态下应输出「未达到预警值」"); + try { java.io.File out = new java.io.File("target/test-output-dcp-report.docx"); java.nio.file.Files.write(out.toPath(), docx); @@ -80,11 +87,11 @@ public class WordRendererDcpReportTemplateTest { /** DCP 模板只需一份,服务全部监测分类(首行标题用 {{device.categoryDesc}} 占位) */ @Test public void testDcpReportTemplateExists() { - // 常量必须与 templates/ 下的文件名严格一致:解析器按 templateCode 拼文件名找资源, - // 常量与文件任一侧改名都会在运行时抛「报告模板不存在」,这里提前挡住 + // 枚举编码必须与 templates/ 下的文件名严格一致:解析器按 templateCode 拼文件名找资源, + // 编码与文件任一侧改名都会在运行时抛「报告模板不存在」,这里提前挡住 assertEquals("DCPReport.docx", - ReportProperties.DCP_REPORT_TEMPLATE_CODE + ".docx", - "常量与模板文件名不一致,改名时需同步"); + ReportTemplate.DCP_REPORT.getCode() + ".docx", + "枚举编码与模板文件名不一致,改名时需同步"); try (InputStream in = getClass().getClassLoader() .getResourceAsStream("templates/DCPReport.docx")) { assertNotNull(in, "DCP 版式模板缺失: DCPReport.docx"); @@ -93,13 +100,15 @@ public class WordRendererDcpReportTemplateTest { } } - /** 配置切到 DCPReport 时,装配出的模板编码与 primaryOnly 标记都正确 */ + /** 用户设置选 DCPReport 时,装配出的模板编码与 primaryOnly 标记都正确 */ @Test public void testAssemblerResolvesDcpReportTemplate() { ReportAssembler assembler = new ReportAssembler(); - ReflectionTestUtils.setField(assembler, "reportProperties", dcpReportProperties()); + ReflectionTestUtils.setField(assembler, "userSettingService", + mockUserSettingService(ReportTemplate.DCP_REPORT.getCode())); com.fastbee.report.core.ReportContext context = new com.fastbee.report.core.ReportContext(); + context.setUserId(1L); context.setCategory(MonitorCategory.CRACK_METER); com.fastbee.iot.domain.Device device = new com.fastbee.iot.domain.Device(); device.setDeviceId(1L); @@ -116,19 +125,19 @@ public class WordRendererDcpReportTemplateTest { context.setReportNo("REPORT-TEST-0001"); ReportModel model = assembler.assemble(context, buildAnalysis(Arrays.asList("crackWidth")), 1); - assertEquals(ReportProperties.DCP_REPORT_TEMPLATE_CODE, model.getTemplateCode()); + assertEquals(ReportTemplate.DCP_REPORT.getCode(), model.getTemplateCode()); assertTrue(model.isPrimaryOnly(), "DCP 版式必须置 primaryOnly,测点表才只出主监测指标"); } - /** 配置切回 V2 时,仍按监测分类取完整版模板,且 primaryOnly=false(原行为不变) */ + /** 用户设置默认 V2 时,仍按监测分类取完整版模板,且 primaryOnly=false(原行为不变) */ @Test public void testAssemblerFallsBackToFullTemplate() { ReportAssembler assembler = new ReportAssembler(); - ReportProperties properties = new ReportProperties(); - properties.setTemplateCode(ReportProperties.FULL_VERSION_CODE); - ReflectionTestUtils.setField(assembler, "reportProperties", properties); + ReflectionTestUtils.setField(assembler, "userSettingService", + mockUserSettingService(ReportTemplate.FULL_VERSION.getCode())); com.fastbee.report.core.ReportContext context = new com.fastbee.report.core.ReportContext(); + context.setUserId(1L); context.setCategory(MonitorCategory.CRACK_METER); com.fastbee.iot.domain.Device device = new com.fastbee.iot.domain.Device(); device.setDeviceId(1L); @@ -152,7 +161,7 @@ public class WordRendererDcpReportTemplateTest { @Test public void testDcpReportWithoutExplicitPrimaryShowsHint() { ReportModel model = buildModel(new ArrayList<>()); - model.setTemplateCode(ReportProperties.DCP_REPORT_TEMPLATE_CODE); + model.setTemplateCode(ReportTemplate.DCP_REPORT.getCode()); byte[] docx = newRenderer().render(model); assertTrue(docx.length > 0, "未配置主监测指标时也应产出可读文档"); } @@ -207,10 +216,41 @@ public class WordRendererDcpReportTemplateTest { throw new IllegalStateException("docx 中未找到 word/document.xml"); } - private ReportProperties dcpReportProperties() { - ReportProperties properties = new ReportProperties(); - properties.setTemplateCode(ReportProperties.DCP_REPORT_TEMPLATE_CODE); - return properties; + /** + * 构造 mock 的 {@link ISysUserSettingService}:按入参模板编码返回对应设置对象 + */ + private ISysUserSettingService mockUserSettingService(String reportTemplateCode) { + return new ISysUserSettingService() { + @Override + public SysUserSetting selectByUserId(Long userId) { + SysUserSetting setting = SysUserSetting.defaultOf(userId); + setting.setReportTemplate(reportTemplateCode); + return setting; + } + + @Override + public int updatePartial(Long userId, SysUserSetting patch, String operator) { + return 1; + } + + @Override + public List listLoginRedirectOptions() { + return new ArrayList<>(); + } + + @Override + public void addPinnedTag(Long userId, String path, String operator) { + } + + @Override + public void removePinnedTag(Long userId, String path, String operator) { + } + + @Override + public List getPinnedTags(Long userId) { + return new ArrayList<>(); + } + }; } /** @@ -221,7 +261,7 @@ public class WordRendererDcpReportTemplateTest { AnalysisResult analysis = buildAnalysis(explicitPrimary); return ReportModel.builder() .reportNo("REPORT-20261004120000-0001") - .templateCode(ReportProperties.DCP_REPORT_TEMPLATE_CODE) + .templateCode(ReportTemplate.DCP_REPORT.getCode()) .fileName("裂缝计-监测分析报告-abc123.docx") .periodDesc("2026-10-01 00:00:00 ~ 2026-10-04 00:00:00") .period(ReportPeriod.CUSTOM) diff --git a/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/domain/SysUserSetting.java b/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/domain/SysUserSetting.java index 0bad61af..807c64b0 100644 --- a/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/domain/SysUserSetting.java +++ b/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/domain/SysUserSetting.java @@ -71,6 +71,16 @@ public class SysUserSetting extends BaseEntity { @ApiModelProperty(value = "设备管理列表是否包含下级机构数据") private Boolean showChild; + /** + * 报告导出模板版式编码 + * + *

取值即 {@link com.fastbee.common.enums.ReportTemplate#getCode()}: + * {@code V2} 完整版 / {@code DCPReport} DCP 三节版式,可扩展其它版式。 + * 报告导出由登录用户触发,模板版式是 per-user 偏好,故落本表而非全局 {@code sys_config}。

+ */ + @ApiModelProperty(value = "报告导出模板版式编码(V2完整版/DCPReport三节版式)") + private String reportTemplate; + public SysUserSetting() { } @@ -87,6 +97,7 @@ public class SysUserSetting extends BaseEntity { setting.setPinnedTags(UserSettingConstants.EMPTY_PINNED_TAGS); setting.setQueryGatewayChild(UserSettingConstants.DEFAULT_QUERY_GATEWAY_CHILD); setting.setShowChild(UserSettingConstants.DEFAULT_SHOW_CHILD); + setting.setReportTemplate(UserSettingConstants.DEFAULT_REPORT_TEMPLATE); return setting; } @@ -138,6 +149,14 @@ public class SysUserSetting extends BaseEntity { this.showChild = showChild; } + public String getReportTemplate() { + return reportTemplate; + } + + public void setReportTemplate(String reportTemplate) { + this.reportTemplate = reportTemplate; + } + @Override public String toString() { return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) @@ -147,6 +166,7 @@ public class SysUserSetting extends BaseEntity { .append("pinnedTags", getPinnedTags()) .append("queryGatewayChild", getQueryGatewayChild()) .append("showChild", getShowChild()) + .append("reportTemplate", getReportTemplate()) .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) .append("updateBy", getUpdateBy()) diff --git a/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/mapper/SysUserSettingMapper.java b/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/mapper/SysUserSettingMapper.java index 6af3d64c..eb7bd818 100644 --- a/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/mapper/SysUserSettingMapper.java +++ b/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/mapper/SysUserSettingMapper.java @@ -51,20 +51,23 @@ public interface SysUserSettingMapper { * *

固定标签的增量写入要在一行上做「锁定读 → 改 → 写」,行必须先存在。 * 刻意不用 ON DUPLICATE 写其它列:那会覆盖 - * login_redirect / query_gateway_child / show_child,把用户已保存的设置冲回默认值。 + * login_redirect / query_gateway_child / show_child / report_template, + * 把用户已保存的设置冲回默认值。 * - * @param userId 用户ID - * @param loginRedirect 新行使用的默认登录跳转 + * @param userId 用户ID + * @param loginRedirect 新行使用的默认登录跳转 * @param queryGatewayChild 新行使用的默认开关值 - * @param showChild 新行使用的默认「包含下级机构数据」值 - * @param operator 操作人用户名 - * @param now 当前时间 + * @param showChild 新行使用的默认「包含下级机构数据」值 + * @param reportTemplate 新行使用的默认报告模板编码 + * @param operator 操作人用户名 + * @param now 当前时间 * @return 影响行数(插入 1 / 已存在 0) */ int insertDefaultIfAbsent(@Param("userId") Long userId, @Param("loginRedirect") String loginRedirect, @Param("queryGatewayChild") Boolean queryGatewayChild, @Param("showChild") Boolean showChild, + @Param("reportTemplate") String reportTemplate, @Param("operator") String operator, @Param("now") Date now); diff --git a/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/service/impl/SysUserSettingServiceImpl.java b/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/service/impl/SysUserSettingServiceImpl.java index c11f370a..0ce88aa8 100644 --- a/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/service/impl/SysUserSettingServiceImpl.java +++ b/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/service/impl/SysUserSettingServiceImpl.java @@ -37,8 +37,9 @@ public class SysUserSettingServiceImpl implements ISysUserSettingService { * *

用 \n 而不是常见的逗号/竖线:loginRedirect 是路由 path 不含换行, * pinnedTags 是 JSON 数组(元素为带引号的字符串)也不含裸换行, - * showChild 与 queryGatewayChild 是布尔字面量,四段互不冲突。 - *

字段顺序固定为:loginRedirect | pinnedTags | showChild | queryGatewayChild, + * showChild 与 queryGatewayChild 是布尔字面量,reportTemplate 是模板编码, + * 五段互不冲突。 + *

字段顺序固定为:loginRedirect | pinnedTags | showChild | queryGatewayChild | reportTemplate, * 与 {@link #parseCached} 一一对应,增删字段必须同步改这两处。 */ private static final String CACHE_SEPARATOR = "\n"; @@ -83,15 +84,17 @@ public class SysUserSettingServiceImpl implements ISysUserSettingService { setting.getLoginRedirect() + CACHE_SEPARATOR + setting.getPinnedTags() + CACHE_SEPARATOR + setting.getShowChild() + CACHE_SEPARATOR - + setting.getQueryGatewayChild()); + + setting.getQueryGatewayChild() + CACHE_SEPARATOR + + setting.getReportTemplate()); } /** * 解析缓存值,格式非法时返回 null 走查库 * - *

段数必须严格等于 4:历史版本只缓存了 2 段(loginRedirect + pinnedTags)、 - * 后又扩到 3 段(追加 queryGatewayChild)。若按缺省值补齐,会把旧格式里 - * 那个位置的字段当成 showChild 读出来,故段数不等一律判为未命中,由查库回填新格式。 + *

段数必须严格等于 5:历史版本只缓存了 2 段(loginRedirect + pinnedTags)、 + * 后又扩到 3 段(追加 queryGatewayChild)、4 段(追加 showChild)。 + * 若按缺省值补齐,会把旧格式里那个位置的字段当成别的字段读出来, + * 故段数不等一律判为未命中,由查库回填新格式。 * 缓存无 TTL,但每个用户最多回填一次。 * * @param cached 缓存字符串 @@ -100,7 +103,7 @@ public class SysUserSettingServiceImpl implements ISysUserSettingService { */ private SysUserSetting parseCached(String cached, Long userId) { String[] parts = cached.split(CACHE_SEPARATOR, -1); - if (parts.length != 4) { + if (parts.length != 5) { return null; } SysUserSetting setting = SysUserSetting.defaultOf(userId); @@ -108,6 +111,7 @@ public class SysUserSettingServiceImpl implements ISysUserSettingService { setting.setPinnedTags(parts[1]); setting.setShowChild(Boolean.parseBoolean(parts[2])); setting.setQueryGatewayChild(Boolean.parseBoolean(parts[3])); + setting.setReportTemplate(parts[4]); return setting; } @@ -119,6 +123,7 @@ public class SysUserSettingServiceImpl implements ISysUserSettingService { // 行必须先存在,否则后面的部分更新会 UPDATE 0 行、静默丢掉本次改动 userSettingMapper.insertDefaultIfAbsent(userId, UserSettingConstants.DEFAULT_LOGIN_REDIRECT, UserSettingConstants.DEFAULT_QUERY_GATEWAY_CHILD, UserSettingConstants.DEFAULT_SHOW_CHILD, + UserSettingConstants.DEFAULT_REPORT_TEMPLATE, operator, now); patch.setUserId(userId); patch.setUpdateBy(operator); @@ -141,6 +146,7 @@ public class SysUserSettingServiceImpl implements ISysUserSettingService { // 行必须先存在,后面的锁定读才有东西可锁 userSettingMapper.insertDefaultIfAbsent(userId, UserSettingConstants.DEFAULT_LOGIN_REDIRECT, UserSettingConstants.DEFAULT_QUERY_GATEWAY_CHILD, UserSettingConstants.DEFAULT_SHOW_CHILD, + UserSettingConstants.DEFAULT_REPORT_TEMPLATE, operator, now); List paths = parsePinnedTags(userSettingMapper.selectPinnedTagsForUpdate(userId)); if (paths.contains(path)) { diff --git a/fastbee-service/fastbee-system-service/src/main/resources/mapper/system/SysUserSettingMapper.xml b/fastbee-service/fastbee-system-service/src/main/resources/mapper/system/SysUserSettingMapper.xml index 24d04ec4..5a4ff477 100644 --- a/fastbee-service/fastbee-system-service/src/main/resources/mapper/system/SysUserSettingMapper.xml +++ b/fastbee-service/fastbee-system-service/src/main/resources/mapper/system/SysUserSettingMapper.xml @@ -11,6 +11,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + @@ -25,6 +26,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" select setting_id, user_id, login_redirect, pinned_tags, query_gateway_child, show_child, + report_template, create_by, create_time, update_by, update_time, remark from sys_user_setting @@ -49,6 +51,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" login_redirect = #{loginRedirect}, query_gateway_child = #{queryGatewayChild}, show_child = #{showChild}, + report_template = #{reportTemplate}, update_by = #{updateBy}, update_time = #{updateTime} @@ -64,9 +67,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" --> insert into sys_user_setting - (user_id, login_redirect, pinned_tags, query_gateway_child, show_child, create_by, create_time, update_by, update_time) + (user_id, login_redirect, pinned_tags, query_gateway_child, show_child, report_template, create_by, create_time, update_by, update_time) values - (#{userId}, #{loginRedirect}, '[]', #{queryGatewayChild}, #{showChild}, #{operator}, #{now}, #{operator}, #{now}) + (#{userId}, #{loginRedirect}, '[]', #{queryGatewayChild}, #{showChild}, #{reportTemplate}, #{operator}, #{now}, #{operator}, #{now}) on duplicate key update user_id = user_id diff --git a/sql/sys_user_setting.sql b/sql/sys_user_setting.sql index 4918599f..3af1cbb1 100644 --- a/sql/sys_user_setting.sql +++ b/sql/sys_user_setting.sql @@ -40,6 +40,10 @@ -- ADD COLUMN `show_child` tinyint(1) NOT NULL DEFAULT 0 -- COMMENT '设备管理列表是否包含下级机构数据(0否1是)' -- AFTER `query_gateway_child`; +-- ALTER TABLE `sys_user_setting` +-- ADD COLUMN `report_template` varchar(32) NOT NULL DEFAULT 'V2' +-- COMMENT '报告导出模板编码(V2完整版/DCPReport三节版式)' +-- AFTER `show_child`; -- -- query_gateway_child:开启后 /iot/device/shortList **按设备编号查询时**(serialNumber 非空): -- ① 命中的若是网关子设备编号,返回其所属的网关设备(子设备 gw_dev_code 存的就是网关 serial_number, @@ -60,6 +64,13 @@ -- 设备管理页进入时读一次该设置并作为查询参数下发,故本列是「前端读取、后端不感知」的偏好, -- DeviceController 在未收到 showChild 时依然按 false 处理。 -- 默认 0 = 与引入本开关前完全一致。 +-- +-- report_template:报告导出模板版式编码,取值即 ReportTemplate.code(V2 完整版 / +-- DCPReport 三节版式,可扩展其它版式)。 +-- 版式是 per-user 偏好:报告导出由登录用户触发(异步线程已显式透传 SecurityContext, +-- 能拿到 userId),故落本表而非全局 sys_config;无登录态不允许导出,不做全局兜底。 +-- 由「系统设置」页维护(下拉选择),新增版式 = 枚举新增项 + 放 templates/ 下模板文件。 +-- 默认 'V2' = 与引入本开关前完全一致(完整版,按监测分类取 DEVICE_{CATEGORY}_V2)。 -- ------------------------------------------------------------ CREATE TABLE IF NOT EXISTS `sys_user_setting` ( `setting_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '设置主键', @@ -68,6 +79,7 @@ CREATE TABLE IF NOT EXISTS `sys_user_setting` ( `pinned_tags` text NOT NULL DEFAULT ('[]') COMMENT '任务栏固定标签JSON数组,按序存 path;空数组表示未固定', `query_gateway_child` tinyint(1) NOT NULL DEFAULT 0 COMMENT '按编号查询时是否查网关子设备(0否1是)', `show_child` tinyint(1) NOT NULL DEFAULT 0 COMMENT '设备管理列表是否包含下级机构数据(0否1是)', + `report_template` varchar(32) NOT NULL DEFAULT 'V2' COMMENT '报告导出模板编码(V2完整版/DCPReport三节版式)', `create_by` varchar(64) DEFAULT NULL COMMENT '创建者', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_by` varchar(64) DEFAULT NULL COMMENT '更新者', @@ -87,7 +99,7 @@ CREATE TABLE IF NOT EXISTS `sys_user_setting` ( -- ------------------------------------------------------------ INSERT INTO `sys_config` (`config_name`, `config_key`, `config_value`, `config_type`, `create_by`, `create_time`, `remark`) -SELECT '是否下发读取设备IMEI/ICCID指令', 'iot.power_on.read_imei_iccid', 'true', 'Y', 'system', NOW(), +SELECT '是否自动下发读取设备IMEI/ICCID指令', 'iot.power_on.read_imei_iccid', 'true', 'Y', 'system', NOW(), '设备上电后是否下发 AT+IMEI/AT+ICCID/AT+LBS 指令读取;读该值的线程无登录上下文,故为全局配置' WHERE NOT EXISTS ( SELECT 1 FROM `sys_config` WHERE `config_key` = 'iot.power_on.read_imei_iccid' diff --git a/yack-web/src/api/iot/report.js b/yack-web/src/api/iot/report.js index 28aaf6e1..d09790b0 100644 --- a/yack-web/src/api/iot/report.js +++ b/yack-web/src/api/iot/report.js @@ -43,3 +43,11 @@ export function listMonitorCategories() { method: 'get', }); } + +// 报告导出模板版式列表(枚举驱动,供系统设置页下拉选择 per-user 模板) +export function listReportTemplates() { + return request({ + url: '/iot/report/templates', + method: 'get', + }); +} diff --git a/yack-web/src/views/system/setting/components/UserSettingForm.vue b/yack-web/src/views/system/setting/components/UserSettingForm.vue index e4125ebf..e7377167 100644 --- a/yack-web/src/views/system/setting/components/UserSettingForm.vue +++ b/yack-web/src/views/system/setting/components/UserSettingForm.vue @@ -25,6 +25,22 @@ + + + + + +

保存 仅提交改动过的配置项 @@ -38,14 +54,17 @@ import { updateUserSetting, listLoginRedirectOptions } from '@/api/system/userSetting'; +import { listReportTemplates } from '@/api/iot/report'; import SettingRow from './SettingRow'; // 默认值与后端 UserSettingConstants 保持一致: -// 未指定的账号保持原有的数据大屏落地行为;子设备开关与「查下级机构数据」默认关闭 +// 未指定的账号保持原有的数据大屏落地行为;子设备开关与「查下级机构数据」默认关闭; +// 报告模板默认完整版(V2) const DEFAULT_FORM = { loginRedirect: '/bigscreen', queryGatewayChild: false, - showChild: false + showChild: false, + reportTemplate: 'V2' }; export default { @@ -55,7 +74,9 @@ export default { return { saving: false, optionsLoading: false, + templatesLoading: false, options: [], + reportTemplates: [], form: { ...DEFAULT_FORM }, // 服务端当前值的快照:保存时只提交与它不同的字段,未改动的不进 payload original: { ...DEFAULT_FORM } @@ -63,6 +84,7 @@ export default { }, created() { this.getOptions(); + this.getTemplates(); this.getSetting(); }, methods: { @@ -76,6 +98,16 @@ export default { this.optionsLoading = false; }); }, + getTemplates() { + this.templatesLoading = true; + listReportTemplates() + .then((res) => { + this.reportTemplates = res.data || []; + }) + .finally(() => { + this.templatesLoading = false; + }); + }, getSetting() { getUserSetting().then((res) => { const data = res.data || {}; @@ -88,7 +120,8 @@ export default { : data.queryGatewayChild, showChild: data.showChild === null || data.showChild === undefined ? DEFAULT_FORM.showChild - : data.showChild + : data.showChild, + reportTemplate: data.reportTemplate || DEFAULT_FORM.reportTemplate }; this.form = { ...form }; // 快照必须与回显值同源(含兜底后的值),否则未改动也会被当成脏数据提交