Explorar o código

feat(stat): 优化瑞都看板状态与安全生产展示

- 合并设备状态和设备使用状态看板,支持标签切换
- 新增瑞都安全生产天数看板并接入部门数据
- 调整瑞都看板九宫格展示顺序
Zimo hai 2 semanas
pai
achega
0dc04b1ca2

+ 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 rdValue from './rdkb/rd-value.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 rdProductionBriefs from './rdkb/rdProductionBriefs.vue'
 
@@ -105,11 +105,11 @@ onUnmounted(() => {
           <div v-if="activePage === 'home'" class="kb-home-page">
             <rdsummary class="kb-stage-card kb-stage-card--1" />
             <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-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-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-cost class="kb-stage-card kb-stage-card--7" />
               <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
 }
 
+withDefaults(
+  defineProps<{
+    embedded?: boolean
+  }>(),
+  {
+    embedded: false
+  }
+)
+
 const chartData: DeviceStatusItem[] = [
   {
     name: '正常',
@@ -121,8 +130,8 @@ onUnmounted(() => {
 </script>
 
 <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">
         <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
 }
 
+withDefaults(
+  defineProps<{
+    embedded?: boolean
+  }>(),
+  {
+    embedded: false
+  }
+)
+
 const RD_DEPT_ID = 163
 const chartRef = ref<HTMLDivElement>()
 const loading = ref(false)
@@ -155,8 +164,8 @@ onUnmounted(() => {
 </script>
 
 <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">
         <span></span>
         <span></span>

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

@@ -108,8 +108,8 @@ onUnmounted(() => {
             <div class="kb-chart-grid">
               <deviceStatus class="kb-stage-card kb-stage-card--1" />
               <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" />
               <exceptionPrompt class="kb-stage-card kb-stage-card--6" />
               <assetValue class="kb-stage-card kb-stage-card--7" />