Thành phần Timeline trong React Native for OpenHarmony

Trong các ứng dụng cần theo dõi tiến độ (đơn hàng, dự án, lịch sử thao tác...), Timeline là thành phần quan trọng để thể hiện chuỗi sự kiện theo thời gian. Bài viết phân tích chi tiết cách triển khai Timeline đa dạng bố cục trong React Native for OpenHarmony.

Cấu trúc dữ liệu

interface EventNode {
  title: string;
  detail?: string;
  timestamp?: string;
  marker?: string;
  statusColor?: string;
}

Mỗi mốc thời gian bao gồm:

  • title: Tiêu đề bắt buộc (Ví dụ: "Đơn hàng được tạo")
  • detail: Mô tả bổ sung (Ví dụ: "Từ kho Hà Nội")
  • timestamp: Thời điểm xảy ra (không bắt buộc)
  • marker: Biểu tượng tùy chọn (Ví dụ: 📦, ✅)
  • statusColor: Màu sắc đại diện trạng thái

Cấu hình Timeline

interface TimelineConfig {
  nodes: EventNode[];
  layoutType?: 'standard' | 'alternating' | 'right-aligned';
  connectorColor?: string;
  customStyle?: ViewStyle;
}

Khác biệt hóa trải nghiệm với 3 kiểu bố cục:

  1. standard: Nội dung bên phải, phù hợp đa số trường hợp
  2. alternating: Lượt sang trái-phải theo thứ tự chẵn-lẻ
  3. right-aligned: Toàn bộ nội dung bên trái

Triển khai chính

const TimelineComponent = ({ 
  nodes, 
  layoutType = 'standard',
  connectorColor = '#e5e7eb',
  customStyle 
}) => {
  return (
    <View style={customStyle}>
      {nodes.map((node, index) => {
        const isFinal = index === nodes.length - 1;
        const markerColor = node.statusColor || '#3b82f6';
        const rightAligned = layoutType === 'right-aligned' || (layoutType === 'alternating' && index % 2);
        
        return (
          <View key={index} style={[baseStyles.container, rightAligned && baseStyles.rightContainer]}>
            {layoutType === 'alternating' && (
              <View style={[baseStyles.sidePanel, rightAligned ? baseStyles.leftPanel : baseStyles.rightPanel]}>
                {!rightAligned && node.timestamp && <Text style={baseStyles.time}>{node.timestamp}</Text>}
                {rightAligned && (
                  <>
                    <Text style={baseStyles.header}>{node.title}</Text>
                    {node.detail && <Text style={baseStyles.subtext}>{node.detail}</Text>}
                  </>
                )}
              </View>
            )}
            <View style={baseStyles.connector}>
              <View style={[baseStyles.marker, { backgroundColor: markerColor }]}>
                {node.marker && <Text style={baseStyles.markerIcon}>{node.marker}</Text>}
              </View>
              {!isFinal && <View style={[baseStyles.line, { backgroundColor: connectorColor }]} />}
            </View>
            <View style={[baseStyles.content, layoutType === 'right-aligned' && baseStyles.rightContent]}>
              {layoutType !== 'alternating' && node.timestamp && <Text style={baseStyles.time}>{node.timestamp}</Text>}
              {(layoutType !== 'alternating' || !rightAligned) && (
                <>
                  <Text style={baseStyles.header}>{node.title}</Text>
                  {node.detail && <Text style={baseStyles.subtext}>{node.detail}</Text>}
                </>
              )}
              {layoutType === 'alternating' && rightAligned && node.timestamp && <Text style={baseStyles.time}>{node.timestamp}</Text>}
            </View>
          </View>
        );
      })}
    </View>
  );
};

Phân tích điểm mấu chốt

  • Điều kiện render: Tính toán rightAligned dựa trên kiểu bố cục và chỉ số phần tử
  • Đối xứng nội dung: Trong chế độ alternating, tiêu đề và thời gian được chia về hai phía
  • Đường kết nối: Sử dụng flex: 1 để đường thẳng tự động điều chỉnh chiều dài
  • Biểu tượng trạng thái: Cho phép cá nhân hóa bằng thuộc tính marker

Thiết kế giao diện

const baseStyles = StyleSheet.create({
  container: { flexDirection: 'row', minHeight: 60 },
  rightContainer: { flexDirection: 'row-reverse' },
  sidePanel: { flex: 1, paddingHorizontal: 16 },
  leftPanel: { alignItems: 'flex-end' },
  rightPanel: { alignItems: 'flex-start' },
  connector: { alignItems: 'center', width: 24 },
  marker: {
    width: 12,
    height: 12,
    borderRadius: 6,
    alignItems: 'center',
    justifyContent: 'center',
  },
  markerIcon: { fontSize: 8, color: '#fff' },
  line: { flex: 1, width: 2, marginVertical: 4 },
  content: { flex: 1, paddingLeft: 16, paddingBottom: 24 },
  rightContent: { paddingLeft: 0, paddingRight: 16, alignItems: 'flex-end' },
  time: { fontSize: 12, color: '#9ca3af', marginBottom: 2 },
  header: { fontSize: 16, fontWeight: '500', color: '#1f2937' },
  subtext: { fontSize: 14, color: '#4b5563', marginTop: 2 },
});

Ứng dụng thực tế

Quản lý đơn hàng

const OrderHistory = ({ trackingData }) => {
  const timelineItems = trackingData.map(item => ({
    title: item.status,
    detail: item.location,
    timestamp: formatDate(item.date),
    statusColor: item.isCurrent ? '#3b82f6' : '#10b981'
  }));
  
  return <TimelineComponent nodes={timelineItems} layoutType="alternating" />;
};

Lịch sử thao tác

const ActivityLog = ({ logs }) => {
  const eventNodes = logs.map(log => ({
    title: `${log.user} - ${log.action}`,
    detail: log.description,
    timestamp: formatTime(log.datetime),
    marker: getActionIcon(log.type)
  }));
  
  return <TimelineComponent nodes={eventNodes} connectorColor="#e5e7eb" />;
};

Lưu ý quan trọng

  • Không hiển thị đường kết nối cuối cùng: Kiểm tra isFinal trước khi render
  • Màu sắc biểu tượng: Sử dụng hệ thống màu nhất quán để thể hiện trạng thái (xanh lá = hoàn thành, cam = chờ xử lý...)
  • Độ cao tối thiểu: Đảm bảo các mục có nội dung ngắn vẫn có kích thước đồng đều
  • Đối xứng trong chế độ alternating: Căn chỉnh alignItems để nội dung sát vào đường kết nối

Thẻ: React Native openharmony timeline UI Component Cross-platform Development

Đăng vào ngày 10 tháng 9 lúc 21:07