1
This commit is contained in:
520
admin/src/views/home/index.vue
Normal file
520
admin/src/views/home/index.vue
Normal 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>
|
||||
Reference in New Issue
Block a user