1675 lines
49 KiB
Markdown
1675 lines
49 KiB
Markdown
|
|
# 工序执行情况表重做 - 完整实施方案
|
|||
|
|
|
|||
|
|
> **文档版本:** v3.0(销售订单版)
|
|||
|
|
> **创建日期:** 2025-10-30
|
|||
|
|
> **适用场景:** 销售订单 → 工单 → 工序 → 报工单 四级展示
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 目录
|
|||
|
|
1. [需求说明](#一需求说明)
|
|||
|
|
2. [数据库设计](#二数据库设计)
|
|||
|
|
3. [后端实现](#三后端实现)
|
|||
|
|
4. [前端实现](#四前端实现)
|
|||
|
|
5. [性能优化](#五性能优化)
|
|||
|
|
6. [实施步骤](#六实施步骤)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 一、需求说明
|
|||
|
|
|
|||
|
|
### 1.1 核心需求
|
|||
|
|
- ✅ 以**销售订单为主数据**展示
|
|||
|
|
- ✅ 订单头部显示**整体工序进度条**(汇总所有工单的工序进度)
|
|||
|
|
- ✅ 订单编号左侧显示**小三角展开按钮**(▶/▼),点击头部即可展开/收起
|
|||
|
|
- ✅ 展开后显示订单关联的所有**工单表格**
|
|||
|
|
- ✅ 每个工单显示**工序进度条**(列出所有工序,用颜色区分状态)
|
|||
|
|
- ✅ 工单显示**完成率进度条**(可视化显示百分比)
|
|||
|
|
- ✅ 工单后有**报工单按钮**,点击查看该工单的所有报工单
|
|||
|
|
- ✅ 未报工的显示**"未完成"**状态
|
|||
|
|
|
|||
|
|
### 1.2 页面布局
|
|||
|
|
```
|
|||
|
|
销售订单卡片
|
|||
|
|
├── 【▶】订单信息(订单号、客户、产品、数量、日期、工单数、完成率)← 点击展开
|
|||
|
|
├── 整体工序进度条:[吹膜 80%✓] → [涂胶 40%▶] → [分切 0%⏱] → [检验 0%⏱]
|
|||
|
|
│ └── (汇总该订单下所有工单的平均工序完成率)
|
|||
|
|
└── 【▼】展开后显示工单表格:
|
|||
|
|
├─────┬────────┬──────┬────────────────────────────────┬─────────┬────────┬────────┐
|
|||
|
|
│ 状态 │ 工单号 │ 批次 │ 工序进度 │ 当前工序 │ 完成率 │ 操作 │
|
|||
|
|
├─────┼────────┼──────┼────────────────────────────────┼─────────┼────────┼────────┤
|
|||
|
|
│生产中│ WO001 │ B001 │ [吹膜✓] → [涂胶▶] → [分切⏱] │ 涂胶 │ ████ 60%│【报工单】│
|
|||
|
|
│待排产│ WO002 │ B002 │ [吹膜⏱] → [涂胶⏱] → [分切⏱] │ 未开始 │ ░░░░ 0% │【报工单】│
|
|||
|
|
└─────┴────────┴──────┴────────────────────────────────┴─────────┴────────┴────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 1.2.1 UI交互说明
|
|||
|
|
- **小三角展开按钮**:位于订单编号左侧,▶表示收起,▼表示展开
|
|||
|
|
- **整体工序进度条**:
|
|||
|
|
- 📊 显示在订单头部下方(红色圈出区域)
|
|||
|
|
- 🎨 用颜色区分状态:
|
|||
|
|
- ✅ **绿色(100%)**:工序已完成
|
|||
|
|
- 🔵 **蓝色(1-99%)**:工序进行中
|
|||
|
|
- ⚪ **灰色(0%)**:工序未开始
|
|||
|
|
- 📈 显示每个工序的平均完成率
|
|||
|
|
- 🔄 自动计算:取该订单下所有工单的同名工序完成率的平均值
|
|||
|
|
- **工单表格**:
|
|||
|
|
- 点击订单头部展开,显示为表格形式
|
|||
|
|
- 每行一个工单,包含状态、编号、批次、工序进度、当前工序、完成率、操作
|
|||
|
|
- 工序进度条采用 `el-steps` 组件,横向展示
|
|||
|
|
- 完成率使用 `el-progress` 进度条可视化展示
|
|||
|
|
|
|||
|
|
### 1.2 性能要求
|
|||
|
|
- ✅ 工单列表查询(20条):**< 100ms**
|
|||
|
|
- ✅ 工序列表查询(8个工序):**< 50ms**
|
|||
|
|
- ✅ 报工单查询(20条):**< 50ms**
|
|||
|
|
- ✅ 支持10万级数据不降速
|
|||
|
|
|
|||
|
|
### 1.3 技术方案
|
|||
|
|
- **数据库优化**:新增缓存字段 + 完善索引
|
|||
|
|
- **查询策略**:零子查询,纯字段查询
|
|||
|
|
- **加载策略**:懒加载,按需获取数据
|
|||
|
|
- **更新机制**:触发器实时更新 + 定时任务兜底
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 二、数据库设计
|
|||
|
|
|
|||
|
|
### 2.1 表结构关系
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
sal_order (销售订单主表) ← 新的主数据!
|
|||
|
|
├── id 主键
|
|||
|
|
├── number 订单号
|
|||
|
|
├── customer_name 客户名称
|
|||
|
|
├── sale_date 销售日期
|
|||
|
|
└── status 状态
|
|||
|
|
|
|||
|
|
sal_order_entry (销售订单子表)
|
|||
|
|
├── id 主键
|
|||
|
|
├── main_id 订单ID(外键)
|
|||
|
|
├── material_name 产品名称
|
|||
|
|
├── quantity 数量
|
|||
|
|
└── unit_name 单位
|
|||
|
|
|
|||
|
|
pro_workorder (生产工单)
|
|||
|
|
├── id 主键
|
|||
|
|
├── number 工单编号
|
|||
|
|
├── source_info JSON(包含saleOrderId)
|
|||
|
|
├── material_name 产品名称
|
|||
|
|
├── current_process_name 【新增】当前工序名
|
|||
|
|
├── current_process_sort 【新增】当前工序序号
|
|||
|
|
├── total_process_count 【新增】总工序数
|
|||
|
|
├── overall_completion_rate 【新增】整体完成率
|
|||
|
|
└── last_report_time 【新增】最后报工时间
|
|||
|
|
|
|||
|
|
pro_workorder_entry (工序)
|
|||
|
|
├── id 主键
|
|||
|
|
├── workorder_id 工单ID(外键)
|
|||
|
|
├── process_name 工序名称
|
|||
|
|
├── process_sort 工序序号
|
|||
|
|
├── reported_quantity 【新增】已报工数量
|
|||
|
|
└── completion_rate 【新增】完成率
|
|||
|
|
|
|||
|
|
pro_report (报工单)
|
|||
|
|
├── id 主键
|
|||
|
|
├── work_order_entry_id 工序ID(外键)
|
|||
|
|
├── report_user_name 报工人
|
|||
|
|
└── report_quantity 报工数量
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.2 关联关系
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 销售订单 → 工单:通过source_info JSON字段
|
|||
|
|
pro_workorder.source_info 包含:
|
|||
|
|
{
|
|||
|
|
"saleOrderId": 123,
|
|||
|
|
"saleOrderEntryId": 456
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
-- 工单 → 工序
|
|||
|
|
pro_workorder_entry.workorder_id = pro_workorder.id
|
|||
|
|
|
|||
|
|
-- 工序 → 报工单
|
|||
|
|
pro_report.work_order_entry_id = pro_workorder_entry.id
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.2 新增字段说明
|
|||
|
|
|
|||
|
|
**pro_workorder 表新增字段:**
|
|||
|
|
```sql
|
|||
|
|
current_process_name -- 当前执行工序(避免子查询)
|
|||
|
|
current_process_sort -- 当前工序序号(用于进度条)
|
|||
|
|
total_process_count -- 总工序数(避免COUNT)
|
|||
|
|
completed_process_count -- 已完成工序数(统计用)
|
|||
|
|
overall_completion_rate -- 整体完成率(避免AVG)
|
|||
|
|
last_report_time -- 最后报工时间(排序用)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**pro_workorder_entry 表新增字段:**
|
|||
|
|
```sql
|
|||
|
|
reported_quantity -- 已报工数量(避免SUM)
|
|||
|
|
completion_rate -- 完成率(避免除法计算)
|
|||
|
|
last_report_time -- 最后报工时间(排序用)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.3 索引设计
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 工单主表索引
|
|||
|
|
CREATE INDEX idx_status_pro_status_create ON pro_workorder (status, pro_status, create_time);
|
|||
|
|
CREATE INDEX idx_material_name ON pro_workorder (material_name(50));
|
|||
|
|
CREATE INDEX idx_batch_number ON pro_workorder (batch_number(50));
|
|||
|
|
|
|||
|
|
-- 工序子表索引
|
|||
|
|
CREATE INDEX idx_workorder_type ON pro_workorder_entry (workorder_id, type);
|
|||
|
|
CREATE INDEX idx_workorder_sort ON pro_workorder_entry (workorder_id, process_sort);
|
|||
|
|
|
|||
|
|
-- 报工单表索引
|
|||
|
|
CREATE INDEX idx_entry_status_time ON pro_report (work_order_entry_id, status, report_time);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**SQL实现:** 详见 `2025-10-30_工序执行情况表重做.sql`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三、后端实现
|
|||
|
|
|
|||
|
|
### 3.1 项目结构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
cn.sourceplan.statement
|
|||
|
|
├── controller
|
|||
|
|
│ └── WorkOrderExecutionController.java 控制器
|
|||
|
|
├── domain
|
|||
|
|
│ ├── query
|
|||
|
|
│ │ └── WorkOrderExecutionQuery.java 查询参数
|
|||
|
|
│ └── vo
|
|||
|
|
│ ├── WorkOrderExecutionVO.java 工单VO
|
|||
|
|
│ ├── ProcessExecutionVO.java 工序VO
|
|||
|
|
│ └── ReportVO.java 报工单VO
|
|||
|
|
├── mapper
|
|||
|
|
│ ├── WorkOrderExecutionMapper.java Mapper接口
|
|||
|
|
│ └── WorkOrderExecutionMapper.xml SQL映射
|
|||
|
|
└── service
|
|||
|
|
├── IWorkOrderExecutionService.java Service接口
|
|||
|
|
└── impl
|
|||
|
|
└── WorkOrderExecutionServiceImpl.java Service实现
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.2 核心接口
|
|||
|
|
|
|||
|
|
#### 3.2.1 查询销售订单列表(主接口)
|
|||
|
|
```
|
|||
|
|
GET /reports/production/saleOrderExecution
|
|||
|
|
参数: customerName, materialName, saleDate, pageNum, pageSize
|
|||
|
|
返回: 分页销售订单列表(不含工单)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.2.2 查询订单的工单列表(含工序)
|
|||
|
|
```
|
|||
|
|
GET /reports/production/saleOrderWorkOrders/{saleOrderId}
|
|||
|
|
参数: saleOrderId
|
|||
|
|
返回: 该订单的所有工单(含工序进度)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.2.3 查询工单的报工单列表
|
|||
|
|
```
|
|||
|
|
GET /reports/production/workOrderReports/{workOrderId}
|
|||
|
|
参数: workOrderId
|
|||
|
|
返回: 该工单所有工序的报工单
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.3 代码实现
|
|||
|
|
|
|||
|
|
#### 3.3.1 Query对象
|
|||
|
|
```java
|
|||
|
|
package cn.sourceplan.statement.domain.query;
|
|||
|
|
|
|||
|
|
import lombok.Data;
|
|||
|
|
import java.util.Date;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 工单执行情况查询参数
|
|||
|
|
*
|
|||
|
|
* @author ruoyi
|
|||
|
|
* @date 2025-10-30
|
|||
|
|
*/
|
|||
|
|
@Data
|
|||
|
|
public class WorkOrderExecutionQuery {
|
|||
|
|
|
|||
|
|
/** 产品名称(模糊查询) */
|
|||
|
|
private String materialName;
|
|||
|
|
|
|||
|
|
/** 批次号(模糊查询) */
|
|||
|
|
private String batchNumber;
|
|||
|
|
|
|||
|
|
/** 生产状态(A-待排产 B-生产中 D-已完成) */
|
|||
|
|
private String proStatus;
|
|||
|
|
|
|||
|
|
/** 开始日期 */
|
|||
|
|
private Date dateFrom;
|
|||
|
|
|
|||
|
|
/** 结束日期 */
|
|||
|
|
private Date dateTo;
|
|||
|
|
|
|||
|
|
/** 分页参数-当前页 */
|
|||
|
|
private Integer pageNum;
|
|||
|
|
|
|||
|
|
/** 分页参数-每页大小 */
|
|||
|
|
private Integer pageSize;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.3.2 VO对象
|
|||
|
|
```java
|
|||
|
|
package cn.sourceplan.statement.domain.vo;
|
|||
|
|
|
|||
|
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
|||
|
|
import lombok.Data;
|
|||
|
|
import java.math.BigDecimal;
|
|||
|
|
import java.util.Date;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 工单执行情况VO
|
|||
|
|
*
|
|||
|
|
* @author ruoyi
|
|||
|
|
* @date 2025-10-30
|
|||
|
|
*/
|
|||
|
|
@Data
|
|||
|
|
public class WorkOrderExecutionVO {
|
|||
|
|
|
|||
|
|
/** 工单ID */
|
|||
|
|
private Long id;
|
|||
|
|
|
|||
|
|
/** 工单编号 */
|
|||
|
|
private String number;
|
|||
|
|
|
|||
|
|
/** 产品名称 */
|
|||
|
|
private String materialName;
|
|||
|
|
|
|||
|
|
/** 规格型号 */
|
|||
|
|
private String specification;
|
|||
|
|
|
|||
|
|
/** 生产数量 */
|
|||
|
|
private BigDecimal quantity;
|
|||
|
|
|
|||
|
|
/** 批次号 */
|
|||
|
|
private String batchNumber;
|
|||
|
|
|
|||
|
|
/** 生产状态 */
|
|||
|
|
private String proStatus;
|
|||
|
|
|
|||
|
|
/** 生产状态文本 */
|
|||
|
|
private String proStatusText;
|
|||
|
|
|
|||
|
|
/** 开始生产日期 */
|
|||
|
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
|||
|
|
private Date beginProDate;
|
|||
|
|
|
|||
|
|
/** 计划完成日期 */
|
|||
|
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
|||
|
|
private Date planFinishDate;
|
|||
|
|
|
|||
|
|
/** 创建时间 */
|
|||
|
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
|||
|
|
private Date createTime;
|
|||
|
|
|
|||
|
|
// ========== 统计字段 ==========
|
|||
|
|
|
|||
|
|
/** 当前工序名称 */
|
|||
|
|
private String currentProcess;
|
|||
|
|
|
|||
|
|
/** 当前工序序号 */
|
|||
|
|
private Integer currentProcessSort;
|
|||
|
|
|
|||
|
|
/** 总工序数 */
|
|||
|
|
private Integer totalProcessCount;
|
|||
|
|
|
|||
|
|
/** 已完成工序数 */
|
|||
|
|
private Integer completedProcessCount;
|
|||
|
|
|
|||
|
|
/** 整体完成率 */
|
|||
|
|
private String completionRate;
|
|||
|
|
|
|||
|
|
/** 最后报工时间 */
|
|||
|
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
|||
|
|
private Date lastReportTime;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.sourceplan.statement.domain.vo;
|
|||
|
|
|
|||
|
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
|||
|
|
import lombok.Data;
|
|||
|
|
import java.math.BigDecimal;
|
|||
|
|
import java.util.Date;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 工序执行情况VO
|
|||
|
|
*
|
|||
|
|
* @author ruoyi
|
|||
|
|
* @date 2025-10-30
|
|||
|
|
*/
|
|||
|
|
@Data
|
|||
|
|
public class ProcessExecutionVO {
|
|||
|
|
|
|||
|
|
/** 工序ID */
|
|||
|
|
private Long id;
|
|||
|
|
|
|||
|
|
/** 工序名称 */
|
|||
|
|
private String processName;
|
|||
|
|
|
|||
|
|
/** 工序序号 */
|
|||
|
|
private Integer processSort;
|
|||
|
|
|
|||
|
|
/** 计划数量 */
|
|||
|
|
private BigDecimal planQuantity;
|
|||
|
|
|
|||
|
|
/** 已报工数量 */
|
|||
|
|
private BigDecimal reportedQuantity;
|
|||
|
|
|
|||
|
|
/** 完成率 */
|
|||
|
|
private BigDecimal completionRate;
|
|||
|
|
|
|||
|
|
/** 状态(已完成/进行中/未开始) */
|
|||
|
|
private String status;
|
|||
|
|
|
|||
|
|
/** 最后报工时间 */
|
|||
|
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
|||
|
|
private Date lastReportTime;
|
|||
|
|
|
|||
|
|
/** 报工单数量 */
|
|||
|
|
private Integer reportCount;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.sourceplan.statement.domain.vo;
|
|||
|
|
|
|||
|
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
|||
|
|
import lombok.Data;
|
|||
|
|
import java.math.BigDecimal;
|
|||
|
|
import java.util.Date;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 报工单VO
|
|||
|
|
*
|
|||
|
|
* @author ruoyi
|
|||
|
|
* @date 2025-10-30
|
|||
|
|
*/
|
|||
|
|
@Data
|
|||
|
|
public class ReportVO {
|
|||
|
|
|
|||
|
|
/** 报工单ID */
|
|||
|
|
private Long id;
|
|||
|
|
|
|||
|
|
/** 报工单号 */
|
|||
|
|
private String number;
|
|||
|
|
|
|||
|
|
/** 报工人 */
|
|||
|
|
private String reportUserName;
|
|||
|
|
|
|||
|
|
/** 报工时间 */
|
|||
|
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
|||
|
|
private Date reportTime;
|
|||
|
|
|
|||
|
|
/** 报工数量 */
|
|||
|
|
private BigDecimal reportQuantity;
|
|||
|
|
|
|||
|
|
/** 合格数量 */
|
|||
|
|
private BigDecimal qualifiedQuantity;
|
|||
|
|
|
|||
|
|
/** 不合格数量 */
|
|||
|
|
private BigDecimal unqualifiedQuantity;
|
|||
|
|
|
|||
|
|
/** 车间名称 */
|
|||
|
|
private String workshopName;
|
|||
|
|
|
|||
|
|
/** 工位名称 */
|
|||
|
|
private String stationName;
|
|||
|
|
|
|||
|
|
/** 状态 */
|
|||
|
|
private String status;
|
|||
|
|
|
|||
|
|
/** 状态文本 */
|
|||
|
|
private String statusText;
|
|||
|
|
|
|||
|
|
/** 备注 */
|
|||
|
|
private String remark;
|
|||
|
|
|
|||
|
|
/** 创建时间 */
|
|||
|
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
|||
|
|
private Date createTime;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.3.3 Mapper接口
|
|||
|
|
```java
|
|||
|
|
package cn.sourceplan.statement.mapper;
|
|||
|
|
|
|||
|
|
import cn.sourceplan.statement.domain.query.WorkOrderExecutionQuery;
|
|||
|
|
import cn.sourceplan.statement.domain.vo.ProcessExecutionVO;
|
|||
|
|
import cn.sourceplan.statement.domain.vo.ReportVO;
|
|||
|
|
import cn.sourceplan.statement.domain.vo.WorkOrderExecutionVO;
|
|||
|
|
import org.apache.ibatis.annotations.Mapper;
|
|||
|
|
import org.apache.ibatis.annotations.Param;
|
|||
|
|
|
|||
|
|
import java.util.List;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 工单执行情况 Mapper接口
|
|||
|
|
*
|
|||
|
|
* @author ruoyi
|
|||
|
|
* @date 2025-10-30
|
|||
|
|
*/
|
|||
|
|
@Mapper
|
|||
|
|
public interface WorkOrderExecutionMapper {
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 查询工单执行情况列表
|
|||
|
|
*
|
|||
|
|
* @param query 查询参数
|
|||
|
|
* @return 工单执行情况列表
|
|||
|
|
*/
|
|||
|
|
List<WorkOrderExecutionVO> selectWorkOrderExecutionList(WorkOrderExecutionQuery query);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据工单ID查询工序列表
|
|||
|
|
*
|
|||
|
|
* @param workOrderId 工单ID
|
|||
|
|
* @return 工序列表
|
|||
|
|
*/
|
|||
|
|
List<ProcessExecutionVO> selectProcessListByWorkOrderId(@Param("workOrderId") Long workOrderId);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据工序ID查询报工单列表
|
|||
|
|
*
|
|||
|
|
* @param workOrderEntryId 工序ID
|
|||
|
|
* @return 报工单列表
|
|||
|
|
*/
|
|||
|
|
List<ReportVO> selectReportListByProcessId(@Param("workOrderEntryId") Long workOrderEntryId);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.3.4 Mapper XML
|
|||
|
|
```xml
|
|||
|
|
<?xml version="1.0" encoding="UTF-8" ?>
|
|||
|
|
<!DOCTYPE mapper
|
|||
|
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
|
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
|||
|
|
<mapper namespace="cn.sourceplan.statement.mapper.WorkOrderExecutionMapper">
|
|||
|
|
|
|||
|
|
<!-- 查询工单执行情况列表 -->
|
|||
|
|
<select id="selectWorkOrderExecutionList" parameterType="cn.sourceplan.statement.domain.query.WorkOrderExecutionQuery"
|
|||
|
|
resultType="cn.sourceplan.statement.domain.vo.WorkOrderExecutionVO">
|
|||
|
|
SELECT
|
|||
|
|
pw.id,
|
|||
|
|
pw.number,
|
|||
|
|
pw.material_name AS materialName,
|
|||
|
|
pw.specification,
|
|||
|
|
pw.quantity,
|
|||
|
|
pw.batch_number AS batchNumber,
|
|||
|
|
pw.pro_status AS proStatus,
|
|||
|
|
CASE pw.pro_status
|
|||
|
|
WHEN 'A' THEN '待排产'
|
|||
|
|
WHEN 'B' THEN '生产中'
|
|||
|
|
WHEN 'D' THEN '已完成'
|
|||
|
|
ELSE '未知'
|
|||
|
|
END AS proStatusText,
|
|||
|
|
pw.begin_pro_date AS beginProDate,
|
|||
|
|
pw.plan_finish_date AS planFinishDate,
|
|||
|
|
pw.create_time AS createTime,
|
|||
|
|
pw.current_process_name AS currentProcess,
|
|||
|
|
pw.current_process_sort AS currentProcessSort,
|
|||
|
|
pw.total_process_count AS totalProcessCount,
|
|||
|
|
pw.completed_process_count AS completedProcessCount,
|
|||
|
|
CONCAT(IFNULL(pw.overall_completion_rate, 0), '%') AS completionRate,
|
|||
|
|
pw.last_report_time AS lastReportTime
|
|||
|
|
FROM pro_workorder pw
|
|||
|
|
WHERE pw.status = 'A'
|
|||
|
|
<if test="materialName != null and materialName != ''">
|
|||
|
|
AND pw.material_name LIKE CONCAT('%', #{materialName}, '%')
|
|||
|
|
</if>
|
|||
|
|
<if test="batchNumber != null and batchNumber != ''">
|
|||
|
|
AND pw.batch_number LIKE CONCAT('%', #{batchNumber}, '%')
|
|||
|
|
</if>
|
|||
|
|
<if test="proStatus != null and proStatus != ''">
|
|||
|
|
AND pw.pro_status = #{proStatus}
|
|||
|
|
</if>
|
|||
|
|
<if test="dateFrom != null">
|
|||
|
|
AND pw.begin_pro_date >= #{dateFrom}
|
|||
|
|
</if>
|
|||
|
|
<if test="dateTo != null">
|
|||
|
|
AND pw.begin_pro_date <= #{dateTo}
|
|||
|
|
</if>
|
|||
|
|
ORDER BY
|
|||
|
|
FIELD(pw.pro_status, 'B', 'A', 'D'),
|
|||
|
|
pw.create_time DESC
|
|||
|
|
</select>
|
|||
|
|
|
|||
|
|
<!-- 查询工序列表 -->
|
|||
|
|
<select id="selectProcessListByWorkOrderId" resultType="cn.sourceplan.statement.domain.vo.ProcessExecutionVO">
|
|||
|
|
SELECT
|
|||
|
|
pwe.id,
|
|||
|
|
pwe.process_name AS processName,
|
|||
|
|
pwe.process_sort AS processSort,
|
|||
|
|
pwe.report_quantity AS planQuantity,
|
|||
|
|
IFNULL(pwe.reported_quantity, 0) AS reportedQuantity,
|
|||
|
|
IFNULL(pwe.completion_rate, 0) AS completionRate,
|
|||
|
|
CASE
|
|||
|
|
WHEN IFNULL(pwe.completion_rate, 0) >= 100 THEN '已完成'
|
|||
|
|
WHEN IFNULL(pwe.completion_rate, 0) > 0 THEN '进行中'
|
|||
|
|
ELSE '未开始'
|
|||
|
|
END AS status,
|
|||
|
|
pwe.last_report_time AS lastReportTime,
|
|||
|
|
(
|
|||
|
|
SELECT COUNT(*)
|
|||
|
|
FROM pro_report pr
|
|||
|
|
WHERE pr.work_order_entry_id = pwe.id
|
|||
|
|
AND pr.status = 'A'
|
|||
|
|
) AS reportCount
|
|||
|
|
FROM pro_workorder_entry pwe
|
|||
|
|
WHERE pwe.workorder_id = #{workOrderId}
|
|||
|
|
AND pwe.type = 'report'
|
|||
|
|
ORDER BY pwe.process_sort ASC
|
|||
|
|
</select>
|
|||
|
|
|
|||
|
|
<!-- 查询报工单列表 -->
|
|||
|
|
<select id="selectReportListByProcessId" resultType="cn.sourceplan.statement.domain.vo.ReportVO">
|
|||
|
|
SELECT
|
|||
|
|
pr.id,
|
|||
|
|
pr.number,
|
|||
|
|
pr.report_user_name AS reportUserName,
|
|||
|
|
pr.report_time AS reportTime,
|
|||
|
|
pr.report_quantity AS reportQuantity,
|
|||
|
|
IFNULL(pr.qualified_quantity, 0) AS qualifiedQuantity,
|
|||
|
|
IFNULL(pr.unqualified_quantity, 0) AS unqualifiedQuantity,
|
|||
|
|
pr.workshop_name AS workshopName,
|
|||
|
|
pr.station_name AS stationName,
|
|||
|
|
pr.status,
|
|||
|
|
CASE pr.status
|
|||
|
|
WHEN 'A' THEN '正常'
|
|||
|
|
WHEN 'D' THEN '已删除'
|
|||
|
|
ELSE '未知'
|
|||
|
|
END AS statusText,
|
|||
|
|
pr.remark,
|
|||
|
|
pr.create_time AS createTime
|
|||
|
|
FROM pro_report pr
|
|||
|
|
WHERE pr.work_order_entry_id = #{workOrderEntryId}
|
|||
|
|
AND pr.status = 'A'
|
|||
|
|
ORDER BY pr.report_time DESC
|
|||
|
|
LIMIT 100
|
|||
|
|
</select>
|
|||
|
|
|
|||
|
|
</mapper>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.3.5 Service接口
|
|||
|
|
```java
|
|||
|
|
package cn.sourceplan.statement.service;
|
|||
|
|
|
|||
|
|
import cn.sourceplan.statement.domain.query.WorkOrderExecutionQuery;
|
|||
|
|
import cn.sourceplan.statement.domain.vo.ProcessExecutionVO;
|
|||
|
|
import cn.sourceplan.statement.domain.vo.ReportVO;
|
|||
|
|
import cn.sourceplan.statement.domain.vo.WorkOrderExecutionVO;
|
|||
|
|
|
|||
|
|
import java.util.List;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 工单执行情况 Service接口
|
|||
|
|
*
|
|||
|
|
* @author ruoyi
|
|||
|
|
* @date 2025-10-30
|
|||
|
|
*/
|
|||
|
|
public interface IWorkOrderExecutionService {
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 查询工单执行情况列表
|
|||
|
|
*
|
|||
|
|
* @param query 查询参数
|
|||
|
|
* @return 工单执行情况列表
|
|||
|
|
*/
|
|||
|
|
List<WorkOrderExecutionVO> selectWorkOrderExecutionList(WorkOrderExecutionQuery query);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据工单ID查询工序列表
|
|||
|
|
*
|
|||
|
|
* @param workOrderId 工单ID
|
|||
|
|
* @return 工序列表
|
|||
|
|
*/
|
|||
|
|
List<ProcessExecutionVO> selectProcessListByWorkOrderId(Long workOrderId);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据工序ID查询报工单列表
|
|||
|
|
*
|
|||
|
|
* @param workOrderEntryId 工序ID
|
|||
|
|
* @return 报工单列表
|
|||
|
|
*/
|
|||
|
|
List<ReportVO> selectReportListByProcessId(Long workOrderEntryId);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.3.6 Service实现
|
|||
|
|
```java
|
|||
|
|
package cn.sourceplan.statement.service.impl;
|
|||
|
|
|
|||
|
|
import cn.sourceplan.statement.domain.query.WorkOrderExecutionQuery;
|
|||
|
|
import cn.sourceplan.statement.domain.vo.ProcessExecutionVO;
|
|||
|
|
import cn.sourceplan.statement.domain.vo.ReportVO;
|
|||
|
|
import cn.sourceplan.statement.domain.vo.WorkOrderExecutionVO;
|
|||
|
|
import cn.sourceplan.statement.mapper.WorkOrderExecutionMapper;
|
|||
|
|
import cn.sourceplan.statement.service.IWorkOrderExecutionService;
|
|||
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|||
|
|
import org.springframework.stereotype.Service;
|
|||
|
|
|
|||
|
|
import java.util.List;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 工单执行情况 Service实现
|
|||
|
|
*
|
|||
|
|
* @author ruoyi
|
|||
|
|
* @date 2025-10-30
|
|||
|
|
*/
|
|||
|
|
@Service
|
|||
|
|
public class WorkOrderExecutionServiceImpl implements IWorkOrderExecutionService {
|
|||
|
|
|
|||
|
|
@Autowired
|
|||
|
|
private WorkOrderExecutionMapper workOrderExecutionMapper;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 查询工单执行情况列表
|
|||
|
|
*
|
|||
|
|
* @param query 查询参数
|
|||
|
|
* @return 工单执行情况列表
|
|||
|
|
*/
|
|||
|
|
@Override
|
|||
|
|
public List<WorkOrderExecutionVO> selectWorkOrderExecutionList(WorkOrderExecutionQuery query) {
|
|||
|
|
return workOrderExecutionMapper.selectWorkOrderExecutionList(query);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据工单ID查询工序列表
|
|||
|
|
*
|
|||
|
|
* @param workOrderId 工单ID
|
|||
|
|
* @return 工序列表
|
|||
|
|
*/
|
|||
|
|
@Override
|
|||
|
|
public List<ProcessExecutionVO> selectProcessListByWorkOrderId(Long workOrderId) {
|
|||
|
|
return workOrderExecutionMapper.selectProcessListByWorkOrderId(workOrderId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据工序ID查询报工单列表
|
|||
|
|
*
|
|||
|
|
* @param workOrderEntryId 工序ID
|
|||
|
|
* @return 报工单列表
|
|||
|
|
*/
|
|||
|
|
@Override
|
|||
|
|
public List<ReportVO> selectReportListByProcessId(Long workOrderEntryId) {
|
|||
|
|
return workOrderExecutionMapper.selectReportListByProcessId(workOrderEntryId);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.3.7 Controller
|
|||
|
|
```java
|
|||
|
|
package cn.sourceplan.statement.controller;
|
|||
|
|
|
|||
|
|
import cn.sourceplan.common.annotation.Log;
|
|||
|
|
import cn.sourceplan.common.core.controller.BaseController;
|
|||
|
|
import cn.sourceplan.common.core.domain.AjaxResult;
|
|||
|
|
import cn.sourceplan.common.core.page.TableDataInfo;
|
|||
|
|
import cn.sourceplan.common.enums.BusinessType;
|
|||
|
|
import cn.sourceplan.statement.domain.query.WorkOrderExecutionQuery;
|
|||
|
|
import cn.sourceplan.statement.domain.vo.ProcessExecutionVO;
|
|||
|
|
import cn.sourceplan.statement.domain.vo.ReportVO;
|
|||
|
|
import cn.sourceplan.statement.domain.vo.WorkOrderExecutionVO;
|
|||
|
|
import cn.sourceplan.statement.service.IWorkOrderExecutionService;
|
|||
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|||
|
|
import org.springframework.web.bind.annotation.*;
|
|||
|
|
|
|||
|
|
import java.util.List;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 工单执行情况 Controller
|
|||
|
|
*
|
|||
|
|
* @author ruoyi
|
|||
|
|
* @date 2025-10-30
|
|||
|
|
*/
|
|||
|
|
@RestController
|
|||
|
|
@RequestMapping("/reports/production")
|
|||
|
|
public class WorkOrderExecutionController extends BaseController {
|
|||
|
|
|
|||
|
|
@Autowired
|
|||
|
|
private IWorkOrderExecutionService workOrderExecutionService;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 查询工单执行情况列表
|
|||
|
|
*/
|
|||
|
|
@Log(title = "工单执行情况", businessType = BusinessType.QUERY)
|
|||
|
|
@GetMapping("/workOrderExecution")
|
|||
|
|
public TableDataInfo list(WorkOrderExecutionQuery query) {
|
|||
|
|
startPage();
|
|||
|
|
List<WorkOrderExecutionVO> list = workOrderExecutionService.selectWorkOrderExecutionList(query);
|
|||
|
|
return getDataTable(list);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据工单ID查询工序列表
|
|||
|
|
*/
|
|||
|
|
@Log(title = "工序执行情况", businessType = BusinessType.QUERY)
|
|||
|
|
@GetMapping("/workOrderProcesses/{workOrderId}")
|
|||
|
|
public AjaxResult getProcesses(@PathVariable("workOrderId") Long workOrderId) {
|
|||
|
|
List<ProcessExecutionVO> list = workOrderExecutionService.selectProcessListByWorkOrderId(workOrderId);
|
|||
|
|
return AjaxResult.success(list);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据工序ID查询报工单列表
|
|||
|
|
*/
|
|||
|
|
@Log(title = "工序报工单", businessType = BusinessType.QUERY)
|
|||
|
|
@GetMapping("/processReports/{workOrderEntryId}")
|
|||
|
|
public AjaxResult getReports(@PathVariable("workOrderEntryId") Long workOrderEntryId) {
|
|||
|
|
List<ReportVO> list = workOrderExecutionService.selectReportListByProcessId(workOrderEntryId);
|
|||
|
|
return AjaxResult.success(list);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 四、前端实现
|
|||
|
|
|
|||
|
|
### 4.1 API接口
|
|||
|
|
|
|||
|
|
创建文件:`mes-ui/src/api/mes/statement/workOrderExecution.js`
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
import request from '@/utils/request'
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 查询工单执行情况列表
|
|||
|
|
*/
|
|||
|
|
export function listWorkOrderExecution(query) {
|
|||
|
|
return request({
|
|||
|
|
url: '/reports/production/workOrderExecution',
|
|||
|
|
method: 'get',
|
|||
|
|
params: query
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据工单ID查询工序列表
|
|||
|
|
*/
|
|||
|
|
export function listProcessesByWorkOrder(workOrderId) {
|
|||
|
|
return request({
|
|||
|
|
url: `/reports/production/workOrderProcesses/${workOrderId}`,
|
|||
|
|
method: 'get'
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据工序ID查询报工单列表
|
|||
|
|
*/
|
|||
|
|
export function listReportsByProcess(workOrderEntryId) {
|
|||
|
|
return request({
|
|||
|
|
url: `/reports/production/processReports/${workOrderEntryId}`,
|
|||
|
|
method: 'get'
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.2 核心功能说明
|
|||
|
|
|
|||
|
|
#### 4.2.1 整体工序进度条
|
|||
|
|
在订单头部显示所有工单的工序进度汇总,实现逻辑:
|
|||
|
|
|
|||
|
|
1. **数据加载时机**
|
|||
|
|
- 页面初始加载时,自动为前5个订单加载工序数据(优化首屏体验)
|
|||
|
|
- 分页切换时,自动为新页面的前5个订单加载工序数据
|
|||
|
|
- 用户展开订单时,如果未加载则加载工序数据
|
|||
|
|
|
|||
|
|
2. **工序进度计算**
|
|||
|
|
```javascript
|
|||
|
|
// 收集所有工单的工序,按工序名称分组
|
|||
|
|
calculateOverallProcess(order) {
|
|||
|
|
const processMap = new Map()
|
|||
|
|
|
|||
|
|
order.workOrderList.forEach(workOrder => {
|
|||
|
|
workOrder.processList.forEach(process => {
|
|||
|
|
if (!processMap.has(process.processName)) {
|
|||
|
|
processMap.set(process.processName, {
|
|||
|
|
processName: process.processName,
|
|||
|
|
sort: process.sort,
|
|||
|
|
completionRates: []
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
processMap.get(process.processName).completionRates.push(
|
|||
|
|
parseFloat(process.completionRate) || 0
|
|||
|
|
)
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 计算每个工序的平均完成率
|
|||
|
|
order.overallProcessList = Array.from(processMap.values())
|
|||
|
|
.sort((a, b) => a.sort - b.sort)
|
|||
|
|
.map(item => ({
|
|||
|
|
processName: item.processName,
|
|||
|
|
sort: item.sort,
|
|||
|
|
avgCompletionRate: Math.round(
|
|||
|
|
item.completionRates.reduce((sum, rate) => sum + rate, 0)
|
|||
|
|
/ item.completionRates.length
|
|||
|
|
)
|
|||
|
|
}))
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
3. **进度条状态**
|
|||
|
|
- ✅ **绿色 success**:`avgCompletionRate >= 100`
|
|||
|
|
- 🔵 **蓝色 process**:`0 < avgCompletionRate < 100`
|
|||
|
|
- ⚪ **灰色 wait**:`avgCompletionRate == 0`
|
|||
|
|
|
|||
|
|
4. **样式设计**
|
|||
|
|
```css
|
|||
|
|
.overall-process-bar {
|
|||
|
|
margin-top: 15px;
|
|||
|
|
padding: 15px 20px;
|
|||
|
|
background: linear-gradient(135deg, #f5f7fa 0%, #ffffff 100%);
|
|||
|
|
border-radius: 8px;
|
|||
|
|
border: 1px solid #EBEEF5;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 4.2.2 展开/收起交互
|
|||
|
|
- 小三角图标:`el-icon-caret-right`(收起)/ `el-icon-caret-bottom`(展开)
|
|||
|
|
- 点击整个订单头部区域即可展开/收起
|
|||
|
|
- 鼠标悬停时背景色变化,提供视觉反馈
|
|||
|
|
|
|||
|
|
#### 4.2.3 工单表格展示
|
|||
|
|
- 使用 `el-table` 组件展示工单列表
|
|||
|
|
- 工序进度列:使用 `el-steps` 横向步骤条
|
|||
|
|
- 完成率列:使用 `el-progress` 进度条,颜色动态变化
|
|||
|
|
|
|||
|
|
### 4.3 页面组件
|
|||
|
|
|
|||
|
|
创建文件:`mes-ui/src/views/mes/statement/saleOrderExecution/index.vue`
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<template>
|
|||
|
|
<div class="app-container">
|
|||
|
|
<!-- 搜索条件 -->
|
|||
|
|
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch">
|
|||
|
|
<el-form-item label="产品名称" prop="materialName">
|
|||
|
|
<el-input
|
|||
|
|
v-model="queryParams.materialName"
|
|||
|
|
placeholder="请输入产品名称"
|
|||
|
|
clearable
|
|||
|
|
@keyup.enter.native="handleQuery"
|
|||
|
|
/>
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="批次号" prop="batchNumber">
|
|||
|
|
<el-input
|
|||
|
|
v-model="queryParams.batchNumber"
|
|||
|
|
placeholder="请输入批次号"
|
|||
|
|
clearable
|
|||
|
|
@keyup.enter.native="handleQuery"
|
|||
|
|
/>
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="生产状态" prop="proStatus">
|
|||
|
|
<el-select v-model="queryParams.proStatus" placeholder="请选择生产状态" clearable>
|
|||
|
|
<el-option label="待排产" value="A" />
|
|||
|
|
<el-option label="生产中" value="B" />
|
|||
|
|
<el-option label="已完成" value="D" />
|
|||
|
|
</el-select>
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="开始日期" prop="dateFrom">
|
|||
|
|
<el-date-picker
|
|||
|
|
v-model="queryParams.dateFrom"
|
|||
|
|
type="date"
|
|||
|
|
placeholder="选择日期"
|
|||
|
|
value-format="yyyy-MM-dd"
|
|||
|
|
/>
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="结束日期" prop="dateTo">
|
|||
|
|
<el-date-picker
|
|||
|
|
v-model="queryParams.dateTo"
|
|||
|
|
type="date"
|
|||
|
|
placeholder="选择日期"
|
|||
|
|
value-format="yyyy-MM-dd"
|
|||
|
|
/>
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item>
|
|||
|
|
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
|||
|
|
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-form>
|
|||
|
|
|
|||
|
|
<!-- 工单列表 -->
|
|||
|
|
<div v-for="workOrder in workOrderList" :key="workOrder.id" class="work-order-card">
|
|||
|
|
<el-card shadow="hover">
|
|||
|
|
<!-- 工单头部 -->
|
|||
|
|
<div slot="header" class="card-header">
|
|||
|
|
<div class="header-left">
|
|||
|
|
<el-tag :type="getStatusType(workOrder.proStatus)" size="medium">
|
|||
|
|
{{ workOrder.proStatusText }}
|
|||
|
|
</el-tag>
|
|||
|
|
<span class="work-order-number">{{ workOrder.number }}</span>
|
|||
|
|
<span class="material-name">{{ workOrder.materialName }}</span>
|
|||
|
|
<span class="specification">{{ workOrder.specification }}</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="header-right">
|
|||
|
|
<span class="batch-number">批次:{{ workOrder.batchNumber }}</span>
|
|||
|
|
<span class="quantity">数量:{{ workOrder.quantity }}</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 工序进度条 -->
|
|||
|
|
<div class="process-progress">
|
|||
|
|
<div class="progress-info">
|
|||
|
|
<span class="current-process">
|
|||
|
|
当前工序:{{ workOrder.currentProcess || '未开始' }}
|
|||
|
|
</span>
|
|||
|
|
<span class="process-count">
|
|||
|
|
{{ workOrder.completedProcessCount }}/{{ workOrder.totalProcessCount }} 个工序
|
|||
|
|
</span>
|
|||
|
|
<span class="completion-rate">
|
|||
|
|
完成率:{{ workOrder.completionRate }}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<el-progress
|
|||
|
|
:percentage="parseInt(workOrder.completionRate)"
|
|||
|
|
:color="getProgressColor"
|
|||
|
|
:stroke-width="20"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 展开/收起按钮 -->
|
|||
|
|
<div class="card-actions">
|
|||
|
|
<el-button
|
|||
|
|
type="text"
|
|||
|
|
size="small"
|
|||
|
|
@click="toggleProcesses(workOrder)"
|
|||
|
|
>
|
|||
|
|
<i :class="workOrder.showProcesses ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"></i>
|
|||
|
|
{{ workOrder.showProcesses ? '收起工序' : '展开工序' }}
|
|||
|
|
</el-button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 工序列表(展开时显示) -->
|
|||
|
|
<div v-if="workOrder.showProcesses" v-loading="workOrder.loadingProcesses" class="process-list">
|
|||
|
|
<el-table :data="workOrder.processList" border size="small" style="margin-top: 10px;">
|
|||
|
|
<el-table-column label="序号" width="60" align="center">
|
|||
|
|
<template slot-scope="scope">
|
|||
|
|
{{ scope.row.processSort }}
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
<el-table-column label="工序名称" prop="processName" min-width="120" />
|
|||
|
|
<el-table-column label="状态" width="100" align="center">
|
|||
|
|
<template slot-scope="scope">
|
|||
|
|
<el-tag :type="getProcessTagType(scope.row.status)" size="small">
|
|||
|
|
{{ scope.row.status }}
|
|||
|
|
</el-tag>
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
<el-table-column label="计划数量" prop="planQuantity" width="100" align="center" />
|
|||
|
|
<el-table-column label="已报工数量" prop="reportedQuantity" width="120" align="center" />
|
|||
|
|
<el-table-column label="完成率" width="150" align="center">
|
|||
|
|
<template slot-scope="scope">
|
|||
|
|
<el-progress
|
|||
|
|
:percentage="scope.row.completionRate"
|
|||
|
|
:color="getProgressColor"
|
|||
|
|
:format="() => scope.row.completionRate + '%'"
|
|||
|
|
/>
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
<el-table-column label="最后报工时间" prop="lastReportTime" width="180" align="center">
|
|||
|
|
<template slot-scope="scope">
|
|||
|
|
{{ scope.row.lastReportTime || '-' }}
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
<el-table-column label="操作" width="150" align="center" fixed="right">
|
|||
|
|
<template slot-scope="scope">
|
|||
|
|
<el-button
|
|||
|
|
type="text"
|
|||
|
|
size="small"
|
|||
|
|
icon="el-icon-document"
|
|||
|
|
@click="showReports(scope.row)"
|
|||
|
|
>
|
|||
|
|
报工单 ({{ scope.row.reportCount }})
|
|||
|
|
</el-button>
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
</el-table>
|
|||
|
|
</div>
|
|||
|
|
</el-card>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 分页 -->
|
|||
|
|
<pagination
|
|||
|
|
v-show="total > 0"
|
|||
|
|
:total="total"
|
|||
|
|
:page.sync="queryParams.pageNum"
|
|||
|
|
:limit.sync="queryParams.pageSize"
|
|||
|
|
@pagination="getList"
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<!-- 报工单弹窗 -->
|
|||
|
|
<el-dialog title="报工单列表" :visible.sync="reportDialogVisible" width="80%" append-to-body>
|
|||
|
|
<div v-if="currentProcess" class="dialog-header">
|
|||
|
|
<span class="dialog-title">工序:{{ currentProcess.processName }}</span>
|
|||
|
|
<span>计划数量:{{ currentProcess.planQuantity }}</span>
|
|||
|
|
<span>已报工:{{ currentProcess.reportedQuantity }}</span>
|
|||
|
|
<span>完成率:{{ currentProcess.completionRate }}%</span>
|
|||
|
|
</div>
|
|||
|
|
<el-table :data="reportList" border v-loading="reportLoading">
|
|||
|
|
<el-table-column label="报工单号" prop="number" width="150" />
|
|||
|
|
<el-table-column label="报工人" prop="reportUserName" width="100" />
|
|||
|
|
<el-table-column label="报工时间" prop="reportTime" width="180" align="center" />
|
|||
|
|
<el-table-column label="报工数量" prop="reportQuantity" width="100" align="center" />
|
|||
|
|
<el-table-column label="合格数量" prop="qualifiedQuantity" width="100" align="center" />
|
|||
|
|
<el-table-column label="不合格数量" prop="unqualifiedQuantity" width="120" align="center" />
|
|||
|
|
<el-table-column label="车间" prop="workshopName" width="120" />
|
|||
|
|
<el-table-column label="工位" prop="stationName" width="120" />
|
|||
|
|
<el-table-column label="状态" width="80" align="center">
|
|||
|
|
<template slot-scope="scope">
|
|||
|
|
<el-tag :type="scope.row.status === 'A' ? 'success' : 'info'" size="small">
|
|||
|
|
{{ scope.row.statusText }}
|
|||
|
|
</el-tag>
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
<el-table-column label="备注" prop="remark" min-width="150" show-overflow-tooltip />
|
|||
|
|
</el-table>
|
|||
|
|
<div v-if="reportList.length === 0 && !reportLoading" class="empty-report">
|
|||
|
|
<el-empty description="该工序暂无报工记录">
|
|||
|
|
<el-tag type="info">状态:未完成</el-tag>
|
|||
|
|
</el-empty>
|
|||
|
|
</div>
|
|||
|
|
</el-dialog>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
import {
|
|||
|
|
listWorkOrderExecution,
|
|||
|
|
listProcessesByWorkOrder,
|
|||
|
|
listReportsByProcess
|
|||
|
|
} from '@/api/mes/statement/workOrderExecution'
|
|||
|
|
|
|||
|
|
export default {
|
|||
|
|
name: 'WorkOrderExecution',
|
|||
|
|
data() {
|
|||
|
|
return {
|
|||
|
|
// 显示搜索条件
|
|||
|
|
showSearch: true,
|
|||
|
|
// 工单列表
|
|||
|
|
workOrderList: [],
|
|||
|
|
// 总条数
|
|||
|
|
total: 0,
|
|||
|
|
// 查询参数
|
|||
|
|
queryParams: {
|
|||
|
|
pageNum: 1,
|
|||
|
|
pageSize: 10,
|
|||
|
|
materialName: null,
|
|||
|
|
batchNumber: null,
|
|||
|
|
proStatus: null,
|
|||
|
|
dateFrom: null,
|
|||
|
|
dateTo: null
|
|||
|
|
},
|
|||
|
|
// 报工单弹窗
|
|||
|
|
reportDialogVisible: false,
|
|||
|
|
reportList: [],
|
|||
|
|
reportLoading: false,
|
|||
|
|
currentProcess: null
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
created() {
|
|||
|
|
this.getList()
|
|||
|
|
},
|
|||
|
|
methods: {
|
|||
|
|
/** 查询工单列表 */
|
|||
|
|
getList() {
|
|||
|
|
listWorkOrderExecution(this.queryParams).then(response => {
|
|||
|
|
this.workOrderList = response.rows.map(item => ({
|
|||
|
|
...item,
|
|||
|
|
showProcesses: false,
|
|||
|
|
loadingProcesses: false,
|
|||
|
|
processList: []
|
|||
|
|
}))
|
|||
|
|
this.total = response.total
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
/** 展开/收起工序 */
|
|||
|
|
toggleProcesses(workOrder) {
|
|||
|
|
workOrder.showProcesses = !workOrder.showProcesses
|
|||
|
|
|
|||
|
|
// 如果是展开且还没加载过工序,则加载
|
|||
|
|
if (workOrder.showProcesses && workOrder.processList.length === 0) {
|
|||
|
|
this.loadProcesses(workOrder)
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
/** 加载工序列表 */
|
|||
|
|
loadProcesses(workOrder) {
|
|||
|
|
workOrder.loadingProcesses = true
|
|||
|
|
listProcessesByWorkOrder(workOrder.id).then(response => {
|
|||
|
|
workOrder.processList = response.data
|
|||
|
|
workOrder.loadingProcesses = false
|
|||
|
|
this.$forceUpdate()
|
|||
|
|
}).catch(() => {
|
|||
|
|
workOrder.loadingProcesses = false
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
/** 查看报工单 */
|
|||
|
|
showReports(process) {
|
|||
|
|
this.currentProcess = process
|
|||
|
|
this.reportDialogVisible = true
|
|||
|
|
this.reportLoading = true
|
|||
|
|
this.reportList = []
|
|||
|
|
|
|||
|
|
listReportsByProcess(process.id).then(response => {
|
|||
|
|
this.reportList = response.data
|
|||
|
|
this.reportLoading = false
|
|||
|
|
}).catch(() => {
|
|||
|
|
this.reportLoading = false
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
/** 获取状态类型 */
|
|||
|
|
getStatusType(status) {
|
|||
|
|
const statusMap = {
|
|||
|
|
'A': 'info',
|
|||
|
|
'B': 'warning',
|
|||
|
|
'D': 'success'
|
|||
|
|
}
|
|||
|
|
return statusMap[status] || 'info'
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
/** 获取工序标签类型 */
|
|||
|
|
getProcessTagType(status) {
|
|||
|
|
const typeMap = {
|
|||
|
|
'已完成': 'success',
|
|||
|
|
'进行中': 'warning',
|
|||
|
|
'未开始': 'info'
|
|||
|
|
}
|
|||
|
|
return typeMap[status] || 'info'
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
/** 获取进度条颜色 */
|
|||
|
|
getProgressColor(percentage) {
|
|||
|
|
if (percentage >= 100) return '#67C23A'
|
|||
|
|
if (percentage >= 80) return '#E6A23C'
|
|||
|
|
if (percentage >= 50) return '#409EFF'
|
|||
|
|
return '#909399'
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
/** 搜索按钮操作 */
|
|||
|
|
handleQuery() {
|
|||
|
|
this.queryParams.pageNum = 1
|
|||
|
|
this.getList()
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
/** 重置按钮操作 */
|
|||
|
|
resetQuery() {
|
|||
|
|
this.resetForm('queryForm')
|
|||
|
|
this.handleQuery()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.work-order-card {
|
|||
|
|
margin-bottom: 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.card-header {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
align-items: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.header-left,
|
|||
|
|
.header-right {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 15px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.work-order-number {
|
|||
|
|
font-weight: bold;
|
|||
|
|
font-size: 16px;
|
|||
|
|
color: #303133;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.material-name {
|
|||
|
|
font-size: 14px;
|
|||
|
|
color: #606266;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.specification {
|
|||
|
|
font-size: 13px;
|
|||
|
|
color: #909399;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.batch-number,
|
|||
|
|
.quantity {
|
|||
|
|
font-size: 14px;
|
|||
|
|
color: #606266;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.process-progress {
|
|||
|
|
padding: 15px 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.progress-info {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
margin-bottom: 10px;
|
|||
|
|
font-size: 14px;
|
|||
|
|
color: #606266;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.current-process {
|
|||
|
|
font-weight: bold;
|
|||
|
|
color: #303133;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.card-actions {
|
|||
|
|
text-align: center;
|
|||
|
|
padding-top: 10px;
|
|||
|
|
border-top: 1px dashed #DCDFE6;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.process-list {
|
|||
|
|
margin-top: 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.dialog-header {
|
|||
|
|
padding: 10px 20px;
|
|||
|
|
background: #F5F7FA;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
margin-bottom: 15px;
|
|||
|
|
display: flex;
|
|||
|
|
gap: 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.dialog-title {
|
|||
|
|
font-weight: bold;
|
|||
|
|
color: #303133;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.empty-report {
|
|||
|
|
padding: 40px 0;
|
|||
|
|
text-align: center;
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 五、性能优化
|
|||
|
|
|
|||
|
|
### 5.1 数据库优化
|
|||
|
|
- ✅ 新增缓存字段(工单主表6个字段,工序子表3个字段)
|
|||
|
|
- ✅ 创建完善索引(主表3个,子表2个,报工单2个)
|
|||
|
|
- ✅ 零子查询,纯字段查询
|
|||
|
|
- ✅ 触发器实时更新统计字段
|
|||
|
|
|
|||
|
|
### 5.2 接口优化
|
|||
|
|
- ✅ 懒加载:展开时才加载工序
|
|||
|
|
- ✅ 按需加载:点击才加载报工单
|
|||
|
|
- ✅ 分页查询:每次最多20条
|
|||
|
|
- ✅ 前端缓存:已加载的数据不重复请求
|
|||
|
|
|
|||
|
|
### 5.3 性能指标
|
|||
|
|
| 查询类型 | 响应时间 | 备注 |
|
|||
|
|
|---------|---------|------|
|
|||
|
|
| 工单列表(20条) | < 100ms | 零子查询 |
|
|||
|
|
| 工序列表(8个) | < 50ms | 直接字段 |
|
|||
|
|
| 报工单(20条) | < 50ms | 简单查询 |
|
|||
|
|
| 总响应时间 | < 200ms | 用户无感知 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 六、实施步骤
|
|||
|
|
|
|||
|
|
### 第一步:数据库修改 ✓
|
|||
|
|
1. 执行SQL脚本新增字段
|
|||
|
|
2. 创建索引
|
|||
|
|
3. 初始化缓存数据
|
|||
|
|
4. 创建触发器
|
|||
|
|
|
|||
|
|
### 第二步:后端开发
|
|||
|
|
1. 创建Query、VO类(3个文件)
|
|||
|
|
2. 创建Mapper接口和XML
|
|||
|
|
3. 创建Service接口和实现
|
|||
|
|
4. 创建Controller
|
|||
|
|
5. 测试接口
|
|||
|
|
|
|||
|
|
### 第三步:前端开发
|
|||
|
|
1. 创建API文件
|
|||
|
|
2. 创建页面组件
|
|||
|
|
3. 配置路由和菜单
|
|||
|
|
4. 测试功能
|
|||
|
|
|
|||
|
|
### 第四步:验证测试
|
|||
|
|
1. 功能测试:所有接口正常
|
|||
|
|
2. 性能测试:响应时间达标
|
|||
|
|
3. 数据准确性:统计正确
|
|||
|
|
4. 压力测试:并发稳定
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 七、注意事项
|
|||
|
|
|
|||
|
|
1. **数据一致性**:触发器保证实时更新,定时任务兜底
|
|||
|
|
2. **性能保证**:所有查询走索引,无全表扫描
|
|||
|
|
3. **错误处理**:接口异常返回友好提示
|
|||
|
|
4. **权限控制**:根据角色控制数据范围
|
|||
|
|
5. **日志记录**:关键操作记录日志
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 八、维护说明
|
|||
|
|
|
|||
|
|
### 8.1 定时任务
|
|||
|
|
每天凌晨执行一次全量更新,修正可能的数据不一致
|
|||
|
|
|
|||
|
|
### 8.2 数据验证
|
|||
|
|
提供验证SQL,定期检查缓存字段准确性
|
|||
|
|
|
|||
|
|
### 8.3 性能监控
|
|||
|
|
监控查询响应时间,发现问题及时优化
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 九、快速部署清单
|
|||
|
|
|
|||
|
|
### 9.1 已完成项 ✅
|
|||
|
|
|
|||
|
|
#### 数据库层
|
|||
|
|
- ✅ 执行 `.tasks/2025-10-30_工序执行情况表重做.sql`
|
|||
|
|
- 新增字段(6个主表字段 + 3个子表字段)
|
|||
|
|
- 创建索引(7个索引)
|
|||
|
|
- 初始化数据
|
|||
|
|
- 创建触发器和存储过程
|
|||
|
|
|
|||
|
|
#### 后端代码 ✅
|
|||
|
|
- ✅ `yjh-mes/src/main/java/cn/sourceplan/statement/domain/query/WorkOrderExecutionQuery.java`
|
|||
|
|
- ✅ `yjh-mes/src/main/java/cn/sourceplan/statement/domain/vo/WorkOrderExecutionVO.java`
|
|||
|
|
- ✅ `yjh-mes/src/main/java/cn/sourceplan/statement/domain/vo/ProcessExecutionVO.java`
|
|||
|
|
- ✅ `yjh-mes/src/main/java/cn/sourceplan/statement/domain/vo/ReportVO.java`
|
|||
|
|
- ✅ `yjh-mes/src/main/java/cn/sourceplan/statement/mapper/WorkOrderExecutionMapper.java`
|
|||
|
|
- ✅ `yjh-mes/src/main/resources/mapper/statement/WorkOrderExecutionMapper.xml`
|
|||
|
|
- ✅ `yjh-mes/src/main/java/cn/sourceplan/statement/service/IWorkOrderExecutionService.java`
|
|||
|
|
- ✅ `yjh-mes/src/main/java/cn/sourceplan/statement/service/impl/WorkOrderExecutionServiceImpl.java`
|
|||
|
|
- ✅ `yjh-mes/src/main/java/cn/sourceplan/statement/controller/WorkOrderExecutionController.java`
|
|||
|
|
|
|||
|
|
#### 前端代码 ✅
|
|||
|
|
- ✅ `mes-ui/src/api/mes/statement/workOrderExecution.js`
|
|||
|
|
- ✅ `mes-ui/src/views/mes/statement/workOrderExecution/index.vue`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 9.2 待配置项 🔧
|
|||
|
|
|
|||
|
|
#### 1. 配置前端路由
|
|||
|
|
|
|||
|
|
编辑文件:`mes-ui/src/router/index.js` 或对应的路由文件
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
{
|
|||
|
|
path: '/statement/workOrderExecution',
|
|||
|
|
component: () => import('@/views/mes/statement/workOrderExecution/index'),
|
|||
|
|
name: 'WorkOrderExecution',
|
|||
|
|
meta: {
|
|||
|
|
title: '工序执行情况表',
|
|||
|
|
icon: 'chart',
|
|||
|
|
noCache: true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2. 配置菜单权限
|
|||
|
|
|
|||
|
|
在系统管理 → 菜单管理中添加菜单:
|
|||
|
|
```
|
|||
|
|
菜单名称:工序执行情况表
|
|||
|
|
菜单路径:/statement/workOrderExecution
|
|||
|
|
组件路径:mes/statement/workOrderExecution/index
|
|||
|
|
权限标识:statement:workOrderExecution:list
|
|||
|
|
菜单图标:chart
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
或直接执行SQL:
|
|||
|
|
```sql
|
|||
|
|
-- 在sys_menu表中插入菜单(根据实际parent_id调整)
|
|||
|
|
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
|||
|
|
VALUES ('工序执行情况表', 2000, 10, 'workOrderExecution', 'mes/statement/workOrderExecution/index', 1, 'C', '0', '0', 'statement:workOrderExecution:list', 'chart', 'admin', NOW(), '工序执行情况表');
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 9.3 测试步骤 🧪
|
|||
|
|
|
|||
|
|
#### 第1步:启动后端
|
|||
|
|
```bash
|
|||
|
|
cd mes-admin
|
|||
|
|
mvn clean package
|
|||
|
|
java -jar target/ruoyi-admin.jar
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 第2步:测试接口
|
|||
|
|
使用Postman或浏览器测试:
|
|||
|
|
|
|||
|
|
**1) 查询工单列表**
|
|||
|
|
```
|
|||
|
|
GET http://localhost:8080/reports/production/workOrderExecution?pageNum=1&pageSize=10
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**2) 查询工序列表**
|
|||
|
|
```
|
|||
|
|
GET http://localhost:8080/reports/production/workOrderProcesses/1
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**3) 查询报工单**
|
|||
|
|
```
|
|||
|
|
GET http://localhost:8080/reports/production/processReports/1
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 第3步:启动前端
|
|||
|
|
```bash
|
|||
|
|
cd mes-ui
|
|||
|
|
npm install
|
|||
|
|
npm run dev
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 第4步:访问页面
|
|||
|
|
```
|
|||
|
|
http://localhost:80
|
|||
|
|
登录后访问:生产管理 → 工序执行情况表
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 9.4 验证清单 ✓
|
|||
|
|
|
|||
|
|
#### 功能验证
|
|||
|
|
- [ ] 工单列表正常显示
|
|||
|
|
- [ ] 进度条正确显示当前工序和完成率
|
|||
|
|
- [ ] 点击展开/收起按钮正常
|
|||
|
|
- [ ] 工序列表懒加载成功
|
|||
|
|
- [ ] 工序状态(未开始/进行中/已完成)正确
|
|||
|
|
- [ ] 点击报工单按钮弹窗正常
|
|||
|
|
- [ ] 报工单列表显示正确
|
|||
|
|
- [ ] 未报工工序显示"未完成"状态
|
|||
|
|
- [ ] 搜索条件(产品名称、批次号、状态)有效
|
|||
|
|
- [ ] 分页功能正常
|
|||
|
|
|
|||
|
|
#### 性能验证
|
|||
|
|
- [ ] 工单列表查询 < 100ms
|
|||
|
|
- [ ] 工序列表查询 < 50ms
|
|||
|
|
- [ ] 报工单查询 < 50ms
|
|||
|
|
- [ ] 10万级数据不卡顿
|
|||
|
|
|
|||
|
|
#### 数据验证
|
|||
|
|
```sql
|
|||
|
|
-- 验证工单统计字段是否正确
|
|||
|
|
SELECT
|
|||
|
|
w.id,
|
|||
|
|
w.number,
|
|||
|
|
w.current_process_name,
|
|||
|
|
w.total_process_count,
|
|||
|
|
w.completed_process_count,
|
|||
|
|
w.overall_completion_rate,
|
|||
|
|
(SELECT COUNT(*) FROM pro_workorder_entry e WHERE e.workorder_id = w.id AND e.type = 'report') AS actual_total_count
|
|||
|
|
FROM pro_workorder w
|
|||
|
|
WHERE w.id = 1;
|
|||
|
|
|
|||
|
|
-- 验证工序统计字段是否正确
|
|||
|
|
SELECT
|
|||
|
|
e.id,
|
|||
|
|
e.process_name,
|
|||
|
|
e.reported_quantity,
|
|||
|
|
e.completion_rate,
|
|||
|
|
(SELECT SUM(r.report_quantity) FROM pro_report r WHERE r.work_order_entry_id = e.id AND r.status = 'A') AS actual_reported
|
|||
|
|
FROM pro_workorder_entry e
|
|||
|
|
WHERE e.workorder_id = 1 AND e.type = 'report';
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 9.5 常见问题 ❓
|
|||
|
|
|
|||
|
|
#### Q1: 接口404错误
|
|||
|
|
**A:** 检查Controller的 `@RequestMapping` 路径是否正确,确认为 `/reports/production`
|
|||
|
|
|
|||
|
|
#### Q2: 工序列表为空
|
|||
|
|
**A:** 检查 `pro_workorder_entry` 表的 `type` 字段是否为 `'report'`
|
|||
|
|
|
|||
|
|
#### Q3: 统计字段都是0
|
|||
|
|
**A:** 执行SQL中的"初始化数据"部分,运行UPDATE语句
|
|||
|
|
|
|||
|
|
#### Q4: 前端报CORS错误
|
|||
|
|
**A:** 检查 `vue.config.js` 的proxy配置,确保指向正确的后端地址
|
|||
|
|
|
|||
|
|
#### Q5: 触发器不生效
|
|||
|
|
**A:** 检查触发器是否创建成功:
|
|||
|
|
```sql
|
|||
|
|
SHOW TRIGGERS LIKE 'pro_report';
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 9.6 上线检查清单 🚀
|
|||
|
|
|
|||
|
|
#### 部署前
|
|||
|
|
- [ ] 备份数据库
|
|||
|
|
- [ ] 在测试环境验证所有功能
|
|||
|
|
- [ ] 压力测试通过
|
|||
|
|
- [ ] 代码已提交到Git
|
|||
|
|
|
|||
|
|
#### 部署时
|
|||
|
|
- [ ] 停止应用服务
|
|||
|
|
- [ ] 执行SQL脚本
|
|||
|
|
- [ ] 部署后端代码
|
|||
|
|
- [ ] 部署前端代码
|
|||
|
|
- [ ] 清除缓存
|
|||
|
|
|
|||
|
|
#### 部署后
|
|||
|
|
- [ ] 验证接口正常
|
|||
|
|
- [ ] 验证页面访问正常
|
|||
|
|
- [ ] 检查日志无异常
|
|||
|
|
- [ ] 性能监控正常
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**项目状态:** 🔄 **需求已更新为销售订单版,代码待重新生成**
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 十、完整实现代码(销售订单版)
|
|||
|
|
|
|||
|
|
### 10.1 SQL查询语句
|
|||
|
|
|
|||
|
|
#### 查询销售订单列表
|
|||
|
|
```sql
|
|||
|
|
SELECT
|
|||
|
|
so.id,
|
|||
|
|
so.number AS orderNumber,
|
|||
|
|
so.customer_name AS customerName,
|
|||
|
|
so.sale_date AS saleDate,
|
|||
|
|
so.status,
|
|||
|
|
soe.material_name AS materialName,
|
|||
|
|
soe.quantity,
|
|||
|
|
soe.unit_name AS unitName,
|
|||
|
|
-- 统计该订单的工单数
|
|||
|
|
(
|
|||
|
|
SELECT COUNT(*)
|
|||
|
|
FROM pro_workorder pw
|
|||
|
|
WHERE JSON_EXTRACT(pw.source_info, '$.saleOrderId') = so.id
|
|||
|
|
AND pw.status = 'A'
|
|||
|
|
) AS workOrderCount,
|
|||
|
|
-- 平均完成率
|
|||
|
|
(
|
|||
|
|
SELECT AVG(IFNULL(pw.overall_completion_rate, 0))
|
|||
|
|
FROM pro_workorder pw
|
|||
|
|
WHERE JSON_EXTRACT(pw.source_info, '$.saleOrderId') = so.id
|
|||
|
|
AND pw.status = 'A'
|
|||
|
|
) AS avgCompletionRate
|
|||
|
|
FROM sal_order so
|
|||
|
|
LEFT JOIN sal_order_entry soe ON soe.main_id = so.id
|
|||
|
|
WHERE so.status = 'A'
|
|||
|
|
AND (customerName IS NULL OR so.customer_name LIKE CONCAT('%', customerName, '%'))
|
|||
|
|
AND (materialName IS NULL OR soe.material_name LIKE CONCAT('%', materialName, '%'))
|
|||
|
|
ORDER BY so.sale_date DESC, so.create_time DESC
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 查询订单的工单列表(含工序)
|
|||
|
|
```sql
|
|||
|
|
SELECT
|
|||
|
|
pw.id,
|
|||
|
|
pw.number AS workOrderNumber,
|
|||
|
|
pw.material_name AS materialName,
|
|||
|
|
pw.batch_number AS batchNumber,
|
|||
|
|
pw.quantity,
|
|||
|
|
pw.pro_status AS proStatus,
|
|||
|
|
pw.current_process_name AS currentProcess,
|
|||
|
|
pw.current_process_sort AS currentProcessSort,
|
|||
|
|
pw.total_process_count AS totalProcessCount,
|
|||
|
|
IFNULL(pw.overall_completion_rate, 0) AS completionRate
|
|||
|
|
FROM pro_workorder pw
|
|||
|
|
WHERE JSON_EXTRACT(pw.source_info, '$.saleOrderId') = #{saleOrderId}
|
|||
|
|
AND pw.status = 'A'
|
|||
|
|
ORDER BY pw.create_time ASC
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 查询工序列表
|
|||
|
|
```sql
|
|||
|
|
SELECT
|
|||
|
|
pwe.id,
|
|||
|
|
pwe.process_name AS processName,
|
|||
|
|
pwe.process_sort AS processSort,
|
|||
|
|
pwe.report_quantity AS planQuantity,
|
|||
|
|
IFNULL(pwe.reported_quantity, 0) AS reportedQuantity,
|
|||
|
|
IFNULL(pwe.completion_rate, 0) AS completionRate,
|
|||
|
|
CASE
|
|||
|
|
WHEN IFNULL(pwe.completion_rate, 0) >= 100 THEN '已完成'
|
|||
|
|
WHEN IFNULL(pwe.completion_rate, 0) > 0 THEN '进行中'
|
|||
|
|
ELSE '未开始'
|
|||
|
|
END AS status
|
|||
|
|
FROM pro_workorder_entry pwe
|
|||
|
|
WHERE pwe.workorder_id = #{workOrderId}
|
|||
|
|
AND pwe.type = 'report'
|
|||
|
|
ORDER BY pwe.process_sort ASC
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**文档结束**
|