xiongxing vor 2 Wochen
Ursprung
Commit
abf301c44f

+ 1 - 0
src/views/Home/COMPLETION_REPORT.md

@@ -384,3 +384,4 @@ npm run dev
 
 
 
+

+ 1 - 0
src/views/Home/FILES_CREATED.md

@@ -353,3 +353,4 @@
 
 
 
+

+ 1 - 0
src/views/Home/IMPLEMENTATION_CHECKLIST.md

@@ -257,3 +257,4 @@
 **最终状态**: ✅ 通过
 
 
+

+ 1 - 0
src/views/Home/QUICK_REFERENCE.md

@@ -265,3 +265,4 @@ npm run dev
 
 
 
+

+ 1 - 0
src/views/Home/README.md

@@ -346,3 +346,4 @@ A: 点击右上角关闭按钮或按 ESC 键。
 **状态**: 生产就绪 ✅
 
 
+

+ 1 - 0
src/views/Home/REFACTORING_GUIDE.md

@@ -497,3 +497,4 @@ A: 检查以下几点:
 
 
 
+

+ 1 - 0
src/views/Home/REFACTORING_SUMMARY.md

@@ -399,3 +399,4 @@ A: 参考现有的对话框组件(如 `AddDeviceDialog.vue`),创建新的
 
 
 
+

+ 1 - 0
src/views/Home/START_HERE.md

@@ -289,3 +289,4 @@ npm run dev
 
 
 
+

+ 1 - 0
src/views/Home/components/DeviceDetailDialog.vue

@@ -302,3 +302,4 @@ $primary-color: #1a73e8;
   }
 }
 </style>
+

+ 66 - 74
src/views/Home/components/RealtimeFeedDrawer.vue

@@ -7,30 +7,35 @@
     append-to-body
     class="realtime-feed-drawer"
   >
-    <div class="feed-container">
-      <div class="feed-header">
-        <div class="summary">
-          <span>实时事件(最多100条)</span>
-          <span class="count">{{ items.length }}</span>
-        </div>
-        <div class="actions">
-          <el-button size="small" @click="clear">清空</el-button>
-        </div>
-      </div>
-
-      <vue3-seamless-scroll class="feed-scroll" :list="items" :class-option="seamlessOption">
-        <div class="feed-item" v-for="(it, idx) in items" :key="it.id || idx">
+    <div style="overflow: hidden; height: 800px">
+      <vue3-seamless-scroll
+        ref="seamlessScrollRef"
+        class="feed-scroll"
+        :list="items"
+        :step="0.5"
+        :visibleCount="1"
+        :hover="true"
+        direction="up"
+        :singleHeight="0"
+        :singleWaitTime="0"
+        style="overflow: hidden; height: auto"
+        v-if="items.length"
+      >
+        <!-- v-if="items.length" -->
+        <div class="feed-item" v-for="(it, idx) in items" :key="it._k">
           <div class="left-mark" :class="it.type"></div>
           <div class="item-main">
             <div class="row top">
-              <span class="tag" :class="it.type">{{ it.type === 'location' ? '定位' : '健康' }}</span>
+              <span class="tag" :class="it.type"
+                >{{ it.type === 'location' ? '定位' : '健康' }}-#{{ idx + 1 }}</span
+              >
               <span class="time">{{ it.time }}</span>
             </div>
 
             <template v-if="it.type === 'location'">
               <div class="row">
-                <span class="label">经纬度:</span>
-                <span class="text">{{ it.longitude.toFixed(6) }}, {{ it.latitude.toFixed(6) }}</span>
+                <span class="label">长者姓名:</span>
+                <span class="text">{{ it.elderName }}</span>
               </div>
               <div class="row" v-if="it.address">
                 <span class="label">位置:</span>
@@ -40,12 +45,21 @@
 
             <template v-else>
               <div class="row">
-                <span class="label">长者:</span>
+                <span class="label">长者姓名:</span>
                 <span class="text">{{ it.elderName || '-' }}</span>
               </div>
               <div class="row">
-                <span class="label">消息:</span>
-                <span class="text">{{ it.message }}</span>
+                <span class="label">健康消息:</span>
+                <div>
+                  <div
+                    class="text"
+                    v-for="(indicator, keyIndex) in it.indicatorResults"
+                    :key="keyIndex"
+                  >
+                    <span class="label">{{ indicator.indicatorName }}:</span>
+                    <span class="text">{{ indicator.message }}-{{ indicator.suggestion }}</span>
+                  </div>
+                </div>
               </div>
             </template>
           </div>
