番茄时钟<微信开发者工具>

一、 需求分析

1.功能介绍

1.开始界面

1.1 开始界面的中央处有倒计时钟,分布在其下的为“工作”与“休息”的两个按钮,点下任一个按钮即可触发响应的倒计时进行计时工作。

1.2 在按钮下发有一个自动聚焦的输入框,在此处输入想要专注的任务,然后点击上方的按钮即可进行专注工作。

1.3最下方为选项卡,第一个对应的即为开始界面,第二个位记录界面,第三个为设置界面。选中图标会跳转到相应的界面同时图标会变成绿色。

android 番茄钟源码_javascript

android 番茄钟源码_Math_02

2.记录界面

会显示“时间段+任务名“,时刻提醒自己做了些什么,底部正中央有个“清除记录”的选项,点击后会清空所有记录。

android 番茄钟源码_Math_03

android 番茄钟源码_ci_04

3.设置界面

工作时长和休息时长可调,最上方有两个滑块,滑动即可进行时间调节。

android 番茄钟源码_ci_05

二、开始界面

一、页面设计

  1. 整理初始代码,删除部分文件及代码。
    1.1 打开app.js文件,删除自带代码,只保留以下代码。
    1.2 删除logs文件包。
    1.3 打开app.json文件,删除"pages/logs/logs",保留以下代码。
{
"pages":[
"pages/index/index"
  ],
"window": {
    "backgroundTextStyle": "light",
    "navigationBarBackgroundColor": "#3197ed",
    "navigationBarTitleText": "",
    "navigationBarTextStyle": "white"
  }
}

1.4 删除index.wxml和index.wxss中所有代码。

1.5 打开index.js文件,删除自带代码,只保留以下代码。

//index.js

//获取应用实例

const app = getApp()

Page({

})

1.6新建如下图文件:

android 番茄钟源码_android 番茄钟源码_06


1、打开app.wxss文件,修改代码

/**app.wxss**/
view{
  box-sizing: border-box;
}
page {
  height: 100%;
}
.container,input,button {
  font: 14px "Helvetica Neue", "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;
}
.container {
  height: 100%;
  background-color: #f5f5f5;
}

2、打开app.json文件,修改代码

"tabBar": {
    "color": "#dddddd",
    "selectedColor": "#3cc51f",
    "borderStyle": "black",
    "backgroundColor": "#ffffff",
    "list": [
      {
        "pagePath": "pages/index/index",
        "iconPath": "image/wechat.png",
        "selectedIconPath": "image/wechatHL.png",
        "text": "主页"
      },
      {
        "pagePath": "pages/record/record",
        "iconPath": "image/wechat.png",
        "selectedIconPath": "image/wechatHL.png",
        "text": "记录"
      },
      {
        "pagePath": "pages/setting/setting",
        "iconPath": "image/wechat.png",
        "selectedIconPath": "image/wechatHL.png",
        "text": "设置"
      }
    ]
  },

3、修改pages/index/index.wxml文件。

<view class="container timer {{isRuning ? 'timer--runing': ''}}">
  <view class="timer_main">
     <view class="timer_time-wrap">
        <view class="timer_progress_mask"></view>
        <view class="timer_progress timer_left">
          <view class="timer_circle timer_circle--left" style="transform: rotate({{leftDeg}}deg);"></view>
        </view>
        <view class="timer_progress timer_right">
          <view class="timer_circle timer_circle--right" style="transform: rotate({{rightDeg}}deg);"></view>
        </view>
        <text wx:if="{{!completed}}" class="timer_time">{{remainTimeText}}</text>
        <text 
          wx:if="{{isRuning}}" 
          animation="{{nameAnimation}}" 
          class="timer_taskName">{{taskName}}{{completed ? '已完成!' : '中'}}</text>
     <image  wx:if="{{completed}}" class="timer_done"  src="../../image/complete.png"></image>
     </view>
     <input 
      type="text" 
      placeholder-style="text-align:center" 
      class="timer_inputname" 
      bindinput="changeLogName"
      placeholder="给您的任务取个名字吧"/>
  </view>
  <view class="timer_footer">
    <view 
      bindtap="startTimer" 
      data-type="work" 
      class="timer_ctrl {{isRuning && timerType == 'rest' ? 'hide' : ''}}" >{{isRuning ? '完成': '工作'}}</view>
    <view 
      bindtap="startTimer" 
      data-type="rest" 
      class="timer_ctrl {{isRuning && timerType == 'work' ? 'hide' : ''}}" >{{isRuning ? '完成': '休息'}}</view>
  </view>
