基于Springboot+Vue的电子博物馆系统
前言:随着信息技术的不断发展,传统博物馆的参观方式逐渐向数字化、在线化转型。电子博物馆作为这一转型的重要组成部分,能够通过信息化手段为用户提供更丰富、更便捷的博物馆参观体验。本文基于Spring Boot和Vue.js框架,设计并实现了一个简单的电子博物馆系统,旨在展示数字化博物馆建设的技术架构及开发过程。
前言
项目功能及技术
用户端主要有首页、文创展示、浏览记录、网站公告、我的收藏、个人中心等模块。
管理端主要有用户管理、博物馆管理、文创管理、公告管理,博物馆视频管理。
vue.js+layui+html+js:用户端界面。
SpringBoot框架+Java程序语言:用户端及后台管理系统API的实现。
Layui前端框架:web后台管理界面样式及数据渲染框架。
MySQL数据库:数据存储。
用户端




管理端



API
SpringBoot框架搭建
1.创建maven project,先创建一个名为SpringBootDemo的项目,选择【New Project】
然后在弹出的下图窗口中,选择左侧菜单的【New Project】
在project下创建module,点击右键选择【new】—【Module…】
左侧选择【Spring initializr】,通过idea中集成的Spring initializr工具进行spring boot项目的快速创建。窗口右侧:name可根据自己喜好设置,group和artifact和上面一样的规则,其他选项保持默认值即可,【next】
Developer Tools模块勾选【Spring Boot DevTools】,web模块勾选【Spring Web】,此时,一个Springboot项目已经搭建完成,可开发后续功能
实体映射创建Mapper
创建一个entity实体类文件夹,并在该文件夹下创建项目用到的实体类
package com.example.demo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Data
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String account;
private String pwd;
private String userDesc;
private String userHead;
private LocalDateTime createTime;
private Long role;
private String nickname;
private String email;
private String tags;
}
接口封装
由于我们使用mybatis-plus,所以简单的增删改查不用自己写,框架自带了,只需要实现或者继承他的Mapper、Service
创建控制器Controller
整合Swagger
添加依赖
先导入spring boot的web包
<!--swagger依赖-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
配置Swagger
创建一个swagger的配置类,命名为SwaggerConfig.java
/*
*用于定义API主界面的信息,比如可以声明所有的API的总标题、描述、版本
*/
private ApiInfo apiDemo() {
return new ApiInfoBuilder()
//用来自定义API的标题
.title("SpringBoot项目SwaggerAPIAPI标题测试")
//用来描述整体的API
.description("SpringBoot项目SwaggerAPI描述测试")
//创建人信息
.contact(new Contact("测试员张三","http://localhost:8080/springboot/swagger-ui.html","[email protected]"))
//用于定义服务的域名
//.termsOfServiceUrl("")
.version("1.0") //可以用来定义版本
.build();
}
接口测试
运行Spring Boot项目,默认端口8080,通过地址栏访问url
接口组定义
根据不同的业务区分不同的接口组,使用@API来划分
@Api(tags = "用户管理") // tags:组名称
@RestController
public class RoleController {
}
接口定义
使用@ApiModel来标注实体类,同时在接口中定义入参为实体类作为参数。
-
@ApiModel:用来标类
-
常用配置项:value:实体类简称;description:实体类说明
-
@ApiModelProperty:用来描述类的字段的含义。
常用字段类型
字段类型 | 所占字节 | 存储范围 | 最大存储值 | 使用场景 |
---|---|---|---|---|
TINYINT | 1 | -128~127 | 127 | 存储小整数 |
INT | 4 | -2147483648~2147483647 | 2147483647 | 存储大整数 |
BIGINT | 8 | -9223372036854775808~9223372036854775807 | 9223372036854775807 | 存储极大整数 |
DECIMAL | 可变长度 | 存储精度要求高的数值 | ||
CHAR | 固定长度 | 最多255字节 | 255个字符 | 存储长度固定的字符串 |
VARCHAR | 可变长度 | 最多65535字节 | 65535个字符 | 存储长度不固定的字符串 |
DATETIME | 8 | ‘1000-01-01 00:00:00’~‘9999-12-31 23:59:59’ | ‘9999-12-31 23:59:59’ | 存储日期和时间 |
参考代码块
<script type="text/javascript">
layui.use(['laydate', 'form'], function() {
var laydate = layui.laydate;
var form = layui.form;
// 监听全选
});
//轻量级框架
var dataInfo = new Vue({
el: "#content-page",
//Vue的数据对象
data: {
timeSort: 'next', //发布时间排序
viewVolumeSort: 'next', //浏览量排序
arrayList: [], //数据列表
title: null,
dynasty: null,
typeId: null,
areaList: [],
appointment: {
},
swiperSrc: '',
indexSwiper: [
'https://photo.16pic.com/00/53/26/16pic_5326792_b.jpg',
'https://img95.699pic.com/desgin_photo/40113/1735_list.jpg',
'https://file3.renrendoc.com/fileroot_temp3/2022-5/20/f16ee02b-429f-46ad-806f-f453cfb33b80/f16ee02b-429f-46ad-806f-f453cfb33b801.gif',
],
swiperIndexl: 0,
bwgArray:[
],
}, //数据对象结束
//方法
methods: {
GetAll: function(desc, desc1) {
var me = this;
me.swiperSrc = me.indexSwiper[0];
let list = me.indexSwiper;
var swiperIndexl = me.swiperIndexl;
me.swiperSrc = list[swiperIndexl];
setInterval(function() {
me.swiperSrc = list[swiperIndexl];
swiperIndexl = (swiperIndexl + 1) % list.length;
}, 3000);
if (sessionStorage.getItem('user') == null || sessionStorage.getItem('user') == undefined ||
sessionStorage.getItem('user') == '') {
location.href = "login/login.html"
}
$.ajax({
url: "http://127.0.0.1:8085/exhibition/list",
async: false,
type: "POST",
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify({
}),
success: function(json) {
me.bwgArray = json.data;
}
});
},
gobwgDetail(e) {
sessionStorage.setItem("exhibition", JSON.stringify(e));
window.location.href = "museumDetail.html";
},
goAreaDetail(item) {
//console.log(id)
sessionStorage.setItem("area", JSON.stringify(item));
window.location.href = "areaDetail.html";
},
}, //方法结束
created: function() {
var vm = this;
vm.GetAll('desc', 'desc');
}, //初始加载方法结束
}); //vue结束
function GetQueryString(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
var r = window.location.search.substr(1).match(reg);
if (r != null) return unescape(r[2]);
return null;
}
</script>
<body>
<div class="container-fluid" id="content-page">
<div class="row top-header">
<div class="col-md-1"> </div>
<div class="col-md-11">
<div class="rightMenu">
<img src="img/logo.png" class="logo" />
<div class="logoTitle">数字博物馆</div>
<div class="menuBox">
<div class="menuTitle">
<a href="index.html" style="text-decoration: none;">首页</a>
</div>
<div class="menuTitle">
<a href="surroundings.html" style="text-decoration: none;">文创展示</a>
</div>
<div class="menuTitle">
<a href="news.html" style="text-decoration: none;">网站公告</a>
</div>
<div class="menuTitle">
<a href="history.html" style="text-decoration: none;">浏览记录</a>
</div>
<div class="menuTitle">
<a href="center.html" style="text-decoration: none;">个人中心</a>
</div>
<div class="menuTitle">
<a href="./login/login.html" style="text-decoration: none;">退出登录</a>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-1"> </div>
<div class="col-md-10 paramBox">
<div class="displayBox">
<div class="sortBox" >
当前位置:网站公告
<img src="img/news.png" class="sortImg" />
</div>
</div>
</div>
<div class="col-md-1"> </div>
</div>
<div class="row">
<div class="col-md-1"> </div>
<div class="col-md-10">
<div class="iconTitle">
<div class="iconParent">
<div class="listIcon">网站公告</div>
</div>
<div class="topTitle"></div>
<div class="newTitle" v-for="item in arrayList" @click="goDetail(item)">· {
{item.title}}</div>
</div>
</div>
<div class="col-md-1"> </div>
</div>
<div class="row">
<div style="height:100px;"></div>
</div>
</div>
<script src="js/jquery.min.js"></script>
<script src="js/vue.js"></script>
<script src="js/utils.js"></script>
<script type="text/javascript">
//轻量级框架
var dataInfo = new Vue({
el: "#content-page",
//Vue的数据对象
data: {
timeSort: 'on', //发布时间排序
viewVolumeSort: 'on', //浏览量排序
arrayList: [
], //数据列表
}, //数据对象结束
//方法
methods: {
GetAll: function() {
var me = this;
$.ajax({
url: "http://127.0.0.1:8085/announcement/selectPageByUser",
async: false,
type: "POST",
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify({
}),
success: function(json) {
me.arrayList = json.list;
}
});
},
goDetail(e){
console.log(e);
sessionStorage.setItem("ann",JSON.stringify(e));
window.location.href="newsDetail.html";
},
}, //方法结束
created: function() {
var vm = this;
vm.GetAll();
}, //初始加载方法结束
}); //vue结束
function GetQueryString(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
var r = window.location.search.substr(1).match(reg);
if (r != null) return unescape(r[2]);
return null;
}
</script>
<div class="container-fluid" id="content-page">
<div class="row top-header">
<div class="col-md-1"> </div>
<div class="col-md-11">
<div class="rightMenu">
<img src="img/logo.png" class="logo" />
<div class="logoTitle">数字博物馆</div>
<div class="menuBox">
<div class="menuTitle">
<a href="index.html" style="text-decoration: none;">首页</a>
</div>
<div class="menuTitle">
<a href="surroundings.html" style="text-decoration: none;">文创展示</a>
</div>
<div class="menuTitle">
<a href="news.html" style="text-decoration: none;">网站公告</a>
</div>
<div class="menuTitle">
<a href="history.html" style="text-decoration: none;">浏览记录</a>
</div>
<div class="menuTitle">
<a href="center.html" style="text-decoration: none;">个人中心</a>
</div>
<div class="menuTitle">
<a href="./login/login.html" style="text-decoration: none;">退出登录</a>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-3">
<div class="prevBox">
<img src="img/prev.png" class="swiperChangeImg" v-on:click="prevImg" />
</div>
</div>
<div class="col-md-6 ">
<div class="swiperBox">
<img :src="showSrc" class="swiperItemImg" />
</div>
</div>
<div class="col-md-3">
<div class="nextBox">
<img src="img/next.png" class="swiperChangeImg" v-on:click="nextImg"/>
</div>
</div>
</div>
<div class="row">
<div class="col-md-2"> </div>
<div class="col-md-8">
<div class="borerBox"></div>
</div>
<div class="col-md-2"> </div>
</div>
<div class="row">
<div class="col-md-2"> </div>
<div class="col-md-8">
<div class="wwDetailBox">
<div class="wwDetailLeft">
<div class="wwDetailTitle">名称与朝代</div>
<div class="wwDetailText">{
{info.title}},{
{info.dynasty}}</div>
<div class="wwDetailTitle">出土时间和地点</div>
<div class="wwDetailText">{
{info.address}}</div>
<div class="wwDetailTitle">材质</div>
<div class="wwDetailText">{
{info.materialQuality}}</div>
</div>
<div class="wwDetailRight">
<div class="wwDetailTitle">历史文化背景</div>
<div class="wwDetailText">{
{info.history}}</div>
</div>
</div>
</div>
<div class="col-md-2"> </div>
</div>
<div class="row">
<div class="col-md-2"> </div>
<div class="col-md-8">
<div style="margin: 20px 0 80px 0;">
<video width="100%" height="400" controls>
<source :src="'http://127.0.0.1:8085/'+info.videoSrc" type="video/mp4"/>
</video>
</div>
</div>
<div class="col-md-2"> </div>
</div>
<!-- <div class="row">
<div class="col-md-3"> </div>
<div class="col-md-6">
<div class="wcTypeBox">
<div>
<div class="wcTypeItem">
生活用品
</div>
</div>
</div>
</div>
<div class="col-md-3"> </div>
</div> -->
<div class="footer">
<div class="row">
<div class="col-md-4"> </div>
<div class="col-md-4 ">
<div class="footerLine1">
<a href="javascript:void(0);" class="lineA">相关链接</a>
<a href="javascript:void(0);" class="lineA">影像授权</a>
<a href="javascript:void(0);" class="lineA">隐私政策</a>
<a href="javascript:void(0);" class="lineA">版权说明</a>
<a href="javascript:void(0);" class="lineA">联系我们</a>
<a href="javascript:void(0);" class="lineA">关于我们</a>
</div>
<div class="footerLine2">
<span class="lineB">京公网安备 11010102004165151号</span>
<span class="lineB">京ICP备05067311号-1</span>
<span class="lineB">© 2023-至今</span>
</div>
</div>
<div class="col-md-4"> </div>
</div>
</div>
</div>
<script src="js/jquery.min.js"></script>
<script src="js/vue.js"></script>
<script src="js/utils.js"></script>
<script type="text/javascript">
//轻量级框架
var dataInfo = new Vue({
el: "#content-page",
//Vue的数据对象
data: {
info:{
id:1,
//名称与朝代
name:'越王勾践剑',
//出土时间和地点
address:'出土时间是1965年12月,,出土于湖北省荆州市江陵县望山楚墓群1号墓',
//材质
materialQuality:'越王勾践剑的材质主要是青铜和锡的合金。这种合金使得剑身既坚硬又富有韧性,能够保持长久的锋利。同时,剑身上还镀有一层含铬的金属,这使得越王勾践剑在历经千年之后仍然能够保持光泽,不生锈蚀。',
//历史文化背景
history:'越王勾践剑在历史上的地位十分重要,它不仅是越国强大军事实力的象征,也体现了当时越国工匠的精湛技艺。这把剑见证了越王勾践卧薪尝胆、励精图治的历史故事,也反映了春秋战国时期诸侯争霸、文化交融的时代背景。关于越王勾践,最为人熟知的便是他卧薪尝胆的故事。在吴国战败后,勾践被俘为奴,但他并未放弃复国的信念。他卧薪尝胆,时刻提醒自己不忘国仇家恨,最终成功复仇并重建越国。越王勾践剑作为他的佩剑,见证了这一伟大历程,具有极高的历史价值。',
//视频
videoSrc:'http://vjs.zencdn.net/v/oceans.mp4',
//朝代
dynasty:'春秋晚期',
//分类
typeName:'青铜器',
//封面图
images:'https://www.chinakongzi.org/zhwh/tpjj/201708/W020170809353217008926.jpg',
//轮播图
bannerList: [
'https://img.zcool.cn/community/015b76608697f511013e3b7dd2fd0d.jpg@1280w_1l_2o_100sh.jpg',
'https://img.zcool.cn/community/01f475608697f511013f47209e7be5.jpg@1280w_1l_2o_100sh.jpg',
'https://img.zcool.cn/community/015b3e608697f611013e3b7d0ca587.jpg@1280w_1l_2o_100sh.jpg',
'https://img.zcool.cn/community/01139e608697f511013f4720a66170.jpg@1280w_1l_2o_100sh.jpg',
'https://img.zcool.cn/community/0124cf608697f511013e3b7da76892.jpg@1280w_1l_2o_100sh.jpg'
],
},
showSrc: '',
currentIndex:0,
}, //数据对象结束
//方法
methods: {
GetAll: function() {
var vm = this;
var id=GetQueryString('id');
$.ajax({
url: "http://127.0.0.1:8085/question/selectById?id="+id,
async: false,
type: "POST",
contentType: 'application/json',
dataType: 'json',
success: function(json) {
vm.info=json.data;
let list = vm.info.banners;
var currentIndex = vm.currentIndex;
vm.showSrc = "http://127.0.0.1:8085/"+list[currentIndex];
setInterval(function() {
vm.showSrc = "http://127.0.0.1:8085/"+list[currentIndex];
currentIndex = (currentIndex + 1) % list.length;
}, 3000);
}
});
},
//点击上一页
prevImg(){
var vm = this;
let list = vm.info.banners;
var currentIndex = vm.currentIndex;
currentIndex = currentIndex-1;
if(currentIndex<0){
currentIndex = list.length-1;
}
vm.showSrc = "http://127.0.0.1:8085/"+list[currentIndex];
vm.currentIndex =currentIndex;
},
//点击下一页
nextImg(){
var vm = this;
let list = vm.info.banners;
var currentIndex = vm.currentIndex;
currentIndex = currentIndex+1;
if(currentIndex==list.length){
currentIndex = 0;
}
vm.showSrc = "http://127.0.0.1:8085/"+list[currentIndex];
vm.currentIndex =currentIndex;
},
infoDetail(e) {
window.location.href = "museumDetails.html";
},
}, //方法结束
created: function() {
var vm = this;
vm.GetAll();
}, //初始加载方法结束
}); //vue结束
function GetQueryString(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
var r = window.location.search.substr(1).match(reg);
if (r != null) return unescape(r[2]);
return null;
}
</script>