服务器知识: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>