鸿蒙智能购物清单应用开发指南

一、功能概述

本文将介绍如何基于HarmonyOS的AI能力开发一款智能购物清单应用,主要功能包括:

  1. 拍照识别商品并自动生成购物清单(使用@ohos.ai.objectDetection)
  2. 支持商品分类和数量识别
  3. 跨设备同步购物清单
  4. 多人协同编辑购物清单

二、技术架构

graph TD
    A[商品拍照] --> B[AI物体识别]
    B --> C[清单生成]
    C --> D[分布式数据同步]
    D --> E[其他设备]

三、核心代码实现

1. 商品识别模块

import objectDetection from '@ohos.ai.objectDetection';
import image from '@ohos.multimedia.image';

class ProductDetector {
    private detector: objectDetection.ObjectDetector;
    
    async initDetector() {
        try {
            const context = getContext(this) as common.UIAbilityContext;
            this.detector = await objectDetection.createObjectDetector(context);
            
            const config: objectDetection.DetectionConfig = {
                detectMode: objectDetection.DetectMode.MODE_ACCURATE,
                objectType: objectDetection.ObjectType.TYPE_GOODS
            };
            await this.detector.setConfig(config);
        } catch (err) {
            console.error(`初始化物体检测器失败: ${err.code}, ${err.message}`);
        }
    }
    
    async detectProducts(imageSource: image.ImageSource): Promise<Product[]> {
        try {
            const pixelMap = await imageSource.createPixelMap();
            const results = await this.detector.detect(pixelMap);
            return this.parseDetectionResults(results);
        } catch (err) {
            console.error(`商品检测失败: ${err.code}, ${err.message}`);
            return [];
        }
    }
    
    private parseDetectionResults(results: objectDetection.DetectionResult[]): Product[] {
        return results.map(result => {
            return {
                name: result.objectName,
                category: this.getCategory(result.objectName),
                quantity: this.estimateQuantity(result.boundingBox),
                boundingBox: result.boundingBox
            };
        });
    }
    
    private getCategory(productName: string): string {
        // 简单分类逻辑 - 实际应用中可以使用更复杂的分类器
        const categories: Record<string, string[]> = {
            '水果': ['苹果', '香蕉', '橙子', '葡萄'],
            '蔬菜': ['胡萝卜', '西红柿', '黄瓜', '土豆'],
            '饮品': ['牛奶', '果汁', '啤酒', '矿泉水']
        };
        
        for (const [category, items] of Object.entries(categories)) {
            if (items.includes(productName)) {
                return category;
            }
        }
        return '其他';
    }
    
    private estimateQuantity(box: objectDetection.Rect): number {
        // 根据物体大小估算数量 - 简化版
        const area = box.right - box.left * (box.bottom - box.top);
        return area > 0.3 ? 2 : 1; // 面积大于阈值认为是2个
    }
}

interface Product {
    name: string;
    category: string;
    quantity: number;
    boundingBox?: objectDetection.Rect;
}

2. 分布式购物清单同步

import distributedData from '@ohos.data.distributedData';
import deviceManager from '@ohos.distributedDeviceManager';

class ShoppingListSync {
    private kvManager: distributedData.KVManager;
    private kvStore: distributedData.KVStore;
    private deviceManager: deviceManager.DeviceManager;
    
    async initSyncService() {
        const config = {
            bundleName: 'com.example.shoppinglist',
            userInfo: {
                userId: 'currentUser'
            }
        };
        
        try {
            this.kvManager = distributedData.createKVManager(config);
            const options = {
                createIfMissing: true,
                encrypt: false,
                backup: false,
                autoSync: true,
                kvStoreType: distributedData.KVStoreType.SINGLE_VERSION,
                schema: JSON.stringify({
                    name: 'ShoppingList',
                    attributes: {
                        items: { type: 'array' },
                        lastModified: { type: 'number' },
                        modifiedBy: { type: 'string' }
                    }
                })
            };
            
            this.kvStore = await this.kvManager.getKVStore('shopping_list', options);
            
            // 初始化设备管理
            const DM_ABILITY_NAME = "com.example.shopping.DmAbility";
            this.deviceManager = deviceManager.createDeviceManager(DM_ABILITY_NAME);
            
            // 订阅数据变更
            this.kvStore.on('dataChange', distributedData.SubscribeType.SUBSCRIBE_TYPE_ALL, (data) => {
                this.handleListUpdate(data);
            });
        } catch (err) {
            console.error(`初始化同步服务失败: ${err.code}, ${err.message}`);
        }
    }
    
    async syncList(items: ShoppingItem[]) {
        const deviceId = this.deviceManager.getLocalDeviceInfo().deviceId;
        const listData = {
            items: items,
            lastModified: Date.now(),
            modifiedBy: deviceId
        };
        
        try {
            await this.kvStore.put('current_list', JSON.stringify(listData));
        } catch (err) {
            console.error(`同步购物清单失败: ${err.code}, ${err.message}`);
        }
    }
    
    private handleListUpdate(data: distributedData.ChangeNotification) {
        const remoteData = data.inserted[0] || data.updated[0];
        if (remoteData && remoteData.key === 'current_list') {
            const listData = JSON.parse(remoteData.value);
            const localDeviceId = this.deviceManager.getLocalDeviceInfo().deviceId;
            
            // 忽略自己发出的更新
            if (listData.modifiedBy !== localDeviceId) {
                this.applyRemoteUpdate(listData.items);
            }
        }
    }
    
    private applyRemoteUpdate(items: ShoppingItem[]) {
        // 更新UI或本地数据
        console.info('收到远程购物清单更新:', items);
    }
}

interface ShoppingItem {
    id: string;
    name: string;
    category: string;
    quantity: number;
    checked: boolean;
}

3. 主界面实现 (ArkUI)

@Entry
@Component
struct ShoppingListPage {
    @State listItems: ShoppingItem[] = [];
    @State isScanning: boolean = false;
    
    private detector = new ProductDetector();
    private syncService = new ShoppingListSync();
    private cameraController: CameraController;
    
    async aboutToAppear() {
        await this.detector.initDetector();
        await this.syncService.initSyncService();
    }
    
    build() {
        Column() {
            // 标题栏
            Row() {
                Text('智能购物清单')
                    .fontSize(24)
                    .fontWeight(FontWeight.Bold)
                
                Button('拍照添加')
                    .onClick(() => this.startScanning())
                    .margin({ left: 20 })
            }
            .width('100%')
            .justifyContent(FlexAlign.SpaceBetween)
            .padding(10)
            
            // 分类标签
            CategoryTabs({
                categories: this.getCategories(),
                onCategoryChange: (category) => this.filterByCategory(category)
            })
            
            // 购物清单列表
            List() {
                ForEach(this.listItems, (item) => {
                    ListItem() {
                        ShoppingItem({
                            item: item,
                            onCheck: (checked) => this.toggleItem(item.id, checked),
                            onQuantityChange: (qty) => this.updateQuantity(item.id, qty)
                        })
                    }
                }, item => item.id)
            }
            .layoutWeight(1)
            .width('100%')
            
            // 扫描界面
            if (this.isScanning) {
                ScannerOverlay({
                    onCancel: () => this.isScanning = false,
                    onConfirm: (image) => this.processScannedImage(image)
                })
            }
        }
        .height('100%')
    }
    
    private getCategories(): string[] {
        const categories = new Set<string>();
        this.listItems.forEach(item => categories.add(item.category));
        return Array.from(categories);
    }
    
    private filterByCategory(category: string) {
        // 实现分类过滤逻辑
    }
    
    private async startScanning() {
        this.isScanning = true;
    }
    
    private async processScannedImage(image: image.ImageSource) {
        this.isScanning = false;
        const detectedProducts = await this.detector.detectProducts(image);
        
        const newItems = detectedProducts.map(product => {
            return {
                id: generateId(),
                name: product.name,
                category: product.category,
                quantity: product.quantity,
                checked: false
            };
        });
        
        this.listItems = this.mergeItems(this.listItems, newItems);
        this.syncService.syncList(this.listItems);
    }
    
    private mergeItems(existing: ShoppingItem[], newItems: ShoppingItem[]): ShoppingItem[] {
        // 合并已有商品和新增商品,相同商品数量累加
        const merged = [...existing];
        
        newItems.forEach(newItem => {
            const existingItem = merged.find(item => item.name === newItem.name);
            if (existingItem) {
                existingItem.quantity += newItem.quantity;
            } else {
                merged.push(newItem);
            }
        });
        
        return merged;
    }
    
    private toggleItem(id: string, checked: boolean) {
        this.listItems = this.listItems.map(item => {
            if (item.id === id) {
                return { ...item, checked };
            }
            return item;
        });
        this.syncService.syncList(this.listItems);
    }
    
    private updateQuantity(id: string, quantity: number) {
        this.listItems = this.listItems.map(item => {
            if (item.id === id) {
                return { ...item, quantity };
            }
            return item;
        });
        this.syncService.syncList(this.listItems);
    }
}

// 生成唯一ID
function generateId(): string {
    return Math.random().toString(36).substring(2, 9);
}

4. 商品组件实现

@Component
struct ShoppingItem {
    @Prop item: ShoppingItem;
    @Emit onCheck: (checked: boolean) => void;
    @Emit onQuantityChange: (quantity: number) => void;
    
    build() {
        Row() {
            // 复选框
            Checkbox()
                .select(this.item.checked)
                .onChange(checked => this.onCheck(checked))
                .margin({ right: 10 })
            
            // 商品信息
            Column() {
                Text(this.item.name)
                    .fontSize(18)
                Text(this.item.category)
                    .fontSize(12)
                    .fontColor(Color.Gray)
            }
            .layoutWeight(1)
            
            // 数量控制
            QuantityStepper({
                quantity: this.item.quantity,
                onChange: qty => this.onQuantityChange(qty)
            })
        }
        .padding(10)
        .borderRadius(8)
        .backgroundColor(this.item.checked ? '#f5f5f5' : '#ffffff')
        .opacity(this.item.checked ? 0.6 : 1)
    }
}

@Component
struct QuantityStepper {
    @State quantity: number = 1;
    @Emit onChange: (quantity: number) => void;
    
    build() {
        Row() {
            Button('-')
                .onClick(() => this.changeQuantity(-1))
                .width(30)
                .height(30)
            
            Text(this.quantity.toString())
                .width(40)
                .textAlign(TextAlign.Center)
            
            Button('+')
                .onClick(() => this.changeQuantity(1))
                .width(30)
                .height(30)
        }
    }
    
    changeQuantity(delta: number) {
        const newQuantity = Math.max(1, this.quantity + delta);
        this.quantity = newQuantity;
        this.onChange(newQuantity);
    }
}

四、关键优化点

  1. ​AI识别优化​​:

    // 使用更精确的商品识别模型
    async initDetector() {
        const config: objectDetection.DetectionConfig = {
            detectMode: objectDetection.DetectMode.MODE_ACCURATE,
            objectType: objectDetection.ObjectType.TYPE_GOODS,
            modelName: 'enhanced_shopping_model' // 使用优化后的模型
        };
        await this.detector.setConfig(config);
    }
  2. ​冲突解决策略​​:

    private handleListUpdate(data: distributedData.ChangeNotification) {
        const remoteData = data.inserted[0] || data.updated[0];
        if (remoteData && remoteData.key === 'current_list') {
            const remoteList = JSON.parse(remoteData.value);
            const localDeviceId = this.deviceManager.getLocalDeviceInfo().deviceId;
            
            // 使用最后修改时间解决冲突
            if (remoteList.lastModified > this.lastModified) {
                this.applyRemoteUpdate(remoteList.items);
            }
        }
    }
  3. ​离线支持​​:

    async syncList(items: ShoppingItem[]) {
        try {
            // 先保存到本地
            await this.kvStore.put('local_list_backup', JSON.stringify(items));
            
            // 尝试同步到网络
            if (this.isOnline()) {
                await this.kvStore.put('current_list', JSON.stringify({
                    items: items,
                    lastModified: Date.now(),
                    modifiedBy: this.deviceManager.getLocalDeviceInfo().deviceId
                }));
            }
        } catch (err) {
            console.error('同步失败,已保存本地备份');
        }
    }

