吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1224|回复: 27
上一主题 下一主题
收起左侧

[其他原创] 薪急如焚,打工人的摸鱼好伴侣

[复制链接]
跳转到指定楼层
楼主
kunsir001 发表于 2026-8-26 12:27 回帖奖励
摸鱼的时候就爱看薪资跳动的感觉,上班痛苦没有关系,重要的是知道自己每秒值多少钱。新开发的小程序【薪急如焚】:看钱一秒一秒跳进账,摸鱼计时结算白嫖金额,消费换算成搬砖时长。今天,也要薪急如焚地摸鱼。

源码奉上
wenxin.zip (64.7 KB, 下载次数: 40)



[JavaScript] 纯文本查看 复制代码
const salary = require('../../utils/salary');
const storage = require('../../utils/storage');

const GENDERS = ['保密', '男', '女'];
const PAYDAY_LIST = Array.from({ length: 31 }, (_, i) => `${i + 1}号`);
const FISH_TICK_MS = 100; // 摸鱼计价刷新间隔
const GATE_TICK_MS = 30 * 1000; // 可摸鱼状态刷新间隔(跨过上班/下班点时自动更新)

// 非工作时段的摸鱼拦截文案(按 todayStatus 的 phase 区分)
const FISH_GATE_TEXTS = {
  before: '还没上班就想摸鱼?先养精蓄锐,等上班了再来白嫖',
  break: '现在是午休,摸鱼不计薪。等开工了再来,鱼要摸在点子上',
  after: '都下班了还摸什么鱼,回家躺着才是真本事',
  rest: '今天不开工,摸鱼没有薪资加成,安心休息吧',
};

