smart-car-web/src/views/device/data-recognition/index.vue

400 lines
13 KiB
Vue
Raw Normal View History

2025-12-23 09:22:03 +08:00
<template>
<!-- 设备管理-数据识别 -->
<el-card class="data-recognition-container">
<!-- 顶部过滤器 -->
<el-card v-show="showSearch" class="search-card">
<el-form :inline="true" ref="queryFormRef" :model="queryParams" label-width="auto">
<el-form-item>
<el-date-picker v-model="timeRange" type="datetimerange" range-separator=" ~ "
start-placeholder="开始时间" end-placeholder="结束时间" value-format="yyyy-MM-dd HH:mm"
format="yyyy-MM-dd HH:mm" style="width: 400px" @change="handleTimeRangeChange" />
</el-form-item>
<el-form-item>
<el-button class="query-btn" @click="handleQuery">查询</el-button>
<el-button class="reset-btn" @click="handleReset">重置</el-button>
<el-button class="export-btn" @click="handleExport">导出</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 车辆数据统计 -->
<el-card class="stats-card">
<h3 class="stats-title">车辆数据</h3>
<div class="stats-grid">
<div class="stat-card" v-for="(stat, index) in statistics" :key="index">
<div class="stat-value">{{ stat.value }}</div>
<div class="stat-label">{{ stat.label }}</div>
</div>
</div>
</el-card>
<!-- 数据列表 -->
<div class="table-container">
<TableModel :formLabel="[]" :showOperation="false" :showRightTools="false" ref="dataRecognitionTableRef"
:columnsList="columnsList" :request-api="wrappedAPI" :handleColWidth="250" :showSearch="false">
<template slot="tableTitle">
<h3>数据列表</h3>
</template>
<template slot="recognitionPhoto" slot-scope="{ data }">
<el-image :src="data.recognitionPhoto || test_image" alt="识别照片" class="recognition-photo"
fit="cover" :preview-src-list="previewImageList">
<div slot="error" class="image-error">
<i class="el-icon-picture"></i>
</div>
</el-image>
</template>
</TableModel>
</div>
</el-card>
</template>
<script>
import TableModel from '@/components/TableModel2'
import { columnsList } from './config'
import { dataRecognitionListAPI, exportDataRecognitionAPI } from '@/api/device/data-recognition'
import test_image from '@/assets/images/test_image.png'
export default {
name: 'DataRecognition',
components: {
TableModel,
},
data() {
return {
showSearch: true,
columnsList,
dataRecognitionListAPI,
wrappedAPI: null,
test_image,
timeRange: null,
statistics: [
{ label: '车辆总数(辆)', value: '20000' },
{ label: '油车数量(辆)', value: '15000' },
{ label: '新能源数量(辆)', value: '5000' },
{ label: '总车流量(PCU/h)', value: '20000' },
{ label: '油车车流量(PCU/h)', value: '15000' },
{ label: '新能源车流量(PCU/h)', value: '5000' },
],
queryParams: {
pageNum: 1,
pageSize: 10,
startTime: '',
endTime: '',
},
}
},
computed: {
previewImageList() {
// 从表格数据中获取所有图片URL
if (this.$refs.dataRecognitionTableRef && this.$refs.dataRecognitionTableRef.tableList) {
return this.$refs.dataRecognitionTableRef.tableList
.map(item => item.recognitionPhoto || this.test_image)
.filter(url => url)
}
return []
},
},
created() {
// 包装API函数以支持测试数据
this.wrappedAPI = this.wrapAPIWithTestData(dataRecognitionListAPI)
},
methods: {
/** 包装API函数支持测试数据 */
wrapAPIWithTestData(apiFunc) {
return async (params) => {
try {
// 尝试调用真实API
const res = await apiFunc(params)
if (res.code === 200) {
return res
}
} catch (error) {
console.log('API调用失败使用测试数据:', error)
}
// 使用测试数据
const allTestData = this.getTestData()
// 应用过滤条件
let filteredData = allTestData
if (params.startTime) {
filteredData = filteredData.filter(item => {
const itemTime = new Date(item.recognitionTime.replace(/\//g, '-'))
const startTime = new Date(params.startTime.replace(' ', 'T'))
return itemTime >= startTime
})
}
if (params.endTime) {
filteredData = filteredData.filter(item => {
const itemTime = new Date(item.recognitionTime.replace(/\//g, '-'))
const endTime = new Date(params.endTime.replace(' ', 'T'))
return itemTime <= endTime
})
}
// 分页处理
const pageNum = params.pageNum || 1
const pageSize = params.pageSize || 10
const start = (pageNum - 1) * pageSize
const end = start + pageSize
return {
code: 200,
rows: filteredData.slice(start, end),
total: filteredData.length,
msg: 'success'
}
}
},
/** 获取测试数据 */
getTestData() {
const plateColors = ['蓝色', '黄色', '绿色', '白色']
const vehicleTypes = ['油车', '新能源']
const locations = ['合肥市蜀山区xxxxxx', '合肥市包河区xxxxxx', '合肥市庐阳区xxxxxx']
const testData = []
// 生成1000条测试数据
for (let i = 0; i < 1000; i++) {
const date = new Date()
date.setDate(date.getDate() - Math.floor(i / 50))
date.setHours(8 + (i % 12), (i % 60), 0, 0)
const recognitionTime = this.formatDateTime(date)
// 生成车牌号皖A + 字母数字组合)
const letters = 'ABCDEFGHJKLMNPQRSTUVWXYZ'
const numbers = '0123456789'
const plateLetter = letters[Math.floor(Math.random() * letters.length)]
const plateNumbers = Array.from({ length: 4 }, () => numbers[Math.floor(Math.random() * numbers.length)]).join('')
const licensePlate = `皖A${plateLetter}${plateNumbers}`
testData.push({
serialNo: i + 1,
location: locations[i % locations.length],
recognitionTime: recognitionTime,
licensePlate: licensePlate,
plateColor: plateColors[i % plateColors.length],
vehicleType: vehicleTypes[i % vehicleTypes.length],
recognitionPhoto: this.test_image,
})
}
return testData
},
/** 格式化日期时间 */
formatDateTime(date) {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
return `${year}/${month}/${day} ${hours}:${minutes}`
},
/** 查询操作 */
handleQuery() {
if (this.$refs.dataRecognitionTableRef) {
// 合并查询参数
Object.assign(this.$refs.dataRecognitionTableRef.queryParams, this.queryParams)
this.$refs.dataRecognitionTableRef.queryParams.pageNum = 1
this.$refs.dataRecognitionTableRef.getTableList()
}
},
/** 重置操作 */
handleReset() {
this.queryParams.startTime = ''
this.queryParams.endTime = ''
this.timeRange = null
if (this.$refs.dataRecognitionTableRef) {
Object.assign(this.$refs.dataRecognitionTableRef.queryParams, this.queryParams)
this.$refs.dataRecognitionTableRef.queryParams.pageNum = 1
this.$refs.dataRecognitionTableRef.getTableList()
}
},
/** 导出操作 */
async handleExport() {
try {
const params = { ...this.queryParams }
const res = await exportDataRecognitionAPI(params)
if (res.code === 200) {
// 处理文件下载
const blob = new Blob([res], { type: 'application/vnd.ms-excel' })
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `数据识别数据_${new Date().getTime()}.xlsx`
link.click()
window.URL.revokeObjectURL(url)
this.$message.success('导出成功')
} else {
this.$message.error(res.msg || '导出失败')
}
} catch (error) {
console.error('导出失败:', error)
this.$message.error('导出失败')
}
},
/** 时间范围变化 */
handleTimeRangeChange(value) {
if (value && value.length === 2) {
this.queryParams.startTime = value[0]
this.queryParams.endTime = value[1]
} else {
this.queryParams.startTime = ''
this.queryParams.endTime = ''
}
},
},
}
</script>
<style scoped lang="scss">
.data-recognition-container {
height: calc(100vh - 84px);
overflow: hidden;
background: linear-gradient(180deg, #f1f6ff 20%, #e5efff 100%);
}
.search-card {
margin-bottom: 10px;
::v-deep .el-form-item {
margin-bottom: 10px;
}
.query-btn {
width: 98px;
height: 36px;
background: #1f72ea;
box-shadow: 0px 4px 8px 0px rgba(51, 135, 255, 0.5);
border-radius: 4px;
color: #fff;
border: none;
font-size: 14px;
transition: all 0.3s;
&:hover {
background: #4a8bff;
box-shadow: 0px 6px 12px 0px rgba(51, 135, 255, 0.6);
}
}
.reset-btn {
width: 98px;
height: 36px;
background: #ffffff;
box-shadow: 0px 4px 8px 0px rgba(76, 76, 76, 0.2);
border-radius: 4px;
color: #333;
border: none;
font-size: 14px;
transition: all 0.3s;
&:hover {
background: #f5f5f5;
box-shadow: 0px 6px 12px 0px rgba(76, 76, 76, 0.3);
color: #40a9ff;
}
}
.export-btn {
width: 98px;
height: 36px;
background: #1f72ea;
box-shadow: 0px 4px 8px 0px rgba(51, 135, 255, 0.5);
border-radius: 4px;
color: #fff;
border: none;
font-size: 14px;
transition: all 0.3s;
&:hover {
background: #4a8bff;
box-shadow: 0px 6px 12px 0px rgba(51, 135, 255, 0.6);
}
}
}
.stats-card {
margin-bottom: 10px;
.stats-title {
font-size: 16px;
font-weight: 600;
color: #333;
margin: 0 0 16px 0;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 16px;
.stat-card {
background: linear-gradient(135deg, #1f72ea 0%, #4a8bff 100%);
border-radius: 8px;
padding: 24px;
color: #fff;
text-align: center;
box-shadow: 0px 4px 12px 0px rgba(31, 114, 234, 0.3);
transition: all 0.3s;
&:hover {
transform: translateY(-2px);
box-shadow: 0px 6px 16px 0px rgba(31, 114, 234, 0.4);
}
.stat-value {
font-size: 32px;
font-weight: 700;
margin-bottom: 8px;
line-height: 1.2;
}
.stat-label {
font-size: 14px;
opacity: 0.9;
line-height: 1.4;
}
}
}
}
.table-container {
height: calc(100vh - 420px);
overflow: hidden;
}
::v-deep .table-card {
height: calc(100vh - 420px) !important;
}
.recognition-photo {
width: 80px;
height: 60px;
border-radius: 4px;
cursor: pointer;
}
.image-error {
display: flex;
align-items: center;
justify-content: center;
width: 80px;
height: 60px;
background: #f5f5f5;
border-radius: 4px;
color: #999;
i {
font-size: 24px;
}
}
</style>