Vue3 计算属性和侦听器实战(computed、watch)——简易点餐页面

news2024/12/26 21:05:24

文章目录

  • 📋前言
  • 🎯项目介绍
  • 🎯项目创建
  • 🎯代码分析
  • 🎯完整代码(含 CSS 代码)
  • 📝最后


在这里插入图片描述

📋前言

这篇文章记录一下 Vue3 计算属性和侦听器 (computed、watch) 实战的内容,这篇文章我们在有计算属性和侦听器的基础上,我们来制作一个简易点餐页面,接下来我们一起来从零到一开始制作。

计算属性和侦听器相关文章推荐:
深入与浅谈 Vue 中计算属性和侦听器的区别和使用(Vue3版本为例)
浅谈在 Vue2 和 Vue3 中计算属性和侦听器的一些变化


🎯项目介绍

在创建项目之前,我们先简单看一下这次项目需要完成的页面内容,如下图。主页列表罗列着菜品名称、图片介绍,用户通过单机添加按钮,实现菜品添加的点餐功能。最后在页面的下方会显示用户点餐详情以及总数和总价,同时可以通过单机删除按钮,实现菜品的删除的取消点餐功能。
在这里插入图片描述
在这里插入图片描述


🎯项目创建

要创建一个 Vite 项目,需要先安装 Vite。可以使用 npm 或者 yarn 进行安装。在命令行中输入:

npm install vite -g  # 全局安装 vite
或者
yarn global add vite  # 全局安装 vite

然后通过以下命令创建一个 Vite 项目,名称是 vite-demo

npm init vite@latest vite-demo

选择 Vue
在这里插入图片描述
然后选择 TypeScript
在这里插入图片描述
默认生成的项目结构如下,然后在控制台输入 npm install 安装相关依赖 (主要选择当前文件夹)
在这里插入图片描述
最后输入 npm run dev 启动项目,出现如下页面表示运行成功。
在这里插入图片描述
在这里插入图片描述
到此项目创建完成,接下来我们来看看具体代码。


🎯代码分析

我们根据上面项目介绍的图片展示,点餐页面分为三个部分,即菜品列表、点餐列表以及消费价格。可以先根据这个页面设计来实现代码的布局。在项目中的 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">价格 {{item.price}} 元/份</span>
        </li>
      </ul>
      <!-- 点餐列表 -->
      <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>
      <!-- 总消费价格 -->
      <div class="food-total-price">
        <span>
          <span class="total-count">已点 {{totalCount}} 份餐</span>
          <span>共计 <b>{{total}}</b></span>
        </span>
      </div>
    </div>
  </div>
</template>

在这段代码里,使用 v-for 指令分别渲染菜品列表和点餐列表。添加按钮和删除按钮分别绑定 orderFood()removeFood() 方法。最后通过 totalCount 的值是否为 0 来显示点餐份数。

接下来我们来实现点餐页面的业务逻辑,修改 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;
}

// 菜品数据列表
const foodList = reactive<Food[]>([
  { name: '宫保鸡丁', url: '/src/assets/gbjd.png', price: 12.0 },
  { name: '鱼香肉丝', url: '/src/assets/yxrs.png', price: 17.0 },
  { name: '红烧排骨', url: '/src/assets/hspg.png', price: 20.0 },
]);

// 订单数据列表
const orderList = reactive<Order[]>([]);

// 总价
const total = ref(0);

// 总个数
const totalCount = computed((): number => {
  let count = 0;
  orderList.forEach((item: Order) => {
    count += item.count;
  })
  return count;
});

// 点餐函数
const orderFood = (index: number):void => {
  // 查看当前菜品是否已经被点
  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,
    })
  }
};

// 取消点餐操作
const removeFood = (index: number):void => {
  if (orderList[index].count > 0) {
    orderList[index].count -= 1;
  }
  if (orderList[index].count === 0) {
    orderList.splice(index, 1);
  }
};

// 监听订单列表变化
watch(
  () => orderList,
  () => {
    total.value = 0;
    orderList.forEach((order: Order) => {
      total.value += order.count * order.price;
    });
  },
  {deep:true}
);
</script>

这里首先分别定义了 FoodOrder 两个类型。然后初始化 foodListorderListtotal 变量,对应的菜品列表、点餐列表和消费总价。接下来使用一个 totalCount 计算属性统计总点餐份数。orderFood() 方法和 removeFood() 方法分别对应模板的添加和删除按钮。最后使用侦听器属性,检测 orderList 对象的变化。通过 orderList 数据变化来计算总点餐花费。这样,一个简单的点餐页面就完成了,运行效果如下。
在这里插入图片描述


🎯完整代码(含 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;
}

// 菜品数据列表
const foodList = reactive<Food[]>([
  { name: '宫保鸡丁', url: '/src/assets/gbjd.png', price: 12.0 },
  { name: '鱼香肉丝', url: '/src/assets/yxrs.png', price: 17.0 },
  { name: '红烧排骨', url: '/src/assets/hspg.png', price: 20.0 },
]);

// 订单数据列表
const orderList = reactive<Order[]>([]);

// 总价
const total = ref(0);

// 总个数
const totalCount = computed((): number => {
  let count = 0;
  orderList.forEach((item: Order) => {
    count += item.count;
  })
  return count;
});

// 点餐函数
const orderFood = (index: number):void => {
  // 查看当前菜品是否已经被点
  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,
    })
  }
};

// 取消点餐操作
const removeFood = (index: number):void => {
  if (orderList[index].count > 0) {
    orderList[index].count -= 1;
  }
  if (orderList[index].count === 0) {
    orderList.splice(index, 1);
  }
};

// 监听订单列表变化
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">价格 {{item.price}} 元/份</span>
        </li>
      </ul>
      <!-- 点餐列表 -->
      <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>
      <!-- 总消费价格 -->
      <div class="food-total-price">
        <span>
          <span class="total-count">已点 {{totalCount}} 份餐</span>
          <span>共计 <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>

📝最后

到此文章结束,这就是 Vue3 计算属性和侦听器 (computed、watch) 实战的全部内容了,通过这篇文章我们从零到一制作了一个简易点餐页面,这样可以提高我们使用计算属性和侦听器的熟练程度。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/670542.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络安全自学能学会吗?网络安全如何学习

网络安全是近年来的热门工作&#xff0c;吸引了许多小伙伴开始学习网络安全知识。那么我们应该如何学习网络安全呢&#xff1f;这是一个很多人都在考虑的问题。网络安全可以自学吗&#xff1f;自学网络安全能不能学会&#xff1f; 无论什么知识都是自学的&#xff0c;只是说每…

数学物理学家心中的十大最美方程

“你认为最美的数学、物理方程是什么&#xff1f;”当代十位大数学家、物理学家给出了他们自己的回答。这些回答构成了大雅之美&#xff08;The Concinitas Project&#xff09;的十篇文章。我们为读者带来这些大师对自己眼中最美方程的精彩解读。 1.指标定理 撰文 阿蒂亚爵士…

机器翻译与自动文摘评价指标 BLEU 和 ROUGE

机器翻译与自动文摘评价指标 BLEU 和 ROUGE 在机器翻译任务中&#xff0c;BLEU 和 ROUGE 是两个常用的评价指标&#xff0c;BLEU 根据精确率(Precision)衡量翻译的质量&#xff0c;而 ROUGE 根据召回率(Recall)衡量翻译的质量。 1.机器翻译评价指标 使用机器学习的方法生成文…

安科瑞AWT100无线数据采集通信终端

安科瑞AWT100无线数据采集通信终端 安科瑞 崔丽洁

js \d正则匹配数字失败问题记录

记录一次的正则匹配数字失败的问题 在一次开发中&#xff0c;需要匹配卡号&#xff0c;正则表达式较为复杂&#xff0c;想通过元字符进行简化&#xff0c;便由&#xff1a; new RegExp(^622(12[6-9]|1[3-9][0-9]|[2-8][0-9]{2}|9[01][0-9]|92[0-5])[0-9]{10,}$)变成&#xff…

EMQ的使用和介绍

首先先了解一下底层的协议: 1. MQTT MQTT&#xff08;Message Queuing Telemetry Transport&#xff0c;消息队列遥测传输协议&#xff09;&#xff0c;是一种基于发布/订阅 &#xff08;publish/subscribe&#xff09;模式的"轻量级"通讯协议&#xff0c;该协议构建…

【盘点】百家量子企业正展露头角

光子盒研究院 量子计算是一个可能彻底改变我们在金融、材料科学、密码学和药物发现等领域解决复杂问题的方式。过去十年左右&#xff0c;量子计算初创公司正迅速崛起。 现在&#xff0c;根据光子盒的量子企业数据库&#xff0c;全球大约有一千家公司直接参与到量子技术中&#…

反调试技术

文章目录 前言系统API实现方式IsDebuggerPresent (0x2)NtGlobalFlag&#xff08;0x68&#xff09;Heap flags&#xff08;0x18&#xff09;CheckRemoteDebuggerPresentNtQueryInformationProcessZwSetInformationThread 示例示例1比较明文字符串和输入字符串NtGlobalFlag时间差…

支持向量机SVM的原理和python实现

文章目录 1 SVM概述1.1 概念1.2 SVM的优缺点1.2.1 优点1.2.2 缺点 2 在python中使用SVM2.1 scikit-learn库2.2 SVM在scikit-learn库中的使用2.2.1 安装依赖库2.2.2 svm.SVC2.2.3 应用实例 总结 1 SVM概述 1.1 概念 支持向量机&#xff08;SVM&#xff09;是一类按监督学习方式…

CRM系统如何选择?哪些是必备功能?

CRM系统可以收集、整理并分析客户数据、优化企业销售流程、实现团队协作和共享&#xff0c;提高客户转化率&#xff0c;实现业绩增长。那么&#xff0c;如何选择CRM系统&#xff1f;CRM系统哪家好&#xff1f; 一、明确自己的业务需求 不同行业和规模的企业有不同的业务需求&…

JMU 软件工程经济学 复习总结

文章目录 碎碎念0. 基准收益率 i1. 现金流量图2. 净现值 NPV&#xff0c;内部收益率 IRR3. 单利&#xff0c;复利计算4. 等额年金NAV5. 动态回收期 P t ′ P_t Pt′​6. 固定资产折旧 [书P44]7. 增值税8. 软件行业增值税的即征即退9. 利息备付率 ICR&#xff0c;偿债备付率 DSC…

这6种最佳移动自动化测试工具你知道吗?

最好的移动自动化测试工具 在本文章关于移动应用程序测试的这一部分中&#xff0c;我们将研究 2023 年 6 种最佳移动自动化测试工具。 1、Appium Appium 是一个非常流行的开源自动化测试框架&#xff0c;支持各种操作系统的自动化。它可以与本机、混合和移动 Web 应用程序一…

微机原理基础知识

前言 微机原理期末复习的一些概念性的基础知识总结。 内容 &#xff08;1&#xff09;微处理器、微机与微机系统三者之间有什么异同&#xff1f; &#xff08;1&#xff09;把CPU&#xff08;运算器和控制器&#xff09;用大规模集成电路技术做在一个芯片上&#xff0c;即为微…

Vue实现Base64转png、jpg

method中写两个方法&#xff1a; 根据base64转图片的方法 根据转换出blob格式的文件导出的方法 //base64转pngbase64ImgtoFile(dataurl, filename file) {const arr dataurl.split(,)const mime arr[0].match(/:(.*?);/)[1]const suffix mime.split(/)[1]const bstr a…

Windows安装postgresql数据库图文教程

数据库使用排行榜&#xff1a;https://db-engines.com/en/ranking 目录 一、软件简介 二、软件下载 三、安装教程 四、启动教程 一、软件简介 PostgreSQL是一种特性非常齐全的自由软件的对象-关系型数据库管理系统&#xff08;ORDBMS&#xff09;&#xff0c;是以加州大学计…

Python采集二手房源数据信息并做可视化展示

目录标题 前言环境使用:模块使用:python技术实现: <基本流程步骤>代码展示尾语 前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! 环境使用: Python 3.8 jupyter --> pip install jupyter notebook pycharm 也可以 模块使用: requests >>> pip instal…

【MySQL 数据查询】:提高查询的效率

前言 ✨欢迎来到小K的MySQL专栏&#xff0c;本节将为大家带来MySQL数据查询的讲解&#xff08;基本、分组、排序、聚合、分页、条件查询&#xff09; 目录 前言一、基本查询二、条件查询三、聚合函数(统计函数)四、分组查询五、排序查询五、分页查询六、总结 一、基本查询 MySQ…

2020年CSP-J认证 CCF非专业级别软件能力认证第一轮真题--阅读程序题

2020 CCF认证第一轮&#xff08;CSP-J&#xff09;真题 二、阅读程序题 (程序输入不超过数组或字符串定义的范围&#xff0c;判断题正确填√错误填X;除特殊说明外&#xff0c;判断题 1.5分&#xff0c;选择题3分&#xff0c;共计4 分) 第一题 01 #include <cstdlib> …

Java调优

Java调优 Java 性能调优不像是学一门编程语言&#xff0c;无法通过直线式的思维来掌握和应用&#xff0c;它对于工程师的技术广度和深度都有着较高的要求。 互联网时代&#xff0c;一个简单的系统就囊括了应用程序、数据库、容器、操作系统、网络等技术&#xff0c;线上一旦出…

【深度学习】GPT-1

GPT-1是OpenAI在《Improving Language Understanding by Generative Pre-Training》中于2018年提出的生成式预训练语言模型。 1.GPT-1 简介 在自然语言处理任务中&#xff0c;存在大量无标签的语料数据&#xff0c;而有标签的语料数据相对较少&#xff0c;因此基于有监督训练的…