注:本文没有展示全部代码,部分样式没有写上,只挑拣部分重要功能代码详解
课程详情请见千锋教育PC端原生JavaScript项目案例实战开发,高含金量web前端项目教学
需求分析
登录界面
能够实现正常用户上传文章账号,以及后台管理账号
在登录进去时需要动态加载,避免安全隐患
管理界面
每个功能模块都能单独展示自己的内容
主页面
动态加载功能而不是多个页面拼凑
能够实现通过后台的操作,更新文章改变主页面的显示
核心功能
登录
利用强大的json serve构建数据存放网址
可以实现管理员登陆和普通登录
动态加载HTML
利用localStorage判断是否有token属性来确定是否加载进主页面
导航栏
点击退出后拿出token属性,防止返回
通过拿取数据获得用户名并显示
侧边栏
每个选项都能展示自己的模块内容
bs样式覆盖
首页模块
每个功能分别做一个页面(个人感觉不太好,一个页面写多个模块功能感觉更简便)
添加用户
输入用户信息,点击button获取并保存
监听事件
照片地址的转换、获取
用户列表
显示所有已经创建的普通账户,并且能进行编辑
DOM操作
动态创建表格
编辑用户
能够打开一个框,里面显示着用户的信息(modal框)
删除用户
再次利用modal块写出是否确定的框
创建新闻
富文本的工具栏和编辑器
实现技术
登录
<style>
html,body{
height: 100%;
}
body{
background-color: #2d3a4b;
display: flex;
justify-content: center;
align-items: center;
color: white;
.logincontainer{
background-color: rgba($color:#000000,$alpha:0.7);
height:400px;
width:600px;
padding:50px;
}
}
</style>
<div class="logincontainer">
<h2>锋企网站后台管理系统</h2>
<form id="loginform">
<div class="mb-3">
<label for="username" class="form-label">用户名</label>
<input type="text" class="form-control" id="username" aria-describedby="emailHelp">
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control"
id="password">
</div>
<button type="submit" class="btn btn-promary">登录</button>
<div id="emailHelp" class="form-text">用户名密码不匹配</div>
</form>
<script>
const loginform=document.querySelector('#loginform')
loginform.onsubmit=function(evt){
evt.preventDefault()//组织点击刷新的默认行为
//正常 post请求
//json-server get获取 post添加 put修改 delete删除
let res = await fetch(`http://localhost:3000/users?username=${username.value}password=${password.value}`).then(res=>res.json())
if(res.length>0){
location.href="/admin/views/home/index.html"
}else{
console.log('失败')
}
}
</script>
</div>
动态加载
//登录页面
if(res.length>0){
localStorage.setItem("token",JSON.stringify({
...res[0]
password:"***"//避免密码放在本地被窃取
}))
location.href="/admin/views/home/index.html"
}else{
console.log('失败')
loginwarning.style.display="block"
}
//主页面
if(localStorage.getItem("token")){
}else{
location.herf="/admin/views/login/index.html"
}
导航栏
<html>
<nav class="navbar navbar-light bg-light">
<div class="container-fulid">
<a class="navbar-brand">锋企网站后台管理系统</a>
<div>
<img src="" alt="" id="topbar-photo" style="width: 40px;">欢迎
<span id="currentUsername">aaaa</span>
回来
<button type="button" class="btn btn-danger" id="exut">退出</button>
</div>
</div>
</nav>
</html>
<script>
function renderTopbar(user){
let photo=document.querySelector("#topbar-photo")
let currentUsername=document.querySelector("#currentUsername")
let exit =document.querySelector("#exit")
photo.src=user.photo
currentUsername.innerHTML=user.username
exit.onclick=function(){
localStorage.removeItem("token")
location.href="/admin/views/login/index.html"
}
}
</script>
侧边栏
<ul class="nav flex-column" style="margin-top: 10px;">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#" id="sidemenu-home">首页</a>
</li>
<li class="nav-item">
<div class="accordion" id="accordionExample">
<div class="accordion-item">
<h2 class="accordion-header" id="headeringOne">
<button class="accordion-header" id="headingOne" data-bs-toggle="collapse" data-ba-target="#user-manage" aria-expanded="true" aria-controls="collapseOne">用户管理</button>
</h2>
<div id="user-manage" class="accordion-collapse collapse show" aria-labelledby="headingOne" data-bs-parent="#accordionExample">
<div class="accordion-body">
<a href="#" class="nav-link active" aria-current="page" id="sidemenu-addUser">添加用户</a>
<a href="#" class="nav-link active" aria-current="page" id="sidemenu-userlist">用户列表</a>
</div>
</div>
</div>
</div>
</li>
<li class="nav-item">
<div class="accordion" id="accordionExample">
<div class="accordion-item">
<h2 class="accordion-header" id="headeringOne">
<button class="accordion-header" id="headingOne" data-bs-toggle="collapse" data-ba-target="#news-manage" aria-expanded="true" aria-controls="collapseOne">新闻管理</button>
</h2>
<div id="news-manage" class="accordion-collapse collapse show" aria-labelledby="headingOne" data-bs-parent="#accordionExample">
<div class="accordion-body">
<a href="#" class="nav-link active" aria-current="page" id="sidemenu-addNews">创建新闻</a>
<a href="#" class="nav-link active" aria-current="page" id="sidemenu-NewsList">新闻列表</a>
</div>
</div>
</div>
</div>
</li>
</ul>
<style>
.accordion-button:not(.collapsed){
color: black;
background-color: white;
box-shadow: none;
}
.accordion-item{
border: 0;
}
.nav-link{
color: black;
}
.accordion-button:focus{
box-shadow: none;
}
</style>
首页模块
//个人信息栏
<div class="maincontent">
//面包屑
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcurmb-item active" aria-current="page">首页</li>
</ol>
</nav>
//个人信息
<div class="userprofile"></div>
<script>
import {load,isLogin} from "/admin/utill/LoadView.js"
load("sidemenu-home")//加载topbar //sidemenu
let user=JSON.parse(isLogin())
document.queryselector(".userprofile").innerHTML=`
<img src="${user.photo}" style="width:100px;"/>
<div>
<div>${user.username}</div>
<div><pre>${user.introduction || "这个人很懒"}</pre></div>
</div>
`
</script>
用户添加
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item" aria=current="page">用户管理</li>
<li class="breadcrumb-item active" aria=current="page">添加用户</li>
</ol>
</nav>
<form id="addUserForm">
<div class="mb-3">
<label for="username" class="form-label">用户名</label>
<input type="text" class="form-control" id="username" aria-describedby="emailHelp" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">密码</label>
<input type="password" class="form-control" id="password" required>
</div>
<div class="mb-3">
<label for="introduction" class="form-label">个人简介</label>
<textarea class="form-control" id="introduction" row="3"></textarea>
</div>
<div class="mb-3">
<label for="photofile" class="form-label">头像</label>
<input type="file" class="form-control" id="photo-file" required >
</div>
<button type="submit" class="btn btn-primary">添加用户</button>
</form>
<script>
import {load} from "/admin/util/LoadView.js"
load("sidemenu-addUser")
let photo=""
addUserForm.onsubmit=async function(evt){
evt.preventDefault()
await fetch("http://localhost:3000/users",{
method:"post",
headers:{
"content-type":"application/json"
},
body:JSON.stringify({
username:username.value
password:password.value
introduction:introduction.value
photo
})
}).then(res=>res.json())
location.herf="/admin/views/user-manage/UserList/index.html"
}
photofile.onchange=function(evt){//内部发生变化就将地址转化
let reader=new FileReader()
reader.readAsDataUrl(evt.target.files[0])
reader.onload=function(e){
console.log(e.target,result)
}
}
</script>
用户列表
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item" aria=current="page">用户管理</li>
<li class="breadcrumb-item active" aria=current="page">用户列表</li>
</ol>
</nav>
<table class="table">
<thead>
<tr>
<th scope="col">用户名</th>
<th scope="col">头像</th>
<th scope="col">操作</th>
</tr>
</thead>
<tbody id="listbody">
</tbody>
</table>
<script>
import {load} from "/admin/until/LoadView.js"
load("sidemenu-userList")
asnyc function render(){
let list = awit fetch("http://localhost:3000/users").then(res=>res.json())
listbody.innerHTML=list.map(item=>`
<tr>
<th scope="row">${item.username}</th>
<td>
<img src="${item.photo}" style="width:50px; border-radius:50%">
</td>
<td>
<button type="button" class="btn btn-primary btn-sm" ${item.default?"disabled":""}>编辑</button>//管理员不能动
<button type="button" class="btn btn-danger btn-sm" ${item.default?"disabled":"">删除</button>
</td>
</tr>
`).join("")
}
</script>
编辑用户
let myModal=new bootstrap.Model(document.getElementById('editModal'))
let updateId=0
let photodata =""
listbody.onclick=function(evt){
if(evt.target.className.includes("btn-edit")){
//显示modal
myEditModal.toggle()
updateId=evt.target.dataset.myid
...
//预填modal
let {username,password,introduction,photo}=list.filter(item=>item.id==updateId)[0]
document,queryselector("#username").value=username
document,queryselector("#password").value=password
document,queryselector("#introduction").value=introduction
photodata=photo
}else if(evt.target.className.includes("btn-del")){
console.log("del")
}
}
editConfirm.onclick=async function(){
await fetch(`http://localhost:3000/users/`,{
method:"PATCH",
headers:{
"content-type":"application/json"
},
body:JSON.stringify({
username:document.queryselector("#username").value,
password:document.queryselector("#password").value,
introduction:document.queryselector("#introduction").value,
photo:photodata
})
}).then(res=>res.json())
myEditModal.toggle()
render()
}
photofile.onchange=function(evt){
let reader=new FileReader()
reader.readAsDataUrl(evt.target.files[0])
reader.onload=function(e){
photodata=e.target.result
}
}
删除用户
let myDelModal= new bootstrap.Modal(document.getElementById('delModel'))
updataId=evt.target.dataset.myid//else if 中的语句
delConfirm.onclick=async function(){
await fetch(`http://localhost:3000/users/${updateId}`,{
method:"delete"
}).then(res=>res.json())
myDelModal.toggle()
render()
}
创建新闻
//下拉菜单
<div class="mb-3">
<label for="category" class="form-label">类别</label>
<select class="form_select" aria-label="Default select example" id="category">
<option value="0">最新动态</option>
<option value="1">典型案例</option>
<option value="2">通知公告</option>
</select>
</div>
//富文本内容删减,详情请前往bootstrap官网查看
项目总结
面对困难
json server环境搭建困难
前后端的数据交互
多层文件的叠加使用
js文件方法的导入导出
首页模块多个功能用多个页面写,虽然代码清晰简便很多,但是感觉不太合适
bootstrap的引用
后续规划
将多个模块多个页面改为一个页面多个模块
整体总结
环境搭建需重视
缺少实践实战
缺少对配件中各个工具的熟悉度和应用度