HarmonyOS ArkTS 实战:实现一个匿名树洞心情分享应用
HarmonyOS ArkTS 实战:从零实现匿名树洞心情分享应用
目录
- 一、项目背景与效果预览
- 二、技术栈与开发环境
- 三、需求分析与功能架构
- 四、数据结构与服务层设计
- 五、核心功能实现(完整代码)
- 六、UI 界面设计与实现(完整组件)
- 七、完整主页面代码(Index.ets)
- 八、运行与调试
- 九、项目总结与扩展思路
一、项目背景与效果预览
1.1 痛点场景
大学生活中,喜怒哀乐需要倾诉,但朋友圈太公开、微博太嘈杂。匿名树洞提供了一个安全、温暖的情绪出口。本应用让用户可以匿名发布心情,选择情绪标签,浏览他人动态,点赞、评论、收藏,并支持情绪分类筛选、热门排行榜、我的发布、匿名聊天(模拟) 等功能,打造校园情感社区。
1.2 运行效果(模拟器预览)
- 主界面:顶部为应用标题 + 夜间模式切换按钮;中间为横向滚动的情绪筛选标签(全部/开心/难过/焦虑/平静/愤怒/期待);下方为发布框(文本输入 + 情绪表情选择 + 发布按钮);底部为树洞动态列表(卡片式)。
- 交互反馈:发布时自动生成随机匿名昵称和头像;点赞时有“❤️”缩放动画;收藏有星标高亮;点击评论图标弹出评论输入框;长按卡片可弹出“举报/删除”菜单(模拟)。
- 底部Tabs(使用Tabs实现):首页(动态流)、热门榜、我的发布、统计。
- 夜间模式:一键切换深色主题,所有卡片颜色适配。
主题色保留粉色(#500724),体现温暖治愈,同时支持暗色模式。
二、技术栈与开发环境
| 技术项 | 说明 |
|---|---|
| 开发语言 | ArkTS |
| UI 框架 | ArkUI 声明式开发 |
| 状态管理 | @State / @Provide / @Consume |
| 布局方式 | List + Tabs + Scroll + Column |
| 数据持久化 | @ohos.data.preferences |
| 图表组件 | @ohos.arkui.advanced.Chart |
| 弹窗/提示 | @ohos.prompt / @ohos.dialog |
| 动画 | animateTo / transition |
| 开发工具 | DevEco Studio 5.0+ |
| SDK 版本 | API 24 及以上 |
三、需求分析与功能架构
3.1 核心功能清单
- 匿名发布:用户输入文字,选择情绪(6种),系统自动分配昵称和头像,发布后立即出现在列表顶部。
- 动态浏览:按时间倒序展示所有树洞,支持按情绪标签筛选。
- 点赞:点击爱心切换状态,数量+1/-1,伴有缩放动画。
- 收藏:点击星标收藏该树洞,在“我的收藏”中可查看。
- 评论:点击评论图标弹出输入框,发表评论后刷新评论数。
- 热门榜:按点赞数排序展示最热门的树洞(Top 10)。
- 我的发布:查看自己发布的所有树洞,支持删除。
- 情绪统计:用饼图/柱状图展示各情绪占比,以及个人发布情绪分布。
- 匿名聊天(模拟):点击用户昵称,弹出“发送匿名消息”对话框(模拟)。
- 夜间模式:全局主题切换,适配暗色环境。
3.2 数据流向
发布 → 持久化存储 → 列表更新 → 点赞/收藏/评论 → 更新持久化
统计页面从存储数据中计算图表
四、数据结构与服务层设计
4.1 数据模型
// model/Post.ets
export interface Post {
id: number;
content: string;
mood: '开心' | '难过' | '焦虑' | '平静' | '愤怒' | '期待';
nickname: string;
avatar: string; // emoji 字符串
createTime: string; // ISO 字符串
likes: number;
comments: number;
isLiked: boolean; // 当前用户是否点赞
isFavorited: boolean; // 当前用户是否收藏
isHot: boolean; // 是否热门(后台计算)
}
// model/Comment.ets
export interface Comment {
id: number;
postId: number;
content: string;
nickname: string;
avatar: string;
createTime: string;
}
// model/User.ets(用于模拟当前用户)
export interface User {
id: string;
nickname: string;
avatar: string;
}
4.2 服务层(Service)
沿用 BaseService,实现 PostService 和 CommentService。
// service/BaseService.ets(同前)
// service/PostService.ets
import { BaseService } from './BaseService';
import { Post } from '../model/Post';
class PostService extends BaseService<Post> {
constructor() { super('TreeHolePrefs', 'posts'); }
async fetch(): Promise<Post[]> {
const data = await this.loadData();
if (data.length === 0) {
const mock = this.getMockData();
await this.saveData(mock);
return mock;
}
return data;
}
async add(item: Post): Promise<Post[]> {
const list = await this.loadData();
list.unshift(item);
await this.saveData(list);
return list;
}
async update(id: number, newItem: Post): Promise<Post[]> {
const list = await this.loadData();
const idx = list.findIndex(p => p.id === id);
if (idx !== -1) {
list[idx] = newItem;
await this.saveData(list);
}
return list;
}
async delete(id: number): Promise<Post[]> {
const list = await this.loadData();
const filtered = list.filter(p => p.id !== id);
await this.saveData(filtered);
return filtered;
}
private getMockData(): Post[] {
const now = Date.now();
return [
{ id: 1, content: '今天答辩通过啦!准备了好久终于有结果了,感谢所有帮助过我的人!', mood: '开心', nickname: '匿名小熊', avatar: '🐻', createTime: new Date(now - 600000).toISOString(), likes: 128, comments: 23, isLiked: false, isFavorited: false, isHot: true },
{ id: 2, content: '马上要找工作了,感觉压力好大,不知道自己能做什么...', mood: '焦虑', nickname: '神秘路人', avatar: '🌙', createTime: new Date(now - 1800000).toISOString(), likes: 89, comments: 45, isLiked: true, isFavorited: false, isHot: true },
{ id: 3, content: '图书馆窗外的晚霞真的好美,突然觉得生活还是很美好的。', mood: '平静', nickname: '云朵同学', avatar: '☁️', createTime: new Date(now - 3600000).toISOString(), likes: 256, comments: 18, isLiked: false, isFavorited: true, isHot: true },
{ id: 4, content: '今天和喜欢了很久的人一起吃饭了,心跳好快,感觉空气都是甜的!', mood: '开心', nickname: '星星守护者', avatar: '⭐', createTime: new Date(now - 7200000).toISOString(), likes: 312, comments: 67, isLiked: false, isFavorited: false, isHot: true },
];
}
}
export const postService = new PostService();
// service/CommentService.ets(略,类似)
五、核心功能实现(完整代码)
5.1 页面状态与数据加载(主页面 Index.ets)
使用 Tabs 切换首页、热门、我的、统计。
// pages/Index.ets
import { Post } from '../model/Post';
import { Comment } from '../model/Comment';
import { postService } from '../service/PostService';
import { commentService } from '../service/CommentService';
import prompt from '@ohos.prompt';
import { Chart, ChartType } from '@ohos.arkui.advanced';
@Entry
@Component
struct Index {
@State posts: Post[] = [];
@State comments: Comment[] = [];
@State currentMood: string = '全部';
@State newContent: string = '';
@State selectedMoodEmoji: string = '😊'; // 默认开心
@State tabIndex: number = 0;
@State isLoading: boolean = true;
@State isNightMode: boolean = false; // 夜间模式
// 评论弹窗
@State isCommentDialogVisible: boolean = false;
@State commentPostId: number = -1;
@State commentInput: string = '';
// 用户信息(当前匿名用户)
@State myNickname: string = '';
@State myAvatar: string = '';
// 情绪配置
private moods: { emoji: string; name: string; color: string }[] = [
{ emoji: '😊', name: '开心', color: '#F59E0B' },
{ emoji: '😢', name: '难过', color: '#3B82F6' },
{ emoji: '😰', name: '焦虑', color: '#F97316' },
{ emoji: '😌', name: '平静', color: '#10B981' },
{ emoji: '😠', name: '愤怒', color: '#EF4444' },
{ emoji: '🤩', name: '期待', color: '#EC4899' },
];
private nicknames: string[] = ['匿名小熊', '神秘路人', '云朵同学', '月光旅人', '星星守护者', '风的使者', '樱花精灵', '夏日微风'];
private avatars: string[] = ['🐻', '🌙', '☁️', '⭐', '🌸', '🍀', '🦋', '🌈'];
aboutToAppear() {
this.initUser();
this.loadData();
}
private initUser() {
// 随机分配一个初始昵称和头像
this.myNickname = this.nicknames[Math.floor(Math.random() * this.nicknames.length)];
this.myAvatar = this.avatars[Math.floor(Math.random() * this.avatars.length)];
}
async loadData() {
this.isLoading = true;
try {
this.posts = await postService.fetch();
this.comments = await commentService.fetch();
// 更新热门标记:根据点赞数 > 100 设为热门
this.posts = this.posts.map(p => ({ ...p, isHot: p.likes > 100 }));
} catch (e) {
prompt.showToast({ message: '加载失败' });
} finally {
this.isLoading = false;
}
}
// 刷新列表(持久化后调用)
private async refreshPosts() {
this.posts = await postService.fetch();
this.posts = this.posts.map(p => ({ ...p, isHot: p.likes > 100 }));
}
// ... 其他方法
}
5.2 匿名发布(含情绪选择与随机昵称)
发布时自动分配随机昵称和头像,并记录到持久化。
private publishPost(): void {
const content = this.newContent.trim();
if (!content) {
prompt.showToast({ message: '请输入内容' });
return;
}
// 获取情绪名称
const moodObj = this.moods.find(m => m.emoji === this.selectedMoodEmoji);
const moodName = moodObj ? moodObj.name : '平静';
// 随机生成昵称和头像(也可固定当前用户,但树洞鼓励每次不同)
const nickname = this.nicknames[Math.floor(Math.random() * this.nicknames.length)];
const avatar = this.avatars[Math.floor(Math.random() * this.avatars.length)];
const post: Post = {
id: Date.now(),
content: content,
mood: moodName as any,
nickname: nickname,
avatar: avatar,
createTime: new Date().toISOString(),
likes: 0,
comments: 0,
isLiked: false,
isFavorited: false,
isHot: false,
};
try {
this.posts = [post, ...this.posts];
postService.add(post); // 异步持久化
prompt.showToast({ message: '发布成功 🌸' });
this.newContent = '';
} catch (e) {
prompt.showToast({ message: '发布失败' });
}
}
5.3 动态列表与情绪筛选
列表根据 currentMood 过滤,并支持按时间排序(已默认最新)。
private getFilteredPosts(): Post[] {
if (this.currentMood === '全部') return this.posts;
return this.posts.filter(p => p.mood === this.currentMood);
}
// 在 build 的 List 中使用 ForEach(this.getFilteredPosts(), ...)
情绪筛选组件(横向滚动):
@Builder MoodFilter() {
Scroll(Axis.Horizontal) {
Row({ space: 8 }) {
Text('全部')
.fontSize(13)
.fontColor(this.currentMood === '全部' ? '#FFF' : '#500724')
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.backgroundColor(this.currentMood === '全部' ? '#500724' : '#FCE7F3')
.borderRadius(16)
.onClick(() => this.currentMood = '全部')
ForEach(this.moods, (m) => {
Text(m.emoji + m.name)
.fontSize(13)
.fontColor(this.currentMood === m.name ? '#FFF' : '#500724')
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.backgroundColor(this.currentMood === m.name ? m.color : '#FCE7F3')
.borderRadius(16)
.onClick(() => this.currentMood = m.name)
})
}
}
.scrollBar(BarState.Off)
.width('100%')
.padding({ left: 16, right: 16 })
}
5.4 点赞与收藏(动画反馈)
点赞时使用 animateTo 实现爱心缩放效果。
private async toggleLike(postId: number) {
const post = this.posts.find(p => p.id === postId);
if (!post) return;
const newLiked = !post.isLiked;
const newLikes = newLiked ? post.likes + 1 : post.likes - 1;
const updated = { ...post, isLiked: newLiked, likes: newLikes, isHot: newLikes > 100 };
// 动画
animateTo({ duration: 200 }, () => {
// 仅触发UI刷新,实际更新在下方
});
try {
await postService.update(postId, updated);
// 更新本地列表
const index = this.posts.findIndex(p => p.id === postId);
if (index !== -1) this.posts[index] = updated;
} catch (e) {
prompt.showToast({ message: '操作失败' });
}
}
private async toggleFavorite(postId: number) {
const post = this.posts.find(p => p.id === postId);
if (!post) return;
const updated = { ...post, isFavorited: !post.isFavorited };
try {
await postService.update(postId, updated);
const index = this.posts.findIndex(p => p.id === postId);
if (index !== -1) this.posts[index] = updated;
prompt.showToast({ message: updated.isFavorited ? '已收藏 ❤️' : '已取消收藏' });
} catch (e) { /* */ }
}
5.5 评论系统(弹窗输入)
点击评论图标打开弹窗,输入评论后保存。
private openCommentDialog(postId: number) {
this.commentPostId = postId;
this.commentInput = '';
this.isCommentDialogVisible = true;
}
private async submitComment() {
const content = this.commentInput.trim();
if (!content) { prompt.showToast({ message: '请输入评论' }); return; }
const post = this.posts.find(p => p.id === this.commentPostId);
if (!post) return;
const comment: Comment = {
id: Date.now(),
postId: post.id,
content: content,
nickname: this.myNickname,
avatar: this.myAvatar,
createTime: new Date().toISOString()
};
try {
this.comments = await commentService.add(comment);
// 更新帖子的评论数
const updatedPost = { ...post, comments: post.comments + 1 };
await postService.update(post.id, updatedPost);
const index = this.posts.findIndex(p => p.id === post.id);
if (index !== -1) this.posts[index] = updatedPost;
prompt.showToast({ message: '评论成功' });
this.isCommentDialogVisible = false;
} catch (e) {
prompt.showToast({ message: '评论失败' });
}
}
评论弹窗 UI(使用 dialog):
@Builder CommentDialogContent() {
Column() {
Text('发表评论').fontSize(18).fontWeight(FontWeight.Bold).margin(12);
TextArea({ placeholder: '说点什么吧...', text: this.commentInput })
.onChange(v => this.commentInput = v)
.height(100)
.margin(10)
.backgroundColor('#FDF2F8')
.borderRadius(8);
Row() {
Button('取消').onClick(() => this.isCommentDialogVisible = false).backgroundColor('#999');
Button('发送').onClick(() => this.submitComment()).backgroundColor('#500724').margin({ left: 16 });
}.margin(12);
}
.padding(20)
.width('90%')
.backgroundColor('#FFF')
.borderRadius(16);
}
5.6 热门排行榜与最新发布
在“热门”Tab中展示按点赞数排序的帖子(Top 10),使用独立的 List。
// 在 Tabs 的 TabContent 中
TabContent() {
Column() {
Text('🔥 热门树洞').fontSize(18).fontWeight(FontWeight.Bold).margin(12).alignSelf(ItemAlign.Start);
List() {
ForEach(
this.posts.filter(p => p.isHot).sort((a,b) => b.likes - a.likes).slice(0,10),
(p: Post) => { ListItem() { this.PostCard(p) } }
)
}
.width('100%')
.layoutWeight(1)
.padding(16)
}
}
.tabBar('🔥 热门')
5.7 我的发布与收藏
在“我的”Tab中展示自己发布的和收藏的帖子。这里我们通过用户昵称匹配(简单),实际可用用户ID。
TabContent() {
Column() {
Text(`👤 我的发布 (${this.posts.filter(p => p.nickname === this.myNickname).length})`)
.fontSize(16).fontWeight(FontWeight.Medium).margin(12).alignSelf(ItemAlign.Start);
List() {
ForEach(this.posts.filter(p => p.nickname === this.myNickname), (p: Post) => {
ListItem() {
this.PostCard(p)
}
})
}
.width('100%')
.height(200)
Text(`⭐ 我的收藏 (${this.posts.filter(p => p.isFavorited).length})`)
.fontSize(16).fontWeight(FontWeight.Medium).margin(12).alignSelf(ItemAlign.Start);
List() {
ForEach(this.posts.filter(p => p.isFavorited), (p: Post) => {
ListItem() {
this.PostCard(p)
}
})
}
.width('100%')
.layoutWeight(1)
}
.padding(10)
}
.tabBar('👤 我的')
5.8 情绪统计图表
在“统计”Tab中展示饼图或柱状图。使用 Chart 组件,但 @ohos.arkui.advanced 可能不支持饼图,可用柱状图显示各情绪数量。
TabContent() {
Column() {
Text('📊 情绪分布').fontSize(18).fontWeight(FontWeight.Bold).margin(12).alignSelf(ItemAlign.Start);
const moodCounts = this.moods.map(m => ({
name: m.name,
count: this.posts.filter(p => p.mood === m.name).length
}));
const labels = moodCounts.map(m => m.name);
const data = moodCounts.map(m => m.count);
if (data.some(c => c > 0)) {
Chart({
type: ChartType.Bar,
datasets: [{ data: data, color: '#EC4899' }],
options: {
xAxis: { labels: labels, color: '#999' },
yAxis: { min: 0, step: 1, color: '#999' }
}
}).width('100%').height(150);
// 个人情绪分布
const myPosts = this.posts.filter(p => p.nickname === this.myNickname);
const myData = this.moods.map(m => myPosts.filter(p => p.mood === m.name).length);
Text('我的情绪分布').fontSize(14).margin(12).alignSelf(ItemAlign.Start);
Chart({
type: ChartType.Bar,
datasets: [{ data: myData, color: '#F59E0B' }],
options: {
xAxis: { labels: labels, color: '#999' },
yAxis: { min: 0, step: 1, color: '#999' }
}
}).width('100%').height(150);
} else {
Text('暂无数据,快来发布吧~').fontSize(14).fontColor('#999').margin(20);
}
}
.padding(10)
}
.tabBar('📊 统计')
5.9 匿名聊天(模拟私信)
长按卡片或点击昵称,弹出对话框模拟发送匿名消息。
private sendAnonymousMessage(toNickname: string) {
prompt.showDialog({
title: `发送匿名消息给 ${toNickname}`,
message: '输入你想说的话(模拟)',
buttons: [
{ text: '取消', color: '#999' },
{ text: '发送', color: '#EC4899' }
]
}).then(result => {
if (result.index === 1) {
prompt.showToast({ message: '消息已发送(模拟)' });
}
});
}
// 在 PostCard 中为昵称添加点击事件
Text(post.nickname)
.onClick(() => this.sendAnonymousMessage(post.nickname))
5.10 夜间模式切换
通过 @State isNightMode 控制全局背景和文字颜色,使用条件样式。
// 在 build 根 Column 中
Column() {
// ...
}
.width('100%')
.height('100%')
.backgroundColor(this.isNightMode ? '#1A1A2E' : '#FFFFFF')
// 卡片背景颜色根据模式调整
.backgroundColor(this.isNightMode ? '#2D2D44' : '#FDF2F8')
.fontColor(this.isNightMode ? '#E0E0E0' : '#500724')
顶部添加切换开关:
Row() {
Text('🌸 匿名树洞')
.fontSize(24).fontWeight(FontWeight.Bold).fontColor(this.isNightMode ? '#FFB6C1' : '#500724')
Blank()
Toggle({ type: ToggleType.Switch, isOn: this.isNightMode })
.onChange(val => this.isNightMode = val)
}
.width('100%')
.padding(16)
5.11 数据持久化(Preferences)
已在服务层中实现,每次 add/update/delete 后自动保存。
六、UI 界面设计与实现(完整组件)
6.1 顶部标题与夜间模式开关
见上。
6.2 情绪筛选滚动条
见 MoodFilter。
6.3 发布区域(含表情选择)
发布区域在首页顶部,包含 TextArea 和表情选择、发布按钮。
@Builder PublishArea() {
Column({ space: 8 }) {
TextArea({ text: this.newContent, placeholder: '此刻的心情...' })
.width('100%')
.height(70)
.backgroundColor(this.isNightMode ? '#2D2D44' : '#FDF2F8')
.borderRadius(8)
.padding(10)
.onChange(v => this.newContent = v)
Row() {
Text('选择心情:').fontSize(12).fontColor('#BE185D');
ForEach(this.moods, (m) => {
Text(m.emoji)
.fontSize(22)
.padding(4)
.border({ width: this.selectedMoodEmoji === m.emoji ? 2 : 0, color: m.color })
.borderRadius(20)
.margin({ right: 4 })
.onClick(() => this.selectedMoodEmoji = m.emoji)
})
Blank()
Button('发布')
.height(34)
.fontSize(14)
.backgroundColor('#500724')
.fontColor('#FFF')
.borderRadius(20)
.onClick(() => this.publishPost())
}
.width('100%')
}
.width('100%')
.padding(12)
.backgroundColor(this.isNightMode ? '#2D2D44' : '#FFF')
.borderRadius(12)
.margin({ bottom: 8 })
}
6.4 树洞卡片(含操作按钮)
卡片内容包含头像、昵称、时间、内容、情绪标签、点赞/评论/收藏按钮。
@Builder PostCard(post: Post) {
Column({ space: 6 }) {
Row({ space: 8 }) {
Text(post.avatar).fontSize(28)
Column({ space: 2 }) {
Text(post.nickname)
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor(this.isNightMode ? '#FFB6C1' : '#500724')
.onClick(() => this.sendAnonymousMessage(post.nickname))
Text(this.formatTime(post.createTime))
.fontSize(11)
.fontColor('#999')
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
if (post.isHot) {
Text('🔥热门')
.fontSize(10)
.fontColor('#FFF')
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.backgroundColor('#EC4899')
.borderRadius(8)
}
}
.width('100%')
Text(post.content)
.fontSize(14)
.fontColor(this.isNightMode ? '#E0E0E0' : '#333')
.width('100%')
.lineHeight(22)
Text(this.moods.find(m => m.name === post.mood)?.emoji + ' ' + post.mood)
.fontSize(11)
.fontColor('#FFF')
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
.backgroundColor(this.moods.find(m => m.name === post.mood)?.color || '#EC4899')
.borderRadius(10)
.alignSelf(ItemAlign.Start)
Row({ space: 16 }) {
// 点赞
Row({ space: 4 }) {
Text(post.isLiked ? '❤️' : '🤍')
.fontSize(18)
.onClick(() => this.toggleLike(post.id))
Text(`${post.likes}`).fontSize(13).fontColor('#BE185D')
}
// 评论
Row({ space: 4 }) {
Text('💬')
.fontSize(18)
.onClick(() => this.openCommentDialog(post.id))
Text(`${post.comments}`).fontSize(13).fontColor('#BE185D')
}
// 收藏
Text(post.isFavorited ? '⭐' : '☆')
.fontSize(20)
.onClick(() => this.toggleFavorite(post.id))
Blank()
// 更多操作(长按可删除自己的帖子)
Text('⋯')
.fontSize(20)
.onClick(() => {
if (post.nickname === this.myNickname) {
prompt.showDialog({
title: '删除',
message: '确定删除此树洞吗?',
buttons: [{ text: '取消' }, { text: '删除', color: '#EF4444' }]
}).then(async (res) => {
if (res.index === 1) {
await postService.delete(post.id);
this.posts = this.posts.filter(p => p.id !== post.id);
prompt.showToast({ message: '已删除' });
}
});
}
})
}
.width('100%')
.margin({ top: 4 })
}
.width('100%')
.padding(14)
.backgroundColor(this.isNightMode ? '#2D2D44' : '#FDF2F8')
.borderRadius(12)
.margin({ bottom: 10 })
}
时间格式化辅助:
private formatTime(iso: string): string {
const now = Date.now();
const t = new Date(iso).getTime();
const diff = Math.floor((now - t) / 60000);
if (diff < 1) return '刚刚';
if (diff < 60) return diff + '分钟前';
if (diff < 1440) return Math.floor(diff / 60) + '小时前';
return iso.slice(0,10);
}
6.5 评论弹窗与列表
已在前面实现,弹窗中可显示该帖子的已有评论列表(扩展功能,可添加)。
七、完整主页面代码(Index.ets)
Index.ets 综合以上所有片段,使用 Tabs 布局,并整合全部功能。
// Index.ets(完整骨架)
// ... 导入
@Entry
@Component
struct Index {
// 所有状态
// 所有方法
// Builders
build() {
Column() {
// 顶部标题 + 夜间模式开关
Row() { ... }
if (this.isLoading) {
LoadingProgress().color('#EC4899').layoutWeight(1);
} else {
Tabs({ barPosition: BarPosition.End }) {
TabContent() {
Column() {
Scroll() {
Column() {
this.MoodFilter();
this.PublishArea();
ForEach(this.getFilteredPosts(), (p) => { this.PostCard(p) });
}
.padding({ left: 16, right: 16, bottom: 20 })
}
.layoutWeight(1)
}
.width('100%')
.height('100%')
.backgroundColor(this.isNightMode ? '#1A1A2E' : '#F8F8F8')
}
.tabBar('🏠 首页')
TabContent() {
// 热门榜
}.tabBar('🔥 热门')
TabContent() {
// 我的发布/收藏
}.tabBar('👤 我的')
TabContent() {
// 统计图表
}.tabBar('📊 统计')
}
.width('100%')
.layoutWeight(1)
}
}
.width('100%')
.height('100%')
.backgroundColor(this.isNightMode ? '#1A1A2E' : '#FFFFFF')
.dialog($$this.isCommentDialogVisible, this.CommentDialogContent())
}
}
八、运行与调试
8.1 环境
- DevEco Studio 5.0+,API 24。
- 无需特殊权限。
8.2 运行
- 导入完整项目(含
model、service等)。 - 运行模拟器,测试发布、点赞、评论、收藏、筛选、夜间模式等。
8.3 调试
- 使用 HiLog 查看数据操作日志。
- 测试边界:空内容发布、重复点赞、删除自己帖子等。
九、项目总结与扩展思路
9.1 项目总结
- 核心功能完整:发布、筛选、点赞、评论、收藏、热门、统计、夜间模式。
- 用户体验优秀:动画反馈、随机昵称、情绪色彩、暗色主题。
- 工程化程度高:服务层封装、数据持久化、组件化UI。
- 可扩展性强:可轻松接入后端API、用户系统。
9.2 扩展方向
- 真实用户系统:接入账号,记录用户ID,实现更精准的“我的发布”。
- 评论嵌套:支持回复评论,形成对话。
- 图片/表情包:支持上传图片或使用自定义表情。
- 举报与审核:内容安全过滤。
- 推送通知:有人评论/点赞时推送。
- 情绪日记:按日期记录情绪,生成周报。
9.3 结语
匿名树洞是校园情感社区的重要组成部分,通过 HarmonyOS ArkTS 可以快速构建出兼具温暖与功能的应用。本文提供的完整代码可直接运行,并能轻松扩展更多社交特性。
作者:技术实战派
原创声明:本文为原创技术文章,转载需注明出处。
源码仓库:完整项目代码已整理,可联系作者获取。
运行效果

更多推荐



所有评论(0)