wordpress_blog

This is a dynamic to static website.

Vue3 複習08

Vue Router

本章節以後 “發問的重要說明”

由於本章節以後,問題相對會複雜很多,很難從單一片段了解問題點。

如:部分程式碼無法運作,實際卻是其它片段的大小寫錯誤

所以本章節以後,為了加速回覆同學們問題的效率,請上傳完整程式碼至 Github 或個人雲端空間(不需要包含 node_modules),好讓我可以運行完整環境來仔細檢視喔。

如果僅上傳部分程式碼或程式碼的截圖,還是會要求重新上傳完整程式碼再行回覆,也感謝同學們的配合

Vue Router 簡介

關於路由 (MVC 概念)

後端路由在頁面上呈現的狀態

傳統後端路由: https://www.hexschool.com/user

前端路由在頁面上顯示的狀態

前端路由: https://www.hexschool.com/#/user

專案中的概念

前端路由: https://www.hexschool.com/#/user
前端路由: https://www.hexschool.com/#/products

路由表
‘/user’ > User.vue
‘/products’ > Products.vue

User.vue
Products.vue

Vue Router 開發流程

準備流程:

  1. 引入 Vue Router 環境
  2. 定義元件
  3. 定義路由表
  4. 加入對應連結

Vue Router 相關資源

注意:Vue 3 搭配的 Router 版本在網址中會有 “next” 的字樣,如:router.vuejs.org 則是 Vue 2 版本的路由

盡可能自己完成本章節的操作。

如果需要參考時,請注意 git 排序是逆向的,也可依據 Commit 名稱尋找,找到後可直接透過 hash 文字打開該章節所調整的片段

在一般網頁中引入 Vue Router

// HTML
<div id="app">
  <h2>路由示範</h2>
  {{ text }}
  <router-link to="/a">a</router-link> |
  <router-link to="/b">b</router-link>
  <router-view></router-view>
</div>
// JS
<script src="https://unpkg.com/vue-router@4.0.5/dist/vue-router.global.js"></script>

<script>
const componentA = {
  template: `
  <div>A</div>
  `
};
const componentB = {
  template: `
  <div>B</div>
  `
};

const app = Vue.createApp({
  data() {
    return {
      counter: 0,
      text: '這裡有一段文字'
    }
  }
});

// 路由設定
const router = VueRouter.createRouter({
  // 網址路徑模式:使用網址 hash 的形式
  history: VueRouter.createWebHashHistory(),
  // 匯入路由表
  routes: [
    {
      // a 路徑
      path: '/a',
      // A 元件
      component: componentA,
    },
    {
      // b 路徑
      path: '/b',
      // B 元件
      component: componentB,
    },
  ]
});
app.use(router);
app.mount('#app');
</script>

Vue Cli 中使用 Vue Router 開發

  1. 使用 vue create vue_router_record
  2. Please pick a preset: Manually select features
  3. Check the features needed for your project: Choose Vue versin, Babel, Router, CSS Pre-processors, Linter
  4. Choose a version of Vue.js that you want to start the project with 3.x (Preview)
  5. Use history mode for router? (Requires proper server setup for index fallback in production) No
  6. Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with node-sass)
  7. Pick a linter / formatter config: Airbnb
  8. Pick additional lint features: Lint on save
  9. Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
  10. Save this as a preset for future projects? N
  11. 運行專案 – npm run serve

講解

Vue Cli 版本和 HTML 開發哪裡不一樣

  • router/index.js 檔案
  • main.js 檔案
  • App.vue 檔案
    <router-view/> – 主要的頁面切換
    <router-link></router-link> – 用戶頁面連結點擊使用
  • views 資料夾 – 主要頁面的設定
    components 資料夾 – 相對比較小的元件
// src/router/index.js
// 匯入 vue-router 資源
import { createRouter, createWebHashHistory } from 'vue-router';
// 匯入獨立元件
import Home from '../views/Home.vue';

// 路由表
const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
  },
];

// router 結構
const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

// 匯出
export default router;
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';

createApp(App).use(router).mount('#app');
// src/App.vue
<template>
  <div id="nav">
    <router-link to="/">Home</router-link> |
    <router-link to="/about">About</router-link>
  </div>
  <router-view/>
</template>

<style lang="scss">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

#nav {
  padding: 30px;

  a {
    font-weight: bold;
    color: #2c3e50;

    &.router-link-exact-active {
      color: #42b983;
    }
  }
}
</style>

