فهرست منبع

style: 统一运行统计页面看板缩放策略

Zimo 1 هفته پیش
والد
کامیت
7b968bcbb6
1فایلهای تغییر یافته به همراه256 افزوده شده و 226 حذف شده
  1. 256 226
      src/views/pms/iotopeationfill/statistics.vue

+ 256 - 226
src/views/pms/iotopeationfill/statistics.vue

@@ -1,154 +1,158 @@
 <template>
-  <div class="statistics-page bg">
-    <header class="header">运行记录填报统计</header>
-
-    <main class="statistics-content">
-      <el-row :gutter="16" class="section-row">
-        <el-col :span="24">
-          <el-card class="chart-card filter-card panel" shadow="never">
-            <div class="panel-title panel-title--md">
-              <div class="icon-decorator">
-                <span></span>
-                <span></span>
-              </div>
-              筛选条件
-            </div>
-            <el-form
-              class="filter-form"
-              :model="queryParams"
-              ref="queryFormRef"
-              :inline="true"
-              label-width="68px">
-              <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: 180px" />
-              </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"
-                  :shortcuts="rangeShortcuts"
-                  start-placeholder="开始日期"
-                  end-placeholder="结束日期"
-                  :default-time="datePickerDefaultTime"
-                  class="!w-220px" />
-              </el-form-item>
-              <el-form-item class="filter-actions">
-                <el-button class="action-btn" size="default" @click="handleQuery">
-                  <Icon icon="ep:search" class="mr-5px" /> 搜索
-                </el-button>
-                <el-button class="action-btn" size="default" @click="resetQuery">
-                  <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="section-row">
-        <el-col :span="12">
-          <el-card class="chart-card panel" shadow="never">
-            <div class="panel-title">
-              <div class="icon-decorator">
-                <span></span>
-                <span></span>
-              </div>
-              运行记录工单统计
-            </div>
-            <div class="metric-grid metric-grid--order">
-              <button
-                v-for="item in orderMetricCards"
-                :key="item.key"
-                type="button"
-                class="metric-card"
-                :style="{
-                  '--metric-accent': item.accent,
-                  '--metric-glow': item.glow,
-                  '--metric-progress': `${item.percent}%`
-                }"
-                @click="openFill(queryParams.deptId, item.status, queryParams.createTime)">
-                <span class="metric-card__label">{{ item.label }}</span>
-                <strong class="metric-card__value">
-                  <count-to
-                    class="metric-card__count"
-                    :start-val="0"
-                    :end-val="item.value"
-                    :duration="800"
-                    separator="" />
-                </strong>
-                <span class="metric-card__hint">{{ item.hint }}</span>
-                <span class="metric-card__progress">
-                  <span></span>
-                </span>
-              </button>
-            </div>
-          </el-card>
-        </el-col>
-        <el-col :span="12">
-          <el-card class="chart-card panel" shadow="never">
-            <div class="panel-title">
-              <div class="icon-decorator">
-                <span></span>
-                <span></span>
-              </div>
-              运行记录设备统计
-            </div>
-            <div class="metric-grid metric-grid--device">
-              <button
-                v-for="item in deviceMetricCards"
-                :key="item.key"
-                type="button"
-                class="metric-card"
-                :style="{
-                  '--metric-accent': item.accent,
-                  '--metric-glow': item.glow,
-                  '--metric-progress': `${item.percent}%`
-                }"
-                @click="openForm(queryParams.deptId, item.status, queryParams.createTime)">
-                <span class="metric-card__label">{{ item.label }}</span>
-                <strong class="metric-card__value">
-                  <count-to
-                    class="metric-card__count"
-                    :start-val="0"
-                    :end-val="item.value"
-                    :duration="800"
-                    separator="" />
-                </strong>
-                <span class="metric-card__hint">{{ item.hint }}</span>
-                <span class="metric-card__progress">
-                  <span></span>
-                </span>
-              </button>
-            </div>
-          </el-card>
-        </el-col>
-      </el-row>
-
-      <el-row class="section-row trend-row">
-        <el-col :span="24">
-          <el-card class="chart-card trend-card panel" shadow="never">
-            <div class="panel-title">
-              <div class="icon-decorator">
-                <span></span>
-                <span></span>
-              </div>
-              部门运行记录工单统计
-            </div>
-            <div ref="chartContainer" class="trend-chart"></div>
-          </el-card>
-        </el-col>
-      </el-row>
-    </main>
+  <div ref="wrapperRef" class="statistics-scale-wrapper bg absolute top-0 left-0 size-full z-10">
+    <div class="mx-a overflow-hidden" :style="targetWrapperStyle">
+      <div class="statistics-page bg" :style="targetAreaStyle">
+        <header class="header">运行记录填报统计</header>
+
+        <main class="statistics-content">
+          <el-row :gutter="16" class="section-row">
+            <el-col :span="24">
+              <el-card class="chart-card filter-card panel" shadow="never">
+                <div class="panel-title panel-title--md">
+                  <div class="icon-decorator">
+                    <span></span>
+                    <span></span>
+                  </div>
+                  筛选条件
+                </div>
+                <el-form
+                  class="filter-form"
+                  :model="queryParams"
+                  ref="queryFormRef"
+                  :inline="true"
+                  label-width="86px">
+                  <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
+                      class="filter-control" />
+                  </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"
+                      :shortcuts="rangeShortcuts"
+                      start-placeholder="开始日期"
+                      end-placeholder="结束日期"
+                      :default-time="datePickerDefaultTime"
+                      class="filter-control filter-control--date" />
+                  </el-form-item>
+                  <el-form-item class="filter-actions">
+                    <el-button class="action-btn" size="default" @click="handleQuery">
+                      <Icon icon="ep:search" class="mr-5px" /> 搜索
+                    </el-button>
+                    <el-button class="action-btn" size="default" @click="resetQuery">
+                      <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="section-row">
+            <el-col :span="12">
+              <el-card class="chart-card panel" shadow="never">
+                <div class="panel-title">
+                  <div class="icon-decorator">
+                    <span></span>
+                    <span></span>
+                  </div>
+                  运行记录工单统计
+                </div>
+                <div class="metric-grid metric-grid--order">
+                  <button
+                    v-for="item in orderMetricCards"
+                    :key="item.key"
+                    type="button"
+                    class="metric-card"
+                    :style="{
+                      '--metric-accent': item.accent,
+                      '--metric-glow': item.glow,
+                      '--metric-progress': `${item.percent}%`
+                    }"
+                    @click="openFill(queryParams.deptId, item.status, queryParams.createTime)">
+                    <span class="metric-card__label">{{ item.label }}</span>
+                    <strong class="metric-card__value">
+                      <count-to
+                        class="metric-card__count"
+                        :start-val="0"
+                        :end-val="item.value"
+                        :duration="800"
+                        separator="" />
+                    </strong>
+                    <span class="metric-card__hint">{{ item.hint }}</span>
+                    <span class="metric-card__progress">
+                      <span></span>
+                    </span>
+                  </button>
+                </div>
+              </el-card>
+            </el-col>
+            <el-col :span="12">
+              <el-card class="chart-card panel" shadow="never">
+                <div class="panel-title">
+                  <div class="icon-decorator">
+                    <span></span>
+                    <span></span>
+                  </div>
+                  运行记录设备统计
+                </div>
+                <div class="metric-grid metric-grid--device">
+                  <button
+                    v-for="item in deviceMetricCards"
+                    :key="item.key"
+                    type="button"
+                    class="metric-card"
+                    :style="{
+                      '--metric-accent': item.accent,
+                      '--metric-glow': item.glow,
+                      '--metric-progress': `${item.percent}%`
+                    }"
+                    @click="openForm(queryParams.deptId, item.status, queryParams.createTime)">
+                    <span class="metric-card__label">{{ item.label }}</span>
+                    <strong class="metric-card__value">
+                      <count-to
+                        class="metric-card__count"
+                        :start-val="0"
+                        :end-val="item.value"
+                        :duration="800"
+                        separator="" />
+                    </strong>
+                    <span class="metric-card__hint">{{ item.hint }}</span>
+                    <span class="metric-card__progress">
+                      <span></span>
+                    </span>
+                  </button>
+                </div>
+              </el-card>
+            </el-col>
+          </el-row>
+
+          <el-row class="section-row trend-row">
+            <el-col :span="24">
+              <el-card class="chart-card trend-card panel" shadow="never">
+                <div class="panel-title">
+                  <div class="icon-decorator">
+                    <span></span>
+                    <span></span>
+                  </div>
+                  部门运行记录工单统计
+                </div>
+                <div ref="chartContainer" class="trend-chart"></div>
+              </el-card>
+            </el-col>
+          </el-row>
+        </main>
+      </div>
+    </div>
   </div>
 </template>
 
