??前言
這篇文章記錄一下 Vue3 計(jì)算屬性和偵聽(tīng)器 (computed、watch) 實(shí)戰(zhàn)的內(nèi)容,這篇文章我們?cè)谟杏?jì)算屬性和偵聽(tīng)器的基礎(chǔ)上,我們來(lái)制作一個(gè)簡(jiǎn)易點(diǎn)餐頁(yè)面,接下來(lái)我們一起來(lái)從零到一開(kāi)始制作。
計(jì)算屬性和偵聽(tīng)器相關(guān)文章推薦:
深入與淺談 Vue 中計(jì)算屬性和偵聽(tīng)器的區(qū)別和使用(Vue3版本為例)
淺談在 Vue2 和 Vue3 中計(jì)算屬性和偵聽(tīng)器的一些變化
??項(xiàng)目介紹
在創(chuàng)建項(xiàng)目之前,我們先簡(jiǎn)單看一下這次項(xiàng)目需要完成的頁(yè)面內(nèi)容,如下圖。主頁(yè)列表羅列著菜品名稱、圖片介紹,用戶通過(guò)單機(jī)添加按鈕,實(shí)現(xiàn)菜品添加的點(diǎn)餐功能。最后在頁(yè)面的下方會(huì)顯示用戶點(diǎn)餐詳情以及總數(shù)和總價(jià),同時(shí)可以通過(guò)單機(jī)刪除按鈕,實(shí)現(xiàn)菜品的刪除的取消點(diǎn)餐功能。
??項(xiàng)目創(chuàng)建
要?jiǎng)?chuàng)建一個(gè) Vite
項(xiàng)目,需要先安裝 Vite
。可以使用 npm
或者 yarn
進(jìn)行安裝。在命令行中輸入:
npm install vite -g # 全局安裝 vite
或者
yarn global add vite # 全局安裝 vite
然后通過(guò)以下命令創(chuàng)建一個(gè) Vite
項(xiàng)目,名稱是 vite-demo。
npm init vite@latest vite-demo
選擇 Vue 。
然后選擇 TypeScript 。
默認(rèn)生成的項(xiàng)目結(jié)構(gòu)如下,然后在控制臺(tái)輸入 npm install
安裝相關(guān)依賴 (主要選擇當(dāng)前文件夾)。
最后輸入 npm run dev
啟動(dòng)項(xiàng)目,出現(xiàn)如下頁(yè)面表示運(yùn)行成功。
到此項(xiàng)目創(chuàng)建完成,接下來(lái)我們來(lái)看看具體代碼。
??代碼分析
我們根據(jù)上面項(xiàng)目介紹的圖片展示,點(diǎn)餐頁(yè)面分為三個(gè)部分,即菜品列表、點(diǎn)餐列表以及消費(fèi)價(jià)格??梢韵雀鶕?jù)這個(gè)頁(yè)面設(shè)計(jì)來(lái)實(shí)現(xiàn)代碼的布局。在項(xiàng)目中的 App.vue
文件中,修改 template
模板部分的代碼。
<template>
<div class="food-container">
<div class="food-wrap">
<!-- 菜品列表 -->
<ul class="food-main">
<li v-for="(item, index) in foodList" :key="item.name">
<img :src="item.url" class="food-image"/>
<label>
<span>{{item.name}}</span>
</label>
<button class="btn btn-add" @click="orderFood(index)">添加</button>
<span class="food-price">價(jià)格 {{item.price}} 元/份</span>
</li>
</ul>
<!-- 點(diǎn)餐列表 -->
<div class="food-order">
<ul class="order-main">
<li class="order-item" v-for="(item, index) in orderList" :key="item.name">
<label>{{item.name}}</label>
<div>
<span class="order-count"> X {{item.count}}</span>
<button class="btn btn-delete" @click="removeFood(index)">刪除</button>
</div>
</li>
</ul>
</div>
<!-- 總消費(fèi)價(jià)格 -->
<div class="food-total-price">
<span>
<span class="total-count">已點(diǎn) {{totalCount}} 份餐</span>
<span>共計(jì) <b>{{total}}</b> 元</span>
</span>
</div>
</div>
</div>
</template>
在這段代碼里,使用 v-for
指令分別渲染菜品列表和點(diǎn)餐列表。添加按鈕和刪除按鈕分別綁定 orderFood() 和 removeFood() 方法。最后通過(guò) totalCount 的值是否為 0 來(lái)顯示點(diǎn)餐份數(shù)。
接下來(lái)我們來(lái)實(shí)現(xiàn)點(diǎn)餐頁(yè)面的業(yè)務(wù)邏輯,修改 App.vue
文件中的 script
部分代碼。
<script setup lang="ts">
import { computed, reactive, ref, watch } from 'vue';
// 菜品接口類
interface Food {
name: string;
url: string;
price: number;
}
// 訂單接口類
interface Order {
name: string;
price: number;
count: number;
}
// 菜品數(shù)據(jù)列表
const foodList = reactive<Food[]>([
{ name: '宮保雞丁', url: '/src/assets/gbjd.png', price: 12.0 },
{ name: '魚(yú)香肉絲', url: '/src/assets/yxrs.png', price: 17.0 },
{ name: '紅燒排骨', url: '/src/assets/hspg.png', price: 20.0 },
]);
// 訂單數(shù)據(jù)列表
const orderList = reactive<Order[]>([]);
// 總價(jià)
const total = ref(0);
// 總個(gè)數(shù)
const totalCount = computed((): number => {
let count = 0;
orderList.forEach((item: Order) => {
count += item.count;
})
return count;
});
// 點(diǎn)餐函數(shù)
const orderFood = (index: number):void => {
// 查看當(dāng)前菜品是否已經(jīng)被點(diǎn)
const isOrdered = orderList.filter((item: Order): boolean => {
return item.name === foodList[index].name;
});
if (isOrdered.length) {
isOrdered[0].count += 1;
} else {
orderList.push({
name: foodList[index].name,
price: foodList[index].price,
count: 1,
})
}
};
// 取消點(diǎn)餐操作
const removeFood = (index: number):void => {
if (orderList[index].count > 0) {
orderList[index].count -= 1;
}
if (orderList[index].count === 0) {
orderList.splice(index, 1);
}
};
// 監(jiān)聽(tīng)訂單列表變化
watch(
() => orderList,
() => {
total.value = 0;
orderList.forEach((order: Order) => {
total.value += order.count * order.price;
});
},
{deep:true}
);
</script>
這里首先分別定義了 Food 和 Order 兩個(gè)類型。然后初始化 foodList、orderList 和 total 變量,對(duì)應(yīng)的菜品列表、點(diǎn)餐列表和消費(fèi)總價(jià)。接下來(lái)使用一個(gè) totalCount 計(jì)算屬性統(tǒng)計(jì)總點(diǎn)餐份數(shù)。orderFood() 方法和 removeFood() 方法分別對(duì)應(yīng)模板的添加和刪除按鈕。最后使用偵聽(tīng)器屬性,檢測(cè) orderList 對(duì)象的變化。通過(guò) orderList 數(shù)據(jù)變化來(lái)計(jì)算總點(diǎn)餐花費(fèi)。這樣,一個(gè)簡(jiǎn)單的點(diǎn)餐頁(yè)面就完成了,運(yùn)行效果如下。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-493986.html
??完整代碼(含 CSS 代碼)
<script setup lang="ts">
import { computed, reactive, ref, watch } from 'vue';
// 菜品接口類
interface Food {
name: string;
url: string;
price: number;
}
// 訂單接口類
interface Order {
name: string;
price: number;
count: number;
}
// 菜品數(shù)據(jù)列表
const foodList = reactive<Food[]>([
{ name: '宮保雞丁', url: '/src/assets/gbjd.png', price: 12.0 },
{ name: '魚(yú)香肉絲', url: '/src/assets/yxrs.png', price: 17.0 },
{ name: '紅燒排骨', url: '/src/assets/hspg.png', price: 20.0 },
]);
// 訂單數(shù)據(jù)列表
const orderList = reactive<Order[]>([]);
// 總價(jià)
const total = ref(0);
// 總個(gè)數(shù)
const totalCount = computed((): number => {
let count = 0;
orderList.forEach((item: Order) => {
count += item.count;
})
return count;
});
// 點(diǎn)餐函數(shù)
const orderFood = (index: number):void => {
// 查看當(dāng)前菜品是否已經(jīng)被點(diǎn)
const isOrdered = orderList.filter((item: Order): boolean => {
return item.name === foodList[index].name;
});
if (isOrdered.length) {
isOrdered[0].count += 1;
} else {
orderList.push({
name: foodList[index].name,
price: foodList[index].price,
count: 1,
})
}
};
// 取消點(diǎn)餐操作
const removeFood = (index: number):void => {
if (orderList[index].count > 0) {
orderList[index].count -= 1;
}
if (orderList[index].count === 0) {
orderList.splice(index, 1);
}
};
// 監(jiān)聽(tīng)訂單列表變化
watch(
() => orderList,
() => {
total.value = 0;
orderList.forEach((order: Order) => {
total.value += order.count * order.price;
});
},
{deep:true}
);
</script>
<template>
<div class="food-container">
<div class="food-wrap">
<!-- 菜品列表 -->
<ul class="food-main">
<li v-for="(item, index) in foodList" :key="item.name">
<img :src="item.url" class="food-image"/>
<label>
<span>{{item.name}}</span>
</label>
<button class="btn btn-add" @click="orderFood(index)">添加</button>
<span class="food-price">價(jià)格 {{item.price}} 元/份</span>
</li>
</ul>
<!-- 點(diǎn)餐列表 -->
<div class="food-order">
<ul class="order-main">
<li class="order-item" v-for="(item, index) in orderList" :key="item.name">
<label>{{item.name}}</label>
<div>
<span class="order-count"> X {{item.count}}</span>
<button class="btn btn-delete" @click="removeFood(index)">刪除</button>
</div>
</li>
</ul>
</div>
<!-- 總消費(fèi)價(jià)格 -->
<div class="food-total-price">
<span>
<span class="total-count">已點(diǎn) {{totalCount}} 份餐</span>
<span>共計(jì) <b>{{total}}</b> 元</span>
</span>
</div>
</div>
</div>
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.btn {
display: inline-block;
padding: 8px 10px;
margin-bottom: 0;
font-size: 14px;
line-height: 14px;
text-align: center;
vertical-align: middle;
cursor: pointer;
box-shadow: inset 0 1px rgba(255, 255, 255, 0.2),
0 1px 2px rgba(0, 0, 0, 0.05);
border-radius: 4px;
}
.btn-add {
color: #fff;
background-color: #0d6efd;
border: none;
}
.btn-delete {
color: #fff;
background-color: #dc3545;
border: none;
}
.btn-delete:hover {
color: #fff;
background-color: #bb2d3b;
}
.btn-add:hover {
color: #fff;
background-color: #0b5ed7;
}
.food-container {
width: 600px;
margin: 40px auto;
}
.food-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
.food-price {
float: right;
margin-right: 10px;
}
.food-main {
margin-left: 0px;
border: 1px solid #ddd;
border-radius: 4px;
padding: 0px;
}
.food-image {
float: left;
height: 100%;
}
.order-main {
margin-left: 0px;
padding: 0px;
}
.order-main li {
display: flex;
}
.order-item {
display: flex;
flex-direction: row;
justify-content: space-between;
}
li {
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
}
.food-main li label {
float: left;
cursor: pointer;
vertical-align: middle;
margin-right: 6px;
position: relative;
top: -1px;
font-weight: bold;
margin-left: 10px;
}
.food-main li button {
float: right;
margin-top: 3px;
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
.total-count {
font-size: 0.8rem;
color: #6c757d;
margin-right: 21px;
}
.food-main li:hover {
background-color: #ddd;
}
.order-count {
margin-right: 30px;
}
.food-order {
line-height: 40px;
padding-left: 6px;
margin-top: 5px;
}
.food-order label {
display: inline-block;
margin-right: 20px;
cursor: pointer;
}
.food-total-price {
display: flex;
justify-content: end;
}
</style>
??最后
到此文章結(jié)束,這就是 Vue3 計(jì)算屬性和偵聽(tīng)器 (computed、watch) 實(shí)戰(zhàn)的全部?jī)?nèi)容了,通過(guò)這篇文章我們從零到一制作了一個(gè)簡(jiǎn)易點(diǎn)餐頁(yè)面,這樣可以提高我們使用計(jì)算屬性和偵聽(tīng)器的熟練程度。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-493986.html
到了這里,關(guān)于Vue3 計(jì)算屬性和偵聽(tīng)器實(shí)戰(zhàn)(computed、watch)——簡(jiǎn)易點(diǎn)餐頁(yè)面的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!