forked from xxhjsb/hbt-prevention-ui
549 lines
16 KiB
Vue
549 lines
16 KiB
Vue
<template>
|
|
<div class="draw-box" v-if="visible" >
|
|
<div class="title">
|
|
<span>{{modelMap[viewModel]}}</span>
|
|
<div class="actions">
|
|
<div class="btn" @click="drawScreenFull">全屏</div>
|
|
<i class="btn el-icon-close" @click="closePop()"></i>
|
|
</div>
|
|
</div>
|
|
<div class="content-box">
|
|
<div class="update-box">
|
|
<div class="form-box">
|
|
<FormComponent v-if="viewModel!=='list'" labelWidth="56px" :isReadonly="isReadonly" :fullBtn="true" :btnPosition="'center'" @change="change" :data.sync="updateParams" @actionCallback="callback" :options="options" :actions="actions"></FormComponent>
|
|
<FormComponent v-else labelWidth="56px" @input="change" :data.sync="listParams" :options="listForm" ></FormComponent>
|
|
</div>
|
|
<div class="tree-box" v-if="viewModel==='list'">
|
|
<el-tree :data="treeData" :props="{children:'children',label:'name'}" :expand-on-click-node="false" default-expand-all highlight-current
|
|
:filter-node-method="filterNode"
|
|
ref="tree" @node-click="handleNodeClick">
|
|
<span class="custom-tree-node" slot-scope="{ node, data }">
|
|
<span class="text-block">{{ node.label }}</span>
|
|
<span>
|
|
<el-button
|
|
v-if="!data.geoJson || data.geoJson==='[]'"
|
|
type="text"
|
|
size="mini"
|
|
@click="() => drawNode(data)">
|
|
绘制
|
|
</el-button>
|
|
<el-tag v-else size="mini" type="success">已绘制</el-tag>
|
|
</span>
|
|
</span>
|
|
</el-tree>
|
|
</div>
|
|
</div>
|
|
<div class="map-box" ref="draw">
|
|
<MapComponent @onLoad="getMap"></MapComponent>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script lang="ts">
|
|
import FormComponent from 'hbt-common/components/common/form.component.vue';
|
|
import { Component, Emit, Prop, PropSync, Vue, Watch } from 'vue-property-decorator';
|
|
import MapComponent from './map.component.vue';
|
|
import screenfull from "screenfull"
|
|
import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css';
|
|
import MapboxDraw from '@mapbox/mapbox-gl-draw';
|
|
import * as turf from '@turf/turf'
|
|
import UnitService from '@/service/unit.service';
|
|
import AreaService from '@/service/area.service';
|
|
@Component({
|
|
components:{
|
|
MapComponent,
|
|
FormComponent
|
|
}
|
|
})
|
|
export default class DrawComponent extends Vue {
|
|
|
|
public unitService = new UnitService()
|
|
public areaService = new AreaService()
|
|
|
|
public treeData = [] as any;
|
|
|
|
public modelMap = {
|
|
list:"批量绘制",
|
|
area:"区域绘制",
|
|
unit:"单元绘制"
|
|
}
|
|
|
|
public listForm = [{
|
|
name:"关键词",
|
|
key:"keyword",
|
|
width:"100%",
|
|
type:"text",
|
|
placeholder:"请输入名称关键词",
|
|
}];
|
|
public listParams = {
|
|
keyword:""
|
|
}
|
|
|
|
public map:any;
|
|
|
|
public updateParams = {} as any;
|
|
|
|
public positions = [] as any;
|
|
|
|
public hasInitDraw = false;
|
|
public options = [];
|
|
public actions = [];
|
|
|
|
public draw = new MapboxDraw({
|
|
displayControlsDefault: false,
|
|
// Select which mapbox-gl-draw control buttons to add to the map.
|
|
controls: {
|
|
polygon: true,
|
|
trash: true
|
|
},
|
|
// Set mapbox-gl-draw to draw by default.
|
|
// The user does not have to click the polygon control button first.
|
|
defaultMode: 'draw_polygon'
|
|
})
|
|
@PropSync("model")
|
|
public viewModel:string;
|
|
@Prop()
|
|
public isReadonly:boolean;
|
|
@PropSync("show",{
|
|
required:true,
|
|
default:true
|
|
})
|
|
public visible:boolean;
|
|
@PropSync("data",{
|
|
required:true,
|
|
})
|
|
public params!:any;
|
|
|
|
public fromList = false;
|
|
|
|
@Watch("params",{immediate:true,deep:true})
|
|
onChanges(newVal,odlVal){
|
|
this.updateParams = JSON.parse(JSON.stringify(newVal))
|
|
if(newVal.geoJson){
|
|
this.positions = JSON.parse(newVal.geoJson);
|
|
|
|
if(this.positions.length){
|
|
this.flyToCenter(16)
|
|
}
|
|
if(this.map){
|
|
|
|
this.addAllPolygon();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Watch("viewModel",{immediate:true,deep:true})
|
|
onModelChanges(newVal){
|
|
if(newVal==="list"){
|
|
this.fromList = true;
|
|
this.setTreeData();
|
|
}
|
|
this.buildUpdate()
|
|
}
|
|
@Watch("visible",{immediate:true,deep:true})
|
|
onVisibleChanges(newVal){
|
|
// console.log("close")
|
|
// if(!newVal){
|
|
// }
|
|
}
|
|
|
|
@Emit("onClose")
|
|
onClose(data){
|
|
//Emit false
|
|
}
|
|
|
|
created(){
|
|
// this.positions = [] as any;
|
|
}
|
|
|
|
public flyToCenter(zoom,timer?){
|
|
if(!this.map ){
|
|
return
|
|
}
|
|
const center = turf.centerOfMass(turf.featureCollection(this.positions));
|
|
this.map.flyTo({center: center.geometry.coordinates, zoom: zoom});
|
|
this.addAllPolygon();
|
|
}
|
|
|
|
public setTreeData(){
|
|
this.unitService.getUnitTree().then(res=>{
|
|
this.treeData = res.data;
|
|
this.positions = this.getAllFeatures(this.treeData);
|
|
if(this.positions.length && this.map){
|
|
this.addAllPolygon();
|
|
}
|
|
})
|
|
}
|
|
|
|
public getMap(map){
|
|
this.map = map;
|
|
this.map.on('draw.create', this.drawCallback);
|
|
this.map.on('draw.delete', this.drawCallback);
|
|
this.map.on('draw.update', this.drawCallback);
|
|
if(this.positions.length){
|
|
this.addAllPolygon();
|
|
this.flyToCenter(this.fromList?14:16)
|
|
}
|
|
}
|
|
|
|
public getAllFeatures(datas,result?){
|
|
if(!result){
|
|
result = []
|
|
}
|
|
datas.forEach((item)=>{
|
|
result.push(...JSON.parse(item.geoJson));
|
|
if(item.children){
|
|
result = this.getAllFeatures(item.children,result)
|
|
}
|
|
});
|
|
return result
|
|
}
|
|
|
|
public addAllPolygon(){
|
|
const featureCollection = turf.featureCollection(this.positions);
|
|
if(!this.map.getSource('allDataSource')){
|
|
this.map.addSource("allDataSource", {
|
|
type: "geojson",
|
|
data: featureCollection,
|
|
});
|
|
this.map.addLayer({
|
|
id:"allDataLayer",
|
|
source:"allDataSource",
|
|
type:"fill-extrusion",
|
|
paint:{
|
|
"fill-extrusion-color":"#38fcf9",
|
|
"fill-extrusion-base":["get","bottomHeight"],
|
|
"fill-extrusion-height":["get","topHeight"],
|
|
"fill-extrusion-opacity":0.6
|
|
}
|
|
});
|
|
this.map.addLayer({
|
|
"id": "textLayer",
|
|
"type": "symbol",
|
|
"source": "allDataSource",
|
|
"layout": {
|
|
"text-field": ["get", "name"],
|
|
"text-size": [
|
|
'interpolate',
|
|
// Set the exponential rate of change to 0.5
|
|
['exponential', 0.5],
|
|
['zoom'],
|
|
// When zoom is 15, buildings will be beige.
|
|
15,
|
|
12,
|
|
// When zoom is 18 or higher, buildings will be yellow.
|
|
18,
|
|
24,
|
|
],
|
|
// "text-allow-overlap": true
|
|
},
|
|
"paint": {
|
|
"text-color": "#FFF",
|
|
"text-halo-color": "#000",
|
|
"text-halo-width": 10
|
|
}
|
|
})
|
|
}else{
|
|
this.map.getSource("allDataSource").setData(featureCollection)
|
|
}
|
|
|
|
}
|
|
|
|
public removeLayer(){
|
|
if(!this.map){
|
|
return
|
|
}
|
|
if(this.map.getSource("allDataSource")){
|
|
this.map.removeLayer("allDataLayer")
|
|
this.map.removeLayer("textLayer")
|
|
this.map.removeSource("allDataSource")
|
|
}
|
|
}
|
|
|
|
public drawCallback(e){
|
|
if(e.type ==="draw.create"){
|
|
this.positions = this.positions.concat(e.features)
|
|
}else if(e.type ==="draw.update"){
|
|
this.positions.splice(this.positions.findIndex((item:any)=>item.id===e.features[0].id),1,e.features[0])
|
|
}else{
|
|
this.positions.splice(this.positions.findIndex((item:any)=>item.id===e.features[0].id),1)
|
|
}
|
|
// console.log(this.positions)
|
|
}
|
|
|
|
public drawScreenFull(){
|
|
screenfull.toggle(this.$refs.draw as any);
|
|
if(this.map){
|
|
this.map.resize()
|
|
}
|
|
}
|
|
// 树节点过滤
|
|
public filterNode(value, data) {
|
|
if (!value) return true;
|
|
return data.name.indexOf(value) !== -1;
|
|
}
|
|
|
|
public change(data,meta){
|
|
if(meta.key === "keyword"){
|
|
(this.$refs.tree as any).filter(data)
|
|
}
|
|
}
|
|
|
|
public handleNodeClick(node){
|
|
const geoJson = JSON.parse(node.geoJson);
|
|
if(!geoJson.length){
|
|
return
|
|
}
|
|
const center = turf.centerOfMass(turf.featureCollection(geoJson));
|
|
this.map.flyTo({center: center.geometry.coordinates, zoom: 18});
|
|
}
|
|
|
|
public drawNode(data){
|
|
this.params =Object.assign({bottomHeight:0,topHeight:0,geoJson:"[]"},data) as any;
|
|
if(data.areaId){
|
|
this.viewModel = "unit"
|
|
}else{
|
|
this.viewModel = "area"
|
|
}
|
|
|
|
}
|
|
// 点击绘制
|
|
public drawCallBack(){
|
|
if(!this.map){
|
|
this.$message.error("地图未初始化完成,请稍后再试")
|
|
return
|
|
}
|
|
if(this.hasInitDraw){
|
|
this.$message.error("绘制工具已打开请点击地图右上角进行绘制")
|
|
return
|
|
}
|
|
this.map.addControl(this.draw);
|
|
if(this.positions.length){
|
|
this.removeLayer()
|
|
this.positions.forEach(feature=>{
|
|
this.draw.add(feature)
|
|
})
|
|
}
|
|
this.hasInitDraw = true;
|
|
}
|
|
|
|
public destoryDraw(){
|
|
this.map.removeControl(this.draw);
|
|
this.hasInitDraw = false;
|
|
}
|
|
|
|
// 点击保存
|
|
public doSave(){
|
|
if(this.updateParams.topHeight<this.updateParams.bottomHeight){
|
|
this.$message.error("顶部高度不能小于底部高度")
|
|
return
|
|
}
|
|
if(!this.positions.length){
|
|
this.$message.error("请绘制地图位置信息");
|
|
return
|
|
}
|
|
this.positions.forEach(feature=>{
|
|
feature.properties = this.updateParams
|
|
})
|
|
this.updateParams.geoJson = JSON.stringify(this.positions);
|
|
if(this.fromList){
|
|
const service = this.viewModel==='unit'?this.unitService:this.areaService;
|
|
service.addOrUpdate(this.updateParams,false).then(res=>{
|
|
this.viewModel = "list";
|
|
this.destoryDraw();
|
|
this.setTreeData()
|
|
})
|
|
this.onClose(false)
|
|
}else{
|
|
this.params = this.updateParams;
|
|
this.visible = false;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public callback(action){
|
|
if(action.value==="draw"){
|
|
this.drawCallBack();
|
|
}else if(action.value==="save"){
|
|
this.doSave()
|
|
}else {
|
|
if(this.fromList){
|
|
this.viewModel = "list";
|
|
if(this.hasInitDraw){
|
|
this.draw.deleteAll()
|
|
}
|
|
}else{
|
|
this.visible = false;
|
|
}
|
|
this.updateParams = {} as any;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
public buildUpdate(){
|
|
this.options = [{
|
|
name:"区域名称",
|
|
width:"100%",
|
|
hide:this.viewModel === "unit",
|
|
key:"name",
|
|
disable:true,
|
|
type:"text",
|
|
placeholder:"请输入区域名称",
|
|
},{
|
|
name:"单元名称",
|
|
key:"name",
|
|
hide:this.viewModel === "area",
|
|
width:"100%",
|
|
disable:true,
|
|
type:"text",
|
|
placeholder:"请输入单元名称",
|
|
},{
|
|
name:"顶部高度",
|
|
key:"topHeight",
|
|
width:"100%",
|
|
type:"number",
|
|
placeholder:"",
|
|
},{
|
|
name:"底部高度",
|
|
key:"bottomHeight",
|
|
width:"100%",
|
|
type:"number",
|
|
placeholder:"",
|
|
}] as any;
|
|
|
|
this.actions = [{
|
|
name:"绘制",
|
|
value:"draw",
|
|
icon:"el-icon-edit",
|
|
type:"primary"
|
|
},{
|
|
name:"保存",
|
|
value:"save",
|
|
icon:"el-icon-s-order",
|
|
type:"primary"
|
|
},{
|
|
name:"取消",
|
|
icon:"el-icon-tickets",
|
|
value:"cancel"
|
|
}] as any;
|
|
}
|
|
|
|
mounted(){
|
|
}
|
|
|
|
|
|
public closePop(){
|
|
this.removeMap();
|
|
this.updateParams = {} as any;
|
|
this.onClose(false)
|
|
this.visible = false;
|
|
}
|
|
|
|
public removeMap(){
|
|
this.map.off('draw.create', this.drawCallback);
|
|
this.map.off('draw.delete', this.drawCallback);
|
|
this.map.off('draw.update', this.drawCallback);
|
|
this.removeLayer();
|
|
this.map.remove();
|
|
this.map = null;
|
|
}
|
|
// destroyed(){
|
|
// // console.log(123)
|
|
// this.removeMap()
|
|
// }
|
|
}
|
|
</script>
|
|
<style lang="scss" scoped>
|
|
::v-deep{
|
|
.el-button{
|
|
padding: 0 8px;
|
|
height: 40px;
|
|
line-height: 40px;
|
|
}
|
|
.el-tree-node__content{
|
|
height: auto;
|
|
}
|
|
.el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content .el-button--text{
|
|
color: #FFF;
|
|
}
|
|
|
|
}
|
|
.draw-box{
|
|
width: 100%;
|
|
height: 100%;
|
|
border-radius: 8px;
|
|
background: #FFF;
|
|
padding: 0 40px 40px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
position: relative;
|
|
z-index: 1000;
|
|
.title{
|
|
height: 68px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
border-bottom: 1px solid #EEE;
|
|
font-size: 20px;
|
|
color: #606266;
|
|
.actions{
|
|
display: inline-flex;
|
|
align-items: center;
|
|
.btn{
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
margin-left: 30px;
|
|
}
|
|
}
|
|
}
|
|
.content-box{
|
|
width: 100%;
|
|
height: 1px;
|
|
flex: 1;
|
|
margin-top: 40px;
|
|
display: flex;
|
|
.update-box{
|
|
width: 250px;
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
.form-box{
|
|
width: 100%;
|
|
}
|
|
.tree-box{
|
|
width: 100%;
|
|
height: 1px;
|
|
flex: 1;
|
|
overflow: hidden;
|
|
overflow-y: auto;
|
|
padding-right: 20px;
|
|
|
|
.custom-tree-node{
|
|
flex: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
font-size: 14px;
|
|
padding: 3px 0;
|
|
padding-right: 20px;
|
|
.text-block{
|
|
flex: 1;
|
|
width: 1px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
padding-right: 10px;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.map-box{
|
|
width: 1px;
|
|
flex: 1;
|
|
height: 100%;
|
|
}
|
|
}
|
|
}
|
|
</style> |