| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718 |
- <template>
- <div class="mapBox">
- <div
- id="map"
- @dragover="e => e.preventDefault()"
- @dragleave="isCheck = false"
- @dragenter="isCheck = true"
- />
- <div
- v-if="isEdit"
- class="mapBox-menu"
- >
- <n-tree-select
- v-model:value="selected"
- :options="options"
- label-field="name"
- key-field="uuid"
- children-field="childList"
- check-strategy="child"
- placeholder="请选择车站"
- @update:value="changeMap"
- />
- <div class="mapBox-menu-content">
- <n-scrollbar>
- <template
- v-for="(item, index) in iconList"
- :key="index"
- >
- <div
- class="mapBox-menu-content-item"
- draggable="true"
- @dragstart="currentItem = item"
- @dragend="itemDragEnd"
- >
- <img
- :src="item.icon"
- alt=""
- srcset=""
- draggable="false"
- >
- <p>
- {{ item.name }}
- </p>
- </div>
- </template>
- </n-scrollbar>
- </div>
- </div>
- <div
- v-if="isEdit && showPoup"
- class="mapBox-poup"
- >
- <n-form
- ref="formRef"
- :model="formMode"
- label-placement="left"
- label-width="auto"
- size="small"
- :rules="rules"
- >
- <n-form-item
- label="元素编号"
- path="uuid"
- >
- <n-input
- v-model:value="formMode.uuid"
- disabled
- />
- </n-form-item>
- <n-form-item
- label="资产编号"
- path="guid"
- >
- <n-select
- v-model:value="formMode.guid"
- placeholder="请选择"
- label-field="name"
- value-field="guid"
- :options="assetOption"
- @update:value="selectChange"
- />
- </n-form-item>
- <n-form-item
- label="元素类型"
- path="type"
- >
- <n-input
- v-model:value="formMode.type"
- disabled
- />
- </n-form-item>
- <n-form-item
- label="x 坐 标 "
- path="x"
- >
- <n-input-number
- v-model:value="formMode.x"
- clearable
- :show-button="false"
- @blur="resetMarker('lng', formMode.x)"
- />
- </n-form-item>
- <n-form-item
- label="y 坐 标 "
- path="y"
- >
- <n-input-number
- v-model:value="formMode.y"
- clearable
- :show-button="false"
- @blur="resetMarker('lat', formMode.y)"
- />
- </n-form-item>
- <n-form-item
- label="横向缩放"
- path="scaleX"
- >
- <n-input-number
- v-model:value="formMode.scaleX"
- clearable
- :min="0.1"
- :show-button="false"
- @blur="resetMarker('scaleX', formMode.scaleX, '0')"
- />
- </n-form-item>
- <n-form-item
- label="纵向缩放"
- path="scaleY"
- >
- <n-input-number
- v-model:value="formMode.scaleY"
- clearable
- :min="0.1"
- :show-button="false"
- @blur="resetMarker('scaleY', formMode.scaleY, '1')"
- />
- </n-form-item>
- <n-form-item
- label="水平翻转"
- path="flipX"
- >
- <n-switch
- v-model:value="formMode.flipX"
- @update:value="resetMarker('flipX', formMode.flipX)"
- />
- </n-form-item>
- <n-form-item
- label="垂直翻转"
- path="flipY"
- >
- <n-switch
- v-model:value="formMode.flipY"
- @update:value="resetMarker('flipY', formMode.flipY)"
- />
- </n-form-item>
- </n-form>
- <div class="mapBox-poup-btns">
- <n-button
- type="error"
- ghost
- @click="del"
- >
- 删除
- </n-button>
- <n-button
- type="primary"
- @click="save"
- >
- 保存
- </n-button>
- </div>
- </div>
- </div>
- </template>
- <script setup lang='ts'>
- import * as L from 'leaflet'
- import {
- onMounted,
- onUnmounted,
- Ref, ref, watch
- } from 'vue'
- import '@/../node_modules/leaflet/dist/leaflet.css'
- import { LatLngExpression } from 'leaflet';
- // ----- 修复L 增加tooltip 缩放报错 重要!!
- (L.Tooltip as any).prototype._animateZoom = function (e: { zoom: any; center: any; }) {
- if (!(this as any)._map) {
- return
- }
- const pos = (this as any)._map._latLngToNewLayerPoint((this as any)._latlng, e.zoom, e.center) as any
- (this as any)._setPosition(pos)
- };
- (L.Tooltip as any).prototype._updatePosition = function () {
- if (!(this as any)._map) {
- return
- }
- const pos = (this as any)._map.latLngToLayerPoint((this as any)._latlng) as any
- (this as any)._setPosition(pos)
- }
- // ---------
- export interface Item {
- icon: string,
- x?: number,
- y?: number,
- scaleX?: number,
- scaleY?: number,
- guid?: string,
- type?: string,
- uuid?: string,
- item?: string,
- isAdd?: boolean,
- deviceId?: string,
- itemName?: string
- }
- export interface BaseMap {
- width: number,
- height: number,
- mapId: string,
- name?: string
- imgUrl: string,
- trackType?: number, // 1电扶梯 2给排水 3通风 4照明 5车站环境检测 6车站暖通 7车站动环
- showTip?: boolean,
- uuid?: string
- }
- const props = withDefaults(defineProps<{
- isEdit?: boolean, // 是否编辑
- baseMap?: BaseMap
- }>(), {
- isEdit: true,
- baseMap: () => ({
- imgUrl: '/system/track/djy.png', width: 1400, height: 430, mapId: 'd33d83d7-02cb-2ce3-37e1-e563320b93dd', showTip: false
- })
- })
- const emit = defineEmits<{(evt: 'markerClick', value: Item): void }>()
- const Map = ref()
- const iconList = ref([
- { name: '电扶梯', icon: 'https://gsm.jdjinsui.com/gsmImage/system/track/电扶梯正常.png' }
- ] as { icon: string, name: string }[])
- const currentItem: Ref<Item | undefined> = ref()
- const isCheck = ref(false)
- const options = ref([
- {
- imgUrl: '/system/track/djy.png', width: 1400, height: 430, mapId: 'd33d83d7-02cb-2ce3-37e1-e563320b93dd', name: 'test', uuid: 'e5cdf830-587a-11ef-a53a-f3fe72299e7d'
- }
- ] as BaseMap[])
- const selected = ref('')
- const assetOption = ref([])
- const formRef = ref()
- const formMode = ref({
- uuid: '',
- guid: '',
- type: '',
- x: 0,
- y: 0,
- scaleX: 1,
- scaleY: 1,
- flipX: false,
- flipY: false,
- icon: '',
- tip: '',
- isAdd: false
- })
- const rules = { guid: { required: true, message: '请选择资产' } }
- const showPoup = ref(false)
- const currentMap: Ref<BaseMap | undefined> = ref()
- const currentMarker = ref()
- const allMarker = ref([] as L.Marker[])
- // 资产下拉
- function selectChange(_: string, option: Item) {
- formMode.value.type = option.type || ''
- }
- // 重置marker样式
- function resetMarker(type: string, num: number | boolean, id?: string) {
- if (type === 'lat' || type === 'lng') {
- const obj = currentMarker.value.getLatLng()
- obj[type] = num
- currentMarker.value.setLatLng(obj)
- console.log('位置更新', obj)
- } else {
- const newIcon = currentMarker.value.getIcon()
- if (type === 'scaleX' || type === 'scaleY') {
- newIcon.options.iconSize[id!] = newIcon.options.iconSize[id!] * (num as number)
- } else {
- const newHtml = newIcon.options.html.replace(/style="[^=>]*"/g, (s: string) => {
- const a = s.split(' ')
- const c = type === 'flipY' ? 'rotateX' : 'rotateY'
- const n = type === 'flipY' ? 1 : 2
- a[n] = num ? `${c}(180deg)${n === 2 ? ';"' : ''}` : `${c}(0deg)${n === 2 ? ';"' : ''}`
- const b = a.join(' ')
- return b
- })
- newIcon.options.html = newHtml
- }
- currentMarker.value.setIcon(newIcon)
- }
- }
- // marker选中状态
- function selectMaker(marker: L.Marker) {
- for (let k = 0; k < allMarker.value.length; k++) {
- const el = allMarker.value[k]
- const dom = el.getElement()
- if ((el as Any)._leaflet_id === (marker as Any)._leaflet_id) {
- dom!.style.boxShadow = '0 0 4px 2px #ff891a'
- } else {
- dom!.style.boxShadow = 'none'
- }
- }
- currentMarker.value = marker
- }
- // 查询设备列表+设置poup
- function setPoupData(marker: L.Marker, option: Item) {
- // 查询设备列表
- // socketService.send('item.query', { itemtype: option.deviceId, isPaging: 0, region: selected.value }).then((res) => {
- // const { success, data: { list } } = res
- // if (success && list) {
- // assetOption.value = list
- // }
- // })
- formMode.value.uuid = option.uuid || ''
- formMode.value.guid = option.guid || option.item || ''
- formMode.value.type = option.type || ''
- formMode.value.x = option.x || 0
- formMode.value.y = option.y || 0
- formMode.value.isAdd = option.isAdd || false
- // 移除其它选中状态
- selectMaker(marker)
- showPoup.value = true
- }
- // 添加marker
- function addMarker(option: Item) {
- if (!Map.value) throw 'Map 未初始化'
- if (!option.icon) return console.log('缺少参数')
- // 自定义图标
- const img = `<img src="${option.icon}" style="width:100%;height:100%;transform: rotateX(0deg) rotateY(0deg);"/>`
- const ICON = L.divIcon({
- iconSize: [ (option.scaleX || 1) * 30, (option.scaleY || 1) * 30 ],
- html: img
- })
- const marker = L.marker([ option.y || 0, option.x || 0 ], {
- icon: ICON,
- draggable: props.isEdit, // 是否可通过鼠标/触摸拖动。
- riseOnHover: true
- }).addTo(Map.value)
- // add
- allMarker.value.push(marker)
- // 显示tip
- if (props.baseMap.showTip && option.itemName) marker.bindTooltip(option.itemName, { direction: 'top', className: 'resetTips' })
- // 显示poup
- if (isCheck.value) {
- console.log('添加marker', option)
- setPoupData(marker, option)
- }
- // 事件
- marker.addEventListener('click', () => {
- console.log('marker点击', option)
- setPoupData(marker, option)
- emit('markerClick', option)
- })
- marker.addEventListener('dragend', (e) => {
- console.log('marker拖动', option)
- const { lat, lng } = e.target.getLatLng()
- option.x = lng
- option.y = lat
- setPoupData(marker, option)
- })
- }
- // 拖拽=>添加marker
- function itemDragEnd(e: DragEvent) {
- e.preventDefault()
- const point = Map.value.containerPointToLayerPoint([ e.layerX, e.layerY ]) // 给定相对于origin pixel的相应像素坐标
- const lnglat = Map.value.layerPointToLatLng(point) // 给定地理坐标,转换为相对于origin pixel的相应像素坐标
- if (isCheck.value) {
- addMarker({
- ...currentItem.value, x: lnglat.lng, y: lnglat.lat, isAdd: true
- } as Item)
- }
- isCheck.value = false
- }
- // 查询marker+绑定
- async function bindMarker(mapId: string) {
- if (!mapId) return
- // 清除
- if (allMarker.value.length) {
- for (let k = 0; k < allMarker.value.length; k++) {
- const marker = allMarker.value[k]
- marker.remove()
- }
- allMarker.value = []
- }
- // 添加所有
- // let page = 1
- // let pages = 0
- // do {
- // const obj = {
- // map: mapId,
- // pageSize: 100,
- // isPaging: 0,
- // page: page++
- // } as { trackType?: number }
- // if (props.baseMap.trackType) obj.trackType = props.baseMap.trackType
- // const { data } = await socketService.send('monitor.itemQuery', obj)
- // store.setObtainedDevices(data.list)
- // console.log('底图关联的marker', data.list)
- // if (data.list) {
- // for (let k = 0; k < data.list.length; k++) {
- // const el = data.list[k]
- // const obj = {
- // parent: el,
- // ...el.mapElement,
- // icon: import.meta.env.VITE_IMG_URL + el.icon,
- // showTip: props.baseMap.showTip
- // }
- // addMarker(obj)
- // }
- // }
- // pages = data.pages
- // } while (page <= pages)
- }
- // 自定义marker
- function drawMarker(item: { latLng: number[], icon: string, children: any, label: string }) {
- if (!Map.value) return console.log('Map未初始化')
- if (!item.latLng || !item.latLng.length) return console.log('缺少参数')
- // 自定义图标
- const img = `<div class="newMark" style="background-image:url(${item.icon})">${item.label}</div>`
- const ICON = L.divIcon({
- iconSize: [ 100, 28 ],
- html: img,
- iconAnchor: [ -5, 14 ]
- })
- const marker = L.marker(item.latLng as LatLngExpression, {
- title: item.children[0],
- icon: ICON,
- draggable: false,
- riseOnHover: true // 该标记将位于其他标记的顶部
- })
- marker.addTo(Map.value)
- marker.addEventListener('click', (event) => {
- console.log('marker点击', event)
- // const { options: { title } } = event.target
- // const path = userData.getUserData().permission.find((el: { url: string }) => el.url === '/objHome')
- // if (path) {
- // // store.setCurrentTunnel({ ...title })
- // // store.setCurrentUuid(null)
- // localStorage.setItem('currentTunnel', JSON.stringify({ ...title }))
- // // router.push('/objHome')
- // } else {
- // window.$notification.warning({ title: '你没有权限,请联系管理员!', duration: 2000 })
- // }
- })
- return marker
- }
- // 切换底图
- async function changeMap(_: string, option: BaseMap) {
- console.log('切换底图', option)
- if (!Map.value) return
- // 清除layer
- Map.value.eachLayer((layer: L.Layer) => {
- Map.value.removeLayer(layer)
- })
- // 添加底图
- if (!option.imgUrl || !option.width || !option.height || !option.mapId) return window.$notification.error({ content: '底图缺少相关参数', duration: 3000, keepAliveOnHover: true })
- currentMap.value = option
- L.imageOverlay(`https://gsm.jdjinsui.com/gsmImage${option.imgUrl}`, [ [ 0, 0 ], [ option.height, option.width ] ]).addTo(Map.value)
- Map.value.setView([ option.height / 2, option.width / (props.isEdit ? 3.5 : 2) ], 0)
- // 获取底图绑定的maker
- bindMarker(option.mapId)
- }
- // 初始化底图
- function initMap(option: BaseMap) {
- if (!option) return
- Map.value = L.map('map', {
- zoom: 0,
- maxZoom: 1,
- minZoom: 0,
- zoomSnap: 0.1,
- center: [ option.height / 2, option.width / (props.isEdit ? 3.5 : 2) ],
- crs: L.CRS.Simple,
- attributionControl: false,
- zoomControl: false
- }).addEventListener('click', (e) => {
- console.log('map点击', e)
- if (currentMarker.value) currentMarker.value.getElement().style.boxShadow = 'none'
- formMode.value.flipX = false
- formMode.value.flipY = false
- formMode.value.scaleX = 1
- formMode.value.scaleY = 1
- formMode.value.uuid = ''
- showPoup.value = false
- })
- // 获取底图列表
- // socketService.send('region.map').then((res) => {
- // const { success, data } = res
- // if (success && data.length) {
- // options.value = data.map((el: { childList: Any[]; }) => {
- // if (el.childList) {
- // el.childList.forEach((es) => {
- // if (es.mapId === props.baseMap.mapId) selected.value = es.id
- // try {
- // const { width, height, imgUrl } = JSON.parse(es.mapConfig)
- // es.width = width
- // es.height = height
- // es.imgUrl = imgUrl
- // } catch (error) {
- // console.log('底图配置数据格式错误')
- // }
- // })
- // }
- // return el
- // })
- // }
- // })
- // 获取所有maker类型
- // socketService.send('itemType.search', { belongs: '3' }).then((res) => {
- // const { success, data } = res
- // if (success && data.length) iconList.value = data.map((el: { imageUrl: string; name: string; guid: string }) => ({ icon: import.meta.env.VITE_IMG_URL + el.imageUrl, name: el.name, deviceId: el.guid }))
- // })
- if (props.isEdit) { window.$notification.warning({ content: '请选择车站', duration: 3000, keepAliveOnHover: true }) } else {
- changeMap('', option)
- }
- }
- // 保存->更新marker
- async function save() {
- await formRef.value.validate()
- const mapElementList = [ {
- flipX: formMode.value.flipX,
- flipY: formMode.value.flipY,
- item: formMode.value.guid,
- map: currentMap.value!.mapId,
- scaleX: formMode.value.scaleX,
- scaleY: formMode.value.scaleY,
- type: formMode.value.type,
- uuid: formMode.value.uuid,
- visiable: true,
- x: formMode.value.x,
- y: formMode.value.y
- } ]
- console.log('保存', mapElementList, formMode.value.isAdd)
- // const url = formMode.value.isAdd ? 'mapElement.insert' : 'mapElement.update'
- // const { success } = await socketService.send(url, { mapElementList })
- // if (success) {
- // bindMarker(currentMap.value!.mapId)
- // window.$notification.success({ content: formMode.value.isAdd ? '添加成功' : '修改成功', duration: 2000 })
- // showPoup.value = false
- // }
- }
- // 删除当前
- async function del() {
- if (!currentMarker.value) return
- const fdid = allMarker.value.findIndex((el: Any) => el._leaflet_id === currentMarker.value._leaflet_id)
- if (fdid !== -1) {
- if (!formMode.value.isAdd) {
- // const { success } = await socketService.send('mapElement.delete', [ formMode.value.uuid ])
- // if (success) window.$notification.success({ content: '删除成功', duration: 2000 })
- }
- allMarker.value.splice(fdid, 1)
- currentMarker.value.remove()
- showPoup.value = false
- } else {
- throw '删除失败!'
- }
- }
- // 销毁
- function destory() {
- if (!Map.value) return
- console.log('销毁')
- Map.value.eachLayer((layer: L.Layer) => {
- Map.value.removeLayer(layer)
- })
- allMarker.value = []
- currentMarker.value = undefined
- currentMap.value = undefined
- Map.value = null
- }
- onMounted(() => {
- document.title = 'leaflet画图'
- initMap(props.baseMap)
- })
- watch(() => props.baseMap, (v) => {
- if (Map.value) {
- changeMap('', v)
- } else {
- initMap(v)
- }
- }, { deep: true })
- onUnmounted(() => destory())
- </script>
- <style lang="scss" scoped>
- .mapBox {
- width: 100vw;
- height: 100vh;
- background: none;
- position: relative;
- #map {
- width: 100%;
- height: 100%;
- background: #011719;
- :deep(.leaflet-div-icon) {
- background: none;
- border: none;
- border-radius: 6px;
- }
- :deep(.resetTips) {
- height: 33px;
- background: #06353A;
- border: 1px solid #34C0AE;
- color: white;
- &::before {
- border-top-color: #34C0AE;
- }
- p {
- margin: 0;
- font-size: 14px;
- }
- }
- }
- &-menu {
- position: absolute;
- left: 0;
- top: 0;
- z-index: 601;
- background: white;
- width: 380px;
- height: 100%;
- padding: 10px;
- &-content {
- width: 100%;
- height: calc(100% - 44px);
- :deep(.n-scrollbar-content) {
- display: flex;
- flex-wrap: wrap;
- align-content: flex-start;
- }
- &-item {
- text-align: center;
- width: 78px;
- height: 78px;
- padding: 10px 0;
- border: 1px solid #C9D3DD;
- cursor: grab;
- margin-top: 16px;
- &:not(:nth-child(4n)) {
- margin-right: 16px;
- }
- img {
- width: 50px;
- height: 40px;
- }
- p {
- margin: 0;
- color: #2c3e50;
- font-size: 12px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- }
- }
- }
- &-poup {
- position: absolute;
- right: 0;
- top: 0;
- width: 334px;
- background: #FFFFFF;
- box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.1);
- backdrop-filter: blur(29.99999943901511px);
- z-index: 601;
- padding: 20px;
- &-btns {
- text-align: center;
- &>button:first-child {
- margin-right: 20px;
- }
- }
- }
- }
- </style>
|