赞
踩
本项目是基于springboot + vue的前后端分离权限项目,适合做毕设项目和个人学习,手把手搭建。
后端技术栈:springboot,MySQL,redis,Maven
前端技术栈:vue,Vuex,Vue-Router,Echarts,elementUI
通用的权限管理项目,可以在本系统的基础上开发大部分的管理系统。
导图:
创建springboot项目,如视频操作
pom.xml文件
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>2.5.9</version>
- <relativePath/> <!-- lookup parent from repository -->
- </parent>
- <groupId>com.example</groupId>
- <artifactId>base-authority</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <name>base-authority</name>
- <description>Demo project for Spring Boot</description>
- <properties>
- <java.version>1.8</java.version>
- </properties>
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter</artifactId>
- </dependency>
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
-
- <!-- swagger-->
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-swagger2</artifactId>
- <version>2.9.2</version>
- </dependency>
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-swagger-ui</artifactId>
- <version>2.9.2</version>
- </dependency>
-
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
-
-
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <scope>runtime</scope>
- </dependency>
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <optional>true</optional>
- </dependency>
- <!-- mybatis-plus -->
- <dependency>
- <groupId>com.baomidou</groupId>
- <artifactId>mybatis-plus-boot-starter</artifactId>
- <version>3.5.1</version>
- </dependency>
- <dependency>
- <groupId>org.mybatis.spring.boot</groupId>
- <artifactId>mybatis-spring-boot-starter</artifactId>
- <version>2.2.1</version>
- </dependency>
-
- <!--redis-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
- <!-- JSON的依赖 -->
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.69</version>
- </dependency>
-
- <dependency>
- <groupId>commons-codec</groupId>
- <artifactId>commons-codec</artifactId>
- </dependency>
-
-
- <!-- JWT -->
- <dependency>
- <groupId>com.auth0</groupId>
- <artifactId>java-jwt</artifactId>
- <version>3.10.3</version>
- </dependency>
-
- </dependencies>
-
- <build>
- <plugins>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- </plugin>
- </plugins>
- </build>
-
- </project>

