HarmonyOS开发:完整电商应用实战

举报
Jack20 发表于 2026/06/26 17:21:13 2026/06/26
【摘要】 HarmonyOS开发:完整电商应用实战📌 核心要点:从架构设计到功能实现,首页、详情、购物车、订单、支付全链路打通,模块化架构+状态管理+数据层是电商App的三大支柱。 背景与动机前面9篇文章,我们把电商App的每个模块拆开讲了——首页、详情、购物车、订单、支付、社交、IM、个人中心、搜索。每个模块都能跑,但拼到一起呢?你把10个模块的代码往一个项目里一扔,发现:购物车的数据怎么传给订...

HarmonyOS开发:完整电商应用实战

📌 核心要点:从架构设计到功能实现,首页、详情、购物车、订单、支付全链路打通,模块化架构+状态管理+数据层是电商App的三大支柱。

背景与动机

前面9篇文章,我们把电商App的每个模块拆开讲了——首页、详情、购物车、订单、支付、社交、IM、个人中心、搜索。每个模块都能跑,但拼到一起呢?

你把10个模块的代码往一个项目里一扔,发现:购物车的数据怎么传给订单页?支付结果怎么通知订单列表?用户登录状态怎么全局共享?首页的商品点击怎么跳到详情页?详情页的加购怎么更新购物车角标?

单模块能跑≠整个App能跑。模块之间的数据流转、状态同步、页面导航,才是电商App真正的难点。

这篇文章把所有模块串起来,从架构设计到数据管理到页面导航,给你一个能跑的完整电商App。

核心原理

完整电商App的核心是模块化架构+全局状态管理+统一数据层。每个模块独立开发,通过全局状态和数据层通信。

flowchart TD
    A[App入口] --> B[全局状态管理 AppStore]
    A --> C[数据层 Repository]
    A --> D[路由管理 Router]
    
    B --> B1[用户状态]
    B --> B2[购物车状态]
    B --> B3[订单状态]
    B --> B4[搜索状态]
    
    C --> C1[用户Repository]
    C --> C2[商品Repository]
    C --> C3[购物车Repository]
    C --> C4[订单Repository]
    
    D --> E[首页]
    D --> F[详情页]
    D --> G[购物车]
    D --> H[订单页]
    D --> I[支付页]
    D --> J[个人中心]
    D --> K[搜索页]
    
    E --> C2
    F --> C2
    G --> C3
    H --> C4
    I --> C4
    
    B2 --> G
    B2 --> E
    
    classDef core fill:#1565C0,color:#fff,stroke:#0D47A1
    classDef state fill:#2E7D32,color:#fff,stroke:#1B5E20
    classDef data fill:#E65100,color:#fff,stroke:#BF360C
    classDef page fill:#6A1B9A,color:#fff,stroke:#4A148C
    
    class A,B,C,D,core
    class B1,B2,B3,B4,state
    class C1,C2,C3,C4,data
    class E,F,G,H,I,J,K,page

架构分层

层级 职责 示例
展示层 UI渲染、用户交互 页面组件、Builder
状态层 全局状态管理、状态同步 AppStore、ViewModel
数据层 数据获取、缓存、持久化 Repository、Service
网络层 HTTP请求、WebSocket HttpClient、WSManager

数据流转规则

  1. 页面→状态层:用户操作触发状态更新
  2. 状态层→数据层:状态变更需要持久化时调用数据层
  3. 数据层→网络层:需要服务端数据时发起网络请求
  4. 网络层→数据层→状态层→页面:数据回流,更新UI

严禁跨层调用。页面不能直接调网络层,网络层不能直接更新UI。

代码实战

基础用法:全局状态管理

电商App有大量全局状态:用户登录状态、购物车数量、未读消息数。这些状态需要在多个页面间共享。

// AppStore.ets — 全局状态管理

// 用户状态
interface UserState {
  isLogin: boolean
  userId: string
  nickname: string
  avatar: string
  phone: string
}

