React Native鸿蒙跨平台实现教育类APP问答模块的跨端开发提供可直接复用的实践参考
在React Native(RN)鸿蒙跨端开发中,教育问答类页面作为“互动密集型+内容差异化”的典型场景,需兼顾列表渲染流畅性、互动状态一致性、多设备适配兼容性三大核心需求。本文以完整的教育问答页面(QAPage)代码为载体,聚焦RN鸿蒙跨端适配的核心技术点,,全程以技术博客解读语气呈现,兼顾专业性与可读性,每个技术点均配套具体代码示例、代码拆解及鸿蒙适配细节,为教育类APP问答模块的跨端开发提供
在React Native(RN)鸿蒙跨端开发中,教育问答类页面作为“互动密集型+内容差异化”的典型场景,需兼顾列表渲染流畅性、互动状态一致性、多设备适配兼容性三大核心需求。本文以完整的教育问答页面(QAPage)代码为载体,聚焦RN鸿蒙跨端适配的核心技术点,重点拆解关键代码片段的适配逻辑,全程以技术博客解读语气呈现,兼顾专业性与可读性,每个技术点均配套具体代码示例、代码拆解及鸿蒙适配细节,为教育类APP问答模块的跨端开发提供可直接复用的实践参考。
本次解读的教育问答页面,基于RN官方核心API开发,无任何第三方依赖(无需引入图标库、状态管理库、列表优化库),涵盖头部导航、问答统计、热门问题列表、底部导航四大模块,集成问答卡片封装、点赞状态联动、条件渲染、触摸交互等高频场景,完全遵循“一次开发、多端复用”理念,无需编写鸿蒙原生适配代码,即可实现iOS、Android、鸿蒙三端一致的视觉与交互体验,其跨端适配思路可直接迁移到各类问答类页面(社区问答、技术答疑等)。
组件封装是RN鸿蒙跨端开发的核心基础,也是提升代码可维护性、降低适配成本的关键。本页面最核心的封装亮点是QuestionCard子组件,其设计完全贴合鸿蒙原生交互规范,同时兼顾复用性与差异化内容展示,完美解决问答卡片在鸿蒙多设备(手机、平板、折叠屏)上的布局适配问题。以下结合核心代码片段,拆解其封装逻辑与鸿蒙适配细节:
1. Props传参
采用React.FC泛型明确定义组件类型,规范Props传参,这是跨端开发中避免参数类型错误、提升代码可维护性的关键,尤其适配鸿蒙系统的RN渲染机制,可提前规避渲染异常。
核心代码片段:
const QuestionCard: React.FC<{
question: any,
onLike: (id: number) => void,
onAnswer: (id: number) => void,
onPress: (id: number) => void
}> = ({ question, onLike, onAnswer, onPress }) => { ... }
代码拆解:
-
React.FC<{}>:泛型定义组件类型,明确组件接收的Props参数结构,在鸿蒙系统中,RN的类型检查机制可正常生效,避免因参数类型错误(如回调函数参数不匹配、question数据格式异常)导致的组件渲染失败、交互失灵;
-
四大核心Props:
① question:接收单个问题的完整数据(标题、分类、点赞数、最佳答案等),为组件渲染提供数据源,适配鸿蒙多设备的数据渲染规范,无数据格式兼容问题;
② onLike/onAnswer/onPress:接收父组件传递的回调函数,实现“子组件交互→父组件状态更新”的通信逻辑,这种通信方式完全兼容鸿蒙系统,无回调延迟、通信失败等问题,是跨端组件通信的最优实践。
2. 分层布局
QuestionCard采用分层布局,外层容器样式贴合鸿蒙原生卡片风格,内部布局通过flex实现自适应,适配鸿蒙多设备屏幕宽高比差异,避免排版错乱。
核心代码片段(布局与样式):
// 组件外层布局
<View style={styles.questionCard}>
{/* 头部:分类+时间 */}
<View style={styles.questionHeader}>...</View>
{/* 内容:标题+描述 */}
<TouchableOpacity onPress={() => onPress(question.id)}>...</TouchableOpacity>
{/* 底部:作者+互动按钮 */}
<View style={styles.questionFooter}>...</View>
{/* 最佳答案:条件渲染 */}
{question.isAnswered && question.bestAnswer && (...)}
</View>
// 核心样式
questionCard: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 16,
marginBottom: 16,
elevation: 3,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
代码拆解:
- 外层容器style.questionCard:
① borderRadius: 12 + 阴影样式:贴合鸿蒙原生应用的卡片视觉规范,鸿蒙系统对RN的阴影样式(shadowColor、shadowOffset等)支持完善,可直接映射为鸿蒙原生阴影,边缘柔和无错位,无需额外编写鸿蒙专属阴影代码;
② elevation: 3:Android专属的阴影层级属性,鸿蒙系统可完美兼容,避免在鸿蒙设备上出现阴影缺失、层级错乱问题;
③ padding: 16 + marginBottom: 16:固定内边距和外边距,配合flex布局,确保在鸿蒙不同尺寸设备上,卡片内部内容排版紧凑,无错位、文本溢出问题。
- 分层布局逻辑:头部(分类+时间)、内容(标题+描述)、底部(作者+互动按钮)、最佳答案(条件渲染)分层设计,符合鸿蒙原生应用的UI设计规范,提升用户体验,同时便于后续单独调整某一层的样式,适配不同鸿蒙设备。
3. 互动按钮封装
点赞、回答、评论三大互动按钮的封装,核心是适配鸿蒙原生交互反馈,确保点击流畅、状态同步,其中点赞按钮的动态切换是重点。
核心代码片段(互动按钮):
<View style={styles.actions}>
<TouchableOpacity style={styles.actionButton} onPress={() => onLike(question.id)}>
<Text style={styles.actionIcon}>{question.isLiked ? '❤️' : ICONS.like}</Text>
<Text style={styles.actionText}>{question.likes}</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.actionButton} onPress={() => onAnswer(question.id)}>
<Text style={styles.actionIcon}>{ICONS.answer}</Text>
<Text style={styles.actionText}>{question.answers}</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.actionButton}>
<Text style={styles.actionIcon}>{ICONS.comment}</Text>
</TouchableOpacity>
</View>
代码拆解:
-
TouchableOpacity组件:RN官方触摸交互组件,在鸿蒙系统中会自动映射为原生触摸组件,其点击反馈(透明度变化)与鸿蒙原生按钮完全一致,无延迟、无生硬感,避免出现点击无响应、点击错位等问题;
-
点赞按钮动态切换:{question.isLiked ? ‘❤️’ : ICONS.like},通过question.isLiked状态控制图标显示,这种JSX条件判断逻辑无需额外适配鸿蒙,RN框架可直接在鸿蒙设备上实现流畅渲染,点击后通过onLike回调同步更新状态,确保图标与点赞数实时联动;
-
样式适配:actionButton采用flexDirection: ‘row’,确保图标与数字横向对齐,适配鸿蒙多设备屏幕宽度,避免出现图标与数字错位、换行等问题。
教育问答页面的核心互动场景(点赞切换、点赞数更新),需依赖精准的状态管理,而鸿蒙系统对RN的状态更新机制有自身特性,若未做针对性设计,易出现状态同步延迟、数据错乱等问题。本代码采用React Hooks轻量化状态管理方案,无需引入Redux等第三方库,既简化代码结构,又确保鸿蒙跨端状态一致性。以下结合核心代码片段拆解:
1. 核心状态
在QAPage根组件中,通过useState钩子定义核心状态,存储热门问题列表及所有问题的互动状态,轻量化设计适配鸿蒙系统的状态渲染机制。
核心代码片段:
// 模拟问答数据(数据源)
const QUESTIONS = [
{
id: 1,
title: '什么是勾股定理?',
// 省略其他字段...
likes: 42,
isLiked: false, // 点赞状态
isAnswered: true, // 解答状态
bestAnswer: '...' // 最佳答案
},
// 省略其他问题...
];
// 根组件状态定义
const QAPage: React.FC = () => {
const [questions, setQuestions] = useState(QUESTIONS);
// 省略其他逻辑...
};
代码拆解:
-
useState(QUESTIONS):通过useState钩子定义questions状态,初始值为模拟数据QUESTIONS,用于存储所有热门问题的完整数据(包括点赞数、点赞状态、解答状态等);
-
鸿蒙适配优势:RN的useState钩子在鸿蒙系统中已做深度适配,状态存储与更新机制和iOS、Android完全一致,无需额外编写鸿蒙专属状态管理代码,避免出现状态同步延迟、数据错乱等问题;
-
轻量化设计:仅定义一个核心状态questions,而非拆分多个独立状态(如likes、isLiked等),简化代码结构,降低鸿蒙跨端适配复杂度,同时便于后续维护与扩展(如对接真实接口、添加筛选功能)。
2. 点赞状态联动逻辑
点赞状态联动是页面核心互动逻辑,通过handleLike回调函数实现“点击点赞→状态更新→UI重渲染”的闭环,核心是采用不可变数据模式,适配鸿蒙系统的RN重渲染机制。
核心代码片段:
const handleLike = (id: number) => {
setQuestions(questions.map(q =>
q.id === id ? { ...q, likes: q.isLiked ? q.likes - 1 : q.likes + 1, isLiked: !q.isLiked } : q
));
};
代码拆解:
-
核心逻辑:通过数组map方法遍历questions状态,根据点击的问题id定位当前问题,修改其点赞状态(isLiked)与点赞数(likes),再通过setQuestions更新状态;
-
不可变数据模式(关键适配点):{ …q, likes: …, isLiked: … },通过扩展运算符…q复制原有问题对象,再修改指定属性(likes、isLiked),而非直接修改原有问题对象(如q.isLiked = !q.isLiked);
为什么适配鸿蒙?:鸿蒙系统中,RN的重渲染机制依赖状态的“引用变化”,若直接修改原有问题对象,状态引用未发生变化,RN框架会认为状态未更新,导致UI不重渲染(出现“点击点赞,图标与数字不变化”的问题);采用不可变数据模式,每次更新状态都会返回一个新的数组对象,状态引用发生变化,RN框架会实时触发UI重渲染,确保鸿蒙设备上点赞图标与点赞数实时同步,无延迟、无错乱;
- 逻辑细节:q.isLiked ? q.likes - 1 : q.likes + 1,判断当前问题是否已点赞,已点赞则点赞数减1,未点赞则加1,同时反转isLiked状态(!q.isLiked),实现点赞与取消点赞的双向联动。
3. 弹窗交互
回答问题、查看详情的弹窗提示,采用RN官方Alert组件,自动适配鸿蒙原生弹窗规范,无需额外编写适配代码。
核心代码片段:
// 回答问题弹窗
const handleAnswer = (id: number) => {
Alert.alert('回答问题', `为问题 ID: ${id} 提供答案`);
};
// 查看详情弹窗
const handleQuestionPress = (id: number) => {
Alert.alert('查看详情', `查看问题 ID: ${id} 的详细信息`);
};
代码拆解:
-
Alert.alert():RN官方弹窗API,在鸿蒙系统中会自动映射为鸿蒙原生弹窗,弹窗的按钮布局、文字样式、交互逻辑(如点击确认/取消的反馈)与鸿蒙原生规范完全一致,无需额外编写鸿蒙专属弹窗代码;
-
适配细节:弹窗文本支持动态拼接(如${id}),在鸿蒙设备上无文本溢出、排版错乱问题,弹窗位置自动居中,适配鸿蒙多设备屏幕尺寸,避免出现弹窗偏移、遮挡等问题。
三、flex布局+样式规范
鸿蒙系统涵盖手机、平板、折叠屏等多形态设备,屏幕宽高比、分辨率差异较大,教育问答页面的列表渲染、模块布局、文本显示易出现错乱问题。本代码通过“flex自适应布局+统一样式规范+细节适配”的方案,无需编写设备判断逻辑,即可实现鸿蒙全设备兼容。以下结合核心代码片段拆解适配逻辑:
1. flex自适应布局适配
页面所有核心模块均采用flex布局,避免固定宽度、固定高度,适配鸿蒙多设备屏幕尺寸差异,这是RN鸿蒙跨端多设备适配的核心方案。
核心代码片段(页面整体布局):
<SafeAreaView style={styles.container}>
{/* 头部导航 */}
<View style={styles.header}>...</View>
{/* 统计信息 */}
<View style={styles.statsContainer}>...</View>
{/* 问题列表(滚动) */}
<ScrollView style={styles.content}>...</ScrollView>
{/* 底部导航 */}
<View style={styles.bottomNav}>...</View>
</SafeAreaView>
// 核心布局样式
const styles = StyleSheet.create({
container: {
flex: 1, // 占满整个屏幕高度
backgroundColor: '#f8fafc',
},
header: {
flexDirection: 'row', // 横向布局
justifyContent: 'space-between', // 左右对齐
alignItems: 'center', // 垂直居中
padding: 20,
// 省略其他样式...
},
statsContainer: {
flexDirection: 'row',
justifyContent: 'space-around', // 平均分布
// 省略其他样式...
},
content: {
flex: 1, // 自适应剩余高度
padding: 16,
},
bottomNav: {
flexDirection: 'row',
justifyContent: 'space-around', // 平均分布
// 省略其他样式...
},
});
代码拆解(鸿蒙多设备适配重点):
-
container.style.flex: 1:根容器占满整个屏幕高度,适配鸿蒙多设备屏幕高度差异,避免出现页面高度不足、内容溢出等问题;
-
横向flex布局(flexDirection: ‘row’):头部导航、统计信息、底部导航均采用横向flex布局,配合justifyContent(space-between/space-around)实现元素对齐与均匀分布,自适应鸿蒙多设备屏幕宽度,无元素错位、遮挡问题;
示例1:header采用justifyContent: ‘space-between’,实现标题与搜索按钮左右对齐,在鸿蒙手机(窄屏)、平板(宽屏)上均无错位,搜索按钮点击区域充足;
示例2:statsContainer采用justifyContent: ‘space-around’,三个统计项(总问题数、总回答数、解决率)平均分布,适配不同宽度的鸿蒙设备,无排版错乱;
- ScrollView.style.flex: 1:问题列表容器自适应剩余高度,在屏幕高度较小的鸿蒙手机上,可正常纵向滚动,查看所有问题;在屏幕高度较大的平板上,自适应填充剩余空间,无需滚动即可查看更多问题,提升用户体验,同时ScrollView组件在鸿蒙系统中已做深度适配,滚动流畅、无回弹异常、无卡顿。
2. 文本
文本尺寸、颜色、行高的规范设计,是适配鸿蒙多设备可读性的关键,同时避免使用平台特定样式属性,确保多端视觉一致。
核心代码片段(文本样式):
const styles = StyleSheet.create({
// 标题文本
title: {
fontSize: 20,
fontWeight: 'bold',
color: '#1e293b',
},
// 问题标题
questionTitle: {
fontSize: 16,
fontWeight: 'bold',
color: '#1e293b',
marginBottom: 6,
},
// 问题描述
questionDescription: {
fontSize: 14,
color: '#64748b',
lineHeight: 18,
marginBottom: 12,
},
// 辅助文本(时间、作者)
timeText: {
fontSize: 12,
color: '#94a3b8',
},
});
代码拆解:
-
文本尺寸规范:采用固定像素值(12px、14px、16px、20px),均为RN的相对像素值,可适配鸿蒙设备的字体缩放功能——当用户调整鸿蒙设备的字体大小时,页面中的所有文本会同步缩放,避免出现文字溢出、排版错乱、可读性下降的问题;
-
颜色规范:采用分层设计(标题深色#1e293b、正文浅色#64748b、辅助文本浅灰色#94a3b8),对比度合理,确保在鸿蒙设备上的可读性,同时避免使用平台特定的颜色属性(如Android的textColorPrimary),确保iOS、Android、鸿蒙三端视觉一致;
-
行高适配:questionDescription设置lineHeight: 18,配合fontSize: 14,确保文本换行均匀,在鸿蒙多设备上无文本重叠、行高异常问题,提升可读性。
3. 底部导航适配
底部导航作为页面核心导航组件,需适配鸿蒙多设备屏幕宽度,确保导航项均匀分布、点击区域充足,贴合鸿蒙原生导航规范。
核心代码片段(底部导航):
<View style={styles.bottomNav}>
<TouchableOpacity style={styles.navItem}>
<Text style={[styles.navIcon, styles.activeNavIcon]}>{ICONS.home}</Text>
<Text style={[styles.navText, styles.activeNavText]}>首页</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navItem}>...</TouchableOpacity>
<TouchableOpacity style={styles.navItem}>...</TouchableOpacity>
<TouchableOpacity style={styles.navItem}>...</TouchableOpacity>
</View>
// 底部导航样式
bottomNav: {
flexDirection: 'row',
justifyContent: 'space-around',
backgroundColor: '#ffffff',
borderTopWidth: 1,
borderTopColor: '#e2e8f0',
paddingVertical: 12,
},
navItem: {
alignItems: 'center', // 图标与文本垂直居中
},
navIcon: {
fontSize: 20,
color: '#94a3b8',
marginBottom: 4,
},
activeNavIcon: {
color: '#3b82f6', // 激活状态颜色
},
代码拆解:
-
justifyContent: ‘space-around’:四个导航项(首页、问答、收藏、我的)平均分布在底部导航栏,自适应鸿蒙多设备屏幕宽度,在窄屏手机、宽屏平板上均无导航项错位、拥挤问题;
-
navItem.alignItems: ‘center’:每个导航项的图标与文本垂直居中,贴合鸿蒙原生导航组件的设计规范,提升用户体验;
-
激活状态适配:通过样式叠加([styles.navIcon, styles.activeNavIcon])实现激活导航项的颜色变化,这种样式叠加方式在鸿蒙系统中完全兼容,无样式错乱问题,同时激活状态的视觉反馈清晰,便于用户识别当前所在页面;
-
paddingVertical: 12:固定上下内边距,确保底部导航栏高度适中,适配鸿蒙多设备,避免出现导航栏过高/过矮、点击区域不足等问题。
RN鸿蒙跨端开发的核心的是“依托RN官方API,遵循‘一次开发、多端复用’理念,聚焦组件封装、状态管理、多设备适配三大核心”,无需编写鸿蒙原生适配代码,即可实现多端一致的视觉与交互体验。
-
组件封装:规范Props传参、分层布局,贴合鸿蒙原生交互规范,提升组件复用性,降低适配成本;
-
状态管理:采用轻量化React Hooks,通过不可变数据模式确保鸿蒙跨端状态一致性,避免状态同步延迟;
-
多设备适配:采用flex自适应布局+统一样式规范,无需设备判断,适配鸿蒙多形态设备,避免排版错乱。
QAPage 组件采用了现代 React 函数组件架构,结合 useState Hook 实现了精细化的状态管理。组件通过 questions 状态变量管理问答列表数据,实现了问题的点赞、回答和查看详情等功能。
QuestionCard 组件作为独立的子组件,负责渲染单个问题的详细信息,通过 props 接收问题数据和多个回调函数,实现了组件的复用和逻辑分离。这种组件化设计使得代码结构清晰,易于维护和扩展。
数据结构
应用使用了结构化的问答数据格式,每个问题包含 ID、标题、描述、分类、作者、时间、点赞数、回答数、是否点赞、是否回答和最佳答案等属性。这种数据结构设计合理,能够满足教育问答页面的各种功能需求。特别是最佳答案的处理,当问题被回答时显示最佳答案,为用户提供了清晰的信息层次。
布局
应用采用了现代化的移动应用布局设计,主要包含头部、统计信息和问题列表三个部分。布局设计上,使用了 SafeAreaView 确保在不同设备上的显示兼容性,使用 ScrollView 确保在内容较长时可以滚动查看。
视觉设计上,使用了简洁明了的风格,通过不同的样式区分不同的功能区域和状态。统计信息的布局清晰,显示了总问题数、总回答数和解决率。问题卡片的布局统一,包含分类标签、标题、描述、作者信息、操作按钮和最佳答案,为用户提供了清晰的视觉信息。
交互
应用实现了丰富的交互功能,包括:
- 问题查看:点击问题卡片可以查看详细内容,通过
Alert.alert提供操作反馈。 - 点赞功能:点击点赞图标可以点赞或取消点赞,点赞状态会实时更新并通过图标变化和点赞数更新提供视觉反馈。
- 回答功能:点击回答图标可以回答问题,通过
Alert.alert提供操作反馈。 - 最佳答案展示:当问题被回答时,显示最佳答案,为用户提供了直接的信息获取方式。
这些交互功能的实现遵循了 React 的最佳实践,通过状态更新驱动 UI 变化,确保了交互的一致性和可靠性。特别是点赞功能,不仅更新了点赞状态,还同步更新了点赞数,为用户提供了即时的操作反馈。
在 React Native 与鸿蒙系统跨端开发中,该应用展现了多项兼容性设计:
-
基础组件选择:使用了
SafeAreaView、ScrollView、TouchableOpacity等基础组件,这些组件在 React Native 和鸿蒙系统中都有对应的实现。 -
样式管理:通过
StyleSheet.create管理样式,确保了在不同平台上的一致表现。 -
资源管理:使用 emoji 作为图标,避免了平台差异带来的图标显示问题,确保了在所有平台上的一致显示。
-
状态管理:使用
useStateHook 进行状态管理,在鸿蒙系统中可以通过相应的状态管理机制(如@State装饰器)实现类似功能。 -
布局系统:使用了 Flexbox 布局系统,这是 React Native 和鸿蒙系统都支持的布局方式,确保了在不同平台上的一致布局效果。
-
API 兼容性:使用了
Alert.alert等跨平台 API,确保了在不同平台上的一致表现。
在将该应用适配到鸿蒙系统时,需要注意以下几点:
-
组件映射:将 React Native 的
SafeAreaView、ScrollView、TouchableOpacity等组件映射到鸿蒙系统的对应组件。例如,TouchableOpacity可以映射到鸿蒙的Button组件,ScrollView可以映射到鸿蒙的ListContainer组件。 -
样式转换:将 React Native 的
StyleSheet样式转换为鸿蒙系统支持的样式格式。例如,React Native 的flexDirection: 'row'对应鸿蒙的flexDirection: FlexDirection.Row。 -
状态管理:鸿蒙系统的状态管理机制与 React Native 不同,需要进行适当的调整。例如,可以使用鸿蒙的
@State装饰器替代useStateHook。 -
事件处理:鸿蒙系统的事件处理机制与 React Native 不同,需要进行适当的调整。例如,鸿蒙系统的点击事件处理方式与 React Native 不同。
-
布局系统:虽然 Flexbox 布局在鸿蒙系统中也得到支持,但具体的实现细节可能有所不同,需要进行适当的调整。
-
性能优化:根据鸿蒙系统的特性,进行针对性的性能优化,确保应用在鸿蒙设备上运行流畅。例如,合理使用鸿蒙的缓存机制和渲染优化策略。
-
API 适配:确保
Alert.alert等 API 在鸿蒙系统中有对应的实现。例如,可以使用鸿蒙的promptAction或自定义弹窗组件。
该教育问答页面展示了一个功能完整、设计优雅的 React Native 应用实现,涵盖了组件化、状态管理、布局设计、交互处理等多个方面的技术点。通过合理的组件架构和状态管理,以及对跨端兼容性的考虑,该应用不仅在 React Native 环境下运行良好,也为后续的鸿蒙系统适配奠定了基础。
真实演示案例代码:
// app.tsx
import React, { useState } from 'react';
import { SafeAreaView, View, Text, StyleSheet, TouchableOpacity, ScrollView, Image, Dimensions, Alert } from 'react-native';
// 图标库
const ICONS = {
home: '🏠',
question: '❓',
user: '👤',
search: '🔍',
star: '⭐',
answer: '✅',
like: '👍',
comment: '💬',
};
const { width } = Dimensions.get('window');
// 模拟问答数据
const QUESTIONS = [
{
id: 1,
title: '什么是勾股定理?',
description: '想了解勾股定理的定义和应用场景',
category: '数学',
author: '学生A',
time: '2小时前',
likes: 42,
answers: 8,
isLiked: false,
isAnswered: true,
bestAnswer: '勾股定理是指在直角三角形中,两条直角边的平方和等于斜边的平方。即a²+b²=c²。'
},
{
id: 2,
title: '牛顿三大定律是什么?',
description: '物理课上学习牛顿定律,希望有详细解释',
category: '物理',
author: '学生B',
time: '4小时前',
likes: 36,
answers: 5,
isLiked: true,
isAnswered: false,
},
{
id: 3,
title: '元素周期表怎么记忆?',
description: '化学元素太多,记不住排列规律',
category: '化学',
author: '学生C',
time: '6小时前',
likes: 28,
answers: 12,
isLiked: false,
isAnswered: true,
bestAnswer: '可以通过元素的电子排布规律来记忆,同一族元素具有相似的化学性质。'
},
{
id: 4,
title: '如何理解时态变化?',
description: '英语时态总是混淆,求详细解释',
category: '英语',
author: '学生D',
time: '8小时前',
likes: 51,
answers: 15,
isLiked: true,
isAnswered: false,
},
{
id: 5,
title: 'DNA和RNA的区别',
description: '生物课上学习核酸,想了解两者不同',
category: '生物',
author: '学生E',
time: '10小时前',
likes: 33,
answers: 7,
isLiked: false,
isAnswered: true,
bestAnswer: 'DNA是双链结构,含有胸腺嘧啶;RNA是单链结构,含有尿嘧啶。'
},
{
id: 6,
title: '中国古代朝代顺序',
description: '历史朝代总是记混,求记忆方法',
category: '历史',
author: '学生F',
time: '12小时前',
likes: 24,
answers: 6,
isLiked: false,
isAnswered: false,
},
];
const QuestionCard: React.FC<{
question: any,
onLike: (id: number) => void,
onAnswer: (id: number) => void,
onPress: (id: number) => void
}> = ({ question, onLike, onAnswer, onPress }) => {
return (
<View style={styles.questionCard}>
<View style={styles.questionHeader}>
<View style={styles.categoryTag}>
<Text style={styles.categoryText}>{question.category}</Text>
</View>
<View style={styles.timeRow}>
<Text style={styles.timeIcon}>{ICONS.question}</Text>
<Text style={styles.timeText}>{question.time}</Text>
</View>
</View>
<TouchableOpacity onPress={() => onPress(question.id)}>
<Text style={styles.questionTitle}>{question.title}</Text>
<Text style={styles.questionDescription}>{question.description}</Text>
</TouchableOpacity>
<View style={styles.questionFooter}>
<View style={styles.authorInfo}>
<Text style={styles.author}>{ICONS.user} {question.author}</Text>
</View>
<View style={styles.actions}>
<TouchableOpacity style={styles.actionButton} onPress={() => onLike(question.id)}>
<Text style={styles.actionIcon}>{question.isLiked ? '❤️' : ICONS.like}</Text>
<Text style={styles.actionText}>{question.likes}</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.actionButton} onPress={() => onAnswer(question.id)}>
<Text style={styles.actionIcon}>{ICONS.answer}</Text>
<Text style={styles.actionText}>{question.answers}</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.actionButton}>
<Text style={styles.actionIcon}>{ICONS.comment}</Text>
</TouchableOpacity>
</View>
</View>
{question.isAnswered && question.bestAnswer && (
<View style={styles.bestAnswer}>
<Text style={styles.bestAnswerTitle}>最佳答案:</Text>
<Text style={styles.bestAnswerText}>{question.bestAnswer}</Text>
</View>
)}
</View>
);
};
const QAPage: React.FC = () => {
const [questions, setQuestions] = useState(QUESTIONS);
const handleLike = (id: number) => {
setQuestions(questions.map(q =>
q.id === id ? { ...q, likes: q.isLiked ? q.likes - 1 : q.likes + 1, isLiked: !q.isLiked } : q
));
};
const handleAnswer = (id: number) => {
Alert.alert('回答问题', `为问题 ID: ${id} 提供答案`);
};
const handleQuestionPress = (id: number) => {
Alert.alert('查看详情', `查看问题 ID: ${id} 的详细信息`);
};
return (
<SafeAreaView style={styles.container}>
{/* 头部 */}
<View style={styles.header}>
<Text style={styles.title}>教育问答</Text>
<TouchableOpacity style={styles.searchButton}>
<Text style={styles.searchIcon}>{ICONS.search}</Text>
</TouchableOpacity>
</View>
{/* 统计信息 */}
<View style={styles.statsContainer}>
<View style={styles.statItem}>
<Text style={styles.statNumber}>128</Text>
<Text style={styles.statLabel}>总问题数</Text>
</View>
<View style={styles.statItem}>
<Text style={styles.statNumber}>342</Text>
<Text style={styles.statLabel}>总回答数</Text>
</View>
<View style={styles.statItem}>
<Text style={styles.statNumber}>89%</Text>
<Text style={styles.statLabel}>解决率</Text>
</View>
</View>
{/* 问题列表 */}
<ScrollView style={styles.content}>
<Text style={styles.sectionTitle}>热门问题</Text>
<Text style={styles.sectionSubtitle}>寻找问题的答案</Text>
{questions.map(question => (
<QuestionCard
key={question.id}
question={question}
onLike={handleLike}
onAnswer={handleAnswer}
onPress={handleQuestionPress}
/>
))}
</ScrollView>
{/* 底部导航 */}
<View style={styles.bottomNav}>
<TouchableOpacity style={styles.navItem}>
<Text style={[styles.navIcon, styles.activeNavIcon]}>{ICONS.home}</Text>
<Text style={[styles.navText, styles.activeNavText]}>首页</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navItem}>
<Text style={styles.navIcon}>{ICONS.question}</Text>
<Text style={styles.navText}>问答</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navItem}>
<Text style={styles.navIcon}>{ICONS.star}</Text>
<Text style={styles.navText}>收藏</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navItem}>
<Text style={styles.navIcon}>{ICONS.user}</Text>
<Text style={styles.navText}>我的</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f8fafc',
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 20,
backgroundColor: '#ffffff',
borderBottomWidth: 1,
borderBottomColor: '#e2e8f0',
},
title: {
fontSize: 20,
fontWeight: 'bold',
color: '#1e293b',
},
searchButton: {
padding: 8,
},
searchIcon: {
fontSize: 20,
color: '#64748b',
},
statsContainer: {
flexDirection: 'row',
justifyContent: 'space-around',
backgroundColor: '#ffffff',
padding: 16,
margin: 16,
borderRadius: 12,
elevation: 2,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
statItem: {
alignItems: 'center',
},
statNumber: {
fontSize: 20,
fontWeight: 'bold',
color: '#3b82f6',
},
statLabel: {
fontSize: 12,
color: '#64748b',
marginTop: 4,
},
content: {
flex: 1,
padding: 16,
},
sectionTitle: {
fontSize: 22,
fontWeight: 'bold',
color: '#1e293b',
marginBottom: 4,
},
sectionSubtitle: {
fontSize: 14,
color: '#64748b',
marginBottom: 20,
},
questionCard: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 16,
marginBottom: 16,
elevation: 3,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
questionHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12,
},
categoryTag: {
backgroundColor: '#dbeafe',
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 12,
},
categoryText: {
fontSize: 12,
color: '#3b82f6',
},
timeRow: {
flexDirection: 'row',
alignItems: 'center',
},
timeIcon: {
fontSize: 12,
color: '#94a3b8',
marginRight: 4,
},
timeText: {
fontSize: 12,
color: '#94a3b8',
},
questionTitle: {
fontSize: 16,
fontWeight: 'bold',
color: '#1e293b',
marginBottom: 6,
},
questionDescription: {
fontSize: 14,
color: '#64748b',
lineHeight: 18,
marginBottom: 12,
},
questionFooter: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
authorInfo: {},
author: {
fontSize: 12,
color: '#94a3b8',
},
actions: {
flexDirection: 'row',
alignItems: 'center',
},
actionButton: {
flexDirection: 'row',
alignItems: 'center',
marginLeft: 16,
},
actionIcon: {
fontSize: 16,
color: '#94a3b8',
},
actionText: {
fontSize: 12,
color: '#94a3b8',
marginLeft: 4,
},
bestAnswer: {
marginTop: 12,
paddingTop: 12,
borderTopWidth: 1,
borderTopColor: '#e2e8f0',
},
bestAnswerTitle: {
fontSize: 14,
fontWeight: '600',
color: '#1e293b',
marginBottom: 4,
},
bestAnswerText: {
fontSize: 14,
color: '#64748b',
lineHeight: 18,
},
bottomNav: {
flexDirection: 'row',
justifyContent: 'space-around',
backgroundColor: '#ffffff',
borderTopWidth: 1,
borderTopColor: '#e2e8f0',
paddingVertical: 12,
},
navItem: {
alignItems: 'center',
},
navIcon: {
fontSize: 20,
color: '#94a3b8',
marginBottom: 4,
},
activeNavIcon: {
color: '#3b82f6',
},
navText: {
fontSize: 12,
color: '#94a3b8',
},
activeNavText: {
color: '#3b82f6',
},
});
export default QAPage;

打包
接下来通过打包命令npn run harmony将reactNative的代码打包成为bundle,这样可以进行在开源鸿蒙OpenHarmony中进行使用。

打包之后再将打包后的鸿蒙OpenHarmony文件拷贝到鸿蒙的DevEco-Studio工程目录去:

最后运行效果图如下显示:

欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
更多推荐



所有评论(0)