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


在分布式办公成为企业常态的背景下,远程打卡系统成为考勤管理的核心基础设施,其对跨端数据一致性、工作状态闭环管理、多终端操作适配有着极高要求。鸿蒙系统的分布式全场景能力为远程办公应用的多终端部署提供了底层支撑,而React Native凭借“一次开发、多端运行”的技术特性,成为这类远程打卡应用跨端开发的最优技术底座。本文将从远程打卡状态管理、工作状态联动更新、跨端时间格式化到鸿蒙生态融合的底层实现,全方位拆解这款远程打卡应用的技术架构,剖析React Native与鸿蒙生态深度融合的关键技术要点。

操作结果即时反馈

这款远程打卡应用的核心架构遵循React Native“通用抽象层+平台适配层”的设计原则,所有核心功能均基于React Native通用API(ViewTextInputModalTouchableOpacity)和Hooks体系构建,未引入任何平台专属代码,这是实现鸿蒙跨端兼容的核心前提。

从底层适配逻辑来看,React Native for HarmonyOS框架会将React Native通用组件无缝映射为鸿蒙ArkUI原生组件:TouchableOpacity对应鸿蒙Button原生组件,保留点击反馈的同时适配鸿蒙交互规范,尤其适合员工选择、签到/签退操作这类需要明确操作反馈的远程办公场景;TextInput映射为鸿蒙TextInput原生控件,支持工作状态这类自定义文本的流畅录入,保证远程打卡日期、签到时间、工作状态等关键数据在鸿蒙设备上的输入准确性,避免因平台输入组件差异导致的考勤数据录入错误;Modal组件转换为鸿蒙Dialog原生模态框,适配鸿蒙系统弹窗交互逻辑,用于展示包含签到签退状态、工作状态的完整远程打卡记录详情,保证办公数据展示的完整性;Alert则调用鸿蒙系统级弹窗能力,在签到/签退操作完成时推送提示,确保操作结果即时反馈,符合企业远程考勤“操作可追溯、状态可确认”的核心诉求。

此外,应用通过Dimensions.get('window')获取设备屏幕尺寸,该API在鸿蒙系统中会被React Native框架适配为鸿蒙getWindowSize原生能力,能够精准获取不同形态鸿蒙设备(手机、平板、智慧屏、便携终端)的屏幕参数——例如在鸿蒙平板上展示远程打卡统计报表和多员工工作状态监控界面,在手机上呈现精简的远程签到录入界面,在智慧屏上适配企业管理层的远程办公状态大屏展示需求,完美契合企业远程办公多终端协作的业务场景。

员工基础档案到远程打卡记录

远程打卡的核心是签到签退状态闭环与工作状态的联动管理,代码中通过TypeScript构建了多层级强类型数据模型,从员工基础档案到远程打卡记录(含空值安全处理的签退时间字段、工作状态字段),形成闭环的企业远程考勤数据体系,既规避前端开发中的类型错误,又在跨端编译阶段拦截数据格式偏差,适配鸿蒙ArkTS的静态类型特性。

// 员工档案模型:聚焦核心身份信息,适配远程打卡的人员识别需求
type Employee = {
  id: string;
  name: string;
  department: string;         // 字符串型部门,适配技术部/市场部等企业组织架构
  position: string;            // 字符串型职位,兼容前端工程师/市场专员等岗位描述
};

// 远程打卡记录核心模型:整合签到签退状态与工作状态,引入空值安全设计
type RemoteWorkRecord = {
  id: string;
  employeeId: string;
  date: string;                // 字符串型日期,兼容跨端日期格式(YYYY-MM-DD)
  checkInTime: string;         // 字符串型签到时间,适配HH:MM格式,非空约束保证打卡基础记录
  checkOutTime: string | null; // 联合类型签退时间,null值适配未签退状态,符合远程打卡业务逻辑
  workStatus: string;          // 字符串型工作状态,适配"工作中"/"已完成"等自定义状态描述
};

这些类型定义严格约束了企业远程打卡相关数据的格式和类型,在鸿蒙系统中,React Native的TypeScript编译器会对JS层与鸿蒙ArkTS层之间的数据交互进行严格校验。例如,checkOutTime采用string | null联合类型,既满足远程办公场景中“未签退”的空值状态表达,又保证跨端数据解析的一致性,避免因鸿蒙ArkTS静态类型特性与JavaScript动态类型特性不兼容导致的状态判定错误;workStatus作为字符串类型,支持企业自定义的工作状态描述(如“居家办公”“异地办公”“项目攻坚中”),在跨端传递时保留完整的文本信息,符合企业远程管理“状态精细化、描述个性化”的核心要求。