</view>

4、修改pages/index/index.wxss文件

/**index.wxss**/
.container {
  display: flex;
  height: 100%;
  flex-direction: column;
  overflow: hidden;
  background-color: #fff;
}
.timer_main {
  position: relative;
  display: flex;
  flex: 2;
  justify-content: center;
  align-items: center;
  text-align: center;
  background-color: #3197ed;
  transition: all .5s;
  z-index: 1;
  padding: 10px 0;
}
.timer_time-wrap {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  width: 150px;
  height: 150px;
  text-align: center;
  transition: all .3s;
}
.timer_progress {
  position: absolute;
  top: 0;
  width: 75px;
  height: 150px;
  overflow: hidden;
  transition: all .3s;
}
.timer_progress_mask {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  border: 3px solid #1589eb;
  opacity: .5;
  border-radius: 50%;
}
.timer_left {
  left: 0;
}
.timer_right {
  right: 0;
}
.timer_circle {
  position: absolute;
  top: 0;
  width: 150px;
  height: 150px;
  border: 3px solid transparent;
  border-radius: 50%;
  transition: all .3s;
}
.timer_circle--left {
  left: 0;
  border-left: 3px solid #fff;
  border-bottom: 3px solid #fff;
  transform: rotate(45deg);
}
.timer_circle--right {
  right: 0;
  border-right: 3px solid #fff;
  border-bottom: 3px solid #fff;
  transform: rotate(-45deg);
}
.timer_time {
  font-size: 40px;
  color: #fff;
  font-weight: lighter;
  transition: font-size .3s;
}
.timer_taskName {
  position: absolute;
  top: -100px;
  font-size: 14px;
  letter-spacing: 5px;
  color: #fff;
}
.timer_done {
  width: 64px;
  height: 64px;
}
.timer_inputname {
  position: absolute;
  bottom: -40px;
  width: 100%;
  text-align: center;
  height: 40px;
  padding-left: 10px;
  border-bottom: 1px solid #f2f2f2;
  color: #999;
}
.timer_footer {
  display: flex;
  justify-content: center;
  align-items: center;
  flex: 1;
  padding-top: 40px;
  transition: all .3s;
}
.timer .timer_ctrl {
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
  font-size: 12px;
  color: #fff;
  width: 80px;
  height: 80px;
  margin: 0 20px;
  border-radius: 50%;
  transition: all .7s;
  background-color: #48c23d;
}
.hide {
  display: none!important;
}
/*runing style*/
.timer--runing .timer_main {
  flex: 1;
}
.timer--runing .timer_time {
  font-size: 45px;
}
.timer--runing .timer_time-wrap {
  width: 200px;
  height: 200px;
}
.timer--runing .timer_progress {
  width: 100px;
  height: 200px;
}
.timer--runing .timer_circle {
  width: 200px;
  height: 200px;
}
.timer--runing .timer_footer {
  flex: 0;
  position: absolute;
  bottom: 0;
  width: 100%;
  z-index: 10;
}
.timer--runing .timer_ctrl {
  background-color: #208DEA;
  height: 30px;
  margin-bottom: 30px;
  border: 1px dashed #dedede;
  border-radius: 20px;
}

二、逻辑层开发
1、打开app.js文件,添加如下代码

//app.js
// 设置全局变量
const defaultTime = {
  defaultWorkTime: 25,
  defaultRestTime: 5
}
App({
  onLaunch: function() {
    let workTime = wx.getStorageSync('workTime')
    let restTime = wx.getStorageSync('restTime')
    if (!workTime) {
      wx.setStorage({
        key: 'workTime',
        data: defaultTime.defaultWorkTime
      })
    }
    if (!restTime) {
      wx.setStorage({
        key: 'restTime',
        data: defaultTime.defaultRestTime
      })
    }
  }
})

2、打开index.js文件,添加代码如下

//index.js
//获取应用实例
const defaultLogName = {
  work: '工作',
  rest: '休息'
}
const actionName = {
  stop: '停止',
  start: '开始'
}
const initDeg = {
  left: 45,
  right: -45,
}
const app = getApp()

