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
20663031
Commit
20663031
authored
May 28, 2025
by
guibin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
更新设备实时状态页面
parent
6b689f4b
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
1314 additions
and
0 deletions
+1314
-0
qdry_project/static/src/view/equipment_status_qdry.html
+1314
-0
No files found.
qdry_project/static/src/view/equipment_status_qdry.html
0 → 100644
View file @
20663031
<!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/routing -->
<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-loading
.
body
.
fullscreen
.
lock=
"loading"
ref=
"fullScreenElement"
>
<!-- 页面标题 -->
<div
class=
"dashboard-header"
>
<div
class=
"header-content"
>
<img
src=
"/roke_workstation_api/static/html/routing/image/header_bg.png"
class=
"header-bg"
alt=
"header background"
/>
<span
class=
"header-text"
>
设备实时看板
</span>
<span
class=
"hintText"
>
<i
style=
"font-size: 30px"
@
click=
"toggleFullscreen"
:class=
"isFullscreen ? 'el-icon-close' : 'el-icon-full-screen'"
></i>
<span>
[[currentTime ]]
</span>
</span>
</div>
</div>
<!-- 设备状态卡片区域 - 新设计 -->
<div
v-for=
"category in deviceList"
:key=
"category.category_name"
>
<div
class=
"device-category-name-container"
>
[[category.category_name]]
</div>
<div
class=
"device-cards-container"
>
<div
v-for=
"(device, index) in category.data"
:key=
"index"
class=
"device-card new-design"
:class=
"getCardClass(device.status)"
>
<!-- 设备名称 - 左上角 -->
<div
class=
"device-name"
>
[[truncateText(device.name, 12)]]
</div>
<!-- 设备状态 - 右上角 -->
<div
class=
"device-status-tag"
:class=
"getStatusClass(device.status)"
>
[[getStatusText(device.status)]]
</div>
<!-- 设备状态水波纹 - 中间 -->
<div
class=
"device-wave-container"
>
<!-- v-if="device.status === 'running' || device.status === 'waiting'" -->
<div
class=
"oee-text"
>
OEE
</div>
<!-- <div class="err-text" v-if="device.status === 'error'" :class="getErrClass(device.status)">
ERR
</div>
<div class="off-text" v-if="device.status === 'off'" :class="getOffClass(device.status)">
OFF
</div> -->
<!-- v-if="device.status === 'running' || device.status === 'waiting'" -->
<div
class=
"percentage-text"
>
[[ device.percentage > 100 ? 100 : device.percentage ]]%
</div>
<!-- 圆形容器 -->
<div
class=
"circle-container"
:class=
"getBorderClass(device.status)"
>
<!-- 水波纹效果 - 通过内联样式直接控制高度百分比 -->
<div
class=
"water-wave"
:class=
"getWaveClass(device.status)"
:style=
"{
'height': getWaveHeight(device.status, device.percentage) + '%',
'bottom': '0',
'left': '0',
'position': 'absolute',
'width': '100%',
'overflow': 'hidden'
}"
>
<div
class=
"water-wave-content"
>
<div
class=
"water-wave-ripple"
></div>
</div>
</div>
</div>
</div>
<!-- 设备状态信息 - 底部 -->
<div
class=
"device-status-info"
>
<span>
已持续 [[device.duration]]
</span>
</div>
<div
style=
"width: 100%; display: flex;margin-left: 13px;"
>
<div
class=
"flxe_sty"
>
<div
class=
"flxe_label_sty"
style=
" background-color: #00aa00;"
>
开工
</div>
[[ device.run_seconds ]]
</div>
<div
class=
"flxe_sty"
>
<div
class=
"flxe_label_sty"
style=
" background-color: #ffaa00;"
>
加工
</div>
[[ device.green_seconds ]]
</div>
</div>
<div
style=
"width: 100%;display: flex;margin-left: 13px;"
>
<div
class=
"flxe_sty"
>
<div
class=
"flxe_label_sty"
style=
" background-color: #797979;"
>
空闲
</div>
[[ device.yellow_seconds ]]
</div>
<div
class=
"flxe_sty"
>
<div
class=
"flxe_label_sty"
style=
" background-color: #f87171;"
>
故障
</div>
[[ device.red_seconds ]]
</div>
</div>
</div>
</div>
</div>
<!-- 底部状态图 -->
<div
class=
"status-chart glass-effect"
v-if=
"false"
>
<!-- 这部分保留但不显示,如果将来需要可以启用 -->
<div
class=
"section-title"
>
<span
class=
"title-text"
>
设备运行状态统计
</span>
<div
class=
"title-decoration"
></div>
</div>
<div
class=
"chart-header"
>
<el-select
v-model=
"selectedDevice"
placeholder=
"请选择设备"
size=
"medium"
@
change=
"selDeviceChange"
>
<el-option
v-for=
"item in deviceList"
:label=
"item.name"
:value=
"item.id"
:key=
"item.id"
>
</el-option>
</el-select>
<h4>
近10天设备运行状态统计
</h4>
</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"
>
h
</span>
</span>
</div>
<!-- 图表主体区域 -->
<div
class=
"chart-content"
>
<!-- 网格区域 -->
<div
class=
"grid-area"
>
<!-- 背景网格 -->
<div
class=
""
>
<div
class=
"horizontal-lines"
>
<div
class=
"horizontal-line"
v-for=
"(value, index) in yAxisValues"
:key=
"index"
></div>
</div>
</div>
<!-- 柱状图组 -->
<div
class=
"columns-container"
>
<div
class=
"column-group"
v-for=
"(item, dayIndex) in pickingOrderList"
:key=
"dayIndex"
>
<div
class=
"column-stack"
>
<div
v-for=
"(segment, stackIndex) in item.data"
:key=
"stackIndex"
class=
"column-segment"
:style=
"{
'height': calculateHeight(segment.value) + '%',
'background-color': segment.color,
'margin-top': '0px'
}"
></div>
</div>
</div>
</div>
</div>
</div>
<!-- X轴 -->
<div
class=
"x-axis"
>
<span
v-for=
"(item, index) in pickingOrderList"
:key=
"index"
class=
"x-axis-label"
>
[[item.name_format]]
</span>
</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
{
isFullscreen
:
false
,
// 全屏状态
currentTime
:
null
,
// 当前时间
timer
:
null
,
// 定时器
windowHeight
:
window
.
innerHeight
,
// 窗口高度
// dwsURL: "https://workstation.rokeris.com", // 基地址
dwsURL
:
""
,
// 基地址
baseURL
:
"https://dws-platform.xbg.rokeris.com/dev-api"
,
// 基地址
loading
:
false
,
// 全局加载效果
deviceList
:
[],
// 设备列表
selectedDevice
:
null
,
// 选中的设备
yAxisValues
:
[
"24"
,
"20"
,
"16"
,
"12"
,
"8"
,
"4"
,
"0"
],
// Y轴刻度值
pickingOrderList
:
[],
// 拣选单列表
dateList
:
[],
// 日期列表
start_time
:
""
,
// 开始时间
end_time
:
""
,
// 结束时间
refreshInterval
:
null
,
// 定时刷新计时器
factoryCode
:
""
,
// 公司CODE
allEquipmentData
:
[],
// 所有已绑定设备数据
};
},
created
()
{
if
(
this
.
getUrlSearch
(
"factory_code"
))
{
this
.
factoryCode
=
this
.
getUrlSearch
(
"factory_code"
);
//截取url后面的参数
}
this
.
initCurrentTimeFn
()
},
computed
:
{
// 选中设备的信息
selDeviceInfo
()
{
return
this
.
deviceList
.
find
((
item
)
=>
item
.
id
==
this
.
selectedDevice
);
},
},
async
mounted
()
{
window
.
addEventListener
(
"resize"
,
this
.
handleResize
);
this
.
$nextTick
(()
=>
{
document
.
getElementById
(
"bodyId"
).
style
.
display
=
"block"
;
});
// // 先加载测试数据以便查看效果
// this.initMockData();
// 初始化数据 - 实际使用时取消这行的注释
await
this
.
initData
();
// 获取最后指定天数的日期
this
.
dateList
=
this
.
getLastAssignDays
();
// 设置定时刷新(每分钟刷新一次,静默模式)
this
.
refreshInterval
=
setInterval
(()
=>
{
this
.
initData
(
true
);
// 传入true表示静默刷新,不显示加载提示
},
60000
);
// 设置设备信息的title属性
this
.
$nextTick
(()
=>
{
document
.
querySelectorAll
(
".device-info"
).
forEach
((
el
)
=>
{
el
.
title
=
el
.
dataset
.
fullTitle
;
});
});
},
updated
()
{
// 在数据更新后也设置title属性
this
.
$nextTick
(()
=>
{
document
.
querySelectorAll
(
".device-info"
).
forEach
((
el
)
=>
{
el
.
title
=
el
.
dataset
.
fullTitle
;
});
});
},
beforeDestroy
()
{
// 清除定时器
if
(
this
.
refreshInterval
)
{
clearInterval
(
this
.
refreshInterval
);
}
if
(
this
.
timer
)
{
clearInterval
(
this
.
timer
)
}
// 移除事件监听
window
.
removeEventListener
(
"resize"
,
this
.
handleResize
);
},
methods
:
{
// 全屏icon点击事件
toggleFullscreen
:
function
()
{
if
(
!
this
.
isFullscreen
)
{
this
.
enterFullScreen
();
}
else
{
this
.
exitFullScreen
();
}
},
// 全屏方法
enterFullScreen
:
function
()
{
// 获取需要全屏的元素
var
elem
=
this
.
$refs
.
fullScreenElement
;
if
(
elem
&&
elem
.
requestFullscreen
)
{
elem
.
requestFullscreen
();
}
else
if
(
elem
&&
elem
.
mozRequestFullScreen
)
{
// Firefox
elem
.
mozRequestFullScreen
();
}
else
if
(
elem
&&
elem
.
webkitRequestFullscreen
)
{
// Chrome, Safari & Opera
elem
.
webkitRequestFullscreen
();
}
else
if
(
elem
&&
elem
.
msRequestFullscreen
)
{
// IE/Edge
elem
.
msRequestFullscreen
();
}
this
.
isFullscreen
=
true
;
},
exitFullScreen
:
function
()
{
if
(
document
.
exitFullscreen
)
{
document
.
exitFullscreen
();
}
else
if
(
document
.
mozCancelFullScreen
)
{
// Firefox
document
.
mozCancelFullScreen
();
}
else
if
(
document
.
webkitExitFullscreen
)
{
// Chrome, Safari and Opera
document
.
webkitExitFullscreen
();
}
else
if
(
document
.
msExitFullscreen
)
{
// IE/Edge
document
.
msExitFullscreen
();
}
this
.
isFullscreen
=
false
;
},
// 初始化当前时间
initCurrentTimeFn
()
{
this
.
timer
=
setInterval
(()
=>
{
const
now
=
new
Date
();
const
year
=
now
.
getFullYear
();
const
month
=
String
(
now
.
getMonth
()
+
1
).
padStart
(
2
,
'0'
);
const
day
=
String
(
now
.
getDate
()).
padStart
(
2
,
'0'
);
const
hours
=
String
(
now
.
getHours
()).
padStart
(
2
,
'0'
);
const
minutes
=
String
(
now
.
getMinutes
()).
padStart
(
2
,
'0'
);
const
seconds
=
String
(
now
.
getSeconds
()).
padStart
(
2
,
'0'
);
this
.
currentTime
=
`
${
year
}
-
${
month
}
-
${
day
}
${
hours
}
:
${
minutes
}
:
${
seconds
}
`
},
1000
)
},
// 通过网址跳转过来的页面,截取后面的参数
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
];
},
// 初始化数据
async
initData
(
silent
=
false
)
{
try
{
// 只有在非静默模式下才显示加载提示
if
(
!
silent
)
{
this
.
loading
=
true
;
}
// 并行请求设备计划运行时间和所有已绑定设备数据
const
[
planTimeResult
,
allEquipmentResult
]
=
await
Promise
.
all
([
this
.
getDevicePlanTime
(),
this
.
getAllEquipmentData
(),
]);
}
catch
(
error
)
{
// console.error("初始化数据出错:", error);
// 只有在非静默模式下才显示错误提示
if
(
!
silent
)
{
this
.
$message
.
error
(
"初始化数据出错: "
+
(
error
.
message
||
"未知错误"
));
}
// 如果接口请求失败,使用模拟数据
this
.
initMockData
();
}
finally
{
// 关闭加载提示
if
(
!
silent
)
{
this
.
loading
=
false
;
}
}
},
// 获取设备计划运行时间
async
getDevicePlanTime
()
{
try
{
// 发送请求获取计划运行时间
const
response
=
await
axios
({
method
:
"post"
,
url
:
this
.
dwsURL
+
"/roke/workstation/equipment/get_plan_time"
,
data
:
{},
headers
:
{
"Content-Type"
:
"application/json"
},
});
// 处理JSON-RPC格式的响应
if
(
response
.
data
&&
response
.
data
.
result
&&
response
.
data
.
result
.
code
===
0
)
{
// 获取计划运行时间数据
const
planTimeList
=
response
.
data
.
result
.
data
||
{}
if
(
this
.
factoryCode
)
{
// 调用设备状态接口
await
this
.
getDeviceStateList
(
planTimeList
);
}
else
{
await
this
.
getFactoryCode
(
planTimeList
);
}
return
planTimeList
;
}
else
{
const
errorMsg
=
response
.
data
.
result
?
response
.
data
.
result
.
message
:
"获取计划运行时间失败"
;
throw
new
Error
(
errorMsg
);
}
}
catch
(
error
)
{
// console.error("获取计划运行时间出错:", error);
throw
error
;
}
},
// 获取所有已绑定设备数据
async
getAllEquipmentData
()
{
try
{
// 发送请求获取所有已绑定设备数据
const
response
=
await
axios
({
method
:
"post"
,
url
:
this
.
dwsURL
+
"/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
)
{
// 存储所有设备数据
this
.
allEquipmentData
=
response
.
data
.
result
.
data
||
[]
return
this
.
allEquipmentData
;
}
else
{
const
errorMsg
=
response
.
data
.
result
?
response
.
data
.
result
.
message
:
"获取所有已绑定设备数据失败"
;
throw
new
Error
(
errorMsg
);
}
}
catch
(
error
)
{
// console.error("获取所有已绑定设备数据出错:", error);
throw
error
;
}
},
// 获取设备状态列表
async
getDeviceStateList
(
planTimeList
)
{
try
{
// 使用CORS代理
// 发送请求获取设备状态
const
response
=
await
axios
({
method
:
"POST"
,
url
:
this
.
baseURL
+
"/public/device_efficiency/device_state_list"
,
data
:
{
factory_code
:
this
.
factoryCode
,
plan_time_list
:
planTimeList
,
},
headers
:
{
"Content-Type"
:
"application/json"
,
},
});
// 处理响应
if
(
response
.
data
&&
response
.
data
.
success
)
{
// 处理设备状态数据
this
.
processDeviceStateData
(
response
.
data
.
data
);
}
else
{
throw
new
Error
(
response
.
data
.
msg
||
"获取设备状态失败"
);
}
}
catch
(
error
)
{
// console.error("获取设备状态出错:", error);
throw
error
;
}
},
// 获取factoryCode
async
getFactoryCode
(
planTimeList
)
{
try
{
// 使用CORS代理
// 发送请求获取设备状态
const
response
=
await
axios
({
method
:
"post"
,
url
:
this
.
dwsURL
+
"/roke/workstation/db_uuid/get"
,
data
:
{},
headers
:
{
"Content-Type"
:
"application/json"
},
});
// 处理JSON-RPC格式的响应
if
(
response
.
data
&&
response
.
data
.
result
&&
response
.
data
.
result
.
code
===
0
)
{
// 获取计划运行时间数据
this
.
factoryCode
=
response
.
data
.
result
.
data
||
""
;
// 调用设备状态接口
await
this
.
getDeviceStateList
(
planTimeList
);
}
else
{
const
errorMsg
=
response
.
data
.
result
?
response
.
data
.
result
.
message
:
"获取账套db_uuid失败"
;
throw
new
Error
(
errorMsg
);
}
}
catch
(
error
)
{
// console.error("获取账套db_uuid失败:", error);
throw
error
;
}
},
// 处理设备状态数据
processDeviceStateData
(
deviceStateData
)
{
if
(
!
deviceStateData
||
!
Array
.
isArray
(
deviceStateData
))
{
return
;
}
// 将API返回的数据转换为页面所需的格式
let
list
=
deviceStateData
.
map
((
device
)
=>
{
// 根据API返回的状态确定前端显示的状态
let
status
=
"off"
;
if
(
device
.
state
===
"green"
)
{
status
=
"running"
;
}
else
if
(
device
.
state
===
"yellow"
)
{
status
=
"waiting"
;
}
else
if
(
device
.
state
===
"red"
)
{
status
=
"error"
;
}
else
if
(
device
.
state
===
"gray"
)
{
status
=
"off"
;
}
// 计算持续时间的显示格式
let
durationText
=
"0"
if
(
device
.
duration_hours
!==
undefined
)
{
durationText
=
this
.
formatTime
(
Number
(
device
.
duration_hours
*
3600
))
}
let
run_seconds
=
"0"
if
(
device
.
run_seconds
!==
undefined
)
run_seconds
=
this
.
formatTime
(
Number
(
device
.
run_seconds
))
let
green_seconds
=
"0"
if
(
device
.
green_seconds
!==
undefined
)
green_seconds
=
this
.
formatTime
(
Number
(
device
.
green_seconds
))
let
yellow_seconds
=
"0"
if
(
device
.
yellow_seconds
!==
undefined
)
yellow_seconds
=
this
.
formatTime
(
Number
(
device
.
yellow_seconds
))
let
red_seconds
=
"0"
if
(
device
.
red_seconds
!==
undefined
)
yellow_seconds
=
this
.
formatTime
(
Number
(
device
.
yellow_seconds
))
// 计算利用率百分比,确保有效值
const
percentage
=
device
.
utilization_rate
!==
undefined
?
Math
.
round
(
device
.
utilization_rate
)
:
0
// 从所有设备列表中获取准确的设备名称
let
deviceName
=
device
.
name
||
device
.
code
// 默认使用API返回的名称或编码
let
categoryName
=
null
// 在所有设备列表中查找匹配的设备
if
(
this
.
allEquipmentData
&&
this
.
allEquipmentData
.
length
>
0
)
{
const
matchedDevice
=
this
.
allEquipmentData
.
find
(
(
equip
)
=>
equip
.
code
===
device
.
code
)
// 如果找到匹配的设备,使用其名称
if
(
matchedDevice
&&
matchedDevice
.
name
)
{
deviceName
=
device
.
name
?
matchedDevice
.
name
:
device
.
code
categoryName
=
matchedDevice
.
category_name
}
}
return
{
id
:
device
.
code
,
code
:
device
.
code
,
name
:
deviceName
,
status
:
status
,
percentage
:
percentage
,
duration
:
durationText
,
run_seconds
:
run_seconds
,
green_seconds
:
green_seconds
,
yellow_seconds
:
yellow_seconds
,
red_seconds
:
red_seconds
,
category_name
:
categoryName
}
});
// 按category_name分组并组装成指定结构
const
grouped
=
{};
list
.
forEach
(
item
=>
{
const
cname
=
item
.
category_name
||
'未分类'
;
if
(
!
grouped
[
cname
])
grouped
[
cname
]
=
[];
grouped
[
cname
].
push
(
item
);
});
// 组装成 [{category_name:'', data:[]}, ...] 并排序
const
result
=
Object
.
keys
(
grouped
).
map
(
category_name
=>
{
// 每组内部按percentage降序排序
const
data
=
grouped
[
category_name
].
sort
((
a
,
b
)
=>
b
.
percentage
-
a
.
percentage
);
return
{
category_name
,
data
};
});
console
.
log
(
result
)
this
.
deviceList
=
result
},
formatTime
(
seconds
)
{
if
(
seconds
<
60
)
{
return
`0min`
;
// 不足 1 分钟显示 0min
}
else
if
(
seconds
<
3600
)
{
const
minutes
=
Math
.
floor
(
seconds
/
60
);
// 转换为分钟
return
`
${
minutes
}
min`
;
// 显示分钟
}
else
if
(
seconds
<
86400
)
{
// 小于 1 天
const
hours
=
Math
.
floor
(
seconds
/
3600
);
// 转换为小时
return
`
${
hours
}
h`
;
// 只返回小时
}
else
{
const
days
=
Math
.
floor
(
seconds
/
86400
);
// 转换为天数
return
`
${
days
}
d`
;
// 只返回天
}
},
// 初始化模拟数据 (保留原有的模拟数据方法,作为备用)
initMockData
()
{
// 模拟设备数据 - 添加不同状态和明显不同百分比的测试数据
this
.
deviceList
=
[
// {
// id: "device001",
// code: "device001",
// name: "设备名称1",
// status: "running", // 运行中
// percentage: 90, // 非常高的百分比
// duration: "2h15m",
// },
// {
// id: "device002",
// code: "device002",
// name: "设备名称2",
// status: "error", // 故障中
// percentage: 50, // 中等百分比
// duration: "4h30m",
// },
// {
// id: "device003",
// code: "device003",
// name: "设备名称3",
// status: "waiting", // 等待中
// percentage: 20, // 非常低的百分比
// duration: "1h45m",
// },
// {
// id: "device004",
// code: "device004",
// name: "设备名称4",
// status: "off", // 停机中
// percentage: 50, // 中等百分比
// duration: "8h20m",
// },
];
// 添加测试日志
console
.
log
(
"测试数据初始化完成,设备列表:"
,
this
.
deviceList
);
},
// 获取卡片的CSS类名
getCardClass
(
status
)
{
switch
(
status
)
{
case
"running"
:
return
"card-running"
;
case
"waiting"
:
return
"card-waiting"
;
case
"error"
:
return
"card-error"
;
case
"off"
:
default
:
return
"card-off"
;
}
},
// 获取边框的CSS类名
getBorderClass
(
status
)
{
switch
(
status
)
{
case
"running"
:
return
"border-running"
;
case
"waiting"
:
return
"border-waiting"
;
case
"error"
:
return
"border-error"
;
case
"off"
:
default
:
return
"border-off"
;
}
},
// 获取设备状态对应的CSS类名
getStatusClass
(
status
)
{
switch
(
status
)
{
case
"running"
:
return
"status-running"
;
case
"waiting"
:
return
"status-waiting"
;
case
"error"
:
return
"status-error"
;
case
"off"
:
default
:
return
"status-off"
;
}
},
// 获取波浪效果的类名
getWaveClass
(
status
)
{
switch
(
status
)
{
case
"running"
:
return
"wave-running"
;
case
"waiting"
:
return
"wave-waiting"
;
case
"error"
:
return
"wave-error"
;
case
"off"
:
default
:
return
"wave-off"
;
}
},
// 获取波浪高度
getWaveHeight
(
status
,
percentage
)
{
// 将百分比限制在10%-100%之间,确保即使是低百分比也有一定的水位可见
let
height
=
percentage
;
// 如果是故障或停机状态,固定显示50%
// if (status === "error" || status === "off") {
// height = 50;
// }
// 确保最小高度为10%,最大为100%
height
=
Math
.
min
(
Math
.
max
(
height
,
10
),
100
);
return
height
;
},
// 获取设备状态对应的文本
getStatusText
(
status
)
{
switch
(
status
)
{
case
"running"
:
return
"运行中"
;
case
"waiting"
:
return
"等待中"
;
case
"error"
:
return
"故障中"
;
case
"off"
:
default
:
return
"停机中"
;
}
},
// 处理小数位数方法
toFixedHandle
(
value
,
num
=
4
)
{
if
(
value
)
{
let
strValue
=
String
(
value
);
if
(
strValue
.
split
(
"."
).
length
>
1
||
strValue
.
split
(
"."
)[
1
]?.
length
>
num
)
{
strValue
=
Number
(
strValue
).
toFixed
(
num
);
}
return
Number
(
strValue
);
}
else
{
return
0
;
}
},
// 计算高度百分比
calculateHeight
(
hours
)
{
// 24小时对应整个高度(100%)
// 每4小时对应一个刻度区间,总共6个区间
// 计算每小时占的百分比:100% / 24 ≈ 4.167%
const
heightPerHour
=
100
/
24
;
const
percentage
=
hours
*
heightPerHour
;
// 确保高度在0-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
;
},
// 处理窗口大小变化
handleResize
()
{
// 窗口大小变化时的处理逻辑
},
// 文本截断方法
truncateText
(
text
,
maxLength
)
{
if
(
!
text
)
return
""
;
if
(
text
.
length
<=
maxLength
)
return
text
;
return
text
.
substring
(
0
,
maxLength
)
+
"..."
;
},
// 显示完整标题
showFullTitle
(
event
,
device
)
{
const
fullTitle
=
device
.
name
+
" | "
+
device
.
code
;
event
.
target
.
setAttribute
(
"title"
,
fullTitle
);
},
// 获取ERR文字的CSS类名
getErrClass
(
status
)
{
if
(
status
===
"error"
)
{
return
"err-error"
;
}
return
""
;
},
// 获取OFF文字的CSS类名
getOffClass
(
status
)
{
if
(
status
===
"off"
)
{
return
"off-status"
;
}
return
""
;
},
},
});
</script>
<style>
/* 基础样式 */
*
{
margin
:
0
;
padding
:
0
;
box-sizing
:
border-box
;
}
body
{
color
:
#fff
;
overflow
:
hidden
;
}
#app
{
width
:
100vw
;
height
:
100vh
;
padding
:
20px
;
display
:
flex
;
flex-direction
:
column
;
gap
:
10px
;
overflow-y
:
auto
;
background
:
url("/roke_workstation_api/static/html/routing/image/bg-ok.png")
no-repeat
center
center
fixed
;
background-size
:
cover
;
}
/* 玻璃态效果 */
.glass-effect
{
background
:
rgba
(
22
,
41
,
60
,
0.8
);
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
);
}
/* 标题样式 */
.dashboard-header
{
width
:
100vw
;
margin
:
-20px
-20px
10px
-20px
;
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
}
.header-content
{
width
:
100%
;
position
:
relative
;
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
}
.header-bg
{
width
:
100%
;
height
:
auto
;
}
.header-text
{
position
:
absolute
;
font-size
:
28px
;
font-weight
:
450
;
color
:
#fff
;
text-shadow
:
0
0
10px
rgba
(
0
,
195
,
255
,
0.5
);
z-index
:
1
;
}
.hintText
{
position
:
absolute
;
font-size
:
14px
;
bottom
:
-5px
;
right
:
30px
;
color
:
rgba
(
255
,
255
,
255
,
0.85
);
display
:
-webkit-box
;
display
:
-webkit-flex
;
display
:
-moz-box
;
display
:
-ms-flexbox
;
display
:
flex
;
-webkit-box-align
:
center
;
-webkit-align-items
:
center
;
-moz-align-items
:
center
;
-ms-flex-align
:
center
;
align-items
:
center
;
}
.hintText
>*
{
margin-left
:
10px
;
}
.hintText
>*
:first-child
{
margin-left
:
0
;
}
.device-category-name-container
{
width
:
100%
;
padding
:
0px
15px
;
font-weight
:
bold
;
font-size
:
20px
;
}
/* 设备卡片容器 */
.device-cards-container
{
display
:
grid
;
grid-template-columns
:
repeat
(
auto-fill
,
minmax
(
180px
,
1
fr
));
gap
:
20px
;
padding
:
15px
;
}
/* 新设计设备卡片样式 */
.device-card.new-design
{
position
:
relative
;
padding
:
5px
10px
10px
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
min-height
:
180px
;
border-radius
:
12px
;
transition
:
all
0.3s
ease
;
background-color
:
rgba
(
20
,
22
,
28
,
0.95
);
/* 更深的背景色 */
}
/* 卡片颜色和阴影 - 调整为适度柔和的阴影效果 */
.card-running
{
border
:
none
;
box-shadow
:
0
0
0
1px
rgba
(
78
,
198
,
138
,
0.5
),
5px
5px
2px
0
rgba
(
78
,
198
,
138
,
0.7
),
5px
5px
15px
2px
rgba
(
78
,
198
,
138
,
0.5
);
}
.card-waiting
{
border
:
none
;
box-shadow
:
0
0
0
1px
rgba
(
235
,
186
,
22
,
0.5
),
5px
5px
2px
0
rgba
(
235
,
186
,
22
,
0.7
),
5px
5px
15px
2px
rgba
(
235
,
186
,
22
,
0.5
);
}
.card-error
{
border
:
none
;
box-shadow
:
0
0
0
1px
rgba
(
235
,
86
,
86
,
0.5
),
5px
5px
2px
0
rgba
(
235
,
86
,
86
,
0.7
),
5px
5px
15px
2px
rgba
(
235
,
86
,
86
,
0.5
);
}
.card-off
{
border
:
none
;
box-shadow
:
0
0
0
1px
rgba
(
144
,
147
,
153
,
0.5
),
5px
5px
2px
0
rgba
(
144
,
147
,
153
,
0.7
),
5px
5px
15px
2px
rgba
(
144
,
147
,
153
,
0.5
);
}
.device-card.new-design
:hover
{
transform
:
translateY
(
-3px
);
box-shadow
:
0
0
0
1px
rgba
(
78
,
198
,
138
,
0.5
),
7px
7px
4px
0
rgba
(
78
,
198
,
138
,
0.7
),
7px
7px
20px
2px
rgba
(
78
,
198
,
138
,
0.6
);
}
/* 设备名称 - 左上角 */
.device-name
{
align-self
:
flex-start
;
font-size
:
14px
;
font-weight
:
bold
;
color
:
#fff
;
max-width
:
100%
;
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
text-shadow
:
0
1px
2px
rgba
(
0
,
0
,
0
,
0.5
);
margin-bottom
:
5px
;
}
/* 设备状态标签 - 右上角 */
.device-status-tag
{
position
:
absolute
;
top
:
25px
;
right
:
5px
;
padding
:
2px
5px
5px
;
border-radius
:
4px
;
font-size
:
12px
;
font-weight
:
bold
;
color
:
#fff
;
min-width
:
50px
;
text-align
:
center
;
}
/* 移除状态标签的阴影 */
.status-running
{
background-color
:
rgba
(
78
,
198
,
138
,
0.9
);
box-shadow
:
none
;
}
.status-waiting
{
background-color
:
rgba
(
235
,
186
,
22
,
0.9
);
box-shadow
:
none
;
}
.status-error
{
background-color
:
rgba
(
235
,
86
,
86
,
0.9
);
box-shadow
:
none
;
}
.status-off
{
background-color
:
rgba
(
144
,
147
,
153
,
0.9
);
box-shadow
:
none
;
}
/* 设备波纹容器 */
.device-wave-container
{
width
:
100%
;
height
:
120px
;
display
:
flex
;
flex-direction
:
column
;
justify-content
:
center
;
align-items
:
center
;
position
:
relative
;
}
/* OEE文字 */
.oee-text
{
position
:
absolute
;
top
:
30px
;
font-size
:
16px
;
font-weight
:
bold
;
color
:
#fff
;
z-index
:
3
;
text-shadow
:
0
2px
4px
rgba
(
0
,
0
,
0
,
0.5
);
}
/* ERR文字 */
.err-text
{
position
:
absolute
;
top
:
30px
;
font-size
:
16px
;
font-weight
:
bold
;
z-index
:
3
;
text-shadow
:
0
2px
4px
rgba
(
0
,
0
,
0
,
0.5
);
left
:
50%
;
transform
:
translateX
(
-50%
);
}
/* ERR颜色 */
.err-error
{
color
:
rgba
(
235
,
86
,
86
,
0.9
);
}
/* OFF文字 */
.off-text
{
position
:
absolute
;
top
:
30px
;
font-size
:
16px
;
font-weight
:
bold
;
z-index
:
3
;
text-shadow
:
0
2px
4px
rgba
(
0
,
0
,
0
,
0.5
);
left
:
50%
;
transform
:
translateX
(
-50%
);
}
/* OFF颜色 */
.off-status
{
color
:
rgba
(
144
,
147
,
153
,
0.9
);
}
/* 百分比文字 */
.percentage-text
{
position
:
absolute
;
top
:
50px
;
font-size
:
25px
;
font-weight
:
bold
;
color
:
#fff
;
z-index
:
3
;
text-shadow
:
0
2px
4px
rgba
(
0
,
0
,
0
,
0.5
);
}
/* 圆形容器 */
.circle-container
{
width
:
100px
;
height
:
100px
;
border-radius
:
50%
;
overflow
:
hidden
;
position
:
relative
;
background-color
:
rgba
(
10
,
12
,
15
,
0.8
);
}
/* 移除圆形容器的阴影 */
.border-running
{
border
:
2px
solid
rgba
(
78
,
198
,
138
,
0.9
);
box-shadow
:
none
;
}
.border-waiting
{
border
:
2px
solid
rgba
(
235
,
186
,
22
,
0.9
);
box-shadow
:
none
;
}
.border-error
{
border
:
2px
solid
rgba
(
235
,
86
,
86
,
0.9
);
box-shadow
:
none
;
}
.border-off
{
border
:
2px
solid
rgba
(
144
,
147
,
153
,
0.9
);
box-shadow
:
none
;
}
/* 水波纹 */
.water-wave
{
position
:
absolute
;
bottom
:
0
;
left
:
0
;
width
:
100%
;
transition
:
height
0.7s
ease
;
overflow
:
hidden
;
border-radius
:
0
0
50px
50px
;
}
/* 水波纹颜色 */
.wave-running
{
background-color
:
rgba
(
78
,
198
,
138
,
0.7
);
}
.wave-waiting
{
background-color
:
rgba
(
235
,
186
,
22
,
0.7
);
}
.wave-error
{
background-color
:
rgba
(
235
,
86
,
86
,
0.7
);
}
.wave-off
{
background-color
:
rgba
(
144
,
147
,
153
,
0.7
);
}
/* 水波纹内容 - 设置相对定位以包含波浪元素 */
.water-wave-content
{
position
:
relative
;
width
:
100%
;
height
:
100%
;
overflow
:
hidden
;
}
/* 波浪容器 - 水平波浪效果 */
.water-wave-ripple
{
width
:
200%
;
height
:
100%
;
position
:
absolute
;
bottom
:
0
;
left
:
0
;
background
:
transparent
;
z-index
:
2
;
}
/* 波浪图形 - 使用SVG背景实现波浪形状 */
.water-wave-ripple
::before
{
content
:
""
;
position
:
absolute
;
top
:
-12px
;
left
:
0
;
width
:
100%
;
height
:
25px
;
background-image
:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 120' preserveAspectRatio='none'%3E%3Cpath fill='white' opacity='0.5' d='M0,0V46.29c47.79,22.2,103.59,32.17,158,28,70.36-5.37,136.33-33.31,206.8-37.5C438.64,32.43,512.34,53.67,583,72.05c69.27,18,138.3,24.88,209.4,13.08,36.15-6,69.85-17.84,104.45-29.34C989.49,25,1113,-14.29,1200,52.47V0Z'%3E%3C/path%3E%3C/svg%3E")
;
background-size
:
100%
100%
;
-webkit-animation
:
wave-horizontal
6s
linear
infinite
;
animation
:
wave-horizontal
6s
linear
infinite
;
}
/* 第二层波浪 - 与第一层错开,增强效果 */
.water-wave-ripple
::after
{
content
:
""
;
position
:
absolute
;
top
:
-14px
;
left
:
0
;
width
:
100%
;
height
:
28px
;
background-image
:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 120' preserveAspectRatio='none'%3E%3Cpath fill='white' opacity='0.3' d='M0,0V46.29c47.79,22.2,103.59,32.17,158,28,70.36-5.37,136.33-33.31,206.8-37.5C438.64,32.43,512.34,53.67,583,72.05c69.27,18,138.3,24.88,209.4,13.08,36.15-6,69.85-17.84,104.45-29.34C989.49,25,1113,-14.29,1200,52.47V0Z'%3E%3C/path%3E%3C/svg%3E")
;
background-size
:
100%
100%
;
-webkit-animation
:
wave-horizontal
8s
linear
infinite
;
animation
:
wave-horizontal
8s
linear
infinite
;
animation-delay
:
-2s
;
}
/* 为不同状态设置不同波浪颜色 */
.wave-running
.water-wave-ripple
::before
,
.wave-running
.water-wave-ripple
::after
{
background-image
:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 120' preserveAspectRatio='none'%3E%3Cpath fill='%234EC68A' opacity='0.5' d='M0,0V46.29c47.79,22.2,103.59,32.17,158,28,70.36-5.37,136.33-33.31,206.8-37.5C438.64,32.43,512.34,53.67,583,72.05c69.27,18,138.3,24.88,209.4,13.08,36.15-6,69.85-17.84,104.45-29.34C989.49,25,1113,-14.29,1200,52.47V0Z'%3E%3C/path%3E%3C/svg%3E")
,
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 120' preserveAspectRatio='none'%3E%3Cpath fill='%234EC68A' opacity='0.3' d='M0,0V46.29c47.79,22.2,103.59,32.17,158,28,70.36-5.37,136.33-33.31,206.8-37.5C438.64,32.43,512.34,53.67,583,72.05c69.27,18,138.3,24.88,209.4,13.08,36.15-6,69.85-17.84,104.45-29.34C989.49,25,1113,-14.29,1200,52.47V0Z'%3E%3C/path%3E%3C/svg%3E")
;
}
.wave-waiting
.water-wave-ripple
::before
,
.wave-waiting
.water-wave-ripple
::after
{
background-image
:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 120' preserveAspectRatio='none'%3E%3Cpath fill='%23EBBA16' opacity='0.5' d='M0,0V46.29c47.79,22.2,103.59,32.17,158,28,70.36-5.37,136.33-33.31,206.8-37.5C438.64,32.43,512.34,53.67,583,72.05c69.27,18,138.3,24.88,209.4,13.08,36.15-6,69.85-17.84,104.45-29.34C989.49,25,1113,-14.29,1200,52.47V0Z'%3E%3C/path%3E%3C/svg%3E")
,
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 120' preserveAspectRatio='none'%3E%3Cpath fill='%23EBBA16' opacity='0.3' d='M0,0V46.29c47.79,22.2,103.59,32.17,158,28,70.36-5.37,136.33-33.31,206.8-37.5C438.64,32.43,512.34,53.67,583,72.05c69.27,18,138.3,24.88,209.4,13.08,36.15-6,69.85-17.84,104.45-29.34C989.49,25,1113,-14.29,1200,52.47V0Z'%3E%3C/path%3E%3C/svg%3E")
;
}
.wave-error
.water-wave-ripple
::before
,
.wave-error
.water-wave-ripple
::after
{
background-image
:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 120' preserveAspectRatio='none'%3E%3Cpath fill='%23EB5656' opacity='0.5' d='M0,0V46.29c47.79,22.2,103.59,32.17,158,28,70.36-5.37,136.33-33.31,206.8-37.5C438.64,32.43,512.34,53.67,583,72.05c69.27,18,138.3,24.88,209.4,13.08,36.15-6,69.85-17.84,104.45-29.34C989.49,25,1113,-14.29,1200,52.47V0Z'%3E%3C/path%3E%3C/svg%3E")
,
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 120' preserveAspectRatio='none'%3E%3Cpath fill='%23EB5656' opacity='0.3' d='M0,0V46.29c47.79,22.2,103.59,32.17,158,28,70.36-5.37,136.33-33.31,206.8-37.5C438.64,32.43,512.34,53.67,583,72.05c69.27,18,138.3,24.88,209.4,13.08,36.15-6,69.85-17.84,104.45-29.34C989.49,25,1113,-14.29,1200,52.47V0Z'%3E%3C/path%3E%3C/svg%3E")
;
}
.wave-off
.water-wave-ripple
::before
,
.wave-off
.water-wave-ripple
::after
{
background-image
:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 120' preserveAspectRatio='none'%3E%3Cpath fill='%23909399' opacity='0.5' d='M0,0V46.29c47.79,22.2,103.59,32.17,158,28,70.36-5.37,136.33-33.31,206.8-37.5C438.64,32.43,512.34,53.67,583,72.05c69.27,18,138.3,24.88,209.4,13.08,36.15-6,69.85-17.84,104.45-29.34C989.49,25,1113,-14.29,1200,52.47V0Z'%3E%3C/path%3E%3C/svg%3E")
,
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 120' preserveAspectRatio='none'%3E%3Cpath fill='%23909399' opacity='0.3' d='M0,0V46.29c47.79,22.2,103.59,32.17,158,28,70.36-5.37,136.33-33.31,206.8-37.5C438.64,32.43,512.34,53.67,583,72.05c69.27,18,138.3,24.88,209.4,13.08,36.15-6,69.85-17.84,104.45-29.34C989.49,25,1113,-14.29,1200,52.47V0Z'%3E%3C/path%3E%3C/svg%3E")
;
}
/* 顶部发光效果 */
.water-wave
::after
{
content
:
""
;
position
:
absolute
;
top
:
0
;
left
:
0
;
width
:
100%
;
height
:
8px
;
background
:
linear-gradient
(
to
bottom
,
rgba
(
255
,
255
,
255
,
0.7
),
transparent
);
z-index
:
10
;
}
/* 设备状态信息 */
.device-status-info
{
font-size
:
12px
;
color
:
rgba
(
255
,
255
,
255
,
0.85
);
text-align
:
center
;
line-height
:
1.5
;
}
/* 响应式调整 */
@media
(
max-width
:
768px
)
{
.device-cards-container
{
grid-template-columns
:
repeat
(
auto-fill
,
minmax
(
150px
,
1
fr
));
}
.device-card.new-design
{
min-height
:
160px
;
padding
:
10px
;
}
.circle-container
{
width
:
80px
;
height
:
80px
;
}
.oee-text
,
.err-text
,
.off-text
{
top
:
20px
;
font-size
:
14px
;
}
.percentage-text
{
top
:
40px
;
font-size
:
20px
;
}
}
/* 为Safari和iOS设备的特别修复 */
@supports
(
-webkit-appearance
:
none
)
{
.water-wave-ripple
::before
,
.water-wave-ripple
::after
{
-webkit-animation-play-state
:
running
;
animation-play-state
:
running
;
}
}
/* 波浪水平移动动画 */
@-webkit-keyframes
wave-horizontal
{
0
%
{
transform
:
translateX
(
0
);
}
100
%
{
transform
:
translateX
(
-50%
);
}
}
@keyframes
wave-horizontal
{
0
%
{
transform
:
translateX
(
0
);
}
100
%
{
transform
:
translateX
(
-50%
);
}
}
.flxe_sty
{
width
:
50%
;
display
:
flex
;
align-items
:
center
;
font-size
:
10px
;
color
:
#fff
;
margin-top
:
5px
;
.flxe_label_sty
{
margin-right
:
2px
;
font-weight
:
bold
;
color
:
#000
;
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
font-size
:
10px
;
width
:
30px
;
height
:
auto
;
border-radius
:
2px
;
margin-left
:
10px
;
}
}
</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