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">
|
|
|
|
|
|
<div class="donut-chart-wrapper">
|
|
|
|
|
|
<Echart :options="highRiskChartOption" width="100%" height="200px" />
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
<div class="operation-distribution">
|
|
|
|
|
|
<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">
|
|
|
|
|
|
<div class="progress-chart-wrapper">
|
|
|
|
|
|
<Echart :options="emergencyPlanChartOption" width="100%" height="200px" />
|
2025-10-17 10:31:13 +08:00
|
|
|
|
</div>
|
2025-11-26 14:13:02 +08:00
|
|
|
|
<div class="drill-info">
|
|
|
|
|
|
<div class="drill-item">
|
|
|
|
|
|
<span>应完成演练</span>
|
|
|
|
|
|
<span class="drill-number">76次</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="drill-item">
|
|
|
|
|
|
<span>已完成演练</span>
|
|
|
|
|
|
<span class="drill-number">59次</span>
|
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">
|
|
|
|
|
|
<div class="progress-bar" :style="{ width: item.percent, backgroundColor: '#8b5cf6' }"></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'
|
|
|
|
|
|
import { useRouter } from 'vue-router'
|
|
|
|
|
|
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'
|
|
|
|
|
|
import { getOutsourcingManagementData } 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-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-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]
|
|
|
|
|
|
}
|
|
|
|
|
|
const dateRange = ref(getCurrentMonthRange())
|
|
|
|
|
|
|
|
|
|
|
|
// 外协管理数据
|
|
|
|
|
|
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
|
|
|
|
// 风险管理数据
|
|
|
|
|
|
const riskDistribution = ref<DistributionItem[]>([
|
|
|
|
|
|
{ level: '低风险', count: 268, percent: '58.8%', color: '#10b981' },
|
|
|
|
|
|
{ level: '一般风险', count: 134, percent: '29.4%', color: '#f59e0b' },
|
|
|
|
|
|
{ level: '较大风险', count: 36, percent: '7.9%', color: '#ef4444' },
|
|
|
|
|
|
{ level: '重大风险', count: 18, percent: '3.9%', color: '#dc2626' }
|
|
|
|
|
|
])
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
// 隐患管理数据
|
|
|
|
|
|
const rectificationStatus = ref<DistributionItem[]>([
|
|
|
|
|
|
{ status: '已逾期', count: 24, color: '#ef4444' },
|
|
|
|
|
|
{ status: '处理中', count: 156, color: '#f59e0b' },
|
|
|
|
|
|
{ status: '已处理', count: 276, color: '#10b981' }
|
|
|
|
|
|
])
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
// 高危作业数据
|
|
|
|
|
|
const operationDistribution = ref([
|
|
|
|
|
|
{ region: '华北区域', count: 78 },
|
|
|
|
|
|
{ region: '华东区域', count: 65 },
|
|
|
|
|
|
{ region: '华南区域', count: 44 }
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
// 应急预案数据
|
|
|
|
|
|
const regionalDrillProgress = ref([
|
|
|
|
|
|
{ region: '华北区域', percent: '85%' },
|
|
|
|
|
|
{ region: '华东区域', percent: '75%' },
|
|
|
|
|
|
{ region: '华南区域', percent: '70%' }
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
// 安全培训数据
|
|
|
|
|
|
const regionalTrainingProgress = ref([
|
|
|
|
|
|
{ region: '华北区域', percent: '92%' },
|
|
|
|
|
|
{ region: '华东区域', percent: '88%' },
|
|
|
|
|
|
{ region: '华南区域', percent: '85%' }
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
// 外协管理环形图配置
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 风险管理环形图配置
|
|
|
|
|
|
const riskChartOption = computed<EChartsOption>(() => ({
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'item',
|
|
|
|
|
|
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
2025-10-17 10:31:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
2025-11-26 14:13:02 +08:00
|
|
|
|
name: '风险',
|
|
|
|
|
|
type: 'pie',
|
|
|
|
|
|
radius: ['55%', '75%'],
|
|
|
|
|
|
center: ['50%', '50%'],
|
|
|
|
|
|
avoidLabelOverlap: false,
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
borderRadius: 0,
|
|
|
|
|
|
borderColor: 'transparent',
|
|
|
|
|
|
borderWidth: 0
|
2025-10-17 10:31:13 +08:00
|
|
|
|
},
|
2025-11-26 14:13:02 +08:00
|
|
|
|
label: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
position: 'center',
|
|
|
|
|
|
formatter: () => {
|
|
|
|
|
|
return '456\n风险总数'
|
|
|
|
|
|
},
|
|
|
|
|
|
fontSize: 16,
|
|
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
|
color: '#333'
|
2025-10-17 10:31:13 +08:00
|
|
|
|
},
|
2025-11-26 14:13:02 +08:00
|
|
|
|
emphasis: {
|
|
|
|
|
|
label: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
fontSize: 18,
|
|
|
|
|
|
fontWeight: 'bold'
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-11-26 14:13:02 +08:00
|
|
|
|
data: [
|
|
|
|
|
|
{ value: 268, name: '低风险', itemStyle: { color: '#10b981' } },
|
|
|
|
|
|
{ value: 134, name: '一般风险', itemStyle: { color: '#f59e0b' } },
|
|
|
|
|
|
{ value: 36, name: '较大风险', itemStyle: { color: '#ef4444' } },
|
|
|
|
|
|
{ value: 18, name: '重大风险', itemStyle: { color: '#dc2626' } }
|
|
|
|
|
|
]
|
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
|
|
|
|
// 隐患管理折线图配置
|
|
|
|
|
|
const hiddenDangerChartOption = computed<EChartsOption>(() => ({
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'axis',
|
|
|
|
|
|
axisPointer: {
|
|
|
|
|
|
type: 'cross'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
legend: {
|
|
|
|
|
|
data: ['一般隐患', '重大隐患'],
|
|
|
|
|
|
top: 10,
|
|
|
|
|
|
textStyle: {
|
|
|
|
|
|
fontSize: 12
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-10-17 10:31:13 +08:00
|
|
|
|
grid: {
|
2025-11-26 14:13:02 +08:00
|
|
|
|
left: '3%',
|
|
|
|
|
|
right: '4%',
|
|
|
|
|
|
bottom: '3%',
|
2025-10-17 10:31:13 +08:00
|
|
|
|
top: '15%',
|
2025-11-26 14:13:02 +08:00
|
|
|
|
containLabel: true
|
2025-10-17 10:31:13 +08:00
|
|
|
|
},
|
2025-11-26 14:13:02 +08:00
|
|
|
|
xAxis: {
|
|
|
|
|
|
type: 'category',
|
|
|
|
|
|
boundaryGap: false,
|
|
|
|
|
|
data: ['16日', '18日', '20日', '22日', '24日'],
|
|
|
|
|
|
axisLine: {
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: '#e5e7eb'
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
},
|
|
|
|
|
|
yAxis: {
|
|
|
|
|
|
type: 'value',
|
|
|
|
|
|
max: 45,
|
|
|
|
|
|
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
|
|
|
|
series: [
|
|
|
|
|
|
{
|
2025-11-26 14:13:02 +08:00
|
|
|
|
name: '一般隐患',
|
2025-10-17 10:31:13 +08:00
|
|
|
|
type: 'line',
|
|
|
|
|
|
smooth: true,
|
2025-11-26 14:13:02 +08:00
|
|
|
|
data: [16, 24, 31, 20, 28],
|
|
|
|
|
|
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)' }
|
|
|
|
|
|
]
|
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
|
|
|
|
name: '重大隐患',
|
2025-10-17 10:31:13 +08:00
|
|
|
|
type: 'line',
|
|
|
|
|
|
smooth: true,
|
2025-11-26 14:13:02 +08:00
|
|
|
|
data: [8, 13, 26, 14, 20],
|
|
|
|
|
|
itemStyle: { color: '#ef4444' },
|
|
|
|
|
|
lineStyle: { color: '#ef4444', width: 2 },
|
2025-10-17 10:31:13 +08:00
|
|
|
|
areaStyle: {
|
|
|
|
|
|
color: {
|
|
|
|
|
|
type: 'linear',
|
|
|
|
|
|
x: 0,
|
|
|
|
|
|
y: 0,
|
|
|
|
|
|
x2: 0,
|
|
|
|
|
|
y2: 1,
|
|
|
|
|
|
colorStops: [
|
2025-11-26 14:13:02 +08:00
|
|
|
|
{ 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
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
|
|
// 高危作业环形图配置
|
|
|
|
|
|
const highRiskChartOption = computed<EChartsOption>(() => ({
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'item',
|
|
|
|
|
|
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
|
|
|
|
|
},
|
|
|
|
|
|
series: [
|
2025-10-17 10:31:13 +08:00
|
|
|
|
{
|
2025-11-26 14:13:02 +08:00
|
|
|
|
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 '187\n本月作业'
|
2025-10-17 10:31:13 +08:00
|
|
|
|
},
|
2025-11-26 14:13:02 +08:00
|
|
|
|
fontSize: 16,
|
|
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
|
color: '#333'
|
2025-10-17 10:31:13 +08:00
|
|
|
|
},
|
2025-11-26 14:13:02 +08:00
|
|
|
|
emphasis: {
|
|
|
|
|
|
label: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
fontSize: 18,
|
|
|
|
|
|
fontWeight: 'bold'
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
},
|
2025-11-26 14:13:02 +08:00
|
|
|
|
data: [
|
|
|
|
|
|
{ value: 50, name: '动火作业', itemStyle: { color: '#f59e0b' } },
|
|
|
|
|
|
{ value: 45, name: '高处作业', itemStyle: { color: '#8b5cf6' } },
|
|
|
|
|
|
{ value: 52, name: '临时用电', itemStyle: { color: '#3b82f6' } },
|
|
|
|
|
|
{ value: 40, name: '有限空间', itemStyle: { color: '#ec4899' } }
|
|
|
|
|
|
]
|
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
|
|
|
|
// 应急预案环形进度图配置
|
|
|
|
|
|
const emergencyPlanChartOption = computed<EChartsOption>(() => ({
|
|
|
|
|
|
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 '78%\n演练完成率'
|
|
|
|
|
|
},
|
|
|
|
|
|
fontSize: 16,
|
|
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
|
color: '#333'
|
|
|
|
|
|
},
|
|
|
|
|
|
emphasis: {
|
|
|
|
|
|
label: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
fontSize: 18,
|
|
|
|
|
|
fontWeight: 'bold'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
data: [
|
|
|
|
|
|
{ value: 78, name: '已完成', itemStyle: { color: '#10b981' } },
|
|
|
|
|
|
{ value: 22, name: '未完成', itemStyle: { color: '#e5e7eb' } }
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}))
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
// 安全培训柱状图配置
|
|
|
|
|
|
const safetyTrainingChartOption = computed<EChartsOption>(() => ({
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'axis',
|
|
|
|
|
|
axisPointer: {
|
|
|
|
|
|
type: 'shadow'
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
},
|
2025-11-26 14:13:02 +08:00
|
|
|
|
legend: {
|
|
|
|
|
|
data: ['培训次数', '参与人次'],
|
|
|
|
|
|
top: 10,
|
|
|
|
|
|
textStyle: {
|
|
|
|
|
|
fontSize: 12
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
grid: {
|
2025-11-26 14:13:02 +08:00
|
|
|
|
left: '3%',
|
|
|
|
|
|
right: '4%',
|
|
|
|
|
|
bottom: '3%',
|
|
|
|
|
|
top: '15%',
|
|
|
|
|
|
containLabel: true
|
2025-10-17 10:31:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
xAxis: {
|
|
|
|
|
|
type: 'category',
|
2025-11-26 14:13:02 +08:00
|
|
|
|
data: ['华北', '华东', '华南'],
|
2025-10-17 10:31:13 +08:00
|
|
|
|
axisLine: {
|
|
|
|
|
|
lineStyle: {
|
2025-11-26 14:13:02 +08:00
|
|
|
|
color: '#e5e7eb'
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-11-26 14:13:02 +08:00
|
|
|
|
yAxis: {
|
|
|
|
|
|
type: 'value',
|
|
|
|
|
|
max: 40,
|
|
|
|
|
|
axisLine: {
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: '#e5e7eb'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
splitLine: {
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: '#f3f4f6'
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-26 14:13:02 +08:00
|
|
|
|
},
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
|
|
|
|
|
name: '培训次数',
|
|
|
|
|
|
type: 'bar',
|
|
|
|
|
|
data: [35, 32, 25],
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
color: '#8b5cf6',
|
|
|
|
|
|
borderRadius: [4, 4, 0, 0]
|
2025-10-17 10:31:13 +08:00
|
|
|
|
},
|
2025-11-26 14:13:02 +08:00
|
|
|
|
barWidth: '40%'
|
2025-10-17 10:31:13 +08:00
|
|
|
|
},
|
2025-11-26 14:13:02 +08:00
|
|
|
|
{
|
|
|
|
|
|
name: '参与人次',
|
|
|
|
|
|
type: 'bar',
|
|
|
|
|
|
data: [25, 20, 18],
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
color: '#a78bfa',
|
|
|
|
|
|
borderRadius: [4, 4, 0, 0]
|
|
|
|
|
|
},
|
|
|
|
|
|
barWidth: '40%'
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}))
|
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',
|
|
|
|
|
|
query: { region: item.name, regionCode: item.code }
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
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 () => {
|
|
|
|
|
|
console.log('刷新数据')
|
|
|
|
|
|
await initOutsourcingData()
|
|
|
|
|
|
// TODO: 刷新其他数据
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化区域数据
|
|
|
|
|
|
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-26 14:13:02 +08:00
|
|
|
|
onMounted(async () => {
|
|
|
|
|
|
await initRegionData()
|
|
|
|
|
|
await initOutsourcingData()
|
|
|
|
|
|
// TODO: 初始化其他数据
|
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,
|
|
|
|
|
|
.line-chart-wrapper,
|
|
|
|
|
|
.bar-chart-wrapper,
|
|
|
|
|
|
.progress-chart-wrapper {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-height: 200px;
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.region-distribution,
|
|
|
|
|
|
.risk-distribution,
|
|
|
|
|
|
.rectification-status,
|
|
|
|
|
|
.operation-distribution,
|
|
|
|
|
|
.regional-progress {
|
|
|
|
|
|
margin-top: 16px;
|
|
|
|
|
|
|
|
|
|
|
|
.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;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
margin-top: 16px;
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
background: #f0fdf4;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
|
|
|
|
|
|
.drill-item {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
|
|
|
|
|
|
span:first-child {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #6b7280;
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.drill-number {
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
color: #059669;
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.progress-list {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
|
|
.region-name {
|
|
|
|
|
|
width: 80px;
|
|
|
|
|
|
color: #374151;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.progress-bar-wrapper {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
height: 8px;
|
|
|
|
|
|
background: #e5e7eb;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.progress-bar {
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
transition: width 0.3s ease;
|
|
|
|
|
|
}
|
2025-10-17 10:31:13 +08:00
|
|
|
|
|
2025-11-26 14:13:02 +08:00
|
|
|
|
.progress-percent {
|
|
|
|
|
|
width: 50px;
|
|
|
|
|
|
text-align: right;
|
|
|
|
|
|
color: #1f2937;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
flex-shrink: 0;
|
2025-10-17 10:31:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|