2020软件工程第二次结对作业

这个作业属于哪个课程
https://edu.cnblogs.com/campus/fzu/SE2020
这个作业要求在哪里
https://edu.cnblogs.com/campus/fzu/SE2020/homework/11277
这个作业的目标
JavaScript、前端学习、单元测试编写、mocha工具使用
学号
031802302(陈凯强)、031802303(陈龙辉)

结对同学的博客链接、本作业博客链接、GitHub项目地址

标题 链接
结对同学博客链接
https://www.cnblogs.com/m1n-/p/13802182.html
本作业博客的链接
https://www.cnblogs.com/fzuckq/p/13802196.html
创建仓库项目地址
https://github.com/m1nme/031802302-031802303

具体分工

学号姓名
任务
031802302陈凯强
界面设计,单元测试
031802303陈龙辉
代码编写,算法实现

PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 30 60
Estimate 估计这个任务需要多少时间 180 180
Development 开发 600 800
Analysis 需求分析 (包括学习新技术) 120 180
Design Spec 生成设计文档 20 20
Design Review 设计复审 20 30
Coding Standard 代码规范 (为目前的开发制定合适的规范) 10 15
Design 具体设计 30 60
Coding 具体编码 520 680
Code Review 代码复审 30 30
Test 测试(自我测试,修改代码,提交修改) 120 480
Reporting 报告 10 20
Test Repor 测试报告 10 20
Size Measurement 计算工作量 10 15
Postmortem & Process Improvement Plan 事后总结, 并提出过程改进计划 30 30
合计 1740 2620

解题思路描述与设计实现说明

关键实现的流程图

