--- tags: Vue,第三章 --- # 同步異步 computed只能做同步任務 watch可以做異步任務 # watch ```javascript= <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <div id="app"> <div> 翻譯成的語言: <select v-model="obj.lang"> <option value="italy">義大利</option> <option value="english">英語</option> <option value="german">德語</option> </select> </div> <!-- 翻譯框 --> <div class="box"> <div class="input-wrap"> <textarea v-model="obj.keyword"></textarea> </div> <div class="output-wrap"> <textarea class="transbox">{{result}}</textarea> </div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script> const app = new Vue({ el: "#app", data: { words: "", result: "", obj: { keyword: "紫陽道長", lang: "english", }, }, // watch默認情況下只能監視簡單數據類型 watch: { // 完整寫法 obj: { immediate: true, // 網頁運行立即執行 deep: true, // 開啟深度監聽 async handler(newVal) { // 對象數據發生變化時自動執行 console.log(newVal); // 發請求 const res = await axios({ url: "https://applet-base-api-t.itheima.net/api/translate", params: { words: newVal.keyword, lang: newVal.lang, }, }); console.log(res.data.data); this.result = res.data.data; }, }, // 高級用法:監視對象的屬性 // 'obj.keyword'(newVal){ // console.log(this.obj.keyword); // } // 基本用法:監視data中的數據 // words(newVal, oldVal) { // console.log(newVal, oldVal); // console.log(newVal); // console.log(this.words); // } }, }); </script> </body> </html> ``` # 綜合案例-購物車 ```javascript= <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <style> #app{ width: 1000px; height: 1000px; margin: 0 auto; } .main { width: 1000px; height: 500px; } .thead .tr { display: flex; } .thead .tr .th { width: 200px; height: 20px; border: 1px solid black; text-align: center; } .tbody { border: 1px solid red; } .tbody .tr { display: flex; } .tbody .tr .td { width: 200px; height: 100px; text-align: center; line-height: 100px; } .active { background-color: #f6f7fa; } .bottom { display: flex; justify-content: space-between; border: 1px solid yellow; } </style> </head> <body> <div id="app"> <!-- 頂部 --> <div><img src="./fruit.png" alt="" /></div> <!-- 麵包屑 --> <div> <span>房子</span> <span>購物車</span> </div> <!-- 購物車主體 --> <div class="main" v-if="fruitList.length>0"> <div class="table"> <!-- 頭部 --> <div class="thead"> <div class="tr"> <div class="th">選中</div> <div class="th">圖片</div> <div class="th">單價</div> <div class="th">個數</div> <div class="th">小計</div> <div class="th">操作</div> </div> </div> <!-- 身體 --> <div class="tbody"> <div class="tr" v-for="item in fruitList" :key="item.id" :class="{active:item.isChecked}" > <div class="td"> <input type="checkbox" v-model="item.isChecked" /> </div> <div class="td"><img :src="item.icon" /></div> <div class="td">{{item.price}}</div> <div class="td"> <div class="my-input-number"> <button :disabled="item.num<=1" @click="item.num--">-</button> <span>{{item.num}}</span> <button @click="item.num++">+</button> </div> </div> <div class="td">{{item.price * item.num}}</div> <div class="td"><button @click="del(item.id)">刪除</button></div> </div> </div> <!-- 底部 --> <div class="bottom"> <!-- 全選 --> <label> <input type="checkbox" v-model="isAll" /> 全選 </label> <div> <!-- 所有總價 --> <span>總價<span>{{totalPrice}}</span></span> <!-- 結算按鈕 --> <button>結算{{totalCount}}</button> </div> </div> </div> </div> <!-- 空車 --> <div class="empty" v-else>空空如也</div> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> const app = new Vue({ el: "#app", data: { fruitList:JSON.parse(localStorage.getItem('list')) || [], // 這段注釋要留著 // fruitList: [ // { // id: 1, // icon: "./apple.png", // isChecked: true, // num: 2, // price: 6, // }, // { // id: 2, // icon: "./banana.png", // isChecked: true, // num: 2, // price: 6, // }, // { // id: 3, // icon: "./watermelon.png", // isChecked: true, // num: 2, // price: 6, // }, // ], }, methods: { del(id) { this.fruitList = this.fruitList.filter((item) => { return item.id !== id; }); }, }, computed: { isAll: { get() { // 統計小選框的狀態 return this.fruitList.every((item) => item.isChecked); }, set(val) { // isAll被修改時自動執行 // 讓小選框狀態跟著全選框改變 this.fruitList.forEach((item) => (item.isChecked = val)); }, }, // 簡單寫法無法被賦值導致報錯 // isAll() { // return this.fruitList.every((item) => item.isChecked); // }, totalPrice() { return this.fruitList.reduce((sum, item) => { // 有勾選的東西才要累加 if (item.isChecked) { return sum + item.price * item.num; } // 累加不能斷 return sum; }, 0); }, totalCount() { return this.fruitList.reduce((sum, item) => { // 有勾選的東西才要累加 if (item.isChecked) { return sum + item.num; } // 累加不能斷 return sum; }, 0); }, }, watch:{ // 深度偵聽 fruitList:{ deep:true, handler(){ console.log('數組變了'); localStorage.setItem('list',JSON.stringify(this.fruitList)) } } } }); </script> </body> </html> ``` # 生命週期 可在chrome輸入以下指令模擬銷毀 `app.$destroy()` ![](https://hackmd.io/_uploads/HJnI3w5h2.png) ![](https://hackmd.io/_uploads/HkJAAho3h.png) ```javascript= <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <div id="app"> <div>{{msg}}</div> <button @click="count++">+</button> <div>{{count}}</div> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> const app = new Vue({ el: "#app", data: { msg: "你好", count: 100, }, beforeCreate() { // 還不能訪問數據 console.log("創建前", this.count); }, created() { // 可以訪問數據 // 建議這時發ajax請求 console.log("創建後", this.count); // 只能拿到模板代碼 console.log(document.querySelector("button")); }, beforeMount() { console.log("掛載前"); }, mounted() { console.log("掛載後"); // 此時才拿到真實DOM console.log(document.querySelector("button")); }, beforeUpdate() { console.log("更新前"); }, updated() { console.log("更新後"); }, beforeDestroy() { console.log("銷毀前"); // 通常會在此清除定時器和卸載事件,因為這些東西屬於全局 }, destroyed() { console.log("銷毀後"); }, }); </script> </body> </html> ``` ## 新聞渲染 ```javascript= <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <div id="app"> <ul> <li v-for="item in list" :key="item.id" class="news"> <div class="left"> <div class="title">{{item.title}}</div> <div class="info"> <span>{{item.source}}</span> <span>{{item.time}}</span> </div> </div> <div class="right"> <img :src="item.img" alt="" /> </div> </li> </ul> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script> const app = new Vue({ el: "#app", data: { list: [], }, async created() { const res = await axios.get("http://hmajax.itheima.net/api/news"); console.log(res.data.data); this.list = res.data.data; }, }); </script> </body> </html> ``` ## 搜索框聚焦 ```javascript= <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <div id="app"> <div class="search-box"> <input type="text" v-model="words" id="inp" /> <!-- 原生HTML用autofocus直接解決 --> <!-- Vue因為重新渲染的關係會害聚焦丟失 --> <!-- <input autofocus type="text" v-model="words" id="inp"> --> <button>搜索一下</button> </div> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> const app = new Vue({ el: "#app", data: { words: "", }, mounted() { document.querySelector("#inp").focus(); }, }); </script> </body> </html> ``` # 綜合案例-記帳清單 ```javascript= <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <style> .red { color: red; } .echarts-box { width: 1000px; height: 1000px; } </style> </head> <body> <div id="app"> <div class="contain"> <!-- 左側列表 --> <div class="list-box"> <!-- 添加資產 --> <form> <input v-model.trim="name" type="text" placeholder="消費名稱" /> <input v-model.number="price" type="text" placeholder="消費價格" /> <button @click="add" type="button">添加帳單</button> </form> <table> <thead> <tr> <th>編號</th> <th>消費名稱</th> <th>消費價格</th> <th>操作</th> </tr> </thead> <tbody> <tr v-for="(item,index) in list" :key="item.id"> <td>{{index + 1}}</td> <td>{{item.name}}</td> <td :class="{red:item.price>100}">{{item.price}}</td> <td><a @click="del(item.id)" href="javascript:;">刪除</a></td> </tr> </tbody> <tfoot> <tr> <td colspan="4">消費總計{{totalPrice.toFixed(2)}}</td> </tr> </tfoot> </table> </div> <!-- 右側圖表 --> <div class="echarts-box" id="main"></div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script> const app = new Vue({ el: "#app", data: { list: [], name: "", price: "", }, async created() { // const res = await axios.get( // "https://applet-base-api-t.itheima.net/bill", // { // params: { // creator: "紫陽", // }, // } // ); // console.log(res.data.data); // this.list = res.data.data; this.getList(); }, mounted() { // 基于准备好的dom,初始化echarts实例 this.myChart = echarts.init(document.getElementById("main")); // 使用刚指定的配置项和数据显示图表。 this.myChart.setOption({ title: { text: "Referer of a Website", subtext: "Fake Data", left: "center", }, tooltip: { trigger: "item", }, legend: { orient: "vertical", left: "left", }, series: [ { name: "消費帳單", type: "pie", radius: "50%", data: [ { value: 1048, name: "表" }, { value: 735, name: "帽子" }, ], emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: "rgba(0, 0, 0, 0.5)", }, }, }, ], }); }, computed: { totalPrice() { return this.list.reduce((sum, item) => sum + item.price, 0); }, }, methods: { async getList() { const res = await axios.get( "https://applet-base-api-t.itheima.net/bill", { params: { creator: "紫陽", }, } ); console.log(res.data.data); this.list = res.data.data; // 圖表渲染 this.myChart.setOption({ series: [ { data: this.list.map((item) => ({ value: item.price, name: item.name, })), }, ], }); }, async add() { if (!this.name) { return alert("請輸入消費名稱"); } if (typeof this.price !== "number") { return alert("請輸入正確的金額"); } const res = await axios.post( "https://applet-base-api-t.itheima.net/bill", { creator: "紫陽", name: this.name, price: this.price, } ); this.getList(); this.name = ""; this.price = ""; }, async del(id) { await axios.delete( `https://applet-base-api-t.itheima.net/bill/${id}` ); this.getList(); }, }, }); </script> </body> </html> ```