Explorar el Código

初始化提交:添加小程序基础代码文件

LiLe hace 3 meses
commit
a3fce870ea
Se han modificado 61 ficheros con 3386 adiciones y 0 borrados
  1. 86 0
      app.js
  2. 48 0
      app.json
  3. 10 0
      app.wxss
  4. 0 0
      images/check-white.png
  5. BIN
      images/check.png
  6. 3 0
      images/check.svg
  7. 0 0
      images/check1.png
  8. BIN
      images/close.png
  9. BIN
      images/comment.png
  10. BIN
      images/community.png
  11. BIN
      images/community2.png
  12. BIN
      images/jishi.png
  13. BIN
      images/jishi2.png
  14. BIN
      images/like.png
  15. BIN
      images/liked.png
  16. BIN
      images/pause.png
  17. BIN
      images/personal.png
  18. BIN
      images/personal2.png
  19. BIN
      images/play.png
  20. BIN
      images/read.png
  21. BIN
      images/setting.png
  22. BIN
      images/setting2.png
  23. BIN
      images/sport.png
  24. BIN
      images/study.png
  25. BIN
      images/think.png
  26. BIN
      images/tongji.png
  27. BIN
      images/tongji2.png
  28. BIN
      images/work.png
  29. BIN
      images/write.png
  30. 74 0
      pages/checkInShare/checkInShare.js
  31. 3 0
      pages/checkInShare/checkInShare.json
  32. 16 0
      pages/checkInShare/checkInShare.wxml
  33. 55 0
      pages/checkInShare/checkInShare.wxss
  34. 294 0
      pages/community/community.js
  35. 3 0
      pages/community/community.json
  36. 110 0
      pages/community/community.wxml
  37. 187 0
      pages/community/community.wxss
  38. 537 0
      pages/index/index.js
  39. 3 0
      pages/index/index.json
  40. 180 0
      pages/index/index.wxml
  41. 534 0
      pages/index/index.wxss
  42. 196 0
      pages/logs/logs.js
  43. 3 0
      pages/logs/logs.json
  44. 25 0
      pages/logs/logs.wxml
  45. 78 0
      pages/logs/logs.wxss
  46. 150 0
      pages/personalCenter/personalCenter.js
  47. 3 0
      pages/personalCenter/personalCenter.json
  48. 24 0
      pages/personalCenter/personalCenter.wxml
  49. 55 0
      pages/personalCenter/personalCenter.wxss
  50. 173 0
      pages/settings/settings.js
  51. 3 0
      pages/settings/settings.json
  52. 35 0
      pages/settings/settings.wxml
  53. 107 0
      pages/settings/settings.wxss
  54. 84 0
      pages/theme/theme.js
  55. 4 0
      pages/theme/theme.json
  56. 47 0
      pages/theme/theme.wxml
  57. 144 0
      pages/theme/theme.wxss
  58. 61 0
      project.config.json
  59. 25 0
      project.private.config.json
  60. 7 0
      sitemap.json
  61. 19 0
      utils/util.js

+ 86 - 0
app.js

@@ -0,0 +1,86 @@
+// app.js
+App({
+  globalData: {
+    userInfo: null,
+    theme: {
+      bgColor: '#F8F8F8', // 默认背景颜色
+      textColor: '#333',
+      primaryColor: '#E7624F',
+      secondaryTextColor: '#222222'
+    },
+    logs: [] // 专注记录
+  },
+
+  onLaunch: function() {
+    // 初始化事件总线
+    this.eventBus = {
+      events: {},
+      on: function(eventName, callback) {
+        if (!this.events[eventName]) {
+          this.events[eventName] = [];
+        }
+        this.events[eventName].push(callback);
+      },
+      off: function(eventName, callback) {
+        if (this.events[eventName]) {
+          this.events[eventName] = this.events[eventName].filter(
+            cb => cb !== callback
+          );
+        }
+      },
+      emit: function(eventName, data) {
+        if (this.events[eventName]) {
+          this.events[eventName].forEach(callback => {
+            callback(data);
+          });
+        }
+      }
+    };
+
+    // 登录
+    wx.login({
+      success: res => {
+        // 发送 res.code 到后台换取 openId, sessionKey, unionId
+        // 建议在获取到用户授权后再调用此接口
+      }
+    });
+
+    // 尝试从缓存获取用户信息
+    try {
+      const userInfo = wx.getStorageSync('userInfo');
+      if (userInfo) {
+        this.globalData.userInfo = userInfo;
+        this.eventBus.emit('userInfoChange', userInfo); // 通知所有监听者用户信息已更新
+      }
+    } catch (e) {
+      console.error('获取用户信息缓存失败', e);
+    }
+
+    // 获取用户信息
+    wx.getSetting({
+      success: res => {
+        if (res.authSetting['scope.userInfo']) {
+          // 已经授权,可以直接调用 getUserProfile 获取头像昵称
+          wx.getUserProfile({
+            desc: '用于完善用户资料',
+            success: res => {
+              this.updateUserInfo(res.userInfo);
+              // 可以在这里将用户信息发送到后端保存
+            }
+          });
+        }
+      }
+    });
+  },
+
+  // 更新用户信息并存储到本地缓存
+  updateUserInfo: function(userInfo) {
+    this.globalData.userInfo = userInfo;
+    try {
+      wx.setStorageSync('userInfo', userInfo);
+      this.eventBus.emit('userInfoChange', userInfo); // 通知所有监听者用户信息已更新
+    } catch (e) {
+      console.error('存储用户信息失败', e);
+    }
+  }
+});

+ 48 - 0
app.json

@@ -0,0 +1,48 @@
+{
+  "requiredPrivateInfo": [
+    "camera"
+  ],
+  "pages": [
+    "pages/index/index",
+    "pages/logs/logs",
+    "pages/settings/settings",
+    "pages/theme/theme",
+    "pages/community/community",
+    "pages/personalCenter/personalCenter"
+  ],
+  "window": {
+    "backgroundTextStyle": "light",
+    "navigationBarBackgroundColor": "#E7624F",
+    "navigationBarTitleText": "WeChat",
+    "navigationBarTextStyle": "white"
+  },
+  "tabBar": {
+    "selectedColor": "#000000",
+    "list": [
+      {
+        "pagePath": "pages/index/index",
+        "text": "计时",
+        "iconPath": "./images/jishi2.png",
+        "selectedIconPath": "./images/jishi.png"
+      },
+      {
+        "pagePath": "pages/logs/logs",
+        "text": "统计",
+        "iconPath": "./images/tongji2.png",
+        "selectedIconPath": "./images/tongji.png"
+      },  
+      {
+        "pagePath": "pages/community/community",
+        "text": "社区",
+        "iconPath": "./images/community2.png",
+        "selectedIconPath": "./images/community.png"
+      },
+      {
+        "pagePath": "pages/settings/settings",
+        "text": "设置",
+        "iconPath": "./images/setting2.png",
+        "selectedIconPath": "./images/setting.png"
+      }
+    ]
+  }
+}

+ 10 - 0
app.wxss

@@ -0,0 +1,10 @@
+/**app.wxss**/
+.container {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: space-between;
+  padding: 200rpx 0;
+  box-sizing: border-box;
+} 

+ 0 - 0
images/check-white.png


BIN
images/check.png


+ 3 - 0
images/check.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
+  <path fill="white" d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z"/>
+</svg>

+ 0 - 0
images/check1.png


BIN
images/close.png


BIN
images/comment.png


BIN
images/community.png


BIN
images/community2.png


BIN
images/jishi.png


BIN
images/jishi2.png


BIN
images/like.png


BIN
images/liked.png


BIN
images/pause.png


BIN
images/personal.png


BIN
images/personal2.png


BIN
images/play.png


BIN
images/read.png


BIN
images/setting.png


BIN
images/setting2.png


BIN
images/sport.png


BIN
images/study.png


BIN
images/think.png


BIN
images/tongji.png


BIN
images/tongji2.png


BIN
images/work.png


BIN
images/write.png


+ 74 - 0
pages/checkInShare/checkInShare.js

@@ -0,0 +1,74 @@
+// checkInShare.js
+Page({
+  data: {
+    checkInList: [],
+    userInfo: {}
+  },
+  onLoad() {
+    const userInfo = wx.getStorageSync('userInfo');
+    if (userInfo) {
+      this.setData({
+        userInfo
+      });
+    }
+    const logs = wx.getStorageSync('logs') || [];
+    const checkInList = logs.map(log => {
+      return {
+        userInfo: this.data.userInfo,
+        date: log.date,
+        cate: this.getCateText(log.cate),
+        time: log.time
+      };
+    });
+    this.setData({
+      checkInList
+    });
+  },
+  getCateText(cateIndex) {
+    const cateArr = [
+      {
+        icon: 'work',
+        text: '工作'
+      },
+      {
+        icon: 'study',
+        text: "学习"
+      },
+      {
+        icon: 'think',
+        text: '思考'
+      },
+      {
+        icon: 'write',
+        text: '写作'
+      },
+      {
+        icon: 'sport',
+        text: '运动'
+      },
+      {
+        icon: 'read',
+        text: "阅读"
+      }
+    ];
+    return cateArr[cateIndex].text;
+  },
+  publishCheckIn() {
+    const logs = wx.getStorageSync('logs') || [];
+    const lastLog = logs[0];
+    const newCheckIn = {
+      userInfo: this.data.userInfo,
+      date: lastLog.date,
+      cate: this.getCateText(lastLog.cate),
+      time: lastLog.time
+    };
+    const checkInList = [newCheckIn, ...this.data.checkInList];
+    this.setData({
+      checkInList
+    });
+    wx.showToast({
+      title: '打卡发布成功',
+      icon: 'success'
+    });
+  }
+});

+ 3 - 0
pages/checkInShare/checkInShare.json

@@ -0,0 +1,3 @@
+{
+  "navigationBarTitleText": "打卡分享"
+}

+ 16 - 0
pages/checkInShare/checkInShare.wxml

@@ -0,0 +1,16 @@
+<view class="container">
+  <!-- 打卡列表 -->
+  <view class="check-in-list">
+    <view class="check-in-item" wx:for="{{checkInList}}" wx:key="*this">
+      <view class="user-info">
+        <image src="{{item.userInfo.avatarUrl}}" mode="aspectFill"></image>
+        <text>{{item.userInfo.nickName}}</text>
+      </view>
+      <view class="check-in-content">
+        <text>在{{item.date}}完成了{{item.cate}}任务,专注时长{{item.time}}分钟</text>
+      </view>
+    </view>
+  </view>
+  <!-- 发布打卡按钮 -->
+  <button bindtap="publishCheckIn">发布打卡</button>
+</view>

+ 55 - 0
pages/checkInShare/checkInShare.wxss

@@ -0,0 +1,55 @@
+.container {
+  padding: 20rpx;
+  text-align: center;
+}
+
+.avatar {
+  width: 200rpx;
+  height: 200rpx;
+  margin: 20rpx auto;
+  border-radius: 50%;
+  overflow: hidden;
+}
+
+.avatar image {
+  width: 100%;
+  height: 100%;
+}
+
+.nickname-wrapper {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin: 20rpx 0;
+}
+
+.input-wrapper {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin: 10rpx 0;
+}
+
+.input-box {
+  width: 60%; /* 调整输入框宽度 */
+  padding: 10rpx;
+  border: 1rpx solid #E7624F;
+  border-radius: 10rpx;
+  margin-right: 10rpx;
+}
+
+.save-button,
+.get-code-button {
+  /* 调整按钮的宽度和内边距 */
+  width: 20%; 
+  padding: 10rpx 15rpx; 
+  border: 1rpx solid #E7624F;
+  border-radius: 10rpx;
+  background-color: #E7624F;
+  color: white;
+  font-size: 24rpx; /* 调整字体大小 */
+}
+
+button {
+  margin-top: 20rpx;
+}

+ 294 - 0
pages/community/community.js

@@ -0,0 +1,294 @@
+// pages/community/community.js
+const app = getApp()
+
+Page({
+  data: {
+    theme: {},
+    containerStyle: '',
+    postContent: '',
+    todayDuration: 0,
+    showCommentId: null,
+    currentComment: '',
+    posts: [],
+    isChecked: false,
+    continuousDays: 0,
+    totalDuration: 0
+  },
+
+  onLoad: function() {
+    // 计算今日专注时长
+    this.calculateTodayDuration()
+    
+    // 加载主题
+    this.setData({
+      theme: app.globalData.theme,
+      containerStyle: `background-color: ${app.globalData.theme.bgColor}; color: ${app.globalData.theme.textColor};`
+    })
+    
+    // 加载签到数据
+    this.loadCheckInData()
+    
+    // 加载帖子数据
+    this.loadPostsData()
+    
+    // 监听主题变化
+    if (app.eventBus) {
+      app.eventBus.on('themeChange', (theme) => {
+        this.setData({
+          theme,
+          containerStyle: `background-color: ${theme.bgColor}; color: ${theme.textColor};`
+        })
+      })
+    }
+  },
+
+  onShow: function() {
+    // 更新导航栏颜色
+    this.updateNavigationBarColor()
+  },
+
+  updateNavigationBarColor: function() {
+    const { theme } = this.data
+    if (theme && theme.primaryColor && theme.textColor) {
+      wx.setNavigationBarColor({
+        frontColor: theme.textColor === '#333' ? '#000000' : '#ffffff',
+        backgroundColor: theme.primaryColor,
+        animation: {
+          duration: 300,
+          timingFunc: 'easeIn'
+        }
+      })
+    }
+  },
+
+  calculateTodayDuration: function() {
+    // 从存储中获取今日专注数据
+    const logs = wx.getStorageSync('logs') || []
+    let duration = 0
+    const today = new Date().toDateString()
+    
+    logs.forEach(log => {
+      if (new Date(log.date).toDateString() === today) {
+        duration += parseInt(log.time) || 0
+      }
+    })
+    
+    this.setData({ todayDuration: duration })
+  },
+
+  loadCheckInData: function() {
+    // 从存储中获取签到数据
+    const checkins = wx.getStorageSync('checkins') || {}
+    const today = new Date().toDateString()
+    
+    // 计算连续签到天数
+    let continuousDays = 0
+    let date = new Date()
+    
+    while (true) {
+      const dateStr = date.toDateString()
+      if (checkins[dateStr]) {
+        continuousDays++
+        date.setDate(date.getDate() - 1)
+      } else {
+        break
+      }
+    }
+    
+    // 计算总专注时长
+    const logs = wx.getStorageSync('logs') || []
+    let totalDuration = 0
+    logs.forEach(log => {
+      totalDuration += parseInt(log.time) || 0
+    })
+    
+    this.setData({
+      isChecked: !!checkins[today],
+      continuousDays,
+      totalDuration
+    })
+  },
+
+  loadPostsData: function() {
+    // 从存储中获取帖子数据,或使用预设数据
+    const savedPosts = wx.getStorageSync('communityPosts') || []
+    
+    // 如果没有保存的帖子,使用预设数据
+    if (savedPosts.length === 0) {
+      const defaultPosts = [
+        {
+          id: 1,
+          user: {
+            name: '番茄达人',
+            avatar: '../../images/avatar1.png'
+          },
+          content: '今天专注学习了3小时,完成了所有任务!',
+          duration: 180,
+          focusTime: '14:30',
+          time: '2小时前',
+          likes: 12,
+          isLiked: false,
+          comments: [
+            { id: 1, user: '用户A', content: '太棒了!向你学习' },
+            { id: 2, user: '用户B', content: '我也要加油了' }
+          ]
+        },
+        {
+          id: 2,
+          user: {
+            name: '学习小能手',
+            avatar: '../../images/avatar2.png'
+          },
+          content: '连续专注5个番茄钟,效率超高!',
+          duration: 125,
+          focusTime: '上午',
+          time: '5小时前',
+          likes: 8,
+          isLiked: true,
+          comments: [
+            { id: 1, user: '用户C', content: '怎么做到的?求经验' }
+          ]
+        }
+      ]
+      
+      wx.setStorageSync('communityPosts', defaultPosts)
+      this.setData({ posts: defaultPosts })
+    } else {
+      this.setData({ posts: savedPosts })
+    }
+  },
+
+  checkIn: function() {
+    if (this.data.isChecked) {
+      wx.showToast({ title: '今日已签到', icon: 'none' })
+      return
+    }
+    
+    // 更新签到状态
+    const today = new Date().toDateString()
+    const checkins = wx.getStorageSync('checkins') || {}
+    checkins[today] = true
+    wx.setStorageSync('checkins', checkins)
+    
+    // 更新页面数据
+    this.setData({
+      isChecked: true,
+      continuousDays: this.data.continuousDays + 1
+    })
+    
+    // 显示签到成功提示
+    wx.showToast({ title: '签到成功', icon: 'success' })
+    
+    // 可以在此处添加签到奖励逻辑
+  },
+
+  onInputContent: function(e) {
+    this.setData({ postContent: e.detail.value })
+  },
+
+  createPost: function() {
+    if (!this.data.postContent.trim()) {
+      wx.showToast({ title: '请输入内容', icon: 'none' })
+      return
+    }
+    
+    const newPost = {
+      id: Date.now(),
+      user: {
+        name: app.globalData.userInfo ? app.globalData.userInfo.nickName : '匿名用户',
+        avatar: app.globalData.userInfo ? app.globalData.userInfo.avatarUrl : '../../images/avatar-default.png'
+      },
+      content: this.data.postContent,
+      duration: this.data.todayDuration,
+      focusTime: new Date().toLocaleTimeString(),
+      time: '刚刚',
+      likes: 0,
+      isLiked: false,
+      comments: []
+    }
+    
+    // 更新帖子列表
+    const posts = [newPost, ...this.data.posts]
+    this.setData({
+      posts,
+      postContent: ''
+    })
+    
+    // 保存到本地存储
+    wx.setStorageSync('communityPosts', posts)
+    
+    wx.showToast({ title: '发布成功', icon: 'success' })
+  },
+
+  toggleLike: function(e) {
+    const postId = e.currentTarget.dataset.id
+    const posts = this.data.posts.map(post => {
+      if (post.id === postId) {
+        return {
+          ...post,
+          likes: post.isLiked ? post.likes - 1 : post.likes + 1,
+          isLiked: !post.isLiked
+        }
+      }
+      return post
+    })
+    
+    this.setData({ posts })
+    wx.setStorageSync('communityPosts', posts)
+  },
+
+  showComments: function(e) {
+    const postId = e.currentTarget.dataset.id
+    this.setData({
+      showCommentId: this.data.showCommentId === postId ? null : postId
+    })
+  },
+
+  onCommentInput: function(e) {
+    this.setData({
+      currentComment: e.detail.value
+    })
+  },
+
+  addComment: function(e) {
+    const postId = e.currentTarget.dataset.id
+    if (!this.data.currentComment.trim()) {
+      wx.showToast({ title: '请输入评论内容', icon: 'none' })
+      return
+    }
+    
+    const posts = this.data.posts.map(post => {
+      if (post.id === postId) {
+        return {
+          ...post,
+          comments: [
+            ...post.comments,
+            {
+              id: Date.now(),
+              user: app.globalData.userInfo ? app.globalData.userInfo.nickName : '匿名用户',
+              content: this.data.currentComment
+            }
+          ]
+        }
+      }
+      return post
+    })
+    
+    this.setData({
+      posts,
+      currentComment: '',
+      showCommentId: postId // 保持评论框打开
+    })
+    
+    wx.setStorageSync('communityPosts', posts)
+  },
+
+  // 页面分享功能
+  onShareAppMessage: function() {
+    return {
+      title: '我在番茄社区分享了我的专注成果,快来看看吧!',
+      path: '/pages/community/community',
+      imageUrl: '../../images/share.png' // 需要准备分享图片
+    }
+  }
+})

+ 3 - 0
pages/community/community.json

@@ -0,0 +1,3 @@
+{
+  "navigationBarTitleText": "番茄社区"
+}

+ 110 - 0
pages/community/community.wxml

@@ -0,0 +1,110 @@
+<!-- pages/community/community.wxml -->
+<view class="container" style="{{containerStyle}}">
+  <!-- 签到区域 -->
+  <view class="checkin-section" style="background-color: {{theme.bgColor}}">
+    <view class="checkin-info">
+      <text>连续签到:{{continuousDays}}天</text>
+      <text>累计专注:{{totalDuration}}分钟</text>
+    </view>
+    <button 
+      class="checkin-btn" 
+      bindtap="checkIn"
+      style="background-color: {{isChecked ? '#ccc' : theme.primaryColor}}; color: {{theme.textColor}}"
+    >{{isChecked ? '今日已签到' : '立即签到'}}</button>
+  </view>
+
+  <!-- 发布区域 -->
+  <view class="post-creator" style="background-color: {{theme.bgColor}}">
+    <textarea 
+      placeholder="分享您的专注成果..." 
+      placeholder-class="placeholder"
+      bindinput="onInputContent"
+      value="{{postContent}}"
+      style="color: {{theme.textColor}}"
+    ></textarea>
+    <view class="post-meta">
+      <text class="duration-text">今日专注: {{todayDuration}}分钟</text>
+      <button 
+        class="post-button" 
+        bindtap="createPost"
+        style="background-color: {{theme.primaryColor}}; color: {{theme.textColor}}"
+      >发布</button>
+    </view>
+  </view>
+
+  <!-- 帖子列表 -->
+  <view class="posts-list">
+    <block wx:for="{{posts}}" wx:key="id">
+      <view class="post-card" style="background-color: {{theme.bgColor}}">
+        <!-- 用户信息 -->
+        <view class="post-header">
+          <image class="avatar" src="{{item.user.avatar}}"></image>
+          <view class="user-info">
+            <text class="username" style="color: {{theme.textColor}}">{{item.user.name}}</text>
+            <text class="post-time" style="color: {{theme.textColor}}">{{item.time}}</text>
+          </view>
+        </view>
+        
+        <!-- 帖子内容 -->
+        <view class="post-content" style="color: {{theme.textColor}}">
+          {{item.content}}
+        </view>
+        
+        <!-- 专注数据 -->
+        <view class="focus-data" style="background-color: {{theme.primaryColor}}; opacity: 0.5">
+          <text>专注时长: {{item.duration}}分钟</text>
+          <text>完成于: {{item.focusTime}}</text>
+        </view>
+        
+        <!-- 互动区域 -->
+        <view class="post-actions">
+          <view 
+            class="action like" 
+            bindtap="toggleLike"
+            data-id="{{item.id}}"
+          >
+            <image src="{{item.isLiked ? '../../images/liked.png' : '../../images/like.png'}}"></image>
+            <text style="color: {{item.isLiked ? theme.primaryColor : theme.textColor}}">{{item.likes}}</text>
+          </view>
+          
+          <view 
+            class="action comment" 
+            bindtap="showComments"
+            data-id="{{item.id}}"
+          >
+            <image src="../../images/comment.png"></image>
+            <text style="color: {{theme.textColor}}">{{item.comments.length}}</text>
+          </view>
+        </view>
+        
+        <!-- 评论区域 -->
+        <view class="comments-section" wx:if="{{showCommentId === item.id}}">
+          <view class="comments-list">
+            <block wx:for="{{item.comments}}" wx:key="id">
+              <view class="comment-item">
+                <text class="comment-user" style="color: {{theme.primaryColor}}">{{item.user}}:</text>
+                <text class="comment-content" style="color: {{theme.textColor}}">{{item.content}}</text>
+              </view>
+            </block>
+          </view>
+          
+          <view class="comment-input">
+            <input 
+              placeholder="写评论..." 
+              placeholder-class="placeholder"
+              bindinput="onCommentInput"
+              data-id="{{item.id}}"
+              style="color: {{theme.textColor}}"
+            />
+            <button 
+              class="send-button"
+              bindtap="addComment"
+              data-id="{{item.id}}"
+              style="background-color: {{theme.primaryColor}}; color: {{theme.textColor}}"
+            >发送</button>
+          </view>
+        </view>
+      </view>
+    </block>
+  </view>
+</view>

+ 187 - 0
pages/community/community.wxss

