vue3选中商品加入购物车

34 阅读4分钟

gwc.gif

简介

基于vue3做的一个小玩意,点击标签加入购物车,话不多说,先献丑上全部的代码(其中大部分class用的是自己公共样式,但不影响功能),然后再分别细细讲解一下个人的小思路。

代码

<template>
    <div class="flex flex-column position-relative">
        <div class="flex justify-center align-center mt-1">
            <el-icon :size="20"><Bowl /></el-icon>
            <span class="ml-1 font-weight-bold">好的,今天我们来做菜!</span>
        </div>

<!--       关键代码-->
        <transition
                appear
                @before-appear="beforeEnter"
                @after-appear="afterEnter"
                v-for="(item, index) in showBall.boolean"
                :key="index"
        >
            <div class="ball rounded"
                 :class="[showBall.flag == 'vegetable' ? 'vegetable-item' : showBall.flag == 'meat' ? 'meat-item' : showBall.flag == 'food' ? 'food-item' : 'kitchenware-item' ]"
                 v-if="item">
                {{showBall.ball.name}}
            </div>
        </transition>

<!--        蔬菜-->
        <div class="font-weight-bold font-lg flex justify-center">{{data.name}}</div>
        <div class="flex flex-row justify-center mt-1">
            <div v-for="(item, i) in data.vegetable" :key="item.id" class="vegetable boxShadow">
                <div class="vegetable-item rounded" @click="addCart(item, 'vegetable')" :ref="el => { if(el) itemRefs.ref[item.id] = el }">
                    {{ item.name }}
                </div>
            </div>
        </div>

<!--        肉-->
        <div class="font-weight-bold font-lg flex justify-center mt-2">🥩 肉肉们</div>
        <div class="flex flex-row justify-center mt-1">
            <div v-for="(item, i) in data.meat" :key="item.id" class="meat boxShadow">
                <div class="meat-item rounded" @click="addCart(item, 'meat')" :ref="el => { if(el) itemRefs.ref[item.id] = el }">{{ item.name }}</div>
            </div>
        </div>

<!--        主食-->
        <div class="font-weight-bold font-lg flex justify-center mt-2">🍚 主食也要一起下锅吗?(不选也行)</div>
        <div class="flex flex-row justify-center mt-1">
            <div v-for="(item, i) in data.food" :key="item.id" class="food boxShadow">
                <div class="food-item rounded" @click="addCart(item, 'food')" :ref="el => { if(el) itemRefs.ref[item.id] = el }">{{ item.name }}</div>
            </div>
        </div>

<!--        厨具-->
        <div class="font-weight-bold font-lg flex justify-center mt-2">🍳 再选一下厨具</div>
        <div class="flex flex-row justify-center mt-1">
            <div v-for="(item, i) in data.kitchenware" :key="item.id" class="kitchenware boxShadow">
                <div class="kitchenware-item rounded" @click="addCart(item, 'kitchenware')" :ref="el => { if(el) itemRefs.ref[item.id] = el }">{{ item.name }}</div>
            </div>
        </div>

        <div ref="card" class="cart-icon">🛒</div>

    </div>
</template>

<script setup lang='ts'>
  // 关于ref和reactive我已经全局导入了,没全局的这里补一下  import { ref, reactive } from 'vue';
  // 为了让你们更好区分,画dom部分方法我就用function,购物车部分用()=>

    let data = reactive({
        name: '🥘 先选一下食材',
        vegetable: [],
        meat: [],
        food: [],
        kitchenware: [],
    })

    function getVegetable() {
        data.vegetable = [
            { name: '🥔土豆', id: 'td' },
            { name: '🥕胡萝卜', id: 'hlb' },
            { name: '🥦花菜', id: 'hc' },
            { name: '🥣白萝卜', id: 'blb' },
            { name: '🥒西葫芦', id: 'xhl' },
            { name: '🍅番茄', id: 'fq' },
            { name: '🥬芹菜', id: 'qc' },
            { name: '🥒黄瓜', id: 'hg' },
            { name: '🧅洋葱', id: 'yc' },
            { name: '🥫莴笋', id: 'ws' },
            { name: '🍄菌菇', id: 'jg' },
            { name: '🍆茄子', id: 'qz' },
            { name: '🍲豆腐', id: 'df' },
            { name: '🥗包菜', id: 'bc' },
            { name: '🥬白菜', id: 'bc' },
        ]
    }

    function getMeat() {
        data.meat = [
            { name: '🥓午餐肉', id: 'wcr' },
            { name: '🌭香肠', id: 'xc' },
            { name: '🥓腊肠', id: 'lr' },
            { name: '🐤鸡肉', id: 'jr' },
            { name: '🐷猪肉', id: 'zr' },
            { name: '🐣鸡蛋', id: 'jd' },
            { name: '🍤虾', id: 'x' },
            { name: '🐮牛肉', id: 'nr' },
            { name: '🦴骨头', id: 'gt' },
            { name: '🐟鱼', id: 'y' },
        ]
    }

    function getFood() {
        data.food = [
            { name: '🍜面食', id: 'ms' },
            { name: '🍞面包', id: 'mb' },
            { name: '🍚米', id: 'm' },
            { name: '🍜方便面', id: 'fbm' },
        ]
    }

    function getKitchenware() {
        data.kitchenware = [
            { name: '😉烤箱', id: 'kx' },
            { name: '😋空气炸锅', id: 'kqzg' },
            { name: '😃微波炉', id: 'wbl' },
            { name: '😊电饭煲', id: 'dfb' },
            { name: '😎一口能炒又能煮的大锅', id: 'dg' },
        ]
    }

    let itemRefs = reactive({
        ref: [], //所有商品的dom结构
        left: 0, // 当前购物车的left
        top: 0,
    })
    let card = ref(null) // 购物车的位置信息

    let showBall = reactive({
        boolean: [],  // 控制商品是否显示
        ball: {}, // 商品数据
        flag: null, // 商品class
    })

    // 动画开始了
    const beforeEnter = (el)=>{
        el.style.transform = `translate3d(${itemRefs.left}px,${itemRefs.top}px,0)`
    }

    // 动画结束了
    const afterEnter = (el)=>{
        let {left, top} = card.value.getBoundingClientRect() // 获取购物车位置
        el.style.transform = `translate3d(${left}px,${top}px,0)`
        //增加移动路径
        el.style.transition = 'transform .55s cubic-bezier(0.3,-0.25,0.7,-0.15)'
        el.style.transition = 'transform .55s linear'
        showBall.boolean = showBall.boolean.map(item => false) // 隐藏商品
    }

    const addCart = (item, flag)=>{ //点击商品按钮后,获取其位置信息
        showBall.ball = item; // 
        showBall.boolean.push('true');
        showBall.flag = flag;
        itemRefs.left = itemRefs.ref[item.id].getBoundingClientRect().left;
        itemRefs.top = itemRefs.ref[item.id].getBoundingClientRect().top;
    }


    function init() {
        getVegetable();
        getMeat();
        getFood();
        getKitchenware();
    }

    init()
