当前位置:   article > 正文

1-前后端分离的权限脚手架项目_程序员云翼

程序员云翼

1.项目介绍和后台项目搭建

本项目是基于springboot + vue的前后端分离权限项目,适合做毕设项目和个人学习,手把手搭建。

后端技术栈:springboot,MySQL,redis,Maven

前端技术栈:vue,Vuex,Vue-Router,Echarts,elementUI

通用的权限管理项目,可以在本系统的基础上开发大部分的管理系统。

视频链接:1.项目介绍和后台搭建_哔哩哔哩_bilibili

导图:

创建springboot项目,如视频操作

pom.xml文件

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <parent>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-parent</artifactId>
  8. <version>2.5.9</version>
  9. <relativePath/> <!-- lookup parent from repository -->
  10. </parent>
  11. <groupId>com.example</groupId>
  12. <artifactId>base-authority</artifactId>
  13. <version>0.0.1-SNAPSHOT</version>
  14. <name>base-authority</name>
  15. <description>Demo project for Spring Boot</description>
  16. <properties>
  17. <java.version>1.8</java.version>
  18. </properties>
  19. <dependencies>
  20. <dependency>
  21. <groupId>org.springframework.boot</groupId>
  22. <artifactId>spring-boot-starter</artifactId>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.springframework.boot</groupId>
  26. <artifactId>spring-boot-starter-test</artifactId>
  27. <scope>test</scope>
  28. </dependency>
  29. <!-- swagger-->
  30. <dependency>
  31. <groupId>io.springfox</groupId>
  32. <artifactId>springfox-swagger2</artifactId>
  33. <version>2.9.2</version>
  34. </dependency>
  35. <dependency>
  36. <groupId>io.springfox</groupId>
  37. <artifactId>springfox-swagger-ui</artifactId>
  38. <version>2.9.2</version>
  39. </dependency>
  40. <dependency>
  41. <groupId>org.springframework.boot</groupId>
  42. <artifactId>spring-boot-starter-web</artifactId>
  43. </dependency>
  44. <dependency>
  45. <groupId>mysql</groupId>
  46. <artifactId>mysql-connector-java</artifactId>
  47. <scope>runtime</scope>
  48. </dependency>
  49. <dependency>
  50. <groupId>org.projectlombok</groupId>
  51. <artifactId>lombok</artifactId>
  52. <optional>true</optional>
  53. </dependency>
  54. <!-- mybatis-plus -->
  55. <dependency>
  56. <groupId>com.baomidou</groupId>
  57. <artifactId>mybatis-plus-boot-starter</artifactId>
  58. <version>3.5.1</version>
  59. </dependency>
  60. <dependency>
  61. <groupId>org.mybatis.spring.boot</groupId>
  62. <artifactId>mybatis-spring-boot-starter</artifactId>
  63. <version>2.2.1</version>
  64. </dependency>
  65. <!--redis-->
  66. <dependency>
  67. <groupId>org.springframework.boot</groupId>
  68. <artifactId>spring-boot-starter-data-redis</artifactId>
  69. </dependency>
  70. <!-- JSON的依赖 -->
  71. <dependency>
  72. <groupId>com.alibaba</groupId>
  73. <artifactId>fastjson</artifactId>
  74. <version>1.2.69</version>
  75. </dependency>
  76. <dependency>
  77. <groupId>commons-codec</groupId>
  78. <artifactId>commons-codec</artifactId>
  79. </dependency>
  80. <!-- JWT -->
  81. <dependency>
  82. <groupId>com.auth0</groupId>
  83. <artifactId>java-jwt</artifactId>
  84. <version>3.10.3</version>
  85. </dependency>
  86. </dependencies>
  87. <build>
  88. <plugins>
  89. <plugin>
  90. <groupId>org.springframework.boot</groupId>
  91. <artifactId>spring-boot-maven-plugin</artifactId>
  92. </plugin>
  93. </plugins>
  94. </build>
  95. </project>

 创建数据库,在创建sys_user表

  1. CREATE TABLE `sys_user` (
  2. `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id主键',
  3. `username` varchar(255) DEFAULT NULL COMMENT '用户名',
  4. `password` varchar(255) DEFAULT NULL COMMENT '密码',
  5. `nickname` varchar(255) DEFAULT NULL COMMENT '昵称',
  6. `address` varchar(255) DEFAULT NULL COMMENT '地址',
  7. `email` varchar(255) DEFAULT NULL COMMENT '邮箱',
  8. `phone` varchar(18) DEFAULT NULL COMMENT '联系方式',
  9. `role_id` int(11) DEFAULT NULL COMMENT '角色id',
  10. `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  11. `header_url` varchar(255) DEFAULT NULL COMMENT '用户头像',
  12. PRIMARY KEY (`id`)
  13. ) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8mb4;

 创建user的后端代码,entity,mapper,service,controller层代码,如视频操作,代码太多就不复制粘贴了。

2.前端项目首页搭建

效果图 

Aside.vue

  1. <template>
  2. <el-menu default-active="1-4-1"
  3. class="el-menu-vertical-demo"
  4. style="min-height:100%;overflow-x: hidden;"
  5. text-color="#fff"
  6. active-text-color="#ffd046"
  7. background-color="rgb(48,65,86)"
  8. :collapse-transition="false"
  9. router
  10. :collapse="isCollapse">
  11. <div style="height: 60px;line-height: 60px;text-align: center">
  12. <img src="../assets/logo.png" style="width:30px;position: relative;margin-right: 5px;top:6px">
  13. <b style="color:white" v-show="logoTextShow">后台管理系统</b>
  14. </div>
  15. <el-submenu index="1">
  16. <template slot="title">
  17. <i class="el-icon-location"></i>
  18. <span slot="title">导航一</span>
  19. </template>
  20. <el-menu-item-group>
  21. <span slot="title">分组一</span>
  22. <el-menu-item index="/user">用户管理</el-menu-item>
  23. <el-menu-item index="/role">角色管理</el-menu-item>
  24. </el-menu-item-group>
  25. <el-menu-item-group title="分组2">
  26. <el-menu-item index="1-3">选项3</el-menu-item>
  27. </el-menu-item-group>
  28. <el-submenu index="1-4">
  29. <span slot="title">选项4</span>
  30. <el-menu-item index="1-4-1">选项1</el-menu-item>
  31. </el-submenu>
  32. </el-submenu>
  33. <el-menu-item index="2">
  34. <i class="el-icon-menu"></i>
  35. <span slot="title">导航二</span>
  36. </el-menu-item>
  37. </el-menu>
  38. </template>
  39. <script>
  40. export default {
  41. name: "Aside",
  42. props:{
  43. isCollapse:Boolean,
  44. logoTextShow:Boolean
  45. }
  46. }
  47. </script>
  48. <style scoped>
  49. .el-menu-vertical-demo:not(.el-menu--collapse) {
  50. width: 200px;
  51. min-height: 400px;
  52. }
  53. </style>

Header.vue

  1. <template>
  2. <div style="line-height: 60px;display: flex">
  3. <div style="flex: 1;font-size: 30px">
  4. <span :class="collapseBtnClass" style="cursor: pointer" @click="collapse"></span>
  5. <el-breadcrumb separator="/" style="display: inline-block;margin-left:10px;position: absolute;top:22px" >
  6. <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
  7. <el-breadcrumb-item><b>{{currentPathName}}</b></el-breadcrumb-item>
  8. </el-breadcrumb>
  9. </div>
  10. <el-dropdown style="width: 150px;cursor: pointer;text-align: center">
  11. <div style="display: inline-block">
  12. <img src="../assets/logo.png" style="width:65px;height:45px;border-radius: 50%;position: relative;top:10px;right:8px"/>
  13. <span>张三</span><i class="el-icon-arrow-down" style="margin-left:5px"></i>
  14. </div>
  15. <el-dropdown-menu slot="dropdown" style="width: 100px;text-align: center;text-decoration: none">
  16. <el-dropdown-item style="font-size: 15px;padding:5px 0">
  17. <router-link to="/person" style="text-decoration: none;color: #606266">个人信息</router-link>
  18. </el-dropdown-item>
  19. <el-dropdown-item style="font-size: 15px;padding:5px 0">
  20. <router-link to="/person" style="text-decoration: none;color: #606266">修改密码</router-link>
  21. </el-dropdown-item>
  22. <el-dropdown-item style="font-size: 15px;padding:5px 0">
  23. <div style="text-decoration: none">退出</div>
  24. </el-dropdown-item>
  25. </el-dropdown-menu>
  26. </el-dropdown>
  27. </div>
  28. </template>
  29. <script>
  30. export default {
  31. name: "Header",
  32. props:{
  33. collapseBtnClass:String
  34. },
  35. computed:{
  36. currentPathName(){
  37. return this.$store.state.currentPathName;
  38. }
  39. },
  40. methods:{
  41. collapse(){
  42. this.$emit('collapse')
  43. }
  44. }
  45. }
  46. </script>
  47. <style scoped>
  48. </style>

Manage.vue

  1. <template>
  2. <el-container style="min-height: 100vh">
  3. <el-aside :width="sideWidth + 'px'">
  4. <Aside :isCollapse="isCollapse" :logoTextShow="logoTextShow"></Aside>
  5. </el-aside>
  6. <el-container>
  7. <el-header style="border-bottom: 1px solid #ccc">
  8. <Header @collapse="collapse" :collapseBtnClass="collapseBtnClass"></Header>
  9. </el-header>
  10. <el-main>
  11. <router-view></router-view>
  12. </el-main>
  13. </el-container>
  14. </el-container>
  15. </template>
  16. <script>
  17. import Aside from "@/components/Aside";
  18. import Header from "@/components/Header";
  19. export default {
  20. name: "Manage",
  21. components:{
  22. Aside,Header
  23. },
  24. data(){
  25. return{
  26. isCollapse:false,
  27. logoTextShow:true,
  28. collapseBtnClass:'el-icon-s-fold',
  29. sideWidth:200
  30. }
  31. },
  32. methods:{
  33. collapse(){
  34. this.isCollapse = !this.isCollapse;
  35. if(this.isCollapse){
  36. //收缩
  37. this.sideWidth = 64;
  38. this.logoTextShow = false;
  39. this.collapseBtnClass = 'el-icon-s-unfold';
  40. }else{
  41. this.sideWidth = 200;
  42. this.logoTextShow = true;
  43. this.collapseBtnClass = 'el-icon-s-fold';
  44. }
  45. }
  46. }
  47. }
  48. </script>
  49. <style scoped>
  50. /*去掉aside侧边栏的底部滚动条*/
  51. .el-aside::-webkit-scrollbar {
  52. display: none;
  53. }
  54. </style>

VUEX的index.js

  1. import Vue from 'vue'
  2. import Vuex from 'vuex'
  3. Vue.use(Vuex)
  4. export default new Vuex.Store({
  5. state: {
  6. currentPathName:''
  7. },
  8. getters: {
  9. },
  10. mutations: {
  11. setPath(state){
  12. state.currentPathName = localStorage.getItem('currentPathName');
  13. }
  14. },
  15. actions: {
  16. },
  17. modules: {
  18. }
  19. })

Router的index.js

  1. import Vue from 'vue'
  2. import VueRouter from 'vue-router'
  3. import store from '../store'
  4. Vue.use(VueRouter)
  5. const routes = [
  6. {
  7. path: '/',
  8. name: 'manage',
  9. component: () => import(/* webpackChunkName: "about" */ '../views/Manage.vue'),
  10. children:[
  11. {
  12. path: 'user',
  13. name: '用户管理',
  14. component: () => import(/* webpackChunkName: "about" */ '../views/User.vue'),
  15. },
  16. {
  17. path: 'role',
  18. name: '角色管理',
  19. component: () => import(/* webpackChunkName: "about" */ '../views/Role.vue'),
  20. }
  21. ]
  22. }
  23. ]
  24. const router = new VueRouter({
  25. mode: 'history',
  26. base: process.env.BASE_URL,
  27. routes
  28. })
  29. router.beforeEach((to,from,next) => {
  30. localStorage.setItem('currentPathName',to.name);
  31. store.commit('setPath')
  32. next();
  33. })
  34. export default router

3.用户管理

补充

sys_user表添加 hearer_url 头像字段

去掉Aside侧边栏的底部滚动条

安装axios

