尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

【共创季稿事节】鸿蒙原生 ArkTS 布局方式之 Column 实现垂直时间轴组件:从 0 到 1 构建 Timeline UI

【共创季稿事节】鸿蒙原生 ArkTS 布局方式之 Column 实现垂直时间轴组件:从 0 到 1 构建 Timeline UI
📅 发布时间:2026/7/4 4:03:50




一、引言
1.1 什么是时间轴(Timeline)
时间轴(Timeline)是一种按时间顺序展示事件的 UI 组件。它在移动应用中无处不在:

订单状态:已下单 → 已支付 → 已发货 → 已签收
项目进度:启动 → 设计 → 开发 → 测试 → 上线
消息历史:聊天记录按时间分组展示
操作日志:系统操作的时间线回顾
时间轴的核心特征是一条垂直的连接线串联起所有节点,每个节点包含一个圆点指示器和一段内容卡片。

1.2 本文要解决的问题
ArkUI 并没有内置的 Timeline 组件。但通过 Column + 自定义组合,我们可以从零构建一个功能完整的时间轴:

不使用任何第三方库,只用 Column、Row、Stack、Text、Divider
这些最基础的组件,组合出一个可交互的时间轴 UI。
1.3 本文核心内容
知识点 说明
Column 纵向骨架 Column 垂直排列所有时间节点
Row 左右分栏 每个节点:左侧(圆点+线)+ 右侧(内容卡片)
自定义连接线 用窄背景 Column 模拟垂直线条
三种状态设计 已完成(绿) / 进行中(蓝) / 待处理(灰)
数据驱动 @State 驱动时间轴状态推进
@Builder 组件化 将圆点、标签、按钮拆分为可复用组件
二、时间轴布局设计
2.1 整体结构
┌─────────────────────────────────┐
│ 垂直时间轴组件 │
│ Column + 自定义连线实现 Timeline │
├─────────────────────────────────┤
│ [🔗 显示连接线] [↻ 重置状态] │ ← 控制栏
├─────────────────────────────────┤
│ │
│ ●── 项目启动 2025-07-01 │ ← 已完成
│ │ │
│ ●── 需求分析 2025-07-03 │ ← 已完成
│ │ │
│ ●── UI 设计 2025-07-05 │ ← 已完成
│ │ │
│ ●── 前端开发 2025-07-08 │ ← 进行中
│ │ │
│ ○── 后端联调 2025-07-15 │ ← 待处理
│ │ │
│ ○── 测试验收 2025-07-20 │ ← 待处理
│ │ │
│ ○── 上线发布 2025-07-25 │ ← 待处理(无连接线)
│ │
├─────────────────────────────────┤
│ ● 已完成 ● 进行中 ○ 待处理 │ ← 图例
└─────────────────────────────────┘
2.2 每个时间节点的内部结构
每个节点 = Row(水平分层)
├── 左侧 Column(30vp 宽)
│ ├── 圆点(Stack 层叠)
│ │ ├── 外圈光晕(背景色半透明)
│ │ └── 实心圆或空心环
│ └── 垂直连接线(2px 宽 Column)
│ └── layoutWeight(1) 撑满剩余高度
│
└── 右侧 Column(layoutWeight 弹性)
├── Row(标题 + 状态标签)
├── Text(描述文字,2行/全部)
└── Row(时间 + 展开/收起按钮)
2.3 三种状态的设计规范
状态 圆点 卡片背景 连接线颜色 语义
已完成 🟢 绿色实心 + 对勾 #1a3a2e 深绿 绿色半透明 已经完成的事件
进行中 🔵 蓝色实心 + 高光 #1a2a4e 深蓝 蓝色半透明 当前正在推进的事件
待处理 ⚪ 灰色空心环 #1a1a2e 深灰 灰色浅色 尚未开始的事件
三、Demo 代码逐层剖析
3.1 项目结构与路由
{
“src”: [“pages/TimelineDemo”]
}
TimelineDemo.ets 共 519 行,完整结构如下:

TimelineDemo.ets (519行)
├── enum TimelineStatus ← COMPLETED / ACTIVE / PENDING
├── interface TimelineItem ← id / title / description / time / status
├── @Component TimelineDemo
│ ├── @State 变量(3个) ← timelineData / showLines / expandedId
│ ├── 颜色常量(3色) ← 绿/蓝/灰
│ ├── aboutToAppear() ← 初始化 7 条数据
│ ├── build()
│ │ ├── 标题区
│ │ ├── 控制栏(2个按钮)
│ │ ├── Scroll + Column ← 时间轴主体
│ │ │ └── ForEach → timelineNode(item, index)
│ │ └── 底部图例(3色)
│ ├── @Builder 方法(6个)
│ │ ├── timelineNode() ← 核心:每个时间节点
│ │ ├── completedDot() ← ✅ 已完成圆点
│ │ ├── activeDot() ← 🔵 进行中圆点
│ │ ├── pendingDot() ← ⚪ 待处理圆点
│ │ ├── statusBadge() ← 状态标签徽章
│ │ ├── configButton() ← 控制按钮
│ │ └── legendDot() ← 图例小点
│ └── 私有方法
│ ├── initTimelineData() ← 数据初始化
│ ├── advanceTimeline() ← 状态推进
│ ├── resetAllToPending() ← 全部重置
│ └── 颜色/文字工具
3.2 数据模型设计
enum TimelineStatus {
COMPLETED, // 已完成
ACTIVE, // 进行中
PENDING // 待处理
}

interface TimelineItem {
id: number;
title: string;
description: string;
time: string;
status: TimelineStatus;
}
7 条示例数据模拟了一个"项目研发"时间轴:

  1. 项目启动 → COMPLETED
  2. 需求分析 → COMPLETED
  3. UI 设计 → COMPLETED
  4. 前端开发 → ACTIVE ← 当前阶段
  5. 后端联调 → PENDING
  6. 测试验收 → PENDING
  7. 上线发布 → PENDING ← 最后一项无连接线
    3.3 时间轴核心布局:Scroll + Column
    Scroll() {
    Column() {
    ForEach(this.timelineData, (item: TimelineItem, index: number) => {
    this.timelineNode(item, index)
    }, (item: TimelineItem) => item.id.toString())
    }
    .width(‘100%’)
    .padding({ left: 16, right: 16, top: 8, bottom: 16 })
    }
    .layoutWeight(1)
    为什么用 Scroll 包裹?—— 当时间轴节点超过屏幕高度时,底部的节点会被截断。Scroll 让用户可以上下滚动查看所有节点。

