Commit 32c48fee by 李宁

1

1 parent 16c0d509
...@@ -2,7 +2,10 @@ ...@@ -2,7 +2,10 @@
"permissions": { "permissions": {
"allow": [ "allow": [
"Bash(claude mcp add:*)", "Bash(claude mcp add:*)",
"Bash(claude:*)" "Bash(claude:*)",
"Bash(nc -U /tmp/pencil-mcp.sock)",
"Bash(curl -s \"http://localhost:3000/api/files/A5Pf8\")",
"Bash(curl -I \"https://fileserver.pencildev.app/api/file/A5Pf8\")"
] ]
} }
} }
...@@ -153,4 +153,13 @@ ...@@ -153,4 +153,13 @@
质检工单列表页面修改 质检工单列表页面修改
筛选模块中的无法拍摄、手动输入、环境异常三个输入框,默认显示“请输入”
\ No newline at end of file \ No newline at end of file
筛选模块中的无法拍摄、手动输入、环境异常三个输入框,默认显示“请输入”
串号数据统计页面修改
1、设备识别通过率图表上,添加2次通过的显示,字段为passRate2
2、表格中添加2次通过率的显示,字段为passRate2
3、页面上所有图表上悬浮显示的数据,加上百分比
串号数据统计页面修改
1、导出表格,改为接口请求,接口地址/zhijian/opt/exportSNStatistics,请求参数(startDate,endDate)
2、日期多选时,也显示出导出表格功能
\ No newline at end of file \ No newline at end of file
...@@ -30,4 +30,4 @@ ...@@ -30,4 +30,4 @@
"vite": "^7.2.4", "vite": "^7.2.4",
"vue-tsc": "^3.1.4" "vue-tsc": "^3.1.4"
} }
} }
\ No newline at end of file \ No newline at end of file
...@@ -145,6 +145,15 @@ export const statsApi = { ...@@ -145,6 +145,15 @@ export const statsApi = {
data data
}) })
}, },
// Export SN Statistics - 导出串号统计数据
exportSNStatistics(data: any): Promise<any> {
return request({
url: '/zhijian/opt/exportSNStatistics',
method: 'get',
params: data,
responseType: 'blob'
})
},
// Process Total Statistics - 质检环节统计 // Process Total Statistics - 质检环节统计
getProcessTotalStatistics(data: any): Promise<any> { getProcessTotalStatistics(data: any): Promise<any> {
return request({ return request({
......
...@@ -145,7 +145,11 @@ const updateChart = () => { ...@@ -145,7 +145,11 @@ const updateChart = () => {
chart.setOption({ chart.setOption({
title: { text: '地市不能拍次数占比统计' }, title: { text: '地市不能拍次数占比统计' },
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } }, tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
valueFormatter: (value: any) => value + '%'
},
legend: { data: ['1次', '2次', '3次'], bottom: 0 }, legend: { data: ['1次', '2次', '3次'], bottom: 0 },
grid: { left: '3%', right: '4%', bottom: '15%', containLabel: true }, grid: { left: '3%', right: '4%', bottom: '15%', containLabel: true },
xAxis: { xAxis: {
......
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted, computed, nextTick, watch } from 'vue' import { ref, reactive, onMounted, computed, nextTick, watch } from 'vue'
import { Search, Download } from '@element-plus/icons-vue' import { Search, Download } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import * as echarts from 'echarts' import * as echarts from 'echarts'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { statsApi } from '../../api' import { statsApi } from '../../api'
...@@ -143,17 +144,22 @@ const initSingleDayCharts = () => { ...@@ -143,17 +144,22 @@ const initSingleDayCharts = () => {
const deviceNames = processList.value.map(item => item.processName) const deviceNames = processList.value.map(item => item.processName)
const pass1Data = processList.value.map(item => item.passRate1 || 0) const pass1Data = processList.value.map(item => item.passRate1 || 0)
const pass2Data = processList.value.map(item => item.passRate2 || 0)
const pass3Data = processList.value.map(item => item.passRate3 || 0) const pass3Data = processList.value.map(item => item.passRate3 || 0)
passRateChart.setOption({ passRateChart.setOption({
title: { text: '设备识别通过率' }, title: { text: '设备识别通过率' },
tooltip: { trigger: 'axis' }, tooltip: {
legend: { data: ['1次通过', '3次通过'], bottom: 0 }, trigger: 'axis',
valueFormatter: (value: any) => value + '%'
},
legend: { data: ['1次通过', '2次通过', '3次通过'], bottom: 0 },
grid: { left: '3%', right: '4%', bottom: '15%', containLabel: true }, grid: { left: '3%', right: '4%', bottom: '15%', containLabel: true },
xAxis: { type: 'category', data: deviceNames, axisLabel: { interval: 0, rotate: 30 } }, xAxis: { type: 'category', data: deviceNames, axisLabel: { interval: 0, rotate: 30 } },
yAxis: { type: 'value', name: '%' }, yAxis: { type: 'value', name: '%' },
series: [ series: [
{ name: '1次通过', type: 'bar', data: pass1Data }, { name: '1次通过', type: 'bar', data: pass1Data },
{ name: '2次通过', type: 'bar', data: pass2Data },
{ name: '3次通过', type: 'bar', data: pass3Data } { name: '3次通过', type: 'bar', data: pass3Data }
] ]
}) })
...@@ -169,7 +175,10 @@ const initSingleDayCharts = () => { ...@@ -169,7 +175,10 @@ const initSingleDayCharts = () => {
trendChart.setOption({ trendChart.setOption({
title: { text: '设备自动识别通过率' }, title: { text: '设备自动识别通过率' },
tooltip: { trigger: 'axis' }, tooltip: {
trigger: 'axis',
valueFormatter: (value: any) => value + '%'
},
legend: { data: ['自动识别率'], bottom: 0 }, legend: { data: ['自动识别率'], bottom: 0 },
xAxis: { type: 'category', data: deviceNames, axisLabel: { interval: 0, rotate: 30 } }, xAxis: { type: 'category', data: deviceNames, axisLabel: { interval: 0, rotate: 30 } },
yAxis: { type: 'value', name: '%' }, yAxis: { type: 'value', name: '%' },
...@@ -199,7 +208,10 @@ const initMultiDayCharts = () => { ...@@ -199,7 +208,10 @@ const initMultiDayCharts = () => {
overallPassRateChart.setOption({ overallPassRateChart.setOption({
title: { text: '整体通过率时间趋势' }, title: { text: '整体通过率时间趋势' },
tooltip: { trigger: 'axis' }, tooltip: {
trigger: 'axis',
valueFormatter: (value: any) => value + '%'
},
legend: { data: ['1次通过率', '3次通过率'], bottom: 0 }, legend: { data: ['1次通过率', '3次通过率'], bottom: 0 },
xAxis: { type: 'category', data: dates }, xAxis: { type: 'category', data: dates },
yAxis: { type: 'value', name: '%' }, yAxis: { type: 'value', name: '%' },
...@@ -221,7 +233,10 @@ const initMultiDayCharts = () => { ...@@ -221,7 +233,10 @@ const initMultiDayCharts = () => {
overallAutoRateChart.setOption({ overallAutoRateChart.setOption({
title: { text: '整体自动识别率时间趋势' }, title: { text: '整体自动识别率时间趋势' },
tooltip: { trigger: 'axis' }, tooltip: {
trigger: 'axis',
valueFormatter: (value: any) => value + '%'
},
xAxis: { type: 'category', data: dates }, xAxis: { type: 'category', data: dates },
yAxis: { type: 'value', name: '%' }, yAxis: { type: 'value', name: '%' },
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
...@@ -239,17 +254,22 @@ const initMultiDayCharts = () => { ...@@ -239,17 +254,22 @@ const initMultiDayCharts = () => {
const deviceNames = processList.value.map(item => item.processName) const deviceNames = processList.value.map(item => item.processName)
const pass1Data = processList.value.map(item => item.passRate1 || 0) const pass1Data = processList.value.map(item => item.passRate1 || 0)
const pass2Data = processList.value.map(item => item.passRate2 || 0)
const pass3Data = processList.value.map(item => item.passRate3 || 0) const pass3Data = processList.value.map(item => item.passRate3 || 0)
devicePassRateChart.setOption({ devicePassRateChart.setOption({
title: { text: '设备识别通过率' }, title: { text: '设备识别通过率' },
tooltip: { trigger: 'axis' }, tooltip: {
legend: { data: ['1次通过', '3次通过'], bottom: 0 }, trigger: 'axis',
valueFormatter: (value: any) => value + '%'
},
legend: { data: ['1次通过', '2次通过', '3次通过'], bottom: 0 },
grid: { left: '3%', right: '4%', bottom: '15%', containLabel: true }, grid: { left: '3%', right: '4%', bottom: '15%', containLabel: true },
xAxis: { type: 'category', data: deviceNames, axisLabel: { interval: 0, rotate: 30 } }, xAxis: { type: 'category', data: deviceNames, axisLabel: { interval: 0, rotate: 30 } },
yAxis: { type: 'value', name: '%' }, yAxis: { type: 'value', name: '%' },
series: [ series: [
{ name: '1次通过', type: 'bar', data: pass1Data }, { name: '1次通过', type: 'bar', data: pass1Data },
{ name: '2次通过', type: 'bar', data: pass2Data },
{ name: '3次通过', type: 'bar', data: pass3Data } { name: '3次通过', type: 'bar', data: pass3Data }
] ]
}) })
...@@ -267,7 +287,10 @@ const initMultiDayCharts = () => { ...@@ -267,7 +287,10 @@ const initMultiDayCharts = () => {
deviceAutoRateChart.setOption({ deviceAutoRateChart.setOption({
title: { text: '设备自动识别率' }, title: { text: '设备自动识别率' },
tooltip: { trigger: 'axis' }, tooltip: {
trigger: 'axis',
valueFormatter: (value: any) => value + '%'
},
legend: { data: ['自动识别率'], bottom: 0 }, legend: { data: ['自动识别率'], bottom: 0 },
xAxis: { type: 'category', data: deviceNames, axisLabel: { interval: 0, rotate: 30 } }, xAxis: { type: 'category', data: deviceNames, axisLabel: { interval: 0, rotate: 30 } },
yAxis: { type: 'value', name: '%' }, yAxis: { type: 'value', name: '%' },
...@@ -309,6 +332,43 @@ const handleResize = () => { ...@@ -309,6 +332,43 @@ const handleResize = () => {
deviceAutoRateChart?.resize() deviceAutoRateChart?.resize()
} }
const handleExport = async () => {
try {
loading.value = true
const params: any = {}
if (queryForm.dateRange && queryForm.dateRange.length === 2) {
const start = queryForm.dateRange[0]
const end = queryForm.dateRange[1]
if (start && end) {
params.startDate = formatDateTime(new Date(start))
const endDate = new Date(end)
endDate.setHours(23, 59, 59, 999)
params.endDate = formatDateTime(endDate)
}
}
const response = await statsApi.exportSNStatistics(params)
const blob = new Blob([response], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `串号统计_${new Date().getTime()}.xlsx`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
ElMessage.success('导出成功')
} catch (error) {
console.error('导出失败:', error)
ElMessage.error('导出失败')
} finally {
loading.value = false
}
}
onMounted(() => { onMounted(() => {
fetchStats() fetchStats()
window.addEventListener('resize', handleResize) window.addEventListener('resize', handleResize)
...@@ -382,7 +442,7 @@ onMounted(() => { ...@@ -382,7 +442,7 @@ onMounted(() => {
<!-- Case 2: Multi Day --> <!-- Case 2: Multi Day -->
<el-card v-else shadow="never" class="mb-4"> <el-card v-else shadow="never" class="mb-4">
<template #header> <template #header>
<div class="flex items-center gap-4"> <div class="flex items-center gap-4 w-full">
<span class="font-bold">趋势分析</span> <span class="font-bold">趋势分析</span>
<el-radio-group v-model="trendTab" size="small"> <el-radio-group v-model="trendTab" size="small">
<el-radio-button value="overall">整体趋势</el-radio-button> <el-radio-button value="overall">整体趋势</el-radio-button>
...@@ -403,27 +463,39 @@ onMounted(() => { ...@@ -403,27 +463,39 @@ onMounted(() => {
<div ref="deviceAutoRateChartRef" style="height: 400px; border: 1px solid #f0f0f0; border-radius: 4px; padding: 10px;" class="mb-4"></div> <div ref="deviceAutoRateChartRef" style="height: 400px; border: 1px solid #f0f0f0; border-radius: 4px; padding: 10px;" class="mb-4"></div>
<!-- Table for Device Details --> <!-- Table for Device Details -->
<el-table :data="processList" border stripe> <el-card shadow="never">
<el-table-column prop="processName" label="设备名称" /> <template #header>
<el-table-column prop="totalCount" label="提交总次数" sortable /> <div class="flex justify-between items-center">
<el-table-column prop="passRate1" label="1次通过率" sortable> <span class="font-bold">设备串号明细</span>
<template #default="{ row }">{{ row.passRate1 }}%</template> <el-button type="success" link :icon="Download" @click="handleExport">导出表格</el-button>
</el-table-column> </div>
<el-table-column prop="passRate3" label="3次通过率" sortable> </template>
<template #default="{ row }">{{ row.passRate3 }}%</template> <el-table :data="processList" border stripe>
</el-table-column> <el-table-column prop="processName" label="设备名称" />
<el-table-column prop="passRateWithin3" label="3次内通过率" sortable> <el-table-column prop="totalCount" label="提交总次数" sortable />
<template #default="{ row }">{{ row.passRateWithin3 }}%</template> <el-table-column prop="passRate1" label="1次通过率" sortable>
</el-table-column> <template #default="{ row }">{{ row.passRate1 }}%</template>
<el-table-column prop="autoRecognizeRate" label="自动识别占比" sortable> </el-table-column>
<template #default="{ row }">{{ row.autoRecognizeRate }}%</template> <el-table-column prop="passRate2" label="2次通过率" sortable>
</el-table-column> <template #default="{ row }">{{ row.passRate2 }}%</template>
<el-table-column prop="avgRecognizeTimes" label="平均识别次数" sortable /> </el-table-column>
<el-table-column prop="avgDuration" label="平均耗时" sortable> <el-table-column prop="passRate3" label="3次通过率" sortable>
<template #default="{ row }">{{ row.avgDuration }}s</template> <template #default="{ row }">{{ row.passRate3 }}%</template>
</el-table-column> </el-table-column>
</el-table> <el-table-column prop="passRateWithin3" label="3次内通过率" sortable>
<template #default="{ row }">{{ row.passRateWithin3 }}%</template>
</el-table-column>
<el-table-column prop="autoRecognizeRate" label="自动识别占比" sortable>
<template #default="{ row }">{{ row.autoRecognizeRate }}%</template>
</el-table-column>
<el-table-column prop="avgRecognizeTimes" label="平均识别次数" sortable />
<el-table-column prop="avgDuration" label="平均耗时" sortable>
<template #default="{ row }">{{ row.avgDuration }}s</template>
</el-table-column>
</el-table>
</el-card>
</div> </div>
</el-card> </el-card>
<!-- Single Day Table (Outside of multi-day card) --> <!-- Single Day Table (Outside of multi-day card) -->
...@@ -432,7 +504,7 @@ onMounted(() => { ...@@ -432,7 +504,7 @@ onMounted(() => {
<template #header> <template #header>
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<span class="font-bold">设备串号明细</span> <span class="font-bold">设备串号明细</span>
<el-button type="success" link :icon="Download">导出表格</el-button> <el-button type="success" link :icon="Download" @click="handleExport">导出表格</el-button>
</div> </div>
</template> </template>
<el-table :data="processList" border stripe> <el-table :data="processList" border stripe>
...@@ -441,6 +513,9 @@ onMounted(() => { ...@@ -441,6 +513,9 @@ onMounted(() => {
<el-table-column prop="passRate1" label="1次通过率" sortable> <el-table-column prop="passRate1" label="1次通过率" sortable>
<template #default="{ row }">{{ row.passRate1 }}%</template> <template #default="{ row }">{{ row.passRate1 }}%</template>
</el-table-column> </el-table-column>
<el-table-column prop="passRate2" label="2次通过率" sortable>
<template #default="{ row }">{{ row.passRate2 }}%</template>
</el-table-column>
<el-table-column prop="passRate3" label="3次通过率" sortable> <el-table-column prop="passRate3" label="3次通过率" sortable>
<template #default="{ row }">{{ row.passRate3 }}%</template> <template #default="{ row }">{{ row.passRate3 }}%</template>
</el-table-column> </el-table-column>
......
...@@ -1089,7 +1089,6 @@ body { ...@@ -1089,7 +1089,6 @@ body {
.warningCon .detail .des { .warningCon .detail .des {
line-height: .6rem; line-height: .6rem;
text-align: justify; text-align: justify;
text-indent: 2em;
font-size: .32rem; font-size: .32rem;
color: #333; color: #333;
} }
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<meta http-equiv="X-UA-Compatible" content="ie=edge"> <meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>江苏移动</title> <title>江苏移动</title>
<link rel="stylesheet" href="https://xpo.oss-cn-beijing.aliyuncs.com/huaian/css/vant.css" /> <link rel="stylesheet" href="https://xpo.oss-cn-beijing.aliyuncs.com/huaian/css/vant.css" />
<link rel="stylesheet" href="css/demo.css?113311"> <link rel="stylesheet" href="css/demo.css?113313311">
</head> </head>
<body> <body>
...@@ -374,7 +374,7 @@ ...@@ -374,7 +374,7 @@
<script src="js/vue.min.js"></script> <script src="js/vue.min.js"></script>
<script src="js/vant.min.js"></script> <script src="js/vant.min.js"></script>
<script src="js/demo.js?sd1d355133"></script> <script src="js/demo.js?90910"></script>
</body> </body>
</html> </html>
\ No newline at end of file \ No newline at end of file
...@@ -142,7 +142,9 @@ var gbBoxClick = false ...@@ -142,7 +142,9 @@ var gbBoxClick = false
//作弊倒计时字段 //作弊倒计时字段
var cheatTimeNum = 10 var cheatTimeNum = 10
var cheatNumRecord = 0
var cheatTimeStr = '' var cheatTimeStr = ''
var ifBeginCheat = true
//无法拍摄倒计时字段 //无法拍摄倒计时字段
var noShootTimeNum = 5 var noShootTimeNum = 5
...@@ -189,9 +191,15 @@ function getProcess() { ...@@ -189,9 +191,15 @@ function getProcess() {
$('#waitting').hide() $('#waitting').hide()
} }
//作弊弹窗的显示逻辑 //为了屏蔽刚进页面时的作弊弹窗显示
if (res.findCheatNum && res.findCheatNum >= 1) { if(ifBeginCheat){
cheatNumRecord = res.findCheatNum || 0
ifBeginCheat = false
}
//作弊弹窗的显示逻辑,cheatNumRecord
if (res.findCheatNum && res.findCheatNum >= 1 && cheatNumRecord!=res.findCheatNum) {
cheatTimeNum = 10 cheatTimeNum = 10
cheatNumRecord = res.findCheatNum
if (cheatTimeStr) { if (cheatTimeStr) {
clearTimeout(cheatTimeStr) clearTimeout(cheatTimeStr)
} }
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!