编程新技术实务二

前后端分离实现后台管理系统,并部署到服务器

Posted by Tang Jibin on November 3, 2021

前言

其实实验二我早就已经完成了,但是最近实在没有时间,所以今天才更新博客 我是第一个验收实验二的,允许验收实验二的第一堂课我就去验收了,最后也是拿到了最高评级 A+ (这里还是要吐槽一句为什么最高评级都只有 95 ,就不能给 100 吗 ) 你可以通过超链接来访问我的后台管理系统,不过等你看到这篇博文的时候,应该已经访问不了了(不要问我为什么,因为穷,服务器是白嫖的第一个月),先来看看效果咋样吧

  • 登录界面:

  • users 界面:

  • person 界面:

其他还有很多小功能,可以访问我的后台管理系统来体验,也可以 clone 我的 GitHub 项目在本地运行,觉得还行的话欢迎 star 和 fork

实验内容

实验的大致内容是

编写web页面,完成对实验一数据库的操作

但是如果按照实验文档中的去写实在太垃圾了,感觉有点跟不上时代了,所以我打算用目前前端最主流的框架—— Vue ,怎么安装的就不打算说,谷歌百度一大堆

本来想自己写布局和页面的,但是前端重复造轮子的东西实在太多,不如用别人的项目模板进行修改,所以我这里用的基础 Vue 模板是 PanJiaChen 大佬的 vue-admin-template 模板,感谢 PanJiaChen 大佬开源了这么简洁但又好看的 Vue 模板

实验过程

步骤一 完成后端 service 层

首先我们先完成 SpringBoot 项目中要给 controller 层提供服务的 service 层,我们在这里将接口和实现放入两个包,接口放入 service 包,实现放入 servicelmpl 包,这样显得更加清晰明了,如下图所示:

具体实现如下:(有些服务 contoller 层不一定会用到,但我们先写上去)

PersonService.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.tjb.backend.service;

import com.github.pagehelper.PageInfo;
import com.tjb.backend.bean.PersonBean;

import java.util.List;

public interface PersonService {

    int createPersontable();

    int insertPerson(PersonBean personBean);

    int deletePerson(String usersname);

    int deletebeginPerson(String usersname);

    int updatePerson(PersonBean personBean);

    List<PersonBean> getPerson(String usersname);

    List<PersonBean> getlikePerson(String usersname);

    List<PersonBean> getAllPerson();

    //分页查询
    PageInfo<PersonBean> getPageInfo(int page, int limit, String username, String type);

    int dropPersontable();

}

UsersService.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.tjb.backend.service;

import com.github.pagehelper.PageInfo;
import com.tjb.backend.bean.UsersBean;

import java.util.List;

public interface UsersService {

    int createUserstable();

    int insertUsers(UsersBean usersBean);

    int deleteUsers(String usersname);

    int deletebeginUsers(String usersname);

    int updateUsers(UsersBean usersBean);

    List<UsersBean> getUsers(String usersname);

    List<UsersBean> getlikeUsers(String usersname);

    List<UsersBean> getAllUsers();

    //分页查询
    PageInfo<UsersBean> getPageInfo(int page, int limit, String username, String type);

    int dropUserstable();

}

PersonServicelmpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package com.tjb.backend.servicelmpl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.tjb.backend.bean.PersonBean;
import com.tjb.backend.bean.UsersBean;
import com.tjb.backend.mapper.PersonMapper;
import com.tjb.backend.mapper.UsersMapper;
import com.tjb.backend.service.PersonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Transactional
public class PersonServicelmpl implements PersonService {

    @Autowired
    UsersMapper usersMapper;

    @Autowired
    PersonMapper personMapper;

    @Override
    public int createPersontable() {
        return personMapper.createPersontable();
    }

    @Override
    public int insertPerson(PersonBean personBean) {
        if((usersMapper.getlikeUsers(personBean.getUsername())).isEmpty()) {
            usersMapper.insertUsers(new UsersBean(personBean.getUsername(),"888888"));
        }
        return personMapper.insertPerson(personBean);
    }

    @Override
    public int deletePerson(String usersname) {
        return personMapper.deletelikePerson(usersname);
    }

    @Override
    public int deletebeginPerson(String usersname) {
        return personMapper.deletelikePerson(usersname + "%");
    }

    @Override
    public int updatePerson(PersonBean personBean) {
        return personMapper.updatePerson(personBean);
    }

    @Override
    public List<PersonBean> getPerson(String usersname) {
        return personMapper.getlikePerson(usersname);
    }

    @Override
    public List<PersonBean> getlikePerson(String usersname) {
        return personMapper.getlikePerson("%" + usersname + "%");
    }

    @Override
    public List<PersonBean> getAllPerson() {
        return personMapper.getAllPerson();
    }

    @Override
    //分页查询
    public PageInfo<PersonBean> getPageInfo(int page, int limit, String username, String type) {
        PageHelper.startPage(page, limit,true);
        List<PersonBean> person;
        if(username == null || username.equals("") || type == null || type.equals("")) person = personMapper.getAllPerson();
        else if(type.equals("exact")) person = personMapper.getlikePerson(username);
        else person = personMapper.getlikePerson("%" + username + "%");
        return new PageInfo<>(person);
    }

    @Override
    public int dropPersontable() {
        return personMapper.dropPersontable();
    }

}

UsersServicelmpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package com.tjb.backend.servicelmpl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.tjb.backend.bean.UsersBean;
import com.tjb.backend.mapper.PersonMapper;
import com.tjb.backend.mapper.UsersMapper;
import com.tjb.backend.service.UsersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Transactional
public class UsersServicelmpl implements UsersService {

    @Autowired
    UsersMapper usersMapper;

    @Autowired
    PersonMapper personMapper;

    @Override
    public int createUserstable() {
        return usersMapper.createUserstable();
    }

    @Override
    public int insertUsers(UsersBean usersBean) {
        return usersMapper.insertUsers(usersBean);
    }

    @Override
    public int deleteUsers(String usersname) {
        if(personMapper.getlikePerson(usersname) != null) personMapper.deletelikePerson(usersname);
        return usersMapper.deletelikeUsers(usersname);
    }

    @Override
    public int deletebeginUsers(String usersname) {
        if(personMapper.getlikePerson(usersname + "%") != null) personMapper.deletelikePerson(usersname + "%");
        return usersMapper.deletelikeUsers(usersname + "%");
    }

    @Override
    public int updateUsers(UsersBean usersBean) {
        return usersMapper.updateUsers(usersBean);
    }

    @Override
    public List<UsersBean> getUsers(String usersname) {
        return usersMapper.getlikeUsers(usersname);
    }

    @Override
    public List<UsersBean> getlikeUsers(String usersname) {
        return usersMapper.getlikeUsers("%" + usersname + "%");
    }

    @Override
    public List<UsersBean> getAllUsers() {
        return usersMapper.getAllUsers();
    }

    @Override
    //分页查询
    public PageInfo<UsersBean> getPageInfo(int page, int limit, String username, String type) {
        PageHelper.startPage(page, limit,true);
        List<UsersBean> users;
        if(username == null || username.equals("") || type == null || type.equals("")) users = usersMapper.getAllUsers();
        else if(type.equals("exact")) users = usersMapper.getlikeUsers(username);
        else users = usersMapper.getlikeUsers("%" + username + "%");
        return new PageInfo<>(users);
    }

    @Override
    public int dropUserstable() {
        return usersMapper.dropUserstable();
    }

}

这里提供了模糊查询和精确查询的服务,并且使用了 PageHelper 插件提供分页服务,具体 PageHelper 是怎么样的话,可以去找官方文档

步骤二 删除前端项目通过模拟得到数据

接下来我们打开 vue-admin-template 项目,大致看一遍项目,了解一些项目的信息

我们可以发现项目通过 mock 文件夹里的文件来给项目提供模拟的数据,这样即使没有后端数据库的支撑,也是可以看到数据的,但都是随机模拟出来的

所以这里我们要做的第一步就是把 mock 文件夹删除,然后设置项目不要从 mock 文件夹里获取数据

打开项目中的 vue.config.js 文件,将标红部分删除

并且在 open 后面改为 false,这里是为了编译启动项目时不要开两个网页

再打开 src 文件夹下的 main.js 文件,删除下面标红部分

这样项目就不会使用前端的模拟数据了

还有一些要注意的要做,因为存在跨域资源请求的问题,前后端交互的网络请求在底层由 node.js 代理,所以我们要告诉前端项目后端项目的端口及它的项目根路径(设或者不设都可以,我在这里设置 /admin ),这样前端项目就会发送 url 请求给指定端口的根路径

打开根目录下的 vue.config.js 文件,在 devServer 中添加 proxy 区域的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
devServer: {
    port: port,
    open: false,
    overlay: {
      warnings: false,
      errors: true
    },
	// 代理所有以‘/admin’开头的网络请求
	proxy: {
      '/admin': {
        target: `http://localhost:8080/`, //后台服务地址(Springboot内置tomcat默认的地址)
        changeOrigin: true,
        pathRewrite: {
        // 路径重写,可以理解为路径重新赋值 
        // '^/admin': '/' 
        // pathRewrite: {'^/admin': '/'} 重写之后url为 http://localhost:8080/xxxx
        // pathRewrite: {'^/admin': '/admin'} 
        // 重写之后url为http://localhost:8080/admin/xxxx
        }
      }
    }
}

生产环境与开发环境通常有不同的服务地址。编辑 .env.development 以及 .env.production 这两个文件,修改其中的 VUE_APP_BASE_API

1
VUE_APP_BASE_API = '/admin'

步骤三 完成后端项目 controller 层

打开 SpringBoot 项目中的 application.yml ,设置项目根路径为 /admin

打开 Vue 项目的 src/utils/request.js ,它封装了一个 axios 请求对象,在这个项目的数据交互的请求都是基于 axios 来处理的。

