源代码在GitHub - 629y/course: Spring Cloud + Vue前后端分离-在线课程
Spring Cloud + Vue前后端分离-第13章 网站开发
13-1 网站模块的搭建
新建web模板
1.网站开发,增加web模块,使用命令:vue create web
vue版本4.2.3
大家拿到一个vue项目后,要先执行npm install,才能运行项目。这一步会去下载node module,类似于maven项目要先下载jar包。
1.创建vue create web
2.cd web
npm run serve
多环境配置
1.网站开发,web模块增加多环境配置,启动命令中,增加--port 8081来修改端口
env.dev
env.prod
package.json
增加路由配置
1.网站开发,web模块增加路由配置,web路由版本是4.2.5,而admin路由版本是3.6.5
使用命令:npm install --save vue-router
注意:要先跳到web模块下,再安装router模块
以下这两个文件都是自动改的
package.json,package-lock.json
这里只是增加了router依赖,后续开发页面时,会增加router的配置
13-2 集成bootstrap官方模板
我们的控台admin,用的是免费的ace admin模版,ace也是基于bootstrap做的封装,有很多组织和个人以卖各种前端模板为生。
本章开发的前端网站,是基于bootstrap官方提供的原生模板
bootstrap4.4.1文档介绍
1.网站开发,增加bootstrap-4.4.1的js,css
jquery.slim是jquery的简化版,也可以引入原版jquery。popper.js是一个js提示插件。
网站首页选用album模板,相册主题
集成album模板
1.网站首页开发,集成album模板
做前端的同学,会经常看到bundle这个词,就是像几个依赖的js或css,打包成一个bundle.js
App.vue
将album模板中的body标签里的内容,复制到template标签中
删除HelloWorld.vue
album.css
.jumbotron {
padding-top: 3rem;
padding-bottom: 3rem;
margin-bottom: 0;
background-color: #fff;
}
@media (min-width: 768px) {
.jumbotron {
padding-top: 6rem;
padding-bottom: 6rem;
}
}
.jumbotron p:last-child {
margin-bottom: 0;
}
.jumbotron h1 {
font-weight: 300;
}
.jumbotron .container {
max-width: 40rem;
}
footer {
padding-top: 3rem;
padding-bottom: 3rem;
}
footer p {
margin-bottom: .25rem;
}
index.html
<!DOCTYPE html>
<html lang="">
<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">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="stylesheet" href="<%= BASE_URL %>bootstrap-4.4.1/css/bootstrap.min.css">
<link rel="stylesheet" href="<%= BASE_URL %>static/css/album.css">
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="<%= BASE_URL %>bootstrap-4.4.1/js/bootstrap.bundle.min.js"></script>
<title>甲蛙在线视频课程系统</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
互联网早期,js脚本容易被黑客利用,所以很多浏览器都有禁用js的选项,现在都不会禁用了。
main.js
因为之前的vue版本太高,所以这里我做了一些调整,将vue的版本调到了4.2.3
vue会将我们写的vue文件做编译压缩,导致我们写的html的换行没有了,所以按钮之间的空隙没有了
顶部组件和底部组件提取
1.网站首页开发,将顶部提取成the-header组件
将app.vue中的header复制过来
the-header.vue
app.vue
小提示:在父组件中打入子组件的标签名,会自动引入子组件。
测试
然后再改过来就可以了
1.网站首页开发,将底部提取成the-footer组件
the-footer.vue
app.vue
13-3 首页开发
路由配置与文案修改
1.网站首页开发,增加路由配置router.js,增加网站首页index.vue
index.vue
router.js
main.js
没有使用route-view的话,就没有路由效果
app.vue
测试
再恢复回去
回车
导航条改为bootstrap 导航条组件
1.网站首页开发,导航条改为bootstrap 导航条组件
文档:https://v4.bootcss.com/docs/components/navbar/
the-header.vue
<template>
<header>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-expanded="false">
Dropdown
</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#">Something else here</a>
</div>
</li>
<li class="nav-item">
<a class="nav-link disabled">Disabled</a>
</li>
</ul>
<form class="form-inline my-2 my-lg-0">
<input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
</form>
</div>
</nav>
</header>
</template>
<script>
export default {
name: 'theHeader',
}
</script>
导航条改为bootstrap 导航条组件
1.网站首页开发,导航条美化:菜单名称修改;增加container布局;样式改为dark;
2.集成fontawesome图标
index.html
fontawesome是一个图标库,可以到官网注册后,获取自己的cdn链接
the-header.vue
<template>
<header>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="#">
<i class="ace-icon fa fa-video-camera"></i> 甲蛙课程
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="#">主页 <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item active">
<a class="nav-link" href="#">全部课程</a>
</li>
<li class="nav-item dropdown active">
<a class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-expanded="false">
更多
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="#">关于我们</a>
<a class="dropdown-item" href="#">渠道合作</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#">更多信息</a>
</div>
</li>
</ul>
<span class="text-white">欢迎:</span>
<button class="btn btn-outline-light my-2 my-sm-0" type="submit">登录/注册</button>
</div>
</div>
</nav>
</header>
</template>
<script>
export default {
name: 'theHeader',
}
</script>
测试
底部文案修改
1.网站首页开发,底部文案修改
the-footer.vue
大屏文案修改
1.网站首页开发,大屏文案修改
index.vue
bootstrap4提供了很多便捷的css样式,p-3就是padding:1rem。大家可以使用p-1,p-2来不断调整自己的样式。类似的还有m-x表示margin。
最新上线与好课推荐
1.网站首页开发,增加【新上好课】
后端接口测试地址:http://127.0.0.1:9002/business/web/course/list-new
以后web模块的请求全会放到web包下,将admin请求和web请求分开,方便做权限控制
CourseService.java
CourseController.java
spring提供两种类名生成方式,默认使用的是AnnotationBeanNameGenerator,它生成的方式就是取当前类名(不含包名)。
测试
首页显示新上好课真实数据bootstrap4图片自适应:使用img-fluid
1.网站首页开发,首页显示新上好课真实数据bootstrap4图片自适应:使用img-fluid
安装axios:npm install axios --save
2.初始user表,test/test
前后端交互,使用axios,也可以用jquery
安装axios:npm install axios --save
cd web
npm install axios --save
会自动对package.json,package-lock.json两个文件进行更新
main.js
index.vue
示例代码不要急于删除,在查看新代码显示出来的样式没问题后,再把示例代码删除
首页显示新上好课真实数据bootstrap4图片自适应:使用img-fluid
1.网站首页开发,
课程card美化,
增加【最新上线】和【好课推荐】,【好课推荐】和【最新上线】使用的一样数据,
响应式的页面,使用rem代替px
style.css
如果设置html标签的font-size=16px,则1rem=16px,0.5rem=8px。后续所有字体,边距都可以用rem作为单位
index.html
index.vue
<template>
<main role="main">
<section class="jumbotron text-center">
<div class="container">
<h1>在线视频课程平台</h1>
<p class="lead text-muted m-3">
知识付费时代刚刚起步,在这个领域有很多的发展机会。整个课程以实战为基础,手把手从零开始,
一步一步搭建一个完整的企业级开发架构。不讲废话,只讲干货。
</p>
<p>
<a href="#" class="btn btn-primary my-2 p-3 font-weight-bold">点击进入所有课程</a>
</p>
</div>
</section>
<div class="album py-5 bg-light">
<div class="container">
<div class="title1">最新上线</div>
<div class="row">
<div v-for="o in news" class="col-md-4">
<div class="card mb-4 shadow-sm course">
<img class="img-fluid" v-bind:src="o.image">
<div class="card-body">
<h4 class="">{{o.name}}</h4>
<p class="card-text">{{o.summary}}</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-secondary">View</button>
<button type="button" class="btn btn-sm btn-outline-secondary">Edit</button>
</div>
<div class="text-muted">
<span class="badge badge-info">
<i class="fa fa-yen" aria-hidden="true"></i> {{o.price}}
</span>
<span class="badge badge-info">
<i class="fa fa-user" aria-hidden="true"></i> 123
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<hr>
<div class="title2">好课推荐</div>
<div class="row">
<div v-for="o in news" class="col-md-4">
<div class="card mb-4 shadow-sm course">
<img class="img-fluid" v-bind:src="o.image">
<div class="card-body">
<h4 class="">{{o.name}}</h4>
<p class="card-text">{{o.summary}}</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-secondary">View</button>
<button type="button" class="btn btn-sm btn-outline-secondary">Edit</button>
</div>
<div class="text-muted">
<span class="badge badge-info">
<i class="fa fa-yen" aria-hidden="true"></i> {{o.price}}
</span>
<span class="badge badge-info">
<i class="fa fa-user" aria-hidden="true"></i> 123
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
name: 'index',
data:function (){
return{
news:[],
}
},
mounted() {
let _this = this;
_this.listNew();
},
methods:{
/**
* 查询新上好课
*/
listNew(){
let _this = this;
_this.$ajax.get(process.env.VUE_APP_SERVER + '/business/web/course/list-new')
.then((response)=>{
console.log("查询新上好课结果:",response);
let resp = response.data;
if (resp.success){
_this.news = resp.content;
}
}).catch((response)=>{
console.log("error:",response);
})
},
}
}
</script>
<style>
.title1{
margin-bottom: 2rem;
color: #fafafa;
letter-spacing: 0;
text-shadow: 0px 1px 0px #999, 0px 2px 0px #888, 0px 3px 0px #777, 0px 4px 0px #666, 0px 5px 0px #555, 0px 6px 0px #444, 0px 7px 0px #333, 0px 8px 7px #001135;
font-size: 2rem;
}
.title2{
margin-bottom: 2rem;
color: transparent;
-webkit-text-stroke: 1px black;
letter-spacing: 0.04em;
font-size: 2rem;
}
.course h4{
font-size: 1.25rem;
margin:15px 0;
}
.course .text-muted .badge{
font-size: 1rem;
}
</style>
课程信息组件提取
增加课程card组件the-course
1.网站首页开发,增加课程card组件the-course
the-course.vue
<template>
<div class="card mb-4 shadow-sm course">
<img class="img-fluid" v-bind:src="course.image">
<div class="card-body">
<h4 class="">{{course.name}}</h4>
<p class="card-text">{{course.summary}}</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-secondary">课程详情</button>
</div>
<div class="text-muted">
<span class="badge badge-info">
<i class="fa fa-yen" aria-hidden="true"></i> {{course.price}}
</span>
<span class="badge badge-info">
<i class="fa fa-user" aria-hidden="true"></i> 123
</span>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'the-course',
props:{
course:{}
},
data:function (){
return{}
},
}
</script>
<style>
.course h4{
font-size: 1.25rem;
margin:15px 0;
}
.course .text-muted .badge{
font-size: 1rem;
}
</style>
index.vue
课程card组件增加登记显示
1.网站首页开发,课程card组件增加登记显示
2.增加过滤器filter.js
EnumGenerator.java
Enum生成器里没有自动创建js目录,所以我们要先手动创建js目录,再执行Enum生成器
index.html
filter.js
将admin中的filter文件拷贝过来
main.js
the-course.vue
13-4 课程列表页面开发
课程列表数据显示
1.网站首页开发,增加课程列表页【全部课程】,并增加路由
list.vue
router.js
index.vue
the-header.vue
router-link + to,相当于a + href
测试
课程列表页调用后端接口显示真实数据
1.网站首页开发,课程列表页调用后端接口显示真实数据
CourseController.java
list.vue
后端接口只能查询已发布的课程
1.课程列表页面开发,后端接口只能查询已发布的课程
如果列表查询接口的请求参数,只有分页相关的参数,可以使用PageDto做为传参的类。
如果请求参数包含其它参数,则需要新建一个类CoursePageDto,继承PageDto,来传递更多的参数
CoursePageDto.java
CourseController.java
这里只是修改了接收参数的类,控台端课程列表查询传递的分页参数并没有变化,功能不影响。
CourseController.java(admin)
CourseService.java
status查询条件不是必需的,控台接口进来的就不需要这个条件,网站接口进来的有这个条件,所以增加一个status条件判断。
这段写法比较常见,参数有值就做为条件查询,没值就不加条件,相当于写动态sql
前端分页,增加分页组件,比admin简单,使用rem
1.课程列表页面开发,前端分页
2.增加分页组件,比admin简单,使用rem
pagination.vue
list.vue
分类筛选功能开发
1.课程列表页面开发,加载所有分类
2.增加axios接口访问日志拦截器
main.js
tool.js
把admin中的tool.js复制过来
index.html
list.vue
CategoryController.java
两级分类访问幕课网显示
1.课程列表页面开发,两级分类访问幕课网显示
list.vue
<template>
<main role="main">
<div class="header-nav">
<div class="clearfix">
<div class="container">
<div class="row">
<div class="col-12">
<a v-on:click="onClickLevel1('00000000')" id="category-00000000" href="javascript:;" class="cur">全部</a>
<a v-for="o in level1" v-on:click="onClickLevel1(o.id)" v-bind:id="'category-' + o.id" href="javascript:;">{{o.name}}</a>
</div>
</div>
</div>
</div>
</div>
<div class="skill clearfix">
<div class="container">
<div class="row">
<div class="col-12">
<a v-on:click="onClickLevel2('11111111')" id="category-11111111" href="javascript:;" class="on">不限</a>
<a v-for="o in level2" v-on:click="onClickLevel2(o.id)" v-bind:id="'category-' + o.id" href="javascript:;">{{o.name}}</a>
<div style="clear:both"></div>
</div>
</div>
</div>
</div>
<div class="album py-5 bg-light">
<div class="container">
<div class="row">
<div class="col-md-12">
<pagination ref="pagination" v-bind:list="listCourse"></pagination>
</div>
</div>
<br>
<div class="row">
<div v-for="o in courses" class="col-md-4">
<the-course v-bind:course="o"></the-course>
</div>
<h3 v-show="courses.length === 0">课程还未上架</h3>
<!--这是一个比较常见的写法:如果列表有值,就显示列表,没有值就显示某段文字-->
</div>
</div>
</div>
</main>
</template>
<script>
import TheCourse from "@/components/the-course.vue";
import Pagination from "@/components/pagination.vue";
export default {
name: 'list',
components: {Pagination, TheCourse},
data:function (){
return{
courses:[],
level1:[],
level2:[],
}
},
mounted() {
let _this = this;
_this.$refs.pagination.size = 1;
_this.listCourse(1);
_this.allCategory();
},
methods:{
/**
* 查询课程列表
*/
listCourse(page){
let _this = this;
_this.$ajax.post(process.env.VUE_APP_SERVER + '/business/web/course/list',{
page:page,
size:_this.$refs.pagination.size,
}).then((response)=>{
let resp = response.data;
if (resp.success){
_this.courses = resp.content.list;
_this.$refs.pagination.render(page,resp.content.total);
}
}).catch((response)=>{
console.log("error:",response);
})
},
/**
* 所有分类查询
*/
allCategory() {
let _this = this;
_this.$ajax.post(process.env.VUE_APP_SERVER +"/business/web/category/all").then((response) => {
let resp = response.data;
_this.categorys = resp.content;
//将所有记录格式化成树形结构
_this.level1 = [];
for (let i = 0; i < _this.categorys.length; i++) {
let c = _this.categorys[i];
//如果分类的父id是00000000,表示它是一级分类,否则是二级分类
if (c.parent === '00000000'){
_this.level1.push(c);
for (let j = 0; j < _this.categorys.length; j++) {
let child = _this.categorys[j];
if (child.parent === c.id){
if(Tool.isEmpty(c.children)){
c.children = [];
}
c.children.push(child);
}
}
}else{
_this.level2.push(c);
}
}
})
},
/**
* 点击一级分类时
* @param level1Id
*/
onClickLevel1(level1Id) {
let _this = this;
},
/**
* 点击一级分类时
* @param level2Id
*/
onClickLevel2(level2Id) {
let _this = this;
},
}
}
</script>
<style>
/* 头部 一级分类 */
.header-nav {
height: auto;
background: #fff;
box-shadow: 0 8px 16px 0 rgba(28,31,33,.1);
padding: 16px 0;
box-sizing: border-box;
position: relative;
z-index: 1;
/*background-color: #d6e9c6;*/
}
.header-nav>div {
width: 100%;
padding-left: 12px;
box-sizing: border-box;
margin-left: auto;
margin-right: auto;
/*background-color: #B4D5AC;*/
}
.header-nav a {
float: left;
font-size: 16px;
color: #07111b;
line-height: 50px;
height: 45px;
position: relative;
margin-right: 46px;
font-weight: 700;
}
.header-nav a:hover {
color: #c80;
}
.header-nav a.cur {
color: #c80;
}
.header-nav a.cur:before {
display: block;
}
.header-nav a:before {
display: none;
content: ' ';
position: absolute;
bottom: 0;
background: #c80;
width: 16px;
height: 3px;
left: 50%;
margin-left: -8px;
}
/* 二级分类 */
.skill {
width: 100%;
padding: 24px 0 0;
position: relative;
margin: 0 auto;
}
.skill a.on {
color: #c80;
background: rgba(204,136,0,.1);
}
.skill a {
float: left;
margin-right: 20px;
padding: 0 12px;
font-size: 14px;
color: #4d555d;
line-height: 32px;
border-radius: 6px;
margin-bottom: 12px;
}
.skill a:hover {
background: #d9dde1;
}
</style>
测试
点击一级分类时显示激活状态,并显示对应的二级分类,如果点击的是【全部】,则显示所有的二级分类
1.课程列表页面开发,点击一级分类时显示激活状态,并显示对应的二级分类,如果点击的是【全部】,则显示所有的二级分类
list.vue
测试
点击二级分类时显示激活状态
1.课程列表页面开发,点击二级分类时显示激活状态
list.vue
测试
根据点击的分类进行课程筛选,动态sql不只可以进行动态列查询,也可以进行动态表关联
1.课程列表页面开发,根据点击的分类进行课程筛选,动态sql不只可以进行动态列查询,也可以进行动态表关联
完成功能:当点分类时,筛选出这个分类下所有的视频
list.vue
一级分类的“全部”激活时,level1Id清空;二级分类的“不限”激活时,level2Id清空,这样categoryId这个参数就是空,即查询全部课程。
【课程表】+ 【课程分类关联表】两张表关联。多表关联的查询,需要写自定义sql.
MyCourseMapper.java
CoursePageDto.java
MyCourseMapper.xml
我们正常写的动态sql,一般是动态的where条件,但其实可以写成动态关联表。其实就是动态的拼接sql字符串。
CourseService.java
不加分类做为查询条件时,生成的sql就是course表的单表查询,性能快一些。
测试
13-5 课程详情页面开发
新增详情页面并配置路由
1.课程详情页面开发,增加课程详情页面和路由
2.课程报名数显示真实数据
detail.vue
router.js
the-course.vue
测试
增加后端课程详情接口
1.课程详情页面开发,增加课程后端接口,只能查询已发布的课程,包含内容、讲师、大章、小节信息,测试地址:http://127.0.0.1:9002/business/web/course/find/00000001
CourseDto.java
一次性返回页面上需要有的所有数据,可以减少前后端交易次数,提升用户体验。
TeacherService.java
ChapterService.java
SectionService.java
CourseService.java
CourseController.java
测试
前端界面显示课程信息
1.课程详情页面开发,增加课程信息显示,可以使用@media(max-width:700px)来精细控制不同分辨率的显示样式
detail.vue
如果地址是xxx?id=a。那在vue代码里要获取id的值的话,可以使用this.$router.query.id
测试
增加课程内容显示
1.课程详情页面开发,增加课程内容显示
detail.vue
使用v-html,可以直接绑定显示html字符串
测试
增加讲师信息显示
1.课程详情页面开发,增加讲师信息显示
detail.vue
13-6 章节显示与视频播放
大章小节分组显示
1.课程详情页面开发,增加大章小节显示,
使用d-none d-sm-inline组合:超小屏隐藏,其它显示,
v-for的第二变量是索引号
detail.vue
第一个变量是遍历的每一个实体,第二变量是数组索引,从0开始
d-none d-sm-inline 组合,让小图标在小屏时不显示。屏幕小时,节约空间,只显示核心信息。
增加大章小节收缩和展开,注意:在v-for里写v-show,只修改属性不起作用,需要$set
1.课程详情页面开发,增加大章小节收缩和展开,注意:在v-for里写v-show,只修改属性不起作用,需要$set
detail.vue
从后端加载出来的大章数据,是没有folded属性的,相当于chapter.folded=false.
folded表示收缩,初始是false,表示不收缩,即初始的时候,所有章节都是展开的。
注意:v-for里面,如果用到了v-show,且v-show里面的控制属性值是随着界面操作而变化的,此时只修改属性值是不生效的,vue监听不到数组里的某个元素的值的变化。需要用到$set.
测试
可自行扩展,增加全部收缩,全部展开的功能,提示:全部收缩即所有大章的folded=true;全部展开即所有大章的folded=false.
视频播放
1.课程详情页面开发,增加视频播放功能
video-image.png
index.html
代码从admin复制过来
modal-player.vue
player.vue
再次提示:控台和网站的接口分开,控台使用admin前缀,网站使用web前缀,方便接口权限控制。
VodController.java
detail.vue
只需要引入modal-player组件,不需要引入play组件
测试
对每个大章里的小节进行排序显示
1.课程详情页面开发,对每个大章里的小节进行排序显示
tool.js
detail.vue
只有免费的视频才可直接播放,收费视频需要登录后才能播放
1.课程详情页面开发,只有免费的视频才可直接播放,收费视频需要登录后才能播放
代码从admin复制过来
toast.js
tool.js
index.html
detail.vue
测试
点击收费的,就不能直接可以播放,而是弹出请先登录
13-7 增加会员注册功能
控台增加会员管理功能
1.增加会员管理功能,新增member表,在之前代码生成器的步骤后面,加上以下步骤:
1) 在resource.json中增加新资源
2) 在【资源管理】页面,配置最新的resource.json
3) 在【角色管理】页面,为相应的角色配置新的资源
4) 重新登录控台
自从为控台加入了权限管理功能后,新做的页面,需要增加这四步,配置相应的权限,才能使用新表的管理功能。
all.sql
会员头像的上传和显示,类似于讲师头像的上传和显示,所以我们就不再重复做了。
generatorConfig.xml
ServerGenerator.java
VueGenerator.java
admin.vue
router.js
到目前为止,跟我们前面介绍的制作单表管理功能的流程一样,但由于加入权限功能,所以需要配置权限后才能看到页面。
1) 在resource.json中增加新资源
2) 在【资源管理】页面,配置最新的resource.json
3) 在【角色管理】页面,为相应的角色配置新的资源
4) 重新登录控台
但是一般不会在这里有新增
控台的会员管理,不能新增,修改,删除。可以查看会员信息,重置密码,报表统计等操作
1.增加会员管理功能,控台的会员管理,不能新增,修改,删除。可以查看会员信息,重置密码,报表统计等操作
MemberController.java
member.vue
只剩下这一个查询功能
测试
网站增加会员注册功能
1.会员登录注册功能开发,增加登录注册模态框,增加注册功能
登录表单、注册表单、忘记密码表单放在同一个模态框中
MemberController.java
MemberService.java
md5.js
index.html
login.vue
把登录注册框做成单独的组件,方便维护
和播放器组件类似,引入组件后,初始是看不到组件内的元素的
登录注册框可以做的大气一点,所以把输入框的高度调高,字体调大
the-header.vue
测试
13-8 增加登录与退出登录功能
登录、注册、忘记密码之间的切换
1.会员登录注册功能开发,增加登录框和忘记密码框,并实现登录框、注册框、忘记密码框切换,默认显示登录框
login.vue
<template>
<div id="login-modal" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog modal-login" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="login-div" v-show="MODAL_STATUS === STATUS_LOGIN">
<h3>登 录</h3>
<div class="form-group">
<input v-model="member.mobile" class="form-control" placeholder="手机号">
</div>
<div class="form-group">
<input class="form-control" type="password" placeholder="密码" v-model="member.passwordOriginal">
</div>
<div class="form-group">
<div class="input-group">
<input id="image-code-input" class="form-control" type="text" placeholder="验证码"
v-model="member.imageCode">
<div class="input-group-addon" id="image-code-addon">
<img id="image-code" alt="验证码" v-on:click="loadImageCode()"/>
</div>
</div>
</div>
<div class="form-group">
<button class="btn btn-primary btn-block submit-button">
登 录
</button>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" class="rememberMe" v-model="rememberMe">记住密码
</label>
<div class="pull-right">
<a href="javascript:;" v-on:click="toForgeDiv">忘记密码</a>
<a href="javascript:;" v-on:click="toRegisterDiv">我要注册</a>
</div>
</div>
</div>
<div class="form-group to-register-div">
</div>
</div>
<div class="register-div" v-show="MODAL_STATUS === STATUS_REGISTER">
<h3>注 册</h3>
<div class="form-group">
<input id="register-mobile" v-model="memberRegister.mobile"
class="form-control" placeholder="手机号">
</div>
<div class="form-group">
<div class="input-group">
<input id="register-mobile-code" class="form-control"
placeholder="手机验证码" v-model="memberRegister.code">
<div class="input-group-append">
<button class="btn btn-outline-secondary" id="register-send-code-btn"
v-on:click="sendSmsForRegister()">发送验证码
</button>
</div>
</div>
</div>
<div class="form-group">
<input id="register-name" v-model="memberRegister.name"
class="form-control" placeholder="昵称">
</div>
<div class="form-group">
<input id="register-password" v-model="memberRegister.passwordOriginal"
class="form-control" placeholder="密码" type="password">
</div>
<div class="form-group">
<input id="register-confirm-password" v-model="memberRegister.confirm"
class="form-control" placeholder="确认密码"
name="memberRegisterConfirm" type="password">
</div>
<div class="form-group">
<button class="btn btn-primary btn-block submit-button" v-on:click="register()">
注 册
</button>
</div>
<div class="form-group to-login-div">
<a href="javascript:;" v-on:click="toLoginDiv">我要登录</a>
</div>
</div>
<div class="forget-div" v-show="MODAL_STATUS === STATUS_FORGET">
<h3>忘记密码</h3>
<div class="form-group">
<input id="forget-mobile" v-model="memberForget.mobile" class="form-control" placeholder="手机号">
</div>
<div class="form-group">
<div class="input-group">
<input id="forget-mobile-code" class="form-control"
placeholder="手机验证码" v-model="memberForget.code">
<div class="input-group-append">
<button class="btn btn-outline-secondary" id="forget-send-code-btn">发送验证码
</button>
</div>
</div>
</div>
<div class="form-group">
<input id="forget-password" v-model="memberForget.passwordOriginal"
class="form-control" placeholder="密码" type="password">
</div>
<div class="form-group">
<input id="forget-confirm-password" v-model="memberForget.confirm"
class="form-control" placeholder="确认密码" type="password">
</div>
<div class="form-group">
<button class="btn btn-primary btn-block submit-button">
重 置
</button>
</div>
<div class="form-group to-login-div">
<a href="javascript:;" v-on:click="toLoginDiv">我要登录</a>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name:'the-login',
data:function (){
return{
//模态框内容切换:登录、注册、忘记密码
STATUS_LOGIN:"STATUS_LOGIN",
STATUS_REGISTER:"STATUS_REGISTER",
STATUS_FORGET:"STATUS_FORGET",
MODAL_STATUS:"",
member:{},
memberForget:{},
memberRegister:{},
rememberMe:true,//记住密码
imageCodeToken:""
}
},
mounted() {
let _this = this;
_this.toLoginDiv();
},
methods:{
/**
* 打开登录注册窗口
*/
openLoginModal(){
let _this = this;
$("#login-modal").modal("show");
},
//------------------登录框、注册框、忘记密码框切换---------------------------------
toLoginDiv(){
let _this = this;
_this.MODAL_STATUS = _this.STATUS_LOGIN
},
toRegisterDiv(){
let _this = this;
_this.MODAL_STATUS = _this.STATUS_REGISTER
},
toForgeDiv(){
let _this = this;
_this.MODAL_STATUS = _this.STATUS_FORGET
},
register(){
let _this = this;
_this.memberRegister.password = hex_md5(_this.memberRegister.passwordOriginal + KEY);
//调服务端注册接口
_this.$ajax.post(process.env.VUE_APP_SERVER + '/business/web/member/register',
_this.memberRegister).then((response)=>{
let resp = response.data;
if (resp.success){
Toast.success("注册成功");
}else {
Toast.warning(resp.message);
}
})
},
}
}
</script>
<style>
/*登录框*/
.login-div .input-group-addon{
padding: 0;
border: 0;
}
#login-modal h3{
text-align: center;
margin-bottom: 20px;
}
#login-modal .modal-login{
max-width: 400px;
}
#login-modal input:not(.rememberMe){
height: 45px;
font-size: 16px;
}
#login-modal .submit-button{
height: 50px;
font-size: 20px;
}
#login-modal .to-login-div{
text-align: center;
}
</style>
通过修改MODAL_STATUS变量值,来达到三块div的切换显示
css3 not : 排除选择器
测试
增加登录验证码功能
1.会员登录注册功能开发,登录框显示验证码
KaptchaController.java
login.vue
测试
增加登录功能,包含图片验证码
1.会员登录注册功能开发,增加登录功能,包含图片验证码
将控台用户登录功能相关代码拷贝到会员登录,并将user替换成member
可以从user相关的功能拷过来改一改
local-storage.js(admin)
session-storage.js
local-storage.js
index.html
前端网站叫会员;运营控台叫用户。
tool.js
login.vue
如果有些代码是需要后面再加的,为了防止忘记,可以加TODO,IDEA会帮我们记录所有带TODO的地方。可以在底部TODO窗口查看
ControllerExceptionHandler.java
MemberController.java(web)
控台保存登录用户信息用的是LoginUserDto,网站保存会员信息用的是LoginMemberDto
LoginMemberDto.java
MemberDto.java
BusinessExceptionCode.java
MemberService.java
这里登录时,需要验证手机号是否存在,和控台登录类似,控台登录时,需要验证登录名是否存在
UserService.java
UserController.java
ControllerExceptionHandler.java
测试
完善【记住我】功能,会员登录名是mobile,不是loginName
1.会员登录注册功能开发,完善【记住我】功能
会员登录名是mobile,不是loginName
login.vue
测试
之前“记住我”的逻辑里,登录名写的是loginName,而会员登录名是mobile,所以没记住。
登录成功后,头部导航显示会员昵称
1.会员登录注册功能开发,登录成功后,头部导航显示会员昵称
login.vue
要在子组件(login组件)中调用父组件(the-header组件)的xx方法(setLoginMember方法),可以使用this.$parent.xxx
the-header.vue
测试
增加退出登录功能
1.会员登录注册功能开发,增加退出登录功能
MemberController.java
the-header.vue
测试
13-9 增加发送短信验证码功能
增加短信验证码管理功能
1.会员登录注册功能开发,增加短信管理,生成代码后,需要配置资源权限
all.sql
初始生成的短信状态是未使用,验证过之后,状态改为已使用,防止同一个验证码被重复使用
SmsStatusEnum.java
SmsUseEnum.java
generatorController.xml
ServerGenerator.java
VueGenerator.java
EnumGenerator.java
admin.vue
router.js
resource.json
测试
短信管理只能查看
1.会员登录注册功能开发,短信管理只能查看
主要是删除多余内容
SmsController.java
sms.vue
只剩下list
测试
增加发送验证码功能
1.会员登录注册功能开发,增加发送注册短信功能,同手机号同操作1分钟内不能重复发送短信
一分钟内发了一次注册验证码和一次忘记密码验证码,是可以的。但是发两次注册验证码或两次忘记密码验证码,是不可以的。
EnumGenerator.java
BusinessExceptionCode.java
SmsService.java
短信验证码可以接阿里云短信服务,在阿里云控台配置短信模板,然后在代码里配置模板id,再调用阿里云jar包里的发送短信方法就可以了。收费是0.045元/条
SmsController.java(web)
这个接口是通用的发送短信验证码的接口
login.vue
测试
增加发送短信后倒计时功能,可修改countdown参数进行调试
1.会员登录注册功能开发,增加发送短信后倒计时功能,可修改countdown参数进行调试
前端的校验都是不可靠的,容易被绕过
login.vue
点击发送短信后,按钮变成不可点击,且显示倒计时
setTimeout,只执行一次的定时器。
setInterval,重复执行的定时器。
测试
为了测试
虽然倒计时是6s,但是6s过后也不能直接再次获取,因为后端有校验
因为刷新网站后,倒计时就没有了
13-10 完善登录注册校验功能
增加手机号后端校验
1.会员登录注册功能开发,注册时,发送短信前,先校验会员是否存在
MemberService.java
MemberController.java
login.vue
测试
增加前端正则校验
1.会员登录注册功能开发,注册时发送验证码前,增加手机号校验;手机号输入框失去焦点时,校验手机号
2.增加正则表达式校验工具类
上一版本的校验是后端的校验,这个版本是前端的校验,校验手机号是否符合规则。
pattern.js
Pattern = {
// 用户名正则,2到16位(字母,数字,下划线)
loginNamePattern: /^[a-zA-Z0-9_]{6,16}$/,
// 昵称正则,6到20位中文,字母,数字,下划线
namePattern: /^[\w\u4e00-\u9fa5]{2,20}$/,
// 强密码强度正则,最少8位,包括至少1个大写字母,1个小写字母,1个数字,1个特殊字符
passwordStrongPattern: /^.*(?=.{8,})(?=.*\d)(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#$%^&*? ]).*$/,
// 弱密码强度正则,最少6位,包括至少1字母,1个数字
passwordWeakPattern: /^.*(?=.{6,})(?=.*\d)(?=.*[A-Za-z]).*$/,
// 手机号正则,11位数字,1开头
mobilePattern: /^1\d{10}$/,
// 图片验证码正则,4位字母,数字
imageCodePattern: /^[a-zA-Z0-9]{4}$/,
// 手机验证码正则,6位数字
mobileCodePattern: /^[0-9]{6}$/,
validateLoginName: function (str) {
if (Tool.isEmpty(str)) {
return false;
}
return this.loginNamePattern.test(str);
},
validateName: function (str) {
if (Tool.isEmpty(str)) {
return false;
}
return this.namePattern.test(str);
},
validatePasswordStrong: function (str) {
if (Tool.isEmpty(str)) {
return false;
}
return this.passwordStrongPattern.test(str);
},
validatePasswordWeak: function (str) {
if (Tool.isEmpty(str)) {
return false;
}
return this.passwordWeakPattern.test(str);
},
validateMobile: function (str) {
if (Tool.isEmpty(str)) {
return false;
}
return this.mobilePattern.test(str);
},
validateImageCode: function (str) {
if (Tool.isEmpty(str)) {
return false;
}
return this.imageCodePattern.test(str);
},
validateMobileCode: function (str) {
if (Tool.isEmpty(str)) {
return false;
}
return this.mobileCodePattern.test(str);
}
};
Pattern 里面都是一些常用的正则表达式校验,关于正则表达式是程序员比较不喜欢的一块,学了不常用容易忘,主要是靠平常的积累。
index.html
login.vue
blur事件,当文本框失去焦点(光标从文件框内跳出)会触发blur事件
测试
完善注册输入框校验
1.会员登录注册功能开发,完善注册输入框校验
login.vue
测试
提交注册之前,先校验所有注册输入框,当有一个文本框校验为false时,其它不校验
1.会员登录注册功能开发,提交注册之前,先校验所有注册输入框
当有一个文本框校验为false时,其它不校验
login.vue
只要有一个是false,后面的都不再计算
接下来实现效果:点击注册的时候,所有的输入框都校验一遍
原来的写法是一边校验,一边计算结果,当有一个为false时,就不再校验后边的。现在改为:先所有的文本框校验一遍,再计算最终校验结果。
login.vue
增加短信验证码校验
1.会员登录注册功能开发,注册时增加短信验证码校验功能
短信验证码5分钟内有效,只能验证一次
MemberDto.java
login.vue
BusinessExceptionCode.java
SmsService.java
MemberController.java
测试
故意填错验证码
正确的
增加忘记密码功能
1.会员登录注册功能开发,增加忘记密码功能,包含短信验证,输入框校验等
BusinessExceptionCode.java
login.vue
注册和忘记密码对手机是否存在的校验是反的。注册时,要求手机是不存在的;忘记密码时,要求手机是已存在的。
MemberService.java
MemberController.java
测试
13-11 增加立即报名功能
新增报名表并生成基本代码
1.会员报名课程功能开发,新增报名表,生成服务端和持久层代码
all.sql
一个人对同一门课程只能报名一次,所以加唯一键约束
generatorConfig.xml
登录后可报名课程
1.会员报名课程功能开发,登录后可报名课程
MemberCourseService.java
真实项目中,报名时,需要对接第三方支付,比如支付宝,还要另外做一个支付回调接口,让支付宝告诉你会员支付是否成功,如果支付成功了,再插入报名表
MemberCourseController.java
报名功能就是保存会员课程关联表,相当于生成的save方法
detail.vue
多次用到了互斥显示
测试
已报名的才可播放收费视频
1.会员报名课程功能开发,已报名的才可播放视频
detail.vue
测试
进入课程详情页面时加载报名信息
1.会员报名课程功能开发,加载完课程信息后,如果已登录,则加载报名信息。
刷新页面也会导致前端报名信息丢失
detail.vue
MemberCourseService.java
MemberCourseController.java
测试
这一章算是对前面知识的一个综合考查,整个网站的功能较多,不过按我的步骤一步一步的将功能点拆解,完成起来不算太难。大家在实际工作中也是这样,把大功能分解成一个一个的小功能,再来开发。