软件工程实践第4次作业-结对编程之实验室程序实现
链接
作业链接:https://edu.cnblogs.com/campus/fzu/SE2020/homework/11277
github项目地址:https://github.com/Limerence910/031802114-031802131.git
结对同学(博客地址):031802114黄颜熠
具体分工
031802114黄颜熠:数据处理,算法设计与实现,el-tree的设计,博客撰写
031802131吴鹏辉:网页前端设计,单元测试,el-tree的设计,博客撰写
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
Estimate | 估计这个任务需要多少时间 | 70 | 75 |
Development | 开发 | ||
Analysis | 需求分析 (包括学习新技术) | 90 | 130 |
Design Spec | 生成设计文档 | 70 | 80 |
Design Review | 设计复审 | 70 | 90 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 70 | 65 |
Design | 具体设计 | 110 | 90 |
Coding | 具体编码 | 650 | 750 |
Code Review | 代码复审 | 100 | 110 |
Test | 测试(自我测试,修改代码,提交修改) | 110 | 130 |
Reporting | 报告 | ||
Test Report | 测试报告 | 70 | 60 |
Size Measurement | 计算工作量 | 70 | 70 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 130 | 150 |
合计 | 1610 | 1760 |
解题思路描述与设计实现说明
代码实现思路
- 我们讨论对比了多种树形框架,决定使用element-ui框架的tree树形控件来实现学术家族树的生成
- 对输入的文本进行数据处理,将其转化为el-tree组件可用的数据格式(json格式)。
- 动态增加节点生成树,再使用element-ui框架中的@on-click添加点击事件,对生成的树添加搜索、增加节点、删除节点、移动节点等功能
- 在树关联时,先建立新树,之后在前树遍历导师的姓名,若找到则加入到前树的子节点中,否则作为一棵新树显示在网页上。
- 使用CSS对网页进行美化,修改文字的大小和颜色,设计简洁、清新的美化风格。
- 将整个HTML划分为HTML + CSS + JS文件,编写单元测试文件,形成项目的基本架构。
关键实现的流程图或数据流图
重要的/有价值的代码片段
<!-- 设置树形控件以及节点属性和操作功能 -->
<el-tree
:data="data"
node-key="id"
:props="defaultProps"
:filter-node-method="filterNode"
style="
position: absolute;
top: 110px;
left: 560px;
border-bottom-right-radius: 10px;
border-bottom-left-radius: 10px;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
"
draggable
:allow-drop="allowDrop"
:allow-drag="allowDrag"
ref="tree">
<span class="custom-tree-node" slot-scope="{ node, data }">
<span>{{ node.label }}</span>
<span>
<!-- 点击"+"号添加成员 -->
<el-button
type="text"
size="mini"
@click="() => append(node, data)"
class="el-icon-plus">
</el-button>
<!-- 点击"-"号删除成员 -->
<el-button
type="text"
size="mini"
@click="() => remove(node, data)"
class="el-icon-delete">
</el-button>
</span>
</span>
</el-tree>
/* 获取输入框信息以及文本处理 */
CInput() {
var str = "";
/* 以换行符分隔 */
lines = this.mytext.split('\n');
for (var i = 0; i < lines.length; i++){
var parts = lines[i].split(":");
if(parts[0] != ""){
if(parts[0] === "导师"){
str = parts[1];
var newChild = { id: this.id2++, label: lines[0], children: [] };
this.data3.push(newChild);
}
else{
/* 正则表达式判断关键词 */
if(parts[0].search(/博士生|硕士生|本科生/) != -1){
parts2 = parts[0].split("级");
if(this.Grade.indexOf(parts2[0]) >= 0){
number = this.Grade.indexOf(parts2[0]);
}
else{
this.number2 = 0;
newChild = {id: this.id2++, label: parts2[0] + "级", children: []};
this.data3[this.teacher].children.push(newChild);
this.Grade.push(parts2[0]);
number = this.Grade.indexOf(parts2[0]);
}
newChild = {id: this.id2++, label: parts2[1],children: []};
this.data3[this.teacher].children[number].children.push(newChild);
students = parts[1].split('、');
for (var j = 0; j < students.length; j++){
newChild = {id: this.id2++, label:students[j], children: []};
this.data3[this.teacher].children[number].children[this.number2].children.push(newChild);
/* 存储节点名,节点位置的信息以便查询插入 */
this.Name.push(students[j]);
this.Location.push([this.teacher,number,this.number2,this.id]);
this.id++;
}
this.id = 0;
this.number2++;
}
else{
experience = parts[1].split('、');
for(var k = 0; k < this.Name.length; k++){
if(this.Name[k] === parts[0]){
for(var l = 0; l < experience.length; l++){
newChild = {id: this.id2++, label: experience[l] };
this.data3[this.Location[k][0]].children[this.Location[k][1]].children[this.Location[k][2]].children[this.Location[k][3]].children.push(newChild);
}
}
}
}
}
}
}
/* 关联树判断与建立 */
this.related(str);
/* 清空中间变量与数组 */
this.Grade = [];
this.Location = [];
this.students = [];
this.data3 = [];
this.Name = [];
this.number = 0;
this.number2 = 0;
},
related(str){
var flag = false;
/* 查找关联节点 */
for(var i = 0;i < this.data.length; i++){
for(var j = 0;j < this.data[i].children.length; j++){
for(var k = 0; k < this.data[i].children[j].children.length; k++){
for(var l = 0; l < this.data[i].children[j].children[k].children.length; l++){
if(this.data[i].children[j].children[k].children[l].label === str){
flag = true;
for(var m = 0; m < this.data3[0].children.length; m++)
this.data[i].children[j].children[k].children[l].children.push(this.data3[0].children[m]);
}
}
}
}
}
/* 若无关联节点添加为新树 */
if(flag === false){
this.data.push(this.data3[0]);
}
},
附加特点设计与展示
设计的创意独到之处,这个设计的意义
使用了element树形控件,以文件夹形式展示更加简明可观,便于用户阅览。我们为家族树添加了增加节点、删除节点、移动节点和搜索节点四个功能,当有新的输入数据或者现在数据出现错误时,使用者能在网页端非常简便的对树的结构进行修改,并直观的看到修改后的效果。添加清空按钮,可以清空所有学术家族树,方便进行大规模修改。
实现思路
利用element-ui中的icon组件,构造点击事件,编写append、remove、check函数跟随在节点之后,实现节点的增加、删除、查找功能,使用el-tree的封装组件实现节点的移动功能(拖拽组件)。
贴出你认为重要的/有价值的代码片段,并解释
/* 以会话框的形式输入需要加入的节点信息 */
append(node,data) {
console.log(node,data,'增加')
this.$prompt('节点名字', '增加节点', {
confirmButtonText: '确定',
cancelButtonText: '取消',
}).then(({ value }) => {
this.dfs(node.id - 1 ,value);
});
},
/* 查找需要加入信息的旧节点信息 */
search(k,value){
for(var m = 0; m < this.data.length; m++){
if(this.data[m].id === k){
newChild = {id: this.id2++ ,label: value, children:[]};
this.data[m].children.push(newChild);
return;
}
else{
for(var n = 0; n < this.data[m].children.length; n++){
// alert(this.data[m].children[n].id);
if(this.data[m].children[n].id === k){
newChild = {id: this.id2++ ,label: value, children:[]};
this.data[m].children[n].children.push(newChild);
return;
}
else{
for(var o = 0; o < this.data[m].children[n].children.length; o++){
if(this.data[m].children[n].children[o].id === k){
newChild = {id: this.id2++ ,label: value, children:[]};
this.data[m].children[n].children[o].children.push(newChild);
return;
}
else{
for(var p = 0; p < this.data[m].children[n].children[o].children.length; p++){
// alert(this.data[m].children[n].children[o].children[p].id);
if(this.data[m].children[n].children[o].children[p].id === k){
newChild = {id: this.id2++ ,label: value};
this.data[m].children[n].children[o].children[p].children.push(newChild);
return;
}
}
}
}
}
}
}
}
},
/* 删除选中节点以及其所有子节点 */
remove(node, data) {
const parent = node.parent;
const children = parent.data.children || parent.data;
const index = children.findIndex(d => d.id === data.id);
children.splice(index, 1);
},
实现成果展示
目录说明和使用说明
目录
如何运行网页
进入GitHub项目,下载整个项目的zip压缩包,解压后打开FamilyTree文件夹,双击打开index.html运行即可。(要保证FamilyTree中的文件没有丢失或移动到别的路径下。)
单元测试
测试工具
通过作业博客里的几份教程,大致学习了JS单元测试的方法,最后选择使用mocha工具来进行单元测试。这里是我们写的一份mocha简易教程。
单元测试代码
var Cinput_test = require('./tree.js');
var expect = require('chai').expect;
var data = [{"id":0,"label":"导师:张三","children":[{"id":1,"label":"2016级","children":[{"id":2,"label":"博士生","children":[{"id":3,"label":"天一","children":[]},{"id":4,"label":"王二","children":[]},{"id":5,"label":"吴五","children":[]}]},{"id":10,"label":"硕士生","children":[{"id":11,"label":"刘一","children":[]},{"id":12,"label":"李二","children":[{"id":21,"label":"字节跳动"},{"id":22,"label":"京东云"}]},{"id":13,"label":"李三","children":[]}]}]},{"id":6,"label":"2015级","children":[{"id":7,"label":"硕士生","children":[{"id":8,"label":"王五","children":[]},{"id":9,"label":"许六","children":[]}]}]},{"id":14,"label":"2017级","children":[{"id":15,"label":"本科生","children":[{"id":16,"label":"刘六","children":[{"id":19,"label":"JAVA"},{"id":20,"label":"数学建模"}]},{"id":17,"label":"琪七","children":[]},{"id":18,"label":"司四","children":[]}]}]}]}];
describe('测试一', function() {
it('信息缺失', function() {
expect(Cinput_test('导师:张三\n' +
'2016级博士生:天一、王二、吴五\n' +
'2015级硕士生:王五、许六\n' +
'2016级硕士生:刘一、李二、李三\n' +
'2017级本科生:刘六、琪七、司四\n' +
'\n' +
'刘六:JAVA、数学建模\n' +
'\n' +
'李二:字节跳动、京东云')).to.be.equal(data);
});
});
我们主要对读入文本内容并转化为可用的JSON格式的Cinput_test()函数进行了5次样例测试。结果如下:
测试数据的构造思路
使用多组不同的数据进行样例测试,尽量包含各种可能出现的极端数据(比如只有一个导师节点、只有前缀没有名字等)。
Github的代码签入记录截图
遇到的代码模块异常及解决方法
- 数据处理模块:在创建新的树节点的时候,会与HTML中的input和button组件产生冲突,输入框和按钮就不见了。后发现新节点的赋值需要使用“:”而不是“=”号
- 关联树模块:每次均生成一棵新树,将其并入前树时由于是数组结构“[]”,无法加入新节点。需要使用this.data[0]来取出其中节点的属性字典
- 单元测试模块:Vue库和mocha无法安装与识别。因为Vue全局安装,文件夹中无法识别,需要局部安装。其次需要在package.json文件中的devDependencies加入mocha,才可以成功安装进行单元测试
评价你的队友
黄颜熠同学代码能力一流,性格活泼,为人阳光,易相处,积极上进,最令我惊讶的是在编程中不达目的不罢休(主要指不睡觉)的毅力,值得我学习。黄颜熠同学需要改进的地方是心态较差,编程遇到困难时容易急躁。