npm install axios -S

 封装axios的请求文件

  1. import axios from 'axios'
  2. import { Notification, MessageBox, Message, Loading } from 'element-ui'
  3. import ElementUI from 'element-ui'
  4. import router from "@/router";
  5. const request = axios.create({
  6. baseURL:'http://localhost:8899/',
  7. timeout:5000
  8. })
  9. request.interceptors.request.use(config => {
  10. config.headers['Content-Type'] = 'application/json;charset=UTF-8'
  11. let user = localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : null
  12. if(user){
  13. config.headers["token"] = user.token;
  14. }
  15. return config;
  16. },error => {
  17. return Promise.reject(error)
  18. })
  19. request.interceptors.response.use(
  20. response => {
  21. let res = response.data;
  22. if(response.config.responseType === 'blob'){
  23. return res;
  24. }
  25. if(typeof res === 'string'){
  26. res = res ? JSON.parse(res) : res
  27. }
  28. // 当权限验证不通过的时候给出提示
  29. if (res.code === '401') {
  30. ElementUI.Message({
  31. message: res.msg,
  32. type: 'error'
  33. });
  34. console.log('router.currentRoute.fullPath ')
  35. console.log(router.currentRoute.fullPath )
  36. // if (router.currentRoute.fullPath !== '/login') {
  37. // router.push('/login')
  38. // }
  39. }
  40. return res;
  41. },
  42. error => {
  43. if(error.code === '401'){
  44. router.push("/login")
  45. }
  46. Message.error(error)
  47. return Promise.reject(error);
  48. }
  49. )
  50. export default request

编写User.vue的界面,五部分组成:

1.搜索栏

2.加新增和批量删除按钮

3.table表格

4.新增和编辑的弹出框

5.分页

生命周期介绍
//created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成识图
//mounted:在模板渲染成html后调用,通常初始化页面完成后,再对html的dom节点进行一些需要的操作。

跨域

 当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域。

报错显示:

解决方法:

  1. package com.example.authority.config;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.web.cors.CorsConfiguration;
  5. import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
  6. import org.springframework.web.filter.CorsFilter;
  7. @Configuration
  8. public class CorsConfig {
  9. // 当前跨域请求最大有效时长。这里默认1
  10. private static final long MAX_AGE = 24 * 60 * 60;
  11. @Bean
  12. public CorsFilter corsFilter() {
  13. UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
  14. CorsConfiguration corsConfiguration = new CorsConfiguration();
  15. corsConfiguration.addAllowedOrigin("*"); // 1 设置访问源地址
  16. corsConfiguration.addAllowedHeader("*"); // 2 设置访问源请求头
  17. corsConfiguration.addAllowedMethod("*"); // 3 设置访问源请求方法
  18. corsConfiguration.setMaxAge(MAX_AGE);
  19. source.registerCorsConfiguration("/**", corsConfiguration); // 4 对接口配置跨域设置
  20. return new CorsFilter(source);
  21. }
  22. }

完成的User.vue页面

  1. <template>
  2. <div>
  3. <div>
  4. <!-- 搜索栏-->
  5. <el-input style="width: 200px;margin-right: 20px" placeholder="请输入用户名" v-model="username" prefix-icon="el-icon-user"></el-input>
  6. <el-input style="width: 200px" placeholder="请输入邮箱" v-model="email" prefix-icon="el-icon-message"></el-input>
  7. <el-button style="margin-left: 10px;" type="primary" @click="load" class="el-icon-search">搜索</el-button>
  8. <el-button style="margin-left: 10px;" type="warning" @click="reset" class="el-icon-refresh">重置</el-button>
  9. </div>
  10. <div style="margin-top:20px;margin-bottom: 20px;">
  11. <!-- 新增。批量删除-->
  12. <el-button style="margin-right: 10px;" type="success" class="el-icon-plus" @click="save">新增</el-button>
  13. <el-popconfirm
  14. confirm-button-text='确定'
  15. cancel-button-text='取消'
  16. icon="el-icon-info"
  17. icon-color="red"
  18. title="确定删除这些数据吗?"
  19. @confirm="deleteBatch"
  20. @cancel="cancel">
  21. <el-button slot="reference" type="danger" style="margin-left:5px;" class="el-icon-delete">批量删除</el-button>
  22. </el-popconfirm>
  23. </div>
  24. <el-table :data="tableData" border stripe :header-cell-style="getRowClass" @selection-change="handleSelectionChange">
  25. <el-table-column type="selection" width="55"></el-table-column>
  26. <el-table-column prop="id" label="id"></el-table-column>
  27. <el-table-column prop="username" label="用户名"></el-table-column>
  28. <el-table-column prop="nickname" label="昵称"></el-table-column>
  29. <el-table-column prop="address" label="地址"></el-table-column>
  30. <el-table-column prop="email" label="邮箱"></el-table-column>
  31. <el-table-column prop="phone" label="联系方式"></el-table-column>
  32. <el-table-column prop="roleId" label="角色"></el-table-column>
  33. <el-table-column prop="createTime" label="创建时间"></el-table-column>
  34. <el-table-column prop="headerUrl" label="头像"></el-table-column>
  35. <el-table-column label="操作" width="200">
  36. <template slot-scope="scope">
  37. <el-button type="primary" class="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
  38. <el-popconfirm
  39. confirm-button-text='确定'
  40. cancel-button-text='取消'
  41. icon="el-icon-info"
  42. icon-color="red"
  43. title="确定删除这些数据吗?"
  44. @confirm="handleDelete(scope.row.id)"
  45. @cancel="cancel">
  46. <el-button slot="reference" type="danger" style="margin-left:5px;" class="el-icon-delete">删除</el-button>
  47. </el-popconfirm>
  48. </template>
  49. </el-table-column>
  50. </el-table>
  51. <div class="block" style="padding:10px 0;align-content: center;margin-left: 30%;margin-top:30px;">
  52. <el-pagination
  53. @size-change="handleSizeChange"
  54. @current-change="handleCurrentChange"
  55. :current-page="pageNum"
  56. :page-sizes="[5, 10, 15, 20]"
  57. :page-size="10"
  58. layout="total, sizes, prev, pager, next, jumper"
  59. :total="total">
  60. </el-pagination>
  61. </div>
  62. <el-dialog title="收货地址" :visible.sync="dialogFormVisible" width="30%">
  63. <el-form :model="form">
  64. <el-form-item label="用户名" :label-width="formLabelWidth">
  65. <el-input v-model="form.username" autocomplete="off"></el-input>
  66. </el-form-item>
  67. <el-form-item label="密码" :label-width="formLabelWidth">
  68. <el-input v-model="form.password" autocomplete="off" type="password" show-password></el-input>
  69. </el-form-item>
  70. <el-form-item label="昵称" :label-width="formLabelWidth">
  71. <el-input v-model="form.nickname" autocomplete="off"></el-input>
  72. </el-form-item>
  73. <el-form-item label="地址" :label-width="formLabelWidth">
  74. <el-input v-model="form.address" autocomplete="off"></el-input>
  75. </el-form-item>
  76. <el-form-item label="邮箱" :label-width="formLabelWidth">
  77. <el-input v-model="form.email" autocomplete="off"></el-input>
  78. </el-form-item>
  79. <el-form-item label="联系方式" :label-width="formLabelWidth">
  80. <el-input v-model="form.phone" autocomplete="off"></el-input>
  81. </el-form-item>
  82. </el-form>
  83. <div slot="footer" class="dialog-footer">
  84. <el-button @click="dialogFormVisible = false">取 消</el-button>
  85. <el-button type="primary" @click="handleAdd">确 定</el-button>
  86. </div>
  87. </el-dialog>
  88. </div>
  89. </template>
  90. <script>
  91. export default {
  92. name: "User",
  93. data(){
  94. return{
  95. pageSize:10,
  96. pageNum:1,
  97. username:'',
  98. email:'',
  99. tableData:[],
  100. dialogFormVisible:false,
  101. form:{},
  102. formLabelWidth: '80px',
  103. multipleSelection:[],
  104. total:0
  105. }
  106. },
  107. created() {
  108. this.load();
  109. },
  110. methods:{
  111. load(){
  112. this.request.get("/user/findPage",{
  113. params:{
  114. pageNum:this.pageNum,
  115. pageSize:this.pageSize,
  116. username:this.username,
  117. email:this.email
  118. }
  119. }).then(res => {
  120. this.tableData = res.data.records;
  121. this.total = res.data.total;
  122. })
  123. },
  124. getRowClass({rowIndex,columnIndex}){
  125. if(rowIndex === 0){
  126. return 'background:#ccc'
  127. }
  128. },
  129. reset(){
  130. this.email = '';
  131. this.username = '';
  132. this.load();
  133. },
  134. save(){
  135. this.dialogFormVisible = true;
  136. this.form = {};
  137. },
  138. handleAdd(){
  139. this.request.post("/user/save",this.form).then(res => {
  140. if(res.code === '200'){
  141. if(this.form.id){
  142. this.$message.success('编辑成功');
  143. }else{
  144. this.$message.success('新增成功');
  145. }
  146. this.dialogFormVisible = false;
  147. this.load();
  148. }else{
  149. this.$message.error('操作失败,请联系管理员')
  150. }
  151. })
  152. },
  153. handleEdit(row){
  154. this.form = JSON.parse(JSON.stringify(row));
  155. this.dialogFormVisible = true;
  156. },
  157. handleDelete(id){
  158. if(id){
  159. this.request.delete('/user/deleteById/' + id).then(res => {
  160. if(res.code === '200'){
  161. this.$message.success('删除数据成功');
  162. this.load();
  163. }else{
  164. this.$message.error('删除数据失败,请联系管理员')
  165. }
  166. })
  167. }else{
  168. this.$message.error('没有id信息,无法删除');
  169. }
  170. },
  171. cancel(){
  172. this.$message.success('取消操作成功');
  173. },
  174. handleSelectionChange(val) {
  175. this.multipleSelection = val;
  176. },
  177. deleteBatch(){
  178. //批量删除数据
  179. if(this.multipleSelection.length === 0){
  180. this.$message.warning("请先选择要删除的数据");
  181. return
  182. }
  183. const ids = this.multipleSelection.map(v => v.id);
  184. this.request.post('/user/deleteBatch',ids).then(res => {
  185. if(res.code === '200'){
  186. this.$message.success('批量删除成功');
  187. this.load();
  188. }else{
  189. this.$message.error('批量删除失败');
  190. }
  191. })
  192. },
  193. handleSizeChange(val) {
  194. this.pageSize = val;
  195. this.load();
  196. },
  197. handleCurrentChange(val) {
  198. this.pageNum = val;
  199. this.load();
  200. }
  201. }
  202. }
  203. </script>
  204. <style scoped>
  205. </style>

4.角色管理

补充:删除数据的分页显示

创建角色表

创建角色管理后台代码

如视频操作,代码较多,不发出来了

创建角色页面

  1. <template>
  2. <div>
  3. <div>
  4. <!-- 搜索栏-->
  5. <el-input style="width: 200px;margin-right: 20px" placeholder="请输入名称" v-model="name" prefix-icon="el-icon-user"></el-input>
  6. <el-button style="margin-left: 10px;" type="primary" @click="load" class="el-icon-search">搜索</el-button>
  7. <el-button style="margin-left: 10px;" type="warning" @click="reset" class="el-icon-refresh">重置</el-button>
  8. </div>
  9. <div style="margin-top:20px;margin-bottom: 20px;">
  10. <!-- 新增。批量删除-->
  11. <el-button style="margin-right: 10px;" type="success" class="el-icon-plus" @click="save">新增</el-button>
  12. <el-popconfirm
  13. confirm-button-text='确定'
  14. cancel-button-text='取消'
  15. icon="el-icon-info"
  16. icon-color="red"
  17. title="确定删除这些数据吗?"
  18. @confirm="deleteBatch"
  19. @cancel="cancel">
  20. <el-button slot="reference" type="danger" style="margin-left:5px;" class="el-icon-delete">批量删除</el-button>
  21. </el-popconfirm>
  22. </div>
  23. <el-table :data="tableData" border stripe :header-cell-style="getRowClass" @selection-change="handleSelectionChange">
  24. <el-table-column type="selection" width="55"></el-table-column>
  25. <el-table-column prop="id" label="id"></el-table-column>
  26. <el-table-column prop="name" label="角色名称"></el-table-column>
  27. <el-table-column prop="code" label="角色编码"></el-table-column>
  28. <el-table-column prop="description" label="描述"></el-table-column>
  29. <el-table-column label="操作" width="200">
  30. <template slot-scope="scope">
  31. <el-button type="primary" class="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
  32. <el-popconfirm
  33. confirm-button-text='确定'
  34. cancel-button-text='取消'
  35. icon="el-icon-info"
  36. icon-color="red"
  37. title="确定删除这些数据吗?"
  38. @confirm="handleDelete(scope.row.id)"
  39. @cancel="cancel">
  40. <el-button slot="reference" type="danger" style="margin-left:5px;" class="el-icon-delete">删除</el-button>
  41. </el-popconfirm>
  42. </template>
  43. </el-table-column>
  44. </el-table>
  45. <div class="block" style="padding:10px 0;align-content: center;margin-left: 30%;margin-top:30px;">
  46. <el-pagination
  47. @size-change="handleSizeChange"
  48. @current-change="handleCurrentChange"
  49. :current-page="pageNum"
  50. :page-sizes="[5, 10, 15, 20]"
  51. :page-size="10"
  52. layout="total, sizes, prev, pager, next, jumper"
  53. :total="total">
  54. </el-pagination>
  55. </div>
  56. <el-dialog title="角色信息" :visible.sync="dialogFormVisible" width="30%">
  57. <el-form :model="form">
  58. <el-form-item label="角色名称" :label-width="formLabelWidth">
  59. <el-input v-model="form.name" autocomplete="off"></el-input>
  60. </el-form-item>
  61. <el-form-item label="角色编码" :label-width="formLabelWidth">
  62. <el-input v-model="form.code" autocomplete="off"></el-input>
  63. </el-form-item>
  64. <el-form-item label="描述" :label-width="formLabelWidth">
  65. <el-input v-model="form.description" autocomplete="off"></el-input>
  66. </el-form-item>
  67. </el-form>
  68. <div slot="footer" class="dialog-footer">
  69. <el-button @click="dialogFormVisible = false">取 消</el-button>
  70. <el-button type="primary" @click="handleAdd">确 定</el-button>
  71. </div>
  72. </el-dialog>
  73. </div>
  74. </template>
  75. <script>
  76. export default {
  77. name: "Role",
  78. data(){
  79. return{
  80. pageSize:2,
  81. pageNum:1,
  82. name:'',
  83. tableData:[],
  84. dialogFormVisible:false,
  85. form:{},
  86. formLabelWidth: '80px',
  87. multipleSelection:[],
  88. total:0
  89. }
  90. },
  91. created() {
  92. this.load();
  93. },
  94. methods:{
  95. load(){
  96. this.request.get("/role/findPage",{
  97. params:{
  98. pageNum:this.pageNum,
  99. pageSize:this.pageSize,
  100. name:this.name,
  101. }
  102. }).then(res => {
  103. this.tableData = res.data.records;
  104. this.total = res.data.total;
  105. })
  106. },
  107. getRowClass({rowIndex,columnIndex}){
  108. if(rowIndex === 0){
  109. return 'background:#ccc'
  110. }
  111. },
  112. reset(){
  113. this.email = '';
  114. this.username = '';
  115. this.load();
  116. },
  117. save(){
  118. this.dialogFormVisible = true;
  119. this.form = {};
  120. },
  121. handleAdd(){
  122. this.request.post("/role/save",this.form).then(res => {
  123. if(res.code === '200'){
  124. if(this.form.id){
  125. this.$message.success('编辑成功');
  126. }else{
  127. this.$message.success('新增成功');
  128. }
  129. this.dialogFormVisible = false;
  130. this.load();
  131. }else{
  132. this.$message.error('操作失败,请联系管理员')
  133. }
  134. })
  135. },
  136. handleEdit(row){
  137. this.form = JSON.parse(JSON.stringify(row));
  138. this.dialogFormVisible = true;
  139. },
  140. handleDelete(id){
  141. if(id){
  142. this.request.delete('/role/deleteById/' + id).then(res => {
  143. if(res.code === '200'){
  144. this.$message.success('删除数据成功');
  145. this.handleCalPageNum();
  146. }else{
  147. this.$message.error('删除数据失败,请联系管理员')
  148. }
  149. })
  150. }else{
  151. this.$message.error('没有id信息,无法删除');
  152. }
  153. },
  154. cancel(){
  155. this.$message.success('取消操作成功');
  156. },
  157. handleSelectionChange(val) {
  158. this.multipleSelection = val;
  159. },
  160. deleteBatch(){
  161. //批量删除数据
  162. if(this.multipleSelection.length === 0){
  163. this.$message.warning("请先选择要删除的数据");
  164. return
  165. }
  166. const ids = this.multipleSelection.map(v => v.id);
  167. this.request.post('/role/deleteBatch',ids).then(res => {
  168. if(res.code === '200'){
  169. this.$message.success('批量删除成功');
  170. this.handleCalPageNum();
  171. }else{
  172. this.$message.error('批量删除失败');
  173. }
  174. })
  175. },
  176. handleSizeChange(val) {
  177. this.pageSize = val;
  178. this.load();
  179. },
  180. handleCurrentChange(val) {
  181. this.pageNum = val;
  182. this.load();
  183. },
  184. handleCalPageNum(){
  185. this.request.get("/role/findPage",{
  186. params:{
  187. pageNum:this.pageNum,
  188. pageSize:this.pageSize,
  189. username:this.username,
  190. email:this.email
  191. }
  192. }).then(res => {
  193. this.total = res.data.total;
  194. this.pageNum = (this.total % this.pageSize === 0) ? (this.total / this.pageSize) : Math.floor((this.total / this.pageSize) + 1);
  195. if(this.pageNum < 1){
  196. this.pageNum = 1;
  197. }
  198. this.load();
  199. })
  200. }
  201. }
  202. }
  203. </script>
  204. <style scoped>
  205. </style><template>
  206. <div>
  207. <div>
  208. <!-- 搜索栏-->
  209. <el-input style="width: 200px;margin-right: 20px" placeholder="请输入名称" v-model="name" prefix-icon="el-icon-user"></el-input>
  210. <el-button style="margin-left: 10px;" type="primary" @click="load" class="el-icon-search">搜索</el-button>
  211. <el-button style="margin-left: 10px;" type="warning" @click="reset" class="el-icon-refresh">重置</el-button>
  212. </div>
  213. <div style="margin-top:20px;margin-bottom: 20px;">
  214. <!-- 新增。批量删除-->
  215. <el-button style="margin-right: 10px;" type="success" class="el-icon-plus" @click="save">新增</el-button>
  216. <el-popconfirm
  217. confirm-button-text='确定'
  218. cancel-button-text='取消'
  219. icon="el-icon-info"
  220. icon-color="red"
  221. title="确定删除这些数据吗?"
  222. @confirm="deleteBatch"
  223. @cancel="cancel">
  224. <el-button slot="reference" type="danger" style="margin-left:5px;" class="el-icon-delete">批量删除</el-button>
  225. </el-popconfirm>
  226. </div>
  227. <el-table :data="tableData" border stripe :header-cell-style="getRowClass" @selection-change="handleSelectionChange">
  228. <el-table-column type="selection" width="55"></el-table-column>
  229. <el-table-column prop="id" label="id"></el-table-column>
  230. <el-table-column prop="name" label="角色名称"></el-table-column>
  231. <el-table-column prop="code" label="角色编码"></el-table-column>
  232. <el-table-column prop="description" label="描述"></el-table-column>
  233. <el-table-column label="操作" width="200">
  234. <template slot-scope="scope">
  235. <el-button type="primary" class="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
  236. <el-popconfirm
  237. confirm-button-text='确定'
  238. cancel-button-text='取消'
  239. icon="el-icon-info"
  240. icon-color="red"
  241. title="确定删除这些数据吗?"
  242. @confirm="handleDelete(scope.row.id)"
  243. @cancel="cancel">
  244. <el-button slot="reference" type="danger" style="margin-left:5px;" class="el-icon-delete">删除</el-button>
  245. </el-popconfirm>
  246. </template>
  247. </el-table-column>
  248. </el-table>
  249. <div class="block" style="padding:10px 0;align-content: center;margin-left: 30%;margin-top:30px;">
  250. <el-pagination
  251. @size-change="handleSizeChange"
  252. @current-change="handleCurrentChange"
  253. :current-page="pageNum"
  254. :page-sizes="[5, 10, 15, 20]"
  255. :page-size="10"
  256. layout="total, sizes, prev, pager, next, jumper"
  257. :total="total">
  258. </el-pagination>
  259. </div>
  260. <el-dialog title="角色信息" :visible.sync="dialogFormVisible" width="30%">
  261. <el-form :model="form">
  262. <el-form-item label="角色名称" :label-width="formLabelWidth">
  263. <el-input v-model="form.name" autocomplete="off"></el-input>
  264. </el-form-item>
  265. <el-form-item label="角色编码" :label-width="formLabelWidth">
  266. <el-input v-model="form.code" autocomplete="off"></el-input>
  267. </el-form-item>
  268. <el-form-item label="描述" :label-width="formLabelWidth">
  269. <el-input v-model="form.description" autocomplete="off"></el-input>
  270. </el-form-item>
  271. </el-form>
  272. <div slot="footer" class="dialog-footer">
  273. <el-button @click="dialogFormVisible = false">取 消</el-button>
  274. <el-button type="primary" @click="handleAdd">确 定</el-button>
  275. </div>
  276. </el-dialog>
  277. </div>
  278. </template>
  279. <script>
  280. export default {
  281. name: "Role",
  282. data(){
  283. return{
  284. pageSize:2,
  285. pageNum:1,
  286. name:'',
  287. tableData:[],
  288. dialogFormVisible:false,
  289. form:{},
  290. formLabelWidth: '80px',
  291. multipleSelection:[],
  292. total:0
  293. }
  294. },
  295. created() {
  296. this.load();
  297. },
  298. methods:{
  299. load(){
  300. this.request.get("/role/findPage",{
  301. params:{
  302. pageNum:this.pageNum,
  303. pageSize:this.pageSize,
  304. name:this.name,
  305. }
  306. }).then(res => {
  307. this.tableData = res.data.records;
  308. this.total = res.data.total;
  309. })
  310. },
  311. getRowClass({rowIndex,columnIndex}){
  312. if(rowIndex === 0){
  313. return 'background:#ccc'
  314. }
  315. },
  316. reset(){
  317. this.email = '';
  318. this.username = '';
  319. this.load();
  320. },
  321. save(){
  322. this.dialogFormVisible = true;
  323. this.form = {};
  324. },
  325. handleAdd(){
  326. this.request.post("/role/save",this.form).then(res => {
  327. if(res.code === '200'){
  328. if(this.form.id){
  329. this.$message.success('编辑成功');
  330. }else{
  331. this.$message.success('新增成功');
  332. }
  333. this.dialogFormVisible = false;
  334. this.load();
  335. }else{
  336. this.$message.error('操作失败,请联系管理员')
  337. }
  338. })
  339. },
  340. handleEdit(row){
  341. this.form = JSON.parse(JSON.stringify(row));
  342. this.dialogFormVisible = true;
  343. },
  344. handleDelete(id){
  345. if(id){
  346. this.request.delete('/role/deleteById/' + id).then(res => {
  347. if(res.code === '200'){
  348. this.$message.success('删除数据成功');
  349. this.handleCalPageNum();
  350. }else{
  351. this.$message.error('删除数据失败,请联系管理员')
  352. }
  353. })
  354. }else{
  355. this.$message.error('没有id信息,无法删除');
  356. }
  357. },
  358. cancel(){
  359. this.$message.success('取消操作成功');
  360. },
  361. handleSelectionChange(val) {
  362. this.multipleSelection = val;
  363. },
  364. deleteBatch(){
  365. //批量删除数据
  366. if(this.multipleSelection.length === 0){
  367. this.$message.warning("请先选择要删除的数据");
  368. return
  369. }
  370. const ids = this.multipleSelection.map(v => v.id);
  371. this.request.post('/role/deleteBatch',ids).then(res => {
  372. if(res.code === '200'){
  373. this.$message.success('批量删除成功');
  374. this.handleCalPageNum();
  375. }else{
  376. this.$message.error('批量删除失败');
  377. }
  378. })
  379. },
  380. handleSizeChange(val) {
  381. this.pageSize = val;
  382. this.load();
  383. },
  384. handleCurrentChange(val) {
  385. this.pageNum = val;
  386. this.load();
  387. },
  388. handleCalPageNum(){
  389. this.request.get("/role/findPage",{
  390. params:{
  391. pageNum:this.pageNum,
  392. pageSize:this.pageSize,
  393. username:this.username,
  394. email:this.email
  395. }
  396. }).then(res => {
  397. this.total = res.data.total;
  398. this.pageNum = (this.total % this.pageSize === 0) ? (this.total / this.pageSize) : Math.floor((this.total / this.pageSize) + 1);
  399. if(this.pageNum < 1){
  400. this.pageNum = 1;
  401. }
  402. this.load();
  403. })
  404. }
  405. }
  406. }
  407. </script>
  408. <style scoped>
  409. </style>

完善用户页面的角色部分

集成git,把代码提交git仓库

5.登录注册页面

        5.1 验证码组件

  1. <template>
  2. <span class="s-canvas">
  3. <canvas id="s-canvas" :width="contentWidth" :height="contentHeight"></canvas>
  4. </span>
  5. </template>
  6. <script>
  7. export default {
  8. name: 'SIdentify',
  9. props: {
  10. identifyCode: {
  11. type: String,
  12. default: '1234'
  13. },
  14. fontSizeMin: {
  15. type: Number,
  16. default: 16
  17. },
  18. fontSizeMax: {
  19. type: Number,
  20. default: 40
  21. },
  22. backgroundColorMin: {
  23. type: Number,
  24. default: 180
  25. },
  26. backgroundColorMax: {
  27. type: Number,
  28. default: 240
  29. },
  30. colorMin: {
  31. type: Number,
  32. default: 50
  33. },
  34. colorMax: {
  35. type: Number,
  36. default: 160
  37. },
  38. lineColorMin: {
  39. type: Number,
  40. default: 40
  41. },
  42. lineColorMax: {
  43. type: Number,
  44. default: 180
  45. },
  46. dotColorMin: {
  47. type: Number,
  48. default: 0
  49. },
  50. dotColorMax: {
  51. type: Number,
  52. default: 255
  53. },
  54. contentWidth: {
  55. type: Number,
  56. default: 112
  57. },
  58. contentHeight: {
  59. type: Number,
  60. default: 38
  61. }
  62. },
  63. methods: {
  64. // 生成一个随机数
  65. randomNum(min, max) {
  66. return Math.floor(Math.random() * (max - min) + min)
  67. },
  68. // 生成一个随机的颜色
  69. randomColor(min, max) {
  70. let r = this.randomNum(min, max)
  71. let g = this.randomNum(min, max)
  72. let b = this.randomNum(min, max)
  73. return 'rgb(' + r + ',' + g + ',' + b + ')'
  74. },
  75. drawPic() {
  76. let canvas = document.getElementById('s-canvas')
  77. let ctx = canvas.getContext('2d')
  78. ctx.textBaseline = 'bottom'
  79. // 绘制背景
  80. ctx.fillStyle = this.randomColor(this.backgroundColorMin, this.backgroundColorMax)
  81. ctx.fillRect(0, 0, this.contentWidth, this.contentHeight)
  82. // 绘制文字
  83. for (let i = 0; i < this.identifyCode.length; i++) {
  84. this.drawText(ctx, this.identifyCode[i], i)
  85. }
  86. this.drawLine(ctx)
  87. this.drawDot(ctx)
  88. },
  89. drawText(ctx, txt, i) {
  90. ctx.fillStyle = this.randomColor(this.colorMin, this.colorMax)
  91. ctx.font = this.randomNum(this.fontSizeMin, this.fontSizeMax) + 'px SimHei'
  92. let x = (i + 1) * (this.contentWidth / (this.identifyCode.length + 1))
  93. let y = this.randomNum(this.fontSizeMax, this.contentHeight - 5)
  94. var deg = this.randomNum(-45, 45)
  95. // 修改坐标原点和旋转角度
  96. ctx.translate(x, y)
  97. ctx.rotate(deg * Math.PI / 180)
  98. ctx.fillText(txt, 0, 0)
  99. // 恢复坐标原点和旋转角度
  100. ctx.rotate(-deg * Math.PI / 180)
  101. ctx.translate(-x, -y)
  102. },
  103. drawLine(ctx) {
  104. // 绘制干扰线
  105. for (let i = 0; i < 3; i++) {
  106. ctx.strokeStyle = this.randomColor(this.lineColorMin, this.lineColorMax)
  107. ctx.beginPath()
  108. ctx.moveTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight))
  109. ctx.lineTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight))
  110. ctx.stroke()
  111. }
  112. },
  113. drawDot(ctx) {
  114. // 绘制干扰点
  115. for (let i = 0; i < 10; i++) {
  116. ctx.fillStyle = this.randomColor(0, 255)
  117. ctx.beginPath()
  118. ctx.arc(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight), 1, 0, 2 * Math.PI)
  119. ctx.fill()
  120. }
  121. }
  122. },
  123. watch: {
  124. identifyCode() {
  125. this.drawPic()
  126. }
  127. },
  128. mounted() { //mounted:在模板渲染成html后调用,通常初始化页面完成后,再对html的dom节点进行一些需要的操作。
  129. this.drawPic()
  130. }
  131. }
  132. </script>
  133. <style>
  134. .s-canvas {
  135. height: 38px;
  136. }
  137. canvas {
  138. margin-top: 1px;
  139. margin-left: 8px;
  140. }
  141. </style>

        5.2 编写Login.vue页面

  1. <template>
  2. <div class="wrapper">
  3. <!-- 项目名称-->
  4. <div style="height: 60px;line-height: 60px;font-size: 20px;padding-left: 50px;color: white;text-align: center;align-content: center;background-color: #cccccc">
  5. 权限脚手架项目
  6. </div>
  7. <div style="display: flex;width:55%;height:40%;margin: 150px auto;background-color: white;border-radius: 10px;overflow: hidden;background-color: bisque">
  8. <!-- 左侧图片显示-->
  9. <div style="width: 50%;margin-top: 30px;margin-left: 30px">
  10. <img src="../assets/register.png" alt="" style="width:100%;height:90%" />
  11. </div>
  12. <!-- 提交表单-->
  13. <div style="width:350px;margin-top: 30px">
  14. <div style="margin: 20px 0;text-align: center;font-size: 20px">登录页面</div>
  15. <el-form :model="userForm" :rules="rules" ref="userForm" label-width="100px" class="demo-ruleForm">
  16. <el-form-item label="用户名" prop="username">
  17. <el-input v-model="userForm.username" prefix-icon="el-icon-user"></el-input>
  18. </el-form-item>
  19. <el-form-item label="密码" prop="password">
  20. <el-input v-model="userForm.password" prefix-icon="el-icon-lock" type="password" show-password></el-input>
  21. </el-form-item>
  22. <el-form-item label="验证码" prop="identifyCode">
  23. <el-input v-model="userForm.identifyCode" style="width:50%;position:relative;bottom:8px;" prefix-icon="el-icon-mobile-phone"></el-input>
  24. <span @click="refreshCode" style="position: relative;top:5px;left:6px;">
  25. <SIdentity :identify-code="identifyCode"></SIdentity>
  26. </span>
  27. </el-form-item>
  28. <el-form-item>
  29. <el-button type="primary" @click="login('userForm')">登录</el-button>
  30. <el-button type="success" @click="$router.push('/register')">前往注册</el-button>
  31. <!-- <el-button type="warning" @click="resetForm('userForm')">重置</el-button>-->
  32. </el-form-item>
  33. </el-form>
  34. </div>
  35. </div>
  36. </div>
  37. </template>
  38. <script>
  39. import SIdentity from "@/components/SIdentity";
  40. export default {
  41. name: "Login",
  42. components:{
  43. SIdentity
  44. },
  45. mounted() {
  46. this.refreshCode()
  47. },
  48. data(){
  49. return{
  50. userForm:{},
  51. identifyCode:'',
  52. identifyCodes:'1234567890zxcvbnmasdfghjklqwertyuiopQWERTYUIPOLSADFGHJ',
  53. rules: {
  54. username: [
  55. { required: true, message: '请输入用户名称', trigger: 'blur' },
  56. { min: 1, max: 20, message: '长度在 1 到 20 个字符', trigger: 'blur' }
  57. ],
  58. password: [
  59. { required: true, message: '请输入密码', trigger: 'blur' },
  60. { min: 6, max: 50, message: '长度不能小于6位', trigger: 'blur' }
  61. ],
  62. identifyCode: [
  63. { required: true, message: '请输入验证码', trigger: 'blur' },
  64. { min: 4, max: 4, message: '验证码长度为4位', trigger: 'blur' }
  65. ]
  66. }
  67. }
  68. },
  69. methods:{
  70. //提交数据
  71. login(formName) {
  72. this.$refs[formName].validate((valid) => {
  73. if (valid) {
  74. if(this.identifyCode.toUpperCase() !== this.userForm.identifyCode.toUpperCase()){
  75. this.$message.warning("验证码输入错误,请重新输入");
  76. return;
  77. }
  78. alert('submit!');
  79. } else {
  80. console.log('error submit!!');
  81. return false;
  82. }
  83. });
  84. },
  85. // 重置数据
  86. resetForm(formName) {
  87. this.$refs[formName].resetFields();
  88. },
  89. // 验证码方法
  90. refreshCode(){
  91. this.identifyCode = ''
  92. this.makeCode(this.identifyCodes,4);
  93. },
  94. makeCode(o,l){
  95. for(let i = 0;i < l;i++){
  96. this.identifyCode += this.identifyCodes[
  97. this.randomNum(0,this.identifyCodes.length)
  98. ]
  99. }
  100. },
  101. randomNum(min,max){
  102. return Math.floor(Math.random() * (max - min) + min);
  103. }
  104. }
  105. }
  106. </script>
  107. <style scoped>
  108. .wrapper{
  109. background: url("../assets/auth.jpg");
  110. width:100%;
  111. height: 100%;
  112. position: fixed;
  113. background-size: 100% 100%;
  114. }
  115. </style>

        5.4 编写Register.vue页面

  1. <template>
  2. <div class="wrapper">
  3. <!-- 项目名称-->
  4. <div style="height: 60px;line-height: 60px;font-size: 20px;padding-left: 50px;color: white;text-align: center;align-content: center;background-color: #cccccc">
  5. 权限脚手架项目
  6. </div>
  7. <div style="display: flex;width:55%;height:40%;margin: 150px auto;background-color: white;border-radius: 10px;overflow: hidden;background-color: bisque">
  8. <!-- 左侧图片显示-->
  9. <div style="width: 50%;margin-top: 30px;margin-left: 30px">
  10. <img src="../assets/register.png" alt="" style="width:100%;height:90%" />
  11. </div>
  12. <!-- 提交表单-->
  13. <div style="width:350px;margin-top: 30px">
  14. <div style="margin: 20px 0;text-align: center;font-size: 20px">注册页面</div>
  15. <el-form :model="userForm" :rules="rules" ref="userForm" label-width="100px" class="demo-ruleForm">
  16. <el-form-item label="用户名" prop="username">
  17. <el-input v-model="userForm.username" prefix-icon="el-icon-user"></el-input>
  18. </el-form-item>
  19. <el-form-item label="密码" prop="password">
  20. <el-input v-model="userForm.password" prefix-icon="el-icon-lock" type="password" show-password></el-input>
  21. </el-form-item>
  22. <el-form-item label="确认密码" prop="confirmPassword">
  23. <el-input v-model="userForm.confirmPassword" prefix-icon="el-icon-lock" type="password" show-password></el-input>
  24. </el-form-item>
  25. <el-form-item>
  26. <el-button type="primary" @click="register('userForm')">注册</el-button>
  27. <el-button type="success" @click="$router.push('/login')">返回登录</el-button>
  28. <!-- <el-button type="warning" @click="resetForm('userForm')">重置</el-button>-->
  29. </el-form-item>
  30. </el-form>
  31. </div>
  32. </div>
  33. </div>
  34. </template>
  35. <script>
  36. export default {
  37. name: "Register",
  38. data(){
  39. return{
  40. userForm:{},
  41. rules: {
  42. username: [
  43. { required: true, message: '请输入用户名称', trigger: 'blur' },
  44. { min: 1, max: 20, message: '长度在 1 到 20 个字符', trigger: 'blur' }
  45. ],
  46. password: [
  47. { required: true, message: '请输入密码', trigger: 'blur' },
  48. { min: 6, max: 50, message: '长度不能小于6位', trigger: 'blur' }
  49. ],
  50. confirmPassword: [
  51. { required: true, message: '请输入确认密码', trigger: 'blur' },
  52. { min: 6, max: 50, message: '长度不能小于6位', trigger: 'blur' }
  53. ]
  54. }
  55. }
  56. },
  57. methods:{
  58. //提交数据
  59. register(formName) {
  60. this.$refs[formName].validate((valid) => {
  61. if (valid) {
  62. if(this.userForm.password !== this.userForm.confirmPassword){
  63. this.$message.warning("两次密码不一致,请重新输入");
  64. return;
  65. }
  66. alert('submit!');
  67. } else {
  68. console.log('error submit!!');
  69. return false;
  70. }
  71. });
  72. },
  73. // 重置数据
  74. resetForm(formName) {
  75. this.$refs[formName].resetFields();
  76. },
  77. // 验证码方法
  78. refreshCode(){
  79. this.identifyCode = ''
  80. this.makeCode(this.identifyCodes,4);
  81. },
  82. makeCode(o,l){
  83. for(let i = 0;i < l;i++){
  84. this.identifyCode += this.identifyCodes[
  85. this.randomNum(0,this.identifyCodes.length)
  86. ]
  87. }
  88. },
  89. randomNum(min,max){
  90. return Math.floor(Math.random() * (max - min) + min);
  91. }
  92. }
  93. }
  94. </script>
  95. <style scoped>
  96. .wrapper{
  97. background: url("../assets/auth.jpg");
  98. width:100%;
  99. height: 100%;
  100. position: fixed;
  101. background-size: 100% 100%;
  102. }
  103. </style>

       5.4 添加两个路由到index.js文件

6.登录注册接口

        创建UserDto类

  1. package com.example.authority.dto;
  2. import lombok.Data;
  3. import java.util.Date;
  4. @Data
  5. public class UserDto {
  6. private Integer id;
  7. private String username;
  8. private String password;
  9. private String nickname;
  10. private String headerUrl;
  11. private String token;
  12. }

编写两个接口代码,看视频

JwtUtils的代码

  1. package com.example.authority.utils;
  2. import com.auth0.jwt.JWT;
  3. import com.auth0.jwt.algorithms.Algorithm;
  4. import org.springframework.stereotype.Component;
  5. import java.util.Date;
  6. @Component
  7. public class JwtUtils {
  8. public static String generateToken(String userId,String sign){
  9. return JWT.create().withAudience(userId)
  10. .withExpiresAt(new Date(System.currentTimeMillis() + 3600 * 24 * 1000))
  11. .sign(Algorithm.HMAC256(sign));
  12. }
  13. }

7.菜单管理

用户管理的bug:

新增和修改用户名的时候加一下判断条件:用户名不能重复

角色管理的bug:

删除角色的时候先判断角色是否被使用了

RBAC:RBAC(Role-Based Access control) ,也就是基于角色的权限分配解决方案,相对于传统方案,RBAC提供了中间层Role(角色),其权限模式是给用户分配角色,给角色分配权限

菜单表:

字典管理,如视频操作

菜单后台代码,如视频操作

菜单页面:

  1. <template>
  2. <div>
  3. <div>
  4. <!-- 搜索栏-->
  5. <el-input style="width: 200px;margin-right: 20px" placeholder="请输入菜单名称" v-model="name" prefix-icon="el-icon-user"></el-input>
  6. <el-button style="margin-left: 10px;" type="primary" @click="load" class="el-icon-search">搜索</el-button>
  7. <el-button style="margin-left: 10px;" type="warning" @click="reset" class="el-icon-refresh">重置</el-button>
  8. </div>
  9. <div style="margin-top:20px;margin-bottom: 20px;">
  10. <!-- 新增。批量删除-->
  11. <el-button style="margin-right: 10px;" type="success" class="el-icon-plus" @click="save(null)">新增</el-button>
  12. <el-popconfirm
  13. confirm-button-text='确定'
  14. cancel-button-text='取消'
  15. icon="el-icon-info"
  16. icon-color="red"
  17. title="确定删除这些数据吗?"
  18. @confirm="deleteBatch"
  19. @cancel="cancel">
  20. <el-button slot="reference" type="danger" style="margin-left:5px;" class="el-icon-delete">批量删除</el-button>
  21. </el-popconfirm>
  22. </div>
  23. <el-table
  24. :data="tableData" border stripe
  25. :header-cell-style="getRowClass"
  26. @selection-change="handleSelectionChange"
  27. row-key="id"
  28. border
  29. default-expand-all>
  30. <el-table-column type="selection" width="55"></el-table-column>
  31. <el-table-column prop="id" label="id"></el-table-column>
  32. <el-table-column prop="name" label="菜单名称"></el-table-column>
  33. <el-table-column prop="path" label="菜单路径"></el-table-column>
  34. <el-table-column label="菜单图标">
  35. <template slot-scope="scope">
  36. <i :class="scope.row.icon"/>
  37. </template>
  38. </el-table-column>
  39. <el-table-column prop="description" label="菜单描述"></el-table-column>
  40. <el-table-column prop="pagePath" label="页面路径"></el-table-column>
  41. <el-table-column prop="sortNum" label="排序"></el-table-column>
  42. <el-table-column label="操作" width="350">
  43. <template slot-scope="scope">
  44. <el-button type="success" class="el-icon-circle-plus" @click="save(scope.row.id)" v-if="!scope.row.pid && !scope.row.path ">新增子菜单</el-button>
  45. <el-button type="primary" class="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
  46. <el-popconfirm
  47. confirm-button-text='确定'
  48. cancel-button-text='取消'
  49. icon="el-icon-info"
  50. icon-color="red"
  51. title="确定删除这些数据吗?"
  52. @confirm="handleDelete(scope.row.id)"
  53. @cancel="cancel">
  54. <el-button slot="reference" type="danger" style="margin-left:5px;" class="el-icon-delete">删除</el-button>
  55. </el-popconfirm>
  56. </template>
  57. </el-table-column>
  58. </el-table>
  59. <el-dialog title="菜单信息" :visible.sync="dialogFormVisible" width="30%">
  60. <el-form :model="form">
  61. <el-form-item label="菜单名称" :label-width="formLabelWidth">
  62. <el-input v-model="form.name" autocomplete="off"></el-input>
  63. </el-form-item>
  64. <el-form-item label="菜单路径" :label-width="formLabelWidth">
  65. <el-input v-model="form.path" autocomplete="off"></el-input>
  66. </el-form-item>
  67. <el-form-item label="菜单图标" :label-width="formLabelWidth">
  68. <el-select v-model="form.icon" filterable placeholder="请选择图标">
  69. <el-option
  70. v-for="dict in dictList"
  71. :key="dict.name"
  72. :label="dict.name"
  73. :value="dict.value">
  74. <i :class="dict.value"></i>&nbsp;&nbsp;{{dict.name}}
  75. </el-option>
  76. </el-select>
  77. </el-form-item>
  78. <el-form-item label="页面路径" :label-width="formLabelWidth">
  79. <el-input v-model="form.pagePath" autocomplete="off"></el-input>
  80. </el-form-item>
  81. <el-form-item label="排序" :label-width="formLabelWidth">
  82. <el-input v-model="form.sortNum" autocomplete="off" type="number"></el-input>
  83. </el-form-item>
  84. <el-form-item label="描述" :label-width="formLabelWidth">
  85. <el-input v-model="form.description" autocomplete="off"></el-input>
  86. </el-form-item>
  87. </el-form>
  88. <div slot="footer" class="dialog-footer">
  89. <el-button @click="dialogFormVisible = false">取 消</el-button>
  90. <el-button type="primary" @click="handleAdd">确 定</el-button>
  91. </div>
  92. </el-dialog>
  93. </div>
  94. </template>
  95. <script>
  96. export default {
  97. name: "Role",
  98. data(){
  99. return{
  100. pageSize:10,
  101. pageNum:1,
  102. name:'',
  103. tableData:[],
  104. dialogFormVisible:false,
  105. form:{},
  106. formLabelWidth: '80px',
  107. multipleSelection:[],
  108. total:0,
  109. dictList:[]
  110. }
  111. },
  112. created() {
  113. this.load();
  114. },
  115. methods:{
  116. load(){
  117. this.request.get("/menu/findAll",{
  118. params:{
  119. name:this.name,
  120. }
  121. }).then(res => {
  122. this.tableData = res.data;
  123. });
  124. this.request.get("/dict/findAll",{
  125. params:{
  126. type:'icon',
  127. }
  128. }).then(res => {
  129. this.dictList = res.data;
  130. })
  131. },
  132. getRowClass({rowIndex,columnIndex}){
  133. if(rowIndex === 0){
  134. return 'background:#ccc'
  135. }
  136. },
  137. reset(){
  138. this.name = '';
  139. this.load();
  140. },
  141. save(pid){
  142. this.dialogFormVisible = true;
  143. this.form = {};
  144. if(pid){
  145. this.form.pid = pid;
  146. }
  147. },
  148. handleAdd(){
  149. this.request.post("/menu/save",this.form).then(res => {
  150. if(res.code === '200'){
  151. if(this.form.id){
  152. this.$message.success('编辑成功');
  153. }else{
  154. this.$message.success('新增成功');
  155. }
  156. this.dialogFormVisible = false;
  157. this.load();
  158. }else{
  159. this.$message.error(res.msg)
  160. }
  161. })
  162. },
  163. handleEdit(row){
  164. this.form = JSON.parse(JSON.stringify(row));
  165. this.dialogFormVisible = true;
  166. },
  167. handleDelete(id){
  168. if(id){
  169. this.request.delete('/menu/deleteById/' + id).then(res => {
  170. if(res.code === '200'){
  171. this.$message.success('删除数据成功');
  172. this.load();
  173. }else{
  174. this.$message.error(res.msg)
  175. }
  176. })
  177. }else{
  178. this.$message.error('没有id信息,无法删除');
  179. }
  180. },
  181. cancel(){
  182. this.$message.success('取消操作成功');
  183. },
  184. handleSelectionChange(val) {
  185. this.multipleSelection = val;
  186. },
  187. deleteBatch(){
  188. //批量删除数据
  189. if(this.multipleSelection.length === 0){
  190. this.$message.warning("请先选择要删除的数据");
  191. return
  192. }
  193. const ids = this.multipleSelection.map(v => v.id);
  194. this.request.post('/menu/deleteBatch',ids).then(res => {
  195. if(res.code === '200'){
  196. this.$message.success('批量删除成功');
  197. this.load();
  198. }else{
  199. this.$message.error(res.msg);
  200. }
  201. })
  202. },
  203. handleSizeChange(val) {
  204. this.pageSize = val;
  205. this.load();
  206. },
  207. handleCurrentChange(val) {
  208. this.pageNum = val;
  209. this.load();
  210. }
  211. }
  212. }
  213. </script>
  214. <style scoped>
  215. </style>

8.角色分配权限

添加swagger配置类

  1. package com.example.authority.config;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import springfox.documentation.builders.ApiInfoBuilder;
  5. import springfox.documentation.builders.PathSelectors;
  6. import springfox.documentation.builders.RequestHandlerSelectors;
  7. import springfox.documentation.service.ApiInfo;
  8. import springfox.documentation.service.Contact;
  9. import springfox.documentation.spi.DocumentationType;
  10. import springfox.documentation.spring.web.plugins.Docket;
  11. import springfox.documentation.swagger2.annotations.EnableSwagger2;
  12. @Configuration
  13. public class SwaggerConfig {
  14. @Bean
  15. public Docket createRestApi() {
  16. return new Docket(DocumentationType.SWAGGER_2)
  17. .pathMapping("/")
  18. .select()
  19. .apis(RequestHandlerSelectors.basePackage("com.example.authority.controller")) //controller类所在的路径
  20. .paths(PathSelectors.any())
  21. .build().apiInfo(new ApiInfoBuilder()
  22. .title("SpringBoot整合Swagger")
  23. .description("SpringBoot整合Swagger,详细信息......")
  24. .version("9.0")
  25. .contact(new Contact("111","blog.csdn.net","www@gmail.com"))
  26. .license("hello")
  27. .licenseUrl("http://www.baidu.com")
  28. .build());
  29. }
  30. }

启动类添加  :@EnableSwagger2

  1. package com.example.authority;
  2. import org.mybatis.spring.annotation.MapperScan;
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. import springfox.documentation.swagger2.annotations.EnableSwagger2;
  6. @SpringBootApplication
  7. @MapperScan("com.example.authority.mapper")
  8. @EnableSwagger2
  9. public class BaseAuthorityApplication {
  10. public static void main(String[] args) {
  11. SpringApplication.run(BaseAuthorityApplication.class, args);
  12. }
  13. }

常用注解

@Api(tags = "用户管理"):加在controller类上做说明
@ApiOperation(value = "新增/修改用户信息"):加在接口方法上

全局异常处理

处理类

  1. package com.example.authority.exception;
  2. import com.example.authority.common.Result;
  3. import org.springframework.web.bind.annotation.ControllerAdvice;
  4. import org.springframework.web.bind.annotation.ExceptionHandler;
  5. import org.springframework.web.bind.annotation.ResponseBody;
  6. /**
  7. * @author wangjy
  8. * @version 1.0
  9. * @date 2023/6/30 15:29
  10. * 全局异常处理器
  11. */
  12. @ControllerAdvice
  13. public class GlobalExceptionHandler {
  14. @ExceptionHandler(AuthException.class)
  15. @ResponseBody
  16. public Result handle(AuthException ex){
  17. return Result.error(ex.getCode(),ex.getMessage());
  18. }
  19. @ExceptionHandler(Exception.class)
  20. @ResponseBody
  21. public Result handle(Exception ex){
  22. return Result.error("500",ex.getMessage());
  23. }
  24. }

自定义异常

  1. package com.example.authority.exception;
  2. import lombok.Data;
  3. @Data
  4. public class AuthException extends RuntimeException{
  5. private String code;
  6. public AuthException( String code,String msg) {
  7. super(msg);
  8. this.code = code;
  9. }
  10. }

在角色页面增加分配菜单功能,如视频操作

9.动态菜单和动态路由

动态菜单:根据角色分配的菜单在Aside页面动态显示

