688
社区成员
发帖
与我相关
我的任务
分享| 这个作业属于哪个课程 | 2023年福大-软件工程实践-W班 |
|---|---|
| 这个作业要求在哪里 | 结对第二次作业--编程实现 |
| 结对学号 | 222000407 222000414 |
| 这个作业的目标 | 学习相应的原型设计工具 、根据客户给出的需求,设计相应的原型模型,满足其需求 、撰写博客 |
| 其他参考文献 | CSDN,vue官方文档,go菜鸟教程,gin官方文档 |
代码仓库:https://gitcode.net/qq_51697026/pair_project
| PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 30 | 30 |
| • Estimate | • 估计这个任务需要多少时间 | 30 | 30 |
| Development | 开发 | 2250 | 2570 |
| • Analysis | • 需求分析(包括学习新技术) | 180 | 240 |
| • Design Spec | • 生成设计文档 | 30 | 30 |
| • Design Review | • 设计复审 | 10 | 30 |
| • Coding Standard | • 代码规范 | 20 | 30 |
| • Coding | • 具体编码 | 1800 | 2000 |
| • Code Review | • 代码复审 | 60 | 90 |
| • Test | • 测试 | 150 | 150 |
| Reporting | 报告 | 90 | 175 |
| • Test Repor | • 测试报告 | 30 | 40 |
| • Size Measurement | • 计算工作量 | 20 | 15 |
| • Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 40 | 120 |
| 合计 | 2370 | 2775 |
首页采用轮博图的形式展示澳网的精彩时刻

选手排名的页面,每次只展示男子单打排名或者女子单打排名,通过单击切换榜单

每日赛程根据场地分类,并且可以通过顶部的按钮切换天数

晋级图显示从4th round到final的选手晋级情况(有点不会画晋级线😭)

通过时间线的形式展示澳网的大事件

首先我们确定了前后端分离的做法,在原型设计的时候就知道了后面还有具体编码的实现提前讨论过,所以我们前后端的分工很快就完成了,前端由LJJ(2220004141)完成,后端和部署由HYJ(222000407)完成,接下来是框架与语言的选择,前端无疑是老三样,html+css+js,由于vue在当下的火热,我们前端选择了vue框架,也当作是一次实操vue的机会,后端没选择java语言和spring框架,而是选择go语言和gin框架,是为了后期团队项目,提前学习和实操代码。当项目进行一部分时,发现前端的学习成本和开发成本确实都要大于后端。


后端采用gin架构

json数据存储在本地,src中包含了3个软件包
buildmap构造一些json文件中映射,如队伍id到选手id的映射,选手id到名字的映射等。
defstruct中定义了一些结构用于读取json文件(晋级图复用每日赛程的结构)。
handlefunc中为响应get请求的函数,为前端的请求返回json对象。
以选手排名rank为例,说明后端的数据处理以及和前端的交互过程
首先main函数中,路由绑定好相应get请求"/rank"和对应的相应函数rank.go,监听端口,当接收到匹配的get请求时,执行rank函数,rank函数中先运行构造映射的函数,为接下来的json文件读取构造映射,然后打开对应的json文件进行遍历读取,将读取的数据构造成结构,最后将结构数组打包成json传给前端。
前端采用Vue框架,并且引入element组件来进行界面设计,引入axious来与后端进行交互,请求数据

