Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
D
dwsproject
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
dws
dwsproject
Commits
a9d775ec
Commit
a9d775ec
authored
Jun 25, 2025
by
nningxx
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
荏原 新增oee 时间利用率报表 个性化需求
parent
f8f15897
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
1336 additions
and
0 deletions
+1336
-0
qdry_project/controllers/controllers.py
+8
-0
qdry_project/static/src/view/oee_time_sequence_table.html
+1328
-0
No files found.
qdry_project/controllers/controllers.py
View file @
a9d775ec
...
...
@@ -30,6 +30,14 @@ class RokeMesThreeColourLight(http.Controller):
data
=
{
"code"
:
1
,
"message"
:
"请求通过"
,
"data"
:
{
"factory_code"
:
factory_code
}}
template
=
env
.
get_template
(
'oee_analysis.html'
)
return
template
.
render
(
data
)
@http.route
(
"/roke/three_color_light/oee_time_sequence_table"
,
type
=
"http"
,
auth
=
'none'
,
cors
=
'*'
,
csrf
=
False
)
def
oee_time_sequence_table
(
self
,
**
kwargs
):
_self
=
http
.
request
factory_code
=
http
.
request
.
env
(
user
=
SUPERUSER_ID
)[
'ir.config_parameter'
]
.
get_param
(
'database.uuid'
,
default
=
""
)
data
=
{
"code"
:
1
,
"message"
:
"请求通过"
,
"data"
:
{
"factory_code"
:
factory_code
}}
template
=
env
.
get_template
(
'oee_time_sequence_table.html'
)
return
template
.
render
(
data
)
@http.route
(
'/roke/workstation/plant/tree'
,
type
=
'json'
,
auth
=
'none'
,
csrf
=
False
,
cors
=
"*"
)
def
get_roke_workstation_plant
(
self
):
...
...
qdry_project/static/src/view/oee_time_sequence_table.html
0 → 100644
View file @
a9d775ec
<!DOCTYPE html>
<html>
<head>
<meta
charset=
"UTF-8"
/>
<title>
OEE时间利用率
</title>
<meta
content=
"width=device-width,initial-scale=1.0, maximum-scale=1.0,user-scalable=0"
name=
"viewport"
/>
<!-- 本地资源引用 -->
<link
rel=
"stylesheet"
href=
"/roke_workstation_api/static/html/routing/element-ui/index.css"
/>
<script
src=
"/roke_workstation_api/static/html/routing/js/echarts.min.js"
></script>
<script
src=
"/roke_workstation_api/static/html/routing/js/moment.min.js"
></script>
<script
src=
"/roke_workstation_api/static/html/routing/js/vue.js"
></script>
<script
src=
"/roke_workstation_api/static/html/routing/js/axios.min.js"
></script>
<script
src=
"/roke_workstation_api/static/html/routing/element-ui/index.js"
></script>
</head>
<body
id=
"bodyId"
style=
"display: none"
>
<div
id=
"app"
>
<!-- v-show="performanceShow" -->
<!-- v-loading.body.fullscreen.lock="idle_rate_loading" -->
<!-- 新增:多设备空闲率图表 -->
<!-- 多设备图表的设备选择器 -->
<div
class=
"chart-select-wrapper"
>
<span
class=
"select-label"
>
设备
</span>
<el-select
v-model=
"selectedMultiDevice"
placeholder=
"请选择设备"
size=
"small"
clearable
@
change=
"multiDeviceChange"
class=
"chart-select"
>
<el-option
v-for=
"(item,index) in deviceList"
:label=
"item.name"
:value=
"item.code"
:key=
"index"
>
</el-option>
</el-select>
</div>
<div
class=
"multi-utilization-chart glass-effect"
>
<div
class=
"section-title"
>
<div
class=
"title-bar"
></div>
<span
class=
"title-text"
>
[[ selectedMultiDevice ? '设备空闲率概览' : '多设备空闲率对比' ]]
</span
>
</div>
<div
id=
"multiUtilizationChart"
ref=
"multiUtilizationChart"
style=
"width: 100%; height: 280px"
></div>
</div>
<!-- 单设备图表的设备选择器 -->
<div
class=
"chart-select-wrapper"
>
<span
class=
"select-label"
>
设备
</span>
<el-select
v-model=
"selectedDevice"
placeholder=
"请选择设备"
size=
"small"
clearable
@
change=
"selDeviceChange"
class=
"chart-select"
>
<el-option
v-for=
"(item,index) in deviceList"
:label=
"item.name"
:value=
"item.code"
:key=
"index"
>
</el-option>
</el-select>
</div>
<div
class=
"utilization-chart glass-effect"
v-if=
"selectedDevice"
>
<div
class=
"section-title"
>
<div
class=
"title-bar"
></div>
<span
class=
"title-text"
>
设备空闲率
</span>
</div>
<div
id=
"utilizationChart"
ref=
"utilizationChart"
style=
"width: 100%; height: 280px"
></div>
</div>
<!-- 底部状态图 -->
<!-- v-loading.body.fullscreen.lock="chart_loading" -->
<!-- 柱状图的设备选择器 -->
<div
class=
"chart-select-wrapper"
>
<span
class=
"select-label"
>
设备
</span>
<el-select
v-model=
"selectedBarChartDevice"
placeholder=
"请选择设备"
size=
"small"
@
change=
"barChartDeviceChange"
class=
"chart-select"
>
<el-option
v-for=
"(item,index) in deviceList"
:label=
"item.name"
:value=
"item.code"
:key=
"index"
>
</el-option>
</el-select>
</div>
<div
class=
"status-chart glass-effect"
>
<div
class=
"section-title"
>
<div
class=
"title-bar"
></div>
<span
class=
"title-text"
>
日均运行时间统计
</span>
</div>
<div
class=
"chart-container"
>
<!-- 图例区域 -->
<div
class=
"flex_legend"
>
<div
class=
"single_sty"
>
<div
style=
"background-color: red"
></div>
<span>
故障
</span>
</div>
<div
class=
"single_sty"
>
<div
style=
"background-color: yellow"
></div>
<span>
等待
</span>
</div>
<div
class=
"single_sty"
>
<div
style=
"background-color: green"
></div>
<span>
运行
</span>
</div>
<div
class=
"single_sty"
>
<div
style=
"background-color: gray"
></div>
<span>
关机
</span>
</div>
</div>
<!-- Y轴刻度 -->
<div
class=
"y-axis"
>
<span
v-for=
"(value, index) in yAxisValues"
:key=
"index"
class=
"y-axis-label"
>
[[value]]
<span
class=
"unit"
>
%
</span>
</span>
</div>
<!-- 图表主体区域 -->
<div
class=
"chart-content"
>
<div
class=
"horizontal-lines"
>
<div
v-for=
"(value, index) in yAxisValues"
:key=
"index"
class=
"horizontal-line"
></div>
</div>
<!-- 改为单个容器包含柱状图和X轴标签 -->
<div
class=
"columns-container"
>
<div
class=
"column-with-label"
v-for=
"(item, dayIndex) in pickingOrderList"
:key=
"dayIndex"
>
<!-- 柱状图部分 -->
<div
class=
"column-group"
>
<div
class=
"column-stack"
>
<div
class=
"column-segment"
v-for=
"(segment, stackIndex) in item"
:key=
"stackIndex"
:style=
"{
'height': calculateHeight(segment.duration_percentage) + '%',
'background-color': segment.state,
'margin-top': '0px'
}"
>
<el-tooltip
effect=
"dark"
:content=
"calculateHeight(segment.duration_percentage) + '%'"
placement=
"top"
>
<div
style=
"height: 100%; width: 100%"
></div>
</el-tooltip>
</div>
</div>
</div>
<!-- X轴标签部分 - 直接绑定在柱状图下方 -->
<div
class=
"x-axis-label"
>
[[ latestDateList[dayIndex] ]]
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
<script>
// 发送消息给父页面(关闭odoo的菜单弹窗)
document
.
addEventListener
(
"click"
,
()
=>
{
window
.
parent
.
postMessage
(
"hidePopover"
,
"*"
);
});
let
vue
=
new
Vue
({
el
:
"#app"
,
delimiters
:
[
"[["
,
"]]"
],
// 替换原本vue的[[ key ]]取值方式(与odoo使用的jinja2冲突问题)
data
()
{
return
{
windowHeight
:
window
.
innerHeight
,
// 窗口高度
// baseURL: "https://workstation.rokeris.com", // 基地址 https://workstation.rokeris.com
// baseURL: "http://qdry.dws.rokecloud.com",
baseURL
:
""
,
//
localURL
:
"https://dws-platform.xbg.rokeris.com/dev-api"
,
// 基地址 https://workstation.rokeris.com
idle_rate_loading
:
false
,
// 加载效果
chart_loading
:
false
,
deviceList
:
[],
// 设备列表
selectedDevice
:
null
,
// 选中的设备
yAxisValues
:
[
"100"
,
"80"
,
"60"
,
"40"
,
"20"
,
"0"
],
// Y轴刻度值
pickingOrderList
:
[],
// 拣选单列表
dateList
:
[],
// 日期列表
start_time
:
""
,
// 开始时间
end_time
:
""
,
// 结束时间
utilizationChart
:
null
,
// 设备空闲率图表实例变量
multiUtilizationChart
:
null
,
// 新增:多设备空闲率图表实例变量
plan_time_list
:
null
,
latestDateList
:
[],
factory_code
:
"8d8dec6e-0d44-11f0-9692-00163e04c506"
,
selectedMultiDevice
:
null
,
// 新增:多设备图表选中的设备
selectedBarChartDevice
:
null
,
// 新增:柱状图选中的设备
};
},
computed
:
{
// 选中设备的信息
selDeviceInfo
()
{
return
this
.
deviceList
.
find
((
item
)
=>
item
.
id
==
this
.
selectedDevice
);
},
},
created
()
{
if
(
this
.
getUrlSearch
(
"factory_code"
))
{
this
.
factory_code
=
this
.
getUrlSearch
(
"factory_code"
);
//截取url后面的参数
}
this
.
get_device_list
();
// 先设置好日期范围,以便后续使用
this
.
latestDateList
=
this
.
getLastTenDays
();
this
.
$nextTick
(()
=>
{
document
.
getElementById
(
"bodyId"
).
style
.
display
=
"block"
;
// 在DOM渲染后初始化图表
if
(
this
.
$refs
.
utilizationChart
)
{
this
.
initUtilizationChart
();
}
// 初始化多设备空闲率图表
if
(
this
.
$refs
.
multiUtilizationChart
)
{
this
.
initMultiUtilizationChart
();
}
});
window
.
addEventListener
(
"resize"
,
this
.
handleResize
);
// 获取最后指定天数的日期
this
.
dateList
=
this
.
getLastAssignDays
();
},
methods
:
{
// 获取所有已绑定设备数据
async
getAllEquipmentData
(
data_list
)
{
try
{
// 发送请求获取所有已绑定设备数据
const
response
=
await
axios
({
method
:
"post"
,
url
:
this
.
baseURL
+
"/roke/workstation/equipment/get_equipment_data"
,
data
:
{},
headers
:
{
"Content-Type"
:
"application/json"
,
},
});
// 处理JSON-RPC格式的响应
if
(
response
.
data
&&
response
.
data
.
result
&&
response
.
data
.
result
.
code
===
0
)
{
// 存储所有设备数据
data_list
.
forEach
((
data_item
)
=>
{
response
.
data
.
result
.
data
.
forEach
((
item
)
=>
{
if
(
data_item
.
device_code
==
item
.
code
)
{
this
.
deviceList
.
push
(
item
);
}
});
});
// 为柱状图设置默认设备
this
.
selectedBarChartDevice
=
this
.
deviceList
[
0
].
code
;
this
.
get_plan_time
();
this
.
get_daily_running_time
();
console
.
log
(
"获取到所有已绑定设备数据:"
,
this
.
deviceList
.
length
,
"条"
);
}
else
{
const
errorMsg
=
response
.
data
.
result
?
response
.
data
.
result
.
message
:
"获取所有已绑定设备数据失败"
;
throw
new
Error
(
errorMsg
);
}
}
catch
(
error
)
{
console
.
error
(
"获取所有已绑定设备数据出错:"
,
error
);
throw
error
;
}
},
// 获取日均运行时间统计
get_daily_running_time
()
{
this
.
idle_rate_loading
=
true
;
axios
({
method
:
"post"
,
url
:
this
.
localURL
+
"/public/device_efficiency/daily_running_time"
,
data
:
{
device_code
:
this
.
selectedBarChartDevice
,
},
headers
:
{
"Content-Type"
:
"application/json"
},
})
.
then
((
res
)
=>
{
if
(
res
.
data
.
code
==
200
)
{
this
.
pickingOrderList
=
res
.
data
.
data
.
daily_running_list
;
// 调用一致性检查
this
.
ensureAlignmentData
();
}
else
{
this
.
$message
.
error
(
"获取日均运行时间失败"
);
}
this
.
idle_rate_loading
=
false
;
})
.
catch
((
err
)
=>
{
this
.
idle_rate_loading
=
false
;
this
.
$message
.
error
(
"获取日均运行时间接口捕获到错误"
);
});
},
// 获取设备列表
get_device_list
()
{
axios
({
method
:
"get"
,
url
:
this
.
localURL
+
"/public/device_efficiency/device_list/"
+
this
.
factory_code
,
data
:
{},
headers
:
{
"Content-Type"
:
"application/json"
},
})
.
then
((
res
)
=>
{
if
(
res
.
data
.
code
==
200
)
{
// this.deviceList = res.data.data
this
.
getAllEquipmentData
(
res
.
data
.
data
);
}
else
{
this
.
$message
.
error
(
"设备列表数据获取失败!"
);
}
})
.
catch
((
err
)
=>
{});
},
// 获取设备编号 设备计划运行时间 plan_time_list 参数
get_plan_time
()
{
axios
({
method
:
"post"
,
url
:
this
.
baseURL
+
"/roke/workstation/equipment/get_plan_time"
,
data
:
{},
headers
:
{
"Content-Type"
:
"application/json"
},
})
.
then
((
res
)
=>
{
if
(
res
.
data
.
result
.
code
==
0
)
{
this
.
plan_time_list
=
res
.
data
.
result
.
data
;
// 只有在选择了设备时才调用单设备空闲率接口
if
(
this
.
selectedDevice
)
{
this
.
get_series_utilization_rate
();
}
// 如果多设备图表已初始化且没有选择设备,则调用多设备接口
if
(
this
.
multiUtilizationChart
&&
!
this
.
selectedMultiDevice
)
{
this
.
get_series_utilization_rate_top_5
();
}
}
})
.
catch
((
err
)
=>
{});
},
// 获取OEE报表:近十天设备空闲率
get_series_utilization_rate
()
{
this
.
chart_loading
=
true
;
axios
({
method
:
"post"
,
url
:
this
.
localURL
+
"/public/device_efficiency/series_utilization_rate"
,
data
:
{
device_code
:
this
.
selectedDevice
,
plan_time_list
:
this
.
plan_time_list
,
},
headers
:
{
"Content-Type"
:
"application/json"
},
})
.
then
((
res
)
=>
{
if
(
res
.
data
.
code
==
200
)
{
this
.
initUtilizationChart
(
res
.
data
.
data
);
}
else
{
this
.
$message
.
error
(
"设备空闲率获取失败!"
);
}
this
.
chart_loading
=
false
;
})
.
catch
((
err
)
=>
{
this
.
chart_loading
=
false
;
this
.
$message
.
error
(
"设备空闲率捕获到错误!"
);
});
},
// 选择设备改变事件
selDeviceChange
(
el
)
{
// 只有在选择了设备时才调用接口
if
(
this
.
selectedDevice
)
{
this
.
get_series_utilization_rate
();
}
},
// 处理窗口大小变化修改图表大小
handleResize
()
{
if
(
this
.
utilizationChart
)
this
.
utilizationChart
.
resize
();
if
(
this
.
multiUtilizationChart
)
this
.
multiUtilizationChart
.
resize
();
},
// 初始化设备空闲率图表
initUtilizationChart
(
data
=
[])
{
// 延迟初始化,确保DOM已完全渲染
setTimeout
(()
=>
{
// 确保元素存在
const
chartDom
=
document
.
getElementById
(
"utilizationChart"
);
if
(
!
chartDom
)
{
console
.
error
(
"找不到utilizationChart元素"
);
return
;
}
// 如果已有实例,先销毁
if
(
this
.
utilizationChart
)
{
this
.
utilizationChart
.
dispose
();
}
// 初始化图表
this
.
utilizationChart
=
echarts
.
init
(
chartDom
);
const
option
=
{
grid
:
{
left
:
"3%"
,
right
:
"4%"
,
bottom
:
"3%"
,
top
:
"8%"
,
containLabel
:
true
,
},
tooltip
:
{
trigger
:
"axis"
,
formatter
:
"{b} : {c}%"
,
},
xAxis
:
{
type
:
"category"
,
data
:
this
.
latestDateList
,
axisLine
:
{
lineStyle
:
{
color
:
"#fff"
,
},
},
axisLabel
:
{
color
:
"#fff"
,
rotate
:
this
.
latestDateList
[
0
].
length
>
5
?
30
:
0
,
// 如果日期文本较长则旋转标签
},
},
yAxis
:
{
type
:
"value"
,
min
:
0
,
max
:
100
,
interval
:
20
,
axisLine
:
{
show
:
false
,
},
axisTick
:
{
show
:
false
,
},
splitLine
:
{
lineStyle
:
{
color
:
"rgba(255, 255, 255, 0.1)"
,
},
},
axisLabel
:
{
color
:
"#fff"
,
formatter
:
"{value}%"
,
},
},
series
:
[
{
name
:
"设备空闲率"
,
type
:
"line"
,
smooth
:
true
,
symbol
:
"circle"
,
symbolSize
:
8
,
label
:
{
show
:
true
,
position
:
"top"
,
color
:
"#FFFFFF"
,
fontSize
:
12
,
formatter
:
"{c}%"
,
},
lineStyle
:
{
color
:
"#36cfc9"
,
width
:
3
,
},
itemStyle
:
{
color
:
"#36cfc9"
,
},
data
:
data
.
map
((
item
)
=>
(
item
>
100
?
100
:
item
)),
},
],
};
this
.
utilizationChart
.
setOption
(
option
);
// 手动触发resize以确保正确渲染
this
.
utilizationChart
.
resize
();
},
300
);
// 延迟300ms初始化
},
// 计算高度百分比
calculateHeight
(
hours
)
{
// 直接使用百分比值
// yAxisValues = ["100", "80", "60", "40", "20", "0"],每个刻度区间是20%
// 确保高度在0-100%之间
return
Math
.
min
(
Math
.
max
(
hours
,
0
),
100
);
},
// 获取最后指定天数的日期
getLastAssignDays
(
num
=
10
)
{
const
dates
=
[];
for
(
let
i
=
num
-
1
;
i
>=
0
;
i
--
)
{
const
date
=
new
Date
();
date
.
setDate
(
date
.
getDate
()
-
i
);
const
year
=
date
.
getFullYear
();
const
month
=
String
(
date
.
getMonth
()
+
1
).
padStart
(
2
,
"0"
);
const
day
=
String
(
date
.
getDate
()).
padStart
(
2
,
"0"
);
dates
.
push
(
`
${
year
}
-
${
month
}
-
${
day
}
`
);
}
this
.
start_time
=
dates
[
0
];
// 第一天
this
.
end_time
=
dates
[
dates
.
length
-
1
];
// 最后一天
return
dates
;
},
// 改进日期格式化方法
getLastTenDays
()
{
const
dates
=
[];
const
formattedDates
=
[];
const
endDate
=
new
Date
();
this
.
end_time
=
moment
(
endDate
).
format
(
"YYYY-MM-DD"
);
const
startDate
=
new
Date
();
startDate
.
setDate
(
startDate
.
getDate
()
-
9
);
this
.
start_time
=
moment
(
startDate
).
format
(
"YYYY-MM-DD"
);
for
(
let
i
=
0
;
i
<
10
;
i
++
)
{
const
date
=
new
Date
(
startDate
);
date
.
setDate
(
startDate
.
getDate
()
+
i
);
dates
.
push
(
date
);
// 修改为MM-DD格式
const
month
=
String
(
date
.
getMonth
()
+
1
).
padStart
(
2
,
"0"
);
const
day
=
String
(
date
.
getDate
()).
padStart
(
2
,
"0"
);
formattedDates
.
push
(
`
${
month
}
-
${
day
}
`
);
}
this
.
dateList
=
dates
;
return
formattedDates
;
},
// 在methods中添加
ensureAlignmentData
()
{
// 确保X轴标签数量与柱状图数量一致
this
.
$nextTick
(()
=>
{
if
(
this
.
pickingOrderList
.
length
!==
this
.
latestDateList
.
length
)
{
console
.
warn
(
"柱状图数量与X轴标签数量不一致"
,
"柱状图:"
,
this
.
pickingOrderList
.
length
,
"X轴标签:"
,
this
.
latestDateList
.
length
);
}
});
},
// 通过网址跳转过来的页面,截取后面的参数
getUrlSearch
(
name
)
{
// 未传参,返回空
if
(
!
name
)
return
""
;
// 查询参数:先通过search取值,如果取不到就通过hash来取
var
after
=
window
.
location
.
search
;
after
=
after
.
substr
(
1
)
||
window
.
location
.
hash
.
split
(
"?"
)[
1
];
// 地址栏URL没有查询参数,返回空
if
(
!
after
)
return
null
;
// 如果查询参数中没有"name",返回空
if
(
after
.
indexOf
(
name
)
===
-
1
)
return
null
;
var
reg
=
new
RegExp
(
"(^|&)"
+
name
+
"=([^&]*)(&|$)"
);
// 当地址栏参数存在中文时,需要解码,不然会乱码
var
r
=
decodeURI
(
after
).
match
(
reg
);
// 如果url中"name"没有值,返回空
if
(
!
r
)
return
null
;
return
r
[
2
];
},
// 新增:初始化多设备空闲率图表
initMultiUtilizationChart
()
{
setTimeout
(()
=>
{
const
chartDom
=
document
.
getElementById
(
"multiUtilizationChart"
);
if
(
!
chartDom
)
{
console
.
error
(
"找不到multiUtilizationChart元素"
);
return
;
}
if
(
this
.
multiUtilizationChart
)
{
this
.
multiUtilizationChart
.
dispose
();
}
this
.
multiUtilizationChart
=
echarts
.
init
(
chartDom
);
// 根据selectedMultiDevice来决定显示内容
if
(
!
this
.
selectedMultiDevice
)
{
this
.
showMultiDeviceChart
();
}
else
{
this
.
showSingleDeviceChart
();
}
},
300
);
},
// 新增:显示多设备图表
showMultiDeviceChart
()
{
// 调用真实的多设备接口
this
.
get_series_utilization_rate_top_5
();
},
// 新增:显示单设备图表
showSingleDeviceChart
()
{
// 这里复用现有的空闲率接口逻辑
this
.
get_series_utilization_rate_for_multi
();
},
// 新增:为多设备图表获取单设备空闲率数据
get_series_utilization_rate_for_multi
()
{
axios
({
method
:
"post"
,
url
:
this
.
localURL
+
"/public/device_efficiency/series_utilization_rate"
,
data
:
{
device_code
:
this
.
selectedMultiDevice
,
plan_time_list
:
this
.
plan_time_list
,
},
headers
:
{
"Content-Type"
:
"application/json"
},
})
.
then
((
res
)
=>
{
if
(
res
.
data
.
code
==
200
)
{
this
.
updateMultiChartWithSingleDevice
(
res
.
data
.
data
);
}
else
{
this
.
$message
.
error
(
"多设备图表-设备空闲率获取失败!"
);
}
})
.
catch
((
err
)
=>
{
this
.
$message
.
error
(
"多设备图表-设备空闲率捕获到错误!"
);
});
},
// 新增:用单设备数据更新多设备图表
updateMultiChartWithSingleDevice
(
data
)
{
const
selectedDeviceInfo
=
this
.
deviceList
.
find
(
(
device
)
=>
device
.
code
===
this
.
selectedMultiDevice
);
const
deviceName
=
selectedDeviceInfo
?
selectedDeviceInfo
.
name
:
"当前设备"
;
const
option
=
{
grid
:
{
left
:
"3%"
,
right
:
"4%"
,
bottom
:
"3%"
,
top
:
"8%"
,
containLabel
:
true
,
},
tooltip
:
{
trigger
:
"axis"
,
formatter
:
"{b} : {c}%"
,
},
legend
:
{
show
:
false
,
},
xAxis
:
{
type
:
"category"
,
data
:
this
.
latestDateList
,
axisLine
:
{
lineStyle
:
{
color
:
"#fff"
,
},
},
axisLabel
:
{
color
:
"#fff"
,
rotate
:
this
.
latestDateList
[
0
].
length
>
5
?
30
:
0
,
},
},
yAxis
:
{
type
:
"value"
,
min
:
0
,
max
:
100
,
interval
:
20
,
axisLine
:
{
show
:
false
,
},
axisTick
:
{
show
:
false
,
},
splitLine
:
{
lineStyle
:
{
color
:
"rgba(255, 255, 255, 0.1)"
,
},
},
axisLabel
:
{
color
:
"#fff"
,
formatter
:
"{value}%"
,
},
},
series
:
[
{
name
:
deviceName
,
type
:
"line"
,
smooth
:
true
,
symbol
:
"circle"
,
symbolSize
:
8
,
label
:
{
show
:
true
,
position
:
"top"
,
color
:
"#FFFFFF"
,
fontSize
:
12
,
formatter
:
"{c}%"
,
},
lineStyle
:
{
color
:
"#36cfc9"
,
width
:
3
,
},
itemStyle
:
{
color
:
"#36cfc9"
,
},
data
:
data
.
map
((
item
)
=>
(
item
>
100
?
100
:
item
)),
},
],
};
// 使用 setOption 的第二个参数 true 来完全替换配置,清除之前的多设备折线
this
.
multiUtilizationChart
.
setOption
(
option
,
true
);
this
.
multiUtilizationChart
.
resize
();
},
// 新增:更新多设备图表
updateMultiUtilizationChart
()
{
if
(
!
this
.
multiUtilizationChart
)
return
;
if
(
this
.
selectedMultiDevice
)
{
this
.
showSingleDeviceChart
();
}
else
{
this
.
showMultiDeviceChart
();
}
},
// 新增:多设备图表设备选择变化事件
multiDeviceChange
(
el
)
{
this
.
updateMultiUtilizationChart
();
},
// 新增:柱状图设备选择变化事件
barChartDeviceChange
(
el
)
{
this
.
get_daily_running_time
();
},
// 新增:获取多设备空闲率数据(Top 5)
get_series_utilization_rate_top_5
()
{
// 构造设备列表参数
const
device_code_list
=
this
.
deviceList
.
map
((
device
)
=>
({
device_name
:
device
.
name
,
device_code
:
device
.
code
,
}));
axios
({
method
:
"post"
,
url
:
this
.
localURL
+
"/public/device_efficiency/series_utilization_rate_top_5"
,
data
:
{
device_code_list
:
device_code_list
,
plan_time_list
:
this
.
plan_time_list
||
{},
},
headers
:
{
"Content-Type"
:
"application/json"
},
})
.
then
((
res
)
=>
{
if
(
res
.
data
.
code
==
200
)
{
this
.
updateMultiChartWithTopDevices
(
res
.
data
.
data
);
}
else
{
this
.
$message
.
error
(
"多设备空闲率获取失败!"
);
}
})
.
catch
((
err
)
=>
{
this
.
$message
.
error
(
"多设备空闲率接口捕获到错误!"
);
console
.
error
(
"多设备空闲率接口错误:"
,
err
);
});
},
// 新增:用多设备数据更新图表
updateMultiChartWithTopDevices
(
data
)
{
if
(
!
data
.
device_data
||
!
Array
.
isArray
(
data
.
device_data
))
{
console
.
error
(
"多设备数据格式错误"
);
return
;
}
const
series
=
data
.
device_data
.
map
((
device
)
=>
({
name
:
device
.
device_name
,
type
:
"line"
,
smooth
:
true
,
symbol
:
"circle"
,
symbolSize
:
6
,
lineStyle
:
{
width
:
2
,
},
data
:
device
.
utilization_rates
.
map
((
item
)
=>
(
item
>
100
?
100
:
item
)),
}));
const
option
=
{
grid
:
{
left
:
"3%"
,
right
:
"4%"
,
bottom
:
"3%"
,
top
:
"15%"
,
containLabel
:
true
,
},
tooltip
:
{
trigger
:
"axis"
,
formatter
:
function
(
params
)
{
let
result
=
params
[
0
].
axisValue
+
"<br/>"
;
params
.
forEach
((
param
)
=>
{
result
+=
param
.
marker
+
param
.
seriesName
+
": "
+
param
.
value
+
"%<br/>"
;
});
return
result
;
},
},
legend
:
{
top
:
"5%"
,
textStyle
:
{
color
:
"#fff"
,
},
},
xAxis
:
{
type
:
"category"
,
data
:
this
.
latestDateList
,
axisLine
:
{
lineStyle
:
{
color
:
"#fff"
,
},
},
axisLabel
:
{
color
:
"#fff"
,
rotate
:
this
.
latestDateList
[
0
].
length
>
5
?
30
:
0
,
},
},
yAxis
:
{
type
:
"value"
,
min
:
0
,
max
:
100
,
interval
:
20
,
axisLine
:
{
show
:
false
,
},
axisTick
:
{
show
:
false
,
},
splitLine
:
{
lineStyle
:
{
color
:
"rgba(255, 255, 255, 0.1)"
,
},
},
axisLabel
:
{
color
:
"#fff"
,
formatter
:
"{value}%"
,
},
},
series
:
series
,
color
:
[
"#36cfc9"
,
"#1890ff"
,
"#722ed1"
,
"#eb2f96"
,
"#fa8c16"
,
"#52c41a"
,
"#faad14"
],
};
// 使用 setOption 的第二个参数 true 来完全替换配置,确保正确切换
this
.
multiUtilizationChart
.
setOption
(
option
,
true
);
this
.
multiUtilizationChart
.
resize
();
},
},
beforeDestroy
()
{
// 页面销毁移除resize事件监听
window
.
removeEventListener
(
"resize"
,
this
.
handleResize
);
// 清理图表实例
if
(
this
.
utilizationChart
)
{
this
.
utilizationChart
.
dispose
();
}
if
(
this
.
multiUtilizationChart
)
{
this
.
multiUtilizationChart
.
dispose
();
}
},
});
</script>
<style>
/* 基础样式 */
*
{
margin
:
0
;
padding
:
0
;
box-sizing
:
border-box
;
}
body
{
background-color
:
#0c1217
;
background-size
:
cover
;
color
:
#fff
;
overflow
:
hidden
;
}
#app
{
/* width: 100vw; */
height
:
100vh
;
padding
:
20px
;
display
:
flex
;
flex-direction
:
column
;
gap
:
10px
;
overflow-y
:
auto
;
}
/* 玻璃态效果 */
.glass-effect
{
background
:
rgba
(
6
,
82
,
158
,
0.65
);
backdrop-filter
:
blur
(
10px
);
border
:
1px
solid
rgba
(
255
,
255
,
255
,
0.1
);
border-radius
:
15px
;
box-shadow
:
0
8px
32px
0
rgba
(
0
,
0
,
0
,
0.37
);
}
.section-title
{
position
:
relative
;
margin-bottom
:
20px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
flex-start
;
padding-left
:
20px
;
}
.title-text
{
font-size
:
16px
;
font-weight
:
500
;
color
:
#fff
;
}
.title-bar
{
width
:
4px
;
height
:
18px
;
background-color
:
#1890ff
;
margin-right
:
8px
;
border-radius
:
2px
;
}
.el-progress__text
{
white-space
:
pre-wrap
;
line-height
:
1.5
;
}
/* 底部状态图样式 */
.status-chart
{
padding
:
20px
;
height
:
auto
;
min-height
:
500px
;
position
:
relative
;
margin-bottom
:
10px
;
}
.chart-header
{
display
:
flex
;
align-items
:
center
;
gap
:
20px
;
margin
:
10px
0
;
}
.chart-container
{
width
:
100%
;
margin
:
0
auto
;
position
:
relative
;
padding
:
10px
40px
;
height
:
360px
;
}
.columns-container
{
position
:
absolute
;
top
:
0
;
left
:
60px
;
width
:
calc
(
100%
-
70px
);
height
:
320px
;
display
:
flex
;
justify-content
:
space-between
;
padding
:
0
10px
;
}
.column-group
{
width
:
30px
;
height
:
280px
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
}
.column-stack
{
width
:
100%
;
height
:
100%
;
display
:
flex
;
flex-direction
:
column-reverse
;
background-color
:
transparent
;
}
/* Y轴样式 */
.y-axis
{
position
:
absolute
;
height
:
280px
;
width
:
50px
;
z-index
:
2
;
}
.y-axis-label
{
position
:
absolute
;
left
:
10px
;
font-size
:
14px
;
font-weight
:
normal
;
text-align
:
right
;
transform
:
translateY
(
-50%
);
white-space
:
nowrap
;
}
/* 水平网格线调整 */
.horizontal-lines
{
position
:
absolute
;
width
:
100%
;
height
:
280px
;
top
:
0
;
left
:
0
;
}
.horizontal-line
{
position
:
absolute
;
width
:
100%
;
height
:
1px
;
background-color
:
rgba
(
255
,
255
,
255
,
0.1
);
left
:
0
;
}
/* 精确调整Y轴刻度和水平线位置 */
.y-axis-label
:nth-child
(
1
),
.horizontal-line
:nth-child
(
1
)
{
top
:
0
;
}
/* 100% */
.y-axis-label
:nth-child
(
2
),
.horizontal-line
:nth-child
(
2
)
{
top
:
56px
;
}
/* 80% */
.y-axis-label
:nth-child
(
3
),
.horizontal-line
:nth-child
(
3
)
{
top
:
112px
;
}
/* 60% */
.y-axis-label
:nth-child
(
4
),
.horizontal-line
:nth-child
(
4
)
{
top
:
168px
;
}
/* 40% */
.y-axis-label
:nth-child
(
5
),
.horizontal-line
:nth-child
(
5
)
{
top
:
224px
;
}
/* 20% */
.y-axis-label
:nth-child
(
6
),
.horizontal-line
:nth-child
(
6
)
{
top
:
280px
;
}
/* 0% */
/* X轴样式调整 */
.x-axis
{
display
:
none
;
}
.grid-area
{
width
:
100%
;
height
:
280px
;
position
:
relative
;
overflow
:
visible
;
}
/* 图例样式优化 */
.flex_legend
{
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
gap
:
20px
;
margin-bottom
:
15px
;
}
.single_sty
{
display
:
flex
;
align-items
:
center
;
}
.single_sty
div
{
width
:
20px
;
height
:
10px
;
margin-right
:
8px
;
border-radius
:
2px
;
}
.single_sty
span
{
color
:
#fff
;
font-size
:
14px
;
}
.chart-content
{
position
:
relative
;
width
:
calc
(
100%
-
60px
);
height
:
280px
;
margin-top
:
10px
;
margin-left
:
60px
;
}
.unit
{
font-size
:
12px
;
margin-left
:
4px
;
color
:
rgba
(
255
,
255
,
255
,
0.7
);
}
/* 动画效果 */
@keyframes
fadeIn
{
from
{
opacity
:
0
;
transform
:
translateY
(
20px
);
}
to
{
opacity
:
1
;
transform
:
translateY
(
0
);
}
}
.status-chart
{
animation
:
fadeIn
0.8s
ease-out
;
}
/* Element UI 样式覆盖 */
.el-select
{
width
:
180px
;
}
.chart-header
h4
{
font-size
:
14px
;
color
:
#fff
;
margin
:
0
;
}
/* Element UI 深色主题样式覆盖 */
.el-select
{
width
:
180px
;
}
/* 输入框样式 */
.el-select
.el-input__inner
{
background
:
rgba
(
255
,
255
,
255
,
0.1
);
border
:
1px
solid
rgba
(
255
,
255
,
255
,
0.2
);
color
:
#fff
;
}
/* 下拉图标颜色 */
.el-select
.el-input__icon
{
color
:
#fff
;
}
/* 下拉面板样式 */
.el-select-dropdown
{
background
:
rgba
(
31
,
45
,
61
,
0.95
);
border
:
1px
solid
rgba
(
255
,
255
,
255
,
0.1
);
backdrop-filter
:
blur
(
10px
);
}
/* 下拉选项样式 */
.el-select-dropdown__item
{
color
:
#fff
;
}
/* 下拉选项悬停样式 */
.el-select-dropdown__item.hover
,
.el-select-dropdown__item
:hover
{
background-color
:
rgba
(
64
,
158
,
255
,
0.2
);
}
/* 选中项样式 */
.el-select-dropdown__item.selected
{
color
:
#409eff
;
background-color
:
rgba
(
64
,
158
,
255
,
0.1
);
}
/* 禁用项样式 */
.el-select-dropdown__item.is-disabled
{
color
:
rgba
(
255
,
255
,
255
,
0.3
);
}
/* 聚焦时的边框颜色 */
.el-select
.el-input.is-focus
.el-input__inner
{
border-color
:
#409eff
;
}
/* 选择框占位符颜色 */
.el-select
.el-input__inner
::placeholder
{
color
:
rgba
(
255
,
255
,
255
,
0.5
);
}
/* 在样式部分添加以下代码 */
.utilization-chart
{
width
:
100%
!important
;
padding
:
15px
20px
;
margin-bottom
:
15px
;
}
.utilization-chart
.section-title
{
display
:
flex
;
align-items
:
center
;
margin-bottom
:
15px
;
}
.utilization-chart
.title-bar
{
width
:
4px
;
height
:
18px
;
background-color
:
#1890ff
;
margin-right
:
8px
;
border-radius
:
2px
;
}
.utilization-chart
.title-text
{
font-size
:
16px
;
font-weight
:
500
;
color
:
#fff
;
}
/* 新增单元结构样式 */
.column-with-label
{
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
width
:
40px
;
/* 稍微宽一点,给标签留足空间 */
}
/* 调整柱状图容器样式 */
.columns-container
{
position
:
absolute
;
top
:
0
;
left
:
60px
;
width
:
calc
(
100%
-
70px
);
height
:
320px
;
/* 增加高度,包含X轴标签 */
display
:
flex
;
justify-content
:
space-between
;
padding
:
0
10px
;
}
/* 柱状图组样式 */
.column-group
{
width
:
40px
;
height
:
280px
;
/* 固定高度为图表区域高度 */
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
}
/* X轴标签样式 */
.x-axis-label
{
margin-top
:
10px
;
font-size
:
14px
;
text-align
:
center
;
width
:
100%
;
}
/* 移除原有X轴容器样式 */
.x-axis
{
display
:
none
;
}
/* 新增:多设备空闲率图表样式 */
.multi-utilization-chart
{
width
:
100%
!important
;
padding
:
15px
20px
;
margin-bottom
:
15px
;
}
.multi-utilization-chart
.section-title
{
display
:
flex
;
align-items
:
center
;
margin-bottom
:
15px
;
}
.multi-utilization-chart
.title-bar
{
width
:
4px
;
height
:
18px
;
background-color
:
#1890ff
;
margin-right
:
8px
;
border-radius
:
2px
;
}
.multi-utilization-chart
.title-text
{
font-size
:
16px
;
font-weight
:
500
;
color
:
#fff
;
}
.utilization-chart
.section-title
{
display
:
flex
;
align-items
:
center
;
margin-bottom
:
15px
;
}
/* 新增:图表选择器容器样式 */
.chart-select-container
{
margin-left
:
auto
;
margin-right
:
20px
;
display
:
flex
;
align-items
:
center
;
gap
:
8px
;
}
/* 新增:卡片外部选择器包装器样式 */
.chart-select-wrapper
{
display
:
flex
;
align-items
:
center
;
gap
:
8px
;
margin-bottom
:
8px
;
padding-left
:
4px
;
}
.select-label
{
color
:
#fff
;
font-size
:
14px
;
white-space
:
nowrap
;
}
.chart-select
{
width
:
250px
;
}
/* 针对图表内选择器的Element UI样式覆盖 */
.chart-select
.el-input__inner
{
background
:
rgba
(
255
,
255
,
255
,
0.1
);
border
:
1px
solid
rgba
(
255
,
255
,
255
,
0.2
);
color
:
#fff
;
font-size
:
12px
;
height
:
32px
;
}
.chart-select
.el-input__icon
{
color
:
#fff
;
}
.chart-select
.el-input__inner
::placeholder
{
color
:
rgba
(
255
,
255
,
255
,
0.5
);
}
.chart-select
.el-input.is-focus
.el-input__inner
{
border-color
:
#409eff
;
}
</style>
</html>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment