|
|
@@ -1,4 +1,4 @@
|
|
|
-<script lang="ts" setup>
|
|
|
+<script lang="ts" setup>
|
|
|
import * as echarts from 'echarts'
|
|
|
import { VueDatePicker } from '@vuepic/vue-datepicker'
|
|
|
import '@vuepic/vue-datepicker/dist/main.css'
|
|
|
@@ -46,6 +46,7 @@ type PermitStat = {
|
|
|
type SafeDayMap = Record<string, number>
|
|
|
|
|
|
type SummaryTabValue = 'home' | 'certificate'
|
|
|
+type QhseMetricValue = 'ltir' | 'trir' | 'pmva'
|
|
|
|
|
|
const userStore = useUserStore()
|
|
|
|
|
|
@@ -83,6 +84,7 @@ const timeVal = ref(getCurrentPickerValue(type.value))
|
|
|
const wrapperRef = ref<HTMLDivElement>()
|
|
|
const hazardChartRef = ref<HTMLDivElement>()
|
|
|
const safeDayChartRef = ref<HTMLDivElement>()
|
|
|
+const qhseTrendChartRef = ref<HTMLDivElement>()
|
|
|
const socChartRef = ref<HTMLDivElement>()
|
|
|
const scale = ref(1)
|
|
|
const supportsZoom = ref(false)
|
|
|
@@ -91,6 +93,7 @@ let resizeObserver: ResizeObserver | null = null
|
|
|
let resizeRaf = 0
|
|
|
let hazardChart: echarts.ECharts | null = null
|
|
|
let safeDayChart: echarts.ECharts | null = null
|
|
|
+let qhseTrendChart: echarts.ECharts | null = null
|
|
|
let socChart: echarts.ECharts | null = null
|
|
|
|
|
|
const summaryTabs: Array<{ label: string; value: SummaryTabValue }> = [
|
|
|
@@ -109,9 +112,11 @@ watch(
|
|
|
nextTick(() => {
|
|
|
initHazardChart()
|
|
|
initSafeDayChart()
|
|
|
+ initQhseTrendChart()
|
|
|
initSocChart()
|
|
|
resizeHazardChart()
|
|
|
resizeSafeDayChart()
|
|
|
+ resizeQhseTrendChart()
|
|
|
resizeSocChart()
|
|
|
})
|
|
|
}
|
|
|
@@ -199,6 +204,38 @@ const qualificationWarnings = ref([
|
|
|
{ label: '即将到期', value: 0, accent: '#e6ab00' }
|
|
|
])
|
|
|
|
|
|
+const qhseMetricTabs = [
|
|
|
+ { label: 'LTIR', value: 'ltir', accent: '#3d7cff' },
|
|
|
+ { label: 'TRIR', value: 'trir', accent: '#17b6c5' },
|
|
|
+ { label: 'PMVA', value: 'pmva', accent: '#f2a93b' }
|
|
|
+] as const
|
|
|
+
|
|
|
+const activeQhseMetric = ref<QhseMetricValue>('ltir')
|
|
|
+
|
|
|
+const qhseTrendSeries = ref<Record<QhseMetricValue, Array<{ year: string; value: number }>>>({
|
|
|
+ ltir: [
|
|
|
+ { year: `${new Date().getFullYear() - 4}`, value: 0.42 },
|
|
|
+ { year: `${new Date().getFullYear() - 3}`, value: 0.35 },
|
|
|
+ { year: `${new Date().getFullYear() - 2}`, value: 0.31 },
|
|
|
+ { year: `${new Date().getFullYear() - 1}`, value: 0.28 },
|
|
|
+ { year: `${new Date().getFullYear()}`, value: 0.24 }
|
|
|
+ ],
|
|
|
+ trir: [
|
|
|
+ { year: `${new Date().getFullYear() - 4}`, value: 0.78 },
|
|
|
+ { year: `${new Date().getFullYear() - 3}`, value: 0.73 },
|
|
|
+ { year: `${new Date().getFullYear() - 2}`, value: 0.68 },
|
|
|
+ { year: `${new Date().getFullYear() - 1}`, value: 0.6 },
|
|
|
+ { year: `${new Date().getFullYear()}`, value: 0.54 }
|
|
|
+ ],
|
|
|
+ pmva: [
|
|
|
+ { year: `${new Date().getFullYear() - 4}`, value: 0.22 },
|
|
|
+ { year: `${new Date().getFullYear() - 3}`, value: 0.19 },
|
|
|
+ { year: `${new Date().getFullYear() - 2}`, value: 0.17 },
|
|
|
+ { year: `${new Date().getFullYear() - 1}`, value: 0.14 },
|
|
|
+ { year: `${new Date().getFullYear()}`, value: 0.12 }
|
|
|
+ ]
|
|
|
+})
|
|
|
+
|
|
|
const bottomCards = ref([
|
|
|
{
|
|
|
title: '体系合规',
|
|
|
@@ -273,6 +310,7 @@ function updateScale() {
|
|
|
nextTick(() => {
|
|
|
resizeHazardChart()
|
|
|
resizeSafeDayChart()
|
|
|
+ resizeQhseTrendChart()
|
|
|
resizeSocChart()
|
|
|
})
|
|
|
})
|
|
|
@@ -448,7 +486,7 @@ function getSafeDayChartOption(): echarts.EChartsOption {
|
|
|
return {
|
|
|
...ANIMATION,
|
|
|
grid: {
|
|
|
- left: 52,
|
|
|
+ left: 32,
|
|
|
right: 32,
|
|
|
top: 12,
|
|
|
bottom: 24,
|
|
|
@@ -545,6 +583,123 @@ function destroySafeDayChart() {
|
|
|
safeDayChart = null
|
|
|
}
|
|
|
|
|
|
+function getQhseTrendChartOption(): echarts.EChartsOption {
|
|
|
+ const activeTab =
|
|
|
+ qhseMetricTabs.find((item) => item.value === activeQhseMetric.value) || qhseMetricTabs[0]
|
|
|
+ const seriesData = qhseTrendSeries.value[activeQhseMetric.value] || []
|
|
|
+
|
|
|
+ return {
|
|
|
+ ...ANIMATION,
|
|
|
+ grid: {
|
|
|
+ left: 32,
|
|
|
+ right: 18,
|
|
|
+ top: 28,
|
|
|
+ bottom: 28,
|
|
|
+ containLabel: true
|
|
|
+ },
|
|
|
+ tooltip: createTooltip({
|
|
|
+ trigger: 'axis',
|
|
|
+ confine: true,
|
|
|
+ appendToBody: false,
|
|
|
+ axisPointer: {
|
|
|
+ type: 'line',
|
|
|
+ lineStyle: {
|
|
|
+ color: `${activeTab.accent}99`,
|
|
|
+ width: 1.5
|
|
|
+ }
|
|
|
+ },
|
|
|
+ formatter(params: any) {
|
|
|
+ if (!params || (Array.isArray(params) && params.length === 0)) return ''
|
|
|
+ const item = Array.isArray(params) ? params[0] : params
|
|
|
+ return `${item.axisValue}<br/>${activeTab.label}:${item.data}`
|
|
|
+ }
|
|
|
+ }),
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ boundaryGap: false,
|
|
|
+ data: seriesData.map((item) => item.year),
|
|
|
+ axisLine: {
|
|
|
+ lineStyle: {
|
|
|
+ color: 'rgba(106, 144, 221, 0.45)'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ axisTick: { show: false },
|
|
|
+ axisLabel: {
|
|
|
+ color: '#5b6f8f',
|
|
|
+ fontSize: 13,
|
|
|
+ fontWeight: 600,
|
|
|
+ fontFamily: FONT_FAMILY
|
|
|
+ }
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ axisLine: { show: false },
|
|
|
+ axisTick: { show: false },
|
|
|
+ axisLabel: {
|
|
|
+ color: '#8a9bb5',
|
|
|
+ fontSize: 12,
|
|
|
+ fontFamily: FONT_FAMILY
|
|
|
+ },
|
|
|
+ splitLine: {
|
|
|
+ lineStyle: {
|
|
|
+ color: 'rgba(104, 139, 205, 0.22)',
|
|
|
+ type: 'dashed'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ type: 'line',
|
|
|
+ smooth: true,
|
|
|
+ symbol: 'circle',
|
|
|
+ symbolSize: 9,
|
|
|
+ showSymbol: true,
|
|
|
+ data: seriesData.map((item) => item.value),
|
|
|
+ lineStyle: {
|
|
|
+ width: 3,
|
|
|
+ color: activeTab.accent
|
|
|
+ },
|
|
|
+ itemStyle: {
|
|
|
+ color: activeTab.accent,
|
|
|
+ borderColor: '#fff',
|
|
|
+ borderWidth: 2
|
|
|
+ },
|
|
|
+ areaStyle: {
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
+ { offset: 0, color: `${activeTab.accent}55` },
|
|
|
+ { offset: 1, color: `${activeTab.accent}08` }
|
|
|
+ ])
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function initQhseTrendChart() {
|
|
|
+ if (!qhseTrendChartRef.value) return
|
|
|
+ qhseTrendChart?.dispose()
|
|
|
+ qhseTrendChart = echarts.init(qhseTrendChartRef.value, undefined, {
|
|
|
+ renderer: CHART_RENDERER
|
|
|
+ })
|
|
|
+ qhseTrendChart.setOption(getQhseTrendChartOption(), { notMerge: true, lazyUpdate: false })
|
|
|
+}
|
|
|
+
|
|
|
+function updateQhseTrendChart() {
|
|
|
+ if (!qhseTrendChart) return
|
|
|
+ qhseTrendChart.clear()
|
|
|
+ qhseTrendChart.setOption(getQhseTrendChartOption(), { notMerge: true, lazyUpdate: false })
|
|
|
+}
|
|
|
+
|
|
|
+function resizeQhseTrendChart() {
|
|
|
+ if (!qhseTrendChart) return
|
|
|
+ qhseTrendChart.resize({ animation: { duration: 300 } })
|
|
|
+}
|
|
|
+
|
|
|
+function destroyQhseTrendChart() {
|
|
|
+ qhseTrendChart?.dispose()
|
|
|
+ qhseTrendChart = null
|
|
|
+}
|
|
|
+
|
|
|
function getSocChartOption(): echarts.EChartsOption {
|
|
|
return {
|
|
|
...ANIMATION,
|
|
|
@@ -663,9 +818,11 @@ async function loadHomeBoard() {
|
|
|
nextTick(() => {
|
|
|
updateHazardChart()
|
|
|
updateSafeDayChart()
|
|
|
+ updateQhseTrendChart()
|
|
|
updateSocChart()
|
|
|
resizeHazardChart()
|
|
|
resizeSafeDayChart()
|
|
|
+ resizeQhseTrendChart()
|
|
|
resizeSocChart()
|
|
|
})
|
|
|
}
|
|
|
@@ -741,6 +898,7 @@ watch(
|
|
|
nextTick(() => {
|
|
|
resizeHazardChart()
|
|
|
resizeSafeDayChart()
|
|
|
+ resizeQhseTrendChart()
|
|
|
resizeSocChart()
|
|
|
})
|
|
|
}
|
|
|
@@ -763,10 +921,12 @@ onMounted(async () => {
|
|
|
}
|
|
|
initHazardChart()
|
|
|
initSafeDayChart()
|
|
|
+ initQhseTrendChart()
|
|
|
initSocChart()
|
|
|
window.addEventListener('resize', updateScale)
|
|
|
window.addEventListener('resize', resizeHazardChart)
|
|
|
window.addEventListener('resize', resizeSafeDayChart)
|
|
|
+ window.addEventListener('resize', resizeQhseTrendChart)
|
|
|
window.addEventListener('resize', resizeSocChart)
|
|
|
|
|
|
await loadHomeBoard()
|
|
|
@@ -777,10 +937,12 @@ onUnmounted(() => {
|
|
|
window.removeEventListener('resize', updateScale)
|
|
|
window.removeEventListener('resize', resizeHazardChart)
|
|
|
window.removeEventListener('resize', resizeSafeDayChart)
|
|
|
+ window.removeEventListener('resize', resizeQhseTrendChart)
|
|
|
window.removeEventListener('resize', resizeSocChart)
|
|
|
cancelAnimationFrame(resizeRaf)
|
|
|
destroyHazardChart()
|
|
|
destroySafeDayChart()
|
|
|
+ destroyQhseTrendChart()
|
|
|
destroySocChart()
|
|
|
})
|
|
|
</script>
|
|
|
@@ -902,10 +1064,6 @@ onUnmounted(() => {
|
|
|
</div>
|
|
|
|
|
|
<div class="risk-hazard-block">
|
|
|
- <div class="panel-title risk-hazard-block__title">
|
|
|
- <span class="icon-decorator"><span></span><span></span></span>
|
|
|
- 隐患排查治理统计
|
|
|
- </div>
|
|
|
<div
|
|
|
ref="hazardChartRef"
|
|
|
class="chart-panel chart-panel--echart risk-hazard-block__chart"></div>
|
|
|
@@ -920,29 +1078,31 @@ onUnmounted(() => {
|
|
|
QHSE指标
|
|
|
</div>
|
|
|
|
|
|
- <section class="board-panel kb-stage-card kb-stage-card--5 pt-2">
|
|
|
- <div class="panel-title">
|
|
|
- <!-- <span class="icon-decorator"><span></span><span></span></span> -->
|
|
|
- 结果指标
|
|
|
- </div>
|
|
|
+ <section class="board-panel kb-stage-card kb-stage-card--5">
|
|
|
<div ref="safeDayChartRef" class="safe-day-chart-panel"></div>
|
|
|
</section>
|
|
|
|
|
|
- <section class="board-panel kb-stage-card kb-stage-card--5 pt-2">
|
|
|
- <div class="panel-title flex items-center justify-between">
|
|
|
- <!-- <span class="icon-decorator"><span></span><span></span></span> -->
|
|
|
- 控制指标
|
|
|
- <div class="panel-title__right">
|
|
|
- <el-button
|
|
|
- type="primary"
|
|
|
- size="small"
|
|
|
- @click="handleAddControl"
|
|
|
- style="margin-right: 10px"
|
|
|
- >添加</el-button
|
|
|
- >
|
|
|
- </div>
|
|
|
+ <section class="kb-stage-card kb-stage-card--5">
|
|
|
+ <div class="qhse-metric-tabs">
|
|
|
+ <button
|
|
|
+ v-for="item in qhseMetricTabs"
|
|
|
+ :key="item.value"
|
|
|
+ type="button"
|
|
|
+ class="qhse-metric-tab"
|
|
|
+ :class="{ 'is-active': activeQhseMetric === item.value }"
|
|
|
+ @click="
|
|
|
+ () => {
|
|
|
+ activeQhseMetric = item.value
|
|
|
+ nextTick(() => {
|
|
|
+ updateQhseTrendChart()
|
|
|
+ resizeQhseTrendChart()
|
|
|
+ })
|
|
|
+ }
|
|
|
+ ">
|
|
|
+ {{ item.label }}
|
|
|
+ </button>
|
|
|
</div>
|
|
|
- <div ref="safeDayChartRef" class="safe-day-chart-panel"></div>
|
|
|
+ <div ref="qhseTrendChartRef" class="qhse-trend-chart-panel"></div>
|
|
|
</section>
|
|
|
</section>
|
|
|
</div>
|
|
|
@@ -954,18 +1114,10 @@ onUnmounted(() => {
|
|
|
行为安全与风险预警
|
|
|
</div>
|
|
|
<section class="board-panel kb-stage-card kb-stage-card--5 pt-2">
|
|
|
- <div class="panel-title">
|
|
|
- <!-- <span class="icon-decorator"><span></span><span></span></span> -->
|
|
|
- SOC卡类型
|
|
|
- </div>
|
|
|
<div ref="socChartRef" class="soc-chart-panel"></div>
|
|
|
</section>
|
|
|
|
|
|
<section class="board-panel kb-stage-card kb-stage-card--6 pl-4">
|
|
|
- <div class="panel-title">
|
|
|
- <span class="icon-decorator"><span></span><span></span></span>
|
|
|
- 人员资质风险预警
|
|
|
- </div>
|
|
|
<div class="qualification-panel">
|
|
|
<div class="qualification-icon">
|
|
|
<el-icon>
|
|
|
@@ -1328,6 +1480,57 @@ onUnmounted(() => {
|
|
|
height: 218px;
|
|
|
}
|
|
|
|
|
|
+.qhse-metric-tabs {
|
|
|
+ display: flex;
|
|
|
+ justify-content: left;
|
|
|
+ gap: 5px;
|
|
|
+ padding-left: 30px;
|
|
|
+}
|
|
|
+
|
|
|
+.qhse-metric-tab {
|
|
|
+ min-height: 24px;
|
|
|
+ padding: 5px 12px;
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 700;
|
|
|
+ line-height: 1.35;
|
|
|
+ color: #4d6487;
|
|
|
+ text-align: center;
|
|
|
+ background: linear-gradient(180deg, rgb(255 255 255 / 86%) 0%, rgb(228 239 255 / 82%) 100%);
|
|
|
+ border: 1px solid rgb(118 167 238 / 28%);
|
|
|
+ border-radius: 14px;
|
|
|
+ box-shadow:
|
|
|
+ inset 0 1px 0 rgb(255 255 255 / 92%),
|
|
|
+ 0 10px 16px rgb(52 94 164 / 8%);
|
|
|
+ cursor: pointer;
|
|
|
+ transition:
|
|
|
+ transform 0.2s ease,
|
|
|
+ box-shadow 0.2s ease,
|
|
|
+ color 0.2s ease,
|
|
|
+ background 0.2s ease,
|
|
|
+ border-color 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.qhse-metric-tab:hover {
|
|
|
+ transform: translateY(-1px);
|
|
|
+ box-shadow:
|
|
|
+ inset 0 1px 0 rgb(255 255 255 / 94%),
|
|
|
+ 0 12px 18px rgb(52 94 164 / 10%);
|
|
|
+}
|
|
|
+
|
|
|
+.qhse-metric-tab.is-active {
|
|
|
+ color: #fff;
|
|
|
+ background: linear-gradient(180deg, #63adff 0%, #347eea 100%);
|
|
|
+ border-color: rgb(62 122 223 / 76%);
|
|
|
+ box-shadow:
|
|
|
+ inset 0 1px 0 rgb(255 255 255 / 18%),
|
|
|
+ 0 12px 20px rgb(45 120 234 / 22%);
|
|
|
+}
|
|
|
+
|
|
|
+.qhse-trend-chart-panel {
|
|
|
+ height: 196px;
|
|
|
+ // margin-top: 14px;
|
|
|
+}
|
|
|
+
|
|
|
.soc-chart-panel {
|
|
|
height: 220px;
|
|
|
}
|