<!-- * @Description: 部件编辑 * @Author: 王晓颖 * @Date: 2021-03-08 09:46:39 --> <template> <app-container> <div id="map" class="leaflet_container"/> <!--查询显示组件--> <search-list ref="searchList" :total-data="searchList" :loading="searchLoading" @search="search" @clear="clearSearch" @change="changeSearchPage" @item-click="clickSearchItem"/> <!--工具箱--> <tools-container> <!-- 绘制工具--> <draw-tool @click="drawBox"/> <!--删除工具--> <delete-tool :disabled="batchDeleteDisabled" @click="batchDelete"/> <!--添加工具--> <add-tool @click="openAddMode"/> <!--图层选择工具--> <layer-choose :layers="layers" :select-layers="selectLayers" @choose="layerChange"/> </tools-container> <!--图层选择组件--> <!--编辑部件弹窗--> <edit-parts-dialog ref="editPartsDialog" @add="onAddFeature" @update="onUpdateFeature" @abort="onAbort"/> </app-container> </template> <script> import L from 'leaflet' import 'leaflet/dist/leaflet.css' import AppContainer from '@/components/layout/AppContainer' import LayerChoose from './components/layerChoose' import SearchList from './components/searchList' import { mapGetters } from 'vuex' import EditPartsDialog from './components/editPartsDialog' import ToolsContainer from './components/toolsContainer' import DrawTool from './components/drawTool' import DeleteTool from './components/deleteTool' import AddTool from './components/addTool' var esri = require('esri-leaflet') export default { name: 'ComponentEdit', components: { AddTool, DeleteTool, DrawTool, ToolsContainer, EditPartsDialog, LayerChoose, AppContainer, SearchList }, data() { return { searchLoading: false, // 加载动画 addMode: false, // 添加元素状态 map: null, // 地图对象 drawLayer: null, // 绘制图层(框选) layers: this.baseConfig.layers, selectLayers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maps: [], // 地图图层 parts: [], // 部件图层 featureCollection: [], // 选中的图层和Feature featureCollectionLength: 0, // 选中的feature个数 batchDeleteDisabled: true, // 批量删除是否不可点 curretLayer: null, // 当前选中featureLayer currentItem: null, // 当前选中部件 searchList: [], // 全部查询结果 currentSearchMarkers: [], // 当前查询页面的markers // searchLength: 0, // 全部查询结果长度 searchIconList: [] // 查询结果的Icon列表 } }, computed: { ...mapGetters([ 'baseUrl', 'partsUrl', 'partsAllUrl', 'mapUrl', 'partsEditUrl' ]) }, watch: { addMode(val) { if (!val) { // 关闭新增模式是要关闭点击事件 this.map.off('click') } } }, // 销毁时移除监听 destroyed() { window.removeEventListener('click', this.handleKeyup, true) this.socket.close() }, // 加载完添加点击事件的监听 mounted() { this.init() this.initIcons() window.addEventListener('click', this.handleKeyup, true) }, methods: { // 地图上点击之后,搜索列表隐藏 handleKeyup(val) { if (this.$refs.searchList && val.target.id === 'map') { this.$refs.searchList.hideSearch() } }, // 移除绘制图层 drawDelete() { if (this.drawLayer) { this.drawLayer.layer.remove() } }, // 绘制选框 drawBox(type) { this.drawDelete() this.$nextTick(() => { this.map.pm.enableDraw(type, { snappable: false }) // 绘制完成 this.map.on('pm:create', e => { // 获取 选区的geojson const geojson = e.layer.toGeoJSON() // 执行查询,遍历所有部件图层 const results = [] // 存放查询结果 let partsCount = 0 // 部件数量 const partsLength = this.parts.length // 全部部件图层数量 for (let i = 0; i < partsLength; i++) { const part = this.parts[i] const query = esri.query({ url: part.options.url }) query.where('1=1') query.within(geojson) query.run((error, featureCollection, response) => { if (error) { console.log(error) return } // 查询成功 if (featureCollection.features.length > 0) { results.push({ layer: part, url: part.options.url, features: featureCollection.features }) partsCount += featureCollection.features.length } // 最后一个图层完毕, 查询结果放入featureCollection中 if (i === (partsLength - 1)) { this.featureCollection = results this.featureCollectionLength = partsCount this.$message.info(`已选中部件: ${partsCount} 个`) this.batchDeleteDisabled = false } }) } }) }) }, // 部件图层切换 layerChange(node, checked) { const selectItem = this.layers.filter(item => item.name === node.name)[0] // 如果选中底图,选中添加,没选中移除 if (selectItem.type === 'map') { if (!checked) { for (const item of this.maps) { this.map.removeLayer(item) } } else { for (const item of this.maps) { this.map.addLayer(item) } } } else if (node.type === 'layer') { // 其他图层 if (!checked) { for (let i = 0; i < selectItem.domain.length; i++) { this.map.removeLayer(this.parts[selectItem.domain[i] - 1]) } } else { for (let i = 0; i < selectItem.domain.length; i++) { this.map.addLayer(this.parts[selectItem.domain[i] - 1]) } } } }, // 初始化地图 init() { this.loading = false const map = L.map('map', { minZoom: 13, // maxZoom: 21, center: this.baseConfig.center, zoom: this.baseConfig.zoom, zoomControl: false, attributionControl: false, crs: L.CRS.EPSG3857 }) map.doubleClickZoom.disable() // 禁止双击 this.map = map // data挂载map window.map = map const that = this // 打开绘制工具 this.map.on('pm:create', e => { that.drawDelete() that.drawLayer = e }) // 打开弹窗添加的事件 this.map.on('popupopen', e => { // 给弹窗的编辑、删除按钮添加点击事件 document.getElementById('btnEdit').onclick = that.editInfo document.getElementById('btnDelete').onclick = that.delete }) // 添加底图 for (let i = 0; i <= 21; i++) { const item = { url: `${this.baseUrl}${this.mapUrl}/${i}` } this.maps.push(esri.featureLayer(item).addTo(map)) } // TODO: 添加部件:可优化为读取配置文件里的部件图层信息 // 添加部件, // const partsLayer = this.baseConfig.partsLayer for (let i = 1; i <= 40; i++) { let item = { url: `${this.baseUrl}${this.partsEditUrl}/${i}`, minZoom: 18 } console.log(item) if (i === 15) { item = { url: `${this.baseUrl}${this.partsEditUrl}/${i}`, minZoom: 18, style: function(feature) { return { color: '#ff0000', opacity: 0.75, weight: 5 } } } } var layer = esri.featureLayer(item).addTo(map) // 点击部件事件 layer.on('click', function(e) { // 获取要素的属性 const properties = e.layer.feature.properties that.currentItem = e.layer.feature that.searchLayerByUrl(e.layer.options.url) // 弹窗样式 var str = `<div class="pop-window"> <div class="pop-title"> ${properties['小类名称']} </div> <div class="pop-line"><col>大类:</col>${properties['大类名称']}</div> <div class="pop-line"><col>小类:</col>${properties['小类名称']}</div> <div class="pop-line"><col>部件编码:</col>${properties['编码']}</div> <div class="pop-line"><col>权属单位:</col>${properties['权属单位'] ? properties.权属单位 : '未知'}</div> <div class="pop-line"><col>详细地址:</col>${properties['详细地址'] ? properties.详细地址 : '未知'}</div> <div class="pop-btns"> <button id="btnEdit" class="mini-btn">编辑</button> <button id="btnDelete" class="mini-btn">删除</button> </div> <div>` var popup = L.popup().setContent(str) e.layer.dragging._marker.bindPopup(popup, { minWidth: 200 }).openPopup() }) this.parts.push(layer) } // this.map.setZoom(this.baseConfig.zoom) }, // 初始化图标 initIcons() { // 加载所有图标,1到5 for (let i = 1; i < 6; i++) { const icon = L.icon({ iconUrl: require(`@/assets/global_images/point${i}.png`), iconSize: [25, 25], iconAnchor: [12.5, 25] }) this.searchIconList.push(icon) } }, // 点击新增部件按钮: 添加按钮并打开弹窗 openAddMode() { if (this.addMode) { // 如果已经处于添加模式,则关闭,并清空页面上的点 this.addMode = false this.onAbort('create') } else { // 开始添加点 this.addMode = true const icon = L.icon({ iconUrl: require('../../assets/icons/icon-position.png'), iconSize: [35, 35], iconAnchor: [17.5, 35] }) this.map.on('click', e => { // 转换为GeoJSON const feat = L.marker(e.latlng, { icon: icon }) feat.addTo(this.map) this.currentItem = feat console.log(feat) this.$refs.editPartsDialog.initDialog(feat, 'create') // TODO: 测试新增代码 // const feat = L.marker(e.latlng).toGeoJSON() // feat.properties['OBJECTID'] = 13234 // feat.properties['POINT_X'] = 408913.539 // feat.properties['POINT_Y'] = 3071272.381 // feat.properties['大类编码'] = 1 // feat.properties['大类名称'] = 'test' // feat.properties['小类编码'] = 1 // feat.properties['小类名称'] = 'test' // console.log(feat) // const layerUrl = `${this.baseUrl}${this.partsEditUrl}/35/` // this.searchLayerByUrl(layerUrl) // this.currentLayer.addFeature(feat, (err, response) => { // console.log(err, response) // debugger // }) }) } }, // 点击编辑: 打开编辑弹窗 editInfo(ev) { // TODO: 临时测试,原样上传更新 this.currentLayer.updateFeature(this.currentItem, (err, response) => { if (err) { } else { this.$message.success('编辑成功') } }) // 打开弹窗 // console.log(this.currentItem) // this.$refs.editPartsDialog.initDialog(this.currentItem, 'update') }, // 点击单个元素的删除,ok delete(code) { // 先确认操作,然后执行删除工作 if (this.currentLayer && this.currentItem) { this.$confirm('确定删除该部件吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { // 确定 this.currentLayer.deleteFeature(this.currentItem.id, (err, response) => { if (err) { this.$message.error('删除失败') } else { this.$message.success('删除成功') this.currentItem = null } }) }) } }, // 框选区域批量删除,ok batchDelete() { if (this.featureCollectionLength > 0) { this.$confirm(`确定要删除所框选的${this.featureCollectionLength}个部件吗?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { // 确定则遍历图层,删除features for (const layer of this.featureCollection) { const ids = layer.features.map(item => item.id) if (layer.layer && layer.features) { layer.layer.deleteFeatures(ids, (err, response) => { if (err) { this.$message.error('批量删除部件失败') } else { this.$message.success('批量删除部件成功') this.drawDelete() this.featureCollection = [] this.featureCollectionLength = 0 } }) } } }).catch(() => { // 放弃则清空选框和查询结果 this.drawDelete() this.featureCollection = [] this.featureCollectionLength = 0 }) } }, // 添加到feature图层 onAddFeature(feature) { const feat = L.marker(feature.latlng).toGeoJSON() // 读取属性 feat.properties['大类编码'] = feature.classCode feat.properties['大类'] = feature.class feat.properties['小类编码'] = feature.subClassCode feat.properties['小类'] = feature.subClass feat.properties['权属单位'] = feature.dept feat.properties['地址'] = feature.address console.log(feat) // 图层地址拼接 const layerUrl = `${this.baseUrl}${this.partsEditUrl}/${feature.layer}/` // 遍历找到当前图层 this.searchLayerByUrl(layerUrl) // 添加部件 this.currentLayer.addFeature(feat, (err, response) => { console.log(err, response) debugger if (err) { console.log('添加失败') } else { // 关闭添加模式, 并关闭弹窗 this.addMode = true this.$refs.editPartsDialog.closeDialog() } }) }, // 新增部件的,放弃操作,dialogStatus弹窗类型 onAbort(dialogStatus) { if (dialogStatus === 'create') { // 清除地图上的锚点 if (this.currentItem) { this.map.removeLayer(this.currentItem) } } }, // 编辑保存操作 onUpdateFeature(feature) { if (this.currentLayer) { console.log('=========') console.log(feature) console.log('=========') this.currentLayer.updateFeature(feature, (err, response) => { if (err) { this.$message.error('部件信息更新失败') } else { this.$message.success('部件信息更新成功') } }) } }, // 地图查询功能 search(keyword) { if (keyword === '') { // 如果是空字符串,表示未输入任何查询条件,根据内容提示用户 this.$message.warning('请输入查询关键字') return } this.searchLoading = true var that = this const results = [] // 存放查询结果列表 // 遍历所有图层进行查询 const partsLength = this.parts.length // 全部部件图层数量 for (let i = 0; i < partsLength; i++) { const part = this.parts[i] var str = ['编码', '小类名称', '大类名称'] var query = esri.query({ url: part.options.url }) let queryString = '1=1' // 默认查询语句 if (keyword) { // 拼接查询语句 queryString = str.map(item => `(${item} like '%${keyword}%')`).join(' OR ') console.log(queryString) } else { this.$message.warning('请输入查询关键字') return } query.where(queryString) // query.within(this.bounds) // 执行查询 query.run((error, featureCollection, response) => { if (error) { console.log(error) return } const featuresLength = featureCollection.features.length if (featuresLength > 0) { // 遍历所有查询结果,放入results for (const feature of featureCollection.features) { results.push({ layer: part, feature: feature }) } } // 如果是最后一个图层 if (i === (partsLength - 1)) { that.searchList = results this.searchLoading = false // that.drawIcon() } }) } }, // 清空查询 clearSearch() { this.searchList = [] if (this.currentSearchMarkers.length > 0) { for (const marker of this.currentSearchMarkers) { this.map.removeLayer(marker) } this.currentSearchMarkers = [] } }, // 查询结果页面变化,重绘icon changeSearchPage(data) { console.log('changeSearchPage') // 清除原来绘制的点 if (this.currentSearchMarkers.length > 0) { for (const marker of this.currentSearchMarkers) { this.map.removeLayer(marker) } } // 赋予新的查询结果 const currentMarkesLength = data.length for (let i = 0; i < currentMarkesLength; i++) { const feature = data[i].feature var icon = this.searchIconList[i] const latlng = L.latLng([feature.geometry.coordinates[1], feature.geometry.coordinates[0]]) var item = L.marker(latlng, { icon: icon, layer: data[i].layer, properties: feature.properties }).addTo(this.map) this.currentSearchMarkers.push(item) } // 中心置为第一个点的中心 if (this.currentSearchMarkers[0]) { this.clickSearchItem(data[0]) } }, // 点击查询列表项,将位置放在中间 clickSearchItem(item) { console.log('clickItem') if (item) { const feature = item.feature const latlng = L.latLng([feature.geometry.coordinates[1], feature.geometry.coordinates[0]]) let zoom = this.map.getZoom() if (zoom < 19) { zoom = 19 } this.map.flyTo(latlng, zoom) } }, // 绘制查询结果 drawIcon() { var that = this for (var i = 0; i < that.markerlist.length; i++) { that.map.removeLayer(that.markerlist[i]) } that.markerlist = [] for (let num = 0; num < that.showlist.length; num++) { } this.cansearch = true }, // 根据url找到layer searchLayerByUrl(url) { const layer = this.parts.filter(item => item.options.url === url)[0] if (layer) { this.currentLayer = layer } } } } </script> <style rel="stylesheet/scss" lang="scss" scoped> $tableTitleHeight:35px; .leaflet_container{ width: 100%; height: 85vh; min-height: 596px; /*max-height: 90vh;*/ background-color: white; } </style> <style rel="stylesheet/scss" lang="scss"> .pop-window{ font-size: 14px;width:200px; padding-right:5px; .pop-title{ font-size: 16px; margin-bottom: 8px; } .pop-line{ line-height:20px; } .pop-btns{ margin:10px 0px; .mini-btn{ display: inline-block; line-height: 1; white-space: nowrap; cursor: pointer; background: #fff; border: 1px solid #dcdfe6; color: #606266; -webkit-appearance: none; text-align: center; box-sizing: border-box; outline: none; margin: 0; transition: .1s; font-weight: 500; padding: 7px 15px; font-size: 12px; border-radius: 3px; } .mini-btn+.mini-btn{ margin-left:10px; } #btnEdit{ color: #409eff; background: #ecf5ff; border-color: #b3d8ff; } #btnEdit:hover{ background: #409eff; border-color: #409eff; color: #fff; } #btnDelete{ color: #f56c6c; background: #fef0f0; border-color: #fbc4c4; } #btnDelete:hover{ background: #f56c6c; border-color: #f56c6c; color: #fff; } } } </style>