创建数据库,在创建sys_user表
- CREATE TABLE `sys_user` (
- `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id主键',
- `username` varchar(255) DEFAULT NULL COMMENT '用户名',
- `password` varchar(255) DEFAULT NULL COMMENT '密码',
- `nickname` varchar(255) DEFAULT NULL COMMENT '昵称',
- `address` varchar(255) DEFAULT NULL COMMENT '地址',
- `email` varchar(255) DEFAULT NULL COMMENT '邮箱',
- `phone` varchar(18) DEFAULT NULL COMMENT '联系方式',
- `role_id` int(11) DEFAULT NULL COMMENT '角色id',
- `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
- `header_url` varchar(255) DEFAULT NULL COMMENT '用户头像',
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8mb4;
创建user的后端代码,entity,mapper,service,controller层代码,如视频操作,代码太多就不复制粘贴了。
效果图
Aside.vue
- <template>
- <el-menu default-active="1-4-1"
- class="el-menu-vertical-demo"
- style="min-height:100%;overflow-x: hidden;"
- text-color="#fff"
- active-text-color="#ffd046"
- background-color="rgb(48,65,86)"
- :collapse-transition="false"
- router
- :collapse="isCollapse">
- <div style="height: 60px;line-height: 60px;text-align: center">
- <img src="../assets/logo.png" style="width:30px;position: relative;margin-right: 5px;top:6px">
- <b style="color:white" v-show="logoTextShow">后台管理系统</b>
- </div>
- <el-submenu index="1">
- <template slot="title">
- <i class="el-icon-location"></i>
- <span slot="title">导航一</span>
- </template>
- <el-menu-item-group>
- <span slot="title">分组一</span>
- <el-menu-item index="/user">用户管理</el-menu-item>
- <el-menu-item index="/role">角色管理</el-menu-item>
- </el-menu-item-group>
- <el-menu-item-group title="分组2">
- <el-menu-item index="1-3">选项3</el-menu-item>
- </el-menu-item-group>
- <el-submenu index="1-4">
- <span slot="title">选项4</span>
- <el-menu-item index="1-4-1">选项1</el-menu-item>
- </el-submenu>
- </el-submenu>
- <el-menu-item index="2">
- <i class="el-icon-menu"></i>
- <span slot="title">导航二</span>
- </el-menu-item>
- </el-menu>
- </template>
-
- <script>
- export default {
- name: "Aside",
- props:{
- isCollapse:Boolean,
- logoTextShow:Boolean
- }
- }
- </script>
-
- <style scoped>
- .el-menu-vertical-demo:not(.el-menu--collapse) {
- width: 200px;
- min-height: 400px;
- }
- </style>

Header.vue
- <template>
- <div style="line-height: 60px;display: flex">
- <div style="flex: 1;font-size: 30px">
- <span :class="collapseBtnClass" style="cursor: pointer" @click="collapse"></span>
- <el-breadcrumb separator="/" style="display: inline-block;margin-left:10px;position: absolute;top:22px" >
- <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
- <el-breadcrumb-item><b>{{currentPathName}}</b></el-breadcrumb-item>
- </el-breadcrumb>
- </div>
-
-
- <el-dropdown style="width: 150px;cursor: pointer;text-align: center">
- <div style="display: inline-block">
- <img src="../assets/logo.png" style="width:65px;height:45px;border-radius: 50%;position: relative;top:10px;right:8px"/>
- <span>张三</span><i class="el-icon-arrow-down" style="margin-left:5px"></i>
- </div>
-
- <el-dropdown-menu slot="dropdown" style="width: 100px;text-align: center;text-decoration: none">
- <el-dropdown-item style="font-size: 15px;padding:5px 0">
- <router-link to="/person" style="text-decoration: none;color: #606266">个人信息</router-link>
- </el-dropdown-item>
- <el-dropdown-item style="font-size: 15px;padding:5px 0">
- <router-link to="/person" style="text-decoration: none;color: #606266">修改密码</router-link>
- </el-dropdown-item>
- <el-dropdown-item style="font-size: 15px;padding:5px 0">
- <div style="text-decoration: none">退出</div>
- </el-dropdown-item>
- </el-dropdown-menu>
- </el-dropdown>
-
- </div>
- </template>
-
- <script>
- export default {
- name: "Header",
- props:{
- collapseBtnClass:String
- },
- computed:{
- currentPathName(){
- return this.$store.state.currentPathName;
- }
- },
- methods:{
- collapse(){
- this.$emit('collapse')
- }
- }
- }
- </script>
-
- <style scoped>
-
- </style>

Manage.vue
- <template>
- <el-container style="min-height: 100vh">
- <el-aside :width="sideWidth + 'px'">
- <Aside :isCollapse="isCollapse" :logoTextShow="logoTextShow"></Aside>
- </el-aside>
-
- <el-container>
- <el-header style="border-bottom: 1px solid #ccc">
- <Header @collapse="collapse" :collapseBtnClass="collapseBtnClass"></Header>
- </el-header>
-
- <el-main>
- <router-view></router-view>
- </el-main>
- </el-container>
- </el-container>
- </template>
-
- <script>
- import Aside from "@/components/Aside";
- import Header from "@/components/Header";
- export default {
- name: "Manage",
- components:{
- Aside,Header
- },
- data(){
- return{
- isCollapse:false,
- logoTextShow:true,
- collapseBtnClass:'el-icon-s-fold',
- sideWidth:200
- }
- },
- methods:{
- collapse(){
- this.isCollapse = !this.isCollapse;
- if(this.isCollapse){
- //收缩
- this.sideWidth = 64;
- this.logoTextShow = false;
- this.collapseBtnClass = 'el-icon-s-unfold';
- }else{
- this.sideWidth = 200;
- this.logoTextShow = true;
- this.collapseBtnClass = 'el-icon-s-fold';
- }
- }
- }
- }
- </script>
-
- <style scoped>
- /*去掉aside侧边栏的底部滚动条*/
- .el-aside::-webkit-scrollbar {
- display: none;
-
- }
- </style>

VUEX的index.js
- import Vue from 'vue'
- import Vuex from 'vuex'
-
- Vue.use(Vuex)
-
- export default new Vuex.Store({
- state: {
- currentPathName:''
- },
- getters: {
- },
- mutations: {
- setPath(state){
- state.currentPathName = localStorage.getItem('currentPathName');
- }
- },
- actions: {
- },
- modules: {
- }
- })

Router的index.js
- import Vue from 'vue'
- import VueRouter from 'vue-router'
- import store from '../store'
- Vue.use(VueRouter)
-
- const routes = [
- {
- path: '/',
- name: 'manage',
- component: () => import(/* webpackChunkName: "about" */ '../views/Manage.vue'),
- children:[
- {
- path: 'user',
- name: '用户管理',
- component: () => import(/* webpackChunkName: "about" */ '../views/User.vue'),
- },
- {
- path: 'role',
- name: '角色管理',
- component: () => import(/* webpackChunkName: "about" */ '../views/Role.vue'),
- }
- ]
- }
- ]
-
- const router = new VueRouter({
- mode: 'history',
- base: process.env.BASE_URL,
- routes
- })
-
- router.beforeEach((to,from,next) => {
- localStorage.setItem('currentPathName',to.name);
- store.commit('setPath')
- next();
- })
- export default router

补充
sys_user表添加 hearer_url 头像字段
去掉Aside侧边栏的底部滚动条
安装axios
npm install axios -S
封装axios的请求文件
- import axios from 'axios'
- import { Notification, MessageBox, Message, Loading } from 'element-ui'
- import ElementUI from 'element-ui'
- import router from "@/router";
-
- const request = axios.create({
- baseURL:'http://localhost:8899/',
- timeout:5000
- })
-
- request.interceptors.request.use(config => {
- config.headers['Content-Type'] = 'application/json;charset=UTF-8'
- let user = localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : null
- if(user){
- config.headers["token"] = user.token;
- }
- return config;
- },error => {
- return Promise.reject(error)
- })
-
- request.interceptors.response.use(
- response => {
- let res = response.data;
- if(response.config.responseType === 'blob'){
- return res;
- }
- if(typeof res === 'string'){
- res = res ? JSON.parse(res) : res
- }
- // 当权限验证不通过的时候给出提示
- if (res.code === '401') {
- ElementUI.Message({
- message: res.msg,
- type: 'error'
- });
- console.log('router.currentRoute.fullPath ')
- console.log(router.currentRoute.fullPath )
- // if (router.currentRoute.fullPath !== '/login') {
- // router.push('/login')
- // }
- }
- return res;
- },
- error => {
- if(error.code === '401'){
- router.push("/login")
- }
- Message.error(error)
- return Promise.reject(error);
- }
- )
-
- export default request

编写User.vue的界面,五部分组成:
1.搜索栏
2.加新增和批量删除按钮
3.table表格
4.新增和编辑的弹出框
5.分页
生命周期介绍 //created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成识图 //mounted:在模板渲染成html后调用,通常初始化页面完成后,再对html的dom节点进行一些需要的操作。
跨域
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域。
报错显示:
解决方法:
- package com.example.authority.config;
-
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.cors.CorsConfiguration;
- import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
- import org.springframework.web.filter.CorsFilter;
-
- @Configuration
- public class CorsConfig {
-
- // 当前跨域请求最大有效时长。这里默认1天
- private static final long MAX_AGE = 24 * 60 * 60;
-
- @Bean
- public CorsFilter corsFilter() {
- UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
- CorsConfiguration corsConfiguration = new CorsConfiguration();
- corsConfiguration.addAllowedOrigin("*"); // 1 设置访问源地址
- corsConfiguration.addAllowedHeader("*"); // 2 设置访问源请求头
- corsConfiguration.addAllowedMethod("*"); // 3 设置访问源请求方法
- corsConfiguration.setMaxAge(MAX_AGE);
- source.registerCorsConfiguration("/**", corsConfiguration); // 4 对接口配置跨域设置
- return new CorsFilter(source);
- }
- }
-

完成的User.vue页面
- <template>
- <div>
- <div>
- <!-- 搜索栏-->
- <el-input style="width: 200px;margin-right: 20px" placeholder="请输入用户名" v-model="username" prefix-icon="el-icon-user"></el-input>
- <el-input style="width: 200px" placeholder="请输入邮箱" v-model="email" prefix-icon="el-icon-message"></el-input>
- <el-button style="margin-left: 10px;" type="primary" @click="load" class="el-icon-search">搜索</el-button>
- <el-button style="margin-left: 10px;" type="warning" @click="reset" class="el-icon-refresh">重置</el-button>
- </div>
-
- <div style="margin-top:20px;margin-bottom: 20px;">
- <!-- 新增。批量删除-->
- <el-button style="margin-right: 10px;" type="success" class="el-icon-plus" @click="save">新增</el-button>
- <el-popconfirm
- confirm-button-text='确定'
- cancel-button-text='取消'
- icon="el-icon-info"
- icon-color="red"
- title="确定删除这些数据吗?"
- @confirm="deleteBatch"
- @cancel="cancel">
- <el-button slot="reference" type="danger" style="margin-left:5px;" class="el-icon-delete">批量删除</el-button>
- </el-popconfirm>
- </div>
-
- <el-table :data="tableData" border stripe :header-cell-style="getRowClass" @selection-change="handleSelectionChange">
- <el-table-column type="selection" width="55"></el-table-column>
- <el-table-column prop="id" label="id"></el-table-column>
- <el-table-column prop="username" label="用户名"></el-table-column>
- <el-table-column prop="nickname" label="昵称"></el-table-column>
- <el-table-column prop="address" label="地址"></el-table-column>
- <el-table-column prop="email" label="邮箱"></el-table-column>
- <el-table-column prop="phone" label="联系方式"></el-table-column>
- <el-table-column prop="roleId" label="角色"></el-table-column>
- <el-table-column prop="createTime" label="创建时间"></el-table-column>
- <el-table-column prop="headerUrl" label="头像"></el-table-column>
- <el-table-column label="操作" width="200">
- <template slot-scope="scope">
- <el-button type="primary" class="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
- <el-popconfirm
- confirm-button-text='确定'
- cancel-button-text='取消'
- icon="el-icon-info"
- icon-color="red"
- title="确定删除这些数据吗?"
- @confirm="handleDelete(scope.row.id)"
- @cancel="cancel">
- <el-button slot="reference" type="danger" style="margin-left:5px;" class="el-icon-delete">删除</el-button>
- </el-popconfirm>
- </template>
- </el-table-column>
- </el-table>
-
- <div class="block" style="padding:10px 0;align-content: center;margin-left: 30%;margin-top:30px;">
- <el-pagination
- @size-change="handleSizeChange"
- @current-change="handleCurrentChange"
- :current-page="pageNum"
- :page-sizes="[5, 10, 15, 20]"
- :page-size="10"
- layout="total, sizes, prev, pager, next, jumper"
- :total="total">
- </el-pagination>
- </div>
-
- <el-dialog title="收货地址" :visible.sync="dialogFormVisible" width="30%">
- <el-form :model="form">
- <el-form-item label="用户名" :label-width="formLabelWidth">
- <el-input v-model="form.username" autocomplete="off"></el-input>
- </el-form-item>
- <el-form-item label="密码" :label-width="formLabelWidth">
- <el-input v-model="form.password" autocomplete="off" type="password" show-password></el-input>
- </el-form-item>
- <el-form-item label="昵称" :label-width="formLabelWidth">
- <el-input v-model="form.nickname" autocomplete="off"></el-input>
- </el-form-item>
- <el-form-item label="地址" :label-width="formLabelWidth">
- <el-input v-model="form.address" autocomplete="off"></el-input>
- </el-form-item>
- <el-form-item label="邮箱" :label-width="formLabelWidth">
- <el-input v-model="form.email" autocomplete="off"></el-input>
- </el-form-item>
- <el-form-item label="联系方式" :label-width="formLabelWidth">
- <el-input v-model="form.phone" autocomplete="off"></el-input>
- </el-form-item>
- </el-form>
- <div slot="footer" class="dialog-footer">
- <el-button @click="dialogFormVisible = false">取 消</el-button>
- <el-button type="primary" @click="handleAdd">确 定</el-button>
- </div>
- </el-dialog>
-
- </div>
- </template>
-
- <script>
- export default {
- name: "User",
- data(){
- return{
- pageSize:10,
- pageNum:1,
- username:'',
- email:'',
- tableData:[],
- dialogFormVisible:false,
- form:{},
- formLabelWidth: '80px',
- multipleSelection:[],
- total:0
- }
- },
- created() {
- this.load();
- },
- methods:{
- load(){
- this.request.get("/user/findPage",{
- params:{
- pageNum:this.pageNum,
- pageSize:this.pageSize,
- username:this.username,
- email:this.email
- }
- }).then(res => {
- this.tableData = res.data.records;
- this.total = res.data.total;
- })
- },
- getRowClass({rowIndex,columnIndex}){
- if(rowIndex === 0){
- return 'background:#ccc'
- }
- },
- reset(){
- this.email = '';
- this.username = '';
- this.load();
- },
- save(){
- this.dialogFormVisible = true;
- this.form = {};
- },
- handleAdd(){
- this.request.post("/user/save",this.form).then(res => {
- if(res.code === '200'){
- if(this.form.id){
- this.$message.success('编辑成功');
- }else{
- this.$message.success('新增成功');
- }
- this.dialogFormVisible = false;
- this.load();
- }else{
- this.$message.error('操作失败,请联系管理员')
- }
- })
- },
- handleEdit(row){
- this.form = JSON.parse(JSON.stringify(row));
- this.dialogFormVisible = true;
- },
- handleDelete(id){
- if(id){
- this.request.delete('/user/deleteById/' + id).then(res => {
- if(res.code === '200'){
- this.$message.success('删除数据成功');
- this.load();
- }else{
- this.$message.error('删除数据失败,请联系管理员')
- }
- })
- }else{
- this.$message.error('没有id信息,无法删除');
- }
- },
- cancel(){
- this.$message.success('取消操作成功');
- },
- handleSelectionChange(val) {
- this.multipleSelection = val;
- },
- deleteBatch(){
- //批量删除数据
- if(this.multipleSelection.length === 0){
- this.$message.warning("请先选择要删除的数据");
- return
- }
- const ids = this.multipleSelection.map(v => v.id);
- this.request.post('/user/deleteBatch',ids).then(res => {
- if(res.code === '200'){
- this.$message.success('批量删除成功');
- this.load();
- }else{
- this.$message.error('批量删除失败');
- }
- })
- },
- handleSizeChange(val) {
- this.pageSize = val;
- this.load();
- },
- handleCurrentChange(val) {
- this.pageNum = val;
- this.load();
- }
- }
- }
- </script>
-
- <style scoped>
-
- </style>

补充:删除数据的分页显示
创建角色表
创建角色管理后台代码
如视频操作,代码较多,不发出来了
创建角色页面
- <template>
- <div>
- <div>
- <!-- 搜索栏-->
- <el-input style="width: 200px;margin-right: 20px" placeholder="请输入名称" v-model="name" prefix-icon="el-icon-user"></el-input>
- <el-button style="margin-left: 10px;" type="primary" @click="load" class="el-icon-search">搜索</el-button>
- <el-button style="margin-left: 10px;" type="warning" @click="reset" class="el-icon-refresh">重置</el-button>
- </div>
-
- <div style="margin-top:20px;margin-bottom: 20px;">
- <!-- 新增。批量删除-->
- <el-button style="margin-right: 10px;" type="success" class="el-icon-plus" @click="save">新增</el-button>
- <el-popconfirm
- confirm-button-text='确定'
- cancel-button-text='取消'
- icon="el-icon-info"
- icon-color="red"
- title="确定删除这些数据吗?"
- @confirm="deleteBatch"
- @cancel="cancel">
- <el-button slot="reference" type="danger" style="margin-left:5px;" class="el-icon-delete">批量删除</el-button>
- </el-popconfirm>
- </div>
-
- <el-table :data="tableData" border stripe :header-cell-style="getRowClass" @selection-change="handleSelectionChange">
- <el-table-column type="selection" width="55"></el-table-column>
- <el-table-column prop="id" label="id"></el-table-column>
- <el-table-column prop="name" label="角色名称"></el-table-column>
- <el-table-column prop="code" label="角色编码"></el-table-column>
- <el-table-column prop="description" label="描述"></el-table-column>
- <el-table-column label="操作" width="200">
- <template slot-scope="scope">
- <el-button type="primary" class="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
- <el-popconfirm
- confirm-button-text='确定'
- cancel-button-text='取消'
- icon="el-icon-info"
- icon-color="red"
- title="确定删除这些数据吗?"
- @confirm="handleDelete(scope.row.id)"
- @cancel="cancel">
- <el-button slot="reference" type="danger" style="margin-left:5px;" class="el-icon-delete">删除</el-button>
- </el-popconfirm>
- </template>
- </el-table-column>
- </el-table>
-
- <div class="block" style="padding:10px 0;align-content: center;margin-left: 30%;margin-top:30px;">
- <el-pagination
- @size-change="handleSizeChange"
- @current-change="handleCurrentChange"
- :current-page="pageNum"
- :page-sizes="[5, 10, 15, 20]"
- :page-size="10"
- layout="total, sizes, prev, pager, next, jumper"
- :total="total">
- </el-pagination>
- </div>
-
- <el-dialog title="角色信息" :visible.sync="dialogFormVisible" width="30%">
- <el-form :model="form">
- <el-form-item label="角色名称" :label-width="formLabelWidth">
- <el-input v-model="form.name" autocomplete="off"></el-input>
- </el-form-item>
- <el-form-item label="角色编码" :label-width="formLabelWidth">
- <el-input v-model="form.code" autocomplete="off"></el-input>
- </el-form-item>
- <el-form-item label="描述" :label-width="formLabelWidth">
- <el-input v-model="form.description" autocomplete="off"></el-input>
- </el-form-item>
- </el-form>
- <div slot="footer" class="dialog-footer">
- <el-button @click="dialogFormVisible = false">取 消</el-button>
- <el-button type="primary" @click="handleAdd">确 定</el-button>
- </div>
- </el-dialog>
-
- </div>
- </template>
-
- <script>
- export default {
- name: "Role",
- data(){
- return{
- pageSize:2,
- pageNum:1,
- name:'',
- tableData:[],
- dialogFormVisible:false,
- form:{},
- formLabelWidth: '80px',
- multipleSelection:[],
- total:0
- }
- },
- created() {
- this.load();
- },
- methods:{
- load(){
- this.request.get("/role/findPage",{
- params:{
- pageNum:this.pageNum,
- pageSize:this.pageSize,
- name:this.name,
- }
- }).then(res => {
- this.tableData = res.data.records;
- this.total = res.data.total;
- })
- },
- getRowClass({rowIndex,columnIndex}){
- if(rowIndex === 0){
- return 'background:#ccc'
- }
- },
- reset(){
- this.email = '';
- this.username = '';
- this.load();
- },
- save(){
- this.dialogFormVisible = true;
- this.form = {};
- },
- handleAdd(){
- this.request.post("/role/save",this.form).then(res => {
- if(res.code === '200'){
- if(this.form.id){
- this.$message.success('编辑成功');
- }else{
- this.$message.success('新增成功');
- }
- this.dialogFormVisible = false;
- this.load();
- }else{
- this.$message.error('操作失败,请联系管理员')
- }
- })
- },
- handleEdit(row){
- this.form = JSON.parse(JSON.stringify(row));
- this.dialogFormVisible = true;
- },
- handleDelete(id){
- if(id){
- this.request.delete('/role/deleteById/' + id).then(res => {
- if(res.code === '200'){
- this.$message.success('删除数据成功');
- this.handleCalPageNum();
- }else{
- this.$message.error('删除数据失败,请联系管理员')
- }
- })
- }else{
- this.$message.error('没有id信息,无法删除');
- }
- },
- cancel(){
- this.$message.success('取消操作成功');
- },
- handleSelectionChange(val) {
- this.multipleSelection = val;
- },
- deleteBatch(){
- //批量删除数据
- if(this.multipleSelection.length === 0){
- this.$message.warning("请先选择要删除的数据");
- return
- }
- const ids = this.multipleSelection.map(v => v.id);
- this.request.post('/role/deleteBatch',ids).then(res => {
- if(res.code === '200'){
- this.$message.success('批量删除成功');
- this.handleCalPageNum();
- }else{
- this.$message.error('批量删除失败');
- }
- })
- },
- handleSizeChange(val) {
- this.pageSize = val;
- this.load();
- },
- handleCurrentChange(val) {
- this.pageNum = val;
- this.load();
- },
- handleCalPageNum(){
- this.request.get("/role/findPage",{
- params:{
- pageNum:this.pageNum,
- pageSize:this.pageSize,
- username:this.username,
- email:this.email
- }
- }).then(res => {
- this.total = res.data.total;
- this.pageNum = (this.total % this.pageSize === 0) ? (this.total / this.pageSize) : Math.floor((this.total / this.pageSize) + 1);
- if(this.pageNum < 1){
- this.pageNum = 1;
- }
- this.load();
- })
- }
- }
- }
- </script>
-
- <style scoped>
-
- </style><template>
- <div>
- <div>
- <!-- 搜索栏-->
- <el-input style="width: 200px;margin-right: 20px" placeholder="请输入名称" v-model="name" prefix-icon="el-icon-user"></el-input>
- <el-button style="margin-left: 10px;" type="primary" @click="load" class="el-icon-search">搜索</el-button>
- <el-button style="margin-left: 10px;" type="warning" @click="reset" class="el-icon-refresh">重置</el-button>
- </div>
-
- <div style="margin-top:20px;margin-bottom: 20px;">
- <!-- 新增。批量删除-->
- <el-button style="margin-right: 10px;" type="success" class="el-icon-plus" @click="save">新增</el-button>
- <el-popconfirm
- confirm-button-text='确定'
- cancel-button-text='取消'
- icon="el-icon-info"
- icon-color="red"
- title="确定删除这些数据吗?"
- @confirm="deleteBatch"
- @cancel="cancel">
- <el-button slot="reference" type="danger" style="margin-left:5px;" class="el-icon-delete">批量删除</el-button>
- </el-popconfirm>
- </div>
-
- <el-table :data="tableData" border stripe :header-cell-style="getRowClass" @selection-change="handleSelectionChange">
- <el-table-column type="selection" width="55"></el-table-column>
- <el-table-column prop="id" label="id"></el-table-column>
- <el-table-column prop="name" label="角色名称"></el-table-column>
- <el-table-column prop="code" label="角色编码"></el-table-column>
- <el-table-column prop="description" label="描述"></el-table-column>
- <el-table-column label="操作" width="200">
- <template slot-scope="scope">
- <el-button type="primary" class="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
- <el-popconfirm
- confirm-button-text='确定'
- cancel-button-text='取消'
- icon="el-icon-info"
- icon-color="red"
- title="确定删除这些数据吗?"
- @confirm="handleDelete(scope.row.id)"
- @cancel="cancel">
- <el-button slot="reference" type="danger" style="margin-left:5px;" class="el-icon-delete">删除</el-button>
- </el-popconfirm>
- </template>
- </el-table-column>
- </el-table>
-
- <div class="block" style="padding:10px 0;align-content: center;margin-left: 30%;margin-top:30px;">
- <el-pagination
- @size-change="handleSizeChange"
- @current-change="handleCurrentChange"
- :current-page="pageNum"
- :page-sizes="[5, 10, 15, 20]"
- :page-size="10"
- layout="total, sizes, prev, pager, next, jumper"
- :total="total">
- </el-pagination>
- </div>
-
- <el-dialog title="角色信息" :visible.sync="dialogFormVisible" width="30%">
- <el-form :model="form">
- <el-form-item label="角色名称" :label-width="formLabelWidth">
- <el-input v-model="form.name" autocomplete="off"></el-input>
- </el-form-item>
- <el-form-item label="角色编码" :label-width="formLabelWidth">
- <el-input v-model="form.code" autocomplete="off"></el-input>
- </el-form-item>
- <el-form-item label="描述" :label-width="formLabelWidth">
- <el-input v-model="form.description" autocomplete="off"></el-input>
- </el-form-item>
- </el-form>
- <div slot="footer" class="dialog-footer">
- <el-button @click="dialogFormVisible = false">取 消</el-button>
- <el-button type="primary" @click="handleAdd">确 定</el-button>
- </div>
- </el-dialog>
-
- </div>
- </template>
-
- <script>
- export default {
- name: "Role",
- data(){
- return{
- pageSize:2,
- pageNum:1,
- name:'',
- tableData:[],
- dialogFormVisible:false,
- form:{},
- formLabelWidth: '80px',
- multipleSelection:[],
- total:0
- }
- },
- created() {
- this.load();
- },
- methods:{
- load(){
- this.request.get("/role/findPage",{
- params:{
- pageNum:this.pageNum,
- pageSize:this.pageSize,
- name:this.name,
- }
- }).then(res => {
- this.tableData = res.data.records;
- this.total = res.data.total;
- })
- },
- getRowClass({rowIndex,columnIndex}){
- if(rowIndex === 0){
- return 'background:#ccc'
- }
- },
- reset(){
- this.email = '';
- this.username = '';
- this.load();
- },
- save(){
- this.dialogFormVisible = true;
- this.form = {};
- },
- handleAdd(){
- this.request.post("/role/save",this.form).then(res => {
- if(res.code === '200'){
- if(this.form.id){
- this.$message.success('编辑成功');
- }else{
- this.$message.success('新增成功');
- }
- this.dialogFormVisible = false;
- this.load();
- }else{
- this.$message.error('操作失败,请联系管理员')
- }
- })
- },
- handleEdit(row){
- this.form = JSON.parse(JSON.stringify(row));
- this.dialogFormVisible = true;
- },
- handleDelete(id){
- if(id){
- this.request.delete('/role/deleteById/' + id).then(res => {
- if(res.code === '200'){
- this.$message.success('删除数据成功');
- this.handleCalPageNum();
- }else{
- this.$message.error('删除数据失败,请联系管理员')
- }
- })
- }else{
- this.$message.error('没有id信息,无法删除');
- }
- },
- cancel(){
- this.$message.success('取消操作成功');
- },
- handleSelectionChange(val) {
- this.multipleSelection = val;
- },
- deleteBatch(){
- //批量删除数据
- if(this.multipleSelection.length === 0){
- this.$message.warning("请先选择要删除的数据");
- return
- }
- const ids = this.multipleSelection.map(v => v.id);
- this.request.post('/role/deleteBatch',ids).then(res => {
- if(res.code === '200'){
- this.$message.success('批量删除成功');
- this.handleCalPageNum();
- }else{
- this.$message.error('批量删除失败');
- }
- })
- },
- handleSizeChange(val) {
- this.pageSize = val;
- this.load();
- },
- handleCurrentChange(val) {
- this.pageNum = val;
- this.load();
- },
- handleCalPageNum(){
- this.request.get("/role/findPage",{
- params:{
- pageNum:this.pageNum,
- pageSize:this.pageSize,
- username:this.username,
- email:this.email
- }
- }).then(res => {
- this.total = res.data.total;
- this.pageNum = (this.total % this.pageSize === 0) ? (this.total / this.pageSize) : Math.floor((this.total / this.pageSize) + 1);
- if(this.pageNum < 1){
- this.pageNum = 1;
- }
- this.load();
- })
- }
- }
- }
- </script>
-
- <style scoped>
-
- </style>

完善用户页面的角色部分
集成git,把代码提交git仓库
- <template>
- <span class="s-canvas">
- <canvas id="s-canvas" :width="contentWidth" :height="contentHeight"></canvas>
- </span>
- </template>
- <script>
- export default {
- name: 'SIdentify',
- props: {
- identifyCode: {
- type: String,
- default: '1234'
- },
- fontSizeMin: {
- type: Number,
- default: 16
- },
- fontSizeMax: {
- type: Number,
- default: 40
- },
- backgroundColorMin: {
- type: Number,
- default: 180
- },
- backgroundColorMax: {
- type: Number,
- default: 240
- },
- colorMin: {
- type: Number,
- default: 50
- },
- colorMax: {
- type: Number,
- default: 160
- },
- lineColorMin: {
- type: Number,
- default: 40
- },
- lineColorMax: {
- type: Number,
- default: 180
- },
- dotColorMin: {
- type: Number,
- default: 0
- },
- dotColorMax: {
- type: Number,
- default: 255
- },
- contentWidth: {
- type: Number,
- default: 112
- },
- contentHeight: {
- type: Number,
- default: 38
- }
- },
- methods: {
- // 生成一个随机数
- randomNum(min, max) {
- return Math.floor(Math.random() * (max - min) + min)
- },
- // 生成一个随机的颜色
- randomColor(min, max) {
- let r = this.randomNum(min, max)
- let g = this.randomNum(min, max)
- let b = this.randomNum(min, max)
- return 'rgb(' + r + ',' + g + ',' + b + ')'
- },
- drawPic() {
- let canvas = document.getElementById('s-canvas')
- let ctx = canvas.getContext('2d')
- ctx.textBaseline = 'bottom'
- // 绘制背景
- ctx.fillStyle = this.randomColor(this.backgroundColorMin, this.backgroundColorMax)
- ctx.fillRect(0, 0, this.contentWidth, this.contentHeight)
- // 绘制文字
- for (let i = 0; i < this.identifyCode.length; i++) {
- this.drawText(ctx, this.identifyCode[i], i)
- }
- this.drawLine(ctx)
- this.drawDot(ctx)
- },
- drawText(ctx, txt, i) {
- ctx.fillStyle = this.randomColor(this.colorMin, this.colorMax)
- ctx.font = this.randomNum(this.fontSizeMin, this.fontSizeMax) + 'px SimHei'
- let x = (i + 1) * (this.contentWidth / (this.identifyCode.length + 1))
- let y = this.randomNum(this.fontSizeMax, this.contentHeight - 5)
- var deg = this.randomNum(-45, 45)
- // 修改坐标原点和旋转角度
- ctx.translate(x, y)
- ctx.rotate(deg * Math.PI / 180)
- ctx.fillText(txt, 0, 0)
- // 恢复坐标原点和旋转角度
- ctx.rotate(-deg * Math.PI / 180)
- ctx.translate(-x, -y)
- },
- drawLine(ctx) {
- // 绘制干扰线
- for (let i = 0; i < 3; i++) {
- ctx.strokeStyle = this.randomColor(this.lineColorMin, this.lineColorMax)
- ctx.beginPath()
- ctx.moveTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight))
- ctx.lineTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight))
- ctx.stroke()
- }
- },
- drawDot(ctx) {
- // 绘制干扰点
- for (let i = 0; i < 10; i++) {
- ctx.fillStyle = this.randomColor(0, 255)
- ctx.beginPath()
- ctx.arc(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight), 1, 0, 2 * Math.PI)
- ctx.fill()
- }
- }
- },
- watch: {
- identifyCode() {
- this.drawPic()
- }
- },
- mounted() { //mounted:在模板渲染成html后调用,通常初始化页面完成后,再对html的dom节点进行一些需要的操作。
- this.drawPic()
- }
- }
- </script>
- <style>
- .s-canvas {
- height: 38px;
- }
-
- canvas {
- margin-top: 1px;
- margin-left: 8px;
- }
- </style>

