我最近做了一个小工具,叫「牛马时钟」,已经上线微信小程序了。今天想和大家分享这个项目的来龙去脉,以及开发过程中的一些技术细节。
一、问题的起点:打工人的时间困惑
每天上班,我总会想一个问题:我的时间到底值多少钱?
月薪 1 万,每天工作 8 小时,一个月 22 个工作日——数学好的人可以立刻算出时薪:10000/(22×8)≈56.8 元/小时。但问题是,这个数字只是静态的。当我实际
坐在工位上时,时间的流逝是动态的:每过 1 分钟,我赚了 0.95 元;每刷 10 分钟手机,就“亏”了 9.5 元。
我需要一个工具,能实时显示时间与收益的关系,让“摸鱼的代价”变得肉眼可见。这就是「牛马时钟」的核心需求。
二、功能设计:从需求到落地
工具的功能很简单,分两步:
1. 输入基础数据:月薪、每日工时、当月工作日数(比如 22 天)。
2. 实时计时:启动后,页面会显示已工作时间(时:分:秒)和累计收益(精确到分)。
举个例子:输入月薪 8800 元、每日 8 小时、22 天工作日,时薪就是 50 元/小时。工作 1 小时 15 分钟,收益就是 50 + (15/60)×50 = 62.5 元。
为了让用户不用重复输入,数据需要本地持久化——下次打开小程序时,自动读取上次的输入。
三、技术选型:Vue3 + uni-app 的组合
我选择 Vue3 作为前端框架,主要是因为它的组合式 API(Composition API)。相比 Vue2 的选项式 API,组合式 API 更适合逻辑复用和代码组织。比如,计时器、数据计算这些独立功能,可以封装成独立的 composables
,代码结构更清晰。
跨平台方面,我用了 uni-app。它能将 Vue 代码编译成微信小程序、H5 等多端代码,一次开发多端运行,非常适合这种轻量级工具。
四、核心代码实现:用 Vue3 写计时器
1. 基础数据与响应式
首先定义输入数据的响应式变量。Vue3 的 ref
可以创建响应式数据,computed
可以定义计算属性。
// 使用组合式 API,在 setup 函数中定义 import { ref, computed, onMounted, onUnmounted } from'vue'; exportdefault { setup() { // 输入数据:月薪、每日工时、工作日数 const monthlySalary = ref(null); const hoursPerDay = ref(null); const workDays = ref(null); // 计算时薪:月薪 / (工作日数 × 每日工时) const hourlyWage = computed(() => { if (!monthlySalary.value || !hoursPerDay.value || !workDays.value) return0; return (monthlySalary.value / (workDays.value * hoursPerDay.value)).toFixed(2); }); return { monthlySalary, hoursPerDay, workDays, hourlyWage }; } };
这里有个细节:hourlyWage
用 computed
定义,意味着当输入数据变化时,它会自动重新计算,无需手动调用。这就是响应式的魅力。
2. 计时器的实现
计时器需要精确到秒,同时要避免内存泄漏(比如组件卸载时忘记清除定时器)。Vue3 的生命周期钩子 onMounted
和 onUnmounted
可以解决这个问题。
// 在 setup 函数中继续添加 const seconds = ref(0); // 已工作秒数 let timer = null; // 定时器引用 // 启动计时 const startTimer = () => { if (timer) return; // 避免重复启动 timer = setInterval(() => { seconds.value++; }, 1000); }; // 停止计时(可选功能) const stopTimer = () => { if (timer) { clearInterval(timer); timer = null; } }; // 组件卸载时清除定时器 onUnmounted(() => { stopTimer(); }); // 计算已工作时间(格式化为 HH:MM:SS) const formattedTime = computed(() => { const hours = Math.floor(seconds.value / 3600); const minutes = Math.floor((seconds.value % 3600) / 60); const secs = seconds.value % 60; return`${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; }); // 计算累计收益 const totalEarnings = computed(() => { if (!hourlyWage.value) return'0.00'; const earnings = (seconds.value / 3600) * hourlyWage.value; return earnings.toFixed(2); });
这段代码的关键是:
- 用
ref
跟踪秒数,定时器每秒递增seconds.value
; - 用
computed
实时格式化时间和计算收益; - 用
onUnmounted
清除定时器,避免组件卸载后定时器仍在运行(内存泄漏)。
3. 数据持久化:uni-app 的本地存储
为了让用户下次打开小程序时能自动读取上次的输入,需要用到 uni-app 的 uni.setStorageSync
和 uni.getStorageSync
(类似浏览器的 localStorage
)。
// 在 setup 函数中添加 // 加载缓存数据(组件挂载时) onMounted(() => { const savedData = uni.getStorageSync('clockConfig'); if (savedData) { monthlySalary.value = savedData.monthlySalary; hoursPerDay.value = savedData.hoursPerDay; workDays.value = savedData.workDays; } }); // 保存数据(输入变化时) constsaveConfig = () => { if (monthlySalary.value && hoursPerDay.value && workDays.value) { uni.setStorageSync('clockConfig', { monthlySalary: monthlySalary.value, hoursPerDay: hoursPerDay.value, workDays: workDays.value }); } }; // 在模板中,输入框的 @change 事件绑定 saveConfig
这样,用户每次修改输入后,数据会自动保存到本地;下次打开小程序时,会自动加载。
五、小程序集成:从 Vue 到小程序的适配
uni-app 的优势在于“一次编码,多端运行”,但小程序有一些特有的限制,需要注意:
1. 样式隔离:小程序的 wxss
不支持 scoped
(类似 Vue 的样式作用域),但 uni-app 会自动处理,只需在 Vue 组件中使用 即可。
2. API 替换:uni-app 封装了跨平台 API(如 uni.navigateTo
),编译到小程序时会自动转换为 wx.navigateTo
,无需额外处理。
3. 性能优化:小程序对页面渲染性能要求较高,计时器的更新要避免频繁触发重绘。这里用 ref
而非 reactive
,因为 ref
对基本类型(如数字)的响应式更高效。
六、上线后的反馈:工具的价值超出预期
上线一周,用户的反馈让我很意外:
- 有自由职业者用它记录项目耗时,计算每单实际收益;
- 有学生用它监督兼职时长,避免被克扣工资;
- 甚至有宝妈用它记录“带娃工时”,调侃自己是“月薪 3 万的家庭 CEO”。
到此这篇关于利用vue3编写一个有趣的牛马时钟并集成到小程序的文章就介绍到这了,更多相关vue3时钟内容请搜索IT俱乐部以前的文章或继续浏览下面的相关文章希望大家以后多多支持IT俱乐部!