HarmonyOS分布式购物开发实战:跨设备购物车
·
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' // 实时模式
};
六、总结一下下
分布式购物场景让购物体验突破设备限制,核心要点:
- 购物车同步:实时同步、冲突解决、离线支持
- 浏览记录同步:跨设备共享浏览历史
- 协同购物:家人共享购物车
- 价格计算:精确计算、库存检查
- HarmonyOS 6:实时数据监听
分布式购物场景的实现,让购物不再受设备限制,真正实现了"随时随地、无缝购物"的体验。
更多推荐



所有评论(0)