فهرست منبع

feat: 优化瑞恒看板与日报联动

- 将瑞恒看板拆分为“运行概况”和“生产日报”两个页签
- 新增瑞恒安全生产天数组件,并接入按部门查询安全天数接口
- 支持设备列表在生产日报页全屏展示,适配表格高度和滚动区域
- 调整运行概况布局,扩展为三行图表区域并加入安全天数面板
- 为今日用气量和历史用气量图表增加点击跳转日报统计能力
- 跳转日报时携带统计时间范围、默认看板视图和目标页签
- 日报统计页支持从路由参数同步查询时间、页签和默认视图
- DailyStatistics 支持外部指定默认展示“表格/看板”
- 优化设备状态饼图 tooltip、外侧标签和引导线展示
- 将安全天数接口改为按 deptId 参数查询,兼容瑞恒与瑞盈不同部门
Zimo 1 هفته پیش
والد
کامیت
ddd084728d

+ 2 - 2
src/api/pms/stat/index.ts

@@ -187,8 +187,8 @@ export const IotStatApi = {
   getSafeCount: async () => {
     return await request.get({ url: `/rq/stat/home/safe` })
   },
-  getSafeCount1: async () => {
-    return await request.get({ url: `/rq/qhse-safe-day/get/dept/` + 158, params: { deptId: 158 } })
+  getSafeCount1: async (deptId: number) => {
+    return await request.get({ url: `/rq/qhse-safe-day/get/dept/` + deptId, params: { deptId } })
   },
   getMttr: async () => {
     return await request.get({ url: `/rq/stat/mttr` })

+ 11 - 0
src/views/pms/iotrhdailyreport/components/DailyStatistics.vue

@@ -31,6 +31,7 @@ interface Query {
 const props = defineProps<{
   query: Query
   deptName: string
+  defaultView?: '表格' | '看板'
   refreshKey: number
 }>()
 
@@ -267,6 +268,8 @@ const currentTab = ref<'表格' | '看板'>('表格')
 const direction = ref<'left' | 'right'>('right')
 
 const handleSelectTab = (val: '表格' | '看板') => {
+  if (tab.value === val && currentTab.value === val) return
+
   tab.value = val
   direction.value = val === '看板' ? 'right' : 'left'
   nextTick(() => {
@@ -277,6 +280,14 @@ const handleSelectTab = (val: '表格' | '看板') => {
   })
 }
 
+watch(
+  () => props.defaultView,
+  (val) => {
+    handleSelectTab(val || '表格')
+  },
+  { immediate: true }
+)
+
 const chartRef = ref<HTMLDivElement | null>(null)
 let chart: ECharts | null = null
 let chartContainerEl: HTMLDivElement | null = null

+ 50 - 0
src/views/pms/iotrhdailyreport/summary.vue

@@ -9,6 +9,7 @@ defineOptions({
   name: 'IotRhDailyReportSummary'
 })
 
+const route = useRoute()
 const deptId = useUserStore().getUser.deptId
 
 interface Query {
@@ -35,9 +36,46 @@ const createDefaultQuery = (): Query => ({
 
 const query = ref<Query>(createDefaultQuery())
 const activeTab = ref<'日报统计' | '非生产时效'>('日报统计')
+const defaultView = ref<'表格' | '看板'>('表格')
 const deptName = ref('瑞恒兴域')
 const refreshKey = ref(0)
 
+const getRouteCreateTime = () => {
+  const createTime = route.query.createTime
+
+  if (Array.isArray(createTime)) {
+    const values = createTime.filter((item): item is string => typeof item === 'string')
+    return values.length === 2 ? values : undefined
+  }
+
+  if (typeof createTime === 'string') {
+    const values = createTime.split(',').filter(Boolean)
+    return values.length === 2 ? values : undefined
+  }
+
+  return undefined
+}
+
+const getRouteActiveTab = () => {
+  return route.query.activeTab === '非生产时效' ? '非生产时效' : '日报统计'
+}
+
+const getRouteDefaultView = () => {
+  return route.query.view === 'kanban' || route.query.defaultView === '看板' ? '看板' : '表格'
+}
+
+const syncQueryFromRoute = () => {
+  const createTime = getRouteCreateTime()
+
+  if (createTime) {
+    query.value.createTime = createTime
+  }
+
+  activeTab.value = getRouteActiveTab()
+  defaultView.value = getRouteDefaultView()
+  handleQuery()
+}
+
 const handleDeptNodeClick = (node: any) => {
   deptName.value = node.name
   handleQuery()
@@ -53,6 +91,17 @@ const resetQuery = () => {
   deptName.value = '瑞恒兴域'
   handleQuery()
 }
+
+onMounted(() => {
+  syncQueryFromRoute()
+})
+
+watch(
+  () => [route.query.createTime, route.query.activeTab],
+  () => {
+    syncQueryFromRoute()
+  }
+)
 </script>
 
 <template>
@@ -123,6 +172,7 @@ const resetQuery = () => {
       v-if="activeTab === '日报统计'"
       :query="query"
       :dept-name="deptName"
+      :default-view="defaultView"
       :refresh-key="refreshKey" />
     <NonProductionEfficiency
       v-else

+ 104 - 13
src/views/pms/stat/rhkb.vue

@@ -5,16 +5,22 @@ import rhsummary from './rhkb/rhsummary.vue'
 import deviceType from './rhkb/deviceType.vue'
 import deviceStatus from './rhkb/deviceStatus.vue'
 import operation from './rhkb/operation.vue'
-import orderTrend from './rhkb/orderTrend.vue'
 import todayGas from './rhkb/todayGas.vue'
 import historyGas from './rhkb/historyGas.vue'
 import deviceList from './rhkb/deviceList.vue'
+import rhsafeday from './rhkb/rhsafeday.vue'
 
 defineOptions({
   name: 'IotRhStatt'
 })
 
 const company = ref('瑞恒')
+const activePage = ref<'home' | 'device'>('home')
+
+const pageTabs = [
+  { label: '运行概况', value: 'home' },
+  { label: '生产日报', value: 'device' }
+] as const
 
 const wrapperRef = ref<HTMLDivElement>()
 const scale = ref(1)
@@ -63,6 +69,12 @@ onMounted(() => {
   window.addEventListener('resize', updateScale)
 })
 
+watch(activePage, () => {
+  nextTick(() => {
+    window.dispatchEvent(new Event('rhkb:resize'))
+  })
+})
+
 onUnmounted(() => {
   resizeObserver?.disconnect()
   window.removeEventListener('resize', updateScale)
@@ -76,16 +88,36 @@ onUnmounted(() => {
       <div class="bg kb-screen" id="rhkb" :style="targetAreaStyle">
         <header class="header">{{ company }}</header>
         <div class="kb-content">
-          <rhsummary class="kb-stage-card kb-stage-card--1" />
-          <div class="kb-chart-grid">
-            <deviceStatus class="kb-stage-card kb-stage-card--2" />
-            <deviceType class="kb-stage-card kb-stage-card--3" />
-            <operation class="kb-stage-card kb-stage-card--4" />
-            <orderTrend class="kb-stage-card kb-stage-card--5" />
-            <todayGas class="kb-stage-card kb-stage-card--6" />
-            <historyGas class="kb-stage-card kb-stage-card--7" />
+          <div class="page-tabs">
+            <button
+              v-for="tab in pageTabs"
+              :key="tab.value"
+              type="button"
+              class="page-tab"
+              :class="{ 'is-active': activePage === tab.value }"
+              @click="activePage = tab.value">
+              {{ tab.label }}
+            </button>
+          </div>
+
+          <div v-if="activePage === 'home'" class="kb-home-page">
+            <rhsummary class="kb-stage-card kb-stage-card--1" />
+            <div class="kb-chart-grid">
+              <deviceStatus class="kb-stage-card kb-stage-card--1" />
+              <deviceType class="kb-stage-card kb-stage-card--2" />
+              <operation class="kb-stage-card kb-stage-card--3" />
+              <rhsafeday class="kb-stage-card kb-stage-card--4" />
+              <!-- <orderTrend class="kb-stage-card kb-stage-card--5" /> -->
+              <todayGas class="kb-stage-card kb-stage-card--6" />
+              <historyGas class="kb-stage-card kb-stage-card--7" />
+            </div>
+          </div>
+
+          <div v-else class="kb-device-page">
+            <deviceList
+              class="kb-stage-card kb-stage-card--8 kb-stage-card--list"
+              page-mode="full" />
           </div>
-          <deviceList class="kb-stage-card kb-stage-card--8 kb-stage-card--list" />
         </div>
       </div>
     </div>
@@ -100,16 +132,75 @@ onUnmounted(() => {
 }
 
 .kb-content {
-  padding: calc(12px * var(--kb-scale)) calc(20px * var(--kb-scale)) 0;
+  position: relative;
+  height: calc(100% - 52px * var(--kb-scale));
+  padding: calc(44px * var(--kb-scale)) calc(20px * var(--kb-scale)) calc(20px * var(--kb-scale));
+}
+
+.page-tabs {
+  position: absolute;
+  top: calc(10px * var(--kb-scale));
+  left: calc(20px * var(--kb-scale));
+  z-index: 3;
+  display: flex;
+  width: fit-content;
+  gap: calc(12px * var(--kb-scale));
+}
+
+.page-tab {
+  height: calc(28px * var(--kb-scale));
+  min-width: calc(82px * var(--kb-scale));
+  padding: 0 calc(14px * var(--kb-scale));
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: calc(15px * var(--kb-scale));
+  line-height: calc(28px * var(--kb-scale));
+  color: #f5f9ff;
+  cursor: pointer;
+  background: linear-gradient(180deg, #83bcff 0%, #2f7ee9 58%, #1762d6 100%);
+  border: 1px solid rgb(255 255 255 / 55%);
+  border-radius: calc(5px * var(--kb-scale));
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 58%),
+    0 calc(4px * var(--kb-scale)) calc(8px * var(--kb-scale)) rgb(30 89 179 / 22%);
+  transition:
+    transform 0.2s ease,
+    filter 0.2s ease,
+    box-shadow 0.2s ease;
+}
+
+.page-tab:hover,
+.page-tab.is-active {
+  filter: brightness(1.08);
+  transform: translateY(calc(-1px * var(--kb-scale)));
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 72%),
+    0 calc(5px * var(--kb-scale)) calc(10px * var(--kb-scale)) rgb(30 89 179 / 28%);
+}
+
+.page-tab.is-active {
+  background: linear-gradient(180deg, #4d9cff 0%, #1f6ee7 56%, #0e4fc4 100%);
+}
+
+.kb-home-page {
+  display: flex;
+  height: 100%;
+  min-height: 0;
+  flex-direction: column;
 }
 
 .kb-chart-grid {
   display: grid;
   width: 100%;
-  height: calc(592px * var(--kb-scale));
+  min-height: 0;
+  flex: 1;
   margin-top: calc(12px * var(--kb-scale));
   gap: calc(12px * var(--kb-scale));
-  grid-template-rows: repeat(2, minmax(0, 1fr));
+  grid-template-rows: repeat(3, minmax(0, 1fr));
   grid-template-columns: repeat(3, minmax(0, 1fr));
 }
+
+.kb-device-page {
+  height: 100%;
+  min-height: 0;
+}
 </style>

+ 38 - 3
src/views/pms/stat/rhkb/deviceList.vue

@@ -28,6 +28,15 @@ const DEFAULT_TIME_RANGE = rangeShortcuts[2]
   .value()
   .map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
 
+const props = withDefaults(
+  defineProps<{
+    pageMode?: 'compact' | 'full'
+  }>(),
+  {
+    pageMode: 'compact'
+  }
+)
+
 const createTime = ref<string[]>(DEFAULT_TIME_RANGE)
 const loading = ref(false)
 const list = ref<RhDeviceListRow[]>([])
@@ -38,7 +47,9 @@ const teamList = ref<RhTeamRateRow[]>([])
 const kbScale = inject<Ref<number>>('rhKbScale', ref(1))
 
 const tableData = computed(() => list.value)
-const tableHeight = computed(() => Math.round(TABLE_HEIGHT * kbScale.value))
+const tableHeight = computed<number | string>(() =>
+  props.pageMode === 'full' ? '100%' : Math.round(TABLE_HEIGHT * kbScale.value)
+)
 
 function formatRate(value?: number | null) {
   return `${(Number(value ?? 0) * 100).toFixed(2)}%`
@@ -97,7 +108,9 @@ onMounted(() => {
 </script>
 
 <template>
-  <div class="panel device-list-panel w-full flex flex-col">
+  <div
+    class="panel device-list-panel w-full min-h-0 flex flex-col"
+    :class="{ 'device-list-panel--full': props.pageMode === 'full' }">
     <div class="panel-title device-list-panel__title flex items-center justify-between">
       <div class="kb-panel-title-text flex items-center">
         <div class="icon-decorator">
@@ -121,11 +134,12 @@ onMounted(() => {
       </div>
     </div>
     <!-- v-loading="loading" -->
-    <div class="device-list-panel__body flex-1 min-h-0">
+    <div class="device-list-panel__body flex flex-col flex-1 min-h-0">
       <el-table
         :data="tableData"
         :height="tableHeight"
         class="device-list-table"
+        :class="{ 'device-list-table--full': props.pageMode === 'full' }"
         @row-click="handleRowClick">
         <el-table-column prop="projectDeptName" label="项目部" min-width="220" align="center" />
         <el-table-column prop="teamCount" label="队伍数量" min-width="120" align="center" />
@@ -183,6 +197,27 @@ onMounted(() => {
 
 <style lang="scss" scoped>
 @import url('@/styles/kb.scss');
+
+.device-list-panel.device-list-panel--full {
+  height: 100%;
+  min-height: 0;
+  margin-top: 0;
+}
+
+.device-list-table--full {
+  :deep(.el-scrollbar__view) {
+    display: block;
+    height: 100%;
+  }
+
+  :deep(.el-table__body) {
+    height: 100%;
+  }
+
+  :deep(.el-table__body tbody) {
+    height: 100%;
+  }
+}
 </style>
 
 <style>

+ 40 - 12
src/views/pms/stat/rhkb/deviceStatus.vue

@@ -12,7 +12,20 @@ function getChartOption(data: ChartItem[]): echarts.EChartsOption {
   return {
     ...ANIMATION,
     grid: THEME.grid,
-    tooltip: createTooltip({ trigger: 'item' }),
+    tooltip: createTooltip({
+      trigger: 'item',
+      formatter(params: any) {
+        return `
+          <div style="font-weight: 600; margin-bottom: 6px;">${params.seriesName}</div>
+          <div style="display: flex; align-items: center; gap: 8px;">
+            <span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${params.color};"></span>
+            <span>${params.name}</span>
+            <span style="font-weight: 700; color: ${THEME.text.strong};">${params.value}</span>
+            <span style="color: ${THEME.text.strong};">${Number(params.percent ?? 0).toFixed(0)}%</span>
+          </div>
+        `
+      }
+    }),
     legend: createLegend(
       { bottom: 10, itemWidth: 12, itemHeight: 12 },
       data.map((item) => item.name)
@@ -21,32 +34,47 @@ function getChartOption(data: ChartItem[]): echarts.EChartsOption {
       {
         name: '设备状态',
         type: 'pie',
-        center: ['50%', '44%'],
-        radius: ['50%', '70%'],
+        center: ['50%', '45%'],
+        radius: ['44%', '63%'],
         minAngle: 5,
         label: {
-          show: false,
-          position: 'center',
+          show: true,
+          position: 'outside',
+          alignTo: 'edge',
+          edgeDistance: 12,
+          distanceToLabelLine: 5,
           formatter(params: any) {
-            return `{name|${params.name}}\n{value|${params.value}}`
+            return `{name|${params.name}} {percent|${Number(params.percent ?? 0).toFixed(0)}%}`
           },
           rich: {
             name: {
-              color: THEME.text.regular,
-              fontSize: 14,
+              color: THEME.text.primary,
+              fontSize: 11,
               fontWeight: 500,
-              lineHeight: 24,
+              lineHeight: 16,
               fontFamily: FONT_FAMILY
             },
-            value: {
+            percent: {
               color: THEME.text.strong,
-              fontSize: 28,
+              fontSize: 12,
               fontWeight: 700,
-              lineHeight: 36,
+              lineHeight: 16,
               fontFamily: FONT_FAMILY
             }
           }
         },
+        labelLine: {
+          show: true,
+          length: 10,
+          length2: 6,
+          smooth: 0.15,
+          lineStyle: {
+            width: 1
+          }
+        },
+        labelLayout: {
+          hideOverlap: true
+        },
         emphasis: {
           label: {
             show: true

+ 1 - 1
src/views/pms/stat/rhkb/deviceType.vue

@@ -168,7 +168,7 @@ onUnmounted(() => {
         <span></span>
         <span></span>
       </div>
-      设备类别top
+      设备类别/状态
     </div>
     <div ref="chartRef" class="flex-1 min-h-0"></div>
   </div>

+ 34 - 0
src/views/pms/stat/rhkb/historyGas.vue

@@ -1,5 +1,6 @@
 <script lang="ts" setup>
 import * as echarts from 'echarts'
+import dayjs from 'dayjs'
 import {
   ANIMATION,
   ChartItem,
@@ -14,8 +15,10 @@ import { IotStatApi } from '@/api/pms/stat'
 
 const chartData = ref<ChartItem[]>([])
 
+const router = useRouter()
 const chartRef = ref<HTMLDivElement>()
 let chart: echarts.ECharts | null = null
+let chartClickBound = false
 
 function getChartOption(data: ChartItem[]): echarts.EChartsOption {
   const names = data.map((item) => item.name)
@@ -137,9 +140,36 @@ function initChart() {
   chart = echarts.init(chartRef.value, undefined, {
     renderer: 'svg'
   })
+  chart.getZr().on('click', handleChartClick)
+  chartClickBound = true
   renderChart()
 }
 
+function getChartMonthRange() {
+  const startDate = dayjs(chartData.value[0]?.name)
+  const endDate = dayjs(chartData.value[chartData.value.length - 1]?.name)
+  if (!startDate.isValid() || !endDate.isValid()) return null
+
+  return [
+    startDate.startOf('month').format('YYYY-MM-DD HH:mm:ss'),
+    endDate.endOf('month').format('YYYY-MM-DD HH:mm:ss')
+  ]
+}
+
+function handleChartClick() {
+  const createTime = getChartMonthRange()
+  if (!createTime) return
+
+  router.push({
+    name: 'IotRhDailyReportSummary',
+    query: {
+      activeTab: '日报统计',
+      view: 'kanban',
+      createTime
+    }
+  })
+}
+
 function renderChart() {
   if (!chart) return
 
@@ -154,6 +184,10 @@ function resizeChart() {
 
 function destroyChart() {
   if (chart) {
+    if (chartClickBound) {
+      chart.getZr().off('click', handleChartClick)
+      chartClickBound = false
+    }
     chart.dispose()
     chart = null
   }

+ 163 - 0
src/views/pms/stat/rhkb/rhsafeday.vue

@@ -0,0 +1,163 @@
+<script lang="ts" setup>
+import CountTo from '@/components/count-to1.vue'
+import { IotStatApi } from '@/api/pms/stat'
+
+const FIXED_SAFE_DAYS = 123
+
+const safeDays = ref(FIXED_SAFE_DAYS)
+
+async function loadSafeDays() {
+  try {
+    const res = await IotStatApi.getSafeCount1(157)
+    safeDays.value = res
+  } catch (error) {
+    console.error('获取安全生产天数失败:', error)
+  } finally {
+    // safeDays.value = FIXED_SAFE_DAYS
+  }
+}
+
+onMounted(() => {
+  loadSafeDays()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title">
+      <div class="icon-decorator">
+        <span></span>
+        <span></span>
+      </div>
+      安全生产天数
+    </div>
+
+    <div class="safe-day-panel flex-1 min-h-0">
+      <div class="safe-day-panel__orbit safe-day-panel__orbit--outer"></div>
+
+      <div class="safe-day-panel__top">
+        <span class="safe-day-panel__tag">SAFE RUN</span>
+      </div>
+
+      <div class="safe-day-panel__center">
+        <div class="safe-day-panel__headline">连续安全生产</div>
+        <div class="safe-day-panel__value-row translate-x-1.5">
+          <CountTo
+            class="safe-day-panel__value"
+            :start-val="0"
+            :end-val="safeDays"
+            :duration="1200" />
+          <span class="safe-day-panel__unit">天</span>
+        </div>
+        <!-- <div class="safe-day-panel__subline">截至当前未发生安全生产事故</div> -->
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+
+.safe-day-panel {
+  position: relative;
+  display: flex;
+  padding: calc(22px * var(--kb-scale, 1)) calc(24px * var(--kb-scale, 1))
+    calc(18px * var(--kb-scale, 1));
+  overflow: hidden;
+  flex-direction: column;
+}
+
+.safe-day-panel__orbit {
+  position: absolute;
+  left: 50%;
+  border: 1px solid rgb(31 91 184 / 15%);
+  border-radius: 999px;
+  transform: translateX(-50%);
+}
+
+.safe-day-panel__orbit--outer {
+  top: calc(32px * var(--kb-scale, 1));
+  width: calc(320px * var(--kb-scale, 1));
+  height: calc(320px * var(--kb-scale, 1));
+}
+
+.safe-day-panel__orbit--inner {
+  top: calc(62px * var(--kb-scale, 1));
+  width: calc(240px * var(--kb-scale, 1));
+  height: calc(240px * var(--kb-scale, 1));
+  border-style: dashed;
+}
+
+.safe-day-panel__top,
+.safe-day-panel__center {
+  position: relative;
+  z-index: 1;
+}
+
+.safe-day-panel__top {
+  display: flex;
+  align-items: center;
+  justify-content: flex-start;
+}
+
+.safe-day-panel__tag {
+  padding: calc(5px * var(--kb-scale, 1)) calc(12px * var(--kb-scale, 1));
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: calc(16px * var(--kb-scale, 1));
+  line-height: 1;
+  letter-spacing: 1px;
+  color: #1f5bb8;
+  background: rgb(255 255 255 / 72%);
+  border: 1px solid rgb(255 255 255 / 75%);
+  border-radius: 999px;
+  box-shadow: inset 0 1px 0 rgb(255 255 255 / 85%);
+}
+
+.safe-day-panel__center {
+  display: flex;
+  padding-top: calc(14px * var(--kb-scale, 1));
+  margin-top: auto;
+  margin-bottom: auto;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  text-align: center;
+}
+
+.safe-day-panel__headline {
+  font-size: calc(20px * var(--kb-scale, 1));
+  font-weight: 600;
+  letter-spacing: 1px;
+  color: #24364f;
+}
+
+.safe-day-panel__value-row {
+  display: flex;
+  margin-top: calc(10px * var(--kb-scale, 1));
+  align-items: flex-end;
+  justify-content: center;
+}
+
+.safe-day-panel__value {
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: calc(82px * var(--kb-scale, 1));
+  line-height: 0.9;
+  letter-spacing: 2px;
+  color: #1f5bb8;
+  text-shadow: 0 12px 24px rgb(31 91 184 / 12%);
+}
+
+.safe-day-panel__unit {
+  padding-bottom: calc(10px * var(--kb-scale, 1));
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: calc(30px * var(--kb-scale, 1));
+  line-height: 1;
+  color: #f08c2e;
+}
+
+.safe-day-panel__subline {
+  margin-top: calc(10px * var(--kb-scale, 1));
+  font-size: calc(15px * var(--kb-scale, 1));
+  color: #6f85aa;
+}
+</style>

+ 30 - 32
src/views/pms/stat/rhkb/rhsummary.vue

@@ -23,13 +23,13 @@ const cardConfigs: CardConfig[] = [
     accent: THEME.color.blue.strong,
     glow: THEME.color.blue.glow
   },
-  {
-    key: 'maintain',
-    title: '维修工单',
-    icon: 'i-material-symbols:home-repair-service',
-    accent: THEME.color.orange.strong,
-    glow: THEME.color.orange.glow
-  },
+  // {
+  //   key: 'maintain',
+  //   title: '维修工单',
+  //   icon: 'i-material-symbols:home-repair-service',
+  //   accent: THEME.color.orange.strong,
+  //   glow: THEME.color.orange.glow
+  // },
   {
     key: 'unfilledCount',
     title: '运行未填写',
@@ -37,13 +37,13 @@ const cardConfigs: CardConfig[] = [
     accent: THEME.color.orange.strong,
     glow: THEME.color.orange.glow
   },
-  {
-    key: 'filledCount',
-    title: '运行已填写',
-    icon: 'i-solar:clipboard-check-linear',
-    accent: THEME.color.green.strong,
-    glow: THEME.color.green.glow
-  },
+  // {
+  //   key: 'filledCount',
+  //   title: '运行已填写',
+  //   icon: 'i-solar:clipboard-check-linear',
+  //   accent: THEME.color.green.strong,
+  //   glow: THEME.color.green.glow
+  // },
   {
     key: 'bytodo',
     title: '待保养',
@@ -51,27 +51,27 @@ const cardConfigs: CardConfig[] = [
     accent: THEME.color.orange.strong,
     glow: THEME.color.orange.glow
   },
-  {
-    key: 'byfinished',
-    title: '已保养',
-    icon: 'i-solar:shield-check-linear',
-    accent: THEME.color.green.strong,
-    glow: THEME.color.green.glow
-  },
+  // {
+  //   key: 'byfinished',
+  //   title: '已保养',
+  //   icon: 'i-solar:shield-check-linear',
+  //   accent: THEME.color.green.strong,
+  //   glow: THEME.color.green.glow
+  // },
   {
     key: 'inspectttodo',
     title: '待巡检',
     icon: 'i-solar:map-point-search-linear',
     accent: THEME.color.orange.strong,
     glow: THEME.color.orange.glow
-  },
-  {
-    key: 'inspecttfinished',
-    title: '已巡检',
-    icon: 'i-solar:check-circle-linear',
-    accent: THEME.color.green.strong,
-    glow: THEME.color.green.glow
   }
+  // {
+  //   key: 'inspecttfinished',
+  //   title: '已巡检',
+  //   icon: 'i-solar:check-circle-linear',
+  //   accent: THEME.color.green.strong,
+  //   glow: THEME.color.green.glow
+  // }
 ]
 
 function createDefaultCardState(): Record<CardKey, CardStateItem> {
@@ -241,8 +241,7 @@ onMounted(() => {
         :style="{
           '--card-accent': card.accent,
           '--card-glow': card.glow
-        }"
-      >
+        }">
         <div class="summary-card__shine"></div>
 
         <div class="summary-card__icon">
@@ -257,8 +256,7 @@ onMounted(() => {
               style="color: #1f5bb8"
               :start-val="0"
               :end-val="card.value"
-              :duration="1200"
-            />
+              :duration="1200" />
             <span v-else class="summary-card__placeholder">--</span>
           </div>
         </div>

+ 35 - 0
src/views/pms/stat/rhkb/todayGas.vue

@@ -1,5 +1,6 @@
 <script lang="ts" setup>
 import * as echarts from 'echarts'
+import dayjs from 'dayjs'
 import {
   ANIMATION,
   ChartData,
@@ -16,8 +17,10 @@ const chartData = ref<ChartData>({
   series: []
 })
 
+const router = useRouter()
 const chartRef = ref<HTMLDivElement>()
 let chart: echarts.ECharts | null = null
+let chartClickBound = false
 
 function getChartOption(data: ChartData): echarts.EChartsOption {
   const xAxisData = data.xAxis || []
@@ -144,9 +147,37 @@ function initChart() {
   chart = echarts.init(chartRef.value, undefined, {
     renderer: 'svg'
   })
+  chart.getZr().on('click', handleChartClick)
+  chartClickBound = true
   renderChart()
 }
 
+function getChartDayRange() {
+  const xAxis = chartData.value.xAxis || []
+  const startDate = dayjs(xAxis[0])
+  const endDate = dayjs(xAxis[xAxis.length - 1])
+  if (!startDate.isValid() || !endDate.isValid()) return null
+
+  return [
+    startDate.startOf('day').format('YYYY-MM-DD HH:mm:ss'),
+    endDate.endOf('day').format('YYYY-MM-DD HH:mm:ss')
+  ]
+}
+
+function handleChartClick() {
+  const createTime = getChartDayRange()
+  if (!createTime) return
+
+  router.push({
+    name: 'IotRhDailyReportSummary',
+    query: {
+      activeTab: '日报统计',
+      view: 'kanban',
+      createTime
+    }
+  })
+}
+
 function renderChart() {
   if (!chart) return
 
@@ -161,6 +192,10 @@ function resizeChart() {
 
 function destroyChart() {
   if (chart) {
+    if (chartClickBound) {
+      chart.getZr().off('click', handleChartClick)
+      chartClickBound = false
+    }
     chart.dispose()
     chart = null
   }

+ 1 - 1
src/views/pms/stat/rykb/safeday.vue

@@ -8,7 +8,7 @@ const safeDays = ref(FIXED_SAFE_DAYS)
 
 async function loadSafeDays() {
   try {
-    const res = await IotStatApi.getSafeCount1()
+    const res = await IotStatApi.getSafeCount1(158)
     safeDays.value = res
   } catch (error) {
     console.error('获取安全生产天数失败:', error)