# 生产监控界面设计流程与开发要点

> 文件路径：`mobile/exhibition/monitor/index.html`  
> API 路径：`api/exhibition/monitor.php`  
> 数据表：`M_REALTIME_DATA`

---

## 一、项目概述

| 项目 | 说明 |
|------|------|
| 目标平台 | 微信小程序 WebView（Mobile H5） |
| 前端技术 | 原生 HTML5 + CSS3 + JavaScript + ECharts |
| 后端技术 | PHP 8.x + PDO + MySQL |
| 数据库 | `miniprogram_exhibition` |
| 核心功能 | 实时生产数据监控、压力曲线展示、配方发送 |

---

## 二、数据库设计

### 2.1 实时数据表 `M_REALTIME_DATA`

上位机每 1 秒更新一次该表中的单条记录（`ID=1` 或最新记录）。**字段名、类型、注释必须与下图严格一致，不得修改**。

| 字段名 | 类型 | 可空 | 默认值 | 注释 |
|--------|------|------|--------|------|
| `ID` | `bigint` | N | `AUTO_INCREMENT` | 自增长 |
| `LOT_ID` | `varchar(20)` | N | - | 批次号 |
| `RECIPE_NAME` | `varchar(30)` | N | `''` | 配方名称 |
| `BATCH_SET` | `int` | Y | `0` | 设定车数 |
| `BATCH1_WEIGH_FINISH` | `int` | Y | `0` | 1#称量完成车数 |
| `BATCH2_WEIGH_FINISH` | `int` | Y | `0` | 2#称量完成车数 |
| `BATCH3_WEIGH_FINISH` | `int` | Y | `0` | 3#称量完成车数 |
| `BATCH1_FINISH` | `int` | Y | `0` | 1#输送完成车数 |
| `BATCH2_FINISH` | `int` | Y | `0` | 2#输送完成车数 |
| `BATCH3_FINISH` | `int` | Y | `0` | 3#输送完成车数 |
| `WEIGHT1` | `decimal(8,3)` | Y | `0` | 秤1实时重量 |
| `WEIGHT2` | `decimal(8,3)` | Y | `0` | 秤2实时重量 |
| `WEIGHT3` | `decimal(8,3)` | Y | `0` | 秤3实时重量 |
| `FEED_START_1` | `tinyint` | Y | `0` | 1#输送开始 |
| `FEED_START_2` | `tinyint` | Y | `0` | 2#输送开始 |
| `FEED_START_3` | `tinyint` | Y | `0` | 3#输送开始 |
| `MAX_PRESSURE_1` | `decimal(8,3)` | Y | `0` | 1#加压压力 |
| `FEED_PRESSURE_1` | `decimal(8,3)` | Y | `0` | 1#输送压力 |
| `MAX_PRESSURE_2` | `decimal(8,3)` | Y | `0` | 2#加压压力 |
| `FEED_PRESSURE_2` | `decimal(8,3)` | Y | `0` | 2#输送压力 |
| `MAX_PRESSURE_3` | `decimal(8,3)` | Y | `0` | 3#加压压力 |
| `FEED_PRESSURE_3` | `decimal(8,3)` | Y | `0` | 3#输送压力 |
| `SAVE_TIME` | `varchar(14)` | N | `''` | 修改时间 |

**建表 SQL 位置**：`sql/exhibition_new_tables.sql`

> ⚠️ MySQL 8.0+ 注意：`TINYINT` 不要写 `TINYINT(1)`，display width 已废弃会产生 1681 警告。

---

## 三、后端 API 设计

### 3.1 接口文件

`api/exhibition/monitor.php`

### 3.2 接口列表

| action | 说明 | 请求方式 | 示例 |
|--------|------|----------|------|
| 空 / `status` | 获取实时状态数据 | GET | `/api/exhibition/monitor.php` |
| `curve` | 获取实时压力快照 | GET | `/api/exhibition/monitor.php?action=curve` |
| `recentWeigh` | 获取最近称重记录 | GET | `/api/exhibition/monitor.php?action=recentWeigh&limit=10` |

