Browse Source

虚拟列表,状态过滤

master
xh 1 week ago
parent
commit
14837a9a53
  1. 1
      web/package.json
  2. 38
      web/pnpm-lock.yaml
  3. 73
      web/src/views/HandDevice/Home/components/TopPanel.vue
  4. 6
      web/src/views/HandDevice/Home/components/constants/map.constants.ts
  5. 3
      web/src/views/HandDevice/Home/components/types/map.types.ts
  6. 283
      web/src/views/HandDevice/Home/index.vue

1
web/package.json

@ -76,6 +76,7 @@
"vue-i18n": "11.1.12", "vue-i18n": "11.1.12",
"vue-router": "4.4.5", "vue-router": "4.4.5",
"vue-types": "^5.1.1", "vue-types": "^5.1.1",
"vue-virtual-scroller": "2.0.0-beta.8",
"vue3-signature": "^0.2.4", "vue3-signature": "^0.2.4",
"vuedraggable": "^4.1.0", "vuedraggable": "^4.1.0",
"web-storage-cache": "^1.1.1", "web-storage-cache": "^1.1.1",

38
web/pnpm-lock.yaml

@ -167,6 +167,9 @@ importers:
vue-types: vue-types:
specifier: ^5.1.1 specifier: ^5.1.1
version: 5.1.3(vue@3.5.24(typescript@5.3.3)) version: 5.1.3(vue@3.5.24(typescript@5.3.3))
vue-virtual-scroller:
specifier: 2.0.0-beta.8
version: 2.0.0-beta.8(vue@3.5.24(typescript@5.3.3))
vue3-signature: vue3-signature:
specifier: ^0.2.4 specifier: ^0.2.4
version: 0.2.4(vue@3.5.24(typescript@5.3.3)) version: 0.2.4(vue@3.5.24(typescript@5.3.3))
@ -4267,6 +4270,9 @@ packages:
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
engines: {node: '>=16 || 14 >=14.17'} engines: {node: '>=16 || 14 >=14.17'}
mitt@2.1.0:
resolution: {integrity: sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg==}
mitt@3.0.1: mitt@3.0.1:
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
@ -5392,6 +5398,16 @@ packages:
peerDependencies: peerDependencies:
vue: ^3.0.0 vue: ^3.0.0
vue-observe-visibility@2.0.0-alpha.1:
resolution: {integrity: sha512-flFbp/gs9pZniXR6fans8smv1kDScJ8RS7rEpMjhVabiKeq7Qz3D9+eGsypncjfIyyU84saU88XZ0zjbD6Gq/g==}
peerDependencies:
vue: ^3.0.0
vue-resize@2.0.0-alpha.1:
resolution: {integrity: sha512-7+iqOueLU7uc9NrMfrzbG8hwMqchfVfSzpVlCMeJQe4pyibqyoifDNbKTZvwxZKDvGkB+PdFeKvnGZMoEb8esg==}
peerDependencies:
vue: ^3.0.0
vue-router@4.4.5: vue-router@4.4.5:
resolution: {integrity: sha512-4fKZygS8cH1yCyuabAXGUAsyi1b2/o/OKgu/RUb+znIYOxPRxdkytJEx+0wGcpBE1pX6vUgh5jwWOKRGvuA/7Q==} resolution: {integrity: sha512-4fKZygS8cH1yCyuabAXGUAsyi1b2/o/OKgu/RUb+znIYOxPRxdkytJEx+0wGcpBE1pX6vUgh5jwWOKRGvuA/7Q==}
peerDependencies: peerDependencies:
@ -5415,6 +5431,11 @@ packages:
vue: vue:
optional: true optional: true
vue-virtual-scroller@2.0.0-beta.8:
resolution: {integrity: sha512-b8/f5NQ5nIEBRTNi6GcPItE4s7kxNHw2AIHLtDp+2QvqdTjVN0FgONwX9cr53jWRgnu+HRLPaWDOR2JPI5MTfQ==}
peerDependencies:
vue: ^3.2.0
vue3-signature@0.2.4: vue3-signature@0.2.4:
resolution: {integrity: sha512-XFwwFVK9OG3F085pKIq2SlNVqx32WdFH+TXbGEWc5FfEKpx8oMmZuAwZZ50K/pH2FgmJSE8IRwU9DDhrLpd6iA==} resolution: {integrity: sha512-XFwwFVK9OG3F085pKIq2SlNVqx32WdFH+TXbGEWc5FfEKpx8oMmZuAwZZ50K/pH2FgmJSE8IRwU9DDhrLpd6iA==}
peerDependencies: peerDependencies:
@ -9988,6 +10009,8 @@ snapshots:
minipass@7.1.2: {} minipass@7.1.2: {}
mitt@2.1.0: {}
mitt@3.0.1: {} mitt@3.0.1: {}
mlly@1.8.0: mlly@1.8.0:
@ -11149,6 +11172,14 @@ snapshots:
'@vue/devtools-api': 6.6.4 '@vue/devtools-api': 6.6.4
vue: 3.5.24(typescript@5.3.3) vue: 3.5.24(typescript@5.3.3)
vue-observe-visibility@2.0.0-alpha.1(vue@3.5.24(typescript@5.3.3)):
dependencies:
vue: 3.5.24(typescript@5.3.3)
vue-resize@2.0.0-alpha.1(vue@3.5.24(typescript@5.3.3)):
dependencies:
vue: 3.5.24(typescript@5.3.3)
vue-router@4.4.5(vue@3.5.24(typescript@5.3.3)): vue-router@4.4.5(vue@3.5.24(typescript@5.3.3)):
dependencies: dependencies:
'@vue/devtools-api': 6.6.4 '@vue/devtools-api': 6.6.4
@ -11172,6 +11203,13 @@ snapshots:
optionalDependencies: optionalDependencies:
vue: 3.5.24(typescript@5.3.3) vue: 3.5.24(typescript@5.3.3)
vue-virtual-scroller@2.0.0-beta.8(vue@3.5.24(typescript@5.3.3)):
dependencies:
mitt: 2.1.0
vue: 3.5.24(typescript@5.3.3)
vue-observe-visibility: 2.0.0-alpha.1(vue@3.5.24(typescript@5.3.3))
vue-resize: 2.0.0-alpha.1(vue@3.5.24(typescript@5.3.3))
vue3-signature@0.2.4(vue@3.5.24(typescript@5.3.3)): vue3-signature@0.2.4(vue@3.5.24(typescript@5.3.3)):
dependencies: dependencies:
default-passive-events: 2.0.0 default-passive-events: 2.0.0

