108
社区成员
这个作业属于哪个课程 | https://bbs.csdn.net/forums/2401_CS_SE_FZU?typeId=7771625&category=0 |
---|---|
这个作业要求在哪里 | https://bbs.csdn.net/topics/619333839 |
结对学号 | 222200125 222200305 |
这个作业的目标 | 基于Web技术的网页原型实现、Git协作、项目部署 |
其他参考文献 | https://www.runoob.com/css/css-tutorial.htmlhttps://www.runoob.com/html/html-tutorial.htmlhttps://www.runoob.com/js/js-tutorial.html |
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planing | 计划 | 30 | 30 |
• Estimate | • 估计这个任务需要多少时间 | 30 | 30 |
Development | 开发 | 1990 | 2230 |
• Analysis | 分析 | 150 | 160 |
• Design Spec | 生成设计文档 | 60 | 50 |
• Design Review | • 设计复审 | 30 | 20 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 60 | 80 |
• Design | 具体设计 | 100 | 90 |
• Coding | 具体编码 | 1200 | 1500 |
• Code Review | 代码复审 | 90 | 80 |
• Test | • 测试(自我测试,修改代码,提交修改 | 300 | 250 |
Reporting | 报告 | 130 | 110 |
• Test Repor | 测试报告 | 60 | 50 |
• Size Measurement | • 计算工作量 | 30 | 30 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 40 | 30 |
合计 | 2150 | 2370 |
git记录
首页
:提供巴黎奥运会的精彩时刻、奖牌榜、夺金时刻以及下一届奥运会的相关信息。点击首页,即可跳转到了解更多页面
,深入了解巴黎奥运会。了解更多
:通过图文结合的方式,详细介绍巴黎奥运会的各个方面,让您全面了解赛事信息。奖牌榜
:展示参赛国家的排名、国旗、国家名称,并详细列出各国获得的金、银、铜牌数量及奖牌总数。每日赛程
:自动记录您上次查看的日期,或默认展示7月24日的赛程信息。您可以通过日历选择特定日期,加载对应的赛程数据。点击感兴趣的团体赛,即可查看详细赛程
。详细赛程
:为您展示所选比赛的具体信息,包括小组队伍、比分、开始时间等。同时显示比赛所属的组别,点击组别即可加载并查看该小组的详细数据。对阵图
:通过图表形式直观展示1/4决赛、半决赛和决赛的参赛国家及比赛成绩。鼠标悬停于特定国家时,将高亮显示,帮助您清晰了解各国的比赛情况。在项目启动初期,我们首先进行了细致的需求分析,以确立系统的核心功能。目标是打造一个用户友好的平台,为用户提供丰富的2024巴黎奥运会信息,包括赛事的精彩时刻、奖牌榜、夺金时刻以及下一届奥运会的预告。
需求分析的重点包括:
在设计阶段,我们采取了以下步骤来构建系统:
奖牌榜:包含国家名、国家ID、金牌数、银牌数、铜牌数和总奖牌数的数据模型。
赛程信息:创建了赛事日期、时间、队伍、比分等字段的数据模型。
分别爬取奖牌榜与每日赛程数据到本地的medal.json与schedule目录下的MM-DD.json文件中,如果项目读取不到数据,则重新爬取并且存储到本地的文件下。这样做是为了防止每次都要重新爬取,多人同时访问服务器的时候,服务器的压力会比较大。
而对于详细赛程的数据,网站设置了反爬机制所以使用浏览器的抓包工具,获得性别与大类对应的详细赛况数据。如男子足球,它在每日赛程数据中的大类Id:eventId = "FBLMTEAM11------------------------",经过研究发现后面详细赛程的code将会对应这个数据,但是后面的-数量会存在出入,所以决定,截取eventId前面的字母,以eventId_1/2.json(男1女2)为命名存储到本地目录下。
1)奖牌榜与详细赛程数据预处理
获取奖牌榜数据或者详细赛程仅进行简单的数据获取与数据返回。
2)每日赛程数据预处理
首先由于我们网站面向中国用户,而爬取的数据是基于巴黎时间,所以在接收到前端发来的日期date请求以后,要在保证不是开始日期的情况下,获取前一天的比赛数据,不是比赛结束日期的情况下,获取今天的比赛数据。分别将这两个数据中的比赛开始时间转换为上海时间,与当天日期比较最终获取到当天的数据。
3.前端实现
1)首页
使用 <div class="homepage">
作为主页内容的容器,确保所有内容居中显示。
页面的 <body>
和<html>
元素被设置为 flex 容器,内容垂直排列,且在垂直方向上自动适应高度。
图片使用百分比宽度和自动高度来确保它们在不同设备上保持比例并自适应屏幕宽度。.text-overlay
类用于在第一张图片上添加文本覆盖层,使用绝对定位放置在右上角。
通过 JavaScript 为文本覆盖层中的<span>
元素添加点击事件监听器,当用户点击时,页面会跳转到相应的页面(例如奖牌榜或每日赛程)。
2)奖牌榜
使用<div class="medalpage">
作为奖牌榜页面的容器。
使用 <div class="text-overlay">
在图片上添加可点击的文本覆盖层,提供导航链接。.header
类用于头部图片的样式,使用 background-image 来展示背景图片,并使用 padding 和 color 属性来调整布局和文本颜色。.table
类用于创建奖牌榜数据表格的样式,使用 box-shadow 属性添加阴影效果。.table-header
和 .data-row
类用于控制表格头部和数据行的样式。
3)每日赛程.Competition1
和.Competition2
类用于控制不同类型比赛信息块的样式。.Time
, .Itemcodename
, .Title
, .Awayname
, .Homename
, .Homescore
, .Awayscore
类用于定位和样式化比赛信息。
使用 <div class="calendar">
显示日历,允许用户选择日期。
为日历触发器添加点击事件,实现日历的显示和隐藏。
4)详细赛况.group
类用于控制比赛信息组的样式,包括背景模糊和背景图片。.match-info
和.score-header
类用于定位和样式化比赛信息和比分。.group-buttons
类用于样式化按钮组,.group-button
类用于单个按钮的样式。.match-card
类用于样式化单个比赛结果卡片。
使用 fillMatchResults 函数动态生成比赛结果卡片,并添加到页面中。
为比赛结果卡片添加点击事件,点击时切换选中状态并更新对阵信息。
5)对阵图
使用 <div class="match-component">
创建单个比赛信息块,包括国家旗帜、队名和比分。
使用 <div class="aet">
和 <div class="aet-extended">
展示特殊状态,如加时赛和点球大战。.match-component
和 .match-extended-component
类用于创建比赛信息块,包括背景、国家旗帜、队名和比分。
使用 JavaScript 实现鼠标悬停在国家名称上时,高亮显示所有相同的国家名称。
1)详细赛况数据有反爬机制:通过url获取到的详细赛况数据只有空的html,而通过浏览器则是可以获得json数据的。
2) 父页面每日赛程如何获取子页面日历中用户选择的日期,并且展示在页面的比赛日期,且获取相应的每日赛程数据。
3)默认其他页面跳转到07-24的每日赛程,所以页面默认加载会自动获取07-24的数据。但是在加载完新的日期以后,由于以上设置会又变回07-24。
4)部署的时候无法读取到resources资源包内的文件,而报错。
2)父页面创建dateSelected自定义事件触发器。子页面用户点击相应的日期以后会触发创建dateSelected自定义事件,并将日期放入事件的detail属性中。通过父窗口发送事件。父页面获取数据以后调用fetchMatchesForDate(date)更新日期显示,并且更新页面。
3)使用localStorage本地用户选择过的日期,在页面加载完成以后优先加载用户选择过的日期,否则为默认日期07-24。
4)文件读取采用获取资源文件的输入流class.getResourceAsStream才能读取JAR包中资源。
<div class="homepage">
作为整个页面的容器,包含所有的内容。<img>
标签用于展示页面的图片,其中 id 用于后续的 JavaScript 交互。<div class="text-overlay">
用于在图片上添加文本覆盖层,提供导航链接。
<div class="homepage">
<img class="Frontpage1" id="learnmore" src="image/frontpage.png" alt="frontpage"/>
<div class="text-overlay">
<span class="underline">首页</span>
<span id="medal">奖牌榜</span>
<span id="schedule">每日赛程</span>
</div>
<img class="Frontpage2" id="medal2" src="image/frontpage2.png" alt="frontpage2"/>
<img class="Frontpage3" src="image/frontpage3.png" alt="frontpage3"/>
</div>
使用 document.getElementById 获取元素,并为它们添加点击事件监听器。
当用户点击文本覆盖层上的 <span>
元素时,页面会跳转到相应的页面(例如奖牌榜或每日赛程)。
document.getElementById("medal").addEventListener("click", function() {
window.location.href = "medal.html";
});
document.getElementById("schedule").addEventListener("click", function() {
window.location.href = "DailyFixtures.html";
});
document.getElementById("learnmore").addEventListener("click", function() {
window.location.href = "learnmore.html";
});
document.getElementById("medal2").addEventListener("click", function() {
window.location.href = "medal.html";
});
<div class="medalpage">
作为奖牌榜页面的容器,包含背景图片和文本覆盖层。<img class="medalpage1">
用于展示背景图片。<div class="text-overlay">
用于在背景图片上添加可点击的文本覆盖层,提供导航链接。
<div class="medalpage">
<img class="medalpage1" id="bar" src="image/bar.png" alt="medalpage" />
<div class="text-overlay">
<span id="homepage">首页</span>
<span id="medal">奖牌榜</span>
<span id="schedule">每日赛程</span>
</div>
</div>
<div class="medal-box">
<div class="table">
<div class="table-header">
<div>顺序</div>
<div class="noc">NOC</div>
<div>金</div>
<div>银</div>
<div>铜</div>
<div>总数</div>
</div>
<div id="medalsContainer" class="data-rows"></div>
</div>
</div>
header 类用于样式化页面头部,包括背景图片和文本样式。
table 类用于样式化奖牌榜的表格。
data-row 类用于样式化表格的每一行,包括边框和布局。
.header {
position: relative;
width: 100%;
height: 434px;
background-image: url('./image/bar.png');
background-size: contain;
background-repeat: no-repeat;
background-position: top;
padding: 20px;
color: black;
display: flex;
justify-content: flex-end;
align-items: flex-start;
text-align: end;
}
.table {
background-color: white;
width: 100%;
max-width: 1280px;
margin: 0 auto;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
margin-top: -4px;
}
.data-row {
display: flex;
padding: 10px 0;
justify-content: space-between;
width: 100%;
max-width: 983px;
margin: 0 auto;
border-bottom: 1px solid #f0f0f0;
}
@media (max-width: 1280px) {
.medal-box {
width: 80%;
}
}
fetchMedals 函数用于从服务器获取奖牌榜数据。
使用 fetch API 发送请求,获取 JSON 格式的响应数据。
动态创建 div 元素并填充奖牌榜数据,然后添加到 medalsContainer 容器中。
window.onload 事件确保在页面加载完成后自动获取奖牌榜数据。
function fetchMedals() {
fetch('http://120.46.58.99:9090/medals')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
if (data.code === "200") {
const medalBox = document.getElementById('medalsContainer');
medalBox.innerHTML = '';
const { items } = data.data;
items.forEach(medal => {
const row = document.createElement('div');
row.className = 'data-row';
row.innerHTML = `
<div>${medal.rank}</div>
<div class="noc"><img src="https://gstatic.olympics.com/s1/t_original/static/noc/oly/3x2/180x120/${medal.countryid}.png" alt="${medal.countryname}" style="width:20px; height:15px;"> ${medal.countryname}</div>
<div>${medal.gold}</div>
<div>${medal.silver}</div>
<div>${medal.bronze}</div>
<div>${medal.count}</div>
`;
medalBox.appendChild(row);
});
} else {
alert(data.msg);
}
})
.catch(error => {
console.error('Error fetching medal data:', error);
alert('网络错误或服务器无响应');
});
}
window.onload = function () {
fetchMedals();
};
<div class="container">
作为整个页面的容器,包含所有内容。<div class="date-container">
包含日期显示和日历触发器。<div class="calendar">
包含一个iframe,用于嵌入日历页面。
<div class="container">
<div class="date-container">
<div class="date-rectangle" id="date-rectangle"></div>
<div class="date-image" id="calendar-trigger"></div>
<div id="date-text">比赛日期</div>
</div>
<!-- Calendar Container -->
<div class="calendar" id="calendar">
<iframe src="calendar.html" width="580" height="480" style="border:none;"></iframe>
</div>
<div id="matchesContainer"></div>
</div>
container 设置为相对定位,确保内部元素可以绝对定位。
Competition1 和 Competition2 定义了比赛信息块的样式。
Time, Itemcodename, Title, Awayname, Homename, Homescore, Awayscore 定义了比赛信息的样式。
HomeNoc, AwayNoc 定义了国家旗帜的样式。
.container {
position: relative;
width: 1280px;
height: auto;
margin: 0 auto;
}
.Competition1, .Competition2 {
position: relative;
background: rgba(255, 255, 255, 0.50);
box-shadow: 2px 2px 4px #BED9F3;
border-radius: 15px;
border: 1px #A7A1A1 solid;
margin: 10px auto;
}
.Time, .Itemcodename, .Title, .Awayname, .Homename, .Homescore, .Awayscore {
position: absolute;
color: black;
font-size: 24px;
font-family: Microsoft YaHei;
}
.HomeNoc, .AwayNoc {
position: absolute;
width: 43px;
height: 26px;
left: 200px;
border: 1px solid #ECE4E7;
}
fetchMatchesForDate 函数用于从服务器获取指定日期的比赛数据。
使用 fetch API 发送请求,获取 JSON 格式的响应数据。
updateMatchesContainer 函数用于将获取到的比赛数据动态生成比赛信息块,并添加到页面中。
DOMContentLoaded 事件确保在页面加载完成后自动获取比赛数据。
async function fetchMatchesForDate(date) {
try {
const response = await fetch(`http://120.46.58.99:9090/matchs?date=${date}`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
if (data.code === "200") {
localStorage.setItem('selectedDate', date);
localStorage.setItem('selectedMatches', JSON.stringify(data));
const localDate = new Date(date);
document.getElementById('date-text').textContent = formatDate(localDate);
updateMatchesContainer(data);
} else {
alert(data.msg);
}
} catch (error) {
console.error('There was a problem with the fetch operation:', error);
alert('网络错误或服务器无响应1');
}
}
function updateMatchesContainer(data) {
const matchesContainer = document.getElementById('matchesContainer');
matchesContainer.innerHTML = '';
const matches = data.data.items;
matches.forEach(match => {
const group16 = document.createElement('div');
group16.className = 'Group16';
// ... 创建比赛信息 ...
matchesContainer.appendChild(group16);
});
}
document.addEventListener('DOMContentLoaded', () => {
const storeDate = localStorage.getItem('selectedDate');
const dateToFetch = storeDate ? storeDate : '07-24';
fetchMatchesForDate(dateToFetch);
});
<div class="container">
作为页面的容器,包含所有内容。<div class="group">
包含比赛信息和背景图片。<div class="match-info">
显示对阵双方的信息。<div class="score-header">
显示比分标题。<div class="group-buttons">
用于放置动态生成的比赛阶段按钮。<div class="match-results-wrapper">
和 <div class="match-results">
用于展示比赛结果。<div class="menu">
包含导航菜单。
<div class="container">
<div class="group">
<img src="image/background4.png" alt="Background" style="width: 100%; height: 100%; position: absolute;">
<div class="match-info">
<span id="team1" style="font-weight: 700;">HOME</span>
<span style="font-weight: 400;"> VS </span>
<span id="team2" style="font-weight: 700;">AWAY</span>
</div>
<div class="score-header">比赛成绩</div>
</div>
<div class="group-buttons">
<!-- 动态生成的按钮组 -->
</div>
<div class="match-results-wrapper">
<div class="match-results">
<!-- 动态生成的比赛结果卡片 -->
</div>
</div>
<div class="menu">
<div id="schedule">赛程</div>
<div style="font-weight: 700;">成绩</div>
<div id="medal">奖牌榜</div>
<div id="match-table">对阵表</div>
</div>
<div id="homepage" style="position: absolute; left: 1152px; top: 35px; font-size: 30px;">首页</div>
</div>
页面布局采用 Flexbox 来实现水平和垂直居中。
container 设置固定宽度和高度,用于包含所有内容。
group 设置背景透明度和模糊效果。
match-card 用于展示单个比赛的信息,.dark 类用于高亮选中的比赛卡片。
body, html {
margin: 0;
font-family: 'Microsoft YaHei', sans-serif;
background: #fff;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.container {
width: 1280px;
height: 1016px;
position: relative;
}
.group {
width: 100%;
height: 365px;
position: absolute;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(90px);
}
.match-card {
width: 325px;
height: 190px;
background: #fff;
border: 1px solid #d9d9d9;
position: relative;
padding: 10px;
margin: 0 10px;
cursor: pointer;
}
.match-card.dark {
background: #41484b;
color: white;
}
为首页、赛程、奖牌榜和对阵表添加点击事件,实现页面跳转。
使用 getUrlParam 函数获取 URL 参数。
在页面加载时,根据赛事 ID 获取赛事详情,并动态生成比赛阶段按钮和比赛结果卡片。
document.getElementById('homepage').addEventListener('click', function() {
window.location.href = 'index.html';
});
document.getElementById('schedule').addEventListener('click', function() {
window.location.href = 'DailyFixtures.html';
});
document.getElementById('medal').addEventListener('click', function() {
window.location.href = 'medal.html';
});
document.getElementById('match-table').addEventListener('click', function() {
window.location.href = 'MatchUps.html';
});
function getUrlParam(name) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(name);
}
// 页面加载时自动填充页面
window.onload = async function() {
// params的eventId与gender处理的得到详细赛程的文件名
const eventId = getUrlParam('eventId');
const id = getUrlParam('id');
const gender = getUrlParam('gender')==='M'?'1':'2';
const phaseCode = getUrlParam('phaseCode');
if (eventId) {
const url = `http://120.46.58.99:9090/detail?eventId=${eventId}_${gender}`;
try {
const response = await fetch(url);
if (!response.ok){
throw new Error(`HTTP error! Status: ${response.status}`); // Check for HTTP errors
}
const data = await response.json();
if (data.code === "-2") {
alert(data.msg);
window.history.back();
return;
}
// 根据phaseCode可定位
displayEvent(data,id,phaseCode);
} catch (error) {
alert(error);
console.error('Fetch error:', error);
}
} else {
console.error('Event ID not provided in the URL'); // Handle missing event ID
}
}
<div class="container">
:包含所有内容的容器。<div class="header">
:包含标题和菜单。<div class="stage">
:显示比赛阶段(1/4决赛、半决赛、决赛)。<div class="legend">
:显示比赛结果的缩写说明(AET: 加时赛后,PSO: 点球大战)。<div class="frame">
:包含比赛组件。<div class="match-component">
:单个比赛的组件,显示对阵双方的国旗、队伍名称和比分。<div class="match-extended-component">
:扩展的比赛组件,用于显示决赛和铜牌赛,包含额外的比赛标题。
<div class="container">
<div class="header">
<div class="header-title">
<h2>足球-男子</h2>
<h1>对阵表</h1>
</div>
<div class="menu">
<div class="active">对阵表</div>
<div id="schedule">赛程</div>
<div id="medal">奖牌榜</div>
</div>
<div class="home-button" id="homepage">首页</div>
</div>
<div class="stage">
<div>1/4决赛</div>
<div>半决赛</div>
<div>决赛</div>
</div>
<div class="legend">
<div>AET : 加时赛后</div>
<div>PSO : 点球大战</div>
</div>
<div class="frame">
<!-- Match components go here -->
</div>
</div>
使用Flexbox进行布局,使内容居中。
设置背景图片、字体、颜色等样式。
使用绝对定位来精确放置元素。
设置响应式布局,使网页在不同设备上都能良好显示。
body {
margin: 0;
font-family: Microsoft YaHei;
background: white;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.container {
width: 90%;
max-width: 1280px;
height: auto;
position: relative;
margin-top: -250px;
}
.header {
width: 100%;
height: 350px;
position: relative;
background: url('./image/background2.png') no-repeat center center;
background-size: cover;
}
.menu {
position: absolute;
left: 50%;
top: 200px;
transform: translateX(-50%);
display: flex;
justify-content: space-around;
width: 422px;
height: 50px;
background: rgba(255, 255, 255, 0.8);
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.25);
border-radius: 15px;
padding: 5px 0;
margin-top: 50px;
}
为.team-name和.team-name-extended添加鼠标悬停和移出事件,改变背景颜色和文字颜色,增强用户交互体验。
为#homepage、#schedule和#medal添加点击事件,实现页面跳转功能。
document.querySelectorAll('.team-name, .team-name-extended').forEach(item => {
item.addEventListener('mouseover', function() {
const countryName = this.textContent;
document.querySelectorAll('.team-name, .team-name-extended').forEach(el => {
if (el.textContent === countryName) {
el.style.backgroundColor = 'purple';
el.style.color = 'white';
}
});
});
item.addEventListener('mouseout', function() {
const countryName = this.textContent;
document.querySelectorAll('.team-name, .team-name-extended').forEach(el => {
if (el.textContent === countryName) {
el.style.backgroundColor = '';
el.style.color = 'black';
}
});
});
});
布局与格式与首页相似,不多做赘述
项目伊始,我们团队成员之间进行了充分的沟通,明确了各自的职责和任务。我们意识到,只有每个人都发挥出自己的长处,团队才能高效运转。在结对编程的过程中,我和我的搭档轮流编写代码和审查代码,这种模式让我们能够即时解决问题,同时也确保了代码的质量和一致性。
在编码过程中,我们也明确了代码规范的内容包括命名约定、注释风格和代码结构等。遵循统一的代码规范不仅让代码更加易于阅读和维护,也减少了因风格不一致带来的潜在错误。我们也意识到,良好的代码规范是团队协作的基石。
在项目执行的过程中,我们发现正如构建之道中所说的,代码复审是结对编程中不可或缺的一部分。每次编写完一个功能模块后,最好是要进行代码复审。在这个过程中,不仅要检查代码的功能是否正确,还要检查代码是否遵循了既定的规范,是否存在潜在的性能问题。复审过程中,我们经常能够发现一些在编写时未注意到的问题,这大大提高了代码的可靠性。
同时,在处理和填充页面数据时,合理分析与规划是至关重要的,因为这不仅影响数据的准确性和可用性,还直接影响到用户界面的友好度和用户体验。你需要明确页面需要展示哪些数据以及这些数据的来源。收集数据时,确保数据的完整性和准确性。也要考虑数据的多样性和覆盖范围。
这次项目,对个人而言,可以自己的编程技能,更重要的是,学会了如何与他人合作,如何进行有效的沟通和协调。团队的力量远远超过个人,而良好的团队合作能够创造出超出预期的成果。
在这次的项目中222200305卢禧同学承担了大部分的数据爬取和整理工作,并且帮助进行前后端
的连接,页面的跳转实现等等工作。并且在合作过程中不排斥学习前端的内容,注重细节,为页面布局做了一些细节上的调整,同时在合作过程中和我充分沟通充分交流,让项目得以高效稳定地进行。
在本次项目中,222200125同学负责了初始版本静态页面的构建、页面细节的精细调整,以及对阵图的关键实现等核心任务。尽管这是她首次涉足前端领域,但她展现出了积极的学习态度和迅速的学习能力,很快就掌握了前端页面开发的基本技能。在项目的合作过程中,她每天都主动与团队沟通,确保每日的任务和目标得到明确和及时的执行。即使面临时间安排上的挑战,她也能够灵活地调整自己的工作计划,以确保项目的顺利进行。与222200125同学的合作体验非常愉快,她的专业精神和团队精神为项目的成功贡献了重要力量。