feat: 修改marker绘制方式,以为之后的动效做准备,对接AllDate和FocusDate接口和右侧树接口,增加虚拟滚动功能以优化大数据下的滚动效率

This commit is contained in:
Xu Zhimeng 2023-06-14 20:05:12 +08:00
parent 20aad9d71d
commit 9c0195ed58
8 changed files with 223 additions and 210 deletions

View File

@ -43,6 +43,7 @@
"vue-print-nb-jeecg": "^1.0.12",
"vue-router": "^3.0.1",
"vue-splitpane": "^1.0.4",
"vue-virtual-scroller": "^1.1.2",
"vuedraggable": "^2.20.0",
"vuex": "^3.1.0",
"vxe-table": "2.9.13",

View File

@ -1,5 +1,13 @@
<template>
<a-tree class="custom-tree" v-model="checkedKeys" checkable :selectedKeys="[]" :tree-data="treeData" @select="onTreeSelect">
<a-tree
class="custom-tree"
v-bind="$attrs"
v-model="checkedKeys"
checkable
:selectedKeys="[]"
:tree-data="treeData"
@select="onTreeSelect"
>
<a-icon slot="switcherIcon" type="down" />
</a-tree>
</template>

View File

@ -17,7 +17,7 @@ export default {
data() {
this.isCustomContainer = this.scrollContainer && typeof this.scrollContainer == 'function' //
return {
scrollEnd: false
scrollEnd: true
}
},
mounted() {
@ -25,10 +25,13 @@ export default {
this.containerEle.addEventListener('scroll', () => {
this.checkScrollEnd()
})
this.containerEle.addEventListener('transitionend', () => { //
this.containerEle.addEventListener('transitionend', () => {
//
this.checkScrollEnd()
})
this.checkScrollEnd()
},
methods: {
@ -36,12 +39,15 @@ export default {
* 检查是否滚动到尾部
*/
checkScrollEnd() {
if (this.direction == 'horizontal') {
this.scrollEnd = this.containerEle.scrollLeft + this.containerEle.offsetWidth == this.containerEle.scrollWidth
} else {
this.scrollEnd = this.containerEle.scrollTop + this.containerEle.offsetHeight == this.containerEle.scrollHeight
if (this.containerEle) {
if (this.direction == 'horizontal') {
this.scrollEnd = this.containerEle.scrollLeft + this.containerEle.offsetWidth == this.containerEle.scrollWidth
} else {
this.scrollEnd =
this.containerEle.scrollTop + this.containerEle.offsetHeight == this.containerEle.scrollHeight
}
this.$emit('scrollEnd', this.scrollEnd)
}
this.$emit('scrollEnd', this.scrollEnd)
}
}
}

View File

@ -58,6 +58,11 @@ import CustomDatePicker from '@/components/CustomDatePicker'
import CustomMonthPicker from '@/components/CustomMonthPicker'
import CustomEmpty from '@/components/CustomEmpty'
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
Vue.component('RecycleScroller', RecycleScroller)
Vue.prototype.rules = rules
Vue.config.productionTip = false
Vue.use(Storage, config.storageOptions)

View File

@ -61,7 +61,7 @@ export default {
]
const view = new View({
projection: 'EPSG:4326', // 使
projection: 'EPSG:900913', // 使
center: [longitude, latitude],
zoom: this.zoom,
maxZoom: this.maxZoom,

View File

@ -1,16 +1,10 @@
<template>
<div ref="mapPopupRef">
<div ref="mapPopupRef" class="popover">
这是弹窗
</div>
</template>
<script>
import { Vector as VectorLayer } from 'ol/layer'
import VectorSource from 'ol/source/Vector'
import Feature from 'ol/Feature'
import { Fill, Icon, Stroke, Style, Text } from 'ol/style'
import { Point } from 'ol/geom'
import Overlay from 'ol/Overlay'
import MarkerImg from './markerImage'
export default {
@ -20,41 +14,29 @@ export default {
required: true
}
},
data() {
return {
currStationInfo: {}
}
},
mounted() {
this.map = this.$parent.getMapInstance()
this.initMarkers()
this.initMapClick()
this.initMapPopup()
this.initMarkers()
},
methods: {
// marker
initMarkers() {
const markerFeatures = []
this.list.forEach((eventItem, index) => {
markerFeatures.push(this.getMarker(eventItem))
markerFeatures.push(this.getCircle(eventItem))
this.list.forEach(eventItem => {
this.map.addOverlay(this.getMarker(eventItem))
})
const markerLayer = new VectorLayer({
source: new VectorSource({
features: markerFeatures
}),
properties: { name: 'eventMarker' }
})
this.map.addLayer(markerLayer)
},
//
initMapClick() {
this.map.on('click', async evt => {
const feature = this.map.forEachFeatureAtPixel(evt.pixel, feature => {
return feature
})
const stationInfo = feature && feature.values_ && feature.values_.stationInfo
if (stationInfo) {
this.showMapPopup(stationInfo)
} else {
this.closeMapPopup()
}
this.map.on('click', () => {
this.closeMapPopup()
})
},
@ -65,7 +47,8 @@ export default {
autoPan: true,
autoPanAnimation: {
duration: 250
}
},
positioning: 'bottom-center'
})
this.map.addOverlay(this.popupOverlay)
},
@ -73,12 +56,7 @@ export default {
//
showMapPopup(stationInfo) {
this.popupOverlay.setPosition([stationInfo.lon, stationInfo.lat])
this.currMapClickEventItem = stationInfo //
if (!stationInfo.appEventDetailList) {
this.isGettingPopupDetail = true
stationInfo.appEventDetailList = stationInfo.appEventDetailList
this.isGettingPopupDetail = false
}
this.currStationInfo = stationInfo //
},
//
@ -89,37 +67,38 @@ export default {
// marker
getMarker(stationInfo) {
const { lon, lat } = stationInfo
const markerFeature = new Feature({
geometry: new Point([lon, lat]),
stationInfo
const img = document.createElement('img')
img.src =
MarkerImg[
['Car', 'GroudMonitoringStation', 'ImsRnStation', 'NuclearFacility', 'Ship'][parseInt(Math.random() * 5)]
]
img.style.width = '30px'
img.style.height = '30px'
img.style.cursor = 'pointer'
img.addEventListener('click', () => {
this.showMapPopup(stationInfo)
})
return new Overlay({
position: [lon, lat],
element: img,
id: stationInfo.stationId,
positioning: 'top-center'
})
markerFeature.setStyle(this.getMarkerStyle(stationInfo))
return markerFeature
},
// circle
getCircle(stationInfo) {
const { lon, lat } = stationInfo
const markerFeature = new Feature({
geometry: new Point([lon, lat]),
stationInfo
})
markerFeature.setStyle(this.getMarkerStyle(stationInfo))
return markerFeature
},
// marker
getMarkerStyle() {
const src = MarkerImg.NuclearFacility
return new Style({
image: new Icon({
src,
scale: 0.8
})
})
// marker
setMarkerPosition(markerId, position) {
const overlay = this.map.getOverlayById(markerId)
overlay.setPosition(position)
}
}
}
</script>
<style lang="less" scoped></style>
<style lang="less" scoped>
.popover {
width: 200px;
height: 300px;
background-color: rgba(2, 26, 29, 0.9);
box-shadow: 0 0 5px rgba(2, 26, 29, 0.9);
}
</style>

View File

@ -54,7 +54,11 @@
<!-- 站点选择树 -->
<div class="station-list-tree">
<custom-tree v-model="checkedKeys" :tree-data="treeData"></custom-tree>
<custom-tree
v-model="checkedKeys"
:tree-data="treeData"
:replaceFields="{ children: 'children', title: 'stationCode', key: 'stationId' }"
></custom-tree>
</div>
<!-- 站点选择树 结束 -->
</div>
@ -67,8 +71,8 @@
<div class="station-operation-infomation-content">
<p class="radius-title">Radius</p>
<div class="radius-search">
<a-input suffix="KM"></a-input>
<a-button type="primary">
<a-input suffix="KM" v-model="radius"></a-input>
<a-button type="primary" @click="handleSearchByRadius">
Search
</a-button>
</div>
@ -79,6 +83,7 @@
:data-source="dataSource"
rowKey="id"
:pagination="false"
:loading="isGettingInfomationList"
></a-table>
</div>
</div>
@ -105,14 +110,14 @@
</div>
</div>
<a-divider style="background-color: #0a544e; margin: 10px 0 0;"></a-divider>
<!-- 站点状态类型 -->
<div class="station-state-list">
<div class="station-state-list-item" v-for="(stateItem, index) in stateList" :key="index">
<!-- 数据质量类型 -->
<div class="station-data-quality-list">
<div class="station-data-quality-list-item" v-for="(stateItem, index) in dataQualityList" :key="index">
<img :src="stateItem.icon" alt="" />
<span>{{ stateItem.title }}</span>
</div>
</div>
<!-- 站点装填类型结束 -->
<!-- 数据质量结束 -->
</div>
</div>
<!-- 站点筛选结束 -->
@ -141,7 +146,11 @@
<img src="@/assets/images/station-operation/toggle.png" @click="leftPaneShow = !leftPaneShow" />
</div>
<div class="content">
<custom-tree v-model="dataStatusCheckedKeys" :tree-data="treeData"></custom-tree>
<custom-tree
v-model="dataStatusCheckedKeys"
:tree-data="treeData"
:replaceFields="{ children: 'children', title: 'stationCode', key: 'stationId' }"
></custom-tree>
</div>
</div>
<div class="data-receive-status-list-item">
@ -211,6 +220,7 @@ import CustomModal from '@/components/CustomModal/index.vue'
import FilterImage from './filterImage'
import CustomTree from '@/components/CustomTree/index.vue'
import RealTimeDataChart from './RealTimeDataChart.vue'
import { postAction } from '../../../api/manage'
// Filter
const filterList = [
@ -246,8 +256,8 @@ const filterList = [
}
]
// Filter
const stateList = [
// Filter
const dataQualityList = [
{
title: 'Excellent data quality',
icon: FilterImage.State1
@ -284,75 +294,6 @@ const columns = [
}
]
const dataSource = [
{
id: 1,
nuclearfaclity: 'Cooper',
station: 'USX74',
distance: 504.366
},
{
id: 11,
nuclearfaclity: 'Davis Besse-1',
station: 'USX74',
distance: 504.366
},
{
id: 2,
nuclearfaclity: 'Cooper',
station: 'USX74',
distance: 504.366
},
{
id: 3,
nuclearfaclity: 'Davis Besse-1',
station: 'USX74',
distance: 504.366
},
{
id: 4,
nuclearfaclity: 'Cooper',
station: 'USX74',
distance: 504.366
},
{
id: 5,
nuclearfaclity: 'Davis Besse-1',
station: 'USX74',
distance: 504.366
},
{
id: 6,
nuclearfaclity: 'Cooper',
station: 'USX74',
distance: 504.366
},
{
id: 7,
nuclearfaclity: 'Davis Besse-1',
station: 'USX74',
distance: 504.366
},
{
id: 8,
nuclearfaclity: 'Cooper',
station: 'USX74',
distance: 504.366
},
{
id: 9,
nuclearfaclity: 'Davis Besse-1',
station: 'USX74',
distance: 504.366
},
{
id: 10,
nuclearfaclity: 'Cooper',
station: 'USX74',
distance: 504.366
}
]
const legendList = [
{
title: 'SPHDPREL',
@ -380,12 +321,14 @@ const legendList = [
}
]
const statusList = [{
title: 'JPX38 23803',
}, {
title: 'JPX38 23804'
}]
const statusList = [
{
title: 'JPX38 23803'
},
{
title: 'JPX38 23804'
}
]
export default {
props: {
@ -394,7 +337,7 @@ export default {
default: 500
},
stationList: {
treeData: {
type: Array
}
},
@ -412,11 +355,13 @@ export default {
checkedKeys: [], //
filterList, //
stateList, //
dataQualityList, //
dataSource: dataSource, // Infomation Radius
radius: 0, //
dataSource: [], // Infomation Radius
isGettingInfomationList: false,
dataStatusModalVisible: true, //
dataStatusModalVisible: false, //
dataStatusCheckedKeys: [], // -
leftPaneShow: true, //
@ -428,6 +373,10 @@ export default {
},
created() {
this.initParentMapProps()
document.addEventListener('fullscreenchange', this.onFullScreenChange)
},
destroyed() {
document.removeEventListener('fullscreenchange', this.onFullScreenChange)
},
methods: {
initParentMapProps() {
@ -439,11 +388,16 @@ export default {
},
handleFullScreen() {
this.isFullScreen = true
const ele = document.querySelector('.station-operation')
ele.requestFullscreen()
},
handleExitFullScreen() {
this.isFullScreen = false
document.exitFullscreen()
},
onFullScreenChange() {
this.isFullScreen = !!document.fullscreenElement
},
//
@ -460,6 +414,26 @@ export default {
this.checkedKeys = []
},
//
async handleSearchByRadius() {
if (this.radius == null || this.radius == undefined) {
this.$message.warn('Please Input Radius To Search')
return
}
try {
this.isGettingInfomationList = true
const res = await postAction('/jeecg-station-operation/stationOperation/getHitEquList', {
radius: this.radius,
stationIds: []
})
console.log('%c [ ]-486', 'font-size:13px; background:pink; color:#bf2c9f;', res)
} catch (error) {
console.error(error)
} finally {
this.isGettingInfomationList = false
}
},
//
handleOpenAnalyzeModal() {
this.dataStatusModalVisible = true
@ -481,26 +455,13 @@ export default {
}
},
//
onModalFullScreen() {
this.showChart = false
this.$nextTick(() => {
this.showChart = true
})
}
},
computed: {
treeData() {
const set = new Set(this.stationList.map(item => item.countryCode))
return Array.from(set).map((countryCode, index) => {
return {
title: countryCode,
key: index.toString(),
children: this.stationList
.filter(item => item.countryCode == countryCode)
.map(item => ({ title: item.stationCode, key: item.stationId.toString(), children: [] }))
}
})
}
}
}
</script>
@ -723,7 +684,7 @@ export default {
}
}
.station-state-list {
.station-data-quality-list {
&-item {
margin-left: 9px;
height: 32px;

View File

@ -79,29 +79,36 @@
</a-row>
<!-- 筛选结束 -->
</div>
<div ref="customScrollContainerRef" class="date-list-content">
<div class="date-list-content">
<a-spin v-if="isGettingDateList"></a-spin>
<template v-else>
<div class="date-list-item" v-for="item of dateList" :key="item.id">
<template>
<RecycleScroller
ref="customScrollContainerRef"
class="scroller"
:items="dateList"
:item-size="129"
key-field="stationId"
v-slot="{ item }"
>
<h4 class="date-list-item-title">
{{ item.stationCode }}
{{ item.stationName }}
</h4>
<div class="date-list-item-container">
<div class="date-list-item-content">
<div class="date-list-item-children">
<div class="date-list-item-child">
<label>Station Type:</label>
<span>{{ item.type }}</span>
<span>{{ item.stationType }}</span>
</div>
<div class="date-list-item-child" style="word-break: break-all">
<label>Altitude:</label>
<span>{{ item.elevation }}m</span>
<span>{{ item.altitude }}</span>
</div>
</div>
<div class="date-list-item-children">
<div class="date-list-item-child">
<label>Lon And Lat:</label>
<span>{{ item.lon.toFixed(6) }} &nbsp;&nbsp; {{ item.lat.toFixed(6) }}</span>
<span>{{ Number(item.lon).toFixed(6) }} &nbsp;&nbsp; {{ Number(item.lat).toFixed(6) }}</span>
</div>
</div>
<div class="date-list-item-children">
@ -111,12 +118,12 @@
</div>
<div class="date-list-item-child">
<label>Signal:</label>
<span class="green">Normally</span>
<span class="green">{{ item.signal }}</span>
</div>
</div>
</div>
</div>
</div>
</RecycleScroller>
<custom-empty v-if="!dateList.length" style="margin-top: 40px"></custom-empty>
</template>
</div>
@ -139,30 +146,30 @@
</div>
</div>
</template>
<ScrollContainer direction="verticle" class="date-list">
<ScrollContainer ref="scrollContainer2Ref" direction="verticle" class="date-list">
<div class="date-list-content">
<a-spin v-if="isGettingDateList"></a-spin>
<a-spin v-if="isGettingFollowedDateList"></a-spin>
<template v-else>
<div class="date-list-item" v-for="item of dateList" :key="item.id">
<div class="date-list-item" v-for="item of followedDateList" :key="item.id">
<h4 class="date-list-item-title">
{{ item.stationCode }}
{{ item.stationName }}
</h4>
<div class="date-list-item-container">
<div class="date-list-item-content">
<div class="date-list-item-children">
<div class="date-list-item-child">
<label>Station Type:</label>
<span>{{ item.type }}</span>
<span>{{ item.stationType }}</span>
</div>
<div class="date-list-item-child" style="word-break: break-all">
<label>Altitude:</label>
<span>{{ item.elevation }}m</span>
<span>{{ item.altitude }}</span>
</div>
</div>
<div class="date-list-item-children">
<div class="date-list-item-child">
<label>Lon And Lat:</label>
<span>{{ item.lon.toFixed(6) }} &nbsp;&nbsp; {{ item.lat.toFixed(6) }}</span>
<span>{{ Number(item.lon).toFixed(6) }} &nbsp;&nbsp; {{ Number(item.lat).toFixed(6) }}</span>
</div>
</div>
<div class="date-list-item-children">
@ -172,13 +179,13 @@
</div>
<div class="date-list-item-child">
<label>Signal:</label>
<span class="green">Normally</span>
<span class="green">{{ item.signal }}</span>
</div>
</div>
</div>
</div>
</div>
<custom-empty v-if="!dateList.length" style="margin-top: 40px"></custom-empty>
<custom-empty v-if="!followedDateList.length" style="margin-top: 40px"></custom-empty>
</template>
</div>
<div class="shadow"></div>
@ -191,7 +198,7 @@
<div class="station-operation-map">
<Map token="AAPK2b935e8bbf564ef581ca3c6fcaa5f2a71ZH84cPqqFvyz3KplFRHP8HyAwJJkh6cnpcQ-qkWh5aiyDQsGJbsXglGx0QM2cPm">
<MapMarker v-if="dateList.length" :list="dateList" />
<MapPane :stationList="dateList" />
<MapPane :treeData="treeData" />
</Map>
</div>
</div>
@ -216,7 +223,10 @@ export default {
activeKey: '1',
isGettingDateList: false,
isGettingFollowedDateList: false,
dateList: [],
followedDateList: [], //
searchPlacementVisible: false, //
@ -230,26 +240,29 @@ export default {
filterVisible: false, //
stationTypeList: []
stationTypeList: [],
treeData: [] //
}
},
created() {
this.getStationList()
this.getFollowedStationList()
this.getStationTypeList()
this.getStationTree()
},
methods: {
//
async getStationList() {
try {
this.isGettingDateList = true
const {
success,
result: { records }
} = await getAction('/gardsStations/findPage?pageIndex=1&pageSize=999')
if (success) {
this.originalDateList = cloneDeep(records)
this.dateList = records
}
const res = await getAction('/jeecg-station-operation/stationOperation/findList')
this.dateList = res
this.originalDateList = cloneDeep(res)
this.$nextTick(() => {
this.$refs.scrollContainerRef.checkScrollEnd()
})
} catch (error) {
console.error(error)
} finally {
@ -257,6 +270,26 @@ export default {
}
},
//
async getFollowedStationList() {
try {
this.isGettingFollowedDateList = true
const res = await getAction('/jeecg-station-operation/sysUserFocusStation/findList')
this.followedDateList = res
const scrollContainer2Ref = this.$refs.scrollContainer2Ref
if (scrollContainer2Ref) {
this.$nextTick(() => {
scrollContainer2Ref.checkScrollEnd()
})
}
} catch (error) {
console.error(error)
} finally {
this.isGettingFollowedDateList = false
}
},
//
async getStationTypeList() {
try {
@ -267,6 +300,22 @@ export default {
}
},
//
async getStationTree() {
try {
const { success, result, message } = await getAction('/stationOperation/findTree')
if(success) {
result.forEach(item => item.stationCode = item.code)
this.treeData = result
}
else {
this.$message.error(message)
}
} catch (error) {
console.error(error);
}
},
handleShowSearch() {
if (this.filterVisible) {
this.searchVisible = true
@ -299,9 +348,9 @@ export default {
this.dateList = this.originalDateList.filter(dateItem => {
const filterSearchText =
!this.filter.searchText ||
-1 !== dateItem.stationCode.toLowerCase().indexOf(this.filter.searchText.toLowerCase())
-1 !== dateItem.stationName.toLowerCase().indexOf(this.filter.searchText.toLowerCase())
const filterStatus = !this.filter.status || this.filter.status == dateItem.status
const filterType = !this.filter.type || this.filter.type == dateItem.type
const filterType = !this.filter.stationType || this.filter.type == dateItem.stationType
return filterSearchText && filterStatus && filterType
})
@ -312,7 +361,7 @@ export default {
},
getScrollContainer() {
return this.$refs.customScrollContainerRef
return this.$refs.customScrollContainerRef.$el
},
getDictSelectTagContainer() {
@ -585,4 +634,8 @@ export default {
height: 100%;
}
}
.scroller {
height: 100%;
}
</style>