(这里以比较复杂的每日赛程和晋级图为例,选手排名实现较简单,省略)
每日赛程
在main中绑定相应的处理函数
//每日赛程
router.GET("/results", handlefunc.Result)
destruct中的RankStruct定义了读取的结构,Match中包含两个Team,每个Team中包含一至多个选手,采用切片和结构的嵌套来实现这一效果。
package defstruct
//根据比赛场地来展示当天的比赛
type Match struct {
Event string //比赛类型
Match_status string //比赛的状态
Duration string //持续的时间
Round string //轮次
Team []Team //对战的队伍
}
type Team struct {
Id string //队伍的编号
Players []Player //队伍中的选手
Iswinner string //是否是冠军队伍
Score []string //得分情况
}
type Player struct {
Name string //选手姓名
Nationality string //选手的国籍
}
rank方法中先为后续的读取建立映射,遍历matches数组,调用BuildMatch函数构造Match对象,根据场地的名称加入映射,以实现比赛按场地分类。BuildMatch中,遍历队伍,添加选手,胜者信息,遍历添加比分。将其构造为Match对象返回。
var teammap map[string][]string
var eventmap map[string]string
var roundmap map[string]string
var playermap map[string]map[string]string
var courtmap map[string]string
func Result(c *gin.Context) {
//设置跨域
c.Header("Access-Control-Allow-Origin", "*")
day, _ := c.GetQuery("dayid")
//建立队伍,映射赛事,回合,选手,队伍的映射
teammap = buildmap.BuildTeamMap(day)
eventmap = buildmap.BuildEventMap(day)
roundmap = buildmap.BuildRoundMap(day)
playermap = buildmap.BuildPlayerMap(day)
courtmap = buildmap.BuildCourtMap()
data, err := ioutil.ReadFile("data/result_" + day + ".json")
if err != nil {
fmt.Println("文件打开失败")
}
//声明要返回的映射
var courtTomatches map[string][]defstruct.Match
courtTomatches = make(map[string][]defstruct.Match)
jsonparser.ArrayEach(data, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
court_id, _ := jsonparser.GetString(value, "court_id")
//添加场地和Match的映射
courtTomatches[courtmap[court_id]] = append(courtTomatches[courtmap[court_id]], BuildMatch(value))
}, "matches")
c.JSON(http.StatusOK, courtTomatches)
}
//构造好match对象
func BuildMatch(value []byte) defstruct.Match {
//每场比赛的基本信息 场地,状态,回合,持续时间
event_id, _ := jsonparser.GetString(value, "event_uuid")
status, _ := jsonparser.GetString(value, "match_status", "name")
round_id, _ := jsonparser.GetString(value, "round_id")
duration, _ := jsonparser.GetString(value, "duration")
var teams []defstruct.Team
//遍历队伍的信息
jsonparser.ArrayEach(value, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
var team defstruct.Team
team.Id, _ = jsonparser.GetString(value, "team_id")
players := teammap[team.Id]
//由队伍的id添加选手
for i := 0; i < len(players); i++ {
player := defstruct.Player{playermap[players[i]]["name"], playermap[players[i]]["nationality"]}
team.Players = append(team.Players, player)
}
//判断是否是胜者
team.Iswinner, _ = jsonparser.GetString(value, "status")
if team.Iswinner == "" {
team.Iswinner = "false"
}
//遍历添加比分
jsonparser.ArrayEach(value, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
score, _ := jsonparser.GetString(value, "game")
team.Score = append(team.Score, score)
}, "score")
teams = append(teams, team)
}, "teams")
//构建Match对象
match := defstruct.Match{eventmap[event_id], status, duration, roundmap[round_id], teams}
return match
}
晋级图
构造drawmap,以场地名称和order顺序作为key,复用每日赛程的Macth结构,在遍历draws.json的matches中调用result(每日赛程)中的BulidMatch方法,构造Match对象加入映射,由于以场地名称和order作为key,故每个round内的比赛桶排序成了合理的顺序,前端可以直接使用。
func Draw(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "*")
var drawmap map[string]map[int64]defstruct.Match
drawmap = make(map[string]map[int64]defstruct.Match)
//建立映射
teammap = buildmap.BuildTeamMap("draws")
eventmap = buildmap.BuildEventMap("draws")
roundmap = buildmap.BuildRoundMap("draws")
playermap = buildmap.BuildPlayerMap("draws")
courtmap = buildmap.BuildCourtMap()
data, err := ioutil.ReadFile("data/draws.json")
if err != nil {
fmt.Println("文件打开失败")
}
jsonparser.ArrayEach(data, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
round_id, _ := jsonparser.GetString(value, "round_id")
roundname := roundmap[round_id]
order, _ := jsonparser.GetInt(value, "order")
if drawmap[roundname] == nil {
drawmap[roundname] = make(map[int64]defstruct.Match)
}
drawmap[roundname][order] = BuildMatch(value)
}, "matches")
c.JSON(http.StatusOK, drawmap)
}
(这里主要是以导航栏、选手排名以及每日赛程为例,晋级图思路与每日赛程类似)
导航栏
创建一个index.js文件,在里面编写组件与路由之间的映射关系,让Vue Router知道每一个路由所渲染的组件
const router = new createRouter({
history:createWebHashHistory(), //hash模式:createWebHashHistory,history模式:createWebHistory
routes: [
{path: '/',name:'home',component:Home},
{path: '/rank',name:'rank',component: PlayersRanking},
{path:'/results',name:'results',component: Schedule},
{path:'/draws',name:'draws',component: PromotionChart},
{path:'/history',name:'history',component: History,meta:{path:"/history"}}
]
})
export default router
在main.js中引入刚才配置的index.js文件,并使用配置到全局
import { createApp} from 'vue'
import App from './App.vue'
import router from './router/index.js'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import axios from 'axios';
const app = createApp(App)
app.use(ElementPlus)
app.use(router)
app.config.globalProperties.$axios = axios //全局配置axios
axios.defaults.baseURL = 'http://47.102.219.152:8080'
app.mount('#app')
通过el-menu菜单组件来显示导航栏
<div id="nav">
<el-menu
class="el-menu-demo"
mode="horizontal"
background-color="#797979"
text-color="#FFFFFF"
active-text-color="#ffd04b"
router>
<el-menu-item index="/rank"><em class="menu_item">选手排名</em></el-menu-item>
<el-menu-item index="/results"><em class="menu_item">赛程</em></el-menu-item>
<el-menu-item index="/draws"><em class="menu_item">晋级图</em></el-menu-item>
<el-menu-item index="/history"><em class="menu_item">澳网历史</em></el-menu-item>
</el-menu>
</div>
选手排名
通过axios向后端请求数据并根据key值将数据赋给相应的数组
created:function(){
this.$axios('/rank').then((result)=>{
console.log(result.data)
this.tableDataM = result.data['Manranklist']
this.tableDataF = result.data['Womenranklist']
})
},
data() {
return {
flag: true,
tableDataM: [],
tableDataF: []
};
通过handleFlip函数来进行页面翻转
handleFlip() {
let container = document.getElementById('container')
container.style.transform = this.flag ? 'rotateY(180deg)':''
this.flag = !this.flag
},
赛程
通过设置el-scrollbar-item的点击事件changeData来获取不同日期的json数据,当点击相应的Item时,根据item组件的内容获取所要请求的网址信息,并请求获取相应数据
changeData(e){
var btnText = e.target.innerText
var base = 115
if (btnText[0] == 'D'){
base += parseInt(btnText.substr(3))
base = "/results?dayid=0" + base
}
else{
base = "/results?dayid="+btnText
}
this.$axios(base).then((result)=>{
this.matches = result.data
})
}
通过el-card组件并结合相应来实现css来进行赛程界面的排版(代码太多了就展示一部分吧)
<div class="area">
<div v-for="(place,index) in matches" :key="index" >
<div style="font-size:28px;font-weight:bolder;margin-left:55px">{{index}}</div>
<div class="place">
<div v-for="match in place" class="match">
<el-card class="box-card">
<!--比赛类型-->
<div>
<div style="font-weight:bolder;float:left">
{{match.Event}}
</div>
<div style="font-weight:bolder;float:right">
{{match.Match_status}}
</div>
</div>
<div style="margin-top:30px">
<div style="color:#AAAAAA;float:left">
{{match.Round}}
</div>
<div style="color:#AAAAAA;float:right">
{{match.Duration}}
</div>
</div>
<!--分割线-->
<el-divider style="margin-top:60px;margin-bottom:10px"></el-divider>
<!--选手1信息-->
<div>
<div style="float:left">
<div style="">
<img :src="match.Team[0].Players[0].Nationality" alt="" class="flag" />
<span> </span>
<span v-if='match.Team[0].Iswinner == "Winner"' class="highlight">
{{ match.Team[0].Players[0].Name }}
</span>
<span v-else>{{ match.Team[0].Players[0].Name }}</span>
</div>
<div v-if="match.Team[0].Players.length>1" style="margin-top:10px;">
<img :src="match.Team[0].Players[1].Nationality" alt="" class="flag" />
<span> </span>
<span v-if='match.Team[0].Iswinner == "Winner"' class="highlight">
{{ match.Team[0].Players[1].Name }}
</span>
<span v-else>{{ match.Team[0].Players[1].Name }}</span>
</div>
</div>
<div style="float:right">
<span v-if='match.Team[0].Iswinner == "Winner"' class="highlight">
222000407:这次我负责后端部分,为了提前为后面的团队项目准备技术经验,我选择了go+gin来进行后端的开发,时间短暂却还要学习新的语言和新的框架,本来有一些焦虑和担忧,但是真正开始着手学习的时候发现编程语言之间还是有不少的融汇贯通之处的,在学习新技术的部分并没有遇到很多困难。我还认识到,前后端的开发要多进行交流来规范数据传输的一致性,后端要为前端提供数据正确性和便捷性的保证,这次的合作中我负责的后端部分由于传输的json数据没做好处理,导致给前端的同学添加了一些麻烦。还有在git的协作上也有了更深入的认识,懂得如何协调前后端的代码,处理冲突,本地代码发生难以解决的问题时,版本回退。
222000414:刚开始拿到作业的时候,我就想着要用Vue来进行前端开发,但是由于此前根本没用过Vue框架,所以就到b站上找了相应的视频进行学习。由于大多数的视频教程都是使用Vue2进行开发的,并且网上的资源很多都是关于Vue2的,所以在一些具体实现上会有差别。比如路由的配置,以及插槽slot的应用等等。这些问题都困扰了我好久,好在最后通过关键字查询以及队友的帮助,最终这些问题都得到了解决。此外,在这次开发中我还明白了整体布局的重要性,在进行前端开发的过程中要与后端保持紧密的联系,要保证前后端所需要内容的一致性,不然就会出现前端与后端代码协同使用时不一致的错误,导致开发停滞。
222000407->222000414:很高兴能和LJJ同学合作完整这一次结对项目,从原型设计到具体的编程实现,LJJ同学都承担了不少的工作量,特别是具体实现部分,LJJ同学积极承担前端部分开发,在这之上LJJ同学也很配合我提出的新要求的实现,也包容了我后端部分出现的错误导致他不必要的时间浪费,总之,LJJ同学是个不可多得的队友。
222000414->222000407:HYJ同学学习能力很强,在没有尝试过Go+Gin的情况下,选择其进行后端开发,并且能够很快熟练应用。在我遇到问题的时候,会跟我积极探讨,总是能够给我不少灵感。总之,HYJ同学是个非常有责任心的队友,和他合作十分愉快。
1.男女选手排名切换挺有意思;2.选择一个全新的后端框架是基于什么考虑?