yanghao преди 16 часа
родител
ревизия
2361178f42
променени са 4 файла, в които са добавени 3181 реда и са изтрити 281 реда
  1. 1 0
      package.json
  2. 92 0
      pnpm-lock.yaml
  3. 396 281
      src/views/Home/Index.vue
  4. 2692 0
      src/views/Home/demo.vue

+ 1 - 0
package.json

@@ -31,6 +31,7 @@
     "@iconify/iconify": "^3.1.1",
     "@microsoft/fetch-event-source": "^2.0.1",
     "@videojs-player/vue": "^1.0.0",
+    "@vuepic/vue-datepicker": "^14.0.0",
     "@vueuse/core": "^10.9.0",
     "@wangeditor-next/editor": "^5.6.46",
     "@wangeditor-next/editor-for-vue": "^5.1.14",

+ 92 - 0
pnpm-lock.yaml

@@ -26,6 +26,9 @@ importers:
       '@videojs-player/vue':
         specifier: ^1.0.0
         version: 1.0.0(@types/video.js@7.3.58)(video.js@7.21.6)(vue@3.5.12(typescript@5.3.3))
+      '@vuepic/vue-datepicker':
+        specifier: ^14.0.0
+        version: 14.0.0(vue@3.5.12(typescript@5.3.3))
       '@vueuse/core':
         specifier: ^10.9.0
         version: 10.11.1(vue@3.5.12(typescript@5.3.3))
@@ -1026,6 +1029,9 @@ packages:
     resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==}
     engines: {node: '>=10'}
 
+  '@date-fns/tz@1.5.0':
+    resolution: {integrity: sha512-lwYN/vDPeNRULcepoE/LO2Pgx+7/RV+S9ARfbc9lr2DtGkOD7pAiruHvbR1RX3Qyf6ja47EWJDMsNK5vK08DJg==}
+
   '@dual-bundle/import-meta-resolve@4.1.0':
     resolution: {integrity: sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==}
 
@@ -1193,12 +1199,24 @@ packages:
   '@floating-ui/core@1.6.8':
     resolution: {integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==}
 
+  '@floating-ui/core@1.8.0':
+    resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==}
+
   '@floating-ui/dom@1.6.12':
     resolution: {integrity: sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==}
 
+  '@floating-ui/dom@1.8.0':
+    resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==}
+
+  '@floating-ui/utils@0.2.12':
+    resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==}
+
   '@floating-ui/utils@0.2.8':
     resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==}
 
+  '@floating-ui/vue@1.1.11':
+    resolution: {integrity: sha512-HzHKCNVxnGS35r9fCHBc3+uCnjw9IWIlCPL683cGgM9Kgj2BiAl8x1mS7vtvP6F9S/e/q4O6MApwSHj8hNLGfw==}
+
   '@form-create/component-elm-checkbox@3.2.14':
     resolution: {integrity: sha512-VtjRvNxbKpjp0bNYQ2BuLRVIQHZdPpYn3Hy0xSuzv6QjEDnffMdmawHImPSlp8wAW3b94wZdHMLMqpyMew8bBw==}
 
@@ -1842,6 +1860,9 @@ packages:
   '@types/web-bluetooth@0.0.20':
     resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
 
+  '@types/web-bluetooth@0.0.21':
+    resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
+
   '@typescript-eslint/eslint-plugin@7.18.0':
     resolution: {integrity: sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==}
     engines: {node: ^18.18.0 || >=20.0.0}
@@ -2219,21 +2240,40 @@ packages:
   '@vue/shared@3.5.13':
     resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==}
 
+  '@vuepic/vue-datepicker@14.0.0':
+    resolution: {integrity: sha512-zDl1U2MRk+udu0gYA3pdY2f0O+ckqUPiKgWmp7qQV3D7CRgDKYvUXFpnt+rgMQ+uLydtlRqzy4eS6nvtVjNS4A==}
+    engines: {node: '>=18.12.0'}
+    peerDependencies:
+      vue: '>=3.5.0'
+
   '@vueuse/core@10.11.1':
     resolution: {integrity: sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==}
 
+  '@vueuse/core@14.4.0':
+    resolution: {integrity: sha512-X4WHz1HlCzCBoYXesUkifzzWBAcZgXG8Fi5iNPQg/epdzOB3gu8Fawj3hvuwYR1nGcXGnvxwYYcUC/71++svtQ==}
+    peerDependencies:
+      vue: ^3.5.0
+
   '@vueuse/core@9.13.0':
     resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==}
 
   '@vueuse/metadata@10.11.1':
     resolution: {integrity: sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==}
 
+  '@vueuse/metadata@14.4.0':
+    resolution: {integrity: sha512-swx/255R6JyHZFJhx845iz5CRWDZdCfvkZOpACWc5+c5WHcG24mv8gUT1WIdFQaHt6dq79rvILd9QnCWiyVm9g==}
+
   '@vueuse/metadata@9.13.0':
     resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==}
 
   '@vueuse/shared@10.11.1':
     resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==}
 
+  '@vueuse/shared@14.4.0':
+    resolution: {integrity: sha512-JRgY90Sz8DDtPMsaDflvPMp9xYk69JZAmbuDvAquUVXKr2gEjqtzGNTTthLfckH0BzBqvnu31gb4a8TGLRe79g==}
+    peerDependencies:
+      vue: ^3.5.0
+
   '@vueuse/shared@9.13.0':
     resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==}
 
@@ -2898,6 +2938,9 @@ packages:
     resolution: {integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==}
     engines: {node: '>=12'}
 
+  date-fns@4.4.0:
+    resolution: {integrity: sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==}
+
   dayjs@1.11.13:
     resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
 
@@ -6086,6 +6129,8 @@ snapshots:
 
   '@ctrl/tinycolor@3.6.1': {}
 
+  '@date-fns/tz@1.5.0': {}
+
   '@dual-bundle/import-meta-resolve@4.1.0': {}
 
   '@element-plus/icons-vue@2.3.2(vue@3.5.12(typescript@5.3.3))':
@@ -6188,13 +6233,33 @@ snapshots:
     dependencies:
       '@floating-ui/utils': 0.2.8
 
+  '@floating-ui/core@1.8.0':
+    dependencies:
+      '@floating-ui/utils': 0.2.12
+
   '@floating-ui/dom@1.6.12':
     dependencies:
       '@floating-ui/core': 1.6.8
       '@floating-ui/utils': 0.2.8
 
+  '@floating-ui/dom@1.8.0':
+    dependencies:
+      '@floating-ui/core': 1.8.0
+      '@floating-ui/utils': 0.2.12
+
+  '@floating-ui/utils@0.2.12': {}
+
   '@floating-ui/utils@0.2.8': {}
 
+  '@floating-ui/vue@1.1.11(vue@3.5.12(typescript@5.3.3))':
+    dependencies:
+      '@floating-ui/dom': 1.8.0
+      '@floating-ui/utils': 0.2.12
+      vue-demi: 0.14.10(vue@3.5.12(typescript@5.3.3))
+    transitivePeerDependencies:
+      - '@vue/composition-api'
+      - vue
+
   '@form-create/component-elm-checkbox@3.2.14':
     dependencies:
       '@form-create/utils': 3.2.14
@@ -6821,6 +6886,8 @@ snapshots:
 
   '@types/web-bluetooth@0.0.20': {}
 
+  '@types/web-bluetooth@0.0.21': {}
+
   '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.3.3))(eslint@8.57.1)(typescript@5.3.3)':
     dependencies:
       '@eslint-community/regexpp': 4.12.1
@@ -7433,6 +7500,16 @@ snapshots:
 
   '@vue/shared@3.5.13': {}
 
+  '@vuepic/vue-datepicker@14.0.0(vue@3.5.12(typescript@5.3.3))':
+    dependencies:
+      '@date-fns/tz': 1.5.0
+      '@floating-ui/vue': 1.1.11(vue@3.5.12(typescript@5.3.3))
+      '@vueuse/core': 14.4.0(vue@3.5.12(typescript@5.3.3))
+      date-fns: 4.4.0
+      vue: 3.5.12(typescript@5.3.3)
+    transitivePeerDependencies:
+      - '@vue/composition-api'
+
   '@vueuse/core@10.11.1(vue@3.5.12(typescript@5.3.3))':
     dependencies:
       '@types/web-bluetooth': 0.0.20
@@ -7443,6 +7520,13 @@ snapshots:
       - '@vue/composition-api'
       - vue
 
+  '@vueuse/core@14.4.0(vue@3.5.12(typescript@5.3.3))':
+    dependencies:
+      '@types/web-bluetooth': 0.0.21
+      '@vueuse/metadata': 14.4.0
+      '@vueuse/shared': 14.4.0(vue@3.5.12(typescript@5.3.3))
+      vue: 3.5.12(typescript@5.3.3)
+
   '@vueuse/core@9.13.0(vue@3.5.12(typescript@5.3.3))':
     dependencies:
       '@types/web-bluetooth': 0.0.16
@@ -7455,6 +7539,8 @@ snapshots:
 
   '@vueuse/metadata@10.11.1': {}
 
+  '@vueuse/metadata@14.4.0': {}
+
   '@vueuse/metadata@9.13.0': {}
 
   '@vueuse/shared@10.11.1(vue@3.5.12(typescript@5.3.3))':
@@ -7464,6 +7550,10 @@ snapshots:
       - '@vue/composition-api'
       - vue
 
+  '@vueuse/shared@14.4.0(vue@3.5.12(typescript@5.3.3))':
+    dependencies:
+      vue: 3.5.12(typescript@5.3.3)
+
   '@vueuse/shared@9.13.0(vue@3.5.12(typescript@5.3.3))':
     dependencies:
       vue-demi: 0.14.10(vue@3.5.12(typescript@5.3.3))
@@ -8201,6 +8291,8 @@ snapshots:
 
   dargs@8.1.0: {}
 
+  date-fns@4.4.0: {}
+
   dayjs@1.11.13: {}
 
   de-indent@1.0.2: {}

+ 396 - 281
src/views/Home/Index.vue

@@ -1,13 +1,35 @@
 <script lang="ts" setup>
 import * as echarts from 'echarts'