代码实现思路文字描述以及代码展示

  1. 解析输入数据转换成JSON

    1.0 定义ROOT = {};用于存储提取出来的JSON

    1.1 通过两个空行分割每一组数据,从每一组数据的第一行提取出导师名字,完成后:

    ROOT = {
    		'导师1':{},
    		'导师2':{}
             }
    

    1.2 遍历每一组数据,利用换行符来分割每一行数据,通过'级博士生'、'级硕士生'、'级本科生'等关键词来提取每一个导师所带过的年级,完成后:

    ROOT = {
    		'导师1':{'xxxx级博士生':{},'xxxx级本科生':{}},
    		'导师2':{'xxxx级博士生':{},'xxxx级硕士生':{}}
             }
    

    1.3 遍历每一组年级数据,利用关键字'、'来分割每一位学生,完成后:

    ROOT = {
    		'导师1':{'xxxx级博士生':{'xxx':"",'xxx':"",'xx':""},'xxxx级本科生':{'xx':"",'xx':""}},
    		'导师2':{'xxxx级博士生':{'xxx':"",'xxx':"",'xx':""},'xxxx级硕士生':{'xx':"",'xx':""}}
            }
    

    1.4 不包含'导师:'、'级xx生'等关键词的数据,通过关键词':'来提取技能树或者任职经历,完成后:

    ROOT = {
    		'导师1':{
                          'xxxx级博士生':{'xxx':"京东云、阿里云、腾讯云",'xxx':"",'xx':""},
    			 'xxxx级本科生':{'xx':"",'xx':""}
    			 },
    		'导师2':{
                           'xxxx级博士生':{'xxx':"",'xxx':"",'xx':"字节跳动、拼多多"},
    			  'xxxx级硕士生':{'xx':"",'xx':""}
    			  }
    	    }
    

    代码展示

    function analyse(){
    	var text = document.getElementById("text").value;
    	var root = text.split(/\n\n\n/);
    	$(".button").show();
    	for(var i in root){
    		var leader = root[i].split(/\n/);
    		var teacher = leader[0].substring(3);
    		ROOT[teacher] = {};
    		var grades = [];
    		var re1=/[0-9]+\u7ea7\u672c\u79d1\u751f/;
    		var re2=/[0-9]+\u7ea7\u535a\u58eb\u751f/;
    		var re3=/[0-9]+\u7ea7\u7855\u58eb\u751f/;
    		var re4 = /\u5bfc\u5e08/;
    		for(var k in leader){
    			if(re1.exec(leader[k])){
    				grades.push(re1.exec(leader[k]));
    				ROOT[teacher][re1.exec(leader[k])] = {};
    				for(var q in leader[k].substring(9).split(/\、/)){
    					ROOT[teacher][re1.exec(leader[k])][leader[k].substring(9).split(/\、/)[q]] = "";
    				}
    			}
    			if(re2.exec(leader[k])){
    				grades.push(re2.exec(leader[k]));
    				ROOT[teacher][re2.exec(leader[k])] = {};
    				for(var q in leader[k].substring(9).split(/\、/)){
    					ROOT[teacher][re2.exec(leader[k])][leader[k].substring(9).split(/\、/)[q]] = "";
    				}
    			}
    			if(re3.exec(leader[k])){
    				grades.push(re3.exec(leader[k]));
    				ROOT[teacher][re3.exec(leader[k])] = {};
    				for(var q in leader[k].substring(9).split(/\、/)){
    					ROOT[teacher][re3.exec(leader[k])][leader[k].substring(9).split(/\、/)[q]] = "";
    				}
    			}
    			if(!re1.exec(leader[k]) && !re2.exec(leader[k]) && !re3.exec(leader[k]) && !re4.exec(leader[k]) && leader[k]!=''){
    				for(var w in ROOT[teacher]){
    					for(var e in ROOT[teacher][w]){
    						if(e == leader[k].split(/\:/)[0]){
    							ROOT[teacher][w][e] = leader[k].split(/\:/)[1];
    						}
    					}
    				}
    			}
    		}
    	}
    	// console.log(ROOT);
    }
    
  2. 通过JSON来绘制师门树图

    2.1 根据遍历ROOT的节点来画出导师节点

    2.2 根据遍历ROOT['导师']的节点来画出每一位导师所带过的年级节点

    2.3 根据遍历ROOT['导师']['年级']的节点来画出每一个年级的同学节点

    2.4 根据遍历所有同学节点,将同一个同学连接在一起形成关联树

    代码展示

    function createTree(){
    	var paras = document.getElementsByClassName('index');
    	while(paras[0]){
    	    paras[0].parentNode.removeChild(paras[0]);
    	}
    	
    	var svg = d3.select('body').append('svg').attr('width',window.innerWidth).attr('height',window.innerHeight);
    		
    	var g = svg.append('g');
    	var i = 1;
    	for(var q in ROOT){
    		var num1 = Object.keys(ROOT).length;
    		var x1 = i*window.innerWidth/(num1+1);
    		var y1 = window.innerHeight/2;
    
    		var j = 0;
    		var rand1 = randomNum(0,90);
    		for(var w in ROOT[q]){
    			var num2 = Object.keys(ROOT[q]).length;
    			var angle = 360/(num2);
    			var x2 = x1 + 150*Math.cos((angle*j+rand1)*Math.PI/180);
    			var y2 = y1 + 150*Math.sin((angle*j+rand1)*Math.PI/180);
    
    			var k = 0;
    			var rand2 = randomNum(0,90);
    			for(var e in ROOT[q][w]){
    				var num3 = Object.keys(ROOT[q][w]).length;
    				var angle2 = 360/(num3);
    				var x3 = x2 + 50*Math.cos((angle2*k+rand2)*Math.PI/180);
    				var y3 = y2 + 50*Math.sin((angle2*k+rand2)*Math.PI/180);
    				createline(x2,y2,x3,y3,'red','1px',q+w,q);
    				
    				if(ROOT[q][w][e]!=''){
    					createcircle(x3,y3,30,e,'SkyBlue','15px',q+w,q,1);
    				}
    				else{
    					createcircle(x3,y3,30,e,'SkyBlue','15px',q+w,q,1);
    				}
    				k++;
    			}
    			createcircle(x2,y2,25,w,'Orchid','7px',q+q);
    			j++;
    		}
    		createcircle(x1,y1,50,q,'Gold','30px','root');
    		i++;
    	}
    	linksame();
    }
    

附加特点设计与展示

