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
a7dce4c0
Commit
a7dce4c0
authored
Jun 25, 2025
by
柏宁宁
Browse files
Options
Browse Files
Download
Plain Diff
合并分支 'renyuan-oee' 到 'master'
荏原 新增oee 时间利用率报表 个性化需求 查看合并请求
!19
parents
f8f15897
a9d775ec
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 @
a7dce4c0
...
@@ -30,6 +30,14 @@ class RokeMesThreeColourLight(http.Controller):
...
@@ -30,6 +30,14 @@ class RokeMesThreeColourLight(http.Controller):
data
=
{
"code"
:
1
,
"message"
:
"请求通过"
,
"data"
:
{
"factory_code"
:
factory_code
}}
data
=
{
"code"
:
1
,
"message"
:
"请求通过"
,
"data"
:
{
"factory_code"
:
factory_code
}}
template
=
env
.
get_template
(
'oee_analysis.html'
)
template
=
env
.
get_template
(
'oee_analysis.html'
)
return
template
.
render
(
data
)
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
=
"*"
)
@http.route
(
'/roke/workstation/plant/tree'
,
type
=
'json'
,
auth
=
'none'
,
csrf
=
False
,
cors
=
"*"
)
def
get_roke_workstation_plant
(
self
):
def
get_roke_workstation_plant
(
self
):
...
...
qdry_project/static/src/view/oee_time_sequence_table.html
0 → 100644
View file @
a7dce4c0
<!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