- <template>
- <div class="wrapper">
-
- <!-- 项目名称-->
- <div style="height: 60px;line-height: 60px;font-size: 20px;padding-left: 50px;color: white;text-align: center;align-content: center;background-color: #cccccc">
- 权限脚手架项目
- </div>
-
- <div style="display: flex;width:55%;height:40%;margin: 150px auto;background-color: white;border-radius: 10px;overflow: hidden;background-color: bisque">
- <!-- 左侧图片显示-->
- <div style="width: 50%;margin-top: 30px;margin-left: 30px">
- <img src="../assets/register.png" alt="" style="width:100%;height:90%" />
- </div>
- <!-- 提交表单-->
- <div style="width:350px;margin-top: 30px">
- <div style="margin: 20px 0;text-align: center;font-size: 20px">登录页面</div>
- <el-form :model="userForm" :rules="rules" ref="userForm" label-width="100px" class="demo-ruleForm">
- <el-form-item label="用户名" prop="username">
- <el-input v-model="userForm.username" prefix-icon="el-icon-user"></el-input>
- </el-form-item>
- <el-form-item label="密码" prop="password">
- <el-input v-model="userForm.password" prefix-icon="el-icon-lock" type="password" show-password></el-input>
- </el-form-item>
- <el-form-item label="验证码" prop="identifyCode">
- <el-input v-model="userForm.identifyCode" style="width:50%;position:relative;bottom:8px;" prefix-icon="el-icon-mobile-phone"></el-input>
- <span @click="refreshCode" style="position: relative;top:5px;left:6px;">
- <SIdentity :identify-code="identifyCode"></SIdentity>
- </span>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="login('userForm')">登录</el-button>
- <el-button type="success" @click="$router.push('/register')">前往注册</el-button>
- <!-- <el-button type="warning" @click="resetForm('userForm')">重置</el-button>-->
- </el-form-item>
- </el-form>
- </div>
- </div>
- </div>
- </template>
-
- <script>
- import SIdentity from "@/components/SIdentity";
- export default {
- name: "Login",
- components:{
- SIdentity
- },
- mounted() {
- this.refreshCode()
- },
- data(){
- return{
- userForm:{},
- identifyCode:'',
- identifyCodes:'1234567890zxcvbnmasdfghjklqwertyuiopQWERTYUIPOLSADFGHJ',
- rules: {
- username: [
- { required: true, message: '请输入用户名称', trigger: 'blur' },
- { min: 1, max: 20, message: '长度在 1 到 20 个字符', trigger: 'blur' }
- ],
- password: [
- { required: true, message: '请输入密码', trigger: 'blur' },
- { min: 6, max: 50, message: '长度不能小于6位', trigger: 'blur' }
- ],
- identifyCode: [
- { required: true, message: '请输入验证码', trigger: 'blur' },
- { min: 4, max: 4, message: '验证码长度为4位', trigger: 'blur' }
- ]
- }
- }
- },
- methods:{
- //提交数据
- login(formName) {
- this.$refs[formName].validate((valid) => {
- if (valid) {
- if(this.identifyCode.toUpperCase() !== this.userForm.identifyCode.toUpperCase()){
- this.$message.warning("验证码输入错误,请重新输入");
- return;
- }
- alert('submit!');
- } else {
- console.log('error submit!!');
- return false;
- }
- });
- },
- // 重置数据
- resetForm(formName) {
- this.$refs[formName].resetFields();
- },
-
- // 验证码方法
- refreshCode(){
- this.identifyCode = ''
- this.makeCode(this.identifyCodes,4);
- },
- makeCode(o,l){
- for(let i = 0;i < l;i++){
- this.identifyCode += this.identifyCodes[
- this.randomNum(0,this.identifyCodes.length)
- ]
- }
- },
- randomNum(min,max){
- return Math.floor(Math.random() * (max - min) + min);
- }
- }
- }
- </script>
-
- <style scoped>
- .wrapper{
- background: url("../assets/auth.jpg");
- width:100%;
- height: 100%;
- position: fixed;
- background-size: 100% 100%;
- }
- </style>

