|
@@ -0,0 +1,411 @@
|
|
|
+import {
|
|
|
+ defineComponent,
|
|
|
+ reactive,
|
|
|
+ PropType,
|
|
|
+ onMounted,
|
|
|
+ watchEffect,
|
|
|
+ watch,
|
|
|
+ computed,
|
|
|
+} from 'vue';
|
|
|
+import { useRouter } from 'vue-router';
|
|
|
+import isString from 'lodash/isString';
|
|
|
+import useMarkerStore, { MarkerType } from '@/store/useMarkerStore';
|
|
|
+import MapView from '../MapView';
|
|
|
+
|
|
|
+/** @ts-ignore */
|
|
|
+import icon_map_yjcl from '@/assets/icons/home/yjcl.svg';
|
|
|
+/** @ts-ignore */
|
|
|
+import icon_map_yjsj from '@/assets/icons/home/yjsj.svg';
|
|
|
+/** @ts-ignore */
|
|
|
+import icon_map_yjdw from '@/assets/icons/home/yjdw.svg';
|
|
|
+/** @ts-ignore */
|
|
|
+import icon_map_yjck from '@/assets/icons/home/yjck.svg';
|
|
|
+/** @ts-ignore */
|
|
|
+import icon_map_spjk from '@/assets/icons/home/spjk.svg';
|
|
|
+/** @ts-ignore */
|
|
|
+import icon_map_dcz from '@/assets/icons/home/dcz.svg';
|
|
|
+/** @ts-ignore */
|
|
|
+import icon_map_dpf from '@/assets/icons/home/dpf.svg';
|
|
|
+
|
|
|
+console.log(icon_map_dpf);
|
|
|
+
|
|
|
+import './index.scss';
|
|
|
+import {
|
|
|
+ GET_INCIDENT_DIALOG_HTML,
|
|
|
+ GET_TEAM_DIALOG_HTML,
|
|
|
+ GET_VEHICLES_DIALOG_HTML,
|
|
|
+ GET_VIDEO_DIALOG_HTML,
|
|
|
+ GET_WAREHOUSE_DIALOG_HTML,
|
|
|
+ renderElement,
|
|
|
+} from './dialog';
|
|
|
+
|
|
|
+const MARKER_MAP_TYPES = [
|
|
|
+ // '待派发事件',
|
|
|
+ // '待处置事件',
|
|
|
+ '路况信息',
|
|
|
+ '视频监控',
|
|
|
+ '应急车辆',
|
|
|
+ '应急队伍',
|
|
|
+ '应急仓库',
|
|
|
+ '应急事件',
|
|
|
+] as const;
|
|
|
+
|
|
|
+export type MarkerMapType = typeof MARKER_MAP_TYPES[number];
|
|
|
+
|
|
|
+interface State {
|
|
|
+ map: any;
|
|
|
+ types: MarkerMapType[];
|
|
|
+ trafficLayerIds: any[];
|
|
|
+ timer?: NodeJS.Timer | null;
|
|
|
+ trafficStatus: boolean;
|
|
|
+ markers: MarkerType[];
|
|
|
+ positions: string[];
|
|
|
+ hasTypes: string[];
|
|
|
+}
|
|
|
+
|
|
|
+interface ActionType {
|
|
|
+ type: MarkerMapType;
|
|
|
+ hasActioned: boolean;
|
|
|
+ action: Function;
|
|
|
+ remove: Function;
|
|
|
+}
|
|
|
+
|
|
|
+// 路况信息刷新间隔
|
|
|
+const REFESH_TRAFFIC_TIME = 60000;
|
|
|
+
|
|
|
+export default defineComponent({
|
|
|
+ name: 'MarkerMap',
|
|
|
+ props: {
|
|
|
+ adrsMapTypes: {
|
|
|
+ type: Array as PropType<string[]>,
|
|
|
+ default: MARKER_MAP_TYPES,
|
|
|
+ },
|
|
|
+ readonly: Boolean,
|
|
|
+ },
|
|
|
+ setup(props, ctx) {
|
|
|
+ const state = reactive<State>({
|
|
|
+ map: null,
|
|
|
+ types: [],
|
|
|
+ trafficLayerIds: [],
|
|
|
+ timer: undefined,
|
|
|
+ trafficStatus: false,
|
|
|
+ markers: [],
|
|
|
+ positions: [],
|
|
|
+ hasTypes: [],
|
|
|
+ });
|
|
|
+
|
|
|
+ const store = useMarkerStore();
|
|
|
+ const router = useRouter();
|
|
|
+
|
|
|
+ const actionTypes = computed<ActionType[]>(() => [
|
|
|
+ {
|
|
|
+ type: '路况信息',
|
|
|
+ hasActioned: state.trafficStatus,
|
|
|
+ action: toggleControlTraffic,
|
|
|
+ remove: toggleControlTraffic,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: '应急事件',
|
|
|
+ hasActioned: state.hasTypes.includes('应急事件'),
|
|
|
+ action: () =>
|
|
|
+ handleAddMarkers('应急事件', store.pendingIncident, icon_map_dpf),
|
|
|
+ remove: () => handleRemoveMarkers('应急事件', store.pendingIncident),
|
|
|
+ },
|
|
|
+
|
|
|
+ {
|
|
|
+ type: '视频监控',
|
|
|
+ hasActioned: state.hasTypes.includes('视频监控'),
|
|
|
+ action: () =>
|
|
|
+ handleAddMarkers('视频监控', store.videoSurveillance, icon_map_spjk),
|
|
|
+ remove: () => handleRemoveMarkers('视频监控', store.videoSurveillance),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: '应急车辆',
|
|
|
+ hasActioned: state.hasTypes.includes('应急车辆'),
|
|
|
+ action: () =>
|
|
|
+ handleAddMarkers('应急车辆', store.emergencyVehicles, icon_map_yjcl),
|
|
|
+ remove: () => handleRemoveMarkers('应急车辆', store.emergencyVehicles),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: '应急队伍',
|
|
|
+ hasActioned: state.hasTypes.includes('应急队伍'),
|
|
|
+ action: () =>
|
|
|
+ handleAddMarkers('应急队伍', store.emergencyTeam, icon_map_yjdw),
|
|
|
+ remove: () => handleRemoveMarkers('应急队伍', store.emergencyTeam),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: '应急仓库',
|
|
|
+ hasActioned: state.hasTypes.includes('应急仓库'),
|
|
|
+ action: () =>
|
|
|
+ handleAddMarkers('应急仓库', store.emergencyWarehouse, icon_map_yjck),
|
|
|
+ remove: () => handleRemoveMarkers('应急仓库', store.emergencyWarehouse),
|
|
|
+ },
|
|
|
+ ]);
|
|
|
+
|
|
|
+ const getMarkerPopupHTML = (type: MarkerMapType) => {
|
|
|
+ switch (type) {
|
|
|
+ case '应急事件':
|
|
|
+ // case '待派发事件':
|
|
|
+ // case '预警事件':
|
|
|
+ default:
|
|
|
+ return GET_INCIDENT_DIALOG_HTML(
|
|
|
+ {
|
|
|
+ name: '事件名称',
|
|
|
+ addr: 'su qian',
|
|
|
+ },
|
|
|
+ () => router.push('/incidentDetail'),
|
|
|
+ );
|
|
|
+ case '应急仓库':
|
|
|
+ return GET_WAREHOUSE_DIALOG_HTML({
|
|
|
+ name: '应急仓库 111',
|
|
|
+ address: 'suqian',
|
|
|
+ manageUnit: 'xxxx',
|
|
|
+ contactName: '张三',
|
|
|
+ contactPhone: '138 1234 1234',
|
|
|
+ });
|
|
|
+ case '应急车辆':
|
|
|
+ return GET_VEHICLES_DIALOG_HTML({
|
|
|
+ name: '苏A· 12345',
|
|
|
+ manageUnit: 'xxxx',
|
|
|
+ });
|
|
|
+ case '应急队伍':
|
|
|
+ return GET_TEAM_DIALOG_HTML({
|
|
|
+ name: 'Team 1',
|
|
|
+ manageUnit: 'xxxxx',
|
|
|
+ // carryGoods: '水, 饮料',
|
|
|
+ // num: '2',
|
|
|
+ contactName: '张三',
|
|
|
+ contactPhone: '138 1234 1234',
|
|
|
+ });
|
|
|
+
|
|
|
+ case '视频监控':
|
|
|
+ return GET_VIDEO_DIALOG_HTML({
|
|
|
+ name: '12312313',
|
|
|
+ addr: 'su qian',
|
|
|
+ link: '',
|
|
|
+ });
|
|
|
+ }
|
|
|
+ };
|
|
|
+ const updateTrafficSource = () => {
|
|
|
+ if (state.map.getSource('Traffic')) {
|
|
|
+ state.map.removeSource('Traffic');
|
|
|
+ }
|
|
|
+ state.map.addSource('Traffic', {
|
|
|
+ type: 'vector',
|
|
|
+ traffic: true,
|
|
|
+ tiles: [
|
|
|
+ 'mineserver://data/dynamic-traffic/ertic?servicetype=0&z={z}&x={x}&y={y}',
|
|
|
+ ],
|
|
|
+ });
|
|
|
+ };
|
|
|
+ const updateTrafficLayerVisibility = (v: 'none' | 'visible') => {
|
|
|
+ state.trafficLayerIds.forEach(function (id) {
|
|
|
+ if (state.map?.getLayer(id)) {
|
|
|
+ state.map?.setLayoutProperty(id, 'visibility', v);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ };
|
|
|
+ const toggleControlTraffic = () => {
|
|
|
+ if (state.trafficStatus) {
|
|
|
+ state.trafficStatus = false;
|
|
|
+ if (state.timer) {
|
|
|
+ clearInterval(state.timer);
|
|
|
+ state.timer = null;
|
|
|
+ }
|
|
|
+ updateTrafficLayerVisibility('none');
|
|
|
+ } else {
|
|
|
+ state.trafficStatus = true;
|
|
|
+ if (state.timer) {
|
|
|
+ clearInterval(state.timer);
|
|
|
+ state.timer = null;
|
|
|
+ }
|
|
|
+ updateTrafficSource();
|
|
|
+ state.timer = setInterval(function () {
|
|
|
+ updateTrafficSource();
|
|
|
+ }, REFESH_TRAFFIC_TIME);
|
|
|
+ updateTrafficLayerVisibility('visible');
|
|
|
+ }
|
|
|
+ };
|
|
|
+ const handleAddMarkers = (
|
|
|
+ type: MarkerMapType,
|
|
|
+ markers: State['markers'],
|
|
|
+ image: any,
|
|
|
+ ) => {
|
|
|
+ state.markers.push(
|
|
|
+ ...markers.map((i) => {
|
|
|
+ const popup = new window.minemap.Popup({
|
|
|
+ anchor: 'left',
|
|
|
+ closeOnClick: false,
|
|
|
+ closeButton: false,
|
|
|
+ offset: [10, 25],
|
|
|
+ maxWidth: 'max-content',
|
|
|
+ // autoPan: true,
|
|
|
+ }).setDOMContent(getMarkerPopupHTML(type));
|
|
|
+ return {
|
|
|
+ location: i.location,
|
|
|
+ popup,
|
|
|
+ marker: new window.minemap.Marker(renderElement(image), {
|
|
|
+ offset: [-25, -25],
|
|
|
+ })
|
|
|
+ .setLngLat({
|
|
|
+ lng: i.location?.split(',')[0],
|
|
|
+ lat: i.location?.split(',')[1],
|
|
|
+ })
|
|
|
+ .setPopup(popup)
|
|
|
+ .addTo(state.map),
|
|
|
+ };
|
|
|
+ }),
|
|
|
+ );
|
|
|
+ state.positions.push(...markers.map((i) => i.location).filter(isString));
|
|
|
+
|
|
|
+ handleFitBounds();
|
|
|
+ };
|
|
|
+ const handleFitBounds = () => {
|
|
|
+ if (state.positions.length === 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const locations = state.positions.map((i) => i.split(',').map(Number));
|
|
|
+
|
|
|
+ const leftTop = locations.reduce((carry, next) => {
|
|
|
+ if (carry.length === 0) return next;
|
|
|
+ return [Math.min(carry[0], next[0]), Math.max(carry[1], next[1])];
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ const rightBottom = locations.reduce((carry, next) => {
|
|
|
+ if (carry.length === 0) return next;
|
|
|
+ return [Math.max(carry[0], next[0]), Math.min(carry[1], next[1])];
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ const leftTopBounds: number[][] = new window.minemap.LngLat(...leftTop)
|
|
|
+ .toBounds(10000)
|
|
|
+ .toArray();
|
|
|
+
|
|
|
+ const rightBottomBounds: number[][] = new window.minemap.LngLat(
|
|
|
+ ...rightBottom,
|
|
|
+ )
|
|
|
+ .toBounds(10000)
|
|
|
+ .toArray();
|
|
|
+
|
|
|
+ state.map.fitBounds([
|
|
|
+ rightBottomBounds.reduce((carry, next) => {
|
|
|
+ if (carry.length === 0) return next;
|
|
|
+ return [Math.max(carry[0], next[0]), Math.min(carry[1], next[1])];
|
|
|
+ }, []),
|
|
|
+ leftTopBounds.reduce((carry, next) => {
|
|
|
+ if (carry.length === 0) return next;
|
|
|
+ return [Math.min(carry[0], next[0]), Math.max(carry[1], next[1])];
|
|
|
+ }, []),
|
|
|
+ ]);
|
|
|
+ };
|
|
|
+ const handleRemoveMarkers = (
|
|
|
+ type: MarkerMapType,
|
|
|
+ markers: State['markers'],
|
|
|
+ ) => {
|
|
|
+ const locations = markers.map((i) => i.location);
|
|
|
+ state.markers.forEach((m) => {
|
|
|
+ if (locations.includes(m.location)) {
|
|
|
+ m.marker.remove();
|
|
|
+ m.popup.remove();
|
|
|
+ m.popup = null;
|
|
|
+ m.marker = null;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ state.markers = state.markers.filter(
|
|
|
+ (m) => !locations.includes(m.location),
|
|
|
+ );
|
|
|
+ state.positions = state.positions.filter((p) => !locations.includes(p));
|
|
|
+
|
|
|
+ handleFitBounds();
|
|
|
+ };
|
|
|
+ onMounted(() => {
|
|
|
+ window.minemap.util.getJSON(
|
|
|
+ 'https://minedata.cn/service/solu/style/id/12878',
|
|
|
+ function (error, data) {
|
|
|
+ data.layers.forEach(function (layer: any) {
|
|
|
+ //判断是否道路线图层
|
|
|
+ if (
|
|
|
+ layer.type == 'line' &&
|
|
|
+ layer.source == 'Traffic' &&
|
|
|
+ layer['source-layer'] == 'Trafficrtic'
|
|
|
+ ) {
|
|
|
+ state.trafficLayerIds.push(layer.id);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ );
|
|
|
+
|
|
|
+ state.map.on('load', function () {
|
|
|
+ updateTrafficSource();
|
|
|
+
|
|
|
+ //如果底图没有配置路况图层,需要自己手动增加
|
|
|
+ state.map.addLayer({
|
|
|
+ id: 'trafficlines',
|
|
|
+ type: 'line',
|
|
|
+ source: 'Traffic',
|
|
|
+ 'source-layer': 'Trafficrtic',
|
|
|
+ layout: {
|
|
|
+ 'line-join': 'round',
|
|
|
+ 'line-cap': 'round',
|
|
|
+ },
|
|
|
+ paint: {
|
|
|
+ 'line-color': {
|
|
|
+ property: 'status',
|
|
|
+ stops: [
|
|
|
+ [0, '#999999'],
|
|
|
+ [1, '#66cc00'],
|
|
|
+ [2, '#ff9900'],
|
|
|
+ [3, '#cc0000'],
|
|
|
+ [4, '#9d0404'],
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ 'line-width': {
|
|
|
+ stops: [
|
|
|
+ [5, 1],
|
|
|
+ [18, 3],
|
|
|
+ ],
|
|
|
+ base: 1.2,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ state.trafficLayerIds.push('trafficlines');
|
|
|
+ state.trafficStatus = false;
|
|
|
+ updateTrafficLayerVisibility('none');
|
|
|
+ });
|
|
|
+ });
|
|
|
+ watch(
|
|
|
+ () => state.types,
|
|
|
+ (next) => {
|
|
|
+ actionTypes.value.forEach((item) => {
|
|
|
+ if (next.includes(item.type) && !item.hasActioned) {
|
|
|
+ state.hasTypes.push(item.type);
|
|
|
+ item.action();
|
|
|
+ }
|
|
|
+ if (!next.includes(item.type) && item.hasActioned) {
|
|
|
+ state.hasTypes = state.hasTypes.filter((t) => t !== item.type);
|
|
|
+ item.remove();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ );
|
|
|
+
|
|
|
+ return () => (
|
|
|
+ <div class="task-map-container">
|
|
|
+ <MapView v-model:map={state.map} />
|
|
|
+ {!props.readonly && (
|
|
|
+ <div class="address-type-card">
|
|
|
+ <el-checkbox-group v-model={state.types}>
|
|
|
+ {props.adrsMapTypes &&
|
|
|
+ props.adrsMapTypes.map((t) => (
|
|
|
+ <el-checkbox key={t} class="card-item" label={t} />
|
|
|
+ ))}
|
|
|
+ </el-checkbox-group>
|
|
|
+ <i class="card-border-bottom-left"></i>
|
|
|
+ <i class="card-border-bottom-right"></i>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ },
|
|
|
+});
|