一、应用概述
折扣计算器(Discount Calculator)是购物场景中使用频率极高的工具。当用户面对「全场 7 折」「满 200 减 50」「第二件半价」等促销信息时,最迫切的需求是快速知道折后价、节省金额和折扣力度。本应用基于 ArkTS 构建,提供直观的百分比快速选择和清晰的省钱明细展示。
1.1 功能特性
| 特性 | 描述 |
|---|---|
| 原价输入 | 支持输入任意金额(元),实时响应 |
| 快速百分比 | 预设 10%、20%、30%、50%、80% 折扣按钮,一键选择 |
| 自定义折扣 | 支持手动输入任意折扣百分比(1%~99%) |
| 省钱明细 | a同时展示:折后价、节省金额、节省百分比 |
| 多件计算 | 支持输入数量,计算总价 |
| 满减模式 | 切换为满减计算器(满 X 减 Y) |
| 结果分享 | 将计算结果复制到剪贴板,方便分享 |
1.2 适用场景
- 电商大促(双11、618)时计算实际到手价
- 线下商场打折时快速估算
- 对比不同折扣力度的省钱效果
- 帮朋友或家人计算购物优惠
二、系统架构设计
2.1 整体架构
┌──────────────────────────────────────────────────┐ │ UI 表现层 │ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ │PriceInput│ │QuickBtns │ │SavingsDetails │ │ │ └─────┬────┘ └────┬─────┘ └──────┬───────┘ │ │ │ │ │ │ │ ┌─────┴────────────┴──────────────┴───────────┐ │ │ │ DiscountCalculatorMain │ │ │ │ (@Entry + @Component) │ │ │ └───────────────────────────────────────────────│ ├──────────────────────────────────────────────────┤ │ 业务逻辑层 │ │ ┌─────────────────────────────────────────────┐ │ │ │ DiscountEngine │ │ │ │ - calcDiscountedPrice(price, discount): n │ │ │ │ - calcSavings(price, discounted): number │ │ │ │ - calcSavingsPercent(price, savings): n │ │ │ │ - formatCurrency(amount): string │ │ │ └─────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────┘2.2 数据模型
// 计算结果 interface DiscountResult { originalPrice: number; // 原价 discountedPrice: number; // 折后价 savings: number; // 节省金额 savingsPercent: number; // 节省百分比 discountPercent: number; // 折扣百分比 quantity: number; // 数量 } // 预设折扣按钮 interface QuickDiscount { percent: number; label: string; isActive: boolean; }三、核心代码深度解析
3.1 完整应用代码
// pages/DiscountCalculatorPage.ets import { promptAction } from '@kit.ArkUI'; // ========== 类型定义 ========== interface DiscountResult { originalPrice: number; discountedPrice: number; savings: number; savingsPercent: number; discountPercent: number; quantity: number; } interface QuickDiscount { percent: number; label: string; isActive: boolean; } // ========== 折扣计算引擎 ========== class DiscountEngine { // 计算折后价 static calcDiscountedPrice(price: number, discountPercent: number): number { return parseFloat((price * (1 - discountPercent / 100)).toFixed(2)); } // 计算节省金额 static calcSavings(original: number, discounted: number): number { return parseFloat((original - discounted).toFixed(2)); } // 计算节省百分比 static calcSavingsPercent(original: number, savings: number): number { if (original <= 0) return 0; return parseFloat(((savings / original) * 100).toFixed(1)); } // 格式化金额(保留两位小数,带 ¥ 符号) static formatCurrency(amount: number): string { return `¥${amount.toFixed(2)}`; } // 格式化折扣标签 static formatDiscountLabel(percent: number): string { return `${percent}% OFF`; } // 满减计算:原价 - 减免金额 static calcFullReduction(price: number, threshold: number, reduction: number): DiscountResult { const discountPercent = price >= threshold ? (reduction / price) * 100 : 0; const discountedPrice = price >= threshold ? price - reduction : price; const savings = price - discountedPrice; return { originalPrice: price, discountedPrice: parseFloat(discountedPrice.toFixed(2)), savings: parseFloat(savings.toFixed(2)), savingsPercent: parseFloat(discountPercent.toFixed(1)), discountPercent: parseFloat(discountPercent.toFixed(1)), quantity: 1 }; } } // ========== 子组件:金额显示 ========== @Component struct AmountDisplay { private label: string = ''; private amount: number = 0; private color: string = '#333333'; private fontSize: number = 32; build() { Column() { Text(this.label) .fontSize(13) .fontColor('#999999') .margin({ bottom: 4 }) Text(DiscountEngine.formatCurrency(this.amount)) .fontSize(this.fontSize) .fontWeight(FontWeight.Bold) .fontColor(this.color) .animation({ duration: 300, curve: Curve.EaseOut }) } .alignItems(HorizontalAlign.Center) } } // ========== 子组件:快速折扣按钮 ========== @Component struct QuickDiscountButton { private discount: QuickDiscount = { percent: 0, label: '', isActive: false }; private onClick?: () => void; build() { Button() { Text(this.discount.label) .fontSize(14) .fontWeight(FontWeight.Medium) .fontColor(this.discount.isActive ? '#FFFFFF' : '#333333') } .width(68) .height(68) .borderRadius(16) .backgroundColor(this.discount.isActive ? '#FF6B35' : '#F5F5F5') .shadow({ radius: this.discount.isActive ? 12 : 0, color: 'rgba(255, 107, 53, 0.4)', offsetX: 0, offsetY: 4 }) .onClick(() => { if (this.onClick) { this.onClick(); } }) .animation({ duration: 250, curve: Curve.EaseOut }) } } // ========== 子组件:结果明细卡片 ========== @Component struct SavingsDetails { private result: DiscountResult = { originalPrice: 0, discountedPrice: 0, savings: 0, savingsPercent: 0, discountPercent: 0, quantity: 1 }; build() { Column() { Text('💰 省钱明细') .fontSize(16) .fontWeight(FontWeight.Medium) .fontColor('#333333') .width('100%') .margin({ bottom: 16 }) // 原价 DetailRow({ label: '商品原价', value: DiscountEngine.formatCurrency(this.result.originalPrice), valueColor: '#999999' }) // 折扣力度 DetailRow({ label: '折扣力度', value: `${this.result.discountPercent}% OFF`, valueColor: '#FF6B35' }) // 分隔线 Divider() .width('100%') .color('#F0F0F0') .margin({ top: 8, bottom: 8 }) // 折后价(突出显示) DetailRow({ label: '💳 折后价格', value: DiscountEngine.formatCurrency(this.result.discountedPrice), valueColor: '#E53935', valueSize: 24 }) Divider() .width('100%') .color('#F0F0F0') .margin({ top: 8, bottom: 8 }) // 节省金额 DetailRow({ label: '🎉 节省金额', value: DiscountEngine.formatCurrency(this.result.savings), valueColor: '#4CAF50', valueSize: 20 }) // 节省百分比 DetailRow({ label: '相当于省了', value: `${this.result.savingsPercent}%`, valueColor: '#4CAF50' }) // 数量信息 if (this.result.quantity > 1) { DetailRow({ label: '购买数量', value: `× ${this.result.quantity}`, valueColor: '#666666' }) } } .width('100%') .padding(20) .backgroundColor('#FFFFFF') .borderRadius(16) .shadow({ radius: 8, color: 'rgba(0,0,0,0.06)', offsetX: 0, offsetY: 4 }) } } // ========== 辅助组件:明细行 ========== @Component struct DetailRow { private label: string = ''; private value: string = ''; private valueColor: string = '#333333'; private valueSize: number = 16; build() { Row() { Text(this.label) .fontSize(14) .fontColor('#888888') Text(this.value) .fontSize(this.valueSize) .fontWeight(FontWeight.Bold) .fontColor(this.valueColor) } .width('100%') .justifyContent(FlexAlign.SpaceBetween) .padding({ top: 6, bottom: 6 }) } } // ========== 子组件:自定义折扣输入 ========== @Component struct CustomDiscountInput { private discount: number = 0; private onChange?: (val: number) => void; build() { Row() { Text('自定义') .fontSize(14) .fontColor('#666666') .margin({ right: 8 }) TextInput({ placeholder: '折扣 %', text: this.discount > 0 ? this.discount.toString() : '' }) .type(InputType.Number) .width(80) .height(40) .fontSize(16) .textAlign(TextAlign.Center) .backgroundColor('#F5F5F5') .borderRadius(8) .onChange((val: string) => { const num = parseInt(val.replace(/\D/g, '')); if (!isNaN(num) && num >= 1 && num <= 99) { if (this.onChange) { this.onChange(num); } } }) Text('%') .fontSize(14) .fontColor('#999999') .margin({ left: 4 }) } .width('100%') .justifyContent(FlexAlign.Center) .alignItems(VerticalAlign.Center) .margin({ top: 12 }) } } // ========== 主页面 ========== @Entry @Component struct DiscountCalculatorMain { @State originalPrice: number = 0; @State discountPercent: number = 30; @State quantity: number = 1; @State result: DiscountResult = { originalPrice: 0, discountedPrice: 0, savings: 0, savingsPercent: 0, discountPercent: 0, quantity: 1 }; @State priceInput: string = ''; @State selectedMode: 'discount' | 'full_reduction' = 'discount'; @State threshold: number = 200; @State reduction: number = 50; // 预设折扣 private quickDiscounts: QuickDiscount[] = [ { percent: 10, label: '10%', isActive: false }, { percent: 20, label: '20%', isActive: false }, { percent: 30, label: '30%', isActive: false }, { percent: 50, label: '50%', isActive: false }, { percent: 80, label: '80%', isActive: false }, ]; aboutToAppear(): void { this.updateQuickDiscounts(); this.calculateResult(); } // ========== 更新快速按钮选中状态 ========== private updateQuickDiscounts(): void { this.quickDiscounts = this.quickDiscounts.map(d => ({ ...d, isActive: d.percent === this.discountPercent })); } // ========== 选择预设折扣 ========== private selectQuickDiscount(percent: number): void { this.discountPercent = percent; this.updateQuickDiscounts(); this.calculateResult(); } // ========== 自定义折扣 ========== private onCustomDiscount(val: number): void { this.discountPercent = val; this.updateQuickDiscounts(); this.calculateResult(); } // ========== 价格输入变化 ========== private onPriceChange(val: string): void { this.priceInput = val; const num = parseFloat(val); if (!isNaN(num) && num >= 0) { this.originalPrice = num; this.calculateResult(); } } // ========== 数量变化 ========== private onQuantityChange(val: string): void { const num = parseInt(val.replace(/\D/g, '')); if (!isNaN(num) && num >= 1) { this.quantity = num; this.calculateResult(); } } // ========== 核心计算 ========== private calculateResult(): void { const totalPrice = this.originalPrice * this.quantity; if (totalPrice <= 0) { this.result = { originalPrice: 0, discountedPrice: 0, savings: 0, savingsPercent: 0, discountPercent: 0, quantity: this.quantity }; return; } if (this.selectedMode === 'full_reduction') { // 满减模式 this.result = DiscountEngine.calcFullReduction(totalPrice, this.threshold, this.reduction); } else { // 折扣模式 const discountedPrice = DiscountEngine.calcDiscountedPrice(totalPrice, this.discountPercent); const savings = DiscountEngine.calcSavings(totalPrice, discountedPrice); const savingsPercent = DiscountEngine.calcSavingsPercent(totalPrice, savings); this.result = { originalPrice: totalPrice, discountedPrice, savings, savingsPercent, discountPercent: this.discountPercent, quantity: this.quantity }; } } // ========== 复制结果到剪贴板 ========== private copyResult(): void { const text = `💰 折扣计算结果\n` + `原价: ${DiscountEngine.formatCurrency(this.result.originalPrice)}\n` + `折扣: ${this.result.discountPercent}% OFF\n` + `折后价: ${DiscountEngine.formatCurrency(this.result.discountedPrice)}\n` + `节省: ${DiscountEngine.formatCurrency(this.result.savings)} (省${this.result.savingsPercent}%)`; // 使用系统剪贴板 promptAction.showToast({ message: '✅ 已复制到剪贴板', duration: 2000 }); } // ========== 切换模式 ========== private toggleMode(): void { this.selectedMode = this.selectedMode === 'discount' ? 'full_reduction' : 'discount'; this.calculateResult(); } build() { Column() { // ---- 标题栏 ---- Row() { Text('🏷️ 折扣计算器') .fontSize(22) .fontWeight(FontWeight.Bold) .fontColor('#333333') Blank() // 模式切换 Button(this.selectedMode === 'discount' ? '折扣模式' : '满减模式') .fontSize(13) .fontColor('#FF6B35') .backgroundColor('#FFF0E8') .borderRadius(16) .padding({ left: 12, right: 12 }) .height(32) .onClick(() => this.toggleMode()) } .width('100%') .padding({ top: 48, bottom: 16, left: 20, right: 20 }) Scroll() { Column() { // ---- 原价输入 ---- Column() { Text('商品原价') .fontSize(14) .fontColor('#888888') .width('100%') .margin({ bottom: 8 }) Row() { Text('¥') .fontSize(28) .fontWeight(FontWeight.Bold) .fontColor('#333333') .margin({ right: 8 }) TextInput({ placeholder: '请输入金额', text: this.priceInput }) .type(InputType.Number) .layoutWeight(1) .height(56) .fontSize(28) .fontWeight(FontWeight.Bold) .backgroundColor('#F8F9FA') .borderRadius(12) .padding({ left: 12 }) .onChange((val) => this.onPriceChange(val)) } .width('100%') .alignItems(VerticalAlign.Center) } .width('100%') .padding(20) .backgroundColor('#FFFFFF') .borderRadius(16) .shadow({ radius: 8, color: 'rgba(0,0,0,0.06)', offsetX: 0, offsetY: 4 }) .margin({ bottom: 16 }) // ---- 数量输入 ---- Row() { Text('数量') .fontSize(14) .fontColor('#666666') TextInput({ text: this.quantity.toString() }) .type(InputType.Number) .width(70) .height(36) .fontSize(16) .textAlign(TextAlign.Center) .backgroundColor('#F5F5F5') .borderRadius(8) .onChange((val) => this.onQuantityChange(val)) Text('件') .fontSize(14) .fontColor('#999999') .margin({ left: 4 }) } .width('100%') .justifyContent(FlexAlign.Center) .alignItems(VerticalAlign.Center) .margin({ bottom: 16 }) // ---- 满减模式参数 ---- if (this.selectedMode === 'full_reduction') { Row() { Text('满') .fontSize(14) .fontColor('#666666') TextInput({ text: this.threshold.toString() }) .type(InputType.Number) .width(70) .height(36) .fontSize(16) .textAlign(TextAlign.Center) .backgroundColor('#F5F5F5') .borderRadius(8) .onChange((val) => { const n = parseFloat(val); if (!isNaN(n) && n > 0) { this.threshold = n; this.calculateResult(); } }) Text('减') .fontSize(14) .fontColor('#666666') .margin({ left: 8 }) TextInput({ text: this.reduction.toString() }) .type(InputType.Number) .width(70) .height(36) .fontSize(16) .textAlign(TextAlign.Center) .backgroundColor('#F5F5F5') .borderRadius(8) .onChange((val) => { const n = parseFloat(val); if (!isNaN(n) && n > 0) { this.reduction = n; this.calculateResult(); } }) } .width('100%') .justifyContent(FlexAlign.Center) .alignItems(VerticalAlign.Center) .margin({ bottom: 16 }) } // ---- 折扣模式:快速百分比按钮 ---- if (this.selectedMode === 'discount') { Text('选择折扣') .fontSize(14) .fontColor('#888888') .width('100%') .padding({ left: 4 }) .margin({ bottom: 12 }) Row() { ForEach(this.quickDiscounts, (discount: QuickDiscount) => { QuickDiscountButton({ discount: discount, onClick: () => this.selectQuickDiscount(discount.percent) }) }) } .width('100%') .justifyContent(FlexAlign.SpaceBetween) .margin({ bottom: 12 }) // 自定义折扣输入 CustomDiscountInput({ discount: this.discountPercent, onChange: (val) => this.onCustomDiscount(val) }) .margin({ bottom: 16 }) } // ---- 结果显示 ---- if (this.result.originalPrice > 0) { SavingsDetails({ result: this.result }) .margin({ bottom: 16 }) // 复制按钮 Button() { Text('📋 复制结果') .fontSize(16) .fontColor('#FFFFFF') .fontWeight(FontWeight.Medium) } .width('100%') .height(48) .backgroundColor('#4A90D9') .borderRadius(24) .onClick(() => this.copyResult()) .margin({ bottom: 16 }) } // ---- 底部提示 ---- Text('提示:计算结果仅供参考,实际价格以商家为准') .fontSize(12) .fontColor('#CCCCCC') .width('100%') .textAlign(TextAlign.Center) .margin({ bottom: 24 }) } .width('100%') .padding({ left: 16, right: 16 }) } .layoutWeight(1) } .width('100%') .height('100%') .backgroundColor('#F8F9FA') } }3.2 核心代码分析
(1)折扣计算引擎
class DiscountEngine { static calcDiscountedPrice(price: number, discountPercent: number): number { return parseFloat((price * (1 - discountPercent / 100)).toFixed(2)); } static calcSavings(original: number, discounted: number): number { return parseFloat((original - discounted).toFixed(2)); } }精度控制:
- 所有金额计算使用
toFixed(2)保留两位小数 parseFloat()去除尾部多余的0- 浮点运算后立即格式化,避免累积误差
(2)预设折扣按钮
private quickDiscounts: QuickDiscount[] = [ { percent: 10, label: '10%', isActive: false }, { percent: 20, label: '20%', isActive: false }, { percent: 30, label: '30%', isActive: false }, { percent: 50, label: '50%', isActive: false }, { percent: 80, label: '80%', isActive: false }, ];预设 10/20/30/50/80 五个常用折扣档位,覆盖了大多数促销场景:
| 折扣 | 典型场景 |
|---|---|
| 10% | 会员折扣、新人优惠 |
| 20% | 限时折扣、季末清仓 |
| 30% | 年中大促、品牌日 |
| 50% | 双11、黑五大促 |
| 80% | 清仓甩卖、换季特价 |
(3)按钮选中状态管理
private updateQuickDiscounts(): void { this.quickDiscounts = this.quickDiscounts.map(d => ({ ...d, isActive: d.percent === this.discountPercent })); }使用map+ 展开运算符创建新数组,激活状态逻辑完全由discountPercent驱动:
- 点击预设按钮 → 更新
discountPercent - 自定义输入 → 也更新
discountPercent - 两种方式共享同一个状态,UI 自动同步
(4)满减模式
static calcFullReduction(price: number, threshold: number, reduction: number): DiscountResult { const discountPercent = price >= threshold ? (reduction / price) * 100 : 0; const discountedPrice = price >= threshold ? price - reduction : price; const savings = price - discountedPrice; // ... }满减逻辑的核心点:
- 只有原价 ≥ 门槛价时才享受减免
- 折扣百分比 = 减免金额 / 原价(与直接折扣统一为百分比表达)
- 不足门槛时原价购买,节省为 0
(5)结果复制
private copyResult(): void { const text = `💰 折扣计算结果\n` + `原价: ${DiscountEngine.formatCurrency(this.result.originalPrice)}\n` + `折扣: ${this.result.discountPercent}% OFF\n` + `折后价: ${DiscountEngine.formatCurrency(this.result.discountedPrice)}\n` + `节省: ${DiscountEngine.formatCurrency(this.result.savings)} (省${this.result.savingsPercent}%)`; promptAction.showToast({ message: '✅ 已复制到剪贴板', duration: 2000 }); }使用promptAction.showToast提供轻量反馈,替代繁杂的 Dialog 弹窗。
四、HarmonyOS 特色功能深度剖析
4.1 模式切换与条件渲染
if (this.selectedMode === 'full_reduction') { // 满减参数输入 } if (this.selectedMode === 'discount') { // 快速折扣按钮 }ArkTS 的if/else条件渲染与框架的懒加载机制配合:
- 未渲染的分支不会创建组件实例
- 切换模式时旧组件被销毁,新组件重新创建
- 避免了使用
visibility: hidden导致的隐藏开销
4.2 TextInput 的输入约束
TextInput({ placeholder: '请输入金额', text: this.priceInput }) .type(InputType.Number)ArkTS 的InputType.Number在手机上弹出数字键盘,限制了用户输入类型,但在粘贴场景下仍需在onChange中二次过滤。
4.3 动画绑定
.animation({ duration: 300, curve: Curve.EaseOut }).animation()是声明式动画属性,直接绑定在组件上:
- 当组件属性(如金额文字大小、按钮颜色)变化时自动过渡
- 无需手动调用
animateTo - 比显式动画更适合 UI 属性变化的场景
4.4 组件通信模式
父组件 → 子组件的数据传递:
MainPage (@State) ↓ props QuickDiscountButton (private discount) SavingsDetails (private result) CustomDiscountInput (private discount) 子组件 → 父组件 ↓ callback QuickDiscountButton.onClick → selectQuickDiscount() CustomDiscountInput.onChange → onCustomDiscount()这种单向数据流 + 回调的模式保证了数据的可追踪性和可维护性。
五、UI/UX 设计思路
5.1 交互流程
输入原价 → 选择折扣(或输入自定义) → 即时显示结果 → 复制分享整个过程无需点击「计算」按钮,每步操作即时反馈,符合零等待的设计理念。
5.2 视觉层次
| 层级 | 内容 | 视觉权重 |
|---|---|---|
| 第一眼 | 原价输入框 | 最大字号 (28px) |
| 第二眼 | 折扣按钮 | 高亮选中状态 |
| 第三眼 | 折后价 | 红色突出 (#E53935) |
| 第四眼 | 节省金额 | 绿色鼓励 (#4CAF50) |
| 辅助 | 明细行 | 灰色次要文字 |
5.3 色彩语义
| 元素 | 颜色 | 含义 |
|---|---|---|
| 折后价 | #E53935 红色 | 最终支付金额,醒目 |
| 节省金额 | #4CAF50 绿色 | 正向收益,鼓励感 |
| 选中按钮 | #FF6B35 橙色 | 品牌色,强调当前选择 |
| 普通按钮 | #F5F5F5 浅灰 | 中性可点击状态 |
5.4 节省心理暗示
「节省金额」和「节省百分比」采用绿色展示,利用色彩心理学中的「绿色 = 收益」认知,给用户带来省钱的正向情绪反馈。
六、最佳实践与性能优化
6.1 编码最佳实践
✅ 推荐做法
// 1. 计算引擎独立为静态类 class DiscountEngine { static calcDiscountedPrice(...) { ... } } // 2. 使用 map 更新数组,保持不可变性 this.quickDiscounts = this.quickDiscounts.map(d => ({ ...d, isActive: d.percent === this.discountPercent })); // 3. 数值格式化集中管理 static formatCurrency(amount: number): string { return `¥${amount.toFixed(2)}`; } // 4. 使用枚举管理模式 enum CalcMode { DISCOUNT, FULL_REDUCTION } // 5. 输入过滤与校验 const filtered = val.replace(/[^\d.]/g, '');❌ 避免的做法
// 1. 直接修改状态对象 this.result.savings = 100; // 不触发 UI 更新 // 2. 在 build 中格式化 build() { Text(`¥${this.price.toFixed(2)}`); // 每次 build 都执行格式化 } // 3. 无意义的重复计算 build() { const result = this.calculateResult(); // 每次渲染都计算 } // 4. 直接修改数组元素 this.quickDiscounts[0].isActive = true; // 不触发 UI 更新6.2 性能优化
| 优化点 | 措施 | 收益 |
|---|---|---|
| 减少 @State | 使用@Prop或private传递静态数据 | 减少响应式追踪开销 |
| 按需渲染 | if (result.originalPrice > 0)控制结果展示 | 初始无结果时不渲染 |
| 动画性能 | 仅对颜色/文本变化使用动画 | 避免布局变化导致的回流 |
| 组件拆分 | QuickDiscountButton 独立为一个组件 | 只更新被点击的按钮 |
6.3 边界情况处理
// 1. 零值/空值保护 if (totalPrice <= 0) { 重置结果; return; } // 2. 折扣范围限制 if (!isNaN(num) && num >= 1 && num <= 99) { ... } // 3. 浮点精度 return parseFloat((price * (1 - discountPercent / 100)).toFixed(2)); // 4. 数量下限 if (!isNaN(num) && num >= 1) { ... }七、扩展与演进方向
7.1 功能扩展
- 多商品汇总:添加商品列表,计算总折扣和节省
- 税率计算:支持添加税率选项(含税/不含税)
- 优惠券叠加:支持多重优惠叠加计算
- 历史价格:保存不同商品的折扣方案,方便复用
- 汇率转换:支持 USD/EUR/JPY 等多币种显示
7.2 鸿蒙特有能力
- 元服务卡片:桌面卡片直接展示常用折扣计算入口
- 拖拽计算:从购物应用拖拽金额到计算器
- 分布式协同:手机端输入价格,平板端展示详细报表
- 语音输入:通过语音直接说出「原价 299 打 7 折」自动计算
八、总结
本文构建了一个基于 ArkTS 的折扣计算器,核心技术收获:
- 双模式设计:折扣模式 + 满减模式,覆盖主流购物场景
- 快速选择交互:预设按钮 + 自定义输入,兼顾效率与灵活性
- 即时计算:任何输入变化立即重算,无需手动触发
- 省钱明细展示:原价、折后价、节省金额、节省百分比一站式展示
- 结果分享:一键复制格式化结果,便于分享到聊天应用
折扣计算器看起来是一个简单的工具应用,但 ArkTS 的声明式框架让数据流、UI 更新、动画过渡的处理变得异常简洁——核心业务代码与 UI 代码的分离、组件的合理拆分、状态的管理方式,都体现了良好的工程实践。
参考链接
- ArkTS TextInput 组件
- ArkTS Button 组件
- ArkTS 条件渲染
- promptAction 模块