@@ -56,7 +70,7 @@
 </template>
 
 <script lang="ts" setup>
-import { reactive, ref, computed, watch } from 'vue'
+import { reactive, ref, computed, watch, nextTick } from 'vue'
 import { amapReverseGeocode } from '@/utils/amapService'
 
 interface FeedLocation {
@@ -65,15 +79,24 @@ interface FeedLocation {
   longitude: number
   latitude: number
   address?: string
-  id?: string
+  elderName?: string
+  _k?: string
+}
+
+interface IndicatorResultsOv {
+  indicatorName: string
+  value: number
+  status: string
+  message: string
+  suggestion: string
 }
 
 interface FeedHealth {
   type: 'health'
   time: string
-  message: string
   elderName?: string
-  id?: string
+  indicatorResults: IndicatorResultsOv[]
+  _k?: string
 }
 
 type FeedItem = FeedLocation | FeedHealth
@@ -113,17 +136,11 @@ watch(
 )
 
 const items = ref<FeedItem[]>([])
-const MAX = 100
+const MAX = 15
+const seamlessScrollRef = ref()
 
-// 滚动配置
-const seamlessOption = reactive({
-  step: 0.5,
-  limitMoveNum: 1,
-  hoverStop: true,
-  direction: 'up',
-  singleHeight: 72,
-  waitTime: 800
-})
+let _seq = 0
+const genKey = () => `${Date.now()}_${++_seq}_${Math.random().toString(36).slice(2, 8)}`
 
 // 地址缓存,key: "lng.toFixed(6),lat.toFixed(6)"
 const addrCache: Record<string, string> = {}
@@ -132,6 +149,10 @@ const ensureMax = () => {
   if (items.value.length > MAX) {
     items.value.splice(0, items.value.length - MAX)
   }
+  // nextTick(() => {
+  //   console.log('seamlessScrollRef.value', seamlessScrollRef.value)
+  //   seamlessScrollRef.value?.reset()
+  // })
 }
 
 const toTimeText = (t?: any) => {
@@ -145,7 +166,7 @@ const pushLocation = async (payload: {
   longitude: number | string
   latitude: number | string
   locationTime?: string | number
-  id?: string
+  elderName?: string
 }) => {
   const lng = Number(payload.longitude)
   const lat = Number(payload.latitude)
@@ -161,32 +182,29 @@ const pushLocation = async (payload: {
   }
 
   items.value.push({
+    _k: genKey(),
     type: 'location',
     time: toTimeText(payload.locationTime),
     longitude: lng,
     latitude: lat,
     address: addrCache[key],
-    id: payload.id
+    elderName: payload.elderName
   })
   ensureMax()
 }
 
 // 对外方法:推入健康
-const pushHealth = (payload: { time?: string | number; message: string; elderName?: string; id?: string }) => {
+const pushHealth = (payload: FeedHealth) => {
   items.value.push({
+    _k: genKey(),
     type: 'health',
     time: toTimeText(payload.time),
-    message: payload.message,
     elderName: payload.elderName,
-    id: payload.id
+    indicatorResults: payload.indicatorResults
   })
   ensureMax()
 }
 
-const clear = () => {
-  items.value = []
-}
-
 defineExpose({ pushLocation, pushHealth })
 </script>
 
@@ -194,35 +212,9 @@ defineExpose({ pushLocation, pushHealth })
 .realtime-feed-drawer {
   color: #fff;
 }
-.feed-container {
-  display: flex;
-  height: 100%;
-  flex-direction: column;
-}
-.feed-header {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding-bottom: 8px;
-  margin-bottom: 8px;
-  border-bottom: 1px solid rgb(255 255 255 / 10%);
-  .summary {
-    display: flex;
-    align-items: center;
-    gap: 8px;
-    .count {
-      padding: 2px 8px;
-      font-size: 12px;
-      color: #fff;
-      background: rgb(255 255 255 / 20%);
-      border-radius: 12px;
-    }
-  }
-}
-.feed-scroll {
-  flex: 1;
-  overflow: hidden;
-}
+// .feed-container {
+//   height: 800px;
+// }
 .feed-item {
   display: flex;
   padding: 10px 12px;
@@ -245,7 +237,7 @@ defineExpose({ pushLocation, pushHealth })
 .row {
   display: flex;
   gap: 6px;
-  align-items: center;
+  align-items: flex-start;
   line-height: 1.6;
 }
 .row.top {
@@ -253,7 +245,7 @@ defineExpose({ pushLocation, pushHealth })
 }
 .tag {
   padding: 2px 8px;
-  font-size: 12px;
+  font-size: 16px;
   border-radius: 10px;
 }
 .tag.location {
@@ -269,10 +261,10 @@ defineExpose({ pushLocation, pushHealth })
 }
 .text {
   color: #fff;
+  flex: 1;
 }
 .time {
   color: #cfd3dc;
   font-size: 12px;
 }
 </style>
-

+ 1 - 0
src/views/Home/components/StatusBar.vue

@@ -85,3 +85,4 @@ $warning-color: #fd9644;
 
 
 
+

+ 1 - 0
src/views/Home/components/TopInfoBar.vue

@@ -159,3 +159,4 @@ $text-gray: #8a8f98;
 
 
 
+

+ 18 - 10
src/views/Home/home-refactored.vue

@@ -843,17 +843,18 @@ const handleHealthAlert = async (healthAlert: any) => {
 
 const handleLocationAlert = async (locationAlert: any) => {
   try {
-    const payload = locationAlert?.data || locationAlert || {}
-    const elderId = Number(payload.elderId || payload.elderID || payload.elder_id || 0)
+    const payload = locationAlert?.data || {}
+    const elderId = Number(payload.elderId || 0)
     const lng = Number(payload.longitude)
     const lat = Number(payload.latitude)
-    const ts = payload.locationTime || payload.timestamp || payload.time || Date.now()
+    const ts = locationAlert.timestamp
 
     // 推入右上角实时抽屉
     realtimeDrawerRef.value?.pushLocation?.({
       longitude: lng,
       latitude: lat,
-      locationTime: ts
+      locationTime: ts,
+      elderName: payload.elderName
     })
 
     // 地图联动:只对当前选中长者联动地图
@@ -874,7 +875,11 @@ const handleLocationAlert = async (locationAlert: any) => {
 }
 
 const handleHealthUpdateAlert = async (healthUpdateAlert: any) => {
-  console.log('healthUpdateAlert', healthUpdateAlert)
+  realtimeDrawerRef.value?.pushHealth?.({
+    time: healthUpdateAlert.timestamp,
+    elderName: healthUpdateAlert.data.elderName,
+    indicatorResults: healthUpdateAlert.data.realtimeHealthAnalysisResult.indicatorResults
+  })
 }
 
 // WebSocket 连接
@@ -956,10 +961,11 @@ $bg-accent-2: rgb(123 97 255 / 25%);
   position: relative;
   padding: 20px;
   overflow: hidden;
-  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
-    sans-serif;
+  font-family:
+    -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
   color: $text-light;
-  background: radial-gradient(circle at 10% 20%, rgb(15 88 255 / 25%) 0%, transparent 35%),
+  background:
+    radial-gradient(circle at 10% 20%, rgb(15 88 255 / 25%) 0%, transparent 35%),
     radial-gradient(circle at 90% 10%, rgb(20 201 201 / 18%) 0%, transparent 30%),
     linear-gradient(135deg, $bg-gradient-start 0%, $bg-gradient-mid 45%, $bg-gradient-end 100%);
 
@@ -995,7 +1001,8 @@ $bg-accent-2: rgb(123 97 255 / 25%);
   z-index: -2;
   width: 100%;
   height: 100%;
-  background: radial-gradient(circle at 20% 30%, rgb(34 123 255 / 18%) 0%, transparent 45%),
+  background:
+    radial-gradient(circle at 20% 30%, rgb(34 123 255 / 18%) 0%, transparent 45%),
     radial-gradient(circle at 80% 70%, rgb(123 97 255 / 15%) 0%, transparent 50%),
     radial-gradient(circle at 50% 50%, rgb(0 255 240 / 8%) 0%, transparent 55%);
   opacity: 0.5;
@@ -1009,7 +1016,8 @@ $bg-accent-2: rgb(123 97 255 / 25%);
   z-index: -1;
   width: 100%;
   height: 100%;
-  background-image: linear-gradient(rgb(255 255 255 / 5%) 1px, transparent 1px),
+  background-image:
+    linear-gradient(rgb(255 255 255 / 5%) 1px, transparent 1px),
     linear-gradient(90deg, rgb(255 255 255 / 5%) 1px, transparent 1px);
   background-position: center;
   background-size: 40px 40px;