@ -77,14 +77,21 @@
< div class = "card-header" >
< h2 > 设备使用频次 < / h2 >
< div class = "chart-controls" >
<!-- < el-select v-model = "timeRang e" placeholder="选择时间范围" @change="updateFrequencyChart " >
< el -option label = "近半年" value = "halfYear" > < / el-option >
< el-option label = "近一年" value = "oneYear" > < / el-option >
< / el-select > -- >
< el-select v-model = "deviceType" placeholder="设备类型" style="width: 150px;" >
< el-select v-model = "deviceTyp e" placeholder="设备类型" style="width: 150px; "
@change ="handleDeviceTypeChange" >
< el -option v-for = "item in deviceTypeOptions" :key="item.value" :label="item.typeName"
:value = "item.deviceTypeId" / >
< / el-select >
< div class = "tab-group" >
< div class = "tab-item" : class = "{ 'tab-item--active': activeTab === '1' }"
@click ="updateFrequencyChart('1')" >
近半年
< / div >
< div class = "tab-item" : class = "{ 'tab-item--active': activeTab === '2' }"
@click ="updateFrequencyChart('2')" >
近一年
< / div >
< / div >
< / div >
< / div >
< div class = "card-body" >
@ -105,12 +112,12 @@
<!-- 环形图容器 : 今日报警处理占比 -- >
< div ref = "alarmRingChartRef" class = "chart-container" > < / div >
< div class = "alarm-stats" >
< div class = "stat-item" >
< div class = "stat red" > 365 < / div >
< div class = "stat-item" v-if = "alarmsData" >
< div class = "stat red" > { { alarmsData . alarmsTotal } } < / div >
< div class = "label" > 报警总数 < / div >
< / div >
< div class = "stat-item" >
< div class = "stat green" > 300 < / div >
< div class = "stat-item" v-if = "alarmsData" >
< div class = "stat green" > { { alarmsData . processingAlarm } } < / div >
< div class = "label" > 总处理报警 < / div >
< / div >
< / div >
@ -148,6 +155,8 @@ const DataOverview = ref<DataOverviewType>({
} ) ;
const deviceTypeOptions = ref ( [ ] ) ; //设备类型
const deviceType = ref ( )
const activeTab = ref ( '1' ) ;
const alarmsData = ref ( )
// ---------------------- 基础数据 ----------------------
// 设备分类数据
const deviceList = ref ( [
@ -155,9 +164,6 @@ const deviceList = ref([
{ name : "蓝牙设备" , current : 0 , total : 0 } ,
{ name : "4G&蓝牙设备" , current : 0 , total : 0 } ,
] ) ;
// 时间范围(控制设备使用频次图表)
const timeRange = ref ( 'halfYear' ) ;
// ---------------------- 图表Ref( 用于挂载图表实例) ----------------------
const frequencyChartRef = ref < HTMLDivElement | null > ( null ) ; // 设备使用频次折线图
const alarmRingChartRef = ref < HTMLDivElement | null > ( null ) ; // 报警环形图
@ -183,141 +189,155 @@ const handleControlPanel = () => {
} ;
// ---------------------- 图表初始化方法 ----------------------
/**
* 1. 设备使用频次折线图
*/
const initFrequencyChart = ( ) => {
const initFrequencyChart = async ( range : any = '1' , deviceTypeId : any ) => {
if ( ! frequencyChartRef . value ) return ;
// 初始化图表实例
frequencyChartInstance = echarts . init ( frequencyChartRef . value ) ;
// 假数据:按时间范围区分
const chartData = timeRange . value === 'halfYear'
? {
xAxis : [ '1月' , '2月' , '3月' , '4月' , '5月' , '6月' ] ,
yAxis : [ 320 , 280 , 450 , 380 , 520 , 480 ] , // 近半年使用次数
peak : { name : '峰值' , value : 520 , month : '5月' }
try {
let data = {
deviceTypeId : deviceTypeId
}
: {
xAxis : [ '1月' , '2月' , '3月' , '4月' , '5月' , '6月' , '7月' , '8月' , '9月' , '10月' , '11月' , '12月' ] ,
yAxis : [ 320 , 280 , 450 , 380 , 520 , 480 , 550 , 620 , 580 , 490 , 530 , 650 ] , // 近一年使用次数
peak : { name : '峰值 ' , value : 650 , month : '12月' }
const res = await api . getEquipmentUsageData ( range , data ) ;
const monthData = res . data [ 0 ] || { } ;
const monthKeys = [ 'm1' , 'm2' , 'm3' , 'm4' , 'm5' , 'm6' , 'm7' , 'm8' , 'm9' , 'm10' , 'm11' , 'm12' ] ;
const monthNames = [ '1月' , '2月' , '3月 ' , '4月' , '5月' , '6月' , '7月' , '8月' , '9月' , '10月' , '11月' , '12月' ] ;
// 3. 计算时间范围(核心逻辑)
let filteredKeys , filteredNames , yAxisData ;
const today = new Date ( ) ;
const currentMonth = today . getMonth ( ) ;
if ( range === '1' ) {
const result = [ ] ;
for ( let i = 0 ; i < 6 ; i ++ ) {
const targetMonth = ( currentMonth - i + 12 ) % 12 ;
result . push ( targetMonth ) ;
}
const recent6Months = result . reverse ( ) ;
// 匹配接口字段和名称
filteredKeys = recent6Months . map ( monthIndex => monthKeys [ monthIndex ] ) ;
filteredNames = recent6Months . map ( monthIndex => monthNames [ monthIndex ] ) ;
yAxisData = filteredKeys . map ( key => monthData [ key ] || 0 ) ;
} else {
// 近一年: 全部12个月( 1月→12月)
filteredKeys = monthKeys ;
filteredNames = monthNames ;
yAxisData = filteredKeys . map ( key => monthData [ key ] || 0 ) ;
}
const chartData = {
xAxis : filteredNames ,
yAxis : yAxisData ,
peak : { name : '峰值' , value : 0 , month : '' }
} ;
// ECharts配置项
const option = {
tooltip : {
trigger : 'axis ' ,
formatter : '{b}: {c} 次'
} ,
grid : {
left : '3%' ,
right : '4%' ,
bottom : '3%' ,
containLabel : true
} ,
xAxis : {
type : 'category' ,
data : chartData . xAxis ,
axisLabel : {
interval : 0 // 强制显示所有x轴标签
}
} ,
yAxis : {
type : 'value' ,
name : '使用次数' ,
min : 0 ,
max : Math . max ( ... chartData . yAxis ) + 100 // y轴最大值留有余量
} ,
series : [
{
name : '使用频次' ,
typ e: 'line' ,
data : chartData . yAxis ,
smooth : false , // 平滑折线
lineStyle : {
width : 3 ,
color : '#409eff'
} ,
itemStyle : {
color : '#409eff' ,
radius : 5
} ,
areaStyle : {
color : {
type : 'linear' ,
x : 0 ,
y : 0 ,
x2 : 0 ,
y2 : 1 ,
colorStops : [ {
offset : 0 , color : 'rgba(64, 158, 255, 0.3)'
} , {
offset : 1 , color : 'rgba(64, 158, 255, 0)'
} ]
}
} ,
// 标记峰值
markPoint : {
data : [ {
name : chartData . peak . name ,
value : chartData . peak . value ,
xAxis : chartData . xAxis . indexOf ( chartData . peak . month ) ,
yAxis : chartData . peak . value ,
itemStyle : {
color : '#ff4d4f'
if ( yAxisData . length ) {
const maxVal = Math . max ( ... yAxisData ) ;
const maxIndex = yAxisData . indexOf ( maxVal ) ;
chartData . peak = {
name : '峰值 ' ,
value : maxVal ,
month : chartData . xAxis [ maxIndex ] || ''
} ;
}
const option = {
tooltip : { trigger : 'axis' , formatter : '{b}: {c} 次' } ,
grid : { left : '3%' , right : '4%' , bottom : '3%' , containLabel: true } ,
xAxis : {
type : 'category' ,
data : chartData . xAxis ,
axisLabel : { interval : 0 }
} ,
yAxis : {
type : 'value' ,
name : '使用次数' ,
min : 0 ,
max : chartData . yAxis . length ? Math . max ( ... chartData . yAxis ) + 100 : 100
} ,
series : [
{
name : '使用频次' ,
type : 'line' ,
data : chartData . yAxis ,
smooth : false ,
lineS tyl e : { width : 3 , color : '#409eff' } ,
itemStyle : { color : '#409eff' , radius : 5 } ,
areaStyle : {
color : {
type : 'linear' ,
x : 0 , y : 0 , x2 : 0 , y2 : 1 ,
colorStops : [
{ offset : 0 , color : 'rgba(64, 158, 255, 0.3)' } ,
{ offset : 1 , color : 'rgba(64, 158, 255, 0)' }
]
}
} ]
} ,
markPoint : {
data : chartData . peak . value
? [ {
name : chartData . peak . name ,
value : chartData . peak . value ,
xAxis : chartData . xAxis . indexOf ( chartData . peak . month ) ,
yAxis : chartData . peak . value ,
itemStyle : { color : '#409eff' }
} ]
: [ ]
}
}
}
]
} ;
// 渲染图表
frequencyChartInstance . setOption ( option ) ;
]
} ;
frequencyChartInstance . setOption ( option ) ;
} catch ( error ) {
}
} ;
/**
* 2. 报警环形图(今日报警处理占比)
*/
const initAlarmRingChart = ( ) => {
const initAlarmRingChart = async ( ) => {
if ( ! alarmRingChartRef . value ) return ;
alarmRingChartInstance = echarts . init ( alarmRingChartRef . value ) ;
// 假数据: 今日报警6次, 已处理6次
const option = {
tooltip : {
trigger : 'item' ,
formatter : '{b}: {c} 次 ({d}%)'
} ,
series : [
{
type : 'pie' ,
radiu s : [ '50%' , '60%' ] , // 环形半径
center : [ '50%' , '50%' ] , // 居中显示
avoidLabelOverlap : false ,
label : {
show : true ,
position : 'center' ,
formatter : '6/6\n今日报警/处理' , // 中心显示“今日 已处理/总数”
fontSize : 16 ,
fontWeight : 'bold ' ,
color : '#333'
} ,
labelLine : {
show : false
} ,
data : [
{ value : 6 , name : '已处理报警' , itemStyle : { color : '#07BE75' } } ,
{ value : 0 , name : '未处理报警' , itemStyle : { color : '#F65757' } }
]
}
]
} ;
alarmRingChartInstance . setOption ( option ) ;
try {
const res = await api . getAlarmInformation ( { } ) ;
const { alarmsTotalToday = 0 , processingAlarmToday = 0 } = res . data || { } ;
alarmsData . value = res . data || '0'
alarmRingChartInstance = echarts . init ( alarmRingChartRef . value ) ;
const option = {
tooltip : {
trigger : 'item' ,
formatter : '{b}: {c} 次 ({d}%)' // 显示数量和百分比
} ,
serie s: [
{
type : 'pie' ,
radius : [ '50%' , '60%' ] , // 环形半径
center : [ '50%' , '50%' ] , // 居中显示
avoidLabelOverlap : false ,
label : {
show : true ,
position : 'center ' ,
formatter : ` ${ alarmsTotalToday } / ${ processingAlarmToday } \ n今日报警/处理 ` ,
fontSize : 16 ,
fontWeight : 'bold' ,
color : '#333'
} ,
labelLine : {
show : false // 隐藏标签连接线
} ,
data : [
{
value : alarmsTotalToday ,
name : '已处理报警' ,
itemStyle : { color : '#07BE75' } // 绿色:已处理
} ,
{
value : processingAlarmToday ,
name : '未处理报警' ,
itemStyle : { color : '#F65757' } // 红色:未处理
}
]
}
]
} ;
alarmRingChartInstance . setOption ( option ) ;
// 报警柱状图
initAlarmBarChart ( )
} catch ( error ) {
}
} ;
/**
@ -325,14 +345,23 @@ const initAlarmRingChart = () => {
*/
const initAlarmBarChart = ( ) => {
if ( ! alarmBarChartRef . value ) return ;
const alarmTypeMap = [
{ name : '强制报警' , field : 'alarmForced' } , // alarmForced
{ name : '撞击闯入' , field : 'intrusionImpact' } , // intrusionImpact
{ name : '手动报警' , field : 'alarmManual' } , // alarmManual
{ name : '电子围栏' , field : 'fenceElectronic' } // fenceElectronic
] ;
alarmBarChartInstance = echarts . init ( alarmBarChartRef . valu e) ;
// 假数据:各类型报警次数
const alarmTypes = [ '强制报警' , '撞击闯入' , '手动报警' , '电子围栏' ] ;
const alarmCounts = [ 50 , 35 , 65 , 50 ] ; // 对应各类型次数
// ECharts配置项
const alarmTypes = alarmTypeMap . map ( item => item . nam e) ;
const alarmCounts = alarmTypeMap . map ( item => {
const value = alarmsData . value [ item . field ] ; // 提取对应字段值
console . log ( ` ${ item . name } 数值: ` , value ) ; // 打印每个类型的数值
return v alue ;
} ) ;
const commonGradient = new echarts . graphic . LinearGradient ( 0 , 0 , 0 , 1 , [
{ offset : 0 , color : 'rgba(246, 87, 87, 1)' } , // 渐变起点:#F65757( 不透明)
{ offset : 1 , color : 'rgba(224, 52, 52, 0)' } // 渐变终点:#E03434( 全透明)
] ) ;
const option = {
tooltip : {
trigger : 'axis' ,
@ -348,8 +377,7 @@ const initAlarmBarChart = () => {
type : 'category' ,
data : alarmTypes ,
axisLabel : {
interval : 0 ,
//rotate: 15 // 标签旋转,避免重叠
interval : 0 // 强制显示所有标签
}
} ,
yAxis : {
@ -362,28 +390,29 @@ const initAlarmBarChart = () => {
name : '报警次数' ,
type : 'bar' ,
data : alarmCounts ,
barWidth : '4 0%' ,
barWidth : '2 0%' ,
itemStyle : {
color : ( params : any ) => {
// 不同类型报警用不同颜色
const colors = [ '#ff4d4f' , '#e6a23c' , '#409eff' , '#67c23a' ] ;
return colors [ params . dataIndex ] ;
}
color : commonGradient , // 所有柱子共用同一渐变色
borderRadius : 4 // 统一4px圆角
}
}
]
} ;
// 4. 初始化并渲染图表
alarmBarChartInstance = echarts . init ( alarmBarChartRef . value ) ;
alarmBarChartInstance . setOption ( option ) ;
} ;
// ---------------------- 图表更新方法(时间范围切换时) ----------------------
const updateFrequencyChart = ( ) => {
// 先销毁旧实例,再重新初始化
const updateFrequencyChart = ( tabValue : any ) => {
activeTab . value = tabValue ;
if ( frequencyChartInstance ) {
frequencyChartInstance . dispose ( ) ;
}
initFrequencyChart ( ) ;
initFrequencyChart ( tabValue , '' ) ;
} ;
const handleDeviceTypeChange = ( all ) => {
initFrequencyChart ( activeTab . value , all ) ;
} ;
// 首页统计接口
const getData = async ( ) => {
@ -437,9 +466,8 @@ const getData = async () => {
// ---------------------- 生命周期钩子(初始化/销毁图表) ----------------------
onMounted ( ( ) => {
// 页面加载时初始化所有图表
initFrequencyChart ( ) ;
initFrequencyChart ( '1' , '' ) ;
initAlarmRingChart ( ) ;
initAlarmBarChart ( ) ;
getData ( )
// 监听窗口 resize, 自动调整图表大小
@ -461,7 +489,7 @@ onUnmounted(() => {
< style lang = "scss" scoped >
. home {
padding : 20 px ;
padding : 0 px 20 px 10 px 20 px ;
background - color : # f5f7fa ;
min - height : calc ( 100 vh - 84 px ) ;
@ -573,6 +601,34 @@ onUnmounted(() => {
}
}
. tab - group {
display : flex ;
gap : 12 px ;
/* 两个标签的间距 */
align - items : center ;
margin - top : 15 px ;
}
. tab - item {
padding : 3 px 10 px ;
cursor : pointer ;
color : # 666 ;
/* 未选中时的文字颜色 */
transition : all 0.2 s ease ;
font - size : 13 px ;
}
. tab - item -- active {
border - color : # 409 eff ;
/* 选中时的边框颜色(示例用 Element UI 主色) */
color : # 409 eff ;
/* 选中时的文字颜色 */
background - color : rgba ( 64 , 158 , 255 , 0.05 ) ;
/* 可选:选中时的浅背景 */
border : 1 px solid rgba ( 2 , 124 , 251 , 1 ) ;
border - radius : 4 px ;
}
. card - header {
display : flex ;
justify - content : space - between ;