基于React Native鸿蒙跨平台通过 ScrollView 实现内容的垂直滚动,适应不同长度的员工列表和打卡记录
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
在移动应用开发领域,跨端技术一直是提升开发效率、降低多端维护成本的核心方向。本文将以一个基于 React Native 开发的弹性工作制打卡应用为例,深入解析其核心实现逻辑,并探讨该应用向鸿蒙(HarmonyOS)生态跨端适配的技术路径与关键要点。
1. 类型定义
该弹性打卡应用采用 React Native 经典的函数式组件+Hooks 架构,完全遵循 React 组件化开发思想。首先通过 TypeScript 类型定义规范了核心业务数据结构:
// 员工类型定义,约束弹性工时核心属性
type Employee = {
id: string;
name: string;
department: string;
position: string;
flexibleHours: boolean; // 是否启用弹性工时
coreHoursStart: string; // 核心工作时段开始
coreHoursEnd: string; // 核心工作时段结束
};
// 弹性打卡记录类型,关联员工ID与工时计算
type FlexibleWorkRecord = {
id: string;
employeeId: string;
date: string;
checkInTime: string;
checkOutTime: string | null;
workHours: number; // 自动计算的工作时长
};
这种强类型定义不仅提升了代码的可维护性,也为后续跨端适配时的数据结构一致性提供了基础保障。
2. 业务逻辑
应用核心状态通过 useState 管理,涵盖员工列表、打卡记录、选中员工、表单数据、模态框状态等关键数据:
employees:存储员工基础信息,初始化两条测试数据,标记是否启用弹性工时及核心时段flexibleWorkRecords:存储所有打卡记录,支持签到、签退、工时计算等操作selectedEmployee:跟踪当前选中的打卡员工,作为后续打卡操作的关联标识newFlexibleRecord:管理打卡表单输入数据,包括日期、签到时间等
核心业务逻辑中,useEffect 实现了一个定时任务(每分钟),模拟生成随机员工的打卡记录,其核心在于工时计算逻辑:
const workHours = (new Date(`1970-01-01T${checkOutTime}`) - new Date(`1970-01-01T${checkInTime}`)) / (1000 * 60 * 60);
通过将时间字符串转换为 Date 对象计算时间差,再转换为小时数,这是 React Native 中处理时间计算的典型方式,该逻辑在跨端时可直接复用,仅需适配不同平台的时间格式化API差异。
3. UI 组件
应用 UI 完全基于 React Native 内置组件构建,遵循移动端原生交互体验:
SafeAreaView:适配刘海屏、全面屏等不同屏幕尺寸,保证内容显示在安全区域ScrollView:处理长列表滚动,适配不同屏幕高度的内容展示TouchableOpacity:实现可点击的员工卡片、打卡按钮等交互元素,提供原生级别的点击反馈TextInput:处理打卡日期和时间的输入,绑定状态实现表单数据双向绑定Modal:实现打卡记录详情的弹窗展示,支持滑动动画和半透明背景
样式方面通过 StyleSheet.create 定义,采用 Flex 布局实现响应式设计,例如:
container: {
flex: 1,
backgroundColor: '#f0f9ff',
},
card: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#f0f9ff',
borderRadius: 12,
padding: 16,
marginBottom: 12,
},
Flex 布局是跨端适配的核心,React Native 的 Flex 布局与鸿蒙的 Flex 布局语法高度一致,这为跨端样式迁移提供了便利。
1. 跨端方案选择
针对该应用的跨端需求,有两种主流技术路径:
(1)基于 ArkTS + React Native 桥接
鸿蒙系统提供了对 React Native 的适配能力,可通过鸿蒙的 React Native 适配层(HarmonyOS React Native)实现应用的直接迁移:
- 核心逻辑:保留原有 React Native 代码,通过鸿蒙提供的 JS 引擎和组件映射层,将 React Native 组件转换为鸿蒙原生组件
- 适配要点:
- 安装鸿蒙 React Native 适配包
@hmscore/react-native-hms - 替换部分原生模块调用,如
Dimensions可替换为鸿蒙的window模块 - 调整样式适配鸿蒙的单位体系(如 px 与 vp 的转换)
- 安装鸿蒙 React Native 适配包
(2)基于跨端框架重构(如 UniApp/Remax)
若需深度适配鸿蒙生态,可基于 UniApp 或 Remax 等支持鸿蒙的跨端框架重构:
- 优势:可同时输出 React Native(iOS/Android)和鸿蒙应用,统一代码基座
- 适配成本:需将 React Native 组件替换为框架内置组件,如
View/Text等基础组件可直接复用,Modal/Alert等交互组件需适配框架API
(1)数据层复用
应用的核心业务逻辑(如工时计算、状态管理、数据处理)完全基于 JavaScript/TypeScript 实现,这部分代码可 100% 复用,无需修改。只需将 React Native 特有的 API 替换为鸿蒙兼容的 API:
// React Native 原代码
const { width, height } = Dimensions.get('window');
// 鸿蒙适配后
import { window } from '@kit.ArkUI';
const width = window.getWindowProperties().windowRect.width;
const height = window.getWindowProperties().windowRect.height;
(2)UI 组件
鸿蒙的 ArkTS 提供了与 React Native 组件对应的原生组件,映射关系如下:
| React Native 组件 | 鸿蒙 ArkTS 组件 | 适配要点 |
|---|---|---|
| View | Column/Row | Flex 布局语法一致,直接迁移样式 |
| Text | Text | 文本样式属性基本一致 |
| TouchableOpacity | Button/Text | 需手动实现点击反馈效果 |
| ScrollView | List | 鸿蒙 List 组件性能更优,需调整数据源绑定方式 |
| Modal | Dialog | 鸿蒙 Dialog 组件需通过控制器管理显隐 |
以员工卡片为例,React Native 代码迁移到鸿蒙的实现:
// React Native 原代码
<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>
</View>
</TouchableOpacity>
// 鸿蒙 ArkTS 适配后
@Builder
EmployeeCard(employee: Employee) {
Column() {
Row() {
Text('👤')
.fontSize(28)
.marginRight(12)
Column() {
Text(employee.name)
.fontSize(16)
.fontWeight(FontWeight.Medium)
Text(`部门: ${employee.department}`)
.fontSize(14)
}
}
.backgroundColor('#f0f9ff')
.borderRadius(12)
.padding(16)
.marginBottom(12)
.borderWidth(this.selectedEmployee === employee.id ? 2 : 0)
.borderColor('#0284c7')
.onClick(() => this.handleSelectEmployee(employee.id))
}
}
(3)交互逻辑
鸿蒙的事件处理机制与 React Native 略有差异,但核心逻辑可复用:
- React Native 的
onPress对应鸿蒙的onClick - React Native 的
Alert.alert对应鸿蒙的prompt.showToast或dialog.show - React Native 的状态更新(
setState)对应鸿蒙的@State装饰器
例如,签到按钮的点击逻辑迁移:
// React Native 原代码
const handleCheckIn = () => {
if (newFlexibleRecord.date && newFlexibleRecord.checkInTime && selectedEmployee) {
// 签到逻辑
Alert.alert('签到成功', '新的弹性打卡记录已添加');
} else {
Alert.alert('提示', '请选择员工并填写完整的签到信息');
}
};
// 鸿蒙适配后
handleCheckIn() {
if (this.newFlexibleRecord.date && this.newFlexibleRecord.checkInTime && this.selectedEmployee) {
// 复用相同的签到逻辑
prompt.showToast({
message: '签到成功,新的弹性打卡记录已添加',
duration: 2000
});
} else {
prompt.showToast({
message: '请选择员工并填写完整的签到信息',
duration: 2000
});
}
}
3. 原生能力集成
鸿蒙跨端适配后,可利用鸿蒙的原生能力提升应用性能:
- 替换
ScrollView为鸿蒙的LazyForEach实现列表懒加载,提升长列表性能 - 集成鸿蒙的生物识别能力(如指纹、人脸),增强打卡安全性
- 利用鸿蒙的分布式数据管理能力,实现多设备间的打卡数据同步
- 适配鸿蒙的深色模式、系统字体大小等系统级设置,提升用户体验
该弹性工作制打卡应用的跨端适配,核心在于逻辑层复用、UI层适配、交互层迁移:
- 业务逻辑(如工时计算、数据处理)完全基于 TypeScript 实现,可直接复用
- UI 组件需根据鸿蒙的组件体系进行映射替换,利用 Flex 布局减少样式调整成本
- 交互逻辑需适配鸿蒙的事件处理和弹窗系统,保持用户体验一致性
- 原生模块调用(如设备信息、时间处理)需替换为鸿蒙的对应 API
在实际项目中,建议采用渐进式适配策略:先通过 React Native 鸿蒙适配层实现快速迁移,验证核心功能;再逐步替换为鸿蒙原生组件,利用鸿蒙的原生能力提升应用性能和用户体验。这种方式既能保证跨端效率,又能充分利用鸿蒙生态的优势,是 React Native 应用向鸿蒙跨端的最优路径。
- 该 React Native 弹性打卡应用核心逻辑基于 TypeScript 实现,采用 Hooks 管理状态,Flex 布局实现响应式 UI,具备良好的跨端复用基础。
- React Native 向鸿蒙跨端可选择桥接适配或跨端框架重构两种路径,核心业务逻辑可 100% 复用,仅需适配 UI 组件和原生 API。
- 跨端适配的关键在于保持数据结构和业务逻辑的一致性,针对不同平台的特性调整 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;
flexibleHours: boolean;
coreHoursStart: string;
coreHoursEnd: string;
};
// 弹性打卡记录类型
type FlexibleWorkRecord = {
id: string;
employeeId: string;
date: string;
checkInTime: string;
checkOutTime: string | null;
workHours: number;
};
这种强类型设计不仅提高了代码可读性,也为鸿蒙跨端适配提供了清晰的数据契约,确保不同平台间数据传递的一致性。数据模型的设计充分考虑了弹性工作制的特点,包含了核心工作时间、弹性工作时间等关键配置,为企业管理弹性工作制提供了全面的数据支持。
状态管理
应用使用 useState Hook 管理多个复杂状态,包括员工列表、弹性打卡记录、选中状态等:
const [employees] = useState<Employee[]>([
{
id: '1',
name: '李先生',
department: '技术部',
position: '前端工程师',
flexibleHours: true,
coreHoursStart: '10:00',
coreHoursEnd: '16:00'
},
// 其他员工...
]);
// 其他状态定义...
特别值得注意的是,应用通过 useEffect 实现了弹性工时的自动计算机制:
// 自动计算弹性工时
useEffect(() => {
const interval = setInterval(() => {
const randomEmployee = employees[Math.floor(Math.random() * employees.length)];
const checkInTime = '09:00';
const checkOutTime = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
const workHours = (new Date(`1970-01-01T${checkOutTime}`) - new Date(`1970-01-01T${checkInTime}`)) / (1000 * 60 * 60);
const newRecord: FlexibleWorkRecord = {
id: (flexibleWorkRecords.length + 1).toString(),
employeeId: randomEmployee.id,
date: new Date().toISOString().split('T')[0],
checkInTime,
checkOutTime,
workHours
};
setFlexibleWorkRecords([...flexibleWorkRecords, newRecord]);
}, 60000);
return () => clearInterval(interval);
}, [employees, flexibleWorkRecords]);
这种基于时间间隔的自动计算机制,模拟了真实场景中弹性工作制下的考勤管理过程,为企业提供了自动化的技术支持。同时,通过 useEffect 的清理函数,确保了定时器在组件卸载时被正确清除,避免了内存泄漏。
在 React Native 鸿蒙跨端开发中,该应用体现了以下关键技术点:
- 组件兼容性:使用 React Native 核心组件(如 SafeAreaView、View、Text、TouchableOpacity、ScrollView、Modal 等),确保在鸿蒙系统上的兼容性
- 资源管理:通过 Base64 编码的图标资源,避免了不同平台资源格式的差异,提高了跨端部署的一致性
- 尺寸适配:使用 Dimensions API 获取屏幕尺寸,实现响应式布局,适应不同设备屏幕
- 状态管理:采用 React Hooks 进行状态管理,保持跨平台代码一致性
- 类型安全:TypeScript 类型定义确保了数据结构在不同平台间的一致性
- API 调用:使用 React Native 统一的 API 调用方式,如 Alert 组件,确保在鸿蒙平台上的正确显示
- 中文命名支持:代码中使用了中文变量名和类型名,展示了 React Native 对中文命名的良好支持,这在鸿蒙等中文生态系统中尤为重要
弹性工作制管理
应用通过 Employee 类型中的 flexibleHours、coreHoursStart 和 coreHoursEnd 字段实现了弹性工作制的配置,为不同员工设置不同的核心工作时间,满足企业对弹性工作制的多样化需求。
工时自动计算
应用实现了工时的自动计算功能,通过计算签到和签退时间的差值,自动得出工作时长:
const workHours = (new Date(`1970-01-01T${checkOutTime}`) - new Date(`1970-01-01T${record.checkInTime}`)) / (1000 * 60 * 60);
打卡管理
应用实现了完整的弹性打卡流程,包括签到和签退功能:
// 签到功能
const handleCheckIn = () => {
if (newFlexibleRecord.date && newFlexibleRecord.checkInTime && selectedEmployee) {
const newRecord: FlexibleWorkRecord = {
id: (flexibleWorkRecords.length + 1).toString(),
employeeId: selectedEmployee,
date: newFlexibleRecord.date,
checkInTime: newFlexibleRecord.checkInTime,
checkOutTime: null,
workHours: 0
};
setFlexibleWorkRecords([...flexibleWorkRecords, newRecord]);
setNewFlexibleRecord({ date: '', checkInTime: '', checkOutTime: '' });
Alert.alert('签到成功', '新的弹性打卡记录已添加');
} else {
Alert.alert('提示', '请选择员工并填写完整的签到信息');
}
};
// 签退功能
const handleCheckOut = (recordId: string) => {
const updatedRecords = flexibleWorkRecords.map(record => {
if (record.id === recordId) {
const checkOutTime = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
const workHours = (new Date(`1970-01-01T${checkOutTime}`) - new Date(`1970-01-01T${record.checkInTime}`)) / (1000 * 60 * 60);
return { ...record, checkOutTime, workHours };
}
return record;
});
setFlexibleWorkRecords(updatedRecords);
Alert.alert('签退成功', '弹性打卡记录已更新');
};
记录查看
应用提供了弹性打卡记录的查看功能,通过模态框展示详细信息:
const handleViewRecord = (recordId: string) => {
const record = flexibleWorkRecords.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.workHours}小时`);
setIsModalVisible(true);
}
};
应用的 UI 设计遵循了现代移动应用的设计原则,使用了以下组件和交互模式:
- 安全区域:通过 SafeAreaView 确保内容显示在安全区域内,适应不同设备的屏幕刘海和底部指示条
- 滚动视图:通过 ScrollView 实现内容的垂直滚动,适应不同长度的员工列表和打卡记录
- 卡片布局:使用 TouchableOpacity 和 View 组合实现卡片式列表项,提供清晰的视觉层次和交互反馈
- 表单输入:通过 TextInput 组件实现打卡信息的输入
- 模态框:通过 Modal 组件展示详细信息,如打卡记录详情
- 交互反馈:使用 Alert 组件提供操作反馈和提示信息
- 响应式设计:根据屏幕尺寸动态调整布局,确保在不同设备上的良好显示效果
- 跨端架构:基于 React Native 构建,实现了一次编码多平台运行的目标,特别关注了鸿蒙平台的适配
- 类型安全:全面使用 TypeScript 类型定义,提高代码质量和可维护性,确保考勤数据的准确性
- 弹性工作制支持:通过核心工作时间配置,实现了对弹性工作制的有效管理
- 自动化工时计算:通过定时任务自动计算工时,提高了考勤管理的效率
- 智能状态管理:通过 React Hooks 实现了简洁的状态管理,提高了代码的可读性和可维护性
- 模块化设计:通过清晰的类型定义和函数划分,实现了代码的模块化,提高了可维护性
- 实时数据反馈:通过即时的 Alert 反馈,增强用户操作体验
- 数据结构设计:通过关联的数据结构,如弹性打卡记录关联员工,实现了复杂考勤数据的有效组织
- 中文命名支持:代码中使用了中文变量名和类型名,展示了 React Native 对中文命名的良好支持,这在鸿蒙等中文生态系统中尤为重要
- 灵活性:支持不同员工的个性化核心工作时间配置,满足企业多样化的弹性工作制需求
–
在实际应用中,还可以考虑以下性能优化策略:
- 状态管理优化:对于大型应用,可以考虑使用 Redux 或 Context API 进行全局状态管理,提高状态更新的效率
- 组件拆分:将大型组件拆分为更小的可复用组件,提高渲染性能和代码可维护性
- 数据缓存:对员工数据和弹性打卡记录进行本地缓存,减少重复计算和网络请求
- 动画性能:使用 React Native 的 Animated API 实现流畅的过渡动画,提升用户体验
- 内存管理:确保及时清理不再使用的状态和事件监听器,避免内存泄漏
- 网络优化:对于实际应用中的远程数据同步,实现合理的网络请求策略,如批量上传、增量同步等
- 计算优化:对于工时计算等操作,可以考虑使用 memoization 技术缓存计算结果
- 列表优化:对于长列表,使用 FlatList 组件替代 ScrollView,提高渲染性能
在开发过程中,可能面临的技术挑战及解决方案:
- 鸿蒙平台适配:通过使用 React Native 核心组件和统一的 API 调用方式,确保应用在鸿蒙系统上的兼容性
- 实时数据同步:在实际应用中,可以实现与后端服务器的实时数据同步,确保弹性打卡数据的一致性
- 弹性工时规则复杂化:可以实现更复杂的弹性工时规则,如不同部门、不同岗位的差异化弹性工作时间
- 数据安全:实现弹性打卡数据的加密存储和传输,保护企业数据安全
- 离线功能:实现基本的离线操作能力,确保在网络不稳定情况下的正常使用
- 性能优化:针对不同设备性能差异,实现自适应的性能优化策略,确保在中低端设备上的流畅运行
- 用户体验一致性:确保在不同平台上的用户体验一致,特别是交互方式和视觉效果
- 多语言支持:实现多语言支持,满足不同地区企业的需求
通过对这个弹性工作制打卡管理应用的技术解读,我们可以看到 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 = {
弹性: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
工时: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
用户: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
时间: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
};
const { width, height } = Dimensions.get('window');
// 员工类型
type Employee = {
id: string;
name: string;
department: string;
position: string;
flexibleHours: boolean;
coreHoursStart: string;
coreHoursEnd: string;
};
// 弹性打卡记录类型
type FlexibleWorkRecord = {
id: string;
employeeId: string;
date: string;
checkInTime: string;
checkOutTime: string | null;
workHours: number;
};
// 弹性工作制打卡管理应用组件
const FlexibleWorkAttendanceApp: React.FC = () => {
const [employees] = useState<Employee[]>([
{
id: '1',
name: '李先生',
department: '技术部',
position: '前端工程师',
flexibleHours: true,
coreHoursStart: '10:00',
coreHoursEnd: '16:00'
},
{
id: '2',
name: '王女士',
department: '市场部',
position: '市场专员',
flexibleHours: true,
coreHoursStart: '09:30',
coreHoursEnd: '17:30'
}
]);
const [flexibleWorkRecords, setFlexibleWorkRecords] = useState<FlexibleWorkRecord[]>([
{
id: '1',
employeeId: '1',
date: '2023-12-01',
checkInTime: '09:00',
checkOutTime: '18:00',
workHours: 8
}
]);
const [selectedEmployee, setSelectedEmployee] = useState<string | null>(null);
const [newFlexibleRecord, setNewFlexibleRecord] = useState({
date: '',
checkInTime: '',
checkOutTime: ''
});
const [isModalVisible, setIsModalVisible] = useState(false);
const [modalContent, setModalContent] = useState('');
// 自动计算弹性工时
useEffect(() => {
const interval = setInterval(() => {
const randomEmployee = employees[Math.floor(Math.random() * employees.length)];
const checkInTime = '09:00';
const checkOutTime = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
const workHours = (new Date(`1970-01-01T${checkOutTime}`) - new Date(`1970-01-01T${checkInTime}`)) / (1000 * 60 * 60);
const newRecord: FlexibleWorkRecord = {
id: (flexibleWorkRecords.length + 1).toString(),
employeeId: randomEmployee.id,
date: new Date().toISOString().split('T')[0],
checkInTime,
checkOutTime,
workHours
};
setFlexibleWorkRecords([...flexibleWorkRecords, newRecord]);
}, 60000);
return () => clearInterval(interval);
}, [employees, flexibleWorkRecords]);
const handleSelectEmployee = (employeeId: string) => {
setSelectedEmployee(employeeId);
Alert.alert('选择员工', '您已选择该员工进行弹性打卡');
};
const handleCheckIn = () => {
if (newFlexibleRecord.date && newFlexibleRecord.checkInTime && selectedEmployee) {
const newRecord: FlexibleWorkRecord = {
id: (flexibleWorkRecords.length + 1).toString(),
employeeId: selectedEmployee,
date: newFlexibleRecord.date,
checkInTime: newFlexibleRecord.checkInTime,
checkOutTime: null,
workHours: 0
};
setFlexibleWorkRecords([...flexibleWorkRecords, newRecord]);
setNewFlexibleRecord({ date: '', checkInTime: '', checkOutTime: '' });
Alert.alert('签到成功', '新的弹性打卡记录已添加');
} else {
Alert.alert('提示', '请选择员工并填写完整的签到信息');
}
};
const handleCheckOut = (recordId: string) => {
const updatedRecords = flexibleWorkRecords.map(record => {
if (record.id === recordId) {
const checkOutTime = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
const workHours = (new Date(`1970-01-01T${checkOutTime}`) - new Date(`1970-01-01T${record.checkInTime}`)) / (1000 * 60 * 60);
return { ...record, checkOutTime, workHours };
}
return record;
});
setFlexibleWorkRecords(updatedRecords);
Alert.alert('签退成功', '弹性打卡记录已更新');
};
const handleViewRecord = (recordId: string) => {
const record = flexibleWorkRecords.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.workHours}小时`);
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>
<Text style={styles.cardDescription}>弹性工时: {employee.flexibleHours ? '是' : '否'}</Text>
<Text style={styles.cardDescription}>核心时段: {employee.coreHoursStart} - {employee.coreHoursEnd}</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={newFlexibleRecord.date}
onChangeText={(text) => setNewFlexibleRecord({ ...newFlexibleRecord, date: text })}
/>
<TextInput
style={styles.input}
placeholder="签到时间 (HH:MM)"
value={newFlexibleRecord.checkInTime}
onChangeText={(text) => setNewFlexibleRecord({ ...newFlexibleRecord, checkInTime: text })}
/>
</View>
<TouchableOpacity
style={styles.addButton}
onPress={handleCheckIn}
>
<Text style={styles.addText}>弹性签到</Text>
</TouchableOpacity>
</View>
{/* 弹性打卡记录 */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>弹性打卡记录</Text>
{flexibleWorkRecords.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.checkOutTime || '未签退'}</Text>
<Text style={styles.cardDescription}>工作时长: {record.workHours}小时</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 FlexibleWorkAttendanceApp;

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

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

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

摘要
本文以React Native开发的弹性工作制打卡应用为例,探讨了向鸿蒙生态跨端适配的技术路径。文章首先介绍了应用的TypeScript类型定义和核心业务逻辑,包括员工数据管理和工时计算功能。在UI组件方面,应用采用React Native原生组件构建,使用Flex布局实现响应式设计。针对跨端需求,提出了两种适配方案:基于ArkTS的React Native桥接和跨端框架重构,并详细对比了组件映射关系和交互逻辑差异。文章重点分析了数据层复用、UI组件转换和交互逻辑适配等关键环节,为开发者提供了实用的跨端迁移指南。
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
更多推荐




所有评论(0)