<template> <div> <h1>課程列表</h1> <div v-for="row in courseList"> <div style="width:350px;float: left;"> <!--<img src="" alt=""/>--> <h3><router-link :to="{name:'detail', params:{id:row.id}}">{{row.title}}</router-link></h3> <p>{{row.level}}</p> </div> </div> </div> </template> <script> export default { name: "index", data() { return { courseList: [] } }, mounted: function () { // vue頁面剛加載時自動執行 this.initCourse() }, methods: { initCourse: function () { /* this.courseList = [ {id:1,title:'Python全棧'}, {id:2,title:'Linux運維'}, {id:3,title:'金融分析'}, ] */ // 經過ajax向接口發送請求,並獲取課程列表 // axios 發送ajax請求 // npm install axios --save // 第一步:在main.js中配置 // 第二步:使用axios發送請求 var that = this; this.$axios.request({ url: 'http://127.0.0.1:8000/api/v1/course/', method: "GET" }).then(function (ret) { // ajax請求發送成功後,獲取的響應內容 console.log(ret.data); if (ret.data.code === 1000) { // 注意這裏的this已經再也不是以前的this that.courseList = ret.data.data }else{ alert("獲取數據失敗"); } }).catch(function (ret) { // ajax請求失敗以後,獲取響應的內容 }) } } } </script> <style scoped> </style>
顯示效果:javascript
<template> <div> <h1>課程詳細頁面</h1> <div> <p>{{detail.course}}</p> <p>{{detail.img}}</p> <p>{{detail.level}}</p> <p>{{detail.slogon}}</p> <p>{{detail.title}}</p> <p>{{detail.why}}</p> <div> <ul v-for="item in detail.chapter"> <li>{{item.name}}</li> </ul> </div> <div> <ul v-for="item in detail.recommends"> <li>{{item.title}}</li> </ul> </div> </div> </div> </template> <script> export default { name: "index", data() { return { detail: { // 定義字典和相關的key course: null, img: null, level: null, slogon: null, title: null, why: null, chapter: [], recommends: [], } } }, mounted() { this.initDetail() }, methods: { initDetail() { var nid = this.$route.params.id; // 獲取當前id值(用於拼接url) var that = this; this.$axios.request({ // 發送axios請求 url: 'http://127.0.0.1:8000/api/v1/course/' + nid + '/', method: 'GET' }).then(function (arg) { // arg是返回的值:{code:1000, data:{...}} // 將拿到的值賦值給detail if (arg.data.code === 1000) { that.detail = arg.data.data // 注意這裏的this已經不是原來的this } else { alert(arg.data.error) } }) } } } </script> <style scoped> </style>
顯示效果:前端
對Detail.vue修改以下:vue
<template> <div> <h1>課程詳細頁面</h1> <div> <p>{{detail.course}}</p> <p>{{detail.img}}</p> <p>{{detail.level}}</p> <p>{{detail.slogon}}</p> <p>{{detail.title}}</p> <p>{{detail.why}}</p> <div> <ul v-for="item in detail.chapter"> <li>{{item.name}}</li> </ul> </div> <div> <h3>推薦課程</h3> <ul v-for="item in detail.recommends"> <li><router-link :to="{name:'detail',params:{id:item.id}}">{{item.title}}</router-link></li> </ul> </div> </div> </div> </template>
給推薦課程添加連接地址,點擊能夠實現url切換,可是因爲組件沒有從新加載,this.initDetail()沒有執行。java
所以頁面的內容並不會發生切換。此方法不合適。python
<template> <div> <h1>課程詳細頁面</h1> <div> <p>{{detail.course}}</p> <p>{{detail.img}}</p> <p>{{detail.level}}</p> <p>{{detail.slogon}}</p> <p>{{detail.title}}</p> <p>{{detail.why}}</p> <div> <ul v-for="item in detail.chapter"> <li>{{item.name}}</li> </ul> </div> <div> <h3>推薦課程</h3> <ul v-for="item in detail.recommends"> <!--爲推薦課程添加點擊事件--> <li @click="changeDetail(item.id)">{{item.title}}</li> </ul> </div> </div> </div> </template> <script> export default { name: "index", data() { return { detail: { // 定義字典和相關的key course: null, img: null, level: null, slogon: null, title: null, why: null, chapter: [], recommends: [], } } }, mounted() { var id = this.$route.params.id; // 獲取當前id值(用於拼接url) this.initDetail(id) }, methods: { initDetail(nid) { var that = this; this.$axios.request({ // 發送axios請求 url: 'http://127.0.0.1:8000/api/v1/course/' + nid + '/', method: 'GET' }).then(function (arg) { // arg是返回的值:{code:1000, data:{...}} // 將拿到的值賦值給detail if (arg.data.code === 1000) { that.detail = arg.data.data // 注意這裏的this已經不是原來的this } else { alert(arg.data.error) } }) }, changeDetail(id){ // click拿到課程id從新加載就能夠渲染成功了 this.initDetail(id); // 切換頁面顯示 this.$router.push({name: 'detail', params: {id:id}}); // 修改url地址 } } } </script> <style scoped> </style>
注意:這裏將var id = this.$route.params.id; 操做提到了vue生命週期mounted方法中。所以initDetail(nid)函數接收的nid,有多是從mounted中傳遞過來的id也能夠是changeDetail傳遞的id。ios
在 Vue 實例內部,你能夠經過 $router
訪問路由實例。所以你能夠調用 this.$router.push
。ajax
this.$router.push({name: 'detail', params: {id:id}}); // 命名的路由
顯示效果以下所示:vuex
點擊推薦課程能夠自由切換頁面路徑和頁面顯示。數據庫
############# App.vue ############### <template> <div id="app"> <router-link to="/index">首頁</router-link> <router-link to="/course">課程</router-link> <router-link to="/micro">微職位</router-link> <router-link to="/news">深科技</router-link> <div> <router-link to="/login">登陸</router-link> </div> <router-view/> </div> </template> ############# index.js ############### import Login from '../components/Login' Vue.use(Router); export default new Router({ routes: [ // 其餘代碼省略 { path: '/login', name: 'login', component: Login }, ] })
<template> <div> <h2>用戶登陸</h2> <div> <p> <input type="text" placeholder="請輸入用戶名" v-model="username"> </p> <p> <input type="password" placeholder="請輸入密碼" v-model="password"> </p> <input type="button" value="登陸" @click="doLogin"> </div> </div> </template> <script> export default { data(){ return { // 經過v-model雙向綁定用戶名和密碼 username:'', password:'' } }, methods: { doLogin(){ this.$axios.request({ url:'http://127.0.0.1:8000/api/v1/auth/', method:'POST', data:{ user:this.username, pwd:this.password }, headers:{ 'Content-Type': 'application/json' } }).then(function (arg) { // 拿回結果 console.log(arg) }).catch(function (arg) { // 拿到錯誤信息 console.log("發生錯誤") }) } } } </script> <style scoped> </style>
注意:這裏是經過v-model雙向綁定用戶名和密碼,並以此經過post請求來發送username和password。npm
(1)路由配置api/urls.py:
urlpatterns = [ """代碼省略""" url(r'^(?P<version>[v1|v2]+)/auth/$', account.AuthView.as_view()), ]
(2)視圖配置api/view/account.py:
from rest_framework.views import APIView from rest_framework.response import Response class AuthView(APIView): def post(self, request, *args, **kwargs): print(request.data) return Response('...')
(3)在前臺頁面嘗試登錄
能夠看到雖然配置的是post請求,但實際卻發送的是OPTIONS請求。
瀏覽器將CORS請求分紅兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。
只要同時知足如下兩大條件,就屬於簡單請求。
(1) 請求方法是如下三種方法之一: HEAD GET POST (2)HTTP的頭信息不超出如下幾種字段: Accept Accept-Language Content-Language Last-Event-ID Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain
凡是不一樣時知足上面兩個條件,就屬於非簡單請求。
若是是複雜請求,會先用options請求進行預檢,經過以後才能發送post請求。
from rest_framework.views import APIView from rest_framework.response import Response from django.shortcuts import HttpResponse class AuthView(APIView): def options(self, request, *args, **kwargs): # 進行預檢 obj = HttpResponse('') obj["Access-Control-Allow-Origin"] = "*" # 容許你的域名來獲取個人數據 obj['Access-Control-Allow-Headers'] = "Content-Type" # 容許你攜帶Content-Type請求頭 return obj def post(self, request, *args, **kwargs): print(request.data) # 同源策略禁止讀取位於 http://127.0.0.1:8000/api/v1/auth/ 的遠程資源。(緣由:CORS 頭缺乏 'Access-Control-Allow-Origin') obj = Response("...") obj["Access-Control-Allow-Origin"] = "*" # 容許你的域名來獲取個人數據 return obj # 返回值再加上一個響應頭
再次訪問登陸頁面,嘗試登陸操做,能夠看到OPTIONS請求經過後,發送POST請求,python後端也打印出request.data中的數據。
上面這種方式過於麻煩了,通常仍是交給中間件來處理跨域問題,爲全部請求都設置頭。
/api/cors.py:
from django.utils.deprecation import MiddlewareMixin class CORSMiddleware(MiddlewareMixin): """自定義中間件""" def process_response(self, request, response): # 添加響應頭 # 容許你的域名來獲取個人數據 response['Access-Control-Allow-Origin'] = "*" # 容許你攜帶Content-Type請求頭,這裏不能寫* # response['Access-Control-Allow-Headers'] = "Content-Type" # 容許你發送GET/POST/DELETE/PUT # response['Access-Control-Allow-Methods'] = "GET, POST" if request.method == "OPTIONS": response["Access-Control-Allow-Headers"] = "Content-Type" return response
class UserInfo(models.Model): user = models.CharField(max_length=32) pwd = models.CharField(max_length=64) class UserToken(models.Model): user = models.OneToOneField(to="UserInfo", on_delete=models.CASCADE) token = models.CharField(max_length=64) # 不只能夠配置token,還能夠配置超時時間
利用makemigrations和migrate完成數據遷移操做。在UserInfo表添加用戶和密碼。
重寫/api/views/account.py以下所示:
from rest_framework.views import APIView from rest_framework.response import Response from django.shortcuts import HttpResponse from api import models import uuid # 網卡和時間生成的隨機字符串 class AuthView(APIView): def post(self, request, *args, **kwargs): """ 用戶登陸認證 :param request: :param args: :param kwargs: :return: """ print(request.data) ret = {'code': 1000} # 用get方法取的話,不存在即爲Null user = request.data.get("user") pwd = request.data.get("pwd") user = models.UserInfo.objects.filter(user=user, pwd=pwd).first() if not user: ret['code'] = 1001 ret['error'] = "用戶名或密碼錯誤" else: uid = str(uuid.uuid4()) # 將生成的隨機對象轉化爲隨機字符串 models.UserToken.objects.update_or_create(user=user, defaults={"token":uid}) ret["token"] = uid return Response(ret)
在vue前端登陸,顯示信息以下:
在python後臺打印request.data信息:{'user': 'asdw', 'pwd': 'asdw131'}、{'user': 'oldboy', 'pwd': '123'}。
1)建立/src/store文件夾,建立並編寫store.js文件:
import Vue from 'vue' import Vuex from 'vuex' // import Cookie from 'vue-cookies' Vue.use(Vuex) export default new Vuex.Store({ // 組件中經過 this.$store.state.username 調用 state: { username: null, token: null, }, })
組件中經過 this.$store.state.username 調用。
2)在main.js中引入store,並放入實例化組件中
import Vue from 'vue' import App from './App' import router from './router' import axios from 'axios' import store from './store/store' // 在vue的全局變量中設置了 $axios=axios // 之後每一個組件使用時:this.$axios Vue.prototype.$axios = axios; Vue.config.productionTip = false; /* eslint-disable no-new */ new Vue({ el: '#app', router, store, // 放入實例化中 components: {App}, template: '<App/>' })
Login.vue:
<script>
export default {
data(){
return {
// 經過v-model雙向綁定用戶名和密碼
username:'',
password:''
}
},
methods: {
doLogin(){
var that = this;
this.$axios.request({
url:'http://127.0.0.1:8000/api/v1/auth/',
method:'POST',
data:{
user:this.username,
pwd:this.password
},
headers:{
'Content-Type': 'application/json'
}
}).then(function (arg) {
// 拿回結果
if (arg.data.code === 1000){
// 成功的狀況下
that.$store.state.token = arg.data.token;
that.$store.state.username = that.username;
}else {
alert(arg.data.error)
}
}).catch(function (arg) {
// 拿到錯誤信息
console.log("發生錯誤")
})
}
}
}
</script>
App.vue:
<template> <div id="app"> <router-link to="/index">首頁</router-link> <router-link to="/course">課程</router-link> <router-link to="/micro">微職位</router-link> <router-link to="/news">深科技</router-link> <div v-if="this.$store.state.token"> <a href="">{{this.$store.state.username}}</a> </div> <div v-else> <router-link to="/login">登陸</router-link> </div> <router-view/> </div> </template> <script> export default { name: 'App' } </script>
如此就能夠經過獲取全局變量實現用戶登陸效果:
可是這種登陸狀態,只要瀏覽器一刷新,登陸狀態就消失了,所以登陸成功不只要設置到全局變量,還要在cookie中放一份全局變量。
(1)store.js
import Vue from 'vue' import Vuex from 'vuex' import Cookie from 'vue-cookies' // 引入cookie,npm install vue-cookies --save Vue.use(Vuex); export default new Vuex.Store({ // 組件中經過 this.$store.state.username 調用 state: { // 默認去cookie中取值 username: Cookie.get("username"), token: Cookie.get("token"), }, mutations: { // 組件中經過this.$store.commit(函數名, 參數)調用 saveToken: function (state, userToken) { state.username = userToken.username; state.token = userToken.token; Cookie.set("username", userToken.username, "20min"); Cookie.set("token", userToken.token, "20min"); }, } })
1)注意引入cookie的方法;
2)注意mutations方法。更改 Vuex 的 store 中的狀態的惟一方法是提交 mutation。Vuex 中的 mutation 很是相似於事件:每一個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調函數 (handler)。
3)組件中經過this.$store.commit(函數名, 參數)調用。
<script>
export default {
data(){
return {
// 經過v-model雙向綁定用戶名和密碼
username:'',
password:''
}
},
methods: {
doLogin(){
var that = this;
this.$axios.request({
url:'http://127.0.0.1:8000/api/v1/auth/',
method:'POST',
data:{
user:this.username,
pwd:this.password
},
headers:{
'Content-Type': 'application/json'
}
}).then(function (arg) {
// 拿回結果
if (arg.data.code === 1000){
// 成功的狀況下
// that.$store.state.token = arg.data.token;
// that.$store.state.username = that.username;
that.$store.commit('saveToken',{token: arg.data.token, username: that.username});
}else {
alert(arg.data.error)
}
}).catch(function (arg) {
// 拿到錯誤信息
console.log("發生錯誤")
})
}
}
}
</script>
App.vue:
<template> <div id="app"> <router-link to="/index">首頁</router-link> <router-link to="/course">課程</router-link> <router-link to="/micro">微職位</router-link> <router-link to="/news">深科技</router-link> <div v-if="this.$store.state.token"> <a href="">{{this.$store.state.username}}</a> <a @click="logout">註銷</a> </div> <div v-else> <router-link to="/login">登陸</router-link> </div> <router-view/> </div> </template> <script> export default { name: 'App', methods:{ logout(){ // 註銷 this.$store.commit('clearToken'); } } } </script>
store.js:
import Vue from 'vue' import Vuex from 'vuex' import Cookie from 'vue-cookies' // 引入cookie,npm install vue-cookies --save Vue.use(Vuex); export default new Vuex.Store({ // 組件中經過 this.$store.state.username 調用 state: { // 默認去cookie中取值 username: Cookie.get("username"), token: Cookie.get("token"), }, mutations: { // 組件中經過this.$store.commit(函數名, 參數)調用 saveToken: function (state, userToken) { state.username = userToken.username; state.token = userToken.token; Cookie.set("username", userToken.username, "20min"); Cookie.set("token", userToken.token, "20min"); }, clearToken: function (state) { state.username = null; state.token = null; Cookie.remove("username"); Cookie.remove("token"); } } })
登出效果以下所示:
點擊註銷後顯示效果:
有些頁面登陸了才能訪問,有些頁面不須要登陸便可訪問。
這裏以micro模塊爲例,給模塊添加登陸判斷,用戶未登陸時訪問微職業,直接跳轉到登陸頁面。
<template> <div> <h1>LuffyX學位</h1> </div> </template> <script> export default { name: "index", data() { return { } }, mounted(){ // 剛加載即執行 if(!this.$store.state.token){ // 重定向返回登陸頁面 this.$router.push({name:"login"}) } } } </script> <style scoped> </style>
可是對於組件不少的網站卻不能這麼處理,而是應該使用vue自帶的攔截器來處理。
index.js:給須要攔截的路由配置meta字段
export default new Router({ routes: [ { path: '/index', name: 'index', component: Index, }, { path: '/course', name: 'course', component: Course }, { path: '/detail/:id', // 動態接收名字爲id的值 name: 'detail', component: Detail }, { path: '/micro', name: 'micro', component: Micro, meta:{ requireAuth:true // 表示必需要登陸 } }, { path: '/news', name: 'news', component: News, meta:{ requireAuth:true // 表示必需要登陸 } }, { path: '/login', name: 'login', component: Login }, ], mode: 'history' })
main.js:
import Vue from 'vue' import App from './App' import router from './router' import axios from 'axios' import store from './store/store' // 在vue的全局變量中設置了 $axios=axios // 之後每一個組件使用時:this.$axios Vue.prototype.$axios = axios; Vue.config.productionTip = false; /* eslint-disable no-new */ new Vue({ el: '#app', router, store, // 放入實例化中 components: {App}, template: '<App/>' }); // 攔截器 to:要去哪 next:去跳轉 from:從哪來 router.beforeEach(function (to, from, next) { if (to.meta.requireAuth) { // 當前要去的url只有登陸後才能訪問 if (store.state.token) { // token爲true表示能夠繼續訪問 next() } else { // token不爲true跳轉到登陸頁面 next({path:'/login',}) } } else { // url不須要訪問便可以訪問 next() } });
好比在訪問微職業時,因爲沒有登陸跳轉到了登陸頁面,輸入帳戶密碼登陸後,顯示的內容應該是微職業的內容。
在url地址中添加返回的url:
// 攔截器 to:要去哪 next:去跳轉 from:從哪來 router.beforeEach(function (to, from, next) { if (to.meta.requireAuth) { // 當前要去的url只有登陸後才能訪問 if (store.state.token) { // token爲true表示能夠繼續訪問 next() } else { // token不爲true跳轉到登陸頁面 next({path:'/login', query:{backUrl: to.fullPath}}) } } else { // url不須要訪問便可以訪問 next() } });
<script>
export default {
data(){
return {
// 經過v-model雙向綁定用戶名和密碼
username:'',
password:''
}
},
methods: {
doLogin(){
var that = this;
this.$axios.request({
url:'http://127.0.0.1:8000/api/v1/auth/',
method:'POST',
data:{
user:this.username,
pwd:this.password
},
headers:{
'Content-Type': 'application/json'
}
}).then(function (arg) {
// 拿回結果
if (arg.data.code === 1000){
// 成功的狀況下
that.$store.commit('saveToken',{token: arg.data.token, username: that.username});
var url = that.$route.query.backUrl;
if (url) {
that.$router.push({path:url})
} else {
that.$router.push({path:'/index'})
}
}else {
alert(arg.data.error)
}
}).catch(function (arg) {
// 拿到錯誤信息
console.log("發生錯誤")
})
}
}
}
</script>
登陸成功後顯示效果:
api/urls.py:
urlpatterns = [ """省略""" url(r'^(?P<version>[v1|v2]+)/micro/$', course.MicroView.as_view()), ]
Couse.py添加MicroView視圖:
class MicroView(APIView): def get(self, request, *args, **kwargs): token = request.query_params.get('token') # 獲取到token obj = models.UserToken.objects.filter(token=token) # 與數據庫中token檢驗 if not obj: return Response("認證失敗") return Response("微職位")
<script> export default { name: "index", data() { return { title:null } }, mounted(){ // 剛加載即執行 this.initMicro() }, methods:{ initMicro(){ this.$axios.request({ url:'http://127.0.0.1:8000/api/v1/micro/', // 這個地址若是被盜,任何人均可以獲取數據 method:"GET", params:{ token:this.$store.state.token } }).then(function (arg) { console.log(arg); }) } } } </script>
這裏須要注意不能只配置Url,這個地址若是被盜,則任何人均可以向後端發送請求獲取數據。
所以配置params參數,在url地址後拼接token參數來發送請求:
當token不正確時:
from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from api import models class LuffyAuth(BaseAuthentication): def authenticate(self, request): token = request.query_params.get("token") obj = models.UserToken.objects.filter(token=token).first() if not obj: raise AuthenticationFailed({"code":1001, "error": "認證失敗"}) return (obj.user.user, obj) # 返回用戶名和token對象
from api.auth.auth import LuffyAuth class MicroView(APIView): authentication_classes = [LuffyAuth] def get(self, request, *args, **kwargs): ret = {"code":1000, "title":"微職位"} return Response(ret)
訪問django頁面驗證:
<template> <div> <h1>LuffyX學位:{{title}}</h1> </div> </template> <script> export default { name: "index", data() { return { title:null } }, mounted(){ // 剛加載即執行 this.initMicro() }, methods:{ initMicro(){ var that = this; this.$axios.request({ url:'http://127.0.0.1:8000/api/v1/micro/', // 這個地址若是被盜,任何人均可以獲取數據 method:"GET", params:{ token:this.$store.state.token } }).then(function (arg) { if (arg.data.code === 1000) { that.title = arg.data.title } }) } } } </script>
訪問http://localhost:8080/micro,效果以下所示:
import Vue from 'vue' import Vuex from 'vuex' import Cookie from 'vue-cookies' // 引入cookie,npm install vue-cookies --save Vue.use(Vuex); export default new Vuex.Store({ // 組件中經過 this.$store.state.username 調用 state: { // 默認去cookie中取值 username: Cookie.get("username"), token: Cookie.get("token"), apiList: { // 全部的接口 course: 'http://127.0.0.1:8000/api/v1/course/', courseDetail: 'http://127.0.0.1:8000/api/v1/course/', auth: 'http://127.0.0.1:8000/api/v1/auth/', micro: "http://127.0.0.1:8000/api/v1/micro/", } }, mutations: { /* 代碼省略*/ } })
均按照以下方法替換:
url: this.store.state.apiList.micro, url: this.store.state.apiList.course, url: this.store.state.apiList.course + nid + '/', url: this.store.state.apiList.auth,