- <template>
- <div class="wrapper">
-
- <!-- 项目名称-->
- <div style="height: 60px;line-height: 60px;font-size: 20px;padding-left: 50px;color: white;text-align: center;align-content: center;background-color: #cccccc">
- 权限脚手架项目
- </div>
-
- <div style="display: flex;width:55%;height:40%;margin: 150px auto;background-color: white;border-radius: 10px;overflow: hidden;background-color: bisque">
- <!-- 左侧图片显示-->
- <div style="width: 50%;margin-top: 30px;margin-left: 30px">
- <img src="../assets/register.png" alt="" style="width:100%;height:90%" />
- </div>
- <!-- 提交表单-->
- <div style="width:350px;margin-top: 30px">
- <div style="margin: 20px 0;text-align: center;font-size: 20px">注册页面</div>
- <el-form :model="userForm" :rules="rules" ref="userForm" label-width="100px" class="demo-ruleForm">
- <el-form-item label="用户名" prop="username">
- <el-input v-model="userForm.username" prefix-icon="el-icon-user"></el-input>
- </el-form-item>
- <el-form-item label="密码" prop="password">
- <el-input v-model="userForm.password" prefix-icon="el-icon-lock" type="password" show-password></el-input>
- </el-form-item>
- <el-form-item label="确认密码" prop="confirmPassword">
- <el-input v-model="userForm.confirmPassword" prefix-icon="el-icon-lock" type="password" show-password></el-input>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="register('userForm')">注册</el-button>
- <el-button type="success" @click="$router.push('/login')">返回登录</el-button>
- <!-- <el-button type="warning" @click="resetForm('userForm')">重置</el-button>-->
- </el-form-item>
- </el-form>
- </div>
- </div>
- </div>
- </template>
-
- <script>
- export default {
- name: "Register",
- data(){
- return{
- userForm:{},
-
- rules: {
- username: [
- { required: true, message: '请输入用户名称', trigger: 'blur' },
- { min: 1, max: 20, message: '长度在 1 到 20 个字符', trigger: 'blur' }
- ],
- password: [
- { required: true, message: '请输入密码', trigger: 'blur' },
- { min: 6, max: 50, message: '长度不能小于6位', trigger: 'blur' }
- ],
- confirmPassword: [
- { required: true, message: '请输入确认密码', trigger: 'blur' },
- { min: 6, max: 50, message: '长度不能小于6位', trigger: 'blur' }
- ]
- }
- }
- },
- methods:{
- //提交数据
- register(formName) {
- this.$refs[formName].validate((valid) => {
- if (valid) {
- if(this.userForm.password !== this.userForm.confirmPassword){
- this.$message.warning("两次密码不一致,请重新输入");
- return;
- }
- alert('submit!');
- } else {
- console.log('error submit!!');
- return false;
- }
- });
- },
- // 重置数据
- resetForm(formName) {
- this.$refs[formName].resetFields();
- },
-
- // 验证码方法
- refreshCode(){
- this.identifyCode = ''
- this.makeCode(this.identifyCodes,4);
- },
- makeCode(o,l){
- for(let i = 0;i < l;i++){
- this.identifyCode += this.identifyCodes[
- this.randomNum(0,this.identifyCodes.length)
- ]
- }
- },
- randomNum(min,max){
- return Math.floor(Math.random() * (max - min) + min);
- }
- }
- }
- </script>
-
- <style scoped>
- .wrapper{
- background: url("../assets/auth.jpg");
- width:100%;
- height: 100%;
- position: fixed;
- background-size: 100% 100%;
- }
- </style>

创建UserDto类
- package com.example.authority.dto;
-
- import lombok.Data;
-
- import java.util.Date;
-
- @Data
- public class UserDto {
-
- private Integer id;
-
- private String username;
-
- private String password;
-
- private String nickname;
-
- private String headerUrl;
-
- private String token;
- }

编写两个接口代码,看视频
JwtUtils的代码
- package com.example.authority.utils;
-
- import com.auth0.jwt.JWT;
- import com.auth0.jwt.algorithms.Algorithm;
- import org.springframework.stereotype.Component;
-
- import java.util.Date;
-
- @Component
- public class JwtUtils {
-
- public static String generateToken(String userId,String sign){
- return JWT.create().withAudience(userId)
- .withExpiresAt(new Date(System.currentTimeMillis() + 3600 * 24 * 1000))
- .sign(Algorithm.HMAC256(sign));
- }
- }

用户管理的bug:
新增和修改用户名的时候加一下判断条件:用户名不能重复
角色管理的bug:
删除角色的时候先判断角色是否被使用了
RBAC:RBAC(Role-Based Access control) ,也就是基于角色的权限分配解决方案,相对于传统方案,RBAC提供了中间层Role(角色),其权限模式是给用户分配角色,给角色分配权限
菜单表:
字典管理,如视频操作
菜单后台代码,如视频操作
菜单页面:
- <template>
- <div>
- <div>
- <!-- 搜索栏-->
- <el-input style="width: 200px;margin-right: 20px" placeholder="请输入菜单名称" v-model="name" prefix-icon="el-icon-user"></el-input>
- <el-button style="margin-left: 10px;" type="primary" @click="load" class="el-icon-search">搜索</el-button>
- <el-button style="margin-left: 10px;" type="warning" @click="reset" class="el-icon-refresh">重置</el-button>
- </div>
-
- <div style="margin-top:20px;margin-bottom: 20px;">
- <!-- 新增。批量删除-->
- <el-button style="margin-right: 10px;" type="success" class="el-icon-plus" @click="save(null)">新增</el-button>
- <el-popconfirm
- confirm-button-text='确定'
- cancel-button-text='取消'
- icon="el-icon-info"
- icon-color="red"
- title="确定删除这些数据吗?"
- @confirm="deleteBatch"
- @cancel="cancel">
- <el-button slot="reference" type="danger" style="margin-left:5px;" class="el-icon-delete">批量删除</el-button>
- </el-popconfirm>
- </div>
-
- <el-table
- :data="tableData" border stripe
- :header-cell-style="getRowClass"
- @selection-change="handleSelectionChange"
- row-key="id"
- border
- default-expand-all>
- <el-table-column type="selection" width="55"></el-table-column>
- <el-table-column prop="id" label="id"></el-table-column>
- <el-table-column prop="name" label="菜单名称"></el-table-column>
- <el-table-column prop="path" label="菜单路径"></el-table-column>
- <el-table-column label="菜单图标">
- <template slot-scope="scope">
- <i :class="scope.row.icon"/>
- </template>
- </el-table-column>
- <el-table-column prop="description" label="菜单描述"></el-table-column>
- <el-table-column prop="pagePath" label="页面路径"></el-table-column>
- <el-table-column prop="sortNum" label="排序"></el-table-column>
- <el-table-column label="操作" width="350">
- <template slot-scope="scope">
- <el-button type="success" class="el-icon-circle-plus" @click="save(scope.row.id)" v-if="!scope.row.pid && !scope.row.path ">新增子菜单</el-button>
- <el-button type="primary" class="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
- <el-popconfirm
- confirm-button-text='确定'
- cancel-button-text='取消'
- icon="el-icon-info"
- icon-color="red"
- title="确定删除这些数据吗?"
- @confirm="handleDelete(scope.row.id)"
- @cancel="cancel">
- <el-button slot="reference" type="danger" style="margin-left:5px;" class="el-icon-delete">删除</el-button>
- </el-popconfirm>
- </template>
- </el-table-column>
- </el-table>
-
-
- <el-dialog title="菜单信息" :visible.sync="dialogFormVisible" width="30%">
- <el-form :model="form">
- <el-form-item label="菜单名称" :label-width="formLabelWidth">
- <el-input v-model="form.name" autocomplete="off"></el-input>
- </el-form-item>
- <el-form-item label="菜单路径" :label-width="formLabelWidth">
- <el-input v-model="form.path" autocomplete="off"></el-input>
- </el-form-item>
- <el-form-item label="菜单图标" :label-width="formLabelWidth">
- <el-select v-model="form.icon" filterable placeholder="请选择图标">
- <el-option
- v-for="dict in dictList"
- :key="dict.name"
- :label="dict.name"
- :value="dict.value">
- <i :class="dict.value"></i> {{dict.name}}
- </el-option>
- </el-select>
- </el-form-item>
- <el-form-item label="页面路径" :label-width="formLabelWidth">
- <el-input v-model="form.pagePath" autocomplete="off"></el-input>
- </el-form-item>
- <el-form-item label="排序" :label-width="formLabelWidth">
- <el-input v-model="form.sortNum" autocomplete="off" type="number"></el-input>
- </el-form-item>
- <el-form-item label="描述" :label-width="formLabelWidth">
- <el-input v-model="form.description" autocomplete="off"></el-input>
- </el-form-item>
- </el-form>
- <div slot="footer" class="dialog-footer">
- <el-button @click="dialogFormVisible = false">取 消</el-button>
- <el-button type="primary" @click="handleAdd">确 定</el-button>
- </div>
- </el-dialog>
-
- </div>
- </template>
-
- <script>
- export default {
- name: "Role",
- data(){
- return{
- pageSize:10,
- pageNum:1,
- name:'',
- tableData:[],
- dialogFormVisible:false,
- form:{},
- formLabelWidth: '80px',
- multipleSelection:[],
- total:0,
- dictList:[]
- }
- },
- created() {
- this.load();
- },
- methods:{
- load(){
- this.request.get("/menu/findAll",{
- params:{
- name:this.name,
- }
- }).then(res => {
- this.tableData = res.data;
- });
- this.request.get("/dict/findAll",{
- params:{
- type:'icon',
- }
- }).then(res => {
- this.dictList = res.data;
- })
- },
- getRowClass({rowIndex,columnIndex}){
- if(rowIndex === 0){
- return 'background:#ccc'
- }
- },
- reset(){
- this.name = '';
- this.load();
- },
- save(pid){
- this.dialogFormVisible = true;
- this.form = {};
- if(pid){
- this.form.pid = pid;
- }
- },
- handleAdd(){
- this.request.post("/menu/save",this.form).then(res => {
- if(res.code === '200'){
- if(this.form.id){
- this.$message.success('编辑成功');
- }else{
- this.$message.success('新增成功');
- }
- this.dialogFormVisible = false;
- this.load();
- }else{
- this.$message.error(res.msg)
- }
- })
- },
- handleEdit(row){
- this.form = JSON.parse(JSON.stringify(row));
- this.dialogFormVisible = true;
- },
- handleDelete(id){
- if(id){
- this.request.delete('/menu/deleteById/' + id).then(res => {
- if(res.code === '200'){
- this.$message.success('删除数据成功');
- this.load();
- }else{
- this.$message.error(res.msg)
- }
- })
- }else{
- this.$message.error('没有id信息,无法删除');
- }
- },
- cancel(){
- this.$message.success('取消操作成功');
- },
- handleSelectionChange(val) {
- this.multipleSelection = val;
- },
- deleteBatch(){
- //批量删除数据
- if(this.multipleSelection.length === 0){
- this.$message.warning("请先选择要删除的数据");
- return
- }
- const ids = this.multipleSelection.map(v => v.id);
- this.request.post('/menu/deleteBatch',ids).then(res => {
- if(res.code === '200'){
- this.$message.success('批量删除成功');
- this.load();
- }else{
- this.$message.error(res.msg);
- }
- })
- },
- handleSizeChange(val) {
- this.pageSize = val;
- this.load();
- },
- handleCurrentChange(val) {
- this.pageNum = val;
- this.load();
- }
- }
- }
- </script>
-
- <style scoped>
-
- </style>

添加swagger配置类
- package com.example.authority.config;
-
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import springfox.documentation.builders.ApiInfoBuilder;
- import springfox.documentation.builders.PathSelectors;
- import springfox.documentation.builders.RequestHandlerSelectors;
- import springfox.documentation.service.ApiInfo;
- import springfox.documentation.service.Contact;
- import springfox.documentation.spi.DocumentationType;
- import springfox.documentation.spring.web.plugins.Docket;
- import springfox.documentation.swagger2.annotations.EnableSwagger2;
-
- @Configuration
- public class SwaggerConfig {
- @Bean
- public Docket createRestApi() {
- return new Docket(DocumentationType.SWAGGER_2)
- .pathMapping("/")
- .select()
- .apis(RequestHandlerSelectors.basePackage("com.example.authority.controller")) //controller类所在的路径
- .paths(PathSelectors.any())
- .build().apiInfo(new ApiInfoBuilder()
- .title("SpringBoot整合Swagger")
- .description("SpringBoot整合Swagger,详细信息......")
- .version("9.0")
- .contact(new Contact("111","blog.csdn.net","www@gmail.com"))
- .license("hello")
- .licenseUrl("http://www.baidu.com")
- .build());
- }
- }

启动类添加 :@EnableSwagger2
- package com.example.authority;
-
- import org.mybatis.spring.annotation.MapperScan;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import springfox.documentation.swagger2.annotations.EnableSwagger2;
-
- @SpringBootApplication
- @MapperScan("com.example.authority.mapper")
- @EnableSwagger2
- public class BaseAuthorityApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(BaseAuthorityApplication.class, args);
- }
-
- }

常用注解
@Api(tags = "用户管理"):加在controller类上做说明@ApiOperation(value = "新增/修改用户信息"):加在接口方法上
全局异常处理
处理类
- package com.example.authority.exception;
-
-
- import com.example.authority.common.Result;
- import org.springframework.web.bind.annotation.ControllerAdvice;
- import org.springframework.web.bind.annotation.ExceptionHandler;
- import org.springframework.web.bind.annotation.ResponseBody;
-
- /**
- * @author wangjy
- * @version 1.0
- * @date 2023/6/30 15:29
- * 全局异常处理器
- */
- @ControllerAdvice
- public class GlobalExceptionHandler {
-
- @ExceptionHandler(AuthException.class)
- @ResponseBody
- public Result handle(AuthException ex){
- return Result.error(ex.getCode(),ex.getMessage());
- }
-
-
- @ExceptionHandler(Exception.class)
- @ResponseBody
- public Result handle(Exception ex){
- return Result.error("500",ex.getMessage());
- }
-
- }

