npm i -g @vue/cli
npm i -g nodemon
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fl6ILs3N-1659141767327)(01/02.PNG)]
初始化
cd server
npm init -y
新建服务的入口文件 index.js
在package.json中配置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P3kDUNWD-1659141767331)(01/02-2.PNG)]
这样启动server项目的时候用 npm run serve
启动
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ThEseTv1-1659141767332)(01/03.PNG)]
用vue2.0 默认
vue create web
选择默认的就好
vue create admin
总的目录:
node-vue-moba
server
admin
web
cd admin 项目
启动项目 npm run serve
在此项目中新开一个命令行窗口,添加一些依赖
vue add element
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8WpiY1fs-1659141767335)(01/04-1.PNG)]
vue add router
选择no
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N4JugLaL-1659141767336)(01/报错01.PNG)]
解决:
原来是用vue3.0 将版本降为2.0的
vue 下新建Main.vue
复制element 官网下的布局
在index.js中引用
安装axios
npm i axios
和main.js同级新建http.js
import axios from 'axios'
const http = axios.create({
baseURL: 'http://localhost:3000/admin/api' // 暂时的
})
export default http
main.js中 应用
import http from './http' ;Vue.prototype.$http = http
在el-menu 中添加 rouer 属性,
el-menu-item index=“/” // 就可以跳转到设置的路由
el-main 里面 这样访问的页面就是在mian 里显示
安装一些插件
npm i express@next mongoose cors
admin项目中 准备视图
根据喜好在element组件中自行寻找
Main.vue
内容管理
分类
新建分类
分类列表
查看
新增
删除
王小虎
CategoryEdit.vue
{{id? '编辑':'新建'}}分类
保存
CategoryList.vue
编辑
删除
http.js中配置了跨域的请求
import axios from 'axios'
const http = axios.create({
baseURL: 'http://localhost:3000/admin/api'
})
export default http
index.js中配置子路由
import Main from '../views/Main.vue'
import CategoryEdit from "../views/CategoryEdit"
import CategoryList from "../views/CategoryList"
const routes = [
{
path: '/',
name: 'Main',
component: Main,
children: [
{path: '/categories/create',component: CategoryEdit},
{path: '/categories/edit/:id',component: CategoryEdit,props: true},
{path: '/categories/list',component: CategoryList},
]
}
]
server项目中
models目录是各种集合的schema
Category.js
const mongoose = require('mongoose')
// 定义schema
const schema = new mongoose.Schema({
name:{type:String}
})
// 导出
module.exports = mongoose.model('Category',schema,'category')
plugins目录下的db.js是连接数据库的
db.js
module.exports = app =>{
const mongoose = require('mongoose')
// 连接数据库
mongoose.connect('mongodb://127.0.0.1:27017/node-vue-moba')
}
index.js 配置了中间件
const express = require("express")
const app = express()
// 配置中间件
app.use(require('cors')()) // 跨域
app.use(express.json())
// 引入
require('./routes/admin')(app)
require('./plugins/db')(app)
app.listen(3000,()=>{
console.log("http://localhost:3000");
})
在router/admin 目录下编写接口文件
admin目录下的index.js
module.exports = app =>{
const express = require("express")
const router = express.Router()
const Category = require('../../models/Category')
// 创建分类
router.post('/categories',async(req,res)=>{
const model = await Category.create(req.body);
res.send(model)
});
// 修改分类
router.put('/categories/:id',async(req,res)=>{
const model = await Category.findByIdAndUpdate(req.params.id,req.body);
res.send(model);
})
// 获取分类
router.get('/categories',async(req,res)=>{
const items = await Category.find().limit(10);
res.send(items)
});
// 根据id获取分类
router.get('/categories/:id',async(req,res)=>{
const model = await Category.findById(req.params.id);
res.send(model)
});
// 删除
router.delete('/categories/:id',async(req,res)=>{
await Category.findByIdAndDelete(req.params.id);
res.send({
success: true
})
});
app.use('/admin/api',router)
}
访问localhost:3000/admin/api/categories
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NJgt58Qq-1659141767338)(01/分类01.PNG)]
admin项目的main.js 中配置了http,所有在项目中可以使用this.$http来使用
import http from './http'
Vue.prototype.$http = http
CategoryList.vue中的数据绑定与方法编写
CategoryEdit.vue中的数据绑定与方法编写
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mva5Tw5l-1659141767340)(01/分类02.PNG)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZpKxqh6S-1659141767341)(01/分类03.PNG)]
设计思路
module.exports = app =>{
const express = require("express")
const router = express.Router()
// 创建分类
router.post('/',async(req,res)=>{
const model = await req.Model.create(req.body);
res.send(model)
});
// 修改分类
router.put('/:id',async(req,res)=>{
const model = await req.Model.findByIdAndUpdate(req.params.id,req.body);
res.send(model);
})
// 获取分类
router.get('/',async(req,res)=>{
// 特定参数处理
const queryOptions = {};
// console.log(req.Model);//Model { Category }
// console.log(req.Model.modelName);//Category
if(req.Model.modelName ==='Category'){
queryOptions.populate = 'parent'
}
const items = await req.Model.find().setOptions(queryOptions).limit(10);
res.send(items)
});
// 根据id获取分类
router.get('/:id',async(req,res)=>{
const model = await req.Model.findById(req.params.id);
res.send(model)
});
// 删除
router.delete('/:id',async(req,res)=>{
await req.Model.findByIdAndDelete(req.params.id);
res.send({
success: true
})
});
// 将最后的路由设为动态参数
// 中间件处理这个参数
app.use('/admin/api/rest/:resource',async(req,res,next)=>{
// 用inflection插件 处理参数,将小写改为大写,复数改单数,就是类的名称
const Model = require('inflection').classify(req.params.resource);
//根据处理后的参数对应引用文件 挂载到reqa的Model参数中
req.Model = require(`../../models/${Model}`);
// 放行
next();
},router)
}
Login.vue
登录
import Vue from 'vue'
import Router from 'vue-router'
import Login from '../views/Login'
Vue.use(Router)
const router =new Router({
routes:[
// 定义了一个meta 方便前端权限验证
{path: '/login',name:'Login',component:Login, meta: { isPublic: true } },
]})
// vue router文档
router.beforeEach((to, from ,next) => {
// 如果要去的页面是不公开的 并且token为空
if (!to.meta.isPublic && !localStorage.token) {
//跳转到登录
return next('/login')
}
// 正常情况放行
next()
})
export default router
用到了一个第三方插件 http-assert
npm i http-assert
作用:当不满足条件时,向前端发送报错信息
app.post('/admin/api/login',async (req,res)=>{
//解构
const {username,password} = req.body;
// 1. 根据用户名判断用户是否存在
// 查的时候取出password字段
const user = await AdminUser.findOne({username}).select('+password');
// if(!user){
// // 如果用户不存在,返回 设置状态码为422
// return res.status(422).send({
// message: '用户不存在'
// })
// }
//当user为空时,状态码是422,提示信息是 用户不存在
assert(user,422,'用户不存在') // 效果与上相同
// 2.校验密码
const isValid = require('bcryptjs').compareSync(password,user.password)
// if(!isValid){
// return res.status(422).send({
// message:'密码错误'
// })
// }
assert(isValid,422,'密码错误')
//3.返回token
//生成一个token 第2个参数是一个密钥,用来验证token是否篡改过
const token = jwt.sign({id:user._id},app.get('secret'))
// console.log(token)
res.send({token})
})
//错误处理
app.use(async (err,req,res,next)=>{
res.status(err.statusCode || 500).send({
message: err.message
})
})
前端封装axios的文件中处理响应
http.js
import axios from 'axios'
import Vue from 'vue'
import router from './router'
const http = axios.create({
baseURL: 'http://localhost:3000/admin/api'
})
// 全局处理
// https://www.npmjs.com/package/axios#interceptors 文档查看具体使用
// 处理响应
http.interceptors.response.use(res =>{
return res
},err =>{
if(err.response.data.message){
// 处理 错误
// vue组件弹出消息
// err.response.data.message 取出服务端返回的错误信息
Vue.prototype.$message({
type: 'error',
message: err.response.data.message
})
if (err.response.status === 401) {
router.push('/login')
}
}
return Promise.reject(err)
})
export default http
当登录成功时,服务端返回了一个token给前端
所以在登录时获取到token后要把它放到请求头里,
http.js
import axios from 'axios'
import Vue from 'vue'
import router from './router'
const http = axios.create({
baseURL: 'http://localhost:3000/admin/api'
})
// 全局处理
// https://www.npmjs.com/package/axios#interceptors 文档查看具体使用
// 处理请求
http.interceptors.request.use(function (config) {
// Do something before request is sent
if (localStorage.token) {
config.headers.Authorization = 'Bearer ' + localStorage.token
}
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// 处理响应
http.interceptors.response.use(res =>{
return res
},err =>{
if(err.response.data.message){
// 处理 错误
// vue组件弹出消息
// err.response.data.message 取出服务端返回的错误信息
Vue.prototype.$message({
type: 'error',
message: err.response.data.message
})
if (err.response.status === 401) {
router.push('/login')
}
}
return Promise.reject(err)
})
export default http
后端接口中,当请求数据时,先判断请求头是否携带token并解析token是否正确,验证通过才返回数据,验证不通过则提示请先登录
使用中间件处理
封装middleware->auth.js
module.exports = options => {
const assert = require('http-assert')
const jwt = require('jsonwebtoken')
const AdminUser = require('../models/AdminUser')
return async (req, res, next) => {
// 获取并截取token
const token = String(req.headers.authorization || '').split(' ').pop()
assert(token, 401, '请先登录')
//根据规格验证获得id
const { id } = jwt.verify(token, req.app.get('secret'))
assert(id, 401, '请先登录')
//通过id去数据库验证是否正确
req.user = await AdminUser.findById(id)
assert(req.user, 401, '请先登录')
await next()
}
}
然后在需要使用拦截的地方引用就ok
const authMiddleware = require('../../middleware/auth')
app.use('/admin/api/rest/:resource', authMiddleware(), router)