【微信小程序】 实现购物车

小程序 0

【微信小程序】 实现购物车

原生实现,不使用任何框架,网上没有啥好看的购物车,而且都是抄来抄去的,我来写一个好点的作为参考吧,拿来就能用。

接口自行添加

链接:购物车代码片段。图片太大了没办法上传到代码片段,自己找几张图片就行。

购物车功能包括:

  • 显示默认地址,或选择地址(需定向到你自己的页面去选择,这里不做演示
  • 显示店铺,点击店铺可全选该店铺的所有商品
  • 显示商品,可选择商品,增加或减少数量,可左滑删除
    • 左滑删除:如果有一个已经显示出删除按钮,未操作,此时滑动另一个商品,之前的显示将回归原位
  • 选择商品下方显示总价并结算订单
  • 选择全部购物车商品
  • 显示已失效商品

效果图片

购物车
购物车
购物车
购物车
左滑删除

cart.wxml

<!-- 选择地址 --><view class="choose-address">    <view class="address">配送至:<text>{{address}}</text></view>    <image class="img" src="/images/arrow_right_grey.svg"></image></view><!-- 生效商品区域 --><view class="cart-effect">    <block wx:for="{{cartEffectList}}" wx:key="key" wx:for-item="item">        <checkbox-group class="check-group" wx:if="{{item.merchandises.length > 0}}">            <checkbox class="check-store-all" data-store="{{item.store}}" bind:tap="checkStoreAll" checked="{{item.checked || item.merchandiseChecked}}">                <view class="store">{{item.store}}</view>            </checkbox>            <block wx:for="{{item.merchandises}}" wx:key="key" wx:for-item="merchandise">                <movable-area class="move-area">                    <movable-view class="move-view" x="{{merchandise.x}}" data-store="{{item.store}}" data-id="{{merchandise.id}}" direction="horizontal" out-of-bounds="true" damping="50" inertia="true" bind:touchstart="touchStart" bind:touchmove="touchMove" bind:touchend="touchEnd" bindchange="touchChange">                        <view class="info">                            <checkbox class="merchandise-check" data-store="{{item.store}}" data-merchandise="{{merchandise}}" bind:tap="checkSingle" checked="{{merchandise.checked}}"></checkbox>                            <view class="merchandise">                                <image class="merchandise-img" src="{{merchandise.imgUrl}}"></image>                                <view class="merchandise-name">{{merchandise.name}}</view>                                <view class="merchandise-price">¥{{merchandise.price}}/瓶</view>                                <view class="merchandise-amount">                                    <view class="minus" data-store="{{item.store}}" data-id="{{merchandise.id}}" bind:tap="minusAmount">-</view>                                    <view class="amount">{{merchandise.amount}}</view>                                    <view class="plus" data-store="{{item.store}}" data-id="{{merchandise.id}}" bind:tap="plusAmount">+</view>                                </view>                            </view>                        </view>                    </movable-view>                    <view class="delete" data-store="{{item.store}}" data-id="{{merchandise.id}}" bind:tap="deleteMerchandise">删除</view>                </movable-area>            </block>        </checkbox-group>    </block></view><!-- 失效商品 --><view class="cart-lapse">    <block wx:for="{{cartLapseList}}" wx:key="key">        <checkbox-group class="check-group" bindchange="checkStoreAll">            <view class="store">已失效商品({{cartLapseList.length}})</view>            <movable-area class="move-area">                <movable-view class="move-view" x="{{item.x}}" data-id="{{item.id}}" direction="horizontal" out-of-bounds="true" damping="50" inertia="true" bind:touchstart="touchStart" bind:touchmove="touchMove" bind:touchend="touchEnd" bindchange="touchChange">                    <view class="info">                        <checkbox class="merchandise-check" disabled="true"></checkbox>                        <view class="merchandise">                            <view class="sold-out">                                <image class="merchandise-img" src="{{item.imgUrl}}"></image>                                <view class="status">{{item.info}}</view>                            </view>                            <view class="merchandise-name">{{item.name}}</view>                            <view class="merchandise-price">¥{{item.price}}/瓶</view>                            <view class="merchandise-amount">                                <view class="minus" style="color: gray;">-</view>                                <view class="amount" style="color: gray;">{{item.amount}}</view>                                <view class="plus" style="color: gray;">+</view>                            </view>                        </view>                    </view>                </movable-view>                <view class="delete" data-id="{{item.id}}" bind:tap="deleteMerchandise">删除</view>            </movable-area>        </checkbox-group>    </block></view><!-- 结算 --><view class="count">    <checkbox class="check-all" bind:tap="checkAll" checked="{{checkedAll}}">全选</checkbox>    <view class="grand">合计:¥{{total}}</view>    <button class="lapse {{totalCount > 0 ? 'settle-btn' : ''}}" bind:tap="settleBill" hover-class="settle-bill" disabled="{{totalCount == 0}}" loading="{{showLoading}}">结算({{totalCount}})</button></view>

cart.wxss

page {    padding: 16rpx;    padding-bottom: 172rpx;    box-sizing: border-box;}/* 选择地址 */.choose-address {    display: flex;    align-items: center;    justify-content: flex-start;    text-align: left;}.choose-address .address {    overflow: hidden;    text-overflow: ellipsis;    white-space: nowrap;}.choose-address .img {    width: 32rpx;    height: 32rpx;}/* 选择地址 *//* 生效商品 */.cart-effect {    display: flex;    flex-direction: column;}.cart-effect .check-group {    background-color: #fbfbfd;    padding: 16rpx;    box-sizing: border-box;    border-radius: 32rpx;    margin-top: 16rpx;    z-index: 11;}.cart-effect .check-group .check-store-all {    display: flex;    justify-content: flex-start;    align-items: center;}.cart-effect .check-group .store {    font-size: 36rpx;    font-weight: bolder;    margin-left: 8rpx;}.cart-effect .check-group .move-area{    /* 减去删除部分的宽度 */    width: calc(100% - 128rpx);    height: 280rpx;    position: relative;    display: flex;}.cart-effect .check-group .move-area .move-view {    display: flex;    justify-content: center;    align-items: center;    height: 100%;    /* 上面减了多少宽度,下面就要加多少,不然无法起到遮挡作用 */    width: calc(100% + 128rpx);    background-color: #fbfbfd;    box-sizing: border-box;    z-index: 10;    padding-right: 12rpx;}.cart-effect .check-group .move-area .delete {    position: absolute;    top: 0;    bottom: 0;    left: 100%;    /* 删除部分的宽度 */    width: 128rpx;    text-align: center;    z-index: 9;    display: flex;    justify-content: center;    align-items: center;    background-color: #f43838;    color: #fbfbfd;}.cart-effect .check-group .move-area .move-view .info {    display: flex;    justify-content: center;    align-items: center;    width: 100vw;    overflow: hidden;    box-sizing: border-box;}.cart-effect .check-group .move-area .move-view .info .merchandise {    display: grid;    grid-template-columns: 1fr 2fr 2fr;    grid-template-rows: 110rpx 60rpx;    grid-column-gap: 12rpx;    grid-row-gap: 12rpx;}.cart-effect .check-group .move-area .move-view .info .merchandise .merchandise-img {    width: 180rpx;    height: 180rpx;    border-radius: 50%;    grid-column: 1 / 2;    grid-row: 1 / 3;}.cart-effect .check-group .move-area .move-view .info .merchandise .merchandise-name {    grid-column: 2 / 4;    grid-row: 1 / 2;    font-size: 32rpx;}.cart-effect .check-group .move-area .move-view .info .merchandise .merchandise-price {    grid-column: 2 / 3;    grid-row: 2 / 3;    font-size: 32rpx;    font-weight: 550;}.cart-effect .check-group .move-area .move-view .merchandise .merchandise-amount {    grid-column: 3 / 4;    grid-row: 2 / 3;    text-align: right;        display: grid;    grid-template-columns: repeat(3, 1fr);    align-items: center;    text-align: center;    line-height: 50rpx;}.cart-effect .check-group .move-area .move-view .merchandise .merchandise-amount .minus {    font-size: 42rpx;    background-color: #e5e5e5;    border-radius: 16rpx;}.cart-effect .check-group .move-area .move-view .merchandise .merchandise-amount .amount {    font-size: 36rpx;}.cart-effect .check-group .move-area .move-view .merchandise .merchandise-amount .plus {    background-color: #e5e5e5;    border-radius: 16rpx;    font-size: 42rpx;}/* 失效商品 */.cart-lapse {    display: flex;    flex-direction: column;}.cart-lapse .check-group {    background-color: #fbfbfd;    padding: 16rpx;    box-sizing: border-box;    border-radius: 32rpx;    margin-top: 16rpx;    z-index: 11;}.cart-lapse .check-group .check-store-all {    display: flex;    justify-content: flex-start;    align-items: center;}.cart-lapse .check-group .store {    font-size: 36rpx;    font-weight: bolder;}.cart-lapse .check-group .move-area{    /* 减去删除部分的宽度 */    width: calc(100% - 128rpx);    height: 280rpx;    position: relative;    display: flex;}.cart-lapse .check-group .move-area .move-view {    display: flex;    justify-content: center;    align-items: center;    height: 100%;    /* 上面减了多少宽度,下面就要加多少,不然无法起到遮挡作用 */    width: calc(100% + 128rpx);    background-color: #fbfbfd;    box-sizing: border-box;    z-index: 10;    padding-right: 12rpx;}.cart-lapse .check-group .move-area .delete {    position: absolute;    top: 0;    bottom: 0;    left: 100%;    /* 删除部分的宽度 */    width: 128rpx;    text-align: center;    z-index: 9;    display: flex;    justify-content: center;    align-items: center;    background-color: #f43838;    color: #fbfbfd;}.cart-lapse .check-group .move-area .move-view .info {    display: flex;    justify-content: center;    align-items: center;    width: 100vw;    overflow: hidden;    box-sizing: border-box;}.cart-lapse .check-group .move-area .move-view .info .merchandise {    display: grid;    grid-template-columns: 1fr 2fr 2fr;    grid-template-rows: 110rpx 60rpx;    grid-column-gap: 12rpx;    grid-row-gap: 12rpx;}.cart-lapse .check-group .move-area .move-view .info .merchandise .sold-out {    position: relative;    width: 180rpx;    height: 180rpx;    border-radius: 50%;}.cart-lapse .check-group .move-area .move-view .info .merchandise .sold-out .merchandise-img {    width: 100%;    height: 100%;    border-radius: 50%;    grid-column: 1 / 2;    grid-row: 1 / 3;}.cart-lapse .check-group .move-area .move-view .info .merchandise .sold-out .status{    width: 100%;    height: 100%;    background-color: #1a1a1a90;    position: absolute;    top: 0;    left: 0;    text-align: center;    display: flex;    justify-content: center;    align-items: center;    border-radius: 50%;    color: #fbfbfb;    font-size: 36rpx;    font-weight: 500;}.cart-lapse .check-group .move-area .move-view .info .merchandise .merchandise-name {    grid-column: 2 / 4;    grid-row: 1 / 2;    font-size: 32rpx;}.cart-lapse .check-group .move-area .move-view .info .merchandise .merchandise-price {    grid-column: 2 / 3;    grid-row: 2 / 3;    font-size: 32rpx;    font-weight: 550;}.cart-lapse .check-group .move-area .move-view .merchandise .merchandise-amount {    grid-column: 3 / 4;    grid-row: 2 / 3;    text-align: right;    display: grid;    grid-template-columns: repeat(3, 1fr);    align-items: center;    text-align: center;    line-height: 60rpx;}.cart-lapse .check-group .move-area .move-view .merchandise .merchandise-amount .minus {    font-size: 42rpx;    background-color: #e5e5e5;    border-radius: 16rpx;}.cart-lapse .check-group .move-area .move-view .merchandise .merchandise-amount .amount {    font-size: 36rpx;}.cart-lapse .check-group .move-area .move-view .merchandise .merchandise-amount .plus {    background-color: #e5e5e5;    border-radius: 16rpx;    font-size: 42rpx;}.count {    width: calc(100% - 32rpx);    box-sizing: border-box;    margin-top: 16rpx;    padding: 16rpx;    background-color: #fbfbfd;    border-radius: 120rpx;    position: fixed;    bottom: 42rpx;    z-index: 99;    display: flex;    justify-content: space-between;    align-items: center;    border: 1rpx solid #e5e5e5;}.count .check-all{    font-size: 34rpx;    font-weight: 550;}.count .grand {    width: 50%;    text-align: right;    font-size: 32rpx;    font-weight: 550;    color: #d1362f;}.count .lapse {    width: 200rpx;    height: 80rpx;    color: #fdfdfd;    border-radius: 120rpx;    margin: 0;    text-align: center;    background-color: #e5e5e5;    padding: 0;    line-height: 80rpx;}.count .settle-btn {    background-color: #d1362f;}.count .settle-bill {    background-color: #d1362f80}

cart.js

Page({    /**     * 页面的初始数据     */    data: {        address: "XX省XX市XX区XX街道XX号XX室",        cartEffectList: [],        // 生效商品        example: [            {                store: "龙门客栈",                checked: false,                merchandiseChecked: false,                merchandises: [                    {                        id: "1",                        imgUrl: "/images/wine1.jpg",                        name: "宫廷玉液酒",                        price: 180,                        amount: 1,                        status: 0,                        x: 0,                        checked: false,                    },                    {                        id: "2",                        imgUrl: "/images/wine2.png",                        name: "群英荟萃",                        price: 98,                        amount: 99,                        status: 1,                        x: 0,                        checked: false,                    },                    {                        id: "3",                        imgUrl: "/images/wine3.png",                        name: "二锅头",                        price: 28,                        amount: 1,                        status: 1,                        x: 0,                        checked: false,                    },                ]            },            {                store: "八马茶叶",                checked: false,                merchandiseChecked: false,                merchandises: [                    {                        id: "1",                        imgUrl: "/images/wine3.png",                        name: "黑普洱茶饼",                        price: 1800,                        amount: 1,                        status: 1,                        x: 0,                        checked: false,                    },                ]            },        ],        // 失效商品        cartLapseList: [            {                id: "3",                imgUrl: "/images/wine2.png",                name: "威士忌",                info: "已售罄",                price: 148,                amount: 1,                x: 0,                status: -1,                checked: false,            }        ],        startX: 0,        moveStore: '',        delBtnW: 128,        isLeft: 0,        total: 0,        totalCount: 0,        checkedAll: false,        billLoading: false,    },    /**     * 生命周期函数--监听页面加载     */    onLoad(options) {        this.getCartList();    },    /**     * 生命周期函数--监听页面显示     */    onShow() {    },    /**     * 获取用户购物车     */    getCartList() {        let cartEffectList = this.data.example;        wx.setStorageSync('cartEffectList', cartEffectList);        this.setData({            cartEffectList: cartEffectList,        });    },    /**     * 店铺全选     * @param {*} e      */    checkStoreAll(e) {        let storeName = e.currentTarget.dataset.store;        let cartEffectList = this.data.cartEffectList;        let updatedCart = cartEffectList.map(store => {            if (store.store === storeName) {                // 切换商店的已勾选标记                store.checked = !store.checked;                // 重置商店的商品已勾选标记                store.merchandiseChecked = false;                store.merchandises = store.merchandises.map(merch => {                    // 根据商店的已检查标记更新商品的已选择标记                    merch.checked = store.checked;                    return merch;                });            }            return store;        });        this.setData({            cartEffectList: updatedCart        });        this.grand();    },    /**     * 单个选择     * @param {*} e      */    checkSingle(e) {        let storeName = e.currentTarget.dataset.store;        let merchandiseData = e.currentTarget.dataset.merchandise;        let cartEffectList = this.data.cartEffectList;            let updatedCart = cartEffectList.map(store => {            if (store.store === storeName) {                store.merchandises = store.merchandises.map(merch => {                    if (merch.id === merchandiseData.id) {                        // 更改商品的选择状态                        merch.checked = !merch.checked;                        // 更改店铺的选中状态                        store.merchandiseChecked = merch.checked;                    }                    return merch;                });            }            return store;        });            this.setData({            cartEffectList: updatedCart        });        this.grand();    },    /**     * 减少数量,下限为1     * @param {*} e      */    minusAmount(e) {        let storeName = e.currentTarget.dataset.store;        let id = e.currentTarget.dataset.id;        let cartEffectList = this.data.cartEffectList;        let updatedCart = cartEffectList.map(store => {            if (store.store === storeName) {                // 更新商店的已选择商品标记                store.merchandiseChecked = true;                store.merchandises = store.merchandises.map(merch => {                    if (merch.id === id) {                        // 更新商品的选择状态                        merch.checked = true;                        if (merch.amount > 1) {                            merch.amount--;                        } else {                            wx.showModal({                                content: '宝贝数量不能再减少了',                                showCancel: false,                            });                        }                    }                    return merch;                });            }            return store;        });            this.setData({            cartEffectList: updatedCart        });        this.grand();    },    /**     * 增加数量,上限为99     * @param {*} e      */    plusAmount(e) {        let storeName = e.currentTarget.dataset.store;        let id = e.currentTarget.dataset.id;        let cartEffectList = this.data.cartEffectList;        let updatedCart = cartEffectList.map(store => {            if (store.store === storeName) {                // 更新店铺的选中状态                store.merchandiseChecked = true;                store.merchandises = store.merchandises.map(merch => {                    if (merch.id === id) {                        // 更新商品的选择状态                        merch.checked = true;                         if (merch.amount < 99) {                            merch.amount++;                        } else {                            wx.showModal({                                content: '宝贝数量不能再增加了',                                showCancel: false,                            });                        }                    }                    return merch;                });            }            return store;        });        this.setData({            cartEffectList: updatedCart        });        this.grand();    },    /**     * 删除商品     * @param {*} e      */    deleteMerchandise(e) {        let storeName = e.currentTarget.dataset.store;        let id = e.currentTarget.dataset.id;        let cartEffectList = this.data.cartEffectList;        let cartLapseList = this.data.cartLapseList;        // 遍历每个商店        let updatedEffectCart = cartEffectList.map(store => {            if (store.store === storeName) {                // 如果是目标商店,过滤掉指定ID的商品                store.merchandises = store.merchandises.filter(merch => merch.id !== id);            }            return store;        });        cartLapseList = cartLapseList.filter(item => item.id !== id);        this.setData({            cartEffectList: updatedEffectCart,            cartLapseList: cartLapseList        });    },    /**     * 全选     */    checkAll() {        let cartEffectList = this.data.cartEffectList;        let checkedAll = this.data.checkedAll;        // 使用map方法更新每个商店和商品的选中状态        let updatedCart = cartEffectList.map(store => {            return {                ...store,                checked: !checkedAll,                merchandiseChecked: !checkedAll,                merchandises: store.merchandises.map(merch => ({                    ...merch,                    checked: !checkedAll                }))            };        });        this.setData({            cartEffectList: updatedCart,            checkedAll: !checkedAll,        });        this.grand();    },    /**     * 合计     */    grand() {        let cartEffectList = this.data.cartEffectList;        // 初始化总价和总数量        let total = 0;        let totalCount = 0;        cartEffectList.forEach(store => {            store.merchandises.forEach(merch => {                if (merch.checked) {                    total += merch.amount * merch.price;                    totalCount += merch.amount;                }            });        });        this.setData({            total: total,            totalCount: totalCount,        });    },    /**     * 结算     */    settleBill() {        this.setData({            showLoading: true,        });        // 进行深拷贝        let cartEffectList = JSON.parse(JSON.stringify(this.data.cartEffectList));        // 筛选出未选中的商店或没有选中商品的商店        let filteredStores = cartEffectList.filter(store => {            if (store.checked) {                // 如果商店被选中,保留它                return true;             }            // 从商店中筛选出未选中的商品            store.merchandises = store.merchandises.filter(merch => merch.checked);            // 如果筛选后,商店有选中的商品,保留该商店            return store.merchandises.length > 0;        });        wx.navigateTo({            url: `/pages/index/settle-bill/settle-bill?chosenList=${JSON.stringify(filteredStores)}`,            complete: () => {                this.setData({                    showLoading: false,                });            }        });    },    /**     * 开始滑动     * @param {*} e      */    touchStart (e) {        let index = e.currentTarget.dataset.id;        let store = e.currentTarget.dataset.store;        let cartEffectList = this.data.cartEffectList;        let cartLapseList = this.data.cartLapseList;        // 复位,这样子就能保证一次显示一个删除按钮        for (let i in cartEffectList) {            for (let j in cartEffectList[i].merchandises) {                cartEffectList[i].merchandises[j].x = 0            }        }        for (let i in cartLapseList) {            cartLapseList[i].x = 0;        }        // 判断是否为多触点        if (e.touches.length == 1) {            // 记录开始触摸的位置            this.setData({                startX: e.touches[0].clientX,                cartEffectList: cartEffectList,                cartLapseList: cartLapseList            });        }    },    /**     * 开始移动     * @param {*} e      */    touchMove (e) {        let id = e.currentTarget.dataset.id;        let store = e.currentTarget.dataset.store;        if (e.touches.length == 1) {            // 记录移动的距离            let disX = e.touches[0].clientX - this.data.startX;            // 大于0则时向右滑,复位            if (disX >= 0) {                // 向右滑                this.setData({                    isLeft: 0                });            } else {                // 小于则是向左滑                this.setData({                    isLeft: 1                });            }        }    },    /**     * 滑动终点     * @param {*} e      */    touchEnd (e) {        let id = e.currentTarget.dataset.id;        let store = e.currentTarget.dataset.store;        let delw = this.data.delBtnW;        if (e.touches.length == 1) {            let endX = e.touches[0].clientX - this.data.startX;            if (endX < 0) {                this.setXmove(id, store, - delw);            } else {                this.setXmove(id, store, 0);            }        }    },    /**     * 滑动事件     * @param {*} e      */    touchChange (e) {        let delw = this.data.delBtnW;        let store = e.currentTarget.dataset.store;        let id = e.currentTarget.dataset.id;        if (this.data.isLeft) {            if (e.detail.source == 'friction') {                if (e.detail.x < 0) {                    this.setXmove(id, store, -delw);                } else {                    this.setXmove(id, store, 0);                }            }        } else {            if (e.detail.source == 'friction') {                this.setXmove(id, store, 0);            }        }    },    /**     * 设置起始位置     * @param {*} id      * @param {*} store      * @param {*} x      */    setXmove(id, store, x) {        let that = this;        if (store) {            let cartEffectList = this.data.cartEffectList;            for (let i in cartEffectList) {                if (cartEffectList[i].store == store) {                    for (let j in cartEffectList[i].merchandises) {                        if (cartEffectList[i].merchandises[j].id == id) {                            cartEffectList[i].merchandises[j].x = x;                        }                    }                }            }            that.setData({                cartEffectList: cartEffectList,            });        } else {            let cartLapseList = this.data.cartLapseList;            for (let i in cartLapseList) {                if (cartLapseList[i].id == id) {                    cartLapseList[i].x = x;                }            }            that.setData({                cartLapseList: cartLapseList,            });        }    }})

checkbox样式更改

checkbox .wx-checkbox-input{    width: 40rpx;     height: 40rpx;     border-radius: 50%;}/* 选中后的背景样式 */checkbox .wx-checkbox-input.wx-checkbox-input-checked{    background: #b0474c;}/* 选中后的勾子样式 */checkbox .wx-checkbox-input.wx-checkbox-input-checked::before{    width: 40rpx;    height: 40rpx;    line-height: 40rpx;    border-radius: 50%;    text-align: center;    font-size:32rpx;     color:#fbfbfd;     background: transparent;    transform:translate(-50%, -50%) scale(1);    -webkit-transform:translate(-50%, -50%) scale(1);}

也许您对下面的内容还感兴趣: