目录
前言
几个月前,笔者发表了一篇爬虫的文章,爬取虎扑的NBA赛事信息(传送门)。但这么多数据只能自己看,当然略显无趣,所以现在笔者在这里基于node.js制作了一个前端网站。该网站实现了用户的注册、登录操作,用户可以登录网站检索想看到的球队的比赛信息,还可以查看各队伍的胜率图、比赛场次图、场均得分图;同时网站还实现了管理员身份,管理员的注册信息从本地数据库插入(想当管理员?得先过了我这关!),管理员可以查看所有用户的注册信息,还可以查看用户的操作日志,还可以停用用户账号。下面就来看一下制作网页的过程吧!
上效果!
<iframe allowfullscreen="true" data-mediaembed="bilibili" id="33PT5F8r-1624693808231" src="https://player.bilibili.com/player.html?aid=376283173"></iframe>NBA爬虫数据前端网页展示
用户注册、登录网页
首先,我们要准备好页面的背景图,还要写好要用的css样式。
*{
margin: 0;
padding: 0;
}
body
{
background-image: url(./pictures/background.jpg);
background-size: cover;
background-position:top;
font-family: sans-serif;
}
.form .login-page
{
width: 360px;
padding: 10% 0 0;
margin: auto;
}
.form
{
position: relative;
top: 150px;
z-index: 1;
background: rgb(205, 186, 226);
max-width: 360px;
margin: 0 auto 100px;
padding: 45px;
text-align: center;
}
.form input
{
outline: none;
background: #f2f2f2;
width: 100%;
border: 0;
margin: 0 0 15px;
padding: 15px;
box-sizing: border-box;
font-size: 14px;
}
.form button
{
text-transform: uppercase;
outline: 0;
background: orange;
width: 100%;
border: none;
padding: 15px;
color: #fff;
font-size: 14px;
cursor: pointer;
transition: .5s;
}
.form button:hover,.form button:active
{
background: green;
border: none;
}
.form .message a
{
color: rgb(240, 17, 17);
text-decoration: none;
}
.register-form
{
display: none;
}
这些是准备工作,然后就可以开始写我们的html网页前端代码了。
前端代码要实现两个模块,登录和注册。
<!--Register-->
<form class="register-form" method="POST" id="register-form" role="form">
<div class="form-group">
<input type="text" ng-model="add_username" tabindex="1" class="form-control" placeholder="User Name">
</div>
<div class="form-group">
<input type="password" ng-model="add_password" tabindex="2" class="form-control" placeholder="Password">
</div>
<div class="form-group">
<input type="password" ng-model="confirm_password" tabindex="2" class="form-control" placeholder="Confirm Your Password">
</div>
<div class="form-group">
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<button type="button" id="register-submit" tabindex="4" class="form-control" ng-click="doAdd()">Register</button>
</div>
</div>
</div>
<p class="massage">Already Registered ? <a href="#">Login</a></p>
</form>
<!--Login-->
<form class="login-form" method="POST" id="login-form" role="form">
<div class="form-group">
<input type="text" ng-model="username" tabindex="1" class="form-control" placeholder="User Name" name="username">
</div>
<div class="form-group">
<input type="password" ng-model="password" tabindex="2" placeholder="Password" name="password">
</div>
<div class="form-group">
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<button type="button" id="login-submit" tabindex="4" class="form-control" ng-click="check_pwd()">Login</button>
</div>
</div>
</div>
<p class="massage">Not Registered ? <a href="#">Register</a></p>
<p class="massage alert alert-warning" ng-if="msg && msg!='ok'">
<a href="#" class="close" data-dismiss="alert">×</a>
<strong>警告!</strong>{{msg}}
</p>
</form>
然后我们需要在同一个页面进行登录和注册的切换,因此用到了这一段代码(前提要引入jQuery模块):
$('.massage a').click(function() {
$('form').animate({height:"toggle", opacity:"toggle"}, "slow");
});
不说了,咱们直接上效果图:
由于万能的css,两个界面切换地很流畅,鼠标悬停在按钮上会有高亮。然后通过angular和express实现前后端的连接,将用户的注册信息写入mysql数据库中,也可以从数据库验证用户的密码。
检索球队信息
成功登录后,我们会进入competition.html页面。
点击检索,会出现如下画面。
这里做了一个html元素的隐藏和显示。代码如下:
<div ng-show="isShow" style="width: 1300px;position:relative; top:70px;left: 80px">
<!-- 查询页面-->
<div ng-include="'search.html'"></div>
</div>
用show属性的false和true来控制标签的显示与否。
后端可以捕获用户输入的球队名称,然后传到mysql检索。
// 查询数据(根据主客场队伍检索)
$scope.search = function () {
var team = $scope.team;
var oppo = $scope.oppo;
// 用户可能一个队伍名都不输入,默认查找全部数据
var myurl = `/competitions/search?t1=${team}&op=${oppo}`;
$http.get(myurl).then(
function (res) {
if(res.data.message=='data'){
$scope.isisshowresult = true; //显示表格查询结果
$scope.initPageSort(res.data.result)
}else {
window.location.href=res.data.result;
}
},function (err) {
$scope.msg = err.data;
});
};
//另一个文件↓
router.get('/search', function(request, response) {
console.log(request.session['username']);
//sql字符串和参数
if (request.session['username']===undefined) {
response.json({message:'url',result:'/index.html'});
}else {
var param = request.query;
compDAO.search(param,function (err, result, fields) {
response.json({message:'data',result:result});
})
}
});
//另一个文件↓
search :function(searchparam, callback) {
// 组合查询条件
var sql = 'select team,ground,scores,record,oppo,ground_op,'+
'opScores,opRecord,time,timeCost,location,'+
'numsOfAd,shoot,three_point,penalty,frontcourt,'+
'backcourt,backboard,assist,foul,steal,mistake,cover from myfetches ';
if(searchparam["t1"]!="undefined"){
sql +=(`where team like '%${searchparam["t1"]}%'`);
if(searchparam["op"]!="undefined"){
sql +=(`AND oppo like '%${searchparam["op"]}%' `);
};
}
else if(searchparam["op"]!="undefined"){
sql +=(`where oppo like '%${searchparam["t1"]}%' `);
};
sql+=';';
pool.getConnection(function(err, conn) {
if (err) {
callback(err, null, null);
} else {
conn.query(sql, function(qerr, vals, fields) {
conn.release(); //释放连接
callback(qerr, vals, fields); //事件驱动回调
});
}
});
}
检索支持双队伍检索,即固定主场队伍和客场队伍(有相对顺序关系)。因此在mysql检索语句中要加一系列条件判断语句,组合成mysql命令行的检索指令。
最后的效果图如下,结果实现了分页处理,而且还可以根据比赛时间排序(阿杜,你没有输!):
图表制作:
点击图片,会下拉三个图表选项,分别是柱状图、饼状图、折线图。图表全部依赖ECharts制作。
柱状图展示的是各队伍的胜率信息:
胜率信息由爬取的record是字符串类型,所以要用parInt函数对record战绩信息的字符字串操作,得到胜场和败场,从而计算出胜率。
$scope.histogram = function () {
$scope.isShow = false;
$http.get("/competitions/histogram")
.then(
function (res) {
if(res.data.message=='url'){
window.location.href=res.data.result;
}else {
let xdata = [], ydata = [], newdata;
res.data.result.forEach(function (element) {
// "record":"XX胜利XX失败"
if(element["record"].length > 4 && xdata.indexOf(element["team"]) == -1)
{
var win = parseInt(element["record"].substr(0, 2));//将字符串数据转化为数字,计算胜率
var lose = parseInt(element["record"].substr(3, 2));
var rate = win / (win + lose);
ydata.push(rate);
xdata.push(element["team"]);
}
});
newdata = {"xdata": xdata, "ydata": ydata};
var myChart = echarts.init(document.getElementById('main1'));
// 指定图表的配置项和数据
var option = {
title: {
text: 'NBA各队伍胜率'
},
tooltip: {},
legend: {
data: ['队伍']
},
xAxis: {
data: newdata["xdata"],
axisLabel: {
show: true,
interval:0
}
},
yAxis: {},
series: [{
name: '胜率',
type: 'bar',
data: newdata["ydata"],
barGap: '80%',
barWidth: '50%'
}]
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
}
},
function (err) {
$scope.msg = err.data;
});
};
饼状图展示的是各队伍的比赛场数
比赛的场次可以直接用mysql中的内置aggregate function COUNT() 得到。
"select team as x,count(team) as y from myfetches group by team;"
然后再将得到的数据作为pie图的数据就好了。
$scope.pie = function () {
$scope.isShow = false;
$http.get("/competitions/pie").then(
function (res) {
if(res.data.message=='url'){
window.location.href=res.data.result;
}else {
let newdata = [];
res.data.result.forEach(function (element) {
newdata.push({name: element["x"], value: element["y"]});
});
var myChart = echarts.init(document.getElementById('main1'));
var app = {};
option = null;
// 指定图表的配置项和数据
var option = {
title: {
text: '各队伍比赛场数',
x: 'center'
},
tooltip: {
trigger: 'item',
formatter: "{a} <br/>{b} : {c} ({d}%)"
},
legend: {
orient: 'vertical',
left: 'left',
// data: ['直接访问', '邮件营销', '联盟广告', '视频广告', '搜索引擎']
},
series: [
{
name: '访问来源',
type: 'pie',
radius: '55%',
center: ['50%', '60%'],
data: newdata,
itemStyle: {
emphasis: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
// myChart.setOption(option);
app.currentIndex = -1;
setInterval(function () {
var dataLen = option.series[0].data.length;
// 取消之前高亮的图形
myChart.dispatchAction({
type: 'downplay',
seriesIndex: 0,
dataIndex: app.currentIndex
});
app.currentIndex = (app.currentIndex + 1) % dataLen;
// 高亮当前图形
myChart.dispatchAction({
type: 'highlight',
seriesIndex: 0,
dataIndex: app.currentIndex
});
// 显示 tooltip
myChart.dispatchAction({
type: 'showTip',
seriesIndex: 0,
dataIndex: app.currentIndex
});
}, 1000);
if (option && typeof option === "object") {
myChart.setOption(option, true);
}
;
}
});
};
折线图展示的是各队伍的平均得分
效果如下:
与饼状图同理,平均得分也可以由AVG()函数得到。
"select team as x,avg(scores) as y from myfetches group by team;"
得到数据后,便将数据加入ECharts组件即可展示折线图。
$scope.line = function () {
$scope.isShow = false;
$http.get("/competitions/line").then(
function (res) {
if(res.data.message=='url'){
window.location.href=res.data.result;
}else {
var myChart = echarts.init(document.getElementById("main1"));
let teams = [];
let avgScores = [];
res.data.result.forEach(function(element) {
teams.push(element["x"]);
avgScores.push(element["y"]);
});
option = {
title: {
text: '各队伍平均得分'
},
xAxis: {
type: 'category',
data: teams,
},
yAxis: {
type: 'value'
},
series: [{
data: avgScores,
type: 'line',
itemStyle: {normal: {label: {show: true}}}
}],
};
if (option && typeof option === "object") {
myChart.setOption(option, true);
}
}
});
};
每次展示图表之前,都先初始化前端的main1标签,避免多张图重叠,然后再展示当前的图片。
管理员操作的实现
我们希望管理员有以下权限:查看所有用户的注册信息以及用户的操作日志,还有停用用户账号。
管理员的注册当然不能写在网页前端,不然人人都可以有管理他人账号的权力了。因此管理员的账号必须由后台直接在mysql中插入。
管理页面的前端和用户登录的前端类似。
当然需要在app文件中加入一个新的路由地址,一系列数据库操作也要和user的操作分离开来(这得创建好多新文件,头秃了......)。
管理员成功登录后,会出现如下界面。
页面的查看选项卡有两个选项,用户注册信息与操作日志浏览、停用用户。因为前者只是实现mysql数据的展示,而后者需要管理员输入,并对数据库信息进行修改,因此我将二者分开。
进入用户注册信息与操作日志浏览选项卡,会出现如下画面。
管理员可以点击两个按钮选择要浏览的信息。
(用户注册信息)
(用户操作日志)
这里的前端实现也与前面的competition类似,显示一个页面时隐藏其他页面。数据展示也实现了分页处理和按时间顺序排序的操作。
点击停用用户按钮,会出现如下画面。
管理员输入要停用的用户名,点击确定按钮,即可删除要删除的用户。
停用成功后,会alert一个弹窗,告诉管理员停用成功。
我们再查看用户注册信息,发现已经没有'mr_king'的注册信息了。
再到登陆界面用mr_king的账号密码登录,也无法登录。
前端页面显示用户不存在。
结语
这次前端网页制作的过程我无疑是收获满满的:学会了很多mysql的命令、更加熟练地使用路由实现前后端交互、html的设计也愈发熟练。希望这个网页能为我的Web编程课提交一份满意的答卷。