我们可以在发送请求之前规定一些后端数据返回的格式,方便我们自己调试和定位网络请求是否正确响应和输出。比如根据服务端的状态码判断请求是否正常,若不正常给出相应的提示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
response => {
    const res = response.data

    // if the custom code is not 20000, it is judged as an error.
    if (res.code !== 20000) {
      Message({
        message: res.message || 'Error',
        type: 'error',
        duration: 5 * 1000
      })

      // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
        // to re-login
        MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
          confirmButtonText: 'Re-Login',
          cancelButtonText: 'Cancel',
          type: 'warning'
        }).then(() => {
          store.dispatch('user/resetToken').then(() => {
            location.reload()
          })
        })
      }
      return Promise.reject(new Error(res.message || 'Error'))
    } else {
      return res
    }
  },
  error => {
    console.log('err' + error) // for debug
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

分析这段代码可以知道,返回数据要有一个 code 和一个 data ,code 是 20000 才说明没出错,data 就是本来要返回数据,所以我们在后端项目中的 controller 层使用 java 中 Map 来返回

接下来进行 controller 层的编写

首先写一个 LoginController.java 来提供 Vue 项目中的登录功能

LoginController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.tjb.backend.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.HashMap;
import java.util.Map;

@Controller
public class LoginController {
    @CrossOrigin
    @PostMapping(value = "/user/login")
    @ResponseBody
    public Map login() {
        HashMap<String, Object> response = new HashMap<>();
        HashMap<String, Object> responseData = new HashMap<>();
        responseData.put("token",1);
        response.put("code",20000);
        response.put("msg","success");
        response.put("data",responseData);
        return response;
    }

    @CrossOrigin
    @GetMapping(value = "/user/info")
    @ResponseBody
    public Map info() {
        HashMap<String, Object> responseInfo = new HashMap<>();
        HashMap<String, Object> responseData = new HashMap<>();
        responseData.put("roles","admin");
        responseData.put("name","Super admin,TJB");
        responseData.put("avatar","https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F01237b5cd29ca8a801208f8b799f9b.gif&refer=http%3A%2F%2Fimg.zcool.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1636955518&t=17b1a571de56d3527a6d79b65f011672");
        responseInfo.put("code",20000);
        responseInfo.put("msg","success");
        responseInfo.put("data",responseData);
        return responseInfo;
    }

    @CrossOrigin
    @PostMapping(value = "/user/logout")
    @ResponseBody
    public Map logout() {
        HashMap<String, Object> response = new HashMap<>();
        HashMap<String, Object> responseData = new HashMap<>();
        response.put("code",20000);
        response.put("msg","success");
        return response;
    }
}

接下来编写给前端提供 users 表和 person 表的增删查改等功能的两个 controller

UsersController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package com.tjb.backend.controller;

import com.github.pagehelper.PageInfo;
import com.tjb.backend.bean.UsersBean;
import com.tjb.backend.service.UsersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/users")
public class UsersController {

    @Autowired
    private UsersService usersService;

    @CrossOrigin
    @PostMapping("/insertusers")
    public Map insertUsers(@RequestBody UsersBean usersBean) {
        usersService.insertUsers(usersBean);
        HashMap<String,Object> responseData = new HashMap<>();
        responseData.put("code",20000);
        responseData.put("data","success");
        return responseData;
    }

    @CrossOrigin
    @DeleteMapping("/deleteusers")
    public Map deleteUsers(@RequestParam("username") String username) {
        usersService.deleteUsers(username);
        HashMap<String,Object> responseData = new HashMap<>();
        responseData.put("code",20000);
        responseData.put("data","success");
        return responseData;
    }

    @DeleteMapping("/deletebeginusers")
    public boolean deletebeginUsers(@RequestParam("username") String username) {
        int tmp = usersService.deletebeginUsers(username);
        return tmp != 0;
    }

    @CrossOrigin
    @PutMapping("/updateusers")
    public Map updateUsers(@RequestBody UsersBean usersBean) {
        usersService.updateUsers(usersBean);
        HashMap<String,Object> responseData = new HashMap<>();
        responseData.put("code",20000);
        responseData.put("data","success");
        return responseData;
    }

    @GetMapping("/getusers")
    public List<UsersBean> getUsers(@RequestParam("username") String username) {
        return usersService.getUsers(username);
    }

    @GetMapping("/getlikeusers")
    public List<UsersBean> getlikeUsers(@RequestParam("username") String username) {
        return usersService.getlikeUsers(username);
    }

    @CrossOrigin
    @GetMapping("/getallusers")
    public Map getAllUsers() {
        List<UsersBean> usersList = usersService.getAllUsers();
        HashMap<String,Object> responseData = new HashMap<>();
        responseData.put("code",20000);
        responseData.put("data",usersList);
        return responseData;
    }

    @CrossOrigin
    @GetMapping("/getPageInfo")
    public Map getPageInfo(@RequestParam("page") int page,@RequestParam("limit") int limit,
                           @RequestParam(value = "username",required = false) String username,
                           @RequestParam(value = "type",required = false) String type) {
        PageInfo<UsersBean> pageInfo = usersService.getPageInfo(page,limit,username,type);
        HashMap<String,Object> responseData = new HashMap<>();
        responseData.put("code",20000);
        responseData.put("data",pageInfo);
        return responseData;
    }

}

PersonController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package com.tjb.backend.controller;

import com.github.pagehelper.PageInfo;
import com.tjb.backend.bean.PersonBean;
import com.tjb.backend.service.PersonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/person")
public class PersonController {

    @Autowired
    private PersonService personService;

    @CrossOrigin
    @PostMapping("/insertperson")
    public Map insertPerson(@RequestBody PersonBean personBean) {
        personService.insertPerson(personBean);
        HashMap<String,Object> responseData = new HashMap<>();
        responseData.put("code",20000);
        responseData.put("data","success");
        return responseData;
    }

    @CrossOrigin
    @DeleteMapping("/deleteperson")
    public Map deletePerson(@RequestParam("username") String username) {
        personService.deletePerson(username);
        HashMap<String,Object> responseData = new HashMap<>();
        responseData.put("code",20000);
        responseData.put("data","success");
        return responseData;
    }

    @DeleteMapping("/deletebeginperson")
    public boolean deletebeginPerson(@RequestParam("username") String username) {
        int tmp = personService.deletebeginPerson(username);
        return tmp != 0;
    }

    @CrossOrigin
    @PutMapping("/updateperson")
    public Map updatePerson(@RequestBody PersonBean personBean) {
        personService.updatePerson(personBean);
        HashMap<String,Object> responseData = new HashMap<>();
        responseData.put("code",20000);
        responseData.put("data","success");
        return responseData;
    }

    @GetMapping("/getperson")
    public List<PersonBean> getPerson(@RequestParam("username") String username) {
        return personService.getPerson(username);
    }

    @GetMapping("/getlikeperson")
    public List<PersonBean> getlikeUsers(@RequestParam("username") String username) {
        return personService.getlikePerson(username);
    }

    @GetMapping("/getallperson")
    public List<PersonBean> getAllPerson() {
        return personService.getAllPerson();
    }

    @CrossOrigin
    @GetMapping("/getPageInfo")
    public Map getPageInfo(@RequestParam("page") int page,@RequestParam("limit") int limit,
                                            @RequestParam(value = "username",required = false) String username,
                                            @RequestParam(value = "type",required = false) String type) {
        PageInfo<PersonBean> pageInfo = personService.getPageInfo(page,limit,username,type);
        HashMap<String,Object> responseData = new HashMap<>();
        responseData.put("code",20000);
        responseData.put("data",pageInfo);
        return responseData;
    }

}

注解 CrossOrigin 是表示跨域的,因为前后端项目是不同的端口,其他的一些注解可以自行百度谷歌,要讲的话也有挺多的,反正都是声明给前端提供 url 的方式还有参数

到这里,后端项目就构建完成了

步骤四 设置前端项目路由并完成各个页面

Vue 是通过项目中的 router 文件夹中的 index.js 文件来指定项目路由的,修改这个文件来得到我们想要的路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

/* Layout */
import Layout from '@/layout'

/**
 * Note: sub-menu only appear when route children.length >= 1
 * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
 *
 * hidden: true                   if set true, item will not show in the sidebar(default is false)
 * alwaysShow: true               if set true, will always show the root menu
 *                                if not set alwaysShow, when item has more than one children route,
 *                                it will becomes nested mode, otherwise not show the root menu
 * redirect: noRedirect           if set noRedirect will no redirect in the breadcrumb
 * name:'router-name'             the name is used by <keep-alive> (must set!!!)
 * meta : {
    roles: ['admin','editor']    control the page roles (you can set multiple roles)
    title: 'title'               the name show in sidebar and breadcrumb (recommend set)
    icon: 'svg-name'/'el-icon-x' the icon show in the sidebar
    breadcrumb: false            if set false, the item will hidden in breadcrumb(default is true)
    activeMenu: '/example/list'  if set path, the sidebar will highlight the path you set
  }
 */

/**
 * constantRoutes
 * a base page that does not have permission requirements
 * all roles can be accessed
 */
export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },

  {
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true
  },

  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [{
      path: 'dashboard',
      name: 'Dashboard',
      component: () => import('@/views/dashboard/index'),
      meta: { title: 'Dashboard', icon: 'dashboard' }
    }]
  },

  {
    path: '/table',
    component: Layout,
    redirect: '/table/users',
    name: 'Table',
    meta: { title: 'Table', icon: 'table' },
    children: [
      {
        path: 'users',
        name: 'Users',
        component: () => import('@/views/userstable/index'),
        meta: { title: 'Users', icon: 'el-icon-user-solid' }
      },
      {
        path: 'person',
        name: 'Person',
        component: () => import('@/views/persontable/index'),
        meta: { title: 'Person', icon: 'el-icon-s-custom' }
      }
    ]
  },

  {
    path: 'github-link',
    component: Layout,
    children: [
      {
        path: 'https://github.com/tjbnbb/LAB1_2',
        meta: { title: 'GitHub-Link', icon: 'link' }
      }
    ]
  },

  // 404 page must be placed at the end !!!
  { path: '*', redirect: '/404', hidden: true }
]

