Răsfoiți Sursa

feat(pms): 优化巡检统计看板样式及缩放适配

- 参照瑞恒看板统一巡检统计页面视觉风格
- 接入 1920×1080 设计画布和 ResizeObserver 等比缩放策略
- 统一看板背景、顶部标题及半透明蓝白面板样式
- 优化部门筛选、日期范围和操作按钮的尺寸及排布
- 重构工单摘要卡片、状态统计及趋势图表区域布局
- 为未完成工单使用橙色状态数值,增强数据区分度
- 复用 ECharts 实例并在画布缩放后统一调整图表尺寸
- 保留原有统计接口、筛选条件及状态跳转逻辑
Zimo 1 săptămână în urmă
părinte
comite
d3d466bebf
1 a modificat fișierele cu 661 adăugiri și 313 ștergeri
  1. 661 313
      src/views/pms/stat/inspect.vue

+ 661 - 313
src/views/pms/stat/inspect.vue

@@ -1,254 +1,260 @@
 <template>
-  <!-- 第一行:筛选条件 -->
-  <el-row :gutter="16" class="mb-6">
-    <el-col :span="24">
-      <el-card class="filter-card" shadow="hover" style="border: none">
-        <el-form
-          class="-mb-15px"
-          :model="queryParams"
-          ref="queryFormRef"
-          :inline="true"
-          label-width="80px"
-        >
-          <el-form-item label="所属部门" prop="project_name">
-            <el-tree-select
-              v-model="queryParams.deptId"
-              :data="deptList"
-              :props="defaultProps"
-              check-strictly
-              node-key="id"
-              filterable
-              placeholder="请选择所在部门"
-              clearable
-              style="width: 200px"
-            />
-          </el-form-item>
-          <el-form-item label="创建时间" prop="createTime">
-            <el-date-picker
-              v-model="queryParams.createTime"
-              value-format="YYYY-MM-DD HH:mm:ss"
-              type="daterange"
-              start-placeholder="开始日期"
-              end-placeholder="结束日期"
-              :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-              class="!w-240px"
-              @change="handleDateChange"
-            />
-          </el-form-item>
-          <el-form-item>
-            <el-button type="primary" @click="handleQuery">
-              <Icon icon="ep:search" class="mr-5px" /> 搜索
-            </el-button>
-            <el-button @click="resetQuery" class="btn-default">
-              <Icon icon="ep:refresh" class="mr-5px" /> 重置
-            </el-button>
-          </el-form-item>
-        </el-form>
-      </el-card>
-    </el-col>
-  </el-row>
-
-  <!-- 第二行:统计卡片行 -->
-  <el-row :gutter="16" class="mb-6">
-    <el-col :span="6">
-      <el-card class="stat-card stat-card-neutral-1" shadow="hover" style="border: none">
-        <div class="stat-content">
-          <div class="stat-header">
-            <div class="stat-icon-wrapper">
-              <Icon icon="ep:calendar" class="stat-icon" />
-            </div>
-            <div class="stat-title">昨日工单数量</div>
-          </div>
-          <el-divider class="stat-divider" />
-          <div class="stat-body">
-            <div class="stat-item">
-              <div class="stat-sub-label">总数量</div>
-              <div class="stat-sub-value text-slate-700">{{ day.total }}</div>
-            </div>
-            <div class="stat-item">
-              <div class="stat-sub-label">未完成</div>
-              <div class="stat-sub-value text-amber-600">{{ day.todo }}</div>
-            </div>
-          </div>
-        </div>
-      </el-card>
-    </el-col>
-    <el-col :span="6">
-      <el-card class="stat-card stat-card-neutral-2" shadow="hover" style="border: none">
-        <div class="stat-content">
-          <div class="stat-header">
-            <div class="stat-icon-wrapper">
-              <Icon icon="ep:star" class="stat-icon" />
-            </div>
-            <div class="stat-title">近一周工单数量</div>
-          </div>
-          <el-divider class="stat-divider" />
-          <div class="stat-body">
-            <div class="stat-item">
-              <div class="stat-sub-label">总数量</div>
-              <div class="stat-sub-value text-slate-700">{{ week.total }}</div>
-            </div>
-            <div class="stat-item">
-              <div class="stat-sub-label">未完成</div>
-              <div class="stat-sub-value text-amber-600">{{ week.todo }}</div>
-            </div>
-          </div>
-        </div>
-      </el-card>
-    </el-col>
-    <el-col :span="6">
-      <el-card class="stat-card stat-card-neutral-3" shadow="hover" style="border: none">
-        <div class="stat-content">
-          <div class="stat-header">
-            <div class="stat-icon-wrapper">
-              <Icon icon="ep:trend-charts" class="stat-icon" />
-            </div>
-            <div class="stat-title">近一月工单数量</div>
-          </div>
-          <el-divider class="stat-divider" />
-          <div class="stat-body">
-            <div class="stat-item">
-              <div class="stat-sub-label">总数量</div>
-              <div class="stat-sub-value text-slate-700">{{ month.total }}</div>
-            </div>
-            <div class="stat-item">
-              <div class="stat-sub-label">未完成</div>
-              <div class="stat-sub-value text-amber-600">{{ month.todo }}</div>
-            </div>
-          </div>
-        </div>
-      </el-card>
-    </el-col>
-    <el-col :span="6">
-      <el-card class="stat-card stat-card-neutral-4" shadow="hover" style="border: none">
-        <div class="stat-content">
-          <div class="stat-header">
-            <div class="stat-icon-wrapper">
-              <Icon icon="ep:data-analysis" class="stat-icon" />
-            </div>
-            <div class="stat-title">工单总数量</div>
-          </div>
-          <el-divider class="stat-divider" />
-          <div class="stat-body">
-            <div class="stat-item">
-              <div class="stat-sub-label">总数量</div>
-              <div class="stat-sub-value text-slate-700">{{ total.total }}</div>
-            </div>
-            <div class="stat-item">
-              <div class="stat-sub-label">未完成</div>
-              <div class="stat-sub-value text-amber-600">{{ total.todo }}</div>
-            </div>
-          </div>
-        </div>
-      </el-card>
-    </el-col>
-  </el-row>
-
-  <!-- 第三行:图表行 -->
-  <el-row :gutter="16" class="mb-6">
-    <el-col :span="12">
-      <el-card class="chart-card-enhanced" shadow="hover" style="border: none">
-        <template #header>
-          <div class="chart-header">
-            <div class="chart-title-wrapper">
-              <div class="chart-title-dot chart-title-dot-blue"></div>
-              <span class="chart-title">工单状态统计</span>
-            </div>
-          </div>
-        </template>
-        <el-row class="chart-grid">
-          <el-col :span="8" class="chart-item">
-            <div ref="finishedChartRef" class="gauge-chart"></div>
-            <div class="chart-label clickable" @click="clickStatus('工单已执行')">
-              <span class="label-dot label-dot-blue"></span>
-              <span class="label-text">已执行</span>
-            </div>
-          </el-col>
-          <el-col :span="8" class="chart-item">
-            <div ref="writeChartRef" class="gauge-chart"></div>
-            <div class="chart-label clickable" @click="clickStatus('工单待执行')">
-              <span class="label-dot label-dot-amber"></span>
-              <span class="label-text">待执行</span>
-            </div>
-          </el-col>
-          <el-col :span="8" class="chart-item">
-            <div ref="ignoreChartRef" class="gauge-chart"></div>
-            <div class="chart-label clickable" @click="clickStatus('工单忽略')">
-              <span class="label-dot label-dot-gray"></span>
-              <span class="label-text">忽略</span>
-            </div>
-          </el-col>
-        </el-row>
-      </el-card>
-    </el-col>
-    <el-col :span="12">
-      <el-card class="chart-card-enhanced" shadow="hover" style="border: none">
-        <template #header>
-          <div class="chart-header">
-            <div class="chart-title-wrapper">
-              <div class="chart-title-dot chart-title-dot-green"></div>
-              <span class="chart-title">设备状态统计</span>
-            </div>
-          </div>
-        </template>
-        <el-row class="chart-grid">
-          <el-col :span="8" class="chart-item">
-            <div ref="finishedTodayChartRef" class="gauge-chart"></div>
-            <div class="chart-label clickable" @click="clickStatus('设备已执行')">
-              <span class="label-dot label-dot-blue"></span>
-              <span class="label-text">已填写</span>
-            </div>
-          </el-col>
-          <el-col :span="8" class="chart-item">
-            <div ref="writeTodayChartRef" class="gauge-chart"></div>
-            <div class="chart-label clickable" @click="clickStatus('设备待执行')">
-              <span class="label-dot label-dot-amber"></span>
-              <span class="label-text">未填写</span>
-            </div>
-          </el-col>
-          <el-col :span="8" class="chart-item">
-            <div ref="ignoreTodayChartRef" class="gauge-chart"></div>
-            <div class="chart-label clickable" @click="clickStatus('设备忽略')">
-              <span class="label-dot label-dot-gray"></span>
-              <span class="label-text">忽略</span>
-            </div>
-          </el-col>
-        </el-row>
-      </el-card>
-    </el-col>
-  </el-row>
-
-  <!-- 第四行:消息统计行 -->
-  <el-row :gutter="16" class="mb-6">
-    <el-col :span="8">
-      <el-card class="chart-card-enhanced" shadow="hover" style="border: none">
-        <template #header>
-          <div class="chart-header">
-            <div class="chart-title-wrapper">
-              <div class="chart-title-dot chart-title-dot-purple"></div>
-              <span class="chart-title">巡检项状态统计</span>
-            </div>
-          </div>
-        </template>
-        <div ref="statusChartRef" class="pie-chart-container"></div>
-      </el-card>
-    </el-col>
-    <el-col :span="16">
-      <el-card class="chart-card-enhanced" shadow="hover" style="border: none">
-        <template #header>
-          <div class="chart-header">
-            <div class="chart-title-wrapper">
-              <div class="chart-title-dot chart-title-dot-indigo"></div>
-              <span class="chart-title">近一年数量统计</span>
-            </div>
-          </div>
-        </template>
-        <div ref="chartContainer" class="bar-chart-container"></div>
-      </el-card>
-    </el-col>
-  </el-row>
-
-  <!-- TODO 第五行:地图 -->
+  <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 kb-screen" id="inspect-kb" :style="targetAreaStyle">
+        <header class="header">巡检统计</header>
+        <main class="inspect-kb-content">
+          <!-- 第一行:筛选条件 -->
+          <el-row :gutter="16" class="filter-row">
+            <el-col :span="24">
+              <el-card class="filter-card" shadow="hover" style="border: none">
+                <el-form
+                  class="-mb-15px"
+                  :model="queryParams"
+                  ref="queryFormRef"
+                  :inline="true"
+                  label-width="80px">
+                  <el-form-item label="所属部门" prop="project_name">
+                    <el-tree-select
+                      v-model="queryParams.deptId"
+                      :data="deptList"
+                      :props="defaultProps"
+                      check-strictly
+                      node-key="id"
+                      filterable
+                      placeholder="请选择所在部门"
+                      clearable
+                      style="width: 200px" />
+                  </el-form-item>
+                  <el-form-item label="创建时间" prop="createTime">
+                    <el-date-picker
+                      v-model="queryParams.createTime"
+                      value-format="YYYY-MM-DD HH:mm:ss"
+                      type="daterange"
+                      start-placeholder="开始日期"
+                      end-placeholder="结束日期"
+                      :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+                      class="!w-240px"
+                      @change="handleDateChange" />
+                  </el-form-item>
+                  <el-form-item>
+                    <el-button type="primary" @click="handleQuery">
+                      <Icon icon="ep:search" class="mr-5px" /> 搜索
+                    </el-button>
+                    <el-button @click="resetQuery" class="btn-default">
+                      <Icon icon="ep:refresh" class="mr-5px" /> 重置
+                    </el-button>
+                  </el-form-item>
+                </el-form>
+              </el-card>
+            </el-col>
+          </el-row>
+
+          <!-- 第二行:统计卡片行 -->
+          <el-row :gutter="16" class="summary-row">
+            <el-col :span="6">
+              <el-card class="stat-card stat-card-neutral-1" shadow="hover" style="border: none">
+                <div class="stat-content">
+                  <div class="stat-header">
+                    <div class="stat-icon-wrapper">
+                      <Icon icon="ep:calendar" class="stat-icon" />
+                    </div>
+                    <div class="stat-title">昨日工单数量</div>
+                  </div>
+                  <el-divider class="stat-divider" />
+                  <div class="stat-body">
+                    <div class="stat-item">
+                      <div class="stat-sub-label">总数量</div>
+                      <div class="stat-sub-value text-slate-700">{{ day.total }}</div>
+                    </div>
+                    <div class="stat-item">
+                      <div class="stat-sub-label">未完成</div>
+                      <div class="stat-sub-value text-amber-600">{{ day.todo }}</div>
+                    </div>
+                  </div>
+                </div>
+              </el-card>
+            </el-col>
+            <el-col :span="6">
+              <el-card class="stat-card stat-card-neutral-2" shadow="hover" style="border: none">
+                <div class="stat-content">
+                  <div class="stat-header">
+                    <div class="stat-icon-wrapper">
+                      <Icon icon="ep:star" class="stat-icon" />
+                    </div>
+                    <div class="stat-title">近一周工单数量</div>
+                  </div>
+                  <el-divider class="stat-divider" />
+                  <div class="stat-body">
+                    <div class="stat-item">
+                      <div class="stat-sub-label">总数量</div>
+                      <div class="stat-sub-value text-slate-700">{{ week.total }}</div>
+                    </div>
+                    <div class="stat-item">
+                      <div class="stat-sub-label">未完成</div>
+                      <div class="stat-sub-value text-amber-600">{{ week.todo }}</div>
+                    </div>
+                  </div>
+                </div>
+              </el-card>
+            </el-col>
+            <el-col :span="6">
+              <el-card class="stat-card stat-card-neutral-3" shadow="hover" style="border: none">
+                <div class="stat-content">
+                  <div class="stat-header">
+                    <div class="stat-icon-wrapper">
+                      <Icon icon="ep:trend-charts" class="stat-icon" />
+                    </div>
+                    <div class="stat-title">近一月工单数量</div>
+                  </div>
+                  <el-divider class="stat-divider" />
+                  <div class="stat-body">
+                    <div class="stat-item">
+                      <div class="stat-sub-label">总数量</div>
+                      <div class="stat-sub-value text-slate-700">{{ month.total }}</div>
+                    </div>
+                    <div class="stat-item">
+                      <div class="stat-sub-label">未完成</div>
+                      <div class="stat-sub-value text-amber-600">{{ month.todo }}</div>
+                    </div>
+                  </div>
+                </div>
+              </el-card>
+            </el-col>
+            <el-col :span="6">
+              <el-card class="stat-card stat-card-neutral-4" shadow="hover" style="border: none">
+                <div class="stat-content">
+                  <div class="stat-header">
+                    <div class="stat-icon-wrapper">
+                      <Icon icon="ep:data-analysis" class="stat-icon" />
+                    </div>
+                    <div class="stat-title">工单总数量</div>
+                  </div>
+                  <el-divider class="stat-divider" />
+                  <div class="stat-body">
+                    <div class="stat-item">
+                      <div class="stat-sub-label">总数量</div>
+                      <div class="stat-sub-value text-slate-700">{{ total.total }}</div>
+                    </div>
+                    <div class="stat-item">
+                      <div class="stat-sub-label">未完成</div>
+                      <div class="stat-sub-value text-amber-600">{{ total.todo }}</div>
+                    </div>
+                  </div>
+                </div>
+              </el-card>
+            </el-col>
+          </el-row>
+
+          <!-- 第三行:图表行 -->
+          <el-row :gutter="16" class="status-row">
+            <el-col :span="12">
+              <el-card class="chart-card-enhanced" shadow="hover" style="border: none">
+                <template #header>
+                  <div class="chart-header">
+                    <div class="chart-title-wrapper">
+                      <div class="chart-title-dot chart-title-dot-blue"></div>
+                      <span class="chart-title">工单状态统计</span>
+                    </div>
+                  </div>
+                </template>
+                <el-row class="chart-grid">
+                  <el-col :span="8" class="chart-item">
+                    <div ref="finishedChartRef" class="gauge-chart"></div>
+                    <div class="chart-label clickable" @click="clickStatus('工单已执行')">
+                      <span class="label-dot label-dot-blue"></span>
+                      <span class="label-text">已执行</span>
+                    </div>
+                  </el-col>
+                  <el-col :span="8" class="chart-item">
+                    <div ref="writeChartRef" class="gauge-chart"></div>
+                    <div class="chart-label clickable" @click="clickStatus('工单待执行')">
+                      <span class="label-dot label-dot-amber"></span>
+                      <span class="label-text">待执行</span>
+                    </div>
+                  </el-col>
+                  <el-col :span="8" class="chart-item">
+                    <div ref="ignoreChartRef" class="gauge-chart"></div>
+                    <div class="chart-label clickable" @click="clickStatus('工单忽略')">
+                      <span class="label-dot label-dot-gray"></span>
+                      <span class="label-text">忽略</span>
+                    </div>
+                  </el-col>
+                </el-row>
+              </el-card>
+            </el-col>
+            <el-col :span="12">
+              <el-card class="chart-card-enhanced" shadow="hover" style="border: none">
+                <template #header>
+                  <div class="chart-header">
+                    <div class="chart-title-wrapper">
+                      <div class="chart-title-dot chart-title-dot-green"></div>
+                      <span class="chart-title">设备状态统计</span>
+                    </div>
+                  </div>
+                </template>
+                <el-row class="chart-grid">
+                  <el-col :span="8" class="chart-item">
+                    <div ref="finishedTodayChartRef" class="gauge-chart"></div>
+                    <div class="chart-label clickable" @click="clickStatus('设备已执行')">
+                      <span class="label-dot label-dot-blue"></span>
+                      <span class="label-text">已填写</span>
+                    </div>
+                  </el-col>
+                  <el-col :span="8" class="chart-item">
+                    <div ref="writeTodayChartRef" class="gauge-chart"></div>
+                    <div class="chart-label clickable" @click="clickStatus('设备待执行')">
+                      <span class="label-dot label-dot-amber"></span>
+                      <span class="label-text">未填写</span>
+                    </div>
+                  </el-col>
+                  <el-col :span="8" class="chart-item">
+                    <div ref="ignoreTodayChartRef" class="gauge-chart"></div>
+                    <div class="chart-label clickable" @click="clickStatus('设备忽略')">
+                      <span class="label-dot label-dot-gray"></span>
+                      <span class="label-text">忽略</span>
+                    </div>
+                  </el-col>
+                </el-row>
+              </el-card>
+            </el-col>
+          </el-row>
+
+          <!-- 第四行:消息统计行 -->
+          <el-row :gutter="16" class="trend-row">
+            <el-col :span="8">
+              <el-card class="chart-card-enhanced" shadow="hover" style="border: none">
+                <template #header>
+                  <div class="chart-header">
+                    <div class="chart-title-wrapper">
+                      <div class="chart-title-dot chart-title-dot-purple"></div>
+                      <span class="chart-title">巡检项状态统计</span>
+                    </div>
+                  </div>
+                </template>
+                <div ref="statusChartRef" class="pie-chart-container"></div>
+              </el-card>
+            </el-col>
+            <el-col :span="16">
+              <el-card class="chart-card-enhanced" shadow="hover" style="border: none">
+                <template #header>
+                  <div class="chart-header">
+                    <div class="chart-title-wrapper">
+                      <div class="chart-title-dot chart-title-dot-indigo"></div>
+                      <span class="chart-title">近一年数量统计</span>
+                    </div>
+                  </div>
+                </template>
+                <div ref="chartContainer" class="bar-chart-container"></div>
+              </el-card>
+            </el-col>
+          </el-row>
+
+          <!-- TODO 第五行:地图 -->
+        </main>
+      </div>
+    </div>
+  </div>
 </template>
 
 <script setup lang="ts" name="Index">
