v-model
.number
- 輸入字符串轉為有效的數字
v-model
.trim
- 輸入首尾空格過濾
<div id="app">
<ul class="box" v-show="isLoad">
<li v-for="(news, i) in allNews.data" :key="news.nsn">
{{i + 1}}. <a :href="news.url">{{news.news_title}}</a>
<img v-show="!isLoad" class="load" src="https://i.giphy.com/media/52qtwCtj9OLTi/giphy.webp" alt="" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.20.0/axios.min.js"></script>
<script src="https://unpkg.com/vue@next"></script>
<script>
const { ref, reactive, onMounted } = Vue;
const app = {
setup() {
const allNews = reactive({ data: [] });
const isLoad = ref(false);
onMounted(() => {
axios.defaults.baseURL = "https://www.lces.tn.edu.tw";
axios
.get("/modules/tadnews/app_api.php?op=list_all_news")
.then((res) => {
allNews.data = res.data;
isLoad.value = true;
.catch((error) => {
console.dir(error);
return {
allNews,
isLoad,
Vue.createApp(app).mount("#app");
</script>
4. 安裝 node.js 及 vue-cli
NVM 是用來管理 node.js 版本的工具:
https://github.com/coreybutler/nvm-windows
nvm -v
:目前該nvm版本
nvm list
:列出目前電腦有安裝的nodejs版本
nvm list available
:目前網路上可用的nodejs版本列表
nvm install v12.19.0
:該nodejs版本下載安裝
nvm uninstall v12.19.0
:移除該nodejs版本
nvm use v12.19.0
:使用該nodejs版本
安裝完 node.js 就會有 npm(node.js套件管理工具)
npm -v
:目前npm的版本
npm init
:新增 package.json
npm install [套件名稱]
:安裝 NPM 套件
npm install [套件名稱] -g
:安裝全域 NPM 套件(C:\Users\[使用者名稱]\AppData\Roaming\npm\node_modules)
npm install [套件名稱] -S
:安裝套件並寫入 package.json 的 dependencies(
-S
等同
--save
)
npm install [套件名稱] -D
:安裝套件並寫入 package.json 的 devDependencies(
-D
等同
--save-dev
)
npm uninstall [套件名稱]
:移除 NPM 套件
npm uninstall [套件名稱] -g
:移除全域 NPM 套件
npm list
:顯示已套件列表
npm install
:還原套件
安裝 vue-cli
npm install -g @vue/cli
vue --version
更新 vue-cli
npm update -g @vue/cli
建立 vue-cli 專案(記得先切換到欲儲存該專案的位置)
vue create 專案名稱
亦可用圖形界面:
vue ui
cd 專案名稱
npm run serve
node_modules
:就是我們透過npm下載下來的套件跟工具都會放在這個資料夾裡面。
package.json
:關於這整包專案所有的資訊,包含我們安裝的套件版本,專案版本,npm指令都可以在這個json檔案裡面找得到,之後要搬移專案重新安裝套件也需要靠這個json檔案(裡面的 script 就是給 npm run 用的)
package-lock.json
:package-lock.json是npm5版本新增的,是專門紀錄package.json裡面更細節的內容,例如安裝的套件的詳細版本,或是確認你的dependency (依賴)是被哪個函式庫所要求的等等,不過這個我們通常就放著不太會管它。
import { createApp } from 'vue'
import App2 from './App2.vue'
import router from './router'
import '@/assets/css/reset.css'
createApp(App2).use(router).mount('#app')
其中
App2.vue
就是一個 Component 組件
import { createApp } from 'vue'
若有
{}
表示是從組建中拆分出來的
import App2 from './App2.vue'
若沒有表示是該組件預設匯出的(一般和檔名一致)
亦可
import
共用的 css 檔
@
來代表以組件的
src
目錄為起點
Component 的基本架構(可放在
components
目錄下,檔案字首大寫 )
<script>
import { ref } from "vue";
// import some from "@/components/some.vue";
export default {
props: {},
emits: {},
components: {},
setup(props, {emit}) {
return {};
</script>
<template>
</template>
<style lang="scss" scoped>
</style>
export default 組件名稱{}
若是沒寫名稱,預設就是檔名
import { ref } from "vue";
中的
{ref}
表示從 vue 組建中拆分出
ref
函式
return { isOpen, HandOpenMenu };
中放常數、函式等,以便讓
<template>
樣板中的
{{文字樣板}}
或
v-bind
、
v-model
、
v-if
、
v-on
...等修飾符使用
<style lang="scss" scoped>
代表要用 scss 預編譯器,且樣式設定只限定在此組件中
<script>
import Header from "@/components/Header.vue";
import Footer from "@/components/Footer.vue";
export default {
components: {
Header,
Footer,
</script>
<template>
<Header></Header>
<Footer></Footer>
</template>
<style lang="scss">
margin: 0;
padding: 0;
background-image: url("~@/assets/images/rightbtn2.jpg");
</style>
import Header from "@/components/Header.vue";
中用
import
來引入
Header
表示組件預設匯出的內容,所以無須放到
{}
中,路徑的
@
來代表以組件的
src
目錄為起點(若是放在
<style>
中要引入素材的話,要用
~@
來代表
src
目錄為起點)
components:{ Header }
中的
Header
表示要放到
<template>
樣板中去使用的組件名稱,可用
<Header></Header>
或
<Header />
來呈現
SCSS用法:
練習網站:
https://www.sassmeister.com/
用法:
https://blog.techbridge.cc/2017/06/30/sass-scss-tutorial-introduction/
validator: function(value) {
// 這個值必須匹配下列字符串中的一個
return ['success', 'warning', 'danger'].indexOf(value) !== -1
若是有參數要在兩個子組件中傳遞(或共用),可以將該參數設到父組件中,然後再由父組件傳遞給個別子組件。
可以先在
emits:{}
中定義要傳什麼東西到父組件(沒設也行),也就是要送到父組件的函式,可以在裡面做參數的驗證。
在
setup(props, { emit }) {}
中從
context
解構出
emit
功能,並在掛載後將
TimeOut
函數及
num
值透過
emit
傳送到父組件
<script>
import TimerBox from "@/components/TimerBox.vue";
export default {
components: {
TimerBox,
setup() {
const handleTimeOut = (num) => {
if (num.value === 0) {
console.log("時間到:", num.value);
return {
handleTimeOut,
</script>
<template>
<TimerBox @TimeOut="handleTimeOut" />
</template>
<style>
</style>
當子組件將
TimeOut
函數及
num
值
emit
到父組件時,就會觸發
<TimerBox @TimeOut ="handleTimeOut" />
中的
@TimeOut
事件,同時會執行
handleTimeOut
函式
父組件要在
setup(){}
中定義好
handleTimeOut
函式的內容並
return
出來
.名稱-enter-active {} /* 整個進入動畫期間的狀態 */
.名稱-enter-from {} /* 進入動畫從 */
.名稱-enter-to {} /* 進入動畫止 */
.名稱-leave-active {} /* 整個離開動畫期間的狀態 */
.名稱-leave-from {} /* 離開動畫從 */
.名稱-leave-to {} /* 離開動畫止 */
<EventBack @click="handleEventBack(100, 'test', $event)" />
利用
$event
可以取得點擊的所有事件
前面可以接一堆參數,
$event
一定要放在最後面
收到時,
$event
就有包含所有資訊
$event.target
就是點擊的實體本身
子元件:@/components/EventBack.vue
<script>
export default {
setup() {
</script>
<template>
<a href="javascript:;">點我</a>
</template>
<style></style>
設定一個
const txtInput = ref(null)
,然後在樣板中用
ref="txtInput"
綁定即可
如此,在 setup 裡面就可以取得被綁定的動元素,並做一些動作,例如取得焦點:
txtInput.value.focus();
完整範例:
import { createApp } from 'vue'
import { numPrice } from './lib/tools'
import App from './App.vue'
const app = createApp(App)
app.directive('focus', {
mounted(el) {
el.focus()
app.directive('money', {
mounted(el, binding) {
const p = numPrice(binding.value)
el.innerHTML = p
updated(el, binding) {
const p = numPrice(binding.value)
el.innerHTML = p
app.directive('price', {
mounted(el) {
const p = numPrice(el.innerHTML)
el.innerHTML = p
updated(el) {
const p = numPrice(el.innerHTML)
el.innerHTML = p
app.mount('#app')
引入
lib/tools.js
時,不要加副檔名
利用
app.directive('名稱', {生命週期})
就可以定義一組樣板語法
mounted(el, binding)
中的
el
就是代表該元件實體,
binding
就是傳進來的資料
若是用
<div v-xxx="值"></div>
的,其值要用
binding.value
若是用
<div v-xxx>{{值}}</div>
,其值要用
el.innerHTML
,也就是取得包著的內容
用
el.innerHTML = 新值
可以替換該元件的顯示值
此外,資料改變時,也要處理其值,所以記得加入
updated()
事件,內容和
mounted()
一致
子組件:@/components/TemplateRef.vue
<script>
import { ref } from "vue";
export default {
setup() {
const num = ref(12345678);
return { num };
</script>
<template>
<input v-focus v-model="num" type="text" placeholder="請輸入文字" />
<h1 v-money="num"></h1>
<h2 v-price>{{ num }}</h2>
</template>
<style></style>
直接套用定義好的樣板語法即可
完整範例:
修改
src\router\index.js
,配置需要的路由,例如:
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import About from "../views/About.vue";
import AboutHome from "../views/About/index.vue";
import Guide from "../views/About/Guide.vue";
import Reference from "../views/About/Reference.vue";
import Changelog from "../views/About/Changelog.vue";
import GitHub from "../views/About/GitHub.vue";
import NotFound from '@/views/NotFound.vue'
const routes = [
path: '/',
name: 'Home',
component: Home
path: '/Chat',
name: 'Chat',
component: () => import('@/components/Chat.vue')
path: "/Courses/:id",
name: "Courses_id",
component: () => import("../views/Courses/_id.vue"),
path: "/about",
name: "About",
component: About,
children: [
path: "",
component: AboutHome,
path: "guide",
component: Guide,
path: "reference",
component: Reference,
path: "changelog",
component: Changelog,
path: "gitHub",
component: GitHub,
path: '/:pathMatch(.*)*',
name: 'not-found',
component: NotFound
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
export default router
全部載入(簡單架構適用):要先
import Home from '../views/Home.vue'
,然後設為
component: Home
即可
動態載入(複雜架構才需要):不事先載入:
component: () => import('@/components/Chat.vue')
若是有傳入變數的,用「
:變數
」來設定
path
利用
children:[]
來做路由套嵌(在某個頁面裡面的一堆連結)
預設的頁面要設為
path: "",
預設history模式為
createWebHistory(process.env.BASE_URL)
,若遇到 http://xxx/index.html 會失效(請後端重新設定網站設定)
若將history模式改為
createWebHashHistory()
,網只會變成 http://xxx/index.html#/,可解決上方問題,但會跟原始錨點#相衝,SEO也不好(所以不建議,常用於後台界面)
例外處理
path: '/:pathMatch(.*)*',
接著修改主架構,以及個別頁面的內容。
修改
App.vue
,設定頁面欲呈現的主要架構,其中用
<router-view></router-view>
來顯示路由內容,其餘部份就是頁首、頁尾等固定的呈現區域
<script>
import Header from "@/components/Header.vue";
import Footer from "@/components/Footer.vue";
export default {
components: {
Header,
Footer,
</script>
<template>
<Header></Header>
<router-view></router-view>
<Footer></Footer>
</template>
<style lang="scss">
margin: 0;
padding: 0;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
font-family: "Microsoft JhengHei", "Heiti TC", "sans-serif";
img {
display: block;
html,
body {
width: 100%;
height: 100%;
</style>
<script>
import Article from "@/components/Article.vue";
import Aside from "@/components/Aside.vue";
import Main from "@/components/Main.vue";
export default {
name: 'Home',
components: {
Article,
Aside,
Main,
</script>
<template>
<Article></Article>
<Aside></Aside>
<Main></Main>
</template>
src\views\NotFound.vue
內容(配合例外處理用)
<script>
export default {};
</script>
<template>
<div id="NotFoundpage">
<h3>404 Not Found</h3>
</template>
<style lang="scss" scoped>
#NotFoundpage {
width: 100%;
height: 578px;
background: rgb(211, 118, 118);
background-size: cover;
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
> div {
text-align: center;
font-size: 48px;
color: #fff;
@media screen and (max-width: 1044px) {
font-size: 30px;
text-align: center;
font-size: 14px;
color: #fff;
line-height: 3em;
@media screen and (max-width: 1044px) {
font-size: 14px;
line-height: 25px;
@media screen and (max-width: 1044px) {
width: 90%;
height: auto;
margin: 0 auto;
@media screen and (max-width: 730px) {
height: 349px;
@media screen and (max-width: 640px) {
height: 175px;
</style>
9-2 加入連結
方法一
<router-link to="xx">
:會自動變成
<a href="/Rwd" class="nav-link router-link-active">RWD</a>
<router-link to="/Rwd">RWD</router-link>
<script>
import axios from "axios";
import { onMounted, onUnmounted, reactive, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
export default {
setup() {
const route = useRoute();
const router = useRouter();
const pageDetal = reactive({ data: {} });
const isError = ref(false);
let timer = null;
onMounted(() => {
axios
.get(`https://vue-lessons-api.herokuapp.com/courses/${route.params.id}`)
.then((res) => {
pageDetal.data = res.data.data[0];
.catch((error) => {
isError.value = true;
pageDetal.data["error_message"] = error.response.data.error_message;
timer = setTimeout(() => {
router.go(-1);
}, 3000);
onUnmounted(() => {
clearTimeout(timer);
return { pageDetal, isError };
</script>
<template>
<div v-if="!isError">
<h1>{{ pageDetal.data.name }}</h1>
<h2>NTD: {{ pageDetal.data.money }}</h2>
<img :src="pageDetal.data.photo" alt="" />
<img :src="pageDetal.data.teacher?.img" alt="" />
<p>{{ pageDetal.data.teacher?.name }}</p>
<h1 v-if="isError">{{ pageDetal.data.error_message }}</h1>
</template>
<style></style>
const openNewTab = (id) => {
const routeData = router.resolve({ path: `/courses/${id}` });
window.open(routeData.href, "_blank");
Vuex就是把資料拉出來,統一在外部存取,不需用props或emmits傳來傳去。
啟用Vuex後會多一個
\src\store\
資料夾,其中
index.js
可以定義各種資料的存取
import { createStore } from "vuex";
export default createStore({
state:{
isOpen: false,
actions:{
handOpenState(context) {
const isOpen = !context.state.isOpen;
context.commit("OpenState", isOpen);
mutations:{
OpenState(state, payload) {
state.isOpen = payload;
getters(
isOpen(state) {
return state.isOpen;
modules: {},
state:{}
用來設定初始變數
actions:{}
用來設定讓組件用
dispatch()
呼叫的函數
函式可以傳入
context
參數(用
context.state
就可取得
state
中的變數值),也可以有第二個參數,也就是外部傳進來的值
用
context.commit
觸發
muations
中的函式以改變資料值
muations:{}
用來改變
state
中資料值
函式一般會傳入
state
,用來取得
state
中的變數值及第二個參數,也就是
actions
傳進來的值
元件可以直接用 store.commit() 來執行
muations
中的函式(但不是好的作法)
getters:{}
用來重組資料(類似computed),函式一般會傳入
state
,用來取得
state
中的變數值
從 Vuex 取出
useStore()
函式,用
store.state.isOpen
便可取到定義在state中的變數,但建議改用
store.getters.isOpen
(或
store.getters['isOpen']
)
const isOpen = computed(() => {
//return store.getters.isOpen;
return store.getters["isOpen"];
用
store.dispatch()
觸發
actions
中的函式(盡量不要用
store.commit()
觸發
muations
中的函式直接來改值,這樣流程不一致不太好)
<script>
import { useStore } from "vuex";
export default {
setup() {
const store = useStore();
const handClickMenu = () => {
store.dispatch("handOpenState");
return { handClickMenu };
</script>
import { createStore } from "vuex";
import state from "./state.js";
import actions from "./actions.js";
import mutations from "./mutations.js";
import getters from "./getters.js";
import Auth from "./Auth";
export default createStore({
state,
actions,
mutations,
getters,
modules: {
Auth,
handOpenState(context) {
const isOpen = !context.state.isOpen;
context.commit("OpenState", isOpen);
import { createStore } from "vuex";
import state from "./state.js";
import actions from "./actions.js";
import mutations from "./mutations.js";
import getters from "./getters.js";
import Auth from "./Auth";
export default createStore({
state,
actions,
mutations,
getters,
modules: {
Auth,
onMounted(() => {
store.dispatch("Auth/handSetToken", "Acbz1x3WQw4eq9qilpFjregn");
console.log("TOKEN =>", store.getters["Auth/getToken"]);
return {};
</script>
import ElementPlus from 'element-plus';
import locale from 'element-plus/lib/locale/lang/zh-tw'
import 'element-plus/lib/theme-chalk/index.css';
const app = createApp(App)
app.use(ElementPlus, { locale })
app.use(store)
app.use(router)
app.mount('#app')
12-1 配置版面
利用布局容器來配置版面(
https://element-plus.gitee.io/#/zh-CN/component/container
),例如(
src\App.vue
):
<template>
<el-container>
<el-aside width="200px">Aside</el-aside>
<el-container>
<el-header><Header/></el-header>
<el-main>
<router-view></router-view>
</el-main>
<el-footer><Footer/></el-footer>
</el-container>
</el-container>
</template>
利用
https://element-plus.gitee.io/#/zh-CN/component/menu
來製作選單,例如(
src\components\Header.vue
):
<script>
import { ref } from "vue";
export default {
setup() {
const activeIndex = ref(1);
const handleSelect = (key, keyPath) => {
console.log(key, keyPath);
return { activeIndex, handleSelect };
</script>
<template>
<el-menu
:default-active="activeIndex"
class="el-menu-demo"
mode="horizontal"
@select="handleSelect"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
<el-menu-item index="1">回首頁</el-menu-item>
<el-submenu index="2">
<template #title>各種功能</template>
<el-menu-item index="2-1">選項1</el-menu-item>
<el-menu-item index="2-2">選項2</el-menu-item>
<el-menu-item index="2-3">選項3</el-menu-item>
<el-submenu index="2-4">
<template #title>選項4</template>
<el-menu-item index="2-4-1">選項1</el-menu-item>
<el-menu-item index="2-4-2">選項2</el-menu-item>
<el-menu-item index="2-4-3">選項3</el-menu-item>
</el-submenu>
</el-submenu>
<el-menu-item
index="3"
disabled
>消息中心</el-menu-item>
<el-menu-item index="4"><a
href="https://www.ele.me"
target="_blank"
>後臺管理</a></el-menu-item>
</el-menu>
</template>
<style lang="scss" scoped>
</style>
13. 使用 websocket
安裝 websocket 套件(
https://www.npmjs.com/package/vue-native-websocket-vue3
)
npm install vue-native-websocket-vue3 --save
在 main.js 加入設定以使用websocket 套件:
import VueNativeSock from "vue-native-websocket-vue3";
// 使用VueNativeSock插件,並進行相關配置
app.use(VueNativeSock, "ws://120.115.2.76:8443", {
store: store,
format: 'json',
nuxt 是一個 vue 用來做 SSR 的框架(建構在Node之上)
SSR:Server Side Rendering 每一個不同頁面就回傳一份不同的 html 檔案 ,SEO好,但 loading 較大
SPA:Single Page Application 用 JavaScript 把畫面 render 出來,不會載入新的 HTML 檔案,不利于SEO ,先慢後快
nuxt 官網:
https://nuxtjs.org/
用 npx 或 npm 來建立專案
npx create-nuxt-app <project-name>
npm init nuxt-app <project-name>
assets:用來放靜態元件,但此資料夾仍會被 nuxt 處理,例如壓縮圖片、CSS檔等
static:也是用來放靜態元件,但不會被 nuxt 處理,例如影片檔、pdf檔
middleware:中間層,從路由到頁面之間的中間層,可以用來做一些共同的檢查、驗證
plugins:全局套件,會自動注入到nuxt.config.js中,全局可用
store:vuex的部份
components:頁面最小的單位組件,可以直接使用,無須 import
讓 nuxt 支援 scss,需安裝以下套件
npm install --save-dev sass sass-loader fibers
伺服器啟動 (
nuxt start
) 當使用靜態網站產生時,伺服器步驟只在構建時執行,但每一個將要產生的頁面都會執行一次。
產生過程開始 (
nuxt generate
)
Nuxt hooks
serverMiddleware
Server-side Nuxt plugins:按照在
nuxt.config.js
中定義的順序進行。
nuxtServerInit
僅在伺服器端呼叫的 Vuex 操作,用於預載 store。
第一個參數是
Vuex context
,第二個參數是
Nuxt.js context
。
從這裡 Dispatch 其他動作→伺服器端隨後的 store 唯一 "入口點"
只能在
store/index.js
中定義。
Middleware
Global middleware
Layout middleware
Route middleware
asyncData
beforeCreate
Vue生命週期方法
created
Vue生命週期方法
新的 fetch(從上到下,同胞元素=平行)
狀態的序列化(
render:routeContext
Nuxt.js hook
)
HTML渲染(
render:route
Nuxt.js hook
)
render:routeDone
hook 當HTML被髮送到瀏覽器時
generate:before
Nuxt.js hook
產生HTML檔案
全靜態產生
generate:page
(可編輯的HTML)
generate:routeCreated
(Route 生成)
generate:done
當所有的HTML檔案都產生后
Client端的生命週期
無論你選擇哪種Nuxt.js模式,這部分的生命週期都會在瀏覽器中完全執行。
接收 HTML
載入 assets (如: JavaScript)
Vue 激活
Middleware
Global middleware
Layout middleware
Route middleware
asyncData(blocking 鎖定)
client-side Nuxt.js plugin:按照在
nuxt.config.js
中定義的順序進行。
beforeCreate
Vue生命週期方法
created
Vue生命週期方法
新的 fetch(從上到下,同胞元素=平行,non-blocking,無鎖定)
beforeMount
Vue生命週期方法
mounted
Vue生命週期方法
14-2 後台的 asyncData
asyncData 是在
Server 端
處理非同步的生命週期(所以前端的各種函數或物件都不能用,例如:$this、alert()),只會執行一次,通常用來擷取API結果,其內容會覆蓋 data 中的東西
asyncData() {
const name = 'Tad';
return {name};
data 的值會被 asyncData 覆蓋,所以除非有要再次修改 asyncData 中的變數,否則一般不會在 data 中去設一個一樣名稱的變數
data() {
return {
name = '';
methods{
handName() {
this.name = 'Kai';
asyncData 只有在 page 中能用,其餘目錄沒有這個生命週期
搭配 async、await 使用:
<script>
import axios from "axios";
export default {
async asyncData() {
const res = await axios.get("http://blog.lces.tn.edu.tw/api.php");
return { res: res.data.data };
</script>
fetch 可以在任何元件目錄下執行, asyncData 只能在 page 目錄下執行
fetch 在 vue 實體產生後才執行(所以可以取得 this) ,也就是在asyncData 之後
fetch 無法直接 return,所以傲用 this.變數來指定新值,覆蓋 data 中的值,例如:
<script>
import axios from "axios";
export default {
// async asyncData() {
// const res = await axios.get("http://blog.lces.tn.edu.tw/api.php");
// return { res: res.data.data };
data() {
return {
res: []
async fetch() {
this.res = await axios
.get("http://blog.lces.tn.edu.tw/api.php")
.then(respon => respon.data.data);
</script>
若是加入
fetchOnServer: false
,則會變成在前端執行,如:
<script>
import axios from "axios";
export default {
data() {
return {
res: []
fetchOnServer: false,
async fetch() {
this.res = await axios
.get("http://blog.lces.tn.edu.tw/api.php")
.then(respon => respon.data.data);
</script>
$fetchState.pending
可以取得 fetch 是否執行完畢的狀態,例如:
<template>
<div class="container">
<Logo />
<h1 v-if="$fetchState.pending">載入中...</h1>
<h1 v-if="!$fetchState.pending" class="title">
<ul v-if="!$fetchState.pending">
<li v-for="news in res" key="news.id">{{ news.title }}</li>
</template>
<Logo />
<h1 v-if="$fetchState.pending">載入中...</h1>
<h1 v-if="$fetchState.error">出錯了...{{ $fetchState.error }}</h1>
</template>
$fetchState.timestamp
可搭配
keep-alive
使用於
activated
生命週期中使用(
activated
生命週期只有有用
keep-alive
時才會有),讓資料可以進行緩存,例如:
<template>
<Nuxt keep-alive />
</template>
page/index.vue
<script>
import axios from "axios";
export default {
data() {
return {
res: [],
activated() {
// Call fetch again if last fetch more than 30 sec ago
if (this.$fetchState.timestamp <= Date.now() - 30000) {
this.$fetch();
fetchOnServer: false,
async fetch() {
this.res = await axios
.get("http://blog.lces.tn.edu.tw/api.php")
.then((respon) => respon.data.data);
</script>
meta: [
{ charset: "utf-8" },
{ name: "viewport", content: "width=device-width, initial-scale=1" },
{ property: "op:url", content: "https://go.nuxtjs.dev/config-head" },
{ property: "op:image", content: "https://nuxtjs.org/logos/nuxt.svg" },
{ hid: "description", name: "description", content: "" }
link: [
{ rel: "icon", type: "image/x-icon", href: "/favicon.ico" },
{ rel: "stylesheet", type: "text/css", href: "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.0.0-beta1/css/bootstrap.min.css" },
{ rel: "stylesheet", type: "text/css", href: "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css" }
script: [
{ src: 'https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.0.0-beta1/js/bootstrap.bundle.min.js' },
{ src: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js' },
若是放在各個頁面,則可以做該頁面設定,例如:
<script>
export default {
head: {
title: '關於我們',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ property: 'op:url', content: 'https://go.nuxtjs.dev/config-head' },
{ property: 'op:image', content: 'https://nuxtjs.org/logos/nuxt.svg' },
{ hid: 'description', name: 'description', content: '' }
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
</script>
也可以將 head 改為函式,如此可以接收 data 的內容來動態改變值,如:
<script>
import axios from "axios";
export default {
head() {
return {
title: this.news.title,
link: [
rel: "stylesheet",
type: "text/css",
href:
"https://cdnjs.cloudflare.com/ajax/libs/fancybox/3.5.7/jquery.fancybox.min.css",
script: [
"https://cdnjs.cloudflare.com/ajax/libs/fancybox/3.5.7/jquery.fancybox.min.js",
async asyncData(context) {
const res = await axios.get(
"http://blog.lces.tn.edu.tw/api.php?op=show&id=" + context.params.id
return { news: res.data };
</script>
14-5 Nuxt 使用 Router
連結可以用 <NuxtLink to=""></NuxtLink > 或 <nuxt-link to=""></nuxt-link>,等同<router-link to=""></router-link>
巢狀路由(嵌套式路由),例如: /about/aaa 只要在 pages 下建立 about 目錄,底下建立 aaa.vue 即可(其中 <Nuxt /> 要換成 <NuxtChild />)
透過 vue router 去切換頁面的內容的時候在 nuxtjs 中我們使用
<
Nuxt /
>
這個組件來切換,而不是 <router-view />
<router-view /> -> <Nuxt />
在 nuxtjs 裡面我們需要點擊連結然後切換router的頁面的時候,我們會使用 <NuxtLink to="/about">about </NuxtLink> 這種方式來做為我們的連結
<router-link to="/">Home</router-link> -> <NuxtLink to="/">Home</NuxtLink>
當我們在做
嵌套網址
的時候,我們會使用
<NuxtChild /> 來切換我們頁面的內容,而不是用原本的 <router-view />,這樣在識別的時候也會比較好識別這個組件是不是用嵌套網址
<router-view /> -> <NuxtChild />
如果要取得參數來切換內容,例如 show/1、show/2 可以建立 pages/show/_id.vue,asyncData可用context取得路由的參數:
<script>
import axios from "axios";
export default {
async asyncData(context) {
const res = await axios.get(
"http://blog.lces.tn.edu.tw/api.php?op=show&id=" + context.params.id
return { news: res.data };
</script>
若要在method中使用router,必須寫成 this.$router 的寫法,如:
methods: {
goToNews(id) {
this.$router.push("/show/"+id);
export default {
props: ["error"],
// layout: 'blog' // you can set a custom layout for the error page
</script>
<template>
<h1 v-if="error.statusCode === 404">沒有此頁面</h1>
<h1 v-else>喔喔~我秀斗了</h1>
</template>
export default ({ app }, inject) => {
// 注入 $hello(msg) in Vue, context and store.
inject('hello', msg => console.log(`Hello ${msg}!`))
async asyncData({$hello, $axios}) {
$hello('我在後端被asyncData了')
const res =await $axios.get('http://blog.lces.tn.edu.tw/api.php?op=index')
return {res: res.data.data}
若是axios無法取得資料,可以用 try{}.catch(){} 方式來呈現錯誤,但一旦用到axios的地方很多,這樣會顯得麻煩,所以,可以做一個 plugin來處理。
先建立
plugins/axios.js
export default function ({ $axios, redirect }) {
$axios.onError(error => {
if (error.response.status === 500) {
console.log('500');
redirect('/500')
if(error.response.status === 404) {
console.log('404');
redirect('/404')
加入設定檔nuxt.config.js
plugins: ["~/plugins/hello.js", "~/plugins/axios.js"],
如此當api傳回404或500時就會自動導向到 page/404.vue或500.vue(要自己做)
<script>
export default {
</script>
<template>
<h1>404 咩有~</h1>
</template>
<style lang="scss" scoped>
</style>
14-7-3 整合一般npm套件 plugin
先安裝此套件
https://www.npmjs.com/package/vue-notification
(要有支援SSR的套件才不會有問題)
npm install --save vue-notification
建立/plugins/notification.js
import Vue from "vue";
// for SPA:
// import Notifications from 'vue-notification'
// for SSR:
import Notifications from "vue-notification/dist/ssr.js";
Vue.use(Notifications);
border-left-color: #42A85F;
.vn-fade-enter-active, .vn-fade-leave-active, .vn-fade-move {
transition: all .5s;
.vn-fade-enter, .vn-fade-leave-to {
opacity: 0;
https://developer.mozilla.org/zh-TW/docs/Web/API/Window/localStorage
https://medium.com/%E9%BA%A5%E5%85%8B%E7%9A%84%E5%8D%8A%E8%B7%AF%E5%87%BA%E5%AE%B6%E7%AD%86%E8%A8%98/javascript-localstorage-%E7%9A%84%E4%BD%BF%E7%94%A8-e0da6f402453
建立plugin/localStorage.js
export default ({ app }, inject) => {
inject("localStorage", {
set(key = "", val = {}) {
localStorage.setItem(key, JSON.stringify(val));
get(key = "") {
const obj = JSON.parse(localStorage.getItem(key));
// 避免傳回 null
if (!obj) {
return {};
return obj;
remove(key = "") {
localStorage.removeItem(key);
removeAll() {
localStorage.clear();
mounted() {
this.$hello("我在前端被mounted了");
this.$localStorage.set("userData", { name: "tad", age: 48 });
console.log('userData', this.$localStorage.get("userData"));
store目錄內的每一個.js檔案都會被轉換為一個 namespaced module (index為根模組)。你的
state
應該始終是一個函式,以避免在伺服器端出現不必要的共享狀態。
加入methods,以便觸發vuex actions中的方法,並加入 computed 以取得 getters 的值
methods: {
handCounter() {
this.$store.dispatch('handleAddCounter');
// console.log(this.$store.getters.getCounter);
computed:{
getCount(){
return this.$store.getters.getCounter;
樣板部份也加入按鈕並顯示之
<button @click="handCounter">按我計數+1 {{getCount}}</button>
也可以從後端直接取得 state 的值
async asyncData({ app }) {
return { counter: app.store.state.counter };
從後端也可以利用 dispatch 將值塞進 state 中,若要如此,就不要 return,而是在 computed 中用 getters 去取得 state 的值,不然會造成兩份資料可能會不一致的問題。
若要做成 Vuex module,只要在 store/ 下建立一個目錄即可,如 store/User/index.js
若要拆分 Vuex,只要分別存成 store/User/state.js、store/User/actions.js、store/User/mutations.js、store/User/getters.js 即可