2025-10-17 10:31:13 +08:00
|
|
|
|
<template>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
<div class="dashboard-container">
|
|
|
|
|
|
<!-- 顶部标题栏 -->
|
|
|
|
|
|
<div class="header-container">
|
|
|
|
|
|
<div class="header-left">
|
|
|
|
|
|
<div class="back-button" @click="openRegionSelector">
|
|
|
|
|
|
{{ currentView }}
|
|
|
|
|
|
<span>···</span>
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
2025-11-27 16:07:54 +08:00
|
|
|
|
<h1 class="header-title">集团视角数据看板</h1>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
<div class="header-right">
|
|
|
|
|
|
<div class="date-range-wrapper">
|
|
|
|
|
|
<el-date-picker v-model="dateRange" type="daterange" range-separator="至" start-placeholder="开始日期"
|
|
|
|
|
|
end-placeholder="结束日期" format="YYYY-MM-DD" value-format="YYYY-MM-DD" @change="handleDateChange" />
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
<el-button type="primary" :icon="Refresh" @click="refreshData" class="refresh-btn">
|
|
|
|
|
|
刷新数据
|
|
|
|
|
|
</el-button>
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 主内容区 - 6个卡片 -->
|
|
|
|
|
|
<div class="content-container">
|
|
|
|
|
|
<!-- 第一行:外协管理、风险管理、隐患管理 -->
|
|
|
|
|
|
<div class="card-row">
|
|
|
|
|
|
<!-- 外协管理卡片 -->
|
|
|
|
|
|
<div class="dashboard-card">
|
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
|
<div class="card-title">
|
|
|
|
|
|
<span class="card-icon">👥</span>
|
|
|
|
|
|
外协管理
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
<el-button type="text" class="manage-btn">管理</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="card-content">
|
|
|
|
|
|
<div class="donut-chart-wrapper">
|
|
|
|
|
|
<Echart :options="outsourcingChartOption" width="100%" height="200px" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="region-distribution">
|
|
|
|
|
|
<div class="distribution-title">区域分布</div>
|
|
|
|
|
|
<div class="distribution-list">
|
|
|
|
|
|
<div class="distribution-item" v-for="item in outsourcingDistribution" :key="item.region">
|
|
|
|
|
|
<span class="dot" :style="{ backgroundColor: item.color }"></span>
|
|
|
|
|
|
<span class="region-name">{{ item.region }}</span>
|
|
|
|
|
|
<span class="region-count">{{ item.count }}人</span>
|
|
|
|
|
|
<span class="region-percent">({{ item.percent }})</span>
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 风险管理卡片 -->
|
|
|
|
|
|
<div class="dashboard-card">
|
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
|
<div class="card-title">风险管理</div>
|
|
|
|
|
|
<el-button type="text" class="manage-btn">管理</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="card-content">
|
|
|
|
|
|
<div class="donut-chart-wrapper">
|
|
|
|
|
|
<Echart :options="riskChartOption" width="100%" height="200px" />
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
<div class="risk-distribution">
|
|
|
|
|
|
<div class="distribution-title">风险等级分布</div>
|
|
|
|
|
|
<div class="distribution-list">
|
|
|
|
|
|
<div class="distribution-item" v-for="item in riskDistribution" :key="item.level">
|
|
|
|
|
|
<span class="dot" :style="{ backgroundColor: item.color }"></span>
|
|
|
|
|
|
<span class="region-name">{{ item.level }}</span>
|
|
|
|
|
|
<span class="region-count">{{ item.count }}项</span>
|
|
|
|
|
|
<span class="region-percent">({{ item.percent }})</span>
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 隐患管理卡片 -->
|
|
|
|
|
|
<div class="dashboard-card">
|
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
|
<div class="card-title">
|
|
|
|
|
|
<span class="card-icon warning">⚠️</span>
|
|
|
|
|
|
隐患管理
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
<el-button type="text" class="manage-btn">管理</el-button>
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
<div class="card-content">
|
|
|
|
|
|
<div class="line-chart-wrapper">
|
|
|
|
|
|
<Echart :options="hiddenDangerChartOption" width="100%" height="180px" />
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
<div class="rectification-status">
|
|
|
|
|
|
<div class="distribution-title">整改状态</div>
|
|
|
|
|
|
<div class="status-list">
|
|
|
|
|
|
<div class="status-item" v-for="item in rectificationStatus" :key="item.status">
|
|
|
|
|
|
<span class="dot" :style="{ backgroundColor: item.color }"></span>
|
|
|
|
|
|
<span class="status-name">{{ item.status }}</span>
|
|
|
|
|
|
<span class="status-count">{{ item.count }}项</span>
|
|
|
|
|
|
</div>
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 第二行:高危作业、应急预案、安全培训 -->
|
|
|
|
|
|
<div class="card-row">
|
|
|
|
|
|
<!-- 高危作业卡片 -->
|
|
|
|
|
|
<div class="dashboard-card">
|
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
|
<div class="card-title">高危作业</div>
|
|
|
|
|
|
<el-button type="text" class="manage-btn">管理</el-button>
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
<div class="card-content">
|
2025-11-29 14:44:18 +08:00
|
|
|
|
<div class="high-risk-top">
|
|
|
|
|
|
<div class="donut-chart-wrapper-small">
|
|
|
|
|
|
<Echart :options="highRiskChartOption" width="100%" height="250px" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="operation-type-list">
|
|
|
|
|
|
<div class="operation-type-item" v-for="item in operationTypeDistribution" :key="item.type">
|
|
|
|
|
|
<span class="dot" :style="{ backgroundColor: item.color }"></span>
|
|
|
|
|
|
<span class="operation-name">{{ item.type }}</span>
|
|
|
|
|
|
<div class="operation-bar-wrapper">
|
|
|
|
|
|
<div class="operation-bar" :style="{ width: item.percent, backgroundColor: item.color }"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span class="operation-percent">{{ item.count }}项</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
2025-11-29 14:44:18 +08:00
|
|
|
|
<div class="park-operation-distribution">
|
2025-11-26 14:13:02 +08:00
|
|
|
|
<div class="distribution-title">区域作业分布</div>
|
|
|
|
|
|
<div class="distribution-list">
|
|
|
|
|
|
<div class="distribution-item" v-for="item in operationDistribution" :key="item.region">
|
|
|
|
|
|
<span class="region-name">{{ item.region }}</span>
|
|
|
|
|
|
<span class="region-count">{{ item.count }}项</span>
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 应急预案卡片 -->
|
|
|
|
|
|
<div class="dashboard-card">
|
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
|
<div class="card-title">
|
|
|
|
|
|
<span class="card-icon">📄</span>
|
|
|
|
|
|
应急预案
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<el-button type="text" class="manage-btn">管理</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="card-content">
|
2025-11-29 14:44:18 +08:00
|
|
|
|
<div class="emergency-plan-top">
|
|
|
|
|
|
<div class="progress-chart-wrapper">
|
|
|
|
|
|
<Echart :options="emergencyPlanChartOption" width="100%" height="250px" />
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
2025-11-29 14:44:18 +08:00
|
|
|
|
<div class="drill-info">
|
|
|
|
|
|
<div class="drill-item">
|
|
|
|
|
|
<span>应完成演练</span>
|
|
|
|
|
|
<span class="drill-number">{{ emergencyPlanTotal }}次</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="drill-item">
|
|
|
|
|
|
<span>已完成演练</span>
|
|
|
|
|
|
<span class="drill-number">{{ emergencyPlanCompleted }}次</span>
|
|
|
|
|
|
</div>
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
<div class="regional-progress">
|
|
|
|
|
|
<div class="distribution-title">区域演练完成率</div>
|
|
|
|
|
|
<div class="progress-list">
|
|
|
|
|
|
<div class="progress-item" v-for="item in regionalDrillProgress" :key="item.region">
|
|
|
|
|
|
<span class="region-name">{{ item.region }}</span>
|
|
|
|
|
|
<div class="progress-bar-wrapper">
|
|
|
|
|
|
<div class="progress-bar" :style="{ width: item.percent, backgroundColor: '#10b981' }"></div>
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
<span class="progress-percent">{{ item.percent }}</span>
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 安全培训卡片 -->
|
|
|
|
|
|
<div class="dashboard-card">
|
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
|
<div class="card-title">
|
|
|
|
|
|
<span class="card-icon">📚</span>
|
|
|
|
|
|
安全培训
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
<el-button type="text" class="manage-btn">管理</el-button>
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
<div class="card-content">
|
|
|
|
|
|
<div class="bar-chart-wrapper">
|
|
|
|
|
|
<Echart :options="safetyTrainingChartOption" width="100%" height="180px" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="regional-progress">
|
|
|
|
|
|
<div class="distribution-title">区域培训完成率</div>
|
|
|
|
|
|
<div class="progress-list">
|
|
|
|
|
|
<div class="progress-item" v-for="item in regionalTrainingProgress" :key="item.region">
|
|
|
|
|
|
<span class="region-name">{{ item.region }}</span>
|
|
|
|
|
|
<div class="progress-bar-wrapper">
|
2025-11-29 14:44:18 +08:00
|
|
|
|
<div class="progress-bar" :style="{ width: item.percent }"></div>
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
<span class="progress-percent">{{ item.percent }}</span>
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 区域选择弹窗 -->
|
|
|
|
|
|
<RegionSelector v-model="regionSelectorVisible" :modelSelected="selectedRegion" :regions="regionOption"
|
|
|
|
|
|
@change="onRegionChange" />
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import { ref, computed, onMounted } from 'vue'
|
2025-11-29 14:44:18 +08:00
|
|
|
|
import { useRouter, useRoute } from 'vue-router'
|
2025-11-26 14:13:02 +08:00
|
|
|
|
import { Refresh } from '@element-plus/icons-vue'
|
|
|
|
|
|
import Echart from '@/components/Echart/src/Echart.vue'
|
|
|
|
|
|
import RegionSelector from '@/views/screen/components/RegionSelector.vue'
|
|
|
|
|
|
import { getTableList } from '@/api/design/report'
|
|
|
|
|
|
import type { EChartsOption } from 'echarts'
|
|
|
|
|
|
import dayjs from 'dayjs'
|
2025-11-29 14:44:18 +08:00
|
|
|
|
import {
|
|
|
|
|
|
getHiddenDangerManagementData,
|
|
|
|
|
|
getHiddenDangerManagementDataWeek,
|
|
|
|
|
|
getHiddenDangerManagementDataMonth,
|
|
|
|
|
|
getOutsourcingManagementData,
|
|
|
|
|
|
getRiskManagementData,
|
|
|
|
|
|
getHighRiskManagementData,
|
|
|
|
|
|
getEmergencyPlanManagementData,
|
|
|
|
|
|
getTrainingManagementData
|
|
|
|
|
|
} from '@/api'
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
|
|
|
|
|
defineOptions({ name: 'Home10' })
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
// 类型定义
|
|
|
|
|
|
interface RegionItem {
|
|
|
|
|
|
name: string
|
|
|
|
|
|
code: string
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
|
|
|
|
|
interface DistributionItem {
|
|
|
|
|
|
region?: string
|
|
|
|
|
|
level?: string
|
|
|
|
|
|
status?: string
|
|
|
|
|
|
count: number
|
|
|
|
|
|
percent?: string
|
|
|
|
|
|
color: string
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
const router = useRouter()
|
2025-11-29 14:44:18 +08:00
|
|
|
|
const route = useRoute()
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
// 区域选择相关
|
|
|
|
|
|
const currentView = ref('总部')
|
|
|
|
|
|
const regionSelectorVisible = ref(false)
|
|
|
|
|
|
const selectedRegion = ref('')
|
|
|
|
|
|
const regionOption = ref<RegionItem[]>([])
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-29 14:44:18 +08:00
|
|
|
|
// 时间选择相关 - 默认当前月起止,如果路由中有日期范围参数则使用
|
2025-11-26 14:13:02 +08:00
|
|
|
|
const getCurrentMonthRange = () => {
|
|
|
|
|
|
const start = dayjs().startOf('month').format('YYYY-MM-DD')
|
|
|
|
|
|
const end = dayjs().endOf('month').format('YYYY-MM-DD')
|
|
|
|
|
|
return [start, end]
|
|
|
|
|
|
}
|
2025-11-29 14:44:18 +08:00
|
|
|
|
// 从路由参数读取日期范围,如果没有则使用默认值
|
|
|
|
|
|
const getInitialDateRange = () => {
|
|
|
|
|
|
const sDate = route.query.sDate as string
|
|
|
|
|
|
const eDate = route.query.eDate as string
|
|
|
|
|
|
if (sDate && eDate) {
|
|
|
|
|
|
return [sDate, eDate]
|
|
|
|
|
|
}
|
|
|
|
|
|
return getCurrentMonthRange()
|
|
|
|
|
|
}
|
|
|
|
|
|
const dateRange = ref(getInitialDateRange())
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
|
|
|
|
|
// 外协管理数据
|
|
|
|
|
|
const outsourcingTotal = ref<number>(0) // 外协人员总数
|
|
|
|
|
|
const outsourcingDistribution = ref<DistributionItem[]>([])
|
|
|
|
|
|
|
|
|
|
|
|
// 区域颜色配置
|
|
|
|
|
|
const regionColors = ['#3b82f6', '#8b5cf6', '#06b6d4', '#10b981', '#f59e0b', '#ef4444', '#ec4899']
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化外协管理数据
|
|
|
|
|
|
const initOutsourcingData = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await getOutsourcingManagementData({
|
|
|
|
|
|
sDate: dateRange.value[0],
|
|
|
|
|
|
eDate: dateRange.value[1],
|
|
|
|
|
|
pageNo: 1,
|
|
|
|
|
|
pageSize: 10000
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
console.log('外协管理接口返回:', response)
|
|
|
|
|
|
|
|
|
|
|
|
// axios封装后,response就是 { code: 0, data: {...}, msg: "" }
|
|
|
|
|
|
// 所以records在 response.data.records
|
|
|
|
|
|
const records = response?.records || []
|
|
|
|
|
|
|
|
|
|
|
|
if (records && records.length > 0) {
|
|
|
|
|
|
// 计算总人数(从records中累加total)
|
|
|
|
|
|
const total = records.reduce((sum: number, item: any) => {
|
|
|
|
|
|
return sum + Number(item.total || 0)
|
|
|
|
|
|
}, 0)
|
|
|
|
|
|
|
|
|
|
|
|
outsourcingTotal.value = total
|
|
|
|
|
|
|
|
|
|
|
|
// 处理区域分布数据
|
|
|
|
|
|
outsourcingDistribution.value = records.map((item: any, index: number) => {
|
|
|
|
|
|
const count = Number(item.total || 0)
|
|
|
|
|
|
const percent = total > 0 ? ((count / total) * 100).toFixed(1) + '%' : '0%'
|
|
|
|
|
|
const color = regionColors[index % regionColors.length]
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
region: item.name,
|
|
|
|
|
|
count,
|
|
|
|
|
|
percent,
|
|
|
|
|
|
color
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
console.log('处理后的外协管理数据:', {
|
|
|
|
|
|
total: outsourcingTotal.value,
|
|
|
|
|
|
distribution: outsourcingDistribution.value
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果没有数据,重置为空
|
|
|
|
|
|
outsourcingTotal.value = 0
|
|
|
|
|
|
outsourcingDistribution.value = []
|
|
|
|
|
|
console.log('外协管理无数据')
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取外协管理数据失败:', error)
|
|
|
|
|
|
// 如果接口失败,重置为空
|
|
|
|
|
|
outsourcingTotal.value = 0
|
|
|
|
|
|
outsourcingDistribution.value = []
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
// 风险管理数据
|
2025-11-29 14:44:18 +08:00
|
|
|
|
const riskTotal = ref<number>(0) // 风险总数
|
|
|
|
|
|
const riskDistribution = ref<DistributionItem[]>([])
|
|
|
|
|
|
|
|
|
|
|
|
// 风险等级映射和颜色配置
|
|
|
|
|
|
const riskLevelMap: Record<string, { name: string; color: string }> = {
|
|
|
|
|
|
'低': { name: '低风险', color: '#10b981' },
|
|
|
|
|
|
'低风险': { name: '低风险', color: '#10b981' },
|
|
|
|
|
|
'一般': { name: '一般风险', color: '#f59e0b' },
|
|
|
|
|
|
'一般风险': { name: '一般风险', color: '#f59e0b' },
|
|
|
|
|
|
'较大': { name: '较大风险', color: '#ef4444' },
|
|
|
|
|
|
'较大风险': { name: '较大风险', color: '#ef4444' },
|
|
|
|
|
|
'重大': { name: '重大风险', color: '#dc2626' },
|
|
|
|
|
|
'重大风险': { name: '重大风险', color: '#dc2626' }
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
// 隐患管理数据
|
2025-11-29 14:44:18 +08:00
|
|
|
|
const hiddenDangerTrend = ref<Array<{ date: string; general: number; major: number }>>([])
|
2025-11-26 14:13:02 +08:00
|
|
|
|
const rectificationStatus = ref<DistributionItem[]>([
|
2025-11-29 14:44:18 +08:00
|
|
|
|
{ status: '已逾期', count: 0, color: '#ef4444' },
|
|
|
|
|
|
{ status: '处理中', count: 0, color: '#f59e0b' },
|
|
|
|
|
|
{ status: '已处理', count: 0, color: '#10b981' }
|
2025-11-26 14:13:02 +08:00
|
|
|
|
])
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
// 高危作业数据
|
2025-11-29 14:44:18 +08:00
|
|
|
|
const highRiskTotal = ref<number>(0) // 高危作业总数
|
|
|
|
|
|
const operationTypeDistribution = ref<Array<{ type: string; count: number; percent: string; color: string }>>([]) // 作业类型分布(用于环形图)
|
|
|
|
|
|
const operationDistribution = ref<Array<{ region: string; count: number }>>([]) // 区域作业分布
|
|
|
|
|
|
|
|
|
|
|
|
// 作业类型颜色配置
|
|
|
|
|
|
const operationTypeColors: Record<string, string> = {
|
|
|
|
|
|
'动火作业': '#f59e0b',
|
|
|
|
|
|
'高处作业': '#8b5cf6',
|
|
|
|
|
|
'临时用电': '#3b82f6',
|
|
|
|
|
|
'有限空间': '#ec4899',
|
|
|
|
|
|
'动土作业': '#10b981',
|
|
|
|
|
|
'吊装作业': '#ef4444'
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
|
|
|
|
|
// 应急预案数据
|
2025-11-29 14:44:18 +08:00
|
|
|
|
const emergencyPlanTotal = ref<number>(0) // 应完成演练总数
|
|
|
|
|
|
const emergencyPlanCompleted = ref<number>(0) // 已完成演练总数
|
|
|
|
|
|
const regionalDrillProgress = ref<Array<{ region: string; percent: string }>>([
|
2025-11-26 14:13:02 +08:00
|
|
|
|
{ region: '华北区域', percent: '85%' },
|
|
|
|
|
|
{ region: '华东区域', percent: '75%' },
|
|
|
|
|
|
{ region: '华南区域', percent: '70%' }
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
// 安全培训数据
|
2025-11-29 14:44:18 +08:00
|
|
|
|
const trainingBarData = ref<{ regions: string[]; trainingCount: number[]; participants: number[] }>({
|
|
|
|
|
|
regions: [],
|
|
|
|
|
|
trainingCount: [],
|
|
|
|
|
|
participants: []
|
|
|
|
|
|
})
|
|
|
|
|
|
const regionalTrainingProgress = ref<Array<{ region: string; percent: string }>>([])
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
|
|
|
|
|
// 外协管理环形图配置
|
|
|
|
|
|
const outsourcingChartOption = computed<EChartsOption>(() => {
|
|
|
|
|
|
// 准备图表数据
|
|
|
|
|
|
const chartData = outsourcingDistribution.value.map(item => ({
|
|
|
|
|
|
value: item.count,
|
|
|
|
|
|
name: item.region,
|
|
|
|
|
|
itemStyle: { color: item.color }
|
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有数据,显示空状态
|
|
|
|
|
|
if (chartData.length === 0 || outsourcingTotal.value === 0) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'item',
|
|
|
|
|
|
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
|
|
|
|
|
},
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
|
|
|
|
|
name: '外协人员',
|
|
|
|
|
|
type: 'pie',
|
|
|
|
|
|
radius: ['55%', '75%'],
|
|
|
|
|
|
center: ['50%', '50%'],
|
|
|
|
|
|
avoidLabelOverlap: false,
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
borderRadius: 0,
|
|
|
|
|
|
borderColor: 'transparent',
|
|
|
|
|
|
borderWidth: 0
|
|
|
|
|
|
},
|
|
|
|
|
|
label: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
position: 'center',
|
|
|
|
|
|
formatter: () => {
|
|
|
|
|
|
return `${outsourcingTotal.value}\n外协人员总数`
|
|
|
|
|
|
},
|
|
|
|
|
|
fontSize: 16,
|
2025-10-17 10:31:13 +08:00
|
|
|
|
fontWeight: 'bold',
|
2025-11-26 14:13:02 +08:00
|
|
|
|
color: '#333'
|
2025-10-17 10:31:13 +08:00
|
|
|
|
},
|
2025-11-26 14:13:02 +08:00
|
|
|
|
data: [
|
|
|
|
|
|
{ value: 1, name: '暂无数据', itemStyle: { color: '#e5e7eb' } }
|
|
|
|
|
|
]
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
]
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'item',
|
|
|
|
|
|
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
2025-10-17 10:31:13 +08:00
|
|
|
|
},
|
2025-11-26 14:13:02 +08:00
|
|
|
|
series: [
|
|
|
|
|
|
{
|
|
|
|
|
|
name: '外协人员',
|
|
|
|
|
|
type: 'pie',
|
|
|
|
|
|
radius: ['55%', '75%'],
|
|
|
|
|
|
center: ['50%', '50%'],
|
|
|
|
|
|
avoidLabelOverlap: false,
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
borderRadius: 0,
|
|
|
|
|
|
borderColor: 'transparent',
|
|
|
|
|
|
borderWidth: 0
|
|
|
|
|
|
},
|
|
|
|
|
|
label: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
position: 'center',
|
|
|
|
|
|
formatter: () => {
|
|
|
|
|
|
return `${outsourcingTotal.value}\n外协人员总数`
|
|
|
|
|
|
},
|
|
|
|
|
|
fontSize: 16,
|
|
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
|
color: '#333'
|
|
|
|
|
|
},
|
|
|
|
|
|
emphasis: {
|
|
|
|
|
|
label: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
fontSize: 18,
|
|
|
|
|
|
fontWeight: 'bold'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
data: chartData
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 风险管理环形图配置
|
2025-11-29 14:44:18 +08:00
|
|
|
|
const riskChartOption = computed<EChartsOption>(() => {
|
|
|
|
|
|
const chartData = riskDistribution.value
|
|
|
|
|
|
.filter(item => item.count > 0) // 只显示有数据的风险等级
|
|
|
|
|
|
.map(item => ({
|
|
|
|
|
|
value: item.count,
|
|
|
|
|
|
name: item.level,
|
|
|
|
|
|
itemStyle: { color: item.color }
|
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有数据,显示空状态
|
|
|
|
|
|
if (chartData.length === 0 || riskTotal.value === 0) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
tooltip: { trigger: 'item', formatter: '{a} <br/>{b}: {c} ({d}%)' },
|
|
|
|
|
|
series: [{
|
|
|
|
|
|
name: '风险',
|
|
|
|
|
|
type: 'pie',
|
|
|
|
|
|
radius: ['55%', '75%'],
|
|
|
|
|
|
center: ['50%', '50%'],
|
|
|
|
|
|
avoidLabelOverlap: false,
|
|
|
|
|
|
itemStyle: { borderRadius: 0, borderColor: 'transparent', borderWidth: 0 },
|
|
|
|
|
|
label: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
position: 'center',
|
|
|
|
|
|
formatter: () => `${riskTotal.value}\n风险总数`,
|
|
|
|
|
|
fontSize: 16,
|
|
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
|
color: '#333'
|
|
|
|
|
|
},
|
|
|
|
|
|
data: [{ value: 1, name: '暂无数据', itemStyle: { color: '#e5e7eb' } }]
|
|
|
|
|
|
}]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
tooltip: { trigger: 'item', formatter: '{a} <br/>{b}: {c} ({d}%)' },
|
|
|
|
|
|
series: [{
|
2025-11-26 14:13:02 +08:00
|
|
|
|
name: '风险',
|
|
|
|
|
|
type: 'pie',
|
|
|
|
|
|
radius: ['55%', '75%'],
|
|
|
|
|
|
center: ['50%', '50%'],
|
|
|
|
|
|
avoidLabelOverlap: false,
|
2025-11-29 14:44:18 +08:00
|
|
|
|
itemStyle: { borderRadius: 0, borderColor: 'transparent', borderWidth: 0 },
|
2025-11-26 14:13:02 +08:00
|
|
|
|
label: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
position: 'center',
|
2025-11-29 14:44:18 +08:00
|
|
|
|
formatter: () => `${riskTotal.value}\n风险总数`,
|
2025-11-26 14:13:02 +08:00
|
|
|
|
fontSize: 16,
|
|
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
|
color: '#333'
|
2025-10-17 10:31:13 +08:00
|
|
|
|
},
|
2025-11-29 14:44:18 +08:00
|
|
|
|
emphasis: { label: { show: true, fontSize: 18, fontWeight: 'bold' } },
|
|
|
|
|
|
data: chartData
|
|
|
|
|
|
}]
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
// 隐患管理折线图配置
|
2025-11-29 14:44:18 +08:00
|
|
|
|
const hiddenDangerChartOption = computed<EChartsOption>(() => {
|
|
|
|
|
|
// 提取日期和对应的数据
|
|
|
|
|
|
const dates = hiddenDangerTrend.value.map(item => item.date)
|
|
|
|
|
|
const generalData = hiddenDangerTrend.value.map(item => item.general)
|
|
|
|
|
|
const majorData = hiddenDangerTrend.value.map(item => item.major)
|
|
|
|
|
|
|
|
|
|
|
|
// 计算Y轴最大值(向上取整到最近的10的倍数)
|
|
|
|
|
|
const maxValue = Math.max(
|
|
|
|
|
|
...generalData,
|
|
|
|
|
|
...majorData,
|
|
|
|
|
|
10 // 最小值为10,避免图表显示过小
|
|
|
|
|
|
)
|
|
|
|
|
|
const yAxisMax = Math.ceil(maxValue / 10) * 10 || 10
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'axis',
|
|
|
|
|
|
axisPointer: {
|
|
|
|
|
|
type: 'cross'
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
2025-11-29 14:44:18 +08:00
|
|
|
|
},
|
|
|
|
|
|
legend: {
|
|
|
|
|
|
data: ['一般隐患', '重大隐患'],
|
|
|
|
|
|
top: 10,
|
|
|
|
|
|
textStyle: {
|
|
|
|
|
|
fontSize: 12
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-11-29 14:44:18 +08:00
|
|
|
|
grid: {
|
|
|
|
|
|
left: '3%',
|
|
|
|
|
|
right: '4%',
|
|
|
|
|
|
bottom: '3%',
|
|
|
|
|
|
top: '15%',
|
|
|
|
|
|
containLabel: true
|
|
|
|
|
|
},
|
|
|
|
|
|
xAxis: {
|
|
|
|
|
|
type: 'category',
|
|
|
|
|
|
boundaryGap: false,
|
|
|
|
|
|
data: dates.length > 0 ? dates : [],
|
|
|
|
|
|
axisLine: {
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: '#e5e7eb'
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
2025-11-29 14:44:18 +08:00
|
|
|
|
},
|
|
|
|
|
|
yAxis: {
|
|
|
|
|
|
type: 'value',
|
|
|
|
|
|
max: yAxisMax,
|
|
|
|
|
|
axisLine: {
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: '#e5e7eb'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
splitLine: {
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: '#f3f4f6'
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
},
|
2025-11-29 14:44:18 +08:00
|
|
|
|
series: [
|
|
|
|
|
|
{
|
|
|
|
|
|
name: '一般隐患',
|
|
|
|
|
|
type: 'line',
|
|
|
|
|
|
smooth: true,
|
|
|
|
|
|
data: generalData.length > 0 ? generalData : [],
|
|
|
|
|
|
itemStyle: { color: '#f59e0b' },
|
|
|
|
|
|
lineStyle: { color: '#f59e0b', width: 2 },
|
|
|
|
|
|
areaStyle: {
|
|
|
|
|
|
color: {
|
|
|
|
|
|
type: 'linear',
|
|
|
|
|
|
x: 0,
|
|
|
|
|
|
y: 0,
|
|
|
|
|
|
x2: 0,
|
|
|
|
|
|
y2: 1,
|
|
|
|
|
|
colorStops: [
|
|
|
|
|
|
{ offset: 0, color: 'rgba(245, 158, 11, 0.3)' },
|
|
|
|
|
|
{ offset: 1, color: 'rgba(245, 158, 11, 0.05)' }
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
name: '重大隐患',
|
|
|
|
|
|
type: 'line',
|
|
|
|
|
|
smooth: true,
|
|
|
|
|
|
data: majorData.length > 0 ? majorData : [],
|
|
|
|
|
|
itemStyle: { color: '#ef4444' },
|
|
|
|
|
|
lineStyle: { color: '#ef4444', width: 2 },
|
|
|
|
|
|
areaStyle: {
|
|
|
|
|
|
color: {
|
|
|
|
|
|
type: 'linear',
|
|
|
|
|
|
x: 0,
|
|
|
|
|
|
y: 0,
|
|
|
|
|
|
x2: 0,
|
|
|
|
|
|
y2: 1,
|
|
|
|
|
|
colorStops: [
|
|
|
|
|
|
{ offset: 0, color: 'rgba(239, 68, 68, 0.3)' },
|
|
|
|
|
|
{ offset: 1, color: 'rgba(239, 68, 68, 0.05)' }
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
2025-11-29 14:44:18 +08:00
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
|
|
|
|
|
// 高危作业环形图配置
|
2025-11-29 14:44:18 +08:00
|
|
|
|
const highRiskChartOption = computed<EChartsOption>(() => {
|
|
|
|
|
|
const chartData = operationTypeDistribution.value
|
|
|
|
|
|
.filter(item => item.count > 0) // 只显示有数据的作业类型
|
|
|
|
|
|
.map(item => ({
|
|
|
|
|
|
value: item.count,
|
|
|
|
|
|
name: item.type,
|
|
|
|
|
|
itemStyle: { color: item.color }
|
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有数据,显示空状态
|
|
|
|
|
|
if (chartData.length === 0 || highRiskTotal.value === 0) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
tooltip: { trigger: 'item', formatter: '{a} <br/>{b}: {c} ({d}%)' },
|
|
|
|
|
|
series: [{
|
|
|
|
|
|
name: '高危作业',
|
|
|
|
|
|
type: 'pie',
|
|
|
|
|
|
radius: ['49%', '61%'],
|
|
|
|
|
|
center: ['50%', '50%'],
|
|
|
|
|
|
avoidLabelOverlap: false,
|
|
|
|
|
|
itemStyle: { borderRadius: 0, borderColor: 'transparent', borderWidth: 0 },
|
|
|
|
|
|
label: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
position: 'center',
|
|
|
|
|
|
formatter: () => `${highRiskTotal.value}\n累计作业`,
|
|
|
|
|
|
fontSize: 18,
|
|
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
|
color: '#333'
|
|
|
|
|
|
},
|
|
|
|
|
|
data: [{ value: 1, name: '暂无数据', itemStyle: { color: '#e5e7eb' } }]
|
|
|
|
|
|
}]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
tooltip: { trigger: 'item', formatter: '{a} <br/>{b}: {c} ({d}%)' },
|
|
|
|
|
|
series: [{
|
2025-11-26 14:13:02 +08:00
|
|
|
|
name: '高危作业',
|
|
|
|
|
|
type: 'pie',
|
2025-11-29 14:44:18 +08:00
|
|
|
|
radius: ['49%', '61%'],
|
2025-11-26 14:13:02 +08:00
|
|
|
|
center: ['50%', '50%'],
|
|
|
|
|
|
avoidLabelOverlap: false,
|
2025-11-29 14:44:18 +08:00
|
|
|
|
itemStyle: { borderRadius: 0, borderColor: 'transparent', borderWidth: 0 },
|
2025-11-26 14:13:02 +08:00
|
|
|
|
label: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
position: 'center',
|
2025-11-29 14:44:18 +08:00
|
|
|
|
formatter: () => `${highRiskTotal.value}\n累计作业`,
|
|
|
|
|
|
fontSize: 18,
|
2025-11-26 14:13:02 +08:00
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
|
color: '#333'
|
2025-10-17 10:31:13 +08:00
|
|
|
|
},
|
2025-11-29 14:44:18 +08:00
|
|
|
|
emphasis: { label: { show: true, fontSize: 20, fontWeight: 'bold' } },
|
|
|
|
|
|
data: chartData
|
|
|
|
|
|
}]
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
// 应急预案环形进度图配置
|
2025-11-29 14:44:18 +08:00
|
|
|
|
const emergencyPlanChartOption = computed<EChartsOption>(() => {
|
|
|
|
|
|
const percent = emergencyPlanTotal.value > 0
|
|
|
|
|
|
? ((emergencyPlanCompleted.value / emergencyPlanTotal.value) * 100).toFixed(0)
|
|
|
|
|
|
: 0
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
tooltip: { trigger: 'item' },
|
|
|
|
|
|
series: [{
|
2025-11-26 14:13:02 +08:00
|
|
|
|
name: '演练完成率',
|
|
|
|
|
|
type: 'pie',
|
2025-11-29 14:44:18 +08:00
|
|
|
|
radius: ['49%', '61%'],
|
2025-11-26 14:13:02 +08:00
|
|
|
|
center: ['50%', '50%'],
|
|
|
|
|
|
avoidLabelOverlap: false,
|
2025-11-29 14:44:18 +08:00
|
|
|
|
itemStyle: { borderRadius: 0, borderColor: 'transparent', borderWidth: 0, color: '#10b981' },
|
2025-11-26 14:13:02 +08:00
|
|
|
|
label: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
position: 'center',
|
2025-11-29 14:44:18 +08:00
|
|
|
|
formatter: () => `${percent}%\n演练完成率`,
|
|
|
|
|
|
fontSize: 18,
|
2025-11-26 14:13:02 +08:00
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
|
color: '#333'
|
|
|
|
|
|
},
|
2025-11-29 14:44:18 +08:00
|
|
|
|
emphasis: { label: { show: true, fontSize: 20, fontWeight: 'bold' } },
|
2025-11-26 14:13:02 +08:00
|
|
|
|
data: [
|
2025-11-29 14:44:18 +08:00
|
|
|
|
{ value: emergencyPlanCompleted.value, name: '已完成', itemStyle: { color: '#10b981' } },
|
|
|
|
|
|
{ value: emergencyPlanTotal.value - emergencyPlanCompleted.value, name: '未完成', itemStyle: { color: '#e5e7eb' } }
|
2025-11-26 14:13:02 +08:00
|
|
|
|
]
|
2025-11-29 14:44:18 +08:00
|
|
|
|
}]
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
// 安全培训柱状图配置
|
2025-11-29 14:44:18 +08:00
|
|
|
|
const safetyTrainingChartOption = computed<EChartsOption>(() => {
|
|
|
|
|
|
const regions = trainingBarData.value.regions || []
|
|
|
|
|
|
const trainingCount = trainingBarData.value.trainingCount || []
|
|
|
|
|
|
const participants = trainingBarData.value.participants || []
|
|
|
|
|
|
|
|
|
|
|
|
// 计算Y轴最大值(向上取整到最近的10的倍数)
|
|
|
|
|
|
const maxValue = Math.max(
|
|
|
|
|
|
...trainingCount,
|
|
|
|
|
|
...participants,
|
|
|
|
|
|
10 // 最小值为10,避免图表显示过小
|
|
|
|
|
|
)
|
|
|
|
|
|
const yAxisMax = Math.ceil(maxValue / 10) * 10 || 10
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'axis',
|
|
|
|
|
|
axisPointer: {
|
|
|
|
|
|
type: 'shadow'
|
|
|
|
|
|
},
|
|
|
|
|
|
formatter: (params: any) => {
|
|
|
|
|
|
let result = `${params[0].axisValue}<br/>`
|
|
|
|
|
|
params.forEach((item: any) => {
|
|
|
|
|
|
result += `${item.marker}${item.seriesName}: ${item.value}<br/>`
|
|
|
|
|
|
})
|
|
|
|
|
|
return result
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-11-29 14:44:18 +08:00
|
|
|
|
legend: {
|
|
|
|
|
|
data: ['培训次数', '参与人次'],
|
|
|
|
|
|
top: 10,
|
|
|
|
|
|
textStyle: {
|
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
|
color: '#666'
|
|
|
|
|
|
},
|
|
|
|
|
|
itemGap: 20
|
|
|
|
|
|
},
|
|
|
|
|
|
grid: {
|
|
|
|
|
|
left: '6%',
|
|
|
|
|
|
right: '4%',
|
|
|
|
|
|
bottom: '0',
|
|
|
|
|
|
top: '25%',
|
|
|
|
|
|
containLabel: true
|
|
|
|
|
|
},
|
|
|
|
|
|
xAxis: {
|
|
|
|
|
|
type: 'category',
|
|
|
|
|
|
data: regions.length > 0 ? regions : [],
|
|
|
|
|
|
axisLine: {
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: '#d1d5db'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
axisLabel: {
|
|
|
|
|
|
color: '#666',
|
|
|
|
|
|
fontSize: 12
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
2025-11-29 14:44:18 +08:00
|
|
|
|
},
|
|
|
|
|
|
yAxis: {
|
|
|
|
|
|
type: 'value',
|
|
|
|
|
|
max: yAxisMax,
|
|
|
|
|
|
axisLine: {
|
|
|
|
|
|
show: false
|
|
|
|
|
|
},
|
|
|
|
|
|
axisTick: {
|
|
|
|
|
|
show: false
|
2025-10-17 10:31:13 +08:00
|
|
|
|
},
|
2025-11-29 14:44:18 +08:00
|
|
|
|
axisLabel: {
|
|
|
|
|
|
color: '#666',
|
|
|
|
|
|
fontSize: 12
|
|
|
|
|
|
},
|
|
|
|
|
|
splitLine: {
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: '#f3f4f6',
|
|
|
|
|
|
type: 'dashed'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
},
|
2025-11-29 14:44:18 +08:00
|
|
|
|
series: [
|
|
|
|
|
|
{
|
|
|
|
|
|
name: '培训次数',
|
|
|
|
|
|
type: 'bar',
|
|
|
|
|
|
data: trainingCount.length > 0 ? trainingCount : [],
|
|
|
|
|
|
barWidth: 32,
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
color: {
|
|
|
|
|
|
type: 'linear',
|
|
|
|
|
|
x: 0,
|
|
|
|
|
|
y: 0,
|
|
|
|
|
|
x2: 0,
|
|
|
|
|
|
y2: 1,
|
|
|
|
|
|
colorStops: [
|
|
|
|
|
|
{ offset: 0, color: '#8b5cf6' },
|
|
|
|
|
|
{ offset: 1, color: '#7c3aed' }
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
borderRadius: [6, 6, 0, 0]
|
|
|
|
|
|
},
|
|
|
|
|
|
label: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
position: 'insideTop',
|
|
|
|
|
|
color: '#fff',
|
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
|
formatter: (params: any) => {
|
|
|
|
|
|
return params.value > 0 ? params.value : ''
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
emphasis: {
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
shadowBlur: 10,
|
|
|
|
|
|
shadowColor: 'rgba(139, 92, 246, 0.5)'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
},
|
2025-11-29 14:44:18 +08:00
|
|
|
|
{
|
|
|
|
|
|
name: '参与人次',
|
|
|
|
|
|
type: 'bar',
|
|
|
|
|
|
data: participants.length > 0 ? participants : [],
|
|
|
|
|
|
barWidth: 32,
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
color: {
|
|
|
|
|
|
type: 'linear',
|
|
|
|
|
|
x: 0,
|
|
|
|
|
|
y: 0,
|
|
|
|
|
|
x2: 0,
|
|
|
|
|
|
y2: 1,
|
|
|
|
|
|
colorStops: [
|
|
|
|
|
|
{ offset: 0, color: '#c4b5fd' },
|
|
|
|
|
|
{ offset: 1, color: '#a78bfa' }
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
borderRadius: [6, 6, 0, 0]
|
|
|
|
|
|
},
|
|
|
|
|
|
label: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
position: 'top',
|
|
|
|
|
|
color: '#7c3aed',
|
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
|
formatter: (params: any) => {
|
|
|
|
|
|
return params.value > 0 ? params.value : ''
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
emphasis: {
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
shadowBlur: 10,
|
|
|
|
|
|
shadowColor: 'rgba(196, 181, 253, 0.5)'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
// 方法定义
|
|
|
|
|
|
const openRegionSelector = (): void => {
|
|
|
|
|
|
regionSelectorVisible.value = true
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
const onRegionChange = (item: RegionItem): void => {
|
|
|
|
|
|
selectedRegion.value = item.name
|
|
|
|
|
|
router.push({
|
|
|
|
|
|
path: '/region',
|
2025-11-29 14:44:18 +08:00
|
|
|
|
query: {
|
|
|
|
|
|
region: item.name,
|
|
|
|
|
|
regionCode: item.code,
|
|
|
|
|
|
sDate: dateRange.value[0],
|
|
|
|
|
|
eDate: dateRange.value[1]
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
const handleDateChange = async (value: string[] | null) => {
|
|
|
|
|
|
if (value) {
|
|
|
|
|
|
console.log('日期范围变化:', value)
|
|
|
|
|
|
await refreshData()
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
const refreshData = async () => {
|
2025-11-29 14:44:18 +08:00
|
|
|
|
initData()
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化区域数据
|
|
|
|
|
|
const initRegionData = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { records } = await getTableList('park_info_list')
|
|
|
|
|
|
if (records && records.length > 0) {
|
|
|
|
|
|
// 去重region字段,使用Map来确保唯一性
|
|
|
|
|
|
const regionMap = new Map<string, RegionItem>()
|
|
|
|
|
|
records.forEach((el: any) => {
|
|
|
|
|
|
if (!regionMap.has(el.region)) {
|
|
|
|
|
|
regionMap.set(el.region, {
|
|
|
|
|
|
name: el.region,
|
|
|
|
|
|
code: el.region_id
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
})
|
2025-11-26 14:13:02 +08:00
|
|
|
|
// 转换为数组
|
|
|
|
|
|
regionOption.value = Array.from(regionMap.values())
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('初始化区域数据失败:', error)
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-29 14:44:18 +08:00
|
|
|
|
// 初始化风险管理数据
|
|
|
|
|
|
const initRiskData = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await getRiskManagementData({
|
|
|
|
|
|
sDate: dateRange.value[0],
|
|
|
|
|
|
eDate: dateRange.value[1],
|
|
|
|
|
|
pageNo: 1,
|
|
|
|
|
|
pageSize: 10000
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
console.log('风险管理接口返回:', response)
|
|
|
|
|
|
|
|
|
|
|
|
const records = response?.records || []
|
|
|
|
|
|
|
|
|
|
|
|
if (records && records.length > 0) {
|
|
|
|
|
|
// 计算风险总数
|
|
|
|
|
|
const total = records.reduce((sum: number, item: any) => {
|
|
|
|
|
|
return sum + Number(item.total || 0)
|
|
|
|
|
|
}, 0)
|
|
|
|
|
|
|
|
|
|
|
|
riskTotal.value = total
|
|
|
|
|
|
|
|
|
|
|
|
// 处理风险等级分布数据
|
|
|
|
|
|
riskDistribution.value = records.map((item: any) => {
|
|
|
|
|
|
const count = Number(item.total || 0)
|
|
|
|
|
|
const percent = total > 0 ? ((count / total) * 100).toFixed(1) + '%' : '0%'
|
|
|
|
|
|
|
|
|
|
|
|
// 映射风险等级名称和颜色
|
|
|
|
|
|
const levelKey = item.name || ''
|
|
|
|
|
|
const levelConfig = riskLevelMap[levelKey] || { name: levelKey || '未知', color: '#9ca3af' }
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
level: levelConfig.name,
|
|
|
|
|
|
count,
|
|
|
|
|
|
percent,
|
|
|
|
|
|
color: levelConfig.color
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有数据,确保包含所有四个风险等级(显示为0)
|
|
|
|
|
|
const allLevels = [
|
|
|
|
|
|
{ level: '低风险', count: 0, percent: '0%', color: '#10b981' },
|
|
|
|
|
|
{ level: '一般风险', count: 0, percent: '0%', color: '#f59e0b' },
|
|
|
|
|
|
{ level: '较大风险', count: 0, percent: '0%', color: '#ef4444' },
|
|
|
|
|
|
{ level: '重大风险', count: 0, percent: '0%', color: '#dc2626' }
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
// 合并数据:如果某个等级有数据就使用数据,没有就保留0
|
|
|
|
|
|
const mergedDistribution = allLevels.map(defaultItem => {
|
|
|
|
|
|
const existingItem = riskDistribution.value.find(item => item.level === defaultItem.level)
|
|
|
|
|
|
return existingItem || defaultItem
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
riskDistribution.value = mergedDistribution
|
|
|
|
|
|
|
|
|
|
|
|
console.log('处理后的风险管理数据:', {
|
|
|
|
|
|
total: riskTotal.value,
|
|
|
|
|
|
distribution: riskDistribution.value
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果没有数据,设置为默认值
|
|
|
|
|
|
riskTotal.value = 0
|
|
|
|
|
|
riskDistribution.value = [
|
|
|
|
|
|
{ level: '低风险', count: 0, percent: '0%', color: '#10b981' },
|
|
|
|
|
|
{ level: '一般风险', count: 0, percent: '0%', color: '#f59e0b' },
|
|
|
|
|
|
{ level: '较大风险', count: 0, percent: '0%', color: '#ef4444' },
|
|
|
|
|
|
{ level: '重大风险', count: 0, percent: '0%', color: '#dc2626' }
|
|
|
|
|
|
]
|
|
|
|
|
|
console.log('风险管理无数据')
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取风险管理数据失败:', error)
|
|
|
|
|
|
// 如果接口失败,设置为默认值
|
|
|
|
|
|
riskTotal.value = 0
|
|
|
|
|
|
riskDistribution.value = [
|
|
|
|
|
|
{ level: '低风险', count: 0, percent: '0%', color: '#10b981' },
|
|
|
|
|
|
{ level: '一般风险', count: 0, percent: '0%', color: '#f59e0b' },
|
|
|
|
|
|
{ level: '较大风险', count: 0, percent: '0%', color: '#ef4444' },
|
|
|
|
|
|
{ level: '重大风险', count: 0, percent: '0%', color: '#dc2626' }
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 根据日期范围判断应该调用哪个接口
|
|
|
|
|
|
const getHiddenDangerApi = (startDate: string, endDate: string) => {
|
|
|
|
|
|
const start = dayjs(startDate)
|
|
|
|
|
|
const end = dayjs(endDate)
|
|
|
|
|
|
const daysDiff = end.diff(start, 'day') + 1 // 包含起始和结束日期
|
|
|
|
|
|
|
|
|
|
|
|
// 如果日期范围 <= 7天,使用"天"接口
|
|
|
|
|
|
if (daysDiff <= 7) {
|
|
|
|
|
|
return getHiddenDangerManagementData
|
|
|
|
|
|
}
|
|
|
|
|
|
// 如果日期范围 <= 30天,使用"周"接口
|
|
|
|
|
|
else if (daysDiff <= 30) {
|
|
|
|
|
|
return getHiddenDangerManagementDataWeek
|
|
|
|
|
|
}
|
|
|
|
|
|
// 如果日期范围 > 30天,使用"月"接口
|
|
|
|
|
|
else {
|
|
|
|
|
|
return getHiddenDangerManagementDataMonth
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const initDangerData = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 根据日期范围选择接口
|
|
|
|
|
|
const apiFunc = getHiddenDangerApi(dateRange.value[0], dateRange.value[1])
|
|
|
|
|
|
|
|
|
|
|
|
const response = await apiFunc({
|
|
|
|
|
|
sDate: dateRange.value[0],
|
|
|
|
|
|
eDate: dateRange.value[1],
|
|
|
|
|
|
pageNo: 1,
|
|
|
|
|
|
pageSize: 10000
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
console.log('隐患管理接口返回:', response)
|
|
|
|
|
|
|
|
|
|
|
|
const records = response?.records || []
|
|
|
|
|
|
|
|
|
|
|
|
if (records && records.length > 0) {
|
|
|
|
|
|
// 按日期和等级分组统计,用于折线图
|
|
|
|
|
|
const trendMap = new Map<string, { general: number; major: number }>()
|
|
|
|
|
|
|
|
|
|
|
|
// 按状态统计,用于整改状态列表
|
|
|
|
|
|
const statusMap = new Map<string, number>()
|
|
|
|
|
|
|
|
|
|
|
|
records.forEach((item: any) => {
|
|
|
|
|
|
const dayname = item.dayname || ''
|
|
|
|
|
|
const level = item.name || ''
|
|
|
|
|
|
const status = item.status || ''
|
|
|
|
|
|
const count = Number(item.total || 0)
|
|
|
|
|
|
|
|
|
|
|
|
// 统计趋势数据(按日期和等级)
|
|
|
|
|
|
if (dayname) {
|
|
|
|
|
|
if (!trendMap.has(dayname)) {
|
|
|
|
|
|
trendMap.set(dayname, { general: 0, major: 0 })
|
|
|
|
|
|
}
|
|
|
|
|
|
const trend = trendMap.get(dayname)!
|
|
|
|
|
|
if (level === '一般' || level === '一般隐患') {
|
|
|
|
|
|
trend.general += count
|
|
|
|
|
|
} else if (level === '重大' || level === '重大隐患') {
|
|
|
|
|
|
trend.major += count
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 统计状态数据
|
|
|
|
|
|
if (status) {
|
|
|
|
|
|
statusMap.set(status, (statusMap.get(status) || 0) + count)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为数组并按日期排序
|
|
|
|
|
|
hiddenDangerTrend.value = Array.from(trendMap.entries())
|
|
|
|
|
|
.map(([date, counts]) => ({ date, ...counts }))
|
|
|
|
|
|
.sort((a, b) => {
|
|
|
|
|
|
// 提取日期中的数字部分进行排序
|
|
|
|
|
|
const numA = parseInt(a.date.replace('日', '')) || 0
|
|
|
|
|
|
const numB = parseInt(b.date.replace('日', '')) || 0
|
|
|
|
|
|
return numA - numB
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 更新整改状态数据
|
|
|
|
|
|
rectificationStatus.value = [
|
|
|
|
|
|
{
|
|
|
|
|
|
status: '已逾期',
|
|
|
|
|
|
count: statusMap.get('已逾期') || 0,
|
|
|
|
|
|
color: '#ef4444'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
status: '处理中',
|
|
|
|
|
|
count: statusMap.get('处理中') || 0,
|
|
|
|
|
|
color: '#f59e0b'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
status: '已处理',
|
|
|
|
|
|
count: statusMap.get('已处理') || 0,
|
|
|
|
|
|
color: '#10b981'
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
console.log('处理后的隐患管理数据:', {
|
|
|
|
|
|
trend: hiddenDangerTrend.value,
|
|
|
|
|
|
status: rectificationStatus.value
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果没有数据,重置为空
|
|
|
|
|
|
hiddenDangerTrend.value = []
|
|
|
|
|
|
rectificationStatus.value = [
|
|
|
|
|
|
{ status: '已逾期', count: 0, color: '#ef4444' },
|
|
|
|
|
|
{ status: '处理中', count: 0, color: '#f59e0b' },
|
|
|
|
|
|
{ status: '已处理', count: 0, color: '#10b981' }
|
|
|
|
|
|
]
|
|
|
|
|
|
console.log('隐患管理无数据')
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取隐患管理数据失败:', error)
|
|
|
|
|
|
// 如果接口失败,重置为空
|
|
|
|
|
|
hiddenDangerTrend.value = []
|
|
|
|
|
|
rectificationStatus.value = [
|
|
|
|
|
|
{ status: '已逾期', count: 0, color: '#ef4444' },
|
|
|
|
|
|
{ status: '处理中', count: 0, color: '#f59e0b' },
|
|
|
|
|
|
{ status: '已处理', count: 0, color: '#10b981' }
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const initHighRiskData = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await getHighRiskManagementData({
|
|
|
|
|
|
sDate: dateRange.value[0],
|
|
|
|
|
|
eDate: dateRange.value[1],
|
|
|
|
|
|
pageNo: 1,
|
|
|
|
|
|
pageSize: 10000
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
console.log('高危作业接口返回:', response)
|
|
|
|
|
|
|
|
|
|
|
|
const records = response?.records || []
|
|
|
|
|
|
|
|
|
|
|
|
if (records && records.length > 0) {
|
|
|
|
|
|
// 按作业类型分组统计,用于环形图
|
|
|
|
|
|
const typeMap = new Map<string, number>()
|
|
|
|
|
|
|
|
|
|
|
|
// 按区域分组统计,用于区域分布列表
|
|
|
|
|
|
const areaMap = new Map<string, number>()
|
|
|
|
|
|
|
|
|
|
|
|
records.forEach((item: any) => {
|
|
|
|
|
|
const itemType = item.item || ''
|
|
|
|
|
|
const area = item.area || ''
|
|
|
|
|
|
const count = Number(item.total || 0)
|
|
|
|
|
|
|
|
|
|
|
|
// 统计作业类型
|
|
|
|
|
|
if (itemType) {
|
|
|
|
|
|
typeMap.set(itemType, (typeMap.get(itemType) || 0) + count)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 统计区域分布
|
|
|
|
|
|
if (area) {
|
|
|
|
|
|
areaMap.set(area, (areaMap.get(area) || 0) + count)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 计算总数
|
|
|
|
|
|
const total = Array.from(typeMap.values()).reduce((sum, count) => sum + count, 0)
|
|
|
|
|
|
highRiskTotal.value = total
|
|
|
|
|
|
|
|
|
|
|
|
// 处理作业类型分布数据
|
|
|
|
|
|
operationTypeDistribution.value = Array.from(typeMap.entries())
|
|
|
|
|
|
.map(([type, count]) => ({
|
|
|
|
|
|
type,
|
|
|
|
|
|
count,
|
|
|
|
|
|
percent: total > 0 ? ((count / total) * 100).toFixed(1) + '%' : '0%',
|
|
|
|
|
|
color: operationTypeColors[type] || '#9ca3af' // 如果没有配置颜色,使用灰色
|
|
|
|
|
|
}))
|
|
|
|
|
|
.sort((a, b) => b.count - a.count) // 按数量降序排序
|
|
|
|
|
|
|
|
|
|
|
|
// 处理区域分布数据
|
|
|
|
|
|
operationDistribution.value = Array.from(areaMap.entries())
|
|
|
|
|
|
.map(([region, count]) => ({
|
|
|
|
|
|
region,
|
|
|
|
|
|
count
|
|
|
|
|
|
}))
|
|
|
|
|
|
.sort((a, b) => b.count - a.count) // 按数量降序排序
|
|
|
|
|
|
|
|
|
|
|
|
console.log('处理后的高危作业数据:', {
|
|
|
|
|
|
total: highRiskTotal.value,
|
|
|
|
|
|
typeDistribution: operationTypeDistribution.value,
|
|
|
|
|
|
areaDistribution: operationDistribution.value
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果没有数据,重置为空
|
|
|
|
|
|
highRiskTotal.value = 0
|
|
|
|
|
|
operationTypeDistribution.value = []
|
|
|
|
|
|
operationDistribution.value = []
|
|
|
|
|
|
console.log('高危作业无数据')
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取高危作业数据失败:', error)
|
|
|
|
|
|
// 如果接口失败,重置为空
|
|
|
|
|
|
highRiskTotal.value = 0
|
|
|
|
|
|
operationTypeDistribution.value = []
|
|
|
|
|
|
operationDistribution.value = []
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const initEmergencyPlanData = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await getEmergencyPlanManagementData({
|
|
|
|
|
|
sDate: dateRange.value[0],
|
|
|
|
|
|
eDate: dateRange.value[1],
|
|
|
|
|
|
pageNo: 1,
|
|
|
|
|
|
pageSize: 10000
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
console.log('应急预案接口返回:', response)
|
|
|
|
|
|
|
|
|
|
|
|
const records = response?.records || []
|
|
|
|
|
|
|
|
|
|
|
|
if (records && records.length > 0) {
|
|
|
|
|
|
// 统计应完成演练总数(所有记录的total总和)
|
|
|
|
|
|
const total = records.reduce((sum: number, item: any) => {
|
|
|
|
|
|
return sum + Number(item.total || 0)
|
|
|
|
|
|
}, 0)
|
|
|
|
|
|
|
|
|
|
|
|
emergencyPlanTotal.value = total
|
|
|
|
|
|
|
|
|
|
|
|
// 统计已完成演练数(根据状态判断)
|
|
|
|
|
|
// 可能的完成状态:已完成、已完成演练、已执行等
|
|
|
|
|
|
const completedCount = records.reduce((sum: number, item: any) => {
|
|
|
|
|
|
const status = item.status || ''
|
|
|
|
|
|
const count = Number(item.total || 0)
|
|
|
|
|
|
// 判断是否已完成:包含"完成"或"已执行"的状态
|
|
|
|
|
|
if (status.includes('完成') || status.includes('已执行')) {
|
|
|
|
|
|
return sum + count
|
|
|
|
|
|
}
|
|
|
|
|
|
return sum
|
|
|
|
|
|
}, 0)
|
|
|
|
|
|
|
|
|
|
|
|
emergencyPlanCompleted.value = completedCount
|
|
|
|
|
|
|
|
|
|
|
|
// 按区域统计演练完成率
|
|
|
|
|
|
const areaMap = new Map<string, { total: number; completed: number }>()
|
|
|
|
|
|
|
|
|
|
|
|
records.forEach((item: any) => {
|
|
|
|
|
|
const area = item.area || ''
|
|
|
|
|
|
const count = Number(item.total || 0)
|
|
|
|
|
|
const status = item.status || ''
|
|
|
|
|
|
const isCompleted = status.includes('完成') || status.includes('已执行')
|
|
|
|
|
|
|
|
|
|
|
|
if (area) {
|
|
|
|
|
|
if (!areaMap.has(area)) {
|
|
|
|
|
|
areaMap.set(area, { total: 0, completed: 0 })
|
|
|
|
|
|
}
|
|
|
|
|
|
const areaData = areaMap.get(area)!
|
|
|
|
|
|
areaData.total += count
|
|
|
|
|
|
if (isCompleted) {
|
|
|
|
|
|
areaData.completed += count
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为数组并计算完成率
|
|
|
|
|
|
regionalDrillProgress.value = Array.from(areaMap.entries())
|
|
|
|
|
|
.map(([region, data]) => {
|
|
|
|
|
|
const percent = data.total > 0
|
|
|
|
|
|
? ((data.completed / data.total) * 100).toFixed(0) + '%'
|
|
|
|
|
|
: '0%'
|
|
|
|
|
|
return { region, percent }
|
|
|
|
|
|
})
|
|
|
|
|
|
.sort((a, b) => {
|
|
|
|
|
|
// 按完成率降序排序
|
|
|
|
|
|
const percentA = parseFloat(a.percent)
|
|
|
|
|
|
const percentB = parseFloat(b.percent)
|
|
|
|
|
|
return percentB - percentA
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
console.log('处理后的应急预案数据:', {
|
|
|
|
|
|
total: emergencyPlanTotal.value,
|
|
|
|
|
|
completed: emergencyPlanCompleted.value,
|
|
|
|
|
|
regionalProgress: regionalDrillProgress.value
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果没有数据,重置为空
|
|
|
|
|
|
emergencyPlanTotal.value = 0
|
|
|
|
|
|
emergencyPlanCompleted.value = 0
|
|
|
|
|
|
regionalDrillProgress.value = []
|
|
|
|
|
|
console.log('应急预案无数据')
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取应急预案数据失败:', error)
|
|
|
|
|
|
// 如果接口失败,重置为空
|
|
|
|
|
|
emergencyPlanTotal.value = 0
|
|
|
|
|
|
emergencyPlanCompleted.value = 0
|
|
|
|
|
|
regionalDrillProgress.value = []
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const initTrainingData = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await getTrainingManagementData({
|
|
|
|
|
|
sDate: dateRange.value[0],
|
|
|
|
|
|
eDate: dateRange.value[1],
|
|
|
|
|
|
pageNo: 1,
|
|
|
|
|
|
pageSize: 10000
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
console.log('安全培训接口返回:', response)
|
|
|
|
|
|
|
|
|
|
|
|
const records = response?.records || []
|
|
|
|
|
|
|
|
|
|
|
|
if (records && records.length > 0) {
|
|
|
|
|
|
// 按区域分组统计
|
|
|
|
|
|
const areaMap = new Map<string, { trainingCount: number; participants: number }>()
|
|
|
|
|
|
|
|
|
|
|
|
records.forEach((item: any) => {
|
|
|
|
|
|
// 只统计有区域字段的记录
|
|
|
|
|
|
if (!item.area) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const area = item.area
|
|
|
|
|
|
const trainingCount = Number(item.plannum || 0) // 计划数量作为培训次数
|
|
|
|
|
|
const participants = Number(item.exenum || 0) // 执行数量作为参与人次
|
|
|
|
|
|
|
|
|
|
|
|
if (!areaMap.has(area)) {
|
|
|
|
|
|
areaMap.set(area, { trainingCount: 0, participants: 0 })
|
|
|
|
|
|
}
|
|
|
|
|
|
const areaData = areaMap.get(area)!
|
|
|
|
|
|
areaData.trainingCount += trainingCount
|
|
|
|
|
|
areaData.participants += participants
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为数组并排序
|
|
|
|
|
|
const areaDataArray = Array.from(areaMap.entries())
|
|
|
|
|
|
.map(([region, data]) => ({
|
|
|
|
|
|
region,
|
|
|
|
|
|
...data,
|
|
|
|
|
|
// 计算完成率(参与人次 / 培训次数 * 100%)
|
|
|
|
|
|
percent: data.trainingCount > 0
|
|
|
|
|
|
? ((data.participants / data.trainingCount) * 100).toFixed(0) + '%'
|
|
|
|
|
|
: '0%'
|
|
|
|
|
|
}))
|
|
|
|
|
|
.sort((a, b) => {
|
|
|
|
|
|
// 按培训次数降序排序
|
|
|
|
|
|
return b.trainingCount - a.trainingCount
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 更新柱状图数据
|
|
|
|
|
|
trainingBarData.value = {
|
|
|
|
|
|
regions: areaDataArray.map(item => item.region),
|
|
|
|
|
|
trainingCount: areaDataArray.map(item => item.trainingCount),
|
|
|
|
|
|
participants: areaDataArray.map(item => item.participants)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新区域培训完成率数据
|
|
|
|
|
|
regionalTrainingProgress.value = areaDataArray.map(item => ({
|
|
|
|
|
|
region: item.region,
|
|
|
|
|
|
percent: item.percent
|
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
|
|
console.log('处理后的安全培训数据:', {
|
|
|
|
|
|
barData: trainingBarData.value,
|
|
|
|
|
|
regionalProgress: regionalTrainingProgress.value
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果没有数据,重置为空
|
|
|
|
|
|
trainingBarData.value = {
|
|
|
|
|
|
regions: [],
|
|
|
|
|
|
trainingCount: [],
|
|
|
|
|
|
participants: []
|
|
|
|
|
|
}
|
|
|
|
|
|
regionalTrainingProgress.value = []
|
|
|
|
|
|
console.log('安全培训无数据')
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取安全培训数据失败:', error)
|
|
|
|
|
|
// 如果接口失败,重置为空
|
|
|
|
|
|
trainingBarData.value = {
|
|
|
|
|
|
regions: [],
|
|
|
|
|
|
trainingCount: [],
|
|
|
|
|
|
participants: []
|
|
|
|
|
|
}
|
|
|
|
|
|
regionalTrainingProgress.value = []
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const initData = async () => {
|
2025-11-26 14:13:02 +08:00
|
|
|
|
await initRegionData()
|
2025-11-29 14:44:18 +08:00
|
|
|
|
initOutsourcingData()
|
|
|
|
|
|
|
|
|
|
|
|
initRiskData()
|
|
|
|
|
|
initDangerData()
|
|
|
|
|
|
initHighRiskData()
|
|
|
|
|
|
initEmergencyPlanData()
|
|
|
|
|
|
initTrainingData()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
|
initData()
|
2025-10-17 10:31:13 +08:00
|
|
|
|
})
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.dashboard-container {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
|
background: #f5f7fa;
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
|
|
|
|
|
|
// 确保内容不会贴边
|
2025-10-17 10:31:13 +08:00
|
|
|
|
max-width: 100%;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
overflow-x: hidden;
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.header-container {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
padding: 16px 24px;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
|
|
|
|
|
|
.header-left {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
|
|
|
|
.back-button {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 8px 16px;
|
|
|
|
|
|
background: #f3f4f6;
|
|
|
|
|
|
border: 1px solid #e5e7eb;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
user-select: none;
|
|
|
|
|
|
|
|
|
|
|
|
span {
|
|
|
|
|
|
margin-left: 8px;
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
color: #6b7280;
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
&:hover {
|
|
|
|
|
|
background: #e5e7eb;
|
|
|
|
|
|
border-color: #d1d5db;
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
&:active {
|
|
|
|
|
|
transform: scale(0.98);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.header-title {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
color: #1f2937;
|
|
|
|
|
|
margin: 0;
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.header-right {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
min-width: 0; // 防止flex子元素溢出
|
|
|
|
|
|
|
|
|
|
|
|
.date-range-wrapper {
|
|
|
|
|
|
:deep(.el-date-editor) {
|
|
|
|
|
|
width: 280px;
|
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.refresh-btn {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
flex-shrink: 0; // 防止按钮被压缩
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.content-container {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 20px;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
box-sizing: border-box;
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.card-row {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(3, 1fr);
|
|
|
|
|
|
gap: 20px;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
box-sizing: border-box;
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
@media (max-width: 1400px) {
|
|
|
|
|
|
grid-template-columns: repeat(2, 1fr);
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
gap: 16px;
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.dashboard-card {
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
transition: box-shadow 0.3s;
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
&:hover {
|
|
|
|
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.card-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
padding-bottom: 12px;
|
|
|
|
|
|
border-bottom: 1px solid #e5e7eb;
|
|
|
|
|
|
|
|
|
|
|
|
.card-title {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
color: #1f2937;
|
|
|
|
|
|
|
|
|
|
|
|
.card-icon {
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
|
|
|
|
|
|
&.warning {
|
|
|
|
|
|
color: #ef4444;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.manage-btn {
|
|
|
|
|
|
color: #3b82f6;
|
|
|
|
|
|
padding: 4px 8px;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
transition: color 0.3s;
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
&:hover {
|
|
|
|
|
|
color: #2563eb;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.card-content {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.donut-chart-wrapper,
|
2025-11-29 14:44:18 +08:00
|
|
|
|
.donut-chart-wrapper-small {
|
|
|
|
|
|
height: 250px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.line-chart-wrapper,
|
2025-11-29 14:44:18 +08:00
|
|
|
|
.bar-chart-wrapper {
|
|
|
|
|
|
height: 200px;
|
|
|
|
|
|
background: linear-gradient(180deg, #faf9ff 0%, #ffffff 100%);
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
padding: 8px;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.progress-chart-wrapper {
|
2025-11-29 14:44:18 +08:00
|
|
|
|
height: 250px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.high-risk-top {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 15px;
|
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.donut-chart-wrapper-small {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.operation-type-list {
|
|
|
|
|
|
flex: 1.2;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.operation-type-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.operation-name {
|
|
|
|
|
|
width: 55px;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.operation-bar-wrapper {
|
2025-11-26 14:13:02 +08:00
|
|
|
|
flex: 1;
|
2025-11-29 14:44:18 +08:00
|
|
|
|
height: 12px;
|
|
|
|
|
|
background-color: #e5e7eb;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.operation-bar {
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.operation-percent {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
min-width: 40px;
|
|
|
|
|
|
text-align: right;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.emergency-plan-top {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 15px;
|
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.progress-chart-wrapper {
|
|
|
|
|
|
flex: 1.5;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.region-distribution,
|
|
|
|
|
|
.risk-distribution,
|
|
|
|
|
|
.rectification-status,
|
|
|
|
|
|
.operation-distribution,
|
2025-11-29 14:44:18 +08:00
|
|
|
|
.park-operation-distribution,
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.regional-progress {
|
2025-11-29 14:44:18 +08:00
|
|
|
|
margin-top: 10px;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
|
|
|
|
|
.distribution-title {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #374151;
|
|
|
|
|
|
margin-bottom: 12px;
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.distribution-list,
|
|
|
|
|
|
.status-list {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 8px;
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.distribution-item,
|
|
|
|
|
|
.status-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
color: #6b7280;
|
|
|
|
|
|
padding: 4px 0;
|
|
|
|
|
|
|
|
|
|
|
|
.dot {
|
|
|
|
|
|
width: 8px;
|
|
|
|
|
|
height: 8px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
flex-shrink: 0;
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.region-name,
|
|
|
|
|
|
.status-name {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
color: #374151;
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.region-count,
|
|
|
|
|
|
.status-count {
|
|
|
|
|
|
color: #1f2937;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
margin-left: auto;
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.region-percent {
|
|
|
|
|
|
color: #9ca3af;
|
|
|
|
|
|
margin-left: 4px;
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.drill-info {
|
|
|
|
|
|
display: flex;
|
2025-11-29 14:44:18 +08:00
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-29 14:44:18 +08:00
|
|
|
|
.drill-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 8px 10px;
|
|
|
|
|
|
background-color: #f0fdf4;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-29 14:44:18 +08:00
|
|
|
|
.drill-number {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
color: #10b981;
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.progress-list {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
2025-11-29 14:44:18 +08:00
|
|
|
|
gap: 14px;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
margin-top: 12px;
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.progress-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
font-size: 13px;
|
2025-11-29 14:44:18 +08:00
|
|
|
|
padding: 4px 0;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
|
|
|
|
|
.region-name {
|
2025-11-29 14:44:18 +08:00
|
|
|
|
width: 90px;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
color: #374151;
|
|
|
|
|
|
flex-shrink: 0;
|
2025-11-29 14:44:18 +08:00
|
|
|
|
font-weight: 500;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.progress-bar-wrapper {
|
|
|
|
|
|
flex: 1;
|
2025-11-29 14:44:18 +08:00
|
|
|
|
height: 10px;
|
|
|
|
|
|
background: #f3f4f6;
|
|
|
|
|
|
border-radius: 5px;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
overflow: hidden;
|
2025-11-29 14:44:18 +08:00
|
|
|
|
position: relative;
|
|
|
|
|
|
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.05);
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.progress-bar {
|
|
|
|
|
|
height: 100%;
|
2025-11-29 14:44:18 +08:00
|
|
|
|
border-radius: 5px;
|
|
|
|
|
|
transition: width 0.5s ease;
|
|
|
|
|
|
background: linear-gradient(90deg, #8b5cf6 0%, #a78bfa 100%);
|
|
|
|
|
|
box-shadow: 0 2px 4px rgba(139, 92, 246, 0.3);
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
|
|
|
|
&::after {
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
background: linear-gradient(
|
|
|
|
|
|
90deg,
|
|
|
|
|
|
transparent 0%,
|
|
|
|
|
|
rgba(255, 255, 255, 0.3) 50%,
|
|
|
|
|
|
transparent 100%
|
|
|
|
|
|
);
|
|
|
|
|
|
animation: shimmer 2s infinite;
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.progress-percent {
|
2025-11-29 14:44:18 +08:00
|
|
|
|
min-width: 45px;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
text-align: right;
|
2025-11-29 14:44:18 +08:00
|
|
|
|
color: #7c3aed;
|
|
|
|
|
|
font-weight: 600;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
flex-shrink: 0;
|
2025-11-29 14:44:18 +08:00
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@keyframes shimmer {
|
|
|
|
|
|
0% {
|
|
|
|
|
|
transform: translateX(-100%);
|
|
|
|
|
|
}
|
|
|
|
|
|
100% {
|
|
|
|
|
|
transform: translateX(100%);
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|