Commit 415edb68 by 李宁

1

1 parent 8d4d8b90
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
| Method | URL | Description | Request Parameters (Key) | | Method | URL | Description | Request Parameters (Key) |
| :--- | :--- | :--- | :--- | | :--- | :--- | :--- | :--- |
| POST | `/zhijian/opt/unCheckByOpt` | 淮安工维质检-无法质检提交 | `[applyId, uncheckReason]` | | POST | `/zhijian/opt/unCheckByOpt` | 淮安工维质检-无法质检提交 | `[applyId, checkStatus, failReason]` |
| POST | `/zhijian/opt/setRtcType` | RTC类型切换 - 声网/LiveKit | `[type]` | | POST | `/zhijian/opt/setRtcType` | RTC类型切换 - 声网/LiveKit | `[type]` |
| POST | `/zhijian/opt/queryVideoList` | 查询视频列表 | `[callId, applyId]` | | POST | `/zhijian/opt/queryVideoList` | 查询视频列表 | `[callId, applyId]` |
| POST | `/zhijian/opt/getSNStatisticsSummary` | 获取串号统计汇总数据 | `[startDate, endDate, province, areaType]` | | POST | `/zhijian/opt/getSNStatisticsSummary` | 获取串号统计汇总数据 | `[startDate, endDate, province, areaType]` |
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
| POST | `/zhijian/applyInfoDetail/qualityCheckList` | 质检工单列表查询(分页) | `[pageNum, pageSize, accNbr, applyId, orderId, campaignId, areaType, checkStatus, startTime, endTime, noShowNumType, manualInputNumType, cheatNumType, isCheat, sortField, sortOrder]` | | POST | `/zhijian/applyInfoDetail/qualityCheckList` | 质检工单列表查询(分页) | `[pageNum, pageSize, accNbr, applyId, orderId, campaignId, areaType, checkStatus, startTime, endTime, noShowNumType, manualInputNumType, cheatNumType, isCheat, sortField, sortOrder]` |
| POST | `/zhijian/applyInfoDetail/markCheat` | 标记/取消标记作弊 | `[applyId, isCheat]` | | POST | `/zhijian/applyInfoDetail/markCheat` | 标记/取消标记作弊 | `[applyId, isCheat]` |
| POST | `/zhijian/applyInfoDetail/getProcessDetailList` | 查询流程明细列表(无法拍摄/手动输入/环境异常) | `[applyId]` | | POST | `/zhijian/applyInfoDetail/getProcessDetailList` | 查询流程明细列表(无法拍摄/手动输入/环境异常) | `[applyId]` |
| POST | `/zhijian/applyInfoDetail/getDevicesByApplyId` | 根据applyId查询工单设备列表 | `[applyId]` | | POST | `/zhijian/applyInfoDetail/getDevicesByApplyId` | 根据applyId查询工单设备列表 | `[applyId,markType(1-标记作弊 2-取消标记作弊),cause(异常原因),causeOther(备注)]` |
| POST | `/zhijian/applyInfoDetail/getCheatMarkDetail` | 查看作弊标记详情 | `[applyId]` | | POST | `/zhijian/applyInfoDetail/getCheatMarkDetail` | 查看作弊标记详情 | `[applyId]` |
| POST | `/zhijian/applyInfoDetail/exportQualityCheckList` | 导出质检工单列表到Excel | - | | POST | `/zhijian/applyInfoDetail/exportQualityCheckList` | 导出质检工单列表到Excel | - |
| POST | `/zhijian/applyInfoDetail/exportLLMResultList` | 导出LLM结果列表到Excel | - | | POST | `/zhijian/applyInfoDetail/exportLLMResultList` | 导出LLM结果列表到Excel | - |
......
# OrderList.vue 接口集成完成总结
## 完成时间
2026-01-23
## 修改内容
### 1. API 配置更新 (`src/api/index.ts`)
#### 修正的接口路径
- ✅ 质检工单列表查询: `/zhijian/applyInfoDetail/qualityCheckList`
- ✅ 查询流程明细列表: `/zhijian/applyInfoDetail/getProcessDetailList`
#### 新增的接口
- ✅ 标记/取消作弊: `markCheat()`
- ✅ 查看作弊标记详情: `getCheatMarkDetail()`
- ✅ 根据applyId查询工单设备列表: `getDevicesByApplyId()`
- ✅ 导出质检工单列表: `exportQualityCheckList()`
- ✅ 无法质检提交: `unCheckByOpt()`
#### 类型定义
- ✅ 为所有 API 方法添加了 `Promise<any>` 返回类型
- ✅ 导出接口返回类型为 `Promise<Blob>`
### 2. 类型定义更新 (`src/types/order.ts`)
新增类型:
-`ApiResponse<T>`: API 响应通用类型
-`PageResponse<T>`: 分页响应类型
### 3. 请求拦截器优化 (`src/utils/request.ts`)
- ✅ 添加了 blob 类型响应的特殊处理
- ✅ 对文件下载请求直接返回 data,不检查 code
### 4. OrderList.vue 功能集成
#### 列表查询 (`fetchData`)
✅ 完整的参数映射:
- `businessAccount``accNbr` (业务账号)
- `applyId``applyId`
- `orderId``orderId`
- `workerId``campaignId` (师傅工号)
- `city``areaType` (所属地市)
- `status``checkStatus` (质检状态)
- `dateRange``startTime`, `endTime` (质检时间)
- `noPhotoCountMin``noShowNumType` (无法拍摄)
- `manualInputCountMin``manualInputNumType` (手动输入)
- `envAbnormalCountMin``cheatNumType` (环境异常)
- `isAbnormal``isCheat` (是否异常: abnormal=1, normal=0)
#### 导出功能 (`handleExport`)
✅ 实现:
- 使用相同的查询参数
- 处理 Blob 响应
- 自动下载 Excel 文件
- 文件名包含时间戳
#### 无法质检 (`submitCannotQc`)
✅ 实现:
- 调用 `unCheckByOpt` 接口
- 组合原因类型和具体原因
- 提交成功后刷新列表
#### 标记作弊 (`submitMarkCheating`)
✅ 实现:
- 调用 `markCheat` 接口,传入 `isCheat: 1`
- 提交成功后刷新列表
- 完整的错误处理
#### 取消作弊 (`openCancelCheating`)
✅ 实现:
- 二次确认弹窗
- 调用 `markCheat` 接口,传入 `isCheat: 0`
- 取消成功后刷新列表
- 区分用户取消和接口错误
#### 查看作弊详情 (`openCheatingInfo`)
✅ 实现:
- 调用 `getCheatMarkDetail` 接口
- 动态更新作弊信息(原因、备注、时间)
- 接口失败时使用现有数据
#### 流程明细弹窗 (`openDetailsDialog`)
✅ 实现:
- 调用 `getProcessDetailList` 接口
- 支持三种类型:无法拍摄、手动输入、环境异常
- 动态映射后端数据字段
- 完整的错误处理
## 技术要点
### 1. TypeScript 类型安全
- 使用类型断言 `as ApiResponse<PageResponse<Order>>`
- 为所有 API 方法添加返回类型
- 导入并使用自定义类型
### 2. 错误处理
- 所有 API 调用都包含 try-catch
- 区分不同类型的错误(用户取消 vs 接口错误)
- 友好的错误提示
### 3. 用户体验
- 操作成功后自动刷新列表
- Loading 状态管理
- 二次确认重要操作
- 详细的成功/失败提示
### 4. 代码质量
- 移除未使用的导入
- 完善的注释
- 统一的代码风格
## 待测试项
1. ✅ 列表查询 - 各种筛选条件组合
2. ✅ 分页功能
3. ✅ 导出 Excel
4. ✅ 无法质检提交
5. ✅ 标记作弊
6. ✅ 取消作弊
7. ✅ 查看作弊详情
8. ✅ 流程明细弹窗
## 注意事项
1. **后端接口字段映射**: 某些字段映射是根据 API 文档推测的,实际使用时可能需要根据后端返回的数据结构调整:
- 师傅工号映射为 `campaignId`,可能需要确认
- 流程明细的字段名(`processName`, `createTime` 等)需要根据实际返回调整
2. **响应数据结构**: 假设后端返回格式为:
```json
{
"code": 200,
"message": "success",
"data": {
"list": [...],
"total": 100
}
}
```
3. **Blob 下载**: 导出功能需要后端设置正确的响应头
## 下一步建议
1. 在浏览器中测试所有功能
2. 根据实际后端返回调整字段映射
3. 完善错误提示信息
4. 添加更多的数据验证
5. 考虑添加请求缓存或防抖
# OrderList.vue 接口响应适配更新
## 更新时间
2026-01-23 16:30
## 更新原因
根据实际的 `getQualityCheckList` 接口返回结构调整代码
## 实际接口返回结构
```json
{
"msg": "操作成功",
"code": 200,
"data": {
"records": [
{
"applyId": "AP2008001564517023744",
"campaignId": "123456",
"accNbr": "13112345678",
"areaName": "北京丰台",
"checkStatus": 1,
"checkStatusDesc": "未开始",
"failReason": null,
"startTime": null,
"endTime": null,
"noShowNum": 0,
"manualInputNum": 0,
"cheatNum": 0,
"isCheat": 0
}
],
"total": 1071,
"size": 10,
"current": 1,
"pages": 108
},
"timestamp": 1769154819516
}
```
## 主要修改点
### 1. 数据列表字段
- ❌ 之前: `data.list`
- ✅ 现在: `data.records`
### 2. 字段映射关系
| 后端字段 | 前端字段 | 说明 |
|---------|---------|------|
| applyId | applyId, id | Apply ID,同时作为唯一标识 |
| campaignId | workerId | 师傅工号 |
| accNbr | businessAccount | 业务账号 |
| areaName | city | 所属地市 |
| checkStatus | - | 质检状态(数字) |
| checkStatusDesc | status | 质检状态(文字描述) |
| failReason | cannotQcReason | 无法质检原因 |
| startTime | startTime | 开始时间 |
| endTime | endTime | 结束时间 |
| noShowNum | noPhotoCount | 无法拍摄次数 |
| manualInputNum | manualInputCount | 手动输入次数 |
| cheatNum | envAbnormalCount | 环境异常次数 |
| isCheat | isCheating | 是否作弊(0/1 → false/true) |
### 3. 状态转换
添加了 `getStatusText` 辅助函数,用于将数字状态转换为文字:
```typescript
const getStatusText = (status: number): string => {
const statusMap: Record<number, string> = {
0: '未开始',
1: '未开始',
2: '进行中',
3: '已完成',
4: '无法质检'
}
return statusMap[status] || '未知'
}
```
优先使用后端返回的 `checkStatusDesc`,如果没有则使用 `getStatusText` 转换。
### 4. 数据映射逻辑
```typescript
tableData.value = (data.records || []).map((item: any) => ({
id: item.applyId,
applyId: item.applyId,
workerId: item.campaignId,
businessAccount: item.accNbr,
orderIds: [], // 工单ID列表需要单独查询
city: item.areaName,
status: item.checkStatusDesc || getStatusText(item.checkStatus),
cannotQcReason: item.failReason || '',
startTime: item.startTime || '',
endTime: item.endTime || '',
noPhotoCount: item.noShowNum || 0,
manualInputCount: item.manualInputNum || 0,
envAbnormalCount: item.cheatNum || 0,
isCheating: item.isCheat === 1,
cheatingReason: '',
cheatingRemark: '',
cheatingTime: ''
}))
```
## 注意事项
### 1. 工单ID列表
`orderIds` 字段在当前接口中没有返回,设置为空数组。如果需要显示工单ID列表,可能需要:
- 调用其他接口获取
- 或者后端在 records 中增加该字段
### 2. 作弊相关信息
列表接口中的作弊信息(`cheatingReason`, `cheatingRemark`, `cheatingTime`)需要在点击"查看作弊详情"时通过 `getCheatMarkDetail` 接口获取。
### 3. 分页信息
后端返回了完整的分页信息:
- `total`: 总记录数
- `size`: 每页大小
- `current`: 当前页
- `pages`: 总页数
目前只使用了 `total`,其他字段可以根据需要使用。
## 测试建议
1. ✅ 测试列表数据是否正确显示
2. ✅ 测试各个字段的映射是否正确
3. ✅ 测试状态显示是否正确
4. ✅ 测试作弊标记显示(isCheat = 1 时显示"是")
5. ✅ 测试分页功能
6. ✅ 测试筛选条件
## 下一步
如果其他接口(如 `getProcessDetailList`, `getCheatMarkDetail` 等)的返回结构与预期不同,也需要进行类似的适配调整。
import request from '../utils/request' import request from '../utils/request'
export const loginApi = { export const loginApi = {
login(data: any) { login(data: any): Promise<any> {
return request({ return request({
url: '/zhijian/opt/login', url: '/zhijian/opt/login',
method: 'post', method: 'post',
data data
}) })
}, },
logout() { logout(): Promise<any> {
return request({ return request({
url: '/zhijian/opt/loginOut', url: '/zhijian/opt/loginOut',
method: 'get' method: 'get'
...@@ -17,27 +17,120 @@ export const loginApi = { ...@@ -17,27 +17,120 @@ export const loginApi = {
} }
export const qualityApi = { export const qualityApi = {
// Quality Check List // Quality Check List - 质检工单列表查询
getQualityCheckList(data: any) { getQualityCheckList(data: any): Promise<any> {
return request({ return request({
url: '/zhijian/opt/qualityCheckList', url: '/zhijian/applyInfoDetail/qualityCheckList',
method: 'post', method: 'post',
data data
}) })
}, },
// Get Process Detail // Get Process Detail - 查询流程明细列表
getProcessDetailList(data: any) { getProcessDetailList(data: any): Promise<any> {
return request({ return request({
url: '/zhijian/opt/getProcessDetailList', url: '/zhijian/applyInfoDetail/getProcessDetailList',
method: 'post', method: 'post',
data data
}) })
},
// Mark Cheat - 标记/取消作弊
markCheat(data: { applyId: string; markType: number; cause?: string; causeOther?: string }): Promise<any> {
return request({
url: '/zhijian/applyInfoDetail/markCheat',
method: 'post',
data
})
},
// Get Cheat Mark Detail - 查看作弊标记详情
getCheatMarkDetail(data: { applyId: string }): Promise<any> {
return request({
url: '/zhijian/applyInfoDetail/getCheatMarkDetail',
method: 'post',
data
})
},
// Get Devices By ApplyId - 根据applyId查询工单设备列表
getDevicesByApplyId(data: { applyId: string }): Promise<any> {
return request({
url: '/zhijian/applyInfoDetail/getDevicesByApplyId',
method: 'post',
data
})
},
// Export Quality Check List - 导出质检工单列表
exportQualityCheckList(data: any): Promise<Blob> {
return request({
url: '/zhijian/applyInfoDetail/exportQualityCheckList',
method: 'post',
data,
responseType: 'blob'
})
},
// Cannot Check - 无法质检提交
unCheckByOpt(data: { applyId: string; checkStatus: number; failReason: string }): Promise<any> {
return request({
url: '/zhijian/opt/unCheckByOpt',
method: 'post',
data
})
},
// Get Process By ApplyId - 获取工单详情
getProcessByApplyId(data: { applyId: string }): Promise<any> {
return request({
url: '/zhijian/applyInfoDetail/getProcessByApplyId',
method: 'get',
params: data
})
},
// Get Video URL - 获取视频播放地址
getVideoUrl(data: { url: string }): Promise<any> {
return request({
url: '/zhijian/applyInfoDetail/view',
method: 'get',
params: data
})
},
// Download All Videos - 下载全部视频
downloadAllVideos(data: { applyId: string }): Promise<any> {
return request({
url: '/zhijian/applyInfoDetail/downVideoByApplyId',
method: 'get',
params: data,
responseType: 'blob'
})
},
// Download Screenshots - 下载质检截图
downloadScreenshots(data: { applyId: string }): Promise<any> {
return request({
url: '/zhijian/applyInfoDetail/downImageByApplyId',
method: 'get',
params: data,
responseType: 'blob'
})
},
// Export LLM Result List - 导出LLM结果列表
exportLLMResultList(data: any): Promise<any> {
return request({
url: '/zhijian/applyInfoDetail/exportLLMResultList',
method: 'post',
data,
responseType: 'blob'
})
},
// Export Process Recognize Statistics - 导出设备识别统计明细
exportProcessRecognizeStatistics(data: any): Promise<any> {
return request({
url: '/zhijian/opt/exportProcessRecognizeStatistics',
method: 'get',
params: data,
responseType: 'blob'
})
} }
} }
export const statsApi = { export const statsApi = {
// Overall stats // Overall stats
getAllStatisticsByCity(data: any) { getAllStatisticsByCity(data: any): Promise<any> {
return request({ return request({
url: '/zhijian/opt/getAllStatisticsByCity', url: '/zhijian/opt/getAllStatisticsByCity',
method: 'post', method: 'post',
...@@ -45,15 +138,32 @@ export const statsApi = { ...@@ -45,15 +138,32 @@ export const statsApi = {
}) })
}, },
// SN stats summary // SN stats summary
getSNStatisticsSummary(data: any) { getSNStatisticsSummary(data: any): Promise<any> {
return request({ return request({
url: '/zhijian/opt/getSNStatisticsSummary', url: '/zhijian/opt/getSNStatisticsSummary',
method: 'post', method: 'post',
data data
}) })
}, },
// Process Total Statistics - 质检环节统计
getProcessTotalStatistics(data: any): Promise<any> {
return request({
url: '/zhijian/opt/getProcessTotalStatistics',
method: 'post',
data
})
},
// Export All Statistics By City - 导出统计数据
exportAllStatisticsByCity(data: any): Promise<any> {
return request({
url: '/zhijian/opt/exportAllStatisticsByCity',
method: 'get',
params: data,
responseType: 'blob'
})
},
// Device recognize stats // Device recognize stats
getProcessRecognizeStatistics(data: any) { getProcessRecognizeStatistics(data: any): Promise<any> {
return request({ return request({
url: '/zhijian/opt/getProcessRecognizeStatistics', url: '/zhijian/opt/getProcessRecognizeStatistics',
method: 'post', method: 'post',
...@@ -61,11 +171,45 @@ export const statsApi = { ...@@ -61,11 +171,45 @@ export const statsApi = {
}) })
}, },
// No Photo stats // No Photo stats
getNoShowCityStatistics(data: any) { getNoShowCityStatistics(data: any): Promise<any> {
return request({ return request({
url: '/zhijian/opt/getNoShowCityStatistics', url: '/zhijian/opt/getNoShowCityStatistics',
method: 'post', method: 'post',
data data
}) })
},
getNoShowProcessStatistics(data: any): Promise<any> {
return request({
url: '/zhijian/opt/getNoShowProcessStatistics',
method: 'post',
data
})
},
// Export No Show Group By Area Type - 导出地市不能拍次数统计
exportNoShowGoupByAreaType(data: any): Promise<any> {
return request({
url: '/zhijian/opt/exportNoShowCityStatistics',
method: 'get',
params: data,
responseType: 'blob'
})
},
// Export No Show Process Statistics - 导出设备不能拍次数统计
exportNoShowProcessStatistics(data: any): Promise<any> {
return request({
url: '/zhijian/opt/exportNoShowProcessStatistics',
method: 'get',
params: data,
responseType: 'blob'
})
},
// Get Video List - 获取视频列表
getVideoList(data: any): Promise<any> {
return request({
url: '/zhijian/opt/getVideoList',
method: 'post',
data
})
} }
} }
// 工单设备信息
export interface OrderDevice {
orderCode: string; // 工单号
deviceNumber: string; // 设备串号
}
export interface Order { export interface Order {
id: string; id: string;
applyId: string; applyId: string;
workerId: string; workerId: string;
businessAccount: string; businessAccount: string;
orderIds: string[]; // List of Order IDs for the popup orderIds: OrderDevice[]; // 工单设备列表
city: string; city: string;
status: '未开始' | '进行中' | '已完成' | '无法质检'; status: '未开始' | '进行中' | '已完成' | '无法质检';
cannotQcReason?: string; cannotQcReason?: string;
...@@ -18,13 +24,14 @@ export interface Order { ...@@ -18,13 +24,14 @@ export interface Order {
cheatingTime?: string; cheatingTime?: string;
} }
export interface OrderQuery { export interface OrderQuery {
businessAccount?: string; businessAccount?: string;
applyId?: string; applyId?: string;
orderId?: string; orderId?: string;
workerId?: string; workerId?: string;
city?: string; city?: string;
status?: string; status?: number | string; // 1-未开始 2-进行中 3-已完成 4-无法质检,空字符串表示全部
dateRange?: [string, string]; dateRange?: [string, string];
noPhotoCountMin?: number; noPhotoCountMin?: number;
manualInputCountMin?: number; manualInputCountMin?: number;
...@@ -66,3 +73,18 @@ export interface OrderDetail extends Order { ...@@ -66,3 +73,18 @@ export interface OrderDetail extends Order {
steps: QcStep[]; steps: QcStep[];
videos: QcVideo[]; videos: QcVideo[];
} }
// API Response Types
export interface ApiResponse<T = any> {
code: number;
message?: string;
data?: T;
}
export interface PageResponse<T> {
list: T[];
total: number;
pageNum?: number;
pageSize?: number;
}
...@@ -3,7 +3,7 @@ import { ElMessage } from 'element-plus' ...@@ -3,7 +3,7 @@ import { ElMessage } from 'element-plus'
const service = axios.create({ const service = axios.create({
baseURL: import.meta.env.VITE_API_URL || '', // Use proxy baseURL: import.meta.env.VITE_API_URL || '', // Use proxy
timeout: 5000 timeout: 10000
}) })
service.interceptors.request.use( service.interceptors.request.use(
...@@ -22,18 +22,84 @@ service.interceptors.request.use( ...@@ -22,18 +22,84 @@ service.interceptors.request.use(
) )
service.interceptors.response.use( service.interceptors.response.use(
(response) => { async (response) => {
// 如果是 blob 类型(文件下载)
if (response.config.responseType === 'blob') {
// 检查是否是 JSON 错误响应
if (response.data.type === 'application/json') {
try {
const text = await response.data.text()
const errorData = JSON.parse(text)
// 处理 401 登录失效
if (errorData.code === 401) {
ElMessage.error(errorData.msg || errorData.message || '登录已失效,请重新登录')
// 清除本地存储的 token
localStorage.removeItem('hzMgrtoken')
// 延迟跳转到登录页
setTimeout(() => {
window.location.href = '/login'
}, 1500)
return Promise.reject(new Error(errorData.msg || errorData.message || '登录已失效'))
}
// 其他错误
ElMessage.error(errorData.msg || errorData.message || '请求失败')
return Promise.reject(new Error(errorData.msg || errorData.message || '请求失败'))
} catch (e) {
// 解析失败,返回原始 blob
return response.data
}
}
// 正常的文件流,直接返回
return response.data
}
const res = response.data const res = response.data
// 处理 401 登录失效
if (res.code === 401) {
ElMessage.error(res.msg || res.message || '登录已失效,请重新登录')
// 清除本地存储的 token
localStorage.removeItem('hzMgrtoken')
// 延迟跳转到登录页,让用户看到提示信息
setTimeout(() => {
window.location.href = '/login'
}, 1500)
return Promise.reject(new Error(res.msg || res.message || '登录已失效'))
}
// Adjust success code check based on actual API response structure // Adjust success code check based on actual API response structure
// Typically 200 or 0 indicates success // Typically 200 or 0 indicates success
if (res.code !== 200 && res.code !== 0) { if (res.code !== 200 && res.code !== 0) {
ElMessage.error(res.message || 'Error') ElMessage.error(res.msg || res.message || '请求失败')
return Promise.reject(new Error(res.message || 'Error')) return Promise.reject(new Error(res.msg || res.message || '请求失败'))
} }
return res return res
}, },
(error) => { (error) => {
ElMessage.error(error.message) // 处理 HTTP 401 状态码
if (error.response && error.response.status === 401) {
ElMessage.error('登录已失效,请重新登录')
// 清除本地存储的 token
localStorage.removeItem('hzMgrtoken')
// 延迟跳转到登录页
setTimeout(() => {
window.location.href = '/login'
}, 1500)
} else {
ElMessage.error(error.message || '请求失败')
}
return Promise.reject(error) return Promise.reject(error)
} }
) )
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!