这个作业属于哪个课程 | 福州大学-202302软件工程实践 |
---|---|
这个作业要求在哪里 | 结对第二次作业——编程实现 |
结对学号 | 222100122 & 222100119 |
这个作业的目标 | 采用web技术来实现原型中的功能 |
其他参考文献 | 《构建之法》、React中文文档、Ant Design |
目录
一、git仓库链接和代码规范链接
二、PSP表格
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 20 |
• Estimate | • 估计这个任务需要多少时间 | 20 | 30 |
Development | 开发 | 840 | 980 |
• Analysis | • 需求分析 (包括学习新技术) | 180 | 300 |
• Design Spec | • 生成设计文档 | 35 | 50 |
• Design Review | • 设计复审 | 15 | 10 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 25 | 40 |
• Design | • 具体设计 | 45 | 55 |
• Coding | • 具体编码 | 360 | 490 |
• Code Review | • 代码复审 | 15 | 15 |
• Test | • 测试(自我测试,修改代码,提交修改) | 55 | 115 |
Reporting | 报告 | 140 | 135 |
• Test Report | • 测试报告 | 125 | 105 |
• Size Measurement | • 计算工作量 | 15 | 15 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 20 | 15 |
合计 | 1235 | 1445 |
三、项目访问链接
四、成品展示
4.1、导航栏
导航栏包括奖牌榜、选手信息、每日赛程、详细赛况、了解更多,用户点击相应的按钮分别可以跳转到对应的页面。除此之外,点击导航栏上的图标可以跳转到首页。
4.2、首页
首页用于展示游泳锦标赛的基本信息和亮点概览。用户通过点击按钮可以跳转到相应的官网。首页的设计主要分为首页大图、赛事简介、赛事亮点概览。
4.3、选手信息
选手信息整体上分为大标题栏以及展示选手信息的表格。
表格支持图片预览、表格内容的滚动显示、分页以及选手详细信息的浏览。
4.4、奖牌榜
奖牌榜采用与选手信息相似的设计方式,整体上分为大标题栏与参赛国获奖情况的表格,支持分页。直观友好地展示各个参赛国的获奖情况。
4.5、每日赛程
每日赛程页面采用瀑布流的设计方式展示每一天的赛事,显示比赛类型和比赛时间,对于决赛予以突出显示。支持点击查看详细赛况。
4.6、详细赛况
详细赛况页面展示比赛的成绩,包含本场比赛参赛选手,选手排名,比赛积分,落后积分等。页面整体上分为新闻栏、赛事信息栏、用于展示选手信息的表格。
表格支持图片预览、滚动查询、分页。
4.7、了解更多
了解更多页面介绍世界游泳锦标赛的举办背景,图文并茂,使平台更具吸引力,引起人们对世界游泳锦标赛的兴趣。用户可以通过点击按钮浏览相应的网站来了解其它信息。
4.8、页脚
页脚展示相应的信息以及作者。
五、结对讨论过程描述
刚开始拿到题目后,我们就开始针对所采用的技术和分工进行了讨论。我们一起分析了题目要求,再考虑到自身技术水平所限,我们决定采用纯前端的方式实现本次作业的需求。
但前端方面我们只会之前Web课程上学习过的html、css、javascript,我们就想着趁这个机会多学习一点新技术,扩充自己的技术储备,后来通过查阅相关资料以及请教周围同学等方式,我们就选择将React作为自己学习的前端框架。
分工方面由洪冠诚同学负责每日赛程、详细赛况、了解更多页面的编写。由柯昊旸同学负责首页、奖牌榜、选手信息页面的编写。
在合作的过程中,遇到问题我们通过积极交流和讨论、查阅网上资料、请教周围同学等方式,共同克服了困难。由于我们是在一个宿舍的舍友,大多是线下交流,因此聊天记录大多只有一些简要的讨论和交流。以下是部分聊天记录。
六、设计实现过程
6.1、功能结构图
6.2、设计实现过程
基本功能设计过程:
奖牌榜:使用了antd库中的Table组件来实现表格展示,并且通过数组自定义了表格的列和数据源,利用Table组件实现分页和滚动。
选手信息:同样结合antd库中的Table组件实现自定义表格组件,设置表格的列和数据源,通过Link组件实现点击时跳转至选手详细信息的页面。
每日赛程:通过div元素设计好布局,其中嵌套多级div元素实现赛程的布局排版,通过引入react的useNavigate实现点击时跳转至详细赛况页面。
详细赛况:利用div元素设计好布局,复用之前设计好的表格组件进行实现,并结合css调整样式。
扩展功能设计过程:
首页:通过div元素设计好大体的布局框架,然后引入React的antd库的Button和SearchOutlined等UI组件进行快速设计。并使用memo来提高性能,避免不必要的渲染更新。
了解更多:通过div元素布局以及引入antd库的Image等组件实现。
表格结合antd库中的Table组件自带的属性进行设置。
6.3、遇到的问题及解决方式
问题:针对服务器部署,我们遇到了以下问题:
Node.js版本与服务器glic环境不兼容,同时与es6和jsx语法也存在兼容性问题。为解决此问题,我们通过安装指定版本的Node.js环境来达成兼容。
服务器防火墙问题:系统自带的防火墙和阿里云安全组的端口开放设置存在问题,导致使用nmap扫描时无法发现开放的端口。对此,我们采取了正确配置安全组和firewalld的方法来解决问题。
解决方案: 通过查阅网上资料以及咨询有部署服务器经验的同学,我们选择通过宝塔面板(一个成熟的现有部署环境)来提供稳定的网站访问体验。
问题:部署完服务器后,发现访问链接时显示空白。
解决方案:刚开始以为是自己部署服务器时出现了问题,在经过一个小时的各种尝试后,发现是路由设置没有设置好,导致首页链接无法正常跳转,在增设了针对默认访问页面的路由之后,问题成功解决。
6.4、扩展功能实现情况
在第一次结对作业原型的基础上,增加了表格滚动、分页、图片预览、选手详细信息查看等功能。
实现了首页和了解更多页面。
七、代码说明
7.1、RouterComponent.jsx
我们自定义了一个React应用的路由组件(RouterComponent),并引入所需的库和组件(包括一系列自定义组件),代码使用React Router DOM进行路由导航,并结合Context API(通过LayoutContext)来统一管理页面的头部(Header)和底部(Footer)布局。
其中,LayoutContext是一个React上下文,它被用来共享布局组件(即Header和Footer)。
而LayoutProvider组件使用LayoutContext.Provider来提供上下文值。这个值是一个对象,包含header和footer两个属性,分别对应Header和Footer组件。
PageLayout组件使用useContext Hook 来消费LayoutContext中的值,然后渲染header,children和footer。这样,每个页面都会有一个通用的头部和尾部。
RouterComponent组件使用React Router的Router,Routes和Route组件来定义路由规则。所有的Route都被包裹在Routes组件中,而Routes组件又被包裹在LayoutProvider组件中,这样每个页面都可以访问到LayoutContext的值。
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { createContext, useContext } from "react";
import Header from "./Header";
import Footer from "./Footer";
import EveryDay from "./EveryDay";
import HomePage from "./HomePage";
import More from "./More";
import Detail from "./detail";
import AtheleteInfo from "./AtheleteInfo.jsx";
import Medal from "./Medal.jsx";
const LayoutContext = createContext();
function LayoutProvider({ children }) {
return (
<LayoutContext.Provider value={{ header: <Header />, footer: <Footer /> }}>
{children}
</LayoutContext.Provider>
);
}
function PageLayout({ children }) {
const { header, footer } = useContext(LayoutContext);
return (
<div>
{header}
<main>{children}</main>
{footer}
</div>
);
}
function RouterComponent() {
return (
<Router>
<LayoutProvider>
<Routes>
{/* 在这里添加接下来的页面 */}
<Route
path="/"
element={
<PageLayout>
<HomePage />
</PageLayout>
}
/>
<Route
path="/HomePage.jsx"
element={
<PageLayout>
<HomePage />
</PageLayout>
}
/>
<Route
path="/Medal.jsx"
element={
<PageLayout>
<Medal />
</PageLayout>
}
/>
<Route
path="/AtheleteInfo.jsx"
element={
<PageLayout>
<AtheleteInfo />
</PageLayout>
}
/>
<Route
path="/everyday"
element={
<PageLayout>
<EveryDay />
</PageLayout>
}
/>
<Route
path="/detail"
element={
<PageLayout>
<Detail />
</PageLayout>
}
/>
<Route
path="/more"
element={
<PageLayout>
<More />
</PageLayout>
}
/>
{/* 如果还有其他页面,可以在这里添加 */}
</Routes>
</LayoutProvider>
</Router>
);
}
export default RouterComponent;
7.2、AthleteInfoTable.jsx
我们定义了一个名为columns的数组,这个数组用于配置一个antd库中的Table组件,使其能够展示运动员的信息。
在render函数中,使用模板字符串和变量来动态生成单元格的内容。例如,通过访问record.flagUrl来显示国旗图片,通过访问record.athleteName来显示选手姓名。最后一列的render函数使用了Link组件,用于在页面内创建可点击的链接,以查看运动员的详细信息。
import {Table} from 'antd';
import { Image, Space } from 'antd';
import './RankTable.css';
import {RightSquareOutlined} from "@ant-design/icons";
import {Link} from "react-router-dom";
const columns = [
{
title: '国家或地区',
dataIndex: 'country',
key: 'country',
render: (text, record) => (
<Space size="small">
{/* 显示国旗图片 */}
<Image src={record.flagUrl} alt={`国旗 - ${record.countryName}`} width={20} height={20} />
{/* 显示国家名称 */}
{record.countryName}
</Space>
),
},
{
title: '选手',
dataIndex: 'athlete',
key: 'athlete',
render: (text, record) => (
<Space size="small">
{/* 显示选手图片 */}
<Image
src={record.photoUrl}
alt={`选手照片 - ${record.athleteName}`}
width={40}
height={40}
style={{borderRadius: '50%'}}
/>
{/* 显示选手姓名 */}
{record.athleteName}
</Space>
),
},
{
title: '性别',
dataIndex: 'gender',
key: 'gender',
},
{
title: '出生日期',
dataIndex: 'DOB',
key: 'DOB',
},
{
title: '项目',
dataIndex: 'discipline',
key: 'discipline',
},
{
title: '查看详细信息',
dataIndex: 'details',
key: 'details',
render: (text, record) => (
<Space size="small">
<Link to={record.infoUrl}>
<RightSquareOutlined style={{ fontSize: '24px' }}/>
</Link>
</Space>
),
},
];
7.3、EveryDay.jsx
由于代码较长,这里选取比较有代表性的一个部分进行说明。这段代码首先定义了一个父div块,内部为纵向布局,内容部分再定义两个子div块,子div块内部横向排列,用于容纳比赛项目,比赛项目的展示部分再使用div块纵向排列展示比赛信息。
对于决赛部分使用css样式进行突出显示,最后定义事件处理函数在用户点击相应比赛区块时跳转至相应的详细赛况页面。
<div className="everyday-vertical everyday-block2Item">
{/* 18号上半 */}
<span className="date">Thursday 18th January</span>
<div className="everyday-horizontal everyday-container">
<div className="everyday-vertical everyday-wrapper everyday-wrapper1">
<div className="everyday-iconDownWrapper">
<img
className="everyday-iconDown"
src={img_swim}
alt="img_swim.png"
/>
</div>
<span className="womenSpringboard">Women 1m Springboard</span>
<span className="everyday-time">时间:5:00 PM</span>
<div className="everyday-horizontal everyday-group">
<span className="everyday-tag">比赛类型</span>
<span className="everyday-eventType">Preliminaries</span>
</div>
</div>
<div className="everyday-vertical everyday-wrapper everyday-wrapper1">
<div className="everyday-iconDownWrapper">
<img
className="everyday-iconDown"
src={img_swim}
alt="img_swim.png"
/>
</div>
<span className="menSpringboard">Men 1m Springboard</span>
<span className="everyday-time">时间:5 :00 PM</span>
<div className="everyday-horizontal everyday-group">
<span className="everyday-tag">比赛类型</span>
<span className="everyday-eventType">Preliminaries</span>
</div>
</div>
</div>
{/* 18号下半 */}
<div
className="everyday-horizontal everyday-container"
style={{ marginTop: "44px" }}
>
<div className="everyday-horizontal everyday-wrapper2">
<div className="everyday-vertical everyday-group1">
<div className="everyday-iconDownWrapper">
<img
className="everyday-iconDown"
src={img_swim}
alt="img_swim.png"
/>
</div>
<span className="menSpringboard1">Men 1m Springboard</span>
<span className="everyday-time">时间:10:00 PM</span>
<div
className="everyday-horizontal everyday-group"
style={{ width: "343px" }}
>
<span className="everyday-tag">比赛类型</span>
<span className="finals">Finals</span>
<img
className="finalIcon"
src={img_final}
alt="img_final.png"
/>
</div>
</div>
</div>
<div className="everyday-vertical everyday-wrapper3">
<div className="everyday-iconDownWrapper">
<img
className="everyday-iconDown"
src={img_swim}
alt="img_swim.png"
/>
</div>
<span className="menSynchronised">Men 3m Synchronised</span>
<span className="everyday-time">时间:10:00 PM</span>
<div
className="everyday-horizontal everyday-group"
style={{ width: "343px" }}
>
<span className="everyday-tag">比赛类型</span>
<span className="finals">Finals</span>
<img className="finalIcon" src={img_final} alt="img_final.png" />
</div>
</div>
<div
className="everyday-vertical everyday-wrapper3"
style={{ right: "0px" }}
onClick={handleClick}
>
<div className="everyday-iconDownWrapper">
<img
className="everyday-iconDown"
src={img_swim}
alt="img_swim.png"
/>
</div>
<span className="womenSpringboard1">Women 1m Springboard</span>
<span className="everyday-time">时间:10:00 PM</span>
<div
className="everyday-horizontal everyday-group"
style={{ width: "343px" }}
>
<span className="everyday-tag">比赛类型</span>
<span className="finals">Finals</span>
<img className="finalIcon" src={img_final} alt="img_final.png" />
</div>
</div>
</div>
</div>
7.4、Header.jsx
React组件HoverToBlueLink接收一个路径to和子元素作为输入。当鼠标悬停在组件生成的链接上时,链接颜色会变为蓝色,鼠标离开后恢复原色。组件内部通过状态管理useState实现这一交互效果。最后,该组件返回一个Link组件,可以通过点击跳转到对应页面。
Header组件通过div元素包含多个HoverToBlueLink组件,用于渲染页面顶部的导航栏。
import "./Header.css";
import img_0 from "./assets/img_0.png";
import img_1 from "./assets/bars-solid.svg";
import { Link } from "react-router-dom"; // 导入Link组件
import React, { useState } from "react";
const HoverToBlueLink = ({ children, to }) => {
const [isHovered, setIsHovered] = useState(false);
const handleMouseEnter = () => setIsHovered(true);
const handleMouseLeave = () => setIsHovered(false);
return (
<Link
to={to} // 指定链接路径
style={{
color: isHovered ? "#2ce0fd" : "inherit",
textDecoration: "none", // 移除下划线
margin: "3px",
}}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{children}
</Link>
);
};
export default function Header() {
return (
<div className="header-bar">
<HoverToBlueLink to="/HomePage.jsx">
<img id="icon-1" alt="img_0" src={img_0} />
</HoverToBlueLink>
<div id="icon-2">
<img id="icon-2" alt="icon-2" src={img_1} />{" "}
</div>
<div className="right-aligned-tags">
<HoverToBlueLink to="/Medal.jsx">
{" "}
<span id="tag-0" className="tag">
奖牌榜
</span>
</HoverToBlueLink>
<HoverToBlueLink to="/AtheleteInfo.jsx">
{" "}
<span id="tag-1" className="tag">
选手信息
</span>
</HoverToBlueLink>
<HoverToBlueLink to="/everyday">
{" "}
<span id="tag-2" className="tag">
每日赛程
</span>
</HoverToBlueLink>
<HoverToBlueLink to="/detail">
{" "}
<span id="tag-3" className="tag">
详细赛况
</span>
</HoverToBlueLink>
<HoverToBlueLink to="/more">
{" "}
<span id="tag-4" className="tag">
了解更多
</span>
</HoverToBlueLink>
</div>
</div>
);
}
八、心路历程和收获
222100122洪冠诚:
在这次React项目实践中,我深入理解了组件化开发的理念,并通过与柯昊旸同学的结对编程,不仅提升了自己的技术实践能力,更学会了如何更好地团队协作,解决实际问题。在遇到困难时,我们互相支持、共同探讨,这种思维碰撞让我受益匪浅。此外,通过这次作业我也学习使用了git管理项目的更多功能,对git的使用更加熟悉了。
222100119柯昊旸:
本次结对编程过程中,我深感合作的重要性。与洪冠诚同学一起攻克项目难关,我体会到了理论与实践相结合的价值所在。他在项目设计和逻辑梳理方面的严谨态度对我影响颇深,我们一起面对挑战,共享成功,这样的经历无疑提升了我的编程技巧和解决问题的能力。
九、评价结对队友
222100122洪冠诚对222100119柯昊旸的评价:柯昊旸同学的动手实践能力和学习新技术的速度都比我强,经常我在犹犹豫豫不知道怎么下手寻找切入点的时候,他总是能很快地找到相应的解决方案并且实现它。我非常欣赏柯昊旸同学出色的动手实践能力和敏捷的思维方式,他的敢想敢做的行事风格总是能有效推动我们的项目进度,期待未来能够有再次与他合作的机会。
222100119柯昊旸对222100122洪冠诚的评价:洪冠诚同学在项目中展现出极高的逻辑思维能力和扎实的基础知识,他对React框架的理解深入且独到,总能在项目架构层面提供关键性的建议。同时,他的耐心与细致也给我留下了深刻印象,在我遇到困惑时,他总能耐心指导并帮助我理清思路。与洪冠诚的合作让我看到了自己需要提升的地方,同时也从他身上学到了很多宝贵的经验,希望将来还有机会能继续携手完成更多的项目。