复合业务实体建模

多邮箱管理应用展现了企业级 TypeScript 类型系统的深度实践:

type EmailAccount = {
  id: string;
  name: string;
  email: string;
  totalMails: number;
  unreadMails: number;
  color: string;
  isActive: boolean;
};

type Email = {
  id: string;
  accountId: string;
  subject: string;
  sender: string;
  content: string;
  timestamp: string;
  isRead: boolean;
  isStarred: boolean;
  hasAttachment: boolean;
};

type MailStats = {
  type: string;
  count: number;
  percentage: number;
  color: string;
};

这种类型定义方式在跨平台企业应用开发中具有重要的架构意义。通过将核心业务实体(邮箱账户、邮件、统计)进行明确的类型化约束,代码的可读性、可维护性和类型安全性得到了系统性的提升。在鸿蒙平台上,这种类型驱动的开发模式可以确保数据模型在不同设备间的传输一致性,有效避免因类型不匹配导致的运行时错误。特别是对于邮件管理这类涉及敏感数据的应用,严格的类型约束可以防止数据泄露和格式错误。

枚举与联合类型应用

代码中使用了精妙的字符串字面量类型:

// 在样式和逻辑中体现的类型约束
color: string; // 虽然定义为string,但实际使用特定的颜色代码

这种设计在实际开发中可以通过更严格的类型约束来增强代码的健壮性。在跨平台开发中,颜色值的平台一致性尤为重要,可以考虑使用枚举或特定的颜色类型来确保在各平台上视觉效果的一致性。

状态管理与数据流架构

多层级状态管理

应用实现了复杂的多状态管理模式:

const [accounts, setAccounts] = useState<EmailAccount[]>([]);
const [emails, setEmails] = useState<Email[]>([]);
const [stats] = useState([]);
const [mailTypeStats] = useState<MailStats[]>([]);

这种状态拆分策略在企业级应用中具有重要的技术意义。每个状态变量都有清晰的业务语义边界:accounts 管理邮箱账户信息,emails 存储邮件数据,statsmailTypeStats 处理统计信息。在鸿蒙的分布式场景中,这种设计可以支持更精细的状态同步策略。例如,邮件数据可能需要实时同步,而统计信息可以延迟同步,账户信息可能需要在所有设备间保持一致性。

不可变数据更新模式

代码实现了精确的状态更新逻辑:

const toggleEmailRead = (id: string) => {
  setEmails(prev => 
    prev.map(email => 
      email.id === id ? { ...email, isRead: !email.isRead } : email
    )
  );
};

这种不可变数据更新模式在跨平台邮件应用中具有重要的性能意义。通过创建新的对象引用,React 可以精确检测到状态变化并优化重渲染过程。在鸿蒙平台上,这种模式可以与鸿蒙的响应式编程模型很好地结合。对于邮件阅读状态这种高频操作,性能优化尤为重要。

组件化架构深度解析

高阶业务组件设计

应用采用了多层次组件架构:

const EmailItem = ({ email, account, onToggleRead, onToggleStar }) => {
  // 邮件项组件实现
};

const AccountCard = ({ account, onPress }) => {
  // 账户卡片组件实现
};

const StatsCard = ({ title, value, icon, color }) => {
  // 统计卡片组件实现
};

const MailTypeStats = ({ stats }) => {
  // 邮件类型统计组件实现
};

这种组件设计模式在跨平台企业应用开发中展现了强大的架构优势。每个组件都通过明确的 props 接口定义其输入和输出,实现了业务逻辑与表现层的完全解耦。在鸿蒙平台上,这种设计使得组件可以独立进行平台适配,例如为鸿蒙平台优化邮件渲染性能或集成鸿蒙的原生邮件服务。

条件渲染与动态样式

代码中大量使用了条件渲染和动态样式:

<Text style={[styles.sender, !email.isRead && styles.unreadText]}>{email.sender}</Text>
<Text style={styles.starIcon}>{email.isStarred ? ICONS.star : '☆'}</Text>

