Vue3 + Vuex4 构建点餐页面

不才
• 阅读 999

前言

前进!前进!不择手段地前进!!

Vue3 + Vuex4 构建点餐页面

距离Vue3.0 beta 发布已经过了半个多月了。本来这个东西上个月就应该写了,由于公司上个月赶项目一直没时间。趁着劳动节把这个东西写了一下,也顺便把一些坑过了一下。

介绍

页面比较简单,算是把 Composition API 过了一下了 Vue3 + Vuex4 构建点餐页面

  • 基于Vue3.0 beta 这种页面也比较老套
  • 涉及了 Vue3.0 beta and Vuex4 beta and axios

安装vue3环境

题外话: 听说vite不错,这几天得了解下🤪

  1. 安装(升级)最近的vue-cli
  2. vue create projectName ps: 如果自己不会手动装vuex4或者vue-router4这些库最好是直接安装步骤全部安装(减少一些踩坑的时间成本)
  3. vue add vue-next 这个命令会把项目中的一些依赖自动升级成支持vue3的版本
  4. npm run serve

Composition-API

这里建议直接看文档 https://vue-composition-api-rfc.netlify.app/#summary

🤐直接贴代码了

毕竟思路都差不多,可以的话建议直接看 https://github.com/notbucai/vue3-demo

useScroll

import { ref } from 'vue';
export const useScrollTop = () => {
  const top = ref(0);
  window.addEventListener('scroll', () => {
    const scrollTop = document.documentElement.scrollTop;
    top.value = scrollTop;
  });
  return top;
}

store

import { createStore } from "vuex";

export default createStore({
  state: {
    shopcart: {},
    foodList: [],
  },
  getters: {
    // TODO: 这里的金额计算 是"有问题"的
    // 计算金额与数量
    settlement(state) {

      let allPrice = 0;
      let allCount = 0;

      const shopcart = state.shopcart;
      const foodList = state.foodList;
      // 得到所有食物列表
      const foods = foodList.reduce((previousValue, currentValue) => {
        return previousValue.concat(currentValue.foods);
      }, []);
      // 通过购物车计算金额数量
      Object.keys(shopcart).forEach(key => {
        const count = shopcart[key];
        if (!count) return;
        const food = foods.find(item => item.item_id == key);
        const price = food.specfoods[0].price;
        // 这里只是一种简单的处理浮点金额的方案(不保险),实际上后端直接返回以分为单位的金额会更好,【在后端计算金额一定情况下可能更靠谱】
        allPrice += (price * 10000 * count);
        allCount += count;
      });

      return {
        price: allPrice / 10000,
        count: allCount,
      }
    }
  },
  mutations: {
    ADD_ITEM(state, id) {
      state.shopcart[id] = state.shopcart[id] || 0;
      state.shopcart[id]++;
    },
    SUB_ITEM(state, id) {
      state.shopcart[id] = state.shopcart[id] || 0;
      state.shopcart[id]--;
    },
    SET_FOOD_LIST(state, payload) {
      state.foodList = payload;
    }
  },
  actions: {
    setFoodList({ commit }, payload) {
      commit('SET_FOOD_LIST', payload)
    },
    addItem({ commit }, id) {
      commit('ADD_ITEM', id)
    },
    subItem({ commit }, id) {
      commit('SUB_ITEM', id)
    },
  }
});

页面

<template>
  <div class="Catering">
    <header class="header">
      <div class="shop">
        <div class="shop-pic">
          <img
            src="https://upload.jianshu.io/users/upload_avatars/7759683/995f635d-357b-4539-b008-55aa6b0ac140.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/96/h/96/format/webp"
            alt
          />
        </div>
        <div class="shop-name">一袋米</div>
      </div>
      <div class="navbar">
        <div class="nav-item">点餐</div>
        <div class="nav-item">评价</div>
        <div class="nav-item">商家</div>
      </div>
      <div class="outher-box"></div>
    </header>
    <main class="main">
      <div class="left">
        <div
          class="left-item"
          v-for="(item, index) in list"
          :class="{active:elIndex == index}"
          :key="index"
          @click="srcollTo(index)"
        >{{item.name}}</div>
      </div>
      <div class="right">
        <template v-for="(_item, index) in list">
          <!-- 这里的ref就又点难受了 -->
          <div :key="index" :ref="el=>itemEls[index] = el">
            <food-item :item="_item" />
          </div>
        </template>
      </div>
    <settlement-bottom />
    </main>
  </div>
</template>
<script>
import { watch, ref, onBeforeUpdate, onMounted, computed } from 'vue';
import { useStore } from 'vuex';
import { useScrollTop } from '../hooks';
import { getList } from '../ajax/index';
import FoodItem from './components/FoodItem';
import SettlementBottom from './components/SettlementBottom';

export default {
  components: { FoodItem, SettlementBottom },
  setup() {
    // store
    const store = useStore();

    const list = computed(() => store.state.foodList);

    onMounted(async () => {
      // NOTE: 获取数据
      let resList = await getList();
      // 存入store
      store.dispatch('setFoodList', resList);
    });

    // 组节点列表
    const itemEls = ref([]);
    // 当前选中节点
    const elIndex = ref(0);

    // 监听数据
    const scrollTop = useScrollTop([]);

    onBeforeUpdate(() => {
      // 更新时清空
      itemEls.value = [];
    });

    // 监听滚动条变化
    watch(scrollTop, () => {
      let index = 0;
      // 判断距离头部最近的元素 实际上这里是可以缓存高度的
      itemEls.value.forEach((el, i) => {
        // console.log('el=>',el,'val=>', scrollTop.value);
        if (el.offsetTop > scrollTop.value) return;
        index = i;
      });
      // 绑定节点
      elIndex.value = index;
    });

    const srcollTo = index => {
      elIndex.value = index;
      const el = itemEls.value[index];
      const offsetTop = el.offsetTop;
      window.scrollTo(0, offsetTop);
    };

    return {
      list,
      itemEls,
      elIndex,
      srcollTo
    };
  }
};
</script>
<style lang="scss">
......
</style>

FoodItem.vue

<template>
  <div class="FoodItem">
    <div class="food-title">{{item.name}}</div>
    <div class="food-item" v-for="(food) in item.foods" :key="food.item_id">
      <div class="food-pic">
        <img
          src="https://upload.jianshu.io/users/upload_avatars/16175630/e2ee85e5-7cb0-429d-a517-bb1c6f1833e4?imageMogr2/auto-orient/strip|imageView2/1/w/80/h/80/format/webp"
          alt
        />
      </div>
      <div class="food-main">
        <div class="food-info">
          <div class="title">{{food.name}}</div>
          <div class="info">{{food.applicable_quantity_text}}</div>
        </div>
        <div class="money">¥{{food.specfoods[0].price}}</div>
      </div>
      <div class="food-action">
        <div class="action-item minus" v-if="shopcart[food.item_id]" @click="handleMinus(food)">-</div>
        <div class="action-num" v-if="shopcart[food.item_id]">{{shopcart[food.item_id]}}</div>
        <div class="action-item puls" @click="handlePuls(food)">+</div>
      </div>
    </div>
  </div>
</template>
<script>
// import { reactive, unref } from 'vue';
import { useStore } from 'vuex';
import { computed } from 'vue';
export default {
  components: {},
  props: {
    item: Object
  },
  setup() {
    // store
    const store = useStore();
    // 购物车
    const shopcart = computed(() => store.state.shopcart);

    const handleChangeCount = (food, type) => {
      const id = food.item_id;
      const actionName = type > 0 ? 'addItem' : 'subItem';
      store.dispatch(actionName, id);
    };

    const handleMinus = food => {
      handleChangeCount(food, -1);
    };
    const handlePuls = food => {
      handleChangeCount(food, 1);
    };

    return {
      shopcart,
      handleMinus,
      handlePuls
    };
  }
};
</script>
<style lang="scss" scoped>
......
</style>

SettlementBottom.vue

<template>
  <div class="SettlementBottom">
    <div class="num_box">x{{settlement.count}}</div>
    <div class="price_box">¥{{settlement.price}}</div>
    <div class="submit_btn" @click="handleSubmit">结算</div>
  </div>
</template>
<script>
import { useStore } from 'vuex';
import { computed } from 'vue';
export default {
  props: {},
  setup() {
    const store = useStore();
    const settlement = computed(() => store.getters.settlement);
    const handleSubmit = () => {
      const sc = store.state.shopcart;
      const shopcart = Object.keys(sc).map(key => ({
        id: key,
        count: sc[key]
      }));

      console.log('shopcart=>', shopcart);
      console.log('如果需要原食物的参数可以直接去state中获取');
    };
    return {
      settlement,
      handleSubmit
    };
  }
};
</script>
<style lang="scss" scoped>
......
</style>

github notbucai

notbucai blog

点赞
收藏
评论区
推荐文章
秃头王路飞 秃头王路飞
4个月前
webpack5手撸vue2脚手架
webpack5手撸vue相信工作个12年的小伙伴们在面试的时候多多少少怕被问到关于webpack方面的知识,本菜鸟最近闲来无事,就尝试了手撸了下vue2的脚手架,第一次发帖实在是没有经验,望海涵。languageJavaScript"name":"vuecliversion2","version":"1.0.0","desc
光头强的博客 光头强的博客
4个月前
Java面向对象试题
1、请创建一个Animal动物类,要求有方法eat()方法,方法输出一条语句“吃东西”。创建一个接口A,接口里有一个抽象方法fly()。创建一个Bird类继承Animal类并实现接口A里的方法输出一条有语句“鸟儿飞翔”,重写eat()方法输出一条语句“鸟儿吃虫”。在Test类中向上转型创建b对象,调用eat方法。然后向下转型调用eat()方
刚刚好 刚刚好
4个月前
css问题
1、在IOS中图片不显示(给图片加了圆角或者img没有父级)<div<imgsrc""/</divdiv{width:20px;height:20px;borderradius:20px;overflow:h
blmius blmius
1年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
小森森 小森森
4个月前
校园表白墙微信小程序V1.0 SayLove -基于微信云开发-一键快速搭建,开箱即用
后续会继续更新,敬请期待2.0全新版本欢迎添加左边的微信一起探讨!项目地址:(https://www.aliyun.com/activity/daily/bestoffer?userCodesskuuw5n)\2.Bug修复更新日历2.情侣脸功能大家不要使用了,现在阿里云的接口已经要收费了(土豪请随意),\\和注意
晴空闲云 晴空闲云
4个月前
css中box-sizing解放盒子实际宽高计算
我们知道传统的盒子模型,如果增加内边距padding和边框border,那么会撑大整个盒子,造成盒子的宽度不好计算,在实务中特别不方便。boxsizing可以设置盒模型的方式,可以很好的设置固定宽高的盒模型。盒子宽高计算假如我们设置如下盒子:宽度和高度均为200px,那么这会这个盒子实际的宽高就都是200px。但是当我们设置这个盒子的边框和内间距的时候,那
艾木酱 艾木酱
3个月前
快速入门|使用MemFire Cloud构建React Native应用程序
MemFireCloud是一款提供云数据库,用户可以创建云数据库,并对数据库进行管理,还可以对数据库进行备份操作。它还提供后端即服务,用户可以在1分钟内新建一个应用,使用自动生成的API和SDK,访问云数据库、对象存储、用户认证与授权等功能,可专
密钥管理系统-为你的天翼云资产上把“锁
本文关键词:数据安全,密码机,密钥管理一、你的云上资产真的安全么?1.2021年1月,巴西的一个数据库30TB数据被破坏,泄露的数据包含有1.04亿辆汽车和约4000万家公司的详细信息,受影响的人员数量可能有2.2亿;2.2021年2月,广受欢迎的音频聊天室应用Clubhouse的用户数据被恶意黑客或间谍窃取。据悉,一位身份不明的用户能够将Clubho
helloworld_28799839 helloworld_28799839
4个月前
常用知识整理
Javascript判断对象是否为空jsObject.keys(myObject).length0经常使用的三元运算我们经常遇到处理表格列状态字段如status的时候可以用到vue
NVIDIA安培架构下MIG技术分析
关键词:NVIDIA、MIG、安培一什么是MIG2020年5月,NVIDIA发布了最新的GPU架构:安培,以及基于安培架构的最新的GPU:A100。安培提供了许多新的特性,MIG是其中一项非常重要的新特性。MIG的全名是MultiInstanceGPU。NVIDIA安培架构中的MIG模式可以在A100GPU上并行运行七个作业。多实