index.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. <template>
  2. <div class="project-layer-wrapper height-100">
  3. <div class="scrollbar">
  4. <LayersTree :layer-data="mapLayers" class="project-layer-content" @select="selectLayer"
  5. v-model:selectedKeys="selectedKeys" v-model:checkedKeys="checkedKeys" v-model:expandedKeys="expandedKeys" />
  6. </div>
  7. <a-drawer title="地图元素" placement="right" :closable="true" v-model:visible="visible" :mask="true"
  8. getContainer="#g-container" :wrap-style="{ position: 'absolute' }" wrapClassName="drawer-element-wrapper"
  9. @close="closeDrawer" width="300">
  10. <div class="drawer-element-content">
  11. <div class="element-item">
  12. <span class="mr30">名称:</span>
  13. <a-input v-model:value="layerState.layerName" style="width:110px" placeholder="element name"
  14. @change="changeLayer" />
  15. </div>
  16. <div class="element-item" v-if="layerState.currentType === geoType.Point">
  17. <span class="mr30">经度:</span>
  18. {{ layerState.longitude || '--' }}
  19. </div>
  20. <div class="element-item" v-if="layerState.currentType === geoType.Point">
  21. <span class="mr30">纬度:</span>
  22. {{ layerState.latitude || '--' }}
  23. </div>
  24. <div class="element-item" v-if="layerState.element_from === 2">
  25. <span class="mr30">高度:</span>
  26. {{ layerState.height || '--' }}
  27. </div>
  28. <div class="element-item" v-if="layerState.currentType === geoType.Polygon">
  29. <span class="mr30">面积:</span>
  30. {{ layerState.area || '--' }}
  31. </div>
  32. <div class="color-content">
  33. <span class="mr30">颜色: </span>
  34. <div v-for="item in colors" :key="item.id" class="color-item" :style="'background:' + item.color"
  35. @click="changeColor(item)">
  36. <svg-icon v-if="item.color === layerState.color" :size="18" name="check"></svg-icon>
  37. </div>
  38. </div>
  39. <div class="element-item">
  40. <span class="mr30">用户:</span>
  41. {{ layerState.user_name || '--' }}
  42. </div>
  43. <div class="element-item">
  44. <span class="mr30">来源:</span>
  45. <Icon v-if="layerState.element_from === 2">
  46. <template #component>
  47. <svg width="1em" height="1em" fill="currentColor" viewBox="0 0 1024 1024">
  48. <path
  49. d="M1024 418.21v576H0v-576h1024z m-672 192c-53.02 0-96 42.98-96 96s42.98 96 96 96 96-42.98 96-96-42.98-96-96-96z m320 0c-53.02 0-96 42.98-96 96s42.98 96 96 96 96-42.98 96-96-42.98-96-96-96zM236.59 29.79h125.55v320H236.59v-320z m425.28 0h125.55v320H661.87v-320z"
  50. p-id="4408" />
  51. </svg>
  52. </template>
  53. </Icon>
  54. <DesktopOutlined v-else />
  55. </div>
  56. </div>
  57. <div class="flex-row flex-justify-around flex-align-center mt20">
  58. <a-button type="primary" @click="deleteElement">删除</a-button>
  59. </div>
  60. </a-drawer>
  61. <PhotoDrawer :visible="state.photoDrawerVisible" :fileId="selectedKeys[0]" :closeDrawer="closeDrawer" />
  62. </div>
  63. </template>
  64. <script lang="ts" setup>
  65. import { onMounted, reactive, ref, watch } from 'vue'
  66. import Icon from '@ant-design/icons-vue';
  67. import { DesktopOutlined } from '@ant-design/icons-vue';
  68. import LayersTree from '/@/components/LayersTree.vue'
  69. import PhotoDrawer from './components/PhotoDrawer.vue'
  70. import { MapElementEnum } from '/@/constants/map'
  71. import { useGMapCover } from '/@/hooks/use-g-map-cover'
  72. import { GeojsonCoordinate, LayerResource } from '/@/types/map'
  73. import { Color, GeoType } from '/@/types/mapLayer'
  74. import {
  75. deleteElementReq,
  76. getElementGroupsReq,
  77. updateElementsReq
  78. } from '/@/api/layer'
  79. import { useMyStore } from '/@/store'
  80. import { gcj02towgs84, wgs84togcj02 } from '/@/vendors/coordtransform'
  81. import { getRoot } from '/@/root'
  82. const root = getRoot()
  83. interface Props {
  84. mapClickElement: {
  85. id: string,
  86. type: string,
  87. },
  88. };
  89. const props = withDefaults(defineProps<Props>(), {
  90. });
  91. const state = reactive({
  92. photoDrawerVisible: false,
  93. })
  94. watch(() => props.mapClickElement, (newValue, oldValue) => {
  95. if (newValue.id) {
  96. if (newValue.type === 'DEFAULT') {// 默认文件夹
  97. // 默认文件夹列表
  98. const defaultFileList: any = mapLayers.value.filter((item: any) => item.type === 2);
  99. if (defaultFileList.length === 0) {
  100. return;
  101. }
  102. const pid = defaultFileList[0].id;
  103. expandedKeys.value = [pid];
  104. selectedKeys.value = [`resource__${newValue.id}`];
  105. selectedLayer.value = getCurrentLayer(`resource__${newValue.id}`)
  106. setBaseInfo()
  107. visible.value = true;
  108. state.photoDrawerVisible = false
  109. } else if (newValue.type === 'PHOTO') {
  110. const expandedKeyList: string[] = [];
  111. // 图片标注列表
  112. const photoList: any = mapLayers.value.filter((item: any) => item.type === 3);
  113. if (photoList.length === 0) {
  114. return;
  115. }
  116. const list = photoList[0];
  117. expandedKeyList.push(list.id);
  118. list.elements.forEach((item: any) => {
  119. item.elements.forEach((element: any) => {
  120. if (element.id === newValue.id) {
  121. const coordinates = element.resource.content.geometry.coordinates;
  122. root.$map.setCenter(coordinates);
  123. expandedKeyList.push(element.pid)
  124. expandedKeys.value = expandedKeyList;
  125. selectedKeys.value = [`resource__${newValue.id}`];
  126. selectedLayer.value = getCurrentLayer(`resource__${newValue.id}`)
  127. state.photoDrawerVisible = true
  128. visible.value = false;
  129. }
  130. });
  131. })
  132. }
  133. }
  134. }, { deep: true });
  135. const store = useMyStore()
  136. let useGMapCoverHook = useGMapCover()
  137. const mapLayers = ref(store.state.Layers)
  138. const checkedKeys = ref<string[]>([])
  139. const expandedKeys = ref<string[]>([])
  140. const selectedKeys = ref<string[]>([])
  141. const selectedKey = ref<string>('')
  142. const selectedLayer = ref<any>(null)
  143. const visible = ref<boolean>(false)
  144. const geoType = GeoType
  145. const layerState = reactive({
  146. layerId: '',
  147. layerName: '',
  148. longitude: 0,
  149. latitude: 0,
  150. height: 0,
  151. area: '',// 面积
  152. currentType: '',// “LineString”,"Polygon","Point"
  153. color: '#212121',
  154. user_name: '',
  155. element_from: 1,// 1-web 2-遥控器
  156. })
  157. const colors = ref<Color[]>([
  158. { id: 1, name: 'BLUE', color: '#2D8CF0', selected: true },
  159. { id: 2, name: 'GREEN', color: '#19BE6B', selected: false },
  160. { id: 3, name: 'YELLOW', color: '#FFBB00', selected: false },
  161. { id: 4, name: 'ORANGE', color: '#B620E0', selected: false },
  162. { id: 5, name: 'RED', color: '#E23C39', selected: false },
  163. { id: 6, name: 'NAME_DEFAULT', color: '#212121', selected: false }
  164. ])
  165. const scorllHeight = ref()
  166. async function getAllElement() {
  167. getElementGroups('init')
  168. setTimeout(() => {
  169. useGMapCoverHook = useGMapCover()
  170. initMapCover()
  171. }, 1000)
  172. }
  173. function initMapCover() {
  174. mapLayers.value.forEach((item: any) => {
  175. if (item.elements) {
  176. setMapCoverByElement(item.elements)
  177. }
  178. })
  179. }
  180. watch(() => store.state.Layers, newData => {
  181. mapLayers.value = newData
  182. }, { deep: true })
  183. function setMapCoverByElement(elements: LayerResource[]) {
  184. elements.forEach((element: any) => {
  185. if (element.elements && element.elements.length) {// 图片标注
  186. element.elements.forEach((item: any) => {
  187. const name = item.name;
  188. const content: any = item.resource?.content;
  189. const pictureInfo = content.picture;
  190. const coordinates: any = content.geometry.coordinates;
  191. useGMapCoverHook.updatePhotoElement(pictureInfo.file_id, name, pictureInfo.thumbnail_url, coordinates);
  192. })
  193. } else {// 默认文件夹
  194. const name = element.name;
  195. const content: any = element.resource?.content;
  196. const color = content.properties.color;
  197. updateMapElement(element, name, color);
  198. }
  199. })
  200. }
  201. function updateMapElement(
  202. element: LayerResource,
  203. name: string,
  204. color: string | undefined
  205. ) {
  206. const geoType = element.resource?.content.geometry.type
  207. const id = element.id
  208. const type = element.resource?.type as number
  209. if (MapElementEnum.PIN === type) {
  210. const coordinates = element.resource?.content.geometry
  211. .coordinates as GeojsonCoordinate
  212. useGMapCoverHook.updatePinElement(id, name, coordinates, color)
  213. } else if (MapElementEnum.LINE === type && geoType === 'LineString') {
  214. const coordinates = element.resource?.content.geometry
  215. .coordinates as GeojsonCoordinate[]
  216. useGMapCoverHook.updatePolylineElement(id, name, coordinates, color)
  217. } else if (MapElementEnum.POLY === type && geoType === 'Polygon') {
  218. const coordinates = element.resource?.content.geometry
  219. .coordinates as GeojsonCoordinate[][]
  220. useGMapCoverHook.updatePolygonElement(id, name, coordinates, color)
  221. }
  222. }
  223. function selectLayer(keys: string[], e) {
  224. visible.value = false
  225. state.photoDrawerVisible = false
  226. if (e.selected) {
  227. selectedKey.value = e.node.eventKey
  228. selectedLayer.value = getCurrentLayer(selectedKey.value)
  229. const type = e.node.label;
  230. if (type === 'DEFAULT') {// 默认文件夹
  231. setBaseInfo()
  232. visible.value = true;
  233. } else if (type === 'PHOTO') {// 图片标注
  234. state.photoDrawerVisible = true;
  235. }
  236. }
  237. }
  238. function getCurrentLayer(id: string) {
  239. const Layers = store.state.Layers
  240. const key = id.replaceAll('resource__', '')
  241. let layer = null
  242. const findCan = function (V: any) {
  243. V.forEach((item: any) => {
  244. if (item.id === key) {
  245. layer = item
  246. }
  247. if (item.elements) {
  248. findCan(item.elements)
  249. }
  250. })
  251. }
  252. findCan(Layers)
  253. return layer
  254. }
  255. function setBaseInfo() {
  256. const layer = selectedLayer.value
  257. if (layer) {
  258. const content = layer.resource?.content;
  259. const coordinates = content.geometry.coordinates;
  260. const type = content.geometry.type;
  261. layerState.layerId = layer.id;
  262. layerState.layerName = layer.name;
  263. layerState.longitude = coordinates[0];
  264. layerState.latitude = coordinates[1];
  265. layerState.height = layer.height;
  266. layerState.currentType = type;
  267. layerState.color = content.properties.color;
  268. layerState.user_name = layer.resource?.user_name;
  269. layerState.element_from = layer.element_from;
  270. if (type === 'Polygon') {
  271. const area = useGMapCoverHook.getPolygonArea(coordinates);
  272. layerState.area = area + 'm²';
  273. }
  274. }
  275. }
  276. onMounted(() => {
  277. const element = document.getElementsByClassName('scrollbar').item(0) as HTMLDivElement
  278. const parent = element?.parentNode as HTMLDivElement
  279. scorllHeight.value = parent?.clientHeight - parent.firstElementChild!.clientHeight
  280. getAllElement()
  281. })
  282. function closeDrawer() {
  283. store.commit('SET_MAP_CLICK_ELEMENT', {
  284. id: '',
  285. type: '',
  286. });
  287. visible.value = false
  288. state.photoDrawerVisible = false
  289. selectedKeys.value = []
  290. }
  291. function changeColor(color: Color) {
  292. layerState.color = color.color
  293. updateElements()
  294. }
  295. function changeLayer(val: string) {
  296. updateElements()
  297. }
  298. async function deleteElement() {
  299. const elementid = selectedLayer.value.id
  300. await deleteElementReq(elementid, {}).then(async (res: any) => {
  301. if (res.code !== 0) {
  302. return
  303. }
  304. visible.value = false
  305. // 移除标点
  306. useGMapCoverHook.removeCoverFromMap(elementid)
  307. // 移除文本覆盖物
  308. useGMapCoverHook.removeCoverFromMap(elementid + '_other')
  309. getElementGroups()
  310. })
  311. }
  312. async function getElementGroups(type?: string) {
  313. const result = await getElementGroupsReq({
  314. groupId: '',
  315. isDistributed: true
  316. })
  317. mapLayers.value = result.data.map((item: any) => {
  318. const data = { ...item };
  319. if (item.elements) {// 默认文件夹
  320. data.elements = item.elements.map((o: any) => {
  321. return {
  322. ...o,
  323. id: o.id,
  324. height: o.resource.content.geometry.coordinates[2]
  325. }
  326. })
  327. } else {// 图片标注
  328. data.elements = item.child_groups.map((o: any) => {
  329. return {
  330. ...o,
  331. elements: o.elements.map((val: any) => {
  332. const values = { ...val };
  333. const coordinates = values.resource?.content.geometry.coordinates as GeojsonCoordinate;
  334. const transResult = wgs84togcj02(
  335. coordinates[0],
  336. coordinates[1]
  337. ) as GeojsonCoordinate
  338. values.id = val.resource.content.picture.file_id;
  339. values.pid = 'resource__' + o.id;
  340. values.resource.content.geometry.coordinates = transResult;
  341. return values;
  342. })
  343. }
  344. });
  345. delete data.child_groups;
  346. }
  347. return data;
  348. })
  349. mapLayers.value = updateWgs84togcj02()
  350. if (type && type === 'init') {
  351. store.dispatch('setLayerInfo', mapLayers.value)
  352. }
  353. store.commit('SET_LAYER_INFO', mapLayers.value)
  354. }
  355. async function updateElements() {
  356. let content = null
  357. const currentLayer = selectedLayer.value
  358. content = currentLayer.resource.content
  359. content.properties.color = layerState.color
  360. // 移除标点
  361. useGMapCoverHook.removeCoverFromMap(currentLayer.id)
  362. // 移除文本覆盖物
  363. useGMapCoverHook.removeCoverFromMap(currentLayer.id + '_other')
  364. updateMapElement(selectedLayer.value, layerState.layerName, layerState.color)
  365. const result = await updateElementsReq(layerState.layerId, {
  366. name: layerState.layerName,
  367. content: content
  368. })
  369. getElementGroups()
  370. }
  371. function updateWgs84togcj02() {
  372. const layers = mapLayers.value
  373. layers.forEach((item: any) => {
  374. if (item.elements) {
  375. item.elements.forEach((ele: any) => {
  376. updateCoordinates('wgs84-gcj02', ele)
  377. })
  378. }
  379. })
  380. return layers
  381. }
  382. function updateCoordinates(transformType: string, element: LayerResource) {
  383. const type = element.resource?.type as number
  384. if (element.resource) {
  385. if (MapElementEnum.PIN === type) {
  386. const coordinates = element.resource?.content.geometry
  387. .coordinates as GeojsonCoordinate
  388. if (transformType === 'wgs84-gcj02') {
  389. const transResult = wgs84togcj02(
  390. coordinates[0],
  391. coordinates[1]
  392. ) as GeojsonCoordinate
  393. element.resource.content.geometry.coordinates = transResult
  394. } else if (transformType === 'gcj02-wgs84') {
  395. const transResult = gcj02towgs84(
  396. coordinates[0],
  397. coordinates[1]
  398. ) as GeojsonCoordinate
  399. element.resource.content.geometry.coordinates = transResult
  400. }
  401. } else if (MapElementEnum.LINE === type) {
  402. const coordinates = element.resource?.content.geometry
  403. .coordinates as GeojsonCoordinate[]
  404. if (transformType === 'wgs84-gcj02') {
  405. coordinates.forEach((coordinate, i, arr) => {
  406. arr[i] = wgs84togcj02(
  407. coordinate[0],
  408. coordinate[1]
  409. ) as GeojsonCoordinate
  410. })
  411. } else if (transformType === 'gcj02-wgs84') {
  412. coordinates.forEach((coordinate, i, arr) => {
  413. arr[i] = gcj02towgs84(
  414. coordinate[0],
  415. coordinate[1]
  416. ) as GeojsonCoordinate
  417. })
  418. }
  419. element.resource.content.geometry.coordinates = coordinates
  420. } else if (MapElementEnum.POLY === type) {
  421. const coordinates = element.resource?.content.geometry
  422. .coordinates[0] as GeojsonCoordinate[]
  423. if (transformType === 'wgs84-gcj02') {
  424. coordinates.forEach((coordinate, i, arr) => {
  425. arr[i] = wgs84togcj02(
  426. coordinate[0],
  427. coordinate[1]
  428. ) as GeojsonCoordinate
  429. })
  430. } else if (transformType === 'gcj02-wgs84') {
  431. coordinates.forEach((coordinate, i, arr) => {
  432. arr[i] = gcj02towgs84(
  433. coordinate[0],
  434. coordinate[1]
  435. ) as GeojsonCoordinate
  436. })
  437. }
  438. element.resource.content.geometry.coordinates = [coordinates]
  439. }
  440. }
  441. }
  442. </script>
  443. <style lang="scss" scoped>
  444. @import '/@/styles/index.scss';
  445. </style>
  446. <style lang="scss">
  447. .drawer-element-wrapper {
  448. .ant-drawer-content {
  449. background-color: $dark-highlight;
  450. color: $text-white-basic;
  451. .ant-drawer-header {
  452. background-color: $dark-highlight;
  453. .ant-drawer-title {
  454. color: $text-white-basic;
  455. }
  456. .ant-drawer-close {
  457. color: $text-white-basic;
  458. }
  459. }
  460. .ant-input {
  461. background-color: #101010;
  462. border-color: $dark-border;
  463. color: $text-white-basic;
  464. }
  465. }
  466. .color-content {
  467. display: flex;
  468. align-items: center;
  469. margin: 8px 0 10px;
  470. .color-item {
  471. cursor: pointer;
  472. width: 18px;
  473. height: 18px;
  474. line-height: 18px;
  475. display: flex;
  476. align-items: center;
  477. margin-right: 5px;
  478. }
  479. }
  480. .title {
  481. display: inline-flex;
  482. width: 80px;
  483. }
  484. .element-item {
  485. margin-bottom: 10px;
  486. }
  487. }
  488. .scrollbar {
  489. overflow: auto;
  490. }
  491. </style>