svg 实现拓扑图功能

包含svg元素的创建 元素的拖动 元素间连线 以及连线后的拖动 等

<template>
	<div class="contents" id="svgcontents">
			<div class="svgDiv">
				<svg   @touchstart.self="dragStart($event)" @touchmove.stop.prevent.self="dragSvg($event)" id="svgDom"    width="100%"  height="100%" :viewBox="svgViewBox" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
					<!--<text class="nodeText iconfont" :x="50" :y="120" >&#xe621;</text>-->
					<g @touchstart="dragStart($event)" :data-mold="item.type"  :data-catalog="item.catalog" @touchmove.prevent="dragDom($event,item.id)" v-for="(item,index) in iData.node" :id="item.id" @click="addClass($event,item)" :class="{selectG : item.id == curent}" >
						<!--<image   :x="calPosition(item).imgPos[0]" :y="calPosition(item).imgPos[1]" :xlink:href="require('../assets/img/'+item.type+'.png')"  height="40" width="40" preserveAspectRatio="xMidYMid meet "/>-->
						<!--<foreignObject :x="item.x" :y="item.y" width="30" height="30"  style="">
				            <i class="iconfont">&#xe611;</i>
				        </foreignObject> -->
				        <text data-type="icon" class="nodeText iconfont iconText"  :x="calPosition(item).imgPos[0]" :y="calPosition(item).imgPos[1]" >{{iconTranscod(item.type)}}</text>
						<text data-type="text" class="nodeText nameText" :x="calPosition(item).textPos[0]" :y="calPosition(item).textPos[1]"  >{{item.name}}</text>
						
						<circle :cx="calPosition(item).cirLine[0]" :cy="calPosition(item).cirLine[1]" r="4"  stroke-width="0"  :class="item.onlineStatus == true? 'cir-true' : 'cir-false'"/>
						<circle :cx="calPosition(item).cirVer[0]" :cy="calPosition(item).cirVer[1]" r="4"  stroke-width="0"  :class="item.verificaStatus == true? 'cir-true' : 'cir-false'"/>
					</g>
					
					
					<!--<path @click="test()" d = "M415 75,L75 140" style="stroke: #0000cc; stroke-width: 2px;fill : #ccccff;" marker-end="url(#idArrow)"></path>-->
					<path @click="choosePath" class="linkPath"  v-for="(item,index) in pathAttrArr" :id="item.start+'_'+item.end" :data-start="item.start" :data-end="item.end" :d = "item.d" style="stroke: #41a7bb; stroke-width: 2px;fill : #41a7bb;" marker-end="url(#idArrow)"></path>
					<text class="pathText" :rotate="item.dir"  v-for="(item,index) in pathAttrArr"    text-anchor="middle" style="font-size:10pt;">
						<textPath :xlink:href="'#'+item.start+'_'+item.end" startOffset="50%" >
							<tspan >{{item.text}}</tspan>
							<input type="text" value="dfdfdd" />
						</textPath>
					</text>
					
					
					<!--箭头标志-->
					<marker id="idArrow"
	         			viewBox="0 0 20 20" refX="0" refY="10"
	         			markerUnits="strokeWidth" markerWidth="5" markerHeight="18" orient="auto">
	         			<path d="M 0 0 L 20 10 L 0 20 z" fill="#41a7bb" stroke="#41a7bb"/>
	     			</marker>
					
				</svg>
			</div>
			<!--情景状态切换-->
			<van-dropdown-menu>
			  <van-dropdown-item v-model="sceneVal" :options="sceneOption" @change="sceneChange" />
			</van-dropdown-menu>
			
			<!--保存按钮-->
			<div v-if="svgStatus == 1" class="btn_group" >
				<!--<van-button type="primary" plain  size="large" @click="saveData">保存</van-button>-->
				<!--<a class="save_btn" @click="saveData">保存  <i class="iconfont">&#xe609;</i></a>-->
				<van-button type="default" block>刷新<i class="iconfont"></i></van-button>
				<van-button type="default" @click="addNode" block>新增节点</van-button>
				<van-button type="default" block @click="saveData">保存</van-button>
			
			</div>
			<!--连接线输入框-->
			<div v-else-if="svgStatus == 0" class="input-div">
				<van-field id="pathtext" type="digit" ref="pathText" size="large" autofocus  v-model="pathText" label="" border clearable />
			</div>
			<!--节点输入框-->
			<div v-else-if="svgStatus == 2" class="input-div">
				<van-field id="nodetext"  ref="nodeText" size="large" autofocus  v-model="nodeText" label="" border clearable />
			</div>
			
			<!--连线菜单-->
			<div v-show="isMenuShow" id="menuDiv" class="menus" :key="menuKey"  :style="'top:'+ alertDia.y + 'px; left:'+ alertDia.x +'px;'">
		        <div  id="childLevel" @click="upPathText" class="circle circle1"><i class="iconfont">&#xe60b;</i></div>  
		        <div  id="delLevel" @click="delPath" class="circle circle2"><i class="iconfont">&#xe655;</i></div>
		        <div  id="textLevel" @click="upPathText"  class="circle circle3"><i class="iconfont">&#xe699;</i></div>
			</div>
			
			<!--节点菜单-->
			<transition name="van-fade" >
			  <div id="nodeMenu" class="nodeMenu" v-show="nodeMenuShow" :style="'top:'+ alertNode.y + 'px; left:'+ alertNode.x +'px;'">
			  	<!--当前节点状态-->
			  	<div class="nodeStatus">  
			  		<!--验证状态-->
			  		<div class="verSta ">
			  			<label> 是否验证:</label>
			  			<label v-if="chooseNodeData.verificaStatus == true" class="lab-true"></label>
			  			<label v-else class="lab-false"></label>
			  		</div>
			  		<!-- 上线状态 -->
			  		<div class="lineSta">
			  			<label> 是否在线:</label>
			  			<label v-if="chooseNodeData.onlineStatus == true" class="lab-true"></label>
			  			<label v-else class="lab-false"></label>
			  		</div>
			  	</div>
			  	<!--操作按钮-->
			  	<div class="nodeOper">
			  		<div class="operDiv">
			  			<span id="" @click="updateNodeData">修改</span>
				  		<span id="" @click="delNodeData">删除</span>
				  		<span id="" @click="createPathLine">连接</span>
				  		<span id="" @click="verifyNodePop">验证</span>
				  		<span v-if="verifyDef(chooseNodeData.type) " id="" @click="defaultNodePop">缺省值</span>
			  		</div>
			  	</div>
			  </div>
			</transition>
			
			<!--pupup  缺省值设置-->
			<van-popup v-model="defaultShow"  :style="{ height: '37%',width:'85%'}" >
				<div  class="defInputDiv">
					<!--空调-->
					<div v-if="chooseNodeData.type == 'ktyk'" class="kt_default nodeDefault">
						<van-field name="defaultData.wd" label="模式">
						  <template #input>
						    <van-radio-group v-model="chooseNodeData.default.ktms" >
						      <van-radio name="1" style="padding:0.1rem 0.05rem">制冷</van-radio>
						      <van-radio name="2" style="padding:0.1rem 0.05rem">制热</van-radio>
						      <van-radio name="3" style="padding:0.1rem 0.05rem">除湿</van-radio>
						    </van-radio-group>
						  </template>
						</van-field>
						<van-field  name="stepper"  label="温度" >
							<template #input>
							    <van-stepper name="温度" v-model="chooseNodeData.default.ktwd" step="2" />
							</template>
						</van-field>
					</div>
					<!--窗帘-->
					<div v-if="chooseNodeData.type == 'clyk'" class="cl_default nodeDefault">
						<van-field name="defaultData.wd" label="开合状态">
						  <template #input>
						    <van-radio-group v-model="chooseNodeData.default.clkhzt" >
						      <van-radio name="1" style="padding:0.1rem 0.05rem">半开</van-radio>
						      <van-radio name="2" style="padding:0.1rem 0.05rem">全开</van-radio>
						      <van-radio name="3" style="padding:0.1rem 0.05rem">全关</van-radio>
						    </van-radio-group>
						  </template>
						</van-field>
						
					</div>
					<!--热水器-->
					<div v-if="chooseNodeData.type == 'rsqyk'" class="rsq_default nodeDefault">
						<van-field  name="stepper"  label="温度" >
							<template #input>
							    <van-stepper  v-model="chooseNodeData.default.rsqwd" step="25" />
							</template>
						</van-field>
						
					</div>
					<!--音响-->
					<div v-if="chooseNodeData.type == 'yxyk'" class="rsq_default nodeDefault">
						<van-field  name="stepper"  label="音量">
							<template #input>
							    <van-stepper v-model="chooseNodeData.default.yxyl" step="2" />
							</template>
						</van-field>
					</div>
					<!--电视-->
					<div v-if="chooseNodeData.type == 'dsyk'" class="ds_default nodeDefault">
						<van-field name="chooseNodeData.default.dsxhy" label="信号源">
						  <template #input>
						    <van-radio-group v-model="chooseNodeData.default.dsxhy" >
						      <van-radio name="1" style="padding:0.1rem 0.05rem">HDMI</van-radio>
						      <van-radio name="2" style="padding:0.1rem 0.05rem">VCD/DVD</van-radio>
						    </van-radio-group>
						  </template>
						</van-field>
						<van-field  name="stepper"  label="频道值">
							<template #input>
							    <van-stepper v-model="chooseNodeData.default.dspdz" step="2" />
							</template>
						</van-field>
					</div>
				</div>
				<div class="defBtnDiv">
					<van-button round type="info" block>确定</van-button>
				</div>
			</van-popup>
			
			<!--pupup  新增元素节点-->
			<van-popup v-model="popupShow" position="bottom" :style="{ height: '45%' }" >
				<!--<van-divider  content-position="left"
				  :style="{  color: '#1989fa', borderColor: '#1989fa' ,padding:'0 0.3rem', margin:'0.2rem'}"
				>
				  选择电器种类
				</van-divider>-->
				<div class="elecNodes">
					 <van-col class="elecCol" v-for="(item,index) in elecList" @click="addElc(index)" data-lx="item.type" :key="index" span="6">
					 	<i class="iconfont" v-html="item.icon">{{item.icon}}</i>
					 	<label>{{item.name}}</label>
					 </van-col>
				</div>
			</van-popup>
			
			<!--遥控类型弹出层-->
			<van-popup v-model="ykTypeShow"  :style="{ height: '40%',width:'80%' }" >
				<van-picker
				  title="类型"
				  show-toolbar
				  :columns="ykTypesCol"
				  @confirm="ykTypeConfirm"
				/>
			</van-popup>
			
			<!--节点验证弹窗-->
			<van-popup v-model="verifyPopup" position="bottom" :style="{ height: '35%' }" >
				<van-tabs >
				  <van-tab title="自动验证">
				  	<div class="verifyAuto_div">
				  		<van-button round block :loading="handling==0 ? false : (handling==1?true:false)" :icon="handling==2 ? handlingIcon :''" :loading-text="handling ? '验证中...' : ''"   type="info" @click="verifyNodeAuto">{{handlingText}}</van-button>
				  	</div>
				  </van-tab>
				  <van-tab title="自定义验证">
				  	<van-form >
					  <van-field
					    v-model="customVerify"
					    name="节点ID"
					    label="节点ID"
					    placeholder="节点ID"
					    :rules="[{ required: false, message: '请填写ID' }]"
					  />
					  
					  <div style="margin: 16px;">
					    <van-button round block type="info" native-type="submit">
					      提交
					    </van-button>
					  </div>
					</van-form>
				  </van-tab>
				</van-tabs>
			</van-popup>
	</div>
