# nuxt {%hackmd BJrTq20hE %} ## 建立專案: ```javascript= npx create-nuxt-app <项目名> npx nuxi init <檔名> //(nuxt 3) ``` ### 路徑快捷: | 別名(ALIAS) | 目錄(DIRECTORY) | |-----------|---------------| | ~ or @ | srcDir,原始碼根目錄 | | ~~ or @@ | rootDir,專案根目錄 | ### 目錄規則: - assets: 放需要 webpack 編譯的靜態資源 - static: 不需要編譯的靜態資源 - pages: 各頁對應的頁面元件 (相當於你寫 SPA 時,VueRouter路由指定的元件) - components: 跨頁面的元件,不具狀態 - nuxt.config.js: Nuxt 全域設定檔 - .nuxt: Nuxt 暫存資料夾 ## components加入子資料夾( subfolder ): ``` ├── assets ├── components │   └── form │       ├── FormGroup.vue │       ├── TextField.vue │       ├── Button.vue ``` 其組件名稱為 <FormTextField /> and <FormButton />, 而不是 <FormGroup /> ## nuxt-child(Router-view)巢狀視窗: ``` pages/ --| index.vue (首頁放nuxt-child) --| index/ --| index.vue (首頁會預設顯示此頁) --| login.vue (網址為:http://localhost:3000/login) --| parent.vue (多重巢狀-放nuxt-child) --| parent/ (建立一個同名資料夾,網址為:http://localhost:3000/parent) --| index.vue (要顯示的首頁一律叫index) --| children2.vue ``` ## nuxt部屬&環境相關設定 - 環境參數設置 - 当使用 `nuxt` 命令时,`dev` 会被强制设置成 `true`( 開發模式 ) - 当使用 `nuxt build`, `nuxt start` 或 `nuxt generate` 命令时,`dev` 会被强制设置成 `false`( 生產模式 ) ```javascript= module.exports = { dev: process.env.NODE_ENV !== 'production' } ``` - nuxt部屬路徑修改 ```javascript= //nuxt.config.js target: 'static', //使用generate必加(預設是server) router: { mode: 'hash', // 使用 'hash' 主要是為了適配以相對路徑開啟的靜態站點, 必須使用 'hash' 否則路由跳轉不生效 base: process.env.NODE_ENV==='production'?'./':'/', // 使用 './' 主要是為了適配以相對路徑開啟的靜態站點(生產模式) } ``` - Github page 會自動過濾掉某些開頭名字的資料夾 [如果檔案對應的路由,會發現對應的位子沒錯,但卻發生404?](https://medium.com/joelifestory/nuxt-js-%E9%9D%9C%E6%85%8B%E7%B6%B2%E7%AB%99%E6%8E%A8%E9%80%81%E5%88%B0-github-page-6004a70f83c4) By default, Jekyll doesn’t build files or folders that: - are located in a folder called /node_modules or /vendor - start with _, ., or # - end with ~ - are excluded by the exclude setting in your configuration file 解決方式: ==在根目錄加上名為 `.nojekyll` 的空白檔案== 加上去之後可以解決 github pages 讀不到 _framework, _content 這幾個資料夾中的檔案 - npm run generate錯誤 `DOMException: Failed to execute 'appendChild' on 'Node': This node type does not support this method.` [解決方法](https://github.com/nuxt/nuxt.js/issues/1552#issuecomment-341729165) ```javascript= //nuxt.config.js generate: { minify: { collapseWhitespace: false } } ``` - `push-dir`使用套件來佈署: https://nuxtjs.org/deployments/github-pages#command-line-deployment 1. `npm i -D push-dir` 2. 新增deploy指令: ```javascript= "scripts": { ... "deploy": "push-dir --dir=dist --branch=gh-pages --cleanup" }, ``` 1. ```javascript= yarn generate yarn deploy ``` ## 安裝sass: - 指定 @10 版本是因為它與 Nuxt 中使用的 webpack 版本兼容。 - Fiber 自動啟用與 sass 的同步編譯(速度提高 2 倍) - @nuxtjs/style-loaders 確保您的變量和 mixin 在您的所有組件中都可用,而無需在每個文件中>導入它們。 - `-S`:`--save`的简写,它是把安装包的名称和版本号存到到dependencies中,是在生产环境中要用到的。 - `-D`:`--save-dev`標誌確保這些包不會出現在最終構建中,從而使我們的項目規模盡可能小。由於 SCSS/Sass 在構建時被編譯為標準 CSS,因此不需要這些。 1. `npm install --save-dev sass node-sass sass-loader@10` 2. 加入資源資料夾及檔案: ``` assets |-scss |-css ``` 3. 添加scss路徑如下: ```javascript= css: [ { src: '~/assets/scss/all.scss', lang: 'sass' } ], ``` ## 啟用vite模式: `npm i -D nuxt-vite` 設定: ```javascript= // nuxt.config export default { buildModules: [ 'nuxt-vite' ], vite: { ssr: true }, //vite啟用ssr } ``` ## 啟用composition api:[文件](https://composition-api.nuxtjs.org/getting-started/introduction/) `npm install @nuxtjs/composition-api --save` ```javascript= //nuxt.config.js { buildModules: [ '@nuxtjs/composition-api/module' ] } //引入的時候 [x] const { createApp, ref, onMounted, onUnmounted, computed} = Vue; //vue3才有內含 [O] import { useStore, useRoute, useRouter } from '@nuxtjs/composition-api' ``` ## 啟用axios: `npm install @nuxtjs/axios` ```javascript= //nuxt.config.js export default { modules: ['@nuxtjs/axios'] } ``` 如使用vite mode,nuxt-vite需降版為0.2.4,否則會`cannot find module 'lib/axios' from...` (PS:截稿此時最新版為0.3.5) ### 封裝axios - 因為nuxt自己封裝過,所以無法使用`axios.defaults.headers.common['Authorization']`,所以要自己重新封裝 [參考文件](https://zhuanlan.zhihu.com/p/75864109) 1. 創建/api/request.js,加入攔截器 ```javascript= /** * 封装Axios * 处理请求、响应错误信息 */ import { Message } from 'element-ui' //引用饿了么UI消息组件 import axios from 'axios' //引用axios import Cookies from 'js-cookie' // create an axios instance const service = axios.create({ baseURL: 'http://114.33.243.242', // 所有异步请求都加上/api,nginx转发到后端Springboot withCredentials: true, // send cookies when cross-domain requests timeout: 5000 // request timeout }) // request interceptor(攔截器) service.interceptors.request.use( config => { // do something before request is sent // config.headers['-Token'] = getToken() let token = Cookies.get('allwellToken') if (token) { config.headers['token'] = token } return config }, error => { // do something with request error console.log(error) // for debug return Promise.reject(error) } ) // response interceptor service.interceptors.response.use( /** * If you want to get http information such as headers or status * Please return response => response */ /** * Determine the request status by custom code * Here is just an example * You can also judge the status by HTTP Status Code */ response => { const res = response.data //res is my own data if (res.code === 0) { // do somethings when response success // Message({ // message: res.message || '操作成功', // type: 'success', // duration: 1 * 1000 // }) return res } else { // if the custom code is not 200000, it is judged as an error. Message({ message: res.message || 'Error', type: 'error', duration: 2 * 1000 }) console.log(res); return Promise.reject(new Error(res.message || 'Error')) } }, error => { console.log('err' + error) // for debug Message({ message: error.message, type: 'error', duration: 5 * 1000 }) return Promise.reject(error) } ) export default service //导出封装后的axios ``` 2. 創建api接口:/api/user.js ```javascript= import request from './request' export function apiUserLogin(data) { return request({ url: '/api/core/userInfo/login', method: 'post', data: data }) } export function apiUserCheckToken() { return request({ url: '/api/core/userInfo/checkToken', method: 'get', }) } ``` 3. 使用: ```javascript= import { apiUserLogin } from '@/api/user.js' apiUserLogin(this.data) .then((res) => { console.log(res); if (res.code == 0) { ... ``` ## 安裝bootstrap: `npm install bootstrap` Bootstrap5 有用到 Popper,另外安裝 `npm install @popperjs/core ` 自訂bootstrap: ```sass= @import "node_modules/bootstrap/scss/functions"; @import "./helpers/_variables"; @import "./helpers/_utilities"; @import "node_modules/bootstrap/scss/bootstrap"; ``` 在plugins資料夾新增`boostrap.js`: ```javascript= import * as all from '~/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js' export default all ``` 設定: ```javascript= //nuxt.config.js export default { css: [ { src: '~/assets/scss/all.scss', lang: 'sass' } ], //js要放在plugins裡面,放在header,webpack不會編譯header路徑 plugins: [ { src: '~plugins/bootstrap.js', mode: 'client' }, ], } ``` plugin不能直接引入`{ src: '~/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js', ssr: false}`,否則會出現警告:[9121](https://github.com/nuxt/nuxt.js/issues/9121) ``` WARN Found 2 plugins that match the configuration, suggest to specify extension: - C:/Users/Allwell-dev02/Documents/vue/nuxt/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js - C:/Users/Allwell-dev02/Documents/vue/nuxt/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js.map ``` ## 安裝vee-validate("^3.4.14"): [教學stackFlow](https://stackoverflow.com/questions/59485231/veevalidate-3-x-nuxt-js-push-a-custom-error-to-validationobserver) [rules查詢](https://vee-validate.logaretm.com/v3/guide/rules.html#rules) 1. npm install vee-validate --save 2. 在根目錄建立plugins資料夾 3. plugins下創建vee-validate.js,引入 ```javascript= import Vue from "vue"; import { extend, ValidationObserver, ValidationProvider } from "vee-validate"; //引入全部 import * as rules from "vee-validate/dist/rules"; for (let [rule, validation] of Object.entries(rules)) { // noinspection TypeScriptValidateTypes extend(rule, { ...validation }); } //引入部分規則 import { required, email, min, confirmed } from "vee-validate/dist/rules"; extend("required", { ...required, message: "This field is required", }); extend("email", email); extend("min", min); extend("confirmed", { ...confirmed, message: "密碼不一致" }); Vue.component("ValidationProvider", ValidationProvider); Vue.component("ValidationObserver", ValidationObserver); ``` 4. 在nuxt.config.js設定: ```javascript= plugins: [ { src: '~/plugins/vee-validate.js', ssr: false } ], ``` 5. 使用方式:沒用form ```htmlembedded= <template> <ValidationObserver tag="form" ref="form"> <!--tag可自訂標籤(optional)--> <!-- Tel input --> <ValidationProvider v-slot="{ errors }" rules="required|isPhone" name="tel" > <div class="form-outline mb-4"> <input id="tel" v-model="data.tel" name="tel" type="text" class="form-control" :class="{ 'is-invalid': errors[0], 'is-valid': data.tel }" /> <span class="invalid-feedback">{{ errors[0] }}</span> <label class="d-block form-label" for="registerName">手機號</label> </div> </ValidationProvider> </ValidationObserver> </template> ``` ```javascript= const form = ref(null); const data = ref({ id: 'abc', tel: '', }) //自訂規則(須在該頁新增,不能在plugin定義,會找不到): import { extend } from 'vee-validate' extend('isPhone', (value) => { const phoneNumber = /^(09)[0-9]{8}$/ return phoneNumber.test(value) ? true : '電話號碼格式錯誤' }) function submit() { // 送出表單 const submit = form.value.validate() submit.then((isValid) => { if (isValid) { console.log('doSomething...') } } }) ``` 密碼確認寫法: ```htmlembedded= <ValidationObserver tag="form" ref="form"> <!--可當form標籤用or not--> <!-- password input --> <ValidationProvider v-slot="{ errors }" rules="required" name="pass"> <div class="form-outline mb-4"> <input id="tel" v-model="data.pass" name="pass" type="password" class="form-control" :class="{ 'is-invalid': errors[0], 'is-valid': data.pass }" /> <span class="invalid-feedback">{{ errors[0] }}</span> <label class="d-block form-label" for="registerName">密碼</label> </div> </ValidationProvider> <ValidationProvider v-slot="{ errors }" rules="required|confirmed:pass" name="pass2"> <div class="form-outline mb-4"> <input id="tel" v-model="data.pass2" name="tel" type="password" class="form-control" :class="{ 'is-invalid': errors[0], 'is-valid': data.pass2 }" /> <span class="invalid-feedback">{{ errors[0] }}</span> <label class="d-block form-label" for="registerName">密碼確認</label> </div> </ValidationProvider> </ValidationObserver> ``` ## 安裝element-ui [官方文件](https://element.eleme.cn/#/zh-CN/component/installation) 1. `npm install element-ui -S` 2. 在 plugins 文件夹下新建 `element-ui.js` 文件 ```javascript= import Vue from 'vue' import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' import locale from 'element-ui/lib/locale/lang/zh-TW' Vue.use(ElementUI, { locale }) ``` 3. 配置 nuxu.config.js ```javascript= plugins: [ '@/plugins/element-ui' ], ``` ## optional api與composition api混用 [CSDN-link](https://blog.csdn.net/csl125/article/details/115712848) ```javascript= <template> <div class="mt-l72"> <p>{{ text1 }}</p> <button @click="myFun1">按钮1</button> <p>{{ age }}</p> <button @click="myfun2">按钮2</button> </div> </template> <script> import { ref } from "@nuxtjs/composition-api"; export default { name: "App", data() { return { text1: 1, }; }, // setu函数是组合api的入口函数 setup() { console.log("setUp函数被执行", this); let age = ref(20); function myfun2() { age.value+=1 } //optional api可以接到return出去的值 return { age, myfun2 }; }, beforeCreate() { console.log("beforeCreate被执行", this); }, created() { console.log("Create被执行", this); }, components: {}, methods: { myFun1() { this.age+=1 this.text1 +=2 }, }, }; </script> ``` 注意: - 只有 option api 引用 composition api 的份,没有反过来的份; - 而且由于 composition api 立即执行并 return 的原因,它不被允许作为 async 异步函数进行定义,只能是同步不能是异步。 - setup函数无法使用data和methods,setup函数中this为undefined //{%hackmd 05Dx8Q4HTa6H23X9PNrFqQ %} ## middleware(nuxt的路由守衛) [Ray的blog](https://hsiangfeng.github.io/vue/20190816/3627128539/) 1. 在根目錄下新建 `middleware` 資料夾,然後新增 `routerAuth.js` ```javascript= export default ({ route, redirect }) => { console.log('此為路由守衛'); console.log(route.meta[1].requiresAuth) //由某頁面導入的參數 if (route.meta[1].requiresAuth) { let authStates = false; // 改為 true 就可以進入了 //驗證中... if (!authStates) { // 若沒有登入就使用返回登入頁面 return redirect('/login') } } } ``` 2. 在需要守衛的頁面加上 ```javascript= export default { middleware: 'routerAuth', //導向哪一隻路由守衛 //要導入的參數(optional) meta: { requiresAuth: true, }, } ``` 如果要全頁面驗證,直接在 `nuxt.config.js` 設置 ```javascript= export default { middleware: [] /* 單數亦可用字串 */ /* middleware: 'stat' */ } ``` ## asyncData 方法 (真SSR) 可以在设置组件的数据之前能异步获取或处理数据。如圖: [官方文件](https://www.nuxtjs.cn/guide/async-data) ![](https://i.imgur.com/boD2y9k.jpg) asyncData只是在首屏的时候调用一次,后续交互还是交给client处理 ```javascript= <template> <div class="mt-5"> <h1>姓名:{{ info.userId }}</h1> <h1>年龄:{{ info.id }}</h1> <h1>兴趣:{{ info.title }}</h1> {{ name }} </div> </template> <script> import axios from 'axios' export default { data() { return { name: 'Hello World', info: '' } }, async asyncData() { // 此區塊在nuxt渲染的時候會先在此等待,等資料回傳回來之後再一併渲染(html+資料) let { data } = await axios.get('https://jsonplaceholder.typicode.com/todos/1') console.log(data); return { info: data } //return回去的数据会直接和data(){return {}}放在一起 }, created() { // 如果只用一般axios,nuxt渲染不會等資料回傳就已經跑完渲染了(只有html結構) axios.get('https://jsonplaceholder.typicode.com/todos/1').then((res) => { console.log(res); this.info = res.data }) } } </script> ``` ## SEO、meta設置 個性meta設置:比如你現在要作個新聞頁面,那爲了搜索引擎對新聞的收錄,需要每個頁面對新聞都有不同的title和meta設置。 [教學](https://www.twblogs.net/a/5c015e09bd9eee7aec4ea830) 1. 使用鏈結傳遞title等參數 ```java= // /pages/news/index.vue <li><nuxt-link :to="{name:'news-id',params:{id:123,title:'nuxt.com'}}">News-1</nuxt-link></li> ``` 2. 接著修改/pages/news/_id.vue,讓它根據傳遞值變成獨特的meta和title標籤。 ```javascript= // /pages/news/_id.vue <template> <div> <h2>News-Content [{{$route.params.id}}]</h2> <ul> <li><a href="/">Home</a></li> </ul> </div> </template> <script> export default { validate ({ params }) { // Must be a number,使用正則驗證參數,返回true正常進入頁面,如果返回false進入404頁面。 return /^\d+$/.test(params.id) }, data(){ return{ title:this.$route.params.title, } }, //獨立設置head信息 head(){ return{ title:this.title, meta:[ {hid:'description',name:'news',content:'This is news page'} ] } } } </script> ``` 注意:爲了避免子組件中的meta標籤不能正確覆蓋父組件中相同的標籤而產生重複的現象,建議利用 hid 鍵爲meta標籤配一個唯一的標識編號。 ## vuex in nuxt 只能搭配option api