自定义异常
- package com.example.authority.exception;
-
- import lombok.Data;
-
- @Data
- public class AuthException extends RuntimeException{
-
- private String code;
-
- public AuthException( String code,String msg) {
- super(msg);
- this.code = code;
- }
- }
在角色页面增加分配菜单功能,如视频操作
动态菜单:根据角色分配的菜单在Aside页面动态显示
动态路由:动态路由,动态即不是写死的,是可变的。我们可以根据自己不同的需求加载不同的路由,做到不同的实现及页面的渲染。动态的路由存储可分为两种,一种是将路由存储到前端。另一种则是将路由存储到数据库。动态路由的使用一般结合角色权限控制一起使用。
具体代码看视频
解决路由显示问题
- import Vue, {set} from 'vue'
- import VueRouter from 'vue-router'
- import store from '../store'
- Vue.use(VueRouter)
- import { Notification, MessageBox, Message, Loading } from 'element-ui'
- import ElementUI from 'element-ui'
-
- const routes = [
- {
- path: '/login',
- name: 'Login',
- component: () => import(/* webpackChunkName: "about" */ '../views/Login.vue'),
- },
- {
- path: '/register',
- name: 'Register',
- component: () => import(/* webpackChunkName: "about" */ '../views/Register.vue'),
- },
- {
- path: '/404',
- name: '404',
- component: () => import(/* webpackChunkName: "about" */ '../views/404.vue'),
- }
- ]
-
- const router = new VueRouter({
- mode: 'history',
- base: process.env.BASE_URL,
- routes
- })
-
- export const setRoutes = () => {
- //获取浏览器缓存的菜单数据
- const localMenus = localStorage.getItem("menus") ;
- if(localMenus){
- const currentRoutes = router.getRoutes().map(v => v.name);
- if(!currentRoutes.includes('manage')){
- //当前Router不包含manage,在拼装
- const manageRoute = {
- path: '/',
- name: 'manage',
- component: () => import(/* webpackChunkName: "about" */ '../views/Manage.vue'),
- children:[]
- };
- const menus = JSON.parse(localMenus);
- menus.forEach(item => {
- if(item.path){
- const itemMenu = {
- path:item.path.replace("/",""),
- name:item.name,
- component: () => import(/* webpackChunkName: "about" */ '../views/' + item.pagePath + '.vue'),
- };
- manageRoute.children.push(itemMenu);
- }else if(item.children.length){
- item.children.forEach(item => {
- const itemMenu = {
- path:item.path.replace("/",""),
- name:item.name,
- component: () => import(/* webpackChunkName: "about" */ '../views/' + item.pagePath + '.vue'),
- };
- manageRoute.children.push(itemMenu);
- })
- }
- })
- router.addRoute(manageRoute);
- console.log(router.getRoutes())
- }
- }
- }
-
- setRoutes()
-
- router.beforeEach((to,from,next) => {
- localStorage.setItem('currentPathName',to.name);
- store.commit('setPath')
- const localMenus = localStorage.getItem("menus");
- if(!to.matched.length){
- //没有匹配到路由(也就是未找到路由)
- if(localMenus){
- //用户登录了
- next('/404')
- }else{
- ElementUI.Message({
- message: '请先登录',
- type: 'warning'
- });
- next('/login')
- }
- }
- next();
- })
- export default router

后端拦截器代码:看视频,详细教导。
添加依赖:
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-devtools</artifactId>
- <optional>true</optional>
- </dependency>
-
- <!-- AOP-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-aop</artifactId>
- </dependency>
获取request对象
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
剩下的代码看视频
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- registry.addInterceptor(jwtInterceptor).addPathPatterns("/**").excludePathPatterns(
- "/swagger-resources/**"
- ,"/webjars/**"
- ,"/v2/**"
- ,"/swagger-ui.html/**"
- );
- }
看视频操作
看视频操作
- <template>
- <div>
- <div>
- <!-- 搜索栏-->
- <el-input style="width: 200px;margin-right: 20px" placeholder="请输入操作用户名称" v-model="username" prefix-icon="el-icon-user"></el-input>
- <el-input style="width: 200px;margin-right: 20px" placeholder="请输入操作用户名称" v-model="type" prefix-icon="el-icon-info"></el-input>
- <el-button style="margin-left: 10px;" type="primary" @click="load" class="el-icon-search">搜索</el-button>
- <el-button style="margin-left: 10px;" type="warning" @click="reset" class="el-icon-refresh">重置</el-button>
- </div>
-
- <div style="margin-top:20px;margin-bottom: 20px;">
-
- </div>
-
- <el-table :data="tableData" border stripe :header-cell-style="getRowClass" @selection-change="handleSelectionChange">
- <el-table-column type="selection" width="55"></el-table-column>
- <el-table-column prop="id" label="id"></el-table-column>
- <el-table-column prop="username" label="操作用户"></el-table-column>
- <el-table-column prop="record" label="操作记录"></el-table-column>
- <el-table-column prop="type" label="操作类型"></el-table-column>
- <el-table-column prop="createTime" label="创建时间"></el-table-column>
- </el-table>
-
- <div class="block" style="padding:10px 0;align-content: center;margin-left: 30%;margin-top:30px;">
- <el-pagination
- @size-change="handleSizeChange"
- @current-change="handleCurrentChange"
- :current-page="pageNum"
- :page-sizes="[5, 10, 15, 20]"
- :page-size="10"
- layout="total, sizes, prev, pager, next, jumper"
- :total="total">
- </el-pagination>
- </div>
- </div>
- </template>
-
- <script>
- export default {
- name: "Role",
- data(){
- return{
- pageSize:10,
- pageNum:1,
- name:'',
- tableData:[],
- dialogFormVisible:false,
- menuVisible:false,
- form:{},
- formLabelWidth: '80px',
- multipleSelection:[],
- total:0,
- menuData:[],
- checks:[],
- props: {
- children: 'children',
- label: 'name'
- }
- }
- },
- created() {
- this.load();
- },
- methods:{
- load(){
- this.request.get("/sysLog/findPage",{
- params:{
- pageNum:this.pageNum,
- pageSize:this.pageSize,
- username:this.username,
- type:this.type
- }
- }).then(res => {
- this.tableData = res.data.records;
- this.total = res.data.total;
- })
- },
- getRowClass({rowIndex,columnIndex}){
- if(rowIndex === 0){
- return 'background:#ccc'
- }
- },
- reset(){
- this.username = '';
- this.type = '';
- this.load();
- },
- handleSelectionChange(val) {
- this.multipleSelection = val;
- },
- handleSizeChange(val) {
- this.pageSize = val;
- this.load();
- },
- handleCurrentChange(val) {
- this.pageNum = val;
- this.load();
- },
- }
- }
- </script>
-
- <style scoped>
-
- </style>

- package com.example.authority.controller;
-
- import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
- import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
- import com.baomidou.mybatisplus.core.toolkit.StringUtils;
- import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
- import com.example.authority.annotation.Log;
- import com.example.authority.annotation.NoAuth;
- import com.example.authority.common.Result;
- import com.example.authority.entity.SysFile;
- import com.example.authority.service.SysFileService;
- import org.apache.commons.codec.digest.DigestUtils;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.web.bind.annotation.*;
- import org.springframework.web.multipart.MultipartFile;
-
- import javax.servlet.ServletOutputStream;
- import javax.servlet.http.HttpServletResponse;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.IOException;
- import java.net.URLEncoder;
- import java.util.List;
- import java.util.UUID;
-
- @RestController
- @RequestMapping("/sysFile")
- public class SysFileController {
-
- @Autowired
- private SysFileService sysFileService;
-
- @Value("${files.upload.path}")
- private String fileUploadPath;
-
- /**
- * 批量删除文件
- * @param idList
- * @return
- */
- @PostMapping("/deleteBatch")
- @Log(record = "批量删除文件",type = "删除")
- public Result deleteBatch(@RequestBody List<Integer> idList){
- for (Integer id : idList) {
- SysFile sysFile = sysFileService.getById(id);
- sysFile.setIsDelete(1);
- sysFileService.updateById(sysFile);
- }
- return Result.success();
- }
- /**
- * 改变启用状态
- * @param sysFile
- * @return
- */
- @PostMapping("/updateEnable")
- @Log(record = "updateEnable",type = "修改")
- public Result updateEnable(@RequestBody SysFile sysFile){
- boolean b = sysFileService.updateById(sysFile);
- if(b){
- return Result.success();
- }else{
- return Result.error();
- }
- }
-
- /**
- * 根据id删除
- * @param id
- * @return
- */
- @DeleteMapping("/deleteById/{id}")
- @Log(record = "根据id删除文件",type = "删除")
- public Result deleteById(@PathVariable Integer id){
- SysFile sysFile = sysFileService.getById(id);
- sysFile.setIsDelete(1);
- boolean b = sysFileService.updateById(sysFile);
- if(b){
- return Result.success();
- }else{
- return Result.error();
- }
- }
-
- /**
- * 上传文件
- * @param file
- * @return
- */
- @PostMapping("/upload")
- @Log(record = "上传文件",type = "新增")
- @NoAuth
- public String upload(@RequestParam MultipartFile file) throws IOException {
- String md5 = DigestUtils.md5Hex(file.getBytes());
- String originalFilename = file.getOriginalFilename(); //文件的名称
- String type = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);//文件类型
- long size = file.getSize();
- File uploadParentFile = new File(fileUploadPath);
- if(!uploadParentFile.exists()){
- uploadParentFile.mkdirs();
- }
- List<SysFile> existFileList = sysFileService.getByMD5(md5);
- String url = null;
- if(CollectionUtils.isNotEmpty(existFileList)){
- //文件已经存在上传目录
- url = existFileList.get(0).getUrl();
- }else{
- //文件不存在上传目录
- String uuid = UUID.randomUUID().toString().replaceAll("-", "");
- String fileUUID = uuid + "." + type;
- File uploadFile = new File(fileUploadPath + fileUUID);
- url = "http://localhost:8888/sysFile/" + fileUUID;
- file.transferTo(uploadFile);
- }
-
- //存储数据库
- SysFile sysFile = new SysFile();
- sysFile.setName(originalFilename);
- sysFile.setSize(size / 1024);
- sysFile.setType(type);
- sysFile.setUrl(url);
- sysFile.setMd5(md5);
- sysFileService.save(sysFile);
- return url;
- }
-
- /**
- * 下载文件
- * @param fileUUID
- */
- @GetMapping("/{fileUUID}")
- @NoAuth
- public void download(@PathVariable String fileUUID, HttpServletResponse response){
- File downloadFile = new File(fileUploadPath + fileUUID);
- try {
- FileInputStream fileInputStream = new FileInputStream(downloadFile);
- // 设置输出流的格式
- response.setCharacterEncoding("UTF-8");
- response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileUUID, "UTF-8"));
- //作用是使客户端浏览器区分不同种类的数据,并根据不同的MIME调用浏览器内不同的程序嵌入模块来处理相应的数据。
- response.setContentType("application/octet-stream"); //.*( 二进制流,不知道下载文件类型)
- ServletOutputStream outputStream = response.getOutputStream();
- int len = 0;
- byte[] bytes = new byte[1024];
- while((len = fileInputStream.read(bytes)) != -1){
- outputStream.write(bytes,0,len);
- outputStream.flush();
- }
- outputStream.flush();
- outputStream.close();
- fileInputStream.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
-
- }
- /**
- * 查询全部数据
- * @return
- */
- @GetMapping("/findAll")
- @Log(record = "查询全部文件",type = "查询")
- public Result findAll(@RequestParam(name = "type",defaultValue = "") String type){
- QueryWrapper<SysFile> queryWrapper = new QueryWrapper<>();
- if(StringUtils.isNotBlank(type)){
- queryWrapper.eq("type",type);
- }
- return Result.success(sysFileService.list(queryWrapper));
- }
-
- /**
- * 分页查询
- * @param pageNum:页码
- * @param pageSize:每页条数
- * @param name:角色名称
- * @return
- */
- @GetMapping("/findPage")
- @Log(record = "查询文件分页",type = "查询")
- public Result findPage(@RequestParam Integer pageNum,
- @RequestParam Integer pageSize,
- @RequestParam(name = "name",defaultValue = "") String name){
- Page<SysFile> page = new Page<>(pageNum,pageSize);
- QueryWrapper<SysFile> queryWrapper = new QueryWrapper<>();
- queryWrapper.eq("is_delete",0);
- if(StringUtils.isNotBlank(name)){
- queryWrapper.like("name",name);
- }
- Page<SysFile> sysFilePage = sysFileService.page(page, queryWrapper);
- return Result.success(sysFilePage);
- }
- }