const createRouter = () => new Router({
  // mode: 'history', // require service support
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRoutes
})

const router = createRouter()

// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}

export default router

指定路由之后,我们就要编写每个页面的代码了

这些页面存放在 views 文件夹中

dashboard 页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template>
  <div class="dashboard-container">
    <div class="dashboard-text">Hello! </div>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  name: 'Dashboard',
  computed: {
    ...mapGetters([
      'name'
    ])
  }
}
</script>

<style lang="scss" scoped>
.dashboard {
  &-container {
    margin: 30px;
  }
  &-text {
    font-size: 30px;
    line-height: 46px;
  }
}
</style>

login 页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
<template>
  <div class="login-container">

    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left">

      <div class="title-container">
        <h3 class="title">Login Form</h3>
      </div>

      <el-form-item prop="username">
        <span class="svg-container">
          <svg-icon icon-class="user" />
        </span>
        <el-input
          ref="username"
          v-model="loginForm.username"
          placeholder="Username"
          name="username"
          type="text"
          tabindex="1"
          auto-complete="on"
        />
      </el-form-item>

      <el-form-item prop="password">
        <span class="svg-container">
          <svg-icon icon-class="password" />
        </span>
        <el-input
          :key="passwordType"
          ref="password"
          v-model="loginForm.password"
          :type="passwordType"
          placeholder="Password"
          name="password"
          tabindex="2"
          auto-complete="on"
          @keyup.enter.native="handleLogin"
        />
        <span class="show-pwd" @click="showPwd">
          <svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
        </span>
      </el-form-item>

      <el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">Login</el-button>

      <div class="tips">
        <span style="margin-right:20px;">username: admin</span>
        <span> password: admin</span>
      </div>

    </el-form>
  </div>