</script>

<style scoped lang="scss">
    .vegetable, .meat, .food, .kitchenware{
        margin: 0 5px;
        &-item{
            background-color: #ddf2e7;
            color: #166534;
            padding: 2px 5px;
            font-size: 14px;
            cursor: pointer;
        }
        &-active {
            background-color: #2cab5b;
        }
    }

    .meat-item{
        background-color: rgb(244, 227, 230);
        color: #bf3e3c;
    }

   .kitchenware-item{
       background-color: #f1f1f4;
       color: #000;
   }

   .food-item{
       background-color: #f5efd3;
       color: #c69254;
   }

   .boxShadow{
       box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.15);
   }

   /* 商品选项 */
   .ball{
       max-width: 65px;
       position: fixed;
       top: 0px;
       left: 0px
   }

/*    购物车*/
   .cart-icon {
       position: fixed; /* 或根据需要调整 */
       bottom: 50px; /* 或根据需要调整 */
       right: 50px; /* 或根据需要调整 */
       font-size: 2em; /* 根据需要调整大小 */
   }
</style>

思路

  1. 先创建选项,以蔬菜为例(具体代码上面已全部贴出) image.png
<div v-for="(item, i) in data.vegetable" :key="item.id" class="vegetable boxShadow">
    <div class="vegetable-item rounded" @click="addCart(item, 'vegetable')" :ref="el => { if(el) itemRefs.ref[item.id] = el }">
        {{ item.name }}
    </div>
</div>

给每个蔬菜标签设置对应id的ref并存入itemRefs(:ref="el => { if(el) itemRefs.ref[item.id] = el }"),点击addCart通过id方便获取选中dom的具体位置(left,top)

// 创建itemRefs存储dom
let itemRefs = reactive({
    ref: [], // 所有蔬菜的ref
    left: 0, // 当前购物车的left
    top: 0,
})


const addCart = (item, flag)=>{ //点击蔬菜按钮后,获取其位置信息
    ...// 获取蔬菜标签详细信息代码,上面完整代码有,这里不展示
    itemRefs.left = itemRefs.ref[item.id].getBoundingClientRect().left; // 通过id获取left
    itemRefs.top = itemRefs.ref[item.id].getBoundingClientRect().top;// 通过id获取top
}

2.加入购物车通过vue3的内置组件transition来完成,@before-appear获取点击前的位置,并在蔬菜标签位置生成一个同款商品,@after-appear获取购物车的位置,进行一个简单路径移动就完成了

<transition
        appear
        @before-appear="beforeEnter"
        @after-appear="afterEnter"
        v-for="(item, index) in showBall.boolean"
        :key="index"
>

<!--        和选中蔬菜标签同款样式的标签-->
    <div class="ball rounded"
         :class="[showBall.flag == 'vegetable' ? 'vegetable-item' : showBall.flag == 'meat' ? 'meat-item' : showBall.flag == 'food' ? 'food-item' : 'kitchenware-item' ]"
         v-if="item">
        {{showBall.ball.name}}
    </div>
    
</transition>

<!-- 购物车-->
<div ref="card" class="cart-icon">🛒</div>

let card = ref(null) // 购物车的位置信息

let showBall = reactive({
    boolean: [],  // 控制商品是否显示
    ball: {}, // 商品数据
    flag: null, // 商品class
})

// 动画开始了,将商品位置设置成前面点击蔬菜时获取的位置
const beforeEnter = (el)=>{
    el.style.transform = `translate3d(${itemRefs.left}px,${itemRefs.top}px,0)`
}

// 动画结束了
const afterEnter = (el)=>{
    let {left, top} = card.value.getBoundingClientRect() // 获取购物车位置
    el.style.transform = `translate3d(${left}px,${top}px,0)`
    //增加移动路径
    el.style.transition = 'transform .55s cubic-bezier(0.3,-0.25,0.7,-0.15)'
    el.style.transition = 'transform .55s linear'
    showBall.boolean = showBall.boolean.map(item => false) // 隐藏商品
}