</template>

<script>
	var nAdd;
	import {Notify, Tab, Tabs} from 'vant';

	export default{
		data(){
			return{
				svgViewBox:"0 0 600 345",
				curent:0,
				iData:{},
				gStatus:0,
				preClick:"",
				clicked:1  //点击状态 用于区分单击以及双击
				,time:null  //用于记录点击时间间隔
				,chooseNode:{}  //选中的连线节点   {from:"", to:"" , text:""}
				,chooseNodeData:{}//选中的节点数据 
				,pathArr:[[4,2],[6,3]]
				,pathAttrArr:[]
				,preClientX:0	//上一次clicentx
				,preClientY:0   //上一次clienty
				,propX:1	//x轴拖动比例
				,propY:1  //y轴拖动比例
				,svgView:[]
				,alertDia:{}   //path点击菜单属性    x y
				,isMenuShow:false   //菜单是否显示
				,menuKey:1   
				,nowChoose:""   //当前选中的节点元素
				,pathText:1		//path文本
				,nodeText:""//node文本
				,svgStatus:1  //当前状态  0修改状态   1绘制状态   2
				,popupShow:false   //新增元素节点 popup是否显示
				,elecList:[]       //电器列表
				,targetNodes:[]      //拖动时的节点列表   img text circle
				
				,nodeMenuShow:false  //节点菜单是否显示
				,alertNode:{}   //节点菜单属性  
				,verifyPopup:false  //验证菜单是否显示
				,handling:0   //当前验证按钮状态    0未操作  1验证中  2验证通过  3验证失败
				,handlingText:"点击验证"   //当前验证按钮文本
				,handlingIcon:"checked"
				,customVerify:""     //自定义验证输入框
				,websocketPath:"ws://121.40.165.18:8800"    //websocket链接
				,ykTypeShow:false   //遥控类型弹出层
				,ykTypesCol:["电视","音响","空调","热水器","窗帘"]
				,ykTypeText:""
				,defaultShow:false
				,hasDefault:["rsqyk","dsyk","yxyk","ktyk","clyk"]  //遥控器列表
				,sceneOption:[
			        { text: '迎宾', value: 0 },
			        { text: '遥控', value: 1 },
			        { text: '场景', value: 2 },
			        { text: '退房', value: 3 }
			    ]
				,sceneVal:0
				
			}
		},	
		created(){
			this.getNodeList();//获取默认节点数据
			this.getData();   //获取svg连线数据
			
		},
		watch:{
			alertDia(){
				++this.menuKey;
			},
			iData:{
				handler(){},
				deep:true
			}
		},
		mounted(){
			//屏蔽手机端长按出现右键菜单
			document.getElementsByTagName("body")[0].addEventListener('contextmenu', function(e){
				e.preventDefault();
			});
			//计算拖动移动比例
			let svg = document.getElementById("svgDom");
			let windowW =  window.document.body.offsetWidth ;
			let windowH = window.document.body.offsetHeight;
			this.svgViewBox = "0 0 "+windowW +" "+windowH;
			this.svgView= this.svgViewBox.split(" ");
			this.propX = Number((this.svgView[2] / windowW).toFixed(2));
			this.propY = Number((this.svgView[3] / windowH).toFixed(2));
			
		},
		methods:{
			
			sceneChange(){			//使用情景切换
				console.log(this.sceneVal)
			},
			iconTranscod(type){    //根据类型获取对应图标
				let iconUnicode;
				this.elecList.forEach(function(item,index){
					if(type == item.type){iconUnicode = item.icon}
				})
				return iconUnicode;
			},
			verifyDef(type){   //验证是否显示  缺省值按钮
				if(this.hasDefault.indexOf(type)>-1){
					return true;
				}else{
					return false;
				}
			},
			
			defaultNodePop(){   //缺省值弹窗
				this.defaultShow = true;
			},
			verifyNodeAuto(){   //自动验证
				this.handling = 1;
				
				this.init();
				setTimeout(() =>{
					this.handling = 2;
					this.handlingText = '验证成功';
					this.handlingIcon = "checked"
				},3000)
			},
			verifyNodePop(){			//验证节点数据
				this.verifyPopup = true;
			},
			delNodeData(){		//节点删除
				let thisId = this.chooseNodeData.id;
				this.iData.node.forEach((item,index)=>{
					if(item.id == thisId){this.$delete(this.iData.node,index)}
				})
			},
			updateNodeData(){          //修改节点文本
				this.svgStatus = 2;
				this.$nextTick(()=>{
					this.$refs.nodeText.focus();
				});
				console.log(this.chooseNodeData)
				this.nodeText  = this.chooseNodeData.name;
				
			},
			ykTypeConfirm(value){
				this.ykTypeText = value;
				this.chooseNodeData["name"] = value;
				this.ykTypeShow = false;
				console.log(this.chooseNodeData)
			},
			addElc(index){     //新增节点元素    添加模板
				//获取svg画布当前中心点
				let svgBox = this.svgViewBox.split(" ");  
				svgBox = svgBox.map(Number);
				let cenX = svgBox[0] + svgBox[2] / 2;
				let cenY = svgBox[1] + svgBox[3] / 2;
				let nowElc = this.elecList[index];
				
				let thisId = this.timestampFun();
				this.chooseNodeData ={
						"id": thisId,
						"name": nowElc.name,
						"type": nowElc.type,
						"x": cenX,
						"y": cenY,
						"catalog": nowElc.catalog,
						"group":"",
						"connection":true,
						"onlineStatus": false,
						"verificaStatus": false,
						"default":{
							"ktwd":"",
							"ktms":"",
							"yxyl":"",
							"dsxhy":"",
							"dspdz":"",
							"clkhzt":"2",
							"rsqwd":"34"
						}
						
				}
				this.iData.node.push(this.chooseNodeData)//向数据源中添加数据
				this.popupShow = false;
				console.log(this.iData)
			},
			timestampFun() {       //获得时间戳
				console.log((new Date()).valueOf())
		        return (new Date()).valueOf();
		    },
			calPosition(item){          //计算图片  文本   圆心的位置
				return {
					imgPos:[item.x,item.y],
					textPos:[Number(item.x) + 20, Number(item.y) + 45],
					cirLine:[Number(item.x) + 50,item.y],
					cirVer:[Number(item.x) + 50 , Number(item.y) + 15]
				}
			},
			fullUrl(itemName){    //设定图片路径
				return `require(@/assets/img/${itemName}.png)`;
			},
			
			getNodeList(){      //获取电器节点列表
				this.$axios.get('data/elec.json',{       // 还可以直接把参数拼接在url后边
			    }).then((res)=>{
			       this.elecList = res.data;
			       console.log(this.elecList)
			    }).catch(function (error) {
			        console.log(error);
			    });
			},
			addNode(){					//新增节点
				this.popupShow = true; 
			},
			delPath($event){  	//连线删除方法
				this.$dialog.confirm({
				  title: '',
				  message: '确定删除关联吗',
				})
				.then(() => {
				    // on confirm
				    let index = this.pathAttrArr.findIndex(item =>{
			            if(item.id == this.nowChoose){
			               return true;
			            }
			        })
			        this.pathAttrArr.splice(index, 1)
			    })
				.catch(() => {
				    // on cancel
				    
				});
				this.isMenuShow = false;
			},
			upPathText(){    /*线条文本修改事件*/
				this.svgStatus = 0;
				this.$nextTick(()=>{
					this.$refs.pathText.focus();
				})
				//循环获取
				let index = this.pathAttrArr.findIndex(item =>{
			        if(item.id == this.nowChoose){
			           this.pathText = item.text;
			        }
			    })
				console.log(this.svgStatus)
			},
			choosePath($event){    /*线条点击事件*/
				this.isMenuShow = true;
				this.$set(this.alertDia,'x',$event.x);//触发更新
				this.$set(this.alertDia,'y',$event.y);//触发更新
				//保存选中节点
				this.nowChoose = $event.target.getAttribute("id");
			},
			getData(){
				this.$axios.get('data/indexNew.json',{       // 还可以直接把参数拼接在url后边
			   }).then((res)=>{
			        this.iData = res.data;
			        let Path = this.iData.relation;
			        Path.forEach((val,index)=>{
			        	setTimeout(()=>{
			        		this.createPathData(val)
			        	},200)
			        	
			        });

			    }).catch(function (error) {
			        console.log(error);
			    });
			},
			saveData(){    //保存数据
				let saveData = {"node":[],"relation":[]};
				let allG = document.getElementsByTagName("g");
				saveData["node"] = this.iData.node;
				this.pathAttrArr.forEach(item=>{
					saveData["relation"].push({"from":item.start,"to":item.end,"val":item.text})
				})
				console.log(JSON.stringify(saveData))
				this.$axios.post('http://127.0.0.1:8080/potology',JSON.stringify(saveData)).then((res)=>{
			        if(res.status == true){
			        	Notify({ type: 'success', message: '保存成功' });
			        }else{
			        	Notify({ type: 'danger', message: '保存失败  请重新尝试' });
			        }
			    }).catch(function (error) {
			        Notify({ type: 'danger', message: '保存失败  请重新尝试' });
			    });
			},
			dragStartSvg(){
				console.log("啦啦啦")
			},
			dragStart(e){   //触摸屏幕
				this.targetNodes =  this.getSiblings(e.target);   //储存预拖动的元素集合  img text circle
				/*判断当前菜单    连线菜单  and 节点菜单*/
				if(this.isMenuShow == true){
					this.isMenuShow = false;
				}
				if(this.nodeMenuShow = true){
					this.nodeMenuShow = false;
				}
				
				if(this.svgStatus == 0){   //判断当前显示状态   0  path文本输入状态
					this.pathAttrArr.findIndex(item =>{
			            if(item.id == this.nowChoose){
			               item.text = this.pathText;
			               this.svgStatus = 1; //切换显示状态
			            }
			        })
				}else if(this.svgStatus == 2){  //判断当前显示状态   2 节点文本输入状态
					let idataN = this.iData.node;
					idataN.findIndex(item =>{
						if(item.id == this.nowChoose){
							item.name = this.nodeText;
							this.svgStatus = 1; //切换显示状态
						}
					})
				}
				//记录手指落点坐标
				this.preClientX = e.changedTouches[0].clientX;
				this.preClientY = e.changedTouches[0].clientY;
			},
			dragSvg(e){    //画图拖动
				let eTarget = e.changedTouches;
//				alert(eTarget.length)
				if(eTarget.length == 1){   //单指操作      滑动
					//计算拖动偏移差
					let devX = Number(eTarget[0].clientX - this.preClientX) / this.propX;
					let devY = Number(eTarget[0].clientY - this.preClientY) / this.propY;
					this.svgView = this.svgViewBox.split(" ");
					this.svgViewBox = `${Number(this.svgView[0]) - devX} ${Number(this.svgView[1]) - devY} ${Number(this.svgView[2])} ${Number(this.svgView[3])}`;
					this.preClientX = eTarget[0].clientX;
					this.preClientY = eTarget[0].clientY;
				}else if(eTarget.length == 2){   //双指操作     缩放
					
				}
				
			},
			dragDom(e,id){   //元素节点拖动
				console.log(e.target.dataset.type)
				if(e.target.dataset.type == "text"){   //如果目标节点时text则返回false
					return false;
				}
				let eTarget = e.changedTouches[0];
				//计算拖动偏移差
				let devX = Number(eTarget.clientX - this.preClientX) / this.propX;
				let devY = Number(eTarget.clientY - this.preClientY) /this.propY;
				//获取当前图片节点x和y属性
				let attrX = Number(eTarget.target.getAttribute("x"));
				let attrY = Number(eTarget.target.getAttribute("y"));
				this.targetNodes[0].setAttribute("x",(attrX + devX));
				this.targetNodes[0].setAttribute("y",(attrY + devY));
				this.targetNodes[1].setAttribute("x",(Number(this.targetNodes[1].getAttribute("x")) + devX));
				this.targetNodes[1].setAttribute("y",(Number(this.targetNodes[1].getAttribute("y")) + devY));
				this.targetNodes[2].setAttribute("cx",(Number(this.targetNodes[2].getAttribute("cx")) + devX));
				this.targetNodes[2].setAttribute("cy",(Number(this.targetNodes[2].getAttribute("cy")) + devY));
				this.targetNodes[3].setAttribute("cx",(Number(this.targetNodes[3].getAttribute("cx")) + devX));
				this.targetNodes[3].setAttribute("cy",(Number(this.targetNodes[3].getAttribute("cy")) + devY));
				this.preClientX = eTarget.clientX;
				this.preClientY = eTarget.clientY;
				this.getRelevantPath(id);
			},
			getSiblings(target){            //获取当前目标所有同级元素
				let parNode = target.parentNode;
				let lisg = parNode.childNodes;
				return lisg;
			},
			addClass($event,index){    //元素点击事件
				
				/*计算菜单弹窗相对位置*/
				//let menuW = document.getElementById("nodeMenu").clientWidth
				this.$set(this.alertNode,'x',$event.x - 120);//触发更新
				this.$set(this.alertNode,'y',$event.y - 90);//触发更新
				/*存储当前点击元素*/
				this.nowChoose = index.id;  
				this.chooseNodeData = index;
				/*判断当前nodechoose对象状态     第二次选择完成连线*/
				if(Object.keys(this.chooseNode) != 0){
					this.chooseNode["to"] = index.id;
					this.createPathData(this.chooseNode);  //绘制连接线
					for(let key in this.chooseNode){
					    delete this.chooseNode[key];
					}  //数组置空  等待下一次操作
					Notify.clear();  
				}else{
					this.nodeMenuShow = true;
				}
				
				this.curent = index.id; //存入当前点击的元素id
				index == this.preClick;
			},
			createPathLine(){
				Notify({ type: 'primary', message: '请选择想要链接的节点' ,duration:'0'});
				this.nodeMenuShow = false;
				this.chooseNode["from"] = this.nowChoose;
			},
			getRelevantPath(id){   //获取相关path
				this.pathAttrArr.forEach((val,index) => {
					if(val["start"] == id || val["end"] == id){
						val["d"] = this.createPathD({"from":val["start"],"to":val["end"]});
						val["dir"] = this.judgeDir({"from":val["start"],"to":val["end"]});
					}
				})
			},
			
			createPathData(linkA){   //绘制连接线
				this.pathAttrArr.push({id:linkA.from+"_"+linkA.to,d: this.createPathD(linkA), start:linkA.from , end:linkA.to , dir:this.judgeDir(linkA) , text:linkA.val});
			},
			createPathD(linkA){    //获得连接线  return M
				let h = 40;
				let startX = Number(document.getElementById(linkA.from).getElementsByTagName("text")[0].getAttribute("x")) + h/2;
				let startY = Number(document.getElementById(linkA.from).getElementsByTagName("text")[0].getAttribute("y")) + h/2;
				let endX = Number(document.getElementById(linkA.to).getElementsByTagName("text")[0].getAttribute("x")) + h/2;
				let endY = Number(document.getElementById(linkA.to).getElementsByTagName("text")[0].getAttribute("y")) + h/2;
				//几何临边长度
				let limb = Math.abs(endX - startX);
				let opposite = Math.abs(endY - startY); //几何对边长度
				/*求角度*/
				let angel = 180*(Math.atan2(opposite,limb))/Math.PI;     
				let sOpposite = Number(Math.abs((Math.sin(Math.PI*angel/180) * Math.sqrt(2500)).toFixed(2))); //小临边长度
				let sLimb  = Number(Math.abs((Math.cos(Math.PI*angel/180) * Math.sqrt(2500)).toFixed(2))); //小对边长度
				if(startX > endX){   //初始点大于结束点      x
					startX = startX  - sLimb; 
					endX = endX + sLimb;
				}else{
					startX = startX + sLimb;
					endX = endX - sLimb;
				}
				if(startY > endY){		//初始节点大于结束点  y
					startY = startY - sOpposite;
					endY = endY + sOpposite;
				}else{
					startY = startY + sOpposite;
					endY = endY - sOpposite;
				}
				return "M"+startX+" "+startY +","+endX+" "+ endY;
			},
			judgeDir(linkA){    //判断textpath文本方向   return 0 || 180
				let startX = Number(document.getElementById(linkA.from).getElementsByTagName("text")[0].getAttribute("x"));
				let endX = Number(document.getElementById(linkA.to).getElementsByTagName("text")[0].getAttribute("x"));
				let dir =  startX < endX ? 0 :  180;
				return dir;
			}
			,getSinDataById(id){       //根据数据id获取当前节点数据对象  
				
			},
			/*websocket部分*/
			init: function () {   //初始化链接
	            if(typeof(WebSocket) === "undefined"){
	                alert("您的浏览器不支持socket")
	            }else{
	                // 实例化socket
	                this.socket = new WebSocket(this.websocketPath)
	                
	                // 监听socket连接
	                this.socket.onopen = this.open
	                // 监听socket错误信息
	                this.socket.onerror = this.error
	                // 监听socket消息
	                this.socket.onmessage = this.getMessage
	            }
	        },
	        open: function () {
	            console.log("socket连接成功")
	        },
	        error: function () {
	            console.log("连接错误")
	        },
	        getMessage: function (msg) {
	            console.log(msg.data)
	            
	        },
	        send: function () {
	            this.socket.send(params)
	        },
	        close: function () {
	            console.log("socket已经关闭")
	        }		
		}
	}
