Commit 36594141 by 李宁

1

1 parent 32c48fee
......@@ -121,18 +121,19 @@ const fetchDetail = async () => {
}
}
// 辅助函数:格式化时长
const formatDuration = (seconds: number): string => {
if (!seconds) return '0秒'
const hours = Math.floor(seconds / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
const secs = seconds % 60
// 辅助函数:格式化时长(参数单位:毫秒)
const formatDuration = (milliseconds: number): string => {
if (!milliseconds) return '0秒'
const totalSeconds = Math.floor(milliseconds / 1000) // 将毫秒转换为秒
const hours = Math.floor(totalSeconds / 3600)
const minutes = Math.floor((totalSeconds % 3600) / 60)
const secs = totalSeconds % 60
const parts = []
if (hours > 0) parts.push(`${hours}小时`)
if (minutes > 0) parts.push(`${minutes}分`)
if (secs > 0 || parts.length === 0) parts.push(`${secs}秒`)
return parts.join('')
}
......@@ -157,39 +158,39 @@ const showEnvImages = () => {
}
}
const playVideo = async (recordFile: string) => {
const playVideo = (recordFile: string) => {
// 检查 recordFile 是否存在
if (!recordFile) {
ElMessage.warning('暂不支持播放')
return
}
try {
// 调用接口获取视频播放地址
const response = await qualityApi.getVideoUrl({
url: recordFile
}) as ApiResponse<any>
if (response.code === 200 || response.code === 0) {
// 假设接口返回的视频地址在 data 字段中
const videoUrl = response.data || recordFile
currentVideoUrl.value = videoUrl
videoDialogVisible.value = true
}
} catch (error) {
console.error('获取视频地址失败:', error)
// 错误提示已在 request.ts 中统一处理
}
// 直接播放视频
currentVideoUrl.value = recordFile
videoDialogVisible.value = true
}
const downloadVideo = (url: string) => {
const downloadVideo = async (url: string) => {
try {
// 获取视频文件
const response = await fetch(url)
const blob = await response.blob()
// 创建下载链接
const blobUrl = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.target = '_blank'
link.download = 'video.mp4'
link.href = blobUrl
link.download = `video_${Date.now()}.mp4`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(blobUrl)
ElMessage.success('下载成功')
} catch (error) {
console.error('下载视频失败:', error)
ElMessage.error('下载失败')
}
}
const downloadAllVideos = async () => {
......
......@@ -106,7 +106,7 @@ const fetchData = async () => {
params.cheatNumType = queryForm.envAbnormalCountMin
}
// 是否异常 -> isCheat
// 疑似作弊 -> isCheat
if (queryForm.isAbnormal === 'abnormal') {
params.isCheat = 1
} else if (queryForm.isAbnormal === 'normal') {
......@@ -117,28 +117,34 @@ const fetchData = async () => {
if (response.code === 200 || response.code === 0) {
const data = response.data || { records: [], total: 0 }
// 将后端数据映射到前端 Order 类型
tableData.value = (data.records || []).map((item: any) => ({
id: item.applyId, // 使用 applyId 作为唯一 id
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: ''
}))
tableData.value = (data.records || []).map((item: any) => {
// 根据 areaType 在 cities 中匹配对应的 name
const cityInfo = cities.find(c => c.code === item.areaType)
const cityName = cityInfo ? cityInfo.name : item.areaName || ''
return {
id: item.applyId, // 使用 applyId 作为唯一 id
applyId: item.applyId,
workerId: item.campaignId,
businessAccount: item.accNbr,
orderIds: [], // 工单ID列表需要单独查询或从其他字段获取
city: cityName,
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: ''
}
})
total.value = data.total || 0
}
} catch (error) {
......@@ -305,20 +311,26 @@ const openIdsDialog = async (row: Order) => {
const openDetailsDialog = async (row: Order, type: 'noPhoto' | 'manual' | 'env') => {
currentOrder.value = row
detailsType.value = type
let detailType = 0
// 设置标题
if (type === 'noPhoto') {
detailsTitle.value = '无法拍摄明细'
detailType = 1
} else if (type === 'manual') {
detailsTitle.value = '手动输入明细'
detailType = 2
} else {
detailsTitle.value = '环境异常明细'
detailType = 3
}
try {
// 调用接口获取流程明细列表
const response = await qualityApi.getProcessDetailList({
applyId: row.applyId
applyId: row.applyId,
detailType: detailType
}) as ApiResponse<any[]>
if (response.code === 200 || response.code === 0) {
......@@ -529,23 +541,30 @@ fetchData()
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
class="!w-80"
/>
</el-form-item>
<el-form-item label="无法拍摄">
<el-input-number v-model="queryForm.noPhotoCountMin" :min="1" controls-position="right" class="!w-32" placeholder="请输入" />
<el-input-number v-model="queryForm.noPhotoCountMin" :min="1" controls-position="right" class="!w-32" placeholder="请输入">
<template #prefix></template>
</el-input-number>
</el-form-item>
<el-form-item label="手动输入">
<el-input-number v-model="queryForm.manualInputCountMin" :min="1" controls-position="right" class="!w-32" placeholder="请输入" />
<el-input-number v-model="queryForm.manualInputCountMin" :min="1" controls-position="right" class="!w-32" placeholder="请输入">
<template #prefix></template>
</el-input-number>
</el-form-item>
<el-form-item label="环境异常">
<el-input-number v-model="queryForm.envAbnormalCountMin" :min="1" controls-position="right" class="!w-32" placeholder="请输入" />
<el-input-number v-model="queryForm.envAbnormalCountMin" :min="1" controls-position="right" class="!w-32" placeholder="请输入">
<template #prefix></template>
</el-input-number>
</el-form-item>
<el-form-item label="是否异常">
<el-form-item label="疑似作弊">
<el-select v-model="queryForm.isAbnormal" placeholder="请选择" clearable class="!w-32">
<el-option label="全部" value="" />
<el-option label="异常" value="abnormal" />
<el-option label="正常" value="normal" />
<el-option label="" value="abnormal" />
<el-option label="" value="normal" />
</el-select>
</el-form-item>
<el-form-item class="ml-auto">
......@@ -652,11 +671,11 @@ fetchData()
</el-dialog>
<!-- Dialog: Numeric Details (NoPhoto/Manual/Env) -->
<el-dialog v-model="dialogVisible.details" :title="detailsTitle" width="600px">
<el-dialog v-model="dialogVisible.details" :title="detailsTitle" width="800px">
<el-table :data="detailsData" border stripe>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="process" label="流程" />
<el-table-column prop="reason" label="原因" v-if="detailsType !== 'manual'" />
<el-table-column prop="reason" label="原因" v-if="detailsType !== 'manual'" show-overflow-tooltip/>
<el-table-column prop="time" label="提交时间" />
</el-table>
</el-dialog>
......
......@@ -286,6 +286,7 @@ onMounted(() => {
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
/>
</el-form-item>
<el-form-item>
......@@ -350,6 +351,7 @@ onMounted(() => {
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
style="width: 100%"
/>
</el-form-item>
......@@ -387,6 +389,7 @@ onMounted(() => {
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
/>
</el-form-item>
<el-form-item label="Apply ID">
......
......@@ -266,6 +266,7 @@ onMounted(() => {
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
/>
</el-form-item>
<el-form-item label="地区">
......
......@@ -510,6 +510,7 @@ onMounted(() => {
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
/>
</el-form-item>
<el-form-item label="地区">
......@@ -525,41 +526,45 @@ onMounted(() => {
</el-card>
<!-- Cards -->
<div class="grid grid-cols-2 gap-4 mb-4">
<div class="grid grid-cols-4 gap-4 mb-4">
<el-card shadow="never" class="bg-blue-50">
<div class="text-gray-500 mb-2">工单总数</div>
<div class="text-3xl font-bold mb-3">{{ stats.totalOrders }}</div>
<div class="flex justify-between text-sm">
<span class="text-gray-600">已完成工单数</span>
<span class="font-bold text-blue-600">{{ stats.finishOrders }}</span>
</div>
<div class="text-3xl font-bold text-blue-600">{{ stats.totalOrders }}</div>
</el-card>
<el-card shadow="never" class="bg-blue-50">
<div class="text-gray-500 mb-2">已完成工单数</div>
<div class="text-3xl font-bold text-blue-600">{{ stats.finishOrders }}</div>
</el-card>
<el-card shadow="never" class="bg-green-50">
<div class="text-gray-500 mb-2">质检工单总数</div>
<div class="text-3xl font-bold text-green-600 mb-3">{{ stats.resultCount }}</div>
<div class="flex justify-between text-sm">
<span class="text-gray-600">已完成质检工单数</span>
<span class="font-bold text-green-600">{{ stats.finishResult }}</span>
</div>
<div class="text-3xl font-bold text-green-600">{{ stats.resultCount }}</div>
</el-card>
<el-card shadow="never" class="bg-green-50">
<div class="text-gray-500 mb-2">已完成质检工单数</div>
<div class="text-3xl font-bold text-green-600">{{ stats.finishResult }}</div>
</el-card>
<el-card shadow="never" class="bg-orange-50">
<div class="text-gray-500 mb-2">投诉工单总数</div>
<div class="text-3xl font-bold text-orange-600 mb-3">{{ stats.complaintTotal }}</div>
<div class="flex justify-between text-sm">
<span class="text-gray-600">已完成投诉工单数</span>
<span class="font-bold text-orange-600">{{ stats.complaintFinish }}</span>
</div>
<div class="text-3xl font-bold text-orange-600">{{ stats.complaintTotal }}</div>
</el-card>
<el-card shadow="never" class="bg-orange-50">
<div class="text-gray-500 mb-2">已完成投诉工单数</div>
<div class="text-3xl font-bold text-orange-600">{{ stats.complaintFinish }}</div>
</el-card>
<el-card shadow="never" class="bg-purple-50">
<div class="text-gray-500 mb-2">无法质检数</div>
<div class="text-3xl font-bold text-purple-600 mb-3">{{ stats.noShowCount }}</div>
<div class="flex justify-between text-sm">
<span class="text-gray-600">平均耗时</span>
<span class="font-bold text-purple-600">{{ stats.averageDuration }}</span>
</div>
<div class="text-3xl font-bold text-purple-600">{{ stats.noShowCount }}</div>
</el-card>
<el-card shadow="never" class="bg-purple-50">
<div class="text-gray-500 mb-2">平均耗时(秒)</div>
<div class="text-3xl font-bold text-purple-600">{{ stats.averageDuration }}</div>
</el-card>
</div>
......
......@@ -387,6 +387,7 @@ onMounted(() => {
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
/>
</el-form-item>
<el-form-item>
......@@ -418,7 +419,7 @@ onMounted(() => {
</el-card>
<el-card shadow="never" class="bg-indigo-50">
<div class="text-gray-500 mb-2">平均耗时</div>
<div class="text-gray-500 mb-2">环节平均耗时</div>
<div class="text-4xl font-bold text-indigo-600 mb-3">{{ summary.avgDuration }}<span class="text-xl">s</span></div>
</el-card>
......@@ -489,7 +490,7 @@ onMounted(() => {
<template #default="{ row }">{{ row.autoRecognizeRate }}%</template>
</el-table-column>
<el-table-column prop="avgRecognizeTimes" label="平均识别次数" sortable />
<el-table-column prop="avgDuration" label="平均耗时" sortable>
<el-table-column prop="avgDuration" label="环节平均耗时" sortable>
<template #default="{ row }">{{ row.avgDuration }}s</template>
</el-table-column>
</el-table>
......@@ -526,7 +527,7 @@ onMounted(() => {
<template #default="{ row }">{{ row.autoRecognizeRate }}%</template>
</el-table-column>
<el-table-column prop="avgRecognizeTimes" label="平均识别次数" sortable />
<el-table-column prop="avgDuration" label="平均耗时" sortable>
<el-table-column prop="avgDuration" label="环节平均耗时" sortable>
<template #default="{ row }">{{ row.avgDuration }}s</template>
</el-table-column>
</el-table>
......
......@@ -374,7 +374,7 @@
<script src="js/vue.min.js"></script>
<script src="js/vant.min.js"></script>
<script src="js/demo.js?90910"></script>
<script src="js/demo.js?121222"></script>
</body>
</html>
\ No newline at end of file
......@@ -390,7 +390,7 @@ function snInputShow() {
}
$('#snTimeOut').click(function () {
if (snTimeNum > 0) {
if (snTimeNum>0 && window.timeOutFlag) {
return
}
......@@ -398,6 +398,11 @@ $('#snTimeOut').click(function () {
snInputShow()
})
function cheatShowMethod() {
if(!window.timeOutFlag){
$('#cheatClose').text('重新拍摄')
return
}
$('#cheatClose').addClass('gray')
cheatTimeStr = setTimeout(() => {
cheatTimeNum -= 1
......@@ -412,6 +417,10 @@ function cheatShowMethod() {
}, 1000)
}
function noShootShowMethod() {
if(!window.timeOutFlag){
return
}
$('#noShootTime').addClass('grayNoShoot')
$('#noShootTime').removeClass('clickButt')
if (!noShootTimeStr) {
......@@ -432,6 +441,11 @@ function noShootShowMethod() {
}, 1000)
}
function snShowMethod() {
if(!window.timeOutFlag){
$('#snTimeOut').text('手动输入')
return
}
$('#snTimeOut').addClass('graySn')
snTimeStr = setTimeout(() => {
snTimeNum -= 1
......@@ -856,7 +870,7 @@ $('.clickButt').click(async (e) => {
}
if (key == 'bunengpai') {
if($('#noShootTime').hasClass('grayNoShoot')){
if($('#noShootTime').hasClass('grayNoShoot') && window.timeOutFlag){
return
}else{
noShootTimeIndex += 1
......@@ -1275,7 +1289,7 @@ $('.continueTest').click(() => {
window.location.reload()
})
$('#cheatClose').click(() => {
if (cheatTimeNum >= 1) {
if (cheatTimeNum>=1 && window.timeOutFlag) {
return
}
......
......@@ -21,3 +21,10 @@ if(pa.areaType && cheatArea.includes(pa.areaType)){
window.accountInputNum = 5
}
//控制页面上的倒计时限制是否开启
let timeOutArea = '3208'
window.timeOutFlag = false
if(pa.areaType && timeOutArea.includes(pa.areaType)){
window.timeOutFlag = true
}
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!