@@ -0,0 +1,187 @@
+/* pages/community/community.wxss */
+.container {
+  padding: 20rpx;
+}
+
+.checkin-section {
+  width: 600rpx;
+  padding: 20rpx;
+  border-radius: 12rpx;
+  margin-bottom: 20rpx;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.checkin-info {
+  display: flex;
+  flex-direction: column;
+}
+
+.checkin-info text {
+  font-size: 24rpx;
+  margin-bottom: 10rpx;
+}
+
+.checkin-btn {
+  padding: 0 30rpx;
+  height: 60rpx;
+  line-height: 60rpx;
+  border-radius: 30rpx;
+  text-align: center;
+}
+
+.post-creator {
+  width: 600rpx;
+  padding: 20rpx;
+  border-radius: 12rpx;
+  margin-bottom: 20rpx;
+}
+
+.post-creator textarea {
+  width: 100%;
+  height: 150rpx;
+  font-size: 28rpx;
+  margin-bottom: 15rpx;
+  padding: 15rpx;
+  box-sizing: border-box;
+  border-radius: 12rpx;
+}
+
+.post-meta {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.duration-text {
+  font-size: 24rpx;
+  color: #999;
+}
+
+.post-button {
+  font-size: 26rpx;
+  padding: 0 30rpx;
+  height: 60rpx;
+  line-height: 60rpx;
+  border-radius: 30rpx;
+  text-align: center;
+}
+
+.post-card {
+  border-radius: 12rpx;
+  padding: 20rpx;
+  margin-bottom: 20rpx;
+}
+
+.post-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 15rpx;
+}
+
+.avatar {
+  width: 80rpx;
+  height: 80rpx;
+  border-radius: 50%;
+  margin-right: 15rpx;
+}
+
+.user-info {
+  display: flex;
+  flex-direction: column;
+}
+
+.username {
+  font-size: 28rpx;
+  font-weight: bold;
+}
+
+.post-time {
+  font-size: 22rpx;
+  color: #999;
+  margin-top: 5rpx;
+}
+
+.post-content {
+  font-size: 28rpx;
+  line-height: 1.5;
+  margin-bottom: 15rpx;
+}
+
+.focus-data {
+  width: 600rpx;
+  padding: 15rpx;
+  border-radius: 8rpx;
+  margin-bottom: 15rpx;
+  display: flex;
+  justify-content: space-between;
+  font-size: 24rpx;
+}
+
+.post-actions {
+  display: flex;
+  padding-top: 15rpx;
+  border-top: 1rpx solid #eee;
+}
+
+.action {
+  display: flex;
+  align-items: center;
+  margin-right: 30rpx;
+}
+
+.action image {
+  width: 40rpx;
+  height: 40rpx;
+  margin-right: 10rpx;
+}
+
+.comments-section {
+  margin-top: 15rpx;
+  padding-top: 15rpx;
+  border-top: 1rpx solid #f0f0f0;
+}
+
+.comments-list {
+  margin-bottom: 15rpx;
+}
+
+.comment-item {
+  margin-bottom: 10rpx;
+  font-size: 26rpx;
+  line-height: 1.5;
+}
+
+.comment-user {
+  font-weight: bold;
+  margin-right: 10rpx;
+}
+
+.comment-input {
+  display: flex;
+  margin-top: 15rpx;
+}
+
+.comment-input input {
+  flex: 1;
+  height: 60rpx;
+  padding: 0 15rpx;
+  background-color: #f5f5f5;
+  border-radius: 30rpx;
+  font-size: 26rpx;
+}
+
+.send-button {
+  width: 120rpx;
+  height: 60rpx;
+  line-height: 60rpx;
+  margin-left: 15rpx;
+  border-radius: 30rpx;
+  font-size: 26rpx;
+  text-align: center;
+}
+
+.placeholder {
+  color: #999;
+}

+ 537 - 0
pages/index/index.js

@@ -0,0 +1,537 @@
+//index.js
+const app = getApp()
+const util = require('../../utils/util.js')
+
+Page({
+  data: {
+    userInfo:{},
+    hasUserInfo:false,
+    clockShow: false,
+    clockHeight: 0,
+    time: '25',
+    mTime: 1500000,
+    timeStr: '25:00',
+    rate: '',
+    timer: null,
+    cateArr: [
+      { icon: 'work', text: '工作' },
+      { icon: 'study', text: "学习" },
+      { icon: 'think', text: '思考' },
+      { icon: 'write', text: '写作' },
+      { icon: 'sport', text: '运动' },
+      { icon: 'read', text: "阅读" }
+    ],
+    cateActive: null,
+    okShow: false,
+    pauseShow: true,
+    continueCancleShow: false,
+    customTask: '',
+    showCustomInput: false,
+    plans: [],
+    selectedPlan: null,
+    editMode: false,
+    editPlanId: null,
+    currentTask: '',
+    userInfo: null,
+    isLoggedIn: false,
+    theme: {},
+    containerStyle: '',
+    buttonStyle: '', // 按钮动态样式
+    timerStyle: '',  // 计时器动态样式
+    progressStyle: '', // 进度条动态样式
+    photoPath: '',
+    isTimerRunning: false, // 新增:计时器是否正在运行
+    startTime: 0, // 新增:开始时间戳
+    endTime: 0, // 新增:结束时间戳
+    remainingTime: 0 // 新增:剩余时间(毫秒)
+  },
+
+  onLoad: function () {
+    const res = wx.getSystemInfoSync();
+    const rate = 750 / res.windowWidth;
+    this.setData({
+      rate: rate,
+      clockHeight: res.windowHeight * 2,
+      time: '25',
+      mTime: 25 * 60 * 1000,
+      timeStr: '25:00'
+    });
+
+    const plans = wx.getStorageSync('plans') || [];
+    this.setData({ plans });
+    
+    if (app.globalData.userInfo) {
+      this.setData({
+        userInfo: app.globalData.userInfo,
+        hasUserInfo: true,
+        isLoggedIn: true
+      });
+    } else {
+      // 没有用户信息,引导用户授权
+      wx.showModal({
+        title: '授权提示',
+        content: '需要获取您的用户信息以提供更好的服务,请授权登录',
+        success: res => {
+          if (res.confirm) {
+            wx.getUserProfile({
+              desc: '用于完善用户资料',
+              success: res => {
+                app.globalData.userInfo = res.userInfo;
+                this.setData({
+                  userInfo: res.userInfo,
+                  hasUserInfo: true,
+                  isLoggedIn: true
+                });
+              }
+            });
+          }
+        }
+      });
+    }
+    if (app.eventBus) {
+      app.eventBus.on('userInfoChange', (userInfo) => {
+        this.setData({
+          userInfo: userInfo,
+          isLoggedIn: !!userInfo
+        });
+      });
+
+      app.eventBus.on('themeChange', (theme) => {
+        this.setData({ theme });
+        this.updateThemeStyles();
+      });
+    }
+
+    this.setData({
+      userInfo: app.globalData.userInfo,
+      isLoggedIn: !!app.globalData.userInfo,
+      theme: app.globalData.theme
+    });
+
+    this.updateThemeStyles();
+  },
+
+  onShow: function() {
+    this.updateNavigationBarColor();
+  },
+
+  onUnload: function() {
+    if (this.data.timer) {
+      clearInterval(this.data.timer);
+      this.setData({ timer: null });
+    }
+
+    if (app.eventBus) {
+      app.eventBus.off('userInfoChange');
+      app.eventBus.off('themeChange');
+    }
+  },
+
+  updateNavigationBarColor: function() {
+    const { theme } = this.data;
+    if (theme && theme.primaryColor && theme.textColor) {
+      wx.setNavigationBarColor({
+        frontColor: theme.textColor === '#333' ? '#000000' : '#ffffff',
+        backgroundColor: theme.primaryColor,
+        animation: {
+          duration: 300,
+          timingFunc: 'easeIn'
+        }
+      });
+    }
+  },
+
+  updateThemeStyles: function () {
+    const { theme } = this.data;
+    const { bgColor, textColor, primaryColor } = theme;
+    
+    this.setData({
+      containerStyle: `background-color: ${bgColor}; color: ${textColor};`,
+      buttonStyle: `background-color: ${primaryColor}; color: ${textColor};`,
+      timerStyle: `color: ${primaryColor};`,
+      progressStyle: primaryColor,
+      sliderStyle: primaryColor // 新增滑块主题色
+    });
+  
+    this.updateNavigationBarColor();
+  },
+
+  slideChange: function (e) {
+    const minutes = e.detail.value;
+    this.setData({ 
+      time: minutes.toString(),
+      timeStr: minutes >= 10 ? minutes + ':00' : '0' + minutes + ':00',
+      mTime: minutes * 60 * 1000
+    });
+  },
+
+  clickCate: function (e) {
+    this.setData({
+      cateActive: e.currentTarget.dataset.index,
+      showCustomInput: false,
+      selectedPlan: null,
+      editMode: false
+    });
+  },
+
+  showCustomTaskInput: function() {
+    this.setData({
+      showCustomInput: true,
+      cateActive: null,
+      selectedPlan: null,
+      customTask: '',
+      editMode: false
+    });
+  },
+
+  inputCustomTask: function(e) {
+    this.setData({ customTask: e.detail.value });
+  },
+
+  savePlan: function() {
+    if (!this.data.customTask.trim()) {
+      wx.showToast({ title: '请输入任务内容', icon: 'none' });
+      return;
+    }
+
+    let updatedPlans = [];
+
+    if (this.data.editMode) {
+      updatedPlans = this.data.plans.map(plan => {
+        if (plan.id === this.data.editPlanId) {
+          return { ...plan, content: this.data.customTask, time: this.data.time };
+        }
+        return plan;
+      });
+      wx.showToast({ title: '计划更新成功', icon: 'success' });
+    } else {
+      const newPlan = {
+        id: Date.now(),
+        content: this.data.customTask,
+        time: this.data.time,
+        createdAt: new Date().toISOString()
+      };
+      updatedPlans = [...this.data.plans, newPlan];
+      wx.showToast({ title: '计划添加成功', icon: 'success' });
+    }
+
+    this.setData({
+      plans: updatedPlans,
+      customTask: '',
+      showCustomInput: false,
+      editMode: false,
+      editPlanId: null
+    });
+    wx.setStorageSync('plans', updatedPlans);
+  },
+
+  selectPlan: function(e) {
+    const { index } = e.currentTarget.dataset;
+    const selectedPlan = this.data.plans[index];
+    this.setData({
+      time: selectedPlan.time,
+      customTask: selectedPlan.content,
+      cateActive: null,
+      showCustomInput: false,
+      selectedPlan: selectedPlan,
+      editMode: false
+    });
+  },
+
+  editPlan: function(e) {
+    const { index } = e.currentTarget.dataset;
+    const planToEdit = this.data.plans[index];
+    this.setData({
+      time: planToEdit.time,
+      customTask: planToEdit.content,
+      showCustomInput: true,
+      editMode: true,
+      editPlanId: planToEdit.id,
+      selectedPlan: null
+    });
+  },
+
+  deletePlan: function(e) {
+    const { index } = e.currentTarget.dataset;
+    const planToDelete = this.data.plans[index];
+    wx.showModal({
+      title: '确认删除',
+      content: `确定要删除计划"${planToDelete.content}"吗?`,
+      success: (res) => {
+        if (res.confirm) {
+          const updatedPlans = this.data.plans.filter((_, i) => i !== index);
+          this.setData({ plans: updatedPlans, selectedPlan: null });
+          wx.setStorageSync('plans', updatedPlans);
+          wx.showToast({ title: '计划已删除', icon: 'success' });
+        }
+      }
+    });
+  },
+
+  start: function () {
+    let taskContent = '';
+    let time = parseInt(this.data.time);
+    let categoryIndex = this.data.cateActive;
+
+    if (this.data.selectedPlan) {
+      taskContent = this.data.selectedPlan.content;
+      time = parseInt(this.data.selectedPlan.time);
+    } else if (this.data.showCustomInput && this.data.customTask) {
+      taskContent = this.data.customTask;
+      categoryIndex = null;
+    } else if (this.data.cateActive !== null) {
+      taskContent = this.data.cateArr[this.data.cateActive].text;
+    } else {
+      wx.showToast({ title: '请选择一个任务', icon: 'none' });
+      return;
+    }
+
+    this.setData({
+      clockShow: true,
+      mTime: time * 60 * 1000,
+      timeStr: time >= 10 ? time + ':00' : '0' + time + ':00',
+      time: time.toString(),
+      currentTask: taskContent,
+      cateActive: categoryIndex
+    });
+
+    this.drawBg();
+    this.drawActive();
+    this.showPhotoConfirm();
+  },
+
+  drawBg: function () {
+    const lineWidth = 6 / this.data.rate;
+    const ctx = wx.createCanvasContext('progress_bg');
+    const center = 250 / this.data.rate;
+    const radius = center - 2 * lineWidth;
+
+    ctx.setLineWidth(lineWidth);
+    ctx.setStrokeStyle('#f0f0f0');
+    ctx.setLineCap('round');
+    ctx.beginPath();
+    ctx.arc(center, center, radius, 0, 2 * Math.PI, false);
+    ctx.stroke();
+    ctx.draw();
+  },
+
+  drawActive: function () {
+    const _this = this;
+    const lineWidth = 6 / this.data.rate;
+    const center = 250 / this.data.rate;
+    const radius = center - 2 * lineWidth;
+
+    const timer = setInterval(function () {
+      const angle = 1.5 + 2 * (_this.data.time * 60 * 1000 - _this.data.mTime) / (_this.data.time * 60 * 1000);
+      const currentTime = _this.data.mTime - 100;
+      _this.setData({ mTime: currentTime });
+
+      if (angle < 3.5) {
+        if (currentTime % 1000 == 0) {
+          let timeStr1 = currentTime / 1000;
+          let timeStr2 = parseInt(timeStr1 / 60);
+          let timeStr3 = (timeStr1 - timeStr2 * 60) >= 10 ? 
+                        (timeStr1 - timeStr2 * 60) : '0' + (timeStr1 - timeStr2 * 60);
+          timeStr2 = timeStr2 >= 10 ? timeStr2 : '0' + timeStr2;
+          _this.setData({ timeStr: timeStr2 + ':' + timeStr3 });
+        }
+
+        const ctx = wx.createCanvasContext('progress_active');
+        ctx.setLineWidth(lineWidth);
+        ctx.setStrokeStyle(_this.data.theme.primaryColor);
+        ctx.setLineCap('round');
+        ctx.beginPath();
+        ctx.arc(center, center, radius, 1.5 * Math.PI, angle * Math.PI, false);
+        ctx.stroke();
+        ctx.draw();
+      } else {
+        const logs = wx.getStorageSync('logs') || [];
+        logs.unshift({
+          date: util.formatTime(new Date),
+          task: _this.data.currentTask,
+          cate: _this.data.cateActive,
+          time: _this.data.time
+        });
+        wx.setStorageSync('logs', logs);
+        _this.setData({
+          timeStr: '00:00',
+          okShow: true,
+          pauseShow: false,
+          continueCancleShow: false
+        });
+        clearInterval(timer);
+      }
+    }, 100);
+    _this.setData({ timer: timer });
+  },
+  showPhotoConfirm: function() {
+    wx.showModal({
+      title: '打卡拍照',
+      content: '开始专注前需要拍照打卡确认',
+      confirmText: '拍照',
+      cancelText: '暂不',
+      success: (res) => {
+        if (res.confirm) {
+          this.checkCameraPermission();
+        } else {
+          // 取消拍照则重置界面
+          this.resetTimerUI();
+          wx.showToast({ title: '拍照打卡已取消', icon: 'none' });
+        }
+      }
+    });
+  },
+
+  checkCameraPermission: function() {
+    wx.getSetting({
+      success: (res) => {
+        if (!res.authSetting['scope.camera']) {
+          wx.authorize({
+            scope: 'scope.camera',
+            success: () => this.takePhoto(),
+            fail: () => {
+              wx.showModal({
+                title: '权限申请',
+                content: '需要授予相机权限才能拍照打卡',
+                success: (res) => {
+                  if (res.confirm) wx.openSetting();
+                  else this.resetTimerUI(); // 拒绝权限则重置界面
+                }
+              });
+            }
+          });
+        } else {
+          this.takePhoto();
+        }
+      }
+    });
+  },
+
+  takePhoto: function () {
+    wx.chooseImage({
+      count: 1,
+      sourceType: ['camera'],
+      success: (res) => {
+        const photoPath = res.tempFilePaths[0];
+        this.setData({ photoPath });
+        wx.setStorageSync('checkInPhoto', photoPath);
+        wx.showToast({ title: '拍照成功' });
+        
+        // 拍照成功后才开始计时
+        this.startTimer();
+      },
+      fail: (err) => {
+        console.error('拍照失败', err);
+        wx.showToast({ title: '拍照失败', icon: 'none' });
+        this.resetTimerUI(); // 拍照失败则重置界面
+      }
+    });
+  },
+
+  startTimer: function() {
+    // 记录开始时间和预计结束时间
+    const now = Date.now();
+    this.setData({
+      isTimerRunning: true,
+      startTime: now,
+      endTime: now + this.data.remainingTime
+    });
+    
+    // 更新计时器显示
+    this.updateTimer();
+  },
+
+  updateTimer: function() {
+    if (!this.data.isTimerRunning) return;
+    
+    const now = Date.now();
+    let remaining = this.data.endTime - now;
+    
+    // 处理计时结束
+    if (remaining <= 0) {
+      this.finishTimer();
+      return;
+    }
+    
+    // 更新剩余时间显示
+    const minutes = Math.floor(remaining / 60000);
+    const seconds = Math.floor((remaining % 60000) / 1000);
+    const timeStr = `${minutes >= 10 ? minutes : '0' + minutes}:${seconds >= 10 ? seconds : '0' + seconds}`;
+    
+    this.setData({
+      remainingTime: remaining,
+      timeStr: timeStr
+    });
+    
+    // 更新进度环
+    this.drawActive();
+    
+    // 每秒更新一次
+    this.timerInterval = setTimeout(() => {
+      this.updateTimer();
+    }, 1000);
+  },
+
+  // 重置计时器UI
+  resetTimerUI: function() {
+    this.setData({
+      clockShow: false,
+      isTimerRunning: false,
+      photoPath: ''
+    });
+    clearTimeout(this.timerInterval);
+  },
+
+  // 完成计时
+  finishTimer: function() {
+    this.setData({
+      isTimerRunning: false,
+      timeStr: '00:00'
+    });
+    clearTimeout(this.timerInterval);
+    
+    // 显示完成提示
+    wx.showToast({
+      title: '专注完成!',
+      icon: 'success',
+      duration: 2000
+    });
+  },
+  pause: function () {
+    clearInterval(this.data.timer);
+    this.setData({
+      pauseShow: false,
+      continueCancleShow: true,
+      okShow: false
+    });
+  },
+
+  continue: function () {
+    this.drawActive();
+    this.setData({
+      pauseShow: true,
+      continueCancleShow: false,
+      okShow: false
+    });
+  },
+
+  cancle: function () {
+    clearInterval(this.data.timer);
+    this.setData({
+      pauseShow: true,
+      continueCancleShow: false,
+      okShow: false,
+      clockShow: false
+    });
+  },
+
+  ok: function () {
+    clearInterval(this.data.timer);
+    this.setData({
+      pauseShow: true,
+      continueCancleShow: false,
+      okShow: false,
+      clockShow: false
+    });
+  }
+})

+ 3 - 0
pages/index/index.json

@@ -0,0 +1,3 @@
+{
+  "navigationBarTitleText": "番茄时钟"
+}

+ 180 - 0
pages/index/index.wxml

@@ -0,0 +1,180 @@
+<!--index.wxml-->
+<view class="container" hidden='{{clockShow}}' style="{{containerStyle}}">
+
+  <!-- 时间滑块 -->
+  <view class="time-picker-container" style="{{containerStyle}}">
+    <view class="time-picker-header">
+      <text class="time-picker-title" style="{{containerStyle}}">专注时长</text>
+      <view class="time-value-display">
+        <text class="time-number">{{time}}</text>
+        <text class="time-unit">分钟</text>
+      </view>
+    </view>
+    
+    <view class="slider-container" style="{{containerStyle}}">
+      <slider 
+        min="1" 
+        max="120" 
+        step="1" 
+        value="{{time}}" 
+        activeColor="{{sliderStyle || '#FF6B81'}}" 
+        backgroundColor="#F0F0F0"
+        block-color="{{sliderStyle || '#FF6B81'}}" 
+        block-size="28"
+        bindchange="slideChange"
+      />
+      
+      <view class="slider-scale" style="{{containerStyle}}">
+        <view class="scale-mark" style="left: 0%">
+          <view class="scale-line"></view>
+          <text class="scale-label">1</text>
+        </view>
+        <view class="scale-mark" style="left: 12.5%">
+          <view class="scale-line"></view>
+          <text class="scale-label">15</text>
+        </view>
+        <view class="scale-mark" style="left: 25%">
+          <view class="scale-line"></view>
+          <text class="scale-label">30</text>
+        </view>
+        <view class="scale-mark" style="left: 37.5%">
+          <view class="scale-line"></view>
+          <text class="scale-label">45</text>
+        </view>
+        <view class="scale-mark" style="left: 50%">
+          <view class="scale-line major" style="background-color: {{sliderStyle || '#FF6B81'}}"></view>
+          <text class="scale-label major-label" style="color: {{sliderStyle || '#FF6B81'}}">60</text>
+        </view>
+        <view class="scale-mark" style="left: 75%">
+          <view class="scale-line"></view>
+          <text class="scale-label">90</text>
+        </view>
+        <view class="scale-mark" style="left: 100%">
+          <view class="scale-line major" style="background-color: {{sliderStyle || '#FF6B81'}}"></view>
+          <text class="scale-label major-label" style="color: {{sliderStyle || '#FF6B81'}}">120</text>
+        </view>
+      </view>
+    </view>
+  </view>
+
+  <!-- 任务选择区域 -->
+  <view class='content-section' style="{{containerStyle}}">
+    <view class='section-header'>
+      <text class="section-title" style="{{containerStyle}}">选择一个任务</text>
+      <text class="section-subtitle" style="{{containerStyle}}">在接下来的{{time}}分钟内,你将专注做这件事</text>
+    </view>
+
+    <view class='task-categories' style="{{containerStyle}}">
+      <view wx:for="{{cateArr}}" class='category-item' wx:key="cate" bindtap='clickCate' data-index="{{index}}">
+        <view class='category-icon {{index == cateActive && !selectedPlan ? "active":""}}'>
+          <image src='../../images/{{item.icon}}.png'></image>
+        </view>
+        <view class='category-name'>{{item.text}}</view>
+      </view>
+      <view class='category-item' bindtap='showCustomTaskInput'>
+        <view class='category-icon {{showCustomInput && !selectedPlan ? "active":""}}'>
+          <text class="custom-icon">+</text>
+        </view>
+        <view class='category-name'>自定义</view>
+      </view>
+    </view>
+
+    <!-- 自定义任务输入区域 -->
+    <view class='custom-task-section' wx:if="{{showCustomInput}}" style="{{containerStyle}}">
+      <input placeholder="请输入任务内容..." style="{{containerStyle}}" placeholder-class="placeholder" 
+             value="{{customTask}}" bindinput="inputCustomTask" class="task-input"/>
+      <view class="action-buttons">
+        <button bindtap="savePlan" class="save-btn">{{editMode ? '更新计划' : '保存计划'}}</button>
+        <button bindtap="start" class="start-btn" wx:if="{{customTask}}">直接开始</button>
+      </view>
+    </view>
+
+    <!-- 当前选择的任务展示 -->
+    <view class="current-task-card" wx:if="{{selectedPlan || (cateActive !== null) || (showCustomInput && customTask)}}">
+      <view class="task-card-header">
+        <text class="card-title">当前任务</text>
+        <view class="task-time-badge" style="{{containerStyle}}">{{time}}分钟</view>
+      </view>
+      <view class="task-content" style="{{containerStyle}}">
+        {{selectedPlan ? selectedPlan.content : (cateActive !== null ? cateArr[cateActive].text : customTask)}}
+      </view>
+      <view class="task-actions" wx:if="{{selectedPlan}}">
+        <view class='action-btn edit-btn' bindtap='editPlan' data-index="{{plans.indexOf(selectedPlan)}}" style="{{containerStyle}}">
+          <image src="../../images/edit.png" class="action-icon"></image>
+          <text>编辑</text>
+        </view>
+        <view class='action-btn delete-btn' bindtap='deletePlan' data-index="{{plans.indexOf(selectedPlan)}}" style="{{containerStyle}}">
+          <image src="../../images/delete.png" class="action-icon"></image>
+          <text>删除</text>
+        </view>
+      </view>
+      <button bindtap="start" class="start-focus-btn" style="background-color: {{theme.primaryColor}}; color: {{theme.textColor}}">开始专注</button>
+    </view>
+  </view>
+
+  <!-- 计划列表 -->
+  <view class='content-section' wx:if="{{plans.length > 0}}" style="{{containerStyle}}">
+    <view class='section-header'>
+      <text class="section-title" style="{{containerStyle}}">我的计划</text>
+      <text class="plan-count">共{{plans.length}}项任务</text>
+    </view>
+    
+    <view wx:for="{{plans}}" wx:key="id" class='plan-card {{selectedPlan && selectedPlan.id == item.id ? "active":""}}'>
+      <view class='plan-content' bindtap='selectPlan' data-index="{{index}}">
+        <view class="plan-text" style="{{containerStyle}}">{{item.content}}</view>
+        <view class="plan-meta">
+          <text class="plan-time">{{item.time}}分钟</text>
+          <text class="plan-date">{{util.formatDate(item.createdAt)}}</text>
+        </view>
+      </view>
+      <view class='plan-actions'>
+        <view class='action-btn edit-btn' bindtap='editPlan' data-index="{{index}}" style="{{containerStyle}}">
+          <image src="../../images/edit.png" class="action-icon"></image>
+          <text>编辑</text>
+        </view>
+        <view class='action-btn delete-btn' bindtap='deletePlan' data-index="{{index}}" style="{{containerStyle}}">
+          <image src="../../images/delete.png" class="action-icon"></image>
+          <text>删除</text>
+        </view>
+      </view>
+    </view>
+  </view>
+
+ 
+  <!-- 开始按钮 -->
+  <button class='primary-btn' bindtap='start' wx:if="{{!selectedPlan && cateActive === null && !showCustomInput}}" style="background-color: {{theme.primaryColor}}; color: {{theme.textColor}}">
+    开始专注
+  </button>
+  <view class='back' style="{{containerStyle}}"></view>
+</view>
+
+<!-- 专注时钟界面 -->
+<view class='focus-clock' hidden='{{!clockShow}}' style='height:{{clockHeight}}rpx'>
+  <view class='progress-container'>
+    <canvas canvas-id='progress_bg' class='progress-bg'></canvas>
+    <canvas canvas-id='progress_active' class='progress-active'></canvas>
+    <view class='time-display'>{{timeStr}}</view>
+    <view class='task-name'>{{currentTask}}</view>
+  </view>
+  <view class='control-buttons'>
+    <view class='action-button finish-btn' bindtap='ok' wx:if="{{okShow}}">
+      <image src="../../images/check.png" class="btn-icon"></image>
+      <text>完成</text>
+    </view>
+    <view class='action-button pause-btn' bindtap='pause' wx:if="{{pauseShow}}">
+      <image src="../../images/pause.png" class="btn-icon"></image>
+      <text>暂停</text>
+    </view>
+    <view class='action-buttons-group' wx:if="{{continueCancleShow}}">
+      <view class='action-button continue-btn' bindtap='continue'>
+        <image src="../../images/play.png" class="btn-icon"></image>
+        <text>继续</text>
+      </view>
+      <view class='action-button cancel-btn' bindtap='cancle'>
+        <image src="../../images/close.png" class="btn-icon"></image>
+        <text>放弃</text>
+      </view>
+    </view>
+  </view>
+
+</view>

+ 534 - 0
pages/index/index.wxss

@@ -0,0 +1,534 @@
+/**index.wxss**/
+page {
+  background-color: #F9F9F9;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
+  color: #333;
+  padding-bottom: 40rpx;
+
+}
+/* pages/index/index.wxss */
+/* 已有内容... */
+button {
+  margin: 20rpx auto;
+  width: 200rpx;
+  height: 80rpx;
+  line-height: 80rpx;
+  text-align: center;
+  border: 2rpx solid #E7624F;
+  color: #E7624F;
+  border-radius: 20rpx;
+}
+
+image {
+  width: 100rpx;
+  height: 100rpx;
+  border-radius: 50%;
+  margin: 20rpx auto;
+}
+
+text {
+  font-size: 30rpx;
+  text-align: center;
+  display: block;
+}
+.container {
+  padding: 0 20rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+/* ================= 统一内容区域样式 ================= */
+.time-picker-container,
+.content-section {
+  width: 100%;
+  max-width: 710rpx;
+  background: #FFFFFF;
+  border-radius: 16rpx;
+  padding: 30rpx;
+  margin-bottom: 30rpx;
+  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
+}
+
+/* ================= 时间滑块 ================= */
+.time-picker-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 36rpx;
+}
+
+.time-picker-title {
+  font-size: 32rpx;
+  font-weight: 600;
+  color: #333333;
+}
+
+.time-value-display {
+  background: linear-gradient(135deg, #FF6B81 0%, #FF8E53 100%);
+  border-radius: 24rpx;
+  padding: 12rpx 24rpx;
+  display: flex;
+  align-items: baseline;
+  box-shadow: 0 4rpx 12rpx rgba(255, 107, 129, 0.3);
+}
+
+.time-number {
+  font-size: 36rpx;
+  font-weight: 700;
+  color: #FFFFFF;
+  margin-right: 8rpx;
+}
+
+.time-unit {
+  font-size: 24rpx;
+  color: rgba(255, 255, 255, 0.9);
+}
+
+.slider-container {
+  position: relative;
+  padding: 0 10rpx 40rpx;
+  width: 95%;
+}
+
+.slider-container slider {
+  width: 100% !important;
+  margin: 0;
+  padding: 0;
+}
+
+.slider-container .wx-slider-track {
+  width: 100% !important;
+  height: 8rpx !important;
+  border-radius: 4rpx !important;
+}
+
+.slider-container .wx-slider-handle {
+  width: 28rpx !important;
+  height: 28rpx !important;
+  margin-top: -11rpx !important;
+  border: 4rpx solid #FFFFFF !important;
+  box-shadow: 0 4rpx 12rpx rgba(255, 107, 129, 0.4) !important;
+}
+
+.slider-scale {
+  display: flex;
+  justify-content: space-between;
+  margin-top: 16rpx;
+  width: 100%;
+  position: relative;
+  height: 40rpx;
+}
+
+.scale-mark {
+  position: absolute;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  transform: translateX(-50%);
+}
+
+.scale-line {
+  width: 2rpx;
+  height: 12rpx;
+  background: #D8D8D8;
+  margin-bottom: 8rpx;
+}
+
+.scale-line.major {
+  height: 20rpx;
+  background: #FF6B81;
+}
+
+.scale-label {
+  font-size: 22rpx;
+  color: #999999;
+  font-weight: 400;
+  white-space: nowrap;
+}
+
+.scale-label.major-label {
+  color: #FF6B81;
+  font-weight: 500;
+}
+
+/* ================= 任务选择区域 ================= */
+.section-header {
+  margin-bottom: 30rpx;
+}
+
+.section-title {
+  font-size: 32rpx;
+  font-weight: 500;
+  color: #333;
+  display: block;
+  margin-bottom: 10rpx;
+}
+
+.section-subtitle {
+  font-size: 26rpx;
+  color: #999;
+}
+
+.task-categories {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+  margin-bottom: 20rpx;
+}
+
+.category-item {
+  width: 25%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  margin-bottom: 30rpx;
+}
+
+.category-icon {
+  width: 100rpx;
+  height: 100rpx;
+  border-radius: 50%;
+  background: #F5F5F5;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin-bottom: 15rpx;
+  transition: all 0.2s ease;
+}
+
+.category-icon image {
+  width: 50rpx;
+  height: 50rpx;
+}
+
+.category-icon.active {
+  background: #E7624F;
+}
+
+.category-icon.active image {
+  filter: brightness(0) invert(1);
+}
+
+.custom-icon {
+  font-size: 40rpx;
+  color: #999;
+}
+
+.category-icon.active .custom-icon {
+  color: #FFF;
+}
+
+.category-name {
+  font-size: 26rpx;
+  color: #666;
+}
+
+/* 自定义任务区域 */
+.custom-task-section {
+  margin-top: 20rpx;
+  padding-top: 20rpx;
+  border-top: 1rpx solid #EEE;
+}
+
+.task-input {
+  width: 100%;
+  height: 90rpx;
+  background: #FAFAFA;
+  border-radius: 12rpx;
+  padding: 0 25rpx;
+  font-size: 28rpx;
+  margin-bottom: 25rpx;
+  border: 1rpx solid #EEE;
+}
+
+.placeholder {
+  color: #CCC;
+}
+
+.action-buttons {
+  display: flex;
+  justify-content: space-between;
+}
+
+.save-btn, .start-btn {
+  width: 48%;
+  height: 80rpx;
+  line-height: 80rpx;
+  border-radius: 12rpx;
+  font-size: 28rpx;
+  margin: 0;
+  transition: all 0.2s ease;
+}
+
+.save-btn {
+  background: #E7624F;
+  color: #FFF;
+}
+
+.save-btn:active {
+  background: #E55C70;
+}
+
+.start-btn {
+  background: #4CAF50;
+  color: #FFF;
+}
+
+.start-btn:active {
+  background: #3E8E41;
+}
+
+/* 当前任务卡片 */
+.current-task-card {
+  background: #FFF8F9;
+  border-radius: 16rpx;
+  padding: 30rpx;
+  margin-top: 30rpx;
+  border-left: 6rpx solid #FF6B81;
+}
+
+.task-card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20rpx;
+}
+
+.card-title {
+  font-size: 28rpx;
+  font-weight: 500;
+  color: #FF6B81;
+}
+
+.task-time-badge {
+  background: #FF6B81;
+  color: #FFF;
+  padding: 5rpx 15rpx;
+  border-radius: 20rpx;
+  font-size: 24rpx;
+}
+
+.task-content {
+  font-size: 32rpx;
+  margin-bottom: 30rpx;
+  line-height: 1.4;
+  border-radius: 20rpx;
+}
+
+.start-focus-btn {
+  width: 100%;
+  height: 90rpx;
+  line-height: 90rpx;
+  background: #FF6B81;
+  color: #FFF;
+  border-radius: 12rpx;
+  font-size: 32rpx;
+  font-weight: 500;
+}
+
+.start-focus-btn:active {
+  background: #E55C70;
+}
+
+/* 计划列表 */
+.plan-count {
+  font-size: 26rpx;
+  color: #999;
+  margin-left: 10rpx;
+}
+
+.plan-card {
+  background: #FAFAFA;
+  border-radius: 12rpx;
+  padding: 25rpx;
+  margin-bottom: 20rpx;
+  display: flex;
+  justify-content: space-between;
+  transition: all 0.2s ease;
+}
+
+.plan-card.active {
+  background: #FFF8F9;
+  border-left: 6rpx solid #FF6B81;
+}
+
+.plan-content {
+  flex: 1;
+}
+
+.plan-text {
+  font-size: 28rpx;
+  margin-bottom: 10rpx;
+  line-height: 1.4;
+  border-radius: 20rpx;
+}
+
+
+.plan-meta {
+  display: flex;
+  align-items: center;
+}
+
+.plan-time {
+  font-size: 24rpx;
+  color: #FF6B81;
+  background: rgba(255, 107, 129, 0.1);
+  padding: 3rpx 12rpx;
+  border-radius: 10rpx;
+  margin-right: 15rpx;
+}
+
+.plan-date {
+  font-size: 24rpx;
+  color: #999;
+}
+
+.plan-actions {
+  display: flex;
+  align-items: center;
+}
+
+.action-btn {
+  width: 60rpx;
+  height: 60rpx;
+  border-radius: 50%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin-left: 15rpx;
+}
+
+.edit-btn {
+  background: rgba(74, 144, 226, 0.1);
+}
+
+.delete-btn {
+  background: rgba(255, 77, 79, 0.1);
+}
+
+.action-icon {
+  width: 30rpx;
+  height: 30rpx;
+}
+
+/* 主要按钮 */
+.primary-btn {
+  width: 100%;
+  max-width: 710rpx;
+  height: 90rpx;
+  line-height: 90rpx;
+  background: #FF6B81;
+  color: #FFF;
+  border-radius: 12rpx;
+  font-size: 32rpx;
+  font-weight: 500;
+  margin-top: 30rpx;
+}
+
+.primary-btn:active {
+  background: #E55C70;
+}
+
+/* 专注时钟界面 */
+.focus-clock {
+  background: #E7624F;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  color: #FFF;
+  width: 100%;
+  position: relative;
+}
+
+.progress-container {
+  position: relative;
+  width: 500rpx;
+  height: 500rpx;
+  margin: 0 auto 60rpx;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.progress-bg, .progress-active {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+}
+
+.time-display {
+  position: absolute;
+  top: 40%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  font-size: 72rpx;
+  font-weight: 300;
+  width: 100%;
+  text-align: center;
+}
+
+.task-name {
+  position: absolute;
+  top: 60%;
+  left: 50%;
+  transform: translateX(-50%);
+  font-size: 32rpx;
+  width: 100%;
+  text-align: center;
+  padding: 0 50rpx;
+  box-sizing: border-box;
+}
+
+.control-buttons {
+  width: 100%;
+  padding: 0 60rpx;
+  box-sizing: border-box;
+  position: absolute;
+  bottom: 100rpx;
+  display: flex;
+  justify-content: center;
+}
+
+.action-button {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  color: #FFF;
+  font-size: 28rpx;
+  margin: 0 30rpx;
+}
+
+.btn-icon {
+  width: 60rpx;
+  height: 60rpx;
+  margin-bottom: 10rpx;
+}
+
+.action-buttons-group {
+  display: flex;
+  justify-content: space-between;
+  width: 100%;
+}
+
+.finish-btn .btn-icon {
+  filter: brightness(0) invert(1);
+}
+
+.pause-btn .btn-icon {
+  filter: brightness(0) invert(1);
+}
+
+.continue-btn .btn-icon {
+  filter: brightness(0) invert(1);
+}
+
+.cancel-btn .btn-icon {
+  filter: brightness(0) invert(1);
+}
+.back{
+  height: 270rpx;
+  overflow: hidden;
+}

+ 196 - 0
pages/logs/logs.js

@@ -0,0 +1,196 @@
+//logs.js
+const util = require('../../utils/util.js')
+const app = getApp();
+
+Page({
+  data: {
+    logs: [],
+    activeIndex: 0,
+    dayList: [],
+    list: [],
+    sum: [
+      {
+        title: '今日番茄次数',
+        val: '0'
+      },
+      {
+        title: '累计番茄次数',
+        val: '0'
+      },
+      {
+        title: '今日专注时长',
+        val: '0分钟'
+      },
+      {
+        title: '累计专注时长',
+        val: '0分钟'
+      }
+    ],
+    cateArr: [
+      {
+        icon: 'work',
+        text: '工作'
+      },
+      {
+        icon: 'study',
+        text: "学习",
+      },
+      {
+        icon: 'think',
+        text: '思考'
+      },
+      {
+        icon: 'write',
+        text: '写作'
+      },
+      {
+        icon: 'sport',
+        text: '运动'
+      },
+      {
+        icon: 'read',
+        text: "阅读"
+      }
+    ],
+    containerStyle: '', // 新增动态样式属性
+    theme: {} // 存储当前主题信息
+  },
+  onLoad: function () {
+    this.setData({ theme: app.globalData.theme });
+    this.registerThemeListener();
+    this.updateThemeStyles();
+    this.updateNavigationBarColor(); // 页面加载时更新导航栏颜色
+  },
+  onShow: function () {
+    try {
+      // 获取本地存储的日志数据
+      var logs = wx.getStorageSync('logs') || [];
+      console.log('获取到的日志数量:', logs.length);
+
+      // 打印最后一条日志(用于调试)
+      if (logs.length > 0) {
+        console.log('最后一条日志:', logs[logs.length - 1]);
+      }
+
+      // 数据清洗:移除无效日志(可选,根据实际情况决定是否需要)
+      const validLogs = logs.filter(log => {
+        // 确保日志包含date和time字段,且格式正确
+        return log.date && typeof log.date === 'string' &&
+          !isNaN(parseInt(log.time));
+      });
+
+      // 如果有无效日志,更新存储
+      if (validLogs.length !== logs.length) {
+        wx.setStorageSync('logs', validLogs);
+        console.log('已清理无效日志,有效日志数量:', validLogs.length);
+        logs = validLogs;
+      }
+
+      // 初始化统计数据
+      var day = 0;          // 今日番茄次数
+      var total = logs.length;  // 累计番茄次数
+      var dayTime = 0;      // 今日专注时长(分钟)
+      var totalTime = 0;    // 累计专注时长(分钟)
+      var dayList = [];
+
+      // 提前计算今日日期字符串(YYYY-MM-DD格式)
+      const todayDate = this.formatDate(new Date());
+
+      // 遍历日志数据进行统计
+      if (logs.length > 0) {
+        for (var i = 0; i < logs.length; i++) {
+          const log = logs[i];
+
+          // 安全获取日志日期
+          const logDateStr = (log.date || '').toString();
+
+          // 安全获取日志时长(分钟)
+          const logTime = parseInt(log.time) || 0;
+
+          // 累计总时长
+          totalTime += logTime;
+
+          // 检查是否为今日记录
+          if (logDateStr.substr(0, 10) === todayDate) {
+            day += 1;           // 今日次数+1
+            dayTime += logTime; // 累计今日时长
+            dayList.push(logs[i]);
+          }
+        }
+      }
+
+      // 更新页面数据
+      this.setData({
+        'sum[0].val': day,
+        'sum[1].val': total,
+        'sum[2].val': dayTime + '分钟',
+        'sum[3].val': totalTime + '分钟',
+        dayList: dayList,
+        list: dayList
+      });
+
+      console.log('统计结果:', { day, total, dayTime, totalTime });
+    } catch (error) {
+      console.error('日志统计出错:', error);
+      wx.showToast({
+        title: '数据加载失败',
+        icon: 'none',
+        duration: 2000
+      });
+    }
+    this.setData({ theme: app.globalData.theme });
+    this.updateThemeStyles();
+    this.updateNavigationBarColor(); // 页面显示时更新导航栏颜色
+  },
+
+  // 日期格式化工具函数(确保与日志记录时的格式一致)
+  formatDate: function (date) {
+    const year = date.getFullYear();
+    const month = date.getMonth() + 1;
+    const day = date.getDate();
+    return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
+  },
+  changeType: function (e) {
+    var index = e.currentTarget.dataset.index;
+    if (index == 0) {
+      this.setData({
+        list: this.data.dayList
+      })
+    } else if (index == 1) {
+      var logs = wx.getStorageSync('logs') || [];
+      this.setData({
+        list: logs
+      })
+    }
+    this.setData({
+      activeIndex: index
+    })
+    //console.log(e.currentTarget.dataset.index);
+  },
+  registerThemeListener: function() {
+    if (app.eventBus) {
+      app.eventBus.on('themeChange', (themeData) => {
+        console.log('接收到主题数据', themeData);
+        this.setData({ theme: themeData });
+        this.updateThemeStyles();
+        this.updateNavigationBarColor(); // 主题变更时更新导航栏颜色
+      });
+    }
+  },
+
+  updateThemeStyles: function() {
+    const { theme } = this.data;
+    const { bgColor, textColor } = theme;
+    this.setData({
+      containerStyle: `background-color: ${bgColor}; color: ${textColor};`
+    });
+  },
+
+  updateNavigationBarColor: function() {
+    const { theme } = this.data;
+    wx.setNavigationBarColor({
+      frontColor: theme.textColor === '#333' ? '#000000' : '#ffffff',
+      backgroundColor: theme.primaryColor
+    });
+  }
+})

+ 3 - 0
pages/logs/logs.json

@@ -0,0 +1,3 @@
+{
+  "navigationBarTitleText": "统计"
+}

+ 25 - 0
pages/logs/logs.wxml

@@ -0,0 +1,25 @@
+<!--logs.wxml-->
+<view class="back" style="{{containerStyle}}">
+  <view class='sum' style="{{containerStyle}}">
+    <view class='sum_item' wx:for="{{sum}}">
+      <view class='sum_item_title'>{{item.title}}</view>
+      <view class='sum_item_val'>{{item.val}}</view>
+    </view>
+  </view>  
+  <view class="detail" style="{{containerStyle}}">
+    <view class="detail_title">
+      <view class="detail_title_text">分类统计</view>
+      <view class="detail_title_type">
+        <text class='{{activeIndex==0?"active":""}}' data-index='0' bindtap='changeType' style="{{containerStyle}}"> 今日</text>
+        <text class='{{activeIndex==1?"active":""}}' data-index='1' bindtap='changeType' style="{{containerStyle}}">历史</text>
+      </view>
+    </view>
+    <view class="detail_list" style="{{containerStyle}}">
+      <view class="list_item" wx:for="{{list}}">
+        <view class="list_item_date">{{item.date}}</view>
+        <view class="list_item_cate">{{cateArr[item.cate].text}}</view>
+        <view class="list_item_time">{{item.time}}分钟</view>
+      </view>
+    </view>
+  </view>
+</view>

+ 78 - 0
pages/logs/logs.wxss

@@ -0,0 +1,78 @@
+.sum {
+  display: flex;
+  flex-wrap: wrap;
+  width: 750rpx;
+  height: 350rpx;
+}
+.sum .sum_item {
+  width: 375rpx;
+  height: 160rpx;
+  text-align: center;
+}
+.sum .sum_item .sum_item_title {
+  height: 120rpx;
+  line-height: 120rpx;
+  font-size: 30rpx;
+  color: #6B747E;
+}
+.sum .sum_item .sum_item_val {
+  height: 40rpx;
+  line-height: 40rpx;
+  color: #E7624F;
+}
+.detail{
+  width: 700rpx;
+  margin: 0 auto;
+}
+.detail_title{
+  display: flex;
+  height: 60rpx;
+  line-height: 60rpx;
+}
+.detail_title .detail_title_text{
+  flex: 1;
+  font-size: 30rpx;
+}
+.detail_title .detail_title_type{
+  flex: 1;
+  text-align: right;
+  font-size: 25rpx;
+}
+.detail_title .detail_title_type text{
+  padding-left: 10rpx;
+}
+.active{
+  color: #E7624F;
+}
+.list_item{
+  display: flex;
+  height: 40rpx;
+  line-height: 40rpx;
+}
+/*.list_item>view{
+  width: 200rpx;
+  
+}*/
+.list_item .list_item_date{
+  width: 300rpx;
+  font-size: 20rpx;
+  text-align: center;
+}
+.list_item .list_item_cate{
+  width: 200rpx;
+  font-size: 30rpx;
+  text-align: center;
+}
+.list_item .list_item_time{
+  width: 200rpx;
+  font-size: 30rpx;
+  text-align: right;
+}
+.detail_list{
+  height: 3000rpx;
+}
+.back{
+  width: 750rpx;
+  border: 0;
+  align-items: center;
+}

+ 150 - 0
pages/personalCenter/personalCenter.js

@@ -0,0 +1,150 @@
+// personalCenter.js
+Page({
+  data: {
+    userInfo: {},
+    hasUserInfo: false,
+    phone: '',
+    code: '',
+    codeBtnText: '获取验证码',
+    codeTimer: null,
+    codeCountdown: 60
+  },
+  onLoad() {
+    const userInfo = wx.getStorageSync('userInfo');
+    if (userInfo) {
+      this.setData({
+        userInfo,
+        hasUserInfo: true
+      });
+    }
+  },
+  getUserInfo() {
+    wx.getUserProfile({
+      desc: '用于完善个人信息',
+      success: res => {
+        this.setData({
+          userInfo: res.userInfo,
+          hasUserInfo: true
+        });
+        wx.setStorageSync('userInfo', res.userInfo);
+      },
+      fail: err => {
+        console.error('获取用户信息失败', err);
+      }
+    });
+  },
+  chooseAvatar() {
+    wx.chooseMedia({
+      count: 1,
+      mediaType: ['image'],
+      sourceType: ['album', 'camera'],
+      success: res => {
+        const avatarUrl = res.tempFiles[0].tempFilePath;
+        this.setData({
+          'userInfo.avatarUrl': avatarUrl
+        });
+        const userInfo = this.data.userInfo;
+        wx.setStorageSync('userInfo', userInfo);
+      }
+    });
+  },
+  onNicknameInput(e) {
+    this.setData({
+      'userInfo.nickName': e.detail.value
+    });
+  },
+  saveNickname() {
+    const userInfo = this.data.userInfo;
+    wx.setStorageSync('userInfo', userInfo);
+    wx.showToast({
+      title: '保存成功',
+      icon: 'success'
+    });
+  },
+  onPhoneInput(e) {
+    this.setData({
+      phone: e.detail.value
+    });
+  },
+  onCodeInput(e) {
+    this.setData({
+      code: e.detail.value
+    });
+  },
+  getVerificationCode() {
+    const { phone, codeTimer } = this.data;
+    if (!phone) {
+      wx.showToast({
+        title: '请输入手机号',
+        icon: 'none'
+      });
+      return;
+    }
+    if (codeTimer) {
+      return;
+    }
+    // 模拟发送验证码
+    wx.showToast({
+      title: '验证码已发送',
+      icon: 'success'
+    });
+    this.startCodeCountdown();
+  },
+  startCodeCountdown() {
+    let { codeCountdown } = this.data;
+    const codeTimer = setInterval(() => {
+      codeCountdown--;
+      if (codeCountdown <= 0) {
+        clearInterval(codeTimer);
+        this.setData({
+          codeBtnText: '获取验证码',
+          codeTimer: null,
+          codeCountdown: 60
+        });
+      } else {
+        this.setData({
+          codeBtnText: `${codeCountdown}s后重试`,
+          codeTimer,
+          codeCountdown
+        });
+      }
+    }, 1000);
+    this.setData({
+      codeTimer
+    });
+  },
+  login() {
+    const { phone, code } = this.data;
+    if (!phone || !code) {
+      wx.showToast({
+        title: '请输入手机号和验证码',
+        icon: 'none'
+      });
+      return;
+    }
+    // 模拟登录验证
+    wx.showToast({
+      title: '登录成功',
+      icon: 'success'
+    });
+    this.setData({
+      hasUserInfo: true,
+      userInfo: {
+        nickName: `用户_${phone}`,
+        avatarUrl: 'default_avatar_url'
+      }
+    });
+    wx.setStorageSync('userInfo', this.data.userInfo);
+  },
+  logout() {
+    wx.removeStorageSync('userInfo');
+    this.setData({
+      hasUserInfo: false,
+      userInfo: {}
+    });
+    wx.showToast({
+      title: '登出成功',
+      icon: 'success'
+    });
+  }
+});

+ 3 - 0
pages/personalCenter/personalCenter.json

@@ -0,0 +1,3 @@
+{
+  "navigationBarTitleText": "个人中心"
+}

+ 24 - 0
pages/personalCenter/personalCenter.wxml

@@ -0,0 +1,24 @@
+<view class="container">
+  <!-- 头像 -->
+  <view class="avatar" bindtap="chooseAvatar">
+    <image src="{{userInfo.avatarUrl}}" mode="aspectFill"></image>
+  </view>
+  <!-- 昵称 -->
+  <view class="nickname-wrapper">
+    <input class="input-box" placeholder="请输入昵称" value="{{userInfo.nickName}}" bindinput="onNicknameInput" />
+    <button class="save-button" bindtap="saveNickname">保存</button>
+  </view>
+  <!-- 手机号输入 -->
+  <view class="input-wrapper">
+    <input class="input-box" placeholder="请输入手机号" type="number" bindinput="onPhoneInput" />
+  </view>
+  <!-- 验证码输入 -->
+  <view class="input-wrapper">
+    <input class="input-box" placeholder="请输入验证码" bindinput="onCodeInput" />
+    <button class="get-code-button" bindtap="getVerificationCode">{{codeBtnText}}</button>
+  </view>
+  <!-- 登录按钮 -->
+  <button wx:if="{{!hasUserInfo}}" bindtap="login">登录</button>
+  <!-- 登出按钮 -->
+  <button wx:if="{{hasUserInfo}}" bindtap="logout">登出</button>
+</view>

+ 55 - 0
pages/personalCenter/personalCenter.wxss

@@ -0,0 +1,55 @@
+.container {
+  padding: 20rpx;
+  text-align: center;
+}
+
+.avatar {
+  width: 200rpx;
+  height: 200rpx;
+  margin: 20rpx auto;
+  border-radius: 50%;
+  overflow: hidden;
+}
+
+.avatar image {
+  width: 100%;
+  height: 100%;
+}
+
+.nickname-wrapper {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin: 20rpx 0;
+}
+
+.input-wrapper {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin: 10rpx 0;
+}
+
+.input-box {
+  width: 60%; /* 调整输入框宽度 */
+  padding: 10rpx;
+  border: 1rpx solid #E7624F;
+  border-radius: 10rpx;
+  margin-right: 10rpx;
+}
+
+.save-button,
+.get-code-button {
+  /* 调整按钮的宽度和内边距 */
+  width: 20%; 
+  padding: 10rpx 15rpx; 
+  border: 1rpx solid #E7624F;
+  border-radius: 10rpx;
+  background-color: #E7624F;
+  color: white;
+  font-size: 24rpx; /* 调整字体大小 */
+}
+
+button {
+  margin-top: 20rpx;
+}

+ 173 - 0
pages/settings/settings.js

@@ -0,0 +1,173 @@
+const app = getApp();
+
+Page({
+  data: {
+    userInfo: null,
+    isLoggedIn: false,
+    hasUserInfo: false,
+    canIUse: wx.canIUse('button.open-type.getUserInfo'),
+    theme: {
+      bgColor: '#F8F8F8',
+      textColor: '#333',
+      primaryColor: '#E7624F'
+    },
+    containerStyle: ''
+  },
+
+  onLoad: function () {
+    this.loadThemeSettings();
+    this.checkLoginStatus();
+    
+    if (app.globalData.userInfo) {
+      this.setData({
+        userInfo: app.globalData.userInfo,
+        hasUserInfo: true
+      });
+    } else if (this.data.canIUse) {
+      app.userInfoReadyCallback = res => {
+        this.setData({
+          userInfo: res.userInfo,
+          hasUserInfo: true
+        });
+      };
+    } else {
+      wx.getUserInfo({
+        success: res => {
+          app.globalData.userInfo = res.userInfo;
+          this.setData({
+            userInfo: res.userInfo,
+            hasUserInfo: true
+          });
+        }
+      });
+    }
+
+    if (app.eventBus) {
+      app.eventBus.on('themeChange', this.handleThemeChange);
+    }
+  },
+
+  onShow: function() {
+    // 页面显示时更新导航栏颜色
+    this.updateNavigationBarColor();
+  },
+
+  onUnload: function() {
+    if (app.eventBus) {
+      app.eventBus.off('themeChange', this.handleThemeChange);
+    }
+  },
+
+  loadThemeSettings: function() {
+    const savedTheme = wx.getStorageSync('theme') || app.globalData.theme || {
+      bgColor: '#F8F8F8',
+      textColor: '#333',
+      primaryColor: '#E7624F'
+    };
+    
+    this.setData({
+      theme: savedTheme,
+      containerStyle: `background-color: ${savedTheme.bgColor}; color: ${savedTheme.textColor};`
+    });
+    
+    this.updateNavigationBarColor();
+  },
+
+  handleThemeChange: function(theme) {
+    this.setData({
+      theme: theme,
+      containerStyle: `background-color: ${theme.bgColor}; color: ${theme.textColor};`
+    });
+    
+    this.updateNavigationBarColor();
+  },
+
+  checkLoginStatus: function() {
+    const userInfo = app.globalData.userInfo;
+    const isLoggedIn = !!userInfo;
+    this.setData({
+      userInfo: userInfo,
+      isLoggedIn: isLoggedIn,
+      hasUserInfo: isLoggedIn
+    });
+  },
+
+  handleUserInfo: function(e) {
+    if (e.detail.userInfo) {
+      const userInfo = e.detail.userInfo;
+      app.globalData.userInfo = userInfo;
+      
+      this.setData({
+        userInfo: userInfo,
+        isLoggedIn: true,
+        hasUserInfo: true
+      });
+      
+      wx.showToast({
+        title: '登录成功',
+        icon: 'success'
+      });
+    } else {
+      wx.showToast({
+        title: '您拒绝了授权',
+        icon: 'none'
+      });
+    }
+  },
+
+  handleLogout: function() {
+    wx.showModal({
+      title: '提示',
+      content: '确定要退出登录吗?',
+      success: (res) => {
+        if (res.confirm) {
+          app.globalData.userInfo = null;
+          this.setData({
+            userInfo: null,
+            isLoggedIn: false,
+            hasUserInfo: false
+          });
+          wx.showToast({
+            title: '已退出登录',
+            icon: 'success'
+          });
+        }
+      }
+    });
+  },
+
+  goToThemeSettings: function() {
+    wx.navigateTo({
+      url: '../theme/theme'
+    });
+  },
+
+  goToNotificationSettings: function() {
+    wx.showToast({
+      title: '通知设置功能开发中',
+      icon: 'none'
+    });
+  },
+
+  goToAbout: function() {
+    wx.showToast({
+      title: '关于功能开发中',
+      icon: 'none'
+    });
+  },
+
+  // 更新导航栏颜色的方法
+  updateNavigationBarColor: function() {
+    const { theme } = this.data;
+    if (theme && theme.primaryColor && theme.textColor) {
+      wx.setNavigationBarColor({
+        frontColor: theme.textColor === '#333' ? '#000000' : '#ffffff',
+        backgroundColor: theme.primaryColor,
+        animation: {
+          duration: 300,
+          timingFunc: 'easeIn'
+        }
+      });
+    }
+  }
+});

+ 3 - 0
pages/settings/settings.json

@@ -0,0 +1,3 @@
+{
+  "navigationBarTitleText": "设置"
+}

+ 35 - 0
pages/settings/settings.wxml

@@ -0,0 +1,35 @@
+<view class="settings-container {{theme.textColor === '#f5f5f5' ? 'dark-theme' : ''}}" style="{{containerStyle}}">
+  <view class="section user-section">
+    <view class="user-info" wx:if="{{userInfo}}">
+      <image class="user-avatar" src="{{userInfo.avatarUrl}}"></image>
+      <view class="user-name">{{userInfo.nickName}}</view>
+      <button class="logout-btn">退出登录</button>
+    </view>
+    <view class="login-tip" wx:else>
+      <button class="login-btn" open-type="getUserInfo" bindgetuserinfo="handleUserInfo">
+        <text class="login-text">登录番茄时钟</text>
+      </button>
+    </view>
+  </view>
+
+  <view class="section">
+    <view class="setting-item" bindtap="goToThemeSettings">
+      <view class="item-title">主题设置</view>
+      <view class="item-arrow">></view>
+    </view>
+  </view>
+
+  <view class="section">
+    <view class="setting-item" bindtap="goToNotificationSettings">
+      <view class="item-title">通知设置</view>
+      <view class="item-arrow">></view>
+    </view>
+  </view>
+
+  <view class="section">
+    <view class="setting-item" bindtap="goToAbout">
+      <view class="item-title">关于我们</view>
+      <view class="item-arrow">></view>
+    </view>
+  </view>
+</view>

+ 107 - 0
pages/settings/settings.wxss

@@ -0,0 +1,107 @@
+.settings-container {
+  padding: 20rpx;
+  min-height: 100vh;
+  transition: all 0.3s ease;
+}
+
+.section {
+  background-color: #fff;
+  border-radius: 12rpx;
+  margin-bottom: 20rpx;
+  padding: 30rpx;
+  box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.05);
+  transition: all 0.3s ease;
+}
+
+.user-section {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 40rpx 0;
+}
+
+.user-info {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  margin-bottom: 30rpx;
+}
+
+.user-avatar {
+  width: 120rpx;
+  height: 120rpx;
+  border-radius: 50%;
+  margin-bottom: 20rpx;
+  border: 4rpx solid #E7624F;
+}
+
+.user-name {
+  font-size: 32rpx;
+  font-weight: bold;
+  margin-bottom: 20rpx;
+}
+
+.logout-btn, .login-btn {
+  width: 240rpx;
+  height: 80rpx;
+  line-height: 80rpx;
+  border-radius: 40rpx;
+  margin-top: 20rpx;
+  padding: 0;
+  font-size: 28rpx;
+  border: none;
+}
+
+.logout-btn {
+  background-color: #E7624F;
+  color: #fff;
+}
+
+.login-btn {
+  background-color: #4CAF50;
+  color: #fff;
+}
+
+.login-tip {
+  text-align: center;
+  margin-top: 20rpx;
+}
+
+.login-text {
+  color: #fff;
+}
+
+.setting-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20rpx 0;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+
+.item-title {
+  font-size: 30rpx;
+}
+
+.item-arrow {
+  color: #999;
+  font-size: 30rpx;
+}
+
+/* 深色主题适配 */
+.dark-theme .section {
+  background-color: #333 !important;
+  box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.2);
+}
+
+.dark-theme .item-title {
+  color: #f5f5f5 !important;
+}
+
+.dark-theme .item-arrow {
+  color: #aaa !important;
+}
+
+.dark-theme .setting-item {
+  border-bottom-color: #444 !important;
+}

