110
社区成员
这个作业属于哪个课程 | FZU_SE_teacherW_4 |
---|---|
这个作业要求在哪里 | 结对作业第二次——编程实现 |
结对学号 | 222200413 ,222200415 |
这个作业的目标 | 团队协作,完成2024年巴黎奥运会的编码 |
其他参考文献 | 《构建之法》 |
PSP | Personal Software Process Stages | 预估耗时(小时) | 实际耗时(小时) |
---|---|---|---|
Planning | 计划 | 0.3 | 0.5 |
• Estimate | • 估计这个任务需要多少时间 | 0.3 | 0.5 |
Development | 开发 | 42.85 | 49.4 |
• Analysis | • 需求分析 (包括学习新技术) | 0.5 | 0.6 |
• Design Spec | • 生成设计文档 | 1 | 1 |
• Design Review | • 设计复审 | 0.6 | 1 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 0.25 | 0.3 |
• Design | • 具体设计 | 2 | 2.5 |
• Coding | • 具体编码 | 36 | 40 |
• Code Review | • 代码复审 | 0.5 | 1 |
• Test | • 测试(自我测试,修改代码,提交修改) | 2 | 3 |
Reporting | 报告 | 1.5 | 1.5 |
• Test Report | • 测试报告 | 0.5 | 0.3 |
• Size Measurement | • 计算工作量 | 0.5 | 0.2 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 0.5 | 1 |
合计 | 44.65 | 51.4 |
对接: 通过apifox规定api接口,完成前后端对接
大部分讨论在线下进行
使用vue3+pinia+vue Router+Typescript+element plus+axios
import { createRouter, createWebHistory } from 'vue-router'
import { useDayResultStore } from '@/stores/dayResult'
import { useCompetitionStore } from '@/stores/competition'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/home',
redirect: '/',
},
{
path: '/',
name: 'Home',
component: ()=>import('@/views/home/index.vue')
},
{
path: '/rankings',
name: 'Rankings',
component: ()=>import('@/views/rankings/index.vue')
},
{
path: '/dayResult',
name: 'DayResult',
component: ()=>import('@/views/dayResult/index.vue'),
beforeEnter: async (to, from, next) => {
const dayResultStore = useDayResultStore()
const res = await dayResultStore.fetchDayResults(dayResultStore.date)
if (res.code===1){
next()
}
else {
alert(res.message)
next(false)
}
}
},
{
path: '/compete',
name: 'Compete',
component: ()=>import('@/views/compete/index.vue'),
beforeEnter: async (to, from, next) => {
const competitionStore = useCompetitionStore()
const res1 = await competitionStore.fetchProject()
if (res1.code===1){
const res2 = await competitionStore.fetchTypes(competitionStore.selected.firstname)
if (res2.code===1){
const res3 = await competitionStore.fetchInfo(competitionStore.selected.type.id)
if (res3.code===1){
next()
}
else {
alert(res3.message)
next(false)
}
}
else {
alert(res2.message)
next(false)
}
}
else {
alert(res1.message)
next(false)
}
}
},
{
path: '/about',
name: 'About',
component: ()=>import('@/views/about/index.vue'),
},
{
path: '/detail',
name: 'Detail',
component: ()=>import('@/views/detail/index.vue'),
children:[
{
path:'/matchDetail',
name:'MatchDetail',
component:()=>import('@/views/detail/matchDetail.vue')
},
{
path:'/playerList',
name:'PlayerList',
component:()=>import('@/views/detail/playerList.vue')
},
]
},
]
})
export default router
//获取奖牌榜信息
const fetchMedals = async ():Promise<Status> => {
try {
const response = await axios.get('/api/dataget/GetAllNationalMedals');
const { code, message, data } = response.data;
if (code === 1) {
medals.value = data;
localStorage.setItem('medals', JSON.stringify(medals.value));
// console.log(medals.value);
}
return { code, message };
} catch (error) {
console.error(error);
return { code: 0, message: '请求失败' };
}
};
const fetchDayResults = async (date:string):Promise<Status> => {
try {
const response = await axios.get(`/api/dataget/GetDayResult`, {
params: { date },
});
if (response.data.code === 1) {
dayResults.value = response.data.data;
localStorage.setItem('dayResults', JSON.stringify(dayResults.value));
}
return { code:response.data.code, message:response.data.message };
} catch (error) {
console.error('请求失败:', error);
return { code: 0, message: '请求失败' };
}
};
const fetchTypes = async (firstname:string):Promise<Status> => {
try {
const response = await axios.get(`/api/dataget/GetAllMatchDetailName`,{
params:{ firstname }
});
const { code, message, data } = response.data;
if (code === 1) {
types.value = data;
localStorage.setItem('types', JSON.stringify(types.value));
// console.log(types.value);
}
return { code, message };
} catch (error) {
console.error(error);
return { code: 0, message: '请求失败' };
}
};
const fetchProject = async ():Promise<Status> => {
try {
const response = await axios.get('/api/dataget/GetAllMatchName');
const { code, message, data } = response.data;
if (code === 1) {
projects.value = data;
localStorage.setItem('projects', JSON.stringify(projects.value));
// console.log(projects.value);
}
return { code, message };
} catch (error) {
console.error(error);
return { code: 0, message: '请求失败' };
}
};
const fetchInfo = async (id:string):Promise<Status> => {
try {
const response = await axios.get(`/api/dataget/GetBattleTable`,{
params:{ id }
});
const { code, message, data } = response.data;
if (code === 1) {
information.value = data;
localStorage.setItem('information', JSON.stringify(information.value));
// console.log(information.value);
}
return { code, message };
} catch (error) {
console.error(error);
return { code: 0, message: '请求失败' };
}
};
const fetchCompeteDetail = async (disciplineCode:string,eventId:string):Promise<Status> => {
try {
const response = await axios.get(`api/dataget/GetResultCombine`,{
params:{ disciplineCode,eventId }
});
const { code, message, data } = response.data;
if (code === 1) {
competeDetail.value = data;
localStorage.setItem('competeDetail', JSON.stringify(competeDetail.value));
// console.log(competeDetail.value);
}
return { code, message };
} catch (error) {
console.error(error);
return { code: 0, message: '请求失败' };
}
};
轮播图使用element plus组件库中的el-carousel组件显示
<!-- 轮播图-->
<div class="carousel">
<el-carousel :interval="3000" height="auto">
<el-carousel-item v-for="(item, index) in swiperImages" :key="index" style=" height: 500px; !important;">
<img :src="item" alt="" class="carousel-image"/>
</el-carousel-item>
</el-carousel>
</div>
使用v-for遍历数据和对象,动态渲染相应的元素
<!-- 1/4决赛-->
<div class="col">
<div
class="item"
v-for="(item,index) in competitionStore.information.qFinal.allMatch"
:key="index"
>
<div class="competitor">
<div class="nation">
<img
:src="'https://olympics.com/OG2024/assets/images/flags/OG2024/' + item.competitor1.countryEN + '.webp'"
alt=""
style="height: 25px;margin-right: 10px;border: silver solid 1px;"
>
<p>{{ item.competitor1.name }}</p>
</div>
<p>{{ item.competitor1.score ? item.competitor1.score : '' }}</p>
</div>
<div class="competitor">
<div class="nation">
<img
:src="'https://olympics.com/OG2024/assets/images/flags/OG2024/' + item.competitor2.countryEN + '.webp'"
alt=""
style="height: 25px;margin-right: 10px;border: silver solid 1px;"
>
<p>{{ item.competitor2.name }}</p>
</div>
<p>{{ item.competitor2.score ? item.competitor2.score : '' }}</p>
</div>
</div>
</div>
<!-- 连线-->
<div class="col-gap">
<div class="line-box">
<div class="line-left"></div>
<div class="line-right">
<div></div>
<div></div>
</div>
</div>
<div class="line-box">
<div class="line-left"></div>
<div class="line-right">
<div></div>
<div></div>
</div>
</div>
</div>
<!-- css-->
.col-gap{
width: 5%;
height: 70vh;
min-height: 528px;
display: flex;
flex-direction: column;
justify-content: space-around;
}
.line-box{
width: 100%;
height: 27%;
display: flex;
.line-left{
width: 50%;
height: 100%;
border-top: silver solid 2px;
border-right: silver solid 2px;
border-bottom: silver solid 2px;
}
.line-right{
width: 50%;
height: 100%;
}
.line-right>div:first-child{
width: 100%;
height: 50%;
border-bottom: silver solid 2px;
}
}
后端数据传输服务器设置
static void Main(string[] args)
{
// 7595 是端口,可以改成喜欢的
var config = new HttpSelfHostConfiguration("http://localhost:7595");
config.EnableCors();
config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
config.Formatters.XmlFormatter.SupportedMediaTypes.Clear();
// 定义路由,这里不需要改
config.Routes.MapHttpRoute(
name: "DefaultApi", // 可以改成喜欢的名字,xxxApi
routeTemplate: "api/{controller}/{action}",
defaults: new { id = RouteParameter.Optional }
);
var server = new HttpSelfHostServer(config);
server.OpenAsync().Wait(); // 启动服务器
Console.WriteLine("服务器已启动,按回车停止。");
Console.ReadLine();
}
api类需要继承ApiController并且在命名为xxxController 具体的方法函数添加上标签[HttpGet]
如下声明
public class DataGetController : ApiController
声明DataPraser静态类存放需要用到的数据以及http的获取方法,以及获取路由中json数据的方法
以下四个路由为获取数据使用的(无法获得所有数据,例如跳水比赛,无法使用对阵表)
public static string GetBracketTableHttp(string matchID)
{
return $"https://olympics.com/OG2024/data/GLO_Bracket~comp=OG2024~rsc={matchID}~lang=CHI.json";
}
public static string GetDailyFixturesHttp(string date)
{
return $"https://sph-s-api.olympics.com/summer/schedules/api/CHI/schedule/day/{date}";
}
public static string GetSchedulesHttp(string disciplineCode)
{
return $"https://olympics.com/OG2024/data/SCH_StartList~comp=OG2024~disc={disciplineCode}~lang=CHI.json";
}
public static string GetScheduleResultsHttp(string disciplineCode, string id)
{
return $"https://olympics.com/OG2024/data/RES_ByRSC_H2H~comp=OG2024~disc={disciplineCode}~rscResult={id}~lang=CHI.json";
}
以下为获取json的代码
public static string GetHttpJson(string httpPath)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(httpPath);
request.Proxy = null;
request.KeepAlive = false;
request.Method = "GET";
request.ContentType = "application/json; charset=UTF-8";
request.AutomaticDecompression = DecompressionMethods.GZip;
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream myResponseStream = response.GetResponseStream();
StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.UTF8);
string retString = myStreamReader.ReadToEnd();
myStreamReader.Close();
myResponseStream.Close();
if (response != null)
{
response.Close();
}
if (request != null)
{
request.Abort();
}
return retString;
}
除此之外的代码主要为分析数据并进行简化并保存(降低访问奥运官网带来的延迟)然后传输给前端
对阵表数据去除了某些比赛中1/4决赛以前的比赛,仅从1/4决赛开始记录,通过数据中的bracketCode判断为什么比赛
public static BattleTableData GetBattleTable(string id)
{
BattleTableData result = new BattleTableData();
string savePath = Path.Combine(ResourcesPath, "BracketData", id + ".json");
//获取已经存在的数据
if (File.Exists(savePath))
{
string json = File.ReadAllText(savePath);
result = JsonConvert.DeserializeObject<BattleTableData>(json);
return result;
}
//若不存在则获取
BattleTable data = new BattleTable();
string httpPath = GetBracketTableHttp(id);
var jsonResult = Communicable(httpPath);
if (!jsonResult.Item1) return null;
data = JsonConvert.DeserializeObject<BattleTable>(jsonResult.Item2);
try
{
foreach (var a in data.bracket)
{
result.id = a.documentCode;
Console.WriteLine(a.documentCode);
if (!a.bracketCode.Equals("BRN") && !a.bracketCode.Equals("FNL")) continue;
foreach (var b in a.bracketPhases)
{
BattleTableData.BattleResult target;
if (b.phaseCode.Equals("QFNL"))
{
target = result.qFinal;
}
else if (b.phaseCode.Equals("SFNL"))
{
target = result.halfFinal;
}
else if (b.bracketItems.Count == 1)
{
if (a.bracketCode.Equals("BRN")) target = result.final2;
else if (b.phaseCode.Equals("FNL-")) target = result.final;
else continue;
}//若超过四分之一决赛,舍弃
else continue;
foreach (var c in b.bracketItems)
{
target.description = c.eventUnit.description;
BattleTableData.BattleResult.MatchResult matchResult = new BattleTableData.BattleResult.MatchResult();
bool isWinner;
BattleTable.Bracket.BracketPhases.BracketItems.BracketCompetitors bracketCompetitors;
//参赛一
bracketCompetitors = c.bracketCompetitors[0];
isWinner = (bracketCompetitors.cp_wlt.Equals("W"));
if (bracketCompetitors.participant != null)
matchResult.competitor1 = new BattleTableData.BattleResult.MatchResult.Competitor(bracketCompetitors.participant.organisation.code, bracketCompetitors.participant.shortName, bracketCompetitors.cp_result, isWinner);
else matchResult.competitor1 = new BattleTableData.BattleResult.MatchResult.Competitor("", "轮空", "", false);
//参赛二
bracketCompetitors = c.bracketCompetitors[1];
isWinner = (bracketCompetitors.cp_wlt.Equals("W"));
if (bracketCompetitors.participant != null)
matchResult.competitor2 = new BattleTableData.BattleResult.MatchResult.Competitor(bracketCompetitors.participant.organisation.code, bracketCompetitors.participant.shortName, bracketCompetitors.cp_result, isWinner);
else matchResult.competitor2 = new BattleTableData.BattleResult.MatchResult.Competitor("", "轮空", "", false);
target.allMatch.Add(matchResult);
}
}
}
File.WriteAllText(savePath, JsonConvert.SerializeObject(result));
Console.WriteLine("success");
Console.WriteLine(savePath);
Console.WriteLine(httpPath);
}
catch (Exception e)
{
Console.WriteLine("false");
Console.WriteLine("false");
Console.WriteLine("false");
Console.WriteLine(savePath);
Console.WriteLine(httpPath);
Console.WriteLine();
Console.WriteLine();
Console.WriteLine();
}
return result;
}
获取每日赛程判断了日期是否合法(虽然前端传的应该都是合法的)
在具体的实现过程中出现的存在参赛者但是不存在比赛数据的情况进行处理以及多个比赛结果最多保留前三名的数据进行显示
public GetResult<List<Units>> GetDayResult(string Date)
{
GetResult <List<Units>> result = new GetResult<List<Units>>();
string[] strings = Date.Split('-');
int month= int.Parse(strings[1]), day= int.Parse(strings[2]);
if (string.IsNullOrEmpty(Date) || strings.Length != 3 || !strings[0].Equals("2024") || month < 7|| month>8||(month==7&& (day <24|| day >31))||(month==8&&(day<1||day>11)))
{
result.code = 0;
result.message = "日期错误!";
Console.WriteLine($"参数 {Date} 的数据获取错误!");
return result;
}
string savePath = Path.Combine(DataPraser.DayResultSavePath, Date + ".json");
DateMatchDetails data;
if (saveDayResult.ContainsKey(Date))
{
result.data = saveDayResult[Date].data;
}
else
{
if (File.Exists(savePath))
{
string jsonData = File.ReadAllText(savePath);
data = JsonConvert.DeserializeObject<DateMatchDetails>(jsonData);
}
else
{
string httpPath = DataPraser.GetDailyFixturesHttp(Date);
var jsonResult = DataPraser.Communicable(httpPath);
data = JsonConvert.DeserializeObject<DateMatchDetails>(jsonResult.Item2);
foreach (var a in data.units)
{
a.startDate = a.startDate.Substring(11, 5);
if (a.competitors == null) continue;
if (a.competitors.Count > 3)
{
a.competitors.RemoveRange(3, a.competitors.Count - 3);
}
//存在参赛者但是不存在比赛数据,则不返回具体比赛情况
if (a.competitors.Count > 0 && a.competitors[0].results == null) a.competitors = null;
}
////进行筛选 个人赛保留三位competitor的数据
File.WriteAllText(savePath, JsonConvert.SerializeObject(data));
saveDayResult.Add(Date, result);
useCheck.Add(new StringIntPair(Date, 5));
}
result.data = data.units;
}
//清理
foreach (var a in useCheck)
{
a.value2--;
if (a.value2 < 0) saveDayResult.Remove(a.value1);
Console.WriteLine($"{a.value1} {a.value2}");
}
useCheck.RemoveAll(x => x.value2 < 0);
return result;
}
比赛详情的获取,由于某个比赛的所有比赛数据中并没有比分的数据,因此需要通过获取到的数据结合获取具体比赛result的路由来获取比分数据因此需要多次访问奥运官方数据,延迟较大,在获取过数据后进行保存使用。由于第一步获取的schedules数据中是某种比赛大类的所有比赛数据因此需要根据传入的eventId进行区分然后再对数据进行分析
public static ResultCombineJsonUse GetResultCombine(string disciplineCode, string eventId)
{
string savePath = Path.Combine(ResourcesPath, "ResultCombine", eventId.Substring(0,11)+".json");
if (File.Exists(savePath))
{
string json=File.ReadAllText(savePath);
ResultCombineJsonUse result = JsonConvert.DeserializeObject<ResultCombineJsonUse>(json);
return result;
}
string http = DataPraser.GetSchedulesHttp(disciplineCode);
var jsonResult = DataPraser.Communicable(http);
if (!jsonResult.Item1|| !eventId.Substring(0,3).Equals(disciplineCode))
{
Console.WriteLine($"{http} 数据获取错误");
return null;
}
GetSchelusClass getSchelusClass;
getSchelusClass = JsonConvert.DeserializeObject<GetSchelusClass>(jsonResult.Item2);
eventId = eventId.Substring(0, 11);
getSchelusClass.schedules.RemoveAll(x => !x.code.Contains(eventId)||!x.status.code.Equals("FINISHED"));
List<ResultCombine> allResults = new List<ResultCombine>();
foreach (var a in getSchelusClass.schedules)
{
string resultHttp = DataPraser.GetScheduleResultsHttp(disciplineCode, a.code);
var jsonResult_mid = DataPraser.Communicable(resultHttp);
if (!jsonResult_mid.Item1)
{
Console.WriteLine("");
Console.WriteLine("");
Console.WriteLine("");
Console.WriteLine(resultHttp);
Console.WriteLine("");
Console.WriteLine("");
Console.WriteLine("");
continue;
}
GetResultClass mid = JsonConvert.DeserializeObject<GetResultClass>(jsonResult_mid.Item2);
if (!mid.results.schedule.status.code.Equals("FINISHED")) continue;
if (mid.results.items.Count > 2)
{
Console.WriteLine($"{http} 该项目数据结构不支持期望");
return null;
}
ResultCombine target = allResults.Find(x => x.stateName.Equals(mid.results.eventUnit.shortDescription));
if (target == null || target.Equals(default(ResultCombine)))
{
target = new ResultCombine() { stateName = mid.results.eventUnit.shortDescription };
allResults.Add(target);
}
NewMatchResult matchResult = new NewMatchResult();
GetResultClass.Result.Item item;
item = mid.results.items[0];
matchResult.competitor1 = new MatchResult.Competitor(item.participant.organisation.code, item.participant.shortName, item.resultData, !string.IsNullOrEmpty(item.resultWLT) &&item.resultWLT.Equals("W"));
item = mid.results.items[1];
matchResult.competitor2 = new MatchResult.Competitor(item.participant.organisation.code, item.participant.shortName, item.resultData, !string.IsNullOrEmpty(item.resultWLT) && item.resultWLT.Equals("W"));
matchResult.startDate = mid.results.schedule.startDate.Substring(0,16);
target.results.Add(matchResult);
}
//排序
allResults.Sort((x, y) => x.results.Count.CompareTo(y.results.Count));
foreach (var a in allResults)
{
a.results.Sort((x, y) => TestCompare(x,y));
}
ResultCombineJsonUse resultCombineJsonUse = new ResultCombineJsonUse();
resultCombineJsonUse.resultCombines = allResults;
File.WriteAllText(savePath, JsonConvert.SerializeObject(resultCombineJsonUse));
return resultCombineJsonUse;
}
对阵图的连线,原本是想使用svg或者 < canvas >
在晋级赛之间连线,但是都比较麻烦,后来尝试使用图片完成连线,但是经过缩放会导致连不上
参考官网使用伪元素设置边框,最终用相似的思路在晋级赛之间填充两个div容器,每个div中包含三个div,使用flex完成布局,加上所需要的边框完成连线,同时使用%和vh,vw单位,防止缩放导致布局混乱
由于获取的数据是通过奥运官网http获取的,但是不同的比赛的数据格式和特殊情况是不同的难以直接通过一两个获取到的数据分析出来,因此在对数据的处理过程中,遇到各种特殊情况,比如比赛取消,选手退赛等,这些特殊情况通常需要对代码进行修改。
我的解决方法是在出现问题的时候,分析错误原因,以此对代码进行修改和完善,暂时没有好的处理方法
222200413:
虽然我之前学过一些前端知识,具有一定基础,但在开发过程中仍然遇到了不少挑战。之前使用的是 JavaScript,这次决定尝试 TypeScript。在编码实现的过程中,发现了TypeScript与 JavaScript 的一些差异,例如类型定义和属性访问,这让我花了不少时间熟悉 TypeScript。此外,我还学习了新工具 Apifox 的使用,发现它大大简化了与后端的对接,并且通过 Apifox 集成的 Mock 功能,能够更方便地进行前端测试。在这次开发过程中,我学习并运用了许多新知识,使我对 Vue 3 的使用以及与后端对接的细节有了更深入的理解。总体而言,收获颇丰。
222200415:
在开发奥运赛事信息系统项目初期,我满怀热情投入开发,但遇到不少挑战。首先,数据获取是一大难题。我从奥运官网通过HTTP请求获取数据,却发现数据格式不统一,数据结构差异大,导致数据解析和处理变得复杂。有时参赛者信息完整但比赛数据缺失,我不得不花费大量时间清洗和补充数据。此外,处理比赛详情时,某些比赛数据缺少比分信息,需要多次访问奥运官方数据,导致系统响应变慢,用户体验下降。为解决这一问题,我引入缓存机制,将已获取数据保存在服务器中,减少后续访问延迟。在整个开发过程中,我不断迭代和优化代码,遇到问题就仔细分析并尝试解决。虽然充满挑战,但每当看到系统正确展示数据、处理特殊情况时,我都感到无比满足和自豪。这个项目让我深刻体会到,软件开发不仅是技术挑战,更是耐心和毅力的考验。
222200413:
通过这次结对作业,我学习了很多新知识
1.能力提升:这次结对作业前端部分主要由我独立完成,这显著提高了我的前端开发能力,尤其是在项目协作和沟通方面的技能。
2.新知识学习:学习了许多新知识,包括 TypeScript 的使用,相较于 JavaScript,我对类型定义和属性访问有了更深入的理解。
3.Apifox 工具的使用:掌握了 Apifox 这一新工具,特别是其 Mock 功能,大大简化了与后端的对接流程,使得前端测试更加高效。
4.Vue Router 的理解:加深了对 Vue Router 的理解,特别是学习了路由守卫的使用,能够在页面跳转之前获取数据,确保数据的正确性和完整性。
5.数据本地持久化:加深了localStorage理解,使用localStorage,实现数据的本地持久化,确保用户在页面刷新或关闭后仍能保留必要的信息。
222200415:
通过这个项目,我收获了以下这些宝贵的经验和技能:
1.数据处理能力:我学会了如何高效地获取、解析和处理数据,特别是在数据格式不统一和存在特殊情况时。
2.问题解决能力:在面对各种问题和挑战时,我学会了如何分析问题原因、制定解决方案,并不断地对代码进行迭代和优化。
3.团队协作与沟通:虽然这个项目主要是我独自完成的,但在与前端开发人员和测试人员的沟通中,我学会了如何更好地协作和沟通,以确保项目的顺利进行。
4.技术视野的拓展:在开发过程中,我接触到了许多新的技术和工具,如HTTP请求、数据缓存等,这些都拓宽了我的技术视野。
5.心态的调整:在面对困难和挑战时,我学会了保持冷静和乐观的心态,相信通过不断的努力和学习,一定能够克服一切困难。
222200413:
在这次结对作业中,我的队友展现了出色的团队协作能力和极强的行动力。他能够迅速定位并获取所需的数据,善于进行信息的筛选和“裁剪”,有效地减少了冗余数据。此外,他非常乐于倾听他人的意见,积极参与讨论,共同解决问题。在面对挑战时,他总是保持积极的态度,一起探讨解决方案,展现出良好的沟通能力和团队精神。能够与他合作完成这次作业,我感到非常高兴,也让我受益匪浅。
222200415:
我的队友展现出了扎实的专业基础和不断学习的态度。他总是愿意倾听他人的意见,能够迅速掌握新技术和工具,并将其有效应用于解决实际问题中。他能熟练运用前端的各种框架,实现流畅友好的交互编码实现,并且在面对复杂的问题时能够迅速分析问题的本质,在结对过程中体现了强大的技术能力。此次结对实践是一次非常愉快和富有成效的经历,期待我们能够继续保持这种积极向上的态度,继续学习进步。