# 曲线查询模块文档

## 一、功能概述

曲线查询页面用于查看生产过程中的压力变化曲线，展示每次配方执行时的 `MAX_PRESSURE`（加压压力）和 `FEED_PRESSURE`（输送压力）随时间的变化趋势。支持按日期范围、批号、称重系统三维筛选。

**数据来源**：`R_CURVE` 表（现场 PC 执行配方过程中定时写入压力数据）
**查询方式**：日期范围（最多 5 天）+ 批号下拉筛选 + 系统编号筛选
**展示方式**：ECharts 折线图，最多 6 条线（3 个系统 × 2 种压力）

> 与报表窗口的区别：报表展示的是生产结果汇总（`R_WEIGH`），曲线展示的是生产过程中的实时压力变化（`R_CURVE`）。同一批号在 `R_CURVE` 中有多条时间序列记录（间隔约 2 秒）。

## 二、功能架构

### 2.1 功能结构图

```
┌─────────────────────────────────────────────────────────────┐
│                       曲线查询模块                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌──────────────┐    ┌──────────────┐                      │
│  │   小程序端    │    │   API接口    │                      │
│  │  (微信H5)    │    │  (PHP)       │                      │
│  └──────┬───────┘    └──────┬───────┘                      │
│         │                   │                               │
│         └───────────────────┘                               │
│                             │                               │
│                             ▼                               │
│                    ┌──────────────────┐                    │
│                    │   MySQL数据库    │                    │
│                    │   R_CURVE表      │                    │
│                    └──────────────────┘                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

### 2.2 核心功能列表

| 功能 | 说明 |
|------|------|
| 日期范围筛选 | 开始日期 ~ 结束日期，最多 5 天 |
| 批号动态下拉 | 根据当前日期范围自动加载该时段内的所有批号（无"全部"选项） |
| 系统筛选 | 全部 / 1# / 2# / 3# 三套称重系统 |
| 压力曲线图 | ECharts 折线图，6 条独立颜色曲线 |

## 三、数据模型

### 3.1 数据库表结构 (R_CURVE)

| 字段名 | 类型 | 说明 |
|--------|------|------|
| ID | bigint | 主键ID，自增 |
| LOT_ID | varchar(20) | 批号（唯一标识一次配方执行） |
| BATCH_SET | int | 设定车数 |
| BATCH | int | 实际已完成车数 |
| SYS_NO | tinyint | 系统编号（1/2/3，对应三套称重系统） |
| RECIPE_NAME | varchar(30) | 配方名称 |
| MAX_PRESSURE | decimal(8,3) | 加压压力值 |
| FEED_PRESSURE | decimal(8,3) | 输送压力值 |
| SAVE_TIME | varchar(14) | 保存时间，格式 `yyyyMMddHHmmss` |

> 数据特点：同一 `LOT_ID` 下有多条记录，按 `SAVE_TIME` 递增排列，时间间隔约 2 秒。一次完整的配方执行过程会产生数十到数百条曲线数据。

### 3.2 字段映射与显示

| 字段 | 原始值 | 显示值 | 说明 |
|------|--------|--------|------|
| `SAVE_TIME` | `20260428020715` | `02:07:15` | 取时分秒作为 X 轴标签 |
| `MAX_PRESSURE` | 7.000 | `7.0` | 加压压力，保留原始数值 |
| `FEED_PRESSURE` | 8.000 | `8.0` | 输送压力，保留原始数值 |
| `SYS_NO` | 1 | `1#` | 系统编号后加 `#` 号 |

## 四、接口文档

### 4.1 获取批号列表（用于下拉框）

```
GET /api/exhibition/chart.php?action=listLots&startDate=YYYY-MM-DD&endDate=YYYY-MM-DD
```

**请求参数**：

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| startDate | string | 否 | 开始日期，默认 5 天前 |
| endDate | string | 否 | 结束日期，默认今天 |

**响应示例**：
```json
{
  "code": 0,
  "message": "获取成功",
  "data": {
    "lots": ["202604280133456659", "202604281429296750"]
  }
}
```

**后端逻辑**：
1. 将日期转为 `Ymd000000` / `Ymd235959` 格式
2. 查询 `R_CURVE` 中该时间范围内的所有 `LOT_ID`（去重）
3. 按 `LOT_ID` 降序排列，最多返回 200 条
4. 未传日期参数时，查询全表最近 200 个批号

### 4.2 获取曲线数据（历史查询）