### 3.3 字段映射关系

PHP 读取 `M_REALTIME_DATA` 后，将数据库字段名映射为前端 JSON 字段名：

| 数据库字段 | 前端 JSON 字段 | 说明 |
|-----------|---------------|------|
| `LOT_ID` | `lotId` | 批次号 |
| `RECIPE_NAME` | `recipeName` | 配方名称 |
| `BATCH_SET` | `batchSet` | 设定车数 |
| `BATCH1_FINISH` | `batch1Finish` | 1#输送完成车数 |
| `BATCH2_FINISH` | `batch2Finish` | 2#输送完成车数 |
| `BATCH3_FINISH` | `batch3Finish` | 3#输送完成车数 |
| `WEIGHT1` | `weight1Act` | 秤1实时重量 |
| `WEIGHT2` | `weight2Act` | 秤2实时重量 |
| `WEIGHT3` | `weight3Act` | 秤3实时重量 |
| `FEED_START_1` | `feed1Start` | 1#输送开始状态 |
| `FEED_START_2` | `feed2Start` | 2#输送开始状态 |
| `FEED_START_3` | `feed3Start` | 3#输送开始状态 |
| `SAVE_TIME` | `updateTime` | 修改时间 |
| `MAX_PRESSURE_1` | `maxPressure1` | 1#加压压力 |
| `FEED_PRESSURE_1` | `feedPressure1` | 1#输送压力 |
| `MAX_PRESSURE_2` | `maxPressure2` | 2#加压压力 |
| `FEED_PRESSURE_2` | `feedPressure2` | 2#输送压力 |
| `MAX_PRESSURE_3` | `maxPressure3` | 3#加压压力 |
| `FEED_PRESSURE_3` | `feedPressure3` | 3#输送压力 |

> 压力字段使用 `isset()` 保护，防止字段不存在时报错，默认返回 `0`。

---

## 四、前端页面设计

### 4.1 页面结构（从上到下）

```
┌─────────────────────────────┐
│  配方名称 / 批次号            │  ← info-section
├─────────────────────────────┤
│  完成车次  1# 2# 3#          │  ← status-grid（finish/set 车）
├─────────────────────────────┤
│  实时重量  1# 2# 3#          │  ← weight-grid（显示 1 位小数，单位 Kg）
├─────────────────────────────┤
│  输送状态  1# 2# 3#          │  ← feed-grid（绿点=运行中，灰点=停止）
├─────────────────────────────┤
│  实时曲线（ECharts）          │  ← chart-box（60 点滚动缓冲）
├─────────────────────────────┤
│  📋 📤 📊 📈                 │  ← bottom-nav（底部导航）
└─────────────────────────────┘
          ↑
    点击"发送配方"弹出底部 Sheet
```

### 4.2 发送配方面板（Bottom Sheet）

```
┌─────────────────────────────┐
│  ───  发送配方        ✕     │
├─────────────────────────────┤
│  配方名称 [________] [▼]    │
│  车数     [  3  ▲▼]         │
├─────────────────────────────┤
│  配方参数设定                │
│  ┌────────┬────────┬────────┐
│  │ 1#系统 │ 2#系统 │ 3#系统 │  ← param-grid（灰蓝渐变卡片）
│  │ 设定重量│ 设定重量│ 设定重量│
│  │ [____] │ [____] │ [____] │
│  │ 设定误差│ 设定误差│ 设定误差│
│  │ [____] │ [____] │ [____] │
│  └────────┴────────┴────────┘
├─────────────────────────────┤
│    [  关闭  ]  [  下达  ]   │
└─────────────────────────────┘
```

---

## 五、核心开发要点

### 5.1 数据轮询机制

- **监控状态轮询**：`setInterval(loadMonitorData, 1000)` — 每 1 秒拉取一次
- **曲线数据轮询**：`setInterval(loadCurveData, 1000)` — 每 1 秒拉取一次压力快照
- 页面卸载时应清理定时器（当前已实现，如需增强可监听 `beforeunload`）