- <template>
- <div>
- <div>
- <!-- 搜索栏-->
- <el-input style="width: 200px;margin-right: 20px" placeholder="请输入名称" v-model="name" prefix-icon="el-icon-user"></el-input>
- <el-button style="margin-left: 10px;" type="primary" @click="load" class="el-icon-search">搜索</el-button>
- <el-button style="margin-left: 10px;" type="warning" @click="reset" class="el-icon-refresh">重置</el-button>
- </div>
-
- <div style="margin-top:20px;margin-bottom: 20px;">
- <!-- 新增。批量删除-->
- <el-upload
- style="display: inline-block"
- action="http://localhost:8888/sysFile/upload"
- :show-file-list="false"
- :on-success="handleAvatarSuccess"
- :on-error="handleAvatarError">
- <el-button style="margin-right: 10px;" type="primary" class="el-icon-plus">上传文件</el-button>
- </el-upload>
- <el-popconfirm
- confirm-button-text='确定'
- cancel-button-text='取消'
- icon="el-icon-info"
- icon-color="red"
- title="确定删除这些数据吗?"
- @confirm="deleteBatch"
- @cancel="cancel">
- <el-button slot="reference" type="danger" style="margin-left:5px;" class="el-icon-delete">批量删除</el-button>
- </el-popconfirm>
- </div>
-
- <el-table :data="tableData" border stripe :header-cell-style="getRowClass" @selection-change="handleSelectionChange">
- <el-table-column type="selection" width="55"></el-table-column>
- <el-table-column prop="id" label="id"></el-table-column>
- <el-table-column prop="name" label="文件名称"></el-table-column>
- <el-table-column prop="type" label="文件类型"></el-table-column>
- <el-table-column prop="size" label="文件大小(kb)"></el-table-column>
- <el-table-column prop="enable" label="是否启用">
- <template slot-scope="scope">
- <el-switch
- v-model="scope.row.enable"
- active-color="#13ce66"
- inactive-color="#ff4949"
- @change="changeEnable(scope.row)"
- :active-value="1"
- :inactive-value="0">
- </el-switch>
- </template>
- </el-table-column>
- <el-table-column label="操作" width="300">
- <template slot-scope="scope">
- <el-button type="success" class="el-icon-view" @click="viewImage(scope.row.url)">预览</el-button>
- <el-button type="primary" class="el-icon-download" @click="download(scope.row.url)" v-if="scope.row.enable === 1">下载</el-button>
- <el-popconfirm
- confirm-button-text='确定'
- cancel-button-text='取消'
- icon="el-icon-info"
- icon-color="red"
- title="确定删除这些数据吗?"
- @confirm="handleDelete(scope.row.id)"
- @cancel="cancel">
- <el-button slot="reference" type="danger" style="margin-left:5px;" class="el-icon-delete">删除</el-button>
- </el-popconfirm>
- </template>
- </el-table-column>
- </el-table>
-
- <div class="block" style="padding:10px 0;align-content: center;margin-left: 30%;margin-top:30px;">
- <el-pagination
- @size-change="handleSizeChange"
- @current-change="handleCurrentChange"
- :current-page="pageNum"
- :page-sizes="[5, 10, 15, 20]"
- :page-size="10"
- layout="total, sizes, prev, pager, next, jumper"
- :total="total">
- </el-pagination>
- </div>
-
- <el-dialog title="字典信息" :visible.sync="viewVisible" width="30%">
- <img width="100%" :src="dialogImageUrl" alt="图片"/>
- </el-dialog>
-
- </div>
- </template>
-
- <script>
- export default {
- name: "Role",
- data(){
- return{
- pageSize:10,
- pageNum:1,
- name:'',
- tableData:[],
- dialogImageUrl:'',
- dialogFormVisible:false,
- viewVisible:false,
- form:{},
- formLabelWidth: '80px',
- multipleSelection:[],
- total:0
- }
- },
- created() {
- this.load();
- },
- methods:{
- viewImage(url){
- this.dialogImageUrl = url;
- this.viewVisible = true;
- },
- changeEnable(row){
- this.request.post('/sysFile/updateEnable',row).then(res => {
- if(res.code === '200'){
- this.$message.success('更新启用状态成功')
- }else{
- this.$message.error('更新启用状态失败')
- }
- })
- },
- load(){
- this.request.get("/sysFile/findPage",{
- params:{
- pageNum:this.pageNum,
- pageSize:this.pageSize,
- name:this.name,
- }
- }).then(res => {
- this.tableData = res.data.records;
- this.total = res.data.total;
- })
- },
- getRowClass({rowIndex,columnIndex}){
- if(rowIndex === 0){
- return 'background:#ccc'
- }
- },
- reset(){
- this.name = '';
- this.load();
- },
- save(){
- this.dialogFormVisible = true;
- this.form = {};
- },
- handleAdd(){
- this.request.post("/sysFile/save",this.form).then(res => {
- if(res.code === '200'){
- if(this.form.id){
- this.$message.success('编辑成功');
- }else{
- this.$message.success('新增成功');
- }
- this.dialogFormVisible = false;
- this.load();
- }else{
- this.$message.error(res.msg)
- }
- })
- },
- handleEdit(row){
- this.form = JSON.parse(JSON.stringify(row));
- this.dialogFormVisible = true;
- },
- handleDelete(id){
- if(id){
- this.request.delete('/sysFile/deleteById/' + id).then(res => {
- if(res.code === '200'){
- this.$message.success('删除数据成功');
- this.handleCalPageNum();
- }else{
- this.$message.error(res.msg)
- }
- })
- }else{
- this.$message.error('没有id信息,无法删除');
- }
- },
- cancel(){
- this.$message.success('取消操作成功');
- },
- handleSelectionChange(val) {
- this.multipleSelection = val;
- },
- deleteBatch(){
- //批量删除数据
- if(this.multipleSelection.length === 0){
- this.$message.warning("请先选择要删除的数据");
- return
- }
- const ids = this.multipleSelection.map(v => v.id);
- this.request.post('/sysFile/deleteBatch',ids).then(res => {
- if(res.code === '200'){
- this.$message.success('批量删除成功');
- this.handleCalPageNum();
- }else{
- this.$message.error(res.msg);
- }
- })
- },
- handleSizeChange(val) {
- this.pageSize = val;
- this.load();
- },
- handleCurrentChange(val) {
- this.pageNum = val;
- this.load();
- },
- handleCalPageNum(){
- this.request.get("/sysFile/findPage",{
- params:{
- pageNum:this.pageNum,
- pageSize:this.pageSize,
- name:this.name,
- }
- }).then(res => {
- this.total = res.data.total;
- this.pageNum = (this.total % this.pageSize === 0) ? (this.total / this.pageSize) : Math.floor((this.total / this.pageSize) + 1);
- if(this.pageNum < 1){
- this.pageNum = 1;
- }
- this.load();
- })
- },
- handleAvatarSuccess(res, file) {
- this.$message.success('上传文件成功!');
- this.load();
-
- },
- handleAvatarError() {
- this.$message.error('上传文件失败!');
- },
- download(url){
- window.open(url);
- }
- }
- }
- </script>
-
- <style scoped>
- .avatar-uploader .el-upload {
- border: 1px dashed #d9d9d9;
- border-radius: 6px;
- cursor: pointer;
- position: relative;
- overflow: hidden;
- }
- .avatar-uploader .el-upload:hover {
- border-color: #409EFF;
- }
- .avatar-uploader-icon {
- font-size: 28px;
- color: #8c939d;
- width: 178px;
- height: 178px;
- line-height: 178px;
- text-align: center;
- }
- .avatar {
- width: 178px;
- height: 178px;
- display: block;
- }
- </style>