</template>

<script>
import { validUsername } from '@/utils/validate'
import { validPassword } from '@/utils/validate'

export default {
  name: 'Login',
  data() {
    const validateUsername = (rule, value, callback) => {
      if (!validUsername(value)) {
        callback(new Error('Please enter the correct username'))
      } else {
        callback()
      }
    }
    const validatePassword = (rule, value, callback) => {
      if (!validPassword(value)) {
        callback(new Error('Please enter the correct password'))
      } else {
        callback()
      }
    }
    return {
      loginForm: {
        username: '',
        password: ''
      },
      loginRules: {
        username: [{ required: true, trigger: 'blur', validator: validateUsername }],
        password: [{ required: true, trigger: 'blur', validator: validatePassword }]
      },
      loading: false,
      passwordType: 'password',
      redirect: undefined
    }
  },
  watch: {
    $route: {
      handler: function(route) {
        this.redirect = route.query && route.query.redirect
      },
      immediate: true
    }
  },
  methods: {
    showPwd() {
      if (this.passwordType === 'password') {
        this.passwordType = ''
      } else {
        this.passwordType = 'password'
      }
      this.$nextTick(() => {
        this.$refs.password.focus()
      })
    },
    handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          this.loading = true
          this.$store.dispatch('user/login', this.loginForm).then(() => {
            this.$router.push({ path: this.redirect || '/' })
            this.loading = false
          }).catch(() => {
            this.loading = false
          })
        } else {
          console.log('error submit!!')
          return false
        }
      })
    }
  }
}
</script>

<style lang="scss">
/* 修复input 背景不协调 和光标变色 */
/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */

$bg:#283443;
$light_gray:#fff;
$cursor: #fff;

@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
  .login-container .el-input input {
    color: $cursor;
  }
}

/* reset element-ui css */
.login-container {
  .el-input {
    display: inline-block;
    height: 47px;
    width: 85%;

    input {
      background: transparent;
      border: 0px;
      -webkit-appearance: none;
      border-radius: 0px;
      padding: 12px 5px 12px 15px;
      color: $light_gray;
      height: 47px;
      caret-color: $cursor;

      &:-webkit-autofill {
        box-shadow: 0 0 0px 1000px $bg inset !important;
        -webkit-text-fill-color: $cursor !important;
      }
    }
  }

  .el-form-item {
    border: 1px solid rgba(255, 255, 255, 0.1);
    background: rgba(0, 0, 0, 0.1);
    border-radius: 5px;
    color: #454545;
  }
}
</style>

<style lang="scss" scoped>
$bg:#2d3a4b;
$dark_gray:#889aa4;
$light_gray:#eee;

.login-container {
  min-height: 100%;
  width: 100%;
  background-color: $bg;
  overflow: hidden;

  .login-form {
    position: relative;
    width: 520px;
    max-width: 100%;
    padding: 160px 35px 0;
    margin: 0 auto;
    overflow: hidden;
  }

  .tips {
    font-size: 14px;
    color: #fff;
    margin-bottom: 10px;

    span {
      &:first-of-type {
        margin-right: 16px;
      }
    }
  }

  .svg-container {
    padding: 6px 5px 6px 15px;
    color: $dark_gray;
    vertical-align: middle;
    width: 30px;
    display: inline-block;
  }

  .title-container {
    position: relative;

    .title {
      font-size: 26px;
      color: $light_gray;
      margin: 0px auto 40px auto;
      text-align: center;
      font-weight: bold;
    }
  }

  .show-pwd {
    position: absolute;
    right: 10px;
    top: 7px;
    font-size: 16px;
    color: $dark_gray;
    cursor: pointer;
    user-select: none;
  }
}
</style>

userstable 页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
<template>
  <div class="app-container">
    <div class="filter-container">
      <el-input v-model="listQuery.username" placeholder="Username" style="width: 250px;" class="filter-item" @keyup.enter.native="handleFilter" />
      <el-select v-model="listQuery.type" placeholder="Type" clearable style="width: 120px" class="filter-item">
        <el-option v-for="item in typeOption" :key="item" :label="item" :value="item" />
      </el-select>
      <el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">
        Search
      </el-button>
      <el-button class="filter-item" style="margin-left: 20px;" type="primary" icon="el-icon-edit" @click="handleCreate">
        Add
      </el-button>
    </div>

    <el-table
      v-loading="listLoading"
      :data="list"
      element-loading-text="Loading"
      border
      fit
      highlight-current-row
      style="width: 100%;"
    >
      <el-table-column align="center" label="username" width="550">
        <template slot-scope="scope">
          <span></span>
        </template>
      </el-table-column>
      <el-table-column label="pass" width="550" align="center">
        <template slot-scope="scope">
          <span></span>
        </template>
      </el-table-column>
      <el-table-column label="Actions" align="center" min-width="150" class-name="small-padding fixed-width">
        <template slot-scope="scope">
          <el-button type="primary" size="mini" @click="handleUpdate(scope.row)">
            Edit
          </el-button>
          <el-button size="mini" type="danger" @click="handleDelete(scope.row)">
            Delete
          </el-button>
        </template>
      </el-table-column>
    </el-table>

    <pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="fetchData" />

    <el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
      <el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="100px" style="width: 400px; margin-left:50px;">
        <el-form-item label="Username" prop="username">
          <el-input v-model="temp.username" :disabled="edit" placeholder="Please input username" />
        </el-form-item>
        <el-form-item label="Pass" prop="pass">
          <el-input v-model="temp.pass" placeholder="Please input pass" />
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="dialogFormVisible = false">
          Cancel
        </el-button>
        <el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
          Confirm
        </el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { getPageInfo, deleteUsers, updateUsers, insertUsers } from '@/api/userstable'
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import waves from '@/directive/waves' // waves directive