```
GET /api/exhibition/chart.php?action=history&startTime=YmdHis&endTime=YmdHis&sysNo=0&lotId=
```

**请求参数**：

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| startTime | string | 是 | 开始时间 `YmdHis`（如 `20260428000000`） |
| endTime | string | 是 | 结束时间 `YmdHis`（如 `20260428235959`） |
| sysNo | int | 否 | 系统编号（0=全部，1/2/3=具体系统） |
| lotId | string | 否 | 批号（空字符串=全部） |
| limit | int | 否 | 最大返回条数，默认 2000，最大 5000 |

**响应示例**：
```json
{
  "code": 0,
  "message": "获取成功",
  "data": {
    "count": 49,
    "records": [
      {
        "id": 44,
        "lotId": "202604280133456659",
        "recipeName": "444",
        "batch": 2,
        "sysNo": 1,
        "maxPressure": 7.0,
        "feedPressure": 8.0,
        "saveTime": "20260428021017"
      }
    ]
  }
}
```

**后端逻辑**：
1. 构建动态 WHERE 条件（lotId、sysNo、startTime、endTime）
2. 按 `SAVE_TIME ASC` 排序返回（折线图需要按时间顺序绘制）
3. 返回字段包含 `maxPressure`、`feedPressure`、`saveTime`、`sysNo`

## 五、界面设计

### 5.1 布局结构

```
┌──────────────────────────────────────────┐
│ 2026/04/28  至  2026/04/28         🔍   │  ← 第一行：日期 + 查询按钮
├──────────────────────────────────────────┤
│ 批号: [下拉框    ▼]  系统: [下拉 ▼]      │  ← 第二行：批号下拉 + 系统下拉
├──────────────────────────────────────────┤
│ ── 1#加压压力  ── 1#输送压力             │  ← 图例（ECharts）
│ ── 2#输送压力  ── 3#加压压力             │
│ ── 3#输送压力                            │
├──────────────────────────────────────────┤
│                                          │
│         ╭──╮                             │
│        ╱    ╲    ╭──╮                    │  ← ECharts 折线图
│       ╱      ╲  ╱    ╲                   │
│      ╱        ╲╱      ╲────╮             │
│                                          │
└──────────────────────────────────────────┘
```

### 5.2 筛选栏设计

与报表窗口一致的两排筛选栏：
- 第一排：开始日期 + "至" + 结束日期 + SVG 查询按钮
- 第二排：`批号:` 标签 + 批号下拉框（`flex: 2`）+ `系统:` 标签 + 系统下拉框（`flex: 1; max-width: 90px`）

**与报表窗口的差异**：
1. 无顶部标题栏和返回按钮（微信小程序导航栏已显示标题）
2. 批号下拉框**无"全部"选项**，必须选择具体批号或留空查询所有
3. 选择框 change 时自动触发查询（无需点击查询按钮）

### 5.3 曲线颜色设计

每条线有独立颜色，同一系统的加压和输送使用相近色系：

| 线条 | 颜色 | 色值 |
|------|------|------|
| 1#加压压力 | 红色 | `#ff4444` |
| 1#输送压力 | 粉色 | `#f472b6` |
| 2#输送压力 | 青色 | `#06b6d4` |
| 3#加压压力 | 深棕色 | `#8B4513` |
| 3#输送压力 | 亮黄色 | `#FFFF00` |

> **关键实现**：每条线的 `lineStyle.color` 和 `itemStyle.color` 必须设置为相同值，确保 ECharts 图例圆点颜色、线条颜色、数据点颜色三者完全一致。

## 六、开发说明

### 6.1 文件清单

| 文件路径 | 说明 |
|----------|------|
| `mobile/exhibition/chart/index.html` | 曲线查询页面（筛选栏 + ECharts 折线图） |
| `api/exhibition/chart.php` | 曲线数据 API（`action=listLots` + `action=history`） |

### 6.2 关键代码说明

#### 按系统分组构建 Series