// 购物车状态
interface CartState {
  totalCount: number       // 购物车商品总数
  selectedCount: number    // 选中商品数
  totalPrice: number       // 选中商品总价
}

// 应用全局状态
interface AppState {
  user: UserState
  cart: CartState
  unreadMessageCount: number
  currentTab: number       // 当前Tab索引
}

// 全局状态管理器
class AppStore {
  private static instance: AppStore
  
  // 全局状态
  private state: AppState = {
    user: {
      isLogin: false,
      userId: '',
      nickname: '',
      avatar: '',
      phone: ''
    },
    cart: {
      totalCount: 0,
      selectedCount: 0,
      totalPrice: 0
    },
    unreadMessageCount: 0,
    currentTab: 0
  }

  // 状态变更监听器
  private listeners: Array<() => void> = []

  private constructor() {}

  static getInstance(): AppStore {
    if (!AppStore.instance) {
      AppStore.instance = new AppStore()
    }
    return AppStore.instance
  }

  // ========== 用户状态 ==========

  login(userId: string, nickname: string, avatar: string, phone: string): void {
    this.state.user = { isLogin: true, userId, nickname, avatar, phone }
    this.notifyListeners()
  }

  logout(): void {
    this.state.user = { isLogin: false, userId: '', nickname: '', avatar: '', phone: '' }
    this.state.cart = { totalCount: 0, selectedCount: 0, totalPrice: 0 }
    this.notifyListeners()
  }

  getUser(): UserState {
    return this.state.user
  }

  isLogin(): boolean {
    return this.state.user.isLogin
  }

  // ========== 购物车状态 ==========

  updateCartState(totalCount: number, selectedCount: number, totalPrice: number): void {
    this.state.cart = { totalCount, selectedCount, totalPrice }
    this.notifyListeners()
  }

  getCartCount(): number {
    return this.state.cart.totalCount
  }

  getCartState(): CartState {
    return { ...this.state.cart }
  }

  // ========== 消息状态 ==========

  setUnreadCount(count: number): void {
    this.state.unreadMessageCount = count
    this.notifyListeners()
  }

  getUnreadCount(): number {
    return this.state.unreadMessageCount
  }

  // ========== Tab状态 ==========

  setCurrentTab(index: number): void {
    this.state.currentTab = index
  }

  getCurrentTab(): number {
    return this.state.currentTab
  }

  // ========== 监听器 ==========

  addListener(listener: () => void): void {
    this.listeners.push(listener)
  }

  removeListener(listener: () => void): void {
    const idx = this.listeners.indexOf(listener)
    if (idx >= 0) this.listeners.splice(idx, 1)
  }

  private notifyListeners(): void {
    this.listeners.forEach(l => l())
  }

  // 获取完整状态
  getState(): AppState {
    return { ...this.state }
  }
}

进阶用法:数据层与Repository

数据层封装了所有数据获取逻辑,页面不需要关心数据来自网络还是本地缓存。

// Repository.ets — 数据层

// ========== 商品Repository ==========
class ProductRepository {
  private static instance: ProductRepository
  private cache: Map<string, { data: ProductDetail; expireTime: number }> = new Map()
  private cacheDuration: number = 5 * 60 * 1000  // 缓存5分钟

  private constructor() {}

  static getInstance(): ProductRepository {
    if (!ProductRepository.instance) {
      ProductRepository.instance = new ProductRepository()
    }
    return ProductRepository.instance
  }

  // 获取商品详情(先查缓存,再请求网络)
  async getProductDetail(productId: string): Promise<ProductDetail | null> {
    // 1. 查缓存
    const cached = this.cache.get(productId)
    if (cached && cached.expireTime > Date.now()) {
      console.info(`[ProductRepo] 命中缓存: ${productId}`)
      return cached.data
    }

    // 2. 请求网络
    try {
      // 实际项目:const response = await http.get(`/api/product/${productId}`)
      const data = this.getMockProductDetail(productId)
      
      // 3. 写入缓存
      this.cache.set(productId, {
        data: data,
        expireTime: Date.now() + this.cacheDuration
      })
      
      return data
    } catch (error) {
      console.error(`[ProductRepo] 获取商品详情失败: ${JSON.stringify(error)}`)
      return null
    }
  }

