|
|
@@ -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>
|
|
|
-
|