这种模式在跨平台邮件客户端开发中需要特别注意性能和一致性。未读邮件的粗体显示、星标邮件的视觉反馈等都需要在各个平台上保持一致的用户体验。在鸿蒙平台上,开发者应当使用平台原生的条件渲染优化技术,确保动态样式切换的流畅性。

数据可视化与统计分析

统计图表实现

代码实现了多种数据可视化组件:

const MailTypeStats = ({ stats }: { stats: MailStats[] }) => {
  return (
    <View style={styles.mailTypeStatsContainer}>
      <Text style={styles.mailTypeStatsTitle}>邮件类型分布</Text>
      {stats.map((stat, index) => (
        <View key={index} style={styles.mailTypeStatItem}>
          <Text style={styles.mailTypeLabel}>{stat.type}</Text>
          <View style={styles.mailTypeProgressBar}>
            <View 
              style={[
                styles.mailTypeProgressFill, 
                { 
                  width: `${stat.percentage}%`, 
                  backgroundColor: stat.color 
                }
              ]} 
            />
          </View>
          <Text style={styles.mailTypeCount}>{stat.count}</Text>
        </View>
      ))}
    </View>
  );
};

数据与“协议层”

  • EmailAccount、Email、MailStats 的类型把业务语义稳定地封装在 JS 端,组件只消费这些纯对象而不依赖平台特化字段。这样的“协议层”设计让桥接层只传递简单数据结构,ArkUI、UIKit、View 三端的表现更可预期。
  • 可选属性(hasAttachment、isStarred、isRead)定义了展示与交互的核心变元,渲染时通过条件分支映射为视觉语义。所有状态只在 JS 线程上决策,原生侧负责批量接收更新,端间行为保持一致。

列表与虚拟化的职责

  • 邮件列表用 FlatList 渲染并以 id 作为稳定 key,滚动窗口化与回收由原生后端承担,Bridge 往返更少、三端滚动更平滑。
  • 页面外层用 ScrollView 包裹统计区与 FlatList,会产生嵌套滚动的事件竞争与重复布局。更稳健的跨端方案是让 FlatList“接管整页滚动”,把统计区、账户列表、类型统计作为 ListHeaderComponent 注入;单一滚动容器便于 ArkUI/Skia 合成层优化。
const Header = (
  <View>
    <View><Text>多邮箱管理</Text></View>
    <View>{stats.map((s, i) => <StatsCard key={i} {...s} />)}</View>
    <Text>邮箱账户</Text>
    <View>{accounts.map(a => <AccountCard key={a.id} account={a} onPress={() => selectAccount(a.id)} />)}</View>
    <MailTypeStats stats={mailTypeStats} />
    <View style={styles.sectionHeader}>
      <Text style={styles.sectionTitle}>最新邮件</Text>
      <Text style={styles.emailCount}>({emails.length} 封邮件)</Text>
    </View>
  </View>
);

<FlatList
  data={emails}
  keyExtractor={item => item.id}
  renderItem={({ item }) => (
    <EmailItem
      email={item}
      account={accountMap.get(item.accountId)!}
      onToggleRead={toggleEmailRead}
      onToggleStar={toggleEmailStar}
    />
  )}
  ListHeaderComponent={Header}
  removeClippedSubviews
  initialNumToRender={8}
  windowSize={5}
/>

状态更新与不可变语义

  • 切换已读与星标使用函数式 setState,保证读取的是最新快照,避免并发交互造成“旧值覆盖”。这种不可变更新是 RN 跨端稳定性的基线:只有明确的 setState 才会驱动原生视图树变更。
  • renderItem 中每封邮件都通过 accounts.find(…) 查找账户信息,时间复杂度 O(n)。将账户预构建为 Map(accountId→account)可以把查找降为 O(1),在长列表与低端设备上端到端更稳。
const accountMap = new Map(accounts.map(a => [a.id, a]));

原生桥接与交互

  • Alert 映射为原生弹窗(iOS UIAlertController、Android AlertDialog、鸿蒙 ArkUI 弹窗)。当前用作提示与账户切换,不涉及批量状态更新;真实业务中建议在确认后合并多条更新为一次 setState,降低 Bridge 消息频率并提升三端帧稳定性。
  • 事件闭包只传递最小标识(id),函数引用稳定,列表项不会因为处理器重建而产生额外重绘;配合 useCallback 固定 renderItem 与事件处理器可进一步降低重渲染。

图形渲染路径与性能

  • MailTypeStats 的进度条采用百分比 width 驱动填充,更新会触发布局与测量,属于“布局驱动”的图形。大量或高频更新时,建议改为矢量绘制(react-native-svg/Skia),几何参数一次下发到原生绘制后端,合成层更新更稳定,ArkUI 下帧表现更一致。
  • 邮件卡片中的未读样式通过文本加粗与颜色表达,无需平台特化控件;若需要动效(例如星标闪烁、标记已读的淡入),使用 Animated/Reanimated 并选择 useNativeDriver 的 transform/opacity,让 UI 线程完成插值,避免 JS 定时器与布局重算造成抖动。
import { Animated, useRef, useEffect } from 'react-native';
const Star = ({ active }: { active: boolean }) => {
  const scale = useRef(new Animated.Value(1)).current;
  useEffect(() => {
    Animated.sequence([
      Animated.timing(scale, { toValue: active ? 1.2 : 1, duration: 120, useNativeDriver: true }),
      Animated.timing(scale, { toValue: 1, duration: 100, useNativeDriver: true }),
    ]).start();
  }, [active]);
  return <Animated.Text style={{ transform: [{ scale }] }}>{active ? '⭐' : '☆'}</Animated.Text>;
};

颜色与桥接可用性

  • 账户徽章背景使用 8 位十六进制色(#RRGGBBAA)实现透明度,代码通过 ${account.color}20 追加 alpha。“20”约 12.5% 透明度,RN 支持该格式,ArkUI 侧同样可解析 8 位十六进制。统一使用 8 位十六进制比 rgba 文本在桥面更稳定。
  • 页面图标采用 Emoji 文本,零依赖且跨端可用,但不同平台的字体渲染差异显著。若追求统一视觉与可控颜色,建议统一到 react-native-svg 或图标字库;在鸿蒙端需验证 SVG/字体桥接的完整性与性能,并准备回退到文本图标的策略。

重绘控制与滚动确定性

  • 为邮件行使用 React.memo,把 EmailItem 变为“纯渲染组件”,配合 keyExtractor 稳定 key,避免父状态微变更带来的整列表重绘;鸿蒙 ArkUI 下也能减少视图树重排。
  • 提供 getItemLayout(近似等高行)让三端的滚动定位更确定,避免“滚到位置→再测量→再校正”的跳动;配合 removeClippedSubviews 在长列表中降低内存与合成开销。
const ROW_HEIGHT = 124;
const getItemLayout = (_: Email[], index: number) => ({ length: ROW_HEIGHT, offset: ROW_HEIGHT * index, index });

真实演示案例代码:

// app.tsx
import React, { useState } from 'react';
import { SafeAreaView, View, Text, StyleSheet, TouchableOpacity, ScrollView, Dimensions, Alert, FlatList } from 'react-native';

// 图标库
const ICONS = {
  mail: '✉️',
  inbox: '📥',
  sent: '📤',
  draft: '📝',
  trash: '🗑️',
  star: '⭐',
  account: '👤',
  stats: '📊',
};

const { width } = Dimensions.get('window');

// 邮箱账户类型
type EmailAccount = {
  id: string;
  name: string;
  email: string;
  totalMails: number;
  unreadMails: number;
  color: string;
  isActive: boolean;
};

// 邮件类型
type Email = {
  id: string;
  accountId: string;
  subject: string;
  sender: string;
  content: string;
  timestamp: string;
  isRead: boolean;
  isStarred: boolean;
  hasAttachment: boolean;
};

// 邮件统计类型
type MailStats = {
  type: string;
  count: number;
  percentage: number;
  color: string;
};

// 邮件项组件
const EmailItem = ({ 
  email, 
  account,
  onToggleRead,
  onToggleStar
}: { 
  email: Email; 
  account: EmailAccount;
  onToggleRead: (id: string) => void;
  onToggleStar: (id: string) => void;
}) => {
  return (
    <View style={styles.emailItem}>
      <View style={styles.emailHeader}>
        <View style={[styles.accountBadge, { backgroundColor: `${account.color}20` }]}>
          <Text style={[styles.accountBadgeText, { color: account.color }]}>{account.email.charAt(0)}</Text>
        </View>
        <View style={styles.emailInfo}>
          <Text style={[styles.sender, !email.isRead && styles.unreadText]}>{email.sender}</Text>
          <Text style={[styles.subject, !email.isRead && styles.unreadText]} numberOfLines={1}>{email.subject}</Text>
        </View>
        <View style={styles.emailActions}>
          <TouchableOpacity onPress={() => onToggleStar(email.id)}>
            <Text style={styles.starIcon}>{email.isStarred ? ICONS.star : '☆'}</Text>
          </TouchableOpacity>
          <Text style={styles.time}>{email.timestamp}</Text>
        </View>
      </View>
      <Text style={[styles.preview, !email.isRead && styles.unreadText]} numberOfLines={2}>{email.content}</Text>
      <View style={styles.emailFooter}>
        <Text style={styles.accountName}>{account.name}</Text>
        {email.hasAttachment && <Text style={styles.attachment}>📎</Text>}
      </View>
    </View>
  );
};

// 账户卡片组件
const AccountCard = ({ 
  account, 
  onPress 
}: { 
  account: EmailAccount; 
  onPress: () => void 
}) => {
  return (
    <TouchableOpacity style={styles.accountCard} onPress={onPress}>
      <View style={[styles.accountIcon, { backgroundColor: account.color }]}>
        <Text style={styles.accountIconText}>{ICONS.account}</Text>
      </View>
      <View style={styles.accountDetails}>
        <Text style={styles.accountName}>{account.name}</Text>
        <Text style={styles.accountEmail}>{account.email}</Text>
      </View>
      <View style={styles.accountStats}>
        <Text style={styles.totalMails}>{account.totalMails}</Text>
        <Text style={styles.unreadMails}>{account.unreadMails} 未读</Text>
      </View>
    </TouchableOpacity>
  );
};

// 统计卡片组件
const StatsCard = ({ 
  title, 
  value, 
  icon,
  color 
}: { 
  title: string; 
  value: string | number; 
  icon: string; 
  color: string 
}) => {
  return (
    <View style={styles.statCard}>
      <View style={[styles.statIcon, { backgroundColor: `${color}20` }]}>
        <Text style={[styles.statIconText, { color }]}>{icon}</Text>
      </View>
      <View style={styles.statInfo}>
        <Text style={styles.statValue}>{value}</Text>
        <Text style={styles.statTitle}>{title}</Text>
      </View>
    </View>
  );
};

// 邮件类型统计组件
const MailTypeStats = ({ stats }: { stats: MailStats[] }) => {
  return (
    <View style={styles.mailTypeStatsContainer}>
      <Text style={styles.mailTypeStatsTitle}>邮件类型分布</Text>
      {stats.map((stat, index) => (
        <View key={index} style={styles.mailTypeStatItem}>
          <Text style={styles.mailTypeLabel}>{stat.type}</Text>
          <View style={styles.mailTypeProgressBar}>
            <View 
              style={[
                styles.mailTypeProgressFill, 
                { 
                  width: `${stat.percentage}%`, 
                  backgroundColor: stat.color 
                }
              ]} 
            />
          </View>
          <Text style={styles.mailTypeCount}>{stat.count}</Text>
        </View>
      ))}
    </View>
  );
};

// 主页面组件
const MultiEmailManagerApp: React.FC = () => {
  const [accounts, setAccounts] = useState<EmailAccount[]>([
    {
      id: '1',
      name: '个人邮箱',
      email: 'personal@example.com',
      totalMails: 124,
      unreadMails: 8,
      color: '#3b82f6',
      isActive: true
    },
    {
      id: '2',
      name: '工作邮箱',
      email: 'work@company.com',
      totalMails: 287,
      unreadMails: 15,
      color: '#10b981',
      isActive: true
    },
    {
      id: '3',
      name: '学校邮箱',
      email: 'student@university.edu',
      totalMails: 56,
      unreadMails: 3,
      color: '#f59e0b',
      isActive: false
    },
    {
      id: '4',
      name: '项目邮箱',
      email: 'project@team.com',
      totalMails: 89,
      unreadMails: 0,
      color: '#8b5cf6',
      isActive: true
    }
  ]);

  const [emails, setEmails] = useState<Email[]>([
    {
      id: '1',
      accountId: '1',
      subject: '周末聚会邀请',
      sender: '朋友小李',
      content: '你好,这周末我们几个老同学想聚聚,你有时间参加吗?地点在市中心的咖啡厅...',
      timestamp: '10:30',
      isRead: false,
      isStarred: true,
      hasAttachment: false
    },
    {
      id: '2',
      accountId: '2',
      subject: '项目进度报告',
      sender: '经理王总',
      content: '请查看本季度的项目进度报告,需要你在周五之前反馈意见。报告附件中包含了详细数据...',
      timestamp: '09:45',
      isRead: true,
      isStarred: false,
      hasAttachment: true
    },
    {
      id: '3',
      accountId: '1',
      subject: '生日祝福',
      sender: '家人',
      content: '祝你生日快乐!希望你度过美好的一天,晚上一起吃饭庆祝吧...',
      timestamp: '08:15',
      isRead: true,
      isStarred: false,
      hasAttachment: false
    },
    {
      id: '4',
      accountId: '3',
      subject: '课程安排通知',
      sender: '教务处',
      content: '下学期的课程表已经发布,请登录学生系统查看并确认选课情况...',
      timestamp: '昨天',
      isRead: false,
      isStarred: false,
      hasAttachment: false
    },
    {
      id: '5',
      accountId: '2',
      subject: '会议邀请',
      sender: '同事小张',
      content: '关于新产品发布的会议定于明天下午2点举行,请准时参加。会议室A...',
      timestamp: '昨天',
      isRead: true,
      isStarred: true,
      hasAttachment: false
    }
  ]);

  const [stats] = useState([
    { title: '总邮件数', value: 556, icon: ICONS.mail, color: '#3b82f6' },
    { title: '未读邮件', value: 26, icon: ICONS.inbox, color: '#f59e0b' },
    { title: '已读邮件', value: 530, icon: ICONS.sent, color: '#10b981' },
    { title: '星标邮件', value: 12, icon: ICONS.star, color: '#fbbf24' },
  ]);

  const [mailTypeStats] = useState<MailStats[]>([
    { type: '收件箱', count: 342, percentage: 61.5, color: '#3b82f6' },
    { type: '已发送', count: 156, percentage: 28.0, color: '#10b981' },
    { type: '草稿', count: 32, percentage: 5.8, color: '#f59e0b' },
    { type: '垃圾邮件', count: 26, percentage: 4.7, color: '#ef4444' },
  ]);

  const toggleEmailRead = (id: string) => {
    setEmails(prev => 
      prev.map(email => 
        email.id === id ? { ...email, isRead: !email.isRead } : email
      )
    );
  };

  const toggleEmailStar = (id: string) => {
    setEmails(prev => 
      prev.map(email => 
        email.id === id ? { ...email, isStarred: !email.isStarred } : email
      )
    );
  };

  const selectAccount = (accountId: string) => {
    Alert.alert('切换账户', `切换到 ${accounts.find(acc => acc.id === accountId)?.email} 邮箱`);
  };

  const refreshMails = () => {
    Alert.alert('刷新', '正在同步所有邮箱账户的邮件...');
  };

  return (
    <SafeAreaView style={styles.container}>
      {/* 头部 */}
      <View style={styles.header}>
        <Text style={styles.title}>多邮箱管理</Text>
        <TouchableOpacity style={styles.refreshButton} onPress={refreshMails}>
          <Text style={styles.refreshText}>{ICONS.stats} 同步</Text>
        </TouchableOpacity>
      </View>

      {/* 统计卡片 */}
      <ScrollView style={styles.content}>
        <View style={styles.statsContainer}>
          {stats.map((stat, index) => (
            <StatsCard
              key={index}
              title={stat.title}
              value={stat.value}
              icon={stat.icon}
              color={stat.color}
            />
          ))}
        </View>

        {/* 邮箱账户列表 */}
        <Text style={styles.sectionTitle}>邮箱账户</Text>
        <View style={styles.accountsContainer}>
          {accounts.map(account => (
            <AccountCard
              key={account.id}
              account={account}
              onPress={() => selectAccount(account.id)}
            />
          ))}
        </View>

        {/* 邮件类型统计 */}
        <MailTypeStats stats={mailTypeStats} />

        {/* 最新邮件列表标题 */}
        <View style={styles.sectionHeader}>
          <Text style={styles.sectionTitle}>最新邮件</Text>
          <Text style={styles.emailCount}>({emails.length} 封邮件)</Text>
        </View>

        {/* 邮件列表 */}
        <FlatList
          data={emails}
          keyExtractor={item => item.id}
          renderItem={({ item }) => (
            <EmailItem
              email={item}
              account={accounts.find(acc => acc.id === item.accountId)!}
              onToggleRead={toggleEmailRead}
              onToggleStar={toggleEmailStar}
            />
          )}
          showsVerticalScrollIndicator={false}
        />

        {/* 操作说明 */}
        <View style={styles.instructionCard}>
          <Text style={styles.instructionTitle}>使用说明</Text>
          <Text style={styles.instructionText}>• 点击账户可切换当前操作的邮箱</Text>
          <Text style={styles.instructionText}>• 点击星标可标记重要邮件</Text>
          <Text style={styles.instructionText}>• 左滑邮件可执行删除等操作</Text>
          <Text style={styles.instructionText}>• 右上角同步按钮可刷新所有邮件</Text>
        </View>
      </ScrollView>

      {/* 底部导航 */}
      <View style={styles.bottomNav}>
        <TouchableOpacity style={styles.navItem}>
          <Text style={styles.navIcon}>{ICONS.inbox}</Text>
          <Text style={styles.navText}>收件箱</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.navItem}>
          <Text style={styles.navIcon}>{ICONS.sent}</Text>
          <Text style={styles.navText}>已发送</Text>
        </TouchableOpacity>
        <TouchableOpacity style={[styles.navItem, styles.activeNavItem]}>
          <Text style={styles.navIcon}>{ICONS.stats}</Text>
          <Text style={styles.navText}>统计</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.navItem}>
          <Text style={styles.navIcon}>{ICONS.account}</Text>
          <Text style={styles.navText}>账户</Text>
        </TouchableOpacity>
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f8fafc',
  },
  header: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    padding: 20,
    backgroundColor: '#ffffff',
    borderBottomWidth: 1,
    borderBottomColor: '#e2e8f0',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#1e293b',
  },
  refreshButton: {
    backgroundColor: '#3b82f6',
    paddingHorizontal: 16,
    paddingVertical: 8,
    borderRadius: 20,
  },
  refreshText: {
    color: '#ffffff',
    fontSize: 14,
    fontWeight: '500',
  },
  content: {
    flex: 1,
    padding: 16,
  },
  statsContainer: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    justifyContent: 'space-between',
    marginBottom: 16,
  },
  statCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    width: (width - 48) / 2,
    marginBottom: 12,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
    flexDirection: 'row',
    alignItems: 'center',
  },
  statIcon: {
    width: 36,
    height: 36,
    borderRadius: 18,
    alignItems: 'center',
    justifyContent: 'center',
    marginRight: 12,
  },
  statIconText: {
    fontSize: 18,
  },
  statInfo: {
    flex: 1,
  },
  statValue: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#1e293b',
  },
  statTitle: {
    fontSize: 12,
    color: '#64748b',
    marginTop: 4,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#1e293b',
    marginVertical: 12,
  },
  accountsContainer: {
    marginBottom: 16,
  },
  accountCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 12,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
    flexDirection: 'row',
    alignItems: 'center',
  },
  accountIcon: {
    width: 40,
    height: 40,
    borderRadius: 20,
    alignItems: 'center',
    justifyContent: 'center',
    marginRight: 12,
  },
  accountIconText: {
    fontSize: 18,
    color: '#ffffff',
  },
  accountDetails: {
    flex: 1,
  },
  accountName: {
    fontSize: 14,
    fontWeight: 'bold',
    color: '#1e293b',
  },
  accountEmail: {
    fontSize: 12,
    color: '#64748b',
    marginTop: 2,
  },
  accountStats: {
    alignItems: 'flex-end',
  },
  totalMails: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
  },
  unreadMails: {
    fontSize: 12,
    color: '#f59e0b',
    marginTop: 4,
  },
  mailTypeStatsContainer: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 16,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  mailTypeStatsTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 12,
  },
  mailTypeStatItem: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 10,
  },
  mailTypeLabel: {
    width: 60,
    fontSize: 12,
    color: '#64748b',
  },
  mailTypeProgressBar: {
    flex: 1,
    height: 6,
    backgroundColor: '#e2e8f0',
    borderRadius: 3,
    marginRight: 8,
    overflow: 'hidden',
  },
  mailTypeProgressFill: {
    height: '100%',
    borderRadius: 3,
  },
  mailTypeCount: {
    fontSize: 12,
    color: '#64748b',
    width: 30,
  },
  sectionHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 12,
  },
  emailCount: {
    fontSize: 14,
    color: '#64748b',
  },
  emailItem: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 12,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  emailHeader: {
    flexDirection: 'row',
    alignItems: 'flex-start',
    marginBottom: 8,
  },
  accountBadge: {
    width: 24,
    height: 24,
    borderRadius: 12,
    alignItems: 'center',
    justifyContent: 'center',
    marginRight: 12,
    marginTop: 2,
  },
  accountBadgeText: {
    fontSize: 12,
    fontWeight: 'bold',
  },
  emailInfo: {
    flex: 1,
  },
  sender: {
    fontSize: 14,
    fontWeight: 'bold',
    color: '#1e293b',
  },
  subject: {
    fontSize: 14,
    color: '#334155',
    marginTop: 2,
  },
  emailActions: {
    alignItems: 'flex-end',
  },
  starIcon: {
    fontSize: 18,
    color: '#fbbf24',
    marginBottom: 4,
  },
  time: {
    fontSize: 12,
    color: '#94a3b8',
  },
  preview: {
    fontSize: 13,
    color: '#64748b',
    lineHeight: 18,
    marginBottom: 8,
  },
  emailFooter: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
  },
  accountName: {
    fontSize: 12,
    color: '#94a3b8',
  },
  attachment: {
    fontSize: 14,
  },
  unreadText: {
    fontWeight: 'bold',
    color: '#1e293b',
  },
  instructionCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginTop: 16,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  instructionTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 8,
  },
  instructionText: {
    fontSize: 12,
    color: '#64748b',
    lineHeight: 18,
    marginBottom: 4,
  },
  bottomNav: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    backgroundColor: '#ffffff',
    borderTopWidth: 1,
    borderTopColor: '#e2e8f0',
    paddingVertical: 12,
  },
  navItem: {
    alignItems: 'center',
    flex: 1,
  },
  activeNavItem: {
    paddingBottom: 2,
    borderBottomWidth: 2,
    borderBottomColor: '#3b82f6',
  },
  navIcon: {
    fontSize: 20,
    color: '#94a3b8',
    marginBottom: 4,
  },
  activeNavIcon: {
    color: '#3b82f6',
  },
  navText: {
    fontSize: 12,
    color: '#94a3b8',
  },
  activeNavText: {
    color: '#3b82f6',
    fontWeight: '500',
  },
});

export default MultiEmailManagerApp;

请添加图片描述

请添加图片描述


打包

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

在这里插入图片描述

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

最后运行效果图如下显示:
请添加图片描述

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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