  // 搜索商品
  async searchProducts(
    keyword: string,
    page: number,
    size: number,
    filter?: SearchFilter
  ): Promise<{ items: SearchResultItem[]; total: number }> {
    try {
      // 实际项目:const response = await http.post('/api/search', { keyword, page, size, filter })
      return {
        items: this.getMockSearchResults(keyword, page, size),
        total: 100
      }
    } catch (error) {
      return { items: [], total: 0 }
    }
  }

  // 获取首页推荐商品
  async getHomeProducts(page: number, size: number): Promise<ProductItem[]> {
    // 实际项目:const response = await http.get(`/api/home/products?page=${page}&size=${size}`)
    return this.getMockHomeProducts(page, size)
  }

  // 清除缓存
  clearCache(): void {
    this.cache.clear()
  }

  // 模拟数据
  private getMockProductDetail(productId: string): ProductDetail {
    return {
      id: productId,
      title: '精选商品详情展示',
      subtitle: '高品质好物推荐',
      images: [
        { id: '1', url: `https://picsum.photos/750/750?random=${productId}1`, width: 750, height: 750, isVideo: false },
        { id: '2', url: `https://picsum.photos/750/750?random=${productId}2`, width: 750, height: 750, isVideo: false },
      ],
      specGroups: [
        { id: '1', name: '颜色', options: [
          { id: '1', name: '黑色', available: true, selected: false },
          { id: '2', name: '白色', available: true, selected: false },
        ]},
        { id: '2', name: '尺码', options: [
          { id: '3', name: 'M', available: true, selected: false },
          { id: '4', name: 'L', available: true, selected: false },
        ]},
      ],
      skuList: [
        { id: 'sku1', specCombination: { '颜色': '黑色', '尺码': 'M' }, price: 299, originalPrice: 399, stock: 10, imageUrl: '' },
        { id: 'sku2', specCombination: { '颜色': '黑色', '尺码': 'L' }, price: 319, originalPrice: 419, stock: 5, imageUrl: '' },
        { id: 'sku3', specCombination: { '颜色': '白色', '尺码': 'M' }, price: 299, originalPrice: 399, stock: 8, imageUrl: '' },
        { id: 'sku4', specCombination: { '颜色': '白色', '尺码': 'L' }, price: 319, originalPrice: 419, stock: 0, imageUrl: '' },
      ],
      description: '',
      specs: [{ name: '材质', value: '纯棉' }, { name: '风格', value: '休闲' }],
      rating: 4.8,
      reviewCount: 2365,
      reviews: [],
      shopName: '品牌旗舰店',
      shopId: 'shop_001'
    }
  }

  private getMockSearchResults(keyword: string, page: number, size: number): SearchResultItem[] {
    return Array.from({ length: size }, (_, i) => ({
      id: `search_${page}_${i}`,
      title: `${keyword}相关商品 ${page * size + i + 1}`,
      price: Math.round(Math.random() * 500 + 10),
      originalPrice: Math.round(Math.random() * 800 + 100),
      coverUrl: `https://picsum.photos/200/200?random=${page * size + i}`,
      sales: Math.round(Math.random() * 2000),
      brand: '品牌',
      category: '分类',
      tags: []
    }))
  }

  private getMockHomeProducts(page: number, size: number): ProductItem[] {
    return Array.from({ length: size }, (_, i) => ({
      id: `home_${page}_${i}`,
      title: `精选好物推荐 ${page * size + i + 1}`,
      price: Math.round(Math.random() * 500 + 10),
      originalPrice: Math.round(Math.random() * 800 + 100),
      coverUrl: `https://picsum.photos/300/${200 + Math.round(Math.random() * 100)}?random=${page * size + i}`,
      sales: Math.round(Math.random() * 2000),
      tags: [],
      imageHeight: 150 + Math.round(Math.random() * 80)
    }))
  }
}