动态路由:动态路由,动态即不是写死的,是可变的。我们可以根据自己不同的需求加载不同的路由,做到不同的实现及页面的渲染。动态的路由存储可分为两种,一种是将路由存储到前端。另一种则是将路由存储到数据库。动态路由的使用一般结合角色权限控制一起使用。

具体代码看视频

10.后端拦截器

解决路由显示问题

  1. import Vue, {set} from 'vue'
  2. import VueRouter from 'vue-router'
  3. import store from '../store'
  4. Vue.use(VueRouter)
  5. import { Notification, MessageBox, Message, Loading } from 'element-ui'
  6. import ElementUI from 'element-ui'
  7. const routes = [
  8. {
  9. path: '/login',
  10. name: 'Login',
  11. component: () => import(/* webpackChunkName: "about" */ '../views/Login.vue'),
  12. },
  13. {
  14. path: '/register',
  15. name: 'Register',
  16. component: () => import(/* webpackChunkName: "about" */ '../views/Register.vue'),
  17. },
  18. {
  19. path: '/404',
  20. name: '404',
  21. component: () => import(/* webpackChunkName: "about" */ '../views/404.vue'),
  22. }
  23. ]
  24. const router = new VueRouter({
  25. mode: 'history',
  26. base: process.env.BASE_URL,
  27. routes
  28. })
  29. export const setRoutes = () => {
  30. //获取浏览器缓存的菜单数据
  31. const localMenus = localStorage.getItem("menus") ;
  32. if(localMenus){
  33. const currentRoutes = router.getRoutes().map(v => v.name);
  34. if(!currentRoutes.includes('manage')){
  35. //当前Router不包含manage,在拼装
  36. const manageRoute = {
  37. path: '/',
  38. name: 'manage',
  39. component: () => import(/* webpackChunkName: "about" */ '../views/Manage.vue'),
  40. children:[]
  41. };
  42. const menus = JSON.parse(localMenus);
  43. menus.forEach(item => {
  44. if(item.path){
  45. const itemMenu = {
  46. path:item.path.replace("/",""),
  47. name:item.name,
  48. component: () => import(/* webpackChunkName: "about" */ '../views/' + item.pagePath + '.vue'),
  49. };
  50. manageRoute.children.push(itemMenu);
  51. }else if(item.children.length){
  52. item.children.forEach(item => {
  53. const itemMenu = {
  54. path:item.path.replace("/",""),
  55. name:item.name,
  56. component: () => import(/* webpackChunkName: "about" */ '../views/' + item.pagePath + '.vue'),
  57. };
  58. manageRoute.children.push(itemMenu);
  59. })
  60. }
  61. })
  62. router.addRoute(manageRoute);
  63. console.log(router.getRoutes())
  64. }
  65. }
  66. }
  67. setRoutes()
  68. router.beforeEach((to,from,next) => {
  69. localStorage.setItem('currentPathName',to.name);
  70. store.commit('setPath')
  71. const localMenus = localStorage.getItem("menus");
  72. if(!to.matched.length){
  73. //没有匹配到路由(也就是未找到路由)
  74. if(localMenus){
  75. //用户登录了
  76. next('/404')
  77. }else{
  78. ElementUI.Message({
  79. message: '请先登录',
  80. type: 'warning'
  81. });
  82. next('/login')
  83. }
  84. }
  85. next();
  86. })
  87. export default router

后端拦截器代码:看视频,详细教导。

11.AOP记录日志

添加依赖:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-devtools</artifactId>
  4. <optional>true</optional>
  5. </dependency>
  6. <!-- AOP-->
  7. <dependency>
  8. <groupId>org.springframework.boot</groupId>
  9. <artifactId>spring-boot-starter-aop</artifactId>
  10. </dependency>

获取request对象

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

 剩下的代码看视频

12.日志前端页面,退出登录

        12.1.补充配置拦截器里面放行swagger

  1. @Override
  2. public void addInterceptors(InterceptorRegistry registry) {
  3. registry.addInterceptor(jwtInterceptor).addPathPatterns("/**").excludePathPatterns(
  4. "/swagger-resources/**"
  5. ,"/webjars/**"
  6. ,"/v2/**"
  7. ,"/swagger-ui.html/**"
  8. );
  9. }

        12.2.退出登录

                看视频操作

        12.3.重置路由器的路由集合

                看视频操作

        12.4.日志页面

  1. <template>
  2. <div>
  3. <div>
  4. <!-- 搜索栏-->
  5. <el-input style="width: 200px;margin-right: 20px" placeholder="请输入操作用户名称" v-model="username" prefix-icon="el-icon-user"></el-input>
  6. <el-input style="width: 200px;margin-right: 20px" placeholder="请输入操作用户名称" v-model="type" prefix-icon="el-icon-info"></el-input>
  7. <el-button style="margin-left: 10px;" type="primary" @click="load" class="el-icon-search">搜索</el-button>
  8. <el-button style="margin-left: 10px;" type="warning" @click="reset" class="el-icon-refresh">重置</el-button>
  9. </div>
  10. <div style="margin-top:20px;margin-bottom: 20px;">
  11. </div>
  12. <el-table :data="tableData" border stripe :header-cell-style="getRowClass" @selection-change="handleSelectionChange">
  13. <el-table-column type="selection" width="55"></el-table-column>
  14. <el-table-column prop="id" label="id"></el-table-column>
  15. <el-table-column prop="username" label="操作用户"></el-table-column>
  16. <el-table-column prop="record" label="操作记录"></el-table-column>
  17. <el-table-column prop="type" label="操作类型"></el-table-column>
  18. <el-table-column prop="createTime" label="创建时间"></el-table-column>
  19. </el-table>
  20. <div class="block" style="padding:10px 0;align-content: center;margin-left: 30%;margin-top:30px;">
  21. <el-pagination
  22. @size-change="handleSizeChange"
  23. @current-change="handleCurrentChange"
  24. :current-page="pageNum"
  25. :page-sizes="[5, 10, 15, 20]"
  26. :page-size="10"
  27. layout="total, sizes, prev, pager, next, jumper"
  28. :total="total">
  29. </el-pagination>
  30. </div>
  31. </div>
  32. </template>
  33. <script>
  34. export default {
  35. name: "Role",
  36. data(){
  37. return{
  38. pageSize:10,
  39. pageNum:1,
  40. name:'',
  41. tableData:[],
  42. dialogFormVisible:false,
  43. menuVisible:false,
  44. form:{},
  45. formLabelWidth: '80px',
  46. multipleSelection:[],
  47. total:0,
  48. menuData:[],
  49. checks:[],
  50. props: {
  51. children: 'children',
  52. label: 'name'
  53. }
  54. }
  55. },
  56. created() {
  57. this.load();
  58. },
  59. methods:{
  60. load(){
  61. this.request.get("/sysLog/findPage",{
  62. params:{
  63. pageNum:this.pageNum,
  64. pageSize:this.pageSize,
  65. username:this.username,
  66. type:this.type
  67. }
  68. }).then(res => {
  69. this.tableData = res.data.records;
  70. this.total = res.data.total;
  71. })
  72. },
  73. getRowClass({rowIndex,columnIndex}){
  74. if(rowIndex === 0){
  75. return 'background:#ccc'
  76. }
  77. },
  78. reset(){
  79. this.username = '';
  80. this.type = '';
  81. this.load();
  82. },
  83. handleSelectionChange(val) {
  84. this.multipleSelection = val;
  85. },
  86. handleSizeChange(val) {
  87. this.pageSize = val;
  88. this.load();
  89. },
  90. handleCurrentChange(val) {
  91. this.pageNum = val;
  92. this.load();
  93. },
  94. }
  95. }
  96. </script>
  97. <style scoped>
  98. </style>