73
web/src/views/HandDevice/Home/components/TopPanel.vue

@ -19,14 +19,14 @@
</div> </div>
</div> </div>
<div class="top-right"> <div class="top-right">
<!-- <span class="legend-title">报警图例</span> -->
<div class="normal-legend">正常状态</div>
<div class="alarm1-legend">围栏报警</div>
<div class="alarm2-legend">气体报警</div>
<div class="normal-legend" :class="{'is-active': selectStatus.includes('normal')}" @click="toggleStatus('normal')">正常状态</div>
<div class="fence-legend" :class="{'is-active': selectStatus.includes('fence')}" @click="toggleStatus('fence')">围栏报警</div>
<div class="alarm-legend" :class="{'is-active': selectStatus.includes('alarm')}" @click="toggleStatus('alarm')">气体报警</div>
<div class="offline-legend" :class="{'is-active': selectStatus.includes('offline')}" @click="toggleStatus('offline')">离线状态</div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
// import { ref, computed } from 'vue'
import { ref, computed,defineModel } from 'vue'
import { useAppStore } from '@/store/modules/app' import { useAppStore } from '@/store/modules/app'
const appStore = useAppStore() const appStore = useAppStore()
var props = defineProps({ var props = defineProps({
@ -39,10 +39,22 @@ var props = defineProps({
default: 0 default: 0
} }
}) })
var search = defineModel({
var search = defineModel('search',{
type: String, type: String,
default: '' default: ''
}) })
const selectStatus=defineModel('selectStatus',{
type: Array,
default: () => ['normal', 'offline', 'fence', 'alarm']
})
// var selectStatus = ref(['normal', 'offline'])
function toggleStatus(status: string) {
if (selectStatus.value.includes(status)) {
selectStatus.value = selectStatus.value.filter((item) => item !== status)
} else {
selectStatus.value.push(status)
}
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
/* 顶部面板样式 */ /* 顶部面板样式 */
@ -115,39 +127,66 @@ var search = defineModel({
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px;
gap: 6px;
background: rgba(255, 255, 255, 0.85); background: rgba(255, 255, 255, 0.85);
border: 1px solid rgba(0, 0, 0, 0.06); border: 1px solid rgba(0, 0, 0, 0.06);
padding: 8px 12px; padding: 8px 12px;
border-radius: 8px; border-radius: 8px;
// box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
white-space: nowrap; white-space: nowrap;
.legend-title {
color: #606266;
font-size: 14px;
}
.normal-legend, .normal-legend,
.alarm1-legend,
.alarm2-legend {
.fence-legend,
.alarm-legend, .offline-legend {
position: relative;
padding: 4px 8px; padding: 4px 8px;
border-radius: 999px; border-radius: 999px;
color: #fff; color: #fff;
font-size: 12px; font-size: 12px;
line-height: 1; line-height: 1;
cursor: pointer;
user-select: none;
} }
.normal-legend { .normal-legend {
background: #67c23a; background: #67c23a;
} }
.alarm1-legend {
.fence-legend {
background: #e6a23c; background: #e6a23c;
} }
.alarm2-legend {
.alarm-legend {
background: #f56c6c; background: #f56c6c;
} }
.offline-legend {
background: #909399;
color: #fff;
}
.is-active::after{
content: '';
position: absolute;
width: 100%;
height: 100%;
font-size: 0;
padding: 2px 2px 1px 2px;
box-sizing: content-box;
top: -2px;
left: -3px;
border-radius: 999px;
// border: 1px solid rgba(0, 0, 0, 0.5);
}
.normal-legend.is-active::after{
border: 1px solid #67c23a;
}
.fence-legend.is-active::after{
border: 1px solid #e6a23c;
}
.alarm-legend.is-active::after{
border: 1px solid #f56c6c;
}
.offline-legend.is-active::after{
border: 1px solid #909399;
}
} }
</style> </style>

6
web/src/views/HandDevice/Home/components/constants/map.constants.ts

@ -13,17 +13,14 @@ export const STATUS_DICT = {
gasStatus: [ gasStatus: [
{ value: '0', label: '正常', cssClass: '#67c23a' }, { value: '0', label: '正常', cssClass: '#67c23a' },
{ value: '1', label: '气体报警', cssClass: '#e6a23c' } { value: '1', label: '气体报警', cssClass: '#e6a23c' }
// { value: '2', label: '二级气体告警', cssClass: '#f56c6c' }
] as StatusDictItem[], ] as StatusDictItem[],
batteryStatus: [ batteryStatus: [
{ value: '0', label: '正常', cssClass: '#67c23a' }, { value: '0', label: '正常', cssClass: '#67c23a' },
{ value: '1', label: '低电量报警', cssClass: '#e6a23c' } { value: '1', label: '低电量报警', cssClass: '#e6a23c' }
// { value: '2', label: '二级低电量报警', cssClass: '#f56c6c' }
] as StatusDictItem[], ] as StatusDictItem[],
fenceStatus: [ fenceStatus: [
{ value: '0', label: '正常', cssClass: '#67c23a' }, { value: '0', label: '正常', cssClass: '#67c23a' },
{ value: '1', label: '围栏报警', cssClass: '#e6a23c' } { value: '1', label: '围栏报警', cssClass: '#e6a23c' }
// { value: '2', label: '二级围栏报警', cssClass: '#f56c6c' }
] as StatusDictItem[] ] as StatusDictItem[]
} }
@ -31,9 +28,8 @@ export const STATUS_DICT = {
export const STATUS_PRIORITY = { export const STATUS_PRIORITY = {
gasStatus_2: 1, gasStatus_2: 1,
gasStatus_1: 2, gasStatus_1: 2,
// battery_2: 3,
batteryStatus_1: 4, batteryStatus_1: 4,
// fence_2: 5,
fenceStatus_1: 6, fenceStatus_1: 6,
offline: 7, offline: 7,

3
web/src/views/HandDevice/Home/components/types/map.types.ts

@ -59,7 +59,8 @@ export interface MarkerData extends HandDetectorData {
// /** 状态字符串 */ // /** 状态字符串 */
// statusStr: string // statusStr: string
data?: any
data?: any,
[key: string]: any
} }
// 地图组件 Props 接口 // 地图组件 Props 接口

283
web/src/views/HandDevice/Home/index.vue

@ -12,16 +12,107 @@
@on-click-marker="onClickMarker" @on-click-marker="onClickMarker"
/> />
<TopPanel <TopPanel
v-model="search"
v-model:search="search"
v-model:selectStatus="selectStatus"
:handDetectorCount="handDetectorCount" :handDetectorCount="handDetectorCount"
:onlineCount="onlineCount" :onlineCount="onlineCount"
/> />
</div> </div>
<div class="markerList"> <div class="markerList">
<div style="height: 100%">
<!-- <RecycleScroller
ref="scrollbarRef"
style="height: 100%"
:items="filterMarkers"
:item-size="48"
key-field="id"
v-slot="{ item }"
@scroll="onScroll"
>
<div class="marker-item" :data-id="item.id">
<div class="flex-1 text-left font-400 text-13px"> {{ item.name }}</div>
<div class="text-gray-500 font-400 text-12px pr-1" :style="{ color: item.statusColor }">
{{ item.statusLabel }}
</div>
</div>
</RecycleScroller> -->
<DynamicScroller
ref="scrollbarRef"
class="scroller"
:items="filterMarkers"
:min-item-size="48"
key-field="id"
v-slot="{ item, active }"
style="height: 100%"
@scroll="onScroll"
>
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.expanded]">
<div class="marker-panel">
<div class="marker-item" :data-id="item.id" @click="togglePanel(item)">
<div class="flex-1 text-left font-400 text-13px"> {{ item.name }}</div>
<div
class="text-gray-500 font-400 text-12px pr-1"
:style="{ color: item.statusColor }"
>
{{ item.statusLabel }}
</div>
</div>
<div v-show="item.expanded" class="markerList-content">
<div><span>SN</span>{{ item.sn }}</div>
<div><span>类型</span>{{ item.gasChemical }}</div>
<div
><span>气体状态</span
>{{ getLabelWithTypeValue('gasStatus', item.gasStatus) }}</div
>
<div
><span>围栏状态</span
>{{ getLabelWithTypeValue('fenceStatus', item.fenceStatus) }}</div
>
<div
><span>电池状态</span
>{{ getLabelWithTypeValue('batteryStatus', item.batteryStatus) }}</div
>
<div><span>电量</span>{{ item.battery }}</div>
<div><span>数值</span>{{ item.value }} {{ item.unit }}</div>
<div><span>时间</span>{{ item.timeStr }}</div>
<div style="margin-top: 10px">
<el-button
plain
size="small"
v-if="item.latitude && item.longitude"
@click="setCenter(item)"
>定位</el-button
>
<el-button
v-hasPermi="['gas:hand-td:HistoricalSn']"
plain
size="small"
@click="onClickTrajectory(item)"
>轨迹</el-button
>
<el-button
v-hasPermi="['gas:hand-td:HistoricalSn']"
plain
size="small"
@click="onClickHistoricalCurve(item)"
>历史曲线</el-button
>
</div>
</div>
</div>
</DynamicScrollerItem>
</DynamicScroller>
</div>
</div>
<!--marker列表 -->
<!--
<el-scrollbar height="100%" ref="scrollbarRef" @scroll="onScroll"> <el-scrollbar height="100%" ref="scrollbarRef" @scroll="onScroll">
<!--marker列表 -->
<el-collapse accordion v-model="activeName">
<el-collapse-item :name="item.id" v-for="item in filterMarkers" :key="item.id">
<el-collapse accordion v-model="activeId">
<el-collapse-item :name="item.id" v-for="item in filterMarkers2" :key="item.id">
<template #title> <template #title>
<div class="flex flex-row w-100%"> <div class="flex flex-row w-100%">
<div class="flex-1 text-left font-500"> <div class="flex-1 text-left font-500">
@ -35,7 +126,7 @@
<div class="markerList-content"> <div class="markerList-content">
<div><span>SN</span>{{ item.sn }}</div> <div><span>SN</span>{{ item.sn }}</div>
<div><span>类型</span>{{ item.gasChemical }}</div> <div><span>类型</span>{{ item.gasChemical }}</div>
<!-- <hr /> -->
<div <div
><span>气体状态</span ><span>气体状态</span
>{{ getLabelWithTypeValue('gasStatus', item.gasStatus) }}</div >{{ getLabelWithTypeValue('gasStatus', item.gasStatus) }}</div
@ -78,13 +169,25 @@
</div> </div>
</div> </div>
</el-collapse-item> </el-collapse-item>
</el-collapse>
</el-collapse>
</el-scrollbar> </el-scrollbar>
</div>
-->
<el-popover
ref="popoverRef"
:virtual-ref="virtualRef"
trigger="click"
title="With title"
virtual-triggering
>
<span> Some content </span>
</el-popover>
<HistoricalCurve ref="historicalCurveRef" /> <HistoricalCurve ref="historicalCurveRef" />
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import { RecycleScroller, DynamicScroller, DynamicScrollerItem } from 'vue-virtual-scroller'
import OpenLayerMap from './components/OpenLayerMap.vue' import OpenLayerMap from './components/OpenLayerMap.vue'
import TopPanel from './components/TopPanel.vue' import TopPanel from './components/TopPanel.vue'
import HistoricalCurve from './components/HistoricalCurve.vue' import HistoricalCurve from './components/HistoricalCurve.vue'
@ -107,6 +210,7 @@ import { useHandDetectorStore } from '@/store/modules/handDetector'
import { ElMessage, ElScrollbar } from 'element-plus' import { ElMessage, ElScrollbar } from 'element-plus'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { getDistance } from 'ol/sphere' import { getDistance } from 'ol/sphere'
const componentsIsActive = ref(false) const componentsIsActive = ref(false)
const handDetectorStore = useHandDetectorStore() // store const handDetectorStore = useHandDetectorStore() // store
@ -117,6 +221,7 @@ const fences = ref<FenceData[]>([])
const mapRef = ref<InstanceType<typeof OpenLayerMap>>() const mapRef = ref<InstanceType<typeof OpenLayerMap>>()
const search = ref('') const search = ref('')
const selectStatus = ref(['normal', 'offline', 'fence', 'alarm'])
watch( watch(
() => search.value, () => search.value,
(newSearch, oldSearch) => { (newSearch, oldSearch) => {
@ -131,8 +236,9 @@ watch(
const handDetectorCount = computed(() => markers.value.length) const handDetectorCount = computed(() => markers.value.length)
const onlineCount = computed(() => markers.value.filter((item) => item.onlineStatus === 1).length) const onlineCount = computed(() => markers.value.filter((item) => item.onlineStatus === 1).length)
const filterMarkers = computed(() => { const filterMarkers = computed(() => {
var arr = markers.value
if (search.value) { if (search.value) {
return markers.value.filter((item) => {
arr = markers.value.filter((item) => {
var isName = item?.name?.includes(search.value) var isName = item?.name?.includes(search.value)
var isSn = item.sn?.includes(search.value) var isSn = item.sn?.includes(search.value)
var isGasChemical = item.gasChemical?.includes(search.value) var isGasChemical = item.gasChemical?.includes(search.value)
@ -140,9 +246,70 @@ const filterMarkers = computed(() => {
return isName || isSn || isGasChemical return isName || isSn || isGasChemical
}) })
} }
return markers.value
if (selectStatus.value.length !== 0) {
arr = arr.filter((item) => {
if (!item.statusStr) {
return true
}
if (item.statusStr == 'gasStatus_2' || item.statusStr == 'gasStatus_1') {
return selectStatus.value.includes('alarm')
}
return selectStatus.value.includes(item.statusStr)
})
}
// console.log('markers.value', markers.value)
return arr
}) })
// const filterMarkers2 = computed(() => {
// var arr: MarkerData[] = []
// for (let i = 0; i < 20000; i++) {
// arr.push({
// id: i + 1,
// sn: '867989072728120',
// battery: 3786,
// value: 4,
// longitude: 118.498279,
// latitude: 38.9647966,
// time: 1762937859776,
// name: '',
// fenceIds: '',
// fenceType: 1,
// gasTypeId: 1,
// unit: 'ppm',
// gasChemical: 'CO',
// batteryAlarmValue: 20,
// accuracy: 1,
// alarmLevel: -1,
// maxAlarmLevel: null,
// firstValue: null,
// maxValue: null,
// gpsType: 0,
// gasStatus: 0,
// alarmId: 200,
// batteryStatus: 0,
// batteryStatusAlarmId: null,
// fenceStatus: 0,
// fenceAlarmId: null,
// onlineStatus: 0,
// enableStatus: 1,
// distance: null,
// maxDistance: null,
// tenantId: null,
// talarmStart: null,
// talarmEnd: null,
// coordinates: [118.498279, 38.9647966],
// timeStr: '2025-11-12 16:57:39',
// statusStr: 'offline',
// statusColor: '#909399',
// statusLabel: '线',
// statusPriority: 7,
// expanded: false
// })
// }
// return arr
// })
const getMarkers = async () => { const getMarkers = async () => {
console.log('getMarkers') console.log('getMarkers')
return await getLastDetectorData().then((res: HandDetectorData[]) => { return await getLastDetectorData().then((res: HandDetectorData[]) => {
@ -165,7 +332,8 @@ const getMarkers = async () => {
statusStr: statusStr, // statusStr: statusStr, //
statusColor: getStatusColor(statusStr), // statusColor: getStatusColor(statusStr), //
statusLabel: getStatusLabel(statusStr), // statusLabel: getStatusLabel(statusStr), //
statusPriority: getStatusPriority(statusStr) //,
statusPriority: getStatusPriority(statusStr), //,
expanded: activeId.value === i.id
} }
}) })
.sort((a, b) => a.statusPriority - b.statusPriority) .sort((a, b) => a.statusPriority - b.statusPriority)
@ -322,27 +490,23 @@ function onClickTrajectory(item: MarkerData) {
trajectoryTimeRange.value = [dayjs().subtract(1, 'hour').valueOf(), dayjs().valueOf()] trajectoryTimeRange.value = [dayjs().subtract(1, 'hour').valueOf(), dayjs().valueOf()]
showTrajectory(item) showTrajectory(item)
} }
const scrollbarRef = ref<InstanceType<typeof ElScrollbar>>()
const activeName = ref<number>()
const scrollbarRef = ref()
const virtualRef = ref<HTMLDivElement>()
const activeId = ref<number>()
// //
function onClickMarker(item: MarkerData) {
console.log('onClickMarker', item)
activeName.value = item.id
var findIndex = markers.value.findIndex((item) => item.id === activeName.value)
function onClickMarker(markerItem: MarkerData) {
console.log('onClickMarker', markerItem)
var findIndex = markers.value.findIndex((item) => item.id === markerItem.id)
if (findIndex === -1) { if (findIndex === -1) {
return return
} }
setTimeout(() => {
let top = findIndex * 48
// scrollbarRef.value?.setScrollTop(top)
//
cancelAnimationFrame(AnimationId.value as number)
scrollTo(scrollbarScrollTop.value, top, scrollbarScrollTop.value)
}, 500)
showPannel(markerItem, findIndex)
} }
const scrollbarScrollTop = ref(0) const scrollbarScrollTop = ref(0)
function onScroll({ scrollTop }) {
scrollbarScrollTop.value = scrollTop
function onScroll(e) {
// console.log('onScroll', e)
scrollbarScrollTop.value = e.target.scrollTop
} }
// //
@ -363,12 +527,47 @@ function scrollTo(from: number, to: number, current: number) {
} }
} }
current = current + speed current = current + speed
scrollbarRef.value?.setScrollTop(current)
console.log('scrollbarRef.value', scrollbarRef.value)
// DynamicScrollerRecycleScrollerRecycleScroller
scrollbarRef.value?.$refs?.scroller?.scrollToPosition(current)
AnimationId.value = requestAnimationFrame(() => { AnimationId.value = requestAnimationFrame(() => {
scrollTo(from, to, current) scrollTo(from, to, current)
}) })
} }
const showPannel = (item, index) => {
if (activeId.value && activeId.value !== item.id) {
let last = markers.value.find((lastItem) => lastItem.id === activeId.value)
if (last) {
last.expanded = false
}
}
item.expanded = true
activeId.value = item.id
let top = index * 48
// scrollbarRef.value?.scrollToItem(index)
//
cancelAnimationFrame(AnimationId.value as number)
scrollTo(scrollbarScrollTop.value, top, scrollbarScrollTop.value)
}
const togglePanel = (item) => {
if (activeId.value && activeId.value !== item.id) {
let last = markers.value.find((lastItem) => lastItem.id === activeId.value)
if (last) {
last.expanded = false
}
}
if (item.expanded) {
activeId.value = undefined
item.expanded = false
} else {
activeId.value = item.id
item.expanded = true
}
}
onMounted(() => { onMounted(() => {
getMarkers() getMarkers()
@ -410,12 +609,25 @@ onUnmounted(() => {
overflow: hidden; overflow: hidden;
background-color: rgba(255, 255, 255, 0.8); background-color: rgba(255, 255, 255, 0.8);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
padding: 0 10px;
padding: 5px 0 10px 10px;
margin-left: 10px; margin-left: 10px;
.marker-panel {
box-sizing: border-box;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.marker-item {
display: flex;
flex-direction: row;
height: 48px;
line-height: 48px;
font-family: var(--el-font-family);
}
.markerList-content { .markerList-content {
border-top: 1px solid rgba(0, 0, 0, 0.1);
padding: 10px; padding: 10px;
font-size: 12px; font-size: 12px;
font-weight: 400;
span { span {
display: inline-block; display: inline-block;
@ -433,4 +645,19 @@ onUnmounted(() => {
.el-collapse-item__wrap { .el-collapse-item__wrap {
background-color: rgba(255, 255, 255, 0.8); background-color: rgba(255, 255, 255, 0.8);
} }
::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.1) !important; /* 滑块颜色 */
border-radius: 4px;
}
::-webkit-scrollbar {
width: 8px;
background-color: #f5f5f5;
}
/*
::-webkit-scrollbar-thumb {
border-radius: 6px;
background-color: #555;
} */
</style> </style>

Loading…
Cancel
Save