Forráskód Böngészése

qhse资质证书字段调整

yanghao 2 hete
szülő
commit
d6c6da6818

+ 28 - 5
src/layout/components/Menu/src/Menu.vue

@@ -7,6 +7,8 @@ import { useRenderMenuItem } from './components/useRenderMenuItem'
 import { isUrl } from '@/utils/is'
 import { isUrl } from '@/utils/is'
 import { useDesign } from '@/hooks/web/useDesign'
 import { useDesign } from '@/hooks/web/useDesign'
 import { LayoutType } from '@/types/layout'
 import { LayoutType } from '@/types/layout'
+import { getCenteredScrollTop } from './helper'
+import { useScrollTo } from '@/hooks/event/useScrollTo'
 
 
 const { getPrefixCls } = useDesign()
 const { getPrefixCls } = useDesign()
 
 
@@ -58,6 +60,29 @@ export default defineComponent({
       return path
       return path
     })
     })
 
 
+    const scrollbarRef = ref<ComponentRef<typeof ElScrollbar>>()
+
+    const scrollActiveMenuToCenter = async () => {
+      if (unref(menuMode) !== 'vertical') return
+
+      await nextTick()
+      const wrap$ = unref(scrollbarRef)?.wrapRef
+      const activeMenuItem = wrap$?.querySelector<HTMLElement>('.el-menu-item.is-active')
+      if (!wrap$ || !activeMenuItem) return
+
+      const { start } = useScrollTo({
+        el: wrap$,
+        position: 'scrollTop',
+        to: getCenteredScrollTop(wrap$, activeMenuItem),
+        duration: 300
+      })
+      start()
+    }
+
+    onMounted(scrollActiveMenuToCenter)
+
+    watch(() => unref(activeMenu), scrollActiveMenuToCenter, { flush: 'post' })
+
     const menuSelect = (index: string) => {
     const menuSelect = (index: string) => {
       if (props.menuSelect) {
       if (props.menuSelect) {
         props.menuSelect(index)
         props.menuSelect(index)
@@ -74,7 +99,7 @@ export default defineComponent({
       if (unref(layout) === 'top') {
       if (unref(layout) === 'top') {
         return renderMenu()
         return renderMenu()
       } else {
       } else {
-        return <ElScrollbar>{renderMenu()}</ElScrollbar>
+        return <ElScrollbar ref={scrollbarRef}>{renderMenu()}</ElScrollbar>
       }
       }
     }
     }
 
 
@@ -95,8 +120,7 @@ export default defineComponent({
               ? `${prefixCls}-popper--vertical`
               ? `${prefixCls}-popper--vertical`
               : `${prefixCls}-popper--horizontal`
               : `${prefixCls}-popper--horizontal`
           }
           }
-          onSelect={menuSelect}
-        >
+          onSelect={menuSelect}>
           {{
           {{
             default: () => {
             default: () => {
               const { renderMenuItem } = useRenderMenuItem(unref(menuMode))
               const { renderMenuItem } = useRenderMenuItem(unref(menuMode))
@@ -117,8 +141,7 @@ export default defineComponent({
             'w-[var(--left-menu-min-width)]': unref(collapse) && unref(layout) !== 'cutMenu',
             'w-[var(--left-menu-min-width)]': unref(collapse) && unref(layout) !== 'cutMenu',
             'w-[var(--left-menu-max-width)]': !unref(collapse) && unref(layout) !== 'cutMenu'
             'w-[var(--left-menu-max-width)]': !unref(collapse) && unref(layout) !== 'cutMenu'
           }
           }
-        ]}
-      >
+        ]}>
         {renderMenuWrap()}
         {renderMenuWrap()}
       </div>
       </div>
     )
     )

+ 2 - 1
src/layout/components/Menu/src/components/useRenderMenuItem.tsx

@@ -157,7 +157,8 @@ export const useRenderMenuItem = () =>
                 'QHSE资料库',
                 'QHSE资料库',
                 'QHSE资质证书',
                 'QHSE资质证书',
                 'QHSE体系文件',
                 'QHSE体系文件',
-                '事故案例'
+                '事故案例',
+                '行业标准'
               ].includes(v.meta?.title)
               ].includes(v.meta?.title)
             )
             )
           } else {
           } else {

+ 10 - 0
src/layout/components/Menu/src/helper.ts

@@ -52,3 +52,13 @@ export const hasOneShowingChild = (
     onlyOneChild: unref(onlyOneChild)
     onlyOneChild: unref(onlyOneChild)
   }
   }
 }
 }
