利用vue3编写一个有趣的牛马时钟并集成到小程序_vue.js

文章编号:1002 技术教程 2026-02-04 vue3实现时钟 vue时钟 vue计时器

我最近做了一个小工具,叫「牛马时钟」,已经上线微信小程序了。今天想和大家分享这个项目的来龙去脉,以及开发过程中的一些技术细节。

每天上班,我总会想一个问题:我的时间到底值多少钱?

月薪 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 作为前端框架,主要是因为它的组合式 API(Composition API)。相比 Vue2 的选项式 API,组合式 API 更适合逻辑复用和代码组织。比如,计时器、数据计算这些独立功能,可以封装成独立的 composables ,代码结构更清晰。

平台方面,我用了 uni-app。它能将 Vue 代码编译成微信小程序、H5 等多端代码,一次开发多端运行,非常适合这种轻量级工具。

首先定义输入数据的响应式变量。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 定义,意味着当输入数据变化时,它会自动重新计算,无需手动调用。这就是响应式的魅力。

计时器需要精确到秒,同时要避免内存泄漏(比如组件卸载时忘记清除定时器)。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);});

这段代码的关键是:

为了让用户下次打开小程序时能自动读取上次的输入,需要用到 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

这样,用户每次修改输入后,数据会自动保存到本地;下次打开小程序时,会自动加载。

uni-app 的优势在于“一次编码,多端运行”,但小程序有一些特有的限制,需要注意:

1. 样式隔离:小程序的 wxss 不支持 scoped(类似 Vue 的样式作用域),但 uni-app 会自动处理,只需在 Vue 组件中使用