+import {
+  Checked,
+  CollectionTag,
+  DataAnalysis,
+  DocumentChecked,
+  Files,
+  Histogram,
+  Postcard
+} from '@element-plus/icons-vue'
+import {
+  ANIMATION,
+  CHART_RENDERER,
+  DESIGN_HEIGHT,
+  DESIGN_WIDTH,
+  FONT_FAMILY,
+  THEME,
+  createTooltip
+} from '@/utils/kb'
+
+import '@vuepic/vue-datepicker/dist/main.css'
 
 defineOptions({ name: 'HomeIndex' })
 
 type SummaryCard = {
   title: string
   value: string | number
-  change: string
-  color: string
+  note: string
+  accent: string
+  glow: string
+  icon: any
 }
 
 type ProgressItem = {
@@ -16,20 +38,72 @@ type ProgressItem = {
   color: string
 }
 
-const dateText = '2026-08-06'
-const period = ref<'month' | 'quarter' | 'year'>('month')
+const wrapperRef = ref<HTMLDivElement>()
+const scale = ref(1)
+const supportsZoom = ref(false)
+let resizeObserver: ResizeObserver | null = null
+let resizeRaf = 0
 
 const summaryCards: SummaryCard[] = [
-  { title: '专家数量', value: 150, change: '1.0%', color: '#5d7dff' },
-  { title: '科研项目数量', value: 10, change: '0.0%', color: '#47d39b' },
-  { title: '核心技术', value: 8, change: '0.0%', color: '#ffb84d' },
-  { title: '专利', value: 18, change: '150.0%', color: '#4d96ff' },
-  { title: '论文', value: 28, change: '17.0%', color: '#a88cff' },
-  { title: '荣誉', value: 38, change: '100.0%', color: '#ff93dd' },
-  { title: '资料库', value: 3800, change: '100.0%', color: '#4bc7ff' }
+  {
+    title: '专家数量',
+    value: 150,
+    note: '较上月 +1.0%',
+    accent: '#4f8dff',
+    glow: 'rgba(79,141,255,.22)',
+    icon: Postcard
+  },
+  {
+    title: '科研项目数量',
+    value: 10,
+    note: '较上月 +0.0%',
+    accent: '#43c7ca',
+    glow: 'rgba(67,199,202,.22)',
+    icon: DocumentChecked
+  },
+  {
+    title: '核心技术',
+    value: 8,
+    note: '较上月 +0.0%',
+    accent: '#ffb14a',
+    glow: 'rgba(255,177,74,.22)',
+    icon: DataAnalysis
+  },
+  {
+    title: '专利',
+    value: 18,
+    note: '较上月 +150.0%',
+    accent: '#4f8dff',
+    glow: 'rgba(79,141,255,.22)',
+    icon: Histogram
+  },
+  {
+    title: '论文',
+    value: 28,
+    note: '较上月 +17.0%',
+    accent: '#8d8cff',
+    glow: 'rgba(141,140,255,.22)',
+    icon: CollectionTag
+  },
+  {
+    title: '荣誉',
+    value: 38,
+    note: '较上月 +100.0%',
+    accent: '#ff93dd',
+    glow: 'rgba(255,147,221,.22)',
+    icon: Checked
+  },
+  {
+    title: '资料库',
+    value: 3800,
+    note: '较上月 +100.0%',
+    accent: '#4bc7ff',
+    glow: 'rgba(75,199,255,.22)',
+    icon: Files
+  }
 ]
 
-const projectProgress: ProgressItem[] = [
+const progressItems: ProgressItem[] = [
   { label: '审批中', value: 7, color: '#2e9f8c' },
   { label: '生效中', value: 18, color: '#208d78' },
   { label: '已验收', value: 12, color: '#2b6aa8' },
@@ -37,7 +111,7 @@ const projectProgress: ProgressItem[] = [
   { label: '延期', value: 5, color: '#cf4a4a' }
 ]
 
-const projectDonutData = [
+const donutData = [
   { name: '完工', value: 54, color: '#f35e5c' },
   { name: '闲置', value: 28, color: '#8dd46c' },
   { name: '施工', value: 5, color: '#5578d4' },
@@ -91,41 +165,76 @@ const budgetSeries = {
   ]
 } as const
 
+const period = ref<keyof typeof budgetSeries>('month')
+const dateText = '2026-08-06'
+
 const donutRef = ref<HTMLDivElement | null>(null)
 const barRef = ref<HTMLDivElement | null>(null)
 const lineRef = ref<HTMLDivElement | null>(null)
-
 let donutChart: echarts.ECharts | null = null
 let barChart: echarts.ECharts | null = null
 let lineChart: echarts.ECharts | null = null
 
+const targetWrapperStyle = computed(() => ({
+  width: `${DESIGN_WIDTH * scale.value}px`,
+  height: `${DESIGN_HEIGHT * scale.value}px`
+}))
+
+const targetAreaStyle = computed(() => {
+  const style = {
+    width: `${DESIGN_WIDTH}px`,
+    height: `${DESIGN_HEIGHT}px`,
+    transformOrigin: '0 0'
+  } as Record<string, string | number>
+
+  if (supportsZoom.value) {
+    style.zoom = scale.value
+  } else {
+    style.transform = `scale(${scale.value})`
+  }
+
+  return style
+})
+
+function updateScale() {
+  cancelAnimationFrame(resizeRaf)
+  resizeRaf = requestAnimationFrame(() => {
+    const wrapper = wrapperRef.value
+    if (!wrapper) return
+    const { clientWidth, clientHeight } = wrapper
+    if (!clientWidth || !clientHeight) return
+    scale.value = Math.min(clientWidth / DESIGN_WIDTH, clientHeight / DESIGN_HEIGHT)
+    nextTick(() => resizeCharts())
+  })
+}
+
 function createDonutOption(): echarts.EChartsOption {
   return {
-    grid: { left: 0, right: 0, top: 0, bottom: 0 },
-    tooltip: { trigger: 'item' },
+    ...ANIMATION,
+    tooltip: createTooltip({ trigger: 'item' }),
     legend: {
       bottom: 0,
       icon: 'rect',
-      itemWidth: 10,
-      itemHeight: 10,
-      textStyle: { color: '#5f6f88', fontSize: 12 }
+      itemWidth: 8,
+      itemHeight: 8,
+      textStyle: { color: '#5f6f88', fontSize: 11, fontFamily: FONT_FAMILY }
     },
     series: [
       {
         type: 'pie',
-        radius: ['48%', '72%'],
-        center: ['50%', '48%'],
+        radius: ['46%', '70%'],
+        center: ['50%', '46%'],
         avoidLabelOverlap: false,
-        itemStyle: { borderRadius: 8, borderColor: 'transparent', borderWidth: 4 },
         label: {
           formatter: '{b|{b}}  {c|{d}%}',
           rich: {
-            b: { color: '#3e5575', fontWeight: 700, fontSize: 12 },
-            c: { color: '#3e5575', fontWeight: 700, fontSize: 12 }
+            b: { color: '#3e5575', fontWeight: 700, fontSize: 11 },
+            c: { color: '#3e5575', fontWeight: 700, fontSize: 11 }
           }
         },
-        labelLine: { length: 16, length2: 22 },
-        data: projectDonutData.map((item) => ({
+        labelLine: { length: 12, length2: 16 },
+        itemStyle: { borderRadius: 8, borderColor: 'transparent', borderWidth: 4 },
+        data: donutData.map((item) => ({
           value: item.value,
           name: item.name,
           itemStyle: { color: item.color }
@@ -137,30 +246,31 @@ function createDonutOption(): echarts.EChartsOption {
 
 function createBarOption(): echarts.EChartsOption {
   return {
-    grid: { left: 80, right: 20, top: 24, bottom: 10, containLabel: true },
-    tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
+    ...ANIMATION,
+    grid: { left: 74, right: 18, top: 18, bottom: 10, containLabel: true },
+    tooltip: createTooltip({ trigger: 'axis', axisPointer: { type: 'shadow' } }),
     xAxis: {
       type: 'value',
       max: 200,
-      splitLine: { lineStyle: { color: 'rgba(127,153,200,0.2)' } },
+      splitLine: { lineStyle: { color: THEME.split } },
       axisLine: { show: false },
       axisTick: { show: false },
-      axisLabel: { color: '#51637f' }
+      axisLabel: { color: '#51637f', fontSize: 11, fontFamily: FONT_FAMILY }
     },
     yAxis: {
       type: 'category',
       data: expertSeries.map((item) => item.name),
       axisLine: { show: false },
       axisTick: { show: false },
-      axisLabel: { color: '#e02828', fontWeight: 700 }
+      axisLabel: { color: '#e02828', fontSize: 11, fontWeight: 700, fontFamily: FONT_FAMILY }
     },
     series: [
       {
         type: 'bar',
         data: expertSeries.map((item) => item.value),
-        barWidth: 16,
+        barWidth: 14,
         itemStyle: {
-          borderRadius: [0, 12, 12, 0],
+          borderRadius: [0, 10, 10, 0],
           color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [
             { offset: 0, color: '#2f74e8' },
             { offset: 1, color: '#7bb2ff' }
@@ -170,7 +280,8 @@ function createBarOption(): echarts.EChartsOption {
           show: true,
           position: 'right',
           color: '#2f74e8',
-          fontWeight: 700
+          fontWeight: 700,
+          fontSize: 11
         }
       }
     ]
@@ -180,29 +291,30 @@ function createBarOption(): echarts.EChartsOption {
 function createLineOption(): echarts.EChartsOption {
   const series = budgetSeries[period.value]
   return {
-    grid: { left: 50, right: 24, top: 30, bottom: 30, containLabel: true },
-    tooltip: { trigger: 'axis' },
+    ...ANIMATION,
+    grid: { left: 42, right: 18, top: 22, bottom: 24, containLabel: true },
+    tooltip: createTooltip({ trigger: 'axis' }),
     xAxis: {
       type: 'category',
       boundaryGap: false,
       data: series.map((item) => item.date),
-      axisLine: { lineStyle: { color: 'rgba(127,153,200,0.4)' } },
+      axisLine: { lineStyle: { color: 'rgba(106,144,221,0.45)' } },
       axisTick: { show: false },
-      axisLabel: { color: '#5a6e89' }
+      axisLabel: { color: '#5b6f8f', fontSize: 11, fontFamily: FONT_FAMILY }
     },
     yAxis: {
       type: 'value',
       min: 0,
       max: 100,
-      splitLine: { lineStyle: { color: 'rgba(127,153,200,0.2)' } },
-      axisLabel: { color: '#5a6e89' }
+      splitLine: { lineStyle: { color: 'rgba(104,139,205,0.22)', type: 'dashed' } },
+      axisLabel: { color: '#8a9bb5', fontSize: 11, fontFamily: FONT_FAMILY }
     },
     series: [
       {
         type: 'line',
         smooth: true,
         symbol: 'circle',
-        symbolSize: 8,
+        symbolSize: 7,
         lineStyle: { color: '#2e9e5f', width: 3 },
         itemStyle: { color: '#2e9e5f' },
         areaStyle: { color: 'rgba(46,158,95,0.14)' },
@@ -211,6 +323,7 @@ function createLineOption(): echarts.EChartsOption {
           show: true,
           position: 'top',
           color: '#2e9e5f',
+          fontSize: 10,
           formatter: '{c}.00%'
         }
       }
@@ -218,30 +331,30 @@ function createLineOption(): echarts.EChartsOption {
   }
 }
 
-function resizeCharts() {
-  donutChart?.resize()
-  barChart?.resize()
-  lineChart?.resize()
-}
-
 function initCharts() {
   if (donutRef.value) {
     donutChart?.dispose()
-    donutChart = echarts.init(donutRef.value)
-    donutChart.setOption(createDonutOption())
+    donutChart = echarts.init(donutRef.value, undefined, { renderer: CHART_RENDERER })
+    donutChart.setOption(createDonutOption(), true)
   }
   if (barRef.value) {
     barChart?.dispose()
-    barChart = echarts.init(barRef.value)
-    barChart.setOption(createBarOption())
+    barChart = echarts.init(barRef.value, undefined, { renderer: CHART_RENDERER })
+    barChart.setOption(createBarOption(), true)
   }
   if (lineRef.value) {
     lineChart?.dispose()
-    lineChart = echarts.init(lineRef.value)
-    lineChart.setOption(createLineOption())
+    lineChart = echarts.init(lineRef.value, undefined, { renderer: CHART_RENDERER })
+    lineChart.setOption(createLineOption(), true)
   }
 }
 
+function resizeCharts() {
+  donutChart?.resize({ animation: { duration: 180 } })
+  barChart?.resize({ animation: { duration: 180 } })
+  lineChart?.resize({ animation: { duration: 180 } })
+}
+
 watch(period, () => {
   nextTick(() => {
     lineChart?.setOption(createLineOption(), true)
@@ -250,11 +363,15 @@ watch(period, () => {
 
 onMounted(() => {
   initCharts()
-  window.addEventListener('resize', resizeCharts)
+  updateScale()
+  resizeObserver = new ResizeObserver(updateScale)
+  if (wrapperRef.value) resizeObserver.observe(wrapperRef.value)
+  window.addEventListener('resize', updateScale)
 })
 
 onBeforeUnmount(() => {
-  window.removeEventListener('resize', resizeCharts)
+  resizeObserver?.disconnect()
+  window.removeEventListener('resize', updateScale)
   donutChart?.dispose()
   barChart?.dispose()
   lineChart?.dispose()
@@ -262,307 +379,305 @@ onBeforeUnmount(() => {
 </script>
 
 <template>
-  <div class="research-board">
-    <div class="research-board__topbar">
-      <div class="research-board__title">技术研发</div>
-      <div class="research-board__tools">
-        <span>日期:</span>
-        <strong>{{ dateText }}</strong>
-        <button
-          class="tool-btn"
-          :class="{ 'is-active': period === 'month' }"
-          @click="period = 'month'"
-          >月度</button
-        >
-        <button
-          class="tool-btn"
-          :class="{ 'is-active': period === 'quarter' }"
-          @click="period = 'quarter'"
-          >季度</button
-        >
-        <button
-          class="tool-btn"
-          :class="{ 'is-active': period === 'year' }"
-          @click="period = 'year'"
-          >年度</button
-        >
-        <button class="tool-btn" @click="period = 'month'">初始化</button>
-      </div>
-    </div>
-
-    <section class="panel summary-panel">
-      <div class="panel__head">
-        <h2><span>技术研发</span>概况</h2>
-      </div>
-      <div class="summary-grid">
-        <div v-for="card in summaryCards" :key="card.title" class="summary-card">
-          <div class="summary-card__bar" :style="{ background: card.color }"></div>
-          <div class="summary-card__title">{{ card.title }}</div>
-          <div class="summary-card__row">
-            <span>月度累计:</span>
-            <strong>{{ card.value }}</strong>
-          </div>
-          <div class="summary-card__row">
-            <span>环比变化:</span>
-            <strong>{{ card.change }} ↑</strong>
-          </div>
-        </div>
-      </div>
-    </section>
-
-    <section class="board-grid">
-      <div class="panel chart-panel">
-        <div class="panel__head"><h2>项目进度统计</h2></div>
-        <div ref="donutRef" class="chart-box"></div>
-      </div>
-
-      <div class="panel chart-panel">
-        <div class="panel__head">
-          <h2>专家序列统计</h2>
-          <div class="tab-switch">
-            <span>梯队</span>
-            <span class="is-active">序列</span>
-          </div>
-        </div>
-        <div ref="barRef" class="chart-box"></div>
-      </div>
-
-      <div class="panel chart-panel">
-        <div class="panel__head">
-          <h2>预算费用</h2>
-          <div class="tab-switch">
-            <span :class="{ 'is-active': period === 'month' }" @click="period = 'month'"
-              >近一周</span
+  <div ref="wrapperRef" class="bg absolute top-0 left-0 size-full z-10">
+    <div class="mx-a overflow-hidden" :style="targetWrapperStyle">
+      <div class="bg qhse-board home-board" :style="targetAreaStyle">
+        <header class="header">技术研发</header>
+
+        <div class="summary-toolbar">
+          <div class="summary-toolbar__tabs"> </div>
+
+          <div class="summary-toolbar__date">
+            <span class="summary-toolbar__date-label">日期:</span>
+            <strong class="summary-toolbar__date-value">{{ dateText }}</strong>
+            <button
+              type="button"
+              class="summary-toolbar__tab summary-toolbar__tab--mini is-active"
+              @click="period = 'month'"
+              >月度</button
+            >
+            <button
+              type="button"
+              class="summary-toolbar__tab summary-toolbar__tab--mini"
+              @click="period = 'quarter'"
+              >季度</button
+            >
+            <button
+              type="button"
+              class="summary-toolbar__tab summary-toolbar__tab--mini"
+              @click="period = 'year'"
+              >年度</button
             >
-            <span :class="{ 'is-active': period === 'quarter' }" @click="period = 'quarter'"
-              >近六个月</span
+            <button
+              type="button"
+              class="summary-toolbar__tab summary-toolbar__tab--mini"
+              @click="period = 'month'"
+              >初始化</button
             >
           </div>
         </div>
-        <div ref="lineRef" class="chart-box"></div>
-      </div>
-    </section>
 
-    <section class="panel progress-panel">
-      <div class="panel__head">
-        <h2>项目组合进展</h2>
-        <button class="filter-btn">按部门筛选</button>
-      </div>
-      <div class="progress-list">
-        <div v-for="item in projectProgress" :key="item.label" class="progress-item">
-          <span class="progress-item__label">{{ item.label }}</span>
-          <div class="progress-item__track">
-            <div
-              class="progress-item__bar"
-              :style="{ width: `${item.value * 5}%`, background: item.color }"
-            ></div>
+        <div class="board-body">
+          <section class="panel summary-panel kb-stage-card kb-stage-card--1">
+            <div class="panel-title">
+              <span class="icon-decorator"><span></span><span></span></span>
+              技术研发概况
+            </div>
+            <div class="summary-grid summary-grid--tight">
+              <article
+                v-for="card in summaryCards"
+                :key="card.title"
+                class="summary-card summary-tile"
+                :style="
+                  {
+                    '--card-accent': card.accent,
+                    '--card-glow': card.glow
+                  } as any
+                "
+              >
+                <span class="summary-card__shine"></span>
+                <span class="summary-card__corner"></span>
+                <div class="summary-card__icon">
+                  <el-icon class="summary-card__icon-glyph">
+                    <component :is="card.icon" />
+                  </el-icon>
+                </div>
+                <div class="summary-card__content">
+                  <div class="summary-card__label">{{ card.title }}</div>
+                  <div class="summary-tile__meta">
+                    <span class="summary-tile__value">月度累计{{ card.value }}</span>
+                    <span class="summary-tile__note" :style="{ color: card.accent }">{{
+                      card.note
+                    }}</span>
+                  </div>
+                </div>
+              </article>
+            </div>
+          </section>
+
+          <div class="board-main board-main--static">
+            <section class="panel board-panel kb-stage-card kb-stage-card--3">
+              <div class="panel-title panel-title--center">
+                <span class="icon-decorator"><span></span><span></span></span>
+                项目进度统计
+              </div>
+              <div
+                style="height: 308px"
+                ref="donutRef"
+                class="chart-panel chart-panel--echart chart-panel--compact"
+              ></div>
+            </section>
+
+            <section class="panel board-panel kb-stage-card kb-stage-card--4">
+              <div class="panel-title panel-title--center">
+                <span class="icon-decorator"><span></span><span></span></span>
+                专家序列统计
+              </div>
+              <div
+                ref="barRef"
+                style="height: 308px"
+                class="chart-panel chart-panel--echart chart-panel--compact"
+              ></div>
+            </section>
+
+            <section class="panel board-panel kb-stage-card kb-stage-card--4">
+              <div class="panel-title panel-title--center">
+                <span class="icon-decorator"><span></span><span></span></span>
+                预算费用
+              </div>
+              <div class="qhse-metric-tabs qhse-metric-tabs--compact">
+                <button
+                  type="button"
+                  class="qhse-metric-tab"
+                  :class="{ 'is-active': period === 'month' }"
+                  @click="period = 'month'"
+                  >近一周</button
+                >
+                <button
+                  type="button"
+                  class="qhse-metric-tab"
+                  :class="{ 'is-active': period === 'quarter' }"
+                  @click="period = 'quarter'"
+                  >近六个月</button
+                >
+              </div>
+              <div
+                ref="lineRef"
+                style="height: 308px"
+                class="chart-panel chart-panel--echart chart-panel--compact"
+              ></div>
+            </section>
           </div>
-          <span class="progress-item__value">{{ item.value }}</span>
+
+          <section class="panel progress-panel kb-stage-card kb-stage-card--5">
+            <div class="panel-title">
+              <span class="icon-decorator"><span></span><span></span></span>
+              项目组合进展
+            </div>
+            <div class="progress-list">
+              <div v-for="item in progressItems" :key="item.label" class="progress-item">
+                <span class="progress-item__label">{{ item.label }}</span>
+                <div class="progress-item__track">
+                  <div
+                    class="progress-item__bar"
+                    :style="{ width: `${item.value * 5}%`, background: item.color }"
+                  ></div>
+                </div>
+                <span class="progress-item__value">{{ item.value }}</span>
+              </div>
+            </div>
+          </section>
         </div>
       </div>
-    </section>
+    </div>
   </div>
 </template>
 
-<style scoped lang="scss">
-.research-board {
-  min-height: 100vh;
-  padding: 14px 18px 20px;
-  background: radial-gradient(
-      circle at top,
-      rgba(255, 255, 255, 0.8),
-      rgba(193, 219, 255, 0.22) 38%,
-      rgba(176, 205, 244, 0.1) 70%
-    ),
-    linear-gradient(180deg, #d8e8fb 0%, #edf4fd 100%);
-  color: #28415f;
-}
-
-.research-board__topbar {
-  position: relative;
+<style lang="scss" scoped>
+@import url('@/styles/qhse_kb.scss');
+
+.home-board {
+  overflow: hidden;
+}
+
+.summary-toolbar {
   display: flex;
   align-items: center;
   justify-content: space-between;
-  padding: 0 8px 14px;
+  padding: 8px 18px 4px;
 }
 
-.research-board__title {
-  width: 100%;
-  max-width: 420px;
-  margin: 0 auto;
-  padding: 10px 28px;
-  text-align: center;
-  font-size: 28px;
-  font-weight: 800;
-  color: #c31b1b;
-  background: linear-gradient(180deg, #f6fbff, #8cb9f5 55%, #4f88d7);
-  border-radius: 0 0 30px 30px;
-  box-shadow: 0 8px 20px rgba(65, 109, 176, 0.22);
-}
-
-.research-board__tools {
-  position: absolute;
-  right: 28px;
-  top: 24px;
+.summary-toolbar__tabs,
+.summary-toolbar__date {
   display: flex;
   align-items: center;
   gap: 10px;
-  font-size: 16px;
-  font-weight: 600;
 }
 
-.tool-btn,
-.filter-btn,
-.tab-switch span {
-  border: 1px solid rgba(132, 167, 224, 0.45);
-  background: rgba(255, 255, 255, 0.8);
-  color: #405a7b;
-  border-radius: 18px;
+.summary-toolbar__tab {
+  min-width: 108px;
+  height: 34px;
+  padding: 0 16px;
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: 14px;
+  color: #2b76e9;
+  background: linear-gradient(180deg, rgb(255 255 255 / 92%) 0%, rgb(228 239 255 / 90%) 100%);
+  border: 1px solid rgb(126 174 244 / 40%);
+  border-radius: 10px;
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 96%),
+    0 6px 12px rgb(63 103 171 / 8%);
 }
 
-.tool-btn {
-  padding: 6px 14px;
-  cursor: pointer;
+.summary-toolbar__tab--mini {
+  min-width: 68px;
+  height: 30px;
+  padding: 0 10px;
+  font-size: 12px;
 }
 
-.panel {
-  background: linear-gradient(180deg, rgba(255, 255, 255, 0.72), rgba(225, 236, 250, 0.78));
-  border: 1px solid rgba(255, 255, 255, 0.9);
-  border-radius: 18px;
-  box-shadow:
-    inset 0 1px 0 rgba(255, 255, 255, 0.8),
-    0 10px 24px rgba(86, 118, 170, 0.12);
+.summary-toolbar__tab.is-active {
+  color: #fff;
+  background: linear-gradient(180deg, #59a6ff 0%, #2d78ea 100%);
 }
 
-.summary-panel,
-.progress-panel {
-  margin-top: 10px;
-  padding: 14px 16px 18px;
+.summary-toolbar__date-label {
+  font-size: 14px;
+  font-weight: 700;
+  color: #5c7393;
 }
 
-.panel__head {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 0 2px 12px;
-  border-bottom: 1px solid rgba(109, 139, 190, 0.26);
+.summary-toolbar__date-value {
+  font-size: 13px;
+  font-weight: 700;
+  color: #264564;
 }
 
-.panel__head h2 {
-  margin: 0;
-  font-size: 22px;
-  font-weight: 800;
-  color: #1c4d93;
+.board-body {
+  padding: 4px 14px 12px;
 }
 
-.panel__head h2 span {
-  color: #ef1f1f;
+.summary-panel {
+  height: auto;
 }
 
-.summary-grid {
+.summary-grid--tight {
   display: grid;
   grid-template-columns: repeat(7, minmax(0, 1fr));
-  gap: 12px;
-  margin-top: 12px;
+  gap: 10px;
+  padding: 4px 0 0;
+  width: 100%;
+  padding-bottom: 10px;
 }
 
 .summary-card {
-  position: relative;
-  padding: 18px 16px 16px 22px;
   min-height: 130px;
-  border-radius: 18px;
-  background: rgba(255, 255, 255, 0.4);
+  padding: 16px 14px 12px 18px;
+  border-radius: 12px;
 }
 
-.summary-card__bar {
-  position: absolute;
-  left: 12px;
-  top: 18px;
-  width: 4px;
-  height: 46px;
-  border-radius: 3px;
+.summary-card__title,
+.summary-card__label {
+  font-size: 14px;
 }
 
-.summary-card__title {
-  color: #f32b1f;
+.summary-tile__value {
   font-size: 18px;
-  font-weight: 800;
-  margin-left: 10px;
 }
 
-.summary-card__row {
-  display: flex;
-  justify-content: space-between;
-  margin-top: 14px;
-  font-size: 16px;
-  font-weight: 700;
-  color: #18314f;
+.summary-tile__note {
+  font-size: 12px;
 }
 
-.summary-card__row strong {
-  color: #f23022;
-}
-
-.board-grid {
+.board-main--static {
   display: grid;
-  grid-template-columns: 1fr 1.15fr 1fr;
+  grid-template-columns: repeat(3, minmax(0, 1fr));
   gap: 12px;
-  margin-top: 12px;
-}
-
-.chart-panel {
-  padding: 14px 14px 10px;
-  min-height: 420px;
+  margin-top: 10px;
+  height: 408px;
 }
 
-.chart-box {
-  width: 100%;
-  height: 350px;
+.board-panel {
+  min-height: 272px;
 }
 
-.tab-switch {
-  display: flex;
-  gap: 8px;
-  font-size: 16px;
-  font-weight: 700;
+.chart-panel--compact {
+  height: 208px;
 }
 
-.tab-switch span {
-  padding: 5px 12px;
+.qhse-metric-tabs--compact {
+  padding-left: 24px;
+  margin-top: -4px;
 }
 
-.tab-switch .is-active {
-  background: #fff;
-  color: #1c5cc0;
-  box-shadow: inset 0 -3px 0 #1c5cc0;
+.qhse-metric-tabs--compact .qhse-metric-tab {
+  min-height: 22px;
+  padding: 4px 10px;
+  font-size: 12px;
 }
 
 .progress-panel {
-  margin-top: 12px;
-}
-
-.filter-btn {
-  padding: 8px 18px;
-  font-size: 16px;
-  font-weight: 700;
+  margin-top: 10px;
+  padding-bottom: 10px;
+  height: 352px;
 }
 
 .progress-list {
-  padding: 22px 6px 6px;
+  padding: 12px 12px 4px;
 }
 
 .progress-item {
   display: grid;
-  grid-template-columns: 100px 1fr 36px;
+  grid-template-columns: 70px 1fr 28px;
   align-items: center;
-  gap: 14px;
-  margin-bottom: 16px;
-  font-size: 16px;
+  gap: 10px;
+  margin-bottom: 10px;
+  font-size: 12px;
   font-weight: 700;
 }
 
 .progress-item__track {
-  height: 10px;
+  height: 18px;
   border-radius: 999px;
   background: #e4ece7;
   overflow: hidden;

+ 2692 - 0
src/views/Home/demo.vue

@@ -0,0 +1,2692 @@
+<script lang="ts" setup>
+import * as echarts from 'echarts'
+import '@vuepic/vue-datepicker/dist/main.css'
+import {
+  AlarmClock,
+  Checked,
+  Clock,
+  CollectionTag,
+  DataAnalysis,
+  DocumentChecked,
+  Files,
+  Flag,
+  Histogram,
+  Odometer,
+  Postcard,
+  Warning,
+  Search,
+  Refresh
+} from '@element-plus/icons-vue'
+import {
+  kanbanApi,
+  IotDangerApi,
+  IotHiddenApi,
+  IotInstrumentApi,
+  IotMeasureCertApi,
+  IotCertificateApi
+} from '@/api/pms/qhse/index'
+import { useUserStore } from '@/store/modules/user'
+import {
+  ANIMATION,
+  CHART_RENDERER,
+  DESIGN_HEIGHT,
+  DESIGN_WIDTH,
+  FONT_FAMILY,
+  THEME,
+  createTooltip
+} from '@/utils/kb'
+import { DICT_TYPE, getBoolDictOptions, getStrDictOptions } from '@/utils/dict'
+import { formatDate } from '@/utils/formatTime'
+
+defineOptions({
+  name: 'PmsQhseKanban'
+})
+
+type PermitStat = {
+  label: string
+  value: number
+  color: string
+}
+
+type SafeDayMap = Record<string, number>
+type SafeDayBarItem = {
+  name: string
+  value: number
+}
+
+type SummaryTabValue = 'home' | 'certificate'
+type QhseMetricValue = 'ltir' | 'trir' | 'pmva'
+const userStore = useUserStore()
+
+const type = ref('day')
+
+const handelChange = (value: any) => {
+  console.log('Selected time:', value)
+  // 在这里处理时间选择变化的逻辑,例如更新看板数据
+}
+
+function padDateValue(value: number) {
+  return `${value}`.padStart(2, '0')
+}
+
+function formatDateValue(date = new Date()) {
+  return `${date.getFullYear()}-${padDateValue(date.getMonth() + 1)}-${padDateValue(date.getDate())}`
+}
+
+function getCurrentPickerValue(pickerType: string) {
+  const now = new Date()
+
+  if (pickerType === 'year') {
+    return `${now.getFullYear()}`
+  }
+
+  if (pickerType === 'month') {
+    return `${now.getFullYear()}-${padDateValue(now.getMonth() + 1)}`
+  }
+
+  return formatDateValue(now)
+}
+
+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)
+
+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 }> = [
+  { label: '首页看板', value: 'home' },
+  { label: '证书信息', value: 'certificate' }
+]
+
+const activeSummaryTab = ref<SummaryTabValue>('home')
+
+watch(
+  () => activeSummaryTab.value,
+  async (value) => {
+    if (value === 'certificate') {
+      await getCertificateBoardList()
+    } else {
+      nextTick(() => {
+        initHazardChart()
+        initSafeDayChart()
+        initQhseTrendChart()
+        initSocChart()
+        resizeHazardChart()
+        resizeSafeDayChart()
+        resizeQhseTrendChart()
+        resizeSocChart()
+      })
+    }
+  }
+)
+
+const pageTitle = computed(() =>
+  activeSummaryTab.value === 'certificate' ? '证书信息看板' : 'QHSE管理看板'
+)
+
+const summaryCards = ref([
+  {
+    title: '人工时(小时)',
+    value: 671987,
+    note: '',
+    accent: '#4f8dff',
+    glow: 'rgba(79, 141, 255, 0.22)',
+    icon: Clock
+  },
+  {
+    title: '车辆里程(公里)',
+    value: 631128,
+    note: '',
+    accent: '#4f8dff',
+    glow: 'rgba(79, 141, 255, 0.22)',
+    icon: Odometer
+  },
+  {
+    title: '重大风险数量',
+    value: 0,
+    note: '',
+    accent: '#ff5a62',
+    glow: 'rgba(255, 90, 98, 0.26)',
+    icon: Warning
+  },
+  {
+    title: '特种人员持证率',
+    value: 0,
+    note: 'Warn: 0',
+    accent: '#f2b800',
+    glow: 'rgba(242, 184, 0, 0.22)',
+    icon: Postcard
+  },
+  {
+    title: '设备检测合规率',
+    value: '98%',
+    note: '',
+    accent: '#f2b800',
+    glow: 'rgba(242, 184, 0, 0.22)',
+    icon: DataAnalysis
+  },
+  {
+    title: '作业许可证数量',
+    value: 0,
+    note: '',
+    accent: '#4f8dff',
+    glow: 'rgba(79, 141, 255, 0.22)',
+    icon: DocumentChecked
+  }
+])
+
+const hazardBars = ref([
+  { label: '总数', value: 0, color: '#4f8dff' },
+  { label: '已整改', value: 0, color: '#43c7ca' },
+  { label: '未整改', value: 0, color: '#ff981f' }
+])
+
+const riskZones = ref([
+  { title: '', desc: '办公区 / 展厅 / 主通道', color: '#25b36a', value: 0 },
+  { title: '', desc: '物料库 / 维修 / 装卸区', color: '#3d7cff', value: 0 },
+  { title: '', desc: '焊接 / 机加 / 吊装区', color: '#ff9827', value: 0 },
+  { title: '', desc: '危化库 / 试压区 / 配电房', color: '#ff5b61', value: 0 }
+])
+
+const permitStats = ref<PermitStat[]>([
+  { label: '个人防护', value: 18, color: '#4f8dff' },
+  { label: '规范操作', value: 26, color: '#43c7ca' },
+  { label: '规范指挥', value: 12, color: '#ffb14a' },
+  { label: '人员位置', value: 9, color: '#ff7a7a' },
+  { label: '作业场所', value: 15, color: '#8d8cff' }
+])
+
+const qualificationWarnings = ref([
+  { label: '证件过期', value: 0, accent: '#ff7a7a', key: 'expired' },
+  { label: '即将到期', value: 0, accent: '#e6ab00', key: 'warn' }
+])
+
+const qhseMetricTabs = [
+  { label: 'LTIR(百万工时)', value: 'ltir', accent: '#3d7cff', label2: '百万工时损失工时事故率' },
+  { label: 'TRIR(百万工时)', value: 'trir', accent: '#17b6c5', label2: '百万工时总可记录事件率' },
+  {
+    label: 'PMVA(百万公里)',
+    value: 'pmva',
+    accent: '#f2a93b',
+    label2: '百万公里可预防性交通事故率'
+  }
+] as const
+
+const activeQhseMetric = ref<QhseMetricValue>('ltir')
+const dangerGradeOptions = computed(() => getStrDictOptions(DICT_TYPE.DANGER_GRADE))
+
+const qhseTrendSeries = ref<Record<QhseMetricValue, Array<{ year: string; value: number }>>>({
+  ltir: [
+    { year: '2026年1月', value: 0.42 },
+    { year: '2026年2月', value: 0.39 },
+    { year: '2026年3月', value: 0.36 },
+    { year: '2026年4月', value: 0.34 },
+    { year: '2026年5月', value: 0.31 },
+    { year: '2026年6月', value: 0.29 },
+    { year: '2026年7月', value: 0.27 },
+    { year: '2026年8月', value: 0.25 },
+    { year: '2026年9月', value: 0.24 },
+    { year: '2026年10月', value: 0.22 },
+    { year: '2026年11月', value: 0.21 },
+    { year: '2026年12月', value: 0.2 }
+  ],
+  trir: [
+    { year: '1月', value: 0.78 },
+    { year: '2月', value: 0.75 },
+    { year: '3月', value: 0.73 },
+    { year: '4月', value: 0.7 },
+    { year: '5月', value: 0.68 },
+    { year: '6月', value: 0.66 },
+    { year: '7月', value: 0.63 },
+    { year: '8月', value: 0.61 },
+    { year: '9月', value: 0.59 },
+    { year: '10月', value: 0.57 },
+    { year: '11月', value: 0.55 },
+    { year: '12月', value: 0.54 }
+  ],
+  pmva: [
+    { year: '1月', value: 0.22 },
+    { year: '2月', value: 0.21 },
+    { year: '3月', value: 0.2 },
+    { year: '4月', value: 0.18 },
+    { year: '5月', value: 0.17 },
+    { year: '6月', value: 0.16 },
+    { year: '7月', value: 0.15 },
+    { year: '8月', value: 0.14 },
+    { year: '9月', value: 0.14 },
+    { year: '10月', value: 0.13 },
+    { year: '11月', value: 0.12 },
+    { year: '12月', value: 0.12 }
+  ]
+})
+
+const bottomCards = ref([
+  {
+    title: '体系合规',
+    icon: Files,
+    accent: '#39c6cc',
+    glow: 'rgba(57, 198, 204, 0.22)',
+    lines: ['内审已完成', '外审待安排']
+  },
+  {
+    title: '安全检测',
+    icon: Histogram,
+    accent: '#4f8dff',
+    glow: 'rgba(79, 141, 255, 0.2)',
+    lines: ['在用: 0台', '待检: 0项(重点关注)']
+  },
+  {
+    title: '应急演练',
+    icon: AlarmClock,
+    accent: '#ff5b61',
+    glow: 'rgba(255, 91, 97, 0.22)',
+    lines: ['年度应急演练:80次']
+  },
+  {
+    title: '职业健康',
+    icon: Checked,
+    accent: '#f2c11a',
+    glow: 'rgba(242, 193, 26, 0.2)',
+    lines: ['职业健康体检率', '98.7%(达标)']
+  },
+  {
+    title: '危废管控',
+    icon: Flag,
+    accent: '#28c98b',
+    glow: 'rgba(40, 201, 139, 0.2)',
+    lines: ['危废暂存合规', '三废处置100%达标']
+  }
+])
+
+const targetWrapperStyle = computed(() => ({
+  width: `${DESIGN_WIDTH * scale.value}px`,
+  height: `${DESIGN_HEIGHT * scale.value}px`
+}))
+
+const targetAreaStyle = computed(() => {
+  const style = {
+    width: `${DESIGN_WIDTH}px`,
+    height: `${DESIGN_HEIGHT}px`,
+    transformOrigin: '0 0'
+  } as Record<string, string | number>
+
+  if (supportsZoom.value) {
+    style.zoom = scale.value
+  } else {
+    style.transform = `scale(${scale.value})`
+  }
+
+  return style
+})
+
+function updateScale() {
+  cancelAnimationFrame(resizeRaf)
+
+  resizeRaf = requestAnimationFrame(() => {
+    const wrapper = wrapperRef.value
+    if (!wrapper) return
+
+    const { clientWidth, clientHeight } = wrapper
+    if (!clientWidth || !clientHeight) return
+
+    scale.value = Math.min(clientWidth / DESIGN_WIDTH, clientHeight / DESIGN_HEIGHT)
+
+    nextTick(() => {
+      resizeHazardChart()
+      resizeSafeDayChart()
+      resizeQhseTrendChart()
+      resizeSocChart()
+    })
+  })
+}
+
+const staticData = ref<any>({})
+const safeDay = ref<SafeDayMap>({})
+const safeDayBarData = ref<SafeDayBarItem[]>([])
+const summaryPanel = ref<any>(null)
+const total = ref(0)
+const instrumentExpired = ref(0)
+const safeDayDisplayOrder = ['瑞气能源', '瑞霖技术', '5#项目', '酸化压裂', '气体增产', '钻修井']
+
+function buildSafeDayBarData(baseData: SafeDayMap) {
+  const mappedData = Object.entries(baseData || {}).map(([name, value]) => ({
+    name,
+    value: Number(value) || 0
+  }))
+  const orderedData = safeDayDisplayOrder
+    .map((name) => mappedData.find((item) => item.name === name))
+    .filter(Boolean) as SafeDayBarItem[]
+  const remainingData = mappedData.filter((item) => !safeDayDisplayOrder.includes(item.name))
+
+  safeDayBarData.value = [...orderedData, ...remainingData]
+}
+
+async function getStatic() {
+  const res = await IotDangerApi.getDangerStatistics(userStore.user.deptId)
+  staticData.value = res.classify || []
+
+  riskZones.value.forEach((zone, index) => {
+    const item = staticData.value[index]
+    if (!item) return
+    zone.value = item.count || 0
+    zone.title = `${item.classify}区`
+  })
+}
+
+async function getCertStatic() {
+  const res = await IotMeasureCertApi.getIotMeasureCertStatistics(userStore.user.deptId)
+  qualificationWarnings.value[0].value = res.expired || 0
+  qualificationWarnings.value[1].value = res.warn || 0
+}
+
+async function getInstrumentOverview() {
+  const listData = await IotInstrumentApi.getInstrumentList({
+    pageNo: 1,
+    pageSize: 10,
+    deptId: userStore.user.deptId
+  })
+  total.value = listData.total || 0
+
+  const statData = await IotInstrumentApi.getInstrumentStatistics(userStore.user.deptId)
+  instrumentExpired.value = statData.expired || 0
+
+  bottomCards.value[1].lines = [
+    `在用: ${total.value}台`,
+    `待检: ${instrumentExpired.value}项(重点关注)`
+  ]
+}
+
+function getHazardChartOption(): echarts.EChartsOption {
+  return {
+    ...ANIMATION,
+    grid: {
+      left: 20,
+      right: 10,
+      top: 24,
+      bottom: 24,
+      containLabel: true
+    },
+    tooltip: createTooltip({
+      trigger: 'axis',
+      confine: true,
+      appendToBody: false,
+      axisPointer: {
+        type: 'shadow',
+        shadowStyle: {
+          color: 'rgba(31, 91, 184, 0.08)'
+        }
+      },
+      formatter(params: any) {
+        if (!params || (Array.isArray(params) && params.length === 0)) return ''
+        const item = Array.isArray(params) ? params[0] : params
+        return `${item.name}<br/>数量:${item.value}`
+      }
+    }),
+    xAxis: {
+      type: 'category',
+      data: hazardBars.value.map((item) => item.label),
+      axisLine: { show: false },
+      axisTick: { show: false },
+      axisLabel: {
+        color: '#5b6f8f',
+        fontSize: 16,
+        fontWeight: 700,
+        fontFamily: FONT_FAMILY,
+        interval: 0
+      }
+    },
+    yAxis: {
+      type: 'value',
+      name: '隐患数量',
+      nameLocation: 'end',
+      nameGap: 13,
+      nameTextStyle: {
+        color: '#657897',
+        fontSize: 12,
+        // fontWeight: 600,
+        fontFamily: FONT_FAMILY
+      },
+      axisLine: { show: false },
+      axisTick: { show: false },
+      axisLabel: {
+        color: '#8a9bb5',
+        fontSize: 13,
+        fontFamily: FONT_FAMILY
+      },
+      splitLine: {
+        lineStyle: {
+          color: THEME.split,
+          type: 'dashed'
+        }
+      }
+    },
+    series: [
+      {
+        type: 'bar',
+        data: hazardBars.value.map((item) => ({
+          value: item.value,
+          itemStyle: {
+            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+              { offset: 0, color: item.color },
+              { offset: 1, color: `${item.color}99` }
+            ]),
+            shadowBlur: 14,
+            shadowColor: item.color,
+            borderRadius: [5, 5, 0, 0]
+          }
+        })),
+        barWidth: 70,
+        label: {
+          show: true,
+          position: 'top',
+          color: '#3c5f96',
+          fontSize: 16,
+          fontWeight: 700,
+          fontFamily: FONT_FAMILY
+        },
+        emphasis: {
+          itemStyle: {
+            shadowBlur: 18
+          }
+        }
+      }
+    ]
+  }
+}
+
+function initHazardChart() {
+  if (!hazardChartRef.value) return
+  hazardChart?.dispose()
+  hazardChart = echarts.init(hazardChartRef.value, undefined, {
+    renderer: CHART_RENDERER
+  })
+  hazardChart.setOption(getHazardChartOption(), true)
+  hazardChart.off('click')
+  hazardChart.on('click', handleHazardBarClick)
+}
+
+function updateHazardChart() {
+  if (!hazardChart) return
+  hazardChart.clear()
+  hazardChart.setOption(getHazardChartOption(), { notMerge: true, lazyUpdate: false })
+}
+
+function resizeHazardChart() {
+  if (!hazardChart) return
+  hazardChart.resize({ animation: { duration: 300 } })
+}
+
+function destroyHazardChart() {
+  hazardChart?.dispose()
+  hazardChart = null
+}
+
+function getSafeDayChartOption(): echarts.EChartsOption {
+  const maxValue = Math.max(...safeDayBarData.value.map((item) => item.value), 0)
+  const axisMax = maxValue > 0 ? Math.ceil(maxValue / 300) * 300 : 300
+
+  return {
+    ...ANIMATION,
+    grid: {
+      left: 30,
+      right: 28,
+      top: 18,
+      bottom: 34,
+      containLabel: true
+    },
+    tooltip: createTooltip({
+      trigger: 'axis',
+      confine: true,
+      appendToBody: false,
+      axisPointer: {
+        type: 'shadow',
+        shadowStyle: {
+          color: 'rgba(79, 141, 255, 0.08)'
+        }
+      },
+      formatter(params: any) {
+        if (!params || (Array.isArray(params) && params.length === 0)) return ''
+        const item = Array.isArray(params) ? params[0] : params
+        return `${item.name}<br/>安全生产天数:<span style="font-weight:700">${item.value}</span>`
+      }
+    }),
+    xAxis: {
+      type: 'value',
+      max: axisMax,
+      axisLine: {
+        lineStyle: {
+          color: 'rgba(106, 144, 221, 0.45)'
+        }
+      },
+      axisTick: { show: false },
+      axisLabel: {
+        color: '#5b6f8f',
+        fontSize: 12,
+        fontWeight: 600,
+        fontFamily: FONT_FAMILY
+      },
+      splitLine: {
+        lineStyle: {
+          color: 'rgba(104, 139, 205, 0.32)',
+          type: 'dashed'
+        }
+      }
+    },
+    yAxis: {
+      type: 'category',
+      inverse: true,
+      data: safeDayBarData.value.map((item) => item.name),
+      axisLine: { show: false },
+      axisTick: { show: false },
+      axisLabel: {
+        color: '#2e4262',
+        fontSize: 14,
+        fontWeight: 700,
+        fontFamily: FONT_FAMILY
+      },
+      splitLine: { show: false }
+    },
+    series: [
+      {
+        name: '安全生产天数',
+        type: 'bar',
+        data: safeDayBarData.value.map((item) => item.value),
+        barWidth: 22,
+        showBackground: true,
+        backgroundStyle: {
+          color: 'rgba(116, 152, 216, 0.08)',
+          borderRadius: 11
+        },
+        itemStyle: {
+          color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [
+            { offset: 0, color: '#6f9af0' },
+            { offset: 1, color: '#5f88de' }
+          ]),
+          borderRadius: [0, 11, 11, 0],
+          shadowBlur: 10,
+          shadowColor: 'rgba(95, 136, 222, 0.25)'
+        },
+        label: {
+          show: false
+        },
+        emphasis: {
+          itemStyle: {
+            shadowBlur: 14,
+            shadowColor: 'rgba(95, 136, 222, 0.38)'
+          }
+        }
+      }
+    ],
+    graphic: [
+      {
+        type: 'text',
+        left: 'center',
+        bottom: 0,
+        style: {
+          text: '安全生产天数',
+          fill: '#5b6f8f',
+          font: `700 12px ${FONT_FAMILY}`
+        }
+      }
+    ]
+  }
+}
+
+function initSafeDayChart() {
+  if (!safeDayChartRef.value) return
+  safeDayChart?.dispose()
+  safeDayChart = echarts.init(safeDayChartRef.value, undefined, {
+    renderer: CHART_RENDERER
+  })
+  safeDayChart.setOption(getSafeDayChartOption(), true)
+}
+
+function updateSafeDayChart() {
+  if (!safeDayChart) return
+  safeDayChart.clear()
+  safeDayChart.setOption(getSafeDayChartOption(), { notMerge: true, lazyUpdate: false })
+}
+
+function resizeSafeDayChart() {
+  if (!safeDayChart) return
+  safeDayChart.resize({ animation: { duration: 300 } })
+}
+
+function destroySafeDayChart() {
+  safeDayChart?.dispose()
+  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',
+        rotate: 28,
+        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,
+    title: {
+      text: 'SOC',
+      left: '34%',
+      top: '54%',
+      textAlign: 'center',
+      textVerticalAlign: 'middle',
+      textStyle: {
+        color: '#3d5f94',
+        fontSize: 24,
+        fontWeight: 800,
+        fontFamily: FONT_FAMILY
+      }
+    },
+    tooltip: createTooltip({
+      trigger: 'item',
+      confine: true,
+      appendToBody: false,
+      formatter(params: any) {
+        if (!params) return ''
+        return `${params.name}<br/>数量:${params.value}<br/>占比:${params.percent}%`
+      }
+    }),
+    legend: {
+      orient: 'vertical',
+      right: 100,
+      top: 'center',
+      itemWidth: 12,
+      itemHeight: 12,
+      icon: 'circle',
+      textStyle: {
+        color: '#5b6f8f',
+        fontSize: 15,
+        fontWeight: 600,
+        fontFamily: FONT_FAMILY
+      }
+    },
+    series: [
+      {
+        name: 'SOC卡类型',
+        type: 'pie',
+        radius: ['50%', '74%'],
+        center: ['34%', '54%'],
+        avoidLabelOverlap: true,
+        itemStyle: {
+          borderColor: 'rgba(255, 255, 255, 0.85)',
+          borderWidth: 2,
+          shadowBlur: 10,
+          shadowColor: 'rgba(31, 91, 184, 0.12)'
+        },
+        label: {
+          show: true,
+          color: '#4e6483',
+          fontSize: 13,
+          fontWeight: 700,
+          formatter: '{d}%'
+        },
+        labelLine: {
+          lineStyle: {
+            color: 'rgba(91, 111, 143, 0.5)'
+          },
+          length: 10,
+          length2: 8
+        },
+        data: permitStats.value.map((item) => ({
+          name: item.label,
+          value: item.value,
+          itemStyle: {
+            color: item.color
+          }
+        }))
+      }
+    ]
+  }
+}
+
+function initSocChart() {
+  if (!socChartRef.value) return
+  socChart?.dispose()
+  socChart = echarts.init(socChartRef.value, undefined, {
+    renderer: CHART_RENDERER
+  })
+  socChart.setOption(getSocChartOption(), { notMerge: true, lazyUpdate: false })
+}
+
+function updateSocChart() {
+  if (!socChart) return
+  socChart.clear()
+  socChart.setOption(getSocChartOption(), { notMerge: true, lazyUpdate: false })
+}
+
+function resizeSocChart() {
+  if (!socChart) return
+  socChart.resize({ animation: { duration: 300 } })
+}
+
+function destroySocChart() {
+  socChart?.dispose()
+  socChart = null
+}
+
+const tableRowStyle = ({ row }) => {
+  if (row.expired) {
+    return { backgroundColor: '#ffe6e6' }
+  }
+  if (row.alertWarn) {
+    return { backgroundColor: '#e19f1a' }
+  }
+  return {}
+}
+
+const tableRowClassName = ({ row }) => {
+  if (row.expired) {
+    return 'expired-row'
+  }
+  if (row.alertWarn) {
+    return 'alert-warn-row'
+  }
+  return ''
+}
+
+function formatPercent(numerator: number, denominator: number) {
+  if (!denominator || Number.isNaN(denominator)) {
+    return '0%'
+  }
+  return `${((numerator / denominator) * 100).toFixed(1)}%`
+}
+
+async function loadHomeBoard() {
+  summaryPanel.value = await kanbanApi.getKanban()
+
+  summaryCards.value[3].value = formatPercent(
+    summaryPanel.value.totdalCert - summaryPanel.value.expiredCert,
+    summaryPanel.value.totdalCert
+  )
+  summaryCards.value[3].note = `Warn: ${summaryPanel.value.warnCert || 0}`
+
+  hazardBars.value[0].value = summaryPanel.value.totalHazard || 0
+  hazardBars.value[1].value =
+    (summaryPanel.value.totalHazard || 0) - (summaryPanel.value.todoHazard || 0)
+  hazardBars.value[2].value = summaryPanel.value.todoHazard || 0
+
+  safeDay.value = (await kanbanApi.getSafeDay(userStore.getUser.deptId)) || {}
+  buildSafeDayBarData(safeDay.value)
+
+  await Promise.all([getStatic(), getCertStatic(), getInstrumentOverview()])
+
+  nextTick(() => {
+    updateHazardChart()
+    updateSafeDayChart()
+    updateQhseTrendChart()
+    updateSocChart()
+    resizeHazardChart()
+    resizeSafeDayChart()
+    resizeQhseTrendChart()
+    resizeSocChart()
+  })
+}
+
+const certLoading = ref(false)
+const certTableData = ref<any[]>([])
+const certTotal = ref(0)
+const certQueryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+
+  expired: undefined as string | undefined,
+  alertWarn: undefined as string | undefined
+})
+
+const certWarningDialogVisible = ref(false)
+const certWarningDialogTitle = ref('')
+const certWarningLoading = ref(false)
+const certWarningList = ref<any[]>([])
+const certWarningTotal = ref(0)
+const certWarningQueryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  deptId: userStore.getUser.deptId,
+  type: undefined as string | undefined,
+  classify: undefined as string | undefined,
+  userName: undefined as string | undefined,
+  expired: undefined as boolean | undefined,
+  alertWarn: undefined as boolean | undefined
+})
+
+const hazardDialogVisible = ref(false)
+const hazardDialogTitle = ref('')
+const hazardDialogLoading = ref(false)
+const hazardDialogList = ref<any[]>([])
+const hazardDialogTotal = ref(0)
+const hazardDialogQueryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  deptId: userStore.getUser.deptId,
+  status: undefined as string | undefined,
+  address: '',
+  source: ''
+})
+
+const dangerDialogVisible = ref(false)
+const dangerDialogTitle = ref('')
+const dangerDialogLoading = ref(false)
+const dangerDialogList = ref<any[]>([])
+const dangerDialogTotal = ref(0)
+const dangerDialogQueryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  riskGrade: '',
+  deptId: userStore.getUser.deptId
+})
+
+const instrumentDialogVisible = ref(false)
+const instrumentDialogTitle = ref('')
+const instrumentDialogLoading = ref(false)
+const instrumentDialogList = ref<any[]>([])
+const instrumentDialogTotal = ref(0)
+const instrumentDialogQueryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  measureName: undefined as string | undefined,
+  measureCertNo: undefined as string | undefined,
+  deptId: userStore.getUser.deptId,
+  expired: undefined as boolean | undefined,
+  alertWarn: undefined as boolean | undefined
+})
+
+const getCertificateTypeText = (type: string) => {
+  const map: Record<string, string> = {
+    personal: '个人证书',
+    organization: '组织证书',
+    other: '其他'
+  }
+  return map[type] || type
+}
+
+function formatDateCorrectly(timestamp: any) {
+  if (!timestamp) return ''
+  let time = Number(timestamp)
+  if (time < 10000000000) {
+    time = time * 1000
+  }
+  return formatDate(time).substring(0, 10)
+}
+
+let dialogFileView = ref(false)
+let fileList = ref([])
+const viewFile = (file) => {
+  fileList.value = file.split(',')
+  dialogFileView.value = true
+  // window.open(file)
+}
+
+const viewFileInfo = (file) => {
+  window.open(
+    'http://doc.deepoil.cc:8012/onlinePreview?url=' + encodeURIComponent(Base64.encode(file))
+  )
+}
+
+const extractFileName = (url: string): string => {
+  try {
+    // 移除查询参数和哈希
+    const cleanUrl = url.split('?')[0].split('#')[0]
+    // 获取最后一个斜杠后的内容
+    const parts = cleanUrl.split('/')
+    const fileName = parts[parts.length - 1]
+    // URL 解码
+    return decodeURIComponent(fileName) || url
+  } catch {
+    // 如果解析失败,返回原始 URL
+    return url
+  }
+}
+
+const handleDownload = async (url) => {
+  try {
+    const response = await fetch(url)
+    const blob = await response.blob()
+    const downloadUrl = window.URL.createObjectURL(blob)
+
+    const link = document.createElement('a')
+    link.href = downloadUrl
+    link.download = url.split('/').pop() // 自动获取文件名‌:ml-citation{ref="3" data="citationList"}
+    link.click()
+
+    URL.revokeObjectURL(downloadUrl)
+  } catch (error) {
+    console.error('下载失败:', error)
+  }
+}
+
+async function getCertificateBoardList() {
+  certLoading.value = true
+  try {
+    const data = await IotCertificateApi.getCertificateList({
+      ...certQueryParams
+      // deptId: userStore.user.deptId
+      // type: 'organization'
+    })
+    certTableData.value = data.list || []
+    certTotal.value = data.total || 0
+  } finally {
+    certLoading.value = false
+  }
+}
+
+async function getCertificateWarningList() {
+  certWarningLoading.value = true
+  try {
+    const data = await IotMeasureCertApi.getIotMeasureCertPage(certWarningQueryParams)
+    certWarningList.value = data.list || []
+    certWarningTotal.value = data.total || 0
+  } finally {
+    certWarningLoading.value = false
+  }
+}
+
+async function getHazardDialogList() {
+  hazardDialogLoading.value = true
+  try {
+    const data = await IotHiddenApi.getHiddenList(hazardDialogQueryParams)
+    hazardDialogList.value = data.list || []
+    hazardDialogTotal.value = data.total || 0
+  } finally {
+    hazardDialogLoading.value = false
+  }
+}
+
+async function getDangerDialogList() {
+  dangerDialogLoading.value = true
+  try {
+    const data = await IotDangerApi.getDangerList(dangerDialogQueryParams)
+    dangerDialogList.value = data.list || []
+    dangerDialogTotal.value = data.total || 0
+  } finally {
+    dangerDialogLoading.value = false
+  }
+}
+
+async function getInstrumentDialogList() {
+  instrumentDialogLoading.value = true
+  try {
+    const data = await IotInstrumentApi.getInstrumentList(instrumentDialogQueryParams)
+    instrumentDialogList.value = data.list || []
+    instrumentDialogTotal.value = data.total || 0
+  } finally {
+    instrumentDialogLoading.value = false
+  }
+}
+
+function openHazardDialog(barLabel: string) {
+  hazardDialogQueryParams.pageNo = 1
+  hazardDialogQueryParams.pageSize = 10
+  hazardDialogQueryParams.deptId = userStore.getUser.deptId
+  hazardDialogQueryParams.address = ''
+  hazardDialogQueryParams.source = ''
+
+  if (barLabel === '未整改') {
+    hazardDialogTitle.value = '未整改隐患列表'
+    hazardDialogQueryParams.status = 'todo'
+  } else if (barLabel === '已整改') {
+    hazardDialogTitle.value = '已整改隐患列表'
+    hazardDialogQueryParams.status = 'finished'
+  } else {
+    hazardDialogTitle.value = '隐患总数列表'
+    hazardDialogQueryParams.status = undefined
+  }
+
+  hazardDialogVisible.value = true
+  getHazardDialogList()
+}
+
+function openDangerDialog(zone: { title?: string }) {
+  const riskGradeLabel = String(zone.title || '')
+    .replace(/区$/, '')
+    .trim()
+  const matchedOption = dangerGradeOptions.value.find((item) => item.label === riskGradeLabel)
+  const riskGrade = matchedOption?.value || ''
+  dangerDialogQueryParams.pageNo = 1
+  dangerDialogQueryParams.pageSize = 10
+  dangerDialogQueryParams.deptId = userStore.getUser.deptId
+  dangerDialogQueryParams.riskGrade = riskGrade
+  dangerDialogTitle.value = riskGradeLabel ? `${riskGradeLabel}风险列表` : '风险列表'
+  dangerDialogVisible.value = true
+  getDangerDialogList()
+}
+
+function openInstrumentDialog(type: 'all' | 'expired') {
+  instrumentDialogQueryParams.pageNo = 1
+  instrumentDialogQueryParams.pageSize = 10
+  instrumentDialogQueryParams.measureName = undefined
+  instrumentDialogQueryParams.measureCertNo = undefined
+  instrumentDialogQueryParams.deptId = userStore.getUser.deptId
+  instrumentDialogQueryParams.alertWarn = undefined
+
+  if (type === 'expired') {
+    instrumentDialogTitle.value = '待检设备列表'
+    instrumentDialogQueryParams.expired = true
+  } else {
+    instrumentDialogTitle.value = '在用设备列表'
+    instrumentDialogQueryParams.expired = undefined
+  }
+
+  instrumentDialogVisible.value = true
+  getInstrumentDialogList()
+}
+
+function handleHazardBarClick(params: { name?: string }) {
+  if (!params?.name) return
+  openHazardDialog(params.name)
+}
+
+function openCertificateWarningDialog(type: 'expired' | 'warn') {
+  certWarningQueryParams.pageNo = 1
+  certWarningQueryParams.pageSize = 10
+  certWarningQueryParams.deptId = userStore.getUser.deptId
+  certWarningQueryParams.type = undefined
+  certWarningQueryParams.classify = undefined
+  certWarningQueryParams.userName = undefined
+
+  if (type === 'expired') {
+    certWarningDialogTitle.value = '证件过期'
+    certWarningQueryParams.expired = true
+    certWarningQueryParams.alertWarn = undefined
+  } else {
+    certWarningDialogTitle.value = '证件即将到期'
+    certWarningQueryParams.expired = undefined
+    certWarningQueryParams.alertWarn = true
+  }
+
+  certWarningDialogVisible.value = true
+  getCertificateWarningList()
+}
+
+function handleCertificateQuery() {
+  certQueryParams.pageNo = 1
+  getCertificateBoardList()
+}
+
+function resetCertificateQuery() {
+  certQueryParams.pageNo = 1
+  certQueryParams.pageSize = 10
+
+  certQueryParams.expired = undefined
+  certQueryParams.alertWarn = undefined
+  getCertificateBoardList()
+}
+
+watch(
+  () => activeSummaryTab.value,
+  async (value) => {
+    if (value === 'certificate') {
+      await getCertificateBoardList()
+    } else {
+      nextTick(() => {
+        resizeHazardChart()
+        resizeSafeDayChart()
+        resizeQhseTrendChart()
+        resizeSocChart()
+      })
+    }
+  }
+)
+
+watch(
+  () => type.value,
+  (value) => {
+    timeVal.value = getCurrentPickerValue(value)
+  }
+)
+
+onMounted(async () => {
+  // supportsZoom.value = typeof CSS !== 'undefined' && CSS.supports?.('zoom', '1') === true
+  nextTick(updateScale)
+  resizeObserver = new ResizeObserver(updateScale)
+  if (wrapperRef.value) {
+    resizeObserver.observe(wrapperRef.value)
+  }
+  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()
+})
+
+onUnmounted(() => {
+  resizeObserver?.disconnect()
+  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>
+
+<template>
+  <div ref="wrapperRef" class="bg absolute top-0 left-0 size-full z-10">
+    <div class="mx-a overflow-hidden" :style="targetWrapperStyle">
+      <div id="qhse-kanban" class="bg qhse-board" :style="targetAreaStyle">
+        <header class="header">{{ pageTitle }}</header>
+
+        <div class="summary-toolbar">
+          <div class="summary-toolbar__tabs">
+            <button
+              v-for="tab in summaryTabs"
+              :key="tab.value"
+              type="button"
+              class="summary-toolbar__tab"
+              :class="{ 'is-active': activeSummaryTab === tab.value }"
+              @click="activeSummaryTab = tab.value"
+            >
+              {{ tab.label }}
+            </button>
+          </div>
+
+          <div v-if="activeSummaryTab === 'home'" class="summary-toolbar__date">
+            <span class="summary-toolbar__date-label">日期:</span>
+
+            <div class="flex items-center gap-2 datepicker">
+              <el-date-picker
+                v-model="timeVal"
+                :teleport="true"
+                :type="type === 'day' ? 'date' : type === 'month' ? 'month' : 'year'"
+                class="summary-toolbar__picker el-date-picker"
+                popper-class="summary-toolbar__picker-popper"
+                value-format="YYYY-MM-DD"
+                range-separator="~"
+                start-placeholder="开始"
+                end-placeholder="结束"
+                @change="handelChange"
+                style="width: 150px; height: 40px; margin-left: 10px; background-color: transparent"
+              />
+
+              <span class="bg-[#f5f9ff] rounded-2xl px-2 py-0 cursor-pointer">月度</span>
+              <span class="bg-[#f5f9ff] rounded-2xl px-2 py-0 cursor-pointer">季度</span>
+              <span class="bg-[#f5f9ff] rounded-2xl px-2 py-0 cursor-pointer">年度</span>
+              <span class="bg-[#f5f9ff] rounded-2xl px-2 py-0 cursor-pointer">初始化</span>
+            </div>
+          </div>
+        </div>
+
+        <div v-if="activeSummaryTab === 'home'" class="board-body">
+          <section class="panel summary-panel kb-stage-card kb-stage-card--1">
+            <div class="panel-title">
+              <span class="icon-decorator"><span></span><span></span></span>
+              QHSE关键数据
+            </div>
+            <div class="summary-grid">
+              <article
+                v-for="card in summaryCards"
+                :key="card.title"
+                class="summary-card summary-tile"
+                :style="
+                  {
+                    '--card-accent': card.accent,
+                    '--card-glow': card.glow
+                  } as any
+                "
+              >
+                <span class="summary-card__shine"></span>
+                <span class="summary-card__corner"></span>
+                <div class="summary-card__icon">
+                  <el-icon class="summary-card__icon-glyph">
+                    <component :is="card.icon" />
+                  </el-icon>
+                </div>
+                <div class="summary-card__content">
+                  <div class="summary-card__label">{{ card.title }}</div>
+                  <div class="summary-tile__meta">
+                    <span class="summary-tile__value">{{ card.value }}</span>
+                    <span class="summary-tile__note" :style="{ color: card.accent }">
+                      {{ card.note }}
+                    </span>
+                  </div>
+                </div>
+              </article>
+            </div>
+          </section>
+
+          <div class="board-main">
+            <div class="center-column">
+              <section class="panel board-panel board-panel--center kb-stage-card kb-stage-card--4">
+                <div class="panel-title panel-title--center">
+                  <span class="icon-decorator"><span></span><span></span></span>
+                  风险管控及隐患治理
+                </div>
+                <div class="risk-grid">
+                  <article
+                    v-for="zone in riskZones"
+                    :key="zone.title"
+                    class="risk-card risk-card--clickable"
+                    @click="openDangerDialog(zone)"
+                  >
+                    <div class="risk-card__title">
+                      <span class="risk-card__dot" :style="{ background: zone.color }"></span>
+                      <span :style="{ color: zone.color }">{{ zone.title }}</span>
+                      <CountTo
+                        :duration="2600"
+                        :end-val="zone.value"
+                        :start-val="0"
+                        :style="{ color: zone.color, fontSize: '22px' }"
+                      />
+                    </div>
+                    <div class="risk-card__desc">{{ zone.desc }}</div>
+                  </article>
+                </div>
+
+                <div class="risk-hazard-block mt-2!">
+                  <div
+                    ref="hazardChartRef"
+                    class="chart-panel chart-panel--echart risk-hazard-block__chart"
+                  ></div>
+                </div>
+              </section>
+            </div>
+
+            <div class="left-column">
+              <section class="panel board-panel kb-stage-card kb-stage-card--3">
+                <div class="panel-title panel-title--center">
+                  <span class="icon-decorator"><span></span><span></span></span>
+                  QHSE指标
+                </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">
+                  <div class="qhse-metric-tabs">
+                    <button
+                      v-for="item in qhseMetricTabs"
+                      :key="item.value"
+                      :title="item.label2"
+                      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="qhseTrendChartRef" class="qhse-trend-chart-panel"></div>
+                </section>
+              </section>
+            </div>
+
+            <div class="right-column">
+              <section class="panel board-panel board-panel--center kb-stage-card kb-stage-card--4">
+                <div class="panel-title panel-title--center">
+                  <span class="icon-decorator"><span></span><span></span></span>
+                  行为安全与系统预警
+                </div>
+                <section class="board-panel kb-stage-card kb-stage-card--5 pt-2">
+                  <div ref="socChartRef" class="soc-chart-panel"></div>
+                </section>
+
+                <section class="board-panel kb-stage-card kb-stage-card--6 pl-4">
+                  <div class="qualification-panel">
+                    <div class="qualification-icon">
+                      <el-icon>
+                        <CollectionTag />
+                      </el-icon>
+                    </div>
+                    <div class="qualification-list">
+                      <div
+                        v-for="item in qualificationWarnings"
+                        :key="item.label"
+                        class="qualification-item qualification-item--clickable"
+                        @click="openCertificateWarningDialog(item.key)"
+                      >
+                        <span class="qualification-item__label">{{ item.label }}:</span>
+                        <strong :style="{ color: item.accent }">
+                          <CountTo
+                            :duration="2600"
+                            :end-val="item.value"
+                            :start-val="0"
+                            :style="{ color: item.accent }"
+                          />
+                          <span class="pl-2">人</span>
+                        </strong>
+                      </div>
+                    </div>
+                  </div>
+                </section>
+              </section>
+            </div>
+          </div>
+
+          <section class="bottom-grid">
+            <article
+              v-for="(card, index) in bottomCards"
+              :key="card.title"
+              class="panel bottom-card kb-stage-card"
+              :class="`kb-stage-card--${index + 7}`"
+              :style="
+                {
+                  '--card-accent': card.accent,
+                  '--card-glow': card.glow
+                } as any
+              "
+            >
+              <span class="summary-card__shine"></span>
+              <div class="bottom-card__header">
+                <div class="summary-card__icon bottom-card__icon">
+                  <el-icon class="summary-card__icon-glyph">
+                    <component :is="card.icon" />
+                  </el-icon>
+                </div>
+                <div class="bottom-card__title">{{ card.title }}</div>
+              </div>
+              <div class="bottom-card__content">
+                <template v-if="card.title === '安全检测'">
+                  <button
+                    type="button"
+                    class="bottom-card__action"
+                    @click="openInstrumentDialog('all')"
+                  >
+                    {{ card.lines[0] }}
+                  </button>
+                  <button
+                    type="button"
+                    class="bottom-card__action"
+                    @click="openInstrumentDialog('expired')"
+                  >
+                    {{ card.lines[1] }}
+                  </button>
+                </template>
+                <template v-else>
+                  <p v-for="line in card.lines" :key="line">{{ line }}</p>
+                </template>
+              </div>
+            </article>
+          </section>
+        </div>
+
+        <div v-else class="certificate-board">
+          <section class="panel certificate-filter-panel">
+            <div class="certificate-filters">
+              <div class="certificate-filter-item">
+                <span class="certificate-filter-item__label">是否过期</span>
+                <el-select
+                  v-model="certQueryParams.expired"
+                  clearable
+                  placeholder="请选择是否过期"
+                  class="certificate-filter-item__control"
+                >
+                  <el-option
+                    v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
+                    :key="dict.value"
+                    :label="dict.label"
+                    :value="dict.value"
+                  />
+                </el-select>
+              </div>
+
+              <div class="certificate-filter-item">
+                <span class="certificate-filter-item__label">是否预警</span>
+                <el-select
+                  v-model="certQueryParams.alertWarn"
+                  clearable
+                  placeholder="请选择是否过期"
+                  class="certificate-filter-item__control"
+                >
+                  <el-option
+                    v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
+                    :key="dict.value"
+                    :label="dict.label"
+                    :value="dict.value"
+                  />
+                </el-select>
+              </div>
+
+              <div class="certificate-filter-actions">
+                <el-button type="primary" :icon="Search" @click="handleCertificateQuery"
+                  >查询</el-button
+                >
+                <el-button :icon="Refresh" @click="resetCertificateQuery">重置</el-button>
+              </div>
+            </div>
+          </section>
+
+          <section class="panel certificate-table-panel">
+            <el-table
+              :loading="certLoading"
+              :data="certTableData"
+              :row-style="tableRowStyle"
+              :row-class-name="tableRowClassName"
+              height="100vh"
+              :header-cell-style="{
+                height: '42px'
+              }"
+              class="device-list-table production-brief-table"
+            >
+              <el-table-column label="序号" width="80" align="center">
+                <template #default="scope">
+                  {{ (certQueryParams.pageNo - 1) * certQueryParams.pageSize + scope.$index + 1 }}
+                </template>
+              </el-table-column>
+
+              <el-table-column label="证书类别" align="center" width="150" prop="classify">
+                <template #default="scope">
+                  <dict-tag :type="DICT_TYPE.ORG_CERT" :value="scope.row.classify" />
+                </template>
+              </el-table-column>
+              <el-table-column
+                label="证书名称"
+                align="center"
+                prop="certName"
+                show-overflow-tooltip
+              />
+
+              <el-table-column label="所在部门" align="center" prop="deptName" />
+
+              <el-table-column label="颁发机构" align="center" prop="certOrg" />
+
+              <el-table-column label="证书标准" align="center" prop="certStandard" />
+
+              <el-table-column label="颁发时间" align="center" prop="certIssue">
+                <template #default="scope">
+                  {{ formatDateCorrectly(scope.row.certIssue) }}
+                </template>
+              </el-table-column>
+
+              <el-table-column label="有效期" align="center">
+                <template #default="scope">
+                  {{ formatDateCorrectly(scope.row.certExpire) }}
+                </template>
+              </el-table-column>
+
+              <el-table-column label="到期提醒" align="center">
+                <template #default="scope"> {{ scope.row.noticeBefore }}天前提醒 </template>
+              </el-table-column>
+
+              <el-table-column label="备注" align="center" prop="remark" />
+              <el-table-column label="操作" align="center" fixed="right" min-width="180px" action>
+                <template #default="scope">
+                  <el-button
+                    link
+                    type="success"
+                    v-if="scope.row.certPic"
+                    @click="viewFile(scope.row.certPic)"
+                  >
+                    查看证书
+                  </el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+
+            <div class="certificate-pagination">
+              <Pagination
+                :total="certTotal"
+                v-model:page="certQueryParams.pageNo"
+                v-model:limit="certQueryParams.pageSize"
+                @pagination="getCertificateBoardList"
+              />
+            </div>
+
+            <div class="shadow rounded-lg p-3 min-w-0 flex gap-4 justify-between">
+              <div
+                class="bg-[#fef0f0] text-[#f57979] px-3 flex-1 flex items-center justify-center text-center py-2 text-sm rounded-md"
+                ><el-icon class="stats-card__icon" :size="28">
+                  <Icon icon="ep:info-filled" /> </el-icon
+                >证书已过期红色预警</div
+              >
+              <div
+                class="bg-[#fdf6ec] flex-1 text-[#e6a23c] text-center flex items-center justify-center px-3 py-2 text-sm rounded-md"
+                ><el-icon class="stats-card__icon" :size="28">
+                  <Icon icon="ep:bell-filled" /> </el-icon
+                >证书60天橙色预警</div
+              >
+            </div>
+          </section>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <Dialog v-model="dialogFileView" title="附件">
+    <div
+      v-for="(file, index) in fileList"
+      :key="index"
+      class="mt-5 flex min-w-0 items-center justify-between gap-5"
+    >
+      <span class="file-name-text flex-1 min-w-0">{{ extractFileName(file) }}</span>
+      <div class="flex shrink-0 items-center gap-5 flex-nowrap">
+        <el-button link type="primary" @click="viewFileInfo(file)">
+          <Icon icon="ep:view" class="mr-2px" />查看
+        </el-button>
+        <el-button link type="primary" @click="handleDownload(file)">
+          <Icon icon="ep:download" class="mr-2px" />下载
+        </el-button>
+      </div>
+    </div>
+
+    <template #footer>
+      <div class="dialog-footer mt-10">
+        <el-button type="primary" @click="dialogFileView = false">确认</el-button>
+      </div>
+    </template>
+  </Dialog>
+
+  <Dialog v-model="certWarningDialogVisible" :title="certWarningDialogTitle" width="1100px">
+    <div class="certificate-warning-dialog">
+      <el-table
+        :loading="certWarningLoading"
+        :data="certWarningList"
+        :row-style="tableRowStyle"
+        :row-class-name="tableRowClassName"
+        height="400"
+        class="device-list-table production-brief-table"
+      >
+        <el-table-column label="序号" width="80" align="center">
+          <template #default="scope">
+            {{
+              (certWarningQueryParams.pageNo - 1) * certWarningQueryParams.pageSize +
+              scope.$index +
+              1
+            }}
+          </template>
+        </el-table-column>
+        <el-table-column label="证书类型" align="center" prop="type" width="120">
+          <template #default="scope">
+            {{ getCertificateTypeText(scope.row.type) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="证书类别" align="center" width="150" prop="classify">
+          <template #default="scope">
+            <dict-tag
+              v-if="scope.row.type === 'organization'"
+              :type="DICT_TYPE.ORG_CERT"
+              :value="scope.row.classify"
+            />
+            <dict-tag v-else :type="DICT_TYPE.PERSON_CERT" :value="scope.row.classify" />
+          </template>
+        </el-table-column>
+        <el-table-column
+          label="证书名称"
+          align="center"
+          prop="certName"
+          show-overflow-tooltip
+          width="100"
+        />
+        <el-table-column label="所属人" align="center" prop="userName" width="100" />
+        <el-table-column
+          label="所在部门"
+          align="center"
+          prop="deptName"
+          width="100"
+          show-overflow-tooltip
+        />
+        <el-table-column
+          label="颁发机构"
+          align="center"
+          prop="certOrg"
+          width="180"
+          show-overflow-tooltip
+        />
+        <el-table-column
+          label="证书标准"
+          align="center"
+          prop="certStandard"
+          width="100"
+          show-overflow-tooltip
+        />
+        <el-table-column label="颁发时间" align="center" prop="certIssue" width="120">
+          <template #default="scope">
+            {{ formatDateCorrectly(scope.row.certIssue) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="有效期" align="center" prop="certExpire" width="120">
+          <template #default="scope">
+            {{ formatDateCorrectly(scope.row.certExpire) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
+        <el-table-column label="操作" align="center" fixed="right" min-width="100px">
+          <template #default="scope">
+            <el-button
+              link
+              type="success"
+              v-if="scope.row.certPic"
+              @click="viewFile(scope.row.certPic)"
+            >
+              查看证书
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div class="certificate-pagination">
+        <Pagination
+          :total="certWarningTotal"
+          v-model:page="certWarningQueryParams.pageNo"
+          v-model:limit="certWarningQueryParams.pageSize"
+          @pagination="getCertificateWarningList"
+        />
+      </div>
+    </div>
+  </Dialog>
+
+  <Dialog v-model="hazardDialogVisible" :title="hazardDialogTitle" width="1400px">
+    <div class="certificate-warning-dialog">
+      <el-table
+        :loading="hazardDialogLoading"
+        :data="hazardDialogList"
+        height="400"
+        class="device-list-table production-brief-table"
+      >
+        <el-table-column label="序号" width="80" align="center">
+          <template #default="scope">
+            {{
+              (hazardDialogQueryParams.pageNo - 1) * hazardDialogQueryParams.pageSize +
+              scope.$index +
+              1
+            }}
+          </template>
+        </el-table-column>
+        <el-table-column label="单位" align="center" prop="deptName" min-width="140" />
+        <el-table-column label="类型" align="center" prop="classifyName" min-width="120" />
+        <el-table-column label="来源" align="center" min-width="110">
+          <template #default="scope">
+            <dict-tag :type="DICT_TYPE.QHSE_HAZARD_SOURCE" :value="scope.row.source" />
+          </template>
+        </el-table-column>
+        <el-table-column label="地点" align="center" prop="address" min-width="140" />
+        <el-table-column
+          label="存在问题"
+          align="center"
+          prop="problem"
+          min-width="180"
+          show-overflow-tooltip
+        />
+        <el-table-column label="隐患附件" align="center" min-width="100">
+          <template #default="{ row }">
+            <el-button v-if="row.hazardFile" link type="primary" @click="viewFile(row.hazardFile)">
+              查看
+            </el-button>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column
+          label="整改人"
+          align="center"
+          prop="correctPersonName"
+          min-width="100"
+          show-overflow-tooltip
+        />
+        <el-table-column
+          label="整改要求"
+          align="center"
+          prop="correctStandard"
+          min-width="160"
+          show-overflow-tooltip
+        />
+        <el-table-column
+          label="整改情况"
+          align="center"
+          prop="rectifyDesc"
+          min-width="160"
+          show-overflow-tooltip
+        />
+        <el-table-column label="整改附件" align="center" min-width="100">
+          <template #default="{ row }">
+            <el-button
+              v-if="row.rectifyFile"
+              link
+              type="primary"
+              @click="viewFile(row.rectifyFile)"
+            >
+              查看
+            </el-button>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="排查时间" align="center" min-width="120">
+          <template #default="{ row }">
+            {{ formatDateCorrectly(row.checkTime) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="状态" align="center" min-width="90">
+          <template #default="scope">
+            <dict-tag :type="DICT_TYPE.QHSE_HAZARD_STATUS" :value="scope.row.status" />
+          </template>
+        </el-table-column>
+        <el-table-column
+          label="备注"
+          align="center"
+          prop="remark"
+          min-width="120"
+          show-overflow-tooltip
+        />
+      </el-table>
+
+      <div class="certificate-pagination">
+        <Pagination
+          :total="hazardDialogTotal"
+          v-model:page="hazardDialogQueryParams.pageNo"
+          v-model:limit="hazardDialogQueryParams.pageSize"
+          @pagination="getHazardDialogList"
+        />
+      </div>
+    </div>
+  </Dialog>
+
+  <Dialog v-model="dangerDialogVisible" :title="dangerDialogTitle" width="1400px">
+    <div class="certificate-warning-dialog">
+      <el-table
+        :loading="dangerDialogLoading"
+        :data="dangerDialogList"
+        height="400"
+        class="device-list-table production-brief-table"
+      >
+        <el-table-column label="序号" width="80" align="center">
+          <template #default="scope">
+            {{
+              (dangerDialogQueryParams.pageNo - 1) * dangerDialogQueryParams.pageSize +
+              scope.$index +
+              1
+            }}
+          </template>
+        </el-table-column>
+        <el-table-column
+          label="区域/位置"
+          align="center"
+          prop="region"
+          min-width="140"
+          show-overflow-tooltip
+        />
+        <el-table-column
+          label="危险因素描述"
+          align="center"
+          prop="elementDescription"
+          min-width="180"
+          show-overflow-tooltip
+        />
+        <el-table-column
+          label="可导致的后果"
+          align="center"
+          prop="maybeResult"
+          min-width="180"
+          show-overflow-tooltip
+        />
+        <el-table-column label="风险评价" align="center">
+          <el-table-column prop="evalKn" label="可能性 (L)" width="100" align="center">
+            <template #default="{ row }">
+              {{ row.evalKn }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="evalYz" label="严重性 (S)" width="100" align="center">
+            <template #default="{ row }">
+              {{ row.evalYz }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="evalFxz" label="风险值 (R)" width="100" align="center">
+            <template #default="{ row }">
+              {{ row.evalFxz }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="riskGrade" label="风险等级" width="100" align="center">
+            <template #default="scope">
+              <dict-tag :type="DICT_TYPE.DANGER_GRADE" :value="scope.row.riskGrade" />
+            </template>
+          </el-table-column>
+        </el-table-column>
+        <el-table-column
+          label="控制措施"
+          align="center"
+          prop="controlMethod"
+          show-overflow-tooltip
+        />
+        <el-table-column
+          label="责任人"
+          align="center"
+          prop="charge"
+          min-width="100"
+          show-overflow-tooltip
+        />
+      </el-table>
+
+      <div class="certificate-pagination">
+        <Pagination
+          :total="dangerDialogTotal"
+          v-model:page="dangerDialogQueryParams.pageNo"
+          v-model:limit="dangerDialogQueryParams.pageSize"
+          @pagination="getDangerDialogList"
+        />
+      </div>
+    </div>
+  </Dialog>
+
+  <Dialog v-model="instrumentDialogVisible" :title="instrumentDialogTitle" width="1400px">
+    <div class="certificate-warning-dialog">
+      <el-table
+        :loading="instrumentDialogLoading"
+        :data="instrumentDialogList"
+        :row-style="tableRowStyle"
+        :row-class-name="tableRowClassName"
+        height="400"
+        class="device-list-table production-brief-table"
+      >
+        <el-table-column label="序号" width="80" align="center">
+          <template #default="scope">
+            {{
+              (instrumentDialogQueryParams.pageNo - 1) * instrumentDialogQueryParams.pageSize +
+              scope.$index +
+              1
+            }}
+          </template>
+        </el-table-column>
+        <el-table-column label="名称" align="center" prop="measureName" min-width="140" />
+        <el-table-column label="编码" align="center" prop="measureCode" min-width="120" />
+        <el-table-column label="序列号" align="center" prop="serialNo" min-width="120" />
+        <el-table-column label="部门名称" align="center" prop="deptName" min-width="140" />
+        <el-table-column label="计量单位" align="center" prop="measureUnit" min-width="100" />
+        <el-table-column label="责任人" align="center" prop="dutyPerson" min-width="100" />
+        <el-table-column label="品牌" align="center" prop="brand" min-width="100" />
+        <el-table-column label="规格型号" align="center" prop="modelName" min-width="120" />
+        <el-table-column label="分类" align="center" min-width="120">
+          <template #default="scope">
+            <dict-tag :type="DICT_TYPE.MEASURE_TYPE" :value="scope.row.classify" />
+          </template>
+        </el-table-column>
+        <el-table-column label="采购日期" align="center" min-width="120">
+          <template #default="scope">
+            {{ formatDateCorrectly(scope.row.buyDate) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="价格" align="center" prop="measurePrice" min-width="100" />
+        <el-table-column
+          label="备注"
+          align="center"
+          prop="remark"
+          min-width="140"
+          show-overflow-tooltip
+        />
+      </el-table>
+
+      <div class="certificate-pagination">
+        <Pagination
+          :total="instrumentDialogTotal"
+          v-model:page="instrumentDialogQueryParams.pageNo"
+          v-model:limit="instrumentDialogQueryParams.pageSize"
+          @pagination="getInstrumentDialogList"
+        />
+      </div>
+    </div>
+  </Dialog>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/qhse_kb.scss');
+
+:deep(.el-select__wrapper) {
+  // height: 38px !important;
+  font-size: 14px !important;
+}
+.qhse-board {
+  color: #24364f;
+}
+
+.board-body {
+  padding: 5px 18px 24px;
+}
+
+.summary-toolbar {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 10px 18px 6px;
+}
+
+.summary-toolbar__tabs {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.summary-toolbar__tab {
+  min-width: 120px;
+  height: 40px;
+  padding: 0 22px;
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: 17px;
+  color: #2b76e9;
+  background: linear-gradient(180deg, rgb(255 255 255 / 92%) 0%, rgb(228 239 255 / 90%) 100%);
+  border: 1px solid rgb(126 174 244 / 44%);
+  border-radius: 10px;
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 96%),
+    0 8px 14px rgb(63 103 171 / 8%);
+  cursor: pointer;
+  transition:
+    transform 0.2s ease,
+    box-shadow 0.2s ease,
+    background 0.2s ease,
+    color 0.2s ease;
+}
+
+.summary-toolbar__tab.is-active {
+  color: #fff;
+  background: linear-gradient(180deg, #59a6ff 0%, #2d78ea 100%);
+  border-color: rgb(62 122 223 / 88%);
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 22%),
+    0 10px 18px rgb(45 120 234 / 20%);
+}
+
+.summary-toolbar__tab:hover {
+  transform: translateY(-1px);
+}
+
+.summary-toolbar__date {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+
+.summary-toolbar__date-label {
+  font-size: 18px;
+  font-weight: 700;
+  color: #5c7393;
+}
+
+.bottom-card__action {
+  display: block;
+  width: 100%;
+  padding: 0;
+  text-align: left;
+  color: #617491;
+  background: transparent;
+  border: none;
+  cursor: pointer;
+  transition: color 0.2s ease;
+  font-size: 18px;
+  font-weight: 700;
+  color: #5c7393;
+}
+
+.bottom-card__action:hover {
+  color: #2d78ea;
+}
+
+.risk-card--clickable {
+  cursor: pointer;
+  transition:
+    transform 0.2s ease,
+    box-shadow 0.2s ease;
+}
+
+.risk-card--clickable:hover {
+  transform: translateY(-2px);
+}
+
+.summary-toolbar__picker {
+  width: 190px;
+}
+
+.summary-toolbar__picker :deep(.el-input__wrapper) {
+  min-height: 38px;
+  background: rgb(255 255 255 / 80%);
+  border: 1px solid rgb(137 176 235 / 38%);
+  border-radius: 10px;
+  font-size: 14px !important;
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 92%),
+    0 8px 14px rgb(63 103 171 / 6%);
+}
+
+.summary-toolbar__picker :deep(.el-input__inner),
+.summary-toolbar__picker :deep(.el-range-input),
+.summary-toolbar__picker :deep(.el-input__prefix),
+.summary-toolbar__picker :deep(.el-input__suffix) {
+  font-size: 14px !important;
+}
+
+.summary-toolbar__picker :deep(.el-input__inner::placeholder),
+.summary-toolbar__picker :deep(.el-range-input::placeholder) {
+  font-size: 14px !important;
+}
+
+:deep(.summary-toolbar__picker-popper) {
+  font-size: 14px;
+}
+
+:deep(.summary-toolbar__picker-popper .el-date-table),
+:deep(.summary-toolbar__picker-popper .el-date-table th),
+:deep(.summary-toolbar__picker-popper .el-date-table td),
+:deep(.summary-toolbar__picker-popper .el-month-table),
+:deep(.summary-toolbar__picker-popper .el-year-table),
+:deep(.summary-toolbar__picker-popper .el-picker-panel__icon-btn),
+:deep(.summary-toolbar__picker-popper .el-date-picker__header-label) {
+  font-size: 14px !important;
+}
+
+.summary-panel {
+  padding: 0 10px 10px;
+  height: 200px;
+}
+
+.summary-grid {
+  display: grid;
+  grid-template-columns: repeat(6, minmax(0, 1fr));
+  gap: 24px;
+  margin-top: 10px;
+}
+
+.summary-tile {
+  display: flex;
+  height: 136px;
+  padding: 28px 18px 24px;
+  border-radius: 22px;
+  align-items: center;
+  gap: 22px;
+}
+
+.summary-card__content {
+  position: relative;
+  z-index: 2;
+  min-width: 0;
+  flex: 1;
+}
+
+.summary-tile__meta {
+  display: flex;
+  margin-top: 14px;
+  align-items: baseline;
+  gap: 8px;
+}
+
+.summary-tile__value {
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: 30px;
+  line-height: 1;
+  color: #114a9b;
+  letter-spacing: 1px;
+}
+
+.summary-tile__note {
+  font-size: 16px;
+  font-weight: 700;
+}
+
+.board-main {
+  display: grid;
+  grid-template-columns: 1.02fr 1fr 0.98fr;
+  gap: 28px;
+  margin-top: 10px;
+}
+
+.left-column,
+.right-column {
+  display: grid;
+  gap: 24px;
+  align-content: start;
+}
+.left-column {
+  height: 540px;
+}
+.board-panel {
+  min-height: 258px;
+}
+
+.board-panel.kb-stage-card:hover {
+  transform: none;
+}
+
+.board-panel--center {
+  min-height: 540px;
+  height: 540px;
+}
+
+.chart-panel {
+  margin-top: 18px;
+}
+
+.chart-panel--echart {
+  height: 188px;
+}
+
+.safe-day-chart-panel {
+  height: 230px;
+}
+
+.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: 220px;
+  // margin-top: 14px;
+}
+
+.soc-chart-panel {
+  height: 280px;
+}
+
+.panel-title--center {
+  justify-content: center;
+  padding-left: 0;
+  font-size: 32px;
+}
+
+.panel-title--center .icon-decorator {
+  left: 28px;
+}
+
+.risk-grid {
+  display: grid;
+  padding: 10px 24px 12px;
+  // margin-top: 18px;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  gap: 10px 18px;
+}
+
+.risk-hazard-block {
+  padding: 0 24px 5px;
+  margin-top: 18px;
+}
+
+.risk-hazard-block__title {
+  margin-top: 0;
+}
+
+.risk-hazard-block__chart {
+  margin-top: 12px;
+}
+
+.risk-card {
+  min-height: 124px;
+  padding: 20px 18px;
+  background: linear-gradient(180deg, rgb(255 255 255 / 42%) 0%, rgb(220 232 250 / 28%) 100%);
+  border: 1px solid rgb(255 255 255 / 58%);
+  border-radius: 18px;
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 74%),
+    0 8px 18px rgb(63 103 171 / 7%);
+}
+
+.risk-card__title {
+  display: flex;
+  font-size: 16px;
+  font-weight: 800;
+  align-items: center;
+  gap: 8px;
+}
+
+.risk-card__dot {
+  width: 14px;
+  height: 14px;
+  border-radius: 999px;
+  box-shadow: 0 0 0 4px rgb(255 255 255 / 35%);
+}
+
+.risk-card__desc {
+  margin-top: 18px;
+  font-size: 13px;
+  font-weight: 600;
+  color: #6f819a;
+  line-height: 1.35;
+}
+
+.qualification-panel {
+  display: grid;
+  margin-top: 26px;
+  padding: 28px;
+  grid-template-columns: 140px 1fr;
+  padding-left: 120px;
+  align-items: center;
+  gap: 26px;
+}
+
+.qualification-icon {
+  display: flex;
+  width: 132px;
+  height: 132px;
+  font-size: 74px;
+  color: #f09717;
+  background: radial-gradient(
+    circle at 50% 40%,
+    rgb(255 255 255 / 74%) 0%,
+    rgb(231 240 255 / 42%) 100%
+  );
+  border: 1px solid rgb(255 255 255 / 72%);
+  border-radius: 32px;
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 85%),
+    0 10px 22px rgb(55 94 160 / 10%);
+  align-items: center;
+  justify-content: center;
+}
+
+.qualification-list {
+  display: grid;
+  gap: 22px;
+}
+
+.qualification-item {
+  font-size: 24px;
+  font-weight: 700;
+  color: #516785;
+}
+
+.qualification-item--clickable {
+  cursor: pointer;
+  transition:
+    transform 0.18s ease,
+    opacity 0.18s ease;
+}
+
+.qualification-item--clickable:hover {
+  transform: translateX(4px);
+  opacity: 0.9;
+}
+
+.qualification-item strong {
+  font-size: 28px;
+}
+
+.certificate-warning-dialog {
+  display: flex;
+  flex-direction: column;
+  gap: 14px;
+}
+
+.bottom-grid {
+  display: grid;
+  margin-top: 10px;
+  grid-template-columns: repeat(5, minmax(0, 1fr));
+  gap: 24px;
+}
+
+.bottom-card {
+  min-height: 180px;
+  padding: 15px 22px 0;
+  overflow: hidden;
+}
+
+.bottom-card__header {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+}
+
+.bottom-card__icon {
+  width: 54px;
+  height: 54px;
+}
+
+.bottom-card__title {
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: 24px;
+  color: #114a9b;
+  letter-spacing: 1px;
+}
+
+.bottom-card__content {
+  position: relative;
+  z-index: 2;
+  margin-top: 18px;
+}
+
+.bottom-card__content p {
+  margin: 0;
+  font-size: 18px;
+  font-weight: 700;
+  color: #5d718e;
+  line-height: 1.7;
+}
+
+.certificate-board {
+  padding: 16px 18px 24px;
+}
+
+.certificate-filter-panel {
+  padding: 18px 20px 12px;
+}
+
+.certificate-filters {
+  display: grid;
+  grid-template-columns: repeat(4, minmax(0, 1fr)) auto;
+  gap: 18px 20px;
+  align-items: end;
+}
+
+.certificate-filter-item {
+  display: flex;
+  align-items: center;
+  gap: 14px;
+}
+
+.certificate-filter-item__label {
+  flex-shrink: 0;
+  font-size: 16px;
+  font-weight: 700;
+  color: #4f6583;
+}
+
+.certificate-filter-item__control {
+  flex: 1;
+  height: 42px;
+}
+
+.certificate-filter-item__control :deep(.el-select__wrapper),
+.certificate-filter-item__control :deep(.el-input__wrapper) {
+  height: 42px !important;
+  min-height: 42px !important;
+  border-radius: 12px;
+}
+
+.certificate-filter-actions :deep(.el-button) {
+  height: 42px;
+  line-height: 42px;
+  padding: 0 22px;
+  border-radius: 12px;
+}
+
+.certificate-filter-item__control :deep(.el-input__inner) {
+  height: 42px;
+  line-height: 42px;
+}
+
+.certificate-filter-actions {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  justify-content: flex-end;
+}
+
+.certificate-table-panel {
+  margin-top: 18px;
+  padding: 18px 18px 12px;
+  height: 860px;
+  display: flex;
+  flex-direction: column;
+}
+
+.certificate-table {
+  flex: 1;
+}
+
+.file-name-text {
+  display: block;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+// :deep(.el-input__inner) {
+//   font-size: 14px !important;
+//   border: none !important;
+//   font-size: 14px !important;
+// }
+
+:deep(.datepicker .el-input__wrapper) {
+  background: transparent !important;
+  border: none !important;
+  font-size: 14px !important;
+}
+
+:deep(.el-input) {
+  border: none !important;
+  outline: none !important;
+  font-size: 14px !important;
+}
+
+:deep(.el-date-editor.el-input, .el-date-editor.el-input__inner) {
+  border: none !important;
+  font-size: 14px !important;
+}
+
+:deep(.el-date-editor.is-active) {
+  border-color: #ff6a00 !important;
+}
+
+.certificate-pagination {
+  padding-top: 0px;
+}
+
+.production-brief-table {
+  width: 100%;
+
+  :deep(.el-table__header-wrapper th.el-table__cell) {
+    font-size: calc(16px * var(--kb-scale, 1));
+    line-height: 1.2;
+  }
+
+  :deep(.el-table__body td.el-table__cell) {
+    padding: calc(7px * var(--kb-scale, 1)) 0;
+    font-size: calc(14px * var(--kb-scale, 1));
+  }
+}
+
+.summary-toolbar__picker {
+  :deep(*) {
+    font-size: 14px !important;
+  }
+
+  :deep(input) {
+    font-size: 14px !important;
+    line-height: 14px !important;
+  }
+}
+
+/* 过期行的红色背景 - 基础状态 */
+:deep(.el-table__body tr.expired-row > td.el-table__cell) {
+  background-color: #ffe6e6 !important;
+}
+
+/* 过期行 - 鼠标悬浮状态 */
+:deep(.el-table__body tr.expired-row:hover > td.el-table__cell) {
+  background-color: #ffcccc !important;
+}
+
+/* 确保斑马纹不影响过期行 */
+:deep(.el-table__body tr.expired-row.el-table__row--striped > td.el-table__cell) {
+  background-color: #ffe6e6 !important;
+}
+
+:deep(.el-table__body tr.expired-row.el-table__row--striped:hover > td.el-table__cell) {
+  background-color: #ffcccc !important;
+}
+
+/* 预警行的橙色背景 - 基础状态 */
+:deep(.el-table__body tr.alert-warn-row > td.el-table__cell) {
+  background-color: #fff1df !important;
+}
+
+/* 预警行 - 鼠标悬浮状态 */
+:deep(.el-table__body tr.alert-warn-row:hover > td.el-table__cell) {
+  background-color: #ffe2bf !important;
+}
+
+/* 确保斑马纹不影响预警行 */
+:deep(.el-table__body tr.alert-warn-row.el-table__row--striped > td.el-table__cell) {
+  background-color: #fff1df !important;
+}
+
+:deep(.el-table__body tr.alert-warn-row.el-table__row--striped:hover > td.el-table__cell) {
+  background-color: #ffe2bf !important;
+}
+</style>