订单模块实现流程操作指南
订单页面

一、模块概述
订单模块是蛋糕美食元服务的核心业务闭环模块,包含**订单确认(PageOrderPreview)和订单历史(OrderList)**两个子功能。该模块实现了从购物车到下单、从订单创建到历史查看的完整订单生命周期管理。
二、模块职责
| 子功能 | 职责 |
|---|---|
| PageOrderPreview | 订单确认页:展示订单明细、价格汇总、支付操作 |
| OrderList | 订单历史:展示所有历史订单,支持刷新 |
| OrderViewModel | 订单业务逻辑:创建订单、查询订单、取消订单 |
| OrderModel | 订单数据模型:订单ID、金额、状态、商品列表 |
三、数据模型
订单模型(OrderModel.ets)
typescript
export class OrderDetail {
orderId: string = '' // 格式: CK + 时间戳
price: number = 0 // 订单总价
orderType: OrderType = 0 // 0=堂食, 1=外卖
orderShop: string = '' // 下单门店
takeOutAddress: string = '' // 配送地址(外卖时)
orderTime: string = '' // 下单时间戳
orderStatus: OrderStatus = 0 // 0=待支付,1=处理中,2=已取消
orderItem: CakeDetail[] = [] // 订单商品列表
}
// 订单状态枚举
export enum OrderStatus {
UN_PAY, // 待支付
COMMITED, // 处理中
CANCELED // 已取消
}
// 时间格式化工具
export function getFormatTime(timestamp: string): string {
// 返回 "2025-06-04 14:30:00" 格式
}
四、订单确认页(PageOrderPreview)实现
步骤1:页面数据初始化
从 LocalStorage 读取购物车数据,恢复 CartViewModel:
typescript
@Component
export struct PageOrderPreview {
@LocalStorageLink('buyFoods') buyFoodsJson: string = '[]'
@LocalStorageLink('choosePrice') choosePrice: number = 0
@LocalStorageLink('orderType') orderType: number = 0
@LocalStorageLink('chooseShop') chooseShop: string = ''
@StorageLink('selectedIndex') selectedIndex: number = 0
@State cartVM: CartViewModel = new CartViewModel()
@State enableLoading: boolean = false
aboutToAppear(): void {
if (this.buyFoodsJson.length > 2) {
this.cartVM = CartViewModel.fromJSON(this.buyFoodsJson)
}
}
}
步骤2:门店信息区域
typescript
Column() {
Row() {
Text('🏪').fontSize(24)
Text(this.chooseShop).layoutWeight(1)
}
Row() {
Text(this.orderType === OrderType.DINE_IN ? '堂食' : '外卖配送')
.backgroundColor('#FFF0EB')
Text(this.orderType === OrderType.DINE_IN
? '预计取餐时间:30分钟内'
: '预计配送时间:45分钟内')
}
}
步骤3:外卖地址显示(条件渲染)
typescript
if (this.orderType === OrderType.TAKEOUT) {
Column() {
Row() { Text('📍 配送地址') }
if (this.takeOutAddress.length > 0) {
Text(this.takeOutAddress)
} else {
Text('请选择配送地址')
.onClick(() => {
RouterModule.push({
stackName: NavStackMap.MAIN_STACK,
url: NavRouterMap.PAGE_TAKE_OUT
} as NavRouterInfo)
})
}
}
}
步骤4:订单明细列表
遍历购物车中的商品,展示名称、描述、单价和数量:
typescript
ForEach(this.cartVM.getCartItems(), (item: CakeDetail) => {
Row() {
Text(item.pic).width(40).height(40)
Column() {
Text(item.title)
Text(item.desc)
}.layoutWeight(1)
Text(`¥${item.price.toFixed(1)}`)
Text(`x${item.buyNum}`)
}
})
步骤5:价格汇总
typescript
Column() {
Row() {
Text('商品总价')
Blank()
Text(`¥${this.cartVM.getTotalPrice().toFixed(1)}`)
}
Row() {
Text('配送费')
Blank()
Text(this.orderType === OrderType.DINE_IN ? '¥0.0' : '¥5.0')
}
Row() {
Text('实付金额')
Blank()
Text(`¥${(this.cartVM.getTotalPrice() +
(this.orderType === OrderType.DINE_IN ? 0 : 5)).toFixed(1)}`)
.fontColor('#FF6B35')
}
}
价格计算规则:
- 堂食:无配送费
- 外卖:固定配送费 ¥5.0
步骤6:支付流程
使用 LoadingProgress 组件模拟支付过程:
typescript
private async submitOrder(): Promise<void> {
this.enableLoading = true
try {
const context = getContext(this) as Context
const order = await OrderViewModel.createOrder(
context as UIAbilityContext,
this.cartVM,
this.orderType === OrderType.DINE_IN ? OrderType.DINE_IN : OrderType.TAKEOUT,
this.chooseShop,
this.takeOutAddress
)
if (order) {
this.buyFoodsJson = '[]' // 清空购物车
this.choosePrice = 0
this.selectedIndex = 2 // 切换到订单Tab
RouterModule.popToName(NavStackMap.MAIN_STACK, NavRouterMap.PAGE_MAIN)
}
} catch (e) {}
this.enableLoading = false
}
五、订单业务逻辑(OrderViewModel)
创建订单
typescript
static async createOrder(
context: UIAbilityContext,
cart: CartViewModel,
orderType: OrderType,
shopName: string,
address: string
): Promise<OrderDetail | undefined> {
const order = new OrderDetail(
createOrderId(), // CK + Date.now()
cart.getTotalPrice(),
orderType, shopName, address,
Date.now().toString(),
OrderStatus.COMMITED, // 直接标记为已提交
cart.getCartItems()
)
await PreferenceUtil.putPreference(
context, 'cake.order', order.orderId, JSON.stringify(order)
)
cart.clearCart() // 清空购物车
return order
}
查询订单列表
typescript
static async getOrderList(context: UIAbilityContext): Promise<OrderDetail[]> {
const store = await PreferenceUtil.getPreference(context, 'cake.order')
const entries = await store.getAll()
const orderList: OrderDetail[] = []
const keys: string[] = Object.keys(entries)
for (const key of keys) {
const value = entries[key]
if (value && typeof value === 'string' && value.length > 0) {
orderList.push(JSON.parse(value as string) as OrderDetail)
}
}
// 按时间倒序排列
orderList.sort((a, b) => Number(b.orderTime) - Number(a.orderTime))
return orderList
}
六、订单历史页(OrderList)实现
步骤1:数据加载
typescript
aboutToAppear(): void {
this.loadOrders()
}
private async loadOrders(): Promise<void> {
this.isLoading = true
this.orderList = await OrderViewModel.getOrderList(context)
this.isLoading = false
}
步骤2:空状态展示
typescript
if (this.orderList.length === 0) {
Column() {
Text('📭').fontSize(60)
Text('暂无订单记录')
Text('去订购一个美味的蛋糕吧!')
}
}
步骤3:订单卡片渲染
使用 OrderItemView 组件展示每个订单:
typescript
ForEach(this.orderList, (order: OrderDetail) => {
OrderItemView({ order: order })
})
七、订单数据流图
美食页面(Food) 订单确认页(PageOrderPreview)
│ buyFoods (LocalStorage) ─────→ aboutToAppear 恢复 CartVM
│ │
│ 用户点击"确认付款"
│ │
│ OrderViewModel.createOrder()
│ │
│ Preferences 持久化存储
│ │
│ cartVM.clearCart()
│ │
│ buyFoods = '[]' ←──────────── 清空购物车
│ selectedIndex = 2 ←─────────── 切换到订单Tab
│ │
└────────────────────────────→ 订单历史页(OrderList)
│
aboutToAppear 加载订单
│
Preferences.getAll()
八、注意事项
- 订单ID格式为
CK+ 时间戳,确保全局唯一 popToName用于返回到主页面,清除中间页面栈- 使用
async/await处理异步操作,避免阻塞UI enableLoading状态控制支付按钮可用性,防止重复提交- 订单时间存储为字符串类型的时间戳,展示时格式化为可读格式