index.tsx 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238
  1. import {
  2. defineComponent,
  3. reactive,
  4. PropType,
  5. onMounted,
  6. watchEffect,
  7. watch,
  8. computed,
  9. onUnmounted,
  10. } from 'vue';
  11. import {
  12. ResourceItemDetail,
  13. getResourceItem,
  14. ResourceItemDetailResponse,
  15. } from '@/api/resource';
  16. import { useRoute, useRouter } from 'vue-router';
  17. import isString from 'lodash/isString';
  18. import useMarkerStore, { MarkerType } from '@/store/useMarkerStore';
  19. import MapView from '../MapView';
  20. import { useDaHuaStore,useMainStore } from '@/store';
  21. import emitter from '@/utils/mitt';
  22. /** @ts-ignore */
  23. import icon_map_yjcl from '@/assets/icons/home/yjcl.svg';
  24. /** @ts-ignore */
  25. import icon_map_yjsj from '@/assets/icons/home/yjsj.svg';
  26. /** @ts-ignore */
  27. import icon_map_yjdw from '@/assets/icons/home/yjdw.svg';
  28. /** @ts-ignore */
  29. import icon_map_yjdw_w from '@/assets/icons/home/icon_map_yjdw_w.svg';
  30. /** @ts-ignore */
  31. import icon_map_yjck from '@/assets/icons/home/yjck.svg';
  32. /** @ts-ignore */
  33. import icon_map_yjck_w from '@/assets/icons/home/icon_map_yjck_w.svg';
  34. /** @ts-ignore */
  35. import icon_map_spjk from '@/assets/icons/home/spjk.svg';
  36. /** @ts-ignore */
  37. import icon_map_spjk_offline from '@/assets/icons/home/spjkoffline.svg';
  38. /** @ts-ignore */
  39. import icon_map_dcz from '@/assets/icons/home/icon_map_dcz.svg';
  40. /** @ts-ignore */
  41. import icon_map_dpf from '@/assets/icons/home/dpf.svg';
  42. /** @ts-ignore */
  43. import icon_map_dbsb from '@/assets/icons/home/dbsb.svg';
  44. /** @ts-ignore */
  45. import icon_map_dbsblx from '@/assets/icons/home/dbsblx.svg';
  46. // @ts-ignore
  47. import icon_map_traffic from '@/assets/icons/detail/source2@2x.png';
  48. import './index.scss';
  49. import {
  50. GET_INCIDENT_DIALOG_HTML,
  51. GET_TEAM_DIALOG_HTML,
  52. GET_VEHICLES_DIALOG_HTML,
  53. GET_VIDEO_DIALOG_HTML,
  54. GET_WAREHOUSE_DIALOG_HTML,
  55. GET_SINGLE_DEVICE_DIALOG_HTML,
  56. renderElement,
  57. } from './dialog';
  58. import { IncidentItemDetail } from '@/api/incident';
  59. import { useIncidentStore } from '@/store';
  60. import Popup from '../Popup';
  61. import { ElMessage } from 'element-plus';
  62. import { isEmpty, xor } from 'lodash';
  63. import clsx from 'clsx';
  64. import { SingleDeviceItem } from '@/api/resource';
  65. const MARKER_MAP_TYPES = [
  66. // '待派发事件',
  67. // '待处置事件',
  68. '路况信息',
  69. '视频监控',
  70. '应急车辆',
  71. '应急队伍',
  72. '应急仓库',
  73. '应急事件',
  74. '单兵',
  75. ] as const;
  76. export type MarkerMapType = typeof MARKER_MAP_TYPES[number];
  77. interface State {
  78. map: any;
  79. types: MarkerMapType[];
  80. trafficLayerIds: any[];
  81. timer?: NodeJS.Timer | null;
  82. trafficStatus: boolean;
  83. markers: MarkerType[];
  84. positions: string[];
  85. hasTypes: string[];
  86. markCircle: any;
  87. markCirclelayer:any;
  88. // theSocket: null;
  89. }
  90. interface ActionType {
  91. type: MarkerMapType;
  92. hasActioned: boolean;
  93. action: Function;
  94. remove: Function;
  95. }
  96. // locationType + resourceType
  97. const RESOURCE_ICON_MAP = {
  98. '11': icon_map_yjck_w, // 水上 仓库
  99. '21': icon_map_yjck, // 陆上 仓库
  100. '12': icon_map_yjdw_w, // 水上 队伍
  101. '22': icon_map_yjdw, // 陆上 队伍
  102. '13': icon_map_yjcl, // 水上 车辆
  103. '23': icon_map_yjcl, // 陆上 车辆
  104. };
  105. const getResourceIcon = (locationType: string, resourceType: string) => {
  106. // @ts-ignore
  107. return RESOURCE_ICON_MAP[locationType + resourceType] || '';
  108. };
  109. // 路况信息刷新间隔
  110. const REFRESH_TRAFFIC_TIME = 60000;
  111. // let theSocket = null;
  112. // let vPlayArea =null;
  113. const getIncidentImage = (status: MarkerType['status']) =>
  114. status?.toString() === '1'
  115. ? icon_map_yjsj
  116. : status?.toString() === '2'
  117. ? icon_map_dpf
  118. : status?.toString() === '3'
  119. ? icon_map_dcz
  120. : icon_map_yjsj;
  121. export default defineComponent({
  122. name: 'MarkerMap',
  123. props: {
  124. adrsMapTypes: {
  125. type: Array as PropType<string[]>,
  126. default: MARKER_MAP_TYPES,
  127. },
  128. marker: Object as PropType<IncidentItemDetail | undefined | null>,
  129. readonly: Boolean,
  130. },
  131. setup(props, ctx) {
  132. const state = reactive<State>({
  133. map: null,
  134. types: [],
  135. trafficLayerIds: [],
  136. timer: undefined,
  137. trafficStatus: false,
  138. markers: [],
  139. positions: [],
  140. hasTypes: [],
  141. markCircle: {},
  142. markCirclelayer: {},
  143. // theSocket:null
  144. });
  145. const store = useMarkerStore();
  146. const mainstore = useMainStore();
  147. const incidentStore = useIncidentStore();
  148. const daHuaStore = useDaHuaStore();
  149. const router = useRouter();
  150. const route = useRoute();
  151. // const theSocket = null;
  152. const adrsMapTypes = computed(() =>
  153. props.adrsMapTypes.map((i) => ({
  154. name: i,
  155. icon: ((name) => {
  156. switch(name)
  157. {
  158. case "路况信息": return icon_map_traffic;
  159. case "应急事件": return icon_map_dpf;
  160. case "视频监控": return icon_map_spjk;
  161. case "应急车辆": return icon_map_yjcl;
  162. case "应急队伍": return icon_map_yjdw;
  163. case "应急仓库": return icon_map_yjck;
  164. case "单兵": return icon_map_dbsb;
  165. }
  166. })(i),
  167. disabled: route.query.id && i === '应急事件',
  168. })),
  169. );
  170. const actionTypes = computed<ActionType[]>(() => [
  171. {
  172. type: '路况信息',
  173. hasActioned: state.trafficStatus,
  174. action: toggleControlTraffic,
  175. remove: toggleControlTraffic,
  176. },
  177. {
  178. type: '应急事件',
  179. hasActioned: state.hasTypes.includes('应急事件'),
  180. action: () =>
  181. handleAddMarkers('应急事件', store.incident, icon_map_dpf),
  182. remove: () => handleRemoveMarkers('应急事件', store.incident),
  183. },
  184. {
  185. type: '视频监控',
  186. hasActioned: state.hasTypes.includes('视频监控'),
  187. action: () =>
  188. // console.log(store.videoSurveillance);
  189. handleAddMarkers('视频监控', store.videoSurveillance, icon_map_spjk),
  190. remove: () => handleRemoveMarkers('视频监控', store.videoSurveillance),
  191. },
  192. {
  193. type: '应急车辆',
  194. hasActioned: state.hasTypes.includes('应急车辆'),
  195. action: () =>
  196. handleAddMarkers('应急车辆', store.emergencyVehicles, icon_map_yjcl),
  197. remove: () => handleRemoveMarkers('应急车辆', store.emergencyVehicles),
  198. },
  199. {
  200. type: '应急队伍',
  201. hasActioned: state.hasTypes.includes('应急队伍'),
  202. action: () =>
  203. handleAddMarkers('应急队伍', store.emergencyTeam, icon_map_yjdw),
  204. remove: () => handleRemoveMarkers('应急队伍', store.emergencyTeam),
  205. },
  206. {
  207. type: '应急仓库',
  208. hasActioned: state.hasTypes.includes('应急仓库'),
  209. action: () =>
  210. handleAddMarkers('应急仓库', store.emergencyWarehouse, icon_map_yjck),
  211. remove: () => handleRemoveMarkers('应急仓库', store.emergencyWarehouse),
  212. },
  213. {
  214. type: '单兵',
  215. hasActioned: state.hasTypes.includes('单兵'),
  216. action: () =>
  217. handleAddMarkers('单兵', store.singleDevice, icon_map_dbsb),
  218. remove: () => handleRemoveMarkers('单兵', store.singleDevice),
  219. },
  220. ]);
  221. const getMarkerPopupHTML = (type: MarkerMapType, marker: MarkerType) => {
  222. switch (type) {
  223. case '应急事件':
  224. // case '待派发事件':
  225. // case '预警事件':
  226. default:
  227. return GET_INCIDENT_DIALOG_HTML(marker, async () => {
  228. marker.id && (await incidentStore.getIncidentItem(marker.id));
  229. await router.push(`/incidentDetail?id=${marker?.id}`);
  230. handleSetDetailMarker(marker);
  231. store.currentIncident = marker;
  232. });
  233. case '单兵': {
  234. var callback = () => {
  235. if(marker['stat']==='online'){
  236. mainstore.videoisshow = true;
  237. // // try {
  238. // daHuaStore.DAHUAUserObj(marker["userId"]).then(deviceUser=>{
  239. mainstore.videotitle = marker["userName"] + "";
  240. // });
  241. const userid = marker['userId'];
  242. const dievicecode = marker['deviceCode']+"";
  243. const channelid = marker['channelId']+"";
  244. window.isdanbing = true;
  245. async function cremetting(){
  246. await window.metting.getLocalUserInfo();
  247. const res = await window.metting.queryUser(userid);
  248. window.metting.localUserInfo.name = "danbing_Meeting";
  249. await window.metting.creatMeeting();
  250. await window.metting.startInvite({
  251. userName: res.userName,
  252. userId: res.userCode,
  253. region: res.paasId,
  254. type: "single",
  255. // speak:'true',
  256. number: res.userPhone,
  257. deviceId: dievicecode,
  258. channelId: channelid,
  259. });
  260. }
  261. if(window.metting){
  262. const meeting = window.metting;
  263. meeting.settargetId("vPlayArea");
  264. meeting.playVideo({
  265. type: 'single',
  266. deviceId: marker['deviceCode'],
  267. channelId: channelid,
  268. });
  269. cremetting();
  270. setTimeout(() => {
  271. try{
  272. if(window.vPlayArea){
  273. window.vPlayArea.resize();
  274. window.vPlayArea.dragResize(0,0,0,0)
  275. }
  276. }catch(E){
  277. }
  278. }, 500);
  279. }else{
  280. const meeting = new Meeting();
  281. if(window.theSocket){
  282. meeting.settargetId("vPlayArea");
  283. meeting.playVideo({
  284. type: 'single',
  285. deviceId: marker['deviceCode'],
  286. channelId: channelid,
  287. });
  288. window.metting = meeting;
  289. setTimeout(() => {
  290. try{
  291. if(window.vPlayArea){
  292. window.vPlayArea.resize();
  293. window.vPlayArea.dragResize(0,0,0,0)
  294. }
  295. }catch(E){
  296. }
  297. }, 500);
  298. }else{
  299. meeting.openClient(() => {
  300. meeting.settargetId("vPlayArea");
  301. meeting.playVideo({
  302. type: 'single',
  303. deviceId: marker['deviceCode'],
  304. channelId: channelid,
  305. });
  306. window.metting = meeting;
  307. cremetting();
  308. setTimeout(() => {
  309. try{
  310. if(window.vPlayArea){
  311. window.vPlayArea.resize();
  312. window.vPlayArea.dragResize(0,0,0,0)
  313. }
  314. }catch(E){
  315. }
  316. }, 1000);
  317. });
  318. }
  319. }
  320. }else{
  321. ElMessage.warning('设备离线,请联系大华相关人员');
  322. }
  323. };
  324. return GET_SINGLE_DEVICE_DIALOG_HTML(
  325. marker as SingleDeviceItem,
  326. callback,
  327. );
  328. }
  329. case '应急仓库':
  330. return GET_WAREHOUSE_DIALOG_HTML(marker);
  331. case '应急车辆':
  332. return GET_VEHICLES_DIALOG_HTML(marker);
  333. case '应急队伍':
  334. return GET_TEAM_DIALOG_HTML(marker);
  335. case '视频监控':
  336. return GET_VIDEO_DIALOG_HTML(marker, (type=1) => {
  337. window.isdanbing = false;
  338. if (marker['status'] == 1) {
  339. // mainstore.videourl =
  340. // (import.meta.env.VITE_DH_SERVER as string) +
  341. // '/videoplay.html?channelId=' +
  342. // marker['deviceCode'];
  343. const channelid = marker['channelId']+"";
  344. if(type==1){
  345. mainstore.videoisshow = true;
  346. mainstore.videotitle = marker['name'] + "";
  347. if(window.metting){
  348. const meeting = window.metting;
  349. meeting.settargetId("vPlayArea");
  350. meeting.playVideo({
  351. type: 'single',
  352. deviceId: marker['deviceCode'],
  353. channelId: channelid,
  354. });
  355. setTimeout(() => {
  356. try{
  357. if(window.vPlayArea){
  358. window.vPlayArea.resize();
  359. window.vPlayArea.dragResize(0,0,0,0)
  360. }
  361. }catch(E){
  362. }
  363. }, 500);
  364. }else{
  365. const meeting = new Meeting();
  366. if(window.theSocket){
  367. meeting.settargetId("vPlayArea");
  368. meeting.playVideo({
  369. type: 'single',
  370. deviceId: marker['deviceCode'],
  371. channelId: channelid,
  372. });
  373. window.metting = meeting;
  374. setTimeout(() => {
  375. try{
  376. if(window.vPlayArea){
  377. window.vPlayArea.resize();
  378. window.vPlayArea.dragResize(0,0,0,0)
  379. }
  380. }catch(E){
  381. }
  382. }, 500);
  383. }else{
  384. meeting.openClient(() => {
  385. meeting.settargetId("vPlayArea");
  386. meeting.playVideo({
  387. type: 'single',
  388. deviceId: marker['deviceCode'],
  389. channelId: channelid,
  390. });
  391. window.metting = meeting;
  392. setTimeout(() => {
  393. try{
  394. if(window.vPlayArea){
  395. window.vPlayArea.resize();
  396. window.vPlayArea.dragResize(0,0,0,0)
  397. }
  398. }catch(E){
  399. }
  400. }, 1000);
  401. });
  402. }
  403. }
  404. }
  405. if(type==2){
  406. window.indexp = (window.indexp == 1?2:1);
  407. const meeting = new Meeting();
  408. if(window.theSocket){
  409. meeting.settargetId("relvideo"+window.indexp);
  410. meeting.playVideo({
  411. type: 'single',
  412. deviceId: marker['deviceCode'],
  413. channelId: channelid,
  414. });
  415. }else{
  416. meeting.openClient(() => {
  417. meeting.settargetId("relvideo"+window.indexp);
  418. meeting.playVideo({
  419. type: 'single',
  420. deviceId: marker['deviceCode'],
  421. channelId: channelid,
  422. });
  423. });
  424. }
  425. }
  426. // // try {
  427. // const meeting = new Meeting();
  428. // meeting.closeVideo();
  429. // meeting.openClient(() => {
  430. // meeting.settargetId("");
  431. // meeting.playVideo({
  432. // type: 'single',
  433. // deviceId: marker['deviceCode'],
  434. // channelId: channelid,
  435. // });
  436. // });
  437. // mainstore.videohandle = meeting;
  438. // } catch (E) { }
  439. // const DAHUA = document.getElementById('DAHUA') as HTMLIFrameElement;
  440. // DAHUA?.contentWindow?.postMessage(
  441. // {
  442. // key: 'video',
  443. // value: marker['userId'],
  444. // deviceId: marker['deviceCode'],
  445. // channelId: marker['channelId'],
  446. // },
  447. // '*',
  448. // );
  449. // daHuaStore.dahuaUserVisible = true;
  450. }else{
  451. ElMessage.warning('设备离线,请联系大华相关人员');
  452. }
  453. });
  454. }
  455. };
  456. const videoplay = (item: { type: string; userId: any; channelId: any }) => {
  457. // console.log('播放视频');
  458. // console.log(item);
  459. if (!window.theSocket.websocket) {
  460. ElMessage.warning('请先打开视频插件');
  461. return;
  462. }
  463. if (item.type == 'client') {
  464. window.slectOptionmini(item.userId).then((arr: any) => {
  465. if (arr) {
  466. window.vPlayArea.openAppVideo(arr);
  467. }
  468. });
  469. } else if (item.type == 'single') {
  470. //播放单兵视频
  471. pullFlow(item.channelId);
  472. } else if (item.type == 'vehicle') {
  473. //播放车载视频
  474. pullFlow(item.channelId);
  475. } else if (item.type == 'uav_dev') {
  476. //播放无人机视频
  477. pullFlow(item.channelId);
  478. }
  479. };
  480. const pullFlow = (channelId: any) => {
  481. channelId &&
  482. window.vPlayArea &&
  483. window.vPlayArea.realTimeVideo([{ channelId }]);
  484. };
  485. const initSocket = (
  486. userCode: string,
  487. memberObj: {
  488. userName?: any;
  489. userId: any;
  490. region?: any;
  491. type: string;
  492. number?: any;
  493. deviceId?: any;
  494. channelId: any;
  495. },
  496. ) => {
  497. const self = this;
  498. // console.log(userCode);
  499. localStorage.setItem('userId', userCode);
  500. //初始websocket实例,保存在window中方便调用。一个浏览器tab页面只能初始化一次。所有的控件窗口,通过该websocket实例去生成不同的窗口实例,不同的控件窗口通过自己的窗口实例去调用初始化、关闭、隐藏等
  501. window.theSocket = new window.InitWebSocketClass(
  502. userCode,
  503. localStorage.getItem('DAHUA_token'),
  504. {
  505. //客户端登陆成功通知;
  506. loginSuccess: (v: any) => {
  507. // console.log(2222222222222222);
  508. console.log('loginSuccess-->', v);
  509. initWnd();
  510. },
  511. //客户端窗口被拉起通知
  512. onCreateVideoSuccess: (v: any) => {
  513. console.log('客户端onCreateVideoSuccess-----', v);
  514. },
  515. //重点:统一分发客户端ws消息;vue 可以统一用$bus分发.第三方消息分发自定
  516. onSocketBackInfos: (data: {
  517. method: string;
  518. params: { result: number; handleName: string };
  519. }) => {
  520. //视频窗口创建成功通知
  521. if (
  522. data &&
  523. data.method === 'createVideoDialogReuslt' &&
  524. data.params.result === 0
  525. ) {
  526. if (data.params.handleName === '#vPlayArea') {
  527. //客户端窗口创建好后,界面显示窗口;
  528. window.vPlayArea.resize();
  529. ElMessage.warning('视频窗口创建成功!');
  530. videoplay(memberObj);
  531. }
  532. }
  533. },
  534. },
  535. );
  536. // console.log(theSocket);
  537. //socket实例初始化websocket回调方法;
  538. window.theSocket
  539. .initWebSocket()
  540. .then((v: any) => {
  541. if (v) {
  542. ElMessage.warning('视频插件登陆完成!');
  543. }
  544. })
  545. .catch(() => {
  546. ElMessage.warning('若要观看实时视频,请先安装视频插件');
  547. });
  548. };
  549. const initWnd = () => {
  550. // console.log(1111);
  551. //左边窗口类型参数 分割 2行2列
  552. const typeObj = {
  553. rows: 2,
  554. cols: 2,
  555. wndSpaceing: 10,
  556. embedVideoMode: true,
  557. playerCtrlBarEnable: false,
  558. displayMode: 0,
  559. playMode: 0,
  560. playParams: {},
  561. };
  562. //左边窗口实例
  563. window.vPlayArea = new window.VideoPlay(
  564. '#vPlayArea',
  565. window.theSocket.websocket, //一个浏览器tab页面公用一个
  566. window.theSocket.socketCode, //一个浏览器tab页面公用一个
  567. typeObj,
  568. );
  569. //左边窗口初始化
  570. window.vPlayArea.init();
  571. };
  572. const updateTrafficSource = () => {
  573. if (state.map.getSource('Traffic')) {
  574. state.map.removeSource('Traffic');
  575. }
  576. state.map.addSource('Traffic', {
  577. type: 'vector',
  578. traffic: true,
  579. tiles: [
  580. 'mineserver://data/dynamic-traffic/ertic?servicetype=0&z={z}&x={x}&y={y}',
  581. ],
  582. });
  583. };
  584. const updateTrafficLayerVisibility = (v: 'none' | 'visible') => {
  585. state.trafficLayerIds.forEach(function (id) {
  586. if (state.map?.getLayer(id)) {
  587. state.map?.setLayoutProperty(id, 'visibility', v);
  588. }
  589. });
  590. };
  591. const toggleControlTraffic = () => {
  592. if (state.trafficStatus) {
  593. state.trafficStatus = false;
  594. if (state.timer) {
  595. clearInterval(state.timer);
  596. state.timer = null;
  597. }
  598. updateTrafficLayerVisibility('none');
  599. } else {
  600. state.trafficStatus = true;
  601. if (state.timer) {
  602. clearInterval(state.timer);
  603. state.timer = null;
  604. }
  605. updateTrafficSource();
  606. state.timer = setInterval(function () {
  607. updateTrafficSource();
  608. }, REFRESH_TRAFFIC_TIME);
  609. updateTrafficLayerVisibility('visible');
  610. }
  611. };
  612. const handleAddMarkers = (
  613. type: MarkerMapType,
  614. markers: State['markers'],
  615. image: any,
  616. ) => {
  617. // console.log(state.markers);
  618. state.markers.push(
  619. ...markers.map((i) => {
  620. var nextImage1 =
  621. type === '应急事件'
  622. ? getIncidentImage(i?.status)
  623. : i.locationType && i.resourceType
  624. ? getResourceIcon(
  625. i.locationType.toString(),
  626. i.resourceType.toString(),
  627. )
  628. : image;
  629. try {
  630. // state.map.removeSource('pointSource');
  631. // state.map.removeLayer('polygonLayer');
  632. if (type === '应急事件') {
  633. state.markCircle = {
  634. type: 'geojson',
  635. data:turf.circle(
  636. [
  637. parseFloat(i.locations?.split(',')[0] ?? '0'),
  638. parseFloat(i.locations?.split(',')[1] ?? '0'),
  639. ],
  640. 5,
  641. {
  642. steps: 499,
  643. units: 'kilometers',
  644. properties: { foo: 'bar' },
  645. },
  646. ),
  647. };
  648. // mySource = state.markCircle;
  649. state.map.addSource('pointSource', state.markCircle);
  650. state.map.addSource('pointSource1', {
  651. type: 'geojson',
  652. data: turf.circle(
  653. [
  654. parseFloat(i.locations?.split(',')[0] ?? '0'),
  655. parseFloat(i.locations?.split(',')[1] ?? '0'),
  656. ],
  657. 10,
  658. {
  659. steps: 64,
  660. units: 'kilometers',
  661. properties: { foo: 'bar' },
  662. },
  663. ),
  664. });
  665. state.markCirclelayer = {
  666. id: 'polygonLayer',
  667. type: 'fill',
  668. source: 'pointSource',
  669. layout: {
  670. visibility: 'visible',
  671. },
  672. paint: {
  673. 'fill-antialias': true, //2d的半径是实际尺寸,单位是米,不是像素值
  674. 'fill-color': '#143981',
  675. 'fill-opacity': 0.5,
  676. 'fill-outline-color': 'red',
  677. 'fill-translate':[45,45]
  678. // 'fill-outline-width':20
  679. }
  680. };
  681. state.map.addLayer(state.markCirclelayer);
  682. state.map.addLayer({
  683. id: 'polygonLayer2',
  684. type: 'line',
  685. source: 'pointSource',
  686. layout: {},
  687. paint: {
  688. // 'fill-color': '#0dacfc',
  689. // 'fill-opacity': 0.2,
  690. // 'fill-translate': [-10, -10],
  691. 'line-color': '#143981',
  692. 'line-width': 5,
  693. 'line-translate': [45, 45],
  694. },
  695. });
  696. state.map.addLayer({
  697. id: 'polygonLayer3',
  698. type: 'fill',
  699. source: 'pointSource1',
  700. layout: {
  701. visibility: 'visible',
  702. },
  703. paint: {
  704. 'fill-antialias': true,
  705. 'fill-color': '#143981',
  706. 'fill-opacity': 0.3,
  707. 'fill-translate': [45, 45],
  708. },
  709. minzoom: 7, //2d不支持
  710. maxzoom: 17.5, //2d不支持
  711. });
  712. state.map.addLayer({
  713. id: 'polygonLayer1',
  714. type: 'line',
  715. source: 'pointSource1',
  716. layout: {},
  717. paint: {
  718. // 'fill-color': '#0dacfc',
  719. // 'fill-opacity': 0.2,
  720. // 'fill-translate': [-10, -10],
  721. 'line-color': '#143981',
  722. 'line-width': 5,
  723. 'line-translate':[45,45]
  724. },
  725. });
  726. }
  727. } catch (E) {}
  728. if (type == "视频监控") {
  729. if (i.isOnline == false) {
  730. nextImage1 = icon_map_spjk_offline;
  731. }
  732. }
  733. if (type == "单兵") {
  734. if (i.stat && i.stat==='offline') {
  735. nextImage1 = icon_map_dbsblx;
  736. }
  737. }
  738. const nextImage = nextImage1;
  739. const popup = new window.minemap.Popup({
  740. anchor: 'left',
  741. closeOnClick: true,
  742. closeButton: false,
  743. offset: [10, 25],
  744. maxWidth: 'max-content',
  745. // autoPan: true,
  746. }).setDOMContent(getMarkerPopupHTML(type, i));
  747. popup.on('open', function () {
  748. getResourceItem(i.id ?? 0).then(
  749. (res: ResourceItemDetailResponse) => {
  750. var element = document.getElementById('tbl-' + i.id);
  751. var header = [
  752. '序号',
  753. '名称',
  754. '型号',
  755. '规格',
  756. '仓储数量',
  757. '可用数量',
  758. '单位',
  759. ];
  760. try {
  761. element.innerHTML = ` <tr>
  762. ${header.map((i) => '<th>' + i + '</th>').join('')}
  763. </tr>${res.data.resourceDetailList?.map((itemc,index) => {
  764. return `<tr><td>${index + 1}</td><td>${itemc.name ?? '-'}</td><td>${
  765. itemc.model ?? '-'
  766. }</td><td>${itemc.size ?? '-'}</td><td>${itemc.num ?? '-'}</td><td>${
  767. itemc.availableNum ?? '-'
  768. }</td><td>${itemc.unit ?? '-'}</td></tr>`;
  769. }).join("")}`;
  770. } catch (error) {}
  771. },
  772. );
  773. });
  774. // popup.on('onmouseup',function(){
  775. // alert(11)
  776. // });
  777. // popup.getElement().onmouseout = ()=>{
  778. // alert(12)
  779. // }
  780. // debugger
  781. var imagep = renderElement(nextImage);
  782. if (type === '应急事件') {
  783. imagep.style.width = (24 * 3 * 2) / 576 + 'rem';
  784. imagep.style.height = (25 * 3 * 2) / 576 + 'rem';
  785. }
  786. var markp =
  787. new window.minemap.Marker(imagep, {
  788. offset: [-25, -25],
  789. });
  790. if(type==="单兵"){
  791. i.name = i.userName;
  792. }
  793. imagep.onmouseover = ()=>{
  794. //console.log(222)
  795. imagep.innerHTML = `<div style="position:relative"><div class="tipc">${i.name}</div></div>`
  796. markp.setZIndex(111);
  797. };
  798. imagep.onmouseout = ()=>{
  799. imagep.innerHTML = "";
  800. markp.setZIndex(0);
  801. };
  802. return {
  803. locations: i.locations,
  804. popup,
  805. marker: i.locations && markp.setLngLat({
  806. lng: ((i) => { var ii = parseFloat(i.locations?.split(',')[0] ?? "0"); return ii; })(i),
  807. lat: ((i) => { var ii = parseFloat(i.locations?.split(',')[1] ?? "0"); return ii>90?89:ii<-90?-89:ii; })(i) ,
  808. })
  809. .setPopup(popup)
  810. .addTo(state.map),
  811. };
  812. }),
  813. );
  814. state.positions.push(...markers.map((i) => i.locations).filter(isString));
  815. handleFitBounds();
  816. };
  817. const handleFitBounds = () => {
  818. if (state.positions.length === 0) {
  819. return;
  820. }
  821. const locations = state.positions.map((i) => i.split(',').map(Number));
  822. const leftTop = locations.reduce((carry, next) => {
  823. if (carry.length === 0) return next;
  824. var x = Math.min(carry[0], next[0]);
  825. var y = Math.max(carry[1], next[1]);
  826. return [x<118?118:x, y>35?35:y<30?30:y];
  827. }, []);
  828. // console.log(leftTop)
  829. const rightBottom = locations.reduce((carry, next) => {
  830. if (carry.length === 0) return next;
  831. return [Math.max(carry[0], next[0]), Math.min(carry[1], next[1])];
  832. }, []);
  833. const leftTopBounds: number[][] = new window.minemap.LngLat(...leftTop)
  834. .toBounds(5000)
  835. .toArray();
  836. const rightBottomBounds: number[][] = new window.minemap.LngLat(
  837. ...rightBottom,
  838. )
  839. .toBounds(5000)
  840. .toArray();
  841. state.map.fitBounds([
  842. rightBottomBounds.reduce((carry, next) => {
  843. if (carry.length === 0) return next;
  844. return [Math.max(carry[0], next[0]), Math.min(carry[1], next[1])];
  845. }, []),
  846. leftTopBounds.reduce((carry, next) => {
  847. if (carry.length === 0) return next;
  848. return [Math.min(carry[0], next[0]), Math.max(carry[1], next[1])];
  849. }, []),
  850. ]);
  851. };
  852. const handleRemoveMarkers = (
  853. type: MarkerMapType,
  854. markers: State['markers'],
  855. ) => {
  856. state.hasTypes = state.hasTypes.filter((t) => t !== type);
  857. const locations = markers.map((i) => i.locations);
  858. state.markers.forEach((m) => {
  859. if (locations.includes(m.locations)) {
  860. m.marker?.remove();
  861. m.popup?.remove();
  862. m.popup = null;
  863. m.marker = null;
  864. }
  865. });
  866. state.markers = state.markers.filter(
  867. (m) => !locations.includes(m.locations),
  868. );
  869. state.positions = state.positions.filter((p) => !locations.includes(p));
  870. handleFitBounds();
  871. };
  872. const handleRemoveAllMarkers = () => {
  873. try {
  874. state.map.removeSource('pointSource');
  875. state.map.removeSource('pointSource1');
  876. state.map.removeLayer('polygonLayer');
  877. state.map.removeLayer('polygonLayer1');
  878. state.map.removeLayer('polygonLayer2');
  879. state.map.removeLayer('polygonLayer3');
  880. } catch (E) { }
  881. state.markers.forEach((m) => {
  882. m.marker?.remove();
  883. m.popup?.remove();
  884. });
  885. state.markers = [];
  886. state.positions = [];
  887. };
  888. const handleSetDetailMarker = (marker: MarkerType) => {
  889. handleRemoveAllMarkers();
  890. const locations = marker.locations?.split(',').map(Number);
  891. if (!locations) {
  892. ElMessage.error({ message: '该点位无地址经纬度' });
  893. return;
  894. }
  895. // 获取事件周围的 5km 内的 监控点
  896. const bounds: number[][] = new window.minemap.LngLat(...locations)
  897. .toBounds(10000)
  898. .toArray();
  899. const videos = store.videoSurveillance.reduce((carry, next) => {
  900. const [lng, lat] = next.locations?.split(',') ?? [];
  901. // bounds [right-bottom[lng, lat],left-top[lng, lat], ]
  902. if (
  903. Number(lng) > bounds[0][0] &&
  904. Number(lng) < bounds[1][0] &&
  905. Number(lat) > bounds[0][1] &&
  906. Number(lat) < bounds[1][1]
  907. ) {
  908. carry.push(next);
  909. }
  910. return carry;
  911. }, [] as MarkerType[]);
  912. // 开启路况信息
  913. // if (!state.types.includes('路况信息')) {
  914. // state.types.push('路况信息');
  915. // state.hasTypes.push('路况信息');
  916. // toggleControlTraffic();
  917. // }
  918. handleAddMarkers('应急事件', [marker], icon_map_dpf);
  919. handleAddMarkers('视频监控', videos, icon_map_spjk);
  920. // state.markers[0].marker.togglePopup();
  921. };
  922. onMounted(async () => {
  923. window.minemap.util.getJSON(
  924. (import.meta.env.VITE_MAP_SERVER as string) +
  925. '/service/solu/style/id/12878',
  926. function (error, data) {
  927. data.layers.forEach(function (layer: any) {
  928. //判断是否道路线图层
  929. if (
  930. layer.type == 'line' &&
  931. layer.source == 'Traffic' &&
  932. layer['source-layer'] == 'Trafficrtic'
  933. ) {
  934. state.trafficLayerIds.push(layer.id);
  935. }
  936. });
  937. },
  938. );
  939. try {
  940. const html = document.createElement("div");
  941. actionTypes.value.forEach(atypes => {
  942. if (atypes?.action == null) return;
  943. const name = atypes.type;
  944. const c = (atypes?.action?.toString().split("handleAddMarkers"));
  945. if (c.length <= 1) return;
  946. const args1 = c[1].replaceAll("(","").replaceAll(")","");
  947. // debugger
  948. const args = args1.split(",");
  949. if (eval(args[2]) == undefined) return;
  950. const div = document.createElement("div");
  951. div.className = "tipitem"
  952. const imagee = renderElement((eval(args[2])));
  953. div.append(imagee);
  954. const c1 = document.createElement("span"); c1.innerHTML = (name);
  955. div.append(c1);
  956. html.append(div);
  957. });
  958. state.map["tipcontentRef"].append(html);
  959. } catch (ee) {
  960. }
  961. state.map.on('load', function () {
  962. updateTrafficSource();
  963. //如果底图没有配置路况图层,需要自己手动增加
  964. state.map.addLayer({
  965. id: 'trafficlines',
  966. type: 'line',
  967. source: 'Traffic',
  968. 'source-layer': 'Trafficrtic',
  969. layout: {
  970. 'line-join': 'round',
  971. 'line-cap': 'round',
  972. },
  973. paint: {
  974. 'line-color': {
  975. property: 'status',
  976. stops: [
  977. [0, '#999999'],
  978. [1, '#66cc00'],
  979. [2, '#ff9900'],
  980. [3, '#cc0000'],
  981. [4, '#9d0404'],
  982. ],
  983. },
  984. 'line-width': {
  985. stops: [
  986. [5, 1],
  987. [18, 3],
  988. ],
  989. base: 1.2,
  990. },
  991. },
  992. });
  993. state.trafficLayerIds.push('trafficlines');
  994. state.trafficStatus = false;
  995. updateTrafficLayerVisibility('none');
  996. store.getAllResources();
  997. store.getAllsingleDevice();
  998. store.getAllvideoDevice();
  999. store.getHDIncidentList();
  1000. });
  1001. if (!route.query.id) return;
  1002. // 如果存在id
  1003. await incidentStore.getIncidentItem(route.query.id as string);
  1004. // @ts-ignore
  1005. handleSetDetailMarker(incidentStore.incidentDetail.baseInfo ?? {});
  1006. // const vPlayAreaEl = document.getElementById('vPlayArea');
  1007. // vPlayAreaEl &&
  1008. // (vPlayAreaEl.style.height = window.innerHeight - 20 + 'px');
  1009. // window.theSocket && window.theSocket.resize();
  1010. });
  1011. emitter.on('dosearchstart', () => { handleRemoveMarkers('应急仓库', store.emergencyWarehouse);
  1012. });
  1013. emitter.on('dosearchend', () => {
  1014. handleAddMarkers('应急仓库', store.emergencyWarehouse, icon_map_yjck);
  1015. });
  1016. onUnmounted(() => {
  1017. emitter.off('dosearchstart', () => { });
  1018. emitter.off('dosearchend', () => {});
  1019. })
  1020. watch(
  1021. () => store?.currentIncident,
  1022. (next) => {
  1023. if (!isEmpty(next)) {
  1024. handleSetDetailMarker({
  1025. ...next,
  1026. });
  1027. } else {
  1028. handleRemoveAllMarkers();
  1029. if (state.types.includes('应急事件')) {
  1030. state.types.forEach((next) => {
  1031. actionTypes.value.forEach((item) => {
  1032. if (next === item.type) {
  1033. state.hasTypes.push(item.type);
  1034. item.action();
  1035. }
  1036. });
  1037. });
  1038. } else {
  1039. state.map?.flyTo({
  1040. center: [118.29564, 33.97441],
  1041. zoom: 14,
  1042. bearing: 10,
  1043. pitch: 30,
  1044. duration: 2000,
  1045. });
  1046. }
  1047. }
  1048. },
  1049. );
  1050. watch(
  1051. () => state.types,
  1052. (next) => {
  1053. actionTypes.value.forEach((item) => {
  1054. if (next.includes(item.type) && !item.hasActioned) {
  1055. state.hasTypes.push(item.type);
  1056. item.action();
  1057. }
  1058. if (!next.includes(item.type) && item.hasActioned) {
  1059. item.remove();
  1060. }
  1061. });
  1062. },
  1063. );
  1064. return () => (
  1065. <div class="task-map-container">
  1066. <MapView v-model:map={state.map} />
  1067. {/* <div style="width:35%;height:20%;background-color:blue"> */}
  1068. {/*<div id='vPlayArea' style="width:30%;"/>*/}
  1069. {/* </div> */}
  1070. <div
  1071. class={clsx('address-type-card-container', {
  1072. ['in-detail']: props.readonly,
  1073. })}>
  1074. <Popup
  1075. class={clsx('address-type-card', {
  1076. ['in-detail']: props.readonly,
  1077. })}>
  1078. <el-checkbox-group v-model={state.types}>
  1079. {adrsMapTypes.value &&
  1080. adrsMapTypes.value?.map((t) => (
  1081. <>
  1082. {!t.disabled && (
  1083. <el-checkbox key={t} class="card-item" label={t.name}>
  1084. <span>
  1085. <img class="tipicon" v-show={t.icon!=null} src={t.icon} />
  1086. {t.name}
  1087. </span>
  1088. </el-checkbox>
  1089. )}
  1090. </>
  1091. ))}
  1092. </el-checkbox-group>
  1093. <i class="card-border-bottom-left"></i>
  1094. <i class="card-border-bottom-right"></i>
  1095. </Popup>
  1096. </div>
  1097. </div>
  1098. );
  1099. },
  1100. });