# 历史数据模块 — 开发规格说明 > 本文档属于《污水厂智能控制平台》的一部分,详细描述历史数据模块的功能、数据模型、API接口和业务逻辑,细度可达直接开发级别。 > 本模块与 [数据管理模块](./spec-数据管理.md) 紧密关联,依赖其中的设备、采集点、TDengine 超级表等设计。 --- ## 目录 - [1. 概述](#1-概述) - [2. 数据来源](#2-数据来源) - [3. 历史点位树形结构](#3-历史点位树形结构) - [4. 数据保留策略](#4-数据保留策略) - [5. RESTful API](#5-restful-api) - [6. 曲线模式(前端)](#6-曲线模式前端) - [7. 表格模式(前端)](#7-表格模式前端) - [8. 前端页面结构](#8-前端页面结构) --- ## 1. 概述 ### 1.1 模块定位 历史数据模块负责展示平台中所有已存储的历史时序数据,提供两种查看方式: - **曲线模式**:多条数据在同一时间轴上的变化趋势对比,支持游标查看 - **表格模式**:按固定时间间隔展示数据,支持导出CSV ### 1.2 模块边界 | 交互对象 | 方向 | 内容 | |----------|------|------| | Web前端 | 返回 | 历史点位树、历史数据查询结果、CSV文件 | | TDengine | 查询 | 读取 collection_data 超级表下的子表数据 | | PostgreSQL | 查询 | 读取设备、采集点配置,构建树形结构 | | 数据管理模块 | 依赖 | 采集点配置、设备配置、分组信息 | ### 1.3 页面层级 历史数据与数据管理在同一层级,位于左侧导航栏: ``` ┌──────────┐ │ ▽ 数据管理 │ │ 设备管理 │ │ 数据采集 │ │ 数据写入 │ │ │ │ ○ 历史数据 │ ← 同层级,无子菜单 └──────────┘ ``` --- ## 2. 数据来源 ### 2.1 当前数据来源:采集点历史数据 来自数据管理模块中**启用了 store_history=true** 的采集点,数据存储在 TDengine 的 `collection_data` 超级表中。 ```sql -- 已在数据管理模块中定义的超级表 CREATE STABLE IF NOT EXISTS collection_data ( ts TIMESTAMP, value DOUBLE, quality INT, -- 0=good, 1=bad point_id VARCHAR(32), point_name VARCHAR(128) ) TAGS ( device_id VARCHAR(32), device_name VARCHAR(128), data_type VARCHAR(16), unit VARCHAR(32) ); ``` ### 2.2 预留数据来源:非采集点位(内部数据) 未来的扩展数据(如AI模型推理的建议值、人工录入的补充数据等),统一归属为**内部数据**。 预留数据模型:新增 `computed_data` 超级表 ```sql -- 预留:非采集点位的时序数据 CREATE STABLE IF NOT EXISTS computed_data ( ts TIMESTAMP, value DOUBLE, quality INT, -- 0=good, 1=bad point_id VARCHAR(32), point_name VARCHAR(128), source VARCHAR(32) -- 数据来源: 'ai_model', 'manual_input', 'external' 等 ) TAGS ( category VARCHAR(32), -- 类别,如 'aeration', 'dosing', 'prediction' unit VARCHAR(32) ); ``` **当前阶段只实现采集点历史数据展示,内部数据在树形结构中占位,不可勾选,显示"暂无数据"。** ### 2.3 历史数据点位判定规则 **一个采集点是否出现在历史数据树形结构中**,取决于: ``` 采集点出现在历史树中 ⇔ collection_points.enabled = true AND collection_points.deleted = false AND collection_points.store_history = true AND 关联的 devices.enabled = true AND 关联的 devices.deleted = false ``` --- ## 3. 历史点位树形结构 ### 3.1 树结构定义 ``` 所有历史点位 ├── 分组A │ ├── ☐ 采集点1(最新值,质量戳) │ ├── ☐ 采集点2(最新值,质量戳) │ └── ... ├── 分组B │ ├── ☐ 采集点3(最新值,质量戳) │ └── ... ├── ...(其他分组) └── 内部数据(预留) └── (暂无数据) ``` 分组名称直接取自数据采集点配置中的 `group_name`,同一分组下的点位可来自不同设备。 ### 3.2 构建规则 | 层级 | 数据来源 | 说明 | |------|----------|------| | 第一层:分组 | PostgreSQL `collection_points` 表的 `group_name` | 去重后作为树的顶层节点,只有包含可用历史数据点的分组才显示 | | 第二层:点位 | PostgreSQL `collection_points` | 满足 store_history=true 且 enabled 的采集点,按 group_name 归属到对应分组下 | | 独立节点:内部数据 | 硬编码占位 | 当前阶段显示"暂无数据",后续扩展 | ### 3.3 节点属性 每个采集点节点应包含以下信息(用于展示和查询): ```json { "id": "point-uuid", "name": "曝气池DO_01", "type": "collection", // 或 'computed'(预留) "data_type": "REAL", "unit": "mg/L", "device_id": "device-uuid", "device_name": "一期曝气柜PLC", "group_name": "曝气池", "latest_value": 2.35, "latest_quality": "good", "latest_ts": "2026-07-09T10:00:01+08:00" } ``` > 由于树结构按分组平铺,点位节点需额外携带 `device_id` 和 `device_name` 以便前端识别点位所属设备。 ### 3.4 复选框行为 | 操作 | 行为 | |------|------| | 勾选点位 | 该点位数据加入曲线/表格 | | 取消勾选点位 | 该点位数据从曲线/表格中移除 | | 勾选上限 | 建议最多同时勾选20个点位(前端友好性考虑) | | 勾选状态 | 切换曲线/表格模式时,勾选状态保持不变 | --- ## 4. 数据保留策略 ### 4.1 配置方式 在系统配置中增加一个全局参数: | 参数 | 类型 | 范围 | 默认值 | 说明 | |------|------|------|--------|------| | history_retention_days | int | 1~730 | 365 | 历史数据保留天数 | ### 4.2 实现方式 通过 TDengine 的数据库级 `KEEP` 参数控制: ```sql -- 创建数据库时指定保留天数 CREATE DATABASE IF NOT EXISTS water_plant KEEP 365 -- 数据保留天数(对应配置值) DAYS 10 -- 每10天一个文件 BLOCKS 100; -- 修改保留天数(当用户在系统配置中修改时执行) ALTER DATABASE water_plant KEEP 730; ``` ### 4.3 注意 - 修改保留天数后,TDengine 会自动清理超出保留期的数据 - 保留天数对 `collection_data` 和 `computed_data` 两个超级表同时生效 - 建议在系统配置页面提供"立即清理过期数据"按钮,但通常不需要手动操作 --- ## 5. RESTful API ### 5.1 接口列表 | 方法 | 路径 | 说明 | |------|------|------| | GET | /api/v1/history/tree | 获取历史点位树形结构 | | POST | /api/v1/history/query | 查询历史数据(曲线模式) | | POST | /api/v1/history/query-table | 查询历史数据(表格模式) | | POST | /api/v1/history/export | 导出历史数据为CSV | ### 5.2 接口详细定义 #### 5.2.1 GET /api/v1/history/tree — 获取历史点位树 无需参数,从 PostgreSQL 查询所有符合条件的历史点位,按设备→分组→点位构建树。 Response (200): ```json { "tree": [ { "id": "group-aeration", "name": "曝气池", "type": "group", "children": [ { "id": "point-uuid-1", "name": "曝气池DO_01", "type": "collection", "data_type": "REAL", "unit": "mg/L", "device_id": "device-uuid-1", "device_name": "一期曝气柜PLC", "latest_value": 2.35, "latest_quality": "good", "latest_ts": "2026-07-09T10:00:01+08:00" }, { "id": "point-uuid-2", "name": "曝气池温度", "type": "collection", "data_type": "REAL", "unit": "℃", "device_id": "device-uuid-1", "device_name": "一期曝气柜PLC", "latest_value": 25.1, "latest_quality": "good", "latest_ts": "2026-07-09T10:00:01+08:00" } ] }, { "id": "internal-data", "name": "内部数据", "type": "reserved", "children": [ { "id": "placeholder", "name": "暂无数据", "type": "placeholder", "disabled": true } ] } ] } ``` #### 5.2.2 POST /api/v1/history/query — 曲线模式查询 Request body: ```json { "point_ids": ["point-uuid-1", "point-uuid-2"], "start_time": "2026-07-09T00:00:00+08:00", "end_time": "2026-07-09T01:00:00+08:00" } ``` | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | point_ids | string[] | 是 | 要查询的点位ID列表,最少1个,最多20个 | | start_time | string | 是 | 开始时间,ISO 8601格式 | | end_time | string | 是 | 结束时间,ISO 8601格式 | 后端逻辑: ``` 1. 根据 point_ids 获取每个点位的子表名(从 point_id 映射) 2. 对每个点位,查询 TDengine: SELECT ts, value, quality FROM {subtable_name} WHERE ts >= start_time AND ts <= end_time ORDER BY ts ASC 3. 聚合结果,返回 ``` Response (200): ```json { "series": [ { "point_id": "point-uuid-1", "point_name": "曝气池DO_01", "unit": "mg/L", "data_type": "REAL", "data": [ { "ts": "2026-07-09T00:00:01+08:00", "value": 2.35, "quality": "good" }, { "ts": "2026-07-09T00:00:02+08:00", "value": 2.36, "quality": "good" }, { "ts": "2026-07-09T00:00:05+08:00", "value": 2.38, "quality": "good" }, { "ts": "2026-07-09T00:00:08+08:00", "value": 2.30, "quality": "bad" }, { "ts": "2026-07-09T00:00:10+08:00", "value": 2.40, "quality": "good" } ] }, { "point_id": "point-uuid-2", "point_name": "曝气池温度", "unit": "℃", "data_type": "REAL", "data": [ { "ts": "2026-07-09T00:00:01+08:00", "value": 25.1, "quality": "good" }, { "ts": "2026-07-09T00:00:05+08:00", "value": 25.2, "quality": "good" }, { "ts": "2026-07-09T00:00:10+08:00", "value": 25.0, "quality": "good" } ] } ] } ``` #### 5.2.3 POST /api/v1/history/query-table — 表格模式查询 Request body: ```json { "point_ids": ["point-uuid-1", "point-uuid-2"], "start_time": "2026-07-09T00:00:00+08:00", "end_time": "2026-07-09T01:00:00+08:00", "interval_minutes": 10 } ``` | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | point_ids | string[] | 是 | 要查询的点位ID列表,最少1个,最多20个 | | start_time | string | 是 | 开始时间 | | end_time | string | 是 | 结束时间 | | interval_minutes | int | 是 | 间隔分钟数,范围1~1440,必须能被60整除(建议限制为1, 2, 5, 10, 15, 20, 30, 60) | **后端逻辑(重点 — 最近邻匹配):** ``` 1. 根据 point_ids 获取每个点位的子表名 2. 对每个点位,查询 TDengine 获取 start_time ~ end_time 范围内的所有原始数据 3. 生成目标时间序列:从 start_time 开始,每隔 interval_minutes 生成一个目标时间点 - 例如:00:00, 00:10, 00:20, ..., 01:00 4. 对每个目标时间点 T,执行最近邻匹配: a. 定义匹配窗口 = interval_minutes / 2(向上取整,单位分钟) b. 在原始数据中,找时间戳距离 T 最近的数据点 c. 如果最近距离 ≤ 匹配窗口 → 匹配成功,使用该数据点的 value 和 quality d. 如果最近距离 > 匹配窗口 → 匹配失败,标记为 null 5. 返回按目标时间序列对齐的多条数据 ``` **举例:** interval_minutes=10,匹配窗口=5分钟 | 目标时间 | 原始数据 | 最近距离 | 匹配结果 | |----------|---------|---------|---------| | 00:00:00 | 有 00:00:01 的数据 | 1秒 | 2.35 (good) | | 00:10:00 | 有 00:00:08 和 00:10:02 的数据 | 2秒(00:10:02) | 2.40 (good) | | 00:20:00 | 最近数据在 00:14:00,距离6分钟 | 6分钟 > 5分钟 | null(显示—) | Response (200): ```json { "time_column": [ "2026-07-09T00:00:00+08:00", "2026-07-09T00:10:00+08:00", "2026-07-09T00:20:00+08:00", "2026-07-09T00:30:00+08:00", "2026-07-09T00:40:00+08:00", "2026-07-09T00:50:00+08:00", "2026-07-09T01:00:00+08:00" ], "columns": [ { "point_id": "point-uuid-1", "point_name": "曝气池DO_01", "unit": "mg/L", "data": [ { "value": 2.35, "quality": "good" }, { "value": 2.40, "quality": "good" }, null, { "value": 2.38, "quality": "good" }, { "value": 2.42, "quality": "good" }, null, { "value": 2.36, "quality": "good" } ] }, { "point_id": "point-uuid-2", "point_name": "曝气池温度", "unit": "℃", "data": [ { "value": 25.1, "quality": "good" }, { "value": 25.0, "quality": "good" }, { "value": 25.3, "quality": "good" }, null, { "value": 25.1, "quality": "good" }, { "value": 25.2, "quality": "good" }, { "value": 25.0, "quality": "good" } ] } ] } ``` #### 5.2.4 POST /api/v1/history/export — 导出CSV Request body 与 query-table 一致: ```json { "point_ids": ["point-uuid-1", "point-uuid-2"], "start_time": "2026-07-09T00:00:00+08:00", "end_time": "2026-07-09T01:00:00+08:00", "interval_minutes": 10 } ``` Response:CSV文件(Content-Type: text/csv; charset=utf-8-sig) CSV格式: ```csv 时间,曝气池DO_01(mg/L),曝气池DO_01_质量,曝气池温度(℃),曝气池温度_质量 2026-07-09 00:00:00,2.35,good,25.1,good 2026-07-09 00:10:00,2.40,good,25.0,good 2026-07-09 00:20:00,—,—,25.3,good 2026-07-09 00:30:00,2.38,good,—,— ``` 注意: - CSV使用UTF-8 with BOM编码 - 首行为标题行,包含时间、每个点位的数据列和质量列 - 质量戳为 bad 时,数据列显示为 `—`(全角破折号),质量列显示 `bad` - 未匹配到数据(null)时,数据列显示为 `—`,质量列显示 `—` --- ## 6. 曲线模式(前端) ### 6.1 布局 ``` ┌───────────┬──────────────────────────────────────┐ │ 点位树 │ 时间范围选择器 [过去1小时] [今天] [自定义] │ │ (复选框) │ │ │ │ ┌──────────────────────────────────┐ │ │ ☑ 曝气池 │ │ │ │ │ ☑ DO │ │ 曲线图区域 │ │ │ ☐ 温度 │ │ (ECharts 折线图) │ │ │ ☐ 加药间 │ │ │ │ │ │ └──────────────────────────────────┘ │ │ 内部数据 │ ┌──────────┬──────────┬──────────┐ │ │ ☐ (暂无) │ │ 点位名称 │ 时间 │ 数值 │ │ │ │ │ DO │ 00:00:05 │ 2.38 │ │ │ │ │ 温度 │ 00:00:05 │ 25.2 │ │ │ │ └──────────┴──────────┴──────────┘ │ │ [曲线] [表格]│ │ └───────────┴──────────────────────────────────────┘ ``` ### 6.2 时间范围选择 提供快捷选择 + 自定义: | 选项 | 说明 | |------|------| | 过去1小时 | 快速查看最近1小时 | | 过去6小时 | 快速查看最近6小时 | | 过去24小时 | 快速查看最近1天 | | 过去7天 | 快速查看最近1周 | | 自定义 | 自由选择起止时间(精确到秒) | ### 6.3 曲线图规则 #### 6.3.1 横轴(X轴) - 类型:时间轴 - 自适应:根据选择的时间范围自动调整刻度 - 刻度标签格式: - 1小时内:`HH:mm:ss` - 1小时~1天:`HH:mm` - 1天以上:`MM-dd HH:mm` #### 6.3.2 纵轴(Y轴)— 单轴分段自适应 **核心需求**:一个Y轴,刻度不按数值均匀分布,而是根据数据分布自动调整疏密——数据密集的区域刻度变密(放大),数据稀疏的区域刻度变疏(压缩),让不同量级的多条曲线都能在同一个Y轴上看出变化趋势。 **场景举例**: 同时展示两条曲线,一条 DO 值在 2.0~3.0 mg/L,一条温度在 20~30℃。如果Y轴从0均匀到30,DO曲线会被压缩成一条几乎水平的直线,完全看不出变化。 **实现方式 — 分段映射法**: ``` 原始Y轴(均匀) 显示Y轴(分段自适应) 30 ─ 30 ─ │ │ 温度变化区域(20~30) │ 该区域被适当压缩 20 ─ 20 ─ │ │ ─ ─ ─ 中间区域(3~20) │ 该区域数据稀疏,被大幅压缩 │ 3 ─ 3 ─ │ │ DO变化区域(2.0~3.0) │ 该区域数据密集,被放大展开 2 ─ 2 ─ │ 0 ─ 0 ─ ``` **技术实现**: 1. 后端(或前端)对查询到的所有曲线数据进行全局分析,找出数据分布 2. 将Y轴划分为若干段,每段内使用线性映射,段间不等距 3. 段划分规则: - 找出所有曲线的数据值,按值聚类 - 每个聚类形成一个"密度段",该段在Y轴上占据较大比例的高度 - 聚类之间的空白区域形成一个"稀疏段",该段在Y轴上占据较小比例的高度 4. 数据值 → 显示坐标的映射公式: ``` 对于每个数据值 v,计算其在Y轴上的显示位置 p: p = sum( previous_segment_heights ) + (v - segment_start) / (segment_end - segment_start) * segment_height 其中 segment_height 根据该段的数据密度分配: - 密度段(包含大量数据点):分配较高比例(如60%的Y轴高度) - 稀疏段(几乎无数据点):分配较低比例(如10%的Y轴高度) ``` 5. Y轴刻度标签显示原始值,位置按映射后的坐标放置 **ECharts 实现方案**: 方案一(推荐 — 数据变换):后端将数据值按分段映射关系转换为显示坐标,前端使用线性Y轴绘制,Y轴通过 `axisLabel.formatter` 显示原始值标签,`tooltip.formatter` 显示原始值。 方案二(纯前端):前端获取原始数据后,使用 ECharts 的 `custom` 系列或数据变换插件,在前端完成映射计算。 **Y轴标签规则**: | 规则 | 说明 | |------|------| | 单Y轴 | 物理上只有一个Y轴,位于图表左侧 | | 数值标签 | 显示原始数值,不显示映射后的坐标值 | | 刻度位置 | 刻度线在Y轴上的物理位置由映射函数决定,不是均匀分布 | | 刻度数量 | 自动控制,保持5~10个刻度标签,避免过密 | | 单位标注 | Y轴标题显示 `数值 (单位)`,如 `mg/L` | | 分段视觉提示 | 在Y轴背景上用浅色横条标记不同分段区域,帮助用户识别分段边界 | #### 6.3.3 折线规则 | 数据质量 | 显示方式 | |----------|----------| | 连续 good 数据 | 实线折线,正常连接 | | 连续 bad 数据 | 虚线连接(`--` 样式) | | good → bad 过渡 | 实线连接到 bad 点,bad 点之后变为虚线 | | 数据缺失(gap) | 断线,不连接 | #### 6.3.4 游标功能 **游标是一条垂直的十字线**,跟随鼠标移动。 行为: ``` 鼠标在图表区域移动 │ ▼ 游标垂直线跟随鼠标位置 │ ▼ 对每条曲线,找到游标所在时刻最近的数据点 │ ├── 找到数据点 → 显示该点的值 └── 未找到数据点 → 显示 "--" │ ▼ 游标信息显示在右侧表格中 ``` 右侧游标信息表格: | 点位名称 | 时间 | 数值 | 质量 | |----------|------|------|------| | 曝气池DO_01 | 00:00:05 | 2.38 | good | | 曝气池温度 | 00:00:05 | 25.2 | good | 游标交互细节: - 游标跟随鼠标移动,实时更新 - 鼠标离开图表区域时,游标消失 - 游标所在时刻精确到最近的数据点,不插值 - 游标表格中,质量戳为 bad 时,数值显示为 `—` --- ## 7. 表格模式(前端) ### 7.1 布局 ``` ┌───────────┬──────────────────────────────────────┐ │ 点位树 │ 时间范围选择器 [自定义] │ │ (复选框) │ 间隔: [10分钟] ▼ │ │ │ │ │ ☑ 曝气池 │ ┌──────────────────────────────────┐ │ │ ☑ DO │ │ 时间 │ DO(mg/L)│ 温度(℃)│ │ │ ☐ 温度 │ │──────────────┼─────────┼────────│ │ │ ☐ 加药间 │ │ 00:00:00 │ 2.35 │ 25.1 │ │ │ │ │ 00:10:00 │ 2.40 │ 25.0 │ │ │ 内部数据 │ │ 00:20:00 │ — │ 25.3 │ │ │ ☐ (暂无) │ │ 00:30:00 │ 2.38 │ — │ │ │ │ │ ... │ ... │ ... │ │ │ [曲线] [表格]│ └──────────────────────────────────┘ │ │ │ [导出CSV] │ └───────────┴──────────────────────────────────────┘ ``` ### 7.2 时间间隔选择 | 参数 | 说明 | |------|------| | 间隔时间 | 下拉选择:1分钟、2分钟、5分钟、10分钟、15分钟、20分钟、30分钟、60分钟 | | 默认值 | 10分钟 | ### 7.3 表格数据规则 | 规则 | 说明 | |------|------| | 第一列 | 时间序列,从 start_time 开始,按 interval 递增 | | 数据列 | 每个勾选的点位对应一列,列标题为 `点位名称(单位)` | | 数据对齐 | 使用最近邻匹配(见 5.2.3 后端逻辑) | | bad 质量 | 数据单元格显示 `—`(全角破折号) | | 无匹配数据 | 数据单元格显示 `—`(全角破折号) | | 空值处理 | 空白单元格统一显示 `—`,不显示空单元格 | ### 7.4 导出CSV 点击"导出CSV"按钮,触发 `POST /api/v1/history/export` 接口。 - 导出内容与当前表格显示内容一致(相同的时间范围、间隔、点位) - 文件名格式:`历史数据_{start_time}_{end_time}_{interval}min.csv` - 导出完成后浏览器自动下载 --- ## 8. 前端页面结构 ### 8.1 页面布局 ``` ┌──────────────────────────────────────────┐ │ ┌──────────┐ ┌───────────────────────┐ │ │ │ ▽ 数据管理 │ │ │ │ │ │ 设备管理 │ │ │ │ │ │ 数据采集 │ │ 内容区域 │ │ │ │ 数据写入 │ │ │ │ │ │ │ │ ┌───────────────────┐ │ │ │ │ ○ 历史数据 │ │ │ ☑ 曝气池 │ │ │ │ │ │ │ │ ☑ DO │ │ │ │ └──────────┘ │ │ ☐ 温度 │ │ │ │ │ │ ☐ 加药间 │ │ │ │ │ │ ─────── │ │ │ │ │ │ 内部数据 │ │ │ │ │ │ ☐ (暂无数据) │ │ │ │ │ └───────────────────┘ │ │ │ │ [曲线] [表格] 切换 │ │ │ │ ┌───────────────────┐ │ │ │ │ │ 曲线图/表格内容区域 │ │ │ │ │ │ │ │ │ │ │ └───────────────────┘ │ │ │ └───────────────────────┘ │ └──────────────────────────────────────────┘ ``` ### 8.2 模式切换 - 曲线模式和表格模式通过 Tab 按钮切换 - 切换时,**勾选的点位状态保持不变** - 切换时,**时间范围保持不变** - 切换时,不重复请求数据,按需加载(点击曲线Tab时请求曲线数据,点击表格Tab时请求表格数据) ### 8.3 交互流程 ``` 用户进入历史数据页面 │ ▼ 加载历史点位树(GET /api/v1/history/tree),按分组→点位两层构建 │ ▼ 左侧树渲染,用户勾选点位 │ ▼ 用户选择时间范围 │ ├── 曲线模式 → 点击查询 → POST /api/v1/history/query → 渲染曲线图 └── 表格模式 → 选择间隔 → 点击查询 → POST /api/v1/history/query-table → 渲染表格 │ ▼ 用户勾选/取消勾选点位 → 自动重新查询(保持时间范围不变) 用户切换模式 → 自动重新查询(保持点位和时间范围不变) ``` --- ## 附录A:关键业务规则一览 | 编号 | 规则 | 说明 | |------|------|------| | H001 | 历史数据来源 | 仅展示 store_history=true 且设备/点位均启用的采集点数据 | | H002 | 树形结构 | 按分组→点位两层组织,分组名称取自采集点配置的 group_name,外加"内部数据"预留节点 | | H003 | 最大勾选数 | 同时勾选的点位不超过20个 | | H004 | 数据保留 | 可配置1~730天,通过TDengine KEEP参数实现 | | H005 | 表格时间对齐 | 最近邻匹配,匹配窗口 = interval/2 | | H006 | 曲线Y轴 | 单Y轴分段自适应,数据密集区放大,稀疏区压缩 | | H007 | bad质量显示 | 曲线:虚线;表格:`—`;游标:`—` | | H008 | 数据缺失 | 曲线:断线;表格:`—` | | H009 | 模式切换 | 保持点位勾选状态和时间范围不变 | | H010 | CSV导出 | 使用UTF-8 with BOM编码,与表格显示内容一致 | --- > 本文档版本:v1.0 > 最后更新:2026-07-09