+ 84 - 0
pages/theme/theme.js

@@ -0,0 +1,84 @@
+const app = getApp();
+
+Page({
+  data: {
+    currentTheme: 0,
+    primaryColor: '#E7624F',
+    themes: [
+      { name: '默认主题', bgColor: '#f8f8f8', textColor: '#333' },
+      { name: '深色主题', bgColor: '#333', textColor: '#f5f5f5' },
+      { name: '护眼主题', bgColor: '#e6f7ed', textColor: '#333' }
+    ],
+    colorOptions: [
+      '#E7624F', '#4285F4', '#34A853', '#FBBC05', '#EA4335', '#9C27B0'
+    ],
+    theme: {}
+  },
+
+  onLoad: function () {
+    const savedTheme = wx.getStorageSync('theme') || app.globalData.theme;
+    const currentTheme = savedTheme.currentTheme || 0;
+    const primaryColor = savedTheme.primaryColor || '#E7624F';
+    
+    this.setData({
+      currentTheme,
+      primaryColor,
+      theme: savedTheme
+    });
+
+    this.updateNavigationBarColor();
+
+    if (app.eventBus) {
+      app.eventBus.on('themeChange', (theme) => {
+        this.setData({ theme });
+        this.updateNavigationBarColor();
+      });
+    }
+  },
+
+  switchTheme: function(e) {
+    this.setData({
+      currentTheme: e.currentTarget.dataset.index
+    });
+  },
+
+  setPrimaryColor: function(e) {
+    this.setData({
+      primaryColor: e.currentTarget.dataset.color
+    });
+    this.updateNavigationBarColor();
+  },
+
+  applyTheme: function() {
+    const { currentTheme, primaryColor, themes } = this.data;
+    const newTheme = {
+      ...themes[currentTheme],
+      primaryColor,
+      currentTheme
+    };
+
+    wx.setStorageSync('theme', newTheme);
+    app.globalData.theme = newTheme;
+
+    if (app.eventBus) {
+      app.eventBus.emit('themeChange', newTheme);
+    }
+
+    wx.showToast({
+      title: '主题应用成功',
+      icon: 'success'
+    });
+    
+    setTimeout(() => wx.navigateBack(), 1500);
+  },
+
+  // 新增:更新导航栏颜色的方法
+  updateNavigationBarColor: function() {
+    const { theme, primaryColor } = this.data;
+    const textColor = theme.textColor || '#333';
+    wx.setNavigationBarColor({
+      frontColor: textColor === '#333' ? '#000000' : '#ffffff',
+      backgroundColor: primaryColor
+    });
+  }
+});