@@ -277,12 +283,56 @@ import { ref } from 'vue'
 import { DeptTreeItem } from '@/api/system/dept'
 import * as DeptApi from '@/api/system/dept'
 import { useUserStore } from '@/store/modules/user'
+import { DESIGN_HEIGHT, DESIGN_WIDTH } from '@/utils/kb'
 
 // TODO @super:参考下 /Users/yunai/Java/yudao-ui-admin-vue3/src/views/mall/home/index.vue,拆一拆组件
 
 /** IoT 首页 */
 defineOptions({ name: 'IotInspectStat' })
 
+const wrapperRef = ref<HTMLDivElement>()
+const scale = ref(1)
+const chartInstances = new Set<ReturnType<typeof echarts.init>>()
+
+let resizeObserver: ResizeObserver | null = null
+let resizeRaf = 0
+
+const targetWrapperStyle = computed(() => ({
+  width: `${DESIGN_WIDTH * scale.value}px`,
+  height: `${DESIGN_HEIGHT * scale.value}px`
+}))
+
+const targetAreaStyle = computed(() => ({
+  '--kb-scale': scale.value,
+  width: `${DESIGN_WIDTH * scale.value}px`,
+  height: `${DESIGN_HEIGHT * scale.value}px`
+}))
+
+const getChartInstance = (el: HTMLElement | null) => {
+  if (!el) return null
+  const chart = echarts.getInstanceByDom(el) || echarts.init(el)
+  chartInstances.add(chart)
+  return chart
+}
+
+const resizeCharts = () => {
+  chartInstances.forEach((chart) => chart.resize())
+}
+
+const 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)
+  })
+}
+
 // TODO @super:使用下 Echart 组件,参考 yudao-ui-admin-vue3/src/views/mall/home/components/TradeTrendCard.vue 等
 echarts.use([
   TooltipComponent,
@@ -505,7 +555,8 @@ const getStats = async () => {
 
 /** 初始化图表 */
 const initCharts = () => {
-  const chart = echarts.init(statusChartRef.value)
+  const chart = getChartInstance(statusChartRef.value)
+  if (!chart) return
   chart.setOption({
     tooltip: {
       trigger: 'item'
@@ -543,6 +594,7 @@ const initCharts = () => {
       }
     ]
   })
+  chart.off('click')
   chart.on('click', (params) => {
     console.log('点击的数据值为:', params.value)
     console.log('点击的数据类型为:', params.data.type)
@@ -601,7 +653,8 @@ const initCharts = () => {
 
 /** 初始化仪表盘图表 */
 const initGaugeChart = (el: any, value: number, color: string, type: string) => {
-  const chart = echarts.init(el)
+  const chart = getChartInstance(el)
+  if (!chart) return
   chart.setOption({
     series: [
       {
@@ -646,6 +699,7 @@ const initGaugeChart = (el: any, value: number, color: string, type: string) =>
   })
 
   // 添加点击事件监听器
+  chart.off('click')
   chart.on('click', (params) => {
     console.log('点击的数据值为:', params.value)
     console.log('点击的数据类型为:', params.data.type)
@@ -701,7 +755,7 @@ const initMessageChart = () => {
   })
 
   // 配置图表
-  echarts.init(deviceMessageCountChartRef.value).setOption({
+  getChartInstance(deviceMessageCountChartRef.value)?.setOption({
     tooltip: {
       trigger: 'axis',
       backgroundColor: 'rgba(255, 255, 255, 0.9)',
@@ -794,7 +848,7 @@ const initMessageChart = () => {
 }
 
 const chartContainer = ref(null)
-let chartInstance = null
+let chartInstance: ReturnType<typeof echarts.init> | null = null
 
 // 生成过去12个月份的标签 (格式: YYYY-MM)
 const generateMonthLabels = () => {
@@ -885,11 +939,9 @@ const initChart = async () => {
   }
 
   // 初始化图表
-  chartInstance = echarts.init(chartContainer.value)
+  chartInstance = getChartInstance(chartContainer.value)
+  if (!chartInstance) return
   chartInstance.setOption(option)
-
-  // 窗口缩放监听
-  window.addEventListener('resize', handleResize)
   handleResize()
 }
 
@@ -899,28 +951,62 @@ const handleResize = () => {
 }
 /** 初始化 */
 onMounted(async () => {
+  nextTick(updateScale)
+  resizeObserver = new ResizeObserver(updateScale)
+  if (wrapperRef.value) {
+    resizeObserver.observe(wrapperRef.value)
+  }
+  window.addEventListener('resize', updateScale)
+
   deptList.value = handleTree(await DeptApi.getSimpleDeptList())
   queryParams.deptId = useUserStore().getUser.deptId
   await getStats()
+  nextTick(resizeCharts)
   // await initChart()
   // await initCharts()
 })
 onUnmounted(() => {
-  chartInstance?.dispose()
-  window.removeEventListener('resize', handleResize)
+  resizeObserver?.disconnect()
+  window.removeEventListener('resize', updateScale)
+  cancelAnimationFrame(resizeRaf)
+  chartInstances.forEach((chart) => chart.dispose())
+  chartInstances.clear()
 })
 </script>
 
 <style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+
+// 响应式优化
+@media (width <= 1200px) {
+  .stat-sub-value {
+    font-size: 24px;
+  }
+}
+
+@media (width <= 768px) {
+  .stat-card {
+    margin-bottom: 16px;
+  }
+
+  .chart-item {
+    padding: 8px;
+  }
+
+  .gauge-chart {
+    height: 140px;
+  }
+}
+
 // 筛选卡片样式
 .filter-card {
-  border-radius: 12px;
+  background: linear-gradient(to bottom, #fafafa, #fff);
   border: 1px solid #e5e7eb;
-  background: linear-gradient(to bottom, #fafafa, #ffffff);
+  border-radius: 12px;
   transition: all 0.3s ease;
 
   &:hover {
-    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
+    box-shadow: 0 4px 12px rgb(0 0 0 / 5%);
   }
 
   :deep(.el-card__body) {
@@ -928,8 +1014,8 @@ onUnmounted(() => {
   }
 
   :deep(.el-form-item__label) {
-    color: #6b7280;
     font-weight: 500;
+    color: #6b7280;
   }
 }
 
@@ -941,35 +1027,35 @@ onUnmounted(() => {
   &:hover {
     background: linear-gradient(135deg, #475569 0%, #334155 100%);
     transform: translateY(-1px);
-    box-shadow: 0 4px 8px rgba(100, 116, 139, 0.3);
+    box-shadow: 0 4px 8px rgb(100 116 139 / 30%);
   }
 }
 
 .btn-default {
-  border-color: #d1d5db;
   color: #6b7280;
+  border-color: #d1d5db;
   transition: all 0.3s ease;
 
   &:hover {
-    border-color: #9ca3af;
     color: #4b5563;
     background: #f9fafb;
+    border-color: #9ca3af;
   }
 }
 
 // 统计卡片基础样式
 .stat-card {
-  border-radius: 12px;
+  position: relative;
+  overflow: hidden;
+  background: #fff;
   border: 1px solid #e5e7eb;
+  border-radius: 12px;
   transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
-  overflow: hidden;
-  position: relative;
-  background: #ffffff;
 
   &:hover {
-    transform: translateY(-4px);
-    box-shadow: 0 12px 24px -8px rgba(0, 0, 0, 0.1);
     border-color: #d1d5db;
+    transform: translateY(-4px);
+    box-shadow: 0 12px 24px -8px rgb(0 0 0 / 10%);
   }
 
   :deep(.el-card__body) {
@@ -1007,14 +1093,14 @@ onUnmounted(() => {
 }
 
 .stat-icon-wrapper {
+  display: flex;
   width: 48px;
   height: 48px;
   background: #0090ff;
   border-radius: 12px;
-  display: flex;
+  box-shadow: 0 4px 8px rgb(100 116 139 / 20%);
   align-items: center;
   justify-content: center;
-  box-shadow: 0 4px 8px rgba(100, 116, 139, 0.2);
 }
 
 .stat-icon {
@@ -1029,8 +1115,8 @@ onUnmounted(() => {
 }
 
 .stat-divider {
-  border-color: #e2e8f0;
   margin: 12px 0;
+  border-color: #e2e8f0;
 }
 
 .stat-body {
@@ -1044,19 +1130,19 @@ onUnmounted(() => {
   justify-content: space-between;
   align-items: center;
   padding: 8px 12px;
-  background: rgba(255, 255, 255, 0.6);
+  background: rgb(255 255 255 / 60%);
   border-radius: 8px;
   transition: all 0.2s ease;
 
   &:hover {
-    background: rgba(255, 255, 255, 0.9);
+    background: rgb(255 255 255 / 90%);
   }
 }
 
 .stat-sub-label {
   font-size: 13px;
-  color: #64748b;
   font-weight: 500;
+  color: #64748b;
 }
 
 .stat-sub-value {
@@ -1067,20 +1153,20 @@ onUnmounted(() => {
 
 // 图表卡片增强样式
 .chart-card-enhanced {
-  border-radius: 12px;
+  background: #fff;
   border: 1px solid #e5e7eb;
+  border-radius: 12px;
   transition: all 0.3s ease;
-  background: #ffffff;
 
   &:hover {
-    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
     border-color: #d1d5db;
+    box-shadow: 0 8px 24px rgb(0 0 0 / 8%);
   }
 
   :deep(.el-card__header) {
     padding: 16px 20px;
+    background: linear-gradient(to right, #fafafa, #fff);
     border-bottom: 1px solid #f1f5f9;
-    background: linear-gradient(to right, #fafafa, #ffffff);
   }
 
   :deep(.el-card__body) {
@@ -1104,30 +1190,30 @@ onUnmounted(() => {
   width: 8px;
   height: 8px;
   border-radius: 50%;
-  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+  box-shadow: 0 2px 4px rgb(0 0 0 / 10%);
 }
 
 .chart-title-dot-blue {
-  background: #0055bb;
+  background: #05b;
 }
 
 .chart-title-dot-green {
-  background: #0055bb;
+  background: #05b;
 }
 
 .chart-title-dot-purple {
-  background: #0055bb;
+  background: #05b;
 }
 
 .chart-title-dot-indigo {
-  background: #0055bb;
+  background: #05b;
 }
 
 .chart-title {
   font-size: 16px;
   font-weight: 600;
-  color: #334155;
   letter-spacing: 0.3px;
+  color: #334155;
 }
 
 .chart-grid {
@@ -1154,13 +1240,13 @@ onUnmounted(() => {
 
 .chart-label {
   display: flex;
-  align-items: center;
-  gap: 6px;
-  margin-top: 8px;
   padding: 6px 12px;
+  margin-top: 8px;
   background: #f1f5f9;
   border-radius: 20px;
   transition: all 0.2s ease;
+  align-items: center;
+  gap: 6px;
 
   .chart-item:hover & {
     background: #e2e8f0;
@@ -1177,61 +1263,323 @@ onUnmounted(() => {
 }
 
 .label-dot {
+  display: inline-block;
   width: 8px;
   height: 8px;
   border-radius: 50%;
-  display: inline-block;
 }
 
 .label-dot-blue {
   background: #64748b;
-  box-shadow: 0 0 0 2px rgba(100, 116, 139, 0.2);
+  box-shadow: 0 0 0 2px rgb(100 116 139 / 20%);
 }
 
 .label-dot-amber {
   background: #d97706;
-  box-shadow: 0 0 0 2px rgba(217, 119, 6, 0.2);
+  box-shadow: 0 0 0 2px rgb(217 119 6 / 20%);
 }
 
 .label-dot-gray {
   background: #78716c;
-  box-shadow: 0 0 0 2px rgba(120, 113, 108, 0.2);
+  box-shadow: 0 0 0 2px rgb(120 113 108 / 20%);
 }
 
 .label-text {
   font-size: 13px;
-  color: #64748b;
   font-weight: 500;
+  color: #64748b;
 }
 
 .pie-chart-container {
-  height: 300px;
   width: 100%;
+  height: 300px;
 }
 
 .bar-chart-container {
+  width: 100%;
   height: 300px;
+}
+
+.kb-screen {
+  overflow: hidden;
+}
+
+.inspect-kb-content {
+  display: grid;
+  height: calc(100% - 52px * var(--kb-scale));
+  padding: calc(16px * var(--kb-scale)) calc(20px * var(--kb-scale)) calc(20px * var(--kb-scale));
+  gap: calc(12px * var(--kb-scale));
+  grid-template-rows:
+    calc(74px * var(--kb-scale)) calc(174px * var(--kb-scale))
+    calc(300px * var(--kb-scale)) minmax(0, 1fr);
+}
+
+.inspect-kb-content > .el-row {
+  height: 100%;
+  margin-bottom: 0 !important;
+}
+
+.inspect-kb-content > .el-row > .el-col {
+  height: 100%;
+}
+
+.filter-card,
+.stat-card,
+.chart-card-enhanced {
+  height: 100%;
+  background: linear-gradient(180deg, rgb(235 243 255 / 76%) 0%, rgb(221 233 251 / 58%) 100%);
+  border: 1px solid rgb(255 255 255 / 68%) !important;
+  border-radius: calc(8px * var(--kb-scale));
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 78%),
+    0 calc(10px * var(--kb-scale)) calc(24px * var(--kb-scale)) rgb(46 90 164 / 10%);
+  backdrop-filter: blur(12px);
+}
+
+.filter-card:hover,
+.stat-card:hover,
+.chart-card-enhanced:hover {
+  border-color: rgb(255 255 255 / 84%) !important;
+  transform: none;
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 86%),
+    0 calc(12px * var(--kb-scale)) calc(26px * var(--kb-scale)) rgb(46 90 164 / 14%);
+}
+
+.filter-card :deep(.el-card__body) {
+  display: flex;
+  height: 100%;
+  padding: calc(12px * var(--kb-scale)) calc(18px * var(--kb-scale));
+  align-items: center;
+}
+
+.filter-card :deep(.el-form) {
+  display: flex;
   width: 100%;
+  margin-bottom: 0 !important;
+  align-items: center;
+  gap: calc(16px * var(--kb-scale));
 }
 
-// 响应式优化
-@media (max-width: 1200px) {
-  .stat-sub-value {
-    font-size: 24px;
-  }
+.filter-card :deep(.el-form-item) {
+  margin-right: 0;
+  margin-bottom: 0;
 }
 
-@media (max-width: 768px) {
-  .stat-card {
-    margin-bottom: 16px;
-  }
+.filter-card :deep(.el-form-item__label) {
+  height: calc(32px * var(--kb-scale));
+  padding-right: calc(10px * var(--kb-scale));
+  font-size: calc(14px * var(--kb-scale));
+  line-height: calc(32px * var(--kb-scale));
+  color: #31527e;
+}
 
-  .chart-item {
-    padding: 8px;
-  }
+.filter-card :deep(.el-tree-select) {
+  width: calc(220px * var(--kb-scale)) !important;
+}
 
-  .gauge-chart {
-    height: 140px;
-  }
+.filter-card :deep(.el-date-editor) {
+  width: calc(320px * var(--kb-scale)) !important;
+}
+
+.filter-card :deep(.el-input__wrapper),
+.filter-card :deep(.el-select__wrapper) {
+  min-height: calc(32px * var(--kb-scale));
+  padding: 0 calc(10px * var(--kb-scale));
+  background: rgb(255 255 255 / 72%);
+}
+
+.filter-card :deep(.el-range-input),
+.filter-card :deep(.el-range-separator),
+.filter-card :deep(.el-input__inner) {
+  font-size: calc(13px * var(--kb-scale));
+}
+
+.filter-card :deep(.el-button) {
+  height: calc(32px * var(--kb-scale));
+  padding: 0 calc(14px * var(--kb-scale));
+  margin-left: calc(8px * var(--kb-scale));
+  font-size: calc(13px * var(--kb-scale));
+}
+
+.stat-card {
+  overflow: hidden;
+}
+
+.stat-card :deep(.el-card__body) {
+  height: 100%;
+  padding: 0;
+}
+
+.stat-card-neutral-1,
+.stat-card-neutral-2,
+.stat-card-neutral-3,
+.stat-card-neutral-4 {
+  background: linear-gradient(180deg, rgb(255 255 255 / 58%) 0%, rgb(213 227 249 / 42%) 100%);
+}
+
+.stat-content {
+  display: flex;
+  height: 100%;
+  padding: calc(16px * var(--kb-scale));
+  flex-direction: column;
+}
+
+.stat-header {
+  margin-bottom: calc(8px * var(--kb-scale));
+  gap: calc(10px * var(--kb-scale));
+}
+
+.stat-icon-wrapper {
+  width: calc(38px * var(--kb-scale));
+  height: calc(38px * var(--kb-scale));
+  background: linear-gradient(180deg, rgb(255 255 255 / 96%) 0%, rgb(235 244 255 / 92%) 100%);
+  border: 1px solid rgb(255 255 255 / 88%);
+  border-radius: calc(8px * var(--kb-scale));
+  box-shadow: 0 calc(5px * var(--kb-scale)) calc(10px * var(--kb-scale)) rgb(57 111 194 / 12%);
+}
+
+.stat-icon {
+  font-size: calc(19px * var(--kb-scale));
+  color: #2474dd;
+}
+
+.stat-title {
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: calc(20px * var(--kb-scale));
+  font-weight: normal;
+  letter-spacing: 0;
+  color: #03409b;
+}
+
+.stat-divider {
+  margin: 0 0 calc(8px * var(--kb-scale));
+  border-color: rgb(3 64 155 / 12%);
+}
+
+.stat-body {
+  display: grid;
+  min-height: 0;
+  flex: 1;
+  gap: calc(10px * var(--kb-scale));
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+}
+
+.stat-item {
+  padding: calc(8px * var(--kb-scale)) calc(12px * var(--kb-scale));
+  background: rgb(255 255 255 / 38%);
+  border: 1px solid rgb(255 255 255 / 46%);
+  border-radius: calc(6px * var(--kb-scale));
+}
+
+.stat-sub-label {
+  font-size: calc(13px * var(--kb-scale));
+  color: #55708f;
+}
+
+.stat-sub-value {
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: calc(28px * var(--kb-scale));
+  font-weight: normal;
+  color: #1762c8;
+}
+
+.stat-sub-value.text-amber-600 {
+  color: #e87b1e;
+}
+
+.chart-card-enhanced {
+  display: flex;
+  overflow: hidden;
+  flex-direction: column;
+}
+
+.chart-card-enhanced :deep(.el-card__header) {
+  height: calc(42px * var(--kb-scale));
+  padding: 0 calc(16px * var(--kb-scale)) 0 calc(36px * var(--kb-scale));
+  background: transparent;
+  border-bottom: 1px solid rgb(3 64 155 / 12%);
+}
+
+.chart-card-enhanced :deep(.el-card__body) {
+  min-height: 0;
+  padding: calc(8px * var(--kb-scale)) calc(12px * var(--kb-scale));
+  flex: 1;
+}
+
+.chart-header,
+.chart-title-wrapper {
+  height: 100%;
+}
+
+.chart-title-wrapper {
+  position: relative;
+}
+
+.chart-title-dot {
+  position: absolute;
+  left: calc(-22px * var(--kb-scale));
+  width: calc(4px * var(--kb-scale));
+  height: calc(18px * var(--kb-scale));
+  background: #03409b;
+  border-radius: calc(2px * var(--kb-scale));
+  box-shadow: none;
+}
+
+.chart-title {
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: calc(22px * var(--kb-scale));
+  font-weight: normal;
+  letter-spacing: 0;
+  color: #03409b;
+}
+
+.chart-grid {
+  height: 100%;
+  min-height: 0;
+}
+
+.chart-item {
+  height: 100%;
+  padding: calc(4px * var(--kb-scale));
+  justify-content: center;
+}
+
+.chart-item:hover {
+  background: transparent;
+}
+
+.gauge-chart {
+  height: calc(190px * var(--kb-scale));
+}
+
+.chart-label {
+  min-height: calc(28px * var(--kb-scale));
+  padding: calc(4px * var(--kb-scale)) calc(10px * var(--kb-scale));
+  margin-top: 0;
+  background: rgb(255 255 255 / 52%);
+  border-radius: calc(5px * var(--kb-scale));
+  gap: calc(6px * var(--kb-scale));
+}
+
+.chart-label.clickable:hover {
+  background: rgb(255 255 255 / 78%);
+  transform: none;
+}
+
+.label-dot {
+  width: calc(7px * var(--kb-scale));
+  height: calc(7px * var(--kb-scale));
+}
+
+.label-text {
+  font-size: calc(13px * var(--kb-scale));
+  color: #466484;
+}
+
+.pie-chart-container,
+.bar-chart-container {
+  width: 100%;
+  height: 100%;
 }
 </style>