前段時間和公司一個由科技轉產品的同事探討他的職涯道路,對我說了一句深以為然的話:
「不要把自己禁錮在某一個領域,技術到產品的轉變,首先就是思維上的轉變。你一直做前端,數據的交互你只知道怎麼進,卻不知道裡面是怎麼出的,這就是局限性。
醍醐灌頂般,剛好學習vue的時候看到有個註冊登入的項目,索性我也跟著動手做一個vue項目,引入koa和mongodb,實現客戶端(client)提交-服務端(server)接收返回-入資料庫全過程。
本專案基於vue-cli搭建,利用token方式進行使用者登入驗證,並實現註冊入庫、讀取使用者、刪除使用者等功能。文章預設讀者有一定的node和vue基礎,基礎部分不贅述。
系統環境:MacOS 10.13.3
使用淘寶鏡像安裝
$ npm install -g cnpm --registry=https://registry.npm.taobao.org
然後所有的npm install改為cnpm install
為了讓專案想法和所選技術更清晰明了,畫了一個圖方便理解。
1.初始化項目
$ npm install
2.啟動項目
$ npm run dev
3.啟動MongoDB
$ mongod --dbpath XXX
xxx是專案裡data資料夾(也可以另行新建,資料庫用來存放資料)的路徑,也可直接拖入終端機。
4.啟動服務端
$ node server.js
vue的首選UI庫我是選擇了餓了麼的Element-UI了,其他諸如iview 、 vue-strap好像沒有ele全面。
$ npm i element-ui -S
//在项目里的mian.js里增加下列代码
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
利用UI裡面的選項卡切換做註冊和登入介面的切換,以login元件作為整個登入系統的主介面,register元件作為獨立元件切入。 Element-UI的組成方式,表單驗證等API請查閱官網。
//login组件
<template>
<div class="login">
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="登录" name="first">
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item label="名称" prop="name">
<el-input v-model="ruleForm.name"></el-input>
</el-form-item>
<el-form-item label="密码" prop="pass">
<el-input type="password" v-model="ruleForm.pass" auto-complete="off"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')">登录</el-button>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="注册" name="second">
<register></register>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import register from '@/components/register'
export default {
data() {
var validatePass = (rule, value, callback) => {
if (value === '') {
callback(new Error('请输入密码'));
} else {
if (this.ruleForm.checkPass !== '') {
this.$refs.ruleForm.validateField('checkPass');
}
callback();
}
};
return {
activeName: 'first',
ruleForm: {
name: '',
pass: '',
checkPass: '',
},
rules: {
name: [
{ required: true, message: '请输入您的名称', trigger: 'blur' },
{ min: 2, max: 5, message: '长度在 2 到 5 个字符', trigger: 'blur' }
],
pass: [
{ required: true, validator: validatePass, trigger: 'blur' }
]
},
};
},
methods: {
//选项卡切换
handleClick(tab, event) {
},
//重置表单
resetForm(formName) {
this.$refs[formName].resetFields();
},
//提交表单
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.$message({
type: 'success',
message: '登录成功'
});
this.$router.push('HelloWorld');
} else {
console.log('error submit!!');
return false;
}
});
},
},
components: {
register
}
}
</script>
<style rel="stylesheet/scss" lang="scss">
.login {
width: 400px;
margin: 0 auto;
}
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.el-tabs__item {
text-align: center;
width: 60px;
}
</style>
接下來是註冊元件
//register组件
<template>
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item label="名称" prop="name">
<el-input v-model="ruleForm.name"></el-input>
</el-form-item>
<el-form-item label="密码" prop="pass">
<el-input type="password" v-model="ruleForm.pass" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="确认密码" prop="checkPass">
<el-input type="password" v-model="ruleForm.checkPass" auto-complete="off"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')">注册</el-button>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
</template>
<script>
export default {
data() {
var validatePass = (rule, value, callback) => {
if (value === '') {
callback(new Error('请输入密码'));
} else {
if (this.ruleForm.checkPass !== '') {
this.$refs.ruleForm.validateField('checkPass');
}
callback();
}
};
var validatePass2 = (rule, value, callback) => {
if (value === '') {
callback(new Error('请再次输入密码'));
} else if (value !== this.ruleForm.pass) {
callback(new Error('两次输入密码不一致!'));
} else {
callback();
}
};
return {
activeName: 'second',
ruleForm: {
name: '',
pass: '',
checkPass: '',
},
rules: {
name: [
{ required: true, message: '请输入您的名称', trigger: 'blur' },
{ min: 2, max: 5, message: '长度在 2 到 5 个字符', trigger: 'blur' }
],
pass: [
{ required: true, validator: validatePass, trigger: 'blur' }
],
checkPass: [
{ required: true, validator: validatePass2, trigger: 'blur' }
],
}
};
},
methods: {
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.$message({
type: 'success',
message: '注册成功'
});
// this.activeName: 'first',
} else {
console.log('error submit!!');
return false;
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
}
}
}
</script>
vue-router是vue建立單頁專案的核心,可以透過組合元件來組成應用程序,我們要做的是將元件(components)對應到路由(routes),然後告訴vue-router在哪裡渲染它們。 上面的程式碼裡已有牽涉到一些路由切換,我們現在來完善路由:
$ cnpm i vue-router
import Router from 'vue-router'
Vue.use(Router)
在src資料夾下面新建router(資料夾)/index.js 我們引進了三個元件:
HelloWorld 登入後的展示頁
login 登入主介面
register 註冊元件
利用router.beforeEach路由守衛設定需要先登入的頁面。透過requiresAuth這個欄位來判斷該路由是否需要登入權限,需要權限的路由就攔截,然後再判斷是否有token(下文會講到token),有就直接登錄,沒有就跳到登入頁面。
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import login from '@/components/login'
import register from '@/components/register'
Vue.use(Router)
const router = new Router({
mode: 'history',
routes: [{
path: '/',
name: 'home',
component: HelloWorld,
meta: {
requiresAuth: true
}
},
{
path: '/HelloWorld',
name: 'HelloWorld',
component: HelloWorld,
},
{
path: '/login',
name: 'login',
component: login,
},
{
path: '/register',
name: 'register',
component: register,
},
]
});
//注册全局钩子用来拦截导航
router.beforeEach((to, from, next) => {
//获取store里面的token
let token = store.state.token;
//判断要去的路由有没有requiresAuth
if (to.meta.requiresAuth) {
if (token) {
next();
} else {
next({
path: '/login',
query: { redirect: to.fullPath } // 将刚刚要去的路由path作为参数,方便登录成功后直接跳转到该路由
});
}
} else {
next();
}
});
export default router;
我們可以看到路由守衛中token是從store裡面獲取的,意味著我們是把token的各種狀態存放到store裡面,並進行獲取,更新,刪除等操作,這就需要引入vuex狀態管理。
解釋為什麼一個簡單的註冊登入單頁需要用到vuex:專案中我們各個元件的操作基本上都需要取得到token進行驗證,如果元件A儲存了一個token,元件B要取得這個token就涉及到了元件通信,這會非常繁瑣。引入vuex,不再是組件間的通信,而是組件和store的通信,簡單方便。
$ cnpm i vuex --S
在main.js引入store,vue實例也要加入store
//引入store
import store from './store'
然後在需要使用vuex的組件中引入
//store index.js
import Vuex from 'vuex'
Vue.use(Vuex)
在src資料夾下面新建store(資料夾)/index.js
//store index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
//初始化时用sessionStore.getItem('token'),这样子刷新页面就无需重新登录
const state = {
token: window.sessionStorage.getItem('token'),
username: ''
};
const mutations = {
LOGIN: (state, data) => {
//更改token的值
state.token = data;
window.sessionStorage.setItem('token', data);
},
LOGOUT: (state) => {
//登出的时候要清除token
state.token = null;
window.sessionStorage.removeItem('token');
},
USERNAME: (state, data) => {
//把用户名存起来
state.username = data;
window.sessionStorage.setItem('username', data);
}
};
const actions = {
UserLogin({ commit }, data){
commit('LOGIN', data);
},
UserLogout({ commit }){
commit('LOGOUT');
},
UserName({ commit }, data){
commit('USERNAME', data);
}
};
export default new Vuex.Store({
state,
mutations,
actions
});
可以看到我們透過actions提交mutation,進行token的更改、清除以及用戶名儲存的操作。
此時啟動項目,可以看到初步的註冊登入介面,點選註冊或登入按鈕可以切換到對應介面,並有基礎的表單驗證,登入後會進入helloworld頁面。
我們寫好了基礎介面,接下來就是要把表單資料傳送到後台並進行一系列處理。現在還沒有後端介面沒關係,我們先寫好前端axios請求。
vue的通訊之前使用vue-resource ,有很多坑。直到vue2.0來臨,直接拋棄vue-resource ,而使用axios 。
封裝ajax,用來傳送請求,非同步取得資料。以Promise為基礎的HTTP客戶端,適用於:瀏覽器和node.js。
具體API中文說明:https://www.kancloud.cn/yunye/axios/234845
$ cnpm i -S axios
import axios from 'axios'
在設定vue-router那部分加入了路由守衛攔截需要登入的路由,但這種方式只是簡單的前端路由控制,並不能真正阻止使用者存取需要登入權限的路由。當token失效了,但token依然保存在當地。這時候你去存取需要登入權限的路由時,實際上應該讓使用者重新登入。這時候就需要攔截器interceptors + 後端介面回傳的http狀態碼來判斷。
在src資料夾下面新建axios.js(和App.vue同級)
//axios.js
import axios from 'axios'
import store from './store'
import router from './router'
//创建axios实例
var instance = axios.create({
timeout: 5000, //请求超过5秒即超时返回错误
headers: { 'Content-Type': 'application/json;charset=UTF-8' },
});
//request拦截器
instance.interceptors.request.use(
config => {
//判断是否存在token,如果存在的话,则每个http header都加上token
if (store.state.token) {
config.headers.Authorization = `token ${store.state.token}`;
}
return config;
}
);
//respone拦截器
instance.interceptors.response.use(
response => {
return response;
},
error => { //默认除了2XX之外的都是错误的,就会走这里
if (error.response) {
switch (error.response.status) {
case 401:
router.replace({ //跳转到登录页面
path: 'login',
query: { redirect: router.currentRoute.fullPath } // 将跳转的路由path作为参数,登录成功后跳转到该路由
});
}
}
return Promise.reject(error.response);
}
);
export default {
//用户注册
userRegister(data){
return instance.post('/api/register', data);
},
//用户登录
userLogin(data){
return instance.post('/api/login', data);
},
//获取用户
getUser(){
return instance.get('/api/user');
},
//删除用户
delUser(data){
return instance.post('/api/delUser', data);
}
}
程式碼最後暴露了四個請求方法,分別對應註冊(register)、登入(login)、取得(user)、刪除(delUser)用戶,並且都在/api下面,四個請求介面分別是:
http://localhost:8080/api/login
http://localhost:8080/api/register
http://localhost:8080/api/user
http://localhost:8080/api/delUser
後面我們再利用這四個方法寫出相對應的後台介面。
文章從這裡開始進入服務端,由於服務端需求和資料庫、http安全通訊(jwt)共同搭建,因此請結合本節和下面的資料庫、jwt章節閱讀。
koa2可以使用可以使用async/await語法,免除重複繁瑣的回呼函數嵌套,並使用ctx來存取Context物件。
現在我們用koa2寫專案的API服務介面。
$ cnpm i koa
$ cnpm i koa-router -S //koa路由中间件
$ cnpm i koa-bodyparser -S //处理post请求,并把koa2上下文的表单数据解析到ctx.request.body中
const Koa = require('koa');
在專案根目錄下方新建server.js,作為整個server端的啟動入口。
//server.js
const Koa = require('koa');
const app = new Koa();
//router
const Router = require('koa-router');
//父路由
const router = new Router();
//bodyparser:该中间件用于post请求的数据
const bodyParser = require('koa-bodyparser');
app.use(bodyParser());
//引入数据库操作方法
const UserController = require('./server/controller/user.js');
//checkToken作为中间件存在
const checkToken = require('./server/token/checkToken.js');
//登录
const loginRouter = new Router();
loginRouter.post('/login', UserController.Login);
//注册
const registerRouter = new Router();
registerRouter.post('/register', UserController.Reg);
//获取所有用户
const userRouter = new Router();
userRouter.get('/user', checkToken, UserController.GetAllUsers);
//删除某个用户
const delUserRouter = new Router();
delUserRouter.post('/delUser', checkToken, UserController.DelUser);
//装载上面四个子路由
router.use('/api',loginRouter.routes(),loginRouter.allowedMethods());
router.use('/api',registerRouter.routes(),registerRouter.allowedMethods());
router.use('/api',userRouter.routes(),userRouter.allowedMethods());
router.use('/api',delUserRouter.routes(),delUserRouter.allowedMethods());
//加载路由中间件
app.use(router.routes()).use(router.allowedMethods());
app.listen(8888, () => {
console.log('The server is running at http://localhost:' + 8888);
});
程式碼裡可以看到,取得使用者和刪除使用者都需要驗證token(詳見下文jwt章節),並且我們把四個介面掛在到了/api上,和前面axios的請求路徑一致。
另外由於我們的專案啟動埠是8080,koa介面監聽的連接埠是8888,於是需要在config/index.js檔裡面,在dev配置裡加上:
proxyTable: {
'/api': {
target: 'http://localhost:8888',
changeOrigin: true
}
},
JWT能夠在HTTP通訊過程中,幫助我們進行身分認證。
具體API詳見:https://segmentfault.com/a/1190000009494020
1、客戶端透過使用者名稱和密碼登入伺服器;
2、服務端對客戶端身分進行驗證;
3、服務端對該使用者產生Token,返回給客戶端;
4.客戶端將Token儲存到本機瀏覽器,一般儲存到cookie(本文是用sessionStorage,看情況而定)中;
5.客戶端發起請求,需攜帶該Token;
6.服務端收到請求後,先驗證Token,之後回傳資料。服務端不需要儲存Token,只需要對Token中所攜帶的資訊進行驗證即可。無論客戶端存取後台的哪台伺服器,只要可以通過使用者資訊的驗證即可。
在server資料夾,下面新建/token(資料夾)裡面新增checkToken.js和createToken.js,分別放置檢查和新增token的方法。
$ cnpm i jsonwebtoken -S
const jwt = require('jsonwebtoken');
module.exports = function(user_id){
const token = jwt.sign({user_id: user_id}, 'zhangzhongjie', {expiresIn: '60s'
});
return token;
};
建立token時,我們把使用者名稱當作JWT Payload的屬性,並且把金鑰設定為'zhangzhongjie',token過期時間設定為60s。意思是登入之後,60s內刷新頁面不需要再重新登入。
const jwt = require('jsonwebtoken');
//检查token是否过期
module.exports = async ( ctx, next ) => {
//拿到token
const authorization = ctx.get('Authorization');
if (authorization === '') {
ctx.throw(401, 'no token detected in http headerAuthorization');
}
const token = authorization.split(' ')[1];
let tokenContent;
try {
tokenContent = await jwt.verify(token, 'zhangzhongjie');//如果token过期或验证失败,将抛出错误
} catch (err) {
ctx.throw(401, 'invalid token');
}
await next();
};
先拿到token再用jwt.verify進行驗證,注意此時密鑰要對應上createToken.js的密鑰'zhangzhongjie'。如果token為空、過期、驗證失敗都拋出401錯誤,要求重新登入。
MongoDB是一種文件導向資料庫管理系統,旨在為WEB 應用提供可擴展的高效能資料儲存解決方案。用node連結MongoDB非常方便。
$ cnpm i mongoose -S
MongoDB的連線有好幾種方式,這裡我們用connection。 connection是mongoose模組的預設引用,傳回一個Connetion物件。
在server資料夾下新建db.js,作為資料庫連線入口。
//db.js
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/vue-login');
let db = mongoose.connection;
// 防止Mongoose: mpromise 错误
mongoose.Promise = global.Promise;
db.on('error', function(){
console.log('数据库连接出错!');
});
db.on('open', function(){
console.log('数据库连接成功!');
});
//声明schema
const userSchema = mongoose.Schema({
username: String,
password: String,
token: String,
create_time: Date
});
//根据schema生成model
const User = mongoose.model('User', userSchema)
module.exports = User;
除了我們用的connetion ,還有*connect()和createConnection()*連線方式。
Schema定義表的模板,讓這一類document在資料庫中有一個具體的構成、儲存模式。但也只是定義了Document是什麼樣子的,至於生成document和對document進行各種操作(增刪改查)則是透過相對應的model來進行的,那我們就需要把userSchema轉換成我們可以使用的model,也就是說model才是我們可以進行操作的handle。
編譯完model我們就拿到了一個名為User的model。
注意你在這裡定義的schema表,後面寫入註冊入庫時資料的儲存需要對應這個表。
在server資料夾下新controller(資料夾)/user.js,存放資料庫的操作方法。
先安裝一些功能插件
$ cnpm i moment -s //用于生成时间
$ cnpm i objectid-to-timestamp -s //用于生成时间
$ cnpm i sha1 -s //安全哈希算法,用于密码加密
//user.js
const User = require('../db.js').User;
//下面这两个包用来生成时间
const moment = require('moment');
const objectIdToTimestamp = require('objectid-to-timestamp');
//用于密码加密
const sha1 = require('sha1');
//createToken
const createToken = require('../token/createToken.js');
//数据库的操作
//根据用户名查找用户
const findUser = (username) => {
return new Promise((resolve, reject) => {
User.findOne({ username }, (err, doc) => {
if(err){
reject(err);
}
resolve(doc);
});
});
};
//找到所有用户
const findAllUsers = () => {
return new Promise((resolve, reject) => {
User.find({}, (err, doc) => {
if(err){
reject(err);
}
resolve(doc);
});
});
};
//删除某个用户
const delUser = function(id){
return new Promise(( resolve, reject) => {
User.findOneAndRemove({ _id: id }, err => {
if(err){
reject(err);
}
console.log('删除用户成功');
resolve();
});
});
};
//登录
const Login = async ( ctx ) => {
//拿到账号和密码
let username = ctx.request.body.name;
let password = sha1(ctx.request.body.pass);//解密
let doc = await findUser(username);
if(!doc){
console.log('检查到用户名不存在');
ctx.status = 200;
ctx.body = {
info: false
}
}else if(doc.password === password){
console.log('密码一致!');
//生成一个新的token,并存到数据库
let token = createToken(username);
console.log(token);
doc.token = token;
await new Promise((resolve, reject) => {
doc.save((err) => {
if(err){
reject(err);
}
resolve();
});
});
ctx.status = 200;
ctx.body = {
success: true,
username,
token, //登录成功要创建一个新的token,应该存入数据库
create_time: doc.create_time
};
}else{
console.log('密码错误!');
ctx.status = 200;
ctx.body = {
success: false
};
}
};
//注册
const Reg = async ( ctx ) => {
let user = new User({
username: ctx.request.body.name,
password: sha1(ctx.request.body.pass), //加密
token: createToken(this.username), //创建token并存入数据库
create_time: moment(objectIdToTimestamp(user._id)).format('YYYY-MM-DD HH:mm:ss'),//将objectid转换为用户创建时间
});
//将objectid转换为用户创建时间(可以不用)
user.create_time = moment(objectIdToTimestamp(user._id)).format('YYYY-MM-DD HH:mm:ss');
let doc = await findUser(user.username);
if(doc){
console.log('用户名已经存在');
ctx.status = 200;
ctx.body = {
success: false
};
}else{
await new Promise((resolve, reject) => {
user.save((err) => {
if(err){
reject(err);
}
resolve();
});
});
console.log('注册成功');
ctx.status = 200;
ctx.body = {
success: true
}
}
};
//获得所有用户信息
const GetAllUsers = async( ctx ) => {
//查询所有用户信息
let doc = await findAllUsers();
ctx.status = 200;
ctx.body = {
succsess: '成功',
result: doc
};
};
//删除某个用户
const DelUser = async( ctx ) => {
//拿到要删除的用户id
let id = ctx.request.body.id;
await delUser(id);
ctx.status = 200;
ctx.body = {
success: '删除成功'
};
};
module.exports = {
Login,
Reg,
GetAllUsers,
DelUser
};
上面這些方法構成了專案中資料庫操作的核心,我們來剖析一下。
首先定義了公用的三個基礎方法:findUser、findAllUsers、delUser。其中findUser需要傳入username參數,delUser需要傳入id參數。
拿到用戶post提交的表單信息,new之前按資料庫設計好的並編譯成model的User,把獲取到的用戶名,密碼(需要用sha1哈希加密),token(利用之前創建好的createToken方法,並將使用者名稱當作jwt的payload參數),產生時間存入。
此時要先搜尋資料庫這個使用者名稱是否存在,存在就回傳失敗,否則把user存入資料庫並回傳成功。
拿到用戶post的表單訊息,使用者名稱和密碼(註冊用了雜湊加密,此時要解密)。從資料庫搜尋該使用者名,判斷使用者名稱是否存在,不存在回傳錯誤,存在的話判斷資料庫裡存的密碼和使用者提交的密碼是否一致,一致的話給這個使用者產生一個新的token,並存入資料庫,返回成功。
就是把上面公用findAllUsers方法封裝了一下並把資訊放在result裡面,讓後面helloworld頁面可以取得到這個資料並展示出來。
注意要先拿到需要刪除的用戶id,作為參數傳入。
寫完這些方法,就可以把前面沒有完善的註冊登入功能完善了。
當我們註冊完,資料入庫,此時我們想查看剛才註冊入庫的數據,要用到資料庫視覺化工具。我是用MongoBooster ,操作簡單。
由下圖可以看到範例中註冊的兩條數據,包含了id、username、password、token、time。那串長長的密碼是由於哈希加密編譯而成。
在register.vue的表單驗證後面加上下列程式碼
//register.vue
if (valid) {
axios.userRegister(this.ruleForm)
.then(({}) => {
if (data.success) {
this.$message({
type: 'success',
message: '注册成功'
});
} else {
this.$message({
type: 'info',
message: '用户名已经存在'
});
}
})
}
登入元件我們之前沒有任何資料提交,現在在驗證成功後加入一系列方法完成登入操作: 引入axios
import axios from '../axios.js'
然後在login.vue的表單驗證後加上下列程式碼
//login.vue
if (valid) {
axios.userLogin(this.ruleForm)
.then(({ data }) => {
//账号不存在
if (data.info === false) {
this.$message({
type: 'info',
message: '账号不存在'
});
return;
}
//账号存在
if (data.success) {
this.$message({
type: 'success',
message: '登录成功'
});
//拿到返回的token和username,并存到store
let token = data.token;
let username = data.username;
this.$store.dispatch('UserLogin', token);
this.$store.dispatch('UserName', username);
//跳到目标页
this.$router.push('HelloWorld');
}
});
}
將表單資料提交到後台,返回data狀態,進行帳號存在與否的判斷操作。登入成功需要拿到回傳的token和username存到store,跳到目標HelloWorld頁。
註冊登入成功後,終於到了實際的展示頁了-helloworld!
我們來完善這個元件,讓它展示出目前所有的已註冊用戶名,並給出刪除按鈕。
//Helloworld.vue
<template>
<div class="hello">
<ul>
<li v-for="(item,index) in users" :key="item._id">
{{ index + 1 }}.{{ item.username }}
<el-button @click="del_user(index)">删除</el-button>
</li>
</ul>
<el-button type="primary" @click="logout()">注销</el-button>
</div>
</template>
<script>
import axios from '../axios.js'
export default {
name: 'HelloWorld',
data () {
return {
users:''
}
},
created(){
axios.getUser().then((response) => {
if(response.status === 401){
//不成功跳转回登录页
this.$router.push('/login');
//并且清除掉这个token
this.$store.dispatch('UserLogout');
}else{
//成功了就把data.result里的数据放入users,在页面展示
this.users = response.data.result;
}
})
},
methods:{
del_user(index, event){
let thisID = {
id:this.users[index]._id
}
axios.delUser(thisID)
.then(response => {
this.$message({
type: 'success',
message: '删除成功'
});
//移除节点
this.users.splice(index, 1);
}).catch((err) => {
console.log(err);
});
},
logout(){
//清除token
this.$store.dispatch('UserLogout');
if (!this.$store.state.token) {
this.$router.push('/login')
this.$message({
type: 'success',
message: '注销成功'
})
} else {
this.$message({
type: 'info',
message: '注销失败'
})
}
},
}
}
</script>
<style scoped>
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
.hello {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
width: 400px;
margin: 60px auto 0 auto;
}
</style>
輸出頁面比較簡單,這裡說幾個重點:
1.要在實例創建完成後( created() )立即請求getUser()接口,請求失敗要清除掉token,請求成功要把返回資料放入user以供頁面渲染。
2. thisID要寫成物件格式,否則會報錯
3.註銷時要清除掉token
人的思維轉變確實是最難的。依流程來說,應該是koa先設計出接口,前端再根據這個接口去請求,但我反過來,是先寫好前端請求,再根據這個請求去製定接口。
當然,也遇到了很多困難:當我搞好了前端展示頁面,axios也寫好了,但在用koa寫接口這裡卡了很久,完全沒有概念,就是前言說的“只知道數據怎麼進,不知道怎麼出」。然後遇到接口500報錯又調試了很久,主要是自己對接口沒有調試概念,最後還是公司的瑯琊大佬幫忙解決,感謝。