这个作业属于哪个课程 | https://edu.cnblogs.com/campus/fzu/SE2020 |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/fzu/SE2020/homework/11277 |
这个作业的目标 | 合作分工,前端与页面制作 |
学号 | 031802506 |
一、结对信息
- 分工信息
学号 | 姓名 | 分工 |
---|---|---|
031802504 | 陈新平 | 编写功能代码,UI 设计 |
031802506 | 程灵飞 | 单元测试代码,博客攥写 |
- 链接
队友博客链接: https://www.cnblogs.com/Stareven233/p/13797157.html
本次作业链接: https://edu.cnblogs.com/campus/fzu/SE2020/homework/11277
本次作业 github 链接: https://github.com/Stareven233/031802504-031802506
二、PSP 表格
PSP 2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 10 | 10 |
Estimate | 估计这个任务需要多少时间 | 10 | 10 |
Development | 开发 | 500 | 720 |
Analysis | 需求分析 (包括学习新技术) | 120 | 120 |
Design Spec | 生成设计文档 | 120 | 120 |
Design Review | 设计复审 | 20 | 20 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
Design | 具体设计 | 60 | 60 |
Coding | 具体编码 | 120 | 180 |
Code Review | 代码复审 | 30 | 30 |
Test | 测试(自我测试,修改代码,提交修改) | 120 | 300 |
Reporting | 报告 | 30 | 30 |
Test Report | 测试报告 | 10 | 10 |
Size Measurement | 计算工作量 | 10 | 10 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 60 | 60 |
合计 | 1230 | 1690 |
三、基础功能编写与展示
基于 WEB 端和新的需求,我们重新设计了自己的方案:
网页功能分析
- 通过 textareadu 读取一串固定格式的师生信息
- 用正则将字符串解析为树数据对象,四层结构(导师-年级-学历-学生姓名-学生详细信息)
- 在页面上渲染出树状图,默认不展开
- 缩放:点击相应按钮,修改对应节点的 expand 属性,实现节点的展开与收回
- 多树并存:用数组( treeData )存储所有生成的树对象
- 多树关联:对于新输入的树,调用find方法在节点对象( nodeData )(记录了每个师生对应的节点对象)中搜寻每个叶节点的名称,若有则将其合并到这棵树上
成果展示
关键代码
mergeTree 函数用于合并树,但本质上可以合并某个对象到一个同类对象数组中,要求为 {label: xx, children: [{}, ]}
类的嵌套对象
仅当 obj 的属性 label 与 arr 中某个对象 label 相同时递归合并二者的 children 属性,children 是数组
mergeTree (arr, obj) {
// 若其中存在一个元素其 label 属性与 obj 的 label 相同则执行合并,否则 obj 推入 arr 中
const tmp = arr.find(item => item.label === obj.label)
if (tmp === undefined) {
arr.push(obj)
return
}
for (const c of obj.children || []) {
this.mergeTree(tmp.children, c)
}
// 替换策略:不同 label 直接 push,否则递归合并 children ;最终由于学生姓名处无 children 属性只比较 label 将脱离递归
}
四、流程图
五、附加特点设计与展示
特点设计: 实现思路
- 页面美观大方,正所谓
Simple is Better Than Complex
: Element-ui - 树可动态添加,动态合并,自适应节点展开收缩,丝滑顺畅: 以数组存储每个树对象,每次生成新树时更新数组
- 导师节点(包括动态合并后从叶节点升级的)高亮标出,容易区分: 为组件 v-bind 一个根据节点数据设置 class 的函数,生成时调用
- 每个树都可独立拖动,再也不用担心放不下了: 监听鼠标事件 onmousemove ,实时调整组件位置
- 方便易用,支持横纵切换,一键缩放: 为组件 on-expand 事件绑定函数,调整组件的 expand 属性
- 节点点击后在旁边浮现节点信息(即学生的详细经历): 监听节点 on-node-click 事件,手动调整位置
树支持放大缩小,简单易用, ctrl + 鼠标滚轮: 无
代码片段
showPopover 函数当节点被点击触发,仅当节点为师生时允许允许,效果是在节点旁边用一个 tooltip 框展示节点的附加信息
showPopover (e, data) {
e.stopPropagation()
// 避免同时触发 document 上的 close
const node = e.target
if (node.className.indexOf('tree-teacher') + node.className.indexOf('tree-student') === -2) {
// 两个都找不到时返回值(每个为 -1 )加起来为 -2
return
}
this.popContent = data.tag || '暂无tag'
const popNode = document.getElementById(this.$refs.popover.tooltipId)
const pos = this.getAbsolutePos(node)
// getAbsolutePos 函数借助节点的 offsetParent 方法递归获取元素相对浏览器的绝对坐标
if (popNode === null) {
return
}
popNode.style.left = pos.left + 'px'
popNode.style.top = pos.top + node.getBoundingClientRect().height + 10 + 'px'
// 很坑,left 与 top 必须用绝对坐标,而且 popover show 之前无法获取坐标等信息
this.$refs.popover.doShow()
}
成果展示
六、目录结构
目录说明
使用说明
-
安装 nodejs
注:本仓库使用的是 v14.13.1 -
安装 vue
npm install -g @vue/cli
-
clone该仓库
$ git clone https://github.com/Stareven233/031802504-031802506
-
安装依赖
cd 031802504-031802506
npm install
-
编译 & 运行
npm run serve
-
编译 & 运行(生产环境)
npm run build
七、单元测试
测试工具
选择
用的是与 vue 集成良好的 jest ,再准确一点是 vue 的插件 cli-plugin-unit-jest ,它开箱即用不需配置
学习
先去查了博客,很杂,很多都是 jest 而非 cli-plugin-unit-jest ,不大一样,而且年代有点久,内容还少。
后来照着官方文档学,中英文混杂
简易教程
队友亲笔手写的价值 $999 的 jest 教程:https://www.cnblogs.com/Stareven233/p/13807613.html
代码
这3个测试用于测试 generateTree 函数(负责解析并生成树对象),通过 wrapper.vm.treeData 获取生成结果与已知正确的数据作比较来判断正误
wrapper.findAllComponents({ name: 'el-button' }).at(0).trigger('click') // 先打开侧边输入框
it('generateTree(expand)', () => {
const button = wrapper.findAllComponents({ name: 'el-button' }).at(2)
button.trigger('click')
wrapper.vm.expandChange()
// 先生成,再展开才有数据,而且还不能跟上方的侧边输入框打开放一起
})
it('generateTree(treeData)', () => {
const tData = [{ label: '张三', tag: '导师', isTeacher: true, children: [...]...]
expect(wrapper.vm.treeData).toEqual(tData)
})
it('generateTree(nodeData)', () => {
const nData = { 张三: { isTeacher: true, node: { label: '张三', tag: '导师', isTeacher: true, children: [...]}
expect(wrapper.vm.nodeData).toEqual(nData)
})
构造思路
分成静态与动态两部分,静态指已知数据测试渲染是否正确,动态指给出原始文本能否通过页面输入、点击正确解析出数据并生成树
从组件的渲染、特定节点信息正误与内部变量三个方面综合考虑,重点放于树的生成合并与树渲染测试
站在测试员的角度,站在一般用户的角度思考,比如输入文本可能会是特殊结构,不同编码,或是大量数据造成处理过久溢出等错误
测试与覆盖率结果
八、Github Commit 记录
九、总结
遇到的问题
- 问:久闻 vue 大名,但从没用过,时间紧任务重
解:现卖现学
学:nodejs 、vue 、less 、jest 等前端开发知识 - 问:vue 难以调试,配合上 eslint 体验更糟,代码规范严格,定义语法,分号换行都非常严,仿佛在打 python ,最要命的是不允许暴露内部变量,渲染出错也不能查看数据,绝望。
试:vue-devtools
解:习惯它
学:vue-devtools (但没想象的好用,编译半天还出错)、感受到了 vue 的两面性,也或许只是自己学的还不够... - 问:想为节点添加 popover 弹出框,然而 element 的这个组件要求将按钮包裹在内共同渲染,但是树是动态生成的,页面初始化时无法确定。
试:选用了 ref 引用方式绑定,然而还是没有响应,能弹出却不能自动定位。反复看文档也没用相关 api 提供
解:最后手写定位,但一开始没搞清相对定位跟绝对定位差别,为此浪费了大量时间...
学:组件 props 及原生 js 操作 - 问:jest 文档不全,东拼西凑到处学...一开始由于用了 element 库一直报错说没注册组件
试:查看文档、各论坛、不断调整代码
解:在测试代码中组件要跟 main.js 一样用 Vue.use 注册,这些文档可都没说啊...
学:jest 单元测试
队友评价
- 对工具的学习和掌握进度明显比我快啦,队友非常滴优秀;
- 因为两个人都是第一次学习前端框架,所以学起来比较抓狂,但是他的项目经验比我丰富,也指导了我很多,赞一个;
- 非常好心地不让我熬夜,一熬夜就坐在我背后开启极限编程模式;
- 最直接的感受:这次被带飞了。
不足的地方
- 对页面的美观程度还不够满意,因为考虑到多树共存的情况所以给页面布局的设计带来了一些困难,最终采用了简单化设计,但又过于简单了一些,后续还可以再对页面进行美化工作;
- 在 Vue 框架的学习和不大重要的功能上花费的时间占比较大,虽然 learning by doing,但是学得不够全面,就有点难过;
- 没做搜索功能,不难,但没时间,有些遗憾;
- 设想中还应该用 element-ui 的 tree 组件做一个侧边目录并集成搜索便于浏览;
- 没考虑师生同名的情况,如果有这种数据会因为相互递归而栈溢出。而且动态合并考虑的情况还不够充足。