// ========== 购物车Repository ==========
class CartRepository {
  private static instance: CartRepository
  private cartItems: CartItem[] = []

  private constructor() {}

  static getInstance(): CartRepository {
    if (!CartRepository.instance) {
      CartRepository.instance = new CartRepository()
    }
    return CartRepository.instance
  }

  async getCartItems(): Promise<CartItem[]> {
    // 实际项目:const response = await http.get('/api/cart/list')
    return this.cartItems
  }

  async addToCart(item: CartItem): Promise<boolean> {
    // 实际项目:const response = await http.post('/api/cart/add', item)
    const existing = this.cartItems.find(i => i.skuId === item.skuId)
    if (existing) {
      existing.quantity += item.quantity
    } else {
      this.cartItems.push(item)
    }
    this.updateAppStoreCart()
    return true
  }

  async removeFromCart(cartItemId: string): Promise<boolean> {
    this.cartItems = this.cartItems.filter(i => i.id !== cartItemId)
    this.updateAppStoreCart()
    return true
  }

  async updateQuantity(cartItemId: string, quantity: number): Promise<boolean> {
    const item = this.cartItems.find(i => i.id === cartItemId)
    if (item) {
      item.quantity = Math.max(1, Math.min(quantity, item.stock))
      this.updateAppStoreCart()
    }
    return true
  }

  private updateAppStoreCart(): void {
    const totalCount = this.cartItems.reduce((sum, i) => sum + i.quantity, 0)
    const selectedItems = this.cartItems.filter(i => i.selected && i.isValid)
    const selectedCount = selectedItems.reduce((sum, i) => sum + i.quantity, 0)
    const totalPrice = selectedItems.reduce((sum, i) => sum + i.price * i.quantity, 0)
    AppStore.getInstance().updateCartState(totalCount, selectedCount, totalPrice)
  }
}

// ========== 订单Repository ==========
class OrderRepository {
  private static instance: OrderRepository

  private constructor() {}

  static getInstance(): OrderRepository {
    if (!OrderRepository.instance) {
      OrderRepository.instance = new OrderRepository()
    }
    return OrderRepository.instance
  }

  async createOrder(items: CartItem[], addressId: string): Promise<OrderInfo | null> {
    // 实际项目:const response = await http.post('/api/order/create', { items, addressId })
    const order: OrderInfo = {
      id: `order_${Date.now()}`,
      orderNo: `20241225${Date.now()}`,
      status: OrderStatus.PENDING_PAYMENT,
      totalPrice: items.reduce((sum, i) => sum + i.price * i.quantity, 0),
      items: items.map(i => ({
        productId: i.productId,
        skuId: i.skuId,
        title: i.title,
        imageUrl: i.imageUrl,
        specDesc: i.specDesc,
        price: i.price,
        quantity: i.quantity
      })),
      createTime: new Date().toISOString(),
      shopName: items[0]?.shopName || ''
    }
    return order
  }

  async getOrderList(status?: OrderStatus): Promise<OrderInfo[]> {
    // 实际项目:const response = await http.get('/api/order/list', { status })
    return []
  }

  async cancelOrder(orderId: string): Promise<boolean> {
    // 实际项目:const response = await http.post('/api/order/cancel', { orderId })
    return true
  }
}

完整示例:电商App主框架

把所有模块串成完整App——底部Tab导航、页面路由、全局状态同步。

// ECommerceApp.ets — 完整电商应用
import { router } from '@kit.ArkUI'

// ========== 底部Tab配置 ==========
interface TabConfig {
  title: string
  icon: Resource
  activeIcon: Resource
  pageUrl: string
}

@Entry
@Component
struct ECommerceApp {
  @State currentTab: number = 0
  @State cartCount: number = 0
  @State unreadCount: number = 0
  @State isLogin: boolean = false