新增元件和路由

  1. 新增一個元件加到畫面上
    新增 views/NewPage.vue
    原則: 先建立元件再加入路由
  2. 打開 router/index.js 檔案
    在路由表新增物件內容
  3. 把頁面的連結加到畫面上
    在 App.vue 檔案加入新的 <router-link>
    使用另外一種寫法加入連結,改用動態屬性的方式把連結加入
// views/NewPage.vue
<template>
  <div>新增頁面</div>
</template>
// router/index.js
// 匯入 vue-router 資源
import { createRouter, createWebHashHistory } from 'vue-router';
// 匯入獨立元件
import Home from '../views/Home.vue';

// 路由表
const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
  },
  {
    path: '/newpage',
    name: '新增頁面',
    component: () => import('../views/NewPage.vue'),
  },
];

// router 結構
const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

// 匯出
export default router;
// src/App.vue
<template>
  <div id="nav">
    <router-link to="/">Home</router-link> |
    <router-link to="/about">About</router-link> |
    <!-- 動態屬性 -->
    <router-link :to="{ name: '新增頁面' }">新增頁面</router-link>
  </div>
  <router-view/>
</template>

<style lang="scss">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

#nav {
  padding: 30px;

  a {
    font-weight: bold;
    color: #2c3e50;

    &.router-link-exact-active {
      color: #42b983;
    }
  }
}
</style>

中場: 為目前專案加入樣式

  1. 在 public/index.html 加入 Bootstrap 5 CSS CDN
  2. 在 App.vue 檔案調整 router-link,加入 navbar 的樣式
    選擇相對簡單的樣式,直接複製貼到 App.vue 檔案
  3. 把 router-link 加進來,並調整 class 樣式
  4. router-view 外層加上 .container 樣式
// public/index.html
<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>
// src/App.vue
<template>
  <nav class="navbar navbar-expand-lg bg-body-tertiary">
    <div class="container-fluid">
      <a class="navbar-brand" href="#">Navbar</a>
      <button class="navbar-toggler" type="button"
        data-bs-toggle="collapse" data-bs-target="#navbarNav"
        aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
      </button>
      <div class="collapse navbar-collapse" id="navbarNav">
        <ul class="navbar-nav">
          <li class="nav-item">
            <router-link to="/" class="nav-link active" aria-current="page">Home</router-link>
          </li>
          <li class="nav-item">
            <router-link to="/about" class="nav-link">About</router-link>
          </li>
          <li class="nav-item">
            <router-link :to="{ name: '新增頁面' }" class="nav-link">新增頁面</router-link>
          </li>
        </ul>
      </div>
    </div>
  </nav>
  <div class="container">
    <router-view/>
  </div>
</template>

<style lang="scss">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

#nav {
  padding: 30px;

  a {
    font-weight: bold;
    color: #2c3e50;

    &.router-link-exact-active {
      color: #42b983;
    }
  }
}
</style>

巢狀路由

  1. App.vue CSS 樣式移除
  2. 建立兩個元件 – ComponentA、ComponentB
    建立的元件放在 views 資料夾
  3. 加入路由 – 在 router/index.js 檔案
    要加入子項目會在 newpage 的下方加入 children 陣列形式,然後加上物件內容
  4. 修改 NewPage.vue 檔案
  5. 補上左側的選單 – Bootstrap > List Group > Links and buttons
    複製程式碼貼到 NewPage.vue 選單的地方
    把連結改成<router-link> 標籤、加上路徑
// views/ComponentA.vue
<template>
  元件 A
</template>
// views/ComponentB.vue
<template>
  元件 B
</template>
// router/index.js
// 匯入 vue-router 資源
import { createRouter, createWebHashHistory } from 'vue-router';
// 匯入獨立元件
import Home from '../views/Home.vue';

// 路由表
const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
  },
  {
    path: '/newpage',
    name: '新增頁面',
    component: () => import('../views/NewPage.vue'),
    children: [
      {
        // 子項目的路徑不用加上/
        path: 'a',
        component: () => import('../views/ComponentA.vue'),
      },
      {
        path: 'b',
        component: () => import('../views/ComponentB.vue'),
      },
    ],
  },
];

// router 結構
const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