export default {
  components: { Pagination },
  directives: { waves },
  data() {
    return {
      list: null,
      listLoading: true,
      total: 0,
      listQuery: {
        page: 1,
        limit: 5,
        username: undefined,
        type: undefined
      },
      typeOption: ['exact', 'fuzzy'],
      temp: {
        username: '',
        pass: ''
      },
      dialogFormVisible: false,
      dialogStatus: '',
      textMap: {
        update: 'Edit',
        create: 'Create'
      },
      edit: 'false',
      rules: {
        username: [{ required: true, message: 'username is required', trigger: 'blur' },
          { max: 10, message: 'Can not input over 10 characters', trigger: 'blur' }],
        pass: [{ required: true, message: 'pass is required', trigger: 'blur' },
          { max: 8, message: 'Can not input over 8 characters', trigger: 'blur' }]
      }
    }
  },
  created() {
    this.fetchData()
  },
  methods: {
    fetchData() {
      this.listLoading = true
      getPageInfo(this.listQuery).then(response => {
        this.list = response.data.list
        this.total = response.data.total
        this.listLoading = false
      })
    },
    handleFilter() {
      this.listQuery.page = 1
      this.fetchData()
    },
    handleDelete(row) {
      this.$confirm('This operation will permanently delete this piece of data, the person data associated with it will also be deleted, do you want to continue?', 'warning', {
        confirmButtonText: 'Confirm',
        cancelButtonText: 'Cancel',
        type: 'warning'
      }).then(() => {
        deleteUsers(row.username).then(() => {
          this.$notify({
            title: 'Success',
            message: 'Delete Successfully',
            type: 'success',
            duration: 2000
          })
          this.fetchData()
        })
      })
    },
    resetTemp() {
      this.temp = {
        username: '',
        pass: ''
      }
    },
    handleCreate() {
      this.resetTemp()
      this.dialogStatus = 'create'
      this.dialogFormVisible = true
      this.edit = false
      this.$nextTick(() => {
        this.$refs['dataForm'].clearValidate()
      })
    },
    createData() {
      this.$refs['dataForm'].validate((valid) => {
        if (valid) {
          const tempData = Object.assign({}, this.temp)
          insertUsers(tempData).then(() => {
            this.dialogFormVisible = false
            this.$notify({
              title: 'Success',
              message: 'Create Successfully',
              type: 'success',
              duration: 2000
            })
            this.fetchData()
          })
        }
      })
    },
    handleUpdate(row) {
      this.temp = Object.assign({}, row) // copy obj
      this.dialogStatus = 'update'
      this.dialogFormVisible = true
      this.edit = true
      this.$nextTick(() => {
        this.$refs['dataForm'].clearValidate()
      })
    },
    updateData() {
      this.$confirm('This operation will permanently modify this piece of data, do you want to continue?', 'warning', {
        confirmButtonText: 'Confirm',
        cancelButtonText: 'Cancel',
        type: 'warning'
      }).then(() => {
        this.$refs['dataForm'].validate((valid) => {
          if (valid) {
            const tempData = Object.assign({}, this.temp)
            updateUsers(tempData).then(() => {
              this.dialogFormVisible = false
              this.$notify({
                title: 'Success',
                message: 'Update Successfully',
                type: 'success',
                duration: 2000
              })
              this.fetchData()
            })
          }
        })
      })
    }
  }
}
</script>

persontable 页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
<template>
  <div class="app-container">
    <div class="filter-container">
      <el-input v-model="listQuery.username" placeholder="Username" style="width: 250px;" class="filter-item" @keyup.enter.native="handleFilter" />
      <el-select v-model="listQuery.type" placeholder="Type" clearable style="width: 120px" class="filter-item">
        <el-option v-for="item in typeOption" :key="item" :label="item" :value="item" />
      </el-select>
      <el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">
        Search
      </el-button>
      <el-button class="filter-item" style="margin-left: 20px;" type="primary" icon="el-icon-edit" @click="handleCreate">
        Add
      </el-button>
    </div>

    <el-table
      v-loading="listLoading"
      :data="list"
      element-loading-text="Loading"
      border
      fit
      highlight-current-row
      style="width: 100%;"
    >
      <el-table-column align="center" label="username" width="250">
        <template slot-scope="scope">
          <span></span>
        </template>
      </el-table-column>
      <el-table-column label="name" width="500" align="center">
        <template slot-scope="scope">
          <span></span>
        </template>
      </el-table-column>
      <el-table-column align="center" label="age" width="100">
        <template slot-scope="scope">
          <span></span>
        </template>
      </el-table-column>
      <el-table-column align="center" label="teleno" width="250">
        <template slot-scope="scope">
          <span></span>
        </template>
      </el-table-column>
      <el-table-column label="Actions" align="center" min-width="150" class-name="small-padding fixed-width">
        <template slot-scope="scope">
          <el-button type="primary" size="mini" @click="handleUpdate(scope.row)">
            Edit
          </el-button>
          <el-button size="mini" type="danger" @click="handleDelete(scope.row)">
            Delete
          </el-button>
        </template>
      </el-table-column>
    </el-table>

    <pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="fetchData" />

    <el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
      <el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="100px" style="width: 400px; margin-left:50px;">
        <el-form-item label="Username" prop="username">
          <el-input v-model="temp.username" :disabled="edit" placeholder="Please input username" />
        </el-form-item>
        <el-form-item label="Name" prop="name">
          <el-input v-model="temp.name" placeholder="Please input name" />
        </el-form-item>
        <el-form-item label="Age" prop="age">
          <el-input v-model="temp.age" placeholder="Please input age" />
        </el-form-item>
        <el-form-item label="Teleno" prop="teleno">
          <el-input v-model="temp.teleno" placeholder="Please input teleno" />
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="dialogFormVisible = false">
          Cancel
        </el-button>
        <el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
          Confirm
        </el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { getPageInfo, deletePerson, updatePerson, insertPerson } from '@/api/persontable'
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import waves from '@/directive/waves' // waves directive