```javascript
function buildSeries(records) {
    // 按系统分组
    var sysGroups = {};
    records.forEach(function(r) {
        var sys = r.sysNo || 0;
        if (!sysGroups[sys]) sysGroups[sys] = [];
        sysGroups[sys].push(r);
    });

    // 收集所有唯一时间并排序
    var timeSet = {};
    records.forEach(function(r) {
        var t = r.saveTime;
        var timeStr = t.substring(8,10) + ':' + t.substring(10,12) + ':' + t.substring(12,14);
        timeSet[timeStr] = true;
    });
    var allTimes = Object.keys(timeSet).sort();

    var series = [];
    var legendData = [];

    Object.keys(sysGroups).sort().forEach(function(sys) {
        var sysData = sysGroups[sys];
        var maxMap = {};
        var feedMap = {};
        sysData.forEach(function(r) {
            var t = r.saveTime;
            var timeStr = t.substring(8,10) + ':' + t.substring(10,12) + ':' + t.substring(12,14);
            maxMap[timeStr] = r.maxPressure;
            feedMap[timeStr] = r.feedPressure;
        });

        var maxName = sys + '#加压压力';
        var feedName = sys + '#输送压力';
        var maxColor = lineColors[sys + '_max'] || '#ff4444';
        var feedColor = lineColors[sys + '_feed'] || '#f472b6';
        legendData.push(maxName, feedName);

        // 加压压力
        series.push({
            name: maxName,
            type: 'line',
            data: allTimes.map(function(t) { 
                return maxMap[t] !== undefined ? maxMap[t] : '-'; 
            }),
            smooth: true,
            symbol: 'none',
            lineStyle: { color: maxColor, width: 2 },
            itemStyle: { color: maxColor },  // 确保图例圆点颜色一致
            animationDuration: 300
        });

        // 输送压力
        series.push({
            name: feedName,
            type: 'line',
            data: allTimes.map(function(t) { 
                return feedMap[t] !== undefined ? feedMap[t] : '-'; 
            }),
            smooth: true,
            symbol: 'none',
            lineStyle: { color: feedColor, width: 2 },
            itemStyle: { color: feedColor },  // 确保图例圆点颜色一致
            animationDuration: 300
        });
    });

    return { times: allTimes, series: series, legendData: legendData };
}
```

> **设计要点**：
> 1. 按 `SYS_NO` 分组数据，每个系统生成两条 series
> 2. 收集所有唯一时间点排序后作为 X 轴，各系统在不属于自己的时间点填入 `'-'`（ECharts 不连线）
> 3. `itemStyle.color` 必须与 `lineStyle.color` 一致，否则 ECharts 图例圆点会使用默认调色板颜色，与线条颜色不一致

#### ECharts 图表配置

```javascript
chart.setOption({
    backgroundColor: 'transparent',
    grid: { top: 40, right: 20, bottom: 30, left: 50 },
    legend: {
        data: legendData,
        textStyle: { color: 'rgba(255,255,255,0.6)', fontSize: 11 },
        top: 8
    },
    tooltip: {
        trigger: 'axis',
        backgroundColor: 'rgba(0,0,0,0.8)',
        textStyle: { color: '#fff', fontSize: 12 }
    },
    xAxis: {
        type: 'category',
        data: times,
        axisLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } },
        axisLabel: { color: 'rgba(255,255,255,0.4)', fontSize: 10 }
    },
    yAxis: {
        type: 'value',
        axisLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } },
        splitLine: { lineStyle: { color: 'rgba(255,255,255,0.05)' } },
        axisLabel: { color: 'rgba(255,255,255,0.4)', fontSize: 10 }
    },
    series: series
}, true); // true = not merge, replace
```

> **`true` 参数的含义**：`setOption(option, true)` 表示不合并（not merge），完全替换之前的配置。这在切换查询条件（不同批号/系统）时非常重要，避免旧数据残留。

## 七、曲线查询设计流程与开发思路

### 7.1 需求分析与设计目标

**业务场景**：展会现场，管理人员需要查看某次配方执行过程中的压力变化趋势，判断设备运行是否平稳、压力是否在正常范围内。

**核心需求**：
1. 查看某段时间内所有压力记录
2. 按批号筛选，聚焦某一次配方执行的完整过程
3. 对比加压压力和输送压力两条曲线的变化趋势
4. 支持多系统同时对比（1# / 2# / 3#）
5. 筛选栏操作习惯与报表窗口保持一致

**设计目标**：
1. 筛选零学习成本：复用报表窗口的筛选栏布局
2. 曲线直观可读：颜色区分系统，图例清晰对应
3. 多系统对比：选择"全部"系统时，三个系统的曲线同时显示
4. 点线颜色一致：图例圆点、线条、数据点使用同一颜色

### 7.2 UI 设计决策

#### 为什么用 ECharts 而非 Canvas 自绘？

| 方案 | 体积 | 依赖 | 功能 | 决策 |
|------|------|------|------|------|
| Canvas 自绘 | 0KB | 无 | 需手写坐标轴、网格、tooltip、legend | ❌ 开发成本高，交互体验差 |
| ECharts | ~1MB（本地） | 本地 JS | 完整的坐标轴、tooltip、legend、动画 | ✅ 功能完善，本地加载快 |