+ 4 - 0
pages/theme/theme.json

@@ -0,0 +1,4 @@
+{
+  "navigationBarTitleText": "主题设置",
+  "usingComponents": {}
+}

+ 47 - 0
pages/theme/theme.wxml

@@ -0,0 +1,47 @@
+<view class="theme-container" style="background-color: {{themes[currentTheme].bgColor}}; color: {{themes[currentTheme].textColor}};">
+  <view class="theme-header">
+    <view class="theme-title">主题设置</view>
+    <view class="theme-desc">自定义你的应用外观</view>
+  </view>
+
+  <view class="theme-section">
+    <view class="section-title">主题风格</view>
+    <view class="theme-options">
+      <view 
+        wx:for="{{themes}}" 
+        wx:key="name" 
+        class="theme-option {{currentTheme == index ? 'active' : ''}}"
+        data-index="{{index}}"
+        bindtap="switchTheme">
+        <view class="theme-preview" style="background-color: {{item.bgColor}}; color: {{item.textColor}}">
+          <view class="preview-title">{{item.name}}</view>
+        </view>
+        <view class="theme-check {{currentTheme == index ? 'checked' : ''}}">
+          <image src="/images/check1.png" wx:if="{{currentTheme == index && themes[currentTheme].textColor === '#333'}}"></image>
+          <image src="/images/check-white.png" wx:if="{{currentTheme == index && themes[currentTheme].textColor !== '#333'}}"></image>
+        </view>
+      </view>
+    </view>
+  </view>
+
+  <view class="theme-section">
+    <view class="section-title">主色调</view>
+    <view class="color-options">
+      <view 
+        wx:for="{{colorOptions}}" 
+        wx:key="*this" 
+        class="color-option"
+        data-color="{{item}}"
+        style="background-color: {{item}}"
+        bindtap="setPrimaryColor">
+        <view class="color-check {{primaryColor == item ? 'checked' : ''}}">
+          <image src="/images/check-white.png" wx:if="{{primaryColor == item}}"></image>
+        </view>
+      </view>
+    </view>
+  </view>
+
+  <view class="apply-btn" style="background-color: {{primaryColor}}" bindtap="applyTheme">
+    应用主题
+  </view>
+</view>

