服務器知識:koa、node.js
SSR原理:
*將同⼀個組件渲染爲服務器端的 HTML 字符串,將它們直接發送到瀏覽器,最後將這些靜態標記"激
活"爲客戶端上徹底可交互的應⽤程序。*
應⽤場景:css
ssr的侷限:html
nuxt安裝
npx create-nuxt-app <項⽬名>
啓動
npm run devvue
⽬錄結構node
assets:資源⽬錄 assets ⽤於組織未編譯的靜態資源如 LESS 、 SASS 或 JavaScript 。jquery
components:組件⽬錄 components ⽤於組織應⽤的 Vue.js 組件。Nuxt.js 不會擴展加強該⽬錄下webpack
Vue.js 組件,即這些組件不會像⻚⾯組件那樣有 asyncData ⽅法的特性。ios
layouts:佈局⽬錄 layouts ⽤於組織應⽤的佈局組件。web
middleware: middleware ⽬錄⽤於存放應⽤的中間件。ajax
pages:⻚⾯⽬錄 pages ⽤於組織應⽤的路由及視圖。Nuxt.js 框架讀取該⽬錄下全部的 .vue ⽂vuex
件並⾃動⽣成對應的路由配置。
plugins:插件⽬錄 plugins ⽤於組織那些須要在 根vue.js應⽤ 實例化以前須要運⾏的
Javascript 插件。
static:靜態⽂件⽬錄 static ⽤於存放應⽤的靜態⽂件,此類⽂件不會被 Nuxt.js 調⽤ Webpack 進⾏構建編譯處理。 服務器啓動的時候,該⽬錄下的⽂件會映射⾄應⽤的根路徑 / 下。
store: store ⽬錄⽤於組織應⽤的 Vuex 狀態樹 ⽂件。 Nuxt.js 框架集成了 Vuex 狀態樹 的相關功 能配置,在 store ⽬錄下建立⼀個 index.js ⽂件可激活這些配置。
nuxt.confifig.js: nuxt.config.js ⽂件⽤於組織Nuxt.js 應⽤的個性化配置,以便覆蓋默認配置。
約定優於配置
路由:pages⽬錄中全部 *.vue ⽂件⽣成應⽤的路由配置
導航:
<nuxt-link to="/">⾸⻚</nuxt-link>
嵌套:製造⼀個.vue⽂件和⽂件夾同名
pages/ --| main/ --| main.vue
動態路由:⽂件名或者⽂件夾名稱要帶_
pages/ --| main/ -----| detail/ --------| _id.vue
⾃定義佈局:
<template> <div> <nav> <n-link to="/main">index</n-link> <!-- 別名 --> <n-link to="/main/admin" no-prefetch>管理</n-link> <N-link to="/main/cart">gouwu</N-link> </nav> <nuxt-child /> </div> </template> <script> export default { } </script> <style lang="scss" scoped> </style>
pages/login.vue
<template> <div> <h1>用戶登陸</h1> <el-input v-model="user.username"></el-input> <el-input type="password" v-model="user.password"></el-input> <el-button @click="onLogin">登陸</el-button> </div> </template> <script> export default { data(){ return { user:{ username:"", password:"" } } }, methods:{ onLogin(){ this.$store.dispatch("user/login",this.user).then(ok=>{ if(ok){ this.$router.push("/") } }) } } } </script> <style lang="scss" scoped> </style>
export default { layout: 'main', };
main/admin.vue
<template> <div> <h1>商品管理頁</h1> </div> </template> <script> export default { middleware: 'auth' } </script> <style lang="scss" scoped> </style>
/main/cart.vue
<template> <div> <h1>購物車</h1> </div> </template> <script> export default { } </script> <style lang="scss" scoped> </style>
mian/index.vue
<template> <div> <h1>首頁</h1> <ul> <li v-for="good in goods" :key="good.id"> <nuxt-link :to="{name:'main-detail-id',params:{id:good.id}}"> <span>{{good.text}}</span> <span>¥{{good.price}}</span> <button @click="addCart(good)">加購物車</button> </nuxt-link> </li> </ul> </div> </template> <script> export default { head(){//head裏面項目 return{ title:"列表", meta:[{name:"description",hid:"description",content:"set page meta"}], link:[{rel:"favicon",href:"favicon.ico"}], script:[{src:"https://cdn.jsdelivr.net/npm/jquery/dist/jquery.js"}] } }, layout:'users', // 在這個組件建立以前發送ajax請求 數據回來以後纔開始建立 優先級大於下面的data async asyncData({$axios,error,redirect}) { //asyncData早於時間點 因此不能用this訪問組件實例 const result = await $axios.$get('/api/goods') if(result.ok){ return {goods: result.goods} } //錯誤處理 error({statusCode: 400,message:"查詢數據失敗"}) }, data(){ return { goods:[ {id:1,text:'Web',price:4444}, {id:2,text:'移動',price:5555} ] } } } </script> <style> .container { margin: 0 auto; min-height: 100vh; display: flex; justify-content: center; align-items: center; text-align: center; } .title { font-family: 'Quicksand', 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; display: block; font-weight: 300; font-size: 100px; color: #35495e; letter-spacing: 1px; } .subtitle { font-weight: 300; font-size: 42px; color: #526488; word-spacing: 5px; padding-bottom: 15px; } .links { padding-top: 15px; } </style>
mian/detail/_id.vue
<template> <div> <h2>商品的詳情</h2> ${{goodInfo}} </div> </template> <script> export default { async asyncData(ctx) { console.log(ctx) const {$axios,params,error} = ctx //不能經過this獲取組件實例 可是能夠經過上下文獲取相關數據 const result = await $axios.$get("/api/detail",{params}) if(result.data){ return {goodInfo: result.data} } error({statusCode:400,message:"商品詳情查詢失敗"}) }, } </script> <style lang="scss" scoped> </style>
異步數據:
asyncData
它在組件建立以前執⾏,⾥⾯不要⽤this訪問組件實例
第⼀個參數是上下⽂
能夠在服務端也能夠客戶端都執⾏
asyncData會和data融合
<template> <div> <h1>首頁</h1> <ul> <li v-for="good in goods" :key="good.id"> <nuxt-link :to="{name:'main-detail-id',params:{id:good.id}}"> <span>{{good.text}}</span> <span>¥{{good.price}}</span> <button @click="addCart(good)">加購物車</button> </nuxt-link> </li> </ul> </div> </template> <script> export default { head(){//head裏面項目 return{ title:"列表", meta:[{name:"description",hid:"description",content:"set page meta"}], link:[{rel:"favicon",href:"favicon.ico"}], script:[{src:"https://cdn.jsdelivr.net/npm/jquery/dist/jquery.js"}] } }, layout:'users', // 在這個組件建立以前發送ajax請求 數據回來以後纔開始建立 優先級大於下面的data async asyncData({$axios,error,redirect}) { //asyncData早於時間點 因此不能用this訪問組件實例 const result = await $axios.$get('/api/goods') if(result.ok){ return {goods: result.goods} } //錯誤處理 error({statusCode: 400,message:"查詢數據失敗"}) }, data(){ return { goods:[ {id:1,text:'Web',price:4444}, {id:2,text:'移動',price:5555} ] } } } </script> <style> .container { margin: 0 auto; min-height: 100vh; display: flex; justify-content: center; align-items: center; text-align: center; } .title { font-family: 'Quicksand', 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; display: block; font-weight: 300; font-size: 100px; color: #35495e; letter-spacing: 1px; } .subtitle { font-weight: 300; font-size: 42px; color: #526488; word-spacing: 5px; padding-bottom: 15px; } .links { padding-top: 15px; } </style>
接⼝準備:
const router = require("koa-router")({prefix:"/api"}) const goods = [ {id:1,text:"web",price:1999}, { id: 2, text: "移動", price: 199 }, ]; router.get("/goods",ctx => { ctx.body = { ok: 1, goods } }); router.get("/detail",ctx => { ctx.body = { ok:1, data:goods.find(good => good.id == ctx.query.id) } }); router.post("/login",ctx => { const user = ctx.request.body if(user.username === "jerry" && user.password ==='123'){ // 將token存入cookie const token = 'a mock token' ctx.cookies.set('token',token) ctx.body = { ok:1,token } } else { ctx.body = {ok: 0} } }); module.exports = router
2. 修改服務器配置,server/index.js
const Koa = require('koa') const consola = require('consola') const { Nuxt, Builder } = require('nuxt') // 解析post的數據 const bodyparser = require("koa-bodyparser") const api = require('./api') const app = new Koa() // 設置cookie加密祕鑰 app.keys = ["some secret","another secret"] // Import and Set Nuxt.js options const config = require('../nuxt.config.js') config.dev = app.env !== 'production' async function start () { // Instantiate nuxt.js const nuxt = new Nuxt(config) const { host = process.env.HOST || '127.0.0.1', port = process.env.PORT || 3000 } = nuxt.options.server await nuxt.ready() // Build in development if (config.dev) { const builder = new Builder(nuxt) await builder.build() } // 解析post數據並註冊路由 app.use(bodyparser()) app.use(api.routes()) // 頁面渲染 app.use((ctx) => { ctx.status = 200 ctx.respond = false // Bypass Koa's built-in response handling ctx.req.ctx = ctx // This might be useful later on, e.g. in nuxtServerInit or with nuxt-stash nuxt.render(ctx.req, ctx.res) }) app.listen(port, host) consola.ready({ message: `Server listening on http://${host}:${port}`, badge: true }) } start()
中間件會在⼀個⻚⾯或⼀組⻚⾯渲染以前運⾏咱們定義的函數,常⽤於權限控制、校驗等任務。
⾸⻚重定向,建立middleware/index-redirect.js
export default function({route,redirect}){ //中間件能夠得到上下文,裏面有各類有用信息 //經常使用的有app,route,redirect,store if(route.path === '/'){ redirect('/main') } }
註冊中間件,nuxt.config.js
router: { middleware: ['index-redirect'] },
Nuxt.js會在運⾏Vue應⽤以前執⾏JS插件,須要引⼊或設置Vue插件、⾃定義模塊和第三⽅模塊時特
別有⽤。
建立plugins/api-inject.js
// 注入接口 利用插件機制將服務接口注入組件實例,store實例中 export default({$axios},inject) => { inject("login",user => { return $axios.$post("/api/login",user) }) }
註冊插件,nuxt.config.js
plugins: [ "@/plugins/api-inject" ],
⽤戶登陸及登陸狀態保存,建立store/user.js
// 應用根目錄若是存在Store目錄,Nuxt.js將啓用vuex狀態數,定義各狀態樹時具名導出state,mutations,getters.actions便可 export const state = () => ({ token:'' }) export const mutations = { init(state,token){ state.token = token } } export const getters = { isLogin(state) { return !!state.token } } export const actions = { login({commit},u){ return this.$login(u).then(({token})=>{ if(token){ commit("init",token) } return !!token }) } }
使⽤狀態,建立中間件middleware/auth.js
export default function({redirect,store}){ console.log("token:" + store.state.user.token) //經過vuex中令牌存在與否判斷是否登陸 if(!store.state.user.token){ redirect("/login") } }
註冊該中間件,admin.vue
export default { middleware: 'auth' }
狀態初始化,store/index.js
export const actions = { nuxtServerInit({commit},{req}){ // 服務端就將vuex狀態填充 // 參數1是vuex的上下文 // 參數2是nuxt的上下文 // req.ctx是KOA的上下文 由於在server/index 賦的值 const token = req.ctx.cookies.get('token') if(token){ console.log("初始化token") console.log(token) commit('user/init',token) } } }
nuxt.config.js
module.exports = { mode: 'universal', /* ** Headers of the page */ head: { title: process.env.npm_package_name || '', meta: [ { charset: 'utf-8' }, { name: 'viewport', content: 'width=device-width, initial-scale=1' }, { hid: 'description', name: 'description', content: process.env.npm_package_description || '' } ], link: [ { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' } ] }, /* ** Customize the progress-bar color */ loading: { color: '#fff' }, /* ** Global CSS */ css: [ 'element-ui/lib/theme-chalk/index.css' ], //路由配置 router: { // 順序從前日後執行 middleware: ['index-redirect'] }, /* ** Plugins to load before mounting the App */ plugins: [ '@/plugins/element-ui', '@/plugins/api-inject', {src:"@/plugins.axios",mode:"client"}//僅客戶端執行 ], /* ** Nuxt.js dev-modules */ buildModules: [ ], /* ** Nuxt.js modules */ modules: ['@nuxtjs/axios'], // axios: { // proxy: true // }, // proxy: { // "api": "http://localhost:8080" // }, /* ** Build configuration */ build: { transpile: [/^element-ui/], /* ** You can extend webpack config here */ extend (config, ctx) { } } }
pluygins/axios.js
export default function({$axios}){ // 利用$axios模快幫忙方法setToken設置全局請求頭 // 此處省略token截取邏輯 $axios.setToken(document.cookie,'Bearer') }
總體佈局 默認的
layouts/default.vue
<template> <div> <nuxt /> </div> </template> <style> html { font-family: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; font-size: 16px; word-spacing: 1px; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; box-sizing: border-box; } *, *:before, *:after { box-sizing: border-box; margin: 0; } .button--green { display: inline-block; border-radius: 4px; border: 1px solid #3b8070; color: #3b8070; text-decoration: none; padding: 10px 30px; } .button--green:hover { color: #fff; background-color: #3b8070; } .button--grey { display: inline-block; border-radius: 4px; border: 1px solid #35495e; color: #35495e; text-decoration: none; padding: 10px 30px; margin-left: 15px; } .button--grey:hover { color: #fff; background-color: #35495e; } </style>
自定義佈局
layouts/error.vue
<template> <div class="container"> <h1 v-if="error.statusCode === 404">頁面不存在</h1> <h1 v-else>應用發生錯誤異常</h1> <nuxt-link to="/">首頁</nuxt-link> </div> </template> <script> export default { props:['error'] } </script> <style lang="scss" scoped> </style>
/layouts/users.vue
<template> <div> <h1>自定義佈局</h1> <nuxt></nuxt> </div> </template>