2025-11-26 14:13:02 +08:00
|
|
|
|
<template>
|
2025-11-27 18:12:53 +08:00
|
|
|
|
<div class="dashboard-container">
|
|
|
|
|
|
<!-- 顶部标题栏 -->
|
|
|
|
|
|
<div class="header-container">
|
|
|
|
|
|
<div class="header-left">
|
|
|
|
|
|
<el-icon class="back-arrow" @click="returnToRegion">
|
|
|
|
|
|
<ArrowLeft />
|
|
|
|
|
|
</el-icon>
|
|
|
|
|
|
<div class="park-name">{{ selectedPark }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<h1 class="header-title">园区视角数据看板</h1>
|
|
|
|
|
|
<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-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
2025-11-27 18:12:53 +08:00
|
|
|
|
<el-button type="primary" :icon="Refresh" @click="refreshData" class="refresh-btn">
|
|
|
|
|
|
刷新数据
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
<!-- 主内容区 - 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>
|
|
|
|
|
|
外协管理
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<el-button type="text" class="manage-btn">管理</el-button>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
2025-11-27 18:12:53 +08:00
|
|
|
|
<div class="card-content">
|
|
|
|
|
|
<div class="donut-chart-wrapper">
|
|
|
|
|
|
<Echart :options="outsourcingChartOption" width="100%" height="200px" />
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
2025-11-27 18:12:53 +08:00
|
|
|
|
<div class="region-distribution">
|
|
|
|
|
|
<div class="distribution-title">供应商分布</div>
|
|
|
|
|
|
<div class="distribution-list">
|
|
|
|
|
|
<div class="distribution-item" v-for="item in supplierDistribution" :key="item.supplier">
|
|
|
|
|
|
<span class="dot" :style="{ backgroundColor: item.color }"></span>
|
|
|
|
|
|
<span class="region-name">{{ item.supplier }}</span>
|
|
|
|
|
|
<span class="region-count">{{ item.count }}人</span>
|
|
|
|
|
|
<span class="region-percent">({{ item.percent }})</span>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-11-27 18:12:53 +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-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
2025-11-27 18:12:53 +08:00
|
|
|
|
<div class="risk-distribution-table">
|
|
|
|
|
|
<div class="distribution-title">地点风险分布</div>
|
|
|
|
|
|
<div class="table-wrapper">
|
|
|
|
|
|
<table class="risk-table">
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th>所在地点</th>
|
|
|
|
|
|
<th>低</th>
|
|
|
|
|
|
<th>一般</th>
|
|
|
|
|
|
<th>较大</th>
|
|
|
|
|
|
<th>重大</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
<tr v-for="item in locationRiskDistribution" :key="item.location">
|
|
|
|
|
|
<td>{{ item.location }}</td>
|
|
|
|
|
|
<td>{{ item.low || '' }}</td>
|
|
|
|
|
|
<td>{{ item.general }}</td>
|
|
|
|
|
|
<td>{{ item.moderate }}</td>
|
|
|
|
|
|
<td>{{ item.major }}</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-11-27 18:12:53 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 隐患管理卡片 -->
|
|
|
|
|
|
<div class="dashboard-card">
|
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
|
<div class="card-title">
|
|
|
|
|
|
<span class="card-icon warning">⚠️</span>
|
|
|
|
|
|
隐患管理
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
2025-11-27 18:12:53 +08:00
|
|
|
|
<el-button type="text" class="manage-btn">管理</el-button>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
2025-11-27 18:12:53 +08:00
|
|
|
|
<div class="card-content">
|
|
|
|
|
|
<div class="line-chart-wrapper">
|
|
|
|
|
|
<Echart :options="hiddenDangerChartOption" width="100%" height="180px" />
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
2025-11-27 18:12:53 +08:00
|
|
|
|
<div class="rectification-status-table">
|
|
|
|
|
|
<div class="distribution-title">所属公司整改状态</div>
|
|
|
|
|
|
<div class="table-wrapper">
|
|
|
|
|
|
<table class="status-table">
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th>所属公司</th>
|
|
|
|
|
|
<th>已逾期</th>
|
|
|
|
|
|
<th>处理中</th>
|
|
|
|
|
|
<th>已处理</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
<tr v-for="item in companyRectificationStatus" :key="item.company">
|
|
|
|
|
|
<td>{{ item.company }}</td>
|
|
|
|
|
|
<td class="overdue">{{ item.overdue }}</td>
|
|
|
|
|
|
<td class="processing">{{ item.processing }}</td>
|
|
|
|
|
|
<td class="processed">{{ item.processed }}</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-11-27 18:12:53 +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-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
2025-11-27 18:12:53 +08:00
|
|
|
|
<div class="card-content">
|
|
|
|
|
|
<div class="high-risk-top">
|
|
|
|
|
|
<div class="donut-chart-wrapper-small">
|
|
|
|
|
|
<Echart :options="highRiskChartOption" width="100%" height="250px" />
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
2025-11-27 18:12:53 +08:00
|
|
|
|
<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>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-11-27 18:12:53 +08:00
|
|
|
|
<div class="park-operation-distribution">
|
|
|
|
|
|
<div class="distribution-title">申请公司作业分布</div>
|
|
|
|
|
|
<div class="distribution-list">
|
|
|
|
|
|
<div class="distribution-item" v-for="item in companyOperationDistribution" :key="item.company">
|
|
|
|
|
|
<span class="region-name">{{ item.company }}</span>
|
|
|
|
|
|
<span class="region-count">{{ item.count }}项</span>
|
|
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-11-27 18:12:53 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 应急预案卡片 -->
|
|
|
|
|
|
<div class="dashboard-card">
|
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
|
<div class="card-title">
|
|
|
|
|
|
<span class="card-icon">📄</span>
|
|
|
|
|
|
应急预案
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
2025-11-27 18:12:53 +08:00
|
|
|
|
<el-button type="text" class="manage-btn">管理</el-button>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
2025-11-27 18:12:53 +08:00
|
|
|
|
<div class="card-content">
|
|
|
|
|
|
<div class="emergency-plan-top">
|
|
|
|
|
|
<div class="progress-chart-wrapper">
|
|
|
|
|
|
<Echart :options="emergencyPlanChartOption" width="100%" height="250px" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<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>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-11-27 18:12:53 +08:00
|
|
|
|
<div class="recent-drill-details">
|
|
|
|
|
|
<div class="distribution-title">近期演练详情</div>
|
|
|
|
|
|
<div class="drill-details-list">
|
|
|
|
|
|
<div class="drill-detail-item" v-for="item in recentDrillDetails" :key="item.id">
|
|
|
|
|
|
<div class="drill-detail-info">
|
|
|
|
|
|
<div class="drill-detail-name">{{ item.name }}</div>
|
|
|
|
|
|
<div class="drill-detail-date">{{ item.date }}</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
2025-11-27 18:12:53 +08:00
|
|
|
|
<div class="drill-detail-status" :class="item.status === '已完成' ? 'completed' : 'planned'">
|
|
|
|
|
|
{{ item.status }}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-11-27 18:12:53 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 安全培训卡片 -->
|
|
|
|
|
|
<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">
|
|
|
|
|
|
<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 companyTrainingProgress" :key="item.company">
|
|
|
|
|
|
<span class="region-name">{{ item.company }}</span>
|
|
|
|
|
|
<div class="progress-bar-wrapper">
|
|
|
|
|
|
<div class="progress-bar" :style="{ width: item.percent, backgroundColor: '#8b5cf6' }"></div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
2025-11-27 18:12:53 +08:00
|
|
|
|
<span class="progress-percent">{{ item.percent }}</span>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-11-27 18:12:53 +08:00
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-11-27 18:12:53 +08:00
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import { ref, computed, onMounted } from 'vue'
|
|
|
|
|
|
import { useRouter, useRoute } from 'vue-router'
|
|
|
|
|
|
import { Refresh, ArrowLeft } from '@element-plus/icons-vue'
|
|
|
|
|
|
import Echart from '@/components/Echart/src/Echart.vue'
|
|
|
|
|
|
import type { EChartsOption } from 'echarts'
|
|
|
|
|
|
import dayjs from 'dayjs'
|
|
|
|
|
|
import { getOutsourcingManagementData } from '@/api'
|
|
|
|
|
|
|
|
|
|
|
|
defineOptions({ name: 'Home13' })
|
|
|
|
|
|
|
|
|
|
|
|
// 类型定义
|
|
|
|
|
|
interface DistributionItem {
|
|
|
|
|
|
supplier?: string
|
|
|
|
|
|
company?: string
|
|
|
|
|
|
location?: string
|
|
|
|
|
|
level?: string
|
|
|
|
|
|
type?: string
|
|
|
|
|
|
count: number
|
|
|
|
|
|
percent?: string
|
|
|
|
|
|
color: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface LocationRiskItem {
|
|
|
|
|
|
location: string
|
|
|
|
|
|
low: number | string
|
|
|
|
|
|
general: number
|
|
|
|
|
|
moderate: number
|
|
|
|
|
|
major: number
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface CompanyRectificationItem {
|
|
|
|
|
|
company: string
|
|
|
|
|
|
overdue: number
|
|
|
|
|
|
processing: number
|
|
|
|
|
|
processed: number
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface RecentDrillItem {
|
|
|
|
|
|
id: string
|
|
|
|
|
|
name: string
|
|
|
|
|
|
date: string
|
|
|
|
|
|
status: string
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
const router = useRouter()
|
|
|
|
|
|
const route = useRoute()
|
|
|
|
|
|
|
|
|
|
|
|
// 园区名称 - 从路由参数获取
|
|
|
|
|
|
const selectedPark = ref<string>('')
|
|
|
|
|
|
|
|
|
|
|
|
// 时间选择相关 - 默认当前月起止
|
|
|
|
|
|
const getCurrentMonthRange = () => {
|
|
|
|
|
|
const start = dayjs().startOf('month').format('YYYY-MM-DD')
|
|
|
|
|
|
const end = dayjs().endOf('month').format('YYYY-MM-DD')
|
|
|
|
|
|
return [start, end]
|
|
|
|
|
|
}
|
|
|
|
|
|
const dateRange = ref(getCurrentMonthRange())
|
|
|
|
|
|
|
|
|
|
|
|
// 外协管理数据
|
|
|
|
|
|
const outsourcingTotal = ref<number>(0)
|
|
|
|
|
|
const supplierDistribution = ref<DistributionItem[]>([])
|
|
|
|
|
|
|
|
|
|
|
|
// 风险管理数据
|
|
|
|
|
|
const riskTotal = ref<number>(0)
|
|
|
|
|
|
const riskDistribution = ref<DistributionItem[]>([])
|
|
|
|
|
|
const locationRiskDistribution = ref<LocationRiskItem[]>([])
|
|
|
|
|
|
|
|
|
|
|
|
// 隐患管理数据
|
|
|
|
|
|
const hiddenDangerTrend = ref<any[]>([])
|
|
|
|
|
|
const companyRectificationStatus = ref<CompanyRectificationItem[]>([])
|
|
|
|
|
|
|
|
|
|
|
|
// 高危作业数据
|
|
|
|
|
|
const highRiskTotal = ref<number>(0)
|
|
|
|
|
|
const operationTypeDistribution = ref<DistributionItem[]>([])
|
|
|
|
|
|
const companyOperationDistribution = ref<DistributionItem[]>([])
|
|
|
|
|
|
|
|
|
|
|
|
// 应急预案数据
|
|
|
|
|
|
const emergencyPlanTotal = ref<number>(0)
|
|
|
|
|
|
const emergencyPlanCompleted = ref<number>(0)
|
|
|
|
|
|
const recentDrillDetails = ref<RecentDrillItem[]>([])
|
|
|
|
|
|
|
|
|
|
|
|
// 安全培训数据
|
|
|
|
|
|
const trainingKLineData = ref<{ company: string; values: number[] }[]>([])
|
|
|
|
|
|
const companyTrainingProgress = ref<DistributionItem[]>([])
|
|
|
|
|
|
|
|
|
|
|
|
// 区域颜色配置
|
|
|
|
|
|
const regionColors = ['#3b82f6', '#8b5cf6', '#06b6d4', '#10b981', '#f59e0b', '#ef4444', '#ec4899']
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化园区名称
|
|
|
|
|
|
const initParkName = () => {
|
|
|
|
|
|
if (typeof route.query.park === 'string') {
|
|
|
|
|
|
selectedPark.value = route.query.park
|
2025-11-26 14:13:02 +08:00
|
|
|
|
} else {
|
2025-11-27 18:12:53 +08:00
|
|
|
|
selectedPark.value = '雄安园区'
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
// 返回区域页面
|
|
|
|
|
|
const returnToRegion = () => {
|
|
|
|
|
|
router.push({
|
|
|
|
|
|
path: '/region',
|
|
|
|
|
|
query: { region: route.query.region, regionCode: route.query.regionCode }
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
// 日期变化
|
|
|
|
|
|
const handleDateChange = () => {
|
|
|
|
|
|
refreshData()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 刷新数据
|
|
|
|
|
|
const refreshData = () => {
|
|
|
|
|
|
initOutsourcingData()
|
|
|
|
|
|
initRiskData()
|
|
|
|
|
|
initHiddenDangerData()
|
|
|
|
|
|
initHighRiskData()
|
|
|
|
|
|
initEmergencyPlanData()
|
|
|
|
|
|
initSafetyTrainingData()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化外协管理数据
|
|
|
|
|
|
const initOutsourcingData = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await getOutsourcingManagementData({
|
|
|
|
|
|
sDate: dateRange.value[0],
|
|
|
|
|
|
eDate: dateRange.value[1],
|
|
|
|
|
|
pageNo: 1,
|
|
|
|
|
|
pageSize: 10000
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const records = response?.records || []
|
|
|
|
|
|
|
|
|
|
|
|
if (records && records.length > 0) {
|
|
|
|
|
|
const total = records.reduce((sum: number, item: any) => {
|
|
|
|
|
|
return sum + Number(item.total || 0)
|
|
|
|
|
|
}, 0)
|
|
|
|
|
|
|
|
|
|
|
|
outsourcingTotal.value = total
|
|
|
|
|
|
|
|
|
|
|
|
supplierDistribution.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 {
|
|
|
|
|
|
supplier: item.name || `供应商${String.fromCharCode(65 + index)}`,
|
|
|
|
|
|
count,
|
|
|
|
|
|
percent,
|
|
|
|
|
|
color
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
2025-11-27 18:12:53 +08:00
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
outsourcingTotal.value = 0
|
|
|
|
|
|
supplierDistribution.value = []
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
2025-11-27 18:12:53 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取外协管理数据失败:', error)
|
|
|
|
|
|
outsourcingTotal.value = 0
|
|
|
|
|
|
supplierDistribution.value = []
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
2025-11-27 18:12:53 +08:00
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
// 初始化风险管理数据
|
|
|
|
|
|
const initRiskData = async () => {
|
|
|
|
|
|
// TODO: 调用风险管理API
|
|
|
|
|
|
riskTotal.value = 92
|
|
|
|
|
|
riskDistribution.value = [
|
|
|
|
|
|
{ level: '低风险', count: 0, percent: '0%', color: '#10b981' },
|
|
|
|
|
|
{ level: '一般风险', count: 61, percent: '66.3%', color: '#f59e0b' },
|
|
|
|
|
|
{ level: '较大风险', count: 20, percent: '21.7%', color: '#ef4444' },
|
|
|
|
|
|
{ level: '重大风险', count: 11, percent: '12.0%', color: '#dc2626' }
|
2025-11-26 14:13:02 +08:00
|
|
|
|
]
|
2025-11-27 18:12:53 +08:00
|
|
|
|
|
|
|
|
|
|
locationRiskDistribution.value = [
|
|
|
|
|
|
{ location: '办公区', low: '', general: 28, moderate: 15, major: 5 },
|
|
|
|
|
|
{ location: '停车场', low: '', general: 18, moderate: 12, major: 3 },
|
|
|
|
|
|
{ location: '仓储区', low: '', general: 15, moderate: 8, major: 2 }
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
// 初始化隐患管理数据
|
|
|
|
|
|
const initHiddenDangerData = async () => {
|
|
|
|
|
|
// TODO: 调用隐患管理API
|
|
|
|
|
|
hiddenDangerTrend.value = [
|
|
|
|
|
|
{ date: '16日', general: 17, major: 10 },
|
|
|
|
|
|
{ date: '18日', general: 25, major: 13 },
|
|
|
|
|
|
{ date: '20日', general: 31, major: 25 },
|
|
|
|
|
|
{ date: '22日', general: 20, major: 13 },
|
|
|
|
|
|
{ date: '24日', general: 28, major: 20 }
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
companyRectificationStatus.value = [
|
|
|
|
|
|
{ company: 'A供应商', overdue: 3, processing: 15, processed: 28 },
|
|
|
|
|
|
{ company: 'B供应商', overdue: 2, processing: 10, processed: 18 },
|
|
|
|
|
|
{ company: 'C供应商', overdue: 1, processing: 8, processed: 12 }
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
// 初始化高危作业数据
|
|
|
|
|
|
const initHighRiskData = async () => {
|
|
|
|
|
|
// TODO: 调用高危作业API
|
|
|
|
|
|
highRiskTotal.value = 42
|
|
|
|
|
|
operationTypeDistribution.value = [
|
|
|
|
|
|
{ type: '动火作业', count: 16, percent: '38.1%', color: '#f59e0b' },
|
|
|
|
|
|
{ type: '高处作业', count: 12, percent: '28.6%', color: '#8b5cf6' },
|
|
|
|
|
|
{ type: '临时用电', count: 8, percent: '19.0%', color: '#3b82f6' },
|
|
|
|
|
|
{ type: '有限空间', count: 4, percent: '9.5%', color: '#ec4899' },
|
|
|
|
|
|
{ type: '动土作业', count: 1, percent: '2.4%', color: '#10b981' },
|
|
|
|
|
|
{ type: '吊装作业', count: 1, percent: '2.4%', color: '#ef4444' }
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
companyOperationDistribution.value = [
|
|
|
|
|
|
{ company: 'A供应商', count: 22, color: '#3b82f6' },
|
|
|
|
|
|
{ company: 'B供应商', count: 15, color: '#8b5cf6' },
|
|
|
|
|
|
{ company: 'C供应商', count: 5, color: '#06b6d4' }
|
|
|
|
|
|
]
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
2025-11-27 18:12:53 +08:00
|
|
|
|
|
|
|
|
|
|
// 初始化应急预案数据
|
|
|
|
|
|
const initEmergencyPlanData = async () => {
|
|
|
|
|
|
// TODO: 调用应急预案API
|
|
|
|
|
|
emergencyPlanTotal.value = 16
|
|
|
|
|
|
emergencyPlanCompleted.value = 12
|
|
|
|
|
|
recentDrillDetails.value = [
|
|
|
|
|
|
{ id: '1', name: '消防应急演练', date: '2025-10-15', status: '已完成' },
|
|
|
|
|
|
{ id: '2', name: '化学品泄漏演练', date: '2025-10-20', status: '已完成' },
|
|
|
|
|
|
{ id: '3', name: '停电应急演练', date: '2025-10-28', status: '计划中' }
|
|
|
|
|
|
]
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
2025-11-27 18:12:53 +08:00
|
|
|
|
|
|
|
|
|
|
// 初始化安全培训数据
|
|
|
|
|
|
const initSafetyTrainingData = async () => {
|
|
|
|
|
|
// TODO: 调用安全培训API
|
|
|
|
|
|
trainingKLineData.value = [
|
|
|
|
|
|
{ company: 'A供应商', values: [18, 28, 15, 32] },
|
|
|
|
|
|
{ company: 'B供应商', values: [16, 24, 12, 26] },
|
|
|
|
|
|
{ company: 'C供应商', values: [14, 22, 10, 24] }
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
companyTrainingProgress.value = [
|
|
|
|
|
|
{ company: 'A供应商', percent: '92%', color: '#8b5cf6', count: 0 },
|
|
|
|
|
|
{ company: 'B供应商', percent: '88%', color: '#8b5cf6', count: 0 },
|
|
|
|
|
|
{ company: 'C供应商', percent: '85%', color: '#8b5cf6', count: 0 }
|
|
|
|
|
|
]
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
2025-11-27 18:12:53 +08:00
|
|
|
|
|
|
|
|
|
|
// 外协管理环形图配置
|
|
|
|
|
|
const outsourcingChartOption = computed<EChartsOption>(() => {
|
|
|
|
|
|
const chartData = supplierDistribution.value.map(item => ({
|
|
|
|
|
|
value: item.count,
|
|
|
|
|
|
name: item.supplier,
|
|
|
|
|
|
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: () => `${outsourcingTotal.value}\n外协人员总数`,
|
|
|
|
|
|
fontSize: 16,
|
|
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
|
color: '#333'
|
|
|
|
|
|
},
|
|
|
|
|
|
data: [{ value: 1, name: '暂无数据', itemStyle: { color: '#e5e7eb' } }]
|
|
|
|
|
|
}]
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
2025-11-27 18:12:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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: () => `${outsourcingTotal.value}\n外协人员总数`,
|
|
|
|
|
|
fontSize: 16,
|
|
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
|
color: '#333'
|
2025-11-26 14:13:02 +08:00
|
|
|
|
},
|
2025-11-27 18:12:53 +08:00
|
|
|
|
emphasis: { label: { show: true, fontSize: 18, fontWeight: 'bold' } },
|
|
|
|
|
|
data: chartData
|
|
|
|
|
|
}]
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 风险管理环形图配置
|
|
|
|
|
|
const riskChartOption = computed<EChartsOption>(() => {
|
|
|
|
|
|
const chartData = riskDistribution.value.map(item => ({
|
|
|
|
|
|
value: item.count,
|
|
|
|
|
|
name: item.level,
|
|
|
|
|
|
itemStyle: { color: item.color }
|
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
|
|
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'
|
2025-11-26 14:13:02 +08:00
|
|
|
|
},
|
2025-11-27 18:12:53 +08:00
|
|
|
|
emphasis: { label: { show: true, fontSize: 18, fontWeight: 'bold' } },
|
|
|
|
|
|
data: chartData
|
|
|
|
|
|
}]
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 隐患管理折线图配置
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
tooltip: { trigger: 'axis' },
|
|
|
|
|
|
legend: {
|
|
|
|
|
|
data: ['一般隐患', '重大隐患'],
|
|
|
|
|
|
top: 10
|
2025-11-26 14:13:02 +08:00
|
|
|
|
},
|
2025-11-27 18:12:53 +08:00
|
|
|
|
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
|
|
|
|
|
xAxis: {
|
|
|
|
|
|
type: 'category',
|
|
|
|
|
|
boundaryGap: false,
|
|
|
|
|
|
data: dates
|
2025-11-26 14:13:02 +08:00
|
|
|
|
},
|
2025-11-27 18:12:53 +08:00
|
|
|
|
yAxis: { type: 'value', max: 45 },
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
|
|
|
|
|
name: '一般隐患',
|
|
|
|
|
|
type: 'line',
|
|
|
|
|
|
data: generalData,
|
|
|
|
|
|
itemStyle: { color: '#f59e0b' },
|
|
|
|
|
|
smooth: true
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
name: '重大隐患',
|
|
|
|
|
|
type: 'line',
|
|
|
|
|
|
data: majorData,
|
|
|
|
|
|
itemStyle: { color: '#ef4444' },
|
|
|
|
|
|
smooth: true
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
// 高危作业环形图配置
|
|
|
|
|
|
const highRiskChartOption = computed<EChartsOption>(() => {
|
|
|
|
|
|
return {
|
|
|
|
|
|
tooltip: { trigger: 'item', formatter: '{a} <br/>{b}: {c} ({d}%)' },
|
|
|
|
|
|
series: [{
|
|
|
|
|
|
name: '高危作业',
|
|
|
|
|
|
type: 'pie',
|
|
|
|
|
|
radius: ['60%', '75%'],
|
|
|
|
|
|
center: ['50%', '45%'],
|
|
|
|
|
|
avoidLabelOverlap: false,
|
|
|
|
|
|
itemStyle: { borderRadius: 0, borderColor: 'transparent', borderWidth: 0 },
|
|
|
|
|
|
label: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
position: 'center',
|
|
|
|
|
|
formatter: () => `${highRiskTotal.value}\n本月作业`,
|
|
|
|
|
|
fontSize: 18,
|
|
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
|
color: '#333'
|
|
|
|
|
|
},
|
|
|
|
|
|
emphasis: { label: { show: true, fontSize: 20, fontWeight: 'bold' } },
|
|
|
|
|
|
data: operationTypeDistribution.value.map(item => ({
|
|
|
|
|
|
value: item.count,
|
|
|
|
|
|
name: item.type,
|
|
|
|
|
|
itemStyle: { color: item.color }
|
|
|
|
|
|
}))
|
|
|
|
|
|
}]
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
2025-11-27 18:12:53 +08:00
|
|
|
|
})
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
// 应急预案环形图配置
|
|
|
|
|
|
const emergencyPlanChartOption = computed<EChartsOption>(() => {
|
|
|
|
|
|
const percent = emergencyPlanTotal.value > 0
|
|
|
|
|
|
? ((emergencyPlanCompleted.value / emergencyPlanTotal.value) * 100).toFixed(0)
|
|
|
|
|
|
: 0
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
tooltip: { trigger: 'item' },
|
|
|
|
|
|
series: [{
|
|
|
|
|
|
name: '演练完成率',
|
|
|
|
|
|
type: 'pie',
|
|
|
|
|
|
radius: ['60%', '75%'],
|
|
|
|
|
|
center: ['50%', '45%'],
|
|
|
|
|
|
avoidLabelOverlap: false,
|
|
|
|
|
|
itemStyle: { borderRadius: 0, borderColor: 'transparent', borderWidth: 0, color: '#10b981' },
|
|
|
|
|
|
label: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
position: 'center',
|
|
|
|
|
|
formatter: () => `${percent}%\n演练完成率`,
|
|
|
|
|
|
fontSize: 18,
|
|
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
|
color: '#333'
|
|
|
|
|
|
},
|
|
|
|
|
|
data: [
|
|
|
|
|
|
{ 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-27 18:12:53 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 安全培训K线图配置
|
|
|
|
|
|
const safetyTrainingChartOption = computed<EChartsOption>(() => {
|
|
|
|
|
|
const categories = trainingKLineData.value.map((item) => item.company)
|
|
|
|
|
|
const kData = trainingKLineData.value.map((item) => item.values)
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
tooltip: { trigger: 'axis', axisPointer: { type: 'cross' } },
|
|
|
|
|
|
legend: { data: ['培训走势K线'], top: 10 },
|
|
|
|
|
|
grid: { left: '8%', right: '4%', bottom: '8%', containLabel: true },
|
|
|
|
|
|
xAxis: {
|
|
|
|
|
|
type: 'category',
|
|
|
|
|
|
data: categories,
|
|
|
|
|
|
boundaryGap: true,
|
|
|
|
|
|
axisLine: { lineStyle: { color: '#d1d5db' } }
|
|
|
|
|
|
},
|
|
|
|
|
|
yAxis: {
|
|
|
|
|
|
type: 'value',
|
|
|
|
|
|
scale: true,
|
|
|
|
|
|
axisLine: { lineStyle: { color: '#d1d5db' } },
|
|
|
|
|
|
splitLine: { lineStyle: { color: '#f3f4f6' } }
|
|
|
|
|
|
},
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
|
|
|
|
|
name: '培训走势K线',
|
|
|
|
|
|
type: 'candlestick',
|
|
|
|
|
|
data: kData,
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
color: '#8b5cf6',
|
|
|
|
|
|
color0: '#c4b5fd',
|
|
|
|
|
|
borderColor: '#6d28d9',
|
|
|
|
|
|
borderColor0: '#a78bfa'
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
2025-11-27 18:12:53 +08:00
|
|
|
|
]
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
2025-11-27 18:12:53 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化数据
|
|
|
|
|
|
const initData = async () => {
|
|
|
|
|
|
initParkName()
|
|
|
|
|
|
await initOutsourcingData()
|
|
|
|
|
|
await initRiskData()
|
|
|
|
|
|
await initHiddenDangerData()
|
|
|
|
|
|
await initHighRiskData()
|
|
|
|
|
|
await initEmergencyPlanData()
|
|
|
|
|
|
await initSafetyTrainingData()
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
2025-11-27 18:12:53 +08:00
|
|
|
|
initData()
|
2025-11-26 14:13:02 +08:00
|
|
|
|
})
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.dashboard-container {
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
overflow-x: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.header-container {
|
2025-11-26 14:13:02 +08:00
|
|
|
|
display: flex;
|
2025-11-27 18:12:53 +08:00
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
padding: 15px 20px;
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
2025-11-27 18:12:53 +08:00
|
|
|
|
|
|
|
|
|
|
.header-left {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 10px;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.back-arrow {
|
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
color: #3b82f6;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
color: #2563eb;
|
|
|
|
|
|
transform: translateX(-2px);
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
2025-11-27 18:12:53 +08:00
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.park-name {
|
|
|
|
|
|
padding: 8px 16px;
|
|
|
|
|
|
background: #3b82f6;
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 500;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.header-title {
|
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.header-right {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 15px;
|
|
|
|
|
|
min-width: 0;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.date-range-wrapper {
|
|
|
|
|
|
:deep(.el-date-editor) {
|
|
|
|
|
|
max-width: 100%;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
2025-11-27 18:12:53 +08:00
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.refresh-btn {
|
|
|
|
|
|
flex-shrink: 0;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.content-container {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.card-row {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(3, 1fr);
|
|
|
|
|
|
gap: 20px;
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
box-sizing: border-box;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.dashboard-card {
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
|
padding-bottom: 10px;
|
|
|
|
|
|
border-bottom: 1px solid #e5e7eb;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.card-title {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.card-icon {
|
|
|
|
|
|
font-size: 18px;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.manage-btn {
|
|
|
|
|
|
color: #3b82f6;
|
|
|
|
|
|
padding: 0;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.card-content {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 15px;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.donut-chart-wrapper,
|
|
|
|
|
|
.donut-chart-wrapper-small {
|
|
|
|
|
|
height: 200px;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.line-chart-wrapper,
|
|
|
|
|
|
.bar-chart-wrapper {
|
|
|
|
|
|
height: 180px;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.progress-chart-wrapper {
|
|
|
|
|
|
height: 250px;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.region-distribution,
|
|
|
|
|
|
.risk-distribution-table,
|
|
|
|
|
|
.rectification-status-table,
|
|
|
|
|
|
.park-operation-distribution,
|
|
|
|
|
|
.recent-drill-details,
|
|
|
|
|
|
.regional-progress {
|
|
|
|
|
|
margin-top: 10px;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.distribution-title {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.distribution-list {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 8px;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.distribution-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.dot {
|
|
|
|
|
|
width: 8px;
|
|
|
|
|
|
height: 8px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
flex-shrink: 0;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.region-name {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
color: #666;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.region-count {
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.region-percent {
|
|
|
|
|
|
color: #999;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.table-wrapper {
|
|
|
|
|
|
overflow-x: auto;
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.risk-table,
|
|
|
|
|
|
.status-table {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
border-collapse: collapse;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
|
|
|
|
|
|
thead {
|
|
|
|
|
|
background-color: #f9fafb;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
th, td {
|
|
|
|
|
|
padding: 8px;
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
border-bottom: 1px solid #e5e7eb;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
th {
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
color: #333;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
2025-11-27 18:12:53 +08:00
|
|
|
|
|
|
|
|
|
|
td {
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.status-table {
|
|
|
|
|
|
.overdue {
|
|
|
|
|
|
color: #ef4444;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.processing {
|
|
|
|
|
|
color: #f59e0b;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.processed {
|
|
|
|
|
|
color: #10b981;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.high-risk-top {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 15px;
|
|
|
|
|
|
margin-bottom: 15px;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.donut-chart-wrapper-small {
|
|
|
|
|
|
flex: 1.5;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.operation-type-list {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.operation-type-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.operation-name {
|
|
|
|
|
|
width: 70px;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.operation-bar-wrapper {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
height: 8px;
|
|
|
|
|
|
background-color: #e5e7eb;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.operation-bar {
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.emergency-plan-top {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 15px;
|
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.progress-chart-wrapper {
|
|
|
|
|
|
flex: 1.5;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.drill-info {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +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-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.drill-number {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
color: #10b981;
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.recent-drill-details {
|
|
|
|
|
|
margin-top: 15px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.drill-details-list {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.drill-detail-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
background-color: #f9fafb;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.drill-detail-info {
|
|
|
|
|
|
flex: 1;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.drill-detail-name {
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.drill-detail-date {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.drill-detail-status {
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
|
|
|
|
|
|
&.completed {
|
|
|
|
|
|
color: #10b981;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.planned {
|
|
|
|
|
|
color: #f59e0b;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.progress-list {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.progress-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.progress-bar-wrapper {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
height: 8px;
|
|
|
|
|
|
background-color: #e5e7eb;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.progress-bar {
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
.progress-percent {
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
min-width: 40px;
|
|
|
|
|
|
text-align: right;
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
@media (max-width: 1400px) {
|
|
|
|
|
|
.card-row {
|
|
|
|
|
|
grid-template-columns: repeat(2, 1fr);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
|
2025-11-27 18:12:53 +08:00
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
|
.card-row {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.header-container {
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 15px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.header-title {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.high-risk-top,
|
|
|
|
|
|
.emergency-plan-top {
|
|
|
|
|
|
flex-direction: column;
|
2025-11-26 14:13:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|