  private appStore: AppStore = AppStore.getInstance()
  private tabs: TabConfig[] = [
    { title: '首页', icon: $r('app.media.ic_home'), activeIcon: $r('app.media.ic_home_active'), pageUrl: 'pages/HomePage' },
    { title: '分类', icon: $r('app.media.ic_category'), activeIcon: $r('app.media.ic_category_active'), pageUrl: 'pages/CategoryPage' },
    { title: '购物车', icon: $r('app.media.ic_cart'), activeIcon: $r('app.media.ic_cart_active'), pageUrl: 'pages/CartPage' },
    { title: '消息', icon: $r('app.media.ic_message'), activeIcon: $r('app.media.ic_message_active'), pageUrl: 'pages/MessagePage' },
    { title: '我的', icon: $r('app.media.ic_profile'), activeIcon: $r('app.media.ic_profile_active'), pageUrl: 'pages/ProfilePage' },
  ]

  aboutToAppear() {
    // 监听全局状态变化
    this.appStore.addListener(() => {
      this.cartCount = this.appStore.getCartCount()
      this.unreadCount = this.appStore.getUnreadCount()
      this.isLogin = this.appStore.isLogin()
    })

    // 初始化数据
    this.initApp()
  }

  build() {
    Tabs() {
      // 首页Tab
      TabContent() {
        this.HomeTabContent()
      }
      .tabBar(this.TabBar(0))

      // 分类Tab
      TabContent() {
        this.CategoryTabContent()
      }
      .tabBar(this.TabBar(1))

      // 购物车Tab
      TabContent() {
        this.CartTabContent()
      }
      .tabBar(this.TabBar(2))

      // 消息Tab
      TabContent() {
        this.MessageTabContent()
      }
      .tabBar(this.TabBar(3))

      // 我的Tab
      TabContent() {
        this.ProfileTabContent()
      }
      .tabBar(this.TabBar(4))
    }
    .barPosition(BarPosition.End)
    .scrollable(false)
    .onChange((index: number) => {
      this.currentTab = index
      this.appStore.setCurrentTab(index)
    })
    .width('100%')
    .height('100%')
  }

  // ========== 自定义TabBar ==========
  @Builder
  TabBar(index: number) {
    Column() {
      Stack() {
        Image(index === this.currentTab ? this.tabs[index].activeIcon : this.tabs[index].icon)
          .width(24)
          .height(24)
          .fillColor(index === this.currentTab ? '#FF4444' : '#999999')

        // 购物车角标
        if (index === 2 && this.cartCount > 0) {
          Text(this.cartCount > 99 ? '99+' : `${this.cartCount}`)
            .fontSize(9)
            .fontColor(Color.White)
            .backgroundColor('#FF4444')
            .borderRadius(8)
            .padding({ left: 4, right: 4, top: 1, bottom: 1 })
            .position({ x: 14, y: -4 })
        }

        // 消息角标
        if (index === 3 && this.unreadCount > 0) {
          Circle()
            .width(8)
            .height(8)
            .fill('#FF4444')
            .position({ x: 16, y: -2 })
        }
      }
      .width(24)
      .height(24)

      Text(this.tabs[index].title)
        .fontSize(10)
        .fontColor(index === this.currentTab ? '#FF4444' : '#999999')
        .margin({ top: 2 })
    }
    .width('100%')
    .height(50)
    .justifyContent(FlexAlign.Center)
  }