React Hooks驱动的跨端远程打卡流程

应用的核心业务逻辑(自动远程记录生成、手动签到、签退状态与工作状态联动更新)均基于React Hooks(useStateuseEffect)实现,这种轻量级状态管理方式完美适配React Native的跨端生命周期模型,同时与鸿蒙组件生命周期深度融合,保障了核心远程考勤规则的跨端稳定运行。

更新工作状态

应用通过useState管理核心数据状态,采用“不可变更新”的方式修改状态,避免引用类型数据在跨端环境下的共享冲突。例如,添加新的远程签到记录时,通过解构赋值创建新数组副本:

setRemoteWorkRecords([...remoteWorkRecords, newRecord]);

而在签退操作中,不仅更新签退时间,还联动更新工作状态,同样遵循不可变更新原则:

const updatedRecords = remoteWorkRecords.map(record => 
  record.id === recordId ? { ...record, checkOutTime: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), workStatus: '已完成' } : record
);
setRemoteWorkRecords(updatedRecords);

这种不可变更新策略在鸿蒙系统中尤为关键——鸿蒙分布式数据管理要求数据副本的一致性,不可变更新保证每次状态变更都会生成新的数据源,避免多端数据同步时的冲突问题,确保远程打卡记录在鸿蒙多设备间的同步准确性,符合企业远程考勤“一人一记录、状态可追溯”的核心要求。

自动远程记录生成逻辑:

应用通过useEffect实现每分钟一次的自动远程记录生成,这一核心逻辑在鸿蒙系统中稳定运行的核心在于:setInterval/clearInterval是React Native封装的通用定时器API,已适配鸿蒙任务调度机制;useEffect的返回清理函数对应鸿蒙组件onDestroy生命周期,确保定时器在组件卸载时被销毁,避免鸿蒙设备内存泄漏。

useEffect(() => {
  const interval = setInterval(() => {
    const randomEmployee = employees[Math.floor(Math.random() * employees.length)];
    const newRecord: RemoteWorkRecord = {
      id: (remoteWorkRecords.length + 1).toString(),
      employeeId: randomEmployee.id,
      date: new Date().toISOString().split('T')[0], // 跨端兼容的日期格式化
      checkInTime: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), // 跨端统一的时间格式化
      checkOutTime: null, // 初始化为未签退状态,符合远程打卡业务逻辑
      workStatus: '工作中' // 初始工作状态,适配远程办公场景默认值
    };
    setRemoteWorkRecords([...remoteWorkRecords, newRecord]);
  }, 60000);

  return () => clearInterval(interval); // 适配鸿蒙组件销毁生命周期
}, [employees, remoteWorkRecords]);

自动生成逻辑中,toISOStringtoLocaleTimeString是跨端通用的时间/日期格式化方法,在鸿蒙系统中会解析为标准的YYYY-MM-DDHH:MM格式,保证不同鸿蒙设备上记录生成的时间格式一致性;workStatus初始化为“工作中”,既符合TypeScript类型约束,又适配远程办公“签到即进入工作状态”的业务逻辑,避免跨端数据交互时的状态歧义。

签到签退核心操作逻辑:

handleCheckIn函数是手动远程签到的核心入口,其内部首先校验所有必填字段(日期、签到时间、工作状态、员工)的完整性,保证远程记录数据的合规性;随后构建符合TypeScript类型约束的新记录,通过不可变更新模式添加到状态中,确保鸿蒙分布式数据环境下的同步准确性。

handleCheckOut函数则实现了签退时间与工作状态的联动更新,这是远程打卡场景的核心特性——在更新checkOutTime的同时,将workStatus从“工作中”变更为“已完成”,通过一次不可变更新完成两个关联字段的修改,既保证操作的原子性,又避免因多端并发操作导致的状态不一致,完全适配企业远程办公“状态闭环、数据联动”的业务需求。


应用的UI层基于React Native的StyleSheet统一管理样式,既保证鸿蒙系统中的原生渲染效果,又兼顾远程打卡应用对操作便捷性、状态可视化的特殊要求。

样式系统

StyleSheet将CSS样式抽象为跨平台的样式对象,核心样式属性(flexborderRadiuspaddingelevation)在鸿蒙系统中会被精准转换为ArkUI的布局属性,同时针对远程打卡的操作特性设计了差异化的视觉样式:

const styles = StyleSheet.create({
  section: {
    backgroundColor: '#ffffff',
    marginHorizontal: 16,
    borderRadius: 12,
    padding: 16,
    // 阴影跨端适配:elevation适配鸿蒙/Android,shadow系列适配iOS
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  addButton: {
    backgroundColor: '#0284c7', // 办公场景专属蓝色,适配签到操作按钮
    padding: 12,
    borderRadius: 8,
    alignItems: 'center',
  },
  checkOutButton: {
    backgroundColor: '#10b981', // 绿色系签退按钮,强化操作辨识度
    paddingHorizontal: 12,
    paddingVertical: 6,
    borderRadius: 8,
  },
  input: {
    flex: 1,
    backgroundColor: '#f0f9ff', // 浅蓝主题,适配远程办公视觉规范
    borderRadius: 8,
    paddingHorizontal: 12,
    paddingVertical: 8,
    fontSize: 14,
    color: '#0c4a6e',
  }
});

其中,elevation属性在鸿蒙系统中会被解析为原生阴影层级,borderRadius适配鸿蒙圆角渲染规则,保证UI视觉效果的跨端一致性;签到/签退按钮采用差异化的色彩体系(蓝色/绿色),既符合企业远程办公应用的视觉规范,又适配鸿蒙系统的色彩渲染特性,提升远程操作的辨识度,避免误操作;浅蓝主题(#f0f9ff)的输入框背景色,降低远程办公人员在不同光线环境下的视觉疲劳,适配居家/异地办公的核心场景。

交互组件

核心交互组件TouchableOpacity在鸿蒙系统中会被渲染为具备原生点击反馈的按钮,选中态的selectedCard样式(边框高亮)能够清晰标识当前选择的员工,符合企业考勤应用“操作可追溯、状态可识别”的核心要求;仅对checkOutTimenull的记录展示签退按钮,实现了基于状态的交互逻辑控制,适配远程打卡“先签到、后签退”的业务特性。

Modal组件通过animationType="slide"实现滑动弹窗效果,在鸿蒙系统中适配为原生滑动模态框,用于展示包含远程打卡详情的完整信息(员工信息、签到/签退时间、工作状态),并对未签退状态做友好的文本展示(record.checkOutTime || '未签退'),既保证数据展示的完整性,又符合鸿蒙系统交互规范,提升企业远程数据查看的安全性。


当前代码已实现基础的鸿蒙跨端兼容,在生产环境中,可针对鸿蒙系统特性和远程打卡业务需求进行深度优化,进一步提升应用的企业考勤服务能力:

1. 高性能列表渲染

应用中远程打卡记录列表采用ScrollView + map的方式渲染,在鸿蒙系统中面对大量企业远程办公数据时可能出现卡顿。可替换为React Native的FlatList组件,该组件在鸿蒙系统中会适配ArkUI的List原生组件,实现按需渲染和组件复用,通过getItemLayout优化列表滚动性能,尤其适合展示企业员工月度/年度的远程打卡记录和工作状态统计数据。

2. 鸿蒙原生

远程打卡的核心是状态的实时性和设备的联动性,可通过React Native的Native Module机制封装鸿蒙原生能力:

  • 分布式数据同步:集成鸿蒙DistributedDataManager,实现远程打卡记录在员工鸿蒙手机、企业鸿蒙平板、智慧屏之间的实时同步,确保管理层能即时查看员工远程工作状态;
  • 系统级时间同步:封装鸿蒙TimeKit原生API,替代前端本地时间获取逻辑,确保远程打卡时间与企业服务器时间的一致性,提升考勤数据的精准性;
  • 远程状态推送:利用鸿蒙NotificationKit,向管理员推送员工签到/签退状态变更通知,实现远程办公的精细化管理。

3. 远程工作状态智能化

基于鸿蒙的AI能力和React Native的状态管理,可实现远程工作状态的智能化更新:

  • 通过鸿蒙设备的使用时长、网络状态等数据,自动更新员工的workStatus(如“活跃工作中”“暂时离开”);
  • 结合鸿蒙的分布式任务调度能力,在员工签退超时未操作时,自动推送提醒并更新状态为“异常未签退”,提升远程考勤的自动化水平。

这款基于React Native开发的远程打卡应用,通过强类型数据模型、React Hooks状态管理和通用UI组件设计,构建了具备完整鸿蒙跨端兼容能力的企业考勤应用架构,核心技术要点可总结为:

  • 通用API选型是实现鸿蒙兼容的基础,基于React Native通用组件构建核心逻辑,规避平台专属代码,保证了远程打卡UI和交互的跨端一致性;
  • TypeScript强类型约束引入空值安全设计,工作状态字段支持个性化描述,适配鸿蒙ArkTS的静态类型特性,避免跨端数据交互中的类型错误,保障远程状态判定的准确性;
  • React Hooks状态管理与鸿蒙组件生命周期深度融合,采用不可变更新策略,实现签退时间与工作状态的联动更新,保障了核心远程打卡流程的跨端稳定运行;
  • 统一的StyleSheet样式系统实现了UI在鸿蒙设备上的原生渲染,差异化的视觉设计兼顾了远程打卡应用的操作辨识度和状态可视化需求。

在远程办公日益普及的今天,如何有效管理远程员工的工作状态和考勤成为企业面临的新挑战。本文将深入剖析一个基于 React Native 构建的远程打卡应用,探讨其技术实现细节及鸿蒙跨端能力的应用。

技术选型

该应用采用了现代 React Native 函数式组件架构,通过 TypeScript 类型系统和 React Hooks 实现了一个功能完整的远程考勤管理系统。核心技术栈包括:

  • React Native:作为跨端开发框架,提供了统一的组件 API,确保应用在 iOS、Android 及鸿蒙平台上的一致性体验
  • TypeScript:通过严格的类型定义增强代码可维护性,明确数据结构和组件接口
  • React Hooks:使用 useState 管理应用状态,useEffect 处理副作用逻辑,实现声明式的状态管理
  • Base64 图标:采用 Base64 编码的图标资源,避免了不同平台资源格式的差异,提高了跨端兼容性
  • 响应式布局:使用 Dimensions API 获取屏幕尺寸,实现适配不同设备的响应式界面

数据模型

应用通过 TypeScript 接口定义了两个核心数据类型,构建了完整的远程考勤数据模型体系:

// 员工类型
type Employee = {
  id: string;
  name: string;
  department: string;
  position: string;
};

// 远程打卡记录类型
type RemoteWorkRecord = {
  id: string;
  employeeId: string;
  date: string;
  checkInTime: string;
  checkOutTime: string | null;
  workStatus: string;
};

这种强类型设计不仅提高了代码可读性,也为鸿蒙跨端适配提供了清晰的数据契约,确保不同平台间数据传递的一致性。数据模型的设计充分考虑了远程办公的特点,包含了工作状态等关键信息,为企业管理远程员工提供了全面的数据支持。

状态管理

应用使用 useState Hook 管理多个复杂状态,包括员工列表、远程打卡记录、选中状态等:

const [employees] = useState<Employee[]>([
  {
    id: '1',
    name: '李先生',
    department: '技术部',
    position: '前端工程师'
  },
  {
    id: '2',
    name: '王女士',
    department: '市场部',
    position: '市场专员'
  }
]);

// 其他状态定义...

特别值得注意的是,应用通过 useEffect 实现了远程打卡的自动记录机制:

// 自动记录远程打卡
useEffect(() => {
  const interval = setInterval(() => {
    const randomEmployee = employees[Math.floor(Math.random() * employees.length)];
    const newRecord: RemoteWorkRecord = {
      id: (remoteWorkRecords.length + 1).toString(),
      employeeId: randomEmployee.id,
      date: new Date().toISOString().split('T')[0],
      checkInTime: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
      checkOutTime: null,
      workStatus: '工作中'
    };
    setRemoteWorkRecords([...remoteWorkRecords, newRecord]);
  }, 60000);

  return () => clearInterval(interval);
}, [employees, remoteWorkRecords]);

这种基于时间间隔的自动记录机制,模拟了真实场景中远程员工打卡的过程,为考勤管理提供了自动化的技术支持。同时,通过 useEffect 的清理函数,确保了定时器在组件卸载时被正确清除,避免了内存泄漏。


在 React Native 鸿蒙跨端开发中,该应用体现了以下关键技术点:

  1. 组件兼容性:使用 React Native 核心组件(如 SafeAreaView、View、Text、TouchableOpacity、ScrollView、Modal 等),确保在鸿蒙系统上的兼容性
  2. 资源管理:通过 Base64 编码的图标资源,避免了不同平台资源格式的差异,提高了跨端部署的一致性
  3. 尺寸适配:使用 Dimensions API 获取屏幕尺寸,实现响应式布局,适应不同设备屏幕
  4. 状态管理:采用 React Hooks 进行状态管理,保持跨平台代码一致性
  5. 类型安全:TypeScript 类型定义确保了数据结构在不同平台间的一致性
  6. API 调用:使用 React Native 统一的 API 调用方式,如 Alert 组件,确保在鸿蒙平台上的正确显示

远程打卡管理

应用实现了完整的远程打卡流程,包括签到和签退功能:

// 签到功能
const handleCheckIn = () => {
  if (newRemoteWorkRecord.date && newRemoteWorkRecord.checkInTime && newRemoteWorkRecord.workStatus && selectedEmployee) {
    const newRecord: RemoteWorkRecord = {
      id: (remoteWorkRecords.length + 1).toString(),
      employeeId: selectedEmployee,
      date: newRemoteWorkRecord.date,
      checkInTime: newRemoteWorkRecord.checkInTime,
      checkOutTime: null,
      workStatus: newRemoteWorkRecord.workStatus
    };
    setRemoteWorkRecords([...remoteWorkRecords, newRecord]);
    setNewRemoteWorkRecord({ date: '', checkInTime: '', workStatus: '' });
    Alert.alert('签到成功', '新的远程打卡记录已添加');
  } else {
    Alert.alert('提示', '请选择员工并填写完整的远程打卡信息');
  }
};

// 签退功能
const handleCheckOut = (recordId: string) => {
  const updatedRecords = remoteWorkRecords.map(record =>
    record.id === recordId ? { ...record, checkOutTime: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), workStatus: '已完成' } : record
  );
  setRemoteWorkRecords(updatedRecords);
  Alert.alert('签退成功', '远程打卡记录已更新');
};

工作状态管理

应用通过 workStatus 字段实现了远程员工工作状态的跟踪,从"工作中"到"已完成"的状态流转,为企业提供了清晰的工作状态管理机制。

记录查看功能

应用提供了远程打卡记录的查看功能,通过模态框展示详细信息:

const handleViewRecord = (recordId: string) => {
  const record = remoteWorkRecords.find(r => r.id === recordId);
  if (record) {
    const employee = employees.find(e => e.id === record.employeeId);
    setModalContent(`员工: ${employee?.name}\n部门: ${employee?.department}\n职位: ${employee?.position}\n日期: ${record.date}\n签到时间: ${record.checkInTime}\n签退时间: ${record.checkOutTime || '未签退'}\n工作状态: ${record.workStatus}`);
    setIsModalVisible(true);
  }
};

应用的 UI 设计遵循了现代移动应用的设计原则,使用了以下组件和交互模式:

  • 安全区域:通过 SafeAreaView 确保内容显示在安全区域内,适应不同设备的屏幕刘海和底部指示条

  • 滚动视图:通过 ScrollView 实现内容的垂直滚动,适应不同长度的员工列表和打卡记录

  • 卡片布局:使用 TouchableOpacity 和 View 组合实现卡片式列表项,提供清晰的视觉层次和交互反馈

  • 表单输入:通过 TextInput 组件实现远程打卡信息的输入

  • 模态框:通过 Modal 组件展示详细信息,如打卡记录详情

  • 交互反馈:使用 Alert 组件提供操作反馈和提示信息

  • 响应式设计:根据屏幕尺寸动态调整布局,确保在不同设备上的良好显示效果

  1. 跨端架构:基于 React Native 构建,实现了一次编码多平台运行的目标,特别关注了鸿蒙平台的适配
  2. 类型安全:全面使用 TypeScript 类型定义,提高代码质量和可维护性,确保远程办公数据的准确性
  3. 自动化考勤:通过定时任务自动记录远程打卡,提高了考勤管理的效率
  4. 工作状态跟踪:实现了远程员工工作状态的实时跟踪,从"工作中"到"已完成"的状态流转
  5. 状态管理:通过 React Hooks 实现了简洁的状态管理,提高了代码的可读性和可维护性
  6. 模块化设计:通过清晰的类型定义和函数划分,实现了代码的模块化,提高了可维护性
  7. 实时数据反馈:通过即时的 Alert 反馈,增强用户操作体验
  8. 数据结构设计:通过关联的数据结构,如远程打卡记录关联员工,实现了复杂考勤数据的有效组织
  9. 灵活性:支持手动打卡和自动打卡两种方式,满足不同场景的需求

在实际应用中,还可以考虑以下性能优化策略:

  1. 状态管理优化:对于大型应用,可以考虑使用 Redux 或 Context API 进行全局状态管理,提高状态更新的效率
  2. 组件拆分:将大型组件拆分为更小的可复用组件,提高渲染性能和代码可维护性
  3. 数据缓存:对员工数据和打卡记录进行本地缓存,减少重复计算和网络请求
  4. 动画性能:使用 React Native 的 Animated API 实现流畅的过渡动画,提升用户体验
  5. 内存管理:确保及时清理不再使用的状态和事件监听器,避免内存泄漏
  6. 网络优化:对于实际应用中的远程数据同步,实现合理的网络请求策略,如批量上传、增量同步等
  7. 计算优化:对于考勤数据的统计和分析,可以考虑使用 memoization 技术缓存计算结果
  8. 列表优化:对于长列表,使用 FlatList 组件替代 ScrollView,提高渲染性能

在开发过程中,可能面临的技术挑战及解决方案:

  1. 鸿蒙平台适配:通过使用 React Native 核心组件和统一的 API 调用方式,确保应用在鸿蒙平台上的兼容性
  2. 实时数据同步:在实际应用中,可以实现与后端服务器的实时数据同步,确保远程打卡数据的一致性
  3. 工作状态验证:可以集成更多的工作状态验证机制,如屏幕活动检测、任务完成情况等
  4. 数据安全:实现远程打卡数据的加密存储和传输,保护企业数据安全
  5. 离线功能:实现基本的离线操作能力,确保在网络不稳定情况下的正常使用
  6. 性能优化:针对不同设备性能差异,实现自适应的性能优化策略,确保在中低端设备上的流畅运行
  7. 用户体验一致性:确保在不同平台上的用户体验一致,特别是交互方式和视觉效果
  8. 多语言支持:实现多语言支持,满足不同地区企业的需求

通过对这个远程打卡应用的技术解读,我们可以看到 React Native 在跨端开发中的强大能力。该应用不仅实现了完整的远程考勤管理功能,还展示了如何通过 TypeScript、React Hooks 等现代前端技术构建高质量的跨端应用。


真实演示案例代码:





// App.tsx
import React, { useState, useEffect } from 'react';
import { SafeAreaView, View, Text, StyleSheet, TouchableOpacity, ScrollView, Dimensions, Alert, TextInput, Modal } from 'react-native';

// Base64 图标库
const ICONS_BASE64 = {
  remote: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  clock: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  calendar: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  user: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
};

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

// 员工类型
type Employee = {
  id: string;
  name: string;
  department: string;
  position: string;
};

// 远程打卡记录类型
type RemoteWorkRecord = {
  id: string;
  employeeId: string;
  date: string;
  checkInTime: string;
  checkOutTime: string | null;
  workStatus: string;
};

// 远程打卡应用组件
const RemoteWorkAttendanceApp: React.FC = () => {
  const [employees] = useState<Employee[]>([
    {
      id: '1',
      name: '李先生',
      department: '技术部',
      position: '前端工程师'
    },
    {
      id: '2',
      name: '王女士',
      department: '市场部',
      position: '市场专员'
    }
  ]);

  const [remoteWorkRecords, setRemoteWorkRecords] = useState<RemoteWorkRecord[]>([
    {
      id: '1',
      employeeId: '1',
      date: '2023-12-01',
      checkInTime: '09:00',
      checkOutTime: '18:00',
      workStatus: '工作中'
    }
  ]);

  const [selectedEmployee, setSelectedEmployee] = useState<string | null>(null);
  const [newRemoteWorkRecord, setNewRemoteWorkRecord] = useState({
    date: '',
    checkInTime: '',
    workStatus: ''
  });
  const [isModalVisible, setIsModalVisible] = useState(false);
  const [modalContent, setModalContent] = useState('');

  // 自动记录远程打卡
  useEffect(() => {
    const interval = setInterval(() => {
      const randomEmployee = employees[Math.floor(Math.random() * employees.length)];
      const newRecord: RemoteWorkRecord = {
        id: (remoteWorkRecords.length + 1).toString(),
        employeeId: randomEmployee.id,
        date: new Date().toISOString().split('T')[0],
        checkInTime: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
        checkOutTime: null,
        workStatus: '工作中'
      };
      setRemoteWorkRecords([...remoteWorkRecords, newRecord]);
    }, 60000);

    return () => clearInterval(interval);
  }, [employees, remoteWorkRecords]);

  const handleSelectEmployee = (employeeId: string) => {
    setSelectedEmployee(employeeId);
    Alert.alert('选择员工', '您已选择该员工进行远程打卡');
  };

  const handleCheckIn = () => {
    if (newRemoteWorkRecord.date && newRemoteWorkRecord.checkInTime && newRemoteWorkRecord.workStatus && selectedEmployee) {
      const newRecord: RemoteWorkRecord = {
        id: (remoteWorkRecords.length + 1).toString(),
        employeeId: selectedEmployee,
        date: newRemoteWorkRecord.date,
        checkInTime: newRemoteWorkRecord.checkInTime,
        checkOutTime: null,
        workStatus: newRemoteWorkRecord.workStatus
      };
      setRemoteWorkRecords([...remoteWorkRecords, newRecord]);
      setNewRemoteWorkRecord({ date: '', checkInTime: '', workStatus: '' });
      Alert.alert('签到成功', '新的远程打卡记录已添加');
    } else {
      Alert.alert('提示', '请选择员工并填写完整的远程打卡信息');
    }
  };

  const handleCheckOut = (recordId: string) => {
    const updatedRecords = remoteWorkRecords.map(record => 
      record.id === recordId ? { ...record, checkOutTime: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), workStatus: '已完成' } : record
    );
    setRemoteWorkRecords(updatedRecords);
    Alert.alert('签退成功', '远程打卡记录已更新');
  };

  const handleViewRecord = (recordId: string) => {
    const record = remoteWorkRecords.find(r => r.id === recordId);
    if (record) {
      const employee = employees.find(e => e.id === record.employeeId);
      setModalContent(`员工: ${employee?.name}\n部门: ${employee?.department}\n职位: ${employee?.position}\n日期: ${record.date}\n签到时间: ${record.checkInTime}\n签退时间: ${record.checkOutTime || '未签退'}\n工作状态: ${record.workStatus}`);
      setIsModalVisible(true);
    }
  };

  const openModal = (content: string) => {
    setModalContent(content);
    setIsModalVisible(true);
  };

  const closeModal = () => {
    setIsModalVisible(false);
  };

  return (
    <SafeAreaView style={styles.container}>
      {/* 头部 */}
      <View style={styles.header}>
        <Text style={styles.title}>远程打卡</Text>
        <Text style={styles.subtitle}>支持员工在家或异地办公时进行远程打卡,系统记录工作状态</Text>
      </View>

      <ScrollView style={styles.content}>
        {/* 员工列表 */}
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>员工列表</Text>
          {employees.map(employee => (
            <TouchableOpacity 
              key={employee.id}
              style={[
                styles.card,
                selectedEmployee === employee.id && styles.selectedCard
              ]}
              onPress={() => handleSelectEmployee(employee.id)}
            >
              <Text style={styles.icon}>👤</Text>
              <View style={styles.cardInfo}>
                <Text style={styles.cardTitle}>{employee.name}</Text>
                <Text style={styles.cardDescription}>部门: {employee.department}</Text>
                <Text style={styles.cardDescription}>职位: {employee.position}</Text>
              </View>
            </TouchableOpacity>
          ))}
        </View>

        {/* 远程打卡 */}
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>远程打卡</Text>
          <View style={styles.inputRow}>
            <TextInput
              style={styles.input}
              placeholder="远程打卡日期 (YYYY-MM-DD)"
              value={newRemoteWorkRecord.date}
              onChangeText={(text) => setNewRemoteWorkRecord({ ...newRemoteWorkRecord, date: text })}
            />
            <TextInput
              style={styles.input}
              placeholder="签到时间 (HH:MM)"
              value={newRemoteWorkRecord.checkInTime}
              onChangeText={(text) => setNewRemoteWorkRecord({ ...newRemoteWorkRecord, checkInTime: text })}
            />
          </View>
          <View style={styles.inputRow}>
            <TextInput
              style={styles.input}
              placeholder="工作状态"
              value={newRemoteWorkRecord.workStatus}
              onChangeText={(text) => setNewRemoteWorkRecord({ ...newRemoteWorkRecord, workStatus: text })}
            />
          </View>
          <TouchableOpacity 
            style={styles.addButton}
            onPress={handleCheckIn}
          >
            <Text style={styles.addText}>远程签到</Text>
          </TouchableOpacity>
        </View>

        {/* 远程打卡记录 */}
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>远程打卡记录</Text>
          {remoteWorkRecords.map(record => (
            <TouchableOpacity 
              key={record.id}
              style={styles.recordCard}
              onPress={() => handleViewRecord(record.id)}
            >
              <Text style={styles.icon}>📍</Text>
              <View style={styles.cardInfo}>
                <Text style={styles.cardTitle}>记录ID: {record.id}</Text>
                <Text style={styles.cardDescription}>日期: {record.date}</Text>
                <Text style={styles.cardDescription}>签到时间: {record.checkInTime}</Text>
                <Text style={styles.cardDescription}>工作状态: {record.workStatus}</Text>
                <Text style={styles.cardDescription}>签退时间: {record.checkOutTime || '未签退'}</Text>
              </View>
              {!record.checkOutTime && (
                <TouchableOpacity 
                  style={styles.checkOutButton}
                  onPress={() => handleCheckOut(record.id)}
                >
                  <Text style={styles.checkOutText}>签退</Text>
                </TouchableOpacity>
              )}
            </TouchableOpacity>
          ))}
        </View>

        {/* 使用说明 */}
        <View style={styles.infoCard}>
          <Text style={styles.sectionTitle}>📘 使用说明</Text>
          <Text style={styles.infoText}>• 选择员工进行远程打卡</Text>
          <Text style={styles.infoText}>• 填写远程打卡日期、时间和工作状态</Text>
          <Text style={styles.infoText}>• 下班时记得签退</Text>
          <Text style={styles.infoText}>• 查看历史远程打卡记录</Text>
        </View>

        {/* 弹框内容 */}
        <Modal
          animationType="slide"
          transparent={true}
          visible={isModalVisible}
          onRequestClose={closeModal}
        >
          <View style={styles.modalContainer}>
            <View style={styles.modalContent}>
              <Text style={styles.modalTitle}>详细信息</Text>
              <Text style={styles.modalText}>{modalContent}</Text>
              <TouchableOpacity
                style={styles.closeButton}
                onPress={closeModal}
              >
                <Text style={styles.closeButtonText}>关闭</Text>
              </TouchableOpacity>
            </View>
          </View>
        </Modal>
      </ScrollView>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f0f9ff',
  },
  header: {
    flexDirection: 'column',
    padding: 16,
    backgroundColor: '#ffffff',
    borderBottomWidth: 1,
    borderBottomColor: '#bae6fd',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#0c4a6e',
    marginBottom: 4,
  },
  subtitle: {
    fontSize: 14,
    color: '#0284c7',
  },
  content: {
    flex: 1,
    marginTop: 12,
  },
  section: {
    backgroundColor: '#ffffff',
    marginHorizontal: 16,
    marginBottom: 12,
    borderRadius: 12,
    padding: 16,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  sectionTitle: {
    fontSize: 16,
    fontWeight: '600',
    color: '#0c4a6e',
    marginBottom: 12,
  },
  card: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#f0f9ff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 12,
  },
  selectedCard: {
    borderWidth: 2,
    borderColor: '#0284c7',
  },
  recordCard: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#f0f9ff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 12,
  },
  icon: {
    fontSize: 28,
    marginRight: 12,
  },
  cardInfo: {
    flex: 1,
  },
  cardTitle: {
    fontSize: 16,
    fontWeight: '500',
    color: '#0c4a6e',
    marginBottom: 4,
  },
  cardDescription: {
    fontSize: 14,
    color: '#0284c7',
    marginBottom: 2,
  },
  inputRow: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    marginBottom: 12,
  },
  input: {
    flex: 1,
    backgroundColor: '#f0f9ff',
    borderRadius: 8,
    paddingHorizontal: 12,
    paddingVertical: 8,
    fontSize: 14,
    color: '#0c4a6e',
    marginRight: 8,
  },
  addButton: {
    backgroundColor: '#0284c7',
    padding: 12,
    borderRadius: 8,
    alignItems: 'center',
  },
  addText: {
    color: '#ffffff',
    fontSize: 14,
    fontWeight: '500',
  },
  checkOutButton: {
    backgroundColor: '#10b981',
    paddingHorizontal: 12,
    paddingVertical: 6,
    borderRadius: 8,
  },
  checkOutText: {
    color: '#ffffff',
    fontSize: 12,
    fontWeight: '500',
  },
  infoCard: {
    backgroundColor: '#ffffff',
    marginHorizontal: 16,
    marginBottom: 80,
    borderRadius: 12,
    padding: 16,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  infoText: {
    fontSize: 14,
    color: '#64748b',
    lineHeight: 20,
    marginBottom: 4,
  },
  modalContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'rgba(0, 0, 0, 0.5)',
  },
  modalContent: {
    width: '80%',
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 20,
    elevation: 5,
  },
  modalTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#0c4a6e',
    marginBottom: 12,
    textAlign: 'center',
  },
  modalText: {
    fontSize: 14,
    color: '#0c4a6e',
    lineHeight: 20,
    marginBottom: 20,
  },
  closeButton: {
    backgroundColor: '#0284c7',
    padding: 10,
    borderRadius: 8,
    alignItems: 'center',
  },
  closeButtonText: {
    color: '#ffffff',
    fontSize: 14,
    fontWeight: '500',
  },
});

export default RemoteWorkAttendanceApp;


请添加图片描述


打包

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

在这里插入图片描述

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

在这里插入图片描述

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

请添加图片描述
本文介绍了基于React Native和鸿蒙系统开发的远程打卡应用的技术架构。该应用利用React Native的跨平台特性与鸿蒙的分布式能力,实现多终端适配的远程考勤管理。核心功能包括:1)通过React Native通用组件映射鸿蒙原生组件,确保操作反馈即时性;2)采用TypeScript构建强类型数据模型,保障跨端数据一致性;3)使用React Hooks管理状态,实现签到签退与工作状态的联动更新;4)通过StyleSheet统一管理样式,适配不同鸿蒙设备。该架构完美契合企业远程办公"操作可追溯、状态可视化"的需求,展现了React Native与鸿蒙生态深度融合的技术方案。

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

Logo

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

更多推荐