hbt-prevention-ui/src/components/draw.component.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>