鸿蒙App开发--心愿池的储蓄管理:心愿清单与分类统计
·
心愿池的储蓄管理:心愿清单与分类统计
如果你有心愿目标想攒钱实现,推荐去鸿蒙应用市场搜一下**「心愿池」**,下载体验体验。创建心愿清单、投币储蓄、分类统计,一套走下来对攒钱目标会有更清晰的把控。体验完再回来看这篇文章,你会更清楚心愿管理和分类统计背后是怎么实现的。
写在前面
大家好,我是一名写了十多年Web前端的老兵。从jQuery时代一路走到React/Vue,CSS3动画、requestAnimationFrame、Web Animation API这些都算是看家本领。去年开始转战鸿蒙生态,用ArkTS开发App,这一路踩了不少坑,也积累了不少心得。
很多人觉得"前端转鸿蒙"应该很容易——都是写UI嘛,组件化、状态管理、生命周期,概念都差不多。但真正上手之后你会发现,相似的地方让你觉得亲切,不同的地方让你抓狂。
比如:
- 数据存储:Web的
localStorage到了ArkTS变成了@ohos.data.preferences。 - 列表渲染:React的
map()变成了ForEach。 - 路由导航:React Router变成了
router.pushUrl。
接下来这篇文章,我会用"心愿池"的实际开发经历,带你看看心愿清单管理、分类统计分析。
这篇文章聊什么
心愿池的储蓄管理功能,核心要解决两个问题:
- 心愿清单:创建和管理多个心愿
- 分类统计:按分类分析储蓄数据
第一步:心愿清单页面
@Entry
@Component
struct WishListPage {
@State wishes: Wish[] = []
@State filterCategory: string = 'all'
async aboutToAppear() {
await this.loadWishes()
}
async loadWishes() {
const store = await preferences.getPreferences(getContext(), 'xinyuanchi_data');
const stored = await store.get('wishes', '[]') as string;
this.wishes = JSON.parse(stored);
}
get filteredWishes(): Wish[] {
if (this.filterCategory === 'all') return this.wishes;
return this.wishes.filter(w => w.category === this.filterCategory);
}
get totalSaved(): number {
return this.wishes.reduce((sum, w) => sum + w.savedAmount, 0);
}
get totalTarget(): number {
return this.wishes.reduce((sum, w) => sum + w.targetAmount, 0);
}
build() {
Column() {
// 概览
Row() {
Column() {
Text(`¥${this.totalSaved}`)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#A855F7')
Text('已储蓄')
.fontSize(12)
.fontColor('#9CA3AF')
}
.layoutWeight(1)
Column() {
Text(`${this.wishes.length}`)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#A855F7')
Text('心愿数')
.fontSize(12)
.fontColor('#9CA3AF')
}
.layoutWeight(1)
}
.width('100%')
.padding(16)
.backgroundColor('#1F2937')
.borderRadius(12)
.margin({ bottom: 16 })
// 分类筛选
Flex({ wrap: FlexWrap.Wrap }) {
Text('全部')
.fontSize(12)
.padding(6)
.margin(4)
.borderRadius(6)
.backgroundColor(this.filterCategory === 'all' ? '#A855F7' : '#374151')
.onClick(() => { this.filterCategory = 'all' })
ForEach(WISH_CATEGORIES, (cat) => {
Text(`${cat.icon} ${cat.name}`)
.fontSize(12)
.padding(6)
.margin(4)
.borderRadius(6)
.backgroundColor(this.filterCategory === cat.id ? '#A855F7' : '#374151')
.onClick(() => { this.filterCategory = cat.id })
})
}
.margin({ bottom: 12 })
// 心愿列表
List({ space: 12 }) {
ForEach(this.filteredWishes, (wish: Wish) => {
ListItem() {
Column() {
Row() {
Text(this.getCategoryIcon(wish.category))
.fontSize(20)
Text(wish.name)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
.margin({ left: 8 })
Text(this.getPriorityLabel(wish.priority))
.fontSize(11)
.padding(4)
.borderRadius(4)
.backgroundColor(this.getPriorityColor(wish.priority))
}
.width('100%')
// 进度条
Column() {
Row() {
Text(`¥${wish.savedAmount}`)
.fontSize(14)
.fontColor('#A855F7')
.layoutWeight(1)
Text(`¥${wish.targetAmount}`)
.fontSize(14)
.fontColor('#9CA3AF')
}
.width('100%')
Stack() {
Row()
.width('100%')
.height(8)
.borderRadius(4)
.backgroundColor('#374151')
Row()
.width(`${Math.round(wish.progress * 100)}%`)
.height(8)
.borderRadius(4)
.backgroundColor('#A855F7')
}
.width('100%')
.margin({ top: 4 })
}
.margin({ top: 8 })
}
.width('100%')
.padding(12)
.backgroundColor('#1F2937')
.borderRadius(12)
.onClick(() => {
router.pushUrl({ url: `pages/WishDetail?id=${wish.id}` });
})
}
})
}
.layoutWeight(1)
Button('许个心愿')
.onClick(() => {
router.pushUrl({ url: 'pages/AddWish' });
})
.width('100%')
.backgroundColor('#A855F7')
}
.width('100%')
.height('100%')
.padding(16)
.backgroundColor('#111827')
}
private getCategoryIcon(category: string): string {
return WISH_CATEGORIES.find(c => c.id === category)?.icon || '💝';
}
private getPriorityLabel(priority: string): string {
const labels: Record<string, string> = { 'high': '高', 'medium': '中', 'low': '低' };
return labels[priority] || priority;
}
private getPriorityColor(priority: string): string {
const colors: Record<string, string> = { 'high': '#EF4444', 'medium': '#F59E0B', 'low': '#6B7280' };
return colors[priority] || '#374151';
}
}
第二步:分类统计
@Entry
@Component
struct StatsPage {
@State categoryStats: Record<string, number> = {}
@State totalSaved: number = 0
private settings: RenderingContextSettings = new RenderingContextSettings(true)
private ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
async aboutToAppear() {
await this.loadStats()
}
async loadStats() {
const store = await preferences.getPreferences(getContext(), 'xinyuanchi_data');
const stored = await store.get('wishes', '[]') as string;
const wishes: Wish[] = JSON.parse(stored);
this.totalSaved = wishes.reduce((sum, w) => sum + w.savedAmount, 0);
this.categoryStats = {};
wishes.forEach(w => {
this.categoryStats[w.category] = (this.categoryStats[w.category] || 0) + w.savedAmount;
});
}
build() {
Column() {
Text('储蓄统计')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 16 })
Text(`累计储蓄 ¥${this.totalSaved}`)
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor('#A855F7')
.margin({ bottom: 24 })
// 分类饼图
Canvas(this.ctx)
.width(200)
.height(200)
.onReady(() => {
this.drawPieChart()
})
// 分类明细
List({ space: 8 }) {
ForEach(Object.entries(this.categoryStats), ([cat, amount]) => {
ListItem() {
Row() {
Text(this.getCategoryIcon(cat))
.fontSize(16)
Text(this.getCategoryName(cat))
.fontSize(14)
.layoutWeight(1)
.margin({ left: 8 })
Text(`¥${amount}`)
.fontSize(14)
.fontColor('#A855F7')
}
.width('100%')
.padding(8)
.backgroundColor('#1F2937')
.borderRadius(8)
}
})
}
.layoutWeight(1)
}
.width('100%')
.height('100%')
.padding(16)
.backgroundColor('#111827')
}
private drawPieChart() {
const ctx = this.ctx;
const centerX = 100;
const centerY = 100;
const radius = 70;
const entries = Object.entries(this.categoryStats);
const total = entries.reduce((sum, [, amount]) => sum + amount, 0);
if (total === 0) return;
const colors = ['#A855F7', '#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#EC4899', '#14B8A6', '#6B7280'];
let startAngle = 0;
entries.forEach(([cat, amount], index) => {
const sliceAngle = (amount / total) * Math.PI * 2;
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.arc(centerX, centerY, radius, startAngle, startAngle + sliceAngle);
ctx.closePath();
ctx.fillStyle = colors[index % colors.length];
ctx.fill();
startAngle += sliceAngle;
});
}
private getCategoryIcon(cat: string): string {
return WISH_CATEGORIES.find(c => c.id === cat)?.icon || '💝';
}
private getCategoryName(cat: string): string {
return WISH_CATEGORIES.find(c => c.id === cat)?.name || cat;
}
}
总结
这篇文章围绕"心愿池"的储蓄管理功能,讲解了两个核心主题:
- 心愿清单:多心愿管理、分类筛选、进度条展示
- 分类统计:按分类统计储蓄金额,用饼图可视化
心愿清单的核心是数据过滤和进度计算。分类统计用Canvas饼图展示各分类的储蓄占比。
如果你有心愿目标想攒钱实现,希望这篇文章能帮你理解心愿池背后的管理逻辑。去鸿蒙应用市场下载体验一下吧,有问题欢迎交流。
更多推荐

所有评论(0)