Files
lc_frontend/src/views/screen/components/HiddenDangerPanel.vue
2025-12-13 20:55:17 +08:00

600 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="center-container">
<div class="center-content">
<!-- 隐患排查治理 这个标题需要隐藏 2025-10-31 -->
<span class="title"></span>
<img class="bottom-border-line" src="@/assets/images/title_border_line_1.png" />
<span class="sub-title">隐患等级</span>
<img width="50%" src="@/assets/images/line_1.png" />
<div class="type-wrapper">
<div class="type-item">
<span class="type-btn">重大</span>
<span class="type-num cursor-pointer" @click="handleMajorClick">{{ hiddenDangerData?.major || 0 }}</span>
</div>
<div class="type-item">
<span class="type-btn active">一般</span>
<span class="type-num cursor-pointer" @click="handleMajorClick">{{ hiddenDangerData?.general || 0 }}</span>
</div>
</div>
<div class="clasic-wrapper">
<div class="clasic-item">
<span>处理进度</span>
<img width="100%" src="@/assets/images/line_1.png" />
</div>
<div class="clasic-item">
<span>Top3隐患类</span>
<img width="100%" src="@/assets/images/line_1.png" />
</div>
</div>
<div class="echart-wrapper">
<div class="lf-rt">
<Echart :options="progressChartOption" class="progress-chart" height="80%" />
<div class="progress-legend">
<div class="legend-item"><span class="dot red"></span>已逾期</div>
<div class="legend-item"><span class="dot green"></span>已处理</div>
</div>
<div class="progress-legend">
<!-- <div class="legend-item"><span class="dot yellow"></span>待排查</div>-->
<div class="legend-item"><span class="dot blue"></span>处理中</div>
</div>
</div>
<div class="lf-rt">
<Echart :options="top3TypesChartOption" class="progress-chart" height="80%" />
<div class="progress-legend-column">
<div class="legend-item">
<span class="dot blue"></span>
<span class="legend-text">{{ props.hiddenDangerData?.top3Types?.[0]?.order_type_path_name || "--" }}</span>
</div>
<div class="legend-item">
<span class="dot green"></span>
<span class="legend-text">{{ props.hiddenDangerData?.top3Types?.[1]?.order_type_path_name || "--" }}</span>
</div>
<div class="legend-item">
<span class="dot yellow"></span>
<span class="legend-text">{{ props.hiddenDangerData?.top3Types?.[2]?.order_type_path_name || "--" }}</span>
</div>
</div>
</div>
</div>
<div class="safe-wrapper">
<span class="safe-title">
<img width="22" style="margin: 3px 5px 0 0" src="@/assets/images/ybp_icon.png" />
安全指数:<p title="安全指数 = 逾期隐患百分比 × 60% + 安全考核通过率 × 20% + 安全培训完成率 × 20%"></p>
</span>
<span class="pending-count">{{ hiddenDangerData?.safetyIndex || 0 }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
interface Props {
hiddenDangerData?: {
general: number
major: number
safetyIndex: number
progress: {
overdue: number
processed: number
pending: number
processing: number
}
top3Types: Array<{
num: string
order_type_path_name: string
row_id: string
}>
}
}
const props = defineProps<Props>()
// 图表引用
const progressChartOption = ref<any>({
series: [
{
type: 'pie' as const,
radius: '55%',
center: ['50%', '50%'],
left: 0,
top: 0,
bottom: 0,
data: [],
label: {
alignTo: 'edge' as const,
formatter: '{time|{c} %}\n',
minMargin: 5,
edgeDistance: 10,
lineHeight: 15,
rich: {
time: {
fontSize: 10,
color: '#fff'
}
}
},
labelLine: {
length: 5,
length2: 0,
maxSurfaceAngle: 10
},
labelLayout: function (params: any) {
const isLeft = params.labelRect.x < (params.labelRect.width ? params.labelRect.width : 200) / 2;
const points = params.labelLinePoints;
// 添加安全检查
if (points && points.length >= 3 && points[2]) {
points[2][0] = isLeft
? params.labelRect.x
: params.labelRect.x + params.labelRect.width;
}
return {
labelLinePoints: points
};
},
}
]
})
const top3TypesChartOption = ref<any>({
series: [
{
type: 'pie' as const,
roseType: 'radius' as const,
radius: [30, 50],
center: ['50%', '50%'],
left: 0,
top: 0,
bottom: 0,
data: [],
label: {
alignTo: 'edge' as const,
formatter: '{time|{c}}\n',
minMargin: 5,
edgeDistance: 10,
lineHeight: 15,
rich: {
time: {
fontSize: 10,
color: '#fff'
}
}
},
labelLine: {
length: 5,
length2: 0,
maxSurfaceAngle: 10
},
labelLayout: function (params: any) {
const isLeft = params.labelRect.x < (params.labelRect.width ? params.labelRect.width : 200) / 2;
const points = params.labelLinePoints;
// 添加安全检查
if (points && points.length >= 3 && points[2]) {
points[2][0] = isLeft
? params.labelRect.x
: params.labelRect.x + params.labelRect.width;
}
return {
labelLinePoints: points
};
},
}
]
})
const handleMajorClick = () => {
window.open('http://10.0.64.20/configcenter/console/device-manage', '_blank')
}
watch(() => props.hiddenDangerData?.progress, (newVal) => {
refreshProcessCharts(newVal)
}, { deep: true })
// 辅助函数:安全地将值转换为数字,处理 NaN 和字符串 "NaN" 的情况
const safeNumber = (val: any): number => {
if (val === null || val === undefined || val === '' || val === 'NaN') {
return 0
}
const num = Number(val)
return isNaN(num) ? 0 : num
}
// 更新图表数据
const refreshProcessCharts = (process): void => {
if (!props.hiddenDangerData?.progress) {
console.warn('process is undefined or null')
return
}
const option = { ...progressChartOption.value }
// 确保所有 value 都是有效的数字,将字符串 "NaN" 或真正的 NaN 转换为 0
option.series[0].data = [
{ value: safeNumber(process.overdue), name: '已逾期', itemStyle: { color: '#ef4444' } },
{ value: safeNumber(process.processed), name: '已处理', itemStyle: { color: '#10b981' } },
// { value: safeNumber(process.pending), name: '待排查', itemStyle: { color: '#eab308' } },
{ value: safeNumber(process.processing), name: '处理中', itemStyle: { color: '#3b82f6' } }
]
progressChartOption.value = option
}
watch(() => props.hiddenDangerData?.top3Types, (newVal) => {
refreshTop3TypesCharts(newVal)
}, { deep: true })
// 更新图表数据
const refreshTop3TypesCharts = (top3Types): void => {
if (!top3Types || !Array.isArray(top3Types) || top3Types.length === 0) {
console.warn('top3Types is undefined, null, or empty array')
return
}
const option = { ...top3TypesChartOption.value }
// 定义颜色数组
const colors = ['#5470c6', '#9edf7f', '#fac858']
// 将数组数据转换为图表数据格式
option.series[0].data = top3Types.slice(0, 3).map((item, index) => ({
value: Number(item.num) || 0,
name: item.order_type_path_name || `类型${index + 1}`,
itemStyle: { color: colors[index] || '#999' }
}))
top3TypesChartOption.value = option
}
onMounted(() => {
})
</script>
<style scoped lang="scss">
/* 响应式设计 */
@media (width <=1200px) {
.center-container {
width: 60vh;
height: 60vh;
}
}
@media (width <=1024px) {
.center-container {
width: 55vh;
height: 55vh;
.center-content {
.title {
font-size: 0.8rem;
}
.sub-title {
font-size: 0.7rem;
}
.type-wrapper {
width: 85%;
.type-item .type-btn {
padding: 2px 25px;
font-size: 0.65rem;
}
}
}
}
}
@media (width <=768px) {
.center-container {
top: 60%;
width: 50vh;
height: 50vh;
.center-content {
.title {
margin-top: 1.5vh;
font-size: 0.7rem;
}
.sub-title {
font-size: 0.65rem;
}
.type-wrapper {
width: 90%;
margin-top: 0.3vh;
.type-item {
.type-btn {
padding: 2px 20px;
font-size: 0.6rem;
}
.type-num {
font-size: 0.9rem;
}
}
}
.clasic-wrapper {
width: 90%;
margin-top: 1vh;
.clasic-item {
font-size: 0.7rem;
}
}
.echart-wrapper {
width: 90%;
.lf-rt .progress-legend .legend-item {
font-size: 0.6rem;
}
}
.safe-wrapper {
width: 50%;
.safe-title {
font-size: 0.7rem;
}
.pending-count {
font-size: 1.4rem;
}
}
}
}
}
@media (width <=480px) {
.center-container {
top: 65%;
width: 45vh;
height: 45vh;
.center-content {
.title {
margin-top: 1vh;
font-size: 0.65rem;
}
.sub-title {
font-size: 0.6rem;
}
.type-wrapper {
width: 95%;
.type-item .type-btn {
padding: 1px 15px;
font-size: 0.55rem;
}
}
.echart-wrapper {
width: 95%;
}
.safe-wrapper {
width: 60%;
.safe-title {
font-size: 0.65rem;
}
.pending-count {
font-size: 1.2rem;
}
}
}
}
}
p{width:17px; height:17px; background-color:rgb(125, 125, 152); border-radius:50%; text-align:center; }
p:before{content:'?'; color:yellow; font-weight: bold;}
.center-container {
position: fixed;
top: 55%;
left: 50%;
z-index: 1;
display: flex;
width: 65vh;
height: 65vh;
color: #fff;
background-image: url('@/assets/images/circle_bg.png');
background-size: cover;
transform: translate(-50%, -50%);
align-items: center;
justify-content: center;
.center-content {
display: flex;
width: 77%;
height: 77%;
overflow: hidden;
border-radius: 50%;
align-items: center;
flex-direction: column;
.title {
margin-top: 2vh;
font-size: 0.9rem;
font-weight: bold;
}
.bottom-border-line {
width: 20%;
margin: 1vh 0 1.2vh;
}
.sub-title {
font-size: 0.8rem;
}
.type-wrapper {
display: flex;
justify-content: space-around;
width: 80%;
font-size: 0.8rem;
color: #fff;
.type-item {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 0.5vh;
.type-btn {
padding: 2px 30px;
margin-bottom: 5px;
font-size: 0.7rem;
background-color: #d97706;
border-radius: 15px;
&.active {
background-color: #059669;
}
}
.type-num {
font-size: 1rem;
}
}
}
.clasic-wrapper {
display: flex;
width: 80%;
margin-top: 1.2vh;
font-size: 0.8rem;
color: #fff;
justify-content: space-around;
.clasic-item {
display: flex;
width: 45%;
margin-top: 0.6vh;
margin-bottom: -1vh;
flex-direction: column;
align-items: center;
img {
width: 100%;
height: 2px;
}
}
}
.echart-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
width: 85%;
height: 75%;
.lf-rt {
display: flex;
width: 50%;
height: 100%;
flex-direction: column;
.progress-chart {
display: flex;
width: 100%;
height: 80%;
align-items: center;
justify-content: center;
}
.progress-legend {
display: flex;
justify-content: center;
gap: 8px;
.legend-item {
display: flex;
align-items: center;
gap: 4px;
font-size: 0.7rem;
}
}
.progress-legend-column {
flex-direction: column;
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
.legend-item {
display: flex;
width: 60%;
align-items: center;
gap: 4px;
.legend-text {
overflow: hidden;
font-size: 0.7rem;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
}
}
}
}
}
.safe-wrapper {
display: flex;
width: 40%;
height: 20%;
margin-bottom: 5%;
align-items: center;
column-gap: 1vw;
.safe-title {
display: flex;
align-items: center;
margin-left: 1vw;
}
.pending-count {
margin-bottom: 0.7vh;
font-size: 1.6rem;
font-weight: 500;
color: yellow;
}
}
}
}
.dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.dot.red {
background-color: #ef4444;
}
.dot.green {
background-color: #9edf7f;
}
.dot.yellow {
background-color: #eab308;
}
.dot.blue {
background-color: #3b82f6;
}
</style>