// 匯出
export default router;
// views/NewPage.vue
<template>
  <div class="row">
    <div class="col-4">
      <div class="list-group">
        <router-link to="/newpage/a" class="list-group-item list-group-item-action">
          元件 A
        </router-link>
        <router-link to="/newpage/b" class="list-group-item list-group-item-action">
          元件 B
        </router-link>
      </div>
    </div>
    <div class="col-8">
      <router-view></router-view>
    </div>
  </div>
</template>

一個元件插入多個視圖 – 具名視圖

  1. 新增元件 C
  2. 新增元件 NamedView
    加入兩個 router-view
  3. 打開 router/index.js 檔案
    路由表新增新的物件內容
  4. 在路由 namedView 新增 children 子路由
    在 children 陣列加入其他子路由項目(c2a、a2b)
    因為有兩個視圖(router-view),所以必須載入兩個元件
    components 有加上s,因為要載入多個元件,以物件的形式把它帶進來
  5. 把具名視圖(命名視圖)路徑加到左側的選單
// views/ComponentC.vue
<template>
  元件 C
</template>
// views/NamedView.vue
<template>
  <h2>具名視圖(命名視圖)的範例</h2>
  <div class="row">
    <div class="col-6">
      <router-view name="left"></router-view>
    </div>
    <div class="col-6">
      <router-view name="right"></router-view>
    </div>
  </div>
</template>
// router/index.js
// 匯入 vue-router 資源
import { createRouter, createWebHashHistory } from 'vue-router';
// 匯入獨立元件
import Home from '../views/Home.vue';

// 路由表
const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
  },
  {
    path: '/newpage',
    name: '新增頁面',
    component: () => import('../views/NewPage.vue'),
    children: [
      {
        // 子項目的路徑不用加上/
        path: 'a',
        component: () => import('../views/ComponentA.vue'),
      },
      {
        path: 'b',
        component: () => import('../views/ComponentB.vue'),
      },
      // 具名視圖(命名視圖)
      {
        path: 'namedview',
        component: () => import('../views/NamedView.vue'),
        children: [
          {
            path: 'c2a',
            // 載入多個元件
            components: {
              // 命名視圖使用名稱就是物件所使用的名稱
              left: () => import('../views/ComponentC.vue'),
              right: () => import('../views/ComponentA.vue'),
            },
          },
          {
            path: 'a2b',
            components: {
              left: () => import('../views/ComponentA.vue'),
              right: () => import('../views/ComponentB.vue'),
            },
          },
        ],
      },
    ],
  },
];

// router 結構
const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

// 匯出
export default router;
// views/NewPage.vue
<template>
  <div class="row">
    <div class="col-4">
      <div class="list-group">
        <router-link to="/newpage/a" class="list-group-item list-group-item-action">
          元件 A
        </router-link>
        <router-link to="/newpage/b" class="list-group-item list-group-item-action">
          元件 B
        </router-link>
        <router-link to="/newpage/namedview/c2a" class="list-group-item list-group-item-action">
          命名路由 c2a
        </router-link>
        <router-link to="/newpage/namedview/a2b" class="list-group-item list-group-item-action">
          命名路由 a2b
        </router-link>
      </div>
    </div>
    <div class="col-8">
      <router-view></router-view>
    </div>
  </div>
</template>

透過參數決定路由內容 – 動態路由

  1. 安裝 axios 套件 – npm install axios
  2. 新增 DynamicRouter 元件 – views/DynamicRouter.vue
  3. 重新運行 npm run serve
  4. DynamicRouter 檔案主要會用到 <script> 的部分
    取得遠端資料,以 RANDOM USER 為範例
    複製 api: https://randomuser.me/api/
  5. 載入 axios 套件
    在 created 生命週期把遠端資料載進來
  6. DynamicRouter 元件加到 router 路由表裡面
  7. 確認有沒有取得遠端資料
    http://localhost:8080/#/newpage/dynamicrouter
  8. 觀察 info > seed、資料用戶
    複製 data > info > seed 的資料
    修改 DynamicRouter.vue 程式碼
    透過 id 的形式取得相同一個資料
  9. id 透過網址來進行傳遞
    http://localhost:8080/#/newpage/dynamicrouter/b9039789d7d5a209
    修改 router/index.js 程式碼
    path: ‘dynamicRouter/:id’,
  10. 動態值取出來直接使用
    console.log(this.$route.params.id)
// views/DynamicRouter.vue
<template>
  <div>
  </div>
</template>

<script>
// 載入 axios 套件
import axios from 'axios';

