# 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)

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