const routes = [
{
name: 'home',
path: '/',
meta: {
title: '首页'
},
component: Home,
redirect: '/welcome',
children: [
{
name: 'welcome',
path: '/welcome',
meta: {
title: '欢迎体验Vue3全栈课程'
},
component: () => import('@/views/Welcome.vue')
}
]
},
{
name: 'login',
path: '/login',
meta: {
title: '登录'
},
component: () => import('@/views/Login.vue')
},
{
name: '404',
path: '/404',
meta: {
title: '页面不存在'
},
component: () => import('@/views/404.vue')
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
async function loadAsyncRoutes() {
let userInfo = storage.getItem('userInfo') || {}
if (userInfo.token) {
try {
const { menuList } = await API.getPermissionList()
let routes = utils.generateRoute(menuList)
routes.map(route => {
let url = `./../views/${route.component}.vue`
route.component = () => import(url);
router.addRoute("home", route);
})
} catch (error) {
}
}
}
await loadAsyncRoutes();
// 判断当前地址是否可以访问
/*
function checkPermission(path) {
let hasPermission = router.getRoutes().filter(route => route.path == path).length;
if (hasPermission) {
return true;
} else {
return false;
}
}
*/
// 导航守卫
router.beforeEach((to, from, next) => {
if (router.hasRoute(to.name)) {
document.title = to.meta.title;
next()
} else {
next('/404')
}
})
export default router
页面的布局开发
交互开发
接口mock出来
/user/login
布局-》交互-》接口-》接口联调
storage数据存储,
state,mutations
const state={userInfo:""||stroage.getItem("userInfo");
mutations业务层数据提交
<template>
<div class="login-wrapper">
<div class="modal">
<el-form ref="userForm" :model="user" status-icon :rules="rules">
<div class="title">火星</div>
<el-form-item prop="userName">
<el-input
type="text"
prefix-icon="el-icon-user"
v-model="user.userName"
/>
</el-form-item>
<el-form-item prop="userPwd">
<el-input
type="password"
prefix-icon="el-icon-view"
v-model="user.userPwd"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" class="btn-login" @click="login"
>登录</el-button
>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
import storage from "./../utils/storage";
export default {
name: "login",
data() {
return {
user: {
userName: "admin",
userPwd: "123456",
},
rules: {
userName: [
{
required: true,
message: "请输入用户名",
trigger: "blur",
},
],
userPwd: [
{
required: true,
message: "请输入密码",
trigger: "blur",
},
],
},
};
},
methods: {
login() {
this.$refs.userForm.validate((valid) => {
if (valid) {
this.$api.login(this.user).then(async (res) => {
this.$store.commit("saveUserInfo", res);
await this.loadAsyncRoutes();
this.$router.push("/welcome");
});
} else {
return false;
}
});
},
async loadAsyncRoutes() {
let userInfo = storage.getItem("userInfo") || {};
if (userInfo.token) {
try {
const { menuList } = await this.$api.getPermissionList();
let routes = utils.generateRoute(menuList);
routes.map((route) => {
let url = `./../views/${route.component}.vue`;
route.component = () => import(url);
this.router.addRoute("home", route);
});
} catch (error) {}
}
},
goHome() {
this.$router.push("/welcome");
},
},
};
</script>
<style lang="scss">
.login-wrapper {
display: flex;
justify-content: center;
align-items: center;
background-color: #f9fcff;
width: 100vw;
height: 100vh;
.modal {
width: 500px;
padding: 50px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0px 0px 10px 3px #c7c9cb4d;
.title {
font-size: 50px;
line-height: 1.5;
text-align: center;
margin-bottom: 30px;
}
.btn-login {
width: 100%;
}
}
}
</style>
交互开发
mock出来
/**
* api管理
*/
import request from './../utils/request'
export default {
login(params) {
return request({
url: '/users/login',
method: 'post',
data: params,
})
},
业务层数据提交
/**
* Mutations业务层数据提交
*/
import storage from './../utils/storage'
export default {
saveUserInfo(state, userInfo) {
state.userInfo = userInfo;
storage.setItem('userInfo', userInfo)
},
saveMenuList(state, menuList) {
state.menuList = menuList;
storage.setItem('menuList', menuList)
},
saveActionList(state, actionList) {
state.actionList = actionList;
storage.setItem('actionList', actionList)
},
saveNoticeCount(state, noticeCount) {
state.noticeCount = noticeCount;
storage.setItem('noticeCount', noticeCount)
}
}
vuex状态管理
/**
* Vuex状态管理
*/
import { createStore } from 'vuex'
import mutations from './mutations'
import storage from './../utils/storage'
const state = {
userInfo: storage.getItem("userInfo") || {}, // 获取用户信息
menuList: storage.getItem("menuList"),
actionList: storage.getItem("actionList"),
noticeCount: 0
}
export default createStore({
state,
mutations
})
vuex用户点击派发action,action去commitmuation,muation去保存状态
用户登录的服务端实现
/**
* 配置文件
*/
module.exports = {
URL:'mongodb://127.0.0.1:27017/imooc-manager'
}
数据局链接
/**
* 数据库连接
*/
const mongoose = require('mongoose')
const config = require('./index')
const log4js = require('./../utils/log4j')
mongoose.connect(config.URL,{
useNewUrlParser: true,
useUnifiedTopology: true
})
const db = mongoose.connection;
db.on('error',()=>{
log4js.error('***数据库连接失败***')
})
db.on('open',()=>{
log4js.info('***数据库连接成功***')
})
用户管理路由
/**
* 用户管理模块
*/
const router = require('koa-router')()
const User = require('./../models/userSchema')
const Counter = require('./../models/counterSchema')
const Menu = require('./../models/menuSchema')
const Role = require('./../models/roleSchema')
const util = require('./../utils/util')
const jwt = require('jsonwebtoken')
const md5 = require('md5')
router.prefix('/users')
// 用户登录
router.post('/login', async (ctx) => {
try {
const { userName, userPwd } = ctx.request.body;
/**
* 返回数据库指定字段,有三种方式
* 1. 'userId userName userEmail state role deptId roleList'
* 2. {userId:1,_id:0}
* 3. select('userId')
*/
const res = await User.findOne({
userName,
userPwd: md5(userPwd)
}, 'userId userName userEmail state role deptId roleList')
if (res) {
const data = res._doc;
const token = jwt.sign({
data
}, 'imooc', { expiresIn: '1h' })
data.token = token;
ctx.body = util.success(data)
} else {
ctx.body = util.fail("账号或密码不正确")
}
} catch (error) {
ctx.body = util.fail(error.msg)
}
})
// 用户列表
router.get('/list', async (ctx) => {
const { userId, userName, state } = ctx.request.query;
const { page, skipIndex } = util.pager(ctx.request.query)
let params = {}
if (userId) params.userId = userId;
if (userName) params.userName = userName;
if (state && state != '0') params.state = state;
try {
// 根据条件查询所有用户列表
const query = User.find(params, { _id: 0, userPwd: 0 })
const list = await query.skip(skipIndex).limit(page.pageSize)
const total = await User.countDocuments(params);
ctx.body = util.success({
page: {
...page,
total
},
list
})
} catch (error) {
ctx.body = util.fail(`查询异常:${error.stack}`)
}
})
// 获取全量用户列表
router.get('/all/list', async (ctx) => {
try {
const list = await User.find({}, "userId userName userEmail")
ctx.body = util.success(list)
} catch (error) {
ctx.body = util.fail(error.stack)
}
})
// 用户删除/批量删除
router.post('/delete', async (ctx) => {
// 待删除的用户Id数组
const { userIds } = ctx.request.body
// User.updateMany({ $or: [{ userId: 10001 }, { userId: 10002 }] })
const res = await User.updateMany({ userId: { $in: userIds } }, { state: 2 })
if (res.nModified) {
ctx.body = util.success(res, `共删除成功${res.nModified}条`)
return;
}
ctx.body = util.fail('删除失败');
})
// 用户新增/编辑
router.post('/operate', async (ctx) => {
const { userId, userName, userEmail, mobile, job, state, roleList, deptId, action } = ctx.request.body;
if (action == 'add') {
if (!userName || !userEmail || !deptId) {
ctx.body = util.fail('参数错误', util.CODE.PARAM_ERROR)
return;
}
const res = await User.findOne({ $or: [{ userName }, { userEmail }] }, '_id userName userEmail')
if (res) {
ctx.body = util.fail(`系统监测到有重复的用户,信息如下:${res.userName} - ${res.userEmail}`)
} else {
const doc = await Counter.findOneAndUpdate({ _id: 'userId' }, { $inc: { sequence_value: 1 } }, { new: true })
try {
const user = new User({
userId: doc.sequence_value,
userName,
userPwd: md5('123456'),
userEmail,
role: 1, //默认普通用户
roleList,
job,
state,
deptId,
mobile
})
user.save();
ctx.body = util.success('', '用户创建成功');
} catch (error) {
ctx.body = util.fail(error.stack, '用户创建失败');
}
}
} else {
if (!deptId) {
ctx.body = util.fail('部门不能为空', util.CODE.PARAM_ERROR)
return;
}
try {
const res = await User.findOneAndUpdate({ userId }, { mobile, job, state, roleList, deptId, })
ctx.body = util.success({}, '更新成功')
} catch (error) {
ctx.body = util.fail(error.stack, '更新失败')
}
}
})
// 获取用户对应的权限菜单
router.get("/getPermissionList", async (ctx) => {
let authorization = ctx.request.headers.authorization
let { data } = util.decoded(authorization)
let menuList = await getMenuList(data.role, data.roleList);
let actionList = getAction(JSON.parse(JSON.stringify(menuList)))
ctx.body = util.success({ menuList, actionList });
})
async function getMenuList(userRole, roleKeys) {
let rootList = []
if (userRole == 0) {
rootList = await Menu.find({}) || []
} else {
// 根据用户拥有的角色,获取权限列表
// 现查找用户对应的角色有哪些
let roleList = await Role.find({ _id: { $in: roleKeys } })
let permissionList = []
roleList.map(role => {
let { checkedKeys, halfCheckedKeys } = role.permissionList;
permissionList = permissionList.concat([...checkedKeys, ...halfCheckedKeys])
})
permissionList = [...new Set(permissionList)]
rootList = await Menu.find({ _id: { $in: permissionList } })
}
return util.getTreeMenu(rootList, null, [])
}
function getAction(list) {
let actionList = []
const deep = (arr) => {
while (arr.length) {
let item = arr.pop();
if (item.action) {
item.action.map(action => {
actionList.push(action.menuCode)
})
}
if (item.children && !item.action) {
deep(item.children)
}
}
}
deep(list)
return actionList;
}
module.exports = router
用户表的配置
dbjs数据库链接监听,定义router.prefix("users")作为二级路由,router.post定义login接口,koa-router掉用一级路由prefix("/api"),通过router调用二级路由,
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
const path = require('path')
// https://vitejs.dev/config/
export default defineConfig({
resolve: {
alias:{
'@': path.resolve( __dirname, './src' )
}
},
// css: {
// preprocessorOptions: {
// scss: {
// additionalData: `@import '@/assets/style/base.scss';`
// }
// }
// },
server:{
host:'localhost',
port:8080,
proxy:{
"/api":{
target:"http://localhost:3000"
}
}
},
plugins: [vue()]
})
前段配置代理,前后端联调