  // ========== 首页内容 ==========
  @Builder
  HomeTabContent() {
    Column() {
      // 顶部搜索栏
      Row() {
        Image($r('app.media.ic_scan'))
          .width(24).height(24).fillColor('#333333').margin({ right: 12 })

        Row() {
          Image($r('app.media.ic_search')).width(16).height(16).fillColor('#999999')
          Text('搜索商品').fontSize(14).fontColor('#999999').margin({ left: 6 })
        }
        .layoutWeight(1).height(36).padding({ left: 12 })
        .backgroundColor('#F0F0F0').borderRadius(18)
        .onClick(() => {
          router.pushUrl({ url: 'pages/SearchPage' })
        })

        Image($r('app.media.ic_message')).width(24).height(24).fillColor('#333333').margin({ left: 12 })
      }
      .width('100%').height(52).padding({ left: 16, right: 16 }).backgroundColor(Color.White)

      // 首页内容区(简化展示)
      Scroll() {
        Column() {
          // Banner
          Swiper() {
            ForEach([1, 2, 3], (item: number) => {
              Image(`https://picsum.photos/750/360?random=${item}`)
                .width('100%').height(180).borderRadius(8).objectFit(ImageFit.Cover)
            }, (item: number) => `${item}`)
          }
          .autoPlay(true).interval(4000).loop(true)
          .indicator(new DotIndicator().color('#80FFFFFF').selectedColor('#FFFFFF'))
          .width('100%').height(180).margin({ top: 8, left: 12, right: 12 })

          // 金刚区
          Grid() {
            ForEach(['数码', '服装', '食品', '家居', '美妆', '运动', '图书', '母婴', '家电', '更多'], (name: string) => {
              GridItem() {
                Column() {
                  Circle().width(36).height(36).fill('#F0F0F0')
                  Text(name).fontSize(11).fontColor('#333333').margin({ top: 4 })
                }
              }
            }, (name: string) => name)
          }
          .columnsTemplate('1fr 1fr 1fr 1fr 1fr')
          .rowsGap(12).columnsGap(0)
          .width('100%').padding({ left: 12, right: 12, top: 16, bottom: 16 })
          .backgroundColor(Color.White).borderRadius(8).margin({ top: 8, left: 12, right: 12 })

          // 推荐商品
          Text('为你推荐')
            .fontSize(18).fontWeight(FontWeight.Bold).fontColor('#333333')
            .width('100%').padding({ left: 16, top: 16, bottom: 8 })

          // 瀑布流商品(简化展示)
          Grid() {
            ForEach(Array.from({ length: 6 }, (_, i) => i), (i: number) => {
              GridItem() {
                Column() {
                  Image(`https://picsum.photos/300/${200 + i * 20}?random=${i + 10}`)
                    .width('100%').height(150 + i * 10).borderRadius({ topLeft: 8, topRight: 8 })
                    .objectFit(ImageFit.Cover)
                  Column() {
                    Text(`精选好物推荐 ${i + 1}`).fontSize(13).fontColor('#333333')
                      .maxLines(2).textOverflow({ overflow: TextOverflow.Ellipsis })
                    Text(`¥${Math.round(Math.random() * 500 + 10)}`).fontSize(16)
                      .fontWeight(FontWeight.Bold).fontColor('#FF4444').margin({ top: 4 })
                  }.padding({ left: 8, right: 8, top: 4, bottom: 8 }).alignItems(HorizontalAlign.Start)
                }
                .backgroundColor(Color.White).borderRadius(8)
                .onClick(() => {
                  router.pushUrl({ url: 'pages/ProductDetailPage', params: { productId: `p_${i}` } })
                })
              }
            }, (i: number) => `${i}`)
          }
          .columnsTemplate('1fr 1fr').columnsGap(8).rowsGap(8)
          .padding({ left: 12, right: 12 })
        }
      }
      .layoutWeight(1)
      .scrollBar(BarState.Off)
      .edgeEffect(EdgeEffect.Spring)
    }
    .width('100%').height('100%').backgroundColor('#F5F5F5')
  }

  // ========== 分类Tab ==========
  @Builder
  CategoryTabContent() {
    Column() {
      Text('分类页面').fontSize(16).fontColor('#999999')
    }
    .width('100%').height('100%').justifyContent(FlexAlign.Center)
  }

  // ========== 购物车Tab ==========
  @Builder
  CartTabContent() {
    Column() {
      Row() {
        Text('购物车')
          .fontSize(18).fontWeight(FontWeight.Bold).fontColor('#333333')
        if (this.cartCount > 0) {
          Text(`(${this.cartCount})`).fontSize(14).fontColor('#999999').margin({ left: 4 })
        }
        Blank()
        Text('编辑').fontSize(14).fontColor('#333333')
      }
      .width('100%').height(48).padding({ left: 16, right: 16 }).backgroundColor(Color.White)

      if (this.cartCount === 0) {
        Column() {
          Text('🛒').fontSize(64)
          Text('购物车空空如也').fontSize(16).fontColor('#999999').margin({ top: 16 })
          Button('去逛逛').fontSize(14).fontColor(Color.White)
            .backgroundColor('#FF4444').borderRadius(20).width(120).height(40).margin({ top: 24 })
            .onClick(() => { this.currentTab = 0 })
        }.width('100%').layoutWeight(1).justifyContent(FlexAlign.Center)
      } else {
        Text('购物车商品列表区域')
          .fontSize(14).fontColor('#999999')
          .layoutWeight(1).width('100%').textAlign(TextAlign.Center)
          .padding({ top: 100 })
      }
    }
    .width('100%').height('100%').backgroundColor('#F5F5F5')
  }

  // ========== 消息Tab ==========
  @Builder
  MessageTabContent() {
    Column() {
      Text('消息').fontSize(20).fontWeight(FontWeight.Bold).fontColor('#333333')
        .width('100%').padding({ left: 16, top: 16 })
      Text('会话列表区域').fontSize(14).fontColor('#999999')
        .layoutWeight(1).width('100%').textAlign(TextAlign.Center).padding({ top: 100 })
    }
    .width('100%').height('100%').backgroundColor(Color.White)
  }

  // ========== 我的Tab ==========
  @Builder
  ProfileTabContent() {
    Column() {
      // 用户信息头部
      Row() {
        Image($r('app.media.ic_avatar_default'))
          .width(64).height(64).borderRadius(32).fillColor('#1DA1F2')
        Column() {
          Text(this.isLogin ? '鸿蒙开发者' : '点击登录')
            .fontSize(20).fontWeight(FontWeight.Bold).fontColor(Color.White)
          if (this.isLogin) {
            Text('138****8000').fontSize(13).fontColor('#CCFFFFFF').margin({ top: 4 })
          }
        }
        .alignItems(HorizontalAlign.Start).margin({ left: 16 })
        Blank()
        Image($r('app.media.ic_arrow_right')).width(20).height(20).fillColor('#FFFFFF')
      }
      .width('100%').padding({ left: 20, right: 20, top: 24, bottom: 20 })
      .linearGradient({ direction: GradientDirection.Right, colors: [['#1DA1F2', 0], ['#0D7CC4', 1]] })
      .onClick(() => {
        if (!this.isLogin) {
          router.pushUrl({ url: 'pages/LoginPage' })
        } else {
          router.pushUrl({ url: 'pages/UserProfilePage' })
        }
      })

      // 订单入口
      Row() {
        ForEach(['待付款', '待发货', '待收货', '已完成', '退换货'], (title: string) => {
          Column() {
            Circle().width(28).height(28).fill('#F0F0F0')
            Text(title).fontSize(11).fontColor('#333333').margin({ top: 4 })
          }.layoutWeight(1)
        }, (title: string) => title)
      }
      .width('100%').padding({ top: 16, bottom: 16 })
      .backgroundColor(Color.White).borderRadius(8)
      .margin({ top: -16, left: 12, right: 12 })

      // 功能菜单
      Column() {
        ForEach(['优惠券', '我的收藏', '收货地址', '设置'], (title: string) => {
          Row() {
            Text(title).fontSize(15).fontColor('#333333')
            Blank()
            Image($r('app.media.ic_arrow_right')).width(16).height(16).fillColor('#CCCCCC')
          }
          .width('100%').height(52).padding({ left: 16, right: 16 }).backgroundColor(Color.White)
        }, (title: string) => title)
      }
      .borderRadius(8).margin({ top: 8, left: 12, right: 12 })
    }
    .width('100%').height('100%').backgroundColor('#F5F5F5')
  }

