vue组件之间如何共享数据?

How to share data between vue components?

我不是 javascript 开发人员,我的工作是将我构建的 API 连接到第三方用 vuejs 编写的一些前端代码。我正在尝试访问 'shopping cart' 以将详细信息发送到服务器。我很难确定什么是 child 或 parent。流程如下:

这是产品卡片

<template>
    <div class="flex flex-col flex-wrap justify-center items-center w-1/3 px-12">
        <img :src="require(`../../assets/images/` + backgroundImg)" :alt="product.id"> 
        <h5 class="font-bold text-lg mt-2">{{ product.title }}</h5>
        <p class="text-15px text-gray-500 ">{{ product.short }}</p>
        <p class="text-15px text-gray-500 px-8">{{ product.long }}</p>
        <p class="text-base font-bold">${{ formatPrice(product.price) }}</p>
        <Button @click.native="$emit('add-cart', product)"  msg="Add to Cart" class="bg-seafoam rounded-md lg:text-sm lg:px-8 text-white mt-1"></Button>
    </div>
</template>

<script>
import Button from '../Button.vue';

export default {
    name: 'ProductCard',
    components: {
        Button
    },
    props: {
        product: Object
    },
    data() {
        return {
            backgroundImg: this.product.image,
        }
    },
    methods: {
        formatPrice(value) {
        let val = (value/1).toFixed(2)
        return val.toLocaleString("en", {useGrouping: false, minimumFractionDigits: 2,})
        }
    }
}
</script>

这是产品列表代码

     <template>
            <div>
                <span v-if="!hideMenu">
                    <MenuHero v-if="!viewCart" :locationName="locationName" :selectedVendor="selectedVendor" />
                </span>
                <CartHero v-if="viewCart" />
                <CheckoutHero v-if="showCheckout" />
                <div class="lg:px-40 pt-4 pb-16 m-auto max-w-" :class="{ 'bg-bgBlue': showCheckout, 'max-w-screen-2xl': !showCheckout }">
                    <div v-if="!hideBreadcrumb" class="breadcrumbs flex mb-6 ml-6">
                        <a href="/OrderResturantPicker" class="text-left mt-6 mb-12 lg:pl-0 text-sm lg:text-base bg-arrowRight font-bold text-darkBlueText bg-no-repeat bg-right bg-5px lg:bg-8px pr-3 lg:pr-6">Choose Restaurant</a>
                        <a @click="viewCart = false" class="text-left mt-6 mb-12 text-sm lg:text-base bg-arrowRight font-bold text-darkBlueText bg-no-repeat bg-right bg-5px lg:bg-8px pr-3 lg:pr-6 pl-1 lg:pl-4" >Choose Meal</a>
                        <a v-if="viewCart" class="breadcrumb text-left mt-6 mb-12 text-sm lg:text-base font-bold text-darkBlueText bg-no-repeat bg-right bg-5px lg:bg-8px pr-3 lg:pr-6 pl-1 lg:pl-4">View Cart</a>
                    </div>
                    <div class="flex flex-wrap section-wrapper">
                        <div v-if="!hideMenu" class="flex flex-wrap section-wrapper">
                            <div v-for="product in menu" :key="product.id" v-show="!viewCart" :class="{ sectionTitle: product.section }" class="flex flex-col w-full lg:mb-0 mb-6 lg:w-1/3">
                                <div v-if="product.section" class="text-left w-full text-darkBlueText lg:text-xl font-bold">{{ product.section }}</div>
                                <ProductCard @add-cart="addToCart(product)" :product="product" class="locationOption w-full flex py-2 px-6 lg:py-2 rounded-sm rounded-b-none text-gray-600 lg:text-lg md:text-base text-13px text-center cursor-pointer" />
                            </div>
                        </div>
                        <CartList :cart="cart" v-if="viewCart" @show-checkout="showCheckoutScreen()" :totalPrice="totalPrice" :product="product"/>
                        <CartWidget :cart="cart" v-if="!viewCart && showCartWidget" :itemCount="itemCount" @view-cart="viewCart = !viewCart" :totalPrice="totalPrice"/>
                        <Checkout :cart="cart" :product="product" :totalPrice="totalPrice" v-if="showCheckout" />
                    </div>
                </div>
            </div>
        </template>
        
        <script>
        import ProductCard from './ProductCard'
        import CartList from './TheCartList'
        import CartWidget from './CartWidget'
        import Checkout from './Checkout'
        import MenuHero from '../heros/MenuHero'
        import CartHero from '../heros/CartHero'
        import CheckoutHero from '../heros/CheckoutHero'
        
        export default {
            name: 'ProductList',
            components: {
                MenuHero,
                CartHero,
                CheckoutHero,
                ProductCard,
                CartList,
                CartWidget,
                Checkout,
                
            },
            props: {
                menu: Array,
                locationName: String,
                selectedVendor: String,
        
            },
            data() {
                return {
                    cart: [],
                    showCartWidget: false,
                    itemCount: 0,
                    totalPrice: 0,
                    viewCart: false, 
                    showCheckout: false,
                    hideMenu: false,
                    hideBreadcrumb: false,
                }
            },
            methods: {
                
                calculateTotal(item) {
                    this.totalPrice += item.price
                },
                addToCart(product) {
                    if(this.cart.includes(product)){
                        product.quantity++
                        
                        this.itemCount++
                        this.calculateTotal(product)
                    } else {
                        this.cart.push(product)
                        this.itemCount++
                        this.calculateTotal(product)
                        this.showCartWidget = true
                    }
                }, 
                showCheckoutScreen() {
                    this.showCheckout = true
                    this.viewCart = false
                    this.showCartWidget = false
                    this.hideMenu = true
                    this.hideBreadcrumb = true
                }
            },
        }
        </script>

