|
@@ -0,0 +1,448 @@
|
|
|
+import {
|
|
|
+ defineComponent,
|
|
|
+ reactive,
|
|
|
+ PropType,
|
|
|
+ onMounted,
|
|
|
+ watchEffect,
|
|
|
+ watch,
|
|
|
+ computed,
|
|
|
+} from 'vue';
|
|
|
+import useMarkerStore, { MarkerType } from '@/store/useMarkerStore';
|
|
|
+import isString from 'lodash/isString';
|
|
|
+import MapView from '../MapView';
|
|
|
+import isEmpty from 'lodash/isEmpty';
|
|
|
+/** @ts-ignore */
|
|
|
+import icon_map_location from '@/assets/icons/home/icon_map_location@2x.png';
|
|
|
+/** @ts-ignore */
|
|
|
+import icon_map_yjcl from '@/assets/icons/home/icon_map_yjcl@2x.png';
|
|
|
+/** @ts-ignore */
|
|
|
+import icon_map_yjsj from '@/assets/icons/home/icon_map_yjsj@2x.png';
|
|
|
+/** @ts-ignore */
|
|
|
+import icon_map_yjdw from '@/assets/icons/home/icon_map_yjdw@2x.png';
|
|
|
+/** @ts-ignore */
|
|
|
+import icon_map_yjck from '@/assets/icons/home/icon_map_yjck@2x.png';
|
|
|
+/** @ts-ignore */
|
|
|
+import icon_map_spjk from '@/assets/icons/home/icon_map_spjk@2x.png';
|
|
|
+/** @ts-ignore */
|
|
|
+import icon_map_dcz from '@/assets/icons/home/icon_map_dcz@2x.png';
|
|
|
+/** @ts-ignore */
|
|
|
+import icon_map_dpf from '@/assets/icons/home/icon_map_dpf@2x.png';
|
|
|
+
|
|
|
+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';
|
|
|
+import { Object } from 'ol';
|
|
|
+import { useIncidentStore } from '@/store';
|
|
|
+import { useRoute, useRouter } from 'vue-router';
|
|
|
+import { ElMessage } from 'element-plus';
|
|
|
+
|
|
|
+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: Array as PropType<string[]>,
|
|
|
+ incident: Object as PropType<MarkerType>,
|
|
|
+ },
|
|
|
+ setup(props, ctx) {
|
|
|
+ const state = reactive<State>({
|
|
|
+ map: null,
|
|
|
+ types: [],
|
|
|
+ trafficLayerIds: [],
|
|
|
+ timer: undefined,
|
|
|
+ trafficStatus: false,
|
|
|
+ markers: [],
|
|
|
+ positions: [],
|
|
|
+ hasTypes: [],
|
|
|
+ });
|
|
|
+
|
|
|
+ const router = useRouter();
|
|
|
+
|
|
|
+ const store = useMarkerStore();
|
|
|
+ const incidentStore = useIncidentStore();
|
|
|
+
|
|
|
+ const actionTypes = computed<ActionType[]>(() => [
|
|
|
+ {
|
|
|
+ type: '路况信息',
|
|
|
+ hasActioned: state.trafficStatus,
|
|
|
+ action: toggleControlTraffic,
|
|
|
+ remove: toggleControlTraffic,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: '预警事件',
|
|
|
+ hasActioned: state.hasTypes.includes('预警事件'),
|
|
|
+ action: () =>
|
|
|
+ handleAddMarkers('预警事件', store.warningIncident, icon_map_yjsj),
|
|
|
+ remove: () => handleRemoveMarkers('预警事件', store.warningIncident),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: '待派发事件',
|
|
|
+ hasActioned: state.hasTypes.includes('待派发事件'),
|
|
|
+ action: () =>
|
|
|
+ handleAddMarkers('待派发事件', store.pendingIncident, icon_map_dpf),
|
|
|
+ remove: () => handleRemoveMarkers('待派发事件', store.pendingIncident),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: '待处置事件',
|
|
|
+ hasActioned: state.hasTypes.includes('待处置事件'),
|
|
|
+ action: () =>
|
|
|
+ handleAddMarkers(
|
|
|
+ '待处置事件',
|
|
|
+ store.pendingDisposalIncident,
|
|
|
+ icon_map_dcz,
|
|
|
+ ),
|
|
|
+ remove: () =>
|
|
|
+ handleRemoveMarkers('待处置事件', store.pendingDisposalIncident),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ 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, marker: MarkerType) => {
|
|
|
+ switch (type) {
|
|
|
+ case '待处置事件':
|
|
|
+ case '待派发事件':
|
|
|
+ case '预警事件':
|
|
|
+ default:
|
|
|
+ return GET_INCIDENT_DIALOG_HTML(marker, () =>
|
|
|
+ router.push(`/incident-management/status/${marker.status}`),
|
|
|
+ );
|
|
|
+ case '应急仓库':
|
|
|
+ return GET_WAREHOUSE_DIALOG_HTML(
|
|
|
+ {
|
|
|
+ name: marker.name,
|
|
|
+ address: marker.address,
|
|
|
+ manageUnit: marker.manageUnit,
|
|
|
+ contactName: marker.contactName,
|
|
|
+ contactPhone: marker.contactPhone,
|
|
|
+ },
|
|
|
+ () =>
|
|
|
+ router.push(
|
|
|
+ `/emergency-resources/resources/1/detail?id=${marker.id}&info=detail`,
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ case '应急车辆':
|
|
|
+ return GET_VEHICLES_DIALOG_HTML({
|
|
|
+ name: marker.name,
|
|
|
+ manageUnit: marker.manageUnit,
|
|
|
+ carType: marker.carType,
|
|
|
+ });
|
|
|
+ case '应急队伍':
|
|
|
+ return GET_TEAM_DIALOG_HTML({
|
|
|
+ name: marker.name,
|
|
|
+ manageUnit: marker.manageUnit,
|
|
|
+ carryGoods: marker.carryGoods,
|
|
|
+ num: marker.num,
|
|
|
+ contactName: marker.contactName,
|
|
|
+ contactPhone: marker.contactPhone,
|
|
|
+ });
|
|
|
+
|
|
|
+ case '视频监控':
|
|
|
+ return GET_VIDEO_DIALOG_HTML({
|
|
|
+ name: marker.name,
|
|
|
+ addr: marker.addr,
|
|
|
+ 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: true,
|
|
|
+ closeButton: false,
|
|
|
+ offset: [10, 25],
|
|
|
+ maxWidth: 'max-content',
|
|
|
+ // autoPan: true,
|
|
|
+ }).setDOMContent(getMarkerPopupHTML(type, i));
|
|
|
+ return {
|
|
|
+ locations: i.locations,
|
|
|
+ popup,
|
|
|
+ marker:
|
|
|
+ i.locations &&
|
|
|
+ new window.minemap.Marker(renderElement(image), {
|
|
|
+ offset: [-25, -25],
|
|
|
+ })
|
|
|
+ .setLngLat({
|
|
|
+ lng: i.locations?.split(',')[0],
|
|
|
+ lat: i.locations?.split(',')[1],
|
|
|
+ })
|
|
|
+ .setPopup(popup)
|
|
|
+ .addTo(state.map),
|
|
|
+ };
|
|
|
+ }),
|
|
|
+ );
|
|
|
+ state.positions.push(...markers.map((i) => i.locations).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.locations);
|
|
|
+ state.markers.forEach((m) => {
|
|
|
+ if (locations.includes(m.locations)) {
|
|
|
+ m.marker?.remove();
|
|
|
+ m.popup?.remove();
|
|
|
+ m.popup = null;
|
|
|
+ m.marker = null;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ state.markers = state.markers.filter(
|
|
|
+ (m) => !locations.includes(m.locations),
|
|
|
+ );
|
|
|
+ state.positions = state.positions.filter((p) => !locations.includes(p));
|
|
|
+
|
|
|
+ handleFitBounds();
|
|
|
+ };
|
|
|
+ const handleSetDetailMarker = (marker: MarkerType) => {
|
|
|
+ const locations = marker.locations?.split(',').map(Number);
|
|
|
+
|
|
|
+ if (!locations) {
|
|
|
+ ElMessage.error({ message: '该点位无地址经纬度' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let type: MarkerMapType = '预警事件';
|
|
|
+ let icon = icon_map_yjsj;
|
|
|
+ switch (marker.status?.toString()) {
|
|
|
+ case '1':
|
|
|
+ default:
|
|
|
+ type = '预警事件' as const;
|
|
|
+ icon = icon_map_yjsj;
|
|
|
+ break;
|
|
|
+ case '2':
|
|
|
+ type = '待派发事件' as const;
|
|
|
+ icon = icon_map_dpf;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case '3':
|
|
|
+ type = '待处置事件' as const;
|
|
|
+ icon = icon_map_dcz;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'warehouse':
|
|
|
+ type = '应急仓库' as const;
|
|
|
+ icon = icon_map_yjck;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'teams':
|
|
|
+ type = '应急队伍' as const;
|
|
|
+ icon = icon_map_yjdw;
|
|
|
+ break;
|
|
|
+ case 'vehicles':
|
|
|
+ type = '应急车辆' as const;
|
|
|
+ icon = icon_map_yjcl;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ handleAddMarkers(type, [marker], icon);
|
|
|
+ // handleAddMarkers('视频监控', videos, icon_map_spjk);
|
|
|
+ state.markers[0].marker.togglePopup();
|
|
|
+ };
|
|
|
+ onMounted(() => {
|
|
|
+ window.minemap.util.getJSON(
|
|
|
+ 'https://minedata.cn/service/solu/style/id/16857',
|
|
|
+ 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.trafficStatus = false;
|
|
|
+ updateTrafficLayerVisibility('none');
|
|
|
+ });
|
|
|
+ store.getAllResources();
|
|
|
+ store.getAllIncidents();
|
|
|
+ if (!isEmpty(store.currentIncident)) {
|
|
|
+ handleSetDetailMarker(store.currentIncident);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ 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} />
|
|
|
+ <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>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ },
|
|
|
+});
|