在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,遵循‘一次开发、多端复用’理念,聚焦组件封装、状态管理、多设备适配三大核心”,无需编写鸿蒙原生适配代码,即可实现多端一致的视觉与交互体验。

  1. 组件封装:规范Props传参、分层布局,贴合鸿蒙原生交互规范,提升组件复用性,降低适配成本;

  2. 状态管理:采用轻量化React Hooks,通过不可变数据模式确保鸿蒙跨端状态一致性,避免状态同步延迟;

  3. 多设备适配:采用flex自适应布局+统一样式规范,无需设备判断,适配鸿蒙多形态设备,避免排版错乱。


QAPage 组件采用了现代 React 函数组件架构,结合 useState Hook 实现了精细化的状态管理。组件通过 questions 状态变量管理问答列表数据,实现了问题的点赞、回答和查看详情等功能。

QuestionCard 组件作为独立的子组件,负责渲染单个问题的详细信息,通过 props 接收问题数据和多个回调函数,实现了组件的复用和逻辑分离。这种组件化设计使得代码结构清晰,易于维护和扩展。

数据结构

应用使用了结构化的问答数据格式,每个问题包含 ID、标题、描述、分类、作者、时间、点赞数、回答数、是否点赞、是否回答和最佳答案等属性。这种数据结构设计合理,能够满足教育问答页面的各种功能需求。特别是最佳答案的处理,当问题被回答时显示最佳答案,为用户提供了清晰的信息层次。

布局

应用采用了现代化的移动应用布局设计,主要包含头部、统计信息和问题列表三个部分。布局设计上,使用了 SafeAreaView 确保在不同设备上的显示兼容性,使用 ScrollView 确保在内容较长时可以滚动查看。

视觉设计上,使用了简洁明了的风格,通过不同的样式区分不同的功能区域和状态。统计信息的布局清晰,显示了总问题数、总回答数和解决率。问题卡片的布局统一,包含分类标签、标题、描述、作者信息、操作按钮和最佳答案,为用户提供了清晰的视觉信息。

交互

应用实现了丰富的交互功能,包括:

  • 问题查看:点击问题卡片可以查看详细内容,通过 Alert.alert 提供操作反馈。
  • 点赞功能:点击点赞图标可以点赞或取消点赞,点赞状态会实时更新并通过图标变化和点赞数更新提供视觉反馈。
  • 回答功能:点击回答图标可以回答问题,通过 Alert.alert 提供操作反馈。
  • 最佳答案展示:当问题被回答时,显示最佳答案,为用户提供了直接的信息获取方式。

这些交互功能的实现遵循了 React 的最佳实践,通过状态更新驱动 UI 变化,确保了交互的一致性和可靠性。特别是点赞功能,不仅更新了点赞状态,还同步更新了点赞数,为用户提供了即时的操作反馈。


在 React Native 与鸿蒙系统跨端开发中,该应用展现了多项兼容性设计:

  1. 基础组件选择:使用了 SafeAreaViewScrollViewTouchableOpacity 等基础组件,这些组件在 React Native 和鸿蒙系统中都有对应的实现。

  2. 样式管理:通过 StyleSheet.create 管理样式,确保了在不同平台上的一致表现。

  3. 资源管理:使用 emoji 作为图标,避免了平台差异带来的图标显示问题,确保了在所有平台上的一致显示。

  4. 状态管理:使用 useState Hook 进行状态管理,在鸿蒙系统中可以通过相应的状态管理机制(如 @State 装饰器)实现类似功能。

  5. 布局系统:使用了 Flexbox 布局系统,这是 React Native 和鸿蒙系统都支持的布局方式,确保了在不同平台上的一致布局效果。

  6. API 兼容性:使用了 Alert.alert 等跨平台 API,确保了在不同平台上的一致表现。


在将该应用适配到鸿蒙系统时,需要注意以下几点:

  1. 组件映射:将 React Native 的 SafeAreaViewScrollViewTouchableOpacity 等组件映射到鸿蒙系统的对应组件。例如,TouchableOpacity 可以映射到鸿蒙的 Button 组件,ScrollView 可以映射到鸿蒙的 ListContainer 组件。

  2. 样式转换:将 React Native 的 StyleSheet 样式转换为鸿蒙系统支持的样式格式。例如,React Native 的 flexDirection: 'row' 对应鸿蒙的 flexDirection: FlexDirection.Row

  3. 状态管理:鸿蒙系统的状态管理机制与 React Native 不同,需要进行适当的调整。例如,可以使用鸿蒙的 @State 装饰器替代 useState Hook。

  4. 事件处理:鸿蒙系统的事件处理机制与 React Native 不同,需要进行适当的调整。例如,鸿蒙系统的点击事件处理方式与 React Native 不同。

  5. 布局系统:虽然 Flexbox 布局在鸿蒙系统中也得到支持,但具体的实现细节可能有所不同,需要进行适当的调整。

  6. 性能优化:根据鸿蒙系统的特性,进行针对性的性能优化,确保应用在鸿蒙设备上运行流畅。例如,合理使用鸿蒙的缓存机制和渲染优化策略。

  7. 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工程目录去:

在这里插入图片描述

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

请添加图片描述

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

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