----------
This is the cart list code

    <template>
        <div class="w-full">
            <div class="cart-heading-wrapper flex justify-between text-darkBlueText lg:text-xl font-bold">
                <h5 class="w-1/3 text-left">Item Name</h5>
                <h5 class="w-1/3 text-center ml-2 pl-16">Quantity</h5>
                <h5 class="w-1/3 text-right">Item Price</h5>
            </div>
            <div class="grid border-b border-gray-400">
                <div v-for="product in cart" :key="product.id" class="py-4 border-t border-gray-400">
                    <CartCard :product="product" :totalPrice="totalPrice" @incremented="calculateTotal(product)" @decremented="decrementTotal(product)" class="locationOption w-full flex py-2 lg:py-2 rounded-sm rounded-b-none text-gray-600 lg:text-lg md:text-base text-13px text-center cursor-pointer" />
                </div>
            </div>
    
            <div class="flex flex-col justify-start float-right">
                <div class="mb-4 px-28 mt-8 flex justify-between"><span class="mr-24 text-xl text-darkBlueText font-bold">Tax:</span><span class="text-2xl text-darkBlueText font-bold">${{taxes}}</span></div>
                <div class="mb-4 px-28 flex justify-between"><span class="mr-24 text-xl text-darkBlueText font-bold">Fees:</span><span class="text-2xl text-darkBlueText font-bold">${{ fees }} </span></div>
                <div class="border-t border-gray-500 mb-12 px-28 pt-4 flex justify-between"><span class="mr-24 text-2xl text-darkBlueText font-bold">Total:</span><span class="text-2xl text-darkBlueText font-bold">${{ formatPrice(totalPrice) }}</span></div>
            </div>
            <div class="w-full flex justify-between">
                <div class="flex flex-col">
                    <button class="py-2 px-16 mb-2 bg-white border-2 border-darkBlueText text-darkBlueText text-lg font-bold">Back to Menu</button>
                    <a class="underline" href="#">Browse Restaurants </a>
                </div>
                <span>
                    <button class="px-6 py-3 text-white font-bold bg-seafoam rounded-sm text-lg" @click="$emit('show-checkout')">Proceed To Checkout</button>
                </span>
                
            </div>
        </div>
    </template>
    
    <script>
    import CartCard from './CartCard';
    
    export default {
        name: 'CartList',
        components: {
            CartCard,
        },
        props: {
            cart: Array,
            totalPrice: Number,
            product: Object,
        },
        data() {
            return {
                taxes: 0,
                fees: 0,
                grandTotal: 0,
            }
        },
        methods: {
            formatPrice(value) {
            let val = (value/1).toFixed(2)
            return val.toLocaleString("en", {useGrouping: false, minimumFractionDigits: 2,})
            },
    
            calculateTotal(item) {
                this.totalPrice += item.price
            },
    
            decrementTotal(item) {
                this.totalPrice -= item.price
            },
        },
    }
    </script>

购物车小部件代码

<template>
    <div class="translateCenter left-1/2 m-auto w-1/2 max-w-5xl py-5 px-6 border flex justify-between bg-white fixed bottom-10 border-black rounded-md">
        <div class="font-bold flex items-center">
            <div class="bg-cartImage w-7 h-7 mr-2 font-bold bg-no-repeat bg-cover text-15px bg-right pr-5"></div>
            {{ itemCount }} Item(s)
        </div>
        <div class="flex items-center flex-grow ml-16">
            <div class="rounded-full bg-black h-3 w-3 mr-4"></div>
            <div class="flex items-center pt-px">
                <strong class="mr-2">Total:</strong> ${{ formatPrice(totalPrice) }}
            </div>
          
        </div>
        <div class="flex items-center">
            <div @click="$emit('view-cart')" class="bg-arrowRight font-bold bg-no-repeat bg-8px text-15px bg-right pr-5">View Cart</div>
        </div>
    </div>
</template>

<script>
export default {
    name: "CartWidget",
    props: {
        cart: Array,
        itemCount: Number,
        totalPrice: Number,
    },
    data() {
        return {
            total: 0,
        }
    },
    methods: {
        formatPrice(value) {
        let val = (value/1).toFixed(2)
        return val.toLocaleString("en", {useGrouping: false, minimumFractionDigits: 2,})
        }
    }
}
</script>

购物车卡片代码

<template>
    <div class="flex flex-col flex-wrap justify-center items-center w-1/3">
        <div class="flex justify-between items-center w-full">
            <div class="flex w-1/3">
                <img class="w-32 h-32" :src="require(`../../assets/images/` + backgroundImg)" :alt="product.id">
                <div class="flex flex-col justify-center ml-4 text-left">
                    <h5 class="mb-4 text-xl text-darkBlueText font-bold">{{ product.title }}</h5>
                    <p class="text-15px text-gray-500 leading-5">{{ product.short }}</p>
                    <p class="text-15px text-gray-500 leading-5 whitespace-nowrap overflow-ellipsis overflow-hidden block w-3/4">{{ product.long }}</p>
                </div> 
            </div>
           
            <Quantity class="font-bold" :product="product" :totalPrice="totalPrice" @incremented="incremented()" @decremented="decremented()" />
            <p class="font-bold">${{ formatPrice(product.price) }}</p>
        </div>
    </div>
</template>

<script>
import Quantity from './Quantity';

export default {
    name: 'ProductCard',
    components: {
        Quantity
    },
    props: {
        product: Object,
        totalPrice: Number
    },
    data() {
        return {
            backgroundImg: this.product.image,
            cart: []
        }
    },
    methods: {
        formatPrice(value) {
        let val = (value/1).toFixed(2)
        return val.toLocaleString("en", {useGrouping: false, minimumFractionDigits: 2,})
        },
        incremented() {
            this.$emit('incremented')
        },
        decremented() {
            this.$emit('decremented')
        }
    }
}
</script>

和结帐代码

<template>
    <div>
        <div class="apply-wrapper bg-bgBlue lg:p-32 lg:pt-12">
        <div class="form-wrapper  lg:bg-white rounded-2xl bg-bgBlue py-6 px-8 lg:py-12 lg:px-32 lg:shadow-lg max-w-screen-2xl m-auto">
            <form class="lg:flex flex-wrap m-auto">
                <h4 class="text-darkBlueText text-center text-xl font-bold mb-12 w-full">Contact and Delivery Information</h4>
                <div class="mb-8 lg:mb-4 text-left text-lg lg:text-xl lg:w-1/2 lg:pr-12">
                    <label for="FirstName">First Name</label>
                    <input v-model="firstName" class="mt-2 h-8 lg:h-10 appearance-none border rounded border-black w-full py-3 px-3 text-gray-700 text-sm leading-tight" id="firstname" type="text" name="FirstName" required>
                </div>
                <div class="mb-7 lg:mb-4 text-left text-lg lg:text-xl lg:w-1/2 lg:pl-12">
                    <label for="LastName">Last Name</label>
                    <input v-model="lastName" class="mt-2 h-8 lg:h-10 text-sm appearance-none rounded border border-black w-full py-3 px-3 text-gray-700 mb-3 leading-tight" id="lastname" type="text" name="LastName" required>
                </div>
                <div class="mb-8 lg:mb-4 text-left text-lg lg:text-xl lg:w-1/2 lg:pr-12">
                    <label for="PhoneNumber">Phone Number</label>
                    <input v-model="phone" class="mt-2 h-8 lg:h-10 lg:mt-1 appearance-none rounded border border-black w-full py-3 px-3 text-gray-700 text-sm leading-tight" id="phonenumber" type="tel" name="PhoneNumber" required>
                </div>
                <div class="mb-8 lg:mb-4 text-left text-lg lg:text-xl lg:w-1/2 lg:pl-12">
                    <label for="EmailAddress">Email Address</label>
                    <input v-model="email" class="mt-2 h-8 lg:h-10 lg:mt-1 appearance-none rounded border border-black w-full py-3 px-3 text-gray-700 text-sm leading-tight" id="emailaddress" type="email" name="EmailAddress" required>
                </div>
                <div class="mb-8 lg:mb-4 inline-block text-left text-lg lg:text-xl w-1/2 pr-2 lg:pr-12">
                    <label for="Terminal" class="whitespace-nowrap">Terminal</label>
                    <input v-model="terminal" class="mt-2 h-8 lg:h-10 lg:mt-1 text-sm appearance-none rounded border border-black w-full py-3 px-3 text-gray-700 mb-3 leading-tight" id="terminal" type="text" name="Terminal" required>
                </div>
                <div class="mb-8 lg:mb-4 inline-block text-left text-lg lg:text-xl w-1/2 pl-2 lg:pl-12">
                    <label for="Gate">Gate</label>
                    <input v-model="gate" class="mt-2 h-8 lg:h-10 lg:mt-1 text-sm appearance-none rounded border border-black w-full py-3 px-3 text-gray-700 mb-3 leading-tight" id="gate" type="text" name="Gate" required>
                </div>

                <div class="w-1/2 m-auto">
                    <div class="mb-8 lg:mb-4 inline-block text-left text-lg lg:text-xl w-1/2 pl-2 lg:pl-12">
                        <label for="Tip">Leave A Tip?</label>
                        <currency-input v-model="tip" id="leaveATip" currency="USD" name="Tip" class="mt-2 h-8 lg:h-10 lg:mt-1 text-sm appearance-none rounded border border-black w-full py-3 px-3 text-gray-700 mb-3 leading-tight" />
                    </div>
                    <div>
                        <label for="DeliveryDate" class="block w-full">When Should We Deliver?</label>
                        <input v-model="date" type="date" id="DeliveryDateTime" name="DeliveryDate"
                            value="Today"
                            min="2021-01-01" max="2040-12-31" required>
                        <input v-model="time" type="time" id="delTime" name="DeliveryTime"
                            min="00:00" max="23:59" required>
                    </div>
                </div>
                <!-- stripe div -->
                
                <section class="row payment-form">
                    <h5 class="#e0e0e0 grey lighten-4">
                        Payment Method
                        <span class="right"></span>
                    </h5>

                    <div class="error red center-align white-text"> {{stripeValidationError}}</div>

                        <div class="col s12 card-element">
                            <label>Card Number</label>
                            <div id="card-number-element" class="input-value"></div>
                        </div>

                    <div class="col s6 card-element">
                        <label>Expiry Date</label>
                        <div id="card-expiry-element"></div>
                    </div>

                    <div class="col s6 card-element">
                        <label>CVC</label>
                        <div id="card-cvc-element"></div>
                    </div>

                    <div class="col s12 place-order-button-block">
                        <button class="btn col s12 #e91e63 pink" @click="placeOrderButtonPressed">Place Order</button>
                    </div>
                </section>
                
                <CartList :cart="cart" v-if="viewCart" :totalPrice="totalPrice" :product="product"/> 
            
            </form>
        </div>
    </div>
    </div>