export default {
  components: { Pagination },
  directives: { waves },
  data() {
    return {
      list: null,
      listLoading: true,
      total: 0,
      listQuery: {
        page: 1,
        limit: 5,
        username: undefined,
        type: undefined
      },
      typeOption: ['exact', 'fuzzy'],
      temp: {
        username: '',
        name: '',
        age: undefined,
        teleno: undefined
      },
      dialogFormVisible: false,
      dialogStatus: '',
      textMap: {
        update: 'Edit',
        create: 'Create'
      },
      edit: 'false',
      rules: {
        username: [{ required: true, message: 'username is required', trigger: 'blur' },
          { max: 10, message: 'Can not input over 10 characters', trigger: 'blur' }],
        name: [{ required: true, message: 'name is required', trigger: 'blur' },
          { max: 20, message: 'Can not input over 20 characters', trigger: 'blur' }],
        age: [
          { type: 'number', required: false, message: 'age must be a numeric value', transform(value) { return Number(value) } },
          { type: 'number', min: 0, max: 150, message: 'Please ensure that the input age is between 0~150', transform(value) { return Number(value) }, trigger: 'blur' }
        ],
        teleno: [
          { type: 'number', required: false, message: 'teleno must be a numeric value', transform(value) { return Number(value) } },
          { type: 'number', min: 0, max: 99999999999, message: 'Please ensure that the number of telephone digits does not exceed 11', transform(value) { return Number(value) }, trigger: 'blur' }
        ]
      }
    }
  },
  created() {
    this.fetchData()
  },
  methods: {
    fetchData() {
      this.listLoading = true
      getPageInfo(this.listQuery).then(response => {
        this.list = response.data.list
        this.total = response.data.total
        this.listLoading = false
      })
    },
    handleFilter() {
      this.listQuery.page = 1
      this.fetchData()
    },
    handleDelete(row) {
      this.$confirm('This operation will permanently delete this piece of data, do you want to continue?', 'warning', {
        confirmButtonText: 'Confirm',
        cancelButtonText: 'Cancel',
        type: 'warning'
      }).then(() => {
        deletePerson(row.username).then(() => {
          this.$notify({
            title: 'Success',
            message: 'Delete Successfully',
            type: 'success',
            duration: 2000
          })
          this.fetchData()
        })
      })
    },
    resetTemp() {
      this.temp = {
        username: '',
        name: '',
        age: undefined,
        teleno: undefined
      }
    },
    handleCreate() {
      this.resetTemp()
      this.dialogStatus = 'create'
      this.dialogFormVisible = true
      this.edit = false
      this.temp.age = null
      this.temp.teleno = ''
      this.$nextTick(() => {
        this.$refs['dataForm'].clearValidate()
      })
    },
    createData() {
      this.$confirm('If there is no corresponding users, an initial users will be created, do you want to continue?', 'warning', {
        confirmButtonText: 'Confirm',
        cancelButtonText: 'Cancel',
        type: 'warning'
      }).then(() => {
        this.$refs['dataForm'].validate((valid) => {
          if (valid) {
            const tempData = Object.assign({}, this.temp)
            insertPerson(tempData).then(() => {
              this.dialogFormVisible = false
              this.$notify({
                title: 'Success',
                message: 'Update Successfully',
                type: 'success',
                duration: 2000
              })
              this.fetchData()
            })
          }
        })
      })
    },
    handleUpdate(row) {
      this.temp = Object.assign({}, row) // copy obj
      this.dialogStatus = 'update'
      this.dialogFormVisible = true
      this.edit = true
      this.$nextTick(() => {
        this.$refs['dataForm'].clearValidate()
      })
    },
    updateData() {
      this.$confirm('This operation will permanently modify this piece of data, do you want to continue?', 'warning', {
        confirmButtonText: 'Confirm',
        cancelButtonText: 'Cancel',
        type: 'warning'
      }).then(() => {
        this.$refs['dataForm'].validate((valid) => {
          if (valid) {
            const tempData = Object.assign({}, this.temp)
            updatePerson(tempData).then(() => {
              this.dialogFormVisible = false
              this.$notify({
                title: 'Success',
                message: 'Update Successfully',
                type: 'success',
                duration: 2000
              })
              this.fetchData()
            })
          }
        })
      })
    }
  }
}
</script>

主要关注点在 userstable 和 persontable 两个页面,其他几个页面都只要稍微修改你想要的东西,甚至不修改