+ 144 - 0
pages/theme/theme.wxss

@@ -0,0 +1,144 @@
+.theme-container {
+  padding: 30rpx;
+  min-height: 100vh;
+  transition: all 0.3s ease;
+}
+
+.theme-header {
+  padding: 20rpx 0 40rpx;
+}
+
+.theme-title {
+  font-size: 36rpx;
+  font-weight: bold;
+}
+
+.theme-desc {
+  font-size: 28rpx;
+  margin-top: 10rpx;
+}
+
+.theme-section {
+  margin-bottom: 40rpx;
+}
+
+.section-title {
+  font-size: 32rpx;
+  font-weight: 500;
+  margin-bottom: 20rpx;
+}
+
+.theme-options {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 20rpx;
+}
+
+.theme-option {
+  width: 210rpx;
+  height: 280rpx;
+  border-radius: 12rpx;
+  overflow: hidden;
+  position: relative;
+  border: 2rpx solid #eee;
+  transition: all 0.3s ease;
+}
+
+.theme-option.active {
+  border-color: var(--primaryColor, #E7624F);
+}
+
+.theme-preview {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  transition: all 0.3s ease;
+}
+
+.preview-title {
+  font-size: 30rpx;
+  font-weight: 500;
+}
+
+.theme-check {
+  position: absolute;
+  bottom: 10rpx;
+  right: 10rpx;
+  width: 36rpx;
+  height: 36rpx;
+  border-radius: 50%;
+  background-color: rgba(255,255,255,0.8);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  transition: all 0.3s ease;
+}
+
+.theme-check.checked {
+  background-color: var(--primaryColor, #E7624F);
+}
+
+.theme-check image {
+  width: 24rpx;
+  height: 24rpx;
+}
+
+.color-options {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 20rpx;
+}
+
+.color-option {
+  width: 80rpx;
+  height: 80rpx;
+  border-radius: 50%;
+  position: relative;
+  border: 2rpx solid #eee;
+  transition: all 0.3s ease;
+}
+
+.color-option:active {
+  transform: scale(0.95);
+}
+
+.color-check {
+  position: absolute;
+  bottom: -5rpx;
+  right: -5rpx;
+  width: 30rpx;
+  height: 30rpx;
+  border-radius: 50%;
+  background-color: white;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border: 2rpx solid white;
+  transition: all 0.3s ease;
+}
+
+.color-check.checked {
+  background-color: var(--primaryColor, #E7624F);
+}
+
+.color-check image {
+  width: 20rpx;
+  height: 20rpx;
+}
+
+.apply-btn {
+  height: 90rpx;
+  line-height: 90rpx;
+  text-align: center;
+  color: white;
+  font-size: 32rpx;
+  border-radius: 45rpx;
+  margin-top: 60rpx;
+  transition: all 0.3s ease;
+}
+
+.apply-btn:active {
+  opacity: 0.8;
+}

+ 61 - 0
project.config.json

@@ -0,0 +1,61 @@
+{
+  "description": "番茄时钟小程序项目配置文件",
+  "packOptions": {
+    "ignore": [],
+    "include": []
+  },
+  "setting": {
+    "urlCheck": true,
+    "es6": true,
+    "postcss": true,
+    "minified": true,
+    "newFeature": true,
+    "compileHotReLoad": false,
+    "lazyloadPlaceholderEnable": false,
+    "preloadBackgroundData": false,
+    "minifyWXML": true,
+    "minifyWXSS": true,
+    "ignoreUploadUnusedFiles": false,
+    "useStrict": true,
+    "ignoreDevUnusedFiles": false,
+    "checkInvalidKey": true,
+    "checkSiteMap": true,
+    "uploadWithSourceMap": true,
+    "useIsolateContext": true,
+    "userConfirmedUseIsolateContext": true,
+    "packNpmManually": false,
+    "packNpmRelationList": [],
+    "disableUseStrict": false,
+    "babelSetting": {
+      "ignore": [],
+      "disablePlugins": [],
+      "outputPath": ""
+    },
+    "enableEngineNative": false,
+    "bundle": false,
+    "useCompilerPlugins": false,
+    "userConfirmedBundleSwitch": false,
+    "packWxml": true,
+    "compileWorklet": false,
+    "uglifyFileName": false,
+    "enhance": false,
+    "localPlugins": false,
+    "condition": false,
+    "swc": false,
+    "disableSWC": true
+  },
+  "compileType": "miniprogram",
+  "libVersion": "3.8.8",
+  "appid": "wx87cb3473bc01271c",
+  "projectname": "tomatoClock",
+  "condition": {},
+  "simulatorPluginLibVersion": {},
+  "editorSetting": {
+    "tabIndent": "insertSpaces",
+    "tabSize": 2
+  },
+  "previewSetting": {
+    "url": ""
+  },
+  "isEnableEngineNative": false
+}

+ 25 - 0
project.private.config.json

@@ -0,0 +1,25 @@
+{
+  "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
+  "projectname": "tomatoClock",
+  "setting": {
+    "compileHotReLoad": true,
+    "urlCheck": true,
+    "coverView": true,
+    "lazyloadPlaceholderEnable": false,
+    "skylineRenderEnable": false,
+    "preloadBackgroundData": false,
+    "autoAudits": false,
+    "useApiHook": true,
+    "useApiHostProcess": true,
+    "showShadowRootInWxmlPanel": true,
+    "useStaticServer": false,
+    "useLanDebug": false,
+    "showES6CompileOption": false,
+    "bigPackageSizeSupport": false,
+    "checkInvalidKey": true,
+    "ignoreDevUnusedFiles": true,
+    "useIsolateContext": true
+  },
+  "libVersion": "3.8.8",
+  "condition": {}
+}

+ 7 - 0
sitemap.json

@@ -0,0 +1,7 @@
+{
+  "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
+  "rules": [{
+  "action": "allow",
+  "page": "*"
+  }]
+}

+ 19 - 0
utils/util.js

@@ -0,0 +1,19 @@
+const formatTime = date => {
+  const year = date.getFullYear()
+  const month = date.getMonth() + 1
+  const day = date.getDate()
+  const hour = date.getHours()
+  const minute = date.getMinutes()
+  const second = date.getSeconds()
+
+  return [year, month, day].map(formatNumber).join('-') + ' ' + [hour, minute, second].map(formatNumber).join(':')
+}
+
+const formatNumber = n => {
+  n = n.toString()
+  return n[1] ? n : '0' + n
+}
+
+module.exports = {
+  formatTime: formatTime
+}