ECharts 库文件已下载到本地 `static/js/echarts.min.js`（约 1MB），从服务器本地加载避免外部 CDN 网络延迟。ECharts 的 `tooltip.trigger: 'axis'` 可以悬停查看同一时间点上所有曲线的数值，交互体验远优于 Canvas 自绘。

#### 为什么删除实时模式？

早期版本设计了"查询模式"和"实时模式"两个标签页：
- **查询模式**：按日期范围查询历史数据
- **实时模式**：每 2 秒轮询最新数据，模拟实时曲线

**删除原因**：
1. 展会场景下，用户主要关心已执行完成的生产记录，而非实时监控
2. 实时模式需要持续轮询 API，增加服务器压力和流量消耗
3. 两个模式增加了界面复杂度，与报表/发送记录的简洁风格不一致

**决策**：只保留历史查询模式，页面打开即显示筛选栏和图表。

#### 为什么批号下拉框去除"全部"选项？

报表窗口的批号下拉框有"全部"选项，表示不筛选批号。曲线窗口去除"全部"选项的设计考量：

1. **曲线图的本质**：曲线展示的是一次配方执行过程中的压力变化。如果不选批号，查询结果包含多个批号的数据，画在一张图上会形成多段不连续的曲线，难以解读。
2. **引导用户精确筛选**：去除"全部"后，用户必须选择具体批号（或留空查询所有），更符合曲线分析的使用场景。
3. **减少数据量**：不选批号时返回的数据可能很大，影响加载速度和渲染性能。

### 7.3 交互设计决策

#### 选择框 change 自动触发查询

报表窗口中，批号和系统选择框变化后需要点击查询按钮才刷新数据。曲线窗口改为 change 时自动触发查询：

**原因**：
- 曲线查询的结果是图表而非表格，用户调整筛选条件后期望立即看到曲线变化
- 图表的加载反馈比表格更直观（ECharts 有过渡动画）
- 减少用户操作步骤（不用每次选完批号再点查询）

**实现**：
```javascript
lotSelect.addEventListener('change', function() {
    if (!validateDateRange()) return;
    queryHistory();
});
sysSelect.addEventListener('change', function() {
    if (!validateDateRange()) return;
    queryHistory();
});
```

#### 页面加载自动查询

与报表窗口、发送记录窗口一致，页面加载时自动使用默认日期范围（昨天~今天）查询数据。如果该范围内无数据，显示空状态提示。

### 7.4 图表设计决策

#### 为什么按索引均匀分布而非时间比例？

`R_CURVE` 中同一 `LOT_ID` 的数据间隔约 2 秒，但不同 `LOT_ID` 之间可能间隔数小时。

**按时间比例分布的问题**：
- 如果选"全部"系统且包含多个批号，两个批号之间会出现大片空白
- 曲线变成几段孤立的短线，视觉上难以判断趋势

**按索引均匀分布的优点**：
- 所有数据点等距排列，每段曲线连续可读
- 便于观察压力上升/下降/平稳的趋势
- 不同系统之间的曲线对比更直观

**代价**：失去了时间比例的精确性。但对于"观察压力变化趋势"这个核心需求，趋势比时间比例更重要。

#### 为什么每条线设置 `itemStyle.color`？

ECharts 的 series 如果只设置 `lineStyle.color`，legend 的圆点颜色会从**全局调色板**自动分配，可能与线条颜色不一致。

**问题现象**：1#加压压力的线条是红色，但图例圆点显示为蓝色，用户不知道看图例颜色还是线条颜色来区分。

**解决方案**：同时设置 `lineStyle.color`（线条颜色）和 `itemStyle.color`（图例圆点/数据点颜色），确保三者完全一致。

```javascript
series.push({
    lineStyle: { color: maxColor, width: 2 },  // 线条颜色
    itemStyle: { color: maxColor },             // 图例圆点 + 数据点颜色
});
```

#### 6 条线的颜色选择逻辑

| 系统 | 加压 | 输送 | 色系逻辑 |
|------|------|------|----------|
| 1# | 红 `#ff4444` | 粉 `#f472b6` | 红色系，加压深、输送浅 |
| 2# | — | 青 `#06b6d4` | 2#系统无加压压力参数 |
| 3# | 深棕 `#8B4513` | 亮黄 `#FFFF00` | 棕色系，加压深、输送浅 |

