前言
本文将优化 SPA 前端页面逻辑,
- 登录成功后,Login 菜单转变为 Logout;
- 点击 Logout 后,Member 页面也要退出,还原登入前的页面内容
优化前端页面
之前的文章为了方便用了静态方式编写,为了项目日后复杂度提升的时候,能有更好的兼容和实施效率,优化代码让项目更有实施弹性。
头部菜单以 Unordered List 实现
首先,修改 index.html <header><nav>。增加 nav id 及把原先的<div>内容更换成 <ul></ul>
<header>
<nav id="headerNavigationMenu">
<ul></ul>
</nav>
</header>
把原来 index.css 关于 #headerNavigation div 相关的 css 全部删掉,换成如下:
#headerNavigationMenu ul{
float: right;
margin: 0; padding: 0;
margin-right: 20px;
cursor: pointer;
font-weight:700;
color:#555555ee;
}
#headerNavigationMenu ul li, #headerNavigationMenu ul span{
list-style: none;
margin: 0; padding: 0;
float: left;
}
#headerNavigationMenu ul li.selected{
font-weight: 700;
color:#f36c01ee;
}
#headerNavigationMenu ul li.onHover{
font-weight: 700;
color:#000000;
}
最后在 index.js 里面作一下改动。因为修改的代码部分有点多,这里直接上整份 index.js 内容。可以自行比对一下:
$(function() {
// 定义默认内容页面及菜单
const defaultPage = "Login"
// 定义当前选定的菜单ID
let activeNavId = defaultPage
var navIds = {}
navIds.navHomeLink = "Home"
navIds.navAboutUsLink = "AboutUs"
navIds.navLoginLink = "Login"
navIds.navMemberLink = "Member"
// build a navigation menu link array
// to filter out false click events from $(document).click(function(e) {})
// if user not clicking on navigation menu, no change from displaying page
let navItems = []
for (let key in navIds) {
navItems.push(key)
}
for(let key in navIds){
$('#headerNavigationMenu ul').append("<li id='"+key+"'>"+navIds[key]+"</li>")
}
$('#headerNavigationMenu ul li').each(function() {
_this = $(this)
console.log("_this: "+_this.attr('id'))
for (let key in navIds) {
if (_this.attr('id') == key) {
_this.attr('id', key)
if(_this.attr('id') != $('#headerNavigationMenu ul li:last-child').attr('id')) {
_this.after("<span> | </span>")
}
if(_this.attr('id') == "navMemberLink") {
console.log("true")
_this.append(" <i class='bi bi-person-circle'></i>")
}
}
}
// console.log("$('#headerNavigationMenu ul li:last-child') - attr('id'): "+$('#headerNavigationMenu ul li:last-child').attr('id'))
})
// 初始化页面
updateObjectDisplayStatus(navIds,defaultPage)
$(document).click(function(e) {
let clickedElementId = $(e.target).attr('id')
// console.log('clickedELementId: '+clickedElementId)
// console.log('navItems.includes(clickedElementId: '+navItems.includes(clickedElementId))
headerNavigationHover()
// menu only response when user clicked on the header navigation bar AND
// if the menu items is not being selected already
if (navItems.includes(clickedElementId) && (navIds[clickedElementId] != activeNavId)) {
contentTransition(navIds[clickedElementId], activeNavId)
updateObjectDisplayStatus(navIds,navIds[clickedElementId])
activeNavId = navIds[clickedElementId]
}
})
function updateObjectDisplayStatus(navIds, contentId) {
for (let key in navIds) {
//console.log(key+": "+navIds[key])
if (navIds[key] == contentId) {
console.log("clicked "+contentId)
$('#'+contentId).css("display","block")
$('#'+key).removeClass().addClass('selected')
} else {
$('#'+navIds[key]).css("display","none")
$('#'+key).removeClass()
}
}
}
function contentTransition(fadeInComponent, fadeOutComponent) {
$('#'+fadeInComponent).fadeIn()
$('#'+fadeOutComponent).fadeOut()
}
function headerNavigationHover() {
for (let key in navIds) {
$('#'+key).hover(function() {
if (activeNavId != navIds[key]) {
$("#"+key).addClass('onHover')
}
}, function() {
if (activeNavId != navIds[key]) {
$("#"+key).removeClass()
}
})
}
}
// ***********************************
// Submit Login Form
// ***********************************
$('#ssoLoginForm').submit(function(e) {
e.preventDefault()
e.stopPropagation()
$("#loginResultMessage").css("display","none")
let ssoLoginForm = $('#ssoLoginForm')
let ssoLoginFormObject = {}
$('#ssoLoginForm :input').each(function() {
if(this.id === "submitButton") {
// continue
} else {
ssoLoginFormObject[this.id] = this.value
}
})
console.log(JSON.stringify(ssoLoginFormObject))
$.ajax({
type: ssoLoginForm.attr('method'),
url: ssoLoginForm.attr('action'),
contentType: "application/json",
data: JSON.stringify(ssoLoginFormObject),
success: function(data) {
if (data.code === 200) {
console.log("data to F/E: "+ JSON.stringify(data))
console.log("login status: "+data.objectToFE.status)
contentTransition("Member", activeNavId)
updateObjectDisplayStatus(navIds, "Member")
activeNavId = "Member"
$('#Member h1').html(data.objectToFE.status)
} else if (data.code === 401 || data.code === 404) {
$('#loginResultMessage').fadeIn();
$('#loginResultMessage').html(data.description);
$('#loginResultMessage').css("display","block");
}
console.log("JSON.stringify(data): "+JSON.stringify(data))
},
error: function(e) {
console.log("FAILED: JSON.stringify(e): "+JSON.stringify(e));
}
})
})
})
新的 index.js 重复代码的问题都解决了,实施弹性比之前高很多。
后端部分代码不用改。启动后端应用和确保 Nginx 反向代理运作正常后,重新测试一下 SPA 看看修改后的样子和功能有没有问题。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiZpdmLzMTZ0YzY5kDMhZTZ4QzM5M2M0QTZwYmN5IWM4UWMihzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.gif)
测试全部通过,完成优化。
后续更新内容
SPA 加入登录状态 (session id)及相关更新。
区分登录前后状态
登录状态由两个部分组成:
- 后端发送过来的 session id
- 后端发送 session id 到前端 的 timestamp
源代码
关注我并发表不少于10字评论可以获取本文源代码。
码农经典语录
Linus Torvalds
Talk is cheap, show me the code.
蜂窝码农
- DRY Principle (Don’t Repeat Yourself) 是做技术的最大笑话, DRY Principle应该改成 DORY Principle (Do Repeat Yourself)才对
- 没有中国参与的标准,不能称为世界标准*。
俗语
好读书、好记性不如烂笔头