Commit 5d8b76d7 by nningxx

Merge branch 'master' of https://git.rokedata.com/dws/dwsproject

parents 3d6611a7 15f1b20f
...@@ -160,7 +160,7 @@ ...@@ -160,7 +160,7 @@
let vue = new Vue({ let vue = new Vue({
el: "#app", el: "#app",
delimiters: ["[[", "]]"], // 替换原本vue的[[ key ]]取值方式(与odoo使用的jinja2冲突问题) delimiters: ["[[", "]]"], // 替换原本vue的[[ key ]]取值方式(与odoo使用的jinja2冲突问题)
data: function () { data() {
return { return {
dwsURL: "", // 基地址 dwsURL: "", // 基地址
baseURL: "https://dws-platform.xbg.rokeris.com/dev-api", // 基地址 baseURL: "https://dws-platform.xbg.rokeris.com/dev-api", // 基地址
...@@ -175,24 +175,24 @@ ...@@ -175,24 +175,24 @@
equipmentMaintenanceInfos: [], // 设备维护信息 equipmentMaintenanceInfos: [], // 设备维护信息
} }
}, },
created: function () { created() {
// 截取url后面的参数 // 截取url后面的参数
this.factoryCode = this.getUrlSearch("factory_code") || "" this.factoryCode = this.getUrlSearch("factory_code") || ""
// 初始化当前时间 // 初始化当前时间
this.initCurrentTimeFn() this.initCurrentTimeFn()
}, },
mounted: async function () { async mounted() {
this.$nextTick(function () { this.$nextTick(() => {
document.getElementById("bodyId").style.display = "block" document.getElementById("bodyId").style.display = "block"
}) })
// 初始化数据 - 实际使用时取消这行的注释 // 初始化数据 - 实际使用时取消这行的注释
await this.initData() await this.initData()
// 设置定时刷新(每分钟刷新一次,静默模式) // 设置定时刷新(每分钟刷新一次,静默模式)
this.refreshInterval = setInterval(function () { this.refreshInterval = setInterval(() => {
this.initData(true) // 传入true表示静默刷新,不显示加载提示 this.initData(true) // 传入true表示静默刷新,不显示加载提示
}.bind(this), 60000) }, 60000)
}, },
beforeDestroy: function () { beforeDestroy() {
// 清除定时器 // 清除定时器
if (this.refreshInterval) clearInterval(this.refreshInterval) if (this.refreshInterval) clearInterval(this.refreshInterval)
if (this.timer) clearInterval(this.timer) if (this.timer) clearInterval(this.timer)
...@@ -241,8 +241,8 @@ ...@@ -241,8 +241,8 @@
this.isFullscreen = false this.isFullscreen = false
}, },
// 初始化当前时间 // 初始化当前时间
initCurrentTimeFn: function () { initCurrentTimeFn() {
this.timer = setInterval(function () { this.timer = setInterval(() => {
const now = new Date() const now = new Date()
const year = now.getFullYear() const year = now.getFullYear()
const month = String(now.getMonth() + 1).padStart(2, "0") const month = String(now.getMonth() + 1).padStart(2, "0")
...@@ -251,10 +251,10 @@ ...@@ -251,10 +251,10 @@
const minutes = String(now.getMinutes()).padStart(2, "0") const minutes = String(now.getMinutes()).padStart(2, "0")
const seconds = String(now.getSeconds()).padStart(2, "0") const seconds = String(now.getSeconds()).padStart(2, "0")
this.currentTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}` this.currentTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}.bind(this), 1000) }, 1000)
}, },
// 通过网址跳转过来的页面,截取后面的参数 // 通过网址跳转过来的页面,截取后面的参数
getUrlSearch: function (name) { getUrlSearch(name) {
// 未传参,返回空 // 未传参,返回空
if (!name) return "" if (!name) return ""
// 查询参数:先通过search取值,如果取不到就通过hash来取 // 查询参数:先通过search取值,如果取不到就通过hash来取
...@@ -272,8 +272,7 @@ ...@@ -272,8 +272,7 @@
return r[2] return r[2]
}, },
// 初始化数据 // 初始化数据
initData: async function (silent) { async initData(silent = false) {
if (silent === undefined) silent = false
try { try {
// 只有在非静默模式下才显示加载提示 // 只有在非静默模式下才显示加载提示
if (!silent) this.loading = true if (!silent) this.loading = true
...@@ -292,7 +291,7 @@ ...@@ -292,7 +291,7 @@
} }
}, },
// 获取设备计划运行时间 // 获取设备计划运行时间
getDevicePlanTime: async function () { async getDevicePlanTime() {
try { try {
// 发送请求获取计划运行时间 // 发送请求获取计划运行时间
const response = await axios({ const response = await axios({
...@@ -322,7 +321,7 @@ ...@@ -322,7 +321,7 @@
} }
}, },
// 获取所有已绑定设备数据 // 获取所有已绑定设备数据
getAllEquipmentData: async function () { async getAllEquipmentData() {
try { try {
// 发送请求获取所有已绑定设备数据 // 发送请求获取所有已绑定设备数据
const response = await axios({ const response = await axios({
...@@ -347,7 +346,7 @@ ...@@ -347,7 +346,7 @@
} }
}, },
// 获取设备状态列表 // 获取设备状态列表
getDeviceStateList: async function (planTimeList) { async getDeviceStateList(planTimeList) {
try { try {
// 使用CORS代理 // 使用CORS代理
// 发送请求获取设备状态 // 发送请求获取设备状态
...@@ -375,7 +374,7 @@ ...@@ -375,7 +374,7 @@
} }
}, },
// 获取factoryCode // 获取factoryCode
getFactoryCode: async function (planTimeList) { async getFactoryCode(planTimeList) {
try { try {
// 使用CORS代理 // 使用CORS代理
// 发送请求获取设备状态 // 发送请求获取设备状态
...@@ -400,7 +399,7 @@ ...@@ -400,7 +399,7 @@
} }
}, },
// 获取设备维护信息 // 获取设备维护信息
getEquipmentMaintenanceInfoApi: async function () { async getEquipmentMaintenanceInfoApi() {
try { try {
const response = await axios({ const response = await axios({
method: "post", method: "post",
...@@ -408,10 +407,10 @@ ...@@ -408,10 +407,10 @@
data: {}, data: {},
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
}) })
if (response && response.data && response.data.result && response.data.result.state === "success") { if (response?.data?.result?.state === "success") {
this.equipmentMaintenanceInfos = response.data.result.equipment_maintenance_list || [] this.equipmentMaintenanceInfos = response.data.result.equipment_maintenance_list || []
} else { } else {
const errorMsg = response && response.data && response.data.result && response.data.result.msgs || "获取设备维护信息失败!" const errorMsg = response?.data?.result?.msgs || "获取设备维护信息失败!"
throw new Error(errorMsg) throw new Error(errorMsg)
} }
} catch (error) { } catch (error) {
...@@ -421,8 +420,8 @@ ...@@ -421,8 +420,8 @@
// 处理设备状态数据 // 处理设备状态数据
processDeviceStateData: function (deviceStateData) { processDeviceStateData: function (deviceStateData) {
var self = this var self = this
self.allEquipmentData.forEach(function (item) { self.allEquipmentData.forEach(item => {
self.equipmentMaintenanceInfos.forEach(function (it) { self.equipmentMaintenanceInfos.forEach(it => {
if (it.equipment_code == item.code) { if (it.equipment_code == item.code) {
item['check_info'] = it.check_info item['check_info'] = it.check_info
item['change_info'] = it.change_info item['change_info'] = it.change_info
...@@ -484,7 +483,7 @@ ...@@ -484,7 +483,7 @@
aging = self.formatDecimal(device.pulse_count / Math.floor(Number(device.run_seconds) / 3600), 2) aging = self.formatDecimal(device.pulse_count / Math.floor(Number(device.run_seconds) / 3600), 2)
} }
// 在所有设备列表中查找匹配的设备 // 在所有设备列表中查找匹配的设备
if (self.allEquipmentData && self.allEquipmentData.length) { if (self.allEquipmentData && self.allEquipmentData.length > 0) {
var matchedDevice = self.allEquipmentData.find(function (equip) { var matchedDevice = self.allEquipmentData.find(function (equip) {
return equip.code === device.code return equip.code === device.code
}) })
...@@ -520,10 +519,11 @@ ...@@ -520,10 +519,11 @@
finish_qty: finishQty finish_qty: finishQty
} }
}) })
this.deviceList = this.deviceList.filter(function (device) { return device }) this.deviceList = this.deviceList.filter((device) => device)
}, },
// 处理小数方法 // 处理小数方法
formatDecimal: function (value, digit) { formatDecimal(value, digit) {
if (value === Infinity) return 0
// 没设置位数默认返回原来的值 // 没设置位数默认返回原来的值
if (!digit) return value if (!digit) return value
// 处理 null/undefined/空字符串 // 处理 null/undefined/空字符串
...@@ -537,12 +537,12 @@ ...@@ -537,12 +537,12 @@
// 截取小数位数 // 截取小数位数
const decimalPart = parsedNum.toString().split('.')[1] const decimalPart = parsedNum.toString().split('.')[1]
// 检查小数长度是否大于要求位数,小于或等于直接返回 // 检查小数长度是否大于要求位数,小于或等于直接返回
if (decimalPart && decimalPart.length <= digit) return parsedNum if (decimalPart.length <= digit) return parsedNum
// 保留要求位数(处理浮点误差) // 保留要求位数(处理浮点误差)
return parseFloat((Math.round((parsedNum + Number.EPSILON) * 100) / 100).toFixed(digit)) return parseFloat((Math.round((parsedNum + Number.EPSILON) * 100) / 100).toFixed(digit))
}, },
// 处理时间方法 // 处理时间方法
formatTime: function (seconds) { formatTime(seconds) {
if (seconds < 60) { if (seconds < 60) {
return `0min` // 不足 1 分钟显示 0min return `0min` // 不足 1 分钟显示 0min
} else if (seconds < 3600) { } else if (seconds < 3600) {
...@@ -558,7 +558,7 @@ ...@@ -558,7 +558,7 @@
} }
}, },
// 获取卡片的CSS类名 // 获取卡片的CSS类名
getCardClass: function (status) { getCardClass(status) {
switch (status) { switch (status) {
case "running": case "running":
return "card-running" return "card-running"
...@@ -572,7 +572,7 @@ ...@@ -572,7 +572,7 @@
} }
}, },
// 获取边框的CSS类名 // 获取边框的CSS类名
getBorderClass: function (status) { getBorderClass(status) {
switch (status) { switch (status) {
case "running": case "running":
return "border-running"; return "border-running";
...@@ -586,7 +586,7 @@ ...@@ -586,7 +586,7 @@
} }
}, },
// 获取设备状态对应的CSS类名 // 获取设备状态对应的CSS类名
getStatusClass: function (status) { getStatusClass(status) {
switch (status) { switch (status) {
case "running": case "running":
return "status-running"; return "status-running";
...@@ -600,7 +600,7 @@ ...@@ -600,7 +600,7 @@
} }
}, },
// 获取波浪效果的类名 // 获取波浪效果的类名
getWaveClass: function (status) { getWaveClass(status) {
switch (status) { switch (status) {
case "running": case "running":
return "wave-running"; return "wave-running";
...@@ -614,7 +614,7 @@ ...@@ -614,7 +614,7 @@
} }
}, },
// 获取波浪高度 // 获取波浪高度
getWaveHeight: function (status, percentage) { getWaveHeight(status, percentage) {
// 将百分比限制在10%-100%之间,确保即使是低百分比也有一定的水位可见 // 将百分比限制在10%-100%之间,确保即使是低百分比也有一定的水位可见
let height = percentage; let height = percentage;
...@@ -630,7 +630,7 @@ ...@@ -630,7 +630,7 @@
}, },
// 获取设备状态对应的文本 // 获取设备状态对应的文本
getStatusText: function (status) { getStatusText(status) {
switch (status) { switch (status) {
case "running": case "running":
return "运行中"; return "运行中";
...@@ -644,7 +644,7 @@ ...@@ -644,7 +644,7 @@
} }
}, },
// 获取设备维护信息状态颜色 // 获取设备维护信息状态颜色
getMaintenanceColor: function (status, type) { getMaintenanceColor(status, type) {
if (type && type == '维修' && status == 'in_progress') { if (type && type == '维修' && status == 'in_progress') {
return "rgba(230, 188, 92, 0.7)" return "rgba(230, 188, 92, 0.7)"
} else if (status == 'no_task' || status == 'in_progress' || status == 'finished') { } else if (status == 'no_task' || status == 'in_progress' || status == 'finished') {
...@@ -656,7 +656,7 @@ ...@@ -656,7 +656,7 @@
} }
}, },
// 获取设备维护信息状态颜色 // 获取设备维护信息状态颜色
getMaintenanceText: function (status, type) { getMaintenanceText(status, type) {
if (type == '点检' || type == '保养') { if (type == '点检' || type == '保养') {
if (status == 'no_task') return "无任务" if (status == 'no_task') return "无任务"
if (status == 'not_started') return "未开始" if (status == 'not_started') return "未开始"
...@@ -674,11 +674,10 @@ ...@@ -674,11 +674,10 @@
return "未知" return "未知"
}, },
// 处理小数位数方法 // 处理小数位数方法
toFixedHandle: function (value, num) { toFixedHandle(value, num = 4) {
if (num === undefined) num = 4
if (value) { if (value) {
let strValue = String(value); let strValue = String(value);
if (strValue.split(".") && strValue.split(".").length > 1 || strValue.split(".")[1] && strValue.split(".")[1].length > num) { if (strValue.split(".").length > 1 || strValue.split(".")[1]?.length > num) {
strValue = Number(strValue).toFixed(num); strValue = Number(strValue).toFixed(num);
} }
return Number(strValue); return Number(strValue);
...@@ -688,7 +687,7 @@ ...@@ -688,7 +687,7 @@
}, },
// 计算高度百分比 // 计算高度百分比
calculateHeight: function (hours) { calculateHeight(hours) {
// 24小时对应整个高度(100%) // 24小时对应整个高度(100%)
// 每4小时对应一个刻度区间,总共6个区间 // 每4小时对应一个刻度区间,总共6个区间
// 计算每小时占的百分比:100% / 24 ≈ 4.167% // 计算每小时占的百分比:100% / 24 ≈ 4.167%
...@@ -700,13 +699,13 @@ ...@@ -700,13 +699,13 @@
}, },
// 显示完整标题 // 显示完整标题
showFullTitle: function (event, device) { showFullTitle(event, device) {
const fullTitle = device.name + " | " + device.code; const fullTitle = device.name + " | " + device.code;
event.target.setAttribute("title", fullTitle); event.target.setAttribute("title", fullTitle);
}, },
// 获取ERR文字的CSS类名 // 获取ERR文字的CSS类名
getErrClass: function (status) { getErrClass(status) {
if (status === "error") { if (status === "error") {
return "err-error"; return "err-error";
} }
...@@ -714,7 +713,7 @@ ...@@ -714,7 +713,7 @@
}, },
// 获取OFF文字的CSS类名 // 获取OFF文字的CSS类名
getOffClass: function (status) { getOffClass(status) {
if (status === "off") { if (status === "off") {
return "off-status"; return "off-status";
} }
...@@ -955,13 +954,14 @@ ...@@ -955,13 +954,14 @@
/* 让每个区域平均分配宽度 */ /* 让每个区域平均分配宽度 */
position: relative; position: relative;
height: 100%; height: 100%;
gap: 5px;
} }
/* 新增:图标和文字的水平容器 */ /* 新增:图标和文字的水平容器 */
.maintenance-header { .maintenance-header {
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 5px; gap: 5px;
} }
/* 维护状态竖向虚线分割 - 改为竖向分割线,从上到下,实现密闭效果 */ /* 维护状态竖向虚线分割 - 改为竖向分割线,从上到下,实现密闭效果 */
...@@ -1030,7 +1030,6 @@ ...@@ -1030,7 +1030,6 @@
margin-bottom: 0; margin-bottom: 0;
/* 移除底部边距 */ /* 移除底部边距 */
text-align: center; text-align: center;
margin-left: 5px;
} }
.maintenance-status-badge { .maintenance-status-badge {
...@@ -1065,6 +1064,7 @@ ...@@ -1065,6 +1064,7 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
width: 100%; width: 100%;
gap: 10px;
padding: 0 10px; padding: 0 10px;
/* 增加间距 */ /* 增加间距 */
} }
...@@ -1085,7 +1085,6 @@ ...@@ -1085,7 +1085,6 @@
} }
.stat-item:nth-child(2) { .stat-item:nth-child(2) {
margin: 0 10px;
background: linear-gradient(135deg, rgba(46, 204, 113, 0.6), rgba(39, 174, 96, 0.6)); background: linear-gradient(135deg, rgba(46, 204, 113, 0.6), rgba(39, 174, 96, 0.6));
} }
...@@ -1141,6 +1140,7 @@ ...@@ -1141,6 +1140,7 @@
.left-status { .left-status {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px;
flex: 0 0 auto; flex: 0 0 auto;
/* 改为固定宽度 */ /* 改为固定宽度 */
width: 80px; width: 80px;
...@@ -1151,6 +1151,7 @@ ...@@ -1151,6 +1151,7 @@
.right-status { .right-status {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px;
flex: 0 0 auto; flex: 0 0 auto;
/* 改为固定宽度 */ /* 改为固定宽度 */
width: 80px; width: 80px;
...@@ -1175,14 +1176,7 @@ ...@@ -1175,14 +1176,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
} gap: 6px;
.time-item-side:nth-child(1) {
margin-bottom: 5px;
}
.time-item-side:nth-child(2) {
margin-top: 5px;
} }
/* 右侧状态项布局调整 */ /* 右侧状态项布局调整 */
...@@ -1205,12 +1199,6 @@ ...@@ -1205,12 +1199,6 @@
font-size: 10px; font-size: 10px;
color: rgba(255, 255, 255, 0.9); color: rgba(255, 255, 255, 0.9);
font-weight: 500; font-weight: 500;
margin-left: 6px;
}
.right-status .time-value {
margin-left: 0;
margin-right: 6px;
} }
/* 移动端虚线样式调整 */ /* 移动端虚线样式调整 */
...@@ -1466,6 +1454,7 @@ ...@@ -1466,6 +1454,7 @@
.maintenance-header { .maintenance-header {
margin-bottom: 6px; margin-bottom: 6px;
/* 减小间距 */ /* 减小间距 */
gap: 4px;
} }
.maintenance-icon { .maintenance-icon {
......
...@@ -2,12 +2,13 @@ ...@@ -2,12 +2,13 @@
from odoo import http from odoo import http
from odoo.http import request from odoo.http import request
from odoo.addons.roke_mes_three_colour_light.controller.main import RokeMesThreeColourLight from odoo.addons.roke_mes_three_colour_light.controller.main import RokeMesThreeColourLight
from odoo.addons.roke_workstation_api.controllers.abnormal import RokeWorkstationAbnormal
import os import os
import math import math
from datetime import datetime, time from datetime import datetime, time
from jinja2 import Environment, FileSystemLoader from jinja2 import Environment, FileSystemLoader
BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 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) env = Environment(loader=templateloader)
class ThtProject(http.Controller): class ThtProject(http.Controller):
...@@ -54,7 +55,7 @@ class RokeMesThreeColourLightExt(RokeMesThreeColourLight): ...@@ -54,7 +55,7 @@ class RokeMesThreeColourLightExt(RokeMesThreeColourLight):
"override": True # 添加额外字段 "override": True # 添加额外字段
} }
} }
template = env.get_template('equipment_status.html') template = env.get_template('src/view/equipment_status.html')
return template.render(data) return template.render(data)
@http.route('/roke/equipment/search', type='json', methods=['POST', 'OPTIONS'], auth="none", csrf=False, @http.route('/roke/equipment/search', type='json', methods=['POST', 'OPTIONS'], auth="none", csrf=False,
...@@ -113,3 +114,11 @@ class RokeMesThreeColourLightExt(RokeMesThreeColourLight): ...@@ -113,3 +114,11 @@ class RokeMesThreeColourLightExt(RokeMesThreeColourLight):
'code': 200, 'code': 200,
'data': equipment_list 'data': equipment_list
} }
class RokeWorkstationAbnormalExt(RokeWorkstationAbnormal):
@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
<!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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment