美文网首页
Vue3 + Vuex4 构建点餐页面

Vue3 + Vuex4 构建点餐页面

作者: 小生不才_ | 来源:发表于2020-05-04 14:39 被阅读0次

    前言

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

    image

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

    介绍

    页面比较简单,算是把 Composition API 过了一下了

    image
    • 基于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

    相关文章

      网友评论

          本文标题:Vue3 + Vuex4 构建点餐页面

          本文链接:https://www.haomeiwen.com/subject/qstyghtx.html