This commit is contained in:
张成
2025-11-24 13:23:42 +08:00
commit 5d7444cd65
156 changed files with 50653 additions and 0 deletions

View File

@@ -0,0 +1,520 @@
<template>
<div class="home-statistics">
<!-- 设备选择器 -->
<Card style="margin-bottom: 16px;">
<p slot="title">设备选择</p>
<Select
v-model="selectedDeviceSn"
@on-change="handleDeviceChange"
placeholder="请选择设备"
style="width: 300px;"
>
<Option
v-for="device in deviceList"
:value="device.deviceSn"
:key="device.deviceSn || device.deviceName"
>
{{ device.deviceName || device.deviceSn }}
</Option>
</Select>
</Card>
<!-- 统计卡片 -->
<Row :gutter="16" style="margin-bottom: 16px;">
<Col span="6">
<Card>
<div class="statistic-item">
<div class="statistic-title">今日投递</div>
<div class="statistic-value">{{ todayStats.applyCount }}</div>
</div>
</Card>
</Col>
<Col span="6">
<Card>
<div class="statistic-item">
<div class="statistic-title">今日找工作</div>
<div class="statistic-value">{{ todayStats.jobSearchCount }}</div>
</div>
</Card>
</Col>
<Col span="6">
<Card>
<div class="statistic-item">
<div class="statistic-title">今日沟通</div>
<div class="statistic-value">{{ todayStats.chatCount }}</div>
</div>
</Card>
</Col>
<Col span="6">
<Card>
<div class="statistic-item">
<div class="statistic-title">执行中任务</div>
<div class="statistic-value">{{ todayStats.runningTaskCount }}</div>
</div>
</Card>
</Col>
</Row>
<!-- 当前执行任务的命令区块指令列表 -->
<Card v-if="runningTasks.length > 0" style="margin-bottom: 16px;">
<p slot="title">当前执行中的任务</p>
<Table :columns="taskColumns" :data="runningTasks" :loading="taskLoading">
<template slot-scope="{ row }" slot="commands">
<Tag
v-for="(cmd, index) in row.commands"
:key="index"
:color="getCommandColor(cmd.status)"
style="margin: 2px;"
>
{{ cmd.commandName }}
</Tag>
</template>
</Table>
</Card>
<!-- 趋势图表7天趋势 -->
<Card>
<p slot="title">近7天统计趋势</p>
<div ref="chartContainer" style="height: 400px;"></div>
</Card>
</div>
</template>
<script>
import * as echarts from 'echarts'
import DeviceStatusServer from '../../api/device/device_status_server.js'
import StatisticsServer from '../../api/statistics/statistics_server.js'
export default {
name: 'HomePage',
data() {
return {
selectedDeviceSn: '',
deviceList: [],
todayStats: {
applyCount: 0,
jobSearchCount: 0,
chatCount: 0,
runningTaskCount: 0
},
chartData: {
dates: [],
applyData: [],
jobSearchData: [],
chatData: []
},
runningTasks: [],
taskLoading: false,
chartInstance: null, // ECharts实例
taskColumns: [
{
title: '任务名称',
key: 'taskName',
width: 200
},
{
title: '任务类型',
key: 'taskType',
width: 120
},
{
title: '开始时间',
key: 'startTime',
width: 180
},
{
title: '进度',
key: 'progress',
width: 100,
render: (h, params) => {
return h('Progress', {
props: {
percent: params.row.progress || 0,
status: 'active'
}
})
}
},
{
title: '命令列表',
slot: 'commands',
minWidth: 300
}
],
refreshTimer: null
}
},
computed: {
// 从 store 获取选中的设备
storeDeviceSn() {
return this.$store.getters['device/selectedDeviceSn']
}
},
watch: {
storeDeviceSn: {
immediate: true,
handler(val) {
if (val && val !== this.selectedDeviceSn) {
this.selectedDeviceSn = val
this.loadData()
}
}
}
},
mounted() {
this.loadDeviceList()
// 每30秒刷新一次数据
this.refreshTimer = setInterval(() => {
this.loadData()
}, 30000)
},
beforeDestroy() {
// 清理定时器
if (this.refreshTimer) {
clearInterval(this.refreshTimer)
this.refreshTimer = null
}
// 移除窗口大小调整监听
if (this._chartResizeHandler) {
window.removeEventListener('resize', this._chartResizeHandler)
this._chartResizeHandler = null
}
// 销毁图表实例
if (this.chartInstance) {
this.chartInstance.dispose()
this.chartInstance = null
}
},
methods: {
// 加载设备列表
async loadDeviceList() {
try {
const res = await DeviceStatusServer.page({
seachOption: {},
pageOption: { page: 1, pageSize: 100 }
})
console.log('设备列表接口返回:', res)
console.log('res.data:', res.data)
console.log('res.data.list:', res.data?.list)
console.log('res.data.rows:', res.data?.rows)
// 支持多种数据格式
const deviceList = res.data?.list || res.data?.rows || res.data?.data || []
console.log('解析后的设备列表:', deviceList)
console.log('设备数量:', deviceList.length)
if (deviceList.length > 0) {
// 统一设备字段名,去重处理
const deviceMap = new Map()
deviceList.forEach(device => {
const sn = device.deviceSn || device.sn_code || device.snCode || device.id
if (sn && !deviceMap.has(sn)) {
deviceMap.set(sn, {
deviceSn: sn,
deviceName: device.deviceName || device.device_name || device.name || sn
})
}
})
this.deviceList = Array.from(deviceMap.values())
console.log('处理后的设备列表:', this.deviceList)
console.log('处理后设备数量:', this.deviceList.length)
this.$store.dispatch('device/setDeviceList', this.deviceList)
// 如果没有选中设备且有设备列表,选中第一个
if (!this.selectedDeviceSn && this.deviceList.length > 0) {
this.selectedDeviceSn = this.deviceList[0].deviceSn
this.$store.dispatch('device/setSelectedDevice', this.selectedDeviceSn)
// 立即加载数据
this.loadData()
} else if (this.selectedDeviceSn) {
// 如果已有选中设备,直接加载数据
this.loadData()
}
} else {
console.warn('设备列表为空,原始数据:', res)
}
} catch (error) {
console.error('加载设备列表失败:', error)
this.$Message.error('加载设备列表失败: ' + (error.message || '未知错误'))
}
},
// 设备切换
handleDeviceChange(deviceSn) {
this.$store.dispatch('device/setSelectedDevice', deviceSn)
this.loadData()
},
// 加载所有数据
async loadData() {
if (!this.selectedDeviceSn) return
await Promise.all([
this.loadTodayStats(),
this.loadChartData(),
this.loadRunningTasks()
])
},
// 加载今日统计
async loadTodayStats() {
try {
console.log('加载今日统计设备SN:', this.selectedDeviceSn)
const res = await StatisticsServer.getOverview(this.selectedDeviceSn)
console.log('今日统计接口返回:', res)
if (res.code === 0) {
this.todayStats = {
applyCount: res.data?.applyCount || res.data?.todayApplyCount || 0,
jobSearchCount: res.data?.jobSearchCount || res.data?.todayJobSearchCount || 0,
chatCount: res.data?.chatCount || res.data?.todayChatCount || 0,
runningTaskCount: res.data?.runningTaskCount || res.data?.runningTasks || 0
}
}
} catch (error) {
console.error('加载今日统计失败:', error)
// 不显示错误,避免干扰用户
}
},
// 加载图表数据
async loadChartData() {
try {
console.log('加载图表数据设备SN:', this.selectedDeviceSn)
const res = await StatisticsServer.getDailyStatistics({
deviceSn: this.selectedDeviceSn,
days: 7
})
console.log('图表数据接口返回:', res)
if (res.code === 0) {
this.chartData = {
dates: res.data?.dates || [],
applyData: res.data?.applyData || [],
jobSearchData: res.data?.jobSearchData || [],
chatData: res.data?.chatData || []
}
this.$nextTick(() => {
this.renderChart()
})
} else {
console.warn('加载图表数据失败:', res.msg || res.message)
}
} catch (error) {
console.error('加载图表数据失败:', error)
}
},
// 加载正在执行的任务
async loadRunningTasks() {
this.taskLoading = true
try {
console.log('加载执行中任务设备SN:', this.selectedDeviceSn)
const res = await StatisticsServer.getRunningTasks(this.selectedDeviceSn)
console.log('执行中任务接口返回:', res)
if (res.code === 0) {
this.runningTasks = res.data || []
} else {
console.warn('加载执行中任务失败:', res.msg || res.message)
this.runningTasks = []
}
} catch (error) {
console.error('加载执行中任务失败:', error)
this.runningTasks = []
} finally {
this.taskLoading = false
}
},
// 渲染图表
renderChart() {
if (!this.$refs.chartContainer) return
// 如果没有数据,显示提示
if (!this.chartData.dates || this.chartData.dates.length === 0) {
if (this.chartInstance) {
this.chartInstance.dispose()
this.chartInstance = null
}
this.$refs.chartContainer.innerHTML = `
<div style="text-align: center; padding: 100px 0; color: #999;">
<p>暂无统计数据</p>
</div>
`
return
}
// 初始化或获取图表实例
if (!this.chartInstance) {
this.chartInstance = echarts.init(this.$refs.chartContainer)
}
// 格式化日期显示(只显示月-日)
const formattedDates = this.chartData.dates.map(date => {
const d = new Date(date)
return `${d.getMonth() + 1}-${d.getDate()}`
})
// 配置图表选项
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
}
},
legend: {
data: ['投递', '找工作', '沟通'],
top: 10
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: formattedDates,
axisLabel: {
rotate: 45
}
},
yAxis: {
type: 'value',
name: '数量'
},
series: [
{
name: '投递',
type: 'line',
smooth: true,
data: this.chartData.applyData,
itemStyle: {
color: '#2d8cf0'
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(45, 140, 240, 0.3)' },
{ offset: 1, color: 'rgba(45, 140, 240, 0.1)' }
]
}
}
},
{
name: '找工作',
type: 'line',
smooth: true,
data: this.chartData.jobSearchData,
itemStyle: {
color: '#19be6b'
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(25, 190, 107, 0.3)' },
{ offset: 1, color: 'rgba(25, 190, 107, 0.1)' }
]
}
}
},
{
name: '沟通',
type: 'line',
smooth: true,
data: this.chartData.chatData,
itemStyle: {
color: '#ff9900'
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(255, 153, 0, 0.3)' },
{ offset: 1, color: 'rgba(255, 153, 0, 0.1)' }
]
}
}
}
]
}
// 设置图表选项
this.chartInstance.setOption(option, true)
// 响应式调整 - 使用箭头函数确保this指向正确
if (!this._chartResizeHandler) {
this._chartResizeHandler = () => {
if (this.chartInstance) {
this.chartInstance.resize()
}
}
window.addEventListener('resize', this._chartResizeHandler)
}
},
// 获取命令状态颜色
getCommandColor(status) {
const colorMap = {
'pending': 'default',
'running': 'blue',
'success': 'green',
'failed': 'red',
'skipped': 'warning'
}
return colorMap[status] || 'default'
}
}
}
</script>
<style scoped>
.home-statistics {
padding: 16px;
}
.statistic-item {
text-align: center;
}
.statistic-title {
font-size: 14px;
color: #999;
margin-bottom: 8px;
}
.statistic-value {
font-size: 32px;
font-weight: bold;
color: #2d8cf0;
}
</style>