一、支持修改同学的技能树或者工作经历并保存

  1. 设计的创意独到之处,这个设计的意义

    方便在生成树之后再进行修改,不需要重新生成树

  2. 实现思路

    将每一个同学的技能树通过文本输入框显示,同时增加保存按钮,在修改之后点击保存按钮即可保存

  3. 重要的代码片段

    function detail(name){
    	NAME = name;
    	for(var q in ROOT){
    		for(var w in ROOT[q]){
    			for(var e in ROOT[q][w]){
    				if(e==name){
    					document.getElementById("detail").value = ROOT[q][w][e];
    				}
    			}
    		}
    	}
    	$("#abouts").fadeToggle("slow");
    }
    
    function initdetail(){
    	$(document).ready(function(){
      		$("#close").click(function(){
    			$("#abouts").fadeToggle("slow");
    		});
      		$("#save").click(function(){
    			var new_detail = document.getElementById("detail").value;
       			for(var q in ROOT){
    				for(var w in ROOT[q]){
    					for(var e in ROOT[q][w]){
    						if(e==NAME){
    							ROOT[q][w][e] = new_detail;
    						}
    					}
    				}
    			}
       			$("#abouts").fadeToggle("slow");
      		});
    	});
    }
    
  4. 实现成果展示

二、支持上传TXT文件作为文本输入

  1. 设计的创意独到之处,这个设计的意义

    方便用户直接通过文件导入,无需输入数据

  2. 实现思路

    设置一个文件上传点,上传文件后根据文件内容改变文本框的值,进行接下来的操作

  3. 重要的代码片段

    function fileUpload() {
        let file = document.getElementById('fileinp').files[0];
        let reader = new FileReader();
        reader.readAsText(file, 'utf-8');
        console.log(reader.result);
        reader.onload = function () {
            document.getElementById("text").value = reader.result;
            analyse();
        }
    }
    
  4. 实现成果展示

三、支持将生成的师门树导出为图片

  1. 设计的创意独到之处,这个设计的意义

    方便用户在其它场景使用生成的师门树,不局限于浏览器

  2. 实现思路

    将所绘制出的SVG图片下载到用户电脑

  3. 重要的代码片段

    function save(){
        var serializer = new XMLSerializer();
        var svg1 = document.querySelector('svg');
        var toExport = svg1.cloneNode(true);
        var bb = svg1.getBBox();
        toExport.setAttribute('viewBox', bb.x + ' ' + bb.y + ' ' + bb.width + ' ' + bb.height);
        toExport.setAttribute('width', bb.width);
        toExport.setAttribute('height', bb.height);
        var source = '<?xml version="1.0" standalone="no"?>\r\n' + serializer.serializeToString(toExport);
        var image = new Image;
        image.src = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(source);
        var canvas = document.createElement("canvas");
        canvas.width = bb.width;
        canvas.height = bb.height;
        var context = canvas.getContext("2d");
        context.fillStyle = '#fff';//#fff设置保存后的PNG 是白色的  
        context.fillRect(0, 0, 10000, 10000);
        image.onload = function(){  
    		context.drawImage(image, 0, 0);  
    		var a = document.createElement("a");  
    		a.download = "tree.png";  
    		a.href = canvas.toDataURL("image/png");
    		a.click();
    	}
    }
    
  4. 实现成果展示

