HarmonyOS分布式购物开发实战:跨设备购物车

在手机上浏览商品,加入购物车;到电脑上继续挑选,比较价格;最后用平板完成支付——这种跨设备无缝购物体验,正是鸿蒙分布式能力的魅力所在。

一、背景哦

1.1 传统购物体验的痛点

传统电商购物确实有很多不便:

  • 设备割裂:手机浏览的商品,电脑上看不到浏览记录
  • 购物车不同步:不同设备的购物车内容不一致,容易漏买或重复购买
  • 支付不便:手机支付方便但屏幕小,电脑屏幕大但支付繁琐
  • 分享困难:想和家人分享购物车,只能截图或转发链接

1.2 鸿蒙分布式购物的愿景

鸿蒙让购物体验焕然一新:

购物车同步:所有设备共享同一个购物车,实时同步。

浏览记录同步:浏览历史、收藏夹跨设备同步。

协同购物:家人可以共享购物车,一起挑选商品。

无缝支付:任意设备都能完成支付,安全便捷。

购物流程

加入购物车

修改数量

完成订单

实时同步

实时同步

实时同步

手机浏览

分布式购物车

电脑比较

平板支付

二、核心原理

2.1 购物数据模型

// 购物车商品
interface CartItem {
    itemId: string;               // 商品ID
    skuId: string;                // SKU ID
    name: string;                 // 商品名称
    image: string;                // 商品图片
    price: number;                // 单价
    quantity: number;             // 数量
    selected: boolean;            // 是否选中
    addedBy: string;              // 添加者
    addedAt: number;              // 添加时间
    deviceId: string;             // 添加设备
}

// 购物车
interface ShoppingCart {
    cartId: string;               // 购物车ID
    userId: string;               // 用户ID
    items: CartItem[];            // 商品列表
    totalPrice: number;           // 总价
    totalQuantity: number;        // 总数量
    selectedItems: string[];      // 选中的商品ID
    lastUpdate: number;           // 最后更新时间
}

// 订单
interface Order {
    orderId: string;
    cartId: string;
    userId: string;
    items: CartItem[];
    totalPrice: number;
    status: OrderStatus;
    paymentMethod: string;
    shippingAddress: Address;
    createdAt: number;
}

enum OrderStatus {
    PENDING = 'pending',          // 待支付
    PAID = 'paid',                // 已支付
    SHIPPED = 'shipped',          // 已发货
    DELIVERED = 'delivered',      // 已送达
    CANCELLED = 'cancelled'       // 已取消
}

interface Address {
    name: string;
    phone: string;
    province: string;
    city: string;
    district: string;
    detail: string;
}

2.2 数据同步机制

实时同步:购物车变化立即同步到所有设备。

冲突解决:基于时间戳的自动合并。

离线支持:离线时本地缓存,联网后自动同步。

三、代码实战

3.1 分布式购物车实现

// DistributedShoppingCart.ets
import { distributedData } from '@kit.ArkData';

@Entry
@Component
struct DistributedShoppingCart {
    // 购物车状态
    @State cartItems: CartItem[] = [];
    @State selectedItems: Set<string> = new Set();
    @State totalPrice: number = 0;
    @State totalQuantity: number = 0;
  
    // UI状态
    @State isEditing: boolean = false;
    @State showPayment: boolean = false;
  
    // 分布式数据
    private kvStore: distributedData.KvStore | null = null;
  
    aboutToAppear() {
        this.initDistributedData();
        this.loadCart();
    }
  
    // 初始化分布式数据
    async initDistributedData() {
        try {
            // const kvManager = distributedData.createKvManager({...});
            // this.kvStore = await kvManager.createKvStore('shopping_cart', {...});
          
            // 监听购物车变化
            // this.kvStore.on('dataChange', (data) => {
            //     this.handleCartChange(data);
            // });
          
            console.info('购物车初始化成功');
        } catch (err) {
            console.error(`初始化失败: ${JSON.stringify(err)}`);
        }
    }
  
    // 加载购物车
    async loadCart() {
        // 从分布式数据库加载
        // 模拟数据
        this.cartItems = [
            {
                itemId: 'item_001',
                skuId: 'sku_001',
                name: '鸿蒙开发实战指南',
                image: 'https://example.com/book.jpg',
                price: 99.00,
                quantity: 1,
                selected: true,
                addedBy: 'user_001',
                addedAt: Date.now(),
                deviceId: 'phone_001'
            },
            {
                itemId: 'item_002',
                skuId: 'sku_002',
                name: '智能手表Pro',
                image: 'https://example.com/watch.jpg',
                price: 1999.00,
                quantity: 1,
                selected: true,
                addedBy: 'user_001',
                addedAt: Date.now(),
                deviceId: 'tablet_001'
            }
        ];
      
        this.calculateTotal();
    }
  
    // 添加商品到购物车
    async addToCart(item: CartItem) {
        // 检查是否已存在
        const existingIndex = this.cartItems.findIndex(
            i => i.itemId === item.itemId && i.skuId === item.skuId
        );
      
        if (existingIndex >= 0) {
            // 已存在,增加数量
            this.cartItems[existingIndex].quantity += item.quantity;
        } else {
            // 不存在,添加新商品
            this.cartItems.push(item);
        }
      
        this.calculateTotal();
        await this.syncCart();
    }
  
    // 更新商品数量
    async updateQuantity(itemId: string, quantity: number) {
        const index = this.cartItems.findIndex(i => i.itemId === itemId);
        if (index >= 0) {
            if (quantity <= 0) {
                // 删除商品
                this.cartItems.splice(index, 1);
            } else {
                // 更新数量
                this.cartItems[index].quantity = quantity;
            }
          
            this.calculateTotal();
            await this.syncCart();
        }
    }
  
    // 选择/取消选择商品
    async toggleSelection(itemId: string) {
        if (this.selectedItems.has(itemId)) {
            this.selectedItems.delete(itemId);
        } else {
            this.selectedItems.add(itemId);
        }
      
        this.calculateTotal();
    }
  
    // 全选/取消全选
    async toggleSelectAll() {
        if (this.selectedItems.size === this.cartItems.length) {
            // 取消全选
            this.selectedItems.clear();
        } else {
            // 全选
            this.selectedItems = new Set(this.cartItems.map(i => i.itemId));
        }
      
        this.calculateTotal();
    }
  
    // 删除选中商品
    async deleteSelected() {
        this.cartItems = this.cartItems.filter(i => !this.selectedItems.has(i.itemId));
        this.selectedItems.clear();
      
        this.calculateTotal();
        await this.syncCart();
    }
  
    // 计算总价
    calculateTotal() {
        this.totalPrice = 0;
        this.totalQuantity = 0;
      
        for (const item of this.cartItems) {
            if (this.selectedItems.has(item.itemId)) {
                this.totalPrice += item.price * item.quantity;
                this.totalQuantity += item.quantity;
            }
        }
    }
  
    // 同步购物车
    async syncCart() {
        if (!this.kvStore) {
            return;
        }
      
        const cartData = {
            items: this.cartItems,
            lastUpdate: Date.now()
        };
      
        // await this.kvStore.put('cart', JSON.stringify(cartData));
    }
  
    // 处理购物车变化
    handleCartChange(data: any) {
        // 解析变化数据
        // 更新本地购物车
    }
  
    // 结算
    async checkout() {
        if (this.selectedItems.size === 0) {
            console.warn('请选择商品');
            return;
        }
      
        this.showPayment = true;
    }
  
    build() {
        Column() {
            // 标题栏
            this.buildHeader();
          
            // 商品列表
            if (this.cartItems.length > 0) {
                this.buildCartList();
            } else {
                this.buildEmptyCart();
            }
          
            // 底部结算栏
            this.buildBottomBar();
          
            // 支付弹窗
            if (this.showPayment) {
                this.buildPaymentDialog();
            }
        }
        .width('100%')
        .height('100%')
        .backgroundColor('#f5f5f5');
    }
  
    @Builder
    buildHeader() {
        Row() {
            Text('购物车')
                .fontSize(24)
                .fontWeight(FontWeight.Bold);
          
            Blank();
          
            if (this.cartItems.length > 0) {
                Text(this.isEditing ? '完成' : '编辑')
                    .fontSize(16)
                    .fontColor('#2196F3')
                    .onClick(() => {
                        this.isEditing = !this.isEditing;
                    });
            }
        }
        .width('100%')
        .height(56)
        .padding({ left: 16, right: 16 })
        .backgroundColor(Color.White);
    }
  
    @Builder
    buildCartList() {
        List() {
            ForEach(this.cartItems, (item: CartItem) => {
                ListItem() {
                    this.buildCartItem(item);
                }
            });
        }
        .width('100%')
        .layoutWeight(1)
        .padding({ top: 8, bottom: 8 });
    }
  
    @Builder
    buildCartItem(item: CartItem) {
        Row() {
            // 选择框
            Checkbox()
                .select(this.selectedItems.has(item.itemId))
                .onChange((checked: boolean) => {
                    this.toggleSelection(item.itemId);
                })
                .width(24)
                .height(24);
          
            // 商品图片
            Image(item.image)
                .width(80)
                .height(80)
                .borderRadius(8)
                .margin({ left: 12, right: 12 });
          
            // 商品信息
            Column() {
                Text(item.name)
                    .fontSize(16)
                    .maxLines(2)
                    .textOverflow({ overflow: TextOverflow.Ellipsis });
              
                Blank();
              
                Row() {
                    Text(`¥${item.price.toFixed(2)}`)
                        .fontSize(18)
                        .fontWeight(FontWeight.Bold)
                        .fontColor('#F44336');
                  
                    Blank();
                  
                    // 数量控制
                    Row() {
                        Button('-')
                            .width(28)
                            .height(28)
                            .fontSize(16)
                            .onClick(() => {
                                this.updateQuantity(item.itemId, item.quantity - 1);
                            });
                      
                        Text(`${item.quantity}`)
                            .width(40)
                            .textAlign(TextAlign.Center);
                      
                        Button('+')
                            .width(28)
                            .height(28)
                            .fontSize(16)
                            .onClick(() => {
                                this.updateQuantity(item.itemId, item.quantity + 1);
                            });
                    }
                    .backgroundColor('#f5f5f5')
                    .borderRadius(4);
                }
                .width('100%');
            }
            .layoutWeight(1)
            .height(80)
            .justifyContent(FlexAlign.SpaceBetween);
        }
        .width('100%')
        .padding(16)
        .backgroundColor(Color.White)
        .margin({ bottom: 8 });
    }
  
    @Builder
    buildEmptyCart() {
        Column() {
            Image($r('app.media.ic_cart_empty'))
                .width(120)
                .height(120)
                .opacity(0.5);
          
            Text('购物车是空的')
                .fontSize(16)
                .fontColor('#999999')
                .margin({ top: 16 });
          
            Button('去购物')
                .margin({ top: 24 })
                .onClick(() => {
                    // 跳转到商品列表
                });
        }
        .width('100%')
        .layoutWeight(1)
        .justifyContent(FlexAlign.Center);
    }
  
    @Builder
    buildBottomBar() {
        Row() {
            // 全选
            Row() {
                Checkbox()
                    .select(this.selectedItems.size === this.cartItems.length && this.cartItems.length > 0)
                    .onChange((checked: boolean) => {
                        this.toggleSelectAll();
                    });
              
                Text('全选')
                    .fontSize(14)
                    .margin({ left: 8 });
            }
            .margin({ right: 16 });
          
            Blank();
          
            // 总价
            Column() {
                Text(`合计: ¥${this.totalPrice.toFixed(2)}`)
                    .fontSize(18)
                    .fontWeight(FontWeight.Bold)
                    .fontColor('#F44336');
              
                Text(`已选 ${this.totalQuantity}`)
                    .fontSize(12)
                    .fontColor('#666666');
            }
            .alignItems(HorizontalAlign.End)
            .margin({ right: 16 });
          
            // 结算按钮
            if (this.isEditing) {
                Button('删除')
                    .backgroundColor('#F44336')
                    .onClick(() => {
                        this.deleteSelected();
                    });
            } else {
                Button(`结算(${this.selectedItems.size})`)
                    .onClick(() => {
                        this.checkout();
                    });
            }
        }
        .width('100%')
        .height(80)
        .padding({ left: 16, right: 16 })
        .backgroundColor(Color.White);
    }
  
    @Builder
    buildPaymentDialog() {
        Column() {
            Text('确认订单')
                .fontSize(20)
                .fontWeight(FontWeight.Bold)
                .margin({ bottom: 20 });
          
            // 商品汇总
            Column() {
                ForEach(this.cartItems.filter(i => this.selectedItems.has(i.itemId)), (item: CartItem) => {
                    Row() {
                        Text(item.name)
                            .fontSize(14)
                            .layoutWeight(1);
                      
                        Text(`x${item.quantity}`)
                            .fontSize(14)
                            .margin({ left: 8 });
                      
                        Text(`¥${(item.price * item.quantity).toFixed(2)}`)
                            .fontSize(14)
                            .fontWeight(FontWeight.Medium)
                            .margin({ left: 8 });
                    }
                    .width('100%')
                    .margin({ bottom: 8 });
                });
            }
            .width('100%')
            .padding(16)
            .backgroundColor('#f5f5f5')
            .borderRadius(8);
          
            // 总价
            Row() {
                Text('应付金额:')
                    .fontSize(16);
              
                Text(`¥${this.totalPrice.toFixed(2)}`)
                    .fontSize(24)
                    .fontWeight(FontWeight.Bold)
                    .fontColor('#F44336')
                    .margin({ left: 8 });
            }
            .width('100%')
            .justifyContent(FlexAlign.End)
            .margin({ top: 20 });
          
            // 支付按钮
            Button('立即支付')
                .width('100%')
                .margin({ top: 24 })
                .onClick(() => {
                    this.processPayment();
                });
          
            Button('取消')
                .width('100%')
                .backgroundColor(Color.Transparent)
                .fontColor('#666666')
                .margin({ top: 12 })
                .onClick(() => {
                    this.showPayment = false;
                });
        }
        .width('85%')
        .padding(20)
        .backgroundColor(Color.White)
        .borderRadius(16)
        .shadow({ radius: 20, color: '#00000020' });
    }
  
