前言
本图书管理系统是基于Vue、Ajax、Node.js等技术的管理系统,笔者给其命名为阳光图书管理系统,意味着我们这个年纪应该活得洒脱像阳光一样,应充满活力与信心。再此感谢老师朋友的悉心指导,由于此系统是笔者初次完成的一个小型管理系统,必定有许多纰漏,如有不足请指正。
阳光图书馆设计与实现
1、绪论
1.1 选题背景
1.2 研究的目的及意义
1.3 论文组织结构
2、相关技术
2.1 Ajax异步加载
AJAX 是开发者的梦想,因为您能够:
不刷新页面更新网页
在页面加载后从服务器请求数据
在页面加载后从服务器接收数据
在后台向服务器发送数据
2.2 Axios
2.3 Vue
2.4 Node.js
jsonServer
3、系统需求分析
3.1 可行性设计
3.2 系统功能分析
3.3 系统非功能设计
4、系统设计
4.1 系统功能模块详细设计
下图是该实验的模块设计图
请求方式具体表现:
4.2 系统功能流程分析
5、系统实现
系统的具体实现过程,在此不再赘述,可以评论区留言将项目发送到您的邮箱。
5.1 管理员验证身份功能实现
(1)用户登录页面以及可以伸缩页面兼容性测试【响应式布局】
(2)用户登录失败与成功给出的不同提示
(3)用户注册页面与功能的实现
(4)用户找回密码
(5)用户注销账号
5.2 图书增删改查
(1)新增前、新增后
(2)删除前、删除后
(3)修改前、修改后
(4)查重机制
6、测试
系统开发完成之后,上线使用之前系统测试是必须的,因为只有对系统进行一个全面的测试,我们才可能发现系统中可能存在的问题,这些问题可能是技术缺陷,功能不全,业务需求达不到预先的效果等。新开发出来的系统没有十全十美的,所以这些缺陷有可能在测试中发现并得到解决,系统测试方法有很多,并且各个所占时间都不同,功能测试在整个系统测试的过程中占据的比例较大,功能测试也叫黑盒测试,黑盒测试不需要测试软件内部结构,目的是测试系统的功能。只要测试人员搭建好系统测试环境 。对各个函数接口进行测试即可。
6.1 测试环境
硬件环境:笔记本或台式电脑
操作系统:Windows 10
软件环境: 谷歌浏览器、Microsoft Edge浏览器
6.2 测试用例
只有严格按照完整的测试用例来对系统进行测试,才能使得系统不管是功能还是性能方面的质量都有所保证,没有十全十美的软件系统,但是我们可以通过测试减少很多不必要的错误,下面即是该系统的部分测试用例。
(1)图书管理测试用例
(2)管理员登录操作用例
结束语
本次实验的设计与开发,是对本学期学习的一种很好的总结,在本学期的实验中,本次实验很具有挑战性,因此我选择了这个项目作为我的实验项目,在完成了实验的时候,我也遇到了许多困难与错误,在需求不明确的时候盲目的开发造成错误百出,某些知识点的掌握还不够,也有一些地方与预期不相符,我不得不查询各种资料,在一次次探讨之中,我慢慢的掌握的许多知识,在这些知识的认识之下,完成了该实验的设计与开发,技术与业务能力也有所提高,这对以后的事业发展有一定的积极影响作用
参考文献
致谢
本次实验的开发与设计是在郭德先老师的指导下完成的,老师的建议和谆谆教诲使得实验在一点点的完善,在这一学期的学习中,正是老师的责任心引导着我们完成一次又一次的实验,每次遇到解决不了的问题时,都会向老师咨询,老师总是能给出合理的建议并不断鼓励我完成实验,再次由衷的感谢老师。
源码
项目目录结构
大家根据这个项目的目录结构搭建即可,如有疑问评论区私聊博主。想要搭建好的项目评论区留言邮箱。
主页【index.html】
<!DOCTYPE html>
<html lang="en">
<head>
<Meta charset="UTF-8" />
<Meta http-equiv="X-UA-Compatible" content="IE=edge" />
<Meta name="viewport" content="width=device-width,initial-scale=1.0" />
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<!-- 引入样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css" />
<!-- 引入组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<link rel="stylesheet" href="../css/index.css" />
<title>阳光图书馆</title>
</head>
<body>
<div id="logn" style="text-align: center">
<el-dialog
:visible.sync="dialogVisible"
style="width: 70%; text-align: center; position: absolute; left: 15%; top: 20%"
>
账号:<input type="text" v-model="temp_node.id" /> <br /><br />
密码:<input type="password" v-model="temp_node.name" /> <br /><br />
<!-- 年龄:<input type="text" v-model="temp_node.age" /> <br /><br /> -->
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="dialogVisible = false;dialogs();">确 定</el-button>
</el-dialog>
<el-dialog
:visible.sync="dialogVisible1"
style="width: 70%; text-align: center; position: absolute; left: 15%; top: 20%"
>
账号:<input type="text" v-model="temp_node.id" /> <br /><br />
<!-- 密码:<input type="password" v-model="temp_node.name" /> <br /><br /> -->
年龄:<input type="text" v-model="temp_node.age" /> <br /><br />
<el-button @click="dialogVisible1 = false">取 消</el-button>
<el-button type="primary" @click="dialogVisible1 = false;losepasswd();">确 定</el-button>
</el-dialog>
</div>
<div class="back1" style="position: absolute">
<div class="namebgc">
<div class="head1"></div>
<div class="headfont">
<div>阳</div>
<div>光</div>
<div>图</div>
<div>书</div>
<div>管</div>
<div></div>
</div>
</div>
<div class="headtext">
<a href="#" class="button" onclick="vmadd()"> 登录 </a>
<a href="resiger.html" class="button"> 注册 </a>
<a href="#" class="button" onclick="vmlook()"> 找回密码 </a>
<a href="#" class="button" onclick="vmdel()"> 注销账号 </a>
</div>
</div>
<script>
function vmadd() {
vm.$data.dialogVisible = true
vm.$data.logordel = true
}
function vmdel() {
vm.$data.dialogVisible = true
vm.$data.logordel = false
}
function vmlook() {
vm.$data.dialogVisible1 = true
vm.$data.logordel = 3
}
let vm = new Vue({
el: '#logn',
data: {
userList: [],
dialogVisible: false,
dialogVisible1: false,
textvalue: '',
index: '',
temp_node: {
id: '',
age: '',
name: '',
},
logordel:0,
},
methods: {
//代码还是有点复杂:我们程序员还要手动完成数据组装
search() {
axios({
url: 'http://localhost:3000/users',
})
.then(function (response) {
vm.$data.userList = response.data
})
.catch(function (error) {
//失败
console.log(error)
console.log('===请求失败了===')
})
},
dialogs() {
if (this.$data.logordel) {
this.mlogin()
} else {
this.deluser()
}
this.resetTempNode();
},
// 登录函数
mlogin() {
for (let i = 0; i < this.userList.length; i++) {
if (this.userList[i].id == this.temp_node.id && this.userList[i].name == this.temp_node.name) {
alert('登录成功!')
// 前端小练习\阳光图书馆\html\opbook.html
// location.replace('D:/goStudy/前端大作业/前端小练习/opedata.html')
location.replace('opbook.html')
return
}
}
alert('未找到此用户!')
},
// 删除函数
deluser() {
for (let i = 0; i < this.userList.length; i++) {
if (this.userList[i].id == this.temp_node.id && this.userList[i].name == this.temp_node.name) {
axios({
method: 'DELETE', //删除
url: 'http://localhost:3000/users/' + this.temp_node.id, //id为"hello"
}).then(function (response) {
console.log(response.data)
})
alert('注销成功!')
this.search()
return
}
}
alert('未找到此用户!')
},
losepasswd(){
for (let i = 0; i < this.userList.length; i++) {
if (this.userList[i].id == this.temp_node.id && this.userList[i].age == this.temp_node.age) {
// console.log(this.userList[i].name)
alert("您的密码是:"+this.userList[i].name);
this.resetTempNode();
return
}
}
alert('您的信息有误!')
this.resetTempNode();
},
resetTempNode(){
this.temp_node.id = ''
this.temp_node.name = ''
this.temp_node.age = ''
}
},
created: function () {
this.search()
},
})
</script>
</body>
</html>
图书的增删改查【opbook.html】
<!DOCTYPE html>
<html lang="en">
<head>
<Meta charset="UTF-8" />
<Meta http-equiv="X-UA-Compatible" content="IE=edge" />
<Meta name="viewport" content="width=device-width,initial-scale=1.0" />
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<!-- 引入样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css" />
<!-- 引入组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<title>操作图书</title>
</head>
<!-- style="background-image: url(../imgs/image.png); background-repeat: no-repeat; background-size: cover;" -->
<body>
<div id="app">
<el-container>
<el-header>
<el-menu class="el-menu-demo" mode="horizontal">
<el-menu-item index="1">处理中心</el-menu-item>
<el-submenu index="2">
<temp_nodelate slot="title">我的工作台</temp_nodelate>
<el-menu-item index="2-1">选项1</el-menu-item>
<el-menu-item index="2-2">选项2</el-menu-item>
<el-menu-item index="2-3">选项3</el-menu-item>
<el-submenu index="2-4">
<temp_nodelate slot="title">选项4</temp_nodelate>
<el-menu-item index="2-4-1">选项1</el-menu-item>
<el-menu-item index="2-4-2">选项2</el-menu-item>
<el-menu-item index="2-4-3">选项3</el-menu-item>
</el-submenu>
</el-submenu>
<el-menu-item index="3" disabled>消息中心</el-menu-item>
<el-menu-item index="4"><a href="https://www.ele.me" target="_blank">订单管理</a></el-menu-item>
<el-menu-item index="4" style="position: absolute; right: 10%; background-color: aqua; color: black; border-radius: 40%">
<a href="index.html" style="text-decoration: none">退出登录</a>
</el-menu-item>
</el-menu>
</el-header>
<el-main >
<!-- 信息录入框 -->
<el-dialog
title="请输入您的信息!"
:visible.sync="dialogVisible"
style="width: 70%; text-align: center; position: absolute; left: 15%"
>
id:<input type="text" v-model="temp_node.id" /> <br /><br />
书名:<input type="text" v-model="temp_node.name" /> <br /><br />
作者:<input type="text" v-model="temp_node.author" /> <br /><br />
价格:<input type="text" v-model="temp_node.price" /> <br /><br />
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="dialogVisible = false;add_or_amend();">确 定</el-button>
</el-dialog>
<div id="app" class="mylist">
<!-- 卡片的样式进行展示 -->
<el-card class="Box-card" >
<!-- 信息展示 -->
<el-table :data="bookList" style="width: 100%;">
<el-table-column prop="id" label="编号" width="%20"></el-table-column>
<el-table-column prop="name" label="书名" width="%20"></el-table-column>
<el-table-column prop="author" label="作者" width="%20"></el-table-column>
<el-table-column prop="price" label="价格(元)" width="%20"></el-table-column>
<el-table-column label="操作" width="%20">
<temp_nodelate slot-scope="scope">
<el-button
type="warning"
style="width: 30%"
v-on:click="addoramend=false;dialogVisible=true;index=scope.$index;"
>修改</el-button
>
<el-button type="danger" style="width: 30%" v-on:click="index=scope.$index;del();">删除</el-button>
</temp_nodelate>
</el-table-column>
</el-table>
<!-- 新增按钮 -->
<div style="text-align: center">
<el-button style="width: 15%; margin-top: 1%" v-on:click="addoramend=true;dialogVisible=true"
>+</el-button
>
</div>
</el-card>
</div>
</el-main>
<el-footer style="position: absolute; bottom: 20px; width: 100%">
<el-card>
<div style="text-align: center">
<el-link href="https://element.eleme.io" target="_blank">默认链接</el-link>
<el-link type="primary">主要链接</el-link>
<el-link type="success">成功链接</el-link>
<el-link type="warning">警告链接</el-link>
<el-link type="danger">危险链接</el-link>
<el-link type="info">信息链接</el-link>
</div>
</el-card>
</el-footer>
</el-container>
</div>
<script>
// 创建一个vue对象
let vm = new Vue({
// 将这个vue对象与id为app的标签进行绑定
el: '#app',
// vue对象的数据库
data: {
// 存储所有书的列表
bookList: [],
sorttype: 0,
author: '',
price: '',
addoramend: true,
// vue对象的方法域
methods: {
//代码还是有点复杂:我们程序员还要手动完成数据组装
search() {
axios({
url: 'http://localhost:3000/book',
})
.then(function (response) {
vm.$data.bookList = response.data
})
.catch(function (error) {
//失败
console.log(error)
console.log('===请求失败了===')
})
},
// 删除
del() {
let aaa = this.bookList[this.index]
var mindex = this.index
let bbb = this.bookList.indexOf(aaa)
this.bookList.splice(bbb, 1)
axios({
method: 'DELETE', //删除
url: 'http://localhost:3000/book/' + aaa.id,
}).then(function (response) {
console.log(mindex)
})
},
// 修改
amend() {
let aaa = this.bookList[this.index]
let str = aaa.id
let bbb = this.bookList.indexOf(aaa)
for (let i = 0; i < this.bookList.length; i++) {
if (this.bookList[i].id == this.temp_node.id && i != bbb) {
alert('操作失败')
return
}
}
// 将新的属性给这个节点
this.bookList[bbb].id = this.temp_node.id
this.bookList[bbb].name = this.temp_node.name
this.bookList[bbb].author = this.temp_node.author
this.bookList[bbb].price = this.temp_node.price
// 先进行删除
axios({
method: 'DELETE', //删除
url: 'http://localhost:3000/book/' + str,
}).then(function (response) {
console.log(response.data)
})
// 再进行添加
axios({
method: 'POST', //增加
url: 'http://localhost:3000/book',
data: this.temp_node,
})
},
// 增加
add() {
// 判断一下表单是否为空
if (this.temp_node.id != '' && this.temp_node.age != '') {
let ma = { id: '', name: '', author: '', price: '' }
ma.id = this.temp_node.id
ma.name = this.temp_node.name
ma.author = this.temp_node.author
ma.price = this.temp_node.price
for (let i = 0; i < this.bookList.length; i++) {
if (this.bookList[i].id == ma.id) {
alert('该id已经存在!')
return
}
}
// 将新的节点加入页面
this.bookList.push(ma)
// 将新的节点添加到数据库
axios({
method: 'POST', //增加
url: 'http://localhost:3000/book',
data: ma,
})
.then(function (response) {})
.catch((err) => {
alert('操作失败')
})
}
},
// 判断是修改还是增加
add_or_amend() {
if (this.addoramend) {
this.add()
} else {
this.amend()
}
// 操作完后将表单数据置空
this.temp_node.id = ''
this.temp_node.author = ''
this.temp_node.name = ''
this.temp_node.price = ''
},
// 一个实例被创建之后执行的代码
created: function () {
this.search()
},
})
</script>
</body>
</html>
注册页面【resger.html】
<!DOCTYPE html>
<html lang="en">
<head>
<Meta charset="UTF-8">
<Meta http-equiv="X-UA-Compatible" content="IE=edge">
<Meta name="viewport" content="width=device-width,initial-scale=1.0">
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<link rel="stylesheet" href="../css/resiger.css">
<title>注册</title>
</head>
<body>
<div class="nav">
<form action="python">
<div style="color: steelblue;">
I    D: <input type="text" id="userid" class="texta"><br>
密    码: <input type="password" id="userpasd" class="texta"><br>
年    龄: <input type="text" id="userage" class="texta"><br>
<a type="button" class="mbuttom" href="#" onclick="resetvalue()">清空</a>
<a type="button" class="mbuttom" href="#" onclick="submitvalue()">提交</a>
<a type="button" class="mbuttom" href="index.html" style="width: 110px;">返回主菜单</a>
</div>
</form>
</div>
<script>
// 将临时节点的数据恢复初始值
function resetvalue(){
document.getElementById("userid").value="";
document.getElementById("userpasd").value="";
document.getElementById("userage").value=""
}
// 获取各个文本框的数据
function submitvalue(){
vm.$data.temp_node.id=document.getElementById("userid").value
vm.$data.temp_node.name=document.getElementById("userpasd").value
vm.$data.temp_node.age=document.getElementById("userage").value
vm.add()
}
let vm = new Vue({
el: '#app',
data: {
userList: [],
temp_node: {
id: '',
age: '',
name: '',
},
methods: {
search() {
axios({
url: 'http://localhost:3000/users',
})
.then(function (response) {
vm.$data.userList = response.data
})
.catch(function (error) {
//失败
console.log(error)
console.log('===请求失败了===')
})
},
// 增加
add(){
if(this.temp_node.id!=''&&this.temp_node.age!=''){
let ma={id:'',age:'',name:''};
ma.id=this.temp_node.id;
ma.age=this.temp_node.age;
ma.name=this.temp_node.name;
// 遍历用户列表进行查重
for(let i=0;i<this.userList.length;i++){
if(this.userList[i].id==ma.id){
alert('该id已经存在!请重新输入');
resetvalue();
return;
}
}
// 没有重复将数据插入列表内
this.userList.push(ma);
alert("注册成功!点击确定去登录")
resetvalue();
axios({
method:"POST",//增加
url:"http://localhost:3000/users",
data:ma,
}).then(function(response){
}).catch((err)=>{
alert('操作失败');
})
location.replace('index.html')
}
},
// 一个实例被创建之后执行的代码
created: function () {
this.search()
},
})
</script>
</body>
</html>
主页css样式【index.css】
/* 第一页背景 */
div {
display: inline-block;
}
a {
text-decoration: none;
}
/* body{
width: 100%;
} */
.back1 {
position: relative;
/* display: inline-block; */
width: 100%;
height: 1200px;
background: url('../imgs/image.png') no-repeat;
background-size: 100% 100%;
}
/* 网站简介 */
.namebgc {
position: absolute;
top: -20%;
width: 44%;
height: 16%;
background-color: transparent;
transform: translate(70%,180%);
}
.head1 {
position: absolute;
margin-top: 23px;
margin-left: 39px;
width: 15.3%;
height: 65%;
background: url('../imgs/head1.png') no-repeat;
background-size: 100% 100%;
background-color: transparent;
/* transform: translate(10%,10%); */
transition: all 1s;
}
.head1:hover {
transform: rotate(360deg);
}
.headfont {
background-color: transparent;
width: 70%;
text-align: center;
margin-top: 20px;
margin-left: 20px;
position: absolute;
left: 180px;
font-size: 80px;
font-weight: 700;
transition: all 0.6s;
/* font-family: '幼圆'; */
}
/* 当鼠标经过图标时,有一定的动画效果 */
.headfont div:nth-child(1) {
transition: all 0.3s;
color: blueviolet;
}
.headfont div:nth-child(1):hover {
transform: translateY(-5px);
}
.headfont div:nth-child(2) {
transition: all 0.3s;
color: aqua;
}
.headfont div:nth-child(2):hover {
transform: translateY(-5px);
}
.headfont div:nth-child(3) {
transition: all 0.3s;
color: cornflowerblue;
}
.headfont div:nth-child(3):hover {
transform: translateY(-5px);
}
.headfont div:nth-child(4) {
transition: all 0.3s;
color: gold;
}
.headfont div:nth-child(4):hover {
transform: translateY(-5px);
}
.headfont div:nth-child(5) {
transition: all 0.3s;
color: tomato;
}
.headfont div:nth-child(5):hover {
transform: translateY(-5px);
}
/* 简介 1388*232 */
/* 放置登录、注册几个按钮的盒子 */
.headtext {
background-color: transparent;
position: absolute;
width: 40%;
height: 40%;
transform: translate(80%,60%);
text-align: center;
font-size: 26px;
}
.headtext .button {
margin-left: 30%;
margin-top: 1%;
width: 40%;
height: 100px;
display: block;
border-radius: 30%;
border: cornflowerblue 5px solid;
font-size: 40px;
line-height: 100px;
color: lightblue;
transition: all 0.4s;
}
.headtext .button:hover {
transform-origin: center;
border-color: chartreuse;
color: chartreuse;
transform: scale(110%,110%);
}
注册css样式【resiger.css】
.nav{
display: inline-block;
width: 100%;
height: 780px;
text-align: center;
padding-top: 200px;
line-height: 30px;
background: url("../imgs/image.png") no-repeat;
background-size: 100% 100%;
font-size: 20px;
font-weight: 700;
}
.texta{
display: inline-block;
width: 400px;
height: 20px;
margin-top: 20px;
}
.mbuttom{
display: inline-block;
width: 60px;
height: 30px;
border-radius: 15px;
background-color: darkgray;
margin-left: 20px;
margin-right: 20px;
margin-top: 25px;
text-decoration: none;
color: rgb(85,39,140);
}
root CSS【root.css】
*{
margin: 0;
padding: 0;
}
图片素材
本项目使用的是相对路径建好项目目录后直接将图片放在相应的目录即可
环境安装包
node.js
http://nodejs.cn/
element-ui
https://element.eleme.io/#/zh-CN
vue.js
https://cn.vuejs.org/index.html