### GDSC NYUST x 資訊創客社
<br>
### 網頁前端開發讀書會
#### Vue3 進階課程
<br>
#### 2023/12/11 ( Mon ) 19:00 - 21:00
#### 講師:楊鈞元 Charles
#### 本次課程影片:(⚒️製作中)
<img src="" height="200px">
---
## 前言
----
建置環境請參考上次的
[🔗Vue3 基礎課程](https://hackmd.io/@GDSC-NYUST/SkiZJXPZ6/%2Fu_Ujxb_eRii_XEKV0DetHg)
---
### 元件進階概念 - 如何客製化元件
----
#### 認識元件 ( Components )

----

----
Table 範例 (未拆出子元件)
```html=
<script setup>
const persons = [
{ id: 1, name: '張三', age: 25, city: '台北' },
{ id: 2, name: '李四', age: 30, city: '新竹' },
{ id: 3, name: '王五', age: 28, city: '台中' },
{ id: 4, name: '陳六', age: 35, city: '高雄' },
{ id: 5, name: '林七', age: 22, city: '台南' },
{ id: 6, name: '吳八', age: 32, city: '桃園' },
{ id: 7, name: '張九', age: 27, city: '新北' },
{ id: 8, name: '劉十', age: 29, city: '基隆' },
{ id: 9, name: '林十一', age: 31, city: '苗栗' },
{ id: 10, name: '黃十二', age: 26, city: '嘉義' },
];
</script>
<template>
<div style="border: 3px solid black">
<h1>這是一個表格</h1>
<div class="custom-table">
<h2>這是表格標題</h2>
<table>
<thead>
<tr>
<th>編號</th>
<th>姓名</th>
<th>年齡</th>
<th>城市</th>
</tr>
</thead>
<tbody>
<tr v-for="person in persons" :key="person.id">
<td>{{ person.id }}</td>
<td>{{ person.name }}</td>
<td>{{ person.age }}</td>
<td>{{ person.city }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<style scoped>
table {
border-collapse: collapse;
width: 100%;
}
th,
td {
border: 1px solid #ddd;
padding: 8px;
}
th {
padding-top: 12px;
padding-bottom: 12px;
text-align: left;
background-color: #4caf50;
color: white;
}
.custom-table {
background-color: beige;
}
</style>
```
----
Table 範例 (拆成子元件)
- App.vue (父層)
```html=
<script setup>
import customTable from './components/DataTable.vue';
</script>
<template>
<div style="border: 3px solid black">
<h1>這是一個表格</h1>
<customTable />
</div>
</template>
```
----
Table 範例 (拆成子元件)
- src/components/DataTable.vue (子層)
```html=
<script setup>
const persons = [
{ id: 1, name: '張三', age: 25, city: '台北' },
{ id: 2, name: '李四', age: 30, city: '新竹' },
{ id: 3, name: '王五', age: 28, city: '台中' },
{ id: 4, name: '陳六', age: 35, city: '高雄' },
{ id: 5, name: '林七', age: 22, city: '台南' },
{ id: 6, name: '吳八', age: 32, city: '桃園' },
{ id: 7, name: '張九', age: 27, city: '新北' },
{ id: 8, name: '劉十', age: 29, city: '基隆' },
{ id: 9, name: '林十一', age: 31, city: '苗栗' },
{ id: 10, name: '黃十二', age: 26, city: '嘉義' },
];
</script>
<template>
<div class="custom-table">
<h2>這是表格標題</h2>
<table>
<thead>
<tr>
<th>編號</th>
<th>姓名</th>
<th>年齡</th>
<th>城市</th>
</tr>
</thead>
<tbody>
<tr v-for="person in persons" :key="person.id">
<td>{{ person.id }}</td>
<td>{{ person.name }}</td>
<td>{{ person.age }}</td>
<td>{{ person.city }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<style scoped>
table {
border-collapse: collapse;
width: 100%;
}
th,
td {
border: 1px solid #ddd;
padding: 8px;
}
th {
padding-top: 12px;
padding-bottom: 12px;
text-align: left;
background-color: #4caf50;
color: white;
}
.custom-table {
background-color: beige;
}
</style>
```
----
<img src="https://hackmd.io/_uploads/SJ_rsUMIa.png" style="height:300px" />
- Child 對 Parnet 發出事件
- Parent 對 Child 傳遞屬性
----
複習
- v-bind -> :prop
- v-on -> @evnet
<br>
| v-bind | v-on |
| -------- | -------- |
|  |  |
----
#### 如何溝通父子元件?
[🔗 官方文檔](https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits)
[🔗 完整 API 清單](https://vuejs.org/api/index.html)
----
<ul style="font-size:39px">
<li>defineProps:子元件提供父元件傳入 properties</li>
<li>defineEmits:子元件提供父元件存取 event</li>
<li>defineExpose:子元件提供父元件存取 properties</li>
<li>defineModel:讓父子元件 properties 成為雙向綁定</li>
</ul>
<br>(更多API請參考官方文檔)
----
#### defineProps & defineEmit
[🔗 展示Playgound](https://stackblitz.com/edit/vue3-vite-starter-pwpeb8?file=src%2Fcomponents%2FCustomBtn.vue)
----
#### defineProps & defineEmit
- App.vue
```html=
<script setup>
import DemoButton from '@/components/CustomBtn.vue'
import { ref } from 'vue'
// - DemoButton
const count = ref(0)
const stock = ref(100)
const title = ref('按鈕被按了:0次')
const clickBtn = () => {
count.value++
title.value = `按鈕被按了:${count.value}次`
}
</script>
<template>
<DemoButton :title="title" btnLabel="點我" @change="clickBtn()" />
<DemoButton title="點擊領取庫存商品" btnLabel="點我領取商品" @change="stock--" />
<span>庫存:{{ stock }}</span>
</template>
```
----
#### defineProps & defineEmit
- src/components/CustomBtn.vue
```html=
<script setup>
const props = defineProps({
title: {
type: String,
},
btnLabel: {
type: String,
default: '預設按鈕文字',
},
})
const emit = defineEmits(['change'])
</script>
<template>
<h1>{{ title }}</h1>
<div class="card">
<button @click="emit('change')">{{ btnLabel }}</button>
</div>
</template>
<style>
h1 {
font-size: 20px;
margin: 0;
}
</style>
```
----
#### defineExpose
[🔗 展示 Playground](https://stackblitz.com/edit/vue3-vite-starter-7g9wv2?file=src%2Fcomponents%2FCustomChild.vue,src%2FApp.vue)
----
#### defineExpose
- App.vue
```html=
<script setup>
import { ref, onMounted } from 'vue'
import Child from '@/components/CustomChild.vue'
const childRef = ref(null)
const result = ref()
const getResult = () => {
result.value = childRef.value.result
}
</script>
<template>
<Child ref="childRef"></Child>
<div>
<button @click="getResult">取得結果</button>
<div>
<span>得到的結果:{{ result }}</span>
</div>
</div>
</template>
```
----
#### defineExpose
- src/components/CustomChild.vue
```html=
<script setup>
import { ref, computed } from 'vue'
const inputValue = ref('')
// - 計算費氏數列
const result = computed(() => {
const num = Number(inputValue.value)
if (Number.isNaN(num)) return '請輸入數字'
if (num < 0) return '請輸入大於 0 的數字'
if (num === 0) return 0
if (num === 1) return 1
let a = 0
let b = 1
let c = 0
for (let i = 2; i <= num; i++) {
c = a + b
a = b
b = c
}
return c
})
defineExpose({
result,
})
</script>
<template>
<label>計算費氏數列:</label>
<input v-model="inputValue" type="text" placeholder="請輸入數字" />
</template>
```
----
#### defineModel
[🔗 使用 defineModel 前 - 展示Playgound](https://stackblitz.com/edit/vue3-vite-starter-s7pvxg?file=src%2FApp.vue,src%2Fcomponents%2FCustomInput.vue)
[🔗 自定義 ModelValue - 官方文檔](https://vuejs.org/guide/components/v-model.html#component-v-model)
----
#### 使用 defineModel 後
- App.vue
```html=
<script setup>
import DemoInput from '@/components/CustomInput.vue'
import { ref } from 'vue'
// - DemoInput
const message = ref('可以在這裡預填內容')
</script>
<template>
<div style="margin-top: 24px">
<DemoInput v-model="message" label="第1個問題" />
<DemoInput v-model="message" label="第2個問題" />
<DemoInput v-model="message" label="第3個問題" />
<DemoInput v-model="message" label="第4個問題" />
<DemoInput v-for="index in 5" :key="index" :label="`第${index + 4}個問題`" />
</div>
</template>
```
----
#### 使用 defineModel 後
- src/components/CustomInput.vue
```html=
<script setup>
const inputValue = defineModel()
const props = defineProps({
label: String,
})
</script>
<template>
<div>
<label>{{ label }}</label>
<input v-model="inputValue" placeholder="輸入內容..." />
</div>
</template>
<style>
input {
padding: 10px;
margin: 5px;
border: 2px solid #ccc;
border-radius: 4px;
font-size: 16px;
}
</style>
```
----
如果出現 defineModel() is not defined
請到 vite.config.js 設定
```javascript=
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue({
script: {
defineModel: true,
},
}),
],
});
```
----
#### 延伸閱讀 - Fallthrough Attributes (傳遞屬性)
[🔗 官方文檔](https://vuejs.org/guide/components/attrs.html#accessing-fallthrough-attributes-in-javascript)
----
### Slot (槽位)
[🔗 官方文檔](https://vuejs.org/guide/components/slots.html)
----

----

[🔗 Playground](https://play.vuejs.org/#eNp9U8Fu2zAM/RVCPeTS2isKBEPmBWiHAtswbMO2oy+qTdvqZEmQ6CxdkX8fLceJna65We898pHP0rO4dS7ZdChWIguFV44gIHVunRvVOusJ7mTAL/LJdgSVty0skvQI9aWL3GTpUMtVfCBsnZaEfALIjuJ4ZmTk4aJBWaLf48w01+uP6BFaVTcEDwgSnKwRSJHGLGV63yKdeLzoWmIlOz3aMeXWt9zHy9pL10BlPVDDJlIZKKwhNJRkab/yUW9KkMayzIM1OKHPW1fW0nQhF/dZBAi2xWgmCwJlKvtKx/44S2zCiksxT77/afPaUm2g0DKE97mIbsqgz8XodJp30JbAyBZZPnCs5Z/J8GG4WU3WhzavP9VPFNlpHFO/gXvpN6nJUt5nnkG8YIGe9LDwIIbnofjBeh71iqxbwbXbcuhalXBRFMW7QVBYbf0KLpbL5R6pOKSroP7iCt4kb7GN8C7e6GjCmVPgJCtVJ4/BGo48mvXxtk5p9N8cKWtCLlbjGLmQWts/nyNGvsPLES8aLH7/B38M2x7LxXePAf0Gc3HgSPoaaaDvf37FLX8fyNaWnWb1GfIHcgxdP+Mgu+tMyWNPdHHaT/G1K1P/CvdbfhJhXKofNIYS9bnge/fhzOrHcW+Sm32YO7H7B0y5bRw=)
----
[🔗 應用 slot 寫 table 範例](https://play.vuejs.org/#eNqNVtlO20AU/ZWp+5CXLEC6pmmkgnhoVbVVl6e6D248EINjW/aEglCktoFWlKWoG1spRKpEKIIidVEIlK+JE/zUX+gdOx47xklBQrLvuWfuuXfOjDPJ3dC0+FgBcykubWR1SSPIwKSgZXgFISmvqTpB/YKBbwsTaoGgIV3No0g84YUoOXKNV2h+VlUMgkSBCOg6ekwjCE0iSUyh3ihShDxOoYh59KtenYlEkTAMr30XoygrkQkKvN0355YiqBj1E/sYsbG+YK6tucRkDyM2Pu03dw6CxCQjNhdm67UPrOIVf8V6dTdIvMCI1soPc3qXVfSkWjvL1tpUkAi4K/XLUr1aYhWhB1+P86d6vOQNZxEq7rCKHrFRLpmf3wWJl9umerDOKgLgDSdkqjADl/hmxpx/wYhXPakbNWv1VZAIuNcjEOvV50wt7HGLezK71Ng8VbQXtsyd7GGJkmtzrDDMwC28PNM83nbJT8Ba6YTjTPAkvBCc12SBYNuh6XOxGDKnK1apYm0unlR2/x7NNae3zN1Fc7pUP/wNEXNvtbk6ZZW/N7/WUCzm0HyWTtjLIoC6/DkpPpq9DERcNeh8DgtiK0rjOnumbzk4G7KhCcp1nuvt4bnMyetvVNjx9km50tg8alRWrPJcOkFy3hIJtgY8+ptuK/tUFSc6lhUzkghkT5cTNLfem4vzYcDBT+tPOQzYWDCrL9sAnz67MBqLDak69CeBOCQp9j3Acyg1iida0bgkQuvtK0+CORwIFYshlV2cOqd7BhipewI1WDDjjFMeUlV3y0Om7NvcPv/egu9MMOPewRlLpn33KgTaUC7KtV+69Mam7h/ISbI4oMJVrWDFRhyXBw6KKI25RQ1ZJfZJBL3Utb49OeXbjHOsoJNTRnUO3+C4AGXgCD8yJGUYCUjTVY1uP8lh+i+I7qFrrUlJN0QRqQDqrQz4cBAQjyDSUh8yKirbfXEXIdT+HehtfdK8Ln2Kmd6enhDr9AWiQdMH0pOhi1zovEiHrqjdztIVzevWVcCYgb00ax/h3vyfQ5nAdMLxkM+WYEpigM4haTg+YqgKeHKSpvJcFgwpyVi/qxEJfhXwXArZCMUEWVaf3bJjRC/g1kcCODmcHQ2JjxjjNMZz93RsYH0M8xzDiKAPY5gBhQcf3MHj8MzAvCoWZMjuAt7HhioXqEYnrb+giCDbl2ervWn/EgKDPzQGx2FXDLcpKpRmOl8rnoPjR89ip9Y9ucl40ubxSpEr/gMNDTFL)
---
### Vue Router (路由)
----
#### 介紹 Vue Router
Vue Router 是 Vue.js 官方提供的路由管理工具。
讓前端模擬路由實現單頁式應用
(SPA, Single Page Application),
切換畫面時不需要向後端發出請求
[🔗 深入了解 Vue Router 與前後端路由](https://book.vue.tw/CH4/4-1-vue-router-intro.html)
----
#### 安裝 Vue Router
```script=
yarn add vue-router@4
```
更新 main.js
```javascript=
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import route from '/src/route';
const app = createApp(App)
app.use(route);
app.mount('#app')
```
----
#### 配置設定檔 (src/route/index.js)
```javascript=
import { createRouter, createWebHistory } from 'vue-router';
const routes = [
{
path: '/',
name: 'MainLayout',
component: () => import('/src/layouts/MainLayout.vue'),
children: [
{
path: 'home',
name: 'Home',
component: () => import('/src/pages/HomePage.vue'),
},
],
},
];
const router = createRouter({
history: createWebHistory(),
// history: createWebHashHistory(),
routes,
});
export default router;
```
----
- 路由連結 (router-link)
[🔗 Playground](https://stackblitz.com/edit/vitejs-vite-smaqdz?file=src%2Froute%2Findex.js)
----
- 程式化導航 (router.push, router.replace)
```
// 將 router.push 換成 router.replace,用法一樣
// 差在不會在瀏覽器留下歷史紀錄
// 透過字串指定 URL
router.push('/users/eduardo')
// 透過物件與 path 指定
router.push({ path: '/users/eduardo' })
// 透過物件與 name 指定
router.push({ name: 'Home' })
// 與具名路由、params 搭配
router.push({ name: 'user', params: { username: 'eduardo' } })
// 以 query 指定目標的 query string
router.push({ path: '/register', query: { plan: 'private' } })
```
----
#### 導航防護 (Navigation Guards)
[🔗 延伸閱讀](https://book.vue.tw/CH4/4-4-navigation-guards.html)
---
### Vite
[🔗 官方文檔](https://vitejs.dev/)
----
#### 簡介
[🔗 什麼是 Vite](https://www.explainthis.io/zh-hant/swe/what-is-vite)
<div style="text-align:left; font-size:38px">
Vite 同樣是 Vue 的作者,尤雨溪 (Evan You)建立的專案,
是一個 build tool,做為開發伺服器,核心是用來建立開箱即用的 Web 應用程式,且不僅 Vue 能使用,幾乎所有前端JS框架都採用了 Vite 作為標準的 build tool
</div>
----
對新手來說,我們只需要配置好 vite.config.js
執行 `yarn dev` (或其他 package manager)
就能瞬間得到一個具有 HMR(熱重載) 的本地伺服器

----
#### 配置 vite.config.js
[🔗 官方文檔](https://vitejs.dev/config/)
```javascript=
// - 標準內容
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
})
```
----
透過設定 alias,還能讓資料夾指定更方便
```javascript=
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
})
```
由於所有的檔案、頁面都會放在 /src 中存取
這樣設定後,原先要透過相對路徑或是 /src/...
就能改以 @ 取代 src,在檢視上會更加明確
----
當你的後端 API 與當前環境不同 domain
設定 Proxy 是個方便的做法
```javascript=
export default defineConfig({
server: {
proxy: {
// 字串寫法
'/foo': 'http://localhost:4567',
// 選項寫法
'/api': {
target: 'http://jsonplaceholder.typicode.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
},
// 正則寫法
'^/fallback/.*': {
target: 'http://jsonplaceholder.typicode.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/fallback/, '')
},
}
}
})
```
----
#### 更多設定參數
[🔗 參考懶人包 - 1](https://ithelp.ithome.com.tw/articles/10270465?sc=hot)
[🔗 參考懶人包 - 2](https://hackmd.io/@Jui-Cheng/Hk9fJ5bKi)
---
### Axios
[🔗 官方文檔](https://axios-http.com/docs/example)
----
#### 簡介
<div style="text-align:left; font-size:38px">
axios 是一個以 promise 為基礎的 HTTP 請求工具,可以用來傳送 HTTP 要求和接收 HTTP 回應,可以用在瀏覽器和 node.js 環境中,在瀏覽器環境中使用 XMLHttpRequest。
</div>
----
JS 前後端交互 API 演進
<div style="text-align:center; font-size:42px">
XMLHttpRequest => JQuery ajax => fetch => Axios
</div>
[🔗 關於AJAX與那些前端的request方法](https://medium.com/unitsexhibition/%E9%97%9C%E6%96%BCajax%E8%88%87%E9%82%A3%E4%BA%9B%E5%89%8D%E7%AB%AF%E7%9A%84request%E6%96%B9%E6%B3%95-720a7c9cd220)
----
#### 現代前端選擇 Axios 的理由
- 從瀏覽器創建XMLHttpRequests
- 從node.js 創建 http請求
- 支持 Promise API
- 符合最新的ES規範
- 攔截請求和響應
- 轉換請求數據和響應數據
- 支援取消請求
- 自動轉換JSON數據
- 客戶端支持防禦XSRF攻擊
----
#### 安裝 Axios
```script=
yarn add axios
```
----
#### 配置設定檔 (src/axios/index.js)
```javascript=
import axios from 'axios';
const api = axios.create({
// baseURL: ,
timeout: 5 * 1000,
headers: { 'Content-Type': 'application/json' },
// withCredentials: true, // 跨域請求發送Cookie
});
export default api;
```
----
到 main.js 引入
```javascript=
// ...
import api from 'src/axios';
// ...
app.provide('api', api);
// ...
```
----
#### 到任意vue檔進行 HTTP request
- 同步操作範例
```javascript=
import { inject } from 'vue';
const api = inject('api');
const getData = () => {
try {
api.get('https://randomuser.me/api/');
console.log(res);
} catch (err) {
console.log(err);
}
};
```
----
#### 到任意vue檔進行 HTTP request
- 異步操作範例
```javascript=
import { inject } from 'vue';
const api = inject('api');
const getData = async () => {
try {
const res = await api.get('https://randomuser.me/api/');
console.log(res);
} catch (err) {
console.log(err);
}
};
```
---
### Pinia
[🔗 官方文檔](https://pinia.vuejs.org/)
----
#### 簡介
<div style="text-align:left; font-size:38px">
Pinia 是 Vue 的狀態管理(statement management)工具,基於 Vuex 進化改版,同時 Vuex 也已經不再更新,Pinia 是 Vue 官方目前唯一推薦使用的狀態管理工具
</div>
----
#### Pinia 的特點
- Devtool 支援
- 自動熱更新 (HMR)
- Plugin 支援
- 完整的 TypeScript 支援
- Server Side Rendering 支援
----
#### 安裝 Pinia
```script=
yarn add pinia
```
----
到 main.js 引入
```javascript=
// ...
import { createPinia } from 'pinia'
// ...
const pinia = createPinia()
// ...
app.use(pinia)
// ...
```
----
#### Demo
在 (src/stores) 建立 `名稱.js`
----
#### 建立 counter.js
```javascript=
import { defineStore } from "pinia";
export const useCounterStore = defineStore('counter', {
//定義狀態初始值
state: () => ({ count: 0, name: 'Eduardo' }),
//對狀態加工的 getters,如同 computed
getters: {
doubleCount: (state) => state.count * 2,
},
//定義使用到的函式,可以為同步和非同步,如同 method
actions: {
increment() {
this.count++
},
},
})
```
----
在需要使用的 vue 檔
```javascript=
<script setup>
import { useCounterStore } from "src/stores/counter.js";
const counterStore = useCounterStore();
</script>
<template>
<div>
<h2>counter</h2>
<!-- 取得的 state 會響應式更新 -->
<p>{{ counterStore.count }}</p>
<!-- 可以呼叫 store 內定義的方法來修改 state -->
<button @click="counterStore.increment">+</button>
<!-- 也可以直接修改 state -->
<button @click="counterStore.count++">+</button>
</div>
</template>
```
---
### Vue i18n
[🔗 官方文檔](https://vue-i18n.intlify.dev/)
[🔗 基礎範例](https://ithelp.ithome.com.tw/articles/10324500?sc=rss.iron)
----
#### 簡介
<div style="text-align:left; font-size:38px">
i18n 全名為 Internationalization,也就是國際化的意思,因為單字太長,所以中間的 18 個字母被縮寫為 18,再加上開頭和結尾的字母,就組成了 i18n。
JavaScript i18n API 可以幫助我們對網站進行多語言翻譯,讓它們可以輕鬆適應使用不同語言使用者的需求。
Vue i18n 顧名思義,就是為 Vue 專案實現 i18n,量身訂做的工具
</div>
----
- 安裝 Vue i18n Plugin 到 Vue
```script=
yarn add vue-i18n@9
```
<br>
*注意 Vue3 專案必須要用 v9.x 之後的版本,<br>
v8.x以前相容 Vue 2 專案
----
建立語言包 (src/i18n/language.json)
<div style="text-align:left; font-size:31px">
*可以自己決定分層,例如都放在同個json、依不同語言/功能分json,或者直接寫在 js,不建立json都可以
</div>
```json=
{
"zh-TW": {
"welcome": "這是中文範例"
},
"en-US": {
"welcome": "This is English example"
}
}
```
----
設定進入點 (src/i18n/index.js)
```json=
import { createI18n } from "vue-i18n";
import message from "./language.json";
const i18n = new createI18n({
locale: "zh-TW", // 設定默認語言為繁體中文
messages: message, // 使用引入的 language.json 中的消息作為語言資源。
fallbackLocale: "zh-TW", // 如果找不到匹配的語言翻譯,默認顯示繁體中文。
});
export { i18n };
```
----
到 main.js 引入
```javascript=
// ...
import { i18n } from "/src/i18n"; // 引入 I18n 套件
// ...
app.use(i18n)
// ...
```
----
#### Demo
```html=
<div>
<button type="button" @click="$i18n.locale = 'zh-TW'">中文</button>
<button type="button" @click="$i18n.locale = 'en-US'">英文</button>
<h2>{{ $t('welcome') }}</h2>
</div>
```
----
#### 方便管理 i18n 的 Extension

---
## 結語和問答
----
### 補充學習資源
----
### Q & A
----
#### 如何得到整套解決方案
下週課程<br>
Quasar Framework
Vue3 最強大的企業級跨平台框架
幫你集成管理所有你需要的擴充套件
幫你準備好大量精美又好用的 Components
不必再自己花一堆時間處理麻煩的 CSS 樣式
{"title":"Vue3 進階課程","description":"一個平易近人、高效能且多功能的漸進式JavaScript 框架用於建立面向於 Desktop, Mobile, WebGL, Terminal 應用程式的前端Vue.js 基於標準的 HTML、CSS 和 JavaScript因此最廣泛應用就是用在建立網頁","contributors":"[{\"id\":\"f8142aa2-66aa-4867-821d-2f1ffff7a7ba\",\"add\":23538,\"del\":4285}]"}