export default {
  created() {
    // b9039789d7d5a209
    console.log(this.$route.params.id);
    const seed = this.$route.params.id;
    axios.get(`https://randomuser.me/api/?seed=${seed}`)
      .then((res) => {
        console.log(res);
      });
  },
};
</script>
// router/index.js
// 匯入 vue-router 資源
import { createRouter, createWebHashHistory } from 'vue-router';
// 匯入獨立元件
import Home from '../views/Home.vue';

// 路由表
const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
  },
  {
    path: '/newpage',
    name: '新增頁面',
    component: () => import('../views/NewPage.vue'),
    children: [
      {
        // 子項目的路徑不用加上/
        path: 'a',
        component: () => import('../views/ComponentA.vue'),
      },
      {
        path: 'b',
        component: () => import('../views/ComponentB.vue'),
      },
      // 參數 - 動態路由
      {
        path: 'dynamicrouter/:id',
        component: () => import('../views/DynamicRouter.vue'),
      },
      // 具名視圖(命名視圖)
      {
        path: 'namedview',
        component: () => import('../views/NamedView.vue'),
        children: [
          {
            path: 'c2a',
            // 載入多個元件
            components: {
              // 命名視圖使用名稱就是物件所使用的名稱
              left: () => import('../views/ComponentC.vue'),
              right: () => import('../views/ComponentA.vue'),
            },
          },
          {
            path: 'a2b',
            components: {
              left: () => import('../views/ComponentA.vue'),
              right: () => import('../views/ComponentB.vue'),
            },
          },
        ],
      },
    ],
  },
];

// router 結構
const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

// 匯出
export default router;

Compile-Time Flags / vue-cli

// vue.config.js
module.exports = {
  chainWebpack: (config) => {
    config.plugin('define').tap((definitions) => {
      Object.assign(definitions[0], {
        __VUE_OPTIONS_API__: 'true',
        __VUE_PROD_DEVTOOLS__: 'false',
        __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false'
      })
      return definitions
    })
  }
}

動態路由搭配 Props

Random User 網站

  1. 打開 DynamicRouter.vue 檔案
    直接另存新檔 DynamicRouterByProps.vue
  2. 開啟 router/index.js 檔案
    複製參數 – 動態路由的物件,並把對應的檔案調整
  3. 在網址列貼上新的路徑
    http://localhost:8080/#/newpage/dynamicrouterbyprops/b9039789d7d5a209
  4. 在 router/index.js 檔案
    在動態路由搭配 Props 物件裡面新增一個屬性 props
    加上 id: seed 字串
  5. 在 DynamicRouterByProps.vue 檔案
    做程式碼修改
    新增 props 屬性,props 所取得的是 router/index.js 路由表所定義的 props 的 id
    console 改成直接取得 props 的 id 內容
    console.log(‘props’, this.id);
  6. 在 router/index.js 檔案
    id 要改成使用動態路由的方式帶過來
    調整程式碼 props 的地方,改成大括號直接 return 結果
// 4. 在 router/index.js 檔案
props: () => ({
  // id: seed 字串
  id: 'b9039789d7d5a209',
}),
// 6. 在 router/index.js 檔案
props: (route) => {
  console.log('route:', route);
  return {
    id: route.params.id,
  };
},
// views/DynamicRouterByProps.vue
<template>
  <div>
  </div>
</template>

<script>
// 載入 axios 套件
import axios from 'axios';

export default {
  props: ['id'],
  created() {
    // b9039789d7d5a209
    console.log('props', this.id);
    const seed = this.$route.params.id;
    axios.get(`https://randomuser.me/api/?seed=${seed}`)
      .then((res) => {
        console.log(res);
      });
  },
};
</script>
// router/index.js
// 匯入 vue-router 資源
import { createRouter, createWebHashHistory } from 'vue-router';
// 匯入獨立元件
import Home from '../views/Home.vue';

// 路由表
const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
  },
  {
    path: '/newpage',
    name: '新增頁面',
    component: () => import('../views/NewPage.vue'),
    children: [
      {
        // 子項目的路徑不用加上/
        path: 'a',
        component: () => import('../views/ComponentA.vue'),
      },
      {
        path: 'b',
        component: () => import('../views/ComponentB.vue'),
      },
      // 參數 - 動態路由
      {
        path: 'dynamicrouter/:id',
        component: () => import('../views/DynamicRouter.vue'),
      },
      // 動態路由搭配 Props
      {
        path: 'dynamicrouterbyprops/:id',
        component: () => import('../views/DynamicRouterByProps.vue'),
        props: (route) => {
          console.log('route:', route);
          return {
            id: route.params.id,
          };
        },
      },
      // 具名視圖(命名視圖)
      {
        path: 'namedview',
        component: () => import('../views/NamedView.vue'),
        children: [
          {
            path: 'c2a',
            // 載入多個元件
            components: {
              // 命名視圖使用名稱就是物件所使用的名稱
              left: () => import('../views/ComponentC.vue'),
              right: () => import('../views/ComponentA.vue'),
            },
          },
          {
            path: 'a2b',
            components: {
              left: () => import('../views/ComponentA.vue'),
              right: () => import('../views/ComponentB.vue'),
            },
          },
        ],
      },
    ],
  },
];

// router 結構
const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

// 匯出
export default router;

路由方法介紹

使用 Options API 來操作路由

路由方法路由屬性

  1. 新增 RouterNavigation.vue 檔案、調整 router/index.js 檔案、調整 views/NewPage.vue 檔案
  2. getRoute 取得屬性以及方法
    • $route – 取得目前在這個路由下有哪些的資訊
      fullPath、params、
      http://localhost:8080/#/newpage/routerNavigation?search=123
    • $router – 這個路由下可以使用的方法
      常見方法切換頁面: push、replace
  3. push
  4. replace
  5. go
  6. addRoute
// 1. views/RouterNavigation.vue
<template>
  <button type="button" @click="getRoute">getRoute</button>

  <button type="button" @click="push">Push</button>
  <button type="button" @click="replace">Replace</button>
  <button type="button" @click="go">Go</button>
  <hr>
  <button type="button" @click="addRoute">新增路由</button>
</template>

<script>
export default {
  methods: {
    // 包含歷史紀錄
    push() {

    },
    // 沒有歷史紀錄
    replace() {

    },
    // 操作歷史紀錄
    go() {

    },
    // 取得常用參數
    getRoute() {
      // 取得路由的屬性
      // https://next.router.vuejs.org/zh/api/#routelocationnormalized
      // 範例: this.$route.fullPath (目前網址)
      // console.log(this.$route);

      // 呼叫路由的方法
      // 參考: https://next.router.vuejs.org/zh/api/#router-方法
      // 範例: this.$router.go(-1) (回到前一頁)
      // console.log(this.$router);
    },

    // 延伸介紹
    addRoute() {
    
    },
  },
};
</script>
// 1. router/index.js
// 匯入 vue-router 資源
import { createRouter, createWebHashHistory } from 'vue-router';
// 匯入獨立元件
import Home from '../views/Home.vue';

// 路由表
const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
  },
  {
    path: '/newpage',
    name: '新增頁面',
    component: () => import('../views/NewPage.vue'),
    children: [
      {
        // 子項目的路徑不用加上/
        path: 'a',
        component: () => import('../views/ComponentA.vue'),
      },
      {
        path: 'b',
        component: () => import('../views/ComponentB.vue'),
      },
      // 參數 - 動態路由
      {
        path: 'dynamicrouter/:id',
        component: () => import('../views/DynamicRouter.vue'),
      },
      // 動態路由搭配 Props
      {
        path: 'dynamicrouterbyprops/:id',
        component: () => import('../views/DynamicRouterByProps.vue'),
        props: (route) => {
          console.log('route:', route);
          return {
            id: route.params.id,
          };
        },
      },
      {
        path: 'routernavigation',
        component: () => import('../views/RouterNavigation.vue'),
      },
      // 具名視圖(命名視圖)
      {
        path: 'namedview',
        component: () => import('../views/NamedView.vue'),
        children: [
          {
            path: 'c2a',
            // 載入多個元件
            components: {
              // 命名視圖使用名稱就是物件所使用的名稱
              left: () => import('../views/ComponentC.vue'),
              right: () => import('../views/ComponentA.vue'),
            },
          },
          {
            path: 'a2b',
            components: {
              left: () => import('../views/ComponentA.vue'),
              right: () => import('../views/ComponentB.vue'),
            },
          },
        ],
      },
    ],
  },
];

// router 結構
const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

// 匯出
export default router;
// 1. views/NewPage.vue
<template>
  <div class="row">
    <div class="col-4">
      <div class="list-group">
        <router-link to="/newpage/a" class="list-group-item list-group-item-action">
          元件 A
        </router-link>
        <router-link to="/newpage/b" class="list-group-item list-group-item-action">
          元件 B
        </router-link>
        <router-link to="/newpage/namedview/c2a" class="list-group-item list-group-item-action">
          命名路由 c2a
        </router-link>
        <router-link to="/newpage/namedview/a2b" class="list-group-item list-group-item-action">
          命名路由 a2b
        </router-link>
        <router-link to="/newpage/dynamicrouter/b9039789d7d5a209"
        class="list-group-item list-group-item-action">
          動態路由($route)
        </router-link>
        <router-link to="/newpage/dynamicrouterbyprops/b9039789d7d5a209"
        class="list-group-item list-group-item-action">
          動態路由(props)
        </router-link>
        <router-link to="/newpage/routerNavigation" class="list-group-item list-group-item-action">
          路由導覽
        </router-link>
      </div>
    </div>
    <div class="col-8">
      <router-view></router-view>
    </div>
  </div>
</template>
// views/RouterNavigation.vue
<template>
  <button type="button" @click="getRoute">getRoute</button>

  <button type="button" @click="push">Push</button>
  <button type="button" @click="replace">Replace</button>
  <button type="button" @click="go">Go</button>
  <hr>
  <button type="button" @click="addRoute">新增路由</button>
</template>

<script>
export default {
  methods: {
    // 包含歷史紀錄
    push() {
      // 方法一: 直接帶入網址
      // this.$router.push('/newpage/dynamicrouter/b9039789d7d5a209');
      // 方法二: 使用 name 的方式帶入值
      this.$router.push({
        name: 'About',
      });
    },
    // 沒有歷史紀錄
    replace() {
      this.$router.replace({
        name: 'About',
      });
    },
    // 操作歷史紀錄
    go() {
      // 正整數 - 下一頁
      // 負整數 - 上一頁
      this.$router.go(-1);
    },
    // 取得常用參數
    getRoute() {
      // 取得路由的屬性
      // https://next.router.vuejs.org/zh/api/#routelocationnormalized
      // 範例: this.$route.fullPath (目前網址)
      // console.log(this.$route);

      // 呼叫路由的方法
      // 參考: https://next.router.vuejs.org/zh/api/#router-方法
      // 範例: this.$router.go(-1) (回到前一頁)
      console.log(this.$router);
    },
    // 延伸介紹
    addRoute() {
      this.$router.addRoute({
        path: '/newabout',
        name: 'newAbout',
        component: () => import('./About.vue'),
      });
    },
  },
};
</script>

預設路徑以及重新導向

重新導向說明

  1. 新增 NotFound.vue 檔案
  2. 在 router/index.js 檔案
    製作404頁面以及重新導向
    404頁面提供一個頁面資訊給用戶了解到他現在進入到錯誤的路由,可以如何回到正確的路由下
    重新導向直接把用戶導到一個正確的頁面
// views/NotFound.vue
<template>
  <h1>404</h1>
  <p>這頁找不到囉</p>
</template>
// router/index.js
// 匯入 vue-router 資源
import { createRouter, createWebHashHistory } from 'vue-router';
// 匯入獨立元件
import Home from '../views/Home.vue';

// 路由表
const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
  },
  {
    path: '/newpage',
    name: '新增頁面',
    component: () => import('../views/NewPage.vue'),
    children: [
      {
        // 子項目的路徑不用加上/
        path: 'a',
        component: () => import('../views/ComponentA.vue'),
      },
      {
        path: 'b',
        component: () => import('../views/ComponentB.vue'),
      },
      // 參數 - 動態路由
      {
        path: 'dynamicrouter/:id',
        component: () => import('../views/DynamicRouter.vue'),
      },
      // 動態路由搭配 Props
      {
        path: 'dynamicrouterbyprops/:id',
        component: () => import('../views/DynamicRouterByProps.vue'),
        props: (route) => {
          console.log('route:', route);
          return {
            id: route.params.id,
          };
        },
      },
      {
        path: 'routernavigation',
        component: () => import('../views/RouterNavigation.vue'),
      },
      // 具名視圖(命名視圖)
      {
        path: 'namedview',
        component: () => import('../views/NamedView.vue'),
        children: [
          {
            path: 'c2a',
            // 載入多個元件
            components: {
              // 命名視圖使用名稱就是物件所使用的名稱
              left: () => import('../views/ComponentC.vue'),
              right: () => import('../views/ComponentA.vue'),
            },
          },
          {
            path: 'a2b',
            components: {
              left: () => import('../views/ComponentA.vue'),
              right: () => import('../views/ComponentB.vue'),
            },
          },
        ],
      },
    ],
  },
  // 404 頁面
  {
    path: '/:pathMatch(.*)*',
    component: () => import('../views/NotFound.vue'),
  },
  // 重新導向
  {
    path: '/newPage/:pathMatch(.*)*',
    redirect: {
      name: 'Home',
    },
  },
];

// router 結構
const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

// 匯出
export default router;

路由設定選項

路由選項

  1. 在 App.vue 檔案程式碼調整
    調整 Navbar固定在最上方、調整網頁的高度、在最下方增加一個 router-link
  2. 查看 Vue Router 文件
    VueRouter > RouterOptions
  3. linkActiveClass
  4. scrollBehavior
// App.vue
<template>
  <nav class="navbar navbar-expand-lg bg-body-tertiary fixed-top">
    <div class="container-fluid">
      <a class="navbar-brand" href="#">Navbar</a>
      <button class="navbar-toggler" type="button"
        data-bs-toggle="collapse" data-bs-target="#navbarNav"
        aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
      </button>
      <div class="collapse navbar-collapse" id="navbarNav">
        <ul class="navbar-nav">
          <li class="nav-item">
            <router-link to="/" class="nav-link active" aria-current="page">Home</router-link>
          </li>
          <li class="nav-item">
            <router-link to="/about" class="nav-link">About</router-link>
          </li>
          <li class="nav-item">
            <router-link :to="{ name: '新增頁面' }" class="nav-link">新增頁面</router-link>
          </li>
        </ul>
      </div>
    </div>
  </nav>
  <div class="container" style="height: 300vh">
    <router-view/>
  </div>
  <router-link to="/newpage/routernavigation">/newpage/routernavigation</router-link>
</template>

<style lang="scss">
body {
  padding-top: 80px;
}
</style>
// router/index.js
// 匯入 vue-router 資源
import { createRouter, createWebHashHistory } from 'vue-router';
// 匯入獨立元件
import Home from '../views/Home.vue';

// 路由表
const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
  },
  {
    path: '/newpage',
    name: '新增頁面',
    component: () => import('../views/NewPage.vue'),
    children: [
      {
        // 子項目的路徑不用加上/
        path: 'a',
        component: () => import('../views/ComponentA.vue'),
      },
      {
        path: 'b',
        component: () => import('../views/ComponentB.vue'),
      },
      // 參數 - 動態路由
      {
        path: 'dynamicrouter/:id',
        component: () => import('../views/DynamicRouter.vue'),
      },
      // 動態路由搭配 Props
      {
        path: 'dynamicrouterbyprops/:id',
        component: () => import('../views/DynamicRouterByProps.vue'),
        props: (route) => {
          console.log('route:', route);
          return {
            id: route.params.id,
          };
        },
      },
      {
        path: 'routernavigation',
        component: () => import('../views/RouterNavigation.vue'),
      },
      // 具名視圖(命名視圖)
      {
        path: 'namedview',
        component: () => import('../views/NamedView.vue'),
        children: [
          {
            path: 'c2a',
            // 載入多個元件
            components: {
              // 命名視圖使用名稱就是物件所使用的名稱
              left: () => import('../views/ComponentC.vue'),
              right: () => import('../views/ComponentA.vue'),
            },
          },
          {
            path: 'a2b',
            components: {
              left: () => import('../views/ComponentA.vue'),
              right: () => import('../views/ComponentB.vue'),
            },
          },
        ],
      },
    ],
  },
  // 404 頁面
  {
    path: '/:pathMatch(.*)*',
    component: () => import('../views/NotFound.vue'),
  },
  // 重新導向
  {
    path: '/newPage/:pathMatch(.*)*',
    redirect: {
      name: 'Home',
    },
  },
];

// router 結構
const router = createRouter({
  history: createWebHashHistory(),
  routes,
  linkActiveClass: 'active',
  scrollBehavior(to, from, savedPosition) {
    console.log(to, from, savedPosition);
    // `to` and `from` are both route locations
    // `savedPosition` can be null if there isn't one
    if (to.fullPath.match('newpage')) {
      return {
        top: 0,
      };
    }
    return {};
  },
});

// 匯出
export default router;