@@ -169,6 +173,7 @@ import { CanvasRenderer } from 'echarts/renderers'
 import { IotStatApi } from '@/api/pms/stat'
 import CountTo from '@/components/count-to1.vue'
 import { computed, ref, reactive, onMounted, onUnmounted } from 'vue'
+import { DESIGN_HEIGHT, DESIGN_WIDTH } from '@/utils/kb'
 import { defaultProps, handleTree } from '@/utils/tree'
 import * as DeptApi from '@/api/system/dept'
 import type { DeptVO } from '@/api/system/dept'
@@ -192,6 +197,37 @@ const datePickerDefaultTime: [Date, Date] = [
   dayjs().startOf('day').toDate(),
   dayjs().endOf('day').toDate()
 ]
+const wrapperRef = ref<HTMLDivElement>()
+const scale = ref(1)
+
+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`
+}))
+
+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(handleResize)
+  })
+}
 
 const getDefaultTimeRange = () => {
   const end = dayjs()
@@ -659,7 +695,12 @@ onMounted(async () => {
   queryParams.deptId = useUserStore().getUser.deptId ?? null
 
   Object.assign(initialQueryParams, queryParams)
-  window.addEventListener('resize', handleResize)
+  nextTick(updateScale)
+  resizeObserver = new ResizeObserver(updateScale)
+  if (wrapperRef.value) {
+    resizeObserver.observe(wrapperRef.value)
+  }
+  window.addEventListener('resize', updateScale)
 
   getStats()
   getDevStats()
@@ -667,7 +708,9 @@ onMounted(async () => {
 })
 
 onUnmounted(() => {
-  window.removeEventListener('resize', handleResize)
+  resizeObserver?.disconnect()
+  window.removeEventListener('resize', updateScale)
+  cancelAnimationFrame(resizeRaf)
   chartInstance?.dispose()
   chartInstance = null
 })
@@ -676,30 +719,28 @@ onUnmounted(() => {
 <style lang="scss" scoped>
 @import url('@/styles/kb.scss');
 
-.statistics-page {
-  --kb-scale: 1;
+.statistics-scale-wrapper {
+  overflow: hidden;
+}
 
+.statistics-page {
   display: flex;
-  width: 100%;
-  height: calc(
-    100vh - 20px - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height)
-  );
-  min-height: 720px;
-  overflow: auto;
+  overflow: hidden;
   color: #24364f;
   flex-direction: column;
 }
 
 .statistics-content {
+  position: relative;
   display: flex;
+  height: calc(100% - 52px * var(--kb-scale));
   min-height: 0;
-  padding: 16px 20px 20px;
-  flex: 1;
+  padding: calc(44px * var(--kb-scale)) calc(20px * var(--kb-scale)) calc(20px * var(--kb-scale));
   flex-direction: column;
 }
 
 .section-row {
-  margin-bottom: 14px;
+  margin-bottom: calc(14px * var(--kb-scale));
   flex-shrink: 0;
 
   &:last-child {
@@ -720,7 +761,7 @@ onUnmounted(() => {
   overflow: hidden;
   background: linear-gradient(180deg, rgb(235 243 255 / 76%) 0%, rgb(221 233 251 / 58%) 100%);
   border: 1px solid rgb(255 255 255 / 58%);
-  border-radius: 20px;
+  border-radius: calc(20px * var(--kb-scale));
   box-shadow:
     inset 0 1px 0 rgb(255 255 255 / 72%),
     0 18px 36px rgb(46 90 164 / 12%);
@@ -740,40 +781,44 @@ onUnmounted(() => {
 
 .filter-card {
   :deep(.el-card__body) {
-    padding-bottom: 6px;
+    padding-bottom: calc(6px * var(--kb-scale));
   }
 }
 
 .filter-form {
   display: flex;
   flex-wrap: wrap;
-  padding: 12px 16px 0;
-  gap: 0 12px;
+  padding: calc(12px * var(--kb-scale)) calc(16px * var(--kb-scale)) 0;
+  gap: 0 calc(12px * var(--kb-scale));
 
   :deep(.el-form-item) {
-    margin-bottom: 12px;
+    margin-bottom: calc(12px * var(--kb-scale));
     align-items: center;
   }
 
   :deep(.el-form-item__label) {
-    height: 34px;
+    width: calc(86px * var(--kb-scale)) !important;
+    height: calc(34px * var(--kb-scale));
+    font-size: calc(14px * var(--kb-scale));
     font-weight: 600;
-    line-height: 34px;
+    line-height: calc(34px * var(--kb-scale));
     color: #03409b;
     align-items: center;
+    white-space: nowrap;
   }
 
   :deep(.el-form-item__content) {
     align-items: center;
-    line-height: 34px;
+    line-height: calc(34px * var(--kb-scale));
   }
 
   :deep(.el-input__wrapper),
   :deep(.el-select__wrapper) {
-    min-height: 34px;
+    min-height: calc(34px * var(--kb-scale));
+    padding: 0 calc(11px * var(--kb-scale));
     background: linear-gradient(180deg, rgb(255 255 255 / 92%) 0%, rgb(242 247 255 / 82%) 100%);
     border: 1px solid rgb(255 255 255 / 88%);
-    border-radius: 8px;
+    border-radius: calc(8px * var(--kb-scale));
     box-shadow:
       inset 0 1px 0 rgb(255 255 255 / 95%),
       0 6px 12px rgb(152 181 230 / 12%);
@@ -781,21 +826,33 @@ onUnmounted(() => {
 
   :deep(.el-range-input),
   :deep(.el-range-separator) {
+    font-size: calc(14px * var(--kb-scale));
     color: #24364f;
   }
 }
 
+.filter-control {
+  width: calc(180px * var(--kb-scale));
+}
+
+.filter-control--date {
+  width: calc(220px * var(--kb-scale));
+}
+
 .filter-actions {
-  margin-left: 8px;
+  margin-left: calc(8px * var(--kb-scale));
 }
 
 .action-btn {
-  min-width: 88px;
+  height: calc(34px * var(--kb-scale));
+  min-width: calc(88px * var(--kb-scale));
+  padding: 0 calc(15px * var(--kb-scale));
+  font-size: calc(14px * var(--kb-scale));
   font-weight: 600;
   color: #f5f9ff;
   background: linear-gradient(180deg, #83bcff 0%, #2f7ee9 58%, #1762d6 100%);
   border: 1px solid rgb(255 255 255 / 55%);
-  border-radius: 8px;
+  border-radius: calc(8px * var(--kb-scale));
   box-shadow:
     inset 0 1px 0 rgb(255 255 255 / 58%),
     0 4px 8px rgb(30 89 179 / 22%);
@@ -810,9 +867,9 @@ onUnmounted(() => {
 
 .metric-grid {
   display: grid;
-  min-height: 214px;
-  padding: 12px 14px 16px;
-  gap: 14px;
+  min-height: calc(214px * var(--kb-scale));
+  padding: calc(12px * var(--kb-scale)) calc(14px * var(--kb-scale)) calc(16px * var(--kb-scale));
+  gap: calc(14px * var(--kb-scale));
 }
 
 .metric-grid--order {
@@ -827,15 +884,15 @@ onUnmounted(() => {
   position: relative;
   display: flex;
   min-width: 0;
-  min-height: 168px;
-  padding: 18px 18px 16px;
+  min-height: calc(168px * var(--kb-scale));
+  padding: calc(18px * var(--kb-scale)) calc(18px * var(--kb-scale)) calc(16px * var(--kb-scale));
   overflow: hidden;
   text-align: left;
   cursor: pointer;
   background: linear-gradient(135deg, rgb(255 255 255 / 96%) 0%, rgb(239 246 255 / 88%) 100%),
     radial-gradient(circle at 90% 12%, var(--metric-glow) 0%, rgb(255 255 255 / 0%) 38%);
   border: 1px solid rgb(94 139 203 / 18%);
-  border-radius: 16px;
+  border-radius: calc(16px * var(--kb-scale));
   box-shadow:
     inset 0 1px 0 rgb(255 255 255 / 92%),
     0 10px 22px rgb(46 90 164 / 9%);
@@ -850,10 +907,10 @@ onUnmounted(() => {
 
 .metric-card::before {
   position: absolute;
-  top: 14px;
+  top: calc(14px * var(--kb-scale));
   left: 0;
-  width: 4px;
-  height: calc(100% - 28px);
+  width: calc(4px * var(--kb-scale));
+  height: calc(100% - 28px * var(--kb-scale));
   background: var(--metric-accent);
   border-radius: 0 999px 999px 0;
   content: '';
@@ -862,10 +919,10 @@ onUnmounted(() => {
 
 .metric-card::after {
   position: absolute;
-  right: -28px;
-  bottom: -42px;
-  width: 116px;
-  height: 116px;
+  right: calc(-28px * var(--kb-scale));
+  bottom: calc(-42px * var(--kb-scale));
+  width: calc(116px * var(--kb-scale));
+  height: calc(116px * var(--kb-scale));
   pointer-events: none;
   background: radial-gradient(circle, var(--metric-glow) 0%, rgb(255 255 255 / 0%) 72%);
   content: '';
@@ -879,7 +936,7 @@ onUnmounted(() => {
   background: linear-gradient(135deg, rgb(255 255 255 / 100%) 0%, rgb(232 242 255 / 92%) 100%),
     radial-gradient(circle at 90% 12%, var(--metric-glow) 0%, rgb(255 255 255 / 0%) 42%);
   border-color: rgb(58 103 180 / 28%);
-  transform: translateY(-4px);
+  transform: translateY(calc(-4px * var(--kb-scale)));
   box-shadow:
     inset 0 1px 0 rgb(255 255 255 / 92%),
     0 16px 28px rgb(41 89 164 / 16%);
@@ -899,15 +956,15 @@ onUnmounted(() => {
 }
 
 .metric-card__label {
-  font-size: 14px;
+  font-size: calc(14px * var(--kb-scale));
   font-weight: 700;
   color: #18345a;
 }
 
 .metric-card__value {
-  margin-top: 14px;
+  margin-top: calc(14px * var(--kb-scale));
   font-family: YouSheBiaoTiHei, sans-serif;
-  font-size: 38px;
+  font-size: calc(38px * var(--kb-scale));
   font-weight: normal;
   line-height: 1;
   color: var(--metric-accent);
@@ -931,16 +988,16 @@ onUnmounted(() => {
 }
 
 .metric-card__hint {
-  margin-top: 8px;
-  font-size: 12px;
+  margin-top: calc(8px * var(--kb-scale));
+  font-size: calc(12px * var(--kb-scale));
   font-weight: 700;
   color: #536986;
 }
 
 .metric-card__progress {
   display: block;
-  height: 6px;
-  margin-top: 14px;
+  height: calc(6px * var(--kb-scale));
+  margin-top: calc(14px * var(--kb-scale));
   overflow: hidden;
   background: rgb(31 91 184 / 12%);
   border-radius: 999px;
@@ -956,8 +1013,8 @@ onUnmounted(() => {
 }
 
 .trend-chart {
-  min-height: 260px;
-  padding: 8px 12px 14px;
+  min-height: calc(260px * var(--kb-scale));
+  padding: calc(8px * var(--kb-scale)) calc(12px * var(--kb-scale)) calc(14px * var(--kb-scale));
   flex: 1;
 }
 
@@ -968,31 +1025,4 @@ onUnmounted(() => {
 :deep(.el-card) {
   --el-card-bg-color: transparent;
 }
-
-@media (width <= 1366px) {
-  .statistics-content {
-    padding: 12px;
-  }
-
-  .metric-grid {
-    overflow-x: auto;
-  }
-
-  .metric-grid--order,
-  .metric-grid--device {
-    grid-template-columns: repeat(4, minmax(132px, 1fr));
-  }
-
-  .metric-card {
-    min-height: 150px;
-  }
-}
-
-@media (width <= 1180px) {
-  .metric-grid--order,
-  .metric-grid--device {
-    overflow-x: auto;
-    grid-template-columns: repeat(4, minmax(140px, 1fr));
-  }
-}
 </style>