+
+export const getCenteredScrollTop = (container: HTMLElement, target: HTMLElement) => {
+  const containerRect = container.getBoundingClientRect()
+  const targetRect = target.getBoundingClientRect()
+  const targetTop = container.scrollTop + targetRect.top - containerRect.top
+  const centeredScrollTop = targetTop - (container.clientHeight - targetRect.height) / 2
+  const maxScrollTop = Math.max(0, container.scrollHeight - container.clientHeight)
+
+  return Math.min(maxScrollTop, Math.max(0, centeredScrollTop))
+}

+ 32 - 12
src/layout/components/TabMenu/src/TabMenu.vue

@@ -10,6 +10,8 @@ import { cloneDeep } from 'lodash-es'
 import { filterMenusPath, initTabMap, tabPathMap } from './helper'
 import { filterMenusPath, initTabMap, tabPathMap } from './helper'
 import { useDesign } from '@/hooks/web/useDesign'
 import { useDesign } from '@/hooks/web/useDesign'
 import { isUrl } from '@/utils/is'
 import { isUrl } from '@/utils/is'
+import { getCenteredScrollTop } from '@/layout/components/Menu/src/helper'
+import { useScrollTo } from '@/hooks/event/useScrollTo'
 
 
 const { getPrefixCls, variables } = useDesign()
 const { getPrefixCls, variables } = useDesign()
 
 
@@ -34,6 +36,25 @@ export default defineComponent({
 
 
     const tabRouters = computed(() => unref(routers).filter((v) => !v?.meta?.hidden))
     const tabRouters = computed(() => unref(routers).filter((v) => !v?.meta?.hidden))
 
 
+    const scrollbarRef = ref<ComponentRef<typeof ElScrollbar>>()
+
+    const scrollActiveMenuToCenter = async () => {
+      await nextTick()
+      const wrap$ = unref(scrollbarRef)?.wrapRef
+      const activeMenuItem = wrap$?.querySelector<HTMLElement>(`.${prefixCls}__item.is-active`)
+      if (!wrap$ || !activeMenuItem) return
+
+      const { start } = useScrollTo({
+        el: wrap$,
+        position: 'scrollTop',
+        to: getCenteredScrollTop(wrap$, activeMenuItem),
+        duration: 300
+      })
+      start()
+    }
+
+    onMounted(scrollActiveMenuToCenter)
+
     const setCollapse = () => {
     const setCollapse = () => {
       appStore.setCollapse(!unref(collapse))
       appStore.setCollapse(!unref(collapse))
     }
     }
@@ -71,6 +92,8 @@ export default defineComponent({
       }
       }
     )
     )
 
 
+    watch(() => unref(currentRoute).fullPath, scrollActiveMenuToCenter, { flush: 'post' })
+
     const showTitle = ref(true)
     const showTitle = ref(true)
 
 
     watch(
     watch(
@@ -145,18 +168,18 @@ export default defineComponent({
             'w-[var(--tab-menu-min-width)]': unref(collapse)
             'w-[var(--tab-menu-min-width)]': unref(collapse)
           }
           }
         ]}
         ]}
