Ver código fonte

feat: 调整智慧大屏检测对象、安装位置文案的映射;

liujia 1 mês atrás
pai
commit
4f83bac

+ 1 - 0
src/api/stats/types.ts

@@ -113,6 +113,7 @@ type AgeList = {
 type GuardList = {
   guardType: string // 监测对象类型
   count: number // 数量
+  name?: string // 监测对象类型名称
 }
 type InstallPositionList = {
   installPosition: string // 	安装位置

+ 9 - 9
src/hooks/useDashboardPolling.ts

@@ -19,7 +19,7 @@ export function useDashboardPolling(options: { tenantId: string; queryType: stri
   let retryCountRealtime = 0
   let retryCountBusiness = 0
 
-  // 获取今日数据
+  // 获取今日数据 (实时数据)
   const getTodayData = async () => {
     if (!isVisible) return
     try {
@@ -37,8 +37,8 @@ export function useDashboardPolling(options: { tenantId: string; queryType: stri
     }
   }
 
-  // 获取历史跌打倒和告警数据
-  const getBusinessData = async () => {
+  // 获取历史数据 (跌倒和告警)
+  const getHistoryData = async () => {
     if (!isVisible) return
     try {
       const [fallRes, alarmRes] = await Promise.all([
@@ -56,18 +56,18 @@ export function useDashboardPolling(options: { tenantId: string; queryType: stri
       retryCountBusiness = 0
     } catch (err) {
       retryCountBusiness++
-      console.warn(`业务数据获取失败(第 ${retryCountBusiness} 次)`, err)
+      console.warn(`获取历史数据 获取失败(第 ${retryCountBusiness} 次)`, err)
       if (retryCountBusiness < MAX_RETRY) {
-        setTimeout(getBusinessData, 2000)
+        setTimeout(getHistoryData, 2000)
       }
     }
   }
 
   const startPolling = () => {
     getTodayData()
-    getBusinessData()
-    realtimeTimer = setInterval(getTodayData, 3000) // 3秒获取一次
-    businessTimer = setInterval(getBusinessData, 30000) // 30秒获取一次
+    getHistoryData()
+    realtimeTimer = setInterval(getTodayData, 5000) // 5秒获取一次 实时数据
+    businessTimer = setInterval(getHistoryData, 30000) // 30秒获取一次 历史数据
   }
 
   const stopPolling = () => {
@@ -96,7 +96,7 @@ export function useDashboardPolling(options: { tenantId: string; queryType: stri
     document.removeEventListener('visibilitychange', handleVisibilityChange)
   })
 
-  console.log('🚀🚀🚀@@useDashboardPolling:', todayScreenData, fallHistoryData, alarmHistoryData)
+  console.log('🚀🚀🚀useDashboardPolling:', todayScreenData, fallHistoryData, alarmHistoryData)
 
   return {
     todayScreenData,

+ 26 - 5
src/hooks/useDict.ts

@@ -7,14 +7,30 @@ export interface DictItem {
 }
 
 /**
- * 获取字典值列表
- * @param dicType 字典类型
- * @example 使用示例
- * const { dictList, fetchDict } = useDict('DIC_TYPE')
- * @enum { string } institution_type 机构类型
+ * 获取指定类型的字典数据,并返回列表和映射表
+ *
+ * @param dicType 字典类型标识,例如 'institution_type'、'guardianship_type' 等
+ * @example 返回值结构:
+ *  {
+ *   dictList: Ref<DictItem[]> 字典项列表,包含 label 和 value
+ *   dictNameMap: Ref<Record<string, string>> 字典项映射表,value → label
+ *   fetchDict: () => Promise<void> 异步方法,用于拉取字典数据
+ * }
+ *
+ * @example 使用示例:
+ * const { dictList, dictNameMap, fetchDict } = useDict('institution_type')
+ * await fetchDict()
+ * const name = dictNameMap.value['A01'] || '未知'
+ *
+ * @enum {string}
+ * institution_type 机构类型
+ * * guardianship_type 监护类型
+ * * device_type 设备类型
+ * * install_position 安装位置
  */
 export const useDict = (dicType: string) => {
   const dictList = ref<DictItem[]>([])
+  const dictNameMap = ref<Record<string, string>>({})
 
   const fetchDict = async () => {
     console.log('👏👏👏useDict fetchDict', dicType)
@@ -27,14 +43,19 @@ export const useDict = (dicType: string) => {
             value: item.itemCode,
           }))
         : []
+
+      // 构建 value → label 映射表
+      dictNameMap.value = Object.fromEntries(dictList.value.map((item) => [item.value, item.label]))
     } catch (error) {
       console.error('获取字典数据失败:', error)
       dictList.value = []
+      dictNameMap.value = {}
     }
   }
 
   return {
     dictList,
+    dictNameMap,
     fetchDict,
   }
 }

+ 2 - 2
src/views/dashboard/components/ElderActivityCard/index.vue

@@ -18,14 +18,14 @@ import TechCard from '../TechCard/index.vue'
 defineOptions({ name: 'ElderActivityCard' })
 
 type Props = {
-  activityRate: number
+  activityRate: number | null
 }
 
 const props = withDefaults(defineProps<Props>(), {
   activityRate: 0,
 })
 
-const rate = computed(() => Number(props.activityRate.toFixed(2)))
+const rate = computed(() => Number(props.activityRate && props.activityRate.toFixed(2)))
 
 const chartOption = computed(() => ({
   tooltip: {

+ 18 - 3
src/views/dashboard/components/ObjectDistributionCard/index.vue

@@ -14,6 +14,7 @@ defineOptions({ name: 'ObjectDistributionCard' })
 interface GuardItem {
   guardType: string
   count: string | number
+  name?: string
 }
 
 interface Props {
@@ -23,7 +24,7 @@ interface Props {
 const props = defineProps<Props>()
 
 // 提取分类名称和数量
-const categories = computed(() => props.guardList.map((item) => item.guardType))
+const categories = computed(() => props.guardList.map((item) => item.name))
 const counts = computed(() => props.guardList.map((item) => Number(item.count)))
 
 // 配色方案
@@ -39,12 +40,26 @@ const chartOption = computed(() => ({
     left: 40,
     right: 20,
     top: 20,
-    bottom: 40,
+    bottom: 60,
   },
   xAxis: {
     type: 'category',
     data: categories.value,
-    axisLabel: { color: '#9cc5e0' },
+    axisLabel: {
+      color: '#9cc5e0',
+      interval: 0,
+      fontSize: 10,
+      margin: 10,
+      formatter: (value: string) => {
+        const lines = value.length > 4 ? value.match(/.{1,2}/g) : [value]
+        return lines?.map((line) => `{label|${line}}`).join('\n')
+      },
+      rich: {
+        label: {
+          lineHeight: 14,
+        },
+      },
+    },
     axisTick: { show: false },
     axisLine: { show: false },
   },

+ 23 - 11
src/views/dashboard/index.vue

@@ -51,9 +51,6 @@
         </div>
 
         <div class="data-line">
-          <!-- <AlarmHistoryCard></AlarmHistoryCard>
-          <FallingHistoryCard></FallingHistoryCard> -->
-
           <HistoryChartCard
             title="历史告警统计"
             :dayStatInfo="[
@@ -113,15 +110,13 @@ import PeopleDetectedCard from './components/PeopleDetectedCard/index.vue'
 import DeviceLocationCard from './components/DeviceLocationCard/index.vue'
 import DeviceAgeCard from './components/DeviceAgeCard/index.vue'
 import ObjectDistributionCard from './components/ObjectDistributionCard/index.vue'
-// import AlarmHistoryCard from './components/AlarmHistoryCard/index.vue'
-// import FallingHistoryCard from './components/FallingHistoryCard/index.vue'
 import HistoryChartCard from './components/HistoryChartCard/index.vue'
 import { useUserStore } from '@/stores/user'
 import type { TodayData } from './types'
 import { ZoomInOutlined, ZoomOutOutlined, RedoOutlined } from '@ant-design/icons-vue'
 import { useResponsiveLayout } from '@/utils/chartManager'
 import { useDashboardPolling } from '@/hooks/useDashboardPolling'
-import { deviceInstallPositionNameMap } from '@/const/device'
+import { useDict } from '@/hooks/useDict'
 
 const userStore = useUserStore()
 const tenantName = (userStore.userInfo.tenantName ?? '雷能技术') + ' 智慧大屏'
@@ -171,21 +166,38 @@ const { todayScreenData, fallHistoryData, alarmHistoryData } = useDashboardPolli
   queryType: 'day',
 })
 
+const { fetchDict: fetchDictGuardianship, dictNameMap: guardTypeNameMap } =
+  useDict('guardianship_type')
+fetchDictGuardianship()
+
+const { fetchDict: fetchDictInstallPosition, dictNameMap: installPositionNameMap } =
+  useDict('install_position')
+fetchDictInstallPosition().then(() => {
+  console.log('🚀🚀🚀 installPositionNameMap.value', installPositionNameMap.value)
+})
+
 watch(
   () => todayScreenData.value,
   (val) => {
     console.log('todayScreenData更新了', val)
     todayData.value = val || todayData.value
-    todayData.value.guardList = val?.guardList ?? []
-    todayData.value.ageList = val?.ageList ?? []
+    todayData.value.ageList = val?.ageList.filter((item) => item.count > 0) ?? []
     todayData.value.installPositionList =
       val?.installPositionList.map((item) => ({
         ...item,
         name:
-          deviceInstallPositionNameMap[
-            item.installPosition as keyof typeof deviceInstallPositionNameMap
+          installPositionNameMap.value[
+            item.installPosition as keyof typeof installPositionNameMap.value
           ] || '未知',
-      })) || []
+        names: installPositionNameMap.value,
+      })) ?? []
+    todayData.value.guardList =
+      todayData.value.guardList.map((item) => ({
+        ...item,
+        name:
+          guardTypeNameMap.value[item.guardType as keyof typeof guardTypeNameMap.value] || '未知',
+        names: guardTypeNameMap.value,
+      })) ?? []
   },
   { immediate: true }
 )

+ 7 - 15
src/views/device/detail/components/deviceBaseConfig/index.vue

@@ -31,11 +31,8 @@
           v-model:value="baseFormState.installPosition"
           placeholder="请选择安装位置"
           :style="inputStyle"
+          :options="installPositionOptions"
         >
-          <a-select-option value="Toilet">卫生间</a-select-option>
-          <a-select-option value="Bedroom">卧室</a-select-option>
-          <a-select-option value="LivingRoom">客厅</a-select-option>
-          <a-select-option value="Restaurant">餐厅</a-select-option>
         </a-select>
       </a-form-item>
 
@@ -501,19 +498,14 @@ const fetchTenantList = async () => {
 }
 fetchTenantList()
 
-const guardTypeOptions = ref<{ label: string; value: string }[]>([])
-const { dictList, fetchDict } = useDict('guardianship_type')
-
-const fetchGuardTypeOptions = async () => {
-  fetchDict().then(() => {
-    guardTypeOptions.value = dictList.value.map((item) => ({
-      label: item.label,
-      value: item.value as string,
-    }))
-  })
-}
+const { dictList: guardTypeOptions, fetchDict: fetchGuardTypeOptions } =
+  useDict('guardianship_type')
 fetchGuardTypeOptions()
 
+const { dictList: installPositionOptions, fetchDict: fetchDictInstallPosition } =
+  useDict('install_position')
+fetchDictInstallPosition()
+
 // 获取设备回显数据
 const fetchDeviceBaseInfo = async () => {
   console.log('fetchDeviceDetail', props)

+ 10 - 3
src/views/device/detail/index.vue

@@ -217,8 +217,8 @@
           <info-item label="安装位置">
             <template v-if="detailState.clientId">
               {{
-                deviceInstallPositionNameMap[
-                  detailState.installPosition as keyof typeof deviceInstallPositionNameMap
+                installPositionNameMap[
+                  detailState.installPosition as keyof typeof installPositionNameMap
                 ]
               }}
             </template>
@@ -347,7 +347,7 @@ import type { Furniture } from '@/api/room/types'
 import mqtt, { MqttClient } from 'mqtt'
 import * as deviceApi from '@/api/device'
 import type { DeviceDetailData } from '@/api/device/types'
-import { deviceOnlineStateMap, deviceInstallPositionNameMap } from '@/const/device'
+import { deviceOnlineStateMap } from '@/const/device'
 import deviceConfigDrawer from './components/deviceConfig/index.vue'
 import deviceStatsDrawer from './components/deviceStatsDrawer/index.vue'
 import BreathLineChart from './components/breathLineChart/index.vue'
@@ -360,6 +360,7 @@ import * as alarmApi from '@/api/alarm'
 import { Empty } from 'ant-design-vue'
 const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE
 import { getOriginPosition } from '@/utils'
+import { useDict } from '@/hooks/useDict'
 
 defineOptions({
   name: 'DeviceDetail',
@@ -463,6 +464,8 @@ const detailState = ref<DeviceDetailData>({
   tenantName: '',
   tenantId: '',
   fallingConfirm: null,
+  age: null,
+  guardianshipType: null,
 })
 
 const spinning = ref(false)
@@ -514,6 +517,10 @@ const fetchDeviceDetail = async () => {
 }
 fetchDeviceDetail()
 
+const { fetchDict: fetchDictInstallPosition, dictNameMap: installPositionNameMap } =
+  useDict('install_position')
+fetchDictInstallPosition()
+
 const saveConfigSuccess = () => {
   setTimeout(() => {
     fetchDeviceDetail()