接下来要修改的就是给页面提供函数 api 文件中的 js ,这些 js 中的函数通过指定的 url 调用后端项目 controller 层对应的函数,并且将参数传过去,后端执行完后再将结果返回给前端

user.js 给 login 页面提供对应函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import request from '@/utils/request'

export function login(data) {
  return request({
    url: '/user/login',
    method: 'post',
    data
  })
}

export function getInfo(token) {
  return request({
    url: '/user/info',
    method: 'get',
    params: { token }
  })
}

export function logout() {
  return request({
    url: '/user/logout',
    method: 'post'
  })
}

userstable.js 给 userstable 页面提供对应函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import request from '@/utils/request'

export function getUsersList(params) {
  return request({
    url: '/users/getallusers',
    method: 'get',
    params
  })
}

export function getPageInfo(params) {
  return request({
    url: '/users/getPageInfo',
    method: 'get',
    params: params
  })
}

export function deleteUsers(username) {
  return request({
    url: '/users/deleteusers?username=' + username,
    method: 'delete'
  })
}

export function updateUsers(usersBean) {
  return request({
    url: '/users/updateusers',
    method: 'put',
    data: usersBean
  })
}

export function insertUsers(usersBean) {
  return request({
    url: '/users/insertusers',
    method: 'post',
    data: usersBean
  })
}

persontable.js 给 persontable 页面提供对应函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import request from '@/utils/request'

export function getPersonList(params) {
  return request({
    url: '/person/getallperson',
    method: 'get',
    params
  })
}

export function getPageInfo(params) {
  return request({
    url: '/person/getPageInfo',
    method: 'get',
    params: params
  })
}

export function deletePerson(username) {
  return request({
    url: '/person/deleteperson?username=' + username,
    method: 'delete'
  })
}

export function updatePerson(personBean) {
  return request({
    url: '/person/updateperson',
    method: 'put',
    data: personBean
  })
}

export function insertPerson(personBean) {
  return request({
    url: '/person/insertperson',
    method: 'post',
    data: personBean
  })
}

到这里,前端项目差不多也完成了,但是我为了美观,添加了一个背景粒子特效,我用的是 vue-particles ,至于怎么安装,很简单,这里就不说了

但是你可能不知道怎么用才能让它只作为背景,我也是找了很久资料才搞好,这里你可以直接参考我的 App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<template>
  <div id="app">
    <vue-particles
      id="particles-js"
      color="#dedede"
      :particle-opacity="0.7"
      :particles-number="40"
      shape-type="star"
      :particle-size="4"
      lines-color="#FFFFFF"
      :lines-width="2"
      :line-linked="true"
      :line-opacity="0.4"
      :lines-distance="150"
      :move-speed="3"
      :hover-effect="true"
      hover-mode="grab"
      :click-effect="true"
      click-mode="push"
      class="cash"
    />
    <router-view />
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style scoped>
#particles-js{
      width: 100%;
      height: calc(100% - 100px);
      position: absolute;/*设置absolute,其他DIV设置为relative,这样这个例子效果就作为背景*/
  }
</style>

步骤五 部署项目到服务器

最后只剩将项目部署到服务器了,为了让大家不走弯路,这里我只介绍最简单的方法,我当初就是走了弯路,本来几十分钟的事搞了几个小时

首先你需要有一个服务器,一般这些服务器的商家都是会给新人送免费一个月的服务器,我的服务器就是从阿里云领的一个月,我选的系统是 linux 中的 centos(听说服务器大多是这个,虽然不知道好在哪里)

然后安装软件 xshell(学生是不用钱的,填一份表就是免费,所以大家还是支持正版吧),通过在 xshell 输入命令控制服务器

接下来用 xshell 连接好你的服务器

打开 xshell ,点击新建

名称随便填,主机输入你服务器的公网 IP

然后输入你服务器的用户名和密码,如果没有创建就先去创建

然后点击坐下角的连接,如果出现类似下图的情况就是连接成功了

接下来我们使用宝塔来安装一些必要的软件,宝塔使用起来非常方便,很推荐

复制以下命令进行安装(这是 centos 的,具体是什么应该看你服务器的系统,可以去官网查看命令)

1
yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh

安装完后,访问出现的外网地址输入给出的账号密码就能进入宝塔界面了(记得先在安全组加入端口 8888 ,以后要用到的端口都要加入安全组,之后就不反复提了

安装好初始提示你安装的软件,注意 MySQL 版本最好和你本地的版本一致

安装好后,点击标红部分新建 users 和 person 两张数据表

接下来将 vue 项目打包,输入以下命令

1
npm run build:prod

将生成的 dist 文件夹里的文件复制到 SpringBoot 项目的 static 文件夹中

输入以下命令将 SpringBoot 项目打包

1
mvn clean package

将生成的 .jar 文件通过宝塔上传到服务器上

接下来要安装 jdk,建议和本地一致,否则有可能报错,对于低版本的 jdk 很容易,网上多的是,但是我竟然在中文互联网找不到 centos 如何安装 jdk17 ,最后还是谷歌输入英文找到的,具体可以参考以下链接: https://techviewleo.com/install-java-openjdk-on-rocky-linux-centos/

最后输入以下命令,使服务器在终端退出后也保持后台运行项目就 ok 了

1
nohup java -jar backend-0.0.1-SNAPSHOT.jar &

先在你就可以通过访问你的服务器公网 IP 加上后端项目端口加上项目根目录访问到你的项目,例如我的地址就是:

http://120.25.234.241:8080/admin/

有什么问题可以在下方留言或者联系我