-        onMouseleave={mouseleave}
-      >
-        <ElScrollbar class="!h-[calc(100%-var(--tab-menu-collapse-height))]">
+        onMouseleave={mouseleave}>
+        <ElScrollbar ref={scrollbarRef} class="!h-[calc(100%-var(--tab-menu-collapse-height))]">
           <div>
           <div>
             {() => {
             {() => {
               return unref(tabRouters).map((v) => {
               return unref(tabRouters).map((v) => {
+                const onlyChild = v.children?.[0]
                 const item = (
                 const item = (
-                  v.meta?.alwaysShow || (v?.children?.length && v?.children?.length > 1)
+                  v.meta?.alwaysShow || !onlyChild || (v.children?.length && v.children.length > 1)
                     ? v
                     ? v
                     : {
                     : {
-                        ...(v?.children && v?.children[0]),
-                        path: pathResolve(v.path, (v?.children && v?.children[0])?.path as string)
+                        ...onlyChild,
+                        path: pathResolve(v.path, onlyChild.path)
                       }
                       }
                 ) as AppRouteRecordRaw
                 ) as AppRouteRecordRaw
                 return (
                 return (
@@ -170,8 +193,7 @@ export default defineComponent({
                     ]}
                     ]}
                     onClick={() => {
                     onClick={() => {
                       tabClick(item)
                       tabClick(item)
-                    }}
-                  >
+                    }}>
                     <div>
                     <div>
                       <Icon icon={item?.meta?.icon}></Icon>
                       <Icon icon={item?.meta?.icon}></Icon>
                     </div>
                     </div>
@@ -189,8 +211,7 @@ export default defineComponent({
             `${prefixCls}--collapse`,
             `${prefixCls}--collapse`,
             'text-center h-[var(--tab-menu-collapse-height)] leading-[var(--tab-menu-collapse-height)] cursor-pointer'
             'text-center h-[var(--tab-menu-collapse-height)] leading-[var(--tab-menu-collapse-height)] cursor-pointer'
           ]}
           ]}
-          onClick={setCollapse}
-        >
+          onClick={setCollapse}>
           <Icon icon={unref(collapse) ? 'ep:d-arrow-right' : 'ep:d-arrow-left'}></Icon>
           <Icon icon={unref(collapse) ? 'ep:d-arrow-right' : 'ep:d-arrow-left'}></Icon>
         </div>
         </div>
         <Menu
         <Menu
@@ -203,8 +224,7 @@ export default defineComponent({
               '!w-0': !unref(showMenu) && !unref(fixedMenu)
               '!w-0': !unref(showMenu) && !unref(fixedMenu)
             }
             }
           ]}
           ]}
-          style="transition: width var(--transition-time-02), left var(--transition-time-02);"
-        ></Menu>
+          style="transition: width var(--transition-time-02), left var(--transition-time-02);"></Menu>
       </div>
       </div>
     )
     )
   }
   }

+ 1 - 1
src/layout/components/TabMenu/src/helper.ts