  // ========== 初始化App ==========
  async initApp() {
    // 1. 检查登录状态
    const token = await this.loadToken()
    if (token) {
      // 自动登录
      this.appStore.login('user_001', '鸿蒙开发者', 'https://picsum.photos/128/128?random=1', '13800138000')
      this.isLogin = true

      // 2. 加载购物车数据
      const cartRepo = CartRepository.getInstance()
      await cartRepo.getCartItems()

      // 3. 加载未读消息数
      this.appStore.setUnreadCount(5)
    }
  }

  async loadToken(): Promise<string | null> {
    // 实际项目:从加密存储读取Token
    return 'mock_token'
  }
}

踩坑与注意事项

坑1:购物车角标不更新

首页Tab上购物车角标显示3,加了一个商品后还是3——因为角标数据没有同步。

解决方案:购物车数据变更时,调用AppStore.updateCartState()更新全局状态,TabBar监听全局状态变化自动刷新角标。

坑2:页面间数据传递

详情页加购后,购物车页怎么知道有新商品?

解决方案:不要用页面间参数传递,用全局状态。加购操作更新CartRepositoryCartRepository更新AppStore,购物车页从AppStore读取数据。

坑3:登录状态过期

用户登录后Token过期了,请求接口返回401,但页面还显示已登录。

解决方案:网络层统一处理401,自动跳转登录页。登录后刷新Token,重新发起失败的请求。

坑4:首页数据缓存策略

每次切到首页Tab都重新请求数据?那用户来回切换Tab,首页数据反复加载。

解决方案:首页数据缓存5分钟。5分钟内切回首页不重新请求,超过5分钟才刷新。下拉刷新时强制更新。

坑5:Tab切换时页面状态丢失

用户在首页滑到第50个商品,切到购物车再切回来——首页又回到顶部了。

解决方案:每个Tab的内容用独立组件包裹,组件状态由@State管理。Tabs组件设置scrollable(false),避免预加载导致状态丢失。

HarmonyOS 6适配说明

HarmonyOS 6对完整App架构做了以下更新:

  1. AppStorage全局状态:HarmonyOS 6增强了AppStorage能力,支持跨组件、跨页面的状态共享。不需要自己写全局状态管理器,直接用@StorageProp@StorageLink装饰器。
// HarmonyOS 6全局状态
@Entry
@Component
struct SomePage {
  @StorageProp('cartCount') cartCount: number = 0  // 单向绑定
  @StorageLink('userName') userName: string = ''    // 双向绑定
}

// 设置全局状态
AppStorage.setOrCreate('cartCount', 5)
AppStorage.setOrCreate('userName', '鸿蒙开发者')
  1. Navigation组件增强:Navigation新增了NavPathStack路由栈管理,支持声明式路由配置。不需要手动管理页面跳转和返回。

  2. Tabs组件状态保持:Tabs新增了offscreenMode属性,切到其他Tab时当前Tab的内容不会销毁,切回来时状态保持不变。

  3. 模块化开发HSP:HarmonyOS 6的HSP(Harmony Shared Package)支持模块化开发,首页、购物车、订单等模块可以独立编译、独立测试、独立发布。

  4. 应用启动优化:新增StartupTask框架,App启动时并行初始化各模块。登录、购物车、消息等模块的初始化任务可以并行执行,启动速度提升约40%。

总结

完整电商App的核心不是"每个模块都能跑",而是模块之间的数据流转和状态同步。购物车加购→角标更新→订单创建→支付完成→订单状态变更——这条链路任何一个环节断开,用户体验就崩了。

核心记住三点:

  • 全局状态管理是枢纽,用户状态、购物车状态、消息状态必须全局共享
  • 数据层统一封装,页面不直接调网络接口,通过Repository获取数据
  • 页面间不要传数据,用全局状态同步,不要用路由参数传递业务数据
评估维度 说明
学习难度 ⭐⭐⭐⭐⭐ 架构设计、状态管理、数据流转都需要全局视角
使用频率 ⭐⭐⭐⭐⭐ 完整App开发必须掌握
重要程度 ⭐⭐⭐⭐⭐ 架构做不好,后期维护成本指数级增长

模块拼到一起跑不起来——这不是bug,这是架构问题。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。