    // 处理支付
    async processPayment() {
        // 创建订单
        // 调用支付接口
        // 更新购物车
      
        console.info('处理支付');
        this.showPayment = false;
    }
}

3.2 商品浏览记录同步

// BrowseHistory.ets

interface BrowseRecord {
    itemId: string;
    name: string;
    image: string;
    price: number;
    viewedAt: number;
    deviceId: string;
}

@Entry
@Component
struct BrowseHistory {
    @State history: BrowseRecord[] = [];
  
    // 记录浏览
    async recordBrowse(item: any) {
        const record: BrowseRecord = {
            itemId: item.id,
            name: item.name,
            image: item.image,
            price: item.price,
            viewedAt: Date.now(),
            deviceId: 'local'
        };
      
        // 添加到历史记录
        this.history.unshift(record);
      
        // 同步到分布式数据库
        await this.syncHistory(record);
    }
  
    // 同步浏览记录
    async syncHistory(record: BrowseRecord) {
        // 同步到分布式数据库
    }
  
    build() {
        Column() {
            Text('浏览记录')
                .fontSize(20)
                .fontWeight(FontWeight.Bold);
          
            List() {
                ForEach(this.history, (record: BrowseRecord) => {
                    ListItem() {
                        Row() {
                            Image(record.image)
                                .width(60)
                                .height(60)
                                .borderRadius(4);
                          
                            Column() {
                                Text(record.name)
                                    .fontSize(14)
                                    .maxLines(1);
                              
                                Text(`¥${record.price.toFixed(2)}`)
                                    .fontSize(14)
                                    .fontColor('#F44336')
                                    .margin({ top: 4 });
                            }
                            .alignItems(HorizontalAlign.Start)
                            .margin({ left: 12 })
                            .layoutWeight(1);
                        }
                        .width('100%')
                        .padding(8);
                    }
                });
            }
            .width('100%')
            .layoutWeight(1);
        }
        .width('100%')
        .height('100%')
        .padding(16);
    }
}

四、踩坑与注意事项

4.1 价格精度问题

问题:浮点数计算导致价格不精确。

解决方案

// 价格计算工具
class PriceCalculator {
    // 加法(避免浮点误差)
    static add(a: number, b: number): number {
        const precision = 100;  // 保留两位小数
        return Math.round(a * precision + b * precision) / precision;
    }
  
    // 乘法
    static multiply(a: number, b: number): number {
        const precision = 100;
        return Math.round(a * precision * b) / precision;
    }
  
    // 总价计算
    static calculateTotal(items: CartItem[]): number {
        let total = 0;
        for (const item of items) {
            total = this.add(total, this.multiply(item.price, item.quantity));
        }
        return total;
    }
}

4.2 库存同步问题

问题:商品库存实时变化,购物车商品可能已售罄。

解决方案

// 库存检查
class StockChecker {
    // 检查库存
    async checkStock(items: CartItem[]): Promise<Map<string, number>> {
        const stockMap = new Map<string, number>();
      
        for (const item of items) {
            // 查询实时库存
            const stock = await this.queryStock(item.skuId);
            stockMap.set(item.skuId, stock);
        }
      
        return stockMap;
    }
  
    // 查询库存
    private async queryStock(skuId: string): Promise<number> {
        // 调用库存查询接口
        return 100;
    }
}

五、HarmonyOS 6适配

5.1 API差异

// HarmonyOS 6分布式数据API
import { distributedData } from '@kit.ArkData';

// 新增:数据监听配置
const config: distributedData.SubscribeConfig = {
    type: distributedData.SubscribeType.SUBSCRIBE_TYPE_ALL,
    mode: 'realtime'  // 实时模式
};

六、总结一下下

分布式购物场景让购物体验突破设备限制,核心要点:

  1. 购物车同步:实时同步、冲突解决、离线支持
  2. 浏览记录同步:跨设备共享浏览历史
  3. 协同购物:家人共享购物车
  4. 价格计算:精确计算、库存检查
  5. HarmonyOS 6:实时数据监听

分布式购物场景的实现,让购物不再受设备限制,真正实现了"随时随地、无缝购物"的体验。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