13.文件管理

        13.1.文件表:

        13.2上传文件,下载文件接口

  1. package com.example.authority.controller;
  2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  3. import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
  4. import com.baomidou.mybatisplus.core.toolkit.StringUtils;
  5. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  6. import com.example.authority.annotation.Log;
  7. import com.example.authority.annotation.NoAuth;
  8. import com.example.authority.common.Result;
  9. import com.example.authority.entity.SysFile;
  10. import com.example.authority.service.SysFileService;
  11. import org.apache.commons.codec.digest.DigestUtils;
  12. import org.springframework.beans.factory.annotation.Autowired;
  13. import org.springframework.beans.factory.annotation.Value;
  14. import org.springframework.web.bind.annotation.*;
  15. import org.springframework.web.multipart.MultipartFile;
  16. import javax.servlet.ServletOutputStream;
  17. import javax.servlet.http.HttpServletResponse;
  18. import java.io.File;
  19. import java.io.FileInputStream;
  20. import java.io.IOException;
  21. import java.net.URLEncoder;
  22. import java.util.List;
  23. import java.util.UUID;
  24. @RestController
  25. @RequestMapping("/sysFile")
  26. public class SysFileController {
  27. @Autowired
  28. private SysFileService sysFileService;
  29. @Value("${files.upload.path}")
  30. private String fileUploadPath;
  31. /**
  32. * 批量删除文件
  33. * @param idList
  34. * @return
  35. */
  36. @PostMapping("/deleteBatch")
  37. @Log(record = "批量删除文件",type = "删除")
  38. public Result deleteBatch(@RequestBody List<Integer> idList){
  39. for (Integer id : idList) {
  40. SysFile sysFile = sysFileService.getById(id);
  41. sysFile.setIsDelete(1);
  42. sysFileService.updateById(sysFile);
  43. }
  44. return Result.success();
  45. }
  46. /**
  47. * 改变启用状态
  48. * @param sysFile
  49. * @return
  50. */
  51. @PostMapping("/updateEnable")
  52. @Log(record = "updateEnable",type = "修改")
  53. public Result updateEnable(@RequestBody SysFile sysFile){
  54. boolean b = sysFileService.updateById(sysFile);
  55. if(b){
  56. return Result.success();
  57. }else{
  58. return Result.error();
  59. }
  60. }
  61. /**
  62. * 根据id删除
  63. * @param id
  64. * @return
  65. */
  66. @DeleteMapping("/deleteById/{id}")
  67. @Log(record = "根据id删除文件",type = "删除")
  68. public Result deleteById(@PathVariable Integer id){
  69. SysFile sysFile = sysFileService.getById(id);
  70. sysFile.setIsDelete(1);
  71. boolean b = sysFileService.updateById(sysFile);
  72. if(b){
  73. return Result.success();
  74. }else{
  75. return Result.error();
  76. }
  77. }
  78. /**
  79. * 上传文件
  80. * @param file
  81. * @return
  82. */
  83. @PostMapping("/upload")
  84. @Log(record = "上传文件",type = "新增")
  85. @NoAuth
  86. public String upload(@RequestParam MultipartFile file) throws IOException {
  87. String md5 = DigestUtils.md5Hex(file.getBytes());
  88. String originalFilename = file.getOriginalFilename(); //文件的名称
  89. String type = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);//文件类型
  90. long size = file.getSize();
  91. File uploadParentFile = new File(fileUploadPath);
  92. if(!uploadParentFile.exists()){
  93. uploadParentFile.mkdirs();
  94. }
  95. List<SysFile> existFileList = sysFileService.getByMD5(md5);
  96. String url = null;
  97. if(CollectionUtils.isNotEmpty(existFileList)){
  98. //文件已经存在上传目录
  99. url = existFileList.get(0).getUrl();
  100. }else{
  101. //文件不存在上传目录
  102. String uuid = UUID.randomUUID().toString().replaceAll("-", "");
  103. String fileUUID = uuid + "." + type;
  104. File uploadFile = new File(fileUploadPath + fileUUID);
  105. url = "http://localhost:8888/sysFile/" + fileUUID;
  106. file.transferTo(uploadFile);
  107. }
  108. //存储数据库
  109. SysFile sysFile = new SysFile();
  110. sysFile.setName(originalFilename);
  111. sysFile.setSize(size / 1024);
  112. sysFile.setType(type);
  113. sysFile.setUrl(url);
  114. sysFile.setMd5(md5);
  115. sysFileService.save(sysFile);
  116. return url;
  117. }
  118. /**
  119. * 下载文件
  120. * @param fileUUID
  121. */
  122. @GetMapping("/{fileUUID}")
  123. @NoAuth
  124. public void download(@PathVariable String fileUUID, HttpServletResponse response){
  125. File downloadFile = new File(fileUploadPath + fileUUID);
  126. try {
  127. FileInputStream fileInputStream = new FileInputStream(downloadFile);
  128. // 设置输出流的格式
  129. response.setCharacterEncoding("UTF-8");
  130. response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileUUID, "UTF-8"));
  131. //作用是使客户端浏览器区分不同种类的数据,并根据不同的MIME调用浏览器内不同的程序嵌入模块来处理相应的数据。
  132. response.setContentType("application/octet-stream"); //.*( 二进制流,不知道下载文件类型)
  133. ServletOutputStream outputStream = response.getOutputStream();
  134. int len = 0;
  135. byte[] bytes = new byte[1024];
  136. while((len = fileInputStream.read(bytes)) != -1){
  137. outputStream.write(bytes,0,len);
  138. outputStream.flush();
  139. }
  140. outputStream.flush();
  141. outputStream.close();
  142. fileInputStream.close();
  143. } catch (IOException e) {
  144. e.printStackTrace();
  145. }
  146. }
  147. /**
  148. * 查询全部数据
  149. * @return
  150. */
  151. @GetMapping("/findAll")
  152. @Log(record = "查询全部文件",type = "查询")
  153. public Result findAll(@RequestParam(name = "type",defaultValue = "") String type){
  154. QueryWrapper<SysFile> queryWrapper = new QueryWrapper<>();
  155. if(StringUtils.isNotBlank(type)){
  156. queryWrapper.eq("type",type);
  157. }
  158. return Result.success(sysFileService.list(queryWrapper));
  159. }
  160. /**
  161. * 分页查询
  162. * @param pageNum:页码
  163. * @param pageSize:每页条数
  164. * @param name:角色名称
  165. * @return
  166. */
  167. @GetMapping("/findPage")
  168. @Log(record = "查询文件分页",type = "查询")
  169. public Result findPage(@RequestParam Integer pageNum,
  170. @RequestParam Integer pageSize,
  171. @RequestParam(name = "name",defaultValue = "") String name){
  172. Page<SysFile> page = new Page<>(pageNum,pageSize);
  173. QueryWrapper<SysFile> queryWrapper = new QueryWrapper<>();
  174. queryWrapper.eq("is_delete",0);
  175. if(StringUtils.isNotBlank(name)){
  176. queryWrapper.like("name",name);
  177. }
  178. Page<SysFile> sysFilePage = sysFileService.page(page, queryWrapper);
  179. return Result.success(sysFilePage);
  180. }
  181. }

        13.3前端页面

  1. <template>
  2. <div>
  3. <div>
  4. <!-- 搜索栏-->
  5. <el-input style="width: 200px;margin-right: 20px" placeholder="请输入名称" v-model="name" prefix-icon="el-icon-user"></el-input>
  6. <el-button style="margin-left: 10px;" type="primary" @click="load" class="el-icon-search">搜索</el-button>
  7. <el-button style="margin-left: 10px;" type="warning" @click="reset" class="el-icon-refresh">重置</el-button>
  8. </div>
  9. <div style="margin-top:20px;margin-bottom: 20px;">
  10. <!-- 新增。批量删除-->
  11. <el-upload
  12. style="display: inline-block"
  13. action="http://localhost:8888/sysFile/upload"
  14. :show-file-list="false"
  15. :on-success="handleAvatarSuccess"
  16. :on-error="handleAvatarError">
  17. <el-button style="margin-right: 10px;" type="primary" class="el-icon-plus">上传文件</el-button>
  18. </el-upload>
  19. <el-popconfirm
  20. confirm-button-text='确定'
  21. cancel-button-text='取消'
  22. icon="el-icon-info"
  23. icon-color="red"
  24. title="确定删除这些数据吗?"
  25. @confirm="deleteBatch"
  26. @cancel="cancel">
  27. <el-button slot="reference" type="danger" style="margin-left:5px;" class="el-icon-delete">批量删除</el-button>
  28. </el-popconfirm>
  29. </div>
  30. <el-table :data="tableData" border stripe :header-cell-style="getRowClass" @selection-change="handleSelectionChange">
  31. <el-table-column type="selection" width="55"></el-table-column>
  32. <el-table-column prop="id" label="id"></el-table-column>
  33. <el-table-column prop="name" label="文件名称"></el-table-column>
  34. <el-table-column prop="type" label="文件类型"></el-table-column>
  35. <el-table-column prop="size" label="文件大小(kb)"></el-table-column>
  36. <el-table-column prop="enable" label="是否启用">
  37. <template slot-scope="scope">
  38. <el-switch
  39. v-model="scope.row.enable"
  40. active-color="#13ce66"
  41. inactive-color="#ff4949"
  42. @change="changeEnable(scope.row)"
  43. :active-value="1"
  44. :inactive-value="0">
  45. </el-switch>
  46. </template>
  47. </el-table-column>
  48. <el-table-column label="操作" width="300">
  49. <template slot-scope="scope">
  50. <el-button type="success" class="el-icon-view" @click="viewImage(scope.row.url)">预览</el-button>
  51. <el-button type="primary" class="el-icon-download" @click="download(scope.row.url)" v-if="scope.row.enable === 1">下载</el-button>
  52. <el-popconfirm
  53. confirm-button-text='确定'
  54. cancel-button-text='取消'
  55. icon="el-icon-info"
  56. icon-color="red"
  57. title="确定删除这些数据吗?"
  58. @confirm="handleDelete(scope.row.id)"
  59. @cancel="cancel">
  60. <el-button slot="reference" type="danger" style="margin-left:5px;" class="el-icon-delete">删除</el-button>
  61. </el-popconfirm>
  62. </template>
  63. </el-table-column>
  64. </el-table>
  65. <div class="block" style="padding:10px 0;align-content: center;margin-left: 30%;margin-top:30px;">
  66. <el-pagination
  67. @size-change="handleSizeChange"
  68. @current-change="handleCurrentChange"
  69. :current-page="pageNum"
  70. :page-sizes="[5, 10, 15, 20]"
  71. :page-size="10"
  72. layout="total, sizes, prev, pager, next, jumper"
  73. :total="total">
  74. </el-pagination>
  75. </div>
  76. <el-dialog title="字典信息" :visible.sync="viewVisible" width="30%">
  77. <img width="100%" :src="dialogImageUrl" alt="图片"/>
  78. </el-dialog>
  79. </div>
  80. </template>
  81. <script>
  82. export default {
  83. name: "Role",
  84. data(){
  85. return{
  86. pageSize:10,
  87. pageNum:1,
  88. name:'',
  89. tableData:[],
  90. dialogImageUrl:'',
  91. dialogFormVisible:false,
  92. viewVisible:false,
  93. form:{},
  94. formLabelWidth: '80px',
  95. multipleSelection:[],
  96. total:0
  97. }
  98. },
  99. created() {
  100. this.load();
  101. },
  102. methods:{
  103. viewImage(url){
  104. this.dialogImageUrl = url;
  105. this.viewVisible = true;
  106. },
  107. changeEnable(row){
  108. this.request.post('/sysFile/updateEnable',row).then(res => {
  109. if(res.code === '200'){
  110. this.$message.success('更新启用状态成功')
  111. }else{
  112. this.$message.error('更新启用状态失败')
  113. }
  114. })
  115. },
  116. load(){
  117. this.request.get("/sysFile/findPage",{
  118. params:{
  119. pageNum:this.pageNum,
  120. pageSize:this.pageSize,
  121. name:this.name,
  122. }
  123. }).then(res => {
  124. this.tableData = res.data.records;
  125. this.total = res.data.total;
  126. })
  127. },
  128. getRowClass({rowIndex,columnIndex}){
  129. if(rowIndex === 0){
  130. return 'background:#ccc'
  131. }
  132. },
  133. reset(){
  134. this.name = '';
  135. this.load();
  136. },
  137. save(){
  138. this.dialogFormVisible = true;
  139. this.form = {};
  140. },
  141. handleAdd(){
  142. this.request.post("/sysFile/save",this.form).then(res => {
  143. if(res.code === '200'){
  144. if(this.form.id){
  145. this.$message.success('编辑成功');
  146. }else{
  147. this.$message.success('新增成功');
  148. }
  149. this.dialogFormVisible = false;
  150. this.load();
  151. }else{
  152. this.$message.error(res.msg)
  153. }
  154. })
  155. },
  156. handleEdit(row){
  157. this.form = JSON.parse(JSON.stringify(row));
  158. this.dialogFormVisible = true;
  159. },
  160. handleDelete(id){
  161. if(id){
  162. this.request.delete('/sysFile/deleteById/' + id).then(res => {
  163. if(res.code === '200'){
  164. this.$message.success('删除数据成功');
  165. this.handleCalPageNum();
  166. }else{
  167. this.$message.error(res.msg)
  168. }
  169. })
  170. }else{
  171. this.$message.error('没有id信息,无法删除');
  172. }
  173. },
  174. cancel(){
  175. this.$message.success('取消操作成功');
  176. },
  177. handleSelectionChange(val) {
  178. this.multipleSelection = val;
  179. },
  180. deleteBatch(){
  181. //批量删除数据
  182. if(this.multipleSelection.length === 0){
  183. this.$message.warning("请先选择要删除的数据");
  184. return
  185. }
  186. const ids = this.multipleSelection.map(v => v.id);
  187. this.request.post('/sysFile/deleteBatch',ids).then(res => {
  188. if(res.code === '200'){
  189. this.$message.success('批量删除成功');
  190. this.handleCalPageNum();
  191. }else{
  192. this.$message.error(res.msg);
  193. }
  194. })
  195. },
  196. handleSizeChange(val) {
  197. this.pageSize = val;
  198. this.load();
  199. },
  200. handleCurrentChange(val) {
  201. this.pageNum = val;
  202. this.load();
  203. },
  204. handleCalPageNum(){
  205. this.request.get("/sysFile/findPage",{
  206. params:{
  207. pageNum:this.pageNum,
  208. pageSize:this.pageSize,
  209. name:this.name,
  210. }
  211. }).then(res => {
  212. this.total = res.data.total;
  213. this.pageNum = (this.total % this.pageSize === 0) ? (this.total / this.pageSize) : Math.floor((this.total / this.pageSize) + 1);
  214. if(this.pageNum < 1){
  215. this.pageNum = 1;
  216. }
  217. this.load();
  218. })
  219. },
  220. handleAvatarSuccess(res, file) {
  221. this.$message.success('上传文件成功!');
  222. this.load();
  223. },
  224. handleAvatarError() {
  225. this.$message.error('上传文件失败!');
  226. },
  227. download(url){
  228. window.open(url);
  229. }
  230. }
  231. }
  232. </script>
  233. <style scoped>
  234. .avatar-uploader .el-upload {
  235. border: 1px dashed #d9d9d9;
  236. border-radius: 6px;
  237. cursor: pointer;
  238. position: relative;
  239. overflow: hidden;
  240. }
  241. .avatar-uploader .el-upload:hover {
  242. border-color: #409EFF;
  243. }
  244. .avatar-uploader-icon {
  245. font-size: 28px;
  246. color: #8c939d;
  247. width: 178px;
  248. height: 178px;
  249. line-height: 178px;
  250. text-align: center;
  251. }
  252. .avatar {
  253. width: 178px;
  254. height: 178px;
  255. display: block;
  256. }
  257. </style>

网页显示图片的配置,

InterceptorConfig类
/**
 * 映射路径修改:这段代码意思就配置一个拦截器, 如果访问路径是addResourceHandler中的filepath 这个路径
 * 那么就 映射到访问本地的addResourceLocations 的参数的这个路径上,
 * 这样就可以让别人访问服务器的本地文件了,比如本地图片或者本地音乐视频什么的。
 * @param registry
 */
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/sysFile/show/**").addResourceLocations("file:D:\\temp\\files\\");
}

14.文章管理

文章表:

后端代码,参考视频

安装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替换到文本原位置![...](./0) -> ![...](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"/>

前端页面

  1. <template>
  2. <div>
  3. <div>
  4. <!-- 搜索栏-->
  5. <el-input style="width: 200px;margin-right: 20px" placeholder="请输入文章名称" v-model="name" prefix-icon="el-icon-user"></el-input>
  6. <el-input style="width: 200px;margin-right: 20px" placeholder="请输入创建者名称" v-model="user" prefix-icon="el-icon-user"></el-input>
  7. <el-button style="margin-left: 10px;" type="primary" @click="load" class="el-icon-search">搜索</el-button>
  8. <el-button style="margin-left: 10px;" type="warning" @click="reset" class="el-icon-refresh">重置</el-button>
  9. </div>
  10. <div style="margin-top:20px;margin-bottom: 20px;">
  11. <!-- 新增。批量删除-->
  12. <el-button style="margin-right: 10px;" type="success" class="el-icon-plus" @click="save">新增</el-button>
  13. <el-popconfirm
  14. confirm-button-text='确定'
  15. cancel-button-text='取消'
  16. icon="el-icon-info"
  17. icon-color="red"
  18. title="确定删除这些数据吗?"
  19. @confirm="deleteBatch"
  20. @cancel="cancel">
  21. <el-button slot="reference" type="danger" style="margin-left:5px;" class="el-icon-delete">批量删除</el-button>
  22. </el-popconfirm>
  23. </div>
  24. <el-table :data="tableData" border stripe :header-cell-style="getRowClass" @selection-change="handleSelectionChange">
  25. <el-table-column type="selection" width="55"></el-table-column>
  26. <el-table-column prop="id" label="id"></el-table-column>
  27. <el-table-column prop="name" label="文章名称"></el-table-column>
  28. <el-table-column prop="content" label="文章内容">
  29. <template slot-scope="scope">
  30. <el-button type="primary" class="el-icon-view" @click="view(scope.row.content)">查看内容</el-button>
  31. </template>
  32. </el-table-column>
  33. <el-table-column prop="value" label="创建用户"></el-table-column>
  34. <el-table-column prop="type" label="类型"></el-table-column>
  35. <el-table-column prop="createTime" label="创建时间"></el-table-column>
  36. <el-table-column label="操作" width="200">
  37. <template slot-scope="scope">
  38. <el-button type="primary" class="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
  39. <el-popconfirm
  40. confirm-button-text='确定'
  41. cancel-button-text='取消'
  42. icon="el-icon-info"
  43. icon-color="red"
  44. title="确定删除这些数据吗?"
  45. @confirm="handleDelete(scope.row.id)"
  46. @cancel="cancel">
  47. <el-button slot="reference" type="danger" style="margin-left:5px;" class="el-icon-delete">删除</el-button>
  48. </el-popconfirm>
  49. </template>
  50. </el-table-column>
  51. </el-table>
  52. <div class="block" style="padding:10px 0;align-content: center;margin-left: 30%;margin-top:30px;">
  53. <el-pagination
  54. @size-change="handleSizeChange"
  55. @current-change="handleCurrentChange"
  56. :current-page="pageNum"
  57. :page-sizes="[5, 10, 15, 20]"
  58. :page-size="10"
  59. layout="total, sizes, prev, pager, next, jumper"
  60. :total="total">
  61. </el-pagination>
  62. </div>
  63. <el-dialog title="文章信息" :visible.sync="dialogFormVisible" width="60%">
  64. <el-form :model="form">
  65. <el-form-item label="文章名称" :label-width="formLabelWidth">
  66. <el-input v-model="form.name" autocomplete="off"></el-input>
  67. </el-form-item>
  68. <el-form-item label="文章内容" :label-width="formLabelWidth">
  69. <mavon-editor ref="md" v-model="form.content" :ishljs="true" @imgAdd="imgAdd"/>
  70. </el-form-item>
  71. <el-form-item label="类型" :label-width="formLabelWidth">
  72. <el-input v-model="form.type" autocomplete="off"></el-input>
  73. </el-form-item>
  74. </el-form>
  75. <div slot="footer" class="dialog-footer">
  76. <el-button @click="dialogFormVisible = false">取 消</el-button>
  77. <el-button type="primary" @click="handleAdd">确 定</el-button>
  78. </div>
  79. </el-dialog>
  80. <el-dialog title="文章内容展示" :visible.sync="contentVisible" width="60%">
  81. <el-card>
  82. <mavon-editor
  83. class="md"
  84. :value="content"
  85. :subfield="false"
  86. :defaultOpen="'preview'"
  87. :toolbarsFlag="false"
  88. :editable="false"
  89. :scrollStyle="true"
  90. :ishljs="true"
  91. />
  92. </el-card>
  93. </el-dialog>
  94. </div>
  95. </template>
  96. <script>
  97. import axios from "axios";
  98. export default {
  99. name: "Article",
  100. data(){
  101. return{
  102. pageSize:10,
  103. pageNum:1,
  104. name:'',
  105. user:'',
  106. content:'',
  107. tableData:[],
  108. dialogFormVisible:false,
  109. contentVisible:false,
  110. form:{},
  111. formLabelWidth: '80px',
  112. multipleSelection:[],
  113. total:0
  114. }
  115. },
  116. created() {
  117. this.load();
  118. },
  119. methods:{
  120. view(content){
  121. this.content = content;
  122. this.contentVisible = true;
  123. },
  124. // 绑定@imgAdd event
  125. imgAdd(pos, $file) {
  126. let $vm = this.$refs.md
  127. // 第一步.将图片上传到服务器.
  128. const formData = new FormData();
  129. formData.append('file', $file);
  130. axios({
  131. url: 'http://localhost:8888/sysFile/upload',
  132. method: 'post',
  133. data: formData,
  134. headers: {'Content-Type': 'multipart/form-data'},
  135. }).then((res) => {
  136. console.log(res)
  137. // 第二步.将返回的url替换到文本原位置![...](./0) -> ![...](url)
  138. $vm.$img2Url(pos, res.data);
  139. })
  140. },
  141. load(){
  142. this.request.get("/article/findPage",{
  143. params:{
  144. pageNum:this.pageNum,
  145. pageSize:this.pageSize,
  146. name:this.name,
  147. }
  148. }).then(res => {
  149. this.tableData = res.data.records;
  150. this.total = res.data.total;
  151. })
  152. },
  153. getRowClass({rowIndex,columnIndex}){
  154. if(rowIndex === 0){
  155. return 'background:#ccc'
  156. }
  157. },
  158. reset(){
  159. this.name = '';
  160. this.user = '';
  161. this.load();
  162. },
  163. save(){
  164. this.dialogFormVisible = true;
  165. this.form = {};
  166. },
  167. handleAdd(){
  168. this.request.post("/article/save",this.form).then(res => {
  169. if(res.code === '200'){
  170. if(this.form.id){
  171. this.$message.success('编辑成功');
  172. }else{
  173. this.$message.success('新增成功');
  174. }
  175. this.dialogFormVisible = false;
  176. this.load();
  177. }else{
  178. this.$message.error(res.msg)
  179. }
  180. })
  181. },
  182. handleEdit(row){
  183. this.form = JSON.parse(JSON.stringify(row));
  184. this.dialogFormVisible = true;
  185. },
  186. handleDelete(id){
  187. if(id){
  188. this.request.delete('/article/deleteById/' + id).then(res => {
  189. if(res.code === '200'){
  190. this.$message.success('删除数据成功');
  191. this.handleCalPageNum();
  192. }else{
  193. this.$message.error(res.msg)
  194. }
  195. })
  196. }else{
  197. this.$message.error('没有id信息,无法删除');
  198. }
  199. },
  200. cancel(){
  201. this.$message.success('取消操作成功');
  202. },
  203. handleSelectionChange(val) {
  204. this.multipleSelection = val;
  205. },
  206. deleteBatch(){
  207. //批量删除数据
  208. if(this.multipleSelection.length === 0){
  209. this.$message.warning("请先选择要删除的数据");
  210. return
  211. }
  212. const ids = this.multipleSelection.map(v => v.id);
  213. this.request.post('/article/deleteBatch',ids).then(res => {
  214. if(res.code === '200'){
  215. this.$message.success('批量删除成功');
  216. this.handleCalPageNum();
  217. }else{
  218. this.$message.error(res.msg);
  219. }
  220. })
  221. },
  222. handleSizeChange(val) {
  223. this.pageSize = val;
  224. this.load();
  225. },
  226. handleCurrentChange(val) {
  227. this.pageNum = val;
  228. this.load();
  229. },
  230. handleCalPageNum(){
  231. this.request.get("/article/findPage",{
  232. params:{
  233. pageNum:this.pageNum,
  234. pageSize:this.pageSize,
  235. name:this.name,
  236. user:this.user,
  237. }
  238. }).then(res => {
  239. this.total = res.data.total;
  240. this.pageNum = (this.total % this.pageSize === 0) ? (this.total / this.pageSize) : Math.floor((this.total / this.pageSize) + 1);
  241. if(this.pageNum < 1){
  242. this.pageNum = 1;
  243. }
  244. this.load();
  245. })
  246. }
  247. }
  248. }
  249. </script>
  250. <style scoped>
  251. </style>

15.公告和轮播图管理

        公告表,轮播图表

        后端代码

        前端页面

看视频操作,代码较多

16.个人信息页面和修改密码页面

        参考视频

17.整合Echarts搭建后台首页

Home页面

  1. <template>
  2. <div>
  3. <el-row :gutter="20">
  4. <el-col :span="6">
  5. <el-card>权限项目</el-card>
  6. </el-col>
  7. <el-col :span="6">
  8. <el-card>毕设项目</el-card>
  9. </el-col>
  10. <el-col :span="6">
  11. <el-card>前后端分离</el-card>
  12. </el-col>
  13. <el-col :span="6">
  14. <el-card>脚手架系统</el-card>
  15. </el-col>
  16. </el-row>
  17. <el-row :gutter="20">
  18. <el-col :span="8">
  19. <div style="width:400px;height:400px" id="main">
  20. </div>
  21. </el-col>
  22. <el-col :span="8">
  23. <div style="width:400px;height:400px" id="main1">
  24. </div>
  25. </el-col>
  26. <el-col :span="8">
  27. <div style="width:400px;height:400px" id="main2">
  28. </div>
  29. </el-col>
  30. </el-row>
  31. </div>
  32. </template>
  33. <script>
  34. import * as echarts from 'echarts';
  35. export default {
  36. name: "Home",
  37. mounted() {
  38. var chartDom = document.getElementById('main');
  39. var myChart = echarts.init(chartDom);
  40. var option;
  41. option = {
  42. xAxis: {
  43. type: 'category',
  44. data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
  45. },
  46. yAxis: {
  47. type: 'value'
  48. },
  49. series: [
  50. {
  51. data: [820, 932, 901, 934, 1290, 1330, 1320],
  52. type: 'line',
  53. smooth: true
  54. }
  55. ]
  56. };
  57. var chartDom1 = document.getElementById('main1');
  58. var myChart1 = echarts.init(chartDom1);
  59. var option1;
  60. option1 = {
  61. xAxis: {
  62. type: 'category',
  63. data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
  64. },
  65. yAxis: {
  66. type: 'value'
  67. },
  68. series: [
  69. {
  70. data: [120, 200, 150, 80, 70, 110, 130],
  71. type: 'bar',
  72. showBackground: true,
  73. backgroundStyle: {
  74. color: 'rgba(180, 180, 180, 0.2)'
  75. }
  76. }
  77. ]
  78. };
  79. var chartDom2 = document.getElementById('main2');
  80. var myChart2 = echarts.init(chartDom2);
  81. var option2;
  82. option2 = {
  83. tooltip: {
  84. trigger: 'item'
  85. },
  86. legend: {
  87. top: '5%',
  88. left: 'center'
  89. },
  90. series: [
  91. {
  92. name: 'Access From',
  93. type: 'pie',
  94. radius: ['40%', '70%'],
  95. avoidLabelOverlap: false,
  96. itemStyle: {
  97. borderRadius: 10,
  98. borderColor: '#fff',
  99. borderWidth: 2
  100. },
  101. label: {
  102. show: false,
  103. position: 'center'
  104. },
  105. emphasis: {
  106. label: {
  107. show: true,
  108. fontSize: 40,
  109. fontWeight: 'bold'
  110. }
  111. },
  112. labelLine: {
  113. show: false
  114. },
  115. data: [
  116. { value: 1048, name: 'Search Engine' },
  117. { value: 735, name: 'Direct' },
  118. { value: 580, name: 'Email' },
  119. { value: 484, name: 'Union Ads' },
  120. { value: 300, name: 'Video Ads' }
  121. ]
  122. }
  123. ]
  124. };
  125. this.request.get('/user/echart').then(res => {
  126. if(res.code === '200'){
  127. option.xAxis.data = res.data.list1; //名称的集合
  128. option.series[0].data = res.data.list2; //数值的集合
  129. option && myChart.setOption(option);
  130. option1.xAxis.data = res.data.list1; //名称的集合
  131. option1.series[0].data = res.data.list2; //数值的集合
  132. option1 && myChart1.setOption(option1);
  133. option2.series[0].data = res.data.list3;
  134. option2 && myChart2.setOption(option2);
  135. }
  136. })
  137. }
  138. }
  139. </script>
  140. <style scoped>
  141. </style>

18.前台首页搭建

app.vue中不但可以当做是网站首页,也可以写所有页面中公共需要的动画或者样式。

app.vue是vue页面资源的首加载项,是主组件,页面入口文件,所有页面都是在App.vue下进行切换的;也是整个项目的关键,app.vue负责构建定义及页面组件归集。

index.html---主页,项目入口

App.vue---根组件

main.js---入口文件

        Front.vue和Home.vue页面,实现轮播图和项目介绍,头部展示基本信息

19.前台文章列表显示和文章详情

        看视频操作,代码较多不复制了

20.前台公告列表和项目总结

        看视频操作,代码较多不复制了

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小舞很执着/article/detail/774145
推荐阅读
相关标签
  

闽ICP备14008679号