---
title: 第三堂:Vue 起步走,指令學起來
tags: 2022 Vue 作品實戰班, 六角學院
date: 20220121
image:
---
# 第三堂:Vue 起步走,指令學起來
# 最終作業主題
- 做自已有興趣的
- 浪浪動物
- 服飾
- 先收集素材
- 賣酒,你的產品有哪些
- 台灣啤酒,unsplash 可能沒有
- 換個方向,選擇國外高檔葡萄酒
- 撰寫文案
- 作品牆
- 健身廚房
- 麵包
- 感受到點麵包的愛
- 配色一致
- 誇張系
- 注意授權
- 小魔女諾貝塔雜貨工坊
- 使用免授權圖片
- 台中行李箱
- local 生活感
- api 限制
- NoSql
- 試著突破極限
- 延伸加入更多的欄位
- 同學自行發揮
- 求職網
- 先做電商主題
- 熟悉 api 可以做到什麼程度
- 亞洲媒體藝術節
- 預約功能
- 調整時間
- 新建了web server
- Nuxt
- middle ware
- 鼓勵玩自已想玩的東西
# Vue 起步走
## 物件傳參考
- 物件傳參考的特性:https://youtu.be/y1odVMpi6dU
## JavaScript 型別知識
- 原始型別:字串、布林、數字、undefined、null...
- 物件型別:物件、陣列、函式
> Javascript 有沒有"function" 型別
> 沒有
- 物件型別有傳參考的問題
## 物件參考特性
#### ex:one:
```javascript=
物件:function, array, object
var text = '一段文字';
var text2 = text;
text2 = '更新文字'; // 純值原始型別
console.log(text === text2);
```
- `false`
- text2 重新賦值後,和原來的值沒有關聯性
#### ex:two:
```javascript=
var a = {
name: 'a',
}
var b = {
name: 'a',
}
console.log(a === b);
```
- `false`
- 2個一模一樣的物件
- 宣告物件時,每一組 `{}` 都會產生新的記憶體空間
- 比對的時候,是比對記憶體位置
#### ex:three:
```javascript=
var a = {
name: 'a',
}
var b = a;
b.name = 'b';
console.log(a.name);
```
- b 指向 a 物件
- 把 b 中的 name 改成 b
- ` console.log(a.name);` = **b**
#### ex:four:
```javascript=
var a = {
name: 'a',
}
var b = a;
b.name = 'b';
console.log(a === b);
```
- true
- 只有宣告一個物件
- 2個變數都指向同一個物件
- 修改同一個記憶體空間的 nmae
#### ex:five:
```javascript=
var a = {
name: 'a',
}
function changeData(param) {
param.name = 'b'
return param;
}
var b = changeData(a);
console.log(a === b);
```
- 程式執行流程

---

---
- 實戰中可能會踩到的雷點
- true
- **ESLint**建議不要調整傳入的物件屬性,會改到原始值
- 透過參數傳遞,傳參考特性不會變,這幾個是一樣的
- 從頭到尾只有建立一次物件
:::success
所有的函式執行一定會 return,沒有加 return,也會 return undefined
:::
#### ex:six:
```javascript=
var a = {
name: 'a',
}
function changeData(param) {
return {
name: param.name
}
}
var b = changeData(a);
console.log(a === b);
```
- 把參數帶進物件,return 物件

- 實戰應用面
- fasle
- 有大括號就會建立新物件

- 這2個物件沒有關聯性
## 複製物件
### 淺層複製
```javascript=
var a = {
name: 'a',
}
function changeData(param) {
// 方法一:Object assign
// const newData = Object.assign( {}, param);
// console.log('Object.assign', newData); // 淺層複製
// 方法二:展開
const newData = { ...param };
console.log('Spread syntax', newData); // 淺層複製
return newData;
}
var b = changeData(a);
console.log(a === b);
```
#### `Object.assign()`
- 宣告一個新物件,把第2個參數的值展開過來
#### `const newData = { ...param };`
- 寫法比較簡潔
- 和 `Object.assign()` 的結果一樣
### 淺層拷貝觀念
- 展開背後做了什麼

- 第二層的物件並沒有複製,只有指向而已

- 實戰中很常遇到
- 記憶體空間不相同時,修改值才不會誤改
- 第一層物件,a,b 不相同


---
- 深層物件是指向的,並沒有複製
- 深層物件比對,得到相等的結果


---
- 修改會改到被參考的物件
:::success
陣列、函式都是物件
物件要有幾層,就有幾層
:::
### 深層複制
#### 原生 JS 寫法
```javascript=
var a = {
name: 'a',
}
function changeData(param) {
const newData = JSON.parse( JSON.stringify(param) );
console.log('stringify', newData); // 淺層複製
return newData;
}
var b = changeData(a);
console.log(a === b);
```
#### 超雷無限深層範例
```javascript=
const z = {
z: '123'
}
z.z = z;
console.log(z);
```
- `console.log()`

- 試著用記憶體指向的方式解釋無限層的原因,加深物件傳參考的觀念
### 深層拷貝觀念
#### 先轉成字串,再轉成物件
```javascript=
const newData = JSON.parse( JSON.stringify(param) );
```
* 下圖為淺層拷貝

- 字串轉成物件時,會宣告新的記憶體位置
- 為什麼會有淺層拷貝,因為淺層的 code 比較好寫
- 淺層、深層都會用到
- 如果沒有深層物件,用淺層拷貝就可以
### 物件製範例
#### ex0
```javascript=
<script>
const person1 = {
name: '小明',
}
const person2 = person1;
person2.name = '小美';
console.log(person1.name); // 值為 小明 or 小美
```
- 小美
#### ex1
```javascript=
const person1 = {
name: '小明',
}
const person2 = person1;
person2 = {
name: '小美',
}
console.log(person1.name); // 值為 小明 or 小美
```
- error
- 只要建立新物件,參考的位置就不一樣, const 就會出錯
---
```javascript=
const person1 = {
name: '小明',
};
const person2 = person1;
person2.name = '小美';
console.log(person1.name); // 值為 小明 or 小美
```
- 不會出錯
- 小美
- 參考值不變,可以使用 const
---
```javascript=
let person1 = {
name: '小明',
}
let person2 = person1;
person2 = {
name: '小美',
}
console.log(person1.name); // 值為 小明 or 小美
```
- 用 `let` 宣告,eslint 無法通過,值沒有重新指派,要用 `const`
#### ex2
```javascript=
const person1 = {
name: '小明',
}
function fn(item) {
item.name = '小美';
}
fn(person1);
console.log(person1.name); // 值為 小明 or 小美
```
- 和上一題一樣
- 透過參數改變值
- 小美
#### ex3(機車題)
```javascript=
const person1 = {
name: '小明',
}
function fn(item) {
item = {};
item.name = '小美';
}
fn(person1);
console.log(person1.name); // 值為 小明 or 小美
```
- 小明
- 從頭到尾只有1個 person1,先把參數傳進來,再把參數指向新物件,再把物件裡面的 name 換掉
- 指向 `{}`,就和原來的 `person1` 沒有關係了,所以不會改到原來的 `person1`
#### ex4(小樂高)
```javascript=
let person1 = {
name: '小明',
}
let person2;
function fn(item){
person2 = person1; //此段如何修改,可以避免最後的值變成小美
person2.name = '小美';
}
fn(person1);
console.log(person1.name);
```
- `person2 = JSON.parse(JSON.stringify(person1));`
- `person2 = { ...person1 };`
- 使用展開或深拷貝
#### ⚡⚡ex大樂高(複製物件時可能會遇到)
```javascript=
const people = [
{
name: '卡斯伯',
like: '鍋燒意麵',
price: 95,
imageUrl: 'https://images.unsplash.com/photo-1569562211093-4ed0d0758f12?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1470&q=80'
},
{
name: '瑞',
like: '炒麵',
price: 80,
imageUrl: 'https://images.unsplash.com/photo-1612929633738-8fe44f7ec841?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NHx8ZnJpZWQlMjBub29kbGVzfGVufDB8fDB8fA%3D%3D&auto=format&fit=crop&w=400&q=60'
},
{
name: '小明',
like: '黑胡椒燴飯',
price: 120,
imageUrl: 'https://images.unsplash.com/photo-1637362520022-81292a4bff4b?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80'
},
{
name: '喬伊',
like: '生菜沙拉',
price: 80,
imageUrl: 'https://images.unsplash.com/photo-1505253716362-afaea1d3d1af?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80'
}
];
people.forEach((person) => {
if (person.name === '卡斯伯') {
person = {
name: '杰倫',
like: '香菜',
price: 180,
}
}
});
console.log(people); // Q:請問卡斯伯是否有被替換?
```
- 沒有被替換,因為 person 指向新物件
- 流程示意圖
- 只要看到大括號,就會產生新的參考位置
- 正確寫法
```javascript=
people.forEach((person, key) => {
if (person.name === '卡斯伯') {
people[key] = {
name: '杰倫',
like: '香菜',
price: 180,
};
// person = {
// name: '杰倫',
// like: '香菜',
// price: 180,
// };
}
});
```

# MVVM
## Vue 起手式
<iframe src="https://codesandbox.io/embed/v-init-2022-dgyjy?fontsize=14&hidenavigation=1&theme=dark"
style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
title="v-init 2022"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
- option api,和 vue2 幾乎一模一樣
- data, function return
- methods, 物件
- mounted,函式
- ⚡⚡常見結構,Vue 初始化
```javascript=
const app = Vue.createApp({
data() {
return {
...data
};
},
methods: {},
mounted() {}
});
const data = {...};
```
- 實體化,`app.mount("#app");`
- 生命週期,`mounted` 最常用
#### Option Api
- 簡單、易學
- 資料、方法、生命週期已經分類做好了
- 較好理解
#### Composition Api
- 進階使用
- 需要熟悉 ES6
- 沒有分類,code 內容靠經驗分類
- 官方 doc 提到不適合初學者
#### 加入元件的方式
- `app.component();`
## Vue 藍圖方法

- Vue 用藍圖方法建立元件
- 元件內有資料、方法、生命週期…等等
- 使用 `createApp()` 把元件建立起來
- 使用 `createApp()` 建立的元件稱為==根元件==
- 網頁上有許多區塊,Vue 通常會掛載在 `#app` (最外層的 dom 元素)上,用 `mount` 的方法掛載上來
- 根元件內可以插入許多子元件
### 雙向綁定
#### 在 `mounted` 中觀察 `this`
```javascript=
mounted() {
console.log(this);
}
```

- `Proxy`,Vue3 的新機制,效能非常好,會對內容的資料監控,當值有變動時,會重新渲染到畫面上
#### 專注在資料渲染

- 在方法或生命週期中調整資料
- 資料變動,就會重新渲染
- MVVM概念
- 當資料變動時,能直接渲染至html畫面上
- 資料的調整可透過「方法」、「生命週期」
- 使用Vue時,可專注在資料處理,不需要花太多時間解決資料渲染
#### 從畫面調整資料
- 透過指令v-model雙向綁定後,可直接調整資料內容

:::success
#### ⚡⚡學習等級
- 第1級:僅有閱讀
- 第2級:動手操作
- 第3級:自已寫一次
- 第4級:用自已的方式做成筆記
- 小錯誤 ok der
- 沒有時間,之後會更沒有時間
- 儘量做到 3、4
:::
### 指令
<iframe src="https://codesandbox.io/embed/v-init-2022-dgyjy?fontsize=14&hidenavigation=1&theme=dark"
style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
title="v-init 2022"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
#### Vue 常用指令
- [常用指令](https://hackmd.io/@hexschool/S1DJeKTdL/%2FRhud3_1PR9qv1RJyMfwUmA)

#### `v-model`,雙向綁定
- vue-devtools 如果沒出現,重新開一個分頁
- ⚡⚡養成習慣,預先定義資料,避免一開始就報錯
#### 方法寫在 `methods` 裡
- 在 `methods` 定義方法
- 在 button `v-on:click` 觸發方法,修改資料
#### 元件和指令之間的關聯性

- 全部的集合都是物件,只有 data 是 function return,因為 Vue 要做元件化
- 方法會立刻執行
- 生命週期符合條件,會立刻觸發,只會觸發一次
#### 指令分三大類

- 雙向綁定,`v-model`,可以讀出、寫入資料
- 渲染方法,把資料讀出來,呈現到畫面上
- `{{...}}`
- `v-for`,`v-show`,`v-text`
- 事件綁定,`v-on`
- 觸發方法,修改資料,重新渲染畫面
### 取出多筆資料,呈現在 `ul` 上
```javascript=
<ul>
<li v-for="(item,i) in list" v-bind:key="item.name">
{{i}} / {{item}}
</li>
</ul>
```
- v-for 沒綁 key 的 error 看 [常用指令](https://hackmd.io/@hexschool/S1DJeKTdL/%2FRhud3_1PR9qv1RJyMfwUmA)
- `v-bind:key` 不建議帶 `v-for` 的第2個參數
- 通常帶 id,data 裡沒 id ,可以這樣做,想辦法 bind 獨一無二的值
### html 屬性運用
- `v-bind:src=""`
#### 指令縮寫
- `v-bind` → `:`
- `v-on:` → `@`
### 判斷式
- button,記得加上 `type="button"`
- ask:事件觸發要用什麼方法
- `v-on`
- 修改 `isShow` 的 true、false 可以怎麼做
- `@click` 後面可以加入表達式
- `==` 是判斷式
- 指令儘量練習縮寫
#### `v-if`,toggle 效果
```javascript=
<button type="button" @click="isShow=!isShow">{{isShow}}</button>
<div v-if="isShow">這段要顯示</div>
<div v-else>這段不要顯示</div>
```
#### 指令不熟
- 去玩一下每日任務
- 表單應用滿重要的
# Options API 簡述
## opening
- 我理解很多人沒有時間
- 畢竟,業界不會管你有沒有時間
- 作業 Lv1:補上註解
- 參考範例 code
- 我們尾牙要取消了
- 其實我們對員工不錯吧, array
- 每人一台 mac
- 獎金暑假、年尾各一次,加年終
- 每週三全遠端👀
- 實際上有一點血汗
## computed & watch
### 如何區分 computed & watch
> 除了生命週期外,其它(data、methods)都是集合
* computed 監聽多個資料
* watch 監聽單一資料
### :one: computed

- 把資料撈出來,重新運算,回傳到畫面上
- 監聽多個資料
- computed 不會更動到資料,只撈資料、渲染到畫面上。
- 優點:自動監聽data()內值的變動,自動運行
#### computed sample
<iframe src="https://codesandbox.io/embed/computed-sample-g60yg?fontsize=14&hidenavigation=1&theme=dark"
style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
title="computed-sample"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
- .sort()會帶出前一個值與後一個值用年齡或價格排序
- 主要調整的內容,people
- 目的:把 people 讀出來,重新運算,渲染到畫面上
- 閒聊歸閒聊,苓膏龜苓膏,課程還是要上
#### 加入升降冪
- 用 computed 中的 `sortPeople` 取代原本 data 的 `people`
- 在 `sortPeople` 中重新排序
- 排序的陣列方法,`.sort()`
- 使用三元運算子,`return ascending ? 真的結果 : 假的結果`
- 取資料時記得加 `this`
#### 加入價格、年齡排序
- 用 `this.sortBy` 取代原本的 `name`
- 修改 `data` 中的 `sortBy`
```javascript=
<thead>
<tr>
<th>姓名</th>
<th @click="sortBy = 'age'">年齡</th>
<th>喜好</th>
<th @click="sortBy = 'price'">價格</th>
</tr>
</thead>
```
- `computed` 只有把資料讀出來,沒有寫入
- `computed` 會對使用到的變數進行監聽,監聽到變動會再觸發函式,只要有 `this` 都是會被監聽的
```javascript=
computed: {
// #1 先完成價格排序
// #2 加入 升降冪
// #3 加入價格、年齡的排序
sortPeople() {
const newPeople = this.people.sort((a, b) => {
return this.ascending
? b[this.sortBy] - a[this.sortBy]
: a[this.sortBy] - b[this.sortBy];
});
return this.people;
}
}
```
- `mounted` 只會觸發一次,`computed` 只要變數有變動就會觸發
- 監聽是針對資料監聽
#### :A: getter
- 一偵測到資料集的內容產生更動,就同步觸發 computed 內的函式一次。
- 因為上述特性,computed 很容易拿來做搜尋功能。
#### :B: setter
> 待補
### :two: watch

- 重新調整資料內容,並渲染到畫面上
- 能更動資料內容
- 主要監聽data()資料中**單一的**值,當值有變化,就觸發 methods。
- 觸發 methods 後,watch 可**修改 Data 資料內容**,再把資料渲染到畫面上,或重新取得遠端的值。
- watch 不會產生新的值。
#### watch example
<iframe src="https://codesandbox.io/embed/watch-sample-l1jzk?fontsize=14&hidenavigation=1&theme=dark"
style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
title="watch sample"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
- watch內的 `方法名稱` 和 `data()資料` 同名,就會被監聽

- [random user](https://randomuser.me/)
- [seeds](https://randomuser.me/documentation#seeds)
- 固定的 `seed` 會取出固定的結果
- 第1個參數是 **當前值**,第2個參數是 **前一個值**
- seed 值變動的時候會去取遠端的資料
### debounce 技巧 與 watch
⚡⚡防止 watch 時 call爆 api
###### tags: debounce
lodash,為老牌函式庫
[lodash - debounce](https://lodash.com/docs/4.17.15#debounce)
- 會有持續監聽的行為,但不會立即做反應
(連續觸發時不會有作用,有一個延遲反映的時間)
- JS中加入 lodash 的 debounce
`import { debounce } from "https://cdn.jsdelivr.net/npm/@esm-bundle/lodash@4.17.21/esm/index.js";`
- 有的搜尋工具會分2層,第1層先找 localstorage,debounce 時間到才去打 api
- 💡debounce 裡改傳統函式,`this` 才能正確指向
```javascript=
watch: {
text(current, old) {
// #1 使用 Watch 取得當前值及舊有值
console.log(current, old);
},
// debounce
seed: debounce(function (current) {
console.log(current);
axios
.get(`https://randomuser.me/api/?seed=${current}`)
.then((res) => {
console.log(res.data);
this.people = res.data;
});
}, 1000)
```
- https://stackoverflow.com/questions/45178621/how-to-correctly-use-vue-js-watch-with-lodash-debounce
#### watch test1 如何在 text 文字更新時,觸發 fn 函式
<iframe src="https://codesandbox.io/embed/watch-test-1-n9ikv?fontsize=14&hidenavigation=1&theme=dark"
style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
title="watch test 1"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
### :three: methods
methods 本身是一個大物件,內層可以包很多函式,是主動觸發的事件。
#### 觸發方式:
- 指令觸發 (點擊)
- 由其他 options API 觸發 methods
`備註` 以上三者皆為 options API,在操作中會大量使用 this,因此盡量不要在 options API 中直接使用箭頭函式,this 的指向會出錯。
## 卡老師叮嚀

- 儘量參與作業討論
- 用3~4個月,抵過1年
- 第4周滿難的
- 過年把第4周做完
- 第4周以後難度會上升很多
- 熟悉 github pages
- 裝 nodejs
- 建議裝 14版
- 16版移除了一些語法,有一些套件不支援
### nodejs 相關
- [安裝 nvm 環境](https://wcc723.github.io/development/2022/01/10/install-nvm/)
- [無法辨識 'XXX' 詞彙是否為 Cmdlet、函數、指令檔或可執行程式的名稱](https://hsiangfeng.github.io/nodejs/20190801/449913843/)
- [nvm安裝](https://www.796t.com/article.php?id=418016)
- [顯示 Mac 隱藏檔案 的三個方法](https://macuknow.com/2017/08/26/1428/%e4%b8%89%e6%8b%9b%e8%ae%93-mac-%e9%a1%af%e7%a4%ba%e5%87%ba%e9%9a%b1%e8%97%8f%e6%aa%94%e6%a1%88/)
- 前端必裝工具
- JavaScript 環境
- 可以跑後端
- `nove -v`
- iterm2 主題
# Bootstrap JS
## Alerts Dismissing
- [Component Alerts Dismissing](https://getbootstrap.com/docs/5.0/components/alerts/#dismissing)
- [Component Alerts Dismissing(中文)](https://bootstrap5.hexschool.com/docs/5.1/components/alerts/#dismissing)
- alert 元件可以透過叉叉關閉
- `data-bs` 開頭的屬性,bootstrap 專屬的屬性
- `data-bs-dismiss="alert"`,移除 alert
## Modal
- [Modal live demo](https://bootstrap5.hexschool.com/docs/5.1/components/modal/#live-demo)
- `data-bs-toggle="modal"`,切換 modal
- `data-bs-target="#exampleModal"`,用 `id` 選擇目標
## 如何使用 JS 操作元件
<iframe src="https://codesandbox.io/embed/vue-bootstrap5-001-bdxqu?fontsize=14&hidenavigation=1&theme=dark"
style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
title="Vue Bootstrap5 001"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
- 拿掉 button 中的 `data-bs` 屬性
```javascript=
<button type="button" class="btn btn-primary" id="modalBtn">
Launch demo modal
</button>
```
- [modal usuage](https://bootstrap5.hexschool.com/docs/5.1/components/modal/#usage)
- 透過資料屬性
- 透過 JavaScript
- `var myModal = new bootstrap.Modal(document.getElementById('myModal'), options)`
- modal 實體化
- 選擇 dom 元素
- [options](https://bootstrap5.hexschool.com/docs/5.1/components/modal/#options),相關選項
- [modal 相關方法](https://bootstrap5.hexschool.com/docs/5.1/components/modal/#methods)
- show
- hide
## 在 Vue 中使用 bootstrap modal
<iframe src="https://codesandbox.io/embed/vue-bootstrap5-003-zxii4?fontsize=14&hidenavigation=1&theme=dark"
style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
title="Vue Bootstrap5 003"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
- 在 `mounted` 擷取 dom 元素
## 討論串
- [討論串](https://discord.com/channels/801807326054055996/905656987583397908)