Vue-element-admin前端脚手架

2023/11/19 vuevue-element-admin

# Vue-element-admin前端脚手架

# 登录页面

/views/login/index.vue

image-20231106092509740

无关紧要的组件

import SocialSign from './components/SocialSignin'

export default {
  name: 'Login',
  components: { SocialSign },
  data() {
    // const validateUsername = (rule, value, callback) => {
    //   if (!validUsername(value)) {
    //     callback(new Error('Please enter the correct user name'))
    //   } else {
    //     callback()
    //   }
    // }
    // const validatePassword = (rule, value, callback) => {
    //   if (value.length < 6) {
    //     callback(new Error('The password can not be less than 6 digits'))
    //   } else {
    //     callback()
    //   }
    // }
    return {
      loginForm: {
        username: '',
        password: ''
      },
      loginRules: {
        username: [{ required: true, trigger: 'blur', message: '用户名不能为空' }],
        password: [{ required: true, trigger: 'blur', message: '密码不能为空' }]
      },
      passwordType: 'password',
      capsTooltip: false,
      loading: false,
      showDialog: false,
      redirect: undefined,
      otherQuery: {}
    }
  },
  watch: {
    $route: {
      handler: function(route) {
        const query = route.query
        if (query) {
          this.redirect = query.redirect
          this.otherQuery = this.getOtherQuery(query)
        }
      },
      immediate: true
    }
  },
  created() {
    // window.addEventListener('storage', this.afterQRScan)
  },
  mounted() {
    if (this.loginForm.username === '') {
      this.$refs.username.focus()
    } else if (this.loginForm.password === '') {
      this.$refs.password.focus()
    }
  },
  destroyed() {
    // window.removeEventListener('storage', this.afterQRScan)
  },
  methods: {
    checkCapslock(e) {
      const { key } = e
      this.capsTooltip = key && key.length === 1 && (key >= 'A' && key <= 'Z')
    },
    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 || '/', query: this.otherQuery })
              this.loading = false
            })
            .catch(() => {
              this.loading = false
            })
        } else {
          console.log('error submit!!')
          return false
        }
      })
    },
    getOtherQuery(query) {
      return Object.keys(query).reduce((acc, cur) => {
        if (cur !== 'redirect') {
          acc[cur] = query[cur]
        }
        return acc
      }, {})
    }
    // afterQRScan() {
    //   if (e.key === 'x-admin-oauth-code') {
    //     const code = getQueryObject(e.newValue)
    //     const codeMap = {
    //       wechat: 'code',
    //       tencent: 'code'
    //     }
    //     const type = codeMap[this.auth_type]
    //     const codeName = code[type]
    //     if (codeName) {
    //       this.$store.dispatch('LoginByThirdparty', codeName).then(() => {
    //         this.$router.push({ path: this.redirect || '/' })
    //       })
    //     } else {
    //       alert('第三方登录失败')
    //     }
    //   }
    // }
  }
}
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

# 1、去除mock

# 1.1**vue.config.js中**

去除该段,配置一个代理

image-20231106103522193

    proxy: {
      [process.env.VUE_APP_BASE_API]: {
        target: 'http://localhost:8089/api',
        changeOrigin: true,
        pathRewrite: {
          '^/api': ''
        }
      }
    }
1
2
3
4
5
6
7
8
9

# 1.2 main.js

if (process.env.NODE_ENV === 'production') {
  const { mockXHR } = require('../mock')
  mockXHR()
}
1
2
3
4

把它注释掉

# 1.3 重写src/utilrequest.js

import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
import qs from 'qs'

