我们使用脚手架快速构建Vue项目,项目结构如下图
|--- edu-boss 项目名称
|--- node_modules 存放依赖包的目录
|--- public 静态资源管理目录
|--- src 组件源码目录(我们写的代码)
|--- assets 存放静态图片资源(CSS也可以放在这里)
|--- components 存放基础组件,可复用
|--- router 存放了项目路由文件
|--- services 存放请求后台的 JS文件,
|--- store 保存组件之间的共享数据
|--- utils 管理公用的JS文件
|--- views 放置的为公共组件(各个主要页面)
|--- App.vue app.vue可以当做是网站首页,是一个vue项目的主组件,页面入口文件
|--- main.js 打包运行的入口文件,引入了vue模块和app.vue组件以及路由route
|--- babel.config.js babel配置文件, 对源代码进行转码(把es6=>es5)
|--- package.json 项目及工具的依赖配置文件
|--- paxkage-lock.json 依赖配置文件
|--- README.md 项目说明
|--- vue.config.js 自定义配置文件
CourseManage: 课程管理
AdvertiseManage: 广告管理
PermissionManage: 权限管理
CommentManage:公共
Users.vue: 用户管理
Login.vue: 登录
每一个*.vue 文件都可以看做是一个组件.
组件的组成部分:
<template>
<div>
测试页面...
div>
template>
<script>
//可以导入其组件
// import Header from '../components/header.vue'
//默认写法, 输出该组件
export default {
name:"Home", // 组件名称,用于以后路由跳转
data() {// 当前组件中需要使用的数据
return {}
},
methods: {}
}
script>
<style scoped>
/* 页面样式 加上scoped 表示样式就只在当前组件有效*/
style>
使用ElementUI 表格进行数据展示 https://element.eleme.cn/#/zh-CN/component/table
//数据部分
data() {
//查询条件
const filter = {
courseName: "",
status: ""
};
return {
filter,
courses: [],
loading: false
};
},
//钩子函数
created() {
this.loadCourses();
},
//方法一: 加载课程数据
loadCourses() {
this.loading = true;
const data = {};
//查询条件
if (this.filter.courseName) data.courseName = this.filter.courseName;
if (this.filter.status) data.status = this.filter.status;
console.log(data);
//发送请求
return axios
.post("/course/findAllCourse", data)
.then(resp => {
console.log(resp.data.content);
this.courses = resp.data.content;
this.loading = false;
})
.catch(error => {
this.$message.error("数据获取失败! ! !");
});
},
查询
//条件查询
handleFilter() {
this.loadCourses();
},
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新建课程el-button>
//新建课程 路由跳转
handleAdd() {
this.$router.push({ name: "CourseItem", params: { courseId: "new" } });
},
{
path: "/courses/:courseId",
name: "CourseItem",
meta: { requireAuth: true, title: "课程详情" },
component: () =>
import(
/* webpackChunkName: 'courses' */
"../views/CourseManage/CourseItem.vue"
)
},
https://element.eleme.cn/#/zh-CN/component/form
保存
//保存课程信息
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("保存课程信息失败! ! !");
});
});
},
在SSM前端项目中,图片上传功能使用的是公共的通用组件 UploadImage.vue
在CourseItem.vue,引入了该组件
import UploadImage from "@/components/UploadImage.vue";
<!-- 使用图片上传组件,完成图片上传 -->
<el-form-item label="课程封面" prop="courseImgUrl">
<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>
</el-form-item>
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>
<style scoped>
style>
//布局路由
{
path: "/index",
name: "index",
component: Index,
//添加子路由,使用 children属性 来表示子路由
children: [
//图片上传子路由
{
path: "/upload",
name: "upload",
component: UploadImage,
},
],
},
<el-menu-item-group>
<el-menu-item index="/upload">
<i class="el-icon-menu">i>图片上传
el-menu-item>
el-menu-item-group>
参数 | 说明 | 类型 |
---|---|---|
action | 必选参数,上传的地址 | string |
multiple | 是否支持多选文件 | boolean |
limit | 最大允许上传个数 | number |
before-upload | 上传文件之前的钩子,参数为上传的文件 | function(file) |
on-success | 文件上传成功时的钩子 | function(response, file, fileList) |
on-remove | 文件列表移除文件时的钩子 | function(file, fileList) |
on-exceed | 文件超出个数限制时的钩子 | function(files, fileList) |
file-list | 上传的文件列表 | array |
怎么将一个组件引入另一个组件 ? 接下来我们来演示一下 引入图片组件.
<template>
<div>
<upload-image>upload-image>
div>
template>
<script>
//1.导入组件
import UploadImage from "@/components/UploadImage";
export default {
//2.注册组件
components: {
UploadImage
}
};
script>
<style scoped>
style>
UploadImage.vue
/*
组件传参
uploadUrl:图片上传路径,
getUrl: 函数
*/
props: ["uploadUrl", "getUrl"],
data() {
return {
uploadAction: this.uploadUrl
};
},
//上传成功后的回调函数
uploadSuccess(res, file) {
this.getUrl(file);
}
<el-upload
:action="uploadAction"
list-type="picture-card"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:on-success="uploadSuccess"
>
TestUpload.vue
<template>
<div>
<upload-image
uploadUrl="https://jsonplaceholder.typicode.com/posts/"
:get-url="show">
upload-image>
div>
template>
methods: {
show(file) {
console.log(file.name);
}
}
CourseItem.vue
引入图片上传组件,并使用(UploadImage.vue)
import UploadImage from "@/components/UploadImage.vue";
export default {
name: "CourseItem",
title: "营销信息",
components: { Editor, UploadImage },
}
<el-form-item label="课程封面" prop="courseImgUrl">
<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>
el-form-item>
<el-button size="mini" @click="handleNavigate('CourseItem', scope.row.id)">
编辑
el-button>
//课程编辑&内容管理路由
handleNavigate(name, id) {
this.$router.push({ name, params: { courseId: id } });
},
//钩子函数
created() {
//获取课程id
const id = this.$route.params.courseId;
if (!id) return this.redirectToError();
//判断是新建还是修改
if (id === "new") {
this.pathTitle = "新增课程";
this.$breadcrumbs = [
{ name: "Courses", text: "课程管理" },
{ text: "新增课程" }
];
} else {
this.$breadcrumbs = [
{ name: "Courses", text: "课程管理" },
{ text: "营销信息" }
];
this.loadCourse(id);
}
},
//回显课程信息
loadCourse(id) {
this.loading = true;
return axios
.get("/course/findCourseById?id=" + id)
.then(resp => {
console.log(resp);
this.pathTitle = resp.data.content.courseName;
this.course = Object.assign(this.course, resp.data.content);
this.course.id = id;
this.loading = false;
})
.catch(error => {
this.$message.error("回显数据失败! !");
});
},
点击上架或者下架完成课程状态的切换.
<el-button size="mini" type="danger" v-if="scope.row.status === 1"
@click="handleToggleStatus(scope.row)">下架el-button>
<el-button size="mini" type="success" v-else-if="scope.row.status === 0"
@click="handleToggleStatus(scope.row)">上架el-button>
//切换课程状态
handleToggleStatus(item) {
//设置最新状态
const toggledStatus = 1 - item.status;
//请求后台接口
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 查询课程包含的章节与课时信息
<el-button size="mini" @click="handleNavigate('CourseSections', scope.row.id)">
内容管理
el-button>
created() {
//1.显示当前页面在网站中的位置
this.$breadcrumbs = [
{ name: "Courses", text: "课程管理" },
{ text: "课程结构" }
];
//2.从路由中获取传递的参数 课程id
const id = this.$route.params.courseId;
if (!id) return this.redirectToError();
this.loading = true;
//3.加载课程信息
this.loadCourse(id);
//4.加载课程内容
this.loadSections(id);
},
//加载课程信息
loadCourse(id) {
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("数据获取失败! ! !");
});
},
//加载课程内容(树形结构)
loadSections(courseId) {
this.loading = true;
axios
.get("/courseContent/findSectionAndLesson?courseId=" + courseId)
.then(res => {
this.sections = res.data.content;
console.log(res.data.content);
this.loading = false;
})
.catch(error => {
this.$message.error("数据获取失败! ! !");
});
},
<el-button type="primary" icon="el-icon-plus" @click="handleShowAddSection">添加章节
el-button>
新增章节,需要回显章节对应的课程名称
//显示新增章节表单
handleShowAddSection() {
this.addSectionForm = {
courseId: this.addSectionForm.courseId,
courseName: this.addSectionForm.courseName
};
this.showAddSection = true;
},
<el-button size="small" @click.stop="handleEditSection(data)">编辑el-button>
//编辑章节(回显)
handleEditSection(section) {
this.addSectionForm = Object.assign(this.addSectionForm, section);
this.showAddSection = true;
},
//添加&修改章节
handleAddSection() {
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("操作执行失败! ! !");
});
},
章节状态有3种
//状态信息
const statusMapping = {
0: "已隐藏",
1: "待更新",
2: "已更新"
};
选择状态,点击确定修改状态
<el-button type="primary" @click="handleToggleStatus">确 定el-button>
//修改章节状态
handleToggleStatus() {
//判断要修改的状态
if (this.toggleStatusForm.data.sectionName) {
//修改章节状态
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("修改状态失败! ! !");
});
} else {
//修改课时状态
}
},
课时管理 包括 课时新增、课时修改、课时状态管理. 与章节管理基本相同.
JS部分
<script>
import { axios } from "../../utils";
import moment from "moment";
export default {
data() {
return {
list: null,
listLoading: false
};
},
created() {
//加载广告位数据
this.loadPromotionSpace();
},
//方法1: 加载广告位信息
loadPromotionSpace() {
this.listLoading = true;
axios
.get("/PromotionSpace/findAllPromotionSpace")
.then(res => {
this.list = res.data.content;
this.listLoading = false;
})
.catch(err => {
this.$message("加载数据失败! ! !");
});
},
}
1)点击 按钮,通过路由导航到指定组件
<el-button size="mini" class="btn-add" @click="handleAdd()">添加广告位el-button>
//添加广告位跳转
handleAdd() {
this.$router.push({ path: "/addAdvertiseSpace" });
},
2) 查看路由 router.js, 跳转到的是 AddAdvertiseSpace.vue
{
path: "addAdvertiseSpace",
name: "AddAdvertiseSpace",
component: () => import("../views/AdvertiseManage/AddAdvertiseSpace"),
meta: { requireAuth: true, title: "添加广告位" }
},
3) 查看AddAdvertiseSpace.vue
<template>
//显示组件,并传递了参数 isEdit="false" , 表示是新增操作
<home-advertise-detail :isEdit="false">home-advertise-detail>
template>
<script>
//引入了AdvertiseSpaceDetail组件
import HomeAdvertiseDetail from "./AdvertiseSpaceDetail";
export default {
name: "addHomeAdvertise",
title: "添加广告位",
components: { HomeAdvertiseDetail }
};
script>
4)真正显示的组件是 AdvertiseSpaceDetail.vue
首先判断要进行 新增还是修改操作, 根据isEdit ,true 为修改,false为新增
//钩子函数
created() {
//判断是添加还是修改操作
if (this.isEdit) {
//修改
const id = this.$route.query.id;
this.loadPromotionSpace(id);
} else {
//新增
this.homeAdvertise = {};
}
},
新增
//方法1: 保存广告位信息
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("数据处理失败! !");
});
});
},
需要请求后台接口,进行广告位信息回显
//方法2: 回显广告位信息
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("数据处理失败! !");
});
}
广告列表的展示,使用到了分页组件, 接下来通过一个案例演示一下分页插件的使用
https://element.eleme.cn/#/zh-CN/component/pagination
<template>
<div>
<div class="block">
<span class="demonstration">完整功能span>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage4"
:page-sizes="[100, 200, 300, 400]"
:page-size="100"
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>
参数 | 说明 | 类型 |
---|---|---|
page-size | 每页显示条数 | int |
current-page | 当前页 | int |
total | 总条数 | int |
page-sizes | 每页显示个数选项设置 | [10, 20, 30,] |
layout | 组件布局 |
分析:
事件 | 说明 | 回调函数 |
---|---|---|
size-change | 每页显示条数,改变时会触发 | 每页条数 |
current-change | 当前页,改变时会触发 | 当前页 |
<template>
<div class="app-container">
<div class="table-container">
<el-table ref="homeAdvertiseTable" :data="list" style="width: 100%;" border>
<el-table-column label="id" width="220" align="center">
<template slot-scope="scope">{{scope.row.id}}template>
el-table-column>
<el-table-column label="广告名称" align="center" width="320">
<template slot-scope="scope">{{scope.row.name}}template>
el-table-column>
<el-table-column label="广告图片" width="420" 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 {
total: 0, //总条数
size: 5, //每页显示条数
page: 1, //当前页
list: [] //广告数据
};
},
created() {
this.loadList();
},
methods: {
//加载广告数据
loadList() {
return this.axios
.get("http://localhost:8080/ssm-web/PromotionAd/findAllPromotionAd",{
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(error => {
this.$message.error("数据获取失败! ! !");
});
},
//每页显示条数发生变化
handlePageSizeChange(size) {
this.size = size;
this.loadList();
},
//当前页发生变化
handleCurrentPageChange(page) {
this.page = page;
this.loadList();
}
}
};
script>
我们已经解决了分页问题,接下来再看一下广告页面要展示哪些内容:
广告列表的展示数据来源于两张表:
promotion_ad 广告表
promotion_space 广告位表
edu-boss\src\views\AdvertiseManage\Advertises.vue
//数据部分
data() {
return {
typeMap: {}, //保存广告位对象信息
total: 0, //总条数
size: 5, //每页显示条数
page: 1, //当前页
list: [], //广告数据
listLoading: false
};
},
created() {
//获取广告列表数据
this.loadPromotionAd();
//获取广告位置数据
this.loadPromotionSpace();
},
//方法1; 获取广告列表数据
loadPromotionAd() {
this.listLoading = true;
return axios
.get("/PromotionAd/findAllPromotionAd", {
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 => {});
},
//方法2: 获取广告位置数据
loadPromotionSpace() {
this.listLoading = true;
return axios.get("/PromotionSpace/findAllPromotionSpace").then(res => {
//使用map进行遍历
res.data.content.map(item => {
//将数据保存到 typeMap key就是id,value就是 广告位对象
this.typeMap[item.id] = item;
});
this.listLoading = false;
});
},
//方法3: 获取广告位置名称
getSpaceName(spaceId) {
if (!spaceId) {
return "";
}
return this.typeMap[spaceId] && this.typeMap[spaceId].name;
},
页面部分,使用的是 el-switch 组件
<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部分
//方法4: 修改状态
handleUpdateStatus(row) {
this.$confirm("是否要修改上线/下线状态?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
//请求后台
axios
.get("/PromotionAd/updatePromotionAdStatus", {
params: {
id: row.id,
status: row.status
}
})
.then(res => {
this.loadPromotionAd();
})
.catch(err => {
this.$message("修改状态失败! ! !");
});
});
},
<el-button size="mini" class="btn-add" @click="handleAdd()">添加广告el-button>
//跳转到新增
handleAdd() {
this.$router.push({ path: "/addAdvertise" });
},
{
path: "addAdvertise",
name: "AddAdvertise",
component: () => import("../views/AdvertiseManage/AddAdvertise"),
meta: { requireAuth: true, title: "添加广告" }
},
在AddAdvertise组件中,引入了 AdvertiseDetail组件,真正的操作是在这个组件中完成的
:isEdit=“false” : false表示是新增操作
<template>
<home-advertise-detail :isEdit="false">home-advertise-detail>
template>
<script>
import HomeAdvertiseDetail from './AdvertiseDetail'
export default {
name: 'addHomeAdvertise',
title: '添加广告',
components: { HomeAdvertiseDetail }
}
script>
数据部分
data() {
return {
homeAdvertise, //广告表单对象
typeOptions: [] //广告位下拉列表
};
},
钩子函数
created() {
//判断是新增还是修改
if (this.isEdit) {
//修改
const id = this.$route.query.id;
this.loadPromotion(id);
} else {
//新增
this.homeAdvertise = {};
}
this.loadPromotionSpace();
},
方法
//方法1: 获取广告位置数据
loadPromotionSpace() {
return axios.get("/PromotionSpace/findAllPromotionSpace").then(res => {
//使用map函数进行遍历,获取广告位id 与 name,保存到typeOptions
this.typeOptions = res.data.content.map(item => {
return { label: item.name, value: item.id };
});
});
},
//方法2: 保存广告信息
handleSave() {
this.$refs.form.validate(valid => {
if (!valid) return false;
axios
.post("/PromotionAd/saveOrUpdatePromotionAd", this.homeAdvertise)
.then(res => {
//返回上个页面 并刷新
this.$router.back();
})
.catch(err => {});
});
},
//方法3: 修改回显广告信息
loadPromotion(id) {
return axios
.get("/PromotionAd/findPromotionAdById?id=" + id)
.then(res => {
Object.assign(this.homeAdvertise, res.data.content);
this.homeAdvertise.id = id;
})
.catch(err => {});
},
前端向后端传输数据是,没有设置时间格式,那么可能会发生数据的传输上出现为Sring类型,然后报错
所以修改方式:加上value-format="yyyy-MM-dd HH:mm:ss"设置时间格式
<el-date-picker
type="datetime"
placeholder="选择日期"
value-format="yyyy-MM-dd HH:mm:ss"
v-model="homeAdvertise.endTime"
></el-date-picker>
查询条件:
- 用户手机号
- 注册时间,包含开始日期和结束日期
在查询条件中使用了 ElementUI中的日期选择器,我们一起来简单学习一下日期选择器的使用.
https://element.eleme.cn/#/zh-CN/component/date-picker#mo-ren-xian-shi-ri-qi
<template>
<div>
<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>
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() {
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(params);
}
}
};
script>
edu-boss\src\views\Users.vue
数据部分
//数据部分
return {
pickerOptions,//日期选择器选项设置
total: 0, //总条数
size: 10, //每页显示条数
page: 1, //当前页
filter,
users: [],
loading: false,
allocAdminId: "",
allocDialogVisible: false,
allocRoleIds: [],
allRoleList: []
};
JS部分
created() {
//初始化用户数据
this.loadUsers();
}
//方法1: 加载用户数据
loadUsers() {
this.loading = true;
//设置参数
const params = { currentPage: this.page, pageSize: this.size };
//过滤条件
if (this.filter.username) params.username = this.filter.username;
//设置日期参数
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);
}
//请求后台接口
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("获取数据失败! ! !");
});
},
状态按钮
<el-button size="mini" type="text" @click="handleToggleStatus(scope.row)">
{{ scope.row.status == "ENABLE" ? "禁用" : "启用" }}el-button>
JS部分
//修改用户状态
handleToggleStatus(item) {
return axios
.get("/user/updateUserStatus", {
params: {
id: item.id,
status: item.status
}
})
.then(res => {
debugger;
console.log(res.data.content);
item.status = res.data.content;
})
.catch(err => {
this.$message.error("状态修改失败! ! !");
});
},
角色组件是 Roles.vue ,在该组件中对角色信息进行管理
data() {
return {
listQuery: { name: "" },
list: null,
listLoading: false,
dialogVisible: false,
role: Object.assign({}, defaultRole),
isEdit: false
};
},
created() {
//获取角色列表
this.loadRoles();
},
//获取角色数据
loadRoles() {
return axios
.post("/role/findAllRole", this.listQuery)
.then(res => {
this.list = res.data.content;
this.listLoading = false;
})
.catch(err => {});
},
<el-input v-model="listQuery.name" class="input-width" placeholder="角色名称" clearable>
el-input>
//条件查询
handleSearchList() {
this.loadRoles();
},
<el-button size="mini" class="btn-add" @click="handleAdd()" style="margin-left: 20px">
添加角色
el-button>
//添加角色弹窗
handleAdd() {
this.dialogVisible = true; //打开对话框
this.isEdit = false; //false 修改操作
this.role = Object.assign({}, defaultRole);
},
<el-dialog :title="isEdit?'编辑角色':'添加角色'" :visible.sync="dialogVisible"
width="40%">
<el-form :model="role" label-width="150px" size="small">
<el-form-item label="角色名称:">
<el-input v-model="role.name" style="width: 250px">el-input>
el-form-item>
<el-form-item label="角色编码:">
<el-input v-model="role.code" style="width: 250px">el-input>
el-form-item>
<el-form-item label="描述:">
<el-input v-model="role.description" type="textarea" :rows="5"
style="width: 250px">el-input>
el-form-item>
el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false" size="small">取 消el-button>
<el-button type="primary" @click="handleSave()" size="small">确 定elbutton>
span>
el-dialog>
//添加&修改角色
handleSave() {
axios
.post("/role/saveOrUpdateRole", this.role)
.then(res => {
this.dialogVisible = false;
this.loadRoles();
})
.catch(error => {
this.$message.error("保存课程信息失败! ! !");
});
},
修改按钮,点击传递当前行数据对象
<el-button size="mini" type="text" @click="handleUpdate( scope.row)">编辑el-button>
显示对话框,回显数据
//修改角色弹窗
handleUpdate( row) {
this.dialogVisible = true;
this.isEdit = true;
//回显数据
this.role = Object.assign({}, row);
},
修改角色,还是调用的handleSave 方法
<el-button size="mini" type="text" @click="handleDelete(scope.row)">删除el-button>
这里使用到了ElementIUI中的 MessageBox 弹框
https://element.eleme.cn/#/zh-CN/component/message-box#options
handleDelete(row) {
this.$confirm("是否要删除该角色?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
axios("/role/deleteRole?id=" + row.id)
.then(res => {
this.loadRoles();
})
.catch(err => {
this.$message.error("操作失败! ! !");
});
});
},
需求分析:
前端要实现的效果:
<el-button size="mini" type="text" @click="handleSelectMenu(scope.row)">
分配菜单
el-button>
//为角色分配菜单
handleSelectMenu(row) {
this.$router.push({ path: "/allocMenu", query: { roleId: row.id } });
},
{
path: "allocMenu",
name: "AllocMenu",
component: () =>
import(
/* webpackChunkName: 'allocMenu' */ "../views/PermissionManage/AllocMenu"
),
meta: { requireAuth: true, title: "角色菜单管理" }
},
data() {
return {
menuTreeList: [], //菜单数据
checkedMenuId: [], //被选中的菜单
//树形结构子节点设置
defaultProps: {
children: "subMenuList",
label: "name"
},
roleId: null
};
},
//钩子函数
created() {
//获取路由携带的id
this.roleId = this.$route.query.roleId;
//获取菜单列表
this.treeList();
//获取角色所拥有的菜单信息
this.getRoleMenu(this.roleId);
},
//方法1: 获取菜单列表,使用树形控件展示
treeList() {
axios.get("/role/findAllMenu").then(res => {
console.log(res.data.content);
//获取树形控件所需数据
this.menuTreeList = res.data.content.parentMenuList;
});
},
//方法2: 获取当前角色所拥有菜单列表id
getRoleMenu(roleId) {
axios.get("/role/findMenuByRoleId?roleId=" + roleId).then(res => {
console.log(res.data.content);
//将已有菜单权限设置为选中
this.$refs.tree.setCheckedKeys(res.data.content);
});
},
分配菜单按钮
保存
清空
//方法3: 修改角色所拥有的菜单列表
handleSave() {
//debugger;
//获取所有被选中的节点
const checkedNodes = this.$refs.tree.getCheckedNodes();
//定义常量 保存被选中的菜单id
const checkedMenuIds = [];
if (checkedNodes != null && checkedNodes.length > 0) {
//遍历获取节点对象
for (let i = 0; i < checkedNodes.length; i++) {
const checkedNode = checkedNodes[i];
//保存菜单列表id
checkedMenuIds.push(checkedNode.id);
//判断: 当前节点为子节点 && 其父ID在数组没有出现过,就保存这个父Id
if (
checkedNode.parentId !== -1 &&
checkedMenuIds.filter(item => checkedNode.parentId).length === 0
) {
checkedMenuIds.push(checkedNode.parentId);
}
}
}
this.$confirm("是否分配菜单?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
//准备参数
const params = {
roleId: this.roleId, //角色ID
menuIdList: checkedMenuIds //当前角色拥有的菜单权限ID
};
//请求后台
axios
.post("/role/RoleContextMenu", params)
.then(res => {
this.$router.back();
})
.catch(err => {
this.$message.error("权限分配失败! ! !");
});
});
},
菜单组件是 Menus.vue ,在该组件中对菜单信息进行管理
data() {
return {
total: 0, //总条数
size: 10, //每页显示条数
page: 1, //当前页
list: [], //广告数据
listLoading: true,
parentId: 0 //菜单父id
};
},
created() {
//获取菜单列表
this.loadMenuList();
},
//方法1: 加载菜单列表数据
loadMenuList() {
this.listLoading = true;
return axios
.get("/menu/findAllMenu", {
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(error => {
this.$message.error("数据获取失败! ! !");
});
},
问题:在后端的接口中的查询是未带分页,需要进行更新后端接口,将不是分页的修改为分页,或者是直接添加一个带参数的接口方法。
添加菜单
//新增菜单跳转
handleAddMenu() {
this.$router.push("/addMenu");
},
2)AddMenu.vue 组件中引入了MenuDetail
在打开新增菜单页面后, 需要展示一个下拉框,下拉框中的数据是所有的顶级父菜单.
data() {
return {
menu, //菜单对象
selectMenuList: [], //下拉列表数据
rules
};
},
在钩子函数中会进行判断,如果是修改操作,就根据ID 查询当前菜单信息,以及父菜单信息;如果是新增操作,则只查询父类菜单信息即可
created() {
if (this.isEdit) {
//修改,回显菜单信息
const id = this.$route.query.id;
//获取当前菜单和父菜单信息
this.findMenuInfoById(id);
} else {
//新增
this.menu = {};
//获取父类菜单信息
this.findMenuInfoById(-1);
}
},
//方法1: 添加或修改 下拉父菜单回显
findMenuInfoById(id) {
axios
.get("/menu/findMenuInfoById?id=" + id)
.then(res => {
debugger;
console.log(res.data);
//判断不为null,修改操作需要回显
if (res.data.content.menuInfo != null) {
this.menu = res.data.content.menuInfo;
}
//获取到父菜单信息,保存到selectMenuList
this.selectMenuList = res.data.content.parentMenuList.map(item => {
return { id: item.id, title: item.name };
});
//-1 显示 无上级菜单 (unshift向数组的开头添加一个元素)
this.selectMenuList.unshift({ id: -1, title: "无上级菜单" });
})
.catch(err => {
this.$message.error("数据获取失败! ! !");
});
},
提交
//保存菜单
handleSave() {
this.$refs.form.validate(valid => {
if (!valid) return false;
axios
.post("/menu/saveOrUpdateMenu", this.menu)
.then(res => {
this.$router.back();
})
.catch(error => {
this.$message.error("保存课程信息失败! ! !");
});
});
}
资源组件是 Resources.vue ,在该组件中对资源信息进行管理.
//查询条件
const listQuery = {
currentPage: 1,
pageSize: 5,
name: null,
url: null,
categoryId: null
};
//资源对象
const defaultResource = {
id: null,
name: null,
url: null,
categoryId: null,
description: ""
};
data() {
return {
listQuery,//查询条件
total: 0,
list: [], //资源数据
cateList: [], //资源分类数据
listLoading: false,
dialogVisible: false,
resource: Object.assign({}, defaultResource),
isEdit: false,
categoryOptions: [],
defaultCategoryId: null
};
},
//钩子函数
created() {
//获取资源数据
this.getResourceList();
//获取资源分类数据
this.getResourceCateList();
},
//方法1: 获取资源数据
getResourceList() {
this.listLoading = true;
axios
.post("/resource/findAllResource", this.listQuery)
.then(res => {
this.list = res.data.content.list;
this.total = res.data.content.total;
this.listLoading = false;
})
.catch(err => {
this.$message.error("数据获取失败! ! !");
});
},
getResourceCateList() 方法获取的是资源分类信息,在下拉框中展示
//方法2: 获取资源分类数据
getResourceCateList() {
axios
.get("/ResourceCategory/findAllResourceCategory")
.then(res => {
this.cateList = res.data.content;
//遍历获取资源分类
for (let i = 0; i < this.cateList.length; i++) {
const cate = this.cateList[i];
//将资源分类名与id保存到 categoryOptions中,供下拉列表展示
this.categoryOptions.push({ label: cate.name, value: cate.id });
}
this.defaultCategoryId = this.cateList[0].id;
})
.catch(err => {
this.$message.error("数据获取失败! ! !");
});
},
查询
<el-button style="float:right" type="primary" @click="handleSearchList()" size="small">
查询搜索el-button>
//查询条件对象
const listQuery = {
currentPage: 1,
pageSize: 5,
name: null,
url: null,
categoryId: null
};
//查询按钮
handleSearchList() {
this.getResourceList();
},
<el-button size="mini" class="btn-add" @click="handleAdd()" style="margin-left:20px">
添加el-button>
//添加资源回显
handleAdd() {
this.dialogVisible = true; //显示表单
this.isEdit = false; //新增为false
this.resource = Object.assign({}, defaultResource); //资源对象
this.resource.categoryId = this.defaultCategoryId; //保存默认分类id
},
v-model 的值为当前被选中的 el-option 的 value 属性值
属性 | 说明 |
---|---|
value | 选项的值 |
label | 选项的标签名 |
key | 作为 value 唯一标识的键名 |
<el-form-item label="资源分类:">
<el-select v-model="resource.categoryId" placeholder="全部" clearable style="width: 250px">
<el-option
v-for="item in categoryOptions"
:key="item.value"
:label="item.label"
:value="item.value"
>el-option>
el-select>
el-form-item>
<el-button type="primary" @click="handleSave()" size="small">确 定el-button>
//添加&修改资源
handleSave() {
axios
.post("/resource/saveOrUpdateResource", this.resource)
.then(res => {
this.dialogVisible = false;
this.getResourceList();
})
.catch(error => {
this.$message.error("操作失败! ! !");
});
},
<el-button size="mini" type="text" @click="handleUpdate(scope.row)">编辑el-button>
//编辑资源 回显
handleUpdate(row) {
debugger;
this.dialogVisible = true;
this.isEdit = true;
this.resource = Object.assign({}, row);
},
<el-button type="primary" :loading="loading" @click="submit('login-form')">{{ loading ? 'Loading...' : '登录' }}el-button>
//提交登录表单
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!"
};
}
this.$router.replace({ path: this.$route.query.redirect || "/" });
this.loading = false;
})
.catch(err => {
this.loading = false;
});
});
}
}
/**
* 创建新的客户端令牌
*/
createToken: async ({ commit }, { username, password }) => {
//请求后台登录接口
const res = await TokenService.userLogin({
phone: username.trim(),
password: password.trim()
});
console.log(res);
//判断结果不等于1,登录失败
if (res.state !== 1) {
return Promise.resolve(res);
}
//获取到content
const result = res.content;
//将token保存
commit(CHANGE_SESSION, {
accessToken: result.access_token
});
return res;
},
import { TokenService, UserService } from "../services";
//登录请求 async ES6语法, 作用: 发送异步请求
export const userLogin = async (data) => {
//await 表示等待接收返回的数据
return await PostRequest(`${process.env.VUE_APP_API_FAKE}/user/login${Serialize(data)}`)
}
/**
* 获取当前登录用户权限
*/
getUserPermissions: async ({ commit }) => {
//1.请求后台 获取当前用户的权限
const res = await UserService.getUserPermissions();
//2.判断
if (!res.success) {
//获取失败直接返回 false
return res.success;
}
//3.获取数据成功,取出菜单 与 资源列表
const { menuList, resourceList } = res.content;
//4.下面的代码 就是在生成树形结构的菜单
let menus = [];
const formatMenu = treeData => {
if (treeData.length > 0) {
return treeData.map(item => formatMenu(item));
}
const result = {};
//shown等于表示可以显示,将内容保存
if (treeData.shown == 1) {
result.id = treeData.id;
result.text = treeData.name;
result.label = treeData.name;
result.name = treeData.href;
result.icon = treeData.icon;
result.shown = treeData.shown;
} else {
return "";
}
//获取子节点
if (treeData.subMenuList) {
result.children = [];
treeData.subMenuList.forEach(item => {
formatMenu(item) && result.children.push(formatMenu(item));
});
if (result.children.length === 0) {
delete result.children;
}
}
return result;
};
const memusMap = {};
const splapMenu = treeData => {
if (treeData.length > 0) {
return treeData.map(item => splapMenu(item));
}
const result = {};
result.id = treeData.id;
result.text = treeData.name;
result.label = treeData.name;
result.name = treeData.href;
result.icon = treeData.icon;
result.shown = treeData.shown;
result.name && (memusMap[result.name] = result);
if (treeData.subMenuList) {
result.children = [];
treeData.subMenuList.forEach(item => {
result.children.push(splapMenu(item));
});
}
return result;
};
splapMenu(menuList);
menus = formatMenu(menuList);
commit(CHANGE_SIDERBAR_MENU, menus);
return { menus, resourceList, menuList, memusMap };
},
// 导航守卫 to要访问的url, from从哪个路径跳转过来, next() 放行
router.beforeHooks.unshift((to, from, next) => {
//不需要验证直接放行
if (!to.meta.requireAuth) return next();
//需要验证token,调用 store中的checkToken方法
store.dispatch("checkToken").then(valid => {
//判断是否存在token
if (valid) {
//发送请求到后台,在后台再次判断token是否存在
store.dispatch("getUserPermissions").then(res => {
if (!res) {
//失效 清除token
store.dispatch("deleteToken");
//跳转到登录页面
return next({ name: "ToLogin" });
}
//token正确, 导航到对应的页面
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();
}
// unauthorized
console.log("Unauthorized");
//用户没有登录 跳转到登录页面
next({ name: "Login", query: { redirect: to.fullPath } });
});
});
checkToken: async ({ commit, getters }) => {
//取出token
const token = getters.session.accessToken;
if (!token) {
//不可用
return Promise.resolve(false);
}
return Promise.resolve(true);
},
<el-button size="mini" type="text" @click="handleSelectRole(scope.row)">分配角色
el-button>
//分配角色
handleSelectRole(row) {
//保存用户ID
this.allocAdminId = row.id;
//获取角色列表
this.getRoleList();
//获取当前用户拥有的角色
this.getUserRoleById(row.id);
//打开对话框
this.allocDialogVisible = true;
},
getRoleList(id) {
return axios
.post("/role/findAllRole", this.listQuery)
.then(res => {
this.allRoleList = res.data.content.map(item => {
return { id: item.id, name: item.name };
});
})
.catch(err => {});
},
getUserRoleById(id) {
axios.get("/user/findUserRoleById?id=" + id).then(res => {
const allocRoleList = res.data.content;
this.allocRoleIds = [];
if (allocRoleList != null && allocRoleList.length > 0) {
for (let i = 0; i < allocRoleList.length; i++) {
this.allocRoleIds.push(allocRoleList[i].id);
}
}
});
},
handleAllocRole() {
const params = {
userId: this.allocAdminId,
roleIdList: this.allocRoleIds
};
axios.post("/user/userContextRole", params).then(res => {
this.allocDialogVisible = false;
});
},