项目已上传到git 感兴趣的下伙伴欢迎下载Git连接
创建VUE项目
vue create 名字
初始化项目
npm init -y
安装nodemon
npm i -g nodemon
"scripts": {
"serve":"nodemon index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
安装element
vue add element
router
vue add router
express
导入:npm i express@next mongoose cors
"use strict";
const express = require("express");
const app = express();
// 启用跨域
app.use(require("cors")());
// 启用 json 解析
app.use(express.json());
// 连接数据库
var MongoClient = require("mongodb").MongoClient;
var url = "mongodb://localhost:27017/";
let dbexam;
MongoClient.connect(url, function (err, db) {
if (err) throw err;
dbexam = db.db("exam");
});
// 创建路由
const router = express.Router({ mergeParams: true });
// 路由匹配
router.post("/query", async (req, res) => {
let stuID = req.body["stuID"];
let stuNumber = req.body["stuNumber"];
let queryResult;
dbexam
.collection("stuInfo")
.find({ $and: [{ ksbh: stuNumber }, { zjhm: stuID }] })
.toArray((err, result) => {
// 返回集合中所有数据·
if (err) throw err ;
queryResult = result;
res.send(queryResult);
});
});
// 路由挂载
app.use(router);
// 启动
app.listen(3001, () => {
console.log("Server running at http://localhost:3001");
});
//ajax
function btnSubmit() {
console.log("click");
$.ajax({
/* url 地址可以是 /get-json/ 的方式
* 也可以是 http://www.qfedu.com/get-json/ 的方式
*/
url: "http://localhost:3001/query",
type: "POST",
contentType: "application/json",
dataType: "json",
data: JSON.stringify({
stuID: "152122198812156600",
stuNumber: "104460123401042",
}),
success: function (res) {
// 成功处理逻辑
console.log(res);
},
error: function (res) {
// 错误时处理逻辑
console.log(res);
},
});
}
通用CRUD接口
实现类名数据格式转换inflection包
npm i inflection
// 获取详情页面的接口 :id路径匹配,相当于前端传递的一个参数,即接收参数,参数名为id
//http://localhost:3000/admin/api/rest/categories中categories即为参数
可以通过req.params.resoure获取这个参数
app.use('/admin/api/rest/:resoure', router)
// 定义一个express的子路由
const router = express.Router({
//表示合并URL参数
mergeParams:true
})
// 挂载子路由在app对象上面,路径会自动加载为http://localhost:3000/admin/api/rest/参数
app.use('/admin/api/rest/:resoure',async(req,res,next)=>{
//创建中间键,每次发送的url请求都会经过中间键的处理
const modelName = require('inflection').classify(req.params.resoure)
//将传递的参数改为首字母大写并构建成模型的地址
req.Model = require(`../../models/${modelName}`)
//next()表示执行下一步
next()
}, router)
图片上传
baseURL:'http://localhost:3000/admin/api'
//全局获取baseUrl并且构造图片的上传地址url,elemen组件中upload组件
:action="$http.defaults.baseURl + '/upload'"
//托管静态文件,让__dirname + '/uploads'中的文件可以通过/uploads来访问
app.use('/uploads',express.static(__dirname + '/uploads'))
const multer = require('multer')
const upload = multer({
//__dirname:使用绝对地址 __dirname这个文件所在的文件夹
dest: __dirname + '/../../uploads'
})
//upload.single('file')中间键获取前端提交的file信息并且绑定到req请求对象上
app.use('/admin/api/upload',upload.single('file'),async(req,res)=>{
const file = req.file
file.url = `http://localhost:3000/uploads/${file.filename}`
res.send(file)
})
//vue数据响应式处理
this.$set(this.model,'icon',res.url) //vue的显示赋值效果,用于数据一开始不存在,后面需要加上的情况
中间键的专门处理上传数据multer
npm i multer
一个model对象关联多个其他model对象
//关联数组,实现一个model对象关联多个其它对象
categories: [{
type: mongoose.SchemaType.ObjectId,
ref: 'Category'
}]
命名规范
数组库:model类名:首字母大写
前端js:变量名:首字母小写的驼峰命名,类名:首字母大写的驼峰命名
MYSQL:多个单词下划线
mongo:首字母小写的驼峰命名
避免前端定义对象被后端返回对象覆盖问题
<el-input v-model="model.scores.difficult"></el-input>//未避免difficult未定义报错,需要在data的model对象里面加上scores空对象
//vue里面的显示赋值
this.model = Object.assign({},this.model,res.data);//保证model对象为this.model的数据加上res.data的数据,如果this.model里面有的,res.data就会覆盖,如果res.data里面没有的数据就不会影响this.madel的数据
属性赋值
this.$set(this.model,'avatar',res.url) //vue的显示赋值效果,用于数据一开始不存在,后面需要加上的情况
this.model.avatar = res.url //data里面定义了数据属性
// data为对象数组,需要修改其中一个对象的某个属性
cancelfavo(ID) {
//向后台返回学生stuNo,取消收藏该学生
//调用函数,重新加载页面
for (let index = 0; index < this.data.length; index++) {
const element = this.data[index];
if (element.stuNo === ID) {
this.$set(this.data[index], "isfavorites", false);
this.cancel(element.stuNo);
break;
}
}
}
数组插入删除操作
//插入
@click="model.skills.push({})"//插入空对象
//删除
@click="model.skills.splice(i,count)"//从第i个位置开始删除count个元素
富文本编辑器安装
npm install --save vue2 --editor
import { VueEditor } from "vue2-editor";//引用vue2-editor中的VueEditor子对象
import a from "vue2-editor"; //获取全部,r使用a.VuEditr
//保存图片到服务器
<vue-editor
useCustomImageHandler
@image-added="handleImageAdded"
v-model="model.body"
></vue-editor>
methods: {
async handleImageAdded(file, Editor, cursorLocation, resetUploader) {
// An example of using FormData
// NOTE: Your key could be different such as:
// formData.append('file', file)
const formData = new FormData();
formData.append("file", file);
const res = await this.$http.post('upload',formData)
Editor.insertEmbed(cursorLocation, "image", res.data.url);
resetUploader();
},
全局样式引入
在main.js文件中 import ‘./style.css’ 即可全局引入style.css
根据路由路径来高亮显示
<el-menu router :default-openeds="['1']" unique-opened :default-active="$route.path">
密码加密
安装bcrypt
npm i bcrypt
const schema = new mongoose.Schema({
username: {
type: String
},
password: {
type: String,
select:false,//密码散列默认查不出来,即前端无法得到密码的散列
//用set实现对传递的数据进行处理后再存储到数据库,val代表需要处理的值
set(val){
return require('bcrypt').hashSync(val,10) //生成参数的散列,实现加密,后一个指数越高越耗时,10到12即可,不可列散列,绝对唯一性
}
}
})
响应拦截器(http)
// 给http整个请求加一个拦截器response,会拦截处理所有返回服务器的内容,如果有错进行相应的处理,响应拦截器
http.interceptors.response.use(res => {
return res
}, err => {
if (err.response.data.message) {
// Vue.prototype.$message.error(err.response.data.message)
Vue.prototype.$message({
type: 'error',
message: err.response.data.message
})
}
return Promise.reject(err)
})
登录验证
安装jsonwebtoken
npm i jsonwebtoken
安装http-assert
npm install http-assert
var assert = require('http-assert')
var ok = require('assert')
var username = 'foobar' // username from request
try {
// assert(判断条件, 如果不对返回状态码, '返回信息') 会抛出异常
assert(username === 'fjodor', 401, 'authentication failed')
} catch (err) {
ok(err.status === 401)
ok(err.message === 'authentication failed')
ok(err.expose)
}
// assert(判断条件, 如果不对返回状态码, '返回信息') 会抛出异常 在全局捕获错误加中间件处理
app.use(async(err,req,res,next)=>{
res.status(err.statusCode).send({
message:err.message
})
})
Vue全局绑定mixin
//全局绑定mixin,相当于为每一个Vue组件绑定了一个方法(组件)
Vue.mixin({
computed:{
uploadUrl(){
return this.$http.defaults.baseURL + '/upload'
}
},
methods:{
getAuthHeaders(){
return{
Authorization:`Bearer ${localStorage.token || ''}`
}
}
}
})
HTTP拦截器
import axios from 'axios'
import Vue from 'vue'
import router from './router'
const http = axios.create({
baseURL: 'http://localhost:3000/admin/api'
})
// 给http整个请求加一个拦截器response,会拦截处理所有返回服务器的内容,如果有错进行相应的处理,响应拦截器
http.interceptors.response.use(res => {
return res
}, err => {
if (err.response.data.message) {
// Vue.prototype.$message.error(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)
})
//给http整个请求加一个拦截器request,会拦截处理所有请求,进行相应的处理,请求拦截器
http.interceptors.request.use(function (config) {
if (localStorage.token) {
config.headers.Authorization = 'Bearer ' + (localStorage.token)
}
return config
}, function (error) {
return Promise.reject(error)
})
export default http
路由导航守卫(router)
{
path: '/login',
name: 'Login',
component: Login,
// 设置路由源信息,是否需要路由验证
meta:{
isPublic:true
}
},
//路由导航守卫
router.beforeEach((to,from,next)=>{
if(!to.meta.isPublic && !localStorage.token){
return next('/login')
}
next()
})
//父子路由
{
path: '/',
name: 'Main',
component: Main,
children: [{
path: "/categories/create",
component: CategoryEdit
},]
index路由分配
const AdminUser = require('../../models/AdminUser')
// 导出一个函数,这个函数接收一个app对象,在函数里面就可以用接收的对象
module.exports = app => {
const express = require('express')
const jwt = require('jsonwebtoken')
const AdminUser = require('../../models/AdminUser')
const assert = require('http-assert')
// 引入req.Model模型
// const req.Model = require('../../models/req.Model')
// 定义一个express的子路由
const router = express.Router({
//表示合并URL参数
mergeParams: true
})
// 登录检验中间键
const authMiddleware = require('../../middleware/auth')
//资源中间键
const resourceMiddleware = require('../../middleware/resource')
// post路径为:http://localhost:3000/admin/api/ 创建资源
router.post('/', async (req, res) => {
// 客户端提交的内容中的body为需要构建模型的内容,需要用await转换为同步
const model = await req.Model.create(req.body)
// 返回创建的model,req是客户端对象,res是服务端对象
res.send(model)
})
//删除资源
router.delete('/:id', async (req, res) => {
// params为提交的URl,可获得其中的内容,findByIdAndUpdate(更具什么来查,更新的内容)
await req.Model.findByIdAndDelete(req.params.id)
// 返回创建的model,req是客户端对象,res是服务端对象
res.send({
success: true
})
})
// put路径为:http://localhost:3000/admin/api/ 修改资源
router.put('/:id', async (req, res) => {
// params为提交的URl,可获得其中的内容,findByIdAndUpdate(更具什么来查,更新的内容)
const model = await req.Model.findByIdAndUpdate(req.params.id, req.body)
// 返回创建的model,req是客户端对象,res是服务端对象
res.send(model)
})
// 路径为:http://localhost:3000/admin/api/ 资源列表
router.get('/',authMiddleware(), async (req, res) => {
// populate可以将关联数据以对象的形式传递
const queryOptions = {}
//如果模型有分类加上parent
if (req.Model.modelName === 'Category') {
queryOptions.populate = 'parent'
}
// 查询req.Model模型数据返回,限制10条
//setOptions()创建数据集
const items = await req.Model.find().setOptions(queryOptions).limit(10)
// 返回创建的model,req是客户端对象,res是服务端对象
res.send(items)
})
// 获取详情页面的接口 :id路径匹配,相当于前端传递的一个参数,即接收参数,参数名为id 资源详情页
router.get('/:id', async (req, res) => {
// 根据前端请求的id用findById查询CorCategy模型数据返回
const model = await req.Model.findById(req.params.id)
// 返回创建的model,req是客户端对象,res是服务端对象
res.send(model)
})
// 挂载子路由在app对象上面,路径会自动加载为http://localhost:3000/admin/api
app.use('/admin/api/rest/:resoure', authMiddleware(),resourceMiddleware() , router)
const multer = require('multer')
const upload = multer({
//__dirname:使用绝对地址 __dirname这个文件所在的文件夹
dest: __dirname + '/../../uploads'
})
app.use('/admin/api/upload',authMiddleware(), upload.single('file'), async (req, res) => {
const file = req.file
file.url = `http://localhost:3000/uploads/${file.filename}`
res.send(file)
})
//管理员登录路由
app.post('/admin/api/login', async (req, res) => {
const {
username,
password
} = req.body
// 1.根据用户名找用户
// const AdminUser = require('../../models/AdminUser')
const user = await AdminUser.findOne({
username: username
// username
}).select('+password') //因为再AdminUser模型中设置select:false,所以默认取不到password,故加上select('+password)可以取到
// if (!user) {
// return res.status(422).send({
// message: "用户名不存在"
// })
// }
assert(user, 422, "用户不存在")
// 2.校验密
//compareSync(明文,密文);比较明文和密文是否相等
const isValid = require('bcrypt').compareSync(password, user.password)
assert(isValid, 422, "密码错误")
// 3.返回token
// const jwt = require('jsonwebtoken')
// sing(需要加密的信息[对象],加密的密钥[全局配置]),app.get获取app上全局绑定的属性
const token = jwt.sign({
id: user._id,
// _id:user._id,
}, app.get('secret'))
res.send({
token
})
})
//错误处理函数
app.use(async (err, req, res, next) => {
//没有状态码会报 500 错误
res.status(err.statusCode || 500).send({
message: err.message
})
})
}
auth中间键
module.exports = option =>{
const jwt = require('jsonwebtoken')
const AdminUser = require('../models/AdminUser')
const assert = require('http-assert')
return async (req, res, next) => {
//添加中间件 String(req.header.authorization || '') 将传递的token转换为字符串或者为空(不存在)再通过空格分割,pop()获取最后一个元素
const token = String(req.headers.authorization || '').split(' ').pop()
assert(token, 401, "请先登录")
//verify解析token
const {
id
} = jwt.verify(token, req.app.get('secret'))
assert(id, 401, "请先登录")
//给客户端绑定 user,使得后一个函数也可以使用同一个req
req.user = await AdminUser.findById(id)
assert(req.user, 401, "请先登录")
await next()
}
}
File中间键
module.exports = option => {
return async (req, res, next) => {
//创建中间键,每次发送的url请求都会经过中间键的处理
const modelName = require('inflection').classify(req.params.resoure)
//将传递的参数改为首字母大写并构建成模型的地址
req.Model = require(`../models/${modelName}`)
//next()表示执行下一步
next()
}
}
server服务器index
const express = require("express")
const app=express()
// 引入跨域模块
app.use(require('cors')())
// 接收客户端传递的json数据必须引用express.json
app.use(express.json())
//托管静态文件,让uploads中的文件可以通过/uploads来访问
app.use('/uploads',express.static(__dirname + '/uploads'))
// 将app传递给admin中,在admin中可以用module.exports = app=>{}接收
require('./routes/admin')(app)
require('./plugins/db')(app)
//设置token密钥,app.set方法全局绑定属性
app.set('secret','s21321ajhdakj2131asd213')
app.listen(3000,()=>{
console.log('http://localhst:3000');
})
Hero模型
// 导入mongoose
const mongoose = require('mongoose')
// 定义模型字段
const schema = new mongoose.Schema({
name: {
type: String
},
avatar: {
type: String
},
title: {
type: String
},
//关联数组,实现一个model对象关联多个其它对象
categories: [{
type: mongoose.SchemaTypes.ObjectId, ref: 'Category'
}],
scores: {
difficult: {
type: Number
},
skills: {
type: Number
},
attack: {
type: Number
},
survive: {
type: Number
}
},
// 技能对象数组
skills: [{
icon: {
type: String
},
name: {
type: String
},
discription: {
type: String
},
tips: {
type: String
},
}],
// //顺风局势
items1: [{
type: mongoose.SchemaTypes.ObjectId,
ref: 'Item'
}],
// //逆风局势
items2: [{
type: mongoose.SchemaTypes.ObjectId,
ref: 'Item'
}],
// //技巧
usageTips: {
type: String
},
battleTips: {
type: String
},
teamTips: {
type: String
},
//搭档
partners:[{
hero: {
type: mongoose.SchemaTypes.ObjectId,
ref: 'Hero'
},
description:{type:String}
}]
})
// 导出模型Categorys
module.exports = mongoose.model('Hero', schema)
结语
如果你发现文章有什么问题,欢迎留言指正。
如果你觉得这篇文章还可以,别忘记点个赞加个关注再走哦。
如果你不嫌弃,还可以关注微信公众号———梦码城(持续更新中)。
梦码在这里感激不尽!!
项目已经上传到git 感兴趣的下伙伴欢迎下载