Files
autoAiWorkSys/admin/src/views/home/index.vue
张成 5d7444cd65 1
2025-11-24 13:23:42 +08:00

521 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>