// create an axios instance
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
  // withCredentials: true, // send cookies when cross-domain requests
  timeout: 5000 // request timeout
})
// request interceptor
service.interceptors.request.use(
  config => {
    console.log(config)
    // do something before request is sent
    if (store.getters.token) {
      // let each request carry token
      // ['X-Token'] is a custom headers key
      // please modify it according to the actual situation
      config.headers['token'] = getToken()
    }
    return config
  },
  error => {
  // do something with request error
    console.log(error) // for debug
    return Promise.reject(error)
  }
)
// response interceptor
// 50008: Illegal token; 50012: Other clients logged in; 50014: Token
// response interceptor
service.interceptors.response.use(
  /**
  * If you want to get http information such as headers or status
  * Please return response => response
  */
  /**
  * Determine the request status by custom code
  * Here is just an example
  * You can also judge the status by HTTP Status Code
  */
  response => {
    const res = response.data
    // if the custom code is not 20000, it is judged as an error.
    if (res.code !== 50200) {
      Message({
        message: res.message || 'Error',
        type: 'error',
        duration: 5 * 1000
      })
      // 50008: Illegal token; 50012: Other clients logged in; 50014: Token
      if (res.code === 50404) {
        // to re-login
        MessageBox.confirm('您已注销,可以取消以留在此页面,也可以重新登录', '确认注销', {
          confirmButtonText: '重新登陆',
          cancelButtonText: '取消',
          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)
  }
)

// 请求方法
const http = {
  post(url, params) {
    return service.post(url, params, {
      transformRequest: [(params) => {
        return JSON.stringify(params)
      }],
      headers: {
        'Content-Type': 'application/json'
      }
    })
  },
  put(url, params) {
    return service.put(url, params, {
      transformRequest: [(params) => {
        return JSON.stringify(params)
      }],
      headers: {
        'Content-Type': 'application/json'
      }
    })
  },
  get(url, params) {
    return service.get(url, {
      params: params,
      paramsSerializer: (params) => {
        return qs.stringify(params)
      }
    })
  },
  getRestApi(url, params) {
    let _params
    if (Object.is(params, undefined || null)) {
      _params = ''
    } else {
      _params = '/'
      for (const key in params) {
        console.log(key)
        console.log(params[key])
        if (Object.prototype.hasOwnProperty.call(params, key) && params[key] !== null && params[key] !== '') {
          _params += `${params[key]}/`
        }
      }
      _params = _params.suxbstr(0, _params.length - 1)
    }
    console.log(_params)
    if (_params) {
      return service.get(`${url}${_params}`)
    } else {
      return service.get(url)
    }
  },
  delete(url, params) {
    let _params
    if (Object.is(params, undefined || null)) {
      _params = ''
    } else {
      _params = '/'
      for (const key in params) {
        // eslint-disable-next-line no-prototype-builtins
        if (params.hasOwnProperty(key) && params[key] !== null && params[key] !== '') {
          _params += `${params[key]}/`
        }
      }
      _params = _params.substr(0, _params.length - 1)
    }
    if (_params) {
      return service.delete(`${url}${_params}`).catch(err => {
        Message.error(x.msg)
        return Promise.reject(err)
      })
    } else {
      return service.delete(url).catch(err => {
        Message.error(err.msg)
        return Promise.reject(err)
      })
    }
  },
  upload(url, params) {
    return service.post(url, params, {
      headers: {
        'Content-Type': 'multipart/form-data'
      }
    })
  },
  login(url, params) {
    return service.post(url, params, {
      transformRequest: [(params) => {
        return qs.stringify(params)
      }],
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      }
    })
  }
}
export default http

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

# 1.4 修改src/api/user.js

import http from '@/utils/request'
/**
* 用户登录
* @returns
*/
export async function login(data) {
  return await http.login('/api/user/login', data)
}
/**
* 获取用户信息和权限信息
* @returns
*/
export async function getInfo() {
  return await http.get('/api/sysUser/getInfo')
}
/**
* 退出登录
* @returns
*/
export async function logout(param) {
  return await http.post('/api/sysUser/loginOut', param)
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 1.5 编写后端退出端口


/**
* 用户退出
* @param request
* @param response
* @return
*/
@PostMapping("/logout")
public Result logout(HttpServletRequest request, HttpServletResponse response) {
//获取token
String token = request.getParameter("token");
//如果没有从头部获取token,那么从参数里面获取
if (ObjectUtils.isEmpty(token)) {
token = request.getHeader("token");
}
//获取用户相关信息
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
//清空用户信息
new SecurityContextLogoutHandler().logout(request, response,
authentication);
//清空redis里面的token
String key = "token_" + token;
redisService.del(key);
}
return Result.ok().message("用户退出成功");
}
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

# 1.6 修改.env.development和.env.production请求地址

VUE_APP_BASE_API = 'http://localhost:9999/'
1

改成自己的后端地址就行

# 1.7 修改src/store/modules/user.js

const actions = {
  // 用户登录
  login({ commit }, userInfo) {
  // 从用户信息userInfo中解构出用户名和密码
    const { username, password } = userInfo
    return new Promise((resolve, reject) => {
      // 调用src/api/user.js文件中的login()方法
      login({ username: username.trim(), password: password }).then(response => {
        // 从response中解构出返回的token数据
        const { token } = response
        // 将返回的token数据保存到store中,作为全局变量使用
        commit('SET_TOKEN', token)
        // 将token信息保存到cookie中
        setToken(token)
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  },

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

其实只改了token的键名

# 1.8 编写退出登录接口src/api/user.js

/**
* 退出登录
* @returns
*/
export async function logout(param) {
  return await http.post('/api/user/logout', param)
}
1
2
3
4
5
6
7

# 1.9 编写清空sessionStorage方法 在src/utils/auth.js中添加如下方法:

/**
* 清空sessionStorage
*/
export function clearStorage(){
return sessionStorage.clear();
}
1
2
3
4
5
6

# 1.10修改src/layout/component/Navbar.vue

把原来的layout()

async logout() {
      await this.$store.dispatch('user/logout')
      this.$router.push(`/login?redirect=${this.$route.fullPath}`)
    }
1
2
3
4

改为

    logout() {
      this.$confirm('确定退出系统吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(async() => {
        // 请求参数
        const params = { token: getToken() }
        // 发送退出请求
        const res = await logout(params)
        // 判断是否成功
        if (res.success) {
          // 清空token
          removeToken()
          clearStorage()
          // 跳转到登录页面
          window.location.href = '/login'
        }
      })
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 1.11 在src/utils包下创建myconfirm.js文件

import { MessageBox } from 'element-ui'
// 删除弹框
export default function myconfirm(text) {
  return new Promise((resolve, reject) => {
    MessageBox.confirm(text, '系统提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    }).then(() => {
      resolve(true)
    }).catch(() => {
      reject(false)
    })
  }).catch(() => {
  })
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 1.12 封装信息确认提示框,在src/utils目录下创建myconfirm.js文件

import { MessageBox } from 'element-ui'
// 删除弹框
export default function myconfirm(text) {
  return new Promise((resolve, reject) => {
    MessageBox.confirm(text, '系统提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    }).then(() => {
      resolve(true)
    }).catch(() => {
      reject(false)
    })
  }).catch(() => {
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

在main.js中加入

import myconfirm from '@/utils/myconfirm'

Vue.prototype.$myconfirm = myconfirm
1
2
3

# 2 、动态路由菜单

src/api/user.js里添加

/**
* 获取菜单数据
*/
export async function getMenuList(){
return await http.get("/api/sysUser/getMenuList");
}
1
2
3
4
5
6

找到src/store/permission.jsgenerateRoutes()

*import* { asyncRoutes, constantRoutes } *from* '@/router'

其中asyncRoutes表示实时路由信息,重要信息格式和实体类应该是对应的,根据用户拥有的权限来生成

constantRoutes是表示不变的路由信息

generateRoutes({ commit }, roles) {
    return new Promise((resolve, reject) => {
      getMenuList().then(
        res => {
          console.log(res.data)
          // 存放的路由表
          let accessedRoutes
          // 判断成功
          if (res.code === 50200) {
            console.log(res.data)
            accessedRoutes = filterAsyncRoutes(res.data, roles)
          }
          // 将信息保存到store中
          commit('SET_ROUTES', accessedRoutes)
          resolve(accessedRoutes)
        }
      ).catch(
        err => {
          reject(err)
        }
      )
    })
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

src/store/permission.js里导入import Layout from '@/layout'

找到src/store/permission.js的**filterAsyncRoutes()**

修改为

/**
 * Filter asynchronous routing tables by recursion
 * 动态生成路由信息
 * @param routes asyncRoutes
 * @param roles
 */
export function filterAsyncRoutes(routes, roles) {
  const res = []
  routes.forEach(route => {
    const tmp = { ...route }
    // 判断是否有权限
    if (hasPermission(roles, tmp)) {
      // 获取该路由对应的组件
      const component = tmp.component
      // 先判断是否存在组件
      if (route.component) {
        // 再判断是否为根组件
        if (component === 'Layout') {
          tmp.component = Layout
        } else {
          // 获取对应的组件信息
          // 按照懒加载路由的格式导入的
          tmp.component = (resolve) => require([`@/views${component}`], resolve)
        }
      }
      // 判断是否有子菜单
      if (tmp.children) {
        tmp.children = filterAsyncRoutes(tmp.children, roles)
      }
      res.push(tmp)
    }
  })
  console.log(res)
  return res
}
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

删除src/router/index.jsconstantRoutes不需要的路由信息

# 3、部门管理

因为前端有需求,需要有能展开的部门列表

后端department实体类新增属性"子部门","是否展开"

/**
* 是否展开
*/
@TableField(exist = false)
private Boolean open;
/**
* 子部门
*/
@TableField(exist = false)
private List<Department> children = new ArrayList<Department>();
1
2
3
4
5
6
7
8
9
10

部门树工具类

/**
 * 生成部门树
 */
public class DepartmentTree {

    public static List<Department> makeDepartmentTree(List<Department> departments,Long pid){
        //保存部门信息表
        ArrayList<Department> departmentList = new ArrayList<>();
        //判断部门列表是否未空,如果不为空则使用部门列表,否则创建一个新的集合对象
        Optional.ofNullable(departments).orElse(new ArrayList<>())
                .stream().filter(item -> item != null && Objects.equals(item.getPid(), pid))
                .forEach(item ->{
                    Department department= new Department();
                    //复制属性
                    BeanUtils.copyProperties(item,department);
                    //读取每个item的子部门,递归生成部门树
                    List<Department> children = makeDepartmentTree(departmentList , item.getPid());
                    department.setChildren(children);
                    departmentList.add(department);
                });
        return departmentList;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

部门服务类

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author sunday
 * @since 2023-11-01
 */
@Service
public class DepartmentServiceImpl extends ServiceImpl<DepartmentMapper, Department> implements IDepartmentService {
    @Resource
    private UserMapper userMapper;

    /**
     * 查询部门列表
     *
     * @param departmentQueryVo
     * @return
     */
    @Override
    public List<Department> findDepartmentList(DepartmentQueryVo departmentQueryVo) {
        QueryWrapper<Department> queryWrapper =new QueryWrapper<>();
        // 根据部门名称来查找
        queryWrapper.like(ObjectUtils.isEmpty(departmentQueryVo.getDepartment()),"department_name",departmentQueryVo.getDepartment());
        // 并且排序
        queryWrapper.orderByAsc("order_num");
        // 查询部门列表
        List<Department> departments =baseMapper.selectList(queryWrapper);
        // 生成部门树并返回
        return DepartmentTree.makeDepartmentTree(departments,0L);
    }

    /**
     * 查询上级部门列表
     *
     * @return
     */
    @Override
    public List<Department> findParentDepartment() {
        QueryWrapper<Department> queryWrapper =new QueryWrapper<>();
        queryWrapper.orderByAsc("order_num");
        // 查询部门列表
        List<Department> departments =baseMapper.selectList(queryWrapper);
        // 创建部门对象
        Department department = new Department();
        department.setId(0L);
        department.setDepartment("顶级部门");
        department.setPid(-1L);

        List<Department> departmentTree = DepartmentTree.makeDepartmentTree(departments, -1L);
        departments.add(department);

        return departmentTree;
    }

    /**
     * 判断部门下是否有子部门
     *
     * @param id
     * @return
     */
    @Override
    public boolean hasChildrenOfDepartment(Long id) {
        //创建条件构造器对象
        QueryWrapper<Department> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("pid",id);
        //如果数量大于0,表示存在
        return baseMapper.selectCount(queryWrapper) > 0;
    }

    /**
     * 判断部门下是否存在用户
     *
     * @param id
     * @return
     */
    @Override
    public boolean hasUserOfDepartment(Long id) {
        //创建条件构造器对象
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("department_id",id);
        //如果数量大于0,表示存在
        return userMapper.selectCount(queryWrapper) > 0;
    }
}
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

Controller层

@RestController
@RequestMapping("/api/department")
public class DepartmentController {
    @Resource
    private DepartmentServiceImpl departmentService;

    /**
     * 查询部门列表
     *
     * @param departmentQueryVo
     * @return
     */
    @GetMapping("/list")
    public Result list(DepartmentQueryVo departmentQueryVo) {
        //调用查询部门列表方法
        List<Department> departmentList =
                departmentService.findDepartmentList(departmentQueryVo);
        //返回数据
        return Result.ok(departmentList);
    }

    /**
     * 查询上级部门列表
     *
     * @return
     */
    @GetMapping("/parent/list")
    public Result getParentDepartment() {
        //调用查询上级部门列表方法
        List<Department> departmentList =
                departmentService.findParentDepartment();
        //返回数据
        return Result.ok(departmentList);
    }

    /**
     * 添加部门
     *
     * @param department
     * @return
     */
    @PostMapping("/add")
    public Result add(@RequestBody Department department) {
        if (departmentService.save(department)) {
            return Result.ok(null, "部门添加成功");
        }
        return Result.fail(ERR_CODE, "部门添加失败");
    }

    /**
     * 修改部门
     *
     * @param department
     * @return
     */
    @PutMapping("/update")
    public Result update(@RequestBody Department department) {
        if (departmentService.updateById(department)) {
            return Result.ok(null, "部门修改成功");
        }
        return Result.fail(ERR_CODE, "部门修改失败");
    }

    /**
     * 查询某个部门下是否存在子部门
     *
     * @param id
     * @return
     */
    @GetMapping("/check/{id}")
    public Result check(@PathVariable Long id) {
        //调用查询部门下是否存在子部门的方法
        if (departmentService.hasChildrenOfDepartment(id)) {
            return Result.fail(EXIST_ERR_CODE, "该部门下存在子部门,无法删除");
        }
        //调用查询部门下是否存在用户的方法
        if (departmentService.hasUserOfDepartment(id)) {
            return Result.fail(EXIST_ERR_CODE, "该部门下存在用户,无法删除");
        }
        return Result.ok();
    }

    /**
     * 删除部门
     *
     * @param id
     * @return
     */
    @DeleteMapping("/delete/{id}")
    public Result delete(@PathVariable Long id) {
        if (departmentService.removeById(id)) {
            return Result.ok("部门删除成功");
        }
        return Result.fail(ERR_CODE, "部门删除失败");
    }

}
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
最后更新于: 2024/2/27 17:14:39