index.vue 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177
  1. <template>
  2. <div class="menu-content">
  3. <div class="gradient-text title">实时标绘</div>
  4. <div class="line">
  5. <div class="tabs1">
  6. <div v-for="(item, index) in menu" :key="index" :class="menuActive1 === index ? 'tab tab_active' : 'tab'" @click="clickTab(index)">
  7. {{ item.name }}
  8. </div>
  9. </div>
  10. <div class="btn-box">
  11. <div v-show="!collaboration" class="btn2" @click="handleShare('1')">协同标绘</div>
  12. <div v-show="collaboration" class="btn2" @click="handleCloseCollaboration">关闭协同</div>
  13. <div v-show="collaboration" class="btn2" style="margin-left: 20px">保存</div>
  14. </div>
  15. </div>
  16. <div class="btn1" @click="handleScreenshot">
  17. <div class="icon1"></div>
  18. 当前地图截图导出
  19. </div>
  20. <div v-if="menuActive1 === 0" class="content">
  21. <div class="box1">
  22. <div class="box-item">
  23. <div class="btn" @click="handleUndo">
  24. <div class="revoke-icon"></div>
  25. 撤回
  26. </div>
  27. <div class="line2"></div>
  28. <div class="btn">
  29. <div class="delete-icon"></div>
  30. 删除
  31. </div>
  32. <div class="line2"></div>
  33. <div class="btn" @click="handleShowDialog">
  34. <div class="save-icon"></div>
  35. 保存
  36. </div>
  37. </div>
  38. <div class="btn">
  39. <div class="setting-icon"></div>
  40. 设置
  41. </div>
  42. </div>
  43. <div class="tab-content">
  44. <div class="tabs2">
  45. <div
  46. v-for="(item, index) in menu[menuActive1].children"
  47. :key="index"
  48. :class="menuActive2 === index ? 'tab tab_active' : 'tab'"
  49. @click="clickTab2(index)"
  50. >
  51. {{ item.name }}
  52. </div>
  53. </div>
  54. <div class="tab-content2">
  55. <div v-show="menuActive2 === 0" class="line3">
  56. <LineWidthSelect v-model="mouseToolState.lineWidth" :options="lineWidthOptions" />
  57. <div class="color-container">
  58. <el-color-picker v-model="mouseToolState.color" popper-class="custom-color-picker" show-alpha />
  59. </div>
  60. </div>
  61. <div class="tab-list">
  62. <div
  63. v-for="(item, index) in menu[menuActive1].children[menuActive2].children"
  64. :key="index"
  65. :class="menuActive3 === index ? 'tab tab_active' : 'tab'"
  66. @click="clickTab3(item, index)"
  67. >
  68. <div :class="menuActive3 === index ? 'checked2' : 'checked1'"></div>
  69. <img :src="item.image" class="icon" />
  70. {{ item.name }}
  71. </div>
  72. </div>
  73. </div>
  74. </div>
  75. </div>
  76. <div v-else-if="menuActive1 === 1" class="tab-content3">
  77. <!-- <div class="box1">-->
  78. <!-- <div class="box-item">-->
  79. <!-- <div class="btn">-->
  80. <!-- <div class="merge-icon"></div>-->
  81. <!-- 合并-->
  82. <!-- </div>-->
  83. <!-- </div>-->
  84. <!-- </div>-->
  85. <div class="params-box">
  86. <el-input v-model="queryParams.pattern_name" class="custom-input" placeholder="请输入" @input="handleQuery">
  87. <template #prefix>
  88. <el-icon class="el-input__icon"><search /></el-icon>
  89. </template>
  90. </el-input>
  91. </div>
  92. <div class="custom-table">
  93. <div class="table-header">
  94. <div class="th">预案名称</div>
  95. <div class="th">操作</div>
  96. </div>
  97. <div v-for="(item, index) in patternList" :key="index" class="tr">
  98. <div class="td">
  99. <div>{{ item.pattern_name }}</div>
  100. </div>
  101. <div class="td td2">
  102. <div class="btn" @click="handleDelete(item.id)">
  103. <div class="delete-icon"></div>
  104. 删除
  105. </div>
  106. <div class="line2"></div>
  107. <div class="btn" @click="handleEdit(item.id)">
  108. <div class="edit-icon"></div>
  109. 编辑
  110. </div>
  111. <div class="line2"></div>
  112. <div class="btn" @click="handleShare('2', item.id)">
  113. <div class="share-icon"></div>
  114. 分享
  115. </div>
  116. </div>
  117. </div>
  118. <div class="footer">
  119. <el-pagination
  120. background
  121. :hide-on-single-page="true"
  122. layout="total, prev, pager, next"
  123. :total="total"
  124. :page-size="queryParams.pageSize"
  125. :current-page="queryParams.page"
  126. @current-change="getList"
  127. />
  128. </div>
  129. </div>
  130. </div>
  131. </div>
  132. <!--添加文字-->
  133. <TextEdit v-model="showTextEdit" @add-text="addText" />
  134. <!--绘制提示信息-->
  135. <div v-show="tipTitle !== ''" class="tipTitle">{{ tipTitle }}</div>
  136. <!--保存修改弹窗-->
  137. <EditDialog v-if="showEdit" v-model="showEdit" :edit-data="editData" @submit="handleSubmit" />
  138. <Contact v-if="shareState.showShare" v-model="shareState.showShare" @close="handleCloseShare" @confirm="handleShareConfirm" />
  139. </template>
  140. <script lang="ts" setup name="OnlinePlotting">
  141. import { nanoid } from 'nanoid';
  142. import { deepClone } from '@/utils';
  143. import { useHistory } from '@/hooks/useHistory';
  144. import { deletePatternById, getPatternInfo, getPatternList, startCollaboration } from '@/api/globalMap/onlinePlotting';
  145. import TextEdit from '@/views/globalMap/RightMenu/OnlinePlotting/TextEdit.vue';
  146. import EditDialog from '@/views/globalMap/RightMenu/OnlinePlotting/EditDialog.vue';
  147. import { Search } from '@element-plus/icons-vue';
  148. import html2canvas from 'html2canvas';
  149. import websocketStore from '@/store/modules/websocketStore';
  150. import { tempData } from './tempData';
  151. const { proxy } = getCurrentInstance() as ComponentInternalInstance;
  152. const userWebsocket = websocketStore();
  153. const getDrawTool = inject('getDrawTool');
  154. const getMap = inject('getMap');
  155. const getMapUtils = inject('getMapUtils');
  156. const containerScale = inject('containerScale');
  157. const { currentState, commit, undo, history, future } = useHistory();
  158. const emits = defineEmits(['getCollaborationData']);
  159. const getImageUrl = (name) => {
  160. return new URL(`../../../../assets/images/map/rightMenu/onlinePlotting/icon/${name}.png`, import.meta.url).href;
  161. };
  162. let drawing = ref(false);
  163. const mouseToolState = ref<MouseTool>({
  164. color: 'rgba(248, 1, 2, 1)',
  165. lineWidth: '1',
  166. graphicsType: ''
  167. });
  168. const menuActive1 = ref<string | number>(0);
  169. const menuActive2 = ref<string | number>(0);
  170. const menuActive3 = ref<string | number>('');
  171. const menu = ref([
  172. {
  173. name: '标绘工具',
  174. children: [
  175. {
  176. name: '基本工具',
  177. value: 'basicTools',
  178. children: [
  179. // {
  180. // name: '直箭头',
  181. // value: 'straightArrow',
  182. // image: getImageUrl('straightArrow')
  183. // },
  184. {
  185. name: '矩形',
  186. value: 'rectangle',
  187. image: getImageUrl('rectangle')
  188. },
  189. {
  190. name: '任意面',
  191. value: 'polygon',
  192. image: getImageUrl('polygon')
  193. },
  194. // {
  195. // name: '任意线',
  196. // value: 'anyLine',
  197. // image: getImageUrl('anyLine')
  198. // },
  199. {
  200. name: '圆',
  201. value: 'circle',
  202. image: getImageUrl('circle')
  203. },
  204. {
  205. name: '直线',
  206. value: 'straightLine',
  207. image: getImageUrl('straightLine')
  208. },
  209. {
  210. name: '文字',
  211. value: 'text',
  212. image: getImageUrl('text')
  213. },
  214. {
  215. name: '面积',
  216. value: 'measureArea',
  217. image: getImageUrl('measureArea')
  218. }
  219. ]
  220. },
  221. {
  222. name: '火点',
  223. value: 'firePoint',
  224. children: [
  225. { name: '起火点', value: 'marker', image: getImageUrl('firePoint'), icon: getImageUrl('firePoint'), size: [166, 88] },
  226. { name: '烟点', value: 'marker', image: getImageUrl('smokePoint'), icon: getImageUrl('smokePoint'), size: [166, 88] },
  227. { name: '已灭火点', value: 'marker', image: getImageUrl('extinguishedPoint'), icon: getImageUrl('extinguishedPoint'), size: [166, 88] }
  228. ]
  229. },
  230. {
  231. name: '火线',
  232. value: 'firewire',
  233. children: [
  234. { name: '火线', value: 'marker', image: getImageUrl('firewire'), icon: getImageUrl('firewire'), size: [166, 88] },
  235. { name: '受控火线', value: 'marker', image: getImageUrl('controlledfireline'), icon: getImageUrl('controlledfireline'), size: [166, 88] },
  236. { name: '已灭火线', value: 'marker', image: getImageUrl('extinguishedline'), icon: getImageUrl('extinguishedline'), size: [166, 88] },
  237. { name: '强火线', value: 'marker', image: getImageUrl('StrongFrontline'), icon: getImageUrl('StrongFrontline'), size: [166, 88] },
  238. { name: '中火线', value: 'marker', image: getImageUrl('ZhongxianLine'), icon: getImageUrl('ZhongxianLine'), size: [166, 88] },
  239. { name: '弱火线', value: 'marker', image: getImageUrl('WeakFrontline'), icon: getImageUrl('WeakFrontline'), size: [166, 88] }
  240. ]
  241. },
  242. {
  243. name: '火场',
  244. value: 'fireGround',
  245. children: [
  246. { name: '火场', value: 'marker', image: getImageUrl('fireground'), icon: getImageUrl('fireground'), size: [166, 88] },
  247. {
  248. name: '受控火场',
  249. value: 'marker',
  250. image: getImageUrl('controlledfireground'),
  251. icon: getImageUrl('controlledfireground'),
  252. size: [166, 88]
  253. },
  254. {
  255. name: '已灭火场',
  256. value: 'marker',
  257. image: getImageUrl('extinguishedfireground'),
  258. icon: getImageUrl('extinguishedfireground'),
  259. size: [166, 88]
  260. }
  261. ]
  262. },
  263. {
  264. name: '箭头',
  265. value: 'arrow',
  266. children: [
  267. { name: '曲箭头', value: 'marker', image: getImageUrl('curvedarrow'), icon: getImageUrl('curvedarrow'), size: [166, 88] },
  268. { name: '直箭头', value: 'marker', image: getImageUrl('straightarrow1'), icon: getImageUrl('straightarrow1'), size: [166, 88] },
  269. { name: '细箭头', value: 'marker', image: getImageUrl('thinarrow'), icon: getImageUrl('thinarrow'), size: [166, 88] }
  270. ]
  271. },
  272. // {
  273. // name: '导航',
  274. // value: 'navigation',
  275. // children: [
  276. // { name: '导航', value: 'marker', image: getImageUrl('navigation'), icon: getImageUrl('navigation'), size: [166, 88] }
  277. // ]
  278. // },
  279. {
  280. name: '扑救队伍',
  281. value: 'firefightingTeam',
  282. children: [
  283. { name: '指挥中心', value: 'marker', image: getImageUrl('commandcentre'), icon: getImageUrl('commandcentre'), size: [166, 88] },
  284. { name: '分指中心', value: 'marker', image: getImageUrl('dividingcenter'), icon: getImageUrl('dividingcenter'), size: [166, 88] },
  285. { name: '集结地', value: 'marker', image: getImageUrl('rendezvous'), icon: getImageUrl('rendezvous'), size: [166, 88] },
  286. { name: '军队', value: 'marker', image: getImageUrl('army'), icon: getImageUrl('army'), size: [166, 88] },
  287. { name: '武警', value: 'marker', image: getImageUrl('armedpolice'), icon: getImageUrl('armedpolice'), size: [166, 88] },
  288. { name: '森林警察', value: 'marker', image: getImageUrl('forestpoliceman'), icon: getImageUrl('forestpoliceman'), size: [166, 88] },
  289. { name: '扑火队伍', value: 'marker', image: getImageUrl('firefightingteam'), icon: getImageUrl('firefightingteam'), size: [166, 88] }
  290. // { name: '扑火队伍路线', value: 'marker', image: getImageUrl('Firefightingteamroute'), icon: getImageUrl('Firefightingteamroute'), size: [166, 88] }
  291. ]
  292. },
  293. {
  294. name: '飞机车辆',
  295. value: 'aircraftVehicles',
  296. children: [
  297. { name: '固定翼', value: 'marker', image: getImageUrl('fixedwing'), icon: getImageUrl('fixedwing'), size: [166, 88] },
  298. { name: '直升机', value: 'marker', image: getImageUrl('helicopter'), icon: getImageUrl('helicopter'), size: [166, 88] },
  299. { name: '无人机', value: 'marker', image: getImageUrl('UAV'), icon: getImageUrl('UAV'), size: [166, 88] },
  300. { name: '指挥车', value: 'marker', image: getImageUrl('commandvehicle'), icon: getImageUrl('commandvehicle'), size: [166, 88] },
  301. {
  302. name: '飞机航线设置',
  303. value: 'marker',
  304. image: getImageUrl('aircraftroutesetting'),
  305. icon: getImageUrl('aircraftroutesetting'),
  306. size: [166, 88]
  307. },
  308. {
  309. name: '直升机航线设置',
  310. value: 'marker',
  311. image: getImageUrl('helicopterroutesetting'),
  312. icon: getImageUrl('helicopterroutesetting'),
  313. size: [166, 88]
  314. }
  315. ]
  316. },
  317. {
  318. name: '基础设置',
  319. value: 'basicSetting',
  320. children: [
  321. { name: '航站', value: 'marker', image: getImageUrl('terminal'), icon: getImageUrl('terminal'), size: [166, 88] },
  322. {
  323. name: '起降点',
  324. value: 'marker',
  325. image: getImageUrl('takeoffandlandingpoint'),
  326. icon: getImageUrl('takeoffandlandingpoint'),
  327. size: [166, 88]
  328. },
  329. { name: '取水点', value: 'marker', image: getImageUrl('waterpoint'), icon: getImageUrl('waterpoint'), size: [166, 88] },
  330. { name: '瞭望塔', value: 'marker', image: getImageUrl('watchtower'), icon: getImageUrl('watchtower'), size: [166, 88] },
  331. { name: '物资库', value: 'marker', image: getImageUrl('materialwarehouse'), icon: getImageUrl('materialwarehouse'), size: [166, 88] }
  332. ]
  333. }
  334. // {
  335. // name: '其他',
  336. // value: 'other',
  337. // children: [
  338. // { name: '危险区域', value: 'marker', image: getImageUrl('dangerousarea'), icon: getImageUrl('dangerousarea'), size: [166, 88] },
  339. // { name: '隔离带开采', value: 'marker', image: getImageUrl('Isolationzonemining'), icon: getImageUrl('Isolationzonemining'), size: [166, 88] },
  340. // { name: '应急庇护场所', value: 'marker', image: getImageUrl('emergencyshelter'), icon: getImageUrl('emergencyshelter'), size: [166, 88] },
  341. // { name: '风力风向', value: 'marker', image: getImageUrl('windspeedanddirection'), icon: getImageUrl('windspeedanddirection'), size: [166, 88] },
  342. // { name: '测距', value: 'marker', image: getImageUrl('ranging'), icon: getImageUrl('ranging'), size: [166, 88] },
  343. // { name: '大风', value: 'marker', image: getImageUrl('strongwind'), icon: getImageUrl('strongwind'), size: [166, 88] },
  344. // { name: '人工降雨', value: 'marker', image: getImageUrl('artificialrainfall'), icon: getImageUrl('artificialrainfall'), size: [166, 88] },
  345. // { name: '台风', value: 'marker', image: getImageUrl('typhoon'), icon: getImageUrl('typhoon'), size: [166, 88] }
  346. // ]
  347. // }
  348. ]
  349. },
  350. {
  351. name: '历史预案',
  352. children: []
  353. }
  354. ]);
  355. const lineWidthOptions = reactive([
  356. { name: '1像素', value: '1' },
  357. { name: '2像素', value: '2' },
  358. { name: '3像素', value: '3' }
  359. ]);
  360. let showTextEdit = ref();
  361. let lnglat = ref([]);
  362. // 协同
  363. let collaboration = ref(false);
  364. // 分享
  365. let shareState = reactive({
  366. type: '',
  367. showShare: false,
  368. id: ''
  369. });
  370. let shareId = ref('');
  371. const overlays = [];
  372. const overlaysData = [];
  373. watch(
  374. () => drawing,
  375. () => {
  376. if (!drawing.value) {
  377. menuActive3.value = '';
  378. }
  379. }
  380. );
  381. const tipTitle = computed(() => {
  382. let res = '';
  383. if (
  384. menu.value[menuActive1.value] &&
  385. menu.value[menuActive1.value].children &&
  386. menu.value[menuActive1.value].children[menuActive2.value] &&
  387. menu.value[menuActive1.value].children[menuActive2.value].children &&
  388. menu.value[menuActive1.value].children[menuActive2.value].children[menuActive3.value]
  389. ) {
  390. const data = menu.value[menuActive1.value].children[menuActive2.value].children[menuActive3.value];
  391. if (data.value === 'straightArrow') {
  392. res = '单击开始、单击结束';
  393. } else if (['rectangle', 'polygon', 'anyLine', 'circle'].includes(data.value)) {
  394. res = '鼠标按住拖曳,松开鼠标结束标绘';
  395. } else if (['straightLine', 'measureArea'].includes(data.value)) {
  396. res = '单击开始,移动,单击改变方向,双击结束';
  397. }
  398. }
  399. return res;
  400. });
  401. // 点击一级菜单
  402. const clickTab = (value: number) => {
  403. menuActive1.value = value;
  404. };
  405. // 点击二级菜单
  406. const clickTab2 = (value: number) => {
  407. menuActive2.value = value;
  408. };
  409. // 点击三级菜单
  410. const clickTab3 = (item, index) => {
  411. const type = item.value;
  412. if (mouseToolState.value.graphicsType !== type || (mouseToolState.value.graphicsType === 'marker' && mouseToolState.value.title !== item.name)) {
  413. if (type === 'text') {
  414. mouseToolState.value.graphicsType = type;
  415. handleTextEdit();
  416. } else if (type === 'marker') {
  417. const data = {
  418. graphicsType: type,
  419. lineWidth: mouseToolState.value.lineWidth,
  420. color: mouseToolState.value.color,
  421. icon: item.icon,
  422. size: item.size,
  423. title: item.name
  424. };
  425. drawing.value = true;
  426. mouseToolState.value = data;
  427. } else {
  428. showTextEdit.value = false;
  429. drawing.value = true;
  430. mouseToolState.value.graphicsType = type;
  431. }
  432. const drawTool = getDrawTool();
  433. const newOptions = drawTool.drawGraphics(mouseToolState.value);
  434. if (newOptions.graphicsType !== 'text') {
  435. // 绘制完成事件
  436. initDrawMethod(newOptions);
  437. }
  438. menuActive3.value = index;
  439. } else {
  440. showTextEdit.value = false;
  441. drawing.value = false;
  442. mouseToolState.value.graphicsType = '';
  443. menuActive3.value = '';
  444. }
  445. };
  446. const handleTextEdit = () => {
  447. const drawTool = getDrawTool();
  448. const map = drawTool.getMap();
  449. drawTool.closeDraw();
  450. // 监听地图点击事件
  451. map.on('click', handleClickMap);
  452. };
  453. const handleClickMap = (e) => {
  454. // 获取点击位置的经纬度
  455. lnglat.value = [e.lnglat.lng, e.lnglat.lat];
  456. showTextEdit.value = true;
  457. };
  458. const addText = (textEditState) => {
  459. const data = {
  460. graphicsType: 'text',
  461. lineWidth: mouseToolState.value.lineWidth,
  462. color: mouseToolState.value.color,
  463. text: textEditState.text,
  464. fontColor: textEditState.fontColor,
  465. fontSize: textEditState.fontSize,
  466. lnglat: lnglat.value
  467. };
  468. showTextEdit.value = false;
  469. drawing.value = true;
  470. mouseToolState.value = data;
  471. // 绘制完成事件
  472. const drawTool = getDrawTool();
  473. const res = drawTool.drawGraphics(mouseToolState.value);
  474. overlays.push(res.text);
  475. overlaysData.push(res.data);
  476. commit(deepClone(overlaysData));
  477. res.text.on('rightclick', handleRightClick);
  478. const map = drawTool.getMap();
  479. // 监听地图点击事件
  480. map.off('click', handleClickMap);
  481. close();
  482. };
  483. // watch(
  484. // () => mouseToolState.value.graphicsType,
  485. // (value) => {
  486. // if (value) {
  487. // const map = getMap();
  488. // map.off('click', handleClickMap);
  489. // }
  490. // }
  491. // );
  492. const changeDrawing = () => {
  493. drawing.value = !drawing.value;
  494. };
  495. const close = () => {
  496. const drawTool = getDrawTool();
  497. drawTool.closeDraw();
  498. drawing.value = false;
  499. menuActive3.value = '';
  500. mouseToolState.value.graphicsType = '';
  501. };
  502. // 初始化绘制完成回调
  503. const initDrawMethod = (options) => {
  504. const drawTool = getDrawTool();
  505. const mouseTool = drawTool.getMouseTool();
  506. const onDraw = (event) => {
  507. mouseTool.off('draw', onDraw);
  508. close();
  509. const obj = event.obj;
  510. const id = nanoid();
  511. obj._opts.extData = {
  512. id: id
  513. };
  514. const data: any = deepClone(options);
  515. data.id = id;
  516. if (options.type == 'marker') {
  517. const position = obj.getPosition();
  518. data.lnglat = [position.lng, position.lat];
  519. data.latitude = position.lat;
  520. data.longitude = position.lng;
  521. data.image = data.icon;
  522. data.image = data.icon;
  523. data.imageHover = data.icon;
  524. } else {
  525. const path = obj.getPath();
  526. // 将AMap.LngLat对象数组转换为经纬度数组
  527. const pathArr = path.map((lngLat) => {
  528. // 返回经度和纬度的数组
  529. return [lngLat.lng, lngLat.lat];
  530. });
  531. if (options.type !== 'straightLine') {
  532. pathArr.push(pathArr[0]);
  533. }
  534. data.path = pathArr;
  535. if (options.type == 'circle') {
  536. data.center = [obj.getCenter().lng, obj.getCenter().lat];
  537. data.radius = obj.getRadius();
  538. }
  539. }
  540. if (options.type == 'measureArea') {
  541. const AMap = getDrawTool().getAMap();
  542. const map = getMap();
  543. // 计算区域面积
  544. const area = Math.round(AMap.GeometryUtil.ringArea(data.path));
  545. const text = new AMap.Text({
  546. position: data.path[data.path.length - 1],
  547. text: '区域面积' + area + '平方米',
  548. offset: new AMap.Pixel(-20, -20)
  549. });
  550. data.area = area;
  551. map.add(text);
  552. overlays.push([obj, text]);
  553. overlaysData.push(data);
  554. commit(deepClone(overlaysData));
  555. } else {
  556. overlays.push(obj);
  557. overlaysData.push(data);
  558. commit(deepClone(overlaysData));
  559. console.log(overlaysData);
  560. }
  561. // 右击进入编辑
  562. obj.on('rightclick', handleRightClick);
  563. };
  564. mouseTool.on('draw', onDraw);
  565. };
  566. // 图形右击事件
  567. let rightClickObj;
  568. let initContextMenu = false;
  569. const handleRightClick = (event) => {
  570. rightClickObj = event.target;
  571. const contextMenu = getDrawTool().getContextMenu();
  572. if (!initContextMenu) {
  573. // 右键删除按钮
  574. contextMenu.addItem(
  575. '删除',
  576. function () {
  577. contextMenu.close();
  578. deleteGraphics();
  579. },
  580. 0
  581. );
  582. initContextMenu = true;
  583. }
  584. contextMenu.open(getMap(), event.lnglat);
  585. };
  586. // 删除图形
  587. const deleteGraphics = () => {
  588. const id = rightClickObj.getExtData()?.id;
  589. if (id) {
  590. for (let i = 0; i < overlays.length; i++) {
  591. const overlay = Array.isArray(overlays[i]) ? overlays[i][0] : overlays[i];
  592. if (overlay?.getExtData().id === id) {
  593. removeOverlayByIndex(i);
  594. commit(deepClone(overlaysData));
  595. rightClickObj = null;
  596. }
  597. }
  598. }
  599. };
  600. // 撤销绘制
  601. const handleUndo = () => {
  602. // drawing.value = false;
  603. const previous = history.value[history.value.length - 2];
  604. if (history.value.length > 1) {
  605. if (currentState.value.length > previous.length) {
  606. // 撤销新增
  607. removeOverlayByIndex(currentState.value.length - 1);
  608. } else {
  609. let restoreData;
  610. for (let i = 0; i < previous.length; i++) {
  611. let index = 0;
  612. for (let k = 0; k < currentState.value.length; k++) {
  613. if (previous[i].id !== currentState.value[k].id) {
  614. index++;
  615. } else {
  616. break;
  617. }
  618. }
  619. if (index === previous.length - 1) {
  620. restoreData = previous[i];
  621. break;
  622. }
  623. }
  624. if (restoreData) {
  625. const obj = getDrawTool().createGraphics(restoreData);
  626. overlays.push(obj);
  627. }
  628. }
  629. undo();
  630. console.log(history.value, future.value, currentState.value);
  631. }
  632. };
  633. // 根据索引移除覆盖物
  634. const removeOverlayByIndex = (index: number) => {
  635. const map = getMap();
  636. if (Array.isArray(overlays[index])) {
  637. overlays[index].forEach((overlay) => {
  638. // 移除地图上覆盖物
  639. map.remove(overlay);
  640. });
  641. } else {
  642. // 移除地图上覆盖物
  643. map.remove(overlays[index]);
  644. }
  645. overlays.splice(index, 1);
  646. overlaysData.splice(index, 1);
  647. };
  648. // 历史预案
  649. const queryParams = reactive({
  650. page: 1,
  651. pageSize: 9,
  652. pattern_name: ''
  653. });
  654. const patternList = ref([]);
  655. let total = ref(0);
  656. let editData = ref({
  657. id: '',
  658. pattern_name: '',
  659. content: {}
  660. });
  661. let showEdit = ref(false);
  662. const handleSubmit = () => {
  663. showEdit.value = false;
  664. editData.value = {
  665. id: '',
  666. pattern_name: '',
  667. content: {}
  668. };
  669. getList();
  670. };
  671. const handleQuery = () => {
  672. queryParams.page = 1;
  673. getList();
  674. };
  675. const getList = () => {
  676. // if (menuActive1.value === 1 && menu.value[menuActive1.value].name == '历史预案') {
  677. getPatternList(queryParams).then((res) => {
  678. patternList.value = res.data;
  679. total.value = res.total;
  680. });
  681. // }
  682. };
  683. const handleDelete = (id: number) => {
  684. proxy?.$modal.confirm('是否确认删除选择的数据项?').then(() => {
  685. deletePatternById(id).then(() => {
  686. getList();
  687. });
  688. });
  689. };
  690. const handleEdit = (id) => {
  691. getPatternInfo(id).then((res) => {
  692. editData.value = res.data;
  693. editData.value.id = id;
  694. });
  695. showEdit.value = true;
  696. };
  697. const handleShare = (type, id?: string) => {
  698. if (type === '1') {
  699. // 创建协同
  700. collaboration.value = true;
  701. }
  702. shareState.type = type;
  703. shareState.id = id;
  704. shareState.showShare = true;
  705. };
  706. const handleCloseShare = () => {
  707. shareState.type = '';
  708. shareState.id = '';
  709. };
  710. const handleCloseCollaboration = () => {
  711. collaboration.value = false;
  712. };
  713. const handleShareConfirm = (data) => {
  714. if (shareState.type === '1') {
  715. // 协同标绘
  716. startCollaboration()
  717. userWebsocket.init();
  718. } else {
  719. // 分享
  720. }
  721. shareState.type = '';
  722. shareState.id = '';
  723. };
  724. watch(userWebsocket.webSocketList, (newVal) => {
  725. console.log('监听数据变化');
  726. console.log(newVal);
  727. });
  728. const handleShowDialog = () => {
  729. editData.value = {
  730. id: '',
  731. pattern_name: '',
  732. content: currentState.value
  733. };
  734. showEdit.value = true;
  735. };
  736. let dataURL = ref('');
  737. // 页面元素转图片
  738. const handleScreenshot = () => {
  739. const canvasBox = getMap().Iv;
  740. // 手动创建一个 canvas 标签
  741. const canvas = document.createElement('canvas');
  742. if (canvasBox) {
  743. // 获取父级的宽高
  744. const width = parseInt(window.getComputedStyle(canvasBox).width) / containerScale().scaleX;
  745. const height = parseInt(window.getComputedStyle(canvasBox).height) / containerScale().scaleX;
  746. canvas.style.width = width + 'px';
  747. canvas.style.height = height + 'px';
  748. const options = {
  749. backgroundColor: null, // 设置背景色为透明
  750. useCORS: true, //是否尝试使用CORS从服务器加载图像,解决跨域问题
  751. tainttest: true, // 是否在渲染前测试图片
  752. logging: false // 不启动日志调试
  753. };
  754. // canvasBox是要截图的元素,options是一些相关配置
  755. html2canvas(canvasBox, options).then((canvas) => {
  756. // toDataURL 图片格式转成 base64
  757. dataURL.value = canvas.toDataURL('image/png');
  758. // 新建一个a标签
  759. var oA = document.createElement('a');
  760. oA.download = '截图'; // 设置下载的文件名
  761. oA.href = dataURL.value;
  762. document.body.appendChild(oA);
  763. oA.click(); // 模拟点击a标签
  764. oA.remove(); // 下载之后把创建的元素删除
  765. });
  766. }
  767. };
  768. onMounted(() => {
  769. getList();
  770. });
  771. </script>
  772. <style lang="scss" scoped>
  773. .menu-content {
  774. width: 1584px;
  775. height: 1772px;
  776. background: url('@/assets/images/map/rightMenu/onlinePlotting/dialog.png') no-repeat;
  777. padding: 130px 45px 20px 50px;
  778. font-size: 36px;
  779. position: relative;
  780. color: #ffffff;
  781. }
  782. .title {
  783. font-size: 60px;
  784. position: absolute;
  785. top: 30px;
  786. left: 140px;
  787. }
  788. :deep(.el-color-dropdown__link-btn) {
  789. display: none;
  790. }
  791. .line {
  792. display: flex;
  793. justify-content: space-between;
  794. align-items: center;
  795. }
  796. .tabs1 {
  797. margin-top: 45px;
  798. display: flex;
  799. .tab {
  800. width: 316px;
  801. height: 77px;
  802. line-height: 77px;
  803. background: url('@/assets/images/map/rightMenu/onlinePlotting/tab.png') no-repeat;
  804. cursor: pointer;
  805. font-size: 44px;
  806. color: #b7c1d5;
  807. font-family: YouSheBiaoTiHei;
  808. padding-left: 60px;
  809. &:hover {
  810. width: 317px;
  811. height: 83px;
  812. color: #ffffff;
  813. background: url('@/assets/images/map/rightMenu/onlinePlotting/tabActive.png') no-repeat;
  814. }
  815. }
  816. .tab_active {
  817. width: 317px;
  818. height: 83px;
  819. color: #ffffff;
  820. background: url('@/assets/images/map/rightMenu/onlinePlotting/tabActive.png') no-repeat;
  821. }
  822. }
  823. .btn1 {
  824. width: 440px;
  825. height: 120px;
  826. background: url('@/assets/images/map/rightMenu/onlinePlotting/btn1.png') no-repeat;
  827. display: flex;
  828. justify-content: center;
  829. align-items: center;
  830. cursor: pointer;
  831. font-size: 32px;
  832. color: #edfaff;
  833. margin-bottom: -25px;
  834. .icon1 {
  835. width: 42px;
  836. height: 42px;
  837. background: url('@/assets/images/map/rightMenu/onlinePlotting/screenshot.png') no-repeat;
  838. margin-right: 12px;
  839. }
  840. }
  841. .btn2 {
  842. width: 278px;
  843. height: 78px;
  844. background: url('@/assets/images/map/rightMenu/btn.png') no-repeat;
  845. display: flex;
  846. justify-content: center;
  847. align-items: center;
  848. cursor: pointer;
  849. font-size: 32px;
  850. color: #edfaff;
  851. margin-bottom: -57px;
  852. .icon1 {
  853. width: 42px;
  854. height: 42px;
  855. background: url('@/assets/images/map/rightMenu/onlinePlotting/screenshot.png') no-repeat;
  856. margin-right: 12px;
  857. }
  858. }
  859. .box1 {
  860. margin-top: 20px;
  861. display: flex;
  862. justify-content: space-between;
  863. align-items: center;
  864. width: 1490px;
  865. height: 96px;
  866. background: url('@/assets/images/map/rightMenu/onlinePlotting/box1.png') no-repeat;
  867. padding: 25px 30px;
  868. .box-item {
  869. display: flex;
  870. align-items: center;
  871. }
  872. }
  873. .btn {
  874. display: flex;
  875. align-items: center;
  876. cursor: pointer;
  877. .revoke-icon {
  878. width: 48px;
  879. height: 50px;
  880. margin-right: 10px;
  881. background: url('@/assets/images/map/rightMenu/onlinePlotting/revoke.png') no-repeat;
  882. }
  883. .delete-icon {
  884. width: 50px;
  885. height: 50px;
  886. margin-right: 10px;
  887. background: url('@/assets/images/map/rightMenu/onlinePlotting/delete.png') no-repeat;
  888. }
  889. .edit-icon {
  890. width: 46px;
  891. height: 46px;
  892. margin-right: 10px;
  893. background: url('@/assets/images/map/rightMenu/onlinePlotting/edit.png') no-repeat;
  894. }
  895. .cancel-icon {
  896. width: 50px;
  897. height: 50px;
  898. margin-right: 10px;
  899. background: url('@/assets/images/map/rightMenu/onlinePlotting/cancel.png') no-repeat;
  900. }
  901. .save-icon {
  902. width: 46px;
  903. height: 46px;
  904. margin-right: 10px;
  905. background: url('@/assets/images/map/rightMenu/onlinePlotting/save.png') no-repeat;
  906. }
  907. .share-icon {
  908. width: 46px;
  909. height: 48px;
  910. margin-right: 10px;
  911. background: url('@/assets/images/map/rightMenu/onlinePlotting/share.png') no-repeat;
  912. }
  913. .setting-icon {
  914. width: 48px;
  915. height: 50px;
  916. margin-right: 10px;
  917. background: url('@/assets/images/map/rightMenu/onlinePlotting/setting.png') no-repeat;
  918. }
  919. .merge-icon {
  920. width: 52px;
  921. height: 52px;
  922. margin-right: 10px;
  923. background: url('@/assets/images/map/rightMenu/onlinePlotting/merge.png') no-repeat;
  924. }
  925. }
  926. .line2 {
  927. width: 3px;
  928. height: 24px;
  929. background-color: #a7ccdf;
  930. margin: 0 38px;
  931. }
  932. .tabs2 {
  933. width: 300px;
  934. min-width: 300px;
  935. height: 1340px;
  936. background: url('@/assets/images/map/rightMenu/onlinePlotting/tabBg.png') no-repeat;
  937. background-size: 300px 1188px;
  938. overflow-y: auto;
  939. padding: 30px 0;
  940. .tab {
  941. width: 100%;
  942. height: 99px;
  943. cursor: pointer;
  944. color: #eaf3fc;
  945. font-size: 38px;
  946. padding: 0 60px;
  947. display: flex;
  948. align-items: center;
  949. &:hover {
  950. background: url('@/assets/images/map/rightMenu/onlinePlotting/tabActive2.png') no-repeat;
  951. }
  952. }
  953. .tab_active {
  954. background: url('@/assets/images/map/rightMenu/onlinePlotting/tabActive2.png') no-repeat;
  955. }
  956. }
  957. .tab-content {
  958. display: flex;
  959. margin-top: 20px;
  960. .tab-content2 {
  961. padding: 20px 45px;
  962. display: flex;
  963. flex-direction: column;
  964. .line {
  965. height: 100px;
  966. }
  967. .line3 {
  968. display: flex;
  969. }
  970. .tab-list {
  971. width: 100%;
  972. flex: 1;
  973. overflow-y: auto;
  974. display: flex;
  975. flex-wrap: wrap;
  976. align-content: baseline;
  977. .icon {
  978. width: 160px;
  979. height: 88px;
  980. margin-bottom: 20px;
  981. }
  982. .tab {
  983. &:nth-child(3n-1) {
  984. margin: 40px 50px 0;
  985. }
  986. }
  987. }
  988. .tab {
  989. width: 333px;
  990. height: 235px;
  991. background: url('@/assets/images/map/rightMenu/onlinePlotting/box2.png') no-repeat;
  992. padding: 27px;
  993. cursor: pointer;
  994. position: relative;
  995. font-size: 38px;
  996. color: #eaf3fc;
  997. display: flex;
  998. flex-direction: column;
  999. justify-content: center;
  1000. align-items: center;
  1001. margin-top: 40px;
  1002. &:hover {
  1003. background: url('@/assets/images/map/rightMenu/onlinePlotting/boxHover2.png') no-repeat;
  1004. }
  1005. .checked1 {
  1006. position: absolute;
  1007. top: 30px;
  1008. right: 30px;
  1009. width: 32px;
  1010. height: 32px;
  1011. background: url('@/assets/images/map/rightMenu/onlinePlotting/checked1.png') no-repeat;
  1012. }
  1013. .checked2 {
  1014. position: absolute;
  1015. top: 30px;
  1016. right: 30px;
  1017. width: 36px;
  1018. height: 36px;
  1019. background: url('@/assets/images/map/rightMenu/onlinePlotting/checked2.png') no-repeat;
  1020. }
  1021. }
  1022. .tab_active {
  1023. background: url('@/assets/images/map/rightMenu/onlinePlotting/boxActive2.png') no-repeat;
  1024. }
  1025. }
  1026. }
  1027. .tipTitle {
  1028. position: absolute;
  1029. top: 0;
  1030. left: -2745px;
  1031. z-index: 2;
  1032. padding: 10px 30px;
  1033. font-size: 38px;
  1034. color: #fff;
  1035. background-color: rgba(0, 0, 0, 0.4);
  1036. border-radius: 10px;
  1037. }
  1038. .tab-content3 {
  1039. .custom-table {
  1040. width: 100%;
  1041. color: #000;
  1042. .table-header {
  1043. display: flex;
  1044. font-size: 38px;
  1045. width: 100%;
  1046. background-color: #194ba0;
  1047. .th {
  1048. padding: 25px 30px;
  1049. flex: 1;
  1050. color: #edfaff;
  1051. &:last-child {
  1052. flex: unset;
  1053. width: 620px;
  1054. }
  1055. }
  1056. }
  1057. .tr {
  1058. display: flex;
  1059. font-size: 38px;
  1060. width: 1487px;
  1061. background: url('@/assets/images/map/rightMenu/onlinePlotting/tr.png') no-repeat;
  1062. background-size: 100% 100%;
  1063. margin-top: 10px;
  1064. &:hover {
  1065. background: url('@/assets/images/map/rightMenu/onlinePlotting/trActive.png') no-repeat;
  1066. }
  1067. .td {
  1068. padding: 25px 30px;
  1069. color: #edfaff;
  1070. flex: 1;
  1071. .icon {
  1072. margin: 0 10px;
  1073. cursor: pointer;
  1074. }
  1075. }
  1076. .td2 {
  1077. flex: unset;
  1078. width: 620px;
  1079. display: flex;
  1080. justify-content: center;
  1081. align-items: center;
  1082. }
  1083. }
  1084. }
  1085. }
  1086. .color-container {
  1087. width: 80px;
  1088. height: 50px;
  1089. display: flex;
  1090. justify-content: center;
  1091. align-items: center;
  1092. position: relative;
  1093. background-color: #0d3980;
  1094. border: 4px solid #0b5fbb;
  1095. margin-left: 20px;
  1096. :deep(.el-color-picker) {
  1097. height: 60px !important;
  1098. }
  1099. &::before {
  1100. content: '';
  1101. position: absolute;
  1102. top: 0;
  1103. left: 0;
  1104. width: 12px;
  1105. height: 12px;
  1106. background: url('@/assets/images/inputIcon1.png') no-repeat;
  1107. }
  1108. &::after {
  1109. content: '';
  1110. position: absolute;
  1111. right: 0;
  1112. bottom: 0;
  1113. width: 12px;
  1114. height: 12px;
  1115. background: url('@/assets/images/inputIcon2.png') no-repeat;
  1116. }
  1117. }
  1118. .params-box {
  1119. margin: 30px 0;
  1120. }
  1121. .footer {
  1122. display: flex;
  1123. justify-content: flex-end;
  1124. margin-top: 30px;
  1125. padding-right: 40px;
  1126. :deep(.el-pagination__total) {
  1127. color: #a7ccdf;
  1128. font-size: 32px;
  1129. }
  1130. :deep(.el-pagination) {
  1131. .btn-next,
  1132. .btn-prev {
  1133. background-color: transparent;
  1134. border: none;
  1135. .el-icon {
  1136. font-size: 22px;
  1137. color: #a7ccdf;
  1138. }
  1139. }
  1140. .el-pager li {
  1141. width: 64px;
  1142. height: 64px;
  1143. line-height: 64px;
  1144. text-align: center;
  1145. font-size: 32px;
  1146. color: #a7ccdf;
  1147. background-color: #0e3064;
  1148. border: 1px solid #0c57a7;
  1149. margin: 0 6px;
  1150. &:hover {
  1151. background-color: #038dff;
  1152. border: 1px solid #038dff;
  1153. }
  1154. }
  1155. .el-pager li.is-active {
  1156. background-color: #038dff;
  1157. border: 1px solid #038dff;
  1158. }
  1159. }
  1160. }
  1161. .btn-box {
  1162. display: flex;
  1163. align-items: center;
  1164. }
  1165. </style>