</script>

<style>
	* {
	-webkit-touch-callout:none;
	-webkit-user-select:none;
	-khtml-user-select:none;
	-moz-user-select:none;
	-ms-user-select:none;
	user-select:none;
	}
	/*svgDIv*/
	.svgDiv{
		height: 100%; width: 100%; position: fixed;
	}
	#svgDom{background: #f2fbff;}
	.contents{height: 100%;width: 100%;}
	text{fill: #808080;}
	.nameText{font-size: 0.35em; text-anchor: middle; dominant-baseline:text-before-edge}
	.iconText{font-size: 1rem !important; dominant-baseline: text-before-edge;text-anchor: inherit;}
	.selectG text{fill:#41a7bb }
	text{  
		 -webkit-user-select:none;
	    -moz-user-select:none;
	    -ms-user-select:none;
	    user-select:none;
	    color: darkgray;
	 }
    
    .btn_group{
    	height: 3rem;
    	width: calc(100% - 1rem);
    	padding: 0 0.5rem;
    	position: absolute;
    	bottom: 0;
    	/*background-color: dodgerblue;*/
    	display: flex;
    	/*align-items: center;*/
    	justify-content: center;
    }
    .btn_group button{
    	box-shadow: 0 0 6px 0px rgb(193 188 188 / 71%);
    }
    .save_btn{
    	font-size: 0.8rem;
    	color: white;
    	display: flex;
    	align-items: center;
    }
    .save_btn i{font-size: 1.3rem;padding: 0 0.4rem;}
    
    /*节点状态*/
    .cir-true{fill: rgb(50, 223, 114);}
    .cir-false{ fill: #9e9e9e;}
    .lab-true{color: rgb(50, 223, 114);}
    .lab-false{color: #9E9E9E;}
    /*输入框*/
    .input-div{height: auto; width: 100%; position: fixed; bottom: 0; z-index: 999; background-color: orange}
    #pathtext{border-bottom: 1px solid #c7c7c7;}
    #nodetext{border-bottom: 1px solid #C7C7C7;}
		
		/*电器列表*/
		.elecNodes{
			font-size: 0.35rem;padding: 0.2rem 0.3rem;
		}
		.elecCol{border:1px solid #f9f4f4;padding: 0.2rem 0.1rem; display: flex;align-items: center; justify-content: center; flex-direction: column;}
		.elecCol i{font-size: 0.7rem; color: #b9b9b9;}
		.elecCol label{color: #807d7d; padding-top: 0.2rem;}
		.ifShowElc{display: none;}
	
	/*缺省值设置*/
	.defaultPop{width: 85%; display: flex; align-items: center;}
	.defBtnDiv{padding: 0.2rem;}
	
	/*节点菜单*/
	.nodeMenu{  position: fixed; top: 3rem; left: 1rem; min-width: 6.5rem;}	
	.nodeStatus{display: flex; align-items: center; justify-content: space-around; height: 0.5rem; margin-bottom: 0.1rem; background-color: white;box-shadow: 0 0 4px 1px #e2e2e2;}
	.verSta,.lineSta{display: flex; align-items: center;}
	.nodeStatus label{font-size: 0.35rem; padding: 0 0.1rem; }
	
	.nodeOper {height: 1rem; width: 100%; background-color: white; box-shadow: 0 0 4px 1px #e2e2e2;}
	.nodeOper span{min-width:0.8rem;font-size: 0.35rem; color:#708090; display: block; padding: 0 0.3rem; border-right: 1px solid #9E9E9E;}
	.nodeOper span:last-child{border: none;}
	.operDiv{padding: 0 0.2rem; height: 100%;display: flex; align-items: center; justify-content: center; }
	/*验证按钮*/
	.verifyAuto_div{margin-top: 20%; padding: 0 0.5rem;}
	.van-tab__pane{text-align: center;}
		/*环形菜单*/
    .menus{
		position: fixed;
	}
	    .circle{
	        /* 这里一定要绝对定位,这样位置才能铺开来 */
	        position: absolute;
	        top:  -10px;
	        left: -10px;
	        height: 0;
	        width: 0;
	        line-height: 40px;
	        border-radius:  50%;
	        display: flex; 
	        align-items: center;
	        justify-content: center;
	        background-color: #fff;
	        color: black;
	        box-shadow: 0 0 3px rgba(234, 130, 6, 0.51);
	        opacity: 0;
	    }
	    .circle i{
	    	color: #828282;
	    	font-size: 0.5rem;
	    }
	    .circle1{
	        animation: dropdown1 ease 0.3s  1 both;
	    }
	    .circle1 i{
	    	transform: rotateZ(45deg);
	    }
	    .circle2{
	        animation: dropdown2 ease 0.3s  1 both;
	    }
	    .circle2 i{
	    	transform: rotateZ(90deg);
	    }
	    .circle3{
	        animation: dropdown3 ease 0.3s  1 both;
	    }
	    .circle3 i{
	    	transform: rotateZ(135deg);
	    }
	    .circle4{
	        animation: dropdown4 ease 0.3s  1 both;
	    }
	    .circle4 i{
	    	transform: rotateZ(150deg);
	    }
	    
		@keyframes dropdown1 {
		    0% { transform:rotateZ(0deg) translateY(0);}
		  100% { transform:rotateZ(-45deg) translateY(2rem); height:  1rem; width: 1rem; opacity: 1;}
		}
		@keyframes dropdown2 {
		    0% {transform:rotateZ(0deg) translateY(0);}
		  100% { transform:rotateZ(-90deg) translateY(2rem);height:  1rem; width: 1rem;opacity: 1;}
		}
		@keyframes dropdown3 {
		    0% { transform:rotateZ(0deg) translateY(0);}
		  100% { transform:rotateZ(-135deg) translateY(2rem);height:  1rem; width: 1rem;opacity: 1;}
		}
		@keyframes dropdown4 {
		    0% { transform:rotateZ(0deg) translateY(0);}
		  100% { transform:rotateZ(-150deg) translateY(2rem);height:  2rem; width: 2rem;opacity: 1;}
		}
</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值