3.4 核心:每个时间节点的实现
@Builder
timelineNode(item: TimelineItem, index: number) {
Row() {
// ===== 左侧:时间轴指示器 =====
Column() {
// 圆点(根据状态不同)
if (item.status === COMPLETED) {
this.completedDot() // 绿色实心 + 对勾
} else if (item.status === ACTIVE) {
this.activeDot() // 蓝色实心 + 高光
} else {
this.pendingDot() // 灰色空心环
}

// 垂直连接线(最后一项不显示) if (this.showLines && index < this.timelineData.length - 1) { Column() .width(2) .layoutWeight(1) .backgroundColor(this.getLineColor(item.status)) } } .width(30) .alignItems(HorizontalAlign.Center) // ===== 右侧:内容卡片 ===== Column() { // 标题 + 状态标签 Row() { Text(item.title).fontSize(15).fontColor(Color.White) this.statusBadge(item.status) } // 描述(展开/收起控制行数) Text(item.description) .maxLines(this.expandedId === item.id ? 10 : 2) .textOverflow({ overflow: TextOverflow.Ellipsis }) // 时间 + 展开按钮 Row() { Text(item.time) Text(this.expandedId === item.id ? '收起 ▲' : '展开 ▼') } } .layoutWeight(1) .backgroundColor(this.getCardBg(item.status)) .borderRadius(10) .padding(12) .gesture(TapGesture().onAction(() => { // 点击切换展开 + 推进状态 if (this.expandedId === item.id) { this.expandedId = -1; } else { this.expandedId = item.id; if (item.status === ACTIVE) { this.advanceTimeline(item.id); } } }))

}
.alignItems(VerticalAlign.Top)
}
关键设计细节:

左侧 Column 宽度固定 30vp:确保所有圆点在同一垂直线上
.layoutWeight(1) 撑满连接线:连接线 Column 的高度由父容器决定
maxLines 控制展开:收起时 2 行,展开时 10 行
textOverflow(Ellipsis):超出时显示省略号
alignItems(Top):圆点和内容从顶部对齐
3.5 三种状态圆点的 Stack 实现
三个圆点都用 Stack(层叠布局) 实现——底层是光晕或外圈,上层是实心圆或标记。

已完成圆点:

@Builder
completedDot() {
Stack() {
// 底层:外圈光晕(半透明绿色)
Text().width(18).height(18)
.backgroundColor(‘#2ECC7133’).borderRadius(9)

// 中层:实心圆 Text().width(12).height(12) .backgroundColor('#2ECC71').borderRadius(6) // 上层:对勾标记 Text('✓').fontSize(8).fontColor(Color.White)

}
.width(22).height(22)
}
三层叠加以 22×22 为容器,三种元素在 Z 轴上依次叠加。

进行中圆点:

@Builder
activeDot() {
Stack() {
Text().width(20).height(20).backgroundColor(‘#4A90D922’).borderRadius(10)
Text().width(12).height(12).backgroundColor(‘#4A90D9’).borderRadius(6)
Text().width(4).height(4).backgroundColor(Color.White).borderRadius(2) // 高光
}
.width(24).height(24)
}
进行中圆点比已完成的大 2px(24 vs 22),视觉上更突出。

待处理圆点:

@Builder
pendingDot() {
Stack() {
Text().width(12).height(12)
.border({ width: 2, color: ‘#95A5A6’ }).borderRadius(6)
}
.width(22).height(22)
}
待处理是空心圆环——只有边框(border)没有背景色。

3.6 垂直连接线的实现
连接线是时间轴最关键的视觉元素。Demo 中使用了一个窄背景色的 Column:

if (this.showLines && index < this.timelineData.length - 1) {
Column()
.width(2) // 2vp 宽的垂直线
.layoutWeight(1) // 撑满剩余垂直空间
.backgroundColor(this.getLineColor(item.status)) // 颜色随状态变化
}
为什么不用 Divider? 因为 Divider 是水平分割线,而我们需要的是垂直连接线。用 Column + 背景色可以精确控制宽度、颜色和高度。

连接线颜色的计算:

private getLineColor(status: TimelineStatus): string {
switch (status) {
case COMPLETED: return ‘#2ECC7144’; // 绿色半透明
case ACTIVE: return ‘#4A90D944’; // 蓝色半透明
case PENDING: return ‘#95A5A622’; // 灰色浅透明
}
}
颜色跟随当前节点的状态:已完成的节点下方是绿色线,进行中节点下方是蓝色线,待处理节点下方是灰色线。

3.7 状态推进逻辑
private advanceTimeline(currentId: number): void {
const updated = this.timelineData.map((item) => {
const result = { …item }; // 复制
if (item.id === currentId) {
result.status = COMPLETED; // 当前 → 已完成
}
if (item.id === currentId + 1 && item.status === PENDING) {
result.status = ACTIVE; // 下一个 → 进行中
}
return result;
});
this.timelineData = updated;
}
注意:ArkTS 中不允许使用 { …item, status: … } 对象展开语法。必须显式构建完整对象。

推进规则:

点击"前端开发(ACTIVE)" →
前端开发 → COMPLETED
后端联调 → ACTIVE
3.8 状态标签徽章
@Builder
statusBadge(status: TimelineStatus) {
Text(this.getStatusLabel(status))
.fontSize(9)
.backgroundColor(this.getStatusColor(status) + ‘66’)
.border({ width: 1, color: this.getStatusColor(status) + ‘99’ })
.borderRadius(8)
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.margin({ left: 8 })
}
效果:半透明背景 + 同色边框 → 胶囊形状的标签。

3.9 图例
@Builder
legendDot(color: string, label: string) {
Row() {
Text().width(8).height(8).backgroundColor(color).borderRadius(4)
Text(label).fontSize(11).fontColor(Color.Gray)
}
.alignItems(VerticalAlign.Center)
}
底部图例用三个小圆点 + 文字说明,方便用户理解三种状态的颜色含义。

四、时间轴的变体与扩展
4.1 水平时间轴
Scroll() {
Row() {
ForEach(this.timelineData, (item, index) => {
Column() {
// 上:内容卡片
// 中:连接线
// 下:圆点
}
})
}
}
.scrollable(ScrollDirection.Horizontal)
只需将 Scroll 设置为水平滚动,内部改为 Row 排列,连接线改为水平方向。

4.2 可交互的拖拽时间轴
结合第二篇文章的 PanGesture,可以让用户通过拖拽来调整节点的顺序。

4.3 自定义节点图标
@Builder
customNode(icon: string, color: string) {
Stack() {
Text().width(28).height(28).backgroundColor(color + ‘33’).borderRadius(14)
Text(icon).fontSize(14)
}
}
用 Emoji 或图标代替圆点,让每个节点有不同的视觉标识。

4.4 时间轴动画
// 节点进入动画
Text(item.title)
.transition({
type: TransitionType.Insert,
opacity: 0,
translate: { x: -20 }
})
4.5 懒加载时间轴
对于超长时间轴(几十个节点),可以使用 LazyForEach 替代 ForEach,只渲染可见区域的节点。

五、常见问题与坑点
5.1 连接线高度不准确
现象:连接线没有撑满两个节点之间的空间。

原因:父 Column 没有明确的高度。

解决方案:

Column()
.width(2)
.layoutWeight(1) // ← 关键:撑满剩余空间
同时左侧 Column 需要设置固定高度或 layoutWeight:

Column()
.width(30)
.height(this.expandedId === item.id ? 130 : 90) // ← 固定高度
5.2 最后一项多余连接线
现象:时间轴最后一个节点下面还有一段连接线。

解决方案:

// 最后一项(index === length - 1)不渲染连接线
if (this.showLines && index < this.timelineData.length - 1) {
// 渲染连接线
}
5.3 状态标签颜色不同步
现象:标签文字颜色与背景颜色不匹配。

解决方案:使用统一的颜色获取函数:

// 颜色保持一致
backgroundColor(this.getStatusColor(status) + ‘66’)
border({ color: this.getStatusColor(status) + ‘99’ })
5.4 卡片展开后内容溢出
现象:描述文字过长时,点击展开后超出卡片边界。

解决方案:

Text(item.description)
.maxLines(this.expandedId === item.id ? 10 : 2)
// ↑ 设置最大行数,超出用省略号
5.5 @Builder 中的条件渲染
在 @Builder 中使用 if/else 时,需要注意 ArkTS 的限制:

// ✅ 正确
@Builder timelineNode(item, index) {
if (conditionA) {
this.componentA()
} else {
this.componentB()
}
}

// ❌ 错误:不能直接在 @Builder 中声明变量
@Builder timelineNode() {
const x = 1; // 编译错误
}
变量声明应该在普通方法中完成,@Builder 只做 UI 编排。

六、最佳实践清单
6.1 时间轴组件的标准模板
@Component
struct Timeline {
@Prop data: TimelineItem[];

build() {
Scroll() {
Column() {
ForEach(this.data, (item, index) => {
this.timelineNode(item, index)
})
}
}
}

@Builder timelineNode(item, index) {
Row() {
// 左侧:指示器
Column() {
this.dot(item.status)
if (index < this.data.length - 1) {
Column().width(2).layoutWeight(1).backgroundColor(this.lineColor)
}
}
.width(30).alignItems(HorizontalAlign.Center)

// 右侧:内容 Column() { Text(item.title) Text(item.description) Text(item.time) } .layoutWeight(1) } .alignItems(VerticalAlign.Top)

}
}
6.2 数据与 UI 分离
// 数据层(独立文件)
// timelineData.ets
export const projectTimeline: TimelineItem[] = [ /* …/ ];
export const orderTimeline: TimelineItem[] = [ /
… */ ];

// 展示层
// TimelinePage.ets
@State private data: TimelineItem[] = projectTimeline;
6.3 响应式状态推进
// 使用状态机模式管理推进逻辑
private getNextStatus(current: TimelineStatus): TimelineStatus {
switch (current) {
case PENDING: return ACTIVE;
case ACTIVE: return COMPLETED;
case COMPLETED: return COMPLETED; // 已完成不再变化
}
}
6.4 可定制化配置
interface TimelineConfig {
dotSize?: number; // 圆点大小
lineWidth?: number; // 连接线宽度
cardRadius?: number; // 卡片圆角
colors?: {
completed: string; // 完成色
active: string; // 进行中色
pending: string; // 待处理色
};
}
6.5 无障碍支持
Text(‘✓’)
.accessibilityText(‘已完成’)
.accessibilityLevel(‘auto’)
七、总结与展望
7.1 核心回顾
Column 实现时间轴的公式:

时间轴 = Column(垂直骨架)
+ Row(每个节点: 左侧指示器 + 右侧内容)
+ Column(左侧: 圆点 + 连接线)
+ Column(右侧: 标题 + 描述 + 时间)
+ @State(数据驱动状态变化)
7.2 从本 demo 学到的布局技巧
技巧 实现方式 用途
垂直连接线 Column().width(2).layoutWeight(1) 窄背景 Column 模拟线条
圆点 + 光晕 Stack { 光晕 → 实心圆 → 标记 } 多层叠加实现精致图标
展开/收起 maxLines(2) ↔ maxLines(10) 控制文本行数
状态颜色 switch(status) → color + alpha 统一的颜色管理
骨架 + 组件 Column + @Builder 拆分 可维护的代码结构
7.3 下一步探索
LazyForEach:大数据量时间轴的性能优化
动画:节点出现/状态变化时的过渡动画
拖拽排序:PanGesture 实现节点重排
嵌套时间轴:每个节点内部再嵌套子时间轴
无障碍:为时间轴添加完整无障碍支持
附录 A:完整 Demo 代码
/*

  • TimelineDemo.ets —— 鸿蒙原生 ArkTS 布局方式之 Column 实现垂直时间轴组件
  • ===== 核心技术 =====
    1. Column() —— 作为时间轴的外骨架,垂直排列各个时间节点
    1. 自定义连接线 —— 用 Container 背景色绘制垂直连接线
    1. @Builder 组件化 —— 将时间轴节点抽象为可复用的组件
      */

enum TimelineStatus { COMPLETED, ACTIVE, PENDING }

interface TimelineItem {
id: number;
title: string;
description: string;
time: string;
status: TimelineStatus;
}

@Entry
@Component
struct TimelineDemo {
@State private timelineData: TimelineItem[] = [];
@State private showLines: boolean = true;
@State private expandedId: number = -1;

private readonly COLOR_COMPLETED: string = ‘#2ECC71’;
private readonly COLOR_ACTIVE: string = ‘#4A90D9’;
private readonly COLOR_PENDING: string = ‘#95A5A6’;

aboutToAppear(): void { this.initTimelineData(); }

build() {
Column() {
Text(‘垂直时间轴组件’).fontSize(20).fontWeight(FontWeight.Bold)
.fontColor(Color.White).textAlign(TextAlign.Center)
.width(‘100%’).padding({ top: 14, bottom: 2 })

// 控制栏 Row() { this.configButton(this.showLines ? '🔗 隐藏连接线' : '🔗 显示连接线', this.showLines ? '#ffffff22' : '#333', () => { this.showLines = !this.showLines; }) this.configButton('↻ 重置状态', '#E74C3C', () => { this.resetAllToPending(); }) }.width('100%').padding({ left: 12, right: 12, bottom: 4 }) // Scroll + Column 时间轴主体 Scroll() { Column() { ForEach(this.timelineData, (item, index) => { this.timelineNode(item, index) }, (item) => item.id.toString()) }.width('100%').padding({ left: 16, right: 16, top: 8, bottom: 16 }) }.layoutWeight(1).width('100%') // 图例 Row() { this.legendDot(this.COLOR_COMPLETED, '已完成') this.legendDot(this.COLOR_ACTIVE, '进行中') this.legendDot(this.COLOR_PENDING, '待处理') }.width('100%').justifyContent(FlexAlign.SpaceEvenly) .padding({ top: 8, bottom: 10 }).backgroundColor('#1a1a3e') }.width('100%').height('100%').backgroundColor('#0f3460')

}

// — 核心时间轴节点 —
@Builder
timelineNode(item: TimelineItem, index: number) {
Row() {
// 左侧指示器
Column() {
if (item.status === TimelineStatus.COMPLETED) this.completedDot()
else if (item.status === TimelineStatus.ACTIVE) this.activeDot()
else this.pendingDot()
if (this.showLines && index < this.timelineData.length - 1) {
Column().width(2).layoutWeight(1)
.backgroundColor(this.getLineColor(item.status))
}
}.width(30).alignItems(HorizontalAlign.Center)
.height(this.expandedId === item.id ? 130 : 90)

// 右侧内容卡片 Column() { Row() { Text(item.title).fontSize(15).fontColor(Color.White) .fontWeight(FontWeight.Bold) this.statusBadge(item.status) }.width('100%').alignItems(VerticalAlign.Center) Text(item.description).fontSize(12).fontColor(Color.Gray) .width('100%').margin({ top: 4 }) .maxLines(this.expandedId === item.id ? 10 : 2) .textOverflow({ overflow: TextOverflow.Ellipsis }) Row() { Text(item.time).fontSize(11).fontColor(Color.White).opacity(0.6) Text(this.expandedId === item.id ? '收起 ▲' : '展开 ▼') .fontSize(11).fontColor('#00B4D8') }.width('100%').justifyContent(FlexAlign.SpaceBetween).margin({ top: 6 }) } .layoutWeight(1).backgroundColor(this.getCardBg(item.status)) .borderRadius(10).padding(12).margin({ left: 8, bottom: 4 }) .gesture(TapGesture().onAction(() => { if (this.expandedId === item.id) this.expandedId = -1; else { this.expandedId = item.id; if (item.status === TimelineStatus.ACTIVE) this.advanceTimeline(item.id); } })) }.width('100%').alignItems(VerticalAlign.Top)

}

// — 三种圆点 —
@Builder completedDot() {
Stack() {
Text().width(18).height(18).backgroundColor(this.COLOR_COMPLETED+‘33’)
.borderRadius(9)
Text().width(12).height(12).backgroundColor(this.COLOR_COMPLETED)
.borderRadius(6)
Text(‘✓’).fontSize(8).fontColor(Color.White).fontWeight(FontWeight.Bold)
}.width(22).height(22)
}

@Builder activeDot() {
Stack() {
Text().width(20).height(20).backgroundColor(this.COLOR_ACTIVE+‘22’)
.borderRadius(10)
Text().width(12).height(12).backgroundColor(this.COLOR_ACTIVE)
.borderRadius(6)
Text().width(4).height(4).backgroundColor(Color.White).borderRadius(2)
}.width(24).height(24)
}

@Builder pendingDot() {
Stack() {
Text().width(12).height(12)
.border({ width: 2, color: this.COLOR_PENDING }).borderRadius(6)
}.width(22).height(22)
}

@Builder statusBadge(status: TimelineStatus) {
Text(this.getStatusLabel(status)).fontSize(9).fontColor(Color.White)
.backgroundColor(this.getStatusColor(status)+‘66’)
.border({ width: 1, color: this.getStatusColor(status)+‘99’ })
.borderRadius(8).padding({ left: 6, right: 6, top: 2, bottom: 2 })
.margin({ left: 8 })
}

@Builder configButton(label: string, color: string, action: () => void) {
Button(label).height(30).fontSize(11).backgroundColor(color)
.fontColor(Color.White).borderRadius(6).layoutWeight(1)
.margin({ left: 3, right: 3 })
.gesture(TapGesture().onAction(() => action()))
}

@Builder legendDot(color: string, label: string) {
Row() {
Text().width(8).height(8).backgroundColor(color).borderRadius(4)
.margin({ right: 4 })
Text(label).fontSize(11).fontColor(Color.Gray)
}.alignItems(VerticalAlign.Center)
}

// — 数据 —
private initTimelineData(): void {
this.timelineData = [
{ id:1, title:‘项目启动’, description:‘确定项目目标与范围…’, time:‘2025-07-01’, status:TimelineStatus.COMPLETED },
{ id:2, title:‘需求分析’, description:‘深入调研用户需求…’, time:‘2025-07-03’, status:TimelineStatus.COMPLETED },
{ id:3, title:‘UI 设计’, description:‘基于 PRD 完成原型设计…’, time:‘2025-07-05’, status:TimelineStatus.COMPLETED },
{ id:4, title:‘前端开发’, description:‘基于 ArkTS 开发全部页面…’, time:‘2025-07-08’, status:TimelineStatus.ACTIVE },
{ id:5, title:‘后端联调’, description:‘前后端接口联调…’, time:‘2025-07-15’, status:TimelineStatus.PENDING },
{ id:6, title:‘测试验收’, description:‘功能测试、性能测试…’, time:‘2025-07-20’, status:TimelineStatus.PENDING },
{ id:7, title:‘上线发布’, description:‘生产环境部署…’, time:‘2025-07-25’, status:TimelineStatus.PENDING }
];
}

private advanceTimeline(currentId: number): void {
const updated: TimelineItem[] = this.timelineData.map((item) => {
const r: TimelineItem = { id:item.id, title:item.title,
description:item.description, time:item.time, status:item.status };
if (item.id === currentId) r.status = TimelineStatus.COMPLETED;
if (item.id === currentId+1 && item.status === TimelineStatus.PENDING)
r.status = TimelineStatus.ACTIVE;
return r;
});
this.timelineData = updated;
}

private resetAllToPending(): void {
const updated: TimelineItem[] = this.timelineData.map((item): TimelineItem => {
return { id:item.id, title:item.title,
description:item.description, time:item.time,
status:TimelineStatus.PENDING };
});
if (updated.length > 0) {
updated[0] = { id:updated[0].id, title:updated[0].title,
description:updated[0].description, time:updated[0].time,
status:TimelineStatus.ACTIVE };
}
this.timelineData = updated;
this.expandedId = -1;
}

// — 工具 —
private getStatusColor(s: TimelineStatus): string {
return [this.COLOR_COMPLETED, this.COLOR_ACTIVE, this.COLOR_PENDING][s];
}
private getStatusLabel(s: TimelineStatus): string {
return [‘已完成’, ‘进行中’, ‘待处理’][s];
}
private getCardBg(s: TimelineStatus): string {
return [‘#1a3a2e’, ‘#1a2a4e’, ‘#1a1a2e’][s];
}
private getLineColor(s: TimelineStatus): string {
return [this.COLOR_COMPLETED+‘44’, this.COLOR_ACTIVE+‘44’, this.COLOR_PENDING+‘22’][s];
}
}
附录 B:参考资料
HarmonyOS NEXT 开发者文档 — Column 布局
HarmonyOS NEXT 开发者文档 — Stack 布局
HarmonyOS NEXT 开发者文档 — Scroll 滚动
HarmonyOS NEXT 开发者文档 — @Builder 装饰器
版权声明:本文为 HarmonyOS NEXT 技术分享系列的第七篇,遵循 CC BY-NC 4.0 协议。欢迎转载,但请注明出处。

系列文章:

第一篇:TapGesture 点击手势布局
第二篇:PanGesture 拖拽手势布局
第三篇:GestureGroup 组合手势布局
第四篇:Column 垂直排列入门
第五篇:Column + Scroll 可滚动列表
第六篇:Column + Flex 弹性混合布局
第七篇:Column 垂直时间轴组件(本文)

相关新闻

  • 3分钟掌握闲鱼数据智能采集:自动化市场洞察新方案
  • 永磁同步电机直接转矩控制原理与Simulink实现
  • 小程序制作工具测评:餐宝盈/BBWEYY/比文云/Vev/Beacon(2026年7月更新)含零代码SAAS、AI编程、源码定制交付

最新新闻

  • GPT-4o技术解析与主流大模型选型指南
  • 炉石传说终极模改指南:如何用HsMod提升300%游戏体验
  • E-Hentai Downloader完整使用指南:零基础掌握批量图片下载技术
  • 揭秘E-Viewer架构设计:UWP应用如何高效处理e-hentai数据请求
  • 如何构建高可用分布式网络监控:SmokePing主从架构深度解析
  • 如何用Auto-PPT免费生成专业PPT?3分钟快速上手教程

日新闻

  • STM32F745VG与MC6470 IMU的高性能姿态控制系统设计
  • 机器不消费,人何以生存
  • AI项目操作手册编写规范与最佳实践

周新闻

  • Windows字体自定义终极方案:No!! MeiryoUI完全指南
  • Deepin Boot Maker:告别命令行,3分钟制作Linux启动盘的智能解决方案
  • Plain Craft Launcher 2:重新定义你的Minecraft游戏体验

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号