vue 自定义image组件

本文介绍了如何在Vue中创建一个自定义的Image组件,处理图片的加载状态和显示模式。当图片加载失败或加载中时,提供相应的提示。通过CSS的object-fit属性控制图片适应容器的方式,包括等比缩放、裁剪和填充等。同时分享了个人项目的Vue组件代码库链接。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

介绍

1:当图片加载失败时,给出错误提示。

 

2:当图片加载中时,给出加载提示。

 

3:图片处理模式:等比缩放/裁剪/填充/...

 

1、图片加载状态处理

 通过给图片绑定load事件与error事件处理函数来判断图片加载状态。当图片加载完成时会触发load事件;图片加载出错会触发error事件

// 样本
<img src="..." @load=onLoad @error=onError>

 

2、图片模式

 通过css属性 object-fit(https://developer.mozilla.org/zh-CN/docs/Web/CSS/object-fit) 来控制图片如何适应容器大小。

 

 

3、代码

<template>
    <div class="tm-image" :style="style" @click="onClick">
        <img :src="src"
             :alt="alt"
             :style="{'object-fit': mode}"
             @load="onLoad"
             @error="onError">
        <div v-if="this.loading" class="tm-image-load">
            <slot name="loading">加载中</slot>
        </div>
        <div v-if="this.error" class="tm-image-error">
            <slot name="error">加载出错</slot>
        </div>
    </div>
</template>
<script>
    import suffixPx from '../../utils/suffixPx.js';

    export default {
        name: "tm-image",
        data() {
            return {
                loading: true,
                error: false
            }
        },
        props: {
            src: String,
            alt: String,
            mode: {  // 模式:https://developer.mozilla.org/zh-CN/docs/Web/CSS/object-fit
                type: String,
                default: "fill",
                validator(value) {
                    return ['contain', 'cover', 'fill', 'none', 'scale-down'].includes(value);
                }
            },
            width: [String, Number],
            height: [String, Number]
        },
        computed: {
            style() {
                return {
                    width: suffixPx(this.width),
                    height: suffixPx(this.height)
                }
            }
        },
        methods: {
            onClick(event) {
                this.$emit('click', event);   // 向父节点传递一个自定义事件
            },
            onLoad(event) {
                this.loading = false;
                this.$emit('loading', event); // 向父节点传递一个自定义事件
            },
            onError(event) {
                this.loading = false;
                this.error = true;
                this.$emit('error', event); // 向父节点传递一个自定义事件
            }
        }
    }
</script>
<style scoped>
    .tm-image{
        position: relative;
        overflow: hidden;
    }
    .tm-image .tm-image-load,
    .tm-image .tm-image-error{
        position: absolute;
        top:0;
        left: 0;
        width: 100%;
        height: 100%;
        background: #f2f2f2;
        color: #666;
        display: flex;
        align-items: center;
        justify-content: center;
    }
    .tm-image img {
        width: 100%;
        height: 100%;
    }
</style>

 

个人项目

Vue组件https://gitee.com/whnba/component

 

淘宝天猫粉丝专享优惠券

转载于:https://www.cnblogs.com/whnba/p/10961510.html

<think>我们要实现一个Vue3的自定义组件购物车功能。购物车组件通常需要以下功能: 1. 显示购物车中的商品列表(包括商品名称、单价、数量、小计等) 2. 可以增加或减少商品数量,也可以删除商品 3. 显示购物车总金额 4. 购物车数据可能需要持久化(如使用localStorage)或者与后端交互(这里我们先做前端部分) 我们将使用Vue3的Composition API来实现,并假设商品数据从父组件传入,或者组件内部自己管理(这里我们设计为可传入,同时内部也可以管理状态)。 设计思路: - 使用props接收商品列表(可选,也可以让组件自己管理状态) - 使用emits事件来通知父组件购物车的变化(如数量变化、删除商品等) - 组件内部计算每个商品的小计和购物车总金额 步骤: 1. 创建组件Cart.vue 2. 定义props:items(购物车商品列表) 3. 定义计算属性:总金额 4. 定义方法:增加数量、减少数量、删除商品 5. 在方法中,如果需要,可以触发事件(如update:items)以便父组件同步数据(如果使用v-model的话) 6. 使用localStorage来持久化购物车数据(可选,这里我们加上,以便练习) 注意:如果使用props传入,那么修改购物车数据应该通过事件通知父组件修改,然后父组件再传入新的props(单向数据流)。如果希望组件内部管理状态,那么我们可以使用一个内部状态,然后通过props初始化。 这里我们设计为:组件可以接收初始的items,但内部管理状态,并通过事件通知父组件变化。同时,我们使用localStorage来保存购物车状态。 具体实现: 代码结构: - template:表格形式展示购物车商品,有操作按钮(增加、减少、删除) - script:使用setup语法 我们将使用ref或reactive来管理购物车状态,同时使用watch来监听购物车的变化,并将变化保存到localStorage。 另外,我们还需要考虑从localStorage初始化购物车数据。 但是,如果父组件传入了items,我们是优先使用父组件传入的还是localStorage的?这里我们可以这样设计: - 如果父组件传入了items,那么我们就使用父组件的,并且当父组件传入的items变化时,更新内部状态(但注意,如果内部修改了,也会通知父组件,所以需要避免循环更新) - 或者,我们设计为购物车状态完全由父组件控制(通过props和事件),这样就不需要使用内部状态了。 为了灵活性,我们提供两种方式: - 方式一:完全受控组件(通过props传入,通过事件通知变化) - 方式二:内部状态,但可以接收初始值(props),并且将变化通过事件通知父组件,同时保存到localStorage 这里我们选择方式二,因为这样组件可以独立使用。 具体代码: Cart.vue</think>### Vue3 自定义购物车组件实现 下面是一个完整的 Vue3 购物车组件实现,包含核心功能、状态管理和本地存储: ```vue <template> <div class="cart"> <h2>购物车 ({{ totalItems }}件)</h2> <div v-if="items.length === 0" class="empty-cart"> 购物车是空的 </div> <div v-else> <div v-for="item in items" :key="item.id" class="cart-item"> <div class="item-info"> <img :src="item.image" :alt="item.name" class="product-image"> <div> <h3>{{ item.name }}</h3> <p>¥{{ item.price.toFixed(2) }}</p> </div> </div> <div class="item-controls"> <button @click="updateQuantity(item.id, -1)" :disabled="item.quantity <= 1">-</button> <span>{{ item.quantity }}</span> <button @click="updateQuantity(item.id, 1)">+</button> <button @click="removeItem(item.id)" class="remove-btn">删除</button> </div> <div class="item-total"> ¥{{ (item.price * item.quantity).toFixed(2) }} </div> </div> <div class="cart-summary"> <div class="summary-row"> <span>商品总数:</span> <span>{{ totalItems }}件</span> </div> <div class="summary-row"> <span>总金额:</span> <span class="total-price">¥{{ cartTotal.toFixed(2) }}</span> </div> <button @click="checkout" class="checkout-btn">结算</button> </div> </div> </div> </template> <script setup> import { ref, computed, watch, onMounted } from 'vue'; // 购物车数据结构 const items = ref([]); // 从本地存储加载购物车 const loadCart = () => { const savedCart = localStorage.getItem('vue3_cart'); if (savedCart) { items.value = JSON.parse(savedCart); } }; // 保存购物车到本地存储 const saveCart = () => { localStorage.setItem('vue3_cart', JSON.stringify(items.value)); }; // 初始化时加载购物车 onMounted(loadCart); // 监听购物车变化自动保存 watch(items, saveCart, { deep: true }); // 计算总商品数量 const totalItems = computed(() => { return items.value.reduce((total, item) => total + item.quantity, 0); }); // 计算购物车总金额 const cartTotal = computed(() => { return items.value.reduce((total, item) => total + (item.price * item.quantity), 0); }); // 添加商品到购物车 const addToCart = (product) => { const existingItem = items.value.find(item => item.id === product.id); if (existingItem) { existingItem.quantity += 1; } else { items.value.push({ ...product, quantity: 1 }); } }; // 更新商品数量 const updateQuantity = (productId, change) => { const item = items.value.find(item => item.id === productId); if (item) { item.quantity += change; // 如果数量为0则移除商品 if (item.quantity <= 0) { removeItem(productId); } } }; // 移除商品 const removeItem = (productId) => { items.value = items.value.filter(item => item.id !== productId); }; // 清空购物车 const clearCart = () => { items.value = []; }; // 结算 const checkout = () => { alert(`结算成功!总金额: ¥${cartTotal.value.toFixed(2)}`); clearCart(); }; // 暴露方法供父组件使用 defineExpose({ addToCart, clearCart }); </script> <style scoped> .cart { border: 1px solid #eee; border-radius: 8px; padding: 20px; max-width: 800px; margin: 0 auto; } .cart-item { display: flex; justify-content: space-between; align-items: center; padding: 15px 0; border-bottom: 1px solid #eee; } .item-info { display: flex; align-items: center; gap: 15px; flex: 2; } .product-image { width: 80px; height: 80px; object-fit: cover; border-radius: 4px; } .item-controls { flex: 1; display: flex; align-items: center; gap: 10px; } .item-controls button { padding: 5px 10px; background: #f0f0f0; border: none; border-radius: 4px; cursor: pointer; } .item-controls button:disabled { opacity: 0.5; cursor: not-allowed; } .remove-btn { background: #ffebee !important; color: #f44336; margin-left: 15px; } .item-total { flex: 1; text-align: right; font-weight: bold; } .cart-summary { margin-top: 20px; padding-top: 20px; border-top: 2px solid #eee; } .summary-row { display: flex; justify-content: space-between; margin-bottom: 10px; font-size: 1.1em; } .total-price { font-weight: bold; color: #e91e63; font-size: 1.2em; } .checkout-btn { width: 100%; padding: 12px; background: #4CAF50; color: white; border: none; border-radius: 4px; font-size: 1.1em; cursor: pointer; margin-top: 10px; } .checkout-btn:hover { background: #45a049; } .empty-cart { text-align: center; padding: 40px 0; color: #9e9e9e; } </style> ``` ### 核心功能解释 1. **状态管理**: - 使用 `ref` 管理响应式购物车数据 - 使用 `computed` 计算总数量和总金额 - 通过 `watch` 自动持久化到 localStorage 2. **购物车操作**: - `addToCart()`:添加商品(已存在则增加数量) - `updateQuantity()`:增减商品数量 - `removeItem()`:移除单个商品 - `clearCart()`:清空购物车 3. **持久化存储**: - `loadCart()`:组件挂载时从 localStorage 加载 - `saveCart()`:购物车变化时自动保存 4. **组件设计**: - 响应式布局适配不同屏幕 - 商品图片、名称、单价展示 - 数量增减控制(带禁用状态) - 结算功能与金额汇总 ### 使用示例 在父组件中使用购物车组件: ```vue <template> <div> <product-list @add-to-cart="cartRef.addToCart" /> <shopping-cart ref="cartRef" /> </div> </template> <script setup> import { ref } from 'vue'; import ShoppingCart from './ShoppingCart.vue'; import ProductList from './ProductList.vue'; const cartRef = ref(null); </script> ``` ### 最佳实践建议 1. **状态管理升级**: - 大型应用建议改用 Pinia 管理购物车状态 - 使用 store 模块化分离业务逻辑 2. **性能优化**: - 对大型商品列表使用虚拟滚动 - 添加防抖/节流到频繁操作 3. **扩展功能**: - 添加优惠券/折扣码功能 - 实现商品库存校验 - 添加动画过渡效果 4. **错误处理**: - 添加操作确认对话框 - 实现本地存储失败的回退机制 5. **API 集成**: - 对接后端购物车 API - 实现用户登录状态同步
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值