**选择原则**：
1. 三个系统的加压压力使用**高饱和度、高对比度**的颜色（红、绿、棕），便于一眼区分
2. 同一系统的输送压力使用**同色系较浅**的颜色，视觉上自然关联
3. 避免蓝紫色系（容易在深色背景上不够醒目）

### 7.5 数据库查询设计

#### 为什么历史查询用 `ORDER BY SAVE_TIME ASC`？

报表查询使用 `DESC`（从新到旧），因为表格展示时用户最关心最新记录。但折线图必须从起点画到终点：

```
DESC 排序：t5 → t4 → t3 → t2 → t1
折线绘制：从 t5 往回画到 t1（左右颠倒）

ASC 排序：t1 → t2 → t3 → t4 → t5
折线绘制：从 t1 正常画到 t5（符合时间流向）
```

**决策**：曲线数据按 `ASC` 排序，保证折线从左到右对应时间从早到晚。

## 八、开发中的关键问题与解决方案

### 问题 1：ECharts 图例圆点颜色与线条颜色不一致

**现象**：1#加压压力的线条是红色，但图例中的圆点显示为蓝色。

**根因**：只设置了 `lineStyle.color`，ECharts legend 的圆点颜色从默认调色板取色。

**解决**：同时设置 `itemStyle: { color: maxColor }`，确保图例圆点、线条、数据点使用同一颜色。

### 问题 2：不同系统数据时间范围不重叠，画在一张图上出现大片空白

**现象**：1#系统数据在 02:07~02:14，2#系统在 02:08~02:09，3#系统在 02:10~02:11。按时间比例画 X 轴，三个系统的曲线之间有大量空白。

**根因**：不同系统的生产时间不连续。

**解决**：X 轴不按时间比例分布，而是按数据索引均匀分布。所有数据点等距排列，曲线连续可读。

### 问题 3：切换查询条件后旧曲线残留

**现象**：先查询批号 A（3 条曲线），再查询批号 B（2 条曲线），图表上仍然显示 3 条线，其中一条是旧数据。

**根因**：ECharts 的 `setOption` 默认是合并模式，新配置与旧配置叠加。

**解决**：`setOption(option, true)`，第二个参数传 `true` 表示不合并、完全替换。

### 问题 4：批号下拉框无数据时显示为空

**现象**：默认日期范围内没有 R_CURVE 数据，批号下拉框为空，用户不知道是没数据还是加载失败。

**根因**：曲线窗口去除了"全部"选项，无数据时下拉框完全空白。

**解决**：当前行为是下拉框为空。实际使用中，用户需要调整日期到有数据的范围。如果需要更明确的提示，可在无批号时显示占位提示（当前未实现）。

## 九、最佳实践总结

1. **ECharts 颜色一致性**：`lineStyle.color` 和 `itemStyle.color` 必须同时设置，否则图例圆点颜色与线条颜色不一致
2. **setOption 替换模式**：切换数据时使用 `setOption(option, true)`，避免旧数据残留
3. **时间序列排序**：折线图数据按 `ASC` 排序（从早到晚），表格数据按 `DESC` 排序（从新到旧）
4. **X 轴均匀分布**：多批号/多系统的时间序列数据，按索引均匀分布比时间比例分布更易读
5. **按系统分组绘制**：时间序列数据先按系统分组，再为每个系统独立生成 series，避免数据交叉污染
6. **筛选栏复用**：相同筛选需求的页面复用同一套筛选栏布局和交互逻辑，降低用户学习成本
7. **无"全部"选项设计**：对于需要精确聚焦的查询场景（如曲线图），去除"全部"选项引导用户精确筛选
8. **change 自动查询**：图表类页面，筛选条件变化后自动刷新，减少用户操作步骤

## 十、更新记录

| 日期 | 版本 | 更新内容 |
|------|------|----------|
| 2026-05-06 | v1.1 | 移除 2#加压压力（该系统无此参数），3#颜色改为深棕/亮黄，增加数据有效性过滤（隐藏全 0/null 线条），调整 legend 边距避免遮挡 Y 轴刻度 |
| 2026-05-06 | v1.0 | 曲线查询页面定稿：两排筛选栏（与报表一致）、ECharts 折线图、5 条独立颜色曲线（1#红/粉、2#青、3#棕/橙）、按系统分组绘制、itemStyle+lineStyle 颜色一致、删除实时模式、批号下拉框去除"全部"选项、X 轴按索引均匀分布 |
