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
78a4a8b1
Commit
78a4a8b1
authored
Jul 11, 2025
by
guibin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
更新安灯数据看板
parent
be33b028
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
907 additions
and
2 deletions
+907
-2
tht_project/controllers/controllers.py
+8
-2
tht_project/static/html/abnormal_alarm/view/index.html
+899
-0
No files found.
tht_project/controllers/controllers.py
View file @
78a4a8b1
...
...
@@ -7,10 +7,16 @@ import math
from
datetime
import
datetime
,
time
from
jinja2
import
Environment
,
FileSystemLoader
BASE_DIR
=
os
.
path
.
dirname
(
os
.
path
.
dirname
(
__file__
))
templateloader
=
FileSystemLoader
(
searchpath
=
BASE_DIR
+
"/static
/src/view
"
)
templateloader
=
FileSystemLoader
(
searchpath
=
BASE_DIR
+
"/static"
)
env
=
Environment
(
loader
=
templateloader
)
class
ThtProject
(
http
.
Controller
):
@http.route
(
'/roke/abnormal_alarm/census'
,
type
=
'http'
,
auth
=
'public'
,
csrf
=
False
,
cors
=
"*"
)
def
roke_index_demo_module
(
self
,
**
kwargs
):
template
=
env
.
get_template
(
'html/abnormal_alarm/view/index.html'
)
html
=
template
.
render
({})
return
html
@http.route
(
'/tht/get/equipment/working_time'
,
type
=
'json'
,
auth
=
"none"
,
csrf
=
False
,
cors
=
'*'
)
def
get_equipment_working_time
(
self
,
**
kwargs
):
"""获取车间工作时间"""
...
...
@@ -54,7 +60,7 @@ class RokeMesThreeColourLightExt(RokeMesThreeColourLight):
"override"
:
True
# 添加额外字段
}
}
template
=
env
.
get_template
(
'equipment_status.html'
)
template
=
env
.
get_template
(
'
src/view/
equipment_status.html'
)
return
template
.
render
(
data
)
@http.route
(
'/roke/equipment/search'
,
type
=
'json'
,
methods
=
[
'POST'
,
'OPTIONS'
],
auth
=
"none"
,
csrf
=
False
,
...
...
tht_project/static/html/abnormal_alarm/view/index.html
0 → 100644
View file @
78a4a8b1
<!DOCTYPE html>
<html>
<head>
<meta
charset=
"UTF-8"
/>
<title>
安灯数据
</title>
<meta
content=
"width=device-width,initial-scale=1.0, maximum-scale=1.0,user-scalable=0"
name=
"viewport"
/>
<!-- /roke_workstation_api/static/html/abnormal_alarm -->
<link
rel=
"stylesheet"
href=
"/roke_workstation_api/static/html/abnormal_alarm/element-ui/index.css"
/>
<link
rel=
"stylesheet"
href=
"/roke_workstation_api/static/html/abnormal_alarm/css/dark_element_ui.css"
/>
<script
src=
"/roke_workstation_api/static/html/abnormal_alarm/js/vue.js"
></script>
<script
src=
"/roke_workstation_api/static/html/abnormal_alarm/js/axios.min.js"
></script>
<script
src=
"/roke_workstation_api/static/html/abnormal_alarm/element-ui/index.js"
></script>
<script
src=
"/roke_workstation_api/static/html/abnormal_alarm/js/echarts.min.js"
></script>
</head>
<body
id=
"bodyId"
style=
"display: none"
>
<div
id=
"app"
v-loading
.
body
.
fullscreen
.
lock=
"loading"
>
<!-- 今日工位呼叫明细 -->
<div
class=
"panelCardBox"
>
<div
class=
"panelHeaderBox"
>
今日工位呼叫明细
</div>
<div
ref=
"panelTableRef"
class=
"panelTableBox"
@
mouseenter=
"mouseEnterHandle"
@
mouseleave=
"mouseLeaveHandle"
v-show=
"stationCallRecordList.length"
>
<div
class=
"panelTableHeader"
>
<div
class=
"roll_line"
>
<div>
产线
</div>
<div>
工位
</div>
<div>
呼叫类型
</div>
<div>
呼叫时间
</div>
<div>
处理时间
</div>
</div>
</div>
<div
ref=
"vlist"
class=
"vabsolute_content"
>
<div
class=
"fragment_content"
></div>
<div
class=
"fragment_content"
></div>
</div>
</div>
<div
class=
"emptyBox"
v-show=
"!stationCallRecordList.length"
>
<el-empty
image=
"/roke_workstation_api/static/html/abnormal_alarm/images/empty.png"
description=
"暂无数据"
:image-size=
"100"
></el-empty>
</div>
</div>
<!-- 今日产品缺陷走势图 -->
<div
class=
"panelCardBox"
>
<div
class=
"panelHeaderBox"
>
<span>
今日产品缺陷走势图
</span>
<span
class=
"unitBox"
>
单位:个
</span>
<el-select
v-model=
"selClassesValue"
size=
"mini"
placeholder=
"选择班次"
@
change=
"selChangeHandle($event,'今日产品缺陷走势图')"
>
<el-option
v-for=
"item in classesList"
:key=
"item.id"
:label=
"item.name"
:value=
"item.id"
>
</el-option>
</el-select>
</div>
<div
ref=
"defectTrendRef"
style=
"width: 100%; height: calc(100% - 28px)"
></div>
</div>
<!-- 今日呼叫处理情况分析 -->
<div
class=
"panelCardBox"
>
<div
class=
"panelHeaderBox"
>
今日呼叫处理情况分析
</div>
<div
class=
"handleStatuBox"
>
<div
class=
"statuItemBox"
v-for=
"(item, index) in statusData"
:key=
"index"
>
<span
class=
"number"
:style=
"{background: item.color}"
>
[[item.count]]
</span>
<span
class=
"label"
>
[[item.label]]
</span>
</div>
</div>
</div>
<!-- 近一周生产中断等待时长统计 -->
<div
class=
"panelCardBox"
>
<div
class=
"panelHeaderBox"
>
<span>
近一周生产中断等待时长统计
</span>
<span
class=
"unitBox"
>
单位:分钟
</span>
<el-select
v-model=
"selProductionLineValue"
size=
"mini"
placeholder=
"选择产线"
filterable
@
change=
"selChangeHandle($event,'近一周生产中断等待时长统计')"
>
<el-option
v-for=
"item in productionLineList"
:key=
"item.id"
:label=
"item.name"
:value=
"item.id"
>
</el-option>
</el-select>
</div>
<div
ref=
"weeklyWaitTimeRef"
style=
"width: 100%; height: calc(100% - 28px)"
></div>
</div>
<!-- 近一周安灯呼叫类型统计 -->
<div
class=
"panelCardBox"
>
<div
class=
"panelHeaderBox"
>
近一周安灯呼叫类型统计
</div>
<div
v-show=
"callTypePieChartData.length>0"
ref=
"callTypePieRef"
style=
"width: 100%; height: calc(100% - 28px)"
></div>
<div
class=
"emptyBox"
v-show=
"!callTypePieChartData.length"
>
<el-empty
image=
"/roke_workstation_api/static/html/abnormal_alarm/images/empty.png"
description=
"暂无数据"
:image-size=
"100"
></el-empty>
</div>
</div>
<!-- 近一周设备故障频频率 -->
<div
class=
"panelCardBox"
>
<div
class=
"panelHeaderBox"
>
<span>
近一周设备故障频率
</span>
<span
class=
"unitBox"
>
单位:次
</span>
<el-select
v-model=
"selEquipmentValue"
size=
"mini"
placeholder=
"选择设备"
filterable
@
change=
"selChangeHandle($event,'近一周设备故障频频率')"
>
<el-option
v-for=
"item in equipmentList"
:key=
"item.id"
:label=
"item.name"
:value=
"item.id"
>
</el-option>
</el-select>
</div>
<div
ref=
"deviceFailureRef"
style=
"width: 100%; height: calc(100% - 28px)"
></div>
</div>
</div>
</body>
<script>
// 发送消息给父页面(关闭odoo的菜单弹窗)
document
.
addEventListener
(
"click"
,
()
=>
{
window
.
parent
.
postMessage
(
"hidePopover"
,
"*"
)
})
let
vue
=
new
Vue
({
el
:
"#app"
,
delimiters
:
[
"[["
,
"]]"
],
data
()
{
return
{
windowHeight
:
window
.
innerHeight
,
// 页面高度
baseURL
:
""
,
// 基地址 http://192.168.8.114:8069
loading
:
false
,
// 全局加载效果
stationCallRecordList
:
[],
// 工位呼叫明细数据
containerHeight
:
0
,
// 表体区域高度
perCount
:
0
,
// 可以展示多少行
tableLineHeight
:
40
,
// 表格行高
startIndex
:
0
,
// 片段的起始索引
animationFrameId
:
null
,
// 动画帧id
frameCount
:
0
,
// 当前帧数
chartsProject
:
{},
// 存储图表实例
classesList
:
[],
// 班次列表
selClassesValue
:
null
,
// 选中班次的值
defectTrendChartData
:
{
keys
:
[],
values
:
[]
},
// 今日产品缺陷走势图数据
// 今日呼叫处理情况分析数据
statusData
:
[
{
count
:
0
,
label
:
'待处理'
,
color
:
'#F7BA2A'
},
{
count
:
0
,
label
:
'处理中'
,
color
:
'#67C23A'
},
{
count
:
0
,
label
:
'超时处理'
,
color
:
'#F56C6C'
},
{
count
:
0
,
label
:
'停线呼叫'
,
color
:
'#F56C6C'
},
{
count
:
0
,
label
:
'非停线呼叫'
,
color
:
'#909399'
},
{
count
:
0
,
label
:
'质量异常'
,
color
:
'#409EFF'
},
{
count
:
0
,
label
:
'工艺异常'
,
color
:
'#B8E6FF'
},
{
count
:
0
,
label
:
'设备异常'
,
color
:
'#E6A23C'
}
],
productionLineList
:
[],
// 产线列表
selProductionLineValue
:
null
,
// 选中产线的值
weeklyWaitTimeChartData
:
{
keys
:
[],
values
:
[]
},
// 近一周生产中断等待时长统计数据
callTypePieChartData
:
[],
// 近一周安灯呼叫类型统计数据
equipmentList
:
[],
// 设备列表
selEquipmentValue
:
null
,
// 选中设备的值
deviceFailureChartData
:
{
keys
:
[],
values
:
[]
},
// 近一周设备故障频率数据
}
},
async
created
()
{
this
.
loading
=
true
await
Promise
.
allSettled
([
this
.
getStationCallRecordListApi
(),
// 获取今日工位呼叫明细数据
this
.
generalQueryApi
(
'班次'
),
// 获取班次列表
this
.
generalQueryApi
(
'产线'
),
// 获取产线列表
this
.
generalQueryApi
(
'设备'
),
// 获取设备列表
this
.
getChartDataApi
()
// 获取图表数据
]).
then
(()
=>
{
// 确保DOM完全加载后再初始化图表
this
.
initAllCharts
()
})
this
.
loading
=
false
},
mounted
()
{
this
.
$nextTick
(()
=>
{
document
.
getElementById
(
"bodyId"
).
style
.
display
=
"block"
})
window
.
addEventListener
(
"resize"
,
this
.
resizeHandle
)
},
methods
:
{
// 通用查询接口
generalQueryApi
(
type
)
{
return
new
Promise
((
resolve
,
reject
)
=>
{
let
config
=
{
model
:
''
,
fields
:
[
'id'
,
'name'
],
domain
:
[]
}
if
(
type
==
'班次'
)
{
config
.
model
=
'roke.classes'
config
.
fields
=
[
'id'
,
'name'
,
'start_time'
,
'end_time'
]
config
.
domain
=
[[
"main_id.is_default"
,
"="
,
true
]]
}
else
if
(
type
==
'产线'
)
{
config
.
model
=
'roke.workshop'
}
else
if
(
type
==
'设备'
)
{
config
.
model
=
'roke.mes.equipment'
}
if
(
!
config
.
model
)
return
axios
({
method
:
"POST"
,
url
:
this
.
baseURL
+
'/roke/workstation/search_read'
,
data
:
config
,
headers
:
{
"Content-Type"
:
"application/json"
}
}).
then
(
async
(
result
)
=>
{
if
(
result
?.
data
?.
result
?.
code
==
0
)
{
const
data
=
result
.
data
.
result
.
data
if
(
type
==
'班次'
)
{
data
.
forEach
(
item
=>
{
item
.
start_time
=
this
.
convertNumberToTime
(
item
.
start_time
)
item
.
end_time
=
this
.
convertNumberToTime
(
item
.
end_time
)
item
.
name
=
`
${
item
.
name
}
(
${
item
.
start_time
}
~
${
item
.
end_time
}
)`
})
this
.
classesList
=
data
this
.
selClassesValue
=
data
.
length
>
0
?
data
[
0
].
id
:
null
// 获取今日产品缺陷走势图
if
(
this
.
selClassesValue
)
await
this
.
getProductDefectChartListApi
()
}
else
if
(
type
==
'产线'
)
{
let
list
=
[{
id
:
0
,
name
:
'所有产线'
}]
this
.
productionLineList
=
[...
list
,
...
data
]
this
.
selProductionLineValue
=
0
}
else
if
(
type
==
'设备'
)
{
let
list
=
[{
id
:
0
,
name
:
'所有设备'
}]
this
.
equipmentList
=
[...
list
,
...
data
]
this
.
selEquipmentValue
=
0
}
resolve
()
}
else
if
(
result
?.
data
?.
result
?.
code
==
1
)
{
this
.
$message
.
error
(
result
.
data
.
result
.
message
)
reject
()
}
else
if
(
result
?.
data
?.
error
)
{
this
.
$message
.
error
(
result
.
data
.
error
.
message
)
reject
()
}
}).
catch
((
error
)
=>
{
console
.
error
(
error
)
reject
()
})
})
},
// 获取今日工位呼叫明细数据
getStationCallRecordListApi
(
isRefresh
)
{
return
new
Promise
((
resolve
,
reject
)
=>
{
if
(
isRefresh
)
this
.
resetAnimationFn
()
axios
({
method
:
"POST"
,
url
:
this
.
baseURL
+
'/get/workstation/everyday/abnormal'
,
data
:
{},
headers
:
{
"Content-Type"
:
"application/json"
}
}).
then
((
result
)
=>
{
if
(
result
?.
data
?.
result
?.
code
==
0
)
{
const
data
=
result
.
data
.
result
.
data
const
general
=
result
.
data
.
result
.
general
this
.
statusData
.
forEach
(
item
=>
{
if
(
item
.
label
==
'待处理'
)
{
item
.
count
=
general
.
draft
}
else
if
(
item
.
label
==
'处理中'
)
{
item
.
count
=
general
.
start
}
else
if
(
item
.
label
==
'超时处理'
)
{
item
.
count
=
general
.
overtime
}
else
if
(
item
.
label
==
'停线呼叫'
)
{
item
.
count
=
general
.
stop
}
else
if
(
item
.
label
==
'非停线呼叫'
)
{
item
.
count
=
general
.
running
}
else
if
(
item
.
label
==
'质量异常'
)
{
item
.
count
=
general
.
quality
}
else
if
(
item
.
label
==
'工艺异常'
)
{
item
.
count
=
general
.
routing
}
else
if
(
item
.
label
==
'设备异常'
)
{
item
.
count
=
general
.
equipment
}
})
data
.
forEach
(
item
=>
{
item
.
originating_time
=
item
?.
originating_time
?
item
.
originating_time
.
split
(
' '
)[
1
]
:
'/'
item
.
arrive_time
=
item
?.
arrive_time
?
item
.
arrive_time
.
split
(
' '
)[
1
]
:
'/'
})
this
.
stationCallRecordList
=
data
// 重新初始化滚动表格
this
.
initScrollTable
()
resolve
()
}
else
if
(
result
?.
data
?.
result
?.
code
==
1
)
{
this
.
$message
.
error
(
result
.
data
.
result
.
message
)
reject
()
}
else
if
(
result
?.
data
?.
error
)
{
this
.
$message
.
error
(
result
.
data
.
error
.
message
)
reject
()
}
}).
catch
((
error
)
=>
{
this
.
$message
.
error
(
error
)
reject
()
})
})
},
// 获取今日产品缺陷走势图
getProductDefectChartListApi
()
{
return
new
Promise
((
resolve
,
reject
)
=>
{
axios
({
method
:
"POST"
,
url
:
this
.
baseURL
+
'/get/workstation/everyday/hourly_unqualified_qty'
,
data
:
{
classes
:
this
.
selClassesValue
// 班次id
},
headers
:
{
"Content-Type"
:
"application/json"
}
}).
then
((
result
)
=>
{
if
(
result
?.
data
?.
result
?.
code
==
0
)
{
const
data
=
result
.
data
.
result
.
data
this
.
defectTrendChartData
.
keys
=
Object
.
keys
(
data
)
this
.
defectTrendChartData
.
values
=
Object
.
values
(
data
)
resolve
()
}
else
if
(
result
?.
data
?.
result
?.
code
==
1
)
{
this
.
$message
.
error
(
result
.
data
.
result
.
message
)
reject
()
}
else
if
(
result
?.
data
?.
error
)
{
this
.
$message
.
error
(
result
.
data
.
error
.
message
)
reject
()
}
}).
catch
((
error
)
=>
{
this
.
$message
.
error
(
error
)
reject
()
})
})
},
// 获取图表数据
getChartDataApi
()
{
return
new
Promise
((
resolve
,
reject
)
=>
{
axios
({
method
:
"POST"
,
url
:
this
.
baseURL
+
'/get/workstation/week/abnormal'
,
data
:
{
workshop_id
:
this
.
selProductionLineValue
,
// 产线id
equipment_id
:
this
.
selEquipmentValue
,
// 设备id
},
headers
:
{
"Content-Type"
:
"application/json"
}
}).
then
((
result
)
=>
{
if
(
result
?.
data
?.
result
?.
code
==
0
)
{
const
data
=
result
.
data
.
result
.
data
let
dayList
=
[]
let
countList
=
[]
let
equipmentCountList
=
[]
data
.
week
.
forEach
(
item
=>
{
dayList
.
push
(
item
.
day
+
'号'
)
countList
.
push
(
item
.
count
)
equipmentCountList
.
push
(
item
.
equipment_count
)
})
this
.
weeklyWaitTimeChartData
.
keys
=
dayList
this
.
deviceFailureChartData
.
keys
=
dayList
this
.
weeklyWaitTimeChartData
.
values
=
countList
this
.
deviceFailureChartData
.
values
=
equipmentCountList
let
callTypePieChartData
=
[]
if
(
data
.
routing
)
callTypePieChartData
.
push
({
name
:
'工艺问题'
,
value
:
data
.
routing
})
if
(
data
.
quality
)
callTypePieChartData
.
push
({
name
:
'质量问题'
,
value
:
data
.
quality
})
if
(
data
.
starving
)
callTypePieChartData
.
push
({
name
:
'缺料问题'
,
value
:
data
.
starving
})
if
(
data
.
equipment
)
callTypePieChartData
.
push
({
name
:
'设备维修'
,
value
:
data
.
equipment
})
if
(
data
.
shape
)
callTypePieChartData
.
push
({
name
:
'外形问题'
,
value
:
data
.
shape
})
this
.
callTypePieChartData
=
callTypePieChartData
resolve
()
}
else
if
(
result
?.
data
?.
result
?.
code
==
1
)
{
this
.
$message
.
error
(
result
.
data
.
result
.
message
)
reject
()
}
else
if
(
result
?.
data
?.
error
)
{
this
.
$message
.
error
(
result
.
data
.
error
.
message
)
reject
()
}
}).
catch
((
error
)
=>
{
this
.
$message
.
error
(
error
)
reject
()
})
})
},
// 页面大小改变事件
resizeHandle
()
{
this
.
windowHeight
=
window
.
innerHeight
this
.
chartsResizeFn
()
},
// 重置动画方法
resetAnimationFn
()
{
// 清空动画帧
if
(
this
.
animationFrameId
)
{
window
.
cancelAnimationFrame
(
this
.
animationFrameId
)
this
.
animationFrameId
=
null
}
// 重置起始索引
this
.
startIndex
=
0
// 清空现有数据
this
.
stationCallRecordList
=
[]
// 清空现有片段内容
const
fragments
=
this
.
$refs
.
vlist
.
children
Array
.
from
(
fragments
).
forEach
(
fragment
=>
{
fragment
.
innerHTML
=
''
})
},
// 表格区域鼠标进去事件
mouseEnterHandle
()
{
// 鼠标放到表格暂停滚动
if
(
this
.
animationFrameId
)
{
window
.
cancelAnimationFrame
(
this
.
animationFrameId
)
this
.
animationFrameId
=
null
}
},
// 表格区域鼠标离开事件
mouseLeaveHandle
()
{
// 数据量少于可显示行数时,不启动滚动
if
(
this
.
stationCallRecordList
.
length
<=
this
.
perCount
)
return
// 否则启动滚动
if
(
!
this
.
animationFrameId
)
this
.
loopScrollAnimationFn
()
},
// 初始化滚动表格
initScrollTable
()
{
this
.
$nextTick
(()
=>
{
setTimeout
(()
=>
{
// 获取表格高度
this
.
containerHeight
=
this
.
$refs
.
panelTableRef
.
offsetHeight
-
40
// 计算可以展示多少行
this
.
perCount
=
Math
.
floor
(
this
.
containerHeight
/
this
.
tableLineHeight
)
// 清空现有片段内容
const
vlist
=
this
.
$refs
.
vlist
vlist
.
innerHTML
=
''
// 创建第一个片段
const
firstFragment
=
document
.
createElement
(
'div'
)
firstFragment
.
className
=
'fragment_content'
vlist
.
appendChild
(
firstFragment
)
// 如果数据量少于可显示行数,直接显示所有数据不滚动
if
(
this
.
stationCallRecordList
.
length
<=
this
.
perCount
)
{
// 填充所有数据
this
.
fillScrollTableContentFn
(
firstFragment
,
0
,
this
.
stationCallRecordList
.
length
)
return
}
// 数据量足够时,创建第二个片段并启动滚动
const
secondFragment
=
document
.
createElement
(
'div'
)
secondFragment
.
className
=
'fragment_content'
vlist
.
appendChild
(
secondFragment
)
this
.
loopScrollAnimationFn
(
true
)
},
1
)
})
},
// 循环滚动动画方法
loopScrollAnimationFn
(
isInit
=
false
)
{
// 是否有数据可以显示,如果没有数据,直接返回
if
(
!
this
.
stationCallRecordList
.
length
>
0
)
return
if
(
this
.
startIndex
===
0
&&
isInit
===
true
)
{
// 初始化阶段
// 获取第一个片段DOM元素
const
firstFragment
=
this
.
$refs
.
vlist
.
firstElementChild
// 填充第一个片段的数据
this
.
fillScrollTableContentFn
(
firstFragment
,
this
.
startIndex
)
// 计算下一个片段的起始索引
this
.
startIndex
=
this
.
countStartIndexFn
(
this
.
startIndex
)
// 获取第二个片段DOM元素
const
secondFragment
=
this
.
$refs
.
vlist
.
lastElementChild
// 填充第二个片段的数据
this
.
fillScrollTableContentFn
(
secondFragment
,
this
.
startIndex
)
// 再次计算下一个片段的起始索引
this
.
startIndex
=
this
.
countStartIndexFn
(
this
.
startIndex
)
// 设置初始位置为0
this
.
$refs
.
vlist
.
style
.
transform
=
'translateY(0)'
}
else
{
// 滚动阶段
// 获取滚动容器DOM
const
vlist
=
this
.
$refs
.
vlist
// 获取当前 transform 值
let
currentTransform
=
vlist
.
style
.
transform
// 解析当前Y轴位置,如果没有则默认为0
let
currentY
=
currentTransform
?
parseInt
(
currentTransform
.
match
(
/-
?\d
+/
)?.[
0
]
||
0
)
:
0
// 帧计数器递增
this
.
frameCount
++
// 每2帧执行一次滚动
if
(
this
.
frameCount
>=
2
)
{
// 重置帧计数器
this
.
frameCount
=
0
// 判断是否滚动到了一个片段的高度
if
(
Math
.
abs
(
currentY
)
>=
this
.
tableLineHeight
*
this
.
perCount
)
{
// 移除第一个片段
vlist
.
removeChild
(
vlist
.
firstElementChild
)
// 重置位置到顶部
vlist
.
style
.
transform
=
'translateY(0)'
// 创建新的片段元素
const
newFragment
=
document
.
createElement
(
'div'
)
// 设置片段的类名
newFragment
.
className
=
'fragment_content'
// 填充新片段的数据
this
.
fillScrollTableContentFn
(
newFragment
,
this
.
startIndex
)
// 计算下一个片段的起始索引
this
.
startIndex
=
this
.
countStartIndexFn
(
this
.
startIndex
)
// 将新片段添加到容器末尾
vlist
.
appendChild
(
newFragment
)
}
else
{
// 如果未滚动到片段高度,继续向上滚动1像素
vlist
.
style
.
transform
=
`translateY(
${
currentY
-
1
}
px)`
}
}
}
// 请求下一帧动画
this
.
animationFrameId
=
window
.
requestAnimationFrame
(()
=>
this
.
loopScrollAnimationFn
())
},
// 填充滚动表格内容
fillScrollTableContentFn
(
ele
,
startIndex
,
customCount
)
{
// 创建文档片段,用于提高性能,避免多次DOM操作
const
fragment
=
document
.
createDocumentFragment
()
// 记录当前数据索引
let
currentIndex
=
startIndex
// 使用自定义数量或默认的perCount
const
count
=
customCount
||
this
.
perCount
// 循环创建每一行数据
for
(
let
i
=
0
;
i
<
count
;
i
++
)
{
// 获取当前行的数据
const
curData
=
this
.
stationCallRecordList
[
currentIndex
]
if
(
!
curData
)
break
// 如果没有数据了就停止
// 创建行容器
const
newLine
=
document
.
createElement
(
'div'
)
// 设置行的样式类
newLine
.
className
=
'roll_line'
// 创建产线列
const
workshop_name
=
document
.
createElement
(
'div'
)
workshop_name
.
innerText
=
curData
?.
workshop_name
?
curData
.
workshop_name
:
'/'
// 创建工位列
const
work_center_name
=
document
.
createElement
(
'div'
)
work_center_name
.
innerText
=
curData
?.
work_center_name
?
curData
.
work_center_name
:
'/'
// 创建呼叫类型列
const
abnormal_item
=
document
.
createElement
(
'div'
)
abnormal_item
.
innerText
=
curData
?.
abnormal_item
?
curData
.
abnormal_item
:
'/'
// 根据不同的呼叫类型设置不同的样式
if
(
curData
?.
abnormal_item
===
'设备故障'
)
{
abnormal_item
.
className
=
'danger'
}
else
if
(
curData
?.
abnormal_item
===
'质量问题'
)
{
abnormal_item
.
className
=
'warning'
}
else
if
(
curData
?.
abnormal_item
===
'其他'
)
{
abnormal_item
.
className
=
'info'
}
// 创建呼叫时间列
const
originating_time
=
document
.
createElement
(
'div'
)
originating_time
.
innerText
=
curData
?.
originating_time
?
curData
.
originating_time
:
'/'
// 创建处理时间列
const
arrive_time
=
document
.
createElement
(
'div'
)
arrive_time
.
innerText
=
curData
?.
arrive_time
?
curData
.
arrive_time
:
'/'
// 将所有列添加到行容器中
newLine
.
appendChild
(
workshop_name
)
newLine
.
appendChild
(
work_center_name
)
newLine
.
appendChild
(
abnormal_item
)
newLine
.
appendChild
(
originating_time
)
newLine
.
appendChild
(
arrive_time
)
// 将行添加到文档片段中
fragment
.
appendChild
(
newLine
)
// 更新索引,如果到达末尾则重新从0开始
currentIndex
=
currentIndex
===
this
.
stationCallRecordList
.
length
-
1
?
0
:
currentIndex
+
1
}
// 将整个文档片段一次性添加到DOM中
ele
.
appendChild
(
fragment
)
},
// 计算下一个片段开始显示的数据索引
countStartIndexFn
(
startIndex
)
{
// 数据不够填满下一个片段
if
(
startIndex
+
this
.
perCount
>=
this
.
stationCallRecordList
.
length
)
{
return
this
.
perCount
-
(
this
.
stationCallRecordList
.
length
-
startIndex
)
}
// 数据足够填满下一个片段,直接返回当前索引加上每页显示数量
return
this
.
perCount
+
startIndex
},
// 数字转换成时间格式方法
convertNumberToTime
(
num
)
{
// 提取小时部分,使用 Math.floor 函数向下取整得到整数小时
let
hours
=
Math
.
floor
(
num
)
// 提取分钟部分,先将小数部分乘以 60 得到分钟数
let
minutes
=
Math
.
round
((
num
-
hours
)
*
60
)
// 将小时和分钟转换为字符串,并确保它们是两位数的格式
// 如果小时数小于 10,在前面补 0
hours
=
hours
.
toString
().
padStart
(
2
,
'0'
)
// 如果分钟数小于 10,在前面补 0
minutes
=
minutes
.
toString
().
padStart
(
2
,
'0'
)
// 返回格式化后的时间字符串
return
`
${
hours
}
:
${
minutes
}
`
},
// 初始化所有图标
initAllCharts
()
{
this
.
$nextTick
(()
=>
{
setTimeout
(()
=>
{
if
(
this
.
$refs
.
defectTrendRef
)
this
.
initDefectTrendChart
()
if
(
this
.
$refs
.
weeklyWaitTimeRef
)
this
.
initWeeklyWaitTimeChart
()
if
(
this
.
$refs
.
callTypePieRef
)
this
.
initCallTypePieChart
()
if
(
this
.
$refs
.
deviceFailureRef
)
this
.
initDeviceFailureChart
()
},
1
)
})
},
// 初始化今日产品缺陷走势图图表
initDefectTrendChart
()
{
const
chart
=
echarts
.
init
(
this
.
$refs
.
defectTrendRef
)
this
.
chartsProject
.
defectTrendProject
=
chart
const
option
=
{
tooltip
:
{
trigger
:
'item'
},
grid
:
{
top
:
'45px'
,
left
:
'3%'
,
right
:
'4%'
,
bottom
:
'3%'
,
containLabel
:
true
},
xAxis
:
{
type
:
'category'
,
axisLine
:
{
lineStyle
:
{
color
:
'#7c808d'
}
},
axisLabel
:
{
color
:
'#fff'
,
fontSize
:
11
},
data
:
this
.
defectTrendChartData
.
keys
},
yAxis
:
{
type
:
'value'
,
axisLine
:
{
show
:
true
,
lineStyle
:
{
color
:
'#7c808d'
}
},
splitLine
:
{
lineStyle
:
{
color
:
'#7c808d'
,
width
:
0.5
,
opacity
:
0.5
}
},
axisLabel
:
{
color
:
'#fff'
,
fontSize
:
11
,
formatter
:
'{value}'
}
},
series
:
[{
data
:
this
.
defectTrendChartData
.
values
,
type
:
'line'
,
smooth
:
true
}]
}
chart
.
setOption
(
option
)
},
// 初始化近一周生产中断等待时长统计图表
initWeeklyWaitTimeChart
()
{
const
chart
=
echarts
.
init
(
this
.
$refs
.
weeklyWaitTimeRef
)
this
.
chartsProject
.
weeklyWaitTimeProject
=
chart
const
option
=
{
tooltip
:
{
trigger
:
'item'
},
grid
:
{
top
:
'45px'
,
left
:
'3%'
,
right
:
'4%'
,
bottom
:
'3%'
,
containLabel
:
true
},
xAxis
:
{
type
:
'category'
,
axisLine
:
{
lineStyle
:
{
color
:
'#7c808d'
}
},
axisLabel
:
{
color
:
'#fff'
,
fontSize
:
11
},
data
:
this
.
weeklyWaitTimeChartData
.
keys
},
yAxis
:
{
type
:
'value'
,
axisLine
:
{
show
:
true
,
lineStyle
:
{
color
:
'#7c808d'
}
},
splitLine
:
{
lineStyle
:
{
color
:
'#7c808d'
,
width
:
0.5
,
opacity
:
0.5
}
},
axisLabel
:
{
color
:
'#fff'
,
fontSize
:
11
,
formatter
:
'{value}'
}
},
series
:
[{
data
:
this
.
weeklyWaitTimeChartData
.
values
,
type
:
'bar'
}]
}
chart
.
setOption
(
option
)
},
// 初始化近一周安灯呼叫类型统计图表
initCallTypePieChart
()
{
const
chart
=
echarts
.
init
(
this
.
$refs
.
callTypePieRef
)
this
.
chartsProject
.
callTypePieProject
=
chart
const
option
=
{
tooltip
:
{
trigger
:
'item'
},
series
:
[{
type
:
'pie'
,
radius
:
'70%'
,
data
:
this
.
callTypePieChartData
}]
}
chart
.
setOption
(
option
)
},
// 初始化近一周设备故障频率图表
initDeviceFailureChart
()
{
const
chart
=
echarts
.
init
(
this
.
$refs
.
deviceFailureRef
)
this
.
chartsProject
.
deviceFailureProject
=
chart
const
option
=
{
tooltip
:
{
trigger
:
'item'
},
grid
:
{
top
:
'45px'
,
left
:
'3%'
,
right
:
'4%'
,
bottom
:
'3%'
,
containLabel
:
true
},
xAxis
:
{
type
:
'category'
,
axisLine
:
{
lineStyle
:
{
color
:
'#7c808d'
}
},
axisLabel
:
{
color
:
'#fff'
,
fontSize
:
11
},
data
:
this
.
deviceFailureChartData
.
keys
},
yAxis
:
{
type
:
'value'
,
axisLine
:
{
show
:
true
,
lineStyle
:
{
color
:
'#7c808d'
}
},
splitLine
:
{
lineStyle
:
{
color
:
'#7c808d'
,
width
:
0.5
,
opacity
:
0.5
}
},
axisLabel
:
{
color
:
'#fff'
,
fontSize
:
11
,
formatter
:
'{value}'
}
},
series
:
[{
data
:
this
.
deviceFailureChartData
.
values
,
type
:
'bar'
}]
}
chart
.
setOption
(
option
)
},
// 图标调整大小方法
chartsResizeFn
()
{
Object
.
values
(
this
.
chartsProject
).
forEach
(
chart
=>
{
chart
&&
chart
.
resize
()
})
},
// 下拉框改变事件
async
selChangeHandle
(
el
,
type
)
{
this
.
loading
=
true
if
(
type
==
'今日产品缺陷走势图'
)
{
await
this
.
getProductDefectChartListApi
().
then
(
result
=>
{
if
(
this
.
$refs
.
defectTrendRef
)
this
.
initDefectTrendChart
()
}).
catch
(
error
=>
{
this
.
$message
.
error
(
'获取今日产品缺陷走势图数据失败'
)
})
}
else
if
(
type
==
'近一周生产中断等待时长统计'
||
type
==
'近一周设备故障频频率'
)
{
await
this
.
getChartDataApi
().
then
(
result
=>
{
if
(
type
==
'近一周生产中断等待时长统计'
)
{
if
(
this
.
$refs
.
weeklyWaitTimeRef
)
this
.
initWeeklyWaitTimeChart
()
}
else
if
(
type
==
'近一周设备故障频频率'
)
{
if
(
this
.
$refs
.
deviceFailureRef
)
this
.
initDeviceFailureChart
()
}
}).
catch
(
error
=>
{
this
.
$message
.
error
(
`获取
${
type
}
数据失败`
)
})
}
this
.
loading
=
false
},
beforeDestroy
()
{
window
.
removeEventListener
(
"resize"
,
this
.
resizeHandle
)
// 销毁所有图表实例
Object
.
values
(
this
.
chartsProject
).
forEach
(
chart
=>
{
chart
&&
chart
.
dispose
()
})
if
(
this
.
animationFrameId
)
{
window
.
cancelAnimationFrame
(
this
.
animationFrameId
)
}
}
}
})
</script>
<style>
*
{
margin
:
0
;
padding
:
0
;
box-sizing
:
border-box
;
}
::-webkit-scrollbar
{
width
:
6px
;
height
:
6px
;
}
::-webkit-scrollbar-thumb
{
background
:
#c0c4cc
;
border-radius
:
3px
;
}
::-webkit-scrollbar-track
{
background
:
#f5f7fa
;
}
#app
{
display
:
flex
;
align-content
:
space-between
;
flex-wrap
:
wrap
;
width
:
100vw
;
height
:
100vh
;
padding
:
8px
;
background-color
:
#06114f
;
gap
:
8px
;
.panelCardBox
{
width
:
calc
((
100%
-
16px
)
/
3
);
height
:
calc
(
50%
-
4px
);
border-radius
:
4px
;
padding
:
8px
;
border
:
1px
solid
#7c808d
;
.panelHeaderBox
{
font-size
:
14px
;
font-weight
:
bold
;
color
:
#FFF
;
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
min-height
:
28px
;
position
:
relative
;
.text
{
font-weight
:
400
;
border-bottom
:
1px
solid
#303133
;
}
.unitBox
{
position
:
absolute
;
width
:
100%
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
z-index
:
2
;
bottom
:
-28px
;
font-size
:
12px
;
color
:
#ccc
;
font-weight
:
400
;
}
}
.panelTableBox
{
height
:
calc
(
100%
-
28px
);
width
:
100%
;
overflow-y
:
hidden
;
position
:
relative
;
border
:
1px
solid
#7c808d
;
font-size
:
13px
;
border-radius
:
2px
;
.panelTableHeader
{
border-bottom
:
1px
solid
#7c808d
;
position
:
relative
;
z-index
:
2
;
.roll_line
{
font-weight
:
bold
;
color
:
#FFF
;
border-bottom
:
none
;
}
.roll_line
:hover
{
background-color
:
transparent
!important
;
cursor
:
default
!important
;
}
}
.vabsolute_content
{
width
:
100%
;
position
:
absolute
;
top
:
40px
;
transform
:
translateY
(
0
);
will-change
:
transform
;
height
:
100%
;
}
.roll_line
{
display
:
flex
;
padding
:
0
8px
;
box-sizing
:
border-box
;
width
:
100%
;
height
:
40px
;
color
:
#FFF
;
align-items
:
center
;
border-bottom
:
1px
solid
#7c808d
;
transition
:
background-color
0.25s
ease
;
gap
:
8px
;
}
.roll_line
:hover
{
background-color
:
rgba
(
255
,
255
,
255
,
0.2
);
cursor
:
pointer
;
}
.roll_line
>
div
{
flex
:
1
;
}
.roll_line
>
div
:nth-child
(
4
),
.roll_line
>
div
:nth-child
(
5
)
{
min-width
:
65px
;
max-width
:
65px
;
}
}
.handleStatuBox
{
display
:
flex
;
align-content
:
flex-start
;
flex-wrap
:
wrap
;
gap
:
8px
;
margin-top
:
8px
;
min-height
:
calc
(
100%
-
28px
-
8px
);
overflow-y
:
auto
;
.statuItemBox
{
width
:
calc
((
100%
-
24px
)
/
3
);
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
gap
:
3px
;
.number
{
width
:
40px
;
height
:
40px
;
border-radius
:
50%
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
color
:
white
;
font-weight
:
bold
;
}
.label
{
font-size
:
12px
;
color
:
#FFF
;
}
}
}
.emptyBox
{
width
:
100%
;
height
:
calc
(
100%
-
28px
);
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
.el-empty
{
padding
:
0
}
}
}
}
</style>
</html>
\ No newline at end of file
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