HarmonyOS应用<节气通>开发第16篇:知识问答页面
·

引言
知识问答页面是用户交流和获取帮助的重要平台。本文将实现一个功能完善的问答页面,包括:
- 问答列表展示
- AI智能问答
- 提问功能
- 回答功能
- 点赞和评论
通过本文,你将掌握如何构建一个互动问答社区页面,并集成AI智能问答功能。
学习目标
完成本文后,你将能够:
- ✅ 实现问答列表展示
- ✅ 集成AI智能问答功能
- ✅ 添加提问功能
- ✅ 实现回答功能
- ✅ 添加点赞和评论功能
- ✅ 处理问答数据
需求分析
功能模块设计
| 模块 | 功能描述 | 技术要点 |
|---|---|---|
| 问答列表 | 展示问答内容 | List布局、卡片组件 |
| AI智能问答 | 自然语言交互 | HTTP请求、AI API调用 |
| 提问功能 | 用户发布问题 | 表单验证、弹窗 |
| 回答功能 | 用户回答问题 | 表单验证、提交 |
| 点赞功能 | 点赞问答和回答 | 状态切换、动画 |
| 评论功能 | 评论问答和回答 | 输入框、列表 |
核心实现
步骤1: 页面结构设计
完整代码
// pages/QAPage.ets
import router from '@ohos.router';
import prompt from '@ohos.prompt';
import type { Question, Answer } from '../models/QAModel';
@Entry
@Component
struct QAPage {
// 问答列表
@State questions: Question[] = [];
// 当前选中的问题(用于展开回答)
@State expandedQuestionId: string = '';
// AI聊天状态
@State chatMessages: ChatMessage[] = [];
@State isChatMode: boolean = false;
@State inputMessage: string = '';
@State isLoading: boolean = false;
// AI服务实例
private aiService = AIChatService.getInstance();
/**
* 页面加载时执行
*/
aboutToAppear() {
this.loadQuestions();
}
/**
* 加载问答数据
*/
loadQuestions(): void {
// Mock数据
this.questions = [
{
id: '1',
userId: '1',
userName: '用户A',
avatar: 'avatar1',
title: '立春和春分有什么区别?',
content: '立春和春分都是春季的节气,请问它们有什么区别呢?',
time: '2小时前',
likes: 24,
isLiked: false,
answers: [
{
id: 'a1',
userId: '2',
userName: '用户B',
avatar: 'avatar2',
content: '立春是春季的开始,标志着冬天结束;春分是春季的中点,昼夜平分。',
time: '1小时前',
likes: 18,
isLiked: false
}
]
},
{
id: '2',
userId: '3',
userName: '用户C',
avatar: 'avatar3',
title: '清明为什么要扫墓?',
content: '清明节扫墓有什么传统意义?',
time: '5小时前',
likes: 45,
isLiked: true,
answers: [
{
id: 'a2',
userId: '4',
userName: '用户D',
avatar: 'avatar4',
content: '清明节扫墓是为了缅怀祖先,表达思念之情,是中华民族的传统习俗。',
time: '3小时前',
likes: 32,
isLiked: false
},
{
id: 'a3',
userId: '5',
userName: '用户E',
avatar: 'avatar5',
content: '扫墓不仅是祭祀祖先,也是传承孝道的一种方式。',
time: '2小时前',
likes: 15,
isLiked: false
}
]
},
{
id: '3',
userId: '6',
userName: '用户F',
avatar: 'avatar6',
title: '夏至为什么要吃面条?',
content: '听说夏至有吃面条的习俗,这是为什么呢?',
time: '1天前',
likes: 67,
isLiked: false,
answers: []
}
];
}
/**
* 点赞问题
*/
likeQuestion(questionId: string): void {
const question = this.questions.find((q) => q.id === questionId);
if (question) {
question.isLiked = !question.isLiked;
question.likes += question.isLiked ? 1 : -1;
}
}
/**
* 点赞回答
*/
likeAnswer(questionId: string, answerId: string): void {
const question = this.questions.find((q) => q.id === questionId);
if (question) {
const answer = question.answers.find((a) => a.id === answerId);
if (answer) {
answer.isLiked = !answer.isLiked;
answer.likes += answer.isLiked ? 1 : -1;
}
}
}
/**
* 展开/收起回答
*/
toggleAnswers(questionId: string): void {
this.expandedQuestionId = this.expandedQuestionId === questionId ? '' : questionId;
}
/**
* 提交回答
*/
submitAnswer(questionId: string, content: string): void {
const question = this.questions.find((q) => q.id === questionId);
if (question) {
question.answers.push({
id: 'a' + Date.now(),
userId: 'current_user',
userName: '我',
avatar: 'current',
content: content,
time: '刚刚',
likes: 0,
isLiked: false
});
prompt.showToast({ message: '回答成功' });
}
}
/**
* 切换聊天模式
*/
toggleChatMode(): void {
this.isChatMode = !this.isChatMode;
}
/**
* 发送AI消息
*/
async sendAIMessage(): Promise<void> {
if (!this.inputMessage.trim() || this.isLoading) return;
const userMessage: ChatMessage = {
id: 'msg_' + Date.now(),
role: 'user',
content: this.inputMessage,
timestamp: Date.now()
};
this.chatMessages.push(userMessage);
this.isLoading = true;
this.inputMessage = '';
try {
const response = await this.aiService.sendMessage(userMessage.content);
const aiMessage: ChatMessage = {
id: 'msg_' + Date.now(),
role: 'assistant',
content: response,
timestamp: Date.now()
};
this.chatMessages.push(aiMessage);
} catch (error) {
console.error('发送消息失败:', error);
prompt.showToast({ message: '发送失败,请重试' });
} finally {
this.isLoading = false;
}
}
/**
* 构建UI
*/
build() {
Column({ space: 0 }) {
// 1. 顶部导航
this.buildHeader()
// 2. 模式切换标签
if (!this.isChatMode) {
this.buildAskButton()
// 3. 问答列表
this.buildQuestionList()
} else {
// AI聊天界面
this.buildChatInterface()
}
}
.width('100%')
.height('100%')
.backgroundColor('#F8F7F2')
}
}
interface Question {
id: string;
userId: string;
userName: string;
avatar: string;
title: string;
content: string;
time: string;
likes: number;
isLiked: boolean;
answers: Answer[];
}
interface Answer {
id: string;
userId: string;
userName: string;
avatar: string;
content: string;
time: string;
likes: number;
isLiked: boolean;
}
interface ChatMessage {
id: string;
role: 'user' | 'assistant';
content: string;
timestamp: number;
}
代码解析
1. 状态管理
- questions: 问答列表
- expandedQuestionId: 当前展开的问题ID
2. 功能方法
- likeQuestion(): 点赞问题
- likeAnswer(): 点赞回答
- toggleAnswers(): 展开/收起回答
- submitAnswer(): 提交回答
步骤1.5: AI智能问答服务封装
// services/AIChatService.ts
import http from '@ohos.net.http';
import prompt from '@ohos.prompt';
export class AIChatService {
private static instance: AIChatService;
private mockMode: boolean = true;
private constructor() {}
static getInstance(): AIChatService {
if (!AIChatService.instance) {
AIChatService.instance = new AIChatService();
}
return AIChatService.instance;
}
setMockMode(enabled: boolean): void {
this.mockMode = enabled;
}
async sendMessage(message: string): Promise<string> {
if (this.mockMode) {
return this.getMockResponse(message);
}
return this.callAIAPI(message);
}
private async callAIAPI(message: string): Promise<string> {
try {
const httpRequest = http.createHttp();
const response = await httpRequest.request(
'https://api.example.com/ai/chat',
{
method: http.RequestMethod.POST,
header: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY'
},
extraData: JSON.stringify({
message: message,
model: 'gpt-3.5-turbo',
temperature: 0.7
}),
connectTimeout: 10000,
readTimeout: 30000
}
);
if (response.responseCode === 200) {
const result = JSON.parse(response.result.toString());
return result.choices[0].message.content;
} else {
throw new Error(`API请求失败: ${response.responseCode}`);
}
} catch (error) {
console.error('AI服务调用失败:', error);
prompt.showToast({ message: 'AI服务暂时不可用' });
return '抱歉,暂时无法获取AI回答,请稍后重试。';
}
}
private getMockResponse(message: string): string {
const mockResponses: Record<string, string[]> = {
'立春': [
'立春是二十四节气之首,标志着春季的开始。此时气温回升,万物复苏。',
'立春有咬春、打春等传统习俗,人们会吃春饼、春卷等食物。'
],
'清明': [
'清明节是祭祖扫墓的传统节日,也是踏青郊游的好时节。',
'清明前后,气温升高,雨量增多,适合春耕春种。'
],
'夏至': [
'夏至是北半球一年中白昼最长的一天,标志着炎热季节的开始。',
'夏至有吃面的习俗,寓意长寿安康。'
],
'冬至': [
'冬至是北半球一年中白昼最短的一天,之后白天会逐渐变长。',
'冬至有吃饺子的习俗,据说可以防止耳朵冻掉。'
]
};
for (const [keyword, responses] of Object.entries(mockResponses)) {
if (message.includes(keyword)) {
return responses[Math.floor(Math.random() * responses.length)];
}
}
return '感谢您的提问!关于节气的问题,我可以为您提供详细解答。';
}
}
AI服务设计要点
1. 单例模式
- 确保全局只有一个AI服务实例
- 便于统一管理配置和状态
2. Mock模式支持
- 开发阶段使用Mock数据,无需依赖网络
- 上线后切换为真实API调用
3. API调用封装
- 统一错误处理
- 超时设置
- 请求头配置
4. 常见陷阱
- API超时: 设置合理的超时时间,避免用户等待过久
- 消息列表卡顿: 使用LazyForEach优化渲染
- 状态同步问题: 使用AppStorage管理聊天状态
步骤2: 顶部导航
/**
* 构建顶部导航
*/
@Builder
buildHeader(): void {
Row({ space: 16 }) {
Image($r('app.media.ic_back'))
.width(24)
.height(24)
.fillColor('#333333')
.onClick(() => {
try {
router.back();
} catch (error) {
console.error('返回失败: ' + JSON.stringify(error));
}
})
Text('知识问答')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
Blank()
}
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
.backgroundColor('#FFFFFF')
}
设计要点:
- 返回按钮
- 标题居中
- AI助手入口按钮
步骤2.5: AI聊天界面
/**
* 构建AI聊天界面
*/
@Builder
buildChatInterface(): void {
Column({ space: 0 }) {
// 聊天消息列表
List({ space: 16 }) {
ForEach(this.chatMessages, (message: ChatMessage) => {
ListItem() {
this.buildChatMessageItem(message)
}
}, (message: ChatMessage) => message.id)
if (this.isLoading) {
ListItem() {
Row({ space: 8 }) {
Image($r('app.media.ic_ai_avatar'))
.width(40)
.height(40)
.borderRadius(20)
Row({ space: 4 }) {
Row().width(8).height(8).backgroundColor('#CCCCCC').borderRadius(4)
Row().width(8).height(8).backgroundColor('#CCCCCC').borderRadius(4)
Row().width(8).height(8).backgroundColor('#CCCCCC').borderRadius(4)
}
.padding({ left: 12, right: 12, top: 8, bottom: 8 })
.backgroundColor('#F0F0F0')
.borderRadius(16)
}
.width('100%')
.justifyContent(FlexAlign.Start)
}
}
}
.width('100%')
.flexGrow(1)
.padding({ top: 16, left: 16, right: 16 })
// 输入区域
this.buildChatInput()
}
}
/**
* 构建聊天消息项
*/
@Builder
buildChatMessageItem(message: ChatMessage): void {
Row({ space: 12 }) {
if (message.role === 'assistant') {
Image($r('app.media.ic_ai_avatar'))
.width(40)
.height(40)
.borderRadius(20)
}
Text(message.content)
.fontSize(14)
.fontColor(message.role === 'user' ? '#FFFFFF' : '#333333')
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
.backgroundColor(message.role === 'user' ? '#4A9B6D' : '#FFFFFF')
.borderRadius(message.role === 'user' ? { topLeft: 20, topRight: 4, bottomLeft: 20, bottomRight: 20 } : { topLeft: 4, topRight: 20, bottomLeft: 20, bottomRight: 20 })
.maxWidth('70%')
if (message.role === 'user') {
Image($r('app.media.ic_default_avatar'))
.width(40)
.height(40)
.borderRadius(20)
}
}
.width('100%')
.justifyContent(message.role === 'user' ? FlexAlign.End : FlexAlign.Start)
}
/**
* 构建聊天输入框
*/
@Builder
buildChatInput(): void {
Row({ space: 12 }) {
Image($r('app.media.ic_default_avatar'))
.width(40)
.height(40)
.borderRadius(20)
Stack() {
TextInput({ placeholder: '向AI助手提问...' })
.width('100%')
.height(40)
.backgroundColor('#FFFFFF')
.borderRadius(20)
.padding({ left: 16, right: 80 })
.onChange((value: string) => {
this.inputMessage = value;
})
Button('发送')
.width(60)
.height(32)
.backgroundColor(this.inputMessage.trim() ? '#4A9B6D' : '#DDDDDD')
.fontColor('#FFFFFF')
.fontSize(13)
.borderRadius(16)
.position({ right: 8, top: 4 })
.onClick(() => {
this.sendAIMessage();
})
}
.flexGrow(1)
}
.width('92%')
.padding({ bottom: 20 })
}
设计要点:
- AI助手头像和用户头像区分
- 气泡样式差异化(用户/助手)
- 加载状态动画
- 输入框与发送按钮联动
步骤3: 提问按钮
/**
* 构建提问按钮
*/
@Builder
buildAskButton(): void {
Row({ space: 8 }) {
Image($r('app.media.ic_question'))
.width(24)
.height(24)
.fillColor('#4A9B6D')
Text('我有问题')
.fontSize(15)
.fontColor('#666666')
Blank()
Image($r('app.media.ic_arrow_right'))
.width(16)
.height(16)
.fillColor('#CCCCCC')
}
.width('92%')
.height(48)
.backgroundColor('#FFFFFF')
.borderRadius(24)
.padding({ left: 16, right: 16 })
.margin({ top: 12 })
.onClick(() => {
this.showAskDialog();
})
}
/**
* 显示提问弹窗
*/
showAskDialog(): void {
prompt.showToast({ message: '提问功能开发中' });
}
设计要点:
- 圆角按钮
- 图标+文字
- 点击弹出提问表单
步骤4: 问答列表
/**
* 构建问答列表
*/
@Builder
buildQuestionList(): void {
List({ space: 12 }) {
ForEach(this.questions, (question: Question) => {
ListItem() {
this.buildQuestionCard(question)
}
}, (question: Question) => question.id)
}
.width('92%')
.padding({ top: 12, bottom: 100 })
}
/**
* 构建问答卡片
*/
@Builder
buildQuestionCard(question: Question): void {
Card() {
Column({ space: 12 }) {
// 用户信息
Row({ space: 12 }) {
Image($r('app.media.ic_default_avatar'))
.width(40)
.height(40)
.borderRadius(20)
Column({ space: 4 }) {
Text(question.userName)
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor('#333333')
Text(question.time)
.fontSize(12)
.fontColor('#999999')
}
Blank()
// 点赞按钮
Row({ space: 4 }) {
Image(question.isLiked ? $r('app.media.ic_like_active') : $r('app.media.ic_like'))
.width(20)
.height(20)
.fillColor(question.isLiked ? '#FF5252' : '#999999')
Text(question.likes.toString())
.fontSize(13)
.fontColor(question.isLiked ? '#FF5252' : '#999999')
}
.onClick(() => {
this.likeQuestion(question.id);
})
}
// 问题内容
Column({ space: 8 }) {
Text(question.title)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
Text(question.content)
.fontSize(14)
.fontColor('#666666')
.lineHeight(24)
}
// 回答区域
Column({ space: 0 }) {
// 回答数量和展开按钮
Row({ space: 8 }) {
Text('回答 (' + question.answers.length + ')')
.fontSize(14)
.fontColor('#999999')
if (question.answers.length > 0) {
Text(this.expandedQuestionId === question.id ? '收起' : '查看')
.fontSize(13)
.fontColor('#4A9B6D')
.onClick(() => {
this.toggleAnswers(question.id);
})
}
}
// 展开的回答列表
if (this.expandedQuestionId === question.id && question.answers.length > 0) {
Column({ space: 12 }) {
ForEach(question.answers, (answer: Answer) => {
this.buildAnswerItem(question.id, answer)
}, (answer: Answer) => answer.id)
}
.margin({ top: 12 })
}
// 回答输入框
if (this.expandedQuestionId === question.id) {
this.buildAnswerInput(question.id);
}
}
}
.padding(16)
}
}
设计要点:
- 卡片式布局
- 用户信息展示
- 问题标题和内容
- 回答区域(可展开/收起)
步骤5: 回答项组件
/**
* 构建回答项
*/
@Builder
buildAnswerItem(questionId: string, answer: Answer): void {
Row({ space: 12 }) {
Image($r('app.media.ic_default_avatar'))
.width(36)
.height(36)
.borderRadius(18)
Column({ space: 8 }) {
Row({ space: 8 }) {
Text(answer.userName)
.fontSize(13)
.fontWeight(FontWeight.Medium)
.fontColor('#333333')
Text(answer.time)
.fontSize(12)
.fontColor('#999999')
}
Text(answer.content)
.fontSize(14)
.fontColor('#555555')
.lineHeight(22)
Row({ space: 16 }) {
Row({ space: 4 }) {
Image(answer.isLiked ? $r('app.media.ic_like_active') : $r('app.media.ic_like'))
.width(16)
.height(16)
.fillColor(answer.isLiked ? '#FF5252' : '#999999')
Text(answer.likes.toString())
.fontSize(12)
.fontColor('#999999')
}
.onClick(() => {
this.likeAnswer(questionId, answer.id);
})
Text('回复')
.fontSize(12)
.fontColor('#999999')
}
}
.flexGrow(1)
}
}
/**
* 构建回答输入框
*/
@Builder
buildAnswerInput(questionId: string): void {
@State answerContent: string = '';
Row({ space: 12 }) {
Image($r('app.media.ic_default_avatar'))
.width(40)
.height(40)
.borderRadius(20)
Stack() {
TextInput({ placeholder: '写下你的回答...' })
.width('100%')
.height(40)
.backgroundColor('#F5F5F5')
.borderRadius(20)
.padding({ left: 16, right: 80 })
.onChange((value: string) => {
this.answerContent = value;
})
Button('发送')
.width(60)
.height(32)
.backgroundColor(this.answerContent.trim() ? '#4A9B6D' : '#DDDDDD')
.fontColor('#FFFFFF')
.fontSize(13)
.borderRadius(16)
.position({ right: 8, top: 4 })
.onClick(() => {
if (this.answerContent.trim()) {
this.submitAnswer(questionId, this.answerContent);
this.answerContent = '';
}
})
}
.flexGrow(1)
}
.margin({ top: 12 })
}
设计要点:
- 回答项布局(头像+内容)
- 点赞功能
- 回答输入框
本章小结
核心知识点
本文完成了知识问答页面的实现:
1. 问答列表
- 卡片式布局展示问答
- 用户信息展示
- 问题标题和内容
2. 提问功能
- 提问按钮
- 弹窗表单(待实现)
3. 回答功能
- 展开/收起回答列表
- 回答输入框
- 提交回答
4. 点赞功能
- 问题点赞
- 回答点赞
- 状态切换动画
下一步预告
知识问答页面已经完成!在下一篇文章中,我们将学习:
- 意见反馈页面
- 反馈表单
- 问题分类
- 提交反馈
节气通应用已发布上线,可在应用市场下载体验
相关链接
- 项目源码: Atomgit仓库
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)