</template>

<script>
import ProductList from "./ProductList";

export default {
    name: 'Checkout',
    components: {
        ProductList,
    },
    props: {
        cart: Array
    },
    data(){
       console.log("[DEBUG 1]: " + this.cart + " " + this.cart.product.id + " " + this.cart.product.short);
        return {
            viewCart: true,
            stripe: null,
            cardNumberElement:null,
            cardExpiryElement:null,
            cardCVCElement:null,
            stripeValidationError:null,
            firstName:'',
            lastName:'',
            phone:'',
            email:'',
            terminal:'',
            gate:'',
            tip:'',
            amount:25,
        }
        
    },
    mounted(){
        this.stripe = new Stripe("MY_KEY");
        this.init();
        this.testCart();
    },
    methods: {
        testCart(){
            console.log("[DEBUG 3]: " + this.cart + " " + this.cart.product.id + " " + this.cart.product.short);
            this.cart.printCart();
        },

        init(){
            // invoke and mount
            var elements = this.stripe.elements();

            this.cardNumberElement = elements.create("cardNumber");
            this.cardNumberElement.mount("#card-number-element");

            this.cardExpiryElement = elements.create("cardExpiry");
            this.cardExpiryElement.mount("#card-expiry-element");

            this.cardCVCElement = elements.create("cardCvc");
            this.cardCVCElement.mount("#card-cvc-element");
            
            // change events
            this.cardNumberElement.on("change", this.setValidationError);
            this.cardExpiryElement.on("change", this.setValidationError);
            this.cardCVCElement.on("change", this.setValidationError);
            
        },
        setValidationError(event){
            this.stripeValidationError = event.error ? event.error.message : "";
        },
        async processToken(data){
            const response = await fetch('https://asite.azurewebsites.net/api/payment', {
                method: 'POST',
                headers: {
                    'Accept':'application/json',
                    'Access-Control-Allow-Origin':'*'
                },
                body: JSON.stringify(data)
            });
            const content = await response;
            console.log(content);

            if(content[2] === "True")
            {
                // success
                // create a new swift oder
                // how do I get access to the cart?
            }
        },
        placeOrderButtonPressed(){
            
            this.stripe.createToken(this.cardNumberElement).then(result => {
                if(result.error){
                    this.stripeValidationError = result.error.message;
                } 
                else {
                    var json = {
                        amount: this.amount,
                        source: result.token.id
                    }
                    this.processToken(json);
                }
            });
        }
    }
};

</script>

我想访问购物车中的商品,这样我就可以向服务器发出 post 调用。但我似乎无法做到这一点。

我不需要在屏幕上显示项目或其他任何东西。我只是想访问购物车,它似乎是一个总计项目的数组。

谁能指出我正确的方向?

顺便说一句,产品是一个 object,具有 ID、标题、简短描述、详细描述、价格和默认数量 1。

您的 cart 在您的 ProductList 中定义。您在该组件内实现所有其他组件,因此它是父组件。它可以通过添加 :cart="cart" 属性 将购物车中的项目分配给所有其他组件。为了让子组件访问它,他们只需要在收到的道具中声明 cart

props: {
  cart: Array
}

然后您可以在子组件中访问this.cart