五、扩展功能实现

1. 多人协同编辑

// 添加编辑锁机制
async lockListForEditing() {
    const deviceId = this.deviceManager.getLocalDeviceInfo().deviceId;
    const lockInfo = {
        lockedBy: deviceId,
        lockedAt: Date.now()
    };
    
    try {
        await this.kvStore.put('list_lock', JSON.stringify(lockInfo));
        return true;
    } catch (err) {
        console.error('获取编辑锁失败');
        return false;
    }
}

// 检查列表是否被其他设备编辑
async checkListLock(): Promise<boolean> {
    try {
        const lockData = await this.kvStore.get('list_lock');
        if (lockData) {
            const lockInfo = JSON.parse(lockData.toString());
            const localDeviceId = this.deviceManager.getLocalDeviceInfo().deviceId;
            
            // 锁不是本设备持有且未超时(30秒)
            if (lockInfo.lockedBy !== localDeviceId && 
                Date.now() - lockInfo.lockedAt < 30000) {
                return true;
            }
        }
        return false;
    } catch (err) {
        return false;
    }
}

2. 智能推荐

// 基于历史购物数据推荐商品
class ShoppingRecommender {
    private history: ShoppingHistory[];
    
    async loadHistory() {
        try {
            const historyData = await this.kvStore.get('shopping_history');
            this.history = JSON.parse(historyData.toString()) || [];
        } catch (err) {
            this.history = [];
        }
    }
    
    getRecommendations(): RecommendedItem[] {
        // 分析历史数据,返回推荐商品
        const frequentlyBought = this.analyzeFrequency();
        return frequentlyBought.slice(0, 5); // 返回前5个常购商品
    }
    
    private analyzeFrequency(): { name: string, frequency: number }[] {
        // 实现频率分析逻辑
    }
}

// 在UI中显示推荐
@Component
struct RecommendationBar {
    @State recommendations: RecommendedItem[] = [];
    private recommender = new ShoppingRecommender();
    
    async aboutToAppear() {
        await this.recommender.loadHistory();
        this.recommendations = this.recommender.getRecommendations();
    }
    
    build() {
        Scroll() {
            Row() {
                ForEach(this.recommendations, (item) => {
                    RecommendationItem({ item: item })
                })
            }
        }
        .height(80)
    }
}

六、测试方案

1. 商品识别测试

describe('ProductDetector Test', () => {
    let detector: ProductDetector;
    
    before(async () => {
        detector = new ProductDetector();
        await detector.initDetector();
    });
    
    it('should detect common grocery items', async () => {
        const testImage = await loadTestImage('groceries.jpg');
        const products = await detector.detectProducts(testImage);
        
        expect(products.length).toBeGreaterThan(0);
        expect(products.some(p => p.name === '苹果')).toBeTruthy();
    });
});

2. 分布式同步测试

describe('ShoppingListSync Test', () => {
    let syncService: ShoppingListSync;
    
    before(async () => {
        syncService = new ShoppingListSync();
        await syncService.initSyncService();
    });
    
    it('should sync items across devices', async () => {
        const testItems: ShoppingItem[] = [
            { id: '1', name: '牛奶', category: '饮品', quantity: 2, checked: false }
        ];
        
        await syncService.syncList(testItems);
        
        // 模拟远程设备接收
        const remoteData = await syncService.kvStore.get('current_list');
        const receivedItems = JSON.parse(remoteData.toString()).items;
        
        expect(receivedItems).toEqual(testItems);
    });
});

七、总结

本文实现的智能购物清单应用具有以下特点:

  1. ​智能识别​​:利用@ohos.ai.objectDetection实现高精度商品识别
  2. ​自动分类​​:基于商品名称自动归类,便于管理
  3. ​无缝协同​​:通过分布式数据服务实现多设备实时同步
  4. ​灵活交互​​:支持多人同时编辑和智能推荐

该方案可以轻松扩展为家庭共享购物清单、超市智能导购等场景,展现了HarmonyOS在AI和分布式能力方面的强大优势。开发者可以根据实际需求,进一步优化商品识别算法或增加语音输入等交互方式。

Logo

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

更多推荐