小程序开发实战:数组显示与计时器应用
在现代 AI 工程化实践中,如何实时监控模型训练任务的状态,已经成为开发者日常工作中不可忽视的一环。无论是微调一个大语言模型,还是运行一次批量推理评测,用户都希望看到清晰的任务队列和动态更新的进度信息。而这些功能的背后,其实并不依赖复杂的框架——用小程序原生能力就能快速实现。
设想这样一个场景:你在使用ms-swift框架提交多个 LoRA 微调任务后,想通过一个轻量级前端界面查看当前所有任务的执行状态,并自动刷新进度。这正是我们今天要动手构建的内容:一个基于微信小程序的“实时任务监控面板”。它将涵盖两个核心能力——动态数组渲染和定时轮询更新,而这两种技术几乎贯穿了所有需要展示列表数据并保持同步的业务场景。
我们先从最基础的部分开始:如何把一组任务数据显示成可视化的列表。在小程序中,页面结构由 WXML 定义,样式由 WXSS 控制,逻辑则写在 JS 文件里。这种分层设计让开发变得非常直观。
假设我们要展示如下类型的任务数据:
{ name: 'Qwen3-7B 微调', status: '运行中', progress: 65 }我们需要在页面的data中定义一个tasks数组来存储这些对象:
Page({ data: { tasks: [ { name: 'Qwen3-7B 微调', status: '运行中', progress: 65 }, { name: 'GLM4.5-VL 量化', status: '完成', progress: 100 }, { name: 'InternLM3 推理评测', status: '等待', progress: 0 } ] } })接着,在 WXML 中使用wx:for指令遍历这个数组:
<view class="task-list"> <block wx:for="{{tasks}}" wx:key="index"> <view class="task-item"> <text>📌 {{item.name}}</text> <text>📊 状态: {{item.status}}</text> <text>⏳ 进度: {{item.progress}}%</text> </view> </block> </view>这里有几个关键点值得注意:
-wx:for是小程序提供的列表渲染语法,类似于 Vue 的v-for。
- 使用block标签包裹是为了避免额外的 DOM 层级干扰布局。
-wx:key建议设置为唯一值(如id),若无唯一字段可用index,但要注意性能影响。
当数据发生变化时,必须通过this.setData()来触发视图更新。比如点击按钮添加新任务:
addTask() { const newTask = { name: `任务_${Date.now().toString().substr(-4)}`, status: '排队中', progress: Math.floor(Math.random() * 30) }; this.setData({ tasks: [...this.data.tasks, newTask] }); }注意不能直接对this.data.tasks.push(newTask)这样修改原始数组,因为小程序的数据变更检测依赖于引用变化。只有传入一个新的数组引用,框架才能感知到变化并重新渲染列表。这是“不可变性”原则在小程序中的典型体现。
配合简单的 WXSS 样式美化后,整个任务列表已经具备良好的可读性和响应式表现:
.task-item { background: white; border: 1px solid #ddd; border-radius: 12rpx; padding: 20rpx; margin-bottom: 20rpx; font-size: 28rpx; line-height: 1.6; color: #333; }使用rpx单位可以确保在不同分辨率设备上都能自适应缩放,是移动端开发的最佳实践之一。
现在,静态列表已经就绪,接下来我们要让它“活起来”——模拟真实环境中后台任务不断推进的过程。这就需要用到 JavaScript 的计时器机制。
目标很明确:每 2 秒随机选择一个未完成的任务,将其进度提升 5~15 个百分点,直到达到 100% 后标记为“已完成”。这种周期性行为自然想到setInterval。
我们在onLoad生命周期中启动定时器:
onLoad() { console.log('任务面板加载完成'); const timerId = setInterval(() => { this.updateRandomTask(); }, 2000); this.setData({ timerId }); }其中updateRandomTask方法负责具体的逻辑处理:
updateRandomTask() { const index = Math.floor(Math.random() * this.data.tasks.length); const task = this.data.tasks[index]; // 已完成任务跳过 if (task.progress >= 100) return; let newProgress = task.progress + Math.floor(Math.random() * 10) + 5; if (newProgress > 100) newProgress = 100; const status = newProgress === 100 ? '已完成' : task.status; const newTasks = [...this.data.tasks]; newTasks[index] = { ...task, progress: newProgress, status }; this.setData({ tasks: newTasks }); }这里有几个工程细节值得强调:
- 使用扩展运算符创建新数组和新对象,保证数据不可变性;
- 在更新前判断是否已达 100%,防止重复累加;
- 状态字段根据进度动态切换,增强反馈感。
更重要的是,我们必须在页面卸载时清除定时器,否则可能导致内存泄漏甚至多个定时器叠加运行:
onUnload() { if (this.data.timerId) { clearInterval(this.data.timerId); console.log('定时器已清除'); } }如果不做清理,当你反复进入退出该页面时,每个实例都会保留自己的定时器,最终造成页面卡顿或数据错乱。
为了提升交互体验,还可以加入“暂停/恢复”功能:
<button type="default" bindtap="toggleTimer"> {{ isRunning ? '⏸️ 暂停刷新' : '▶️ 恢复刷新' }} </button>对应的控制逻辑也很简单:
toggleTimer() { if (this.data.isRunning) { clearInterval(this.data.timerId); this.setData({ isRunning: false, timerId: null }); } else { const timerId = setInterval(() => { this.updateRandomTask(); }, 2000); this.setData({ timerId, isRunning: true }); } }只要在data中初始化isRunning: true,就可以实现完整的启停控制。这类细节能显著提升产品的专业度。
虽然这是一个小程序前端示例,但它映射的其实是ms-swift这类模型工程平台的真实需求。我们可以将每一项功能对应到实际系统中:
| 小程序功能 | 对应 ms-swift 场景 |
|---|---|
| 任务列表显示 | 展示当前运行的训练/微调/评测任务 |
| 状态动态更新 | 监控 GPU 利用率、loss 曲线、准确率等指标 |
| 定时刷新机制 | 轮询后端 API 获取最新训练日志 |
| 新增任务按钮 | 触发新的 LoRA 微调或 DPO 对齐任务 |
未来如果要真正接入后端服务,只需将updateRandomTask改为调用 RESTful 接口或 WebSocket 订阅消息即可。例如:
// 替换为真实的 API 请求 wx.request({ url: 'https://api.msswift.dev/tasks/status', success: (res) => { this.setData({ tasks: res.data }); } });甚至可以进一步集成图表库(如 uCharts)绘制 loss 下降曲线,或者引入 Web Workers 处理复杂计算,避免阻塞主线程影响 UI 流畅度。
项目结构建议保持清晰简洁:
miniprogram/ ├── pages/ │ └── tasks/ │ ├── tasks.wxml │ ├── tasks.wxss │ └── tasks.js ├── app.json ├── app.js └── app.wxssapp.json中注册页面并配置导航栏样式:
{ "pages": ["pages/tasks/tasks"], "window": { "navigationBarTitleText": "ms-swift 监控台", "navigationBarBackgroundColor": "#000", "navigationBarTextStyle": "white" }, "sitemapLocation": "sitemap.json" }这样就能拥有一个统一风格的应用入口。
回到本质,小程序开发的核心理念是“数据驱动视图”——你不需要也不应该去操作 DOM。所有的 UI 变化都应该源于data的变更,并通过setData()通知框架进行重绘。这一点与 React/Vue 等现代前端框架高度一致。
同时也要时刻警惕异步资源的管理问题:定时器、网络请求、WebSocket 连接等,都需要在适当生命周期中正确释放。尤其是在多页面跳转的小程序环境中,疏忽会导致严重的性能隐患。
如果你正在参与 AI 平台的前端建设,不妨尝试把这个任务监控页面封装成一个通用组件。将来无论是用于推理队列、数据预处理流水线,还是自动化评测系统,都可以复用这套模式,大幅提升开发效率。
最终你会发现,看似简单的wx:for和setInterval,组合起来却能支撑起相当复杂的业务逻辑。而这正是小程序作为轻量级应用平台的魅力所在:不追求炫技,专注解决实际问题。