### 5.2 曲线缓冲区设计

`M_REALTIME_DATA` 只有单条快照，没有历史时序数据。前端通过 `curveBuffer[]` 数组模拟时间序列：

```javascript
let curveBuffer = []; // 最多保留 60 个点（约 60 秒）

// 每次 polling 时 push 新点
if (curveBuffer.length > 60) curveBuffer.shift();
```

- 每个点包含时间戳 `HH:MM:SS` 和 5 条曲线数据
- ECharts 横轴为时间，纵轴为压力值
- 数据为 `0` / `null` / `undefined` 的曲线不显示

### 5.3 曲线颜色规范

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

> **注意**：2#系统没有加压压力参数，只显示输送压力。

### 5.4 曲线布局参数

```javascript
grid: { top: 56, right: 10, bottom: 24, left: 42 }
```

- `top: 56` 确保图例与 Y 轴标签不重叠
- 曲线查询页面（`chart/index.html`）同步使用相同配置

### 5.5 重量显示规范

- 显示 **1 位小数**：`(data.weight1Act || 0).toFixed(1)`
- 单位统一为 **Kg**（大写 K）

### 5.6 完成车次显示规范

- 格式：`finish / set 车`
- 例如：`3 / 5 车`
- 使用 `BATCH1_FINISH`（输送完成车数），不是 `BATCH1_WEIGH_FINISH`

### 5.7 配方发送验证规则

| 参数 | 1#限制 | 2#限制 | 3#限制 |
|------|--------|--------|--------|
| 设定重量 | `>0` 且 `≤9` | `>0` 且 `≤15` | `>0` 且 `≤8` |
| 设定误差 | `>0` 且 `<9` | `>0` 且 `<15` | `>0` 且 `<8` |
| 误差 ≤ 重量 | 是 | 是 | 是 |

- 车数范围：`1 ~ 5`
- 前端和后端（`api/exhibition/recipe.php`）均需校验

### 5.8 WeChat 小程序导航

必须使用 `wx.miniProgram.navigateTo`，失败时回退到 `window.location.href`：

```javascript
function navTo(page) {
    var url = window.location.origin + '/mobile/exhibition/' + page + '/index.html';
    if (typeof wx !== 'undefined' && wx.miniProgram && wx.miniProgram.navigateTo) {
        wx.miniProgram.navigateTo({
            url: '/pages/webview/webview?url=' + encodeURIComponent(url),
            fail: function() { window.location.href = url; }
        });
    } else {
        window.location.href = url;
    }
}
```

> ⚠️ **禁止使用 `onclick` 内联事件**（微信审核会拦截），必须使用 `addEventListener`。

### 5.9 缓存处理

微信 WebView 缓存极强，更新 CSS/JS 后可能不生效。解决方案：
- 资源 URL 加时间戳：`?v=${Date.now()}`
- 或者修改文件名/版本号

---

## 六、文件清单

| 文件 | 说明 |
|------|------|
| `mobile/exhibition/monitor/index.html` | 主监控界面 |
| `mobile/exhibition/chart/index.html` | 曲线查询界面 |
| `api/exhibition/monitor.php` | 监控数据 & 曲线 API |
| `api/exhibition/recipe.php` | 配方列表 & 发送 API |
| `sql/exhibition_new_tables.sql` | `M_REALTIME_DATA` 建表语句 |
| `static/js/echarts.min.js` | ECharts 图表库 |

---

## 七、常见问题

1. **曲线不显示**：检查 `M_REALTIME_DATA` 中 `MAX_PRESSURE_*` / `FEED_PRESSURE_*` 是否有非零值；PHP 用 `isset()` 保护，字段不存在时返回 `0`。
2. **页面跳转失败**：检查微信小程序 `app.json` 中是否注册了 `/pages/webview/webview` 页面。
3. **样式更新不生效**：微信缓存导致，清缓存或给 URL 加 `?v=时间戳`。
4. **重量显示多位小数**：确认前端使用了 `.toFixed(1)`。