Page({
  data: {
    profile: { nickname: '', avatar: '', gender: '保密' },
    configured: false,
    // 带薪摸鱼
    fishing: false,
    fishReady: true, // 当前是否处于可开始摸鱼的工作时段
    fishGateText: '', // 不可摸时的待机区文案
    fishSinceText: '', // 计时中提示行(含午休/下班截止状态)
    fishCapped: false, // 是否已过下班时刻(计薪截止)
    fishInBreak: false, // 是否正处于午休空档(计薪暂停)
    fishElapsedStr: '00:00:00',
    fishEarnStr: '0.0000',
    // 摸鱼统计(按日/周/月)
    statToday: { earningsStr: '0.00', durationStr: '0秒' },
    statWeek: { earningsStr: '0.00', durationStr: '0秒' },
    statMonth: { earningsStr: '0.00', durationStr: '0秒' },
    // 个人信息弹层
    showProfilePanel: false,
    nicknameInput: '',
    genderIndex: 0,
    genders: GENDERS,
    // 薪资设置弹层
    showSalaryPanel: false,
    paydayList: PAYDAY_LIST,
    form: {
      monthlySalary: '',
      paydayIndex: 9,
      // 工作日(周一~周五)
      weekdayAMStart: '09:00',
      weekdayAMEnd: '12:00',
      weekdayPMStart: '13:30',
      weekdayPMEnd: '18:00',
      // 周六:上午/下午分别勾选
      satAMWork: false,
      satAMStart: '09:00',
      satAMEnd: '12:00',
      satPMWork: false,
      satPMStart: '13:30',
      satPMEnd: '18:00',
      // 周日:上午/下午分别勾选
      sunAMWork: false,
      sunAMStart: '09:00',
      sunAMEnd: '12:00',
      sunPMWork: false,
      sunPMStart: '13:30',
      sunPMEnd: '18:00',
    },
    // 自动计算预览:按自然月日历算出合计工时与每秒价值
    preview: { monthLabel: '', hoursStr: '--', perSecondStr: '--' },
  },

  onShow() {
    this.settings = storage.getSettings();
    const profile = storage.getProfile();
    this.setData({
      profile,
      nicknameInput: profile.nickname || '',
      genderIndex: Math.max(0, GENDERS.indexOf(profile.gender || '保密')),
      configured: Number(this.settings.monthlySalary) > 0,
    });
    this.refreshStats();
    this.restoreFishing();
    this.startGateTimer();

    // 首次使用:自动弹出薪资设置
    const app = getApp();
    if (app.globalData.needSetup && !this.data.configured) {
      app.globalData.needSetup = false;
      this.openSalaryPanel();
    }
  },

  onHide() {
    this.stopFishTimer();
    this.stopGateTimer();
  },

  onUnload() {
    this.stopFishTimer();
    this.stopGateTimer();
  },

  noop() {},

  /* ================= 摸鱼统计 ================= */

  refreshStats() {
    const s = storage.statsByPeriod(new Date(), this.settings); // 按当前薪资动态重算
    const fmt = (x) => ({
      earningsStr: salary.formatMoney(x.earnings, 2),
      durationStr: salary.formatDuration(x.duration),
    });
    this.setData({
      statToday: fmt(s.day),
      statWeek: fmt(s.week),
      statMonth: fmt(s.month),
    });
  },

  /* ================= 带薪摸鱼 ================= */

  restoreFishing() {
    // 已在计时中:仅确保显示刷新的定时器在跑(切后台/切页后回来时重启,幂等);
    // 否则从本地存储恢复(防崩溃/杀进程丢计时状态)
    const active = this.fishStart
      ? { startTime: this.fishStart }
      : storage.getActiveFish();
    if (!active || !active.startTime) return;
    this.fishStart = active.startTime;
    this.setData({
      fishing: true,
      fishSinceText: `从 ${salary.timeLabel(active.startTime)} 摸到现在`,
    });
    this.startFishTimer(); // 内部先 stop 再 start,重复调用安全
  },

  startFish() {
    if (!this.data.configured) {
      wx.showToast({ title: '先设置薪资才能计价', icon: 'none' });
      this.openSalaryPanel();
      return;
    }
    // 只允许在工作时段内开始摸鱼(上午/下午段内才算带薪离席)
    const phase = salary.todayStatus(this.settings, new Date()).phase;
    if (phase !== 'working') {
      wx.showModal({
        title: '先别急',
        content: FISH_GATE_TEXTS[phase] || '现在不是工作时间,等上班了再来',
        showCancel: false,
        confirmText: '好吧',
      });
      this.updateFishGate();
      return;
    }
    const start = Date.now();
    this.fishStart = start;
    storage.setActiveFish({ startTime: start });
    this.setData({
      fishing: true,
      fishSinceText: `从 ${salary.timeLabel(start)} 摸到现在`,
      fishCapped: false,
      fishInBreak: false,
      fishElapsedStr: '00:00:00',
      fishEarnStr: '0.0000',
    });
    this.startFishTimer();
  },

  stopFish() {
    wx.showModal({
      title: '结束摸鱼?',
      content: '点击确定结算这段时间白嫖的钱',
      confirmText: '结算',
      success: (res) => {
        if (res.confirm) this.finishFish();
      },
    });
  },

  finishFish() {
    this.stopFishTimer();
    const start = this.fishStart;
    // 摸到下班后的时间不计薪:截止到开始当天的下班时刻
    const r = salary.settleFish(this.settings, start, Date.now());
    // 按摸鱼开始当天所在月计价(与 recordEarnings 动态口径一致,跨月结算不漂移)
    const earnings = r.duration * salary.perSecond(this.settings, new Date(start));
    if (r.duration >= 1) {
      storage.addRecord({ startTime: start, endTime: r.end, duration: r.duration, earnings });
    }
    storage.clearActiveFish();
    this.fishStart = null;
    this.setData({
      fishing: false,
      fishSinceText: '',
      fishCapped: false,
      fishInBreak: false,
      fishElapsedStr: '00:00:00',
      fishEarnStr: '0.0000',
    });
    this.refreshStats();
    this.updateFishGate();
    if (r.duration < 1) {
      wx.showModal({
        title: '白摸了',
        content: '这次没赶上计薪时段,一分钱都没白嫖到',
        confirmText: '心疼',
        showCancel: false,
      });
      return;
    }
    const notes = [];
    if (r.excludedSec >= 60) notes.push(`已剔除休息 ${salary.formatDuration(r.excludedSec)}`);
    if (r.capped) notes.push('下班后不计薪');
    wx.showModal({
      title: '摸鱼结算',
      content:
        `本次摸鱼 ${salary.formatDuration(r.duration)}\n` +
        `白嫖 &#165;${salary.formatMoney(earnings, 2)}\n` +
        (notes.length ? `(${notes.join(',')})\n` : '') +
        this.fishGrade(r.duration),
      confirmText: '血赚',
      showCancel: false,
    });
  },

  fishGrade(sec) {
    if (sec < 300) return '浅摸一下,见好就收';
    if (sec < 1800) return '标准带薪离席';
    if (sec < 3600) return '深度摸鱼,渐入佳境';
    return '摸鱼大师,老板哭了';
  },

  startFishTimer() {
    this.stopFishTimer();
    this.tickFish();
    this.fishTimer = setInterval(() => this.tickFish(), FISH_TICK_MS);
  },

  stopFishTimer() {
    if (this.fishTimer) {
      clearInterval(this.fishTimer);
      this.fishTimer = null;
    }
  },

  tickFish() {
    if (!this.fishStart) return;
    // 只累计工作时段内的秒数:跨午休自动剔除,过下班冻结
    const r = salary.settleFish(this.settings, this.fishStart, Date.now());
    const phase = salary.todayStatus(this.settings, new Date()).phase;
    let sinceText;
    if (r.capped) {
      // 忘了结算跨天:明确告知只算到开始那天的下班
      const overnight = salary.dateKey(new Date(this.fishStart)) !== salary.dateKey(new Date());
      sinceText = overnight ? '都跨天了,计薪只到那天下班为止' : '已过下班时刻,计薪截止到下班';
    }
    else if (phase === 'break') sinceText = '午休中,休息时间不计薪';
    else sinceText = `从 ${salary.timeLabel(this.fishStart)} 摸到现在`;
    this.setData({
      fishElapsedStr: salary.formatClock(r.duration),
      fishEarnStr: salary.formatMoney(r.duration * salary.perSecond(this.settings), 4),
      fishCapped: r.capped,
      fishInBreak: !r.capped && phase === 'break',
      fishSinceText: sinceText,
    });
  },

  /* ================= 可摸鱼状态门禁 ================= */

  /** 刷新待机区状态:仅工作时段内允许开始(未配置薪资时引导设置) */
  updateFishGate() {
    if (this.data.fishing) return;
    if (!this.data.configured) {
      this.setData({ fishReady: false, fishGateText: '先设置薪资,摸鱼才有价' });
      return;
    }
    const st = salary.todayStatus(this.settings, new Date());
    const allowed = st.phase === 'working';
    this.setData({
      fishReady: allowed,
      fishGateText: allowed ? '' : FISH_GATE_TEXTS[st.phase] || '现在不是工作时间,等上班了再来',
    });
  },

  startGateTimer() {
    this.stopGateTimer();
    this.updateFishGate();
    this.gateTimer = setInterval(() => this.updateFishGate(), GATE_TICK_MS);
  },

  stopGateTimer() {
    if (this.gateTimer) {
      clearInterval(this.gateTimer);
      this.gateTimer = null;
    }
  },

  /* ================= 个人信息 ================= */

  openProfilePanel() {
    this.setData({ showProfilePanel: true });
  },

  closeProfilePanel() {
    this.setData({ showProfilePanel: false });
  },

  onChooseAvatar(e) {
    const temp = e.detail.avatarUrl;
    if (!temp) return;
    const fs = wx.getFileSystemManager();
    const old = this.data.profile.avatar;
    const doSave = () => {
      fs.saveFile({
        tempFilePath: temp,
        success: (res) => this.applyAvatar(res.savedFilePath),
        fail: () => this.applyAvatar(temp), // 持久化失败时退回临时路径(本次会话可用)
      });
    };
    if (old && old.indexOf('store_') >= 0) {
      fs.removeFile({ filePath: old, complete: doSave }); // 清理旧头像文件
    } else {
      doSave();
    }
  },

  applyAvatar(path) {
    const profile = storage.saveProfile({ avatar: path });
    getApp().globalData.profile = profile;
    this.setData({ profile });
    wx.showToast({ title: '头像已更新', icon: 'success' });
  },

  onNicknameInput(e) {
    this.setData({ nicknameInput: e.detail.value });
  },

  onGenderChange(e) {
    this.setData({ genderIndex: Number(e.detail.value) });
  },

  saveProfileInfo() {
    const nickname = (this.data.nicknameInput || '').trim().slice(0, 16);
    const profile = storage.saveProfile({
      nickname,
      gender: GENDERS[this.data.genderIndex] || '保密',
    });
    getApp().globalData.profile = profile;
    this.setData({ profile, showProfilePanel: false });
    wx.showToast({ title: '已保存', icon: 'success' });
  },

  /* ================= 薪资设置 ================= */

  openSalaryPanel() {
    const s = this.settings || storage.getSettings();
    this.setData({
      showSalaryPanel: true,
      form: {
        monthlySalary: s.monthlySalary ? String(s.monthlySalary) : '',
        paydayIndex: (Math.round(Number(s.payday)) || 10) - 1,
        weekdayAMStart: s.weekdayAMStart,
        weekdayAMEnd: s.weekdayAMEnd,
        weekdayPMStart: s.weekdayPMStart,
        weekdayPMEnd: s.weekdayPMEnd,
        satAMWork: !!s.satAMWork,
        satAMStart: s.satAMStart,
        satAMEnd: s.satAMEnd,
        satPMWork: !!s.satPMWork,
        satPMStart: s.satPMStart,
        satPMEnd: s.satPMEnd,
        sunAMWork: !!s.sunAMWork,
        sunAMStart: s.sunAMStart,
        sunAMEnd: s.sunAMEnd,
        sunPMWork: !!s.sunPMWork,
        sunPMStart: s.sunPMStart,
        sunPMEnd: s.sunPMEnd,
      },
    });
    this.updatePreview();
  },

  closeSalaryPanel() {
    this.setData({ showSalaryPanel: false });
  },

  onSalaryInput(e) {
    this.setData({ 'form.monthlySalary': e.detail.value });
    this.updatePreview();
  },

  onPaydayChange(e) {
    this.setData({ 'form.paydayIndex': Number(e.detail.value) });
  },

  /** 统一的时间 picker 处理:data-field 指定 form 字段 */
  onTimeChange(e) {
    const field = e.currentTarget.dataset.field;
    if (!field) return;
    this.setData({ [`form.${field}`]: e.detail.value });
    this.updatePreview();
  },

  /** 周六上/下午复选:勾选的时段展开各自起止时间设置 */
  onSatCheckChange(e) {
    const vals = e.detail.value || [];
    this.setData({
      'form.satAMWork': vals.indexOf('am') >= 0,
      'form.satPMWork': vals.indexOf('pm') >= 0,
    });
    this.updatePreview();
  },

  /** 周日上/下午复选 */
  onSunCheckChange(e) {
    const vals = e.detail.value || [];
    this.setData({
      'form.sunAMWork': vals.indexOf('am') >= 0,
      'form.sunPMWork': vals.indexOf('pm') >= 0,
    });
    this.updatePreview();
  },

  /** 用当前表单构造临时 settings,按当月日历实时预览工时与每秒价值 */
  updatePreview() {
    const now = new Date();
    const tmp = this.formToSettings();
    const secs = salary.monthWorkSeconds(tmp, now.getFullYear(), now.getMonth() + 1);
    const ps = salary.perSecond(tmp, now);
    this.setData({
      preview: {
        monthLabel: `${now.getFullYear()}年${now.getMonth() + 1}月`,
        hoursStr: secs > 0 ? (secs / 3600).toFixed(1) : '--',
        perSecondStr: ps > 0 ? salary.formatMoney(ps, 4) : '--',
      },
    });
  },

  formToSettings() {
    const f = this.data.form;
    return {
      monthlySalary: Number(f.monthlySalary) || 0,
      weekdayAMStart: f.weekdayAMStart,
      weekdayAMEnd: f.weekdayAMEnd,
      weekdayPMStart: f.weekdayPMStart,
      weekdayPMEnd: f.weekdayPMEnd,
      satAMWork: !!f.satAMWork,
      satAMStart: f.satAMStart,
      satAMEnd: f.satAMEnd,
      satPMWork: !!f.satPMWork,
      satPMStart: f.satPMStart,
      satPMEnd: f.satPMEnd,
      sunAMWork: !!f.sunAMWork,
      sunAMStart: f.sunAMStart,
      sunAMEnd: f.sunAMEnd,
      sunPMWork: !!f.sunPMWork,
      sunPMStart: f.sunPMStart,
      sunPMEnd: f.sunPMEnd,
    };
  },

  /** 校验某组上/下午时段:各段有效且下午不早于上午结束 */
  validateDay(prefix, label) {
    const f = this.data.form;
    const amStart = salary.parseHM(f[`${prefix}AMStart`]);
    const amEnd = salary.parseHM(f[`${prefix}AMEnd`]);
    const pmStart = salary.parseHM(f[`${prefix}PMStart`]);
    const pmEnd = salary.parseHM(f[`${prefix}PMEnd`]);
    if (amEnd <= amStart) {
      wx.showToast({ title: `${label}上午:结束要晚于开始`, icon: 'none' });
      return false;
    }
    if (pmEnd <= pmStart) {
      wx.showToast({ title: `${label}下午:结束要晚于开始`, icon: 'none' });
      return false;
    }
    if (pmStart < amEnd) {
      wx.showToast({ title: `${label}下午不能早于上午结束`, icon: 'none' });
      return false;
    }
    return true;
  },

  /** 校验周六/周日:仅校验勾选的时段,且下午不早于上午结束 */
  validateWeekend(prefix, label) {
    const f = this.data.form;
    const amOn = f[`${prefix}AMWork`];
    const pmOn = f[`${prefix}PMWork`];
    if (amOn && salary.parseHM(f[`${prefix}AMEnd`]) <= salary.parseHM(f[`${prefix}AMStart`])) {
      wx.showToast({ title: `${label}上午:结束要晚于开始`, icon: 'none' });
      return false;
    }
    if (pmOn && salary.parseHM(f[`${prefix}PMEnd`]) <= salary.parseHM(f[`${prefix}PMStart`])) {
      wx.showToast({ title: `${label}下午:结束要晚于开始`, icon: 'none' });
      return false;
    }
    if (
      amOn &&
      pmOn &&
      salary.parseHM(f[`${prefix}PMStart`]) < salary.parseHM(f[`${prefix}AMEnd`])
    ) {
      wx.showToast({ title: `${label}下午不能早于上午结束`, icon: 'none' });
      return false;
    }
    return true;
  },

  saveSalary() {
    const f = this.data.form;
    const monthlySalary = Number(f.monthlySalary);
    if (!(monthlySalary > 0)) {
      wx.showToast({ title: '月薪要大于 0', icon: 'none' });
      return;
    }
    if (!this.validateDay('weekday', '工作日')) return;
    if (!this.validateWeekend('sat', '周六')) return;
    if (!this.validateWeekend('sun', '周日')) return;
    const patch = this.formToSettings();
    if (!(salary.monthWorkSeconds(patch, new Date().getFullYear(), new Date().getMonth() + 1) > 0)) {
      wx.showToast({ title: '当月工时为 0,检查时段设置', icon: 'none' });
      return;
    }
    this.settings = storage.saveSettings(
      Object.assign({ payday: f.paydayIndex + 1 }, patch),
    );
    getApp().globalData.settings = this.settings;
    this.setData({ configured: true, showSalaryPanel: false });
    this.updateFishGate(); // 时段设置可能变了,刷新可摸鱼状态
    wx.showToast({ title: '已保存,去看钱跳', icon: 'success' });
  },

  gotoCalendar() {
    wx.switchTab({ url: '/pages/calendar/calendar' });
  },
});

