SSM项目实战前端开发
通过前面后端的开发,接下来我们来操作前端开发(大致说明一下,不会说明全部)
且要注意,这里会与前面博客中后端项目结合起来
Vue回顾:
项目结构说明:
我们使用脚手架快速构建Vue项目,项目结构如下图:
对应的前端部分项目地址(只要部分,即下面我们来补充与后端的连接):
链接:https://pan.baidu.com/s/1o0yyu9gOtOtwwcaDzsnYDQ
提取码:alsk
注意:我们先不要运行,因为是需要登录的,等后面说明了登录才进行运行,当然也可以进行登录
基本可以随便登录的(因为对应的操作被注释了),且设置了固定值,所以基本可以随便登录,后面会进行改变
部分目录和解释:
Views 目录说明:
我们来一起看一下,前端项目的页面部分:
CourseManage:课程管理
AdvertiseManage:广告管理
PermissionManage:权限管理
CommentManage:公共
Users.vue:用户管理
Login.vue:登录
vue组件化开发:
每一个*.vue 文件都可以看做是一个组件
组件的组成部分:
template:组件的HTML部分
script:组件的JS脚本 (使用ES6语法编写)
style:组件的CSS样式
< template>
< div>
测试页面...
</ div>
</ template>
< script>
export default {
name : "Home" ,
data ( ) {
return { }
} ,
methods : { }
}
</ script>
< style scoped >
</ style>
课程模块回顾 :
注意:下面代码是项目里的主要代码
功能分析:
Course.vue 组件(改变名称后),完成课程数据的展示和条件查询
使用ElementUI 表格进行数据展示
https://element.eleme.cn/#/zh-CN/component/table
部分最开始请求:
loadCourses ( ) {
this . loading = true ;
const data = { } ;
if ( this . filter. courseName) data. courseName = this . filter. courseName;
if ( this . filter. status) data. status = this . filter. status;
return axios
. post ( "/course/findAllCourse" , data)
. then ( resp => {
this . courses = resp. data. content;
this . loading = false ;
} )
. catch ( error => {
this . $message. error ( "数据获取失败! ! !" ) ;
} ) ;
}
prop可以说是绑定数据的对应属性名数据
对应js代码:
data ( ) {
const filter = {
courseName : "" ,
status : ""
} ;
return {
filter,
courses : [ ] ,
loading : false
} ;
} ,
created ( ) {
this . loadCourses ( ) ;
}
条件查询(访问的是同一个接口 ):
< el-button @click = " handleFilter" > 查询</ el-button>
handleFilter ( ) {
this . loadCourses ( ) ;
}
新建课程 :
功能分析:
点击新建,由路由导航到 CourseItem.vue
< el-button type = " primary" icon = " el-icon-plus" @click = " handleAdd" > 新建课程</ el-button>
handleAdd ( ) {
this . $router. push ( { name : "CourseItem" , params : { courseId : "new" } } ) ;
}
router.js:
{
path : "/courses/:courseId" ,
name : "CourseItem" ,
meta : { requireAuth : true , title : "课程详情" } ,
component : ( ) =>
import (
"../views/CourseManage/CourseItem.vue"
)
}
CourseItem组件使用ElementUI中的表单来提交课程数据:
https://element.eleme.cn/#/zh-CN/component/form
JS代码编写 :
< el-button type = " primary" @click = " handleSave" > 保存</ el-button>
handleSave ( ) {
this . $refs. form. validate ( valid => {
if ( ! valid) return false ;
axios
. post ( "/course/saveOrUpdateCourse" , this . course)
. then ( res => {
this . $router. back ( ) ;
} )
. catch ( error => {
this . $message. error ( "保存课程信息失败! ! !" ) ;
} ) ;
} ) ;
} ,
课程图片上传:
案例演示:
我们创建一个Vue项目来演示图片上传组件的使用方式
首先要有一个项目:
代码地址如下:
链接:https://pan.baidu.com/s/1PsQRmqdbr3Jj2cbr2X6pcQ
提取码:alsk
我们用这个项目来操作测试
注意上传图片是有要求的,因为目标的服务器(这里是)只会接收符合他条件的图片,如果上传不了,可以上传其他图片
在测试项目的components目录下创建一个 UploadImage.vue组件:
查看ElementUI文档,复制代码到 UploadImage.vue
https://element.eleme.cn/#/zh-CN/component/upload
< template>
< div>
< el-upload
action = " https://jsonplaceholder.typicode.com/posts/"
list-type = " picture-card"
:on-preview = " handlePictureCardPreview"
:on-remove = " handleRemove"
>
< i class = " el-icon-plus" > </ i>
</ el-upload>
< el-dialog :visible.sync = " dialogVisible" >
< img width = " 100%" :src = " dialogImageUrl" alt = " " />
</ el-dialog>
</ div>
</ template>
< script>
export default {
data ( ) {
return {
dialogImageUrl : "" ,
dialogVisible : false ,
} ;
} ,
methods : {
handleRemove ( file, fileList ) {
console. log ( file, fileList) ;
} ,
handlePictureCardPreview ( file ) {
this . dialogImageUrl = file. url;
this . dialogVisible = true ;
} ,
} ,
} ;
</ script>
然后在components目录下的index.vue修改或者添加(也是一个操作路由的):
< el-submenu index = " 1" >
< template slot = " title" >
< i class = " el-icon-location" > </ i>
< span> 导航菜单</ span>
</ template>
< el-menu-item-group>
< el-menu-item index = " /upload" >
< i class = " el-icon-menu" > </ i> 图片上传
</ el-menu-item>
</ el-menu-item-group>
</ el-submenu>
配置路由(操作router目录下的index.js):
children : [
{
path : "/upload" ,
name : "upload" ,
component : ( ) => import ( "@/components/UploadImage" ) ,
}
]
最后可以执行操作了,即访问页面进行测试
属性说明 (了解即可):
上面的action是将图片上传到那里去
组件的引入:
怎么将一个组件引入另一个组件
接下来我们来演示一下 引入图片组件(实际上是根据import导入后来注册使用组件来操作的,这里是局部组件)
在测试项目components目录下创建一个TestUplopad.vue组件:
< template>
< upload-image> </ upload-image>
</ template>
< script>
import UploadImage from '@/components/UploadImage'
export default {
components : { UploadImage } ,
}
</ script>
< style scoped >
</ style>
然后在对应的index.vue中加上:
< el-menu-item-group>
< el-menu-item index = " /text" >
< i class = " el-icon-menu" > </ i> 测试组件的引入
</ el-menu-item>
</ el-menu-item-group>
加上对应路由(index.js):
{
path : "/text" ,
name : "text" ,
component : ( ) => import ( "@/components/TextUpload" ) ,
}
然后就可以进行测试了
对应的项目执行流程可以参照58章博客
组件的传参 :
UploadImage.vue组件的部分修改
data ( ) {
return {
dialogImageUrl : "" ,
dialogVisible : false ,
actionUrl : this . uploadUrl
} ;
} ,
props : [ "uploadUrl" , "getUrl" ] ,
< div>
< el-upload
:action = " actionUrl"
list-type = " picture-card"
:on-preview = " handlePictureCardPreview"
:on-remove = " handleRemove"
:on-success = " successUpload"
>
对应方法:
successUpload ( response, file ) {
this . getUrl ( file) ;
}
对应操作导入上面组件的TestUplopad.vue组件的部分修改:
< upload-image uploadUrl = " https://jsonplaceholder.typicode.com/posts/" :get-url = " show" > </ upload-image>
对应方法:
methods : {
show ( file ) {
console. log ( "show方法被调用了" )
console. log ( file) ;
}
}
接下来进行测试了
我们通过上面的测试操作,接下来再看看如下操作
课程模块图片上传
我们可以看看对应的CourseItem.vue组件的图片上传代码
< upload-image
:content = " course.courseImgUrl && [course.courseImgUrl]"
:get-urls = " getCourseImgUrl"
uploadUrl = " /course/courseUpload"
ref = " courseCoverRef"
max = " 10M"
tipInfo = " 建议尺寸:230*300px,JPG、PNG格式,图片小于10M"
> </ upload-image>
对应部分js如下
import UploadImage from "@/components/UploadImage.vue" ;
对应的部分UploadImage.vue组件:
< el-upload
class = " upload-demo"
:action = " uploadAction"
:multiple = " false"
:limit = " 1"
:before-upload = " beforeUpload"
:on-success = " uploadSuccess"
:on-remove = " removeSuccess"
:on-exceed = " exceedFile"
:file-list = " fileList"
list-type = " picture-card"
>
对应的js
data ( ) {
return {
uploadAction : process. env. VUE_APP_API_BASE + this . uploadUrl,
fileList : [ ] ,
data : { }
} ;
} ,
props : [ "content" , "getUrls" , "max" , "tipInfo" , "uploadUrl" ] ,
发现也是一样的操作
注意:只要不是写死的资源,那么基本都会在检查里中的网络发现访问(注意是全部请求)
但是该访问是自己到这个页面的访问,而在该页面其中操作时,不会显示出来
ctrl+d可以查看当前页面代码选中的其他代码,一般从上到下查找,可以多次按
修改课程 :
CourseItem.vue组件部分代码
axios
. post ( "/course/saveOrUpdateCourse" , this . course)
. then ( res => {
this . $router. back ( ) ;
} )
. catch ( error => {
this . $message. error ( "保存课程信息失败! ! !" ) ;
} ) ;
修改课程与添加课程走的都是同一个后台接口,区别是修改操作必须要携带ID
在这个Typora中按ctrl+.(点)可以切换是否可以输入中文符号,一般默认可以输入
课程状态管理 (Course.vue组件):
点击上架或者下架完成课程状态的切换
部分代码:
axios
. get ( "/course/updateCourseStatus" , {
params : {
status : toggledStatus,
id : item. id
}
} )
. then ( res => {
debugger ;
item. status = toggledStatus;
console. log ( item) ;
window. location. reload;
} )
. catch ( error => {
this . $message. error ( "状态修改失败! ! !" ) ;
} ) ;
课程内容管理 :
获取课程内容数据:
课程内容数据包括章节与课时信息,根据课程ID 查询课程包含的章节与课时信息
CourseSections.vue组件部分代码:
axios
. get ( "/courseContent/findCourseByCourseId?courseId=" + id)
. then ( res => {
const course = res. data. content;
this . addSectionForm. courseId = course. id;
this . addSectionForm. courseName = course. courseName;
this . addLessonForm. courseId = course. id;
this . addLessonForm. courseName = course. courseName;
} )
. catch ( error => {
this . $message. error ( "数据获取失败! ! !" ) ;
} ) ;
章节管理:
新建和修改章节:
CourseSections.vue组件部分代码
axios
. post ( "/courseContent/saveOrUpdateSection" , this . addSectionForm)
. then ( res => {
this . showAddSection = false ;
return this . loadSections ( this . addSectionForm. courseId) ;
} )
. then ( ( ) => {
this . addSectionForm. sectionName = "" ;
this . addSectionForm. description = "" ;
this . addSectionForm. orderNum = 0 ;
this . reload ( ) ;
} )
. catch ( error => {
this . showAddSection = false ;
this . $message. error ( "操作执行失败! ! !" ) ;
} ) ;
添加与修改章节访问的都是同一个接口
章节状态:
CourseSections.vue组件部分代码
axios
. get ( "/courseContent/updateSectionStatus" , {
params : {
id : this . toggleStatusForm. id,
status : this . toggleStatusForm. status
}
} )
. then ( resp => {
this . toggleStatusForm. data. status = this . toggleStatusForm. status;
this . toggleStatusForm = { } ;
this . showStatusForm = false ;
this . reload ( ) ;
} )
. catch ( error => {
this . showStatusForm = false ;
this . $message. error ( "修改状态失败! ! !" ) ;
} ) ;
课时管理基本与章节类似,也就不操作了
大致回顾完毕,接下来我们来操作不一样的
上面是回顾,所以对应登录时可以操作,下面才是真正要操作的地方,所以当你没有编写下面代码,基本是访问不了的
广告模块:
广告位管理:
广告位展示 :
AdvertiseSpaces.vue 组件,为广告位页面
我们需要在其前端项目上进行添加访问后端的代码:
对应的js部分:
loadPromotionSpace ( ) {
this . listLoading = true ;
axios
. get ( "/PromotionSpace/PromotionSpace" )
. then ( res => {
this . list = res. data. content;
this . listLoading = false ;
} )
. catch ( err => {
this . $message. error ( "加载广告位列表数据失败" ) ;
} ) ;
} ,
添加广告位:
在AdvertiseSpaces.vue 组件里有:
< el-button size = " mini" class = " btn-add" @click = " handleAdd()"
> 添加广告位</ el-button>
进行跳转:
对应的js部分:
handleAdd ( ) {
this . $router. push ( { path : "/addAdvertiseSpace" } ) ;
} ,
查看部分路由routes.js(不要认为非常复杂,我们可以将路由看成将vue组件指定显示地方的操作):
{
path : "addAdvertiseSpace" ,
name : "AddAdvertiseSpace" ,
component : ( ) => import ( "../views/AdvertiseManage/AddAdvertiseSpace" ) ,
meta : { requireAuth : true , title : "添加广告位" }
} ,
发现跳转的是AddAdvertiseSpace.vue组件:
js部分(对该组件进行了引用)
< script>
import HomeAdvertiseDetail from "./AdvertiseSpaceDetail" ;
export default {
name : "addHomeAdvertise" ,
title : "添加广告位" ,
components : { HomeAdvertiseDetail }
} ;
</ script>
最后发现是使用了AdvertiseSpaceDetail.vue组件,一般组件里是不能写script标签和style标签的,不会识别
对应js操作
created ( ) {
if ( this . isEdit) {
} else {
this . homeAdvertise = { }
}
} ,
methods : {
handleSave ( ) {
this . $refs. form. validate ( valid => {
if ( ! valid) return false ;
axios. post ( "/PromotionSpace/saveOrUpdatePromotionSpace" , this . homeAdvertise) . then ( res => {
this . $router. back ( ) ;
} ) . catch ( err => {
this . $message. error ( "保存广告位信息失败" ) ;
} ) ;
} )
} ,
}
修改广告位:
对应的跳转步骤与新增类似,可以自行查看(在UpdateAdvertiseSpace.vue组件)
对应js部分:
if ( this . isEdit) {
const id = this . $route. query. id
this . loadPromotionSpace ( id)
}
loadPromotionSpace ( id ) {
return axios. get ( "/PromotionSpace/findPromotionSpaceById?id=" + id) . then ( res => {
Object. assign ( this . homeAdvertise, res. data. content)
this . homeAdvertise. id = id;
} ) . catch ( err => {
this . $message. error ( "回显广告位信息失败" ) ;
} ) ;
}
后台访问方法是一样的,就不做说明了
广告管理:
广告列表的展示,使用到了分页组件,接下来通过一个案例演示一下分页插件的使用
ElementUI 分页组件:
https://element.eleme.cn/#/zh-CN/component/pagination
在测试项目中,创建一个PageList.vue组件,复制代码如下(进行了修改)
< template>
< div>
< div class = " block" >
< span class = " demonstration" > 完整功能</ span>
< el-pagination
@size-change = " handleSizeChange"
@current-change = " handleCurrentChange"
:current-page = " currentPage4"
:page-sizes = " [10, 20, 30, 40]"
:page-size = " 40"
layout = " total, sizes, prev, pager, next, jumper"
:total = " 400" >
</ el-pagination>
</ div>
</ div>
</ template>
< script>
export default {
methods : {
handleSizeChange ( val ) {
console. log ( ` 每页 ${ val} 条 ` ) ;
} ,
handleCurrentChange ( val ) {
console. log ( ` 当前页: ${ val} ` ) ;
}
} ,
data ( ) {
return {
currentPage4 : 4
} ;
}
}
</ script>
对应路由添加(通过地址将该组件渲染到路由地方):
{
path : "/page" ,
name : "page" ,
component : ( ) => import ( "@/components/PageList" ) ,
}
对应index.vue的添加(这个一般是侧边栏的操作,使得访问地址,进行页面的跳转,将组件放到对应地方显示):
< el-menu-item-group>
< el-menu-item index = " /page" >
< i class = " el-icon-menu" > </ i> 分页组件
</ el-menu-item>
</ el-menu-item-group>
接下来我们可以进行测试了
现在我们搞一个数据,赋值项目的代码到该测试项目里面
修改后的PageList.vue组件:
< template>
< div class = " app-container" >
< div class = " table-container" >
< el-table
ref = " homeAdvertiseTable"
:data = " list"
style = " width : 100%; "
v-loading = " listLoading"
border
>
< el-table-column label = " id" width = " 120" align = " center" >
< template slot-scope = " scope" > {{scope.row.id}}</ template>
</ el-table-column>
< el-table-column label = " 广告名称" align = " center" >
< template slot-scope = " scope" > {{scope.row.name}}</ template>
</ el-table-column>
< el-table-column label = " 广告图片" width = " 120" align = " center" >
< template slot-scope = " scope" >
< img style = " height : 80px" :src = " scope.row.img" />
</ template>
</ el-table-column>
</ el-table>
</ div>
< div class = " pagination-container" >
< el-pagination
background
@size-change = " handlePageSizeChange"
@current-change = " handleCurrentPageChange"
layout = " total, sizes,prev, pager, next,jumper"
:current-page = " page"
:page-sizes = " [5,10, 20]"
:page-size = " size"
:total = " total"
> </ el-pagination>
</ div>
</ div>
</ template>
< script>
export default {
data ( ) {
return {
list : [ ] ,
page : 1 ,
size : 5 ,
total : 0 ,
listLoading : true
} ;
} ,
created ( ) {
this . loadList ( ) ;
} ,
methods : {
loadList ( ) {
return
this . axios. get ( "http://localhost:8080/ssm_web/PromotionAd/findAllPromotionAdByPage" ,
{ params : {
CurrentPage : this . page,
PageSize : this . size
} } ) . then ( res => {
console. log ( res) ;
this . list = res. data. content. list;
this . total = res. data. content. total;
console. log ( this . tolal)
this . listLoading= false
} ) . catch ( err => {
console. log ( "获取广告数据失败" ) ;
} )
} ,
handlePageSizeChange ( val ) {
this . size = val;
this . loadList ( ) ;
} ,
handleCurrentPageChange ( val ) {
this . page = val;
this . loadList ( ) ;
}
} ,
}
</ script>
然后就可以操作对应的分页了
测试完成后,接下来我们来看真正的项目代码
广告列表展示 :
通过观察项目的Advertises.vue组件来操作
我们已经解决了分页问题,接下来再看一下对应组件代码编写:
< template>
< div class = "app-container" >
< el- card class = "operate-container" shadow= "never" >
< el- button size= "mini" class = "btn-add" @click= "handleAdd()" > 添加广告< / el- button>
< / el- card>
< div class = "table-container" >
< el- table
ref= "homeAdvertiseTable"
: data= "list"
style= "width: 100%;"
v- loading= "listLoading"
border
>
< el- table- column label= "id" width= "120" align= "center" >
< template slot- scope= "scope" > { { scope. row. id} } < / template>
< / el- table- column>
< el- table- column label= "广告名称" align= "center" >
< template slot- scope= "scope" > { { scope. row. name} } < / template>
< / el- table- column>
< ! -- 获取广告位置信息 spaceId 外键id-- >
< el- table- column label= "广告位置" width= "200" align= "center" >
< template slot- scope= "scope" > { { getSpaceName ( scope. row. spaceId) } } < / template>
< / el- table- column>
< el- table- column label= "广告图片" width= "120" align= "center" >
< template slot- scope= "scope" >
< img style= "height: 80px" : src= "scope.row.img" / >
< / template>
< / el- table- column>
< el- table- column label= "时间" width= "250" align= "center" >
< template slot- scope= "scope" >
< p> 开始时间:{ { scope. row. startTime | formatTime} } < / p>
< p> 到期时间:{ { scope. row. endTime | formatTime} } < / p>
< / template>
< / el- table- column>
< ! -- 上线与下线 -- >
< el- table- column label= "上线/下线" width= "120" align= "center" >
< template slot- scope= "scope" >
< el- switch
@change= "handleUpdateStatus(scope.row)"
: active- value= "1"
: inactive- value= "0"
v- model= "scope.row.status"
> < / el- switch >
< / template>
< / el- table- column>
< ! -- 编辑按钮 -- >
< el- table- column label= "操作" width= "120" align= "center" >
< template slot- scope= "scope" >
< el- button size= "mini" type= "text" @click= "handleUpdate(scope.row)" > 编辑< / el- button>
< / template>
< / el- table- column>
< / el- table>
< / div>
< ! -- 分页 -- >
< div class = "pagination-container" >
< el- pagination
background
@size- change= "handlePageSizeChange"
@current- change= "handleCurrentPageChange"
layout= "total, sizes,prev, pager, next,jumper"
: current- page= "page"
: page- sizes= "[5,10, 20]"
: page- size= "size"
: total= "total"
> < / el- pagination>
< / div>
< / div>
< / template>
< script>
import { axios } from "../../utils" ;
import moment from "moment" ;
export default {
name : "homeAdvertiseList" ,
title : "广告管理" ,
inject : [ "reload" ] ,
data ( ) {
return {
typeMap : { } ,
total : 0 ,
size : 5 ,
page : 1 ,
list : [ ] ,
listLoading : false
} ;
} ,
created ( ) {
this . loadPromotionAd ( ) ;
this . loadPromotionSpace ( ) ;
console. log ( this . typeMap) ;
} ,
methods : {
loadPromotionAd ( ) {
this . listLoading = true ;
axios. get ( "/PromotionAd/findAllPromotionAdByPage" ,
{ params : {
CurrentPage : this . page,
PageSize : this . size
} } )
. then ( res => {
this . list = res. data. content. list;
this . total = res. data. content. total;
this . listLoading = false
} )
. catch ( err => {
this . $message. error ( "获取广告列表数据失败" ) ;
} )
} ,
loadPromotionSpace ( ) {
this . listLoading = true ;
axios. get ( "PromotionSpace/findAllPromotionSpace" )
. then ( res => {
console. log ( res. data. content)
res. data. content. map ( item => {
console. log ( item)
this . typeMap[ item. id] = item;
} )
this . listLoading = false ;
} )
. catch ( err => {
this . $message. error ( "获取广告位置数据失败" ) ;
}
) } ,
getSpaceName ( spaceId ) {
if ( ! spaceId) {
return "" ;
}
return this . typeMap[ spaceId] && this . typeMap[ spaceId] . name;
} ,
handleUpdateStatus ( row ) { } ,
handleAdd ( ) {
this . $router. push ( { path : "/addAdvertise" } ) ;
} ,
handleUpdate ( row ) {
this . $router. push ( { path : "/updateAdvertise" , query : { id : row. id } } ) ;
} ,
handleCurrentPageChange ( page ) {
this . page = page;
this . loadPromotionAd ( ) ;
} ,
handlePageSizeChange ( size ) {
this . size = size;
this . loadPromotionAd ( ) ;
}
} ,
filters : {
formatTime ( time ) {
return moment ( time) . format ( "YYYY-MM-DD HH:mm:ss" ) ;
}
}
} ;
< / script>
< style scoped>
. input- width {
width : 203px;
}
< / style>
我们编写了对应js代码,但还有些是我们需要编写的,所以接下来我们来进行操作
广告状态修改:
对应html:
< el-table-column label = " 上线/下线" width = " 120" align = " center" >
< template slot-scope = " scope" >
< el-switch
@change = " handleUpdateStatus(scope.row)"
:active-value = " 1"
:inactive-value = " 0"
v-model = " scope.row.status"
> </ el-switch>
</ template>
</ el-table-column>
添加部分对应js部分:
我们首先去https://element.eleme.cn/#/zh-CN/component/message-box(Element-UI)里面找到对应的js代码复制过来并进行操作
handleUpdateStatus ( row ) {
this . $confirm ( '是否要修改上线/下线状态?' , '提示' , {
confirmButtonText : '确定' ,
cancelButtonText : '取消' ,
type : 'warning'
} ) . then ( res => {
axios. get ( "/PromotionAd/updatePromotionAdStatus" , { params : {
id : row. id,
status : row. status
} } ) . then ( res => {
this . loadPromotionAd ( )
} ) . catch ( err => {
this . $message. error ( "修改状态失败" )
} )
} ) . catch ( err => {
row. status== 1 ? row. status= 0 : row. status= 1 ;
} )
} ,
广告新增&修改 :
新增操作(对应的html):
< el-button size = " mini" class = " btn-add" @click = " handleAdd()" > 添加广告</ el-button>
对应的js:
handleAdd ( ) {
this . $router. push ( { path : "/addAdvertise" } ) ;
} ,
对应路由:
{
path : "addAdvertise" ,
name : "AddAdvertise" ,
component : ( ) => import ( "../views/AdvertiseManage/AddAdvertise" ) ,
meta : { requireAuth : true , title : "添加广告" }
} ,
对应的AddAdvertise.vue组件:
< template>
< home-advertise-detail :isEdit = " false" > </ home-advertise-detail>
</ template>
< script>
import HomeAdvertiseDetail from './AdvertiseDetail'
export default {
name : 'addHomeAdvertise' ,
title : '添加广告' ,
components : { HomeAdvertiseDetail }
}
</ script>
< style> </ style>
对应的AdvertiseDetail.vue组件(这里进行修改操作,因为最后操作的就是这个组件了):
< template>
< el-card class = " form-container" shadow = " never" >
< el-form :model = " homeAdvertise" :rules = " rules" ref = " form" label-width = " 150px" size = " small" >
< el-form-item label = " 广告名称:" prop = " name" >
< el-input v-model = " homeAdvertise.name" class = " input-width" > </ el-input>
</ el-form-item>
< el-form-item label = " 广告位置:" >
< el-select v-model = " homeAdvertise.spaceId" >
< el-option
v-for = " type in typeOptions"
:key = " type.value"
:label = " type.label"
:value = " type.value"
> </ el-option>
</ el-select>
</ el-form-item>
< el-form-item label = " 开始时间:" prop = " startTime" >
< el-date-picker type = " datetime" placeholder = " 选择日期" v-model = " homeAdvertise.startTime" >
</ el-date-picker>
</ el-form-item>
< el-form-item label = " 到期时间:" prop = " endTime" >
< el-date-picker type = " datetime" placeholder = " 选择日期" v-model = " homeAdvertise.endTime" >
</ el-date-picker>
</ el-form-item>
< el-form-item label = " 上线/下线:" >
< el-radio-group v-model = " homeAdvertise.status" >
< el-radio :label = " 0" > 下线</ el-radio>
< el-radio :label = " 1" > 上线</ el-radio>
</ el-radio-group>
</ el-form-item>
< el-form-item label = " 广告图片:" >
< upload-image
:content = " homeAdvertise.img && [homeAdvertise.img]"
:get-urls = " getADImgUrl"
uploadUrl = " /PromotionAd/PromotionAdUpload"
ref = " courseCoverRef"
max = " 10M"
tipInfo = " 建议尺寸:230*300px,JPG、PNG格式,图片小于150K"
/>
</ el-form-item>
< el-form-item label = " 广告链接:" prop = " link" >
< el-input v-model = " homeAdvertise.link" class = " input-width" > </ el-input>
</ el-form-item>
< el-form-item label = " 广告备注:" >
< el-input
class = " input-width"
type = " textarea"
:rows = " 5"
placeholder = " 请输入内容"
v-model = " homeAdvertise.text"
> </ el-input>
</ el-form-item>
< el-form-item>
< el-button type = " primary" @click = " handleSave()" > 提交</ el-button>
</ el-form-item>
</ el-form>
</ el-card>
</ template>
< script>
import UploadImage from "@/components/UploadImage.vue" ;
import { axios } from "../../utils" ;
const homeAdvertise = {
id : undefined ,
name : null ,
spaceId : "" ,
img : null ,
startTime : null ,
endTime : null ,
status : 0 ,
link : null ,
text : null ,
sort : 0
} ;
const rules = {
name : [
{ required : true , message : "请输入广告名称" , trigger : "blur" } ,
{
min : 2 ,
max : 140 ,
message : "长度在 2 到 140 个字符" ,
trigger : "blur"
}
] ,
link : [ { required : true , message : "请输入广告链接" , trigger : "blur" } ] ,
startTime : [ { required : true , message : "请选择开始时间" , trigger : "blur" } ] ,
endTime : [ { required : true , message : "请选择到期时间" , trigger : "blur" } ] ,
img : [ { required : true , message : "请选择广告图片" , trigger : "blur" } ]
} ;
export default {
name : "HomeAdvertiseDetail" ,
components : { UploadImage } ,
props : {
isEdit : {
type : Boolean,
default : false
}
} ,
data ( ) {
return {
homeAdvertise,
typeOptions : [ ] ,
rules
} ;
} ,
created ( ) {
if ( this . isEdit) {
const id = this . $route. query. id;
this . loadPromotion ( id) ;
} else {
this . homeAdvertise = { } ;
}
this . loadPromotionSpace ( ) ;
} ,
methods : {
loadPromotionSpace ( ) {
return axios. get ( "/PromotionSpace/findAllPromotionSpace" ) . then ( res => {
this . typeOptions = res. data. content. map ( item => {
return {
value : item. id,
label : item. name
} ;
} ) ;
console. log ( this . typeOptions) ;
} )
} ,
handleSave ( ) {
this . $refs. form. validate ( valid => {
if ( ! valid) return false ;
if ( this . homeAdvertise. id == undefined ) {
if ( this . homeAdvertise. startTime != undefined && this . homeAdvertise. endTime!= undefined ) {
var year = this . homeAdvertise. startTime. getFullYear ( ) ;
var month = this . homeAdvertise. startTime. getMonth ( ) + 1 ;
var day = this . homeAdvertise. startTime. getDate ( ) ;
var hour = this . homeAdvertise. startTime. getHours ( ) ;
var mm = this . homeAdvertise. startTime. getMinutes ( ) ;
var s = this . homeAdvertise. startTime. getSeconds ( ) ;
this . homeAdvertise. startTime= year+ "-" + month+ "-" + day+ " " + hour+ ":" + mm+ ":" + s;
year = this . homeAdvertise. endTime. getFullYear ( ) ;
month = this . homeAdvertise. endTime. getMonth ( ) + 1 ;
day = this . homeAdvertise. endTime. getDate ( ) ;
hour = this . homeAdvertise. endTime. getHours ( ) ;
mm = this . homeAdvertise. endTime. getMinutes ( ) ;
s = this . homeAdvertise. endTime. getSeconds ( ) ;
this . homeAdvertise. endTime= year+ "-" + month+ "-" + day+ " " + hour+ ":" + mm+ ":" + s;
}
}
console. log ( this . homeAdvertise. startTime) ;
console. log ( this . homeAdvertise. endTime) ;
axios. post ( "/PromotionAd/saveOrUpdatePromotionAd" , this . homeAdvertise) . then ( res => {
this . $router. back ( ) ;
} ) . catch ( err => {
this . $message. error ( "保存失败" ) ;
} ) ;
} )
} ,
loadPromotion ( id ) { } ,
getADImgUrl ( urls ) {
if ( urls. length > 0 ) {
this . homeAdvertise. img = urls && urls[ 0 ] . filePath;
}
}
}
} ;
</ script>
< style scoped >
.input-width {
width : 70%;
}
</ style>
修改操作对应的html
< el-button size = " mini" type = " text" @click = " handleUpdate(scope.row)" > 编辑</ el-button>
对应的js:
handleUpdate ( row ) {
this . $router. push ( { path : "/updateAdvertise" , query : { id : row. id } } ) ;
} ,
对应的路由:
{
path : "updateAdvertise" ,
name : "UpdateAdvertise" ,
component : ( ) => import ( "../views/AdvertiseManage/UpdateAdvertise" ) ,
meta : { requireAuth : true , title : "编辑广告" }
} ,
对应的UpdateAdvertise.vue组件:
< template>
< home-advertise-detail :isEdit = " true" > </ home-advertise-detail>
</ template>
< script>
import HomeAdvertiseDetail from './AdvertiseDetail'
export default {
name : 'updateHomeAdvertise' ,
title : '编辑广告' ,
components : { HomeAdvertiseDetail }
}
</ script>
< style> </ style>
发现他最后使用的是AdvertiseDetail组件,与新增是一样的
即我们任然操作AdvertiseDetail组件(实际上修改和添加之所以可以使用一个方法,是因为前端操作了对应标识):
< home-advertise-detail :isEdit = " true" > </ home-advertise-detail>
然后通过对应的true和false,给对应对象加上id,使得操作新增和修改
接下来我们看一看修改的主要js代码:
loadPromotion ( id ) {
return axios. get ( "/PromotionAd/findPromotionAdById?id=" + id) . then ( res => {
Object. assign ( this . homeAdvertise, res. data. content) ;
this . homeAdvertise. id = id;
} ) . catch ( err => {
this . $message. error ( "回显广告信息失败" ) ;
} )
} ,
用户管理:
分页&条件查询用户数据:
日期选择器组件 :
在查询条件中使用了 ElementUI中的日期选择器,我们一起来简单学习一下日期选择器的使用
https://element.eleme.cn/#/zh-CN/component/date-picker#mo-ren-xian-shi-ri-qi
在测试项目中,创建一个 TestDate.vue组件,复制代码到页面(下面我进行了修改)
注意复制粘贴时,记得组件只能有一个根标签,若报错了基本就是这个错误,在最外面加上div即可(其他也行,但最好div)
对应html:
< template>
< div class = " block" >
< span class = " demonstration" > 带快捷选项</ span>
< el-date-picker
v-model = " dateTime"
type = " daterange"
align = " right"
unlink-panels
range-separator = " 至"
start-placeholder = " 开始日期"
end-placeholder = " 结束日期"
:picker-options = " pickerOptions" >
</ el-date-picker>
< el-button type = " primary" @click = " getDate" > 查询</ el-button>
</ div>
</ template>
< script>
export default {
data ( ) {
return {
pickerOptions : {
shortcuts : [ {
text : '最近一周' ,
onClick ( picker ) {
const end = new Date ( ) ;
const start = new Date ( ) ;
start. setTime ( start. getTime ( ) - 3600 * 1000 * 24 * 7 ) ;
picker. $emit ( 'pick' , [ start, end] ) ;
}
} , {
text : '最近一个月' ,
onClick ( picker ) {
const end = new Date ( ) ;
const start = new Date ( ) ;
start. setTime ( start. getTime ( ) - 3600 * 1000 * 24 * 30 ) ;
picker. $emit ( 'pick' , [ start, end] ) ;
}
} , {
text : '最近三个月' ,
onClick ( picker ) {
const end = new Date ( ) ;
const start = new Date ( ) ;
start. setTime ( start. getTime ( ) - 3600 * 1000 * 24 * 90 ) ;
picker. $emit ( 'pick' , [ start, end] ) ;
}
} ]
} ,
dateTime : ''
} ;
} ,
methods : {
getDate ( ) {
console. log ( this . dateTime) ;
const params = { } ;
params. startCreateTime = this . dateTime[ 0 ] ;
params. startCreateTime. setHours ( 0 ) ;
params. startCreateTime. setMinutes ( 0 ) ;
params. startCreateTime. setSeconds ( 0 ) ;
params. endCreateTime = this . dateTime[ 1 ] ;
params. endCreateTime. setHours ( 23 ) ;
params. endCreateTime. setMinutes ( 59 ) ;
params. endCreateTime. setSeconds ( 59 ) ;
console. log ( this . dateTime) ;
console. log ( params) ;
}
}
} ;
</ script>
对应路由:
{
path : "/date" ,
name : "date" ,
component : ( ) => import ( "@/components/TestDate" ) ,
}
对应侧边栏:
< el-menu-item-group>
< el-menu-item index = " /date" >
< i class = " el-icon-menu" > </ i> 日期控件
</ el-menu-item>
</ el-menu-item-group>
接下来执行项目就可以看到对应效果了
测试完毕后,我们再来操作项目的用户的分页&条件查询
对应的组件是Users.vue组件(发现也是有这个日期的操作的)
首先我们先操作分页&条件查询:
主要的js部分:
loadUsers ( ) {
this . loading = true ;
if ( this . filter. resTime) {
params. startCreateTime = this . filter. resTime[ 0 ] ;
params. startCreateTime. setHours ( 0 ) ;
params. startCreateTime. setMinutes ( 0 ) ;
params. startCreateTime. setSeconds ( 0 ) ;
params. endCreateTime = this . filter. resTime[ 1 ] ;
params. endCreateTime. setHours ( 23 ) ;
params. endCreateTime. setMinutes ( 59 ) ;
params. endCreateTime. setSeconds ( 59 ) ;
}
const params = { currentPage : this . page, pageSize : this . size } ;
return axios
. post ( "/user/findAllUserByPage" , params)
. then ( res => {
this . users = res. data. content. list;
this . total = res. data. content. total;
this . loading = false ;
} )
. catch ( err => {
this . $message. error ( "获取用户数据失败" ) ;
} ) ;
} ,
用户状态设置:
对应的html部分:
< el-table-column label = " 操作" align = " center" min-width = " 120" >
< template slot-scope = " scope" >
< el-button
size = " mini"
type = " text"
@click = " handleToggleStatus(scope.row)"
> {{ scope.row.status == "ENABLE" ? "禁用" : "启用" }}</ el-button
>
< el-button
size = " mini"
type = " text"
@click = " handleSelectRole(scope.$index, scope.row)"
> 分配角色</ el-button
>
</ template>
</ el-table-column>
对应js部分:
handleToggleStatus ( item ) {
item. status == "ENABLE" ? item. status = "DISABLE" : item. status = "ENABLE"
return axios. get ( "/user/updateUserStatus" , {
params : {
id : item. id,
status : item. status
}
} ) . then ( res => {
console. log ( item. status)
console. log ( res. data. content)
item. status = res. data. content;
} ) . catch ( err => {
this . $message. error ( "修改用户状态失败" ) ;
} )
} ,
用户模块的分配角色后面再进行操作
权限管理:
角色管理:
展示&查询角色列表 :
角色组件是 Roles.vue ,在该组件中对角色信息进行管理
一开始得到数据的对应js部分(条件查询也是):
loadRoles ( ) {
this . listLoading = true
axios. post ( "/role/findAllRole" , this . listQuery) . then ( res => {
this . list = res. data. content;
this . listLoading = false
} ) . catch ( err => {
this . $message. error ( "获取角色列表失败" ) ;
} ) ;
} ,
添加&修改角色 :
对应的js:
handleSave ( ) {
return axios. post ( "/role/saveOrUpdateRole" , this . role) . then ( res => {
this . loadRoles ( ) ;
this . dialogVisible = false ;
} ) . catch ( err => {
this . $message. error ( "操作失败" ) ;
} )
} ,
在这里说明一下,在添加数据到sql时,若是字符串的null,即"null"
是可以添加到设置了not null的字段的,因为他与not null的约束的null是不同的,只有单纯的null才不能添加到设置了not null里面
而"null"(字符串的null)可以
而在sql手动加null时,都默认为null,而不是字符串的"null",即出现不了null在字段中,一般只能通过程序来操作
而我们手动添加"null","也是会当作数据的,所以加上null一般需要程序来操作
删除角色的方法:
对应的js:
handleDelete ( row ) {
this . $confirm ( '是否要删除该角色' , '提示' , {
confirmButtonText : '确定' ,
cancelButtonText : '取消' ,
type : 'warning'
} ) . then ( ( ) => {
axios. get ( "/role/deleteRole?id=" + row. id) . then ( res => {
this . loadRoles ( ) ;
} ) . catch ( err => {
this . $message. error ( "删除失败" ) ;
} ) ;
} ) . catch ( ( ) => {
this . $message ( "已取消" ) ;
} ) ;
} ,
为角色分配菜单:
为角色分配菜单,一个角色可以拥有多个菜单权限
一个菜单权限也可以被多个角色拥有
点击分配菜单,页面展示效果:
前端要实现的效果
第一步:获取到所有的菜单数据,在树形控件中进行展示
第二步:将当前角色拥有的菜单权限,勾选上
菜单展示功能实现:
对应的js:
handleSelectMenu ( row ) {
this . $router. push ( { path : "/allocMenu" , query : { roleId : row. id } } ) ;
} ,
发现他进行了跳转,对应路由:
{
path : "allocMenu" ,
name : "AllocMenu" ,
component : ( ) =>
import (
"../views/PermissionManage/AllocMenu"
) ,
meta : { requireAuth : true , title : "角色菜单管理" }
} ,
找到AllocMenu.vue组件:
对应的html:
< template>
< el-card class = " form-container" shadow = " never" >
< el-tree
:data = " menuTreeList"
show-checkbox
default-expand-all
node-key = " id"
ref = " tree"
highlight-current
:props = " defaultProps"
> </ el-tree>
< div style = " margin-top : 20px" align = " center" >
< el-button type = " primary" @click = " handleSave()" > 保存</ el-button>
< el-button @click = " handleClear()" > 清空</ el-button>
</ div>
</ el-card>
</ template>
< script>
import { axios } from "../../utils" ;
export default {
name : "allocMenu" ,
title : "角色菜单管理" ,
data ( ) {
return {
menuTreeList : [ ] ,
checkedMenuId : [ ] ,
defaultProps : {
children : "subMenuList" ,
label : "name"
} ,
roleId : null
} ;
} ,
created ( ) {
this . roleId = this . $route. query. roleId;
this . treeList ( ) ;
this . getRoleMenu ( this . roleId) ;
} ,
methods : {
treeList ( ) { } ,
getRoleMenu ( roleId ) { } ,
handleSave ( ) { } ,
handleClear ( ) {
this . $refs. tree. setCheckedKeys ( [ ] ) ;
}
}
} ;
</ script>
< style scoped >
</ style>
操作获取菜单列表数据:
对应的js:
treeList ( ) {
axios. get ( "/menu/findAllMenu" ) . then ( res => {
this . menuTreeList = res. data. content. parentMenuList
} ) . catch ( err => {
this . $message. error ( "获取菜单列表失败" ) ;
} ) ;
} ,
然后回显对应已有的菜单
对应的js:
getRoleMenu ( roleId ) {
axios. get ( "/menu/findMenuByRoleId?roleId=" + roleId) . then ( res => {
console. log ( res. data. content) ;
this . $refs. tree. setCheckedKeys ( res. data. content) ;
} )
} ,
接下来进行分配:
对应的js部分:
handleSave ( ) {
const ca = this . $refs. tree. getCheckedNodes ( ) ;
console. log ( ca)
const id = [ ] ;
if ( ca != null ) {
for ( let i = 0 ; i< ca. length; i++ ) {
const cid = ca[ i] ;
id. push ( cid. id) ;
if ( cid. parentId !== - 1 && id. filter ( item => item!= cid. parentId) . length === id. length) {
id. push ( cid. parentId) ;
}
}
}
console. log ( id)
this . $confirm ( '是否分配菜单' , '提示' , {
confirmButtonText : '确定' ,
cancelButtonText : '取消' ,
type : 'warning'
} ) . then ( ( ) => {
const params = {
roleId : this . roleId,
menuIdList : id
}
console. log ( params)
axios. post ( "/menu/roleContextMenu" , params) . then ( res => {
this . $router. back ( ) ;
} ) . catch ( err => {
this . $message. error ( "分配失败" )
} )
} ) . catch ( ( ) => {
this . $message ( "已经取消了" )
} ) ;
} ,
接下来我们来修改一下回显:
getRoleMenu ( roleId ) {
axios. get ( "/menu/findMenuByRoleId?roleId=" + roleId) . then ( res => {
console. log ( res. data. content) ;
const d = [ ] ;
console. log ( this . menuTreeList)
console. log ( this . menuTreeList. length)
for ( let a = 0 ; a< this . menuTreeList. length; a++ ) {
for ( let b = 0 ; b< res. data. content. length; b++ ) {
console. log ( "a:" + this . menuTreeList[ a] . id)
console. log ( "b:" + res. data. content[ b] )
if ( this . menuTreeList[ a] . id == res. data. content[ b] ) {
d. push ( res. data. content[ b] )
}
}
}
console. log ( d)
let s = 0 ;
for ( let c = 0 ; c< d. length; c++ ) {
for ( let dd = 0 ; dd< this . menuTreeList. length; dd++ ) {
if ( this . menuTreeList[ dd] . id== d[ c] ) {
for ( let j = 0 ; j< this . menuTreeList[ dd] . subMenuList. length; j++ ) {
for ( let k = 0 ; k< res. data. content. length; k++ ) {
if ( this . menuTreeList[ dd] . subMenuList[ j] . id== res. data. content[ k] ) {
s++ ;
}
}
}
if ( s!= this . menuTreeList[ dd] . subMenuList. length) {
s= 0
for ( let k = 0 ; k< res. data. content. length; k++ ) {
if ( res. data. content[ k] == this . menuTreeList[ dd] . id) {
res. data. content. splice ( k, 1 ) ;
}
}
}
}
}
}
console. log ( "a" + res. data. content) ;
this . $refs. tree. setCheckedKeys ( res. data. content) ;
} )
} ,
而由于axios是异步的,即我们需要让回显慢一点运行:
created ( ) {
this . roleId = this . $route. query. roleId;
this . treeList ( ) ;
let that = this
setTimeout ( function ( ) { that. getRoleMenu ( that. roleId) } , 1000 ) ;
} ,
但是我们也会发现,若获取菜单列表执行速度超过了1秒钟,那么对应的数据是会出现问题的,所以这里改进一下:
改进后的代码:
created ( ) {
this . roleId = this . $route. query. roleId;
this . treeList ( ) ;
let that = this
let ki = setInterval ( function ( ) {
if ( that. menuTreeList. length != 0 ) {
that. getRoleMenu ( that. roleId)
clearInterval ( ki) ;
}
alert ( 1 )
} , 500 ) ;
} ,
只有当对应的数组有数据时,也就是得到后台响应的数据时,才会进行方法调用
即一直监听是否得到数据,且间隔0.5秒钟进行一次监听
我们发现,为了实现一个功能,代码一般很多的,而算法就可以解决代码非常多的问题,来进行简化
以后来说明算法的妙用
菜单管理:
对应的组件是Menus.vue组件
首先我们进行展示菜单列表
对应的js:
注意:这里需要在对应后台加上分页操作(因为后台一直是查询所有)
loadMenuList ( ) {
this . listLoading = true ;
axios. post ( "/menu/findAllMenuList" ,
{
"currentPage" : this . page,
"pageSize" : this . size
} ) . then ( res => {
this . list = res. data. content. list;
this . total = res. data. content. total;
this . listLoading = false ;
} ) . catch ( err => {
this . $message. error ( "获取菜单列表失败" ) ;
} ) ;
} ,
新增&修改菜单:
新增js:
handleAddMenu ( ) {
this . $router. push ( "/addMenu" ) ;
} ,
对应路由:
{
path : "addMenu" ,
name : "AddMenu" ,
component : ( ) =>
import (
"../views/PermissionManage/AddMenu"
) ,
meta : { requireAuth : true , title : "添加菜单" }
} ,
发现是AddMenu.vue组件:
对应的html:
< template>
< menu-detail :is-edit = ' false' > </ menu-detail>
</ template>
< script>
import MenuDetail from './MenuDetail'
export default {
name : 'addMenu' ,
title : '添加菜单' ,
components : { MenuDetail }
}
</ script>
< style>
</ style>
发现导入了MenuDetail.vue组件来进行显示:
对应组件的html信息:
< template>
< el-card class = " form-container" shadow = " never" >
< el-form :model = " menu" :rules = " rules" ref = " form" label-width = " 150px" >
< el-form-item label = " 菜单名称:" prop = " name" >
< el-input v-model = " menu.name" > </ el-input>
</ el-form-item>
< el-form-item label = " 菜单路径:" prop = " href" >
< el-input v-model = " menu.href" > </ el-input>
</ el-form-item>
< el-form-item label = " 上级菜单:" >
< el-select v-model = " menu.parentId" placeholder = " 请选择菜单" >
< el-option
v-for = " item in selectMenuList"
:key = " item.id"
:label = " item.title"
:value = " item.id"
> </ el-option>
</ el-select>
</ el-form-item>
< el-form-item label = " 描述:" prop = " description" >
< el-input v-model = " menu.description" > </ el-input>
</ el-form-item>
< el-form-item label = " 前端图标:" prop = " icon" >
< el-input v-model = " menu.icon" style = " width : 80%" > </ el-input>
</ el-form-item>
< el-form-item label = " 是否显示:" >
< el-radio-group v-model = " menu.shown" >
< el-radio :label = " 0" > 是</ el-radio>
< el-radio :label = " 1" > 否</ el-radio>
</ el-radio-group>
</ el-form-item>
< el-form-item label = " 排序:" >
< el-input v-model = " menu.orderNum" > </ el-input>
</ el-form-item>
< el-form-item>
< el-button type = " primary" @click = " handleSave()" > 提交</ el-button>
</ el-form-item>
</ el-form>
</ el-card>
</ template>
< script>
import { axios } from "../../utils" ;
const menu = {
description : "" ,
parentId : - 1 ,
name : "" ,
icon : "" ,
shown : 0 ,
orderNum : 0
} ;
const rules = {
description : [
{ required : true , message : "请输入菜单描述" , trigger : "blur" } ,
{
min : 2 ,
max : 140 ,
message : "长度在 2 到 140 个字符" ,
trigger : "blur"
}
] ,
name : [
{ required : true , message : "请输入菜单名称" , trigger : "blur" } ,
{
min : 2 ,
max : 140 ,
message : "长度在 2 到 140 个字符" ,
trigger : "blur"
}
] ,
icon : [
{ required : true , message : "请输入前端图标" , trigger : "blur" } ,
{
min : 2 ,
max : 140 ,
message : "长度在 2 到 140 个字符" ,
trigger : "blur"
}
]
} ;
export default {
name : "MenuDetail" ,
props : {
isEdit : {
type : Boolean,
default : false
}
} ,
data ( ) {
return {
menu,
selectMenuList : [ ] ,
rules
} ;
} ,
created ( ) { } ,
methods : {
findMenuInfoById ( id ) { } ,
handleSave ( ) {
this . $refs. form. validate ( valid => {
if ( ! valid) return false ;
} ) ;
}
}
} ;
</ script>
< style scoped >
</ style>
首先先操作回显的上级菜单信息:
created ( ) {
if ( this . isEdit) {
} else {
this . menu = Object. assign ( { } , this . menu) ;
this . findMenuInfoById ( - 1 )
}
} ,
findMenuInfoById ( id ) {
axios. get ( "/menu/findMenuInfoById?id=" + id) . then ( res => {
console. log ( res. data)
if ( res. data. content. menuInfo != null ) {
this . menu = res. data. content. menuInfo;
}
this . selectMenuList = res. data. content. parentMenuList. map ( item => {
return {
id : item. id,
title : item. name
} ;
} ) ;
this . selectMenuList. unshift ( { id : - 1 , title : "无上级菜单" } )
} )
} ,
修改js:
handleUpdate ( index, row ) {
this . $router. push ( { path : "/updateMenu" , query : { id : row. id } } ) ;
} ,
对应路由:
{
path : "updateMenu" ,
name : "UpdateMenu" ,
component : ( ) =>
import (
"../views/PermissionManage/UpdateMenu"
) ,
meta : { requireAuth : true , title : "编辑菜单" }
} ,
对应的UpdateMenu.vue组件:
< template>
< menu-detail :is-edit = ' true' > </ menu-detail>
</ template>
< script>
import MenuDetail from './MenuDetail'
export default {
name : 'updateMenu' ,
title : '编辑菜单' ,
components : { MenuDetail }
}
</ script>
< style>
</ style>
发现最后也是MenuDetail.vue组件
所以现在我们来操作MenuDetail.vue组件的保存操作
最后注意一下:菜单列表是按照主键来排序的,而不是排序字段,因为我们需要有顺序的显示(可能以后需要用到排序字段)
所以可能对应的父菜单是分开的,而像明显显示给我们看的(如菜单列表),一般都会按照主键来排列
新增&修改菜单的保存操作 :
对应的js:
handleSave ( ) {
this . $refs. form. validate ( valid => {
if ( ! valid) return false ;
console. log ( this . menu)
this . menu. createdBy= "system"
this . menu. updatedBy= "system"
this . menu. parentId== - 1 ? this . menu. level= 0 : this . menu. level= 1 ;
axios. post ( "/menu/saveOrUpdateMenu" , this . menu) . then ( res => {
this . $router. back ( ) ;
} ) . catch ( err => {
this . $message. error ( "操作失败" ) ;
} ) ;
} ) ;
}
当我们编辑时,也有进行回显,改变钩子函数:
created ( ) {
if ( this . isEdit) {
const id = this . $route. query. id
this . findMenuInfoById ( id)
} else {
this . menu = Object. assign ( { } , this . menu) ;
this . findMenuInfoById ( - 1 )
}
} ,
那么我们就可以测试了
注意:我们在后台的菜单级数并没有操作,也就是说默认为0,即一级,所以这里需要你去判断设置一下
最后:由于js可以直接指定元素赋值,如一个空的var a ={},你直接指定的a.id=1,那么对应的就会有a这个参数创建
所以vue的双向绑定时,实际上对应的没有也会创建的,但若对应的直接获取,自然是找不到的,即报错
删除功能:
回到原来的Menus.vue组件:
对应删除的js:
handleDelete ( index, row ) {
this . $confirm ( '是否删除该菜单信息' , '提示' , {
confirmButtonText : '确定' ,
cancelButtonText : '取消' ,
type : 'warning'
} ) . then ( ( ) => {
axios. get ( "/menu/deleteMenu?id=" + row. id) . then ( res => {
this . loadMenuList ( ) ;
} ) . catch ( err => {
this . $message. error ( "删除失败" ) ;
} )
} ) . catch ( ( ) => {
this . $message ( '已取消删除' ) ;
} ) ;
}
资源管理:
对应的组件是Resources.vue组件:
获取资源信息的js:
getResourceList ( ) {
this . listLoading = true
axios. post ( "/resource/findAllResourceByPage" , this . listQuery)
. then ( res => {
this . list = res. data. content. list;
this . total = res. data. content. total;
this . listLoading = false
} ) . catch ( err => {
this . $message. error ( "获取资源数据失败" ) ;
} ) ;
} ,
注意:有些地方不起作用,最好自己去解决(提示:重置)
获取分类信息的js:
getResourceCateList ( ) {
axios. get ( "/resource/findAllResourceCategory" ) . then ( res => {
this . cateList = res. data. content
for ( let k = 0 ; k< this . cateList. length; k++ ) {
const ca = this . cateList[ k]
this . categoryOptions. push ( { label : ca. name, value : ca. id} )
}
} ) . catch ( err => {
this . $message. error ( "获取资源分类数据失败" ) ;
} )
} ,
新增&修改资源:
对应的新增js:
首先是对弹出来的框框进行操作显示
handleAdd ( ) {
this . isEdit = false
this . dialogVisible = true
this . resource = Object. assign ( { } , defaultResource)
} ,
主要添加资源操作js:
注意:对应后台并没有对应的资源的添加,修改,删除,所以需要自己去编写
handleSave ( ) {
console. log ( this . resource)
if ( this . resource. name == null ) this . resource. name = "null"
if ( this . resource. url == null ) this . resource. url = "null"
axios. post ( "/resource/saveOrUpdateResource" , this . resource) . then ( res => {
this . getResourceList ( ) ;
this . dialogVisible = false ;
} ) . catch ( err => {
this . $message. error ( "添加或修改资源失败" ) ;
} )
} ,
修改操作也是需要回显框框:
对应的js如下:
handleUpdate ( row ) {
this . isEdit = true
this . dialogVisible = true
this . resource = Object. assign ( { } , row)
console. log ( row)
console. log ( this . resource)
} ,
保存的操作与添加操作的保存是一样的
后面的资源分类以及对应的删除资源通过上面的操作,可以自己进行编写了
还有对应的分配资源
注意:我们进行分页时使用的Mybatis的分页操作的总条数是查询的条数,然后根据对应的分页操作来获取
即当我们没有数据时,对应的总条数一般都为0,而前端是虽然总条数可以设置为0或者负数
但对应的当前页,在经过总条数时,就算再小也不会超过1,即一般最新的当前页就是第一页
正是因为这样,所以我们可以操作删除进行当前页的减小,从而实现当删除当前页的最后一个数据时,进行到前一页
对应的提示:一般设置对应的当前页–操作,来实现到前一页
用户权限控制:
对应的组件是:Login.vue组件
对应的js部分
this . $store
. dispatch ( 'createToken' , this . model)
. then ( res => {
我们去找到createToken方法,在store的actions.js里面:
对应的js:
总体js操作:
createToken : async ( { commit } , { username, password } ) => {
const res = await TokenService. userLogin ( {
phone : username. trim ( ) ,
password : password. trim ( )
} ) ;
console. log ( res) ;
if ( res. state !== 1 ) {
return Promise. resolve ( res) ;
}
const result = res. content;
commit ( CHANGE_SESSION , {
accessToken : result. access_token
} ) ;
return res;
} ,
找到services里面的js(好像是tokens.js),即可以看看对应的userLogin方法:
export const userLogin = async ( data ) => {
return await PostRequest ( ` ${ process. env. VUE_APP_API_FAKE } /user/login? ${ Serialize ( data) } ` )
}
Login.vue组件不省略的js:
submit ( ref ) {
this . $refs[ ref] . validate ( valid => {
if ( ! valid) return false
this . error = null
this . loading = true
this . $store
. dispatch ( 'createToken' , this . model)
. then ( res => {
if ( res. state !== 1 ) {
this . error = {
title : 'Error occurred' ,
message : 'Abnormal, please try again later!'
}
switch ( res. state) {
case 201 :
this . error. message = '请输入正确的手机号'
break
case 202 :
this . error. message = '请输入密码'
break
case 203 :
this . error. message = '密码错误'
break
case 204 :
this . error. message = '验证码过期'
break
case 205 :
this . error. message = '帐号错误或密码错误'
break
case 206 :
this . error. message = '帐号错误或密码错误'
break
case 207 :
this . error. message = '验证码错误'
break
case 500 :
this . error. message =
'Internal server error, please try again later!'
break
}
this . loading = false
return
}
this . loading = false
this . $router. replace ( { path : this . $route. query. redirect || '/' } )
} )
. catch ( err => {
console. error ( err)
this . error = {
title : 'Error occurred' ,
message : 'Abnormal, please try again later!'
}
switch ( err. response && err. response. status) {
case 401 :
this . error. message = 'Incorrect username or password!'
break
case 500 :
this . error. message =
'Internal server error, please try again later!'
break
}
this . loading = false
} )
} )
}
动态获取用户菜单:
而我们使用vue时,之所以基本只会显示对应的组件,是因为对应的js并没有运行造浏览器
他们通过js操作返回给我们一个网页,即会发现基本其他的组件也看不到
在我们登录成功后,会立即发送第二个请求,来获取用户的菜单权限列表
对应的在store的actions.js里面:
getUserPermissions : async ( { commit } ) => {
const res = UserService. getUserPermissions ( ) ;
对应的方法在users.js里面,对应的getUserPermissions方法:
export const getUserPermissions = async ( ) => {
return await baseRequest ( ` ${ bossBase} /permission/getUserPermissions ` )
}
发现也是传递请求
注意:当跨域请求时,对应的cookie不会传递过去,即不会携带,也就是说,这样的使用cookie来操作的可能会操作不了
但我们的前端项目已经操作可以的,所以不用去解决,具体位置在如下:
在对应的utils里面的axios.js可以发现(我们导入的也是这个,他进行了再次导入):
axios. defaults. withCredentials = true ;
且要注意:若出现了不对劲的地方,可以重启项目,使得刷新
因为当项目够大时,对应的部署可能会有缺陷,但通常不会
一般的,当后台相应给我们对应的token时,我们每次的请求都基本是需要访问一次的
但这样的每次访问是进行操作的,看如下:
导航守卫:
在执行路由之前先执行的一些钩子函数,比如验证用户是否有权限之类的操作,就需要使用
authorize.js 中配置了导航守卫,来对用户的登录进行限制
在plugins里面找到authorize.js:
对应的js
import router from "../router" ;
import store from "../store" ;
export default Vue => {
router. beforeHooks. unshift ( ( to, from, next ) => {
if ( ! to. meta. requireAuth) return next ( ) ;
store. dispatch ( "checkToken" ) . then ( valid => {
if ( valid) {
store. dispatch ( 'getUserPermissions' ) . then ( res => {
const { memusMap } = res
if ( memusMap. Courses && to. name === 'Home' ) {
return next ( )
} else if ( memusMap[ to. name] ) {
return next ( )
} else if ( Object. keys ( memusMap) . length > 0 ) {
return next ( { name : memusMap[ Object. keys ( memusMap) [ 0 ] ] . name } )
} else {
next ( { name : 'PermissionDenied' } )
}
} )
return next ( ) ;
}
console. log ( "Unauthorized" ) ;
next ( { name : "Login" , query : { redirect : to. fullPath } } ) ;
} ) ;
} ) ;
router. beforeEach ( ( to, from, next ) => {
if ( to. name !== "Login" ) return next ( ) ;
store. dispatch ( "checkToken" ) . then ( valid => {
if ( ! valid) return next ( ) ;
console. log ( "Authorized" ) ;
next ( { path : to. query. redirect || "/" } ) ;
} ) ;
} ) ;
} ;
这些只是对应的基础格式,了解即可
分配角色:
我们再次回到用户模块的Users.vue组件,现在我们来操作分配角色:
对应的html:
< el-button size = " mini" type = " text" @click = " handleSelectRole(scope.$index, scope.row)" >
分配角色</el-button
对应的handleSelectRole方法(回显窗口):
handleSelectRole ( index, row ) {
this . allocAdminId = row. id;
this . getRoleList ( ) ;
this . getUserRoleById ( row. id) ;
this . allocDialogVisible = true ;
} ,
获取角色列表:
getRoleList ( ) {
axios. post ( "/role/findAllRole" , { } ) . then ( res => {
this . allRoleList = res. data. content;
console. log ( this . allRoleList)
} ) . catch ( err => {
this . $message. error ( "获取角色列表失败" ) ;
} )
} ,
获取用户的角色:
getUserRoleById ( id ) {
axios. get ( "/user/findUserRelationRoleById?id=" + id) . then ( res => {
const all = res. data. content;
console. log ( all)
this . allocRoleIds = [ ] ;
if ( all != null && all. length > 0 ) {
for ( let i= 0 ; i< all. length; i++ ) {
this . allocRoleIds. push ( all[ i] . id) ;
}
}
console. log ( this . allocRoleIds)
console. log ( this . allRoleList)
} ) . catch ( err => {
this . $message. error ( "回显对应角色失败" ) ;
} )
} ,
最后分配角色:
注意:虽然js的key可以不写分号,但对与json来说是要加上的
而一些插件也会默认给对应的key加上分号,所以axios对应的参数的key可以不加,因为最后帮忙加上了
当然了若有分号自然不会加的,这是判断操作,分号是包括’'(单引号)和""(双引号)
在没有特殊的操作下,他们的最终结果是一样的
若有特殊情况:如java的’'(单引号)和""(双引号),他们就是不同的,但对于js来说他们可以看成是一个结果
但也要注意:字符串只是对数标识,实际上他只是给我们显示看的,最后都是二进制,但正是由于这个标识使得二进制不同
但可以显示相同,如java里面字符串"1"和整数1的显示一般是相同的,乱码除外
handleAllocDialogConfirm ( ) {
const params = {
userId : this . allocAdminId,
roleIdList : this . allocRoleIds
} ;
axios. post ( "/user/userContextRole" , params) . then ( res => {
this . loadUsers ( ) ;
this . allocDialogVisible = false ;
} ) . catch ( err => {
this . $message. error ( "分配角色失败" ) ;
} ) ;
} ,
当然,可能角色这块前端会有问题,但是并不需要理会,只需要知道角色的对应显示通常由前端处理显示的(对我们java程序员来说,有时间可以了解一下)