yanghao vor 1 Woche
Ursprung
Commit
28e32121a7
1 geänderte Dateien mit 236 neuen und 33 gelöschten Zeilen
  1. 236 33
      src/views/pms/qhse/kanban/index.vue

+ 236 - 33
src/views/pms/qhse/kanban/index.vue

@@ -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;
 }