title: 全栈开发后台资金管理系统项目date: 2019-11-27 15:58:28 tags: 项目
后端部分 1. nodemon 使用 当我们做服务器的时候,每次变更都要重启才能生效。
如:我们创建了一个名为 server.js 的文件,作为服务器
使用node ./server.js
即可启动,但我们对其修改后,要看效果就要关闭之前的再启动。
而 nodemon 帮我们解决这个问题。
1 npm install nodemon -g //全局安装nodemon
然后就可以使用 nodemon 运行我们的服务器了
这时,修改文件,服务器会自动重启。
将命令设置到 package.json。
1 2 3 4 5 //在package.json中修改 "scripts": { "start": "node server.js", "server": "nodemon server.js" },
这样在就可以使用npm run start
或npm run server
来运行服务器
2.连接数据库 1 2 //node-app下执行 npm install mongoose
为方便修改配置,新建文件 /config/keys.js, 内容:
1 2 3 4 module .exports = { mongoURI : "mongodb://<username>:<password>@cluster0-shard-00-00-oqdfe.mongodb.net:27017,cluster0-shard-00-01-oqdfe.mongodb.net:27017,cluster0-shard-00-02-oqdfe.mongodb.net:27017/test?ssl=true&replicaSet=Cluster0-shard-0&authSource=admin&retryWrites=true&w=majority" };
该链接需要到 mongoDB 官网注册账户,获取 500M 免费空间,创建一个 Preject 再创建 Clusters,之后点击”Connect”,选择”Connect your Application”进入下一步,域名选择默认确定即可.DRIVER 选择“node.js”, VERSION 选择”2.2.12 or later”,然后 copy 下面的链接即可,注意修改:为对应的用户名和密码。可以在”Database Access”中添加和修改用户。
在 server.js 中引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const db = require ("./config/keys" ).mongoURI ;mongoose.connect (db, { useNewUrlParser : true , useUnifiedTopology : true , useFindAndModify : false }) .then (() => console .log ("mongoDB Connected" )) .catch (err => console .log (err));
3.配置路由和接口 在 node-app 下创建 /route/api/users.js,内容:
用于登录和注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const express = require ("express" );const router = express.Router ();router.get ("/test" , (req, res ) => { res.json ({ msg : "login works" }); }); module .exports = router;
在 server.js 中引用和使用
1 2 3 4 5 6 7 8 const express = require ('express' )const app = express ()const users = require ("./route/api/users" );app.use ("/api/users" , users);
这时 使用浏览器访问 http://localhost:5000/api/users/test
即可看到返回的msg:"login works"
4.创建模型 新建 /models/User.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 const mongoose = require ("mongoose" );const Schema = mongoose.Schema ;const UserSchema = new Schema ({ name : { type : String , required : true }, email : { type : String , required : true }, password : { type : String }, avatar : { type : String , required : true }, date : { type : Date , default : Date .now } }); module .exports = User = mongoose.model ("users" , UserSchema );
4.5 下载postman,安装 可以用来测试接口是否通.
4.6 创建register接口 首先需要安装body-parser.
5.配置注册 安装 body-parser,方便发送 POST 请求
在 server.js 中引用
1 2 3 4 5 const bodyParser = require ("body-parser" );app.use (bodyParser.urlencoded ({ extended : false })); app.use (bodyParser.json ());
在 users.js 中配置接口
1 2 3 4 5 6 router.post ("/register" , (req, res ) => { console .log (req.body ); });
//此处如果连接不上mongoDB,可能是白名单失效.再添加一个白名单在mongoDB即可.
功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 router.post ("/register" , (req, res ) => { User .findOne ({ email : req.body .email }).then (user => { if (user) { return res.status (400 ).json ({ email : "邮箱已被注册!" }); } else { const newUser = new User ({ name : req.body .name , email : req.body .email , password : req.body .password }); } }); });
密码加密 安装 bcrypt
在 users.js 中引入,
1 const bcrypt = require ("bcrypt" );
官方详细说明链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 bcrypt.genSalt (10 , function (err, salt ) { bcrypt.hash (newUser.password , salt, (err, hash ) => { if (err) throw err; newUser.password = hash; newUser .save () .then (user => res.json (user)) .catch (err => console .log (err)); }); });
头像 avatar gravatar 官方说明链接
安装
user.js 中引入
1 const gravatar = require ("gravatar" );
在接口位置使用(user.js)
1 2 3 4 5 6 7 8 9 10 11 if (user) { return res.status (400 ).json ({ email : "邮箱已被注册!" }); } else { const avatar = gravatar.url (req.body .email , { s : "200" , r : "pg" , d : "mm" }); const newUser = new User ({ name : req.body .name , email : req.body .email , avatar, password : req.body .password }); }
如何得到头像?
打开 gravatar 网址
注册 gravatar,其注册实际是注册了 wordpress.com 网站的账户,然后登录 gravatar,任意格式的邮箱均可申请成功,但无法收到邮件,则无法验证并修改头像。因此要使用可以收到验证的邮箱。
上传头像。上传图片时,最后会选择图片会有Choose a rating for your Gravatar
,有四个选项,G、PG、R、X,这里我们选择 pg,我们在使用时也是r: 'pg'
,需要保持一致。
这时,我们使用 postman 向 http://localhost:5000/api/users/register 发送 post 请求,使用(application/x-www-form-urlencoded)(key:email value:user@usertest.com ) 就能得到设置的头像了。
7.登录接口 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 router.post ("/login" , (req, res ) => { const email = req.body .email ; const password = req.body .password ; User .findOne ({ email }).then (user => { if (!user) { return res.status (404 ).json ({ email : "用户不存在" }); } bcrypt.compare (password, user.password ).then (isMatch => { if (isMatch) { res.json ({ msg : "success" }); } else { return res.status (400 ).json ({ password : "密码错误!" }); } }); }); });
返回 token 安装 jsonwebtoken (jwt)
1 npm install jsonwebtoken
在 users.js 引入
1 const jwt = require ("jsonwebtoken" );
在密码验证成功处插入
我们在 config/keys.js 导出的对象中,加入了 secretOrKey:”secret” 属性和值,再引入到 users.js 以方便统一管理配置。
过期时间的 3600 单位为秒
token 前必须是 “Bearer ”(送信人的意思),末尾空格 也不可缺少。
如果 success 为 true,就应该得到 token 值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const rule = { id : user.id , name : user.name }; jwt.sign (rule, keys.secretOrKey , { expiresIn : 3600 }, (err, token ) => { if (err) throw err; res.json ({ success : true , token : "Bearer " + token }); });
验证 token token相当于一个令牌或者钥匙.
使用passport-jwt进行token验证.
users.js
加入接口
1 2 3 4 5 6 7 8 9 router.get ("/current" , (req, res ) => { res.json ({ msg : "success" }); });
安装 passport-jwt 和 passport 1 npm install passport-jwt passport
passport 网址
passport-jwt 网址
在 server.js
中引入,并初始化
1 2 3 4 5 const passport = require ("passport" );app.use (passport.initialize ());
新建文件 /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 30 const JwtStrategy = require ("passport-jwt" ).Strategy , ExtractJwt = require ("passport-jwt" ).ExtractJwt ; const mongoose = require ("mongoose" );const User = mongoose.model ("users" );const keys = require ("../config/keys" );const opts = {};opts.jwtFromRequest = ExtractJwt .fromAuthHeaderAsBearerToken (); opts.secretOrKey = keys.secretOrKey ; module .exports = passport => { passport.use ( new JwtStrategy (opts, (jwt_payload, done ) => { User .findById (jwt_payload.id ) .then (user => { if (user) { return done (null , user); } return done (null , false ); }) .catch (err => console .log (err)); }) ); };
在 server.js 中引入 passport.js
1 2 3 4 app.use (passport.initialize ()); require ("./config/passport" )(passport);
这里使用了一个技巧,require("xxx.js")(对象)
将对象传入xxx.js,同时将该js引入当前文件中。这样就可以在xxx.js
中编写代码,实现分离,而且在xxx.js可以使用传入的对象。
在 users.js 中引入 passport,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const passport = require ("passport" );router.get ( "/current" , passport.authenticate ("jwt" , { session : false }), (req, res ) => { res.json ({ id : req.user .id , name : req.user .name , email : req.user .email }); } );
这里调整了一些输出的内容,将输出对象改为了字符串,可能造成代码实际和上面有些出入。
添加身份 如果想在 user 中添加其他信息(比如添加管理员)可参考此内容
在 models/User.js
的 UserSchema 中添加身份字段
1 2 3 4 identity :{ type :String , required :true },
在 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 29 30 const newUser = new User ({ name : req.body .name , email : req.body .email , avatar, password : req.body .password , identity : req.body .identity , }); const rule = { id : user.id , name : user.name , avatar : user.avatar , identity : user.identity , }; router.get ( "/current" , passport.authenticate ("jwt" , { session : false }), (req, res ) => { res.json ({ id : req.user .id , name : req.user .name , email : req.user .email , identity : req.user .identity , }); } );
配置信息接口 新建 models/Profile.js 建立 ProfileSchema,内容
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 const mongoose = require ("mongoose" );const Schema = mongoose.Schema ;const ProfileSchema = new Schema ({ type : { type : String , }, describe : { 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 );
新建 api/profiles.js 暂不写内容,将其在 server.js 中引入
1 2 3 4 5 const profiles = require ("./route/api/profiles" );app.use ("/api/profiles" , profiles);
在 api/profiles.js 配置信息进行测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const express = require ("express" );const router = express.Router ();const passport = require ("passport" );const Profile = require ("../../models/Profile" );router.get ("/test" , (req, res ) => { res.json ({ msg : "Profile works" }); }); module .exports = router;
postman 发送到 http://localhost:5000/api/profiles/test 返回 Profile works 即链接成功。
更改数据库接口 如果要更改数据库接口,可以/config/keys.js
中的mongoURI
的值,该值的获取方法,参考上述创建时的内容。
创建添加信息的接口 profiles.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 router.post ( "/add" , passport.authenticate ("jwt" , { session : false }), (req, res ) => { const profileFields = {}; if (req.body .type ) profileFields.type = req.body .type ; if (req.body .describe ) profileFields.describe = req.body .describe ; 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); }); } );
获取所有信息 profiles.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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 ) => res.status (404 ).json ("err" )); } );
获取单个信息 profiles.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 router.get ( "/:id" , passport.authenticate ("jwt" , { session : false , }), (req, res ) => { Profile .findOne ({ _id : req.params .id }) .then ((profile ) => { if (!profile) { return res.status (404 ).json ("没有任何内容" ); } res.json (profile); }) .catch ((err ) => res.status (404 ).json (err)); } );
编辑信息 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 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 .describe ) profileFields.describe = req.body .describe ; 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 }, { $set : profileFields }, { new : true } ).then ((profile ) => res.json (profile)); } );
删除信息 profiles.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 router.delete ( "/delete/:id" , passport.authenticate ("jwt" , { session : false , }), (req, res ) => { Profile .findOneAndRemove ({ _id : req.params .id }) .then ((profile ) => { profile.save ().then ((profile ) => res.json (profile)); }) .catch ((err ) => res.status (404 ).json ("删除失败" )); } );
至此,信息的增删改查均已实现。要创建其他 schema 可以参考此方式
前后端连载 查看 vue 版本,是否在 3.0.0 以上,我们要求是在 3.0.0 以上。
vue-cli 的安装见 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 vue -V vue create client 接下来进入选择流程,后面 √ 为我们作出的选择项,-----表示回车到下一选项页 ? Please pick a preset : ❯ default (babel, eslint) (默认配置) Manually select features (手动选择) √ ----- 按键盘a表示全选,i表示反选,空格键 表示切换选中,如果你需要什么就选什么就可以了,这里选择Babel 、Router 、Vuex 。 ? Please pick a preset : Manually select features ? Check the features needed for your project : (Press <space> to select, <a> to toggle all, <i> to invert selection) ❯◯ Babel √ ◯ TypeScript ◯ Progressive Web App (PWA ) Support ◯ Router √ ◯ Vuex √ ◯ CSS Pre -processors ◯ Linter / Formatter ◯ Unit Testing ◯ E2E Testing ----- 是否使用history ,我们输入y,回车,会继续显示其他问题。 ? Please pick a preset : Manually select features ? Check the features needed for your project : Babel , Router , Vuex ? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) In dedicated config filesIn package.json √ Save this as a preset for future projects? (y/N) (是否要保存你当前预制模板) N (第一次时可以保存一次方便之后用) ----- 接下来就是等待安装成功。 会产生一个 client的文件夹 cd client npm run serve
此时使用 http://localhost:8080/
就可以打开前端了,再新建终端,执行 nodemon
就打开了后台。
这需要两个终端打开,较为繁琐,因此采用前后端连载,借助 concurrently 将多个终端启动的项目绑在一起
安装 concurrently
1 npm install concurrently
打开 /client/package.json
1 2 3 4 5 "scripts" : { "serve" : "vue-cli-service serve" , "build" : "vue-cli-service build" , "start" : "npm run serve" } ,
此时,我们在 client 中 使用 npm run start
即可启动前端
在根目录的 package.json 中配置client-stall
、client
和dev
。
1 2 3 4 5 6 7 "scripts" : { "client-install" : "npm install --prefix client" , "client" : "npm start --prefix client" , "start" : "node server.js" , "server" : "nodemon server.js" , "dev" : "concurrently \"npm run server\" \"npm run client\"" } ,
此时我们可以在根目录的终端下执行npm run dev
即可同时启动 前端和后台
前端部分 接后端部分,此文档为 前端部分 内容。
准备工作 为使内容整洁,我们将 vue-cli 创建项目时生成的我们不需要的文件进行整理
我们接下来更多的是在 client 这个文件夹下工作,非强调指明,则以 client 视为根目录。
删除 /src/assets/logo.png (vue 的 logo 图片)
删除 /src/components/HelloWorld.vue
删除 /src/views/ 中的 About.vue 和 Home.vue
新建 /src/views/Index.vue,内容为
1 2 3 4 5 6 7 8 9 10 <template> <div class="index">初始化页面</div> </template> <script> export default { name: "index", components: {}, }; </script>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import Vue from "vue" ;import Router from "vue-router" ;import Index from "./views/Index.vue" ;Vue .use (Router );export default new Router ({ mode : "history" , base : process.env .BASE_URL , routes : [ { path : "/" , redirect : "/index" , }, { path : "/index" , name : "index" , component : Index , }, ], });
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <template> <div id="app"> <router-view /> </div> </template> <style> html, body, #app { width: 100%; height: 100%; } </style>
新建 /public/css/reset.css ,在 /public/index.html 中引入该 css 文件
1 <link rel ="stylesheet" href ="css/reset.css" />
reset.css 内容可以访问 CSS reset 得到,也可在下面设置自己需要的初始样式
本案例中,我们在 reset.css 中追加了 el 中加载相关的样式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 .el-loading { position : absolute; z-index : 2000 ; background-color : rgba (255 , 255 , 255 , 0.7 ); margin : 0 ; padding : 0 ; top : 0 ; right : 0 ; bottom : 0 ; left : 0 ; -webkit-transition : opacity 0.3s ; transition : opacity 0.3s ; } .el-loading-spinner { top : 50% ; margin-top : -21px ; width : 100% ; text-align : center; position : absolute; }
注册页和 404 安装 elementUI
1 2 //此时目录在client中 npm i element-ui -S
在/src/main.js 中引入
1 2 3 4 import ElementUI from "element-ui" ;import "element-ui/lib/theme-chalk/index.css" ;Vue .use (ElementUI );
将 图片文件 放在 /src/asset/ 文件夹下面
分别是 404.gif,bg.jpg,logo.png,showcase.png
初始注册,新建 /src/views/Register.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 42 43 <template> <div class ="register" > <section class ="form_container" > <div class ="manage_tip" > <span class ="title" > 万事屋在线后台管理系统</span > </div > </section > </div > </template> <script > export default { name :"register" , components :{} }; </script > <style scoped > .register { position : relative; width : 100% ; height : 100% ; background : url (../assets/bg.jpg ) no-repeat center center; background-size : 100% 100% ; } .form_container { width : 370px ; height : 210px ; position : absolute; top :10% ; left :34% ; padding :25px ; border-radius : 5px ; text-align : center; } .form_container .manage_tip .title { font-family : 'Microsooft YaHei' ; font-weight : bold; font-size : 26px ; color : #fff ; } </style >
在 router.js 中设置路由
1 2 3 4 5 6 7 8 9 import Register from './views/Register.vue' { path : '/register' , name :'register' , component : Register },
设置 404 页面
新建 /src/views/404.vue 组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <template> <div class ="notfound" > <img src ="../assets/404.png" alt ="页面没找到" > </div > </template > <style scoped > .notfound {width : 100% ;height :100% ;overflow : hidden;} .notfound img {width : 100% ;height :100% ;} </style >
在 router.js 中设置路由
1 2 3 4 5 6 { path : '*' , name :'/404' , component : NotFound }
注册表单 此后大量使用 element 的代码,为避免过长,仅提一些重要的点,其他请结合原文件阅读笔记
密码规则与验证 加载动画和消息提示 安装 axios
1 2 //client目录下 npm install axios
新建 /src/http.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 import axios from "axios" ;import { Message , Loading } from "element-ui" ;let loading;function startLoading ( ) { loading = Loading .service ({ lock : true , text : "拼命加载中..." , background : "rgba(0,0,0,0.7)" , }); } function endLoading ( ) { 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;
在 main.js 中引用
1 2 3 import axios from "./http" ;Vue .prototype .$axios = axios;
http.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 import axios from "axios" ;import { Message , Loading } from "element-ui" ;let loading;function startLoading ( ) { loading = Loading .service ({ lock : true , text : "拼命加载中..." , background : "rgba(0,0,0,0.7)" , }); } function endLoading ( ) { 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;
配置前端跨域请求(使用 vue-cli 项目) 新建 vue.config.js ,在 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 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 const path = require ("path" );const debug = process.env .NODE_ENV !== "production" ;module .exports = { baseUrl : "/" , outputDir : "dist" , assetsDir : "assets" , lintOnSave : false , runtimeCompiler : true , transpileDependencies : [], productionSourceMap : true , configureWebpack : (config ) => { if (debug) { config.devtool = "cheap-module-eval-source-map" ; } else { } }, chainWebpack : (config ) => { if (debug) { } else { } }, parallel : require ("os" ).cpus ().length > 1 , pluginOptions : { }, pwa : { }, devServer : { open : true , host : "localhost" , port : 8080 , https : false , hotOnly : false , proxy : { "/api" : { target : "http://localhost:5000/api" , ws : true , changOrigin : true , pathRewrite : { "^/api" : "" , }, }, }, before : (app ) => {}, }, };
这样就可以访问我们的后台了
在 register.vue 中配置跳转,这样就可以注册用户了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 submitForm (formName ) { this .$refs [formName].validate (valid => { if (valid) { this .$axios .post ("/api/users/register" , this .registerUser ) .then (res => { this .$message({ message : "账号注册成功!" , type : "success" }); }); this .$router .push ("/login" ); } }); }
登录逻辑 新建组件和添加路由参考注册,这里讲下登录逻辑. Login.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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); this .$router .push ('/index' ); }); this .$router .push ("/login" ); } }); }
路由守卫 router.js
1 2 3 4 5 6 7 8 9 10 11 router.beforeEach ((to, from , next ) => { const isLogin = localStorage .eleToken ? true : false ; if (to.path == "/login" || to.path == "/register" ) { next (); } else { isLogin ? next () : next ("login" ); } });
设置 token 和 token 过期 在请求拦截中,如果存在 token,就把 token 设置到请求头中.
在响应拦截里的 error 里,如果状态码是 401 未授权,表示 token 过期.就在 error 返回函数里清除 token,并跳转到登录页.
http.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 axios.interceptors .request .use ( (config ) => { startLoading (); if (localStorage .eleToken ) { config.headers .Authorization = localStorage .eleToken ; } return config; }, (error ) => { return Promise .reject (error); } ); axios.interceptors .response .use ( (response ) => { endLoading (); return response; }, (error ) => { endLoading (); Message .error (error.response .data ); const { status } = error.response ; if (status == 401 ) { Message .error ("token失效,请重新登录!" ); localStorage .removeItem ("eleToken" ); router.push ("/login" ); } return Promise .reject (error); } );
解析 token 存储到 Vuex 中 安装解析 token 的模块
1 2 3 4 5 6 7 8 9 10 11 import jwt_decode from "jwt_decode" ;const decoded = jwt_decode (token);this .$store .dispatch ("setAuthenticated" , !this .isEmpty (decoded));this .$store .dispatch ("setUser" , decoded);
设置 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 const types = { SET_AUTHENTICATED : "SET_AUTHENTICATED" , SET_USER : "SET_USER" , }; const state = { isAuthenticated : false , user : {}, }; const getters = { isAuthenticated : (state ) => state.isAuthenticated , user : (state ) => state.user , }; const mutations = { [types.SET_AUTHENTICATED ](state, isAuthenticated) { if (isAuthenticated) state.isAuthenticated = isAuthenticated; else state.isAuthenticated = false ; }, [types.SET_USER ](state, user) { if (user) state.user = user; else state.user = {}; }, }; const actions = { setAuthenticated : ({ commit }, isAuthenticated ) => { commit (types.SET_AUTHENTICATED , isAuthenticated); }, setUser : ({ commit }, user ) => { commit (types.SET_USER , user); }, clearCurrentState : ({ commit } ) => { commit (types.SET_AUTHENTICATED , false ); commit (types.SET_USER , null ); }, }; export default new Vuex .Store ({ state, getters, mutations, actions, });
方法: 判断是否为空
1 2 3 4 5 6 7 isEmpty (value ){ return ( value === undefined || value === null || (typeof value === "object" && Object .keys (value).length === 0 ) || (typeof value === "string" && value.trim ().length === 0 ) )
在根组件 App.vue 中判断 token 1 2 3 4 5 6 7 8 9 created ( ) { if (localStorage .eleToken ) { const decoded = jwt_decode (localStorage .eleToken ); this .$store .dispatch ("setAuthenticated" , !this .isEmpty (decoded)); this .$store .dispatch ("setUser" , decoded); } }
样式 新建/component/HeadNav.vue
将 HeadNav.vue 引入到 Index.vue,并注册,然后 template 中调用
在 HeadNav.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 methods : { setDialogInfo (cmdItem ) { switch (cmdItem) { case "info" : this .showInfoList (); break ; case "logout" : this .logout (); break ; } }, showInfoList ( ) { console .log ("个人信息" ); }, logout ( ) { localStorage .removeItem ("eleToken" ); this .$store .dispatch ("clearCurrentState" ); this .$router .push ("/login" ); } } };
个人信息 新建 views/Home.vue
在 router.js 中设置二级路由
新建 views/InfoShow.vue
侧面导航栏 新建 assets/component/LeftMenu.vue
编辑收支类型
创建资金列表 新建 views/FundList.vue
添加各个按钮,事件
设置添加按钮,新增对话框 component/Dialog.vue 组件
编辑和添加 编辑和添加功能雷同,把 formData 放到父级 fundlist 中,并用 props 传递
修改父级中 dialog 的属性,新增 title,options,方便切换弹窗的标题和选项
在 handleEdit 中修改 dialog 的 title,当点击编辑时 title 就切换成’编辑’
编辑时已经拿到数据了.this.formData 的值也就是传入值了
同样,添加的也可以新增 this.formData.但是添加的数据默认是空的
在 onSubmit 中判断提交的类型
1 const url = this .dialog .option == "add" ? "add" : `edit/${this .formData.id} ` ;
删除按钮 1 2 this .$axios .delete (`/api/profiles/delete/${row._id} ` );
分页 elementUI 布局整行分为 24 列.
使用标准分页
1 2 3 4 5 6 7 8 9 <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="currentPage4" :page-sizes="[100, 200, 300, 400]" :page-size="100" layout="total, sizes, prev, pager, next, jumper" :total="400" > </el-pagination>
//修改绑定数据
//设置全部数据容器(数组)
allTableData: []
//在获取数据时就开始设置分页数据
this.setPaginations()
在 setPaginations 中设置默认属性
筛选和权限 定义筛选组件
复制 elementUI 的时间选择器.
添加筛选按钮,绑定筛选事件 handleSearch()
绑定开始时间,结束时间.在 data 中定义开始时间 startTime,结束时间 endTime
添加过滤容器 filterTableData:{},在 getProfile()时也存储一次.
权限 使用计算属性 computed 获取此时用户的身份
1 2 3 4 computed : { user ( ){ return this .$store .getters .user }
使用 v-if 决定是否可以使用添加,编辑,删除操作.
1 2 v-if ="user.indentity == 'manager'"