Page({
  data: {
    remainTimeText: '',//显示文字
    timerType: 'work',//定时器类型
    log: {}, //记录
    completed: false,//是否已完成定时器
    isRuning: false,//定时器是否在运行
    leftDeg: initDeg.left,//左半圆进展弧度
    rightDeg: initDeg.right//右半圆进展弧度
  },
  formatTime: function(time, format) {
    let temp = '0000000000' + time
    let len = format.length
    return temp.substr(-len)
  },
  onShow: function() {
    if (this.data.isRuning) return //如果计时器在运行,则直接不执行下面代码
    //获取缓存中设置的时间信息
    let workTime = this.formatTime(wx.getStorageSync('workTime'), 'HH')
    let restTime = this.formatTime(wx.getStorageSync('restTime'), 'HH')
    this.setData({
      workTime: workTime,
      restTime: restTime,
      remainTimeText: workTime + ':00'
    })
  },
  saveLog: function(log) {
    var logs = wx.getStorageSync('logs') || []
    logs.unshift(log)
    wx.setStorageSync('logs', logs)
  },
  changeLogName: function(e) {
    this.logName = e.detail.value
  },
  startNameAnimation: function() {
    let animation = wx.createAnimation({
      duration: 450
    })
    animation.opacity(0.2).step()
    animation.opacity(1).step()
    this.setData({
      nameAnimation: animation.export()
    })
  },
  stopTimer: function() {
    // reset circle progress
    this.setData({
      leftDeg: initDeg.left,
      rightDeg: initDeg.right
    })
    // clear timer
    this.timer && clearInterval(this.timer)
  },
  updateTimer: function() {
    let log = this.data.log
    let now = Date.now()
    let remainingTime = Math.round((log.endTime - now) / 1000)

    let H = this.formatTime(Math.floor(remainingTime / (60 * 60)) % 24, 'HH')
    let M = this.formatTime(Math.floor(remainingTime / (60)) % 60, 'MM')
    let S = this.formatTime(Math.floor(remainingTime) % 60, 'SS')
    let halfTime

    // update text
    if (remainingTime > 0) {
      let remainTimeText = (H === "00" ? "" : (H + ":")) + M + ":" + S
      this.setData({
        remainTimeText: remainTimeText
      })
    } else if (remainingTime == 0) {
      this.setData({
        completed: true
      })
      this.stopTimer()
      return
    }

    // update circle progress
    
    halfTime = log.keepTime / 2
    if ((remainingTime * 1000) > halfTime) {
      
      this.setData({
        leftDeg: initDeg.left - (180 * (now - log.startTime) / halfTime)
      })
    } else {
      this.setData({
        leftDeg: -135
      })
      this.setData({
        rightDeg: initDeg.right - (180 * (now - (log.startTime + halfTime)) / halfTime)
      })
    }
  },
  startTimer: function(e) {
    let startTime = Date.now()
    let isRuning = this.data.isRuning
    let timerType = e.target.dataset.type
    let showTime = this.data[timerType + 'Time']
    let keepTime = showTime * 60 * 1000
    let logName = this.logName || defaultLogName[timerType]
    if (!isRuning) {
      this.timer = setInterval((function() {
        this.updateTimer()
        this.startNameAnimation()
      }).bind(this), 1000)
    } else {
      this.stopTimer()
    }

    this.setData({
      isRuning: !isRuning,
      completed: false,
      timerType: timerType,
      remainTimeText: showTime + ':00',
      taskName: logName
    })

    this.data.log = {
      name: logName,
      startTime: Date.now(),
      keepTime: keepTime,
      endTime: keepTime + startTime,
      action: actionName[isRuning ? 'stop' : 'start'],
      type: timerType
    }
    this.saveLog(this.data.log)
  },
})

三、记录界面

一.页面设计
1、打开record.wxml文件,添加如下代码:

<!--pages/record/record.wxml-->
<block wx:if="{{logs.length}}">
  <scroll-view class="container" scroll-y="true">
    <view class="log panel">
       <view class="log_item" wx:for="{{logs}}" wx:if-index="$index"wx:for-item="log">
       <text class="log_start">{{log.startTime}}</text>
       <text class="log_action">{{log.action}}</text>
       <text class="log_action">{{log.name}}</text>
       </view>
    </view>
  </scroll-view>
  <view class="clear">
  <button bindtap="switchModal" class="clear_btn" size="mini">清除记录</button>
  </view>
</block>
<block wx:else>
   <view class="nodata">
    <image class="nodata_img" src="../../image/nodata.png"></image>
    <text class="nodata_text">暂无记录</text>
   </view>
</block>

2、打开record.wxss文件,添加如下代码:

/* pages/record/record.wxss */
.container{
  padding-bottom: 50px;
  max-height: 92%;
}
.log{
  color: #999;
  margin: 15px 10px;
}
.log_item{
  padding:15px 5px;
}
.log_item text{
  padding-right: 20px;
}
.clear{
  position: absolute;
  bottom: 0px;
  width: 100%;
  text-align: center;
}
.clear.clear_btn{
  margin: 10px;
  height: 35px;
  line-height: 35px;
  color: #fff;
  background-color: #fd8f58;
  border-radius: 2px;
  width: 80%;
}
.panel{
  background:#fff ;
  border-radius: 3px;
  padding: 5px 10px;
}
.nodata{
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
}
.nodata_img{
  display: block;
  width: 45px;
  height: 45px;
}
.nodata_text{
  color: #dedede;
  font-size: 12px;
  padding-left: 10px;
}

3、打开record.js文件,添加如下代码:

// pages/record/record.js
Page({

  /**
   * 页面的初始数据
   */
  data: {
    logs: []
  },
  getLogs: function() {
    let logs = wx.getStorageSync('logs')
    logs.forEach(function(item,index,arry) {
      item.startTime = new Date(item.startTime).toLocaleString()
    })
    this.setData({
      logs: logs
    })
  },
  clearLog: function(e) {
    wx.setStorageSync('logs', [])
    this.getLogs()
    wx.showToast({
      title: '清除成功',
      icon: 'success',
      duration: 2000
    })
  },
  switchModal: function() {
    wx.showModal({
      title: '提示',
      content: '是否清除记录?此操作不可恢复!',
      success:(res)=> {
        if (res.confirm) {
          console.log('用户点击确定')
          this.clearLog();
        } else if (res.cancel) {
          console.log('用户点击取消')
        }
      }
    })
  },
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {

  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
    wx.setNavigationBarTitle({
      title: '任务记录'
    })
    this.getLogs()
  },  

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})

四、设置界面

一.页面设计
1、打开setting.wxml文件,添加如下代码:

<!--pages/setting/setting.wxml-->
<view class="container">
  <view class="section panel">
  <text class="section_title">工作时长(分钟)</text>
  <view class="section_body">
   <slider bindchange="changeWorkTime" show-value="true" min="1" max="60" value="{{workTime}}" left-icon="cancel" right-icon="success_no_circle"/>
  </view>
  </view>
  <view class="section panel">
  <text class="section_title">休息时长(分钟)</text>
   <view class="section_body">
   <slider bindchange="changeRestTime" show-value="true" min="5" max="60" value="{{restTime}}" left-icon="cancel" right-icon="success_no_circle"/>
  </view>
</view>
<view class="section panel">
 <view class="section_title">
  <text>主页背景</text>
 </view>
 <view class="section_body">
  <text bindtab="" class="section_tip">选择背景></text>
 </view>
</view>
<view class="section panel">
 <view class="section_title">
 <switch class="section_check" type="checkbox" size="mini" checked bindchange="switch1Change"/>
 <text>启用铃声</text>
 </view>
 <view class="section_body">
 <text bindtab="" class="section_tip">选择铃声></text>
 </view>
</view>
</view>

2、打开setting.wxss文件,添加如下代码:

/* pages/setting/setting.wxss */
.container{
  padding-left: 15px 10px;
}
.section{
  margin-bottom: 10px;
}
.section_title{
  font-size: 12px;
  color: #999;
}
.section_check{
  margin-right: 5px;
}
.section_tip{
  display: block;
  text-align: right;
  color: #CEC9C9;
    padding-bottom: 10px;
}
.section_body{
  margin-top: 15px;
}
.panel{
  background: #fff;
  border-radius: 3px;
  padding: 5px 10px;
}

3、打开setting.js文件,添加如下代码:

// pages/setting/setting.js
Page({

  /**
   * 页面的初始数据
   */
  data: {

  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {

  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
    wx.setNavigationBarTitle({
      title: '设置'
    })
    this.setData({
      workTime: wx.getStorageSync('workTime'),
      restTime: wx.getStorageSync('restTime')
    })
  },
   changeWorkTime:function(e){
     wx.setStorage({
       key: 'workTime',
       data: 'e.detail.value',
     })
   },
   changeWorkTime: function(e) {
      wx.setStorage({
        key: 'workTime',
        data: e.detail.value
      })
    },
    changeRestTime: function(e) {
      wx.setStorage({
        key: 'restTime',
        data: e.detail.value
      })
    },
  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})

五、项目所需顺序图片

1.complete.png

android 番茄钟源码_Time_07

2.nodata.png

android 番茄钟源码_Time_08


3.view.gif

android 番茄钟源码_android 番茄钟源码_09