@@ -29,7 +29,7 @@ export const filterMenusPath = (
     if (!meta.hidden || meta.canTo) {
     if (!meta.hidden || meta.canTo) {
       const allParentPath = getAllParentPath<AppRouteRecordRaw>(allRoutes, v.path)
       const allParentPath = getAllParentPath<AppRouteRecordRaw>(allRoutes, v.path)
 
 
-      const fullPath = isUrl(v.path) ? v.path : allParentPath.join('/')
+      const fullPath = isUrl(v.path) ? v.path : allParentPath.join('/').replace(/\/{2,}/g, '/')
 
 
       data = cloneDeep(v)
       data = cloneDeep(v)
       data.path = fullPath
       data.path = fullPath

+ 13 - 22
src/views/pms/map/Map.vue

@@ -429,14 +429,12 @@ const handleClusterInit = (clusterer: any) => {
       :controls="mapControls"
       :controls="mapControls"
       :min-zoom="3"
       :min-zoom="3"
       @init="handleMapInit"
       @init="handleMapInit"
-      @zoomend="syncCurrentZoom"
-    >
+      @zoomend="syncCurrentZoom">
       <tdt-marker-clusterer
       <tdt-marker-clusterer
         :markers="clusterMarkers"
         :markers="clusterMarkers"
         :styles="clusterStyles"
         :styles="clusterStyles"
         @click="handleMarkerClick"
         @click="handleMarkerClick"
-        @init="handleClusterInit"
-      />
+        @init="handleClusterInit" />
 
 
       <tdt-infowindow
       <tdt-infowindow
         :target="infoWindowTarget"
         :target="infoWindowTarget"
@@ -445,8 +443,7 @@ const handleClusterInit = (clusterer: any) => {
         :offset="[0, -30]"
         :offset="[0, -30]"
         :auto-pan="true"
         :auto-pan="true"
         :close-on-click="true"
         :close-on-click="true"
-        @update:target="handleInfoWindowTargetChange"
-      >
+        @update:target="handleInfoWindowTargetChange">
         <div v-if="activeDevice" class="device-infowindow">
         <div v-if="activeDevice" class="device-infowindow">
           <div class="device-infowindow__list">
           <div class="device-infowindow__list">
             <div class="device-infowindow__row">
             <div class="device-infowindow__row">
@@ -501,8 +498,7 @@ const handleClusterInit = (clusterer: any) => {
           @touchstart.stop
           @touchstart.stop
           @touchmove.stop
           @touchmove.stop
           @touchend.stop
           @touchend.stop
-          @wheel.stop
-        >
+          @wheel.stop>
           <div class="hero-panel__head">
           <div class="hero-panel__head">
             <div>
             <div>
               <div class="hero-panel__kicker">PMS · TianDiTu</div>
               <div class="hero-panel__kicker">PMS · TianDiTu</div>
@@ -513,22 +509,19 @@ const handleClusterInit = (clusterer: any) => {
             </div>
             </div>
             <div
             <div
               class="hero-panel__map-mode -translate-y-1"
               class="hero-panel__map-mode -translate-y-1"
-              :class="mapMode === 'TMAP_SATELLITE_MAP' ? 'is-satellite' : 'is-map'"
-            >
+              :class="mapMode === 'TMAP_SATELLITE_MAP' ? 'is-satellite' : 'is-map'">
               <button
               <button
                 type="button"
                 type="button"
                 class="mode-chip text-gray-6"
                 class="mode-chip text-gray-6"
                 :class="{ 'is-active': mapMode === 'TMAP_NORMAL_MAP' }"
                 :class="{ 'is-active': mapMode === 'TMAP_NORMAL_MAP' }"
-                @click="switchMapMode('TMAP_NORMAL_MAP')"
-              >
+                @click="switchMapMode('TMAP_NORMAL_MAP')">
                 地图
                 地图
               </button>
               </button>
               <button
               <button
                 type="button"
                 type="button"
                 class="mode-chip text-gray-6"
                 class="mode-chip text-gray-6"
                 :class="{ 'is-active': mapMode === 'TMAP_SATELLITE_MAP' }"
                 :class="{ 'is-active': mapMode === 'TMAP_SATELLITE_MAP' }"
-                @click="switchMapMode('TMAP_SATELLITE_MAP')"
-              >
+                @click="switchMapMode('TMAP_SATELLITE_MAP')">
                 卫星
                 卫星
               </button>
               </button>
             </div>
             </div>
@@ -569,8 +562,7 @@ const handleClusterInit = (clusterer: any) => {
           @touchstart.stop
           @touchstart.stop
           @touchmove.stop
           @touchmove.stop
           @touchend.stop
           @touchend.stop
-          @wheel.stop
-        >
+          @wheel.stop>
           <el-form size="default" label-position="top">
           <el-form size="default" label-position="top">
             <el-form-item label="关键词">
             <el-form-item label="关键词">
               <el-input v-model="keyword" clearable placeholder="设备名称 / 编码 / 位置">
               <el-input v-model="keyword" clearable placeholder="设备名称 / 编码 / 位置">
@@ -590,8 +582,7 @@ const handleClusterInit = (clusterer: any) => {
                 filterable
                 filterable
                 clearable
                 clearable
                 :placeholder="t('deviceForm.deptHolder')"
                 :placeholder="t('deviceForm.deptHolder')"
-                @change="handleDeptChange"
-              />
+                @change="handleDeptChange" />
             </el-form-item>
             </el-form-item>
 
 
             <el-form-item label="设备状态">
             <el-form-item label="设备状态">
@@ -602,8 +593,7 @@ const handleClusterInit = (clusterer: any) => {
                   type="button"
                   type="button"
                   class="status-tab"
                   class="status-tab"
                   :class="{ 'is-active': statusFilter === item.value }"
                   :class="{ 'is-active': statusFilter === item.value }"
-                  @click="statusFilter = item.value"
-                >
+                  @click="statusFilter = item.value">
                   {{ item.label }}
                   {{ item.label }}
                 </button>
                 </button>
               </div>
               </div>
@@ -663,8 +653,7 @@ const handleClusterInit = (clusterer: any) => {
       :ifLine="ifLine"
       :ifLine="ifLine"
       :dept="dept"
       :dept="dept"
       :deviceCode="deviceCode"
       :deviceCode="deviceCode"
-      ref="showDrawer"
-    />
+      ref="showDrawer" />
   </div>
   </div>
 </template>
 </template>
 
 
@@ -683,8 +672,10 @@ const handleClusterInit = (clusterer: any) => {
   --slate: #334155;
   --slate: #334155;
 
 
   position: relative;
   position: relative;
+  z-index: 0;
   height: var(--page-height);
   height: var(--page-height);
   overflow: hidden;
   overflow: hidden;
+  isolation: isolate;
   border: 1px solid rgb(203 213 225 / 70%);
   border: 1px solid rgb(203 213 225 / 70%);
   border-radius: 20px;
   border-radius: 20px;
   box-shadow: 0 24px 60px rgb(15 23 42 / 12%);
   box-shadow: 0 24px 60px rgb(15 23 42 / 12%);

+ 5 - 5
src/views/pms/stat/rdkb.vue

@@ -7,8 +7,8 @@ import rdCost from './rdkb/rd-cost.vue'
 import rdExceptionPrompt from './rdkb/rd-exception-prompt.vue'
 import rdExceptionPrompt from './rdkb/rd-exception-prompt.vue'
 import rdValue from './rdkb/rd-value.vue'
 import rdValue from './rdkb/rd-value.vue'
 import rdRate from './rdkb/rd-rate.vue'
 import rdRate from './rdkb/rd-rate.vue'
-import rdUseStatus from './rdkb/rd-use-status.vue'
-import rdDeviceStatus from './rdkb/rd-device-status.vue'
+import rdStatusSwitch from './rdkb/rd-status-switch.vue'
+import rdSafeDay from './rdkb/rd-safe-day.vue'
 import rdDeviceCategory from './rdkb/rd-device-category.vue'
 import rdDeviceCategory from './rdkb/rd-device-category.vue'
 import rdProductionBriefs from './rdkb/rdProductionBriefs.vue'
 import rdProductionBriefs from './rdkb/rdProductionBriefs.vue'
 
 
@@ -105,11 +105,11 @@ onUnmounted(() => {
           <div v-if="activePage === 'home'" class="kb-home-page">
           <div v-if="activePage === 'home'" class="kb-home-page">
             <rdsummary class="kb-stage-card kb-stage-card--1" />
             <rdsummary class="kb-stage-card kb-stage-card--1" />
             <div class="kb-chart-grid">
             <div class="kb-chart-grid">
-              <rd-device-status class="kb-stage-card kb-stage-card--1" />
+              <rd-status-switch class="kb-stage-card kb-stage-card--1" />
               <rd-value class="kb-stage-card kb-stage-card--2" />
               <rd-value class="kb-stage-card kb-stage-card--2" />
-              <rd-device-category class="kb-stage-card kb-stage-card--3" />
+              <rd-safe-day class="kb-stage-card kb-stage-card--3" />
               <rd-rate class="kb-stage-card kb-stage-card--4" />
               <rd-rate class="kb-stage-card kb-stage-card--4" />
-              <rd-use-status class="kb-stage-card kb-stage-card--5" />
+              <rd-device-category class="kb-stage-card kb-stage-card--5" />
               <rd-exception-prompt class="kb-stage-card kb-stage-card--6" />
               <rd-exception-prompt class="kb-stage-card kb-stage-card--6" />
               <rd-cost class="kb-stage-card kb-stage-card--7" />
               <rd-cost class="kb-stage-card kb-stage-card--7" />
               <rd-maintenance-times class="kb-stage-card kb-stage-card--8" />
               <rd-maintenance-times class="kb-stage-card kb-stage-card--8" />

+ 11 - 2
src/views/pms/stat/rdkb/rd-device-status.vue

@@ -7,6 +7,15 @@ interface DeviceStatusItem {
   value: number
   value: number
 }
 }
 
 
+withDefaults(
+  defineProps<{
+    embedded?: boolean
+  }>(),
+  {
+    embedded: false
+  }
+)
+
 const chartData: DeviceStatusItem[] = [
 const chartData: DeviceStatusItem[] = [
   {
   {
     name: '正常',
     name: '正常',
@@ -121,8 +130,8 @@ onUnmounted(() => {
 </script>
 </script>
 
 
 <template>
 <template>
-  <div class="panel device-status-panel flex flex-col">
-    <div class="panel-title">
+  <div :class="[embedded ? 'h-full' : 'panel', 'device-status-panel flex flex-col']">
+    <div v-if="!embedded" class="panel-title">
       <div class="icon-decorator">
       <div class="icon-decorator">
         <span></span>
         <span></span>
         <span></span>
         <span></span>

+ 142 - 0
src/views/pms/stat/rdkb/rd-safe-day.vue

@@ -0,0 +1,142 @@
+<script lang="ts" setup>
+import CountTo from '@/components/count-to1.vue'
+import { IotStatApi } from '@/api/pms/stat'
+
+const RD_DEPT_ID = 163
+const DEFAULT_SAFE_DAYS = 123
+
+const safeDays = ref(DEFAULT_SAFE_DAYS)
+
+async function loadSafeDays() {
+  try {
+    safeDays.value = await IotStatApi.getSafeCount1(RD_DEPT_ID)
+  } catch (error) {
+    console.error('获取瑞都安全生产天数失败:', error)
+  }
+}
+
+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"></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>
+    </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;
+  top: calc(32px * var(--kb-scale, 1));
+  left: 50%;
+  width: calc(320px * var(--kb-scale, 1));
+  height: calc(320px * var(--kb-scale, 1));
+  border: 1px solid rgb(31 91 184 / 15%);
+  border-radius: 999px;
+  transform: translateX(-50%);
+}
+
+.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;
+}
+</style>

+ 66 - 0
src/views/pms/stat/rdkb/rd-status-switch.vue

@@ -0,0 +1,66 @@
+<script lang="ts" setup>
+import rdDeviceStatus from './rd-device-status.vue'
+import rdUseStatus from './rd-use-status.vue'
+
+type ActiveStatus = 'device' | 'use'
+
+const activeStatus = ref<ActiveStatus>('device')
+
+const statusOptions: Array<{ label: string; value: ActiveStatus }> = [
+  { label: '设备状态', value: 'device' },
+  { label: '设备使用状态', value: 'use' }
+]
+
+const activeTitle = computed(() => (activeStatus.value === 'device' ? '设备状态' : '设备使用状态'))
+
+watch(activeStatus, () => {
+  nextTick(() => window.dispatchEvent(new Event('rdkb:resize')))
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title flex items-center justify-between">
+      <div class="kb-panel-title-text flex items-center">
+        <div class="icon-decorator">
+          <span></span>
+          <span></span>
+        </div>
+        {{ activeTitle }}
+      </div>
+      <el-segmented
+        v-model="activeStatus"
+        :options="statusOptions"
+        size="small"
+        class="status-switch" />
+    </div>
+    <div class="flex-1 min-h-0">
+      <rd-device-status v-show="activeStatus === 'device'" embedded />
+      <rd-use-status v-show="activeStatus === 'use'" embedded />
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+
+.status-switch {
+  --el-segmented-item-selected-color: #03409b;
+  --el-segmented-item-selected-bg-color: rgb(255 255 255 / 86%);
+  --el-segmented-bg-color: rgb(31 91 184 / 10%);
+  --el-segmented-item-hover-bg-color: rgb(255 255 255 / 56%);
+
+  min-height: calc(26px * var(--kb-scale, 1));
+  padding: calc(2px * var(--kb-scale, 1));
+  border: 1px solid rgb(31 91 184 / 12%);
+  transform: translateY(calc(-2px * var(--kb-scale, 1)));
+
+  :deep(.el-segmented__item) {
+    min-height: calc(22px * var(--kb-scale, 1));
+    padding: 0 calc(8px * var(--kb-scale, 1));
+    font-size: calc(13px * var(--kb-scale, 1));
+    font-weight: 600;
+    color: #29527f;
+  }
+}
+</style>

+ 11 - 2
src/views/pms/stat/rdkb/rd-use-status.vue

@@ -14,6 +14,15 @@ interface DeviceStatusResponseItem {
   value?: number | string
   value?: number | string
 }
 }
 
 
+withDefaults(
+  defineProps<{
+    embedded?: boolean
+  }>(),
+  {
+    embedded: false
+  }
+)
+
 const RD_DEPT_ID = 163
 const RD_DEPT_ID = 163
 const chartRef = ref<HTMLDivElement>()
 const chartRef = ref<HTMLDivElement>()
 const loading = ref(false)
 const loading = ref(false)
@@ -155,8 +164,8 @@ onUnmounted(() => {
 </script>
 </script>
 
 
 <template>
 <template>
-  <div class="panel use-status-panel flex flex-col">
-    <div class="panel-title">
+  <div :class="[embedded ? 'h-full' : 'panel', 'use-status-panel flex flex-col']">
+    <div v-if="!embedded" class="panel-title">
       <div class="icon-decorator">
       <div class="icon-decorator">
         <span></span>
         <span></span>
         <span></span>
         <span></span>

+ 2 - 2
src/views/pms/stat/rhkb.vue

@@ -108,8 +108,8 @@ onUnmounted(() => {
             <div class="kb-chart-grid">
             <div class="kb-chart-grid">
               <deviceStatus class="kb-stage-card kb-stage-card--1" />
               <deviceStatus class="kb-stage-card kb-stage-card--1" />
               <deviceType class="kb-stage-card kb-stage-card--2" />
               <deviceType class="kb-stage-card kb-stage-card--2" />
-              <equipmentRate class="kb-stage-card kb-stage-card--3" />
-              <rhsafeday class="kb-stage-card kb-stage-card--4" />
+              <rhsafeday class="kb-stage-card kb-stage-card--3" />
+              <equipmentRate class="kb-stage-card kb-stage-card--4" />
               <gasStatistics class="kb-stage-card kb-stage-card--5" />
               <gasStatistics class="kb-stage-card kb-stage-card--5" />
               <exceptionPrompt class="kb-stage-card kb-stage-card--6" />
               <exceptionPrompt class="kb-stage-card kb-stage-card--6" />
               <assetValue class="kb-stage-card kb-stage-card--7" />
               <assetValue class="kb-stage-card kb-stage-card--7" />