网页显示图片的配置,
InterceptorConfig类
/** * 映射路径修改:这段代码意思就配置一个拦截器, 如果访问路径是addResourceHandler中的filepath 这个路径 * 那么就 映射到访问本地的addResourceLocations 的参数的这个路径上, * 这样就可以让别人访问服务器的本地文件了,比如本地图片或者本地音乐视频什么的。 * @param registry */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/sysFile/show/**").addResourceLocations("file:D:\\temp\\files\\"); }
文章表:
后端代码,参考视频
安装npm依赖包
npm install mavon-editor --s
main.js
// main.js全局注册 import mavonEditor from 'mavon-editor' import 'mavon-editor/dist/css/index.css' // use Vue.use(mavonEditor)
// 绑定@imgAdd event imgAdd(pos, $file) { let $vm = this.$refs.md // 第一步.将图片上传到服务器. const formData = new FormData(); formData.append('file', $file); axios({ url: 'http://localhost:8899/file/upload', method: 'post', data: formData, headers: {'Content-Type': 'multipart/form-data'}, }).then((res) => { // 第二步.将返回的url替换到文本原位置 ->  $vm.$img2Url(pos, res.data); }) }展示富文本
<mavon-editor class="md" :value="content" :subfield="false" :defaultOpen="'preview'" :toolbarsFlag="false" :editable="false" :scrollStyle="true" :ishljs="true" />富文本编辑
<mavon-editor ref="md" v-model="form.content" :ishljs="true" @imgAdd="imgAdd"/>
前端页面
- <template>
- <div>
- <div>
- <!-- 搜索栏-->
- <el-input style="width: 200px;margin-right: 20px" placeholder="请输入文章名称" v-model="name" prefix-icon="el-icon-user"></el-input>
- <el-input style="width: 200px;margin-right: 20px" placeholder="请输入创建者名称" v-model="user" prefix-icon="el-icon-user"></el-input>
- <el-button style="margin-left: 10px;" type="primary" @click="load" class="el-icon-search">搜索</el-button>
- <el-button style="margin-left: 10px;" type="warning" @click="reset" class="el-icon-refresh">重置</el-button>
- </div>
-
- <div style="margin-top:20px;margin-bottom: 20px;">
- <!-- 新增。批量删除-->
- <el-button style="margin-right: 10px;" type="success" class="el-icon-plus" @click="save">新增</el-button>
- <el-popconfirm
- confirm-button-text='确定'
- cancel-button-text='取消'
- icon="el-icon-info"
- icon-color="red"
- title="确定删除这些数据吗?"
- @confirm="deleteBatch"
- @cancel="cancel">
- <el-button slot="reference" type="danger" style="margin-left:5px;" class="el-icon-delete">批量删除</el-button>
- </el-popconfirm>
- </div>
-
- <el-table :data="tableData" border stripe :header-cell-style="getRowClass" @selection-change="handleSelectionChange">
- <el-table-column type="selection" width="55"></el-table-column>
- <el-table-column prop="id" label="id"></el-table-column>
- <el-table-column prop="name" label="文章名称"></el-table-column>
- <el-table-column prop="content" label="文章内容">
- <template slot-scope="scope">
- <el-button type="primary" class="el-icon-view" @click="view(scope.row.content)">查看内容</el-button>
- </template>
- </el-table-column>
- <el-table-column prop="value" label="创建用户"></el-table-column>
- <el-table-column prop="type" label="类型"></el-table-column>
- <el-table-column prop="createTime" label="创建时间"></el-table-column>
- <el-table-column label="操作" width="200">
- <template slot-scope="scope">
- <el-button type="primary" class="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
- <el-popconfirm
- confirm-button-text='确定'
- cancel-button-text='取消'
- icon="el-icon-info"
- icon-color="red"
- title="确定删除这些数据吗?"
- @confirm="handleDelete(scope.row.id)"
- @cancel="cancel">
- <el-button slot="reference" type="danger" style="margin-left:5px;" class="el-icon-delete">删除</el-button>
- </el-popconfirm>
- </template>
- </el-table-column>
- </el-table>
-
- <div class="block" style="padding:10px 0;align-content: center;margin-left: 30%;margin-top:30px;">
- <el-pagination
- @size-change="handleSizeChange"
- @current-change="handleCurrentChange"
- :current-page="pageNum"
- :page-sizes="[5, 10, 15, 20]"
- :page-size="10"
- layout="total, sizes, prev, pager, next, jumper"
- :total="total">
- </el-pagination>
- </div>
-
- <el-dialog title="文章信息" :visible.sync="dialogFormVisible" width="60%">
- <el-form :model="form">
- <el-form-item label="文章名称" :label-width="formLabelWidth">
- <el-input v-model="form.name" autocomplete="off"></el-input>
- </el-form-item>
- <el-form-item label="文章内容" :label-width="formLabelWidth">
- <mavon-editor ref="md" v-model="form.content" :ishljs="true" @imgAdd="imgAdd"/>
- </el-form-item>
- <el-form-item label="类型" :label-width="formLabelWidth">
- <el-input v-model="form.type" autocomplete="off"></el-input>
- </el-form-item>
- </el-form>
- <div slot="footer" class="dialog-footer">
- <el-button @click="dialogFormVisible = false">取 消</el-button>
- <el-button type="primary" @click="handleAdd">确 定</el-button>
- </div>
- </el-dialog>
-
- <el-dialog title="文章内容展示" :visible.sync="contentVisible" width="60%">
- <el-card>
- <mavon-editor
- class="md"
- :value="content"
- :subfield="false"
- :defaultOpen="'preview'"
- :toolbarsFlag="false"
- :editable="false"
- :scrollStyle="true"
- :ishljs="true"
- />
- </el-card>
- </el-dialog>
-
- </div>
- </template>
-
- <script>
- import axios from "axios";
-
- export default {
- name: "Article",
- data(){
- return{
- pageSize:10,
- pageNum:1,
- name:'',
- user:'',
- content:'',
- tableData:[],
- dialogFormVisible:false,
- contentVisible:false,
- form:{},
- formLabelWidth: '80px',
- multipleSelection:[],
- total:0
- }
- },
- created() {
- this.load();
- },
- methods:{
- view(content){
- this.content = content;
- this.contentVisible = true;
- },
- // 绑定@imgAdd event
- imgAdd(pos, $file) {
- let $vm = this.$refs.md
- // 第一步.将图片上传到服务器.
- const formData = new FormData();
- formData.append('file', $file);
- axios({
- url: 'http://localhost:8888/sysFile/upload',
- method: 'post',
- data: formData,
- headers: {'Content-Type': 'multipart/form-data'},
- }).then((res) => {
- console.log(res)
- // 第二步.将返回的url替换到文本原位置 -> 
- $vm.$img2Url(pos, res.data);
- })
- },
- load(){
- this.request.get("/article/findPage",{
- params:{
- pageNum:this.pageNum,
- pageSize:this.pageSize,
- name:this.name,
- }
- }).then(res => {
- this.tableData = res.data.records;
- this.total = res.data.total;
- })
- },
- getRowClass({rowIndex,columnIndex}){
- if(rowIndex === 0){
- return 'background:#ccc'
- }
- },
- reset(){
- this.name = '';
- this.user = '';
- this.load();
- },
- save(){
- this.dialogFormVisible = true;
- this.form = {};
- },
- handleAdd(){
- this.request.post("/article/save",this.form).then(res => {
- if(res.code === '200'){
- if(this.form.id){
- this.$message.success('编辑成功');
- }else{
- this.$message.success('新增成功');
- }
- this.dialogFormVisible = false;
- this.load();
- }else{
- this.$message.error(res.msg)
- }
- })
- },
- handleEdit(row){
- this.form = JSON.parse(JSON.stringify(row));
- this.dialogFormVisible = true;
- },
- handleDelete(id){
- if(id){
- this.request.delete('/article/deleteById/' + id).then(res => {
- if(res.code === '200'){
- this.$message.success('删除数据成功');
- this.handleCalPageNum();
- }else{
- this.$message.error(res.msg)
- }
- })
- }else{
- this.$message.error('没有id信息,无法删除');
- }
- },
- cancel(){
- this.$message.success('取消操作成功');
- },
- handleSelectionChange(val) {
- this.multipleSelection = val;
- },
- deleteBatch(){
- //批量删除数据
- if(this.multipleSelection.length === 0){
- this.$message.warning("请先选择要删除的数据");
- return
- }
- const ids = this.multipleSelection.map(v => v.id);
- this.request.post('/article/deleteBatch',ids).then(res => {
- if(res.code === '200'){
- this.$message.success('批量删除成功');
- this.handleCalPageNum();
- }else{
- this.$message.error(res.msg);
- }
- })
- },
- handleSizeChange(val) {
- this.pageSize = val;
- this.load();
- },
- handleCurrentChange(val) {
- this.pageNum = val;
- this.load();
- },
- handleCalPageNum(){
- this.request.get("/article/findPage",{
- params:{
- pageNum:this.pageNum,
- pageSize:this.pageSize,
- name:this.name,
- user:this.user,
- }
- }).then(res => {
- this.total = res.data.total;
- this.pageNum = (this.total % this.pageSize === 0) ? (this.total / this.pageSize) : Math.floor((this.total / this.pageSize) + 1);
- if(this.pageNum < 1){
- this.pageNum = 1;
- }
- this.load();
- })
- }
- }
- }
- </script>
-
- <style scoped>
-
- </style>

公告表,轮播图表
后端代码
前端页面
看视频操作,代码较多
参考视频
Home页面
- <template>
- <div>
- <el-row :gutter="20">
- <el-col :span="6">
- <el-card>权限项目</el-card>
- </el-col>
- <el-col :span="6">
- <el-card>毕设项目</el-card>
- </el-col>
- <el-col :span="6">
- <el-card>前后端分离</el-card>
- </el-col>
- <el-col :span="6">
- <el-card>脚手架系统</el-card>
- </el-col>
- </el-row>
-
- <el-row :gutter="20">
- <el-col :span="8">
- <div style="width:400px;height:400px" id="main">
-
- </div>
- </el-col>
- <el-col :span="8">
- <div style="width:400px;height:400px" id="main1">
-
- </div>
- </el-col>
- <el-col :span="8">
- <div style="width:400px;height:400px" id="main2">
-
- </div>
- </el-col>
- </el-row>
- </div>
- </template>
-
- <script>
- import * as echarts from 'echarts';
-
- export default {
- name: "Home",
- mounted() {
-
- var chartDom = document.getElementById('main');
- var myChart = echarts.init(chartDom);
- var option;
-
- option = {
- xAxis: {
- type: 'category',
- data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
- },
- yAxis: {
- type: 'value'
- },
- series: [
- {
- data: [820, 932, 901, 934, 1290, 1330, 1320],
- type: 'line',
- smooth: true
- }
- ]
- };
-
-
-
- var chartDom1 = document.getElementById('main1');
- var myChart1 = echarts.init(chartDom1);
- var option1;
-
- option1 = {
- xAxis: {
- type: 'category',
- data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
- },
- yAxis: {
- type: 'value'
- },
- series: [
- {
- data: [120, 200, 150, 80, 70, 110, 130],
- type: 'bar',
- showBackground: true,
- backgroundStyle: {
- color: 'rgba(180, 180, 180, 0.2)'
- }
- }
- ]
- };
-
-
-
- var chartDom2 = document.getElementById('main2');
- var myChart2 = echarts.init(chartDom2);
- var option2;
-
- option2 = {
- tooltip: {
- trigger: 'item'
- },
- legend: {
- top: '5%',
- left: 'center'
- },
- series: [
- {
- name: 'Access From',
- type: 'pie',
- radius: ['40%', '70%'],
- avoidLabelOverlap: false,
- itemStyle: {
- borderRadius: 10,
- borderColor: '#fff',
- borderWidth: 2
- },
- label: {
- show: false,
- position: 'center'
- },
- emphasis: {
- label: {
- show: true,
- fontSize: 40,
- fontWeight: 'bold'
- }
- },
- labelLine: {
- show: false
- },
- data: [
- { value: 1048, name: 'Search Engine' },
- { value: 735, name: 'Direct' },
- { value: 580, name: 'Email' },
- { value: 484, name: 'Union Ads' },
- { value: 300, name: 'Video Ads' }
- ]
- }
- ]
- };
-
-
-
- this.request.get('/user/echart').then(res => {
- if(res.code === '200'){
- option.xAxis.data = res.data.list1; //名称的集合
- option.series[0].data = res.data.list2; //数值的集合
- option && myChart.setOption(option);
-
- option1.xAxis.data = res.data.list1; //名称的集合
- option1.series[0].data = res.data.list2; //数值的集合
- option1 && myChart1.setOption(option1);
-
- option2.series[0].data = res.data.list3;
- option2 && myChart2.setOption(option2);
- }
- })
-
- }
- }
- </script>
-
- <style scoped>
-
- </style>

app.vue中不但可以当做是网站首页,也可以写所有页面中公共需要的动画或者样式。
app.vue是vue页面资源的首加载项,是主组件,页面入口文件,所有页面都是在App.vue下进行切换的;也是整个项目的关键,app.vue负责构建定义及页面组件归集。
index.html---主页,项目入口
App.vue---根组件
main.js---入口文件
Front.vue和Home.vue页面,实现轮播图和项目介绍,头部展示基本信息
看视频操作,代码较多不复制了
看视频操作,代码较多不复制了
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。