feat: 地图轨迹
This commit is contained in:
parent
32f3039153
commit
7dbf13888f
|
@ -0,0 +1,106 @@
|
|||
<template>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { registerMixin } from '@vuemap/vue-amap';
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import type { PathSimplifier, PathDataItemType, PathNavigator } from './types';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PathSimplifier',
|
||||
mixins: [registerMixin],
|
||||
props: {
|
||||
pathData: Array as PropType<PathDataItemType[]>,
|
||||
},
|
||||
data(): {
|
||||
pathSimplifierRef: PathSimplifier | null,
|
||||
PathNavigatorRef: PathNavigator | null,
|
||||
distance: number
|
||||
}{
|
||||
return {
|
||||
pathSimplifierRef: null,
|
||||
PathNavigatorRef: null,
|
||||
distance: 0,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
pathSimplifier(PathObj: PathSimplifier) {
|
||||
this.pathSimplifierRef = new PathObj({
|
||||
zIndex: 100,
|
||||
getPath: (_pathData: any) => {
|
||||
return _pathData.path;
|
||||
},
|
||||
getHoverTitle: (_pathData: any) => {
|
||||
return _pathData.name;
|
||||
},
|
||||
map: this.parentInstance?.$amapComponent
|
||||
});
|
||||
|
||||
this.PathNavigatorRef?.destroy();
|
||||
|
||||
if (this.pathData) {
|
||||
this.pathSimplifierRef?.setData(
|
||||
this.pathData.map((item) => ({
|
||||
name: item.name || '路线',
|
||||
path: item.path,
|
||||
})),
|
||||
);
|
||||
|
||||
const pathData = this.pathSimplifierRef?.getPathData(0);
|
||||
|
||||
if (pathData?.path && pathData?.path.length) {
|
||||
this.PathNavigatorRef =
|
||||
this.pathSimplifierRef?.createPathNavigator(0, {
|
||||
speed: this.distance
|
||||
? (this.distance / 5) * 3.6
|
||||
: 10,
|
||||
}) as any;
|
||||
}
|
||||
}
|
||||
},
|
||||
loadUI() {
|
||||
if ((window as any).AMapUI) {
|
||||
(window as any).AMapUI.load(
|
||||
['ui/misc/PathSimplifier', 'lib/$'],
|
||||
(path: PathSimplifier) => {
|
||||
if (!path.supportCanvas) {
|
||||
console.warn('当前环境不支持 Canvas!');
|
||||
return;
|
||||
}
|
||||
this.pathSimplifier(path);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
start() {
|
||||
this.PathNavigatorRef?.start();
|
||||
},
|
||||
stop() {
|
||||
this.PathNavigatorRef?.moveToPoint(0, 0);
|
||||
this.PathNavigatorRef?.stop();
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
pathData: {
|
||||
handler(newVal) {
|
||||
if (
|
||||
this.parentInstance.$amapComponent &&
|
||||
newVal?.[0]?.path &&
|
||||
newVal?.[0]?.path.length >= 2
|
||||
) {
|
||||
this.loadUI()
|
||||
// 计算速度
|
||||
const pointArr = newVal?.[0]?.path.map(
|
||||
(point: number[]) => new (AMap as any).LngLat(point[0], point[1]),
|
||||
);
|
||||
const distanceOfLine = (AMap as any).GeometryUtil.distanceOfLine(pointArr);
|
||||
this.distance = Math.round(distanceOfLine);
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
expose: ['start', 'stop']
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,71 @@
|
|||
<template>
|
||||
<div
|
||||
:style="props.style || { width: '100%', height: '100%' }"
|
||||
:class="props.class"
|
||||
>
|
||||
<el-amap v-if="amapKey" :zooms="[3, 20]" @init="initMap" ref="mapRef">
|
||||
<template v-if="isOpenUi">
|
||||
<template v-if="uiLoading">
|
||||
<slot></slot>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else><slot></slot></template>
|
||||
</el-amap>
|
||||
<JEmpty v-else description="请配置高德地图key" style="padding: 20%" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { CSSProperties, PropType } from 'vue';
|
||||
import AMap, { initAMapApiLoader } from '@vuemap/vue-amap';
|
||||
import '@vuemap/vue-amap/dist/style.css';
|
||||
import { getAMapUiPromise } from './utils';
|
||||
|
||||
interface AMapProps {
|
||||
style?: CSSProperties;
|
||||
class?: string;
|
||||
AMapUI?: string | boolean;
|
||||
}
|
||||
const amapKey = localStorage.getItem('amap_key') || 'a0415acfc35af15f10221bfa5a6850b4';
|
||||
|
||||
initAMapApiLoader({
|
||||
key: amapKey || '',
|
||||
securityJsCode: 'cae6108ec3dd222f946d1a7237c78be0',
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
style: Object as PropType<AMapProps['style']>,
|
||||
class: String as PropType<AMapProps['class']>,
|
||||
AMapUI: [String, Boolean],
|
||||
center: Array,
|
||||
});
|
||||
|
||||
const mapRef = ref();
|
||||
|
||||
const uiLoading = ref<boolean>(false);
|
||||
|
||||
const map = ref<any>(null);
|
||||
|
||||
const isOpenUi = computed(() => {
|
||||
return 'AMapUI' in props || props.AMapUI;
|
||||
});
|
||||
|
||||
const getAMapUI = () => {
|
||||
const version = typeof props.AMapUI === 'string' ? props.AMapUI : '1.1';
|
||||
getAMapUiPromise(version).then(() => {
|
||||
uiLoading.value = true;
|
||||
});
|
||||
};
|
||||
|
||||
const marker = ref<any[]>([]);
|
||||
|
||||
const initMap = (e: any) => {
|
||||
map.value = e;
|
||||
if (isOpenUi.value) {
|
||||
getAMapUI();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -0,0 +1,130 @@
|
|||
export type PathDataType = number[][];
|
||||
|
||||
export type PathSimplifierOptions = {
|
||||
map?: any;
|
||||
zIndex?: number;
|
||||
data?: number[][];
|
||||
getPath?: (pathData: {}, pathIndex: number) => PathDataType;
|
||||
getZIndex?: (pathData: any, pathIndex: number) => number;
|
||||
getHoverTitle?: (pathData: any, pathIndex: number, pointIndex: number) => string;
|
||||
autoSetFitView?: boolean;
|
||||
clickToSelectPath?: boolean;
|
||||
onTopWhenSelected?: boolean;
|
||||
renderConstructor?: Function;
|
||||
renderOptions?: {};
|
||||
};
|
||||
|
||||
export type PathDataItemType = {
|
||||
name?: string;
|
||||
path: PathDataType;
|
||||
};
|
||||
|
||||
export interface PathSimplifier {
|
||||
new (options: PathSimplifierOptions);
|
||||
|
||||
readonly supportCanvas: boolean;
|
||||
|
||||
getZIndexOfPath: (pathIndex: number) => number;
|
||||
|
||||
setZIndexOfPath: (pathIndex: number, zIndex: number) => void;
|
||||
|
||||
/**
|
||||
* 是否置顶显示pathIndex对应的轨迹
|
||||
* @param pathIndex
|
||||
* @param isTop isTop为真,设置 zIndex 为 现存最大zIndex+1; isTop为假,设置 zIndex 为 构造参数中 getZIndex 的返回值
|
||||
*/
|
||||
toggleTopOfPath: (pathIndex: number, isTop: boolean) => void;
|
||||
|
||||
getPathData: (pathIndex: number) => any;
|
||||
|
||||
createPathNavigator: (pathIndex: number, options: {}) => PathNavigator;
|
||||
|
||||
getPathNavigators: () => any[];
|
||||
|
||||
clearPathNavigators: () => void;
|
||||
|
||||
getSelectedPathData: () => any;
|
||||
|
||||
getSelectedPathIndex: () => number;
|
||||
|
||||
isSelectedPathIndex: (pathIndex: number) => boolean;
|
||||
|
||||
setSelectedPathIndex: (pathIndex: number) => void;
|
||||
|
||||
render: () => void;
|
||||
|
||||
renderLater: (delay: number[]) => void;
|
||||
|
||||
setData: (data: any[]) => void;
|
||||
|
||||
setFitView: (pathIndex: number) => void;
|
||||
|
||||
on: (eventName: string, handler: Function) => void;
|
||||
|
||||
off: (eventName: string, handler: Function) => void;
|
||||
|
||||
hide: () => void;
|
||||
|
||||
show: () => void;
|
||||
|
||||
isHidden: () => boolean;
|
||||
|
||||
getRender: () => boolean;
|
||||
|
||||
getRenderOptions: () => any;
|
||||
}
|
||||
|
||||
export interface PathNavigatorOptions {
|
||||
loop?: boolean;
|
||||
speed?: number;
|
||||
pathNavigatorStyle?: {};
|
||||
animInterval?: number;
|
||||
dirToPosInMillsecs?: number;
|
||||
range?: [number, number];
|
||||
}
|
||||
|
||||
export interface PathNavigator {
|
||||
new (options: PathNavigatorOptions);
|
||||
|
||||
start: (pointIndex?: number) => void;
|
||||
|
||||
pause: () => void;
|
||||
|
||||
resume: () => void;
|
||||
|
||||
stop: () => void;
|
||||
|
||||
destroy: () => void;
|
||||
|
||||
getCursor: () => any;
|
||||
|
||||
getNaviStatus: () => string;
|
||||
|
||||
getPathIndex: () => number;
|
||||
|
||||
getPosition: () => [number, number];
|
||||
|
||||
getSpeed: () => number;
|
||||
|
||||
getMovedDistance: () => number;
|
||||
|
||||
getPathStartIdx: () => number;
|
||||
|
||||
getPathEndIdx: () => number;
|
||||
|
||||
moveByDistance: (distance: number) => void;
|
||||
|
||||
moveToPoint: (idx: number, tail: number) => void;
|
||||
|
||||
isCursorAtPathEnd: () => boolean;
|
||||
|
||||
isCursorAtPathStart: () => boolean;
|
||||
|
||||
setSpeed: (speed: number) => void;
|
||||
|
||||
setRange: (startIndex: number, endIndex: number) => void;
|
||||
|
||||
on: (eventName: string, handler: Function) => void;
|
||||
|
||||
off: (eventName: string, handler: Function) => void;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
const protocol = window.location.protocol;
|
||||
|
||||
const buildScriptTag = (src: string): HTMLScriptElement => {
|
||||
const script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.async = true;
|
||||
script.defer = true;
|
||||
script.src = src;
|
||||
return script;
|
||||
};
|
||||
|
||||
export const getAMapUiPromise = (version: string = '1.0'): Promise<any> => {
|
||||
if ((window as any).AMapUI) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const script = buildScriptTag(`${protocol}//webapi.amap.com/ui/${version}/main-async.js`);
|
||||
const pro = new Promise((resolve) => {
|
||||
script.onload = () => {
|
||||
(window as any).initAMapUI();
|
||||
resolve(true);
|
||||
};
|
||||
});
|
||||
|
||||
document.body.append(script);
|
||||
return pro;
|
||||
};
|
|
@ -12,6 +12,8 @@ import JUpload from './JUpload/index.vue'
|
|||
import { BasicLayoutPage, BlankLayoutPage, PageContainer } from './Layout'
|
||||
import Ellipsis from './Ellipsis/index.vue'
|
||||
import JEmpty from './Empty/index.vue'
|
||||
import AMapComponent from './AMapComponent/index.vue'
|
||||
import PathSimplifier from './AMapComponent/PathSimplifier.vue'
|
||||
|
||||
export default {
|
||||
install(app: App) {
|
||||
|
@ -30,5 +32,7 @@ export default {
|
|||
.component('PageContainer', PageContainer)
|
||||
.component('Ellipsis', Ellipsis)
|
||||
.component('JEmpty', JEmpty)
|
||||
.component('AMapComponent', AMapComponent)
|
||||
.component('PathSimplifier', PathSimplifier)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
<!-- 坐标点拾取组件 -->
|
||||
<template>
|
||||
<div style="width: 100%; height: 400px">
|
||||
<div style="position: relative">
|
||||
<div style="position: absolute; right: 0; top: 5px; z-index: 999">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="start">开始动画</a-button>
|
||||
<a-button type="primary" @click="stop">停止动画</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
<el-amap :center="center" :zooms="[3, 20]" @init="initMap" ref="map"></el-amap>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { initAMapApiLoader } from '@vuemap/vue-amap';
|
||||
import AMapUI from '@vuemap/vue-amap'
|
||||
import '@vuemap/vue-amap/dist/style.css';
|
||||
|
||||
initAMapApiLoader({
|
||||
key: 'a0415acfc35af15f10221bfa5a6850b4',
|
||||
securityJsCode: 'cae6108ec3dd222f946d1a7237c78be0',
|
||||
});
|
||||
|
||||
interface EmitProps {
|
||||
(e: 'update:points', data: string): void;
|
||||
}
|
||||
const props = defineProps({
|
||||
points: { type: Array, default: () => [] },
|
||||
});
|
||||
const emit = defineEmits<EmitProps>();
|
||||
|
||||
// 地图拾取的坐标点(经纬度字符串)
|
||||
const mapPoint = ref('');
|
||||
|
||||
const map = ref(null);
|
||||
|
||||
const center = ref([106.55, 29.56]);
|
||||
const marker = ref(null);
|
||||
|
||||
/**
|
||||
* 地图初始化
|
||||
* @param e
|
||||
*/
|
||||
const initMap = (e: any) => {
|
||||
console.log(e)
|
||||
// map = e;
|
||||
// const pointStr = mapPoint.value as string;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -3,12 +3,14 @@
|
|||
<div style="position: relative">
|
||||
<div style="position: absolute; right: 0; top: 5px; z-index: 999">
|
||||
<a-space>
|
||||
<a-button type="primary">开始动画</a-button>
|
||||
<a-button type="primary">停止动画</a-button>
|
||||
<a-button type="primary" @click="onStart">开始动画</a-button>
|
||||
<a-button type="primary" @click="onStop">停止动画</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
<AMap :points="geoList" />
|
||||
<AMapComponent style="height: 500px">
|
||||
<PathSimplifier :pathData="geoList" ref="amapPath"></PathSimplifier>
|
||||
</AMapComponent>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
|
@ -16,7 +18,6 @@
|
|||
import { getPropertyData } from '@/api/device/instance';
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import encodeQuery from '@/utils/encodeQuery';
|
||||
import AMap from './AMap.vue';
|
||||
|
||||
const instanceStore = useInstanceStore();
|
||||
|
||||
|
@ -33,6 +34,15 @@ const prop = defineProps({
|
|||
|
||||
const geoList = ref<any[]>([]);
|
||||
const loading = ref<boolean>(false);
|
||||
const amapPath = ref()
|
||||
|
||||
const onStart = () => {
|
||||
amapPath.value.start()
|
||||
}
|
||||
|
||||
const onStop = () => {
|
||||
amapPath.value.stop()
|
||||
}
|
||||
|
||||
const query = async () => {
|
||||
loading.value = true;
|
||||
|
@ -53,7 +63,10 @@ const query = async () => {
|
|||
((resp.result as any)?.data || []).forEach((item: any) => {
|
||||
list.push([item.value.lon, item.value.lat]);
|
||||
});
|
||||
geoList.value = list
|
||||
geoList.value = [{
|
||||
name: prop?.data?.name,
|
||||
path: list
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<a-modal title="详情" visible width="50vw" @ok="onCancel" @cancel="onCancel">
|
||||
<div style="margin-bottom: 10px"><TimeComponent v-model="dateValue" /></div>
|
||||
<div>
|
||||
<a-tabs v-model:activeKey="activeKey" style="max-height: 600px; overflow-y: auto">
|
||||
<a-tabs :destroyInactiveTabPane="true" v-model:activeKey="activeKey" style="max-height: 600px; overflow-y: auto">
|
||||
<a-tab-pane key="table" tab="列表">
|
||||
<Table :data="props.data" :time="_getTimes" />
|
||||
</a-tab-pane>
|
||||
|
|
Loading…
Reference in New Issue