目录
- 1 后端
- 2 前端 使用vue-cli3搭建vue
- 2.1 1、使用vue-cli 3 搭建项目
- 2.2 2、前后端的连载
- 2.3 3、vue目录文件结构简介与得到空白的页面
- 2.4 4、重置浏览器的默认样式
- 2.5 5、安装element-ui
- 2.6 6、使用vue的基本流程
- 2.7 7、路由的重定向
- 2.8 8、访问不存在的路由,全都显示404页面
- 2.9 9、vue 引入css文件
- 2.10 10、使用axios请求
- 2.11 11、vue 3 处理跨域请求
- 2.12 12、路由守卫
- 2.13 13、解析token
- 2.14 14、把token解析后的数据存放到vuex
- 2.15 15、vuex数据刷新丢失的问题
- 2.16 16、父子组件通讯与子组件调用父组件方法
- 2.17 17、element-ui的分页
- 3 踩坑
后端
1、初始化项目
新建一个文件夹,作为一个项目,我建立了一个名为cli3的文件夹作为项目。
在cli3目录里,使用 npm init
指令创建项目描述文件 package.json。
命令行里会以交互的形式让你填一些项目的介绍信息,依次介绍如下:(不知道怎么填的直接回车、回车…)
- name 项目名称
- version 项目的版本号
- description 项目的描述信息
- entry point 项目的入口文件
- test command 项目启动时脚本命令
- git repository 如果你有 Git 地址,可以将这个项目放到你的 Git 仓库里
- keywords 关键词
- author 作者叫啥
- license 项目要发行的时候需要的证书,平时玩玩忽略它
2、创建入口文件server.js
因为指定入口文件为server.js,所以在cli3目录创建server.js文件。
3、安装express
使用命令:npm install express
安装express
4、使用express
在server.js文件里这样写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
const express = require('express'); //引入express作为后端 const app = express(); // 实例express const port = 5000; //指定服务器端口号 //express的listen方法监听端口 app.listen(port,()=>{ console.log(`服务器已启动,端口:${port}`); }); //express具备路由功能,返回参数是响应:res 与 请求:req //只监听端口,没有路由,直接使用浏览器访问是不行的 app.get('/',(req,res)=>{ res.send('hello world!'); //res.send() 发送响应 }); |
使用命令:node server.js
启动。express就可以运行了,浏览器访问localhost:5000就可以访问到内容
5、安装nodemon 作为守护
因为每次修改代码,都要手动重启node,修改后的代码才能生效,所以使用nodemon来自动重启。使用命令:npm install nodemon -g
全局安装nodemon。
使用nodemon代替node启动server.js:nodemon server.js
6、修改命令启动脚本
打开package.json,”scripts”字段可以修改启动命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
{ "name": "cli3", "version": "1.0.0", "description": "benz cli3 project", "main": "server.js", "scripts": {//自定义命令脚本 "start": "node server.js", // npm run start 其实执行的是:node server.js "server": "nodemon server.js" // npm run server 其实执行的是:nodemon server.js }, "author": "benz", "license": "ISC", "dependencies": { "express": "^4.16.4", "nodemon": "^1.18.10" } } |
命令行行执行命令:npm run server
7、使用在线MongoDB,安装mongoose管理MongoDB
到mlab网站上申请在线MongoDB,申请流程这里不叙述。
使用命令:npm install mongoose
安装mongoose,
8、使用mongoose
1 2 3 4 5 6 7 8 9 |
const mongoose = require('mongoose'); //引入mongoose const db = require('./config/keys').mongoURI; //MongoDB的链接地址 const mongoOption = { //MongoDB的链接配置 useNewUrlParser: true, } //使用connect()方法链接MongoDB,connect(MongoDB的链接地址,MongoDB的链接配置) mongoose.connect(db,mongoOption) .then(()=>console.log("MongoDB已连接"))//then() 成功后的回调方法,是一个promise .catch(err => console.log(err));//catch() 方法捕获异常 |
这里的mongoURI放到了一个config目录的keys.js文件里,方便管理:
1 2 3 |
module.exports = { mongoURI:"mongodb://这里填写账号:这里填写密码@ds119085.mlab.com:19085/result-api" }; |
9、路由管理
在项目目录建立routes的文件夹,用来存放路由文件,再在routes的文件夹里建立名为api的文件夹,用于存放api路由规则。在api文件夹里建立名为users.js的文件,编写user相关的路由规则。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
const express = require('express');//引入express const router = express.Router();//实例化express的路由Router() //以下,自定义路由 /** * $route GET api/users/test * @desc 返回的请求的json数据 * @access public */ router.get('/test',(req,res)=>{ res.json({"msg":"login works!!!"}); }); //暴露给外部 module.exports = router; |
回到server.js来使用自定义的路由文件:
1 2 3 4 |
//引入自定义路由文件 const users = require('./routes/api/users'); //express.use(路由规则,处理的模块) app.use('/api/users',users);//当匹配到'/api/users',就交给users处理 |
浏览器访问localhost:5000/api/users/test,就会访问到数据
10、MongoDB的操作
在项目创建名为models的文件夹,用来存放数据模型,在models的文件夹下创建名为User.js作为user表(MongoDB不叫表)的数据模型:
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 |
const mongoose = require('mongoose');//引入mongoose const Schema = mongoose.Schema;//实例化Schema //创建user表的schema /*new Schema({ 字段名:{ type:String,//字段的数据类型 required:true//字段是否必须 } });*/ const UserSchema = new Schema({ name:{ type:String, required:true }, email:{ type:String, required:true }, password:{ type:String, required:true }, avatar:{ type:String }, date:{ type:Date, default:Date.now() } }); //暴露给外部 module.exports = User = mongoose.model('users',UserSchema);//MongoDB表名users 与 数据模型UserSchema进行绑定 |
11、安装body-parser
Express中间件body-parser,在http请求种,POST、PUT、PATCH三种请求方法中包含着请求体,也就是所谓的request,在Nodejs原生的http模块中,请求体是要基于流的方式来接受和解析。
body-parser是一个HTTP请求体解析的中间件,使用这个模块可以解析JSON、Raw、文本、URL-encoded格式的请求体。
使用命令:npm install body-parser
安装body-parser。
回到server.js,我们要使用body-parser:
1 2 3 4 5 6 |
//引入body-parser中间件 const bodyParser = require('body-parser'); //使用body-parser中间件 //app即express app.use(bodyParser.urlencoded({extended:false})); app.use(bodyParser.json()); |
这时回到文件:routes/api/users.js,编写用户注册的路由:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
const express = require('express');//引入express const router = express.Router();//实例化express的路由Router() //以下,自定义路由 /** * $route POST api/users/register * @desc 返回的请求的json数据 * @access public */ router.post('/register',(req,res)=>{ console.log(req.body); }); //暴露给外部 module.exports = router; |
POST访问:http://localhost:5000/api/users/register:
就会在控制台接收到数据:
注意:body-parser的使用代码,是紧跟在express监听listen()方法后的,否则可能接收不到数据
12、密码需要加密,安装bcrypt
使用命令:npm install bcrypt
安装bcrypt
加密数据:
1 2 3 4 5 6 7 8 9 10 |
const bcrypt = require('bcrypt');//引入bcrypt const saltRounds = 10;//加密模式 const myPlaintextPassword = 's0/\/\P4$$w0rD';//待加密数据 //加密 bcrypt.genSalt(saltRounds, function(err, salt) { bcrypt.hash(myPlaintextPassword, salt, function(err, hash) { // hash是加密后的数据 }); }); |
密码对比:
1 2 3 4 5 6 |
//密码对比 //myPlaintextPassword 前端接收到的密码 //hash 数据库查询到的密码 bcrypt.compare(myPlaintextPassword, hash, function(err, res) { // 返回的是对比结果:true 或 false }); |
13、注册并把数据存入MongoDB
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 |
const express = require('express');//引入express const router = express.Router();//实例化express的路由Router() const User = require('./../../models/User');//引入user数据模型 const bcrypt = require('bcrypt');//引入bcrypt //以下,自定义路由 /** * $route POST api/users/register * @desc 返回的请求的json数据 * @access public */ router.post('/register',(req,res)=>{ //查询是否存在邮箱 //使用findOne({字段:值})来查询一条数据 User.findOne({email:req.body.email}) .then((user) => { if (user) {//查询到数据,说明邮箱已经被注册了 return res.status(400).json('邮箱已被占用!');//响应 的状态修改为400 并返回json信息 }else {//查询不到数据,实例化数据模型(填充数据模型) const newUser = new User({ name:req.body.name, email:req.body.email, avatar:req.body.avatar, identify:req.body.identify, password:req.body.password }); //加密密码 bcrypt.genSalt(10, (err, salt) => { bcrypt.hash(newUser.password, salt, (err, hash) => { if (err) throw err;//判断有没有错误 newUser.password = hash;//hash是加密后的结果 newUser.save()//保存数据到MongoDB .then(user => { console.log('保存到MongoDB的数据',user); }) .catch(err => console.log('user数据保存错误:',err)); }); }); } }) .catch(err => console.log('user查询错误:',err)); }); //暴露给外部 module.exports = router; |
数据保存成功,在控制台输出MongoDB入库的数据,如下图:
14、登录校验与生成token
jwt作为token,需要安装jsonwebtoken生成jwt,使用命令:npm install jsonwebtoken
安装jsonwebtoken。
在需用用到jwt的地方,引入即可:const jwt = require('jsonwebtoken');//引入jsonwebtoken
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 |
const express = require('express');//引入express const router = express.Router();//实例化express的路由Router() const User = require('./../../models/User');//引入user数据模型 const bcrypt = require('bcrypt');//引入bcrypt const jwt = require('jsonwebtoken');//引入jsonwebtoken //以下,自定义路由 /** 登录接口 * $route POST api/users/login * @desc 返回 token jwt passport * @access public */ router.post('/login',(req,res)=>{ //接收前端传来的参数 const email = req.body.email; const password = req.body.password; //查询是否存在邮箱 //使用findOne({字段:值})来查询一条数据 User.findOne({email}) .then((user) => { if (!user) {//查询不到数据,说明没注册 return res.status(404).json('用户不存在!');//响应 的状态修改为404 并返回json信息 } //密码匹配 bcrypt.compare(password, user.password) .then(isMatch => {//对比成功,返回的是false 或 true if (isMatch) { const rule = { id:user.id, name:user.name, avatar:user.avatar, identify: user.identify };//jwt生成规则,对象里的数据都会加密到jwt const secret = require('./../../config/keys').secretOrKey;//加密名字 //jwt.sign('规则','加密名字','过期时间','箭头函数'); jwt.sign(rule,secret,{expiresIn: 3600},(err,token)=>{ if (err) throw err;//判断token是否生成成功 res.json({ success:true, token:'Bear ' + token }); }); }else{ return res.status(400).json('密码错误!'); } }) .catch(err => console.log('bcrypt compare 执行出错:',err)); }) .catch(err => console.log('user查询错误:',err)); }); //暴露给外部 module.exports = router; |
15、token验证
token(jwt)验证需要passport-jwt、passport,执行命令:npm install passport-jwt passport
安装passport-jwt、passport。
回到server.js,引入passport并初始化passport:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
mongoose.connect(db,mongoOption) .then(()=>console.log("MongoDB已连接")) .catch(err => console.log(err)); const users = require('./routes/api/users'); //初始化要在mongoose链接之后,也要在users引进之后,否则会报错 //使用passport const passport = require('passport'); //passport初始化 app.use(passport.initialize()); //把passport传到config/passport.js,再引进来,实现代码分离 require('./config/passport')(passport); |
在config文件夹里创建passport.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 |
//这部分属于passport-jwt const JwtStrategy = require('passport-jwt').Strategy, ExtractJwt = require('passport-jwt').ExtractJwt; const opts = {} opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken(); opts.secretOrKey = require('./../config/keys').secretOrKey; const mongoose = require('mongoose');//引进mongoose const User = mongoose.model('users');//引进user模型 //passport 处理函数 module.exports = passport => { passport.use(new JwtStrategy(opts, (jwt_payload, done) => { //jwt_payload是翻译得到的信息,done是一个回调 // 根据token得到的id,去查数据库,获取完整的信息 User.findById(jwt_payload.id) .then(user => { if (user){//查询得到数据 return done(null,user);//通过回调函数done()把数据返回 } //没有查询到数据 return done(null,false);//通过回调函数done()把数据返回 }) .catch(err => { console.log('user 数据库查询出错:',err); }); })); }; |
回到路由文件:D:\php_project\cli3\routes\api\users.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 |
const express = require('express');//引入express const router = express.Router();//实例化express的路由Router() const User = require('./../../models/User');//引入user数据模型 const passport = require('passport');//引入passport,这里不是单纯的引入,passport需要先在入口文件处初始化,再编写定义passport的处理规则 //以下,自定义路由 /** 验证token * $route POST api/users/current * @desc 返回 token jwt passport * @access public */ //passport.authenticate('鉴权类型',配置项) //验证token router.get('/current',passport.authenticate('jwt',{session:false}),(req,res)=>{ //req.user是passport.js里的回调函数:return done(null,user)返回的user res.json({ id:req.user.id, name:req.user.name, email:req.user.email, identify:req.user.identify }); }); //暴露给外部 module.exports = router; |
最后,访问http://localhost:5000/api/users/current,在header设置字段Authorization,其值为token,返回数据证明成功。
16、数据与数据的增加、查询
在models文件夹下,创建一个数据模型profile,用来表示数据表:
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 |
const mongoose = require('mongoose');//引入mongoose const Schema = mongoose.Schema;//实例化Schema //创建profile表的schema const ProfileSchema = new Schema({ type:{ type:String }, desc:{ type:String }, income:{ type:String, required:true }, expend:{ type:String, required:true }, cash:{ type:String, required:true }, remark:{ type:String }, date:{ type:Date, default:Date.now() } }); //暴露给外部 module.exports = Profile = mongoose.model('profile',ProfileSchema);//MongoDB表名profile 与 数据模型ProfileSchema进行绑定 |
再到routes/api文件夹下新建profiles.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 110 111 112 113 114 115 116 117 118 119 |
const express = require('express');//引入express const router = express.Router();//实例化express的路由Router() const passport = require('passport'); const Profile = require('./../../models/Profile');//引入Profile数据模型 //以下,自定义路由 /** 数据添加 * $route POST api/profiles/add * @desc 返回的请求的json数据 * @access private */ router.post('/add',passport.authenticate('jwt',{session:false}),(req,res)=>{ const profileFields = {}; if (req.body.type) profileFields.type = req.body.type; if (req.body.desc) profileFields.desc = req.body.desc; if (req.body.income) profileFields.income = req.body.income; if (req.body.expend) profileFields.expend = req.body.expend; if (req.body.cash) profileFields.cash = req.body.cash; if (req.body.remark) profileFields.remark = req.body.remark; new Profile(profileFields).save() .then(profile => { res.json(profile); }) .catch(err => { console.log('profile数据保存失败:',err); }); }); /** 获取所有信息 * $route GET api/profiles/ * @desc 返回的请求的json数据 * @access private */ router.get('/',passport.authenticate('jwt',{session:false}),(req,res)=>{ Profile.find() .then(profile => { if (!profile) { return res.status(404).json('没有任何内容'); } res.json(profile); }) .catch(err => { console.log('profile数据获取失败:',err); }); }); /** 获取单个信息 * $route GET api/profiles/:id * @desc 获取单个信息 * @access private */ router.get('/:id',passport.authenticate('jwt',{session:false}),(req,res)=>{ Profile.findOne({_id:req.params.id})//获取get的参数 .then(profile => { if (!profile) { return res.status(404).json('没有任何内容'); } res.json(profile); }) .catch(err => { console.log('profile数据获取失败:',err); }); }); /** 编辑数据 * $route POST api/profiles/edit/:id * @desc 编辑单个信息 * @access private */ router.post('/edit/:id',passport.authenticate('jwt',{session:false}),(req,res)=>{ const profileFields = {}; if (req.body.type) profileFields.type = req.body.type; if (req.body.desc) profileFields.desc = req.body.desc; if (req.body.income) profileFields.income = req.body.income; if (req.body.expend) profileFields.expend = req.body.expend; if (req.body.cash) profileFields.cash = req.body.cash; if (req.body.remark) profileFields.remark = req.body.remark; Profile.findOneAndUpdate( {_id:req.params.id},//需要更新的数据的id {$set:profileFields},//更新的内容 {new:true}//是否新增 ) .then(profile => { res.json(profile); }) .catch(err => { console.log('profile数据修改失败:',err); }); }); /** 删除数据 * $route GET api/profiles/delete/:id * @desc 删除单个信息 * @access private */ router.delete('/delete/:id',passport.authenticate('jwt',{session:false}),(req,res)=>{ Profile.findOneAndRemove({_id:req.params.id}) .then(profile => { res.json(profile); }) .catch(err => { console.log('profile数据删除失败:',err); }); }); //暴露给外部 module.exports = router; |
最后到入口文件server.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 |
const express = require('express'); //引入express作为后端 const app = express(); // 实例express const port = 5000; //指定服务器端口号 const users = require('./routes/api/users'); const profiles = require('./routes/api/profiles'); //express的listen方法监听端口 app.listen(port,()=>{ console.log(`服务器已启动,端口:${port}`); }); //引入body-parser中间件 const bodyParser = require('body-parser'); app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); const mongoose = require('mongoose'); const db = require('./config/keys').mongoURI; const mongoOption = { useNewUrlParser: true, } mongoose.connect(db,mongoOption) .then(()=>console.log("MongoDB已连接")) .catch(err => console.log(err)); //使用passport const passport = require('passport'); app.use(passport.initialize()); require('./config/passport')(passport); //引入自定义路由文件 app.use('/api/users',users); app.use('/api/profiles',profiles);//当匹配到'/api/profiles',就交给profiles处理 |
至此后端搭建已完成。
前端 使用vue-cli3搭建vue
1、使用vue-cli 3 搭建项目
在cli3目录下使用命令:npm install -g @vue/cli
全局安装vue-cli 3。
使用命令:vue create client
搭建一个名为client的项目。这时候有交互,选择babel、router、vuex这三个就可以了。
最后按提示使用命令:cd client
进入项目,并且使用命令:npm run serve
,启动vue,打开网址,就可以看见vue的初始页面。
2、前后端的连载
前端有它自己的启动命令,后端也有自己的启动命令,这里的所谓的前后端连载,就是一条命令调用启动前后端的启动命令。
在cli3文件夹下(也就是整个项目的根目录),使用命令:npm install concurrently
,使用concurrently来使多个终端的启动绑定在一起。
修改脚本:
client文件夹为前端的目录,其下有一个package.json,在”scripts”里写上client的启动命令:"start": "npm run serve"
:
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 |
{ "name": "client", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "start": "npm run serve" }, "dependencies": { "vue": "^2.5.22", "vue-router": "^3.0.1", "vuex": "^3.0.1" }, "devDependencies": { "@vue/cli-plugin-babel": "^3.4.0", "@vue/cli-service": "^3.4.0", "vue-template-compiler": "^2.5.21" }, "postcss": { "plugins": { "autoprefixer": {} } }, "browserslist": [ "> 1%", "last 2 versions", "not ie <= 8" ] } |
同理,在后端的package.json也写了后端的启动命令:"server": "nodemon server.js"
,现在的目标是在启动后端的同时,启动前端,实现前后端连载。在启动后端并启动前端的时候,应该注意检查前端的依赖是否安装完成:"client-install": "npm install --prefix client"
,其中--prefix client
,是指定路径,还有的就是准备启动前端的命令:"client": "npm run start --prefix client"
,同理,--prefix client
也是指定路径。最后重点来了,使用concurrently实现前后端启动命令的连载(批量执行):"dev": "concurrently \"npm run server\" \"npm run client\""
。
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 |
{ "name": "cli3", "version": "1.0.0", "description": "benz cli3 project", "main": "server.js", "scripts": { "client-install": "npm install --prefix client", "client": "npm run start --prefix client", "start": "node server.js", "server": "nodemon server.js", "dev": "concurrently \"npm run server\" \"npm run client\"" }, "author": "benz", "license": "ISC", "dependencies": { "bcrypt": "^3.0.4", "body-parser": "^1.18.3", "concurrently": "^4.1.0", "express": "^4.16.4", "jsonwebtoken": "^8.4.0", "mongoose": "^5.4.11", "nodemon": "^1.18.10", "passport": "^0.4.0", "passport-jwt": "^4.0.0" } } |
最后,输入命令:npm run dev
即可实现前后端连载。
3、vue目录文件结构简介与得到空白的页面
vue目录文件结构简介如下面:
按照以下步骤得到空白页面:
把assets目录下的logo.png删除
把components目录下的HelloWorld.vue删除
把views下的About.vue、Home.vue删除
打开App.vue,把这段代码删除:
1 2 3 4 |
<div id="nav"> <router-link to="/">Home</router-link> | <router-link to="/about">About</router-link> </div> |
并且把样式删除。
打开router.js把import Home from ‘./views/Home.vue’ 删除,routes的数组里的对象全都清空。
4、重置浏览器的默认样式
打开网址:https://meyerweb.com/eric/tools/css/reset/,下载reset.css。
在public目录下,新建css文件夹,把reset.css文件放到css文件夹里面去。
打开public文件夹下的index.html引入reset.css:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="icon" href="<%= BASE_URL %>favicon.ico"> <link rel="stylesheet" href="css/reset.css"><!--引入reset.css--> <title>client</title> </head> <body> <noscript> <strong>We're sorry but client doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id="app"></div> <!-- built files will be auto injected --> </body> </html> |
5、安装element-ui
打开前端目录:cd client
,使用命令:npm install element-ui
安装element-ui
打开入口文件main.js,引入element-ui并声明使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' import ElementUI from 'element-ui';//引入element-ui import 'element-ui/lib/theme-chalk/index.css';//引入element-ui的样式 Vue.config.productionTip = false; Vue.use(ElementUI);//使用element-ui new Vue({ router, store, render: h => h(App) }).$mount('#app') |
6、使用vue的基本流程
在views文件夹创建vue文件,比如,创建Login.vue,其模板如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<template> </template> <script> export default { name: "Login" } </script> <style scoped> </style> |
然后到router.js,定义路由:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import Vue from 'vue' import Router from 'vue-router' import Login from './views/Login'//引入Login.vue Vue.use(Router); export default new Router({ mode: 'history', base: process.env.BASE_URL, routes: [ { //Login.vue定义路由 path:'/login',//访问路径 name:'login',//Login.vue的name component:Login//上面引入Login.vue定义的名字 } ] }) |
7、路由的重定向
访问“/”转跳到首页“/index”:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import Vue from 'vue' import Router from 'vue-router' import Index from './views/Index' Vue.use(Router); export default new Router({ mode: 'history', base: process.env.BASE_URL, routes: [ { path:'/',//访问根目录 redirect:'/index'//转跳到首页 }, { path:'/index',//访问首页 name:'index', component:Index } ] }) |
8、访问不存在的路由,全都显示404页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import Vue from 'vue' import Router from 'vue-router' import NotFound from './views/NotFound'//404页面 Vue.use(Router); export default new Router({ mode: 'history', base: process.env.BASE_URL, routes: [ {//这个要放到路由的最后,表示什么都没访问到 path:'*', name:'notfound', component:NotFound }, ] }) |
9、vue 引入css文件
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 |
<template> <div class="not_found"> <div class="page-container page-container-responsive"> <div class="row space-top-8 space-8 row-table"> <div class="col-5 col-middle"> <h1 class="text-jumbo text-ginormous">Oops!</h1> <h2>我们好像找不到你要的网页...</h2> <h6>错误代码: 404</h6> <ul class="list-unstyled"> <li>下面的链接或许对你有帮助:</li> <li><a href="/">主页</a></li> </ul> </div> <div class="col-5 col-middle text-center"> <img src="./../assets/not_found.gif" width="313" height="428" alt="Girl has dropped her ice cream."> </div> </div> </div> </div> </template> <script> export default { name: "NotFound" } </script> <style scoped> @import "./../../public/css/not_found.css";//引入css文件 </style> |
10、使用axios请求
在client目录下,使用命令安装axios:npm install axios
。
使用axios的拦截功能:
在main.js同级下创建http.js,里面编写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 |
import axios from 'axios'//引入axios // 请求拦截 axios.interceptors.request.use(config => { //TO DO 逻辑代码 return config //拦截处理完毕,就放行 }, error => { //TO DO 逻辑代码 return Promise.reject(error)//拦截出错,就回调 }) // 响应拦截 axios.interceptors.response.use(response => { //TO DO 逻辑代码 return response //拦截处理完毕,就放行 }, error => { //TO DO 逻辑代码 return Promise.reject(error) //拦截出错,就回调 }) export default axios //以名字axios对外暴露,这样axios就带有了拦截功能 |
最后把axios挂载到vue上面去,全局使用:
打开main.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' import axios from './http' //引入带有拦截逻辑的axios Vue.config.productionTip = false; Vue.prototype.$axios = axios;//axios挂载到vue上,以名字$axios作为vued的一个方法,可以全局使用axios new Vue({ router, store, render: h => h(App) }).$mount('#app') |
例子:
加载动画和消息提醒:
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 |
import axios from 'axios'; //引入axios import { Message, Loading } from 'element-ui'; //引入element-ui的消息提心与加载动画模块 let loading //定义loading变量 function startLoading() { //使用Element loading-start 方法 loading = Loading.service({ lock: true, text: '加载中...', background: 'rgba(0, 0, 0, 0.7)' }) } function endLoading() { //使用Element loading-close 方法 loading.close() } // 请求拦截 axios.interceptors.request.use(config => { // 加载开始 startLoading(); return config; }, error => { return Promise.reject(error); }) // 响应拦截 axios.interceptors.response.use(response => { //加载结束 endLoading(); return response; }, error => { //加载结束 endLoading(); Message.error(error.response.data);// 错误消息提醒 return Promise.reject(error) }) export default axios |
token的处理:
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 |
import axios from 'axios' import { Message, Loading } from 'element-ui'; import router from './router' // 请求拦截 axios.interceptors.request.use(config => { //查看本地是否存在token if (localStorage.eleToken){ //把token设置统一header config.headers.Authorization = localStorage.eleToken } return config }, error => { return Promise.reject(error) }) // 响应拦截 401 token过期处理 axios.interceptors.response.use(response => { return response }, error => { //获取网页状态码 const { status } = error.response //401 鉴权失败 if (status == 401) { Message.error('token值无效,请重新登录') // 清除token localStorage.removeItem('eleToken') // 页面跳转 router.push('/login') } return Promise.reject(error) }) export default axios |
11、vue 3 处理跨域请求
与vue2不同,vue3处理跨域请求,必须在一个名为vue.config.js的文件定义,所以,在和package.json同级目录创建vue.config.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
module.exports = { devServer: { open: true, host: 'localhost',//vue的host port: 8080,//vue的端口 https: false,//是否是https hotOnly: false,//是否开启热启动 proxy: { // 配置跨域 '/api': { target: 'http://localhost:5000/api/',//跨域目标(api请求地址) ws: true, changOrigin: true, pathRewrite: { '^/api': '' } } }, before: app => { } } } |
12、路由守卫
有些路由,是要一定的条件才能访问的,比如,首页的路由必须要登录才能访问,直接访问是不能访问的,只能强制转跳到登录页面。这就需要路由守卫。
在router.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 |
import Vue from 'vue' import Router from 'vue-router' import Index from './views/Index' import Register from './views/Register' import Login from './views/Login' import NotFound from './views/NotFound' Vue.use(Router); const router = new Router({ mode: 'history', base: process.env.BASE_URL, routes: [ { path:'/', redirect:'/index' }, { path:'/index', name:'index', component:Index }, { path:'/register', name:'register', component:Register }, { path:'/login', name:'login', component:Login }, {//什么都没访问到 path:'*', name:'notfound', component:NotFound }, ] }); //路由守卫 router.beforeEach((to, from, next) => { //判断本地是否存储有登录的token const isLogin = localStorage.eleToken ? true : false; //判断当前访问的路由是否登录和注册页面 if (to.path == "/login" || to.path == "/register") { //访问的是登录和注册页面,那么就放行 next(); } else { //除了登录和注册页面,其它路由都要判断是否已登录 // 有就放行,没有就重定向到登录页 isLogin ? next() : next("/login"); } }) export default router; |
13、解析token
登录成功后,拿到了服务器的token,前端需要把token解析成数据。解析token需要借助jwt-decode,在client目录下,使用命令:npm install jwt-decode
安装jwt-decode。
jwt-decode的使用:
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 |
<script> import jwt_decode from "jwt-decode";//引入jwt-decode export default { name: "login", data() { return {}; }, methods: { submitForm(formName) { this.$refs[formName].validate(valid => { if (valid) { this.$axios.post("/api/users/login", this.loginUser).then(res => { // 登录成功 const { token } = res.data; localStorage.setItem("eleToken", token); // 解析token const decode = jwt_decode(token); // 存储数据 this.$store.dispatch("setIsAutnenticated", !this.isEmpty(decode)); this.$store.dispatch("setUser", decode); // 页面跳转 this.$router.push("/index"); }); } else { console.log("error submit!!"); return false; } }); }, isEmpty(value) { return ( value === undefined || value === null || (typeof value === "object" && Object.keys(value).length === 0) || (typeof value === "string" && value.trim().length === 0) ); } } }; </script> |
14、把token解析后的数据存放到vuex
打开store.js,这里是vuex的定义文件。
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 |
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const types = { SET_IS_AUTNENTIATED: 'SET_IS_AUTNENTIATED', // 是否认证通过 SET_USER: 'SET_USER' // 用户信息 } const state = { // 需要维护的状态 isAutnenticated: false, // 是否认证 user: {} // 存储用户信息 } const getters = { isAutnenticated: state => state.isAutnenticated, user: state => state.user } const mutations = { [types.SET_IS_AUTNENTIATED](state, isAutnenticated) { if (isAutnenticated) state.isAutnenticated = isAutnenticated else state.isAutnenticated = false }, [types.SET_USER](state, user) { if (user) state.user = user else state.user = {} } } const actions = { setIsAutnenticated: ({ commit }, isAutnenticated) => { commit(types.SET_IS_AUTNENTIATED, isAutnenticated) }, setUser: ({ commit }, user) => { commit(types.SET_USER, user) }, clearCurrentState: ({ commit }) => { commit(types.SET_IS_AUTNENTIATED, false) commit(types.SET_USER, null) } } export default new Vuex.Store({ state,//vuex的初始状态 getters,//获取vuex的数据 mutations,//更改数据 actions//对vuex操作的自定义方法,方法必有参数{commit},用来执行对vuex数据的修改 }) |
15、vuex数据刷新丢失的问题
这是因为根主键没有判断,打开根主键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 39 40 41 |
<template> <div id="app"> <router-view/> </div> </template> <script> import jwt_decode from "jwt-decode";//引入jwt-decode export default { name: "App", created() {//vue声明周期,创建的时候执行 if (localStorage.eleToken) { const decode = jwt_decode(localStorage.eleToken);//解析token //解析token的数据存到vuex this.$store.dispatch("setIsAutnenticated", !this.isEmpty(decode)); this.$store.dispatch("setUser", decode); } }, methods: { isEmpty(value) { return ( value === undefined || value === null || (typeof value === "object" && Object.keys(value).length === 0) || (typeof value === "string" && value.trim().length === 0) ); } } }; </script> <style> html, body, #app { width: 100%; height: 100%; } </style> |
16、父子组件通讯与子组件调用父组件方法
这里以弹出层为例。
父子通讯:
先在子组件定义好接收的数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<script> export default { name: "DialogFound", props: { dialog: Object,//定义要从父组件接收的数据 form: Object }, data() { return { }; }, methods: { } }; </script> |
父组件:
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 |
<template> <!-- 弹框页面 --> <!-- 绑定数据 等号左边是子组件的,右边是父组件的 --> <DialogFound :dialog='dialog' :form='form'></DialogFound> </div> </template> <script> import DialogFound from "../components/DialogFound"; export default { name: "FoundList", data() { return { dialog: { //父组件的数据 show: false, title: "", option: "edit" }, form: { type: "", describe: "", income: "", expend: "", cash: "", remark: "", id: "" } }; }, components: { DialogFound//注册子组件 } }; </script> |
子组件调用父组件的方法,比如,弹出层添加完数据,关闭弹窗的同时调用父组件的方法刷新父组件的数据。
子组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<script> export default { name: "DialogFound", methods: { onSubmit(form) { this.$refs[form].validate(valid => { if (valid) { //表单数据验证完成之后,并且提交数据成功; //最后,调用父组件的update方法 this.$emit("update"); }); } }); } } }; </script> |
父组件:
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> <!-- 弹框页面 --> <!-- @update是子组件定义的,getProfile是父组件的方法 --> <DialogFound @update="getProfile"></DialogFound> </template> <script> import DialogFound from "../components/DialogFound"; export default { name: "FoundList", components: { DialogFound }, created() { this.getProfile(); }, methods: { getProfile() { // 获取表格数据 this.$axios("/api/profiles/").then(res => { this.allTableData = res.data; }); } } }; </script> |
17、element-ui的分页
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 |
<template> <div class="fillcontain"> <!--分页--> <div class="pagination"> <el-pagination v-if='paginations.total > 0' :page-sizes="paginations.page_sizes" :page-size="paginations.page_size" :layout="paginations.layout" :total="paginations.total" :current-page.sync='paginations.page_index' @current-change='handleCurrentChange' @size-change='handleSizeChange'> </el-pagination> </div> </div> </template> <script> export default { name: "FoundList", data() { return { tableData: [],//默认表单数据 allTableData: [],//所有的表单数据 filterTableData: [],//过滤后的表单数据 //需要给分页组件传的信息 paginations: { page_index: 1, // 当前位于哪页 total: 0, // 总数 page_size: 5, // 1页显示多少条 page_sizes: [5, 10, 15, 20], //每页显示多少条 layout: "total, sizes, prev, pager, next, jumper" // 翻页属性 } }; }, created() { this.getProfile(); }, methods: { getProfile() { // 获取表格数据 this.$axios("/api/profiles/").then(res => { this.allTableData = res.data; this.filterTableData = res.data; // 设置分页数据 this.setPaginations(); }); }, handleCurrentChange(page) { // 当前页 let sortnum = this.paginations.page_size * (page - 1); let table = this.allTableData.filter((item, index) => { return index >= sortnum; }); // 设置默认分页数据 this.tableData = table.filter((item, index) => { return index < this.paginations.page_size; }); }, handleSizeChange(page_size) { // 切换size this.paginations.page_index = 1; this.paginations.page_size = page_size; this.tableData = this.allTableData.filter((item, index) => { return index < page_size; }); }, setPaginations() { // 总页数 this.paginations.total = this.allTableData.length; this.paginations.page_index = 1; this.paginations.page_size = 5; // 设置默认分页数据 this.tableData = this.allTableData.filter((item, index) => { return index < this.paginations.page_size; }); } } }; </script> |
踩坑
1、nodejs 提示‘xxx’ 不是内部或外部命令解决方法
一般出现这样的问题原因是npm安装出现了问题,全局模块目录没有被添加到系统环境变量。
Windows用户检查下npm的目录是否加入了系统变量PATH中,如果不存在需要手动添加,添加之后需要重新启动CMD控制台。
nodejs模块全局目录环境变量
npm目录可以使用npm命令去查找:npm config get prefix