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
1
Merge Requests
1
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
abdbdfb9
Commit
abdbdfb9
authored
Jul 28, 2025
by
wangkangjie
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' of
https://git.rokedata.com/dws/dwsproject
parents
05b3dd4b
8814cb68
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
1152 additions
and
1148 deletions
+1152
-1148
qdry_project/static/src/view/oee_time_sequence_table.html
+1152
-1148
No files found.
qdry_project/static/src/view/oee_time_sequence_table.html
View file @
abdbdfb9
<!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=
"multi-utilization-chart glass-effect"
>
<div
class=
"section-title"
>
<div
class=
"title-bar"
></div>
<span
class=
"title-text"
>
多设备空闲率对比
</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"
>
<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=
"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
:
""
,
// 基地址 https://workstation.rokeris.com
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"
,
};
},
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
.
get_plan_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
()
{
if
(
!
this
.
selectedDevice
)
{
this
.
pickingOrderList
=
[];
return
;
}
this
.
idle_rate_loading
=
true
;
axios
({
method
:
"post"
,
url
:
this
.
localURL
+
"/public/device_efficiency/daily_running_time"
,
data
:
{
device_code
:
this
.
selectedDevice
,
},
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
.
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
();
}
else
{
// 当设备被清空时,清空折线图数据
this
.
initUtilizationChart
([]);
}
// 同时更新柱状图数据
this
.
get_daily_running_time
();
},
// 处理窗口大小变化修改图表大小
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
:
data
.
length
>
0
,
position
:
"top"
,
color
:
"#FFFFFF"
,
fontSize
:
12
,
formatter
:
"{c}%"
,
},
lineStyle
:
{
color
:
"#36cfc9"
,
width
:
3
,
},
itemStyle
:
{
color
:
"#36cfc9"
,
},
data
:
data
.
length
>
0
?
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
);
},
300
);
},
// 新增:获取多设备空闲率数据(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
20px
15px
20px
;
height
:
auto
;
min-height
:
460px
;
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
5px
40px
;
height
:
370px
;
}
.columns-container
{
position
:
absolute
;
top
:
0
;
left
:
60px
;
width
:
calc
(
100%
-
70px
);
height
:
330px
;
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
:
12px
;
text-align
:
center
;
width
:
100%
;
color
:
#fff
;
}
/* 移除原有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>
<!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=
"multi-utilization-chart glass-effect"
>
<div
class=
"section-title"
>
<div
class=
"title-bar"
></div>
<span
class=
"title-text"
>
多设备空闲率对比
</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"
>
<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=
"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': calculateHeightByMinutes(convertSecondsToMinutes(segment.duration)) + '%',
'background-color': segment.state,
'margin-top': '0px'
}"
>
<el-tooltip
effect=
"dark"
:content=
"convertSecondsToMinutes(segment.duration) + '分钟'"
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
:
""
,
// 基地址 https://workstation.rokeris.com
localURL
:
"https://dws-platform.xbg.rokeris.com/dev-api"
,
// 基地址 https://workstation.rokeris.com
idle_rate_loading
:
false
,
// 加载效果
chart_loading
:
false
,
deviceList
:
[],
// 设备列表
selectedDevice
:
null
,
// 选中的设备
yAxisValues
:
[
"1440"
,
"1152"
,
"864"
,
"576"
,
"288"
,
"0"
],
// Y轴刻度值(分钟)
pickingOrderList
:
[],
// 拣选单列表
dateList
:
[],
// 日期列表
start_time
:
""
,
// 开始时间
end_time
:
""
,
// 结束时间
utilizationChart
:
null
,
// 设备空闲率图表实例变量
multiUtilizationChart
:
null
,
// 新增:多设备空闲率图表实例变量
plan_time_list
:
null
,
latestDateList
:
[],
factory_code
:
"8d8dec6e-0d44-11f0-9692-00163e04c506"
,
};
},
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
.
get_plan_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
()
{
if
(
!
this
.
selectedDevice
)
{
this
.
pickingOrderList
=
[];
return
;
}
this
.
idle_rate_loading
=
true
;
axios
({
method
:
"post"
,
url
:
this
.
localURL
+
"/public/device_efficiency/daily_running_time"
,
data
:
{
device_code
:
this
.
selectedDevice
,
},
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
.
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
();
}
else
{
// 当设备被清空时,清空折线图数据
this
.
initUtilizationChart
([]);
}
// 同时更新柱状图数据
this
.
get_daily_running_time
();
},
// 处理窗口大小变化修改图表大小
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
:
data
.
length
>
0
,
position
:
"top"
,
color
:
"#FFFFFF"
,
fontSize
:
12
,
formatter
:
"{c}%"
,
},
lineStyle
:
{
color
:
"#36cfc9"
,
width
:
3
,
},
itemStyle
:
{
color
:
"#36cfc9"
,
},
data
:
data
.
length
>
0
?
data
.
map
((
item
)
=>
(
item
>
100
?
100
:
item
))
:
[],
},
],
};
this
.
utilizationChart
.
setOption
(
option
);
// 手动触发resize以确保正确渲染
this
.
utilizationChart
.
resize
();
},
300
);
// 延迟300ms初始化
},
// 将秒转换为分钟
convertSecondsToMinutes
(
seconds
)
{
return
Math
.
round
(
seconds
/
60
);
},
// 基于分钟数计算高度百分比
calculateHeightByMinutes
(
minutes
)
{
// 将分钟数转换为相对于1440分钟(一天)的百分比
const
percentage
=
(
minutes
/
1440
)
*
100
;
return
Math
.
min
(
Math
.
max
(
percentage
,
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
);
},
300
);
},
// 新增:获取多设备空闲率数据(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
20px
15px
20px
;
height
:
auto
;
min-height
:
460px
;
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
5px
40px
;
height
:
370px
;
}
.columns-container
{
position
:
absolute
;
top
:
0
;
left
:
60px
;
width
:
calc
(
100%
-
70px
);
height
:
330px
;
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
:
12px
;
text-align
:
center
;
width
:
100%
;
color
:
#fff
;
}
/* 移除原有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