193 lines
5.0 KiB
Markdown
193 lines
5.0 KiB
Markdown
|
|
# 工单列表 Row 对象序列化问题修复
|
|||
|
|
|
|||
|
|
## 问题描述
|
|||
|
|
- 点击"进行中"(testStep = 1)或"已完成"(testStep = 5)按钮
|
|||
|
|
- 页面显示"暂无数据"
|
|||
|
|
- 数据库中有数据,但前端收不到
|
|||
|
|
|
|||
|
|
## 根本原因
|
|||
|
|
|
|||
|
|
**DAO 层使用了 `.label()` 后,SQLAlchemy 返回的是 Row 对象,而不是 ORM 模型对象**
|
|||
|
|
|
|||
|
|
### 问题代码
|
|||
|
|
```python
|
|||
|
|
query = (
|
|||
|
|
select(
|
|||
|
|
TestWorkOrder.id.label('id'),
|
|||
|
|
TestWorkOrder.batch_id.label('batch_id'),
|
|||
|
|
# ... 其他字段
|
|||
|
|
)
|
|||
|
|
# ...
|
|||
|
|
)
|
|||
|
|
result = await PageUtil.paginate(db, query, page_num, page_size, is_page)
|
|||
|
|
return result # ❌ 返回的是 Row 对象列表
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 为什么会出问题?
|
|||
|
|
|
|||
|
|
1. **使用 `.label()` 后**,SQLAlchemy 返回 `Row` 对象而不是 ORM 模型
|
|||
|
|
2. **Pydantic 无法直接序列化 Row 对象**
|
|||
|
|
3. **FastAPI 返回数据时序列化失败**,导致前端收到空数据或错误
|
|||
|
|
|
|||
|
|
### Row 对象 vs ORM 模型
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# ORM 模型(可以直接序列化)
|
|||
|
|
result = await db.execute(select(TestWorkOrder))
|
|||
|
|
rows = result.scalars().all() # 返回 TestWorkOrder 对象列表
|
|||
|
|
|
|||
|
|
# Row 对象(需要转换为字典)
|
|||
|
|
result = await db.execute(select(TestWorkOrder.id.label('id'), ...))
|
|||
|
|
rows = result.all() # 返回 Row 对象列表,需要转换
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 解决方案
|
|||
|
|
|
|||
|
|
**将 Row 对象转换为字典**
|
|||
|
|
|
|||
|
|
### 修复代码
|
|||
|
|
```python
|
|||
|
|
# 执行查询并转换为字典列表
|
|||
|
|
result = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)
|
|||
|
|
|
|||
|
|
# 如果是分页查询,需要转换 rows 为字典
|
|||
|
|
if is_page and hasattr(result, 'rows'):
|
|||
|
|
result.rows = [dict(row._mapping) for row in result.rows]
|
|||
|
|
elif not is_page:
|
|||
|
|
result = [dict(row._mapping) for row in result]
|
|||
|
|
|
|||
|
|
return result
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 为什么这样修复?
|
|||
|
|
|
|||
|
|
1. **`row._mapping`**: SQLAlchemy 2.0 提供的属性,返回 Row 对象的字段映射
|
|||
|
|
2. **`dict(row._mapping)`**: 将 Row 对象转换为普通字典
|
|||
|
|
3. **Pydantic 可以序列化字典**: FastAPI 可以正常返回数据
|
|||
|
|
|
|||
|
|
## 修复文件
|
|||
|
|
|
|||
|
|
**文件**: `ruoyi-fastapi-backend/module_admin/system/dao/test_work_order_dao.py`
|
|||
|
|
|
|||
|
|
**方法**: `get_test_work_order_list`
|
|||
|
|
|
|||
|
|
## 验证步骤
|
|||
|
|
|
|||
|
|
### 1. 重启后端服务(必须!)
|
|||
|
|
```bash
|
|||
|
|
# 停止后端服务
|
|||
|
|
# 重新启动
|
|||
|
|
cd ruoyi-fastapi-backend
|
|||
|
|
python main.py
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 清除浏览器缓存
|
|||
|
|
Windows: `Ctrl + Shift + R`
|
|||
|
|
|
|||
|
|
### 3. 测试工单列表
|
|||
|
|
1. 点击"全部工单" - 应该能看到所有工单
|
|||
|
|
2. 点击"待领取" - 应该能看到 test_step = 0 的工单
|
|||
|
|
3. 点击"进行中" - 应该能看到 test_step = 1 的工单
|
|||
|
|
4. 点击"已完成" - 应该能看到 test_step = 5 的工单
|
|||
|
|
|
|||
|
|
### 4. 检查浏览器开发者工具
|
|||
|
|
1. 按 **F12**
|
|||
|
|
2. Network 标签
|
|||
|
|
3. 点击"进行中"按钮
|
|||
|
|
4. 查看 `/system/test_work_order/list` 的响应
|
|||
|
|
|
|||
|
|
**正常响应**:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"code": 200,
|
|||
|
|
"data": {
|
|||
|
|
"rows": [
|
|||
|
|
{
|
|||
|
|
"id": 821,
|
|||
|
|
"batchId": 1767886248798,
|
|||
|
|
"batchName": "批次-20260108233048",
|
|||
|
|
"name": "SN0001-出库测试(PCBA)",
|
|||
|
|
"testEutName": "SN0001",
|
|||
|
|
"creatorName": "管理员",
|
|||
|
|
"testCategoryName": "PCBA测试",
|
|||
|
|
"testItemName": "出库测试",
|
|||
|
|
"testStep": 1,
|
|||
|
|
"testStatus": 0
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"total": 10
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 为什么不需要批次表?
|
|||
|
|
|
|||
|
|
**`batch_id` 和 `batch_name` 已经在 `test_work_order` 表中**:
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- test_work_order 表结构
|
|||
|
|
CREATE TABLE test_work_order (
|
|||
|
|
id INT PRIMARY KEY,
|
|||
|
|
batch_id BIGINT, -- 批次ID(时间戳)
|
|||
|
|
batch_name VARCHAR(100), -- 批次名称
|
|||
|
|
name VARCHAR(200), -- 工单名称
|
|||
|
|
test_step INT, -- 测试步骤
|
|||
|
|
order_id INT, -- 关联的订单ID
|
|||
|
|
-- ... 其他字段
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**批次的作用**:
|
|||
|
|
- `batch_id`: 用于标识同一批次生成的工单(相同的时间戳)
|
|||
|
|
- `batch_name`: 用于显示批次名称(如"批次-20260108233048")
|
|||
|
|
- 不需要单独的批次表,因为批次信息直接存储在工单表中
|
|||
|
|
|
|||
|
|
## 其他可能的问题
|
|||
|
|
|
|||
|
|
### 问题 1: 数据库中没有对应 test_step 的工单
|
|||
|
|
|
|||
|
|
**验证**:
|
|||
|
|
```sql
|
|||
|
|
SELECT test_step, COUNT(*)
|
|||
|
|
FROM test_work_order
|
|||
|
|
WHERE order_id IS NULL
|
|||
|
|
GROUP BY test_step;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**解决方案**:
|
|||
|
|
如果没有 test_step = 1 的工单,手动更新:
|
|||
|
|
```sql
|
|||
|
|
UPDATE test_work_order
|
|||
|
|
SET test_step = 1
|
|||
|
|
WHERE order_id IS NULL
|
|||
|
|
AND test_step = 0
|
|||
|
|
LIMIT 5;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 问题 2: 后端服务没有重启
|
|||
|
|
|
|||
|
|
**症状**: 修改代码后,问题仍然存在
|
|||
|
|
|
|||
|
|
**解决方案**: 必须重启后端服务
|
|||
|
|
|
|||
|
|
### 问题 3: 浏览器缓存
|
|||
|
|
|
|||
|
|
**症状**: 后端已修复,但前端仍显示旧数据
|
|||
|
|
|
|||
|
|
**解决方案**: 清除浏览器缓存(Ctrl + Shift + R)
|
|||
|
|
|
|||
|
|
## 预期结果
|
|||
|
|
|
|||
|
|
修复后:
|
|||
|
|
- ✅ "全部工单"显示所有未关联订单的工单
|
|||
|
|
- ✅ "待领取"显示 test_step = 0 的工单
|
|||
|
|
- ✅ "进行中"显示 test_step = 1 的工单
|
|||
|
|
- ✅ "一审中"显示 test_step = 2 的工单
|
|||
|
|
- ✅ "二审中"显示 test_step = 3 的工单
|
|||
|
|
- ✅ "三审中"显示 test_step = 4 的工单
|
|||
|
|
- ✅ "已完成"显示 test_step = 5 的工单
|
|||
|
|
- ✅ 工单分组(batchName)正确显示
|
|||
|
|
|
|||
|
|
## 完成时间
|
|||
|
|
2026-01-09 00:45
|