四、树的绘制函数全为自主编写,因此风格独特并且支持多棵关联树并存

  1. 设计的创意独到之处,这个设计的意义

    页面设计比平常所见的模板更为独特,同时支持多棵关联树的存在,使得师门树更加清晰明了

  2. 实现思路

    页面设计的思路为,用不同颜色代表每个节点的所属关系,节点利用大小不同的圆绘制而成

    关联树的实现思路为,遍历所有屏幕前显示的学生节点,将同一位学生的多个节点用曲线连接起来,以此实现关联树

  3. 重要的代码片段

    一、树的绘制函数:

    function createTree(){
    	var paras = document.getElementsByClassName('index');
    	while(paras[0]){
    	    paras[0].parentNode.removeChild(paras[0]);
    	}
    
    
    	var svg = d3.select('body').append('svg').attr('width',window.innerWidth).attr('height',window.innerHeight);
    		
    	var g = svg.append('g');
    	var i = 1;
    	for(var q in ROOT){
    		var num1 = Object.keys(ROOT).length;
    		var x1 = i*window.innerWidth/(num1+1);
    		var y1 = window.innerHeight/2;
    
    		var j = 0;
    		var rand1 = randomNum(0,90);
    		for(var w in ROOT[q]){
    			var num2 = Object.keys(ROOT[q]).length;
    			var angle = 360/(num2);
    			var x2 = x1 + 150*Math.cos((angle*j+rand1)*Math.PI/180);
    			var y2 = y1 + 150*Math.sin((angle*j+rand1)*Math.PI/180);
    			//createline(x1,y1,x2,y2,'blue','3px',q+q);
    
    			var k = 0;
    			var rand2 = randomNum(0,90);
    			for(var e in ROOT[q][w]){
    				var num3 = Object.keys(ROOT[q][w]).length;
    				// console.log(num3);
    				var angle2 = 360/(num3);
    				var x3 = x2 + 50*Math.cos((angle2*k+rand2)*Math.PI/180);
    				var y3 = y2 + 50*Math.sin((angle2*k+rand2)*Math.PI/180);
    				// console.log(80*Math.cos(angle2*k*Math.PI/180)+"  "+80*Math.sin(angle2*k*Math.PI/180)+"  "+k);
    				createline(x2,y2,x3,y3,'red','1px',q+w,q);
    				
    				if(ROOT[q][w][e]!=''){
    					createcircle(x3,y3,30,e,'SkyBlue','15px',q+w,q,1);
    				}
    				else{
    					createcircle(x3,y3,30,e,'SkyBlue','15px',q+w,q,1);
    				}
    				k++;
    			}
    			createcircle(x2,y2,25,w,'Orchid','7px',q+q);
    			j++;
    		}
    		createcircle(x1,y1,50,q,'Gold','30px','root');
    		i++;
    	}
    	linksame();
    }
    

    二、多棵关联树并存的实现:

    function linksame(){
    	var svg = d3.select("svg");
    	var g = svg.append('g');
    	for(var i in ROOT){
    		for(var j in ROOT[i]){
    			for(var k in ROOT[i][j]){
    				var point = $("[view='"+k+"']");
    				var result = [];
    				for(var q=0;q<point.length;q++){
    					classs = point.eq(q).attr("class");
    					$('.'+classs).toggle();
    					$('.'+classs).toggle();
    					if($('.'+classs).attr("style")=="display: inline;"){
    						result.push(point.eq(q).attr("xy"));
    					}
    				}
    				point = result;
    				for(var a=0;a<point.length;a++){
    					for(var b=0;b<point.length;b++){
    						var x1 = point[a].split(/\,/)[0];
    						var y1 = point[a].split(/\,/)[1];
    						var x2 = point[b].split(/\,/)[0];
    						var y2 = point[b].split(/\,/)[1];
    						quxian(parseInt(x1),parseInt(y1),parseInt(x2),parseInt(y2));
    					}
    				}
    			}
    		}
    	}
    }
    
  4. 实现成果展示


目录说明和使用说明

目录说明

  ├── static
  │   ├── css	
  		  ├──index.css				//主CSS代码
  │   ├── images					//存放背景图片
  │   └── js 
  │   	  ├──d3.js					//d3库
  │   	  ├──index.js				//主JS代码
  │   	  └──jquery-1.11.0.min.js	//jquery库
  └── index.html					//入口文件

使用说明

  • 从github仓库内下载项目到本地,解压到文件夹,用chrome打开文件夹中www目录下的index.html即可运行。

测试数据格式要求(请务必严格按照格式输入)

导师:张三                  (冒号为中文冒号)(导师名字不要重复)
2016级博士生:天一、王二、吴五 (多个学生间以顿号间隔区分)
2015级硕士生:李四、王五、许六 (多行数据间以换行区分)
2016级硕士生:刘一、李二、李三
2017级本科生:刘六、琪七、司四
刘六:JAVA、数学建模
李二:字节跳动、京东云
                          (这里是第一个空行)							
                          (这里是第二个空行)			 (多组数据间间隔两行并严格按照上述数据格式)
导师:王导                  	  (冒号为中文冒号)(导师名字不要重复)
2016级博士生:天一1、王二2、吴五5 (多个学生间以顿号间隔区分)
2015级硕士生:李四4、王五5、许六6 (多行数据间以换行区分)
2016级硕士生:刘一1、李二2、李三
2017级本科生:刘六、琪七7、司四4
刘六:JAVA、数学建模
李二2:字节跳动、京东云

单元测试

测试工具及简易教程

测试工具使用的是mocha,使用前需要搭建好node.js和npm环境,配置mocha环境主要分为两种,全局配置和当前目录配置,为了方便起见,在配置的时候我们选择了在当前目录和全局内都进行配置的方法。参考的博客有:http://www.ruanyifeng.com/blog/2015/12/a-mocha-tutorial-of-examples.html和廖雪峰的https://www.liaoxuefeng.com/wiki/1022910821149312/1101756368943712

单元测试代码

构建测试数据思路

列举几种在输入文本时容易出现的文本格式错误形式,对代码中的主要函数进行测试。

GitHub的代码迁入记录截图

遇到的代码模块异常及解决方法

一、节点缩放异常

  • 问题描述

    因为是自己写的绘制函数,导致缩放功能也得自己实现,在缩放时,有A(B(C))三类节点,当C类节点被隐藏时,此时缩放A节点,将导致C节点的缩放异常。例:年级节点被收缩起来时,学生节点却仍然存在在屏幕中

  • 做过哪些尝试

    在缩放导师节点之后,将改导师的所有子节点全部隐藏而不是改变是否显示在屏幕上

  • 是否解决

    已解决

  • 有何收获

    在思考问题时,要将代码逻辑理清楚,这样就可以很快速的找到问题所在和解决方法

二、关联树实现异常

  • 问题描述

    同样因为需要自己实现,因此关联树代码写起来有点吃力

    首先第一个遇到的困难是,通过d3选择器获取的节点属性值无法进行遍历,导致无法获取每一个节点的x和y值

    其次,关联曲线没有办法根据节点的缩放而缩放,会一直存在屏幕上

  • 做过哪些尝试

    当d3选择器无法进行遍历时,采用多个选择器联动,来获取一个可以遍历的对象

      当关联曲线没有办法缩放时,采用动态绘制的方法,每一次进行节点缩放是,擦除所有已经画出来的关联曲线,根据当前在屏幕上有显示的节点再绘制关联曲线
    
  • 是否解决

    已解决

  • 有何收获

    1. 在d3选择器所获得的属性值无法进行遍历时,可以采用JS选择器,JQUERY选择器,D3选择器联动实现
    2. 要从多个角度思考解决问题
    3. 学会了各种调试姿势

三、技能树修改异常

  • 问题描述

    技能树查看时,为进行修改就点击保存会导致技能书内容被清空,同时click函数会被触发两次导致

  • 做过哪些尝试

    尝试利用FLAG带标志触发的次数,以此来避免第二次的触发

    最终发现是因为每一个节点的click函数被多次定义导致运行出错,修改后即可正确运行

  • 是否解决

    已解决

  • 有何收获

    记住了不能在同一个地方多次定义jquery选择器的click函数,将会导致代码出现奇奇怪怪的错误

四、.........

  • 在代码的具体编写过程中,难免遇到了各种模块特性、代码逻辑等等各种问题所导致的异常,经常一个问题困扰了我一整个晚上,但是在各种资料收集以及缕清思路之后,总能得到解决。因此学到了以后遇到异常时不要钻牛角尖,学会从其它角度看代和思考问题。

评价你的队友

陈凯强

  1. 值得学习的地方:在协作中心态良好,无论bug有多少都能够耐心修改,在code过程中有时间观念。
  2. 需要改进的地方:自学能力不够出色,面对一些困难时经常没有解决思路,有点直男审美。

陈龙辉

  1. 值得学习的地方:执行编码能力强大,自学能力极强;能肝,好几次code到凌晨两点;思维灵活性强,在代码过程中遇到奇怪的bug积极面向浏览器编程查找答案,在众多奇怪的代码中筛选出可以使用的方案。
  2. 需要改进的地方:编码时变量名的设置仍需改进,部分代码阅读感不够强。
posted @ 2020-10-12 13:05  Lit_Bro  阅读(197)  评论(0编辑  收藏  举报