微信图片_2026-08-26_122144_327.jpg (204.39 KB, 下载次数: 1)

微信图片_2026-08-26_122144_327.jpg

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
庸世俗人罢勒 + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
 楼主| kunsir001 发表于 2026-8-26 14:24 |楼主
room200800 发表于 2026-8-26 14:14
你这挂的什么服务器?

没有服务器,本地运行,所有数据的在本地哈
推荐
room200800 发表于 2026-8-26 14:14
沙发
picoyiyi 发表于 2026-8-26 12:29
3#
lby20260721 发表于 2026-8-26 12:32
笑了,这个软件有趣
4#
maya123 发表于 2026-8-26 12:34
哈哈,比较有意思!!
5#
 楼主| kunsir001 发表于 2026-8-26 13:31 |楼主
picoyiyi 发表于 2026-8-26 12:29
每个月的工资折算成秒的意思吗?

是的。不是最精确的,没有去除节假日
6#
zhh327 发表于 2026-8-26 13:40

谢谢分享 楼主辛苦了
7#
room200800 发表于 2026-8-26 13:52
班次太单一了,不过,就玩玩的话,还不错
8#
ysq151 发表于 2026-8-26 13:55
无聊的时候可以玩玩
9#
ITongLIX 发表于 2026-8-26 14:04
这个是什么软件?
10#
zlzx01 发表于 2026-8-26 14:07
有点意思,哈哈搞笑
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-8-26 17:28

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表