index.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. //index.js
  2. const app = getApp()
  3. const util = require('../../utils/util.js')
  4. Page({
  5. data: {
  6. userInfo:{},
  7. hasUserInfo:false,
  8. clockShow: false,
  9. clockHeight: 0,
  10. time: '25',
  11. mTime: 1500000,
  12. timeStr: '25:00',
  13. rate: '',
  14. timer: null,
  15. cateArr: [
  16. { icon: 'work', text: '工作' },
  17. { icon: 'study', text: "学习" },
  18. { icon: 'think', text: '思考' },
  19. { icon: 'write', text: '写作' },
  20. { icon: 'sport', text: '运动' },
  21. { icon: 'read', text: "阅读" }
  22. ],
  23. cateActive: null,
  24. okShow: false,
  25. pauseShow: true,
  26. continueCancleShow: false,
  27. customTask: '',
  28. showCustomInput: false,
  29. plans: [],
  30. selectedPlan: null,
  31. editMode: false,
  32. editPlanId: null,
  33. currentTask: '',
  34. userInfo: null,
  35. isLoggedIn: false,
  36. theme: {},
  37. containerStyle: '',
  38. buttonStyle: '', // 按钮动态样式
  39. timerStyle: '', // 计时器动态样式
  40. progressStyle: '', // 进度条动态样式
  41. photoPath: '',
  42. isTimerRunning: false, // 新增:计时器是否正在运行
  43. startTime: 0, // 新增:开始时间戳
  44. endTime: 0, // 新增:结束时间戳
  45. remainingTime: 0 // 新增:剩余时间(毫秒)
  46. },
  47. onLoad: function () {
  48. const res = wx.getSystemInfoSync();
  49. const rate = 750 / res.windowWidth;
  50. this.setData({
  51. rate: rate,
  52. clockHeight: res.windowHeight * 2,
  53. time: '25',
  54. mTime: 25 * 60 * 1000,
  55. timeStr: '25:00'
  56. });
  57. const plans = wx.getStorageSync('plans') || [];
  58. this.setData({ plans });
  59. if (app.globalData.userInfo) {
  60. this.setData({
  61. userInfo: app.globalData.userInfo,
  62. hasUserInfo: true,
  63. isLoggedIn: true
  64. });
  65. } else {
  66. // 没有用户信息,引导用户授权
  67. wx.showModal({
  68. title: '授权提示',
  69. content: '需要获取您的用户信息以提供更好的服务,请授权登录',
  70. success: res => {
  71. if (res.confirm) {
  72. wx.getUserProfile({
  73. desc: '用于完善用户资料',
  74. success: res => {
  75. app.globalData.userInfo = res.userInfo;
  76. this.setData({
  77. userInfo: res.userInfo,
  78. hasUserInfo: true,
  79. isLoggedIn: true
  80. });
  81. }
  82. });
  83. }
  84. }
  85. });
  86. }
  87. if (app.eventBus) {
  88. app.eventBus.on('userInfoChange', (userInfo) => {
  89. this.setData({
  90. userInfo: userInfo,
  91. isLoggedIn: !!userInfo
  92. });
  93. });
  94. app.eventBus.on('themeChange', (theme) => {
  95. this.setData({ theme });
  96. this.updateThemeStyles();
  97. });
  98. }
  99. this.setData({
  100. userInfo: app.globalData.userInfo,
  101. isLoggedIn: !!app.globalData.userInfo,
  102. theme: app.globalData.theme
  103. });
  104. this.updateThemeStyles();
  105. },
  106. onShow: function() {
  107. this.updateNavigationBarColor();
  108. },
  109. onUnload: function() {
  110. if (this.data.timer) {
  111. clearInterval(this.data.timer);
  112. this.setData({ timer: null });
  113. }
  114. if (app.eventBus) {
  115. app.eventBus.off('userInfoChange');
  116. app.eventBus.off('themeChange');
  117. }
  118. },
  119. updateNavigationBarColor: function() {
  120. const { theme } = this.data;
  121. if (theme && theme.primaryColor && theme.textColor) {
  122. wx.setNavigationBarColor({
  123. frontColor: theme.textColor === '#333' ? '#000000' : '#ffffff',
  124. backgroundColor: theme.primaryColor,
  125. animation: {
  126. duration: 300,
  127. timingFunc: 'easeIn'
  128. }
  129. });
  130. }
  131. },
  132. updateThemeStyles: function () {
  133. const { theme } = this.data;
  134. const { bgColor, textColor, primaryColor } = theme;
  135. this.setData({
  136. containerStyle: `background-color: ${bgColor}; color: ${textColor};`,
  137. buttonStyle: `background-color: ${primaryColor}; color: ${textColor};`,
  138. timerStyle: `color: ${primaryColor};`,
  139. progressStyle: primaryColor,
  140. sliderStyle: primaryColor // 新增滑块主题色
  141. });
  142. this.updateNavigationBarColor();
  143. },
  144. slideChange: function (e) {
  145. const minutes = e.detail.value;
  146. this.setData({
  147. time: minutes.toString(),
  148. timeStr: minutes >= 10 ? minutes + ':00' : '0' + minutes + ':00',
  149. mTime: minutes * 60 * 1000
  150. });
  151. },
  152. clickCate: function (e) {
  153. this.setData({
  154. cateActive: e.currentTarget.dataset.index,
  155. showCustomInput: false,
  156. selectedPlan: null,
  157. editMode: false
  158. });
  159. },
  160. showCustomTaskInput: function() {
  161. this.setData({
  162. showCustomInput: true,
  163. cateActive: null,
  164. selectedPlan: null,
  165. customTask: '',
  166. editMode: false
  167. });
  168. },
  169. inputCustomTask: function(e) {
  170. this.setData({ customTask: e.detail.value });
  171. },
  172. savePlan: function() {
  173. if (!this.data.customTask.trim()) {
  174. wx.showToast({ title: '请输入任务内容', icon: 'none' });
  175. return;
  176. }
  177. let updatedPlans = [];
  178. if (this.data.editMode) {
  179. updatedPlans = this.data.plans.map(plan => {
  180. if (plan.id === this.data.editPlanId) {
  181. return { ...plan, content: this.data.customTask, time: this.data.time };
  182. }
  183. return plan;
  184. });
  185. wx.showToast({ title: '计划更新成功', icon: 'success' });
  186. } else {
  187. const newPlan = {
  188. id: Date.now(),
  189. content: this.data.customTask,
  190. time: this.data.time,
  191. createdAt: new Date().toISOString()
  192. };
  193. updatedPlans = [...this.data.plans, newPlan];
  194. wx.showToast({ title: '计划添加成功', icon: 'success' });
  195. }
  196. this.setData({
  197. plans: updatedPlans,
  198. customTask: '',
  199. showCustomInput: false,
  200. editMode: false,
  201. editPlanId: null
  202. });
  203. wx.setStorageSync('plans', updatedPlans);
  204. },
  205. selectPlan: function(e) {
  206. const { index } = e.currentTarget.dataset;
  207. const selectedPlan = this.data.plans[index];
  208. this.setData({
  209. time: selectedPlan.time,
  210. customTask: selectedPlan.content,
  211. cateActive: null,
  212. showCustomInput: false,
  213. selectedPlan: selectedPlan,
  214. editMode: false
  215. });
  216. },
  217. editPlan: function(e) {
  218. const { index } = e.currentTarget.dataset;
  219. const planToEdit = this.data.plans[index];
  220. this.setData({
  221. time: planToEdit.time,
  222. customTask: planToEdit.content,
  223. showCustomInput: true,
  224. editMode: true,
  225. editPlanId: planToEdit.id,
  226. selectedPlan: null
  227. });
  228. },
  229. deletePlan: function(e) {
  230. const { index } = e.currentTarget.dataset;
  231. const planToDelete = this.data.plans[index];
  232. wx.showModal({
  233. title: '确认删除',
  234. content: `确定要删除计划"${planToDelete.content}"吗?`,
  235. success: (res) => {
  236. if (res.confirm) {
  237. const updatedPlans = this.data.plans.filter((_, i) => i !== index);
  238. this.setData({ plans: updatedPlans, selectedPlan: null });
  239. wx.setStorageSync('plans', updatedPlans);
  240. wx.showToast({ title: '计划已删除', icon: 'success' });
  241. }
  242. }
  243. });
  244. },
  245. start: function () {
  246. let taskContent = '';
  247. let time = parseInt(this.data.time);
  248. let categoryIndex = this.data.cateActive;
  249. if (this.data.selectedPlan) {
  250. taskContent = this.data.selectedPlan.content;
  251. time = parseInt(this.data.selectedPlan.time);
  252. } else if (this.data.showCustomInput && this.data.customTask) {
  253. taskContent = this.data.customTask;
  254. categoryIndex = null;
  255. } else if (this.data.cateActive !== null) {
  256. taskContent = this.data.cateArr[this.data.cateActive].text;
  257. } else {
  258. wx.showToast({ title: '请选择一个任务', icon: 'none' });
  259. return;
  260. }
  261. this.setData({
  262. clockShow: true,
  263. mTime: time * 60 * 1000,
  264. timeStr: time >= 10 ? time + ':00' : '0' + time + ':00',
  265. time: time.toString(),
  266. currentTask: taskContent,
  267. cateActive: categoryIndex
  268. });
  269. this.drawBg();
  270. this.drawActive();
  271. this.showPhotoConfirm();
  272. },
  273. drawBg: function () {
  274. const lineWidth = 6 / this.data.rate;
  275. const ctx = wx.createCanvasContext('progress_bg');
  276. const center = 250 / this.data.rate;
  277. const radius = center - 2 * lineWidth;
  278. ctx.setLineWidth(lineWidth);
  279. ctx.setStrokeStyle('#f0f0f0');
  280. ctx.setLineCap('round');
  281. ctx.beginPath();
  282. ctx.arc(center, center, radius, 0, 2 * Math.PI, false);
  283. ctx.stroke();
  284. ctx.draw();
  285. },
  286. drawActive: function () {
  287. const _this = this;
  288. const lineWidth = 6 / this.data.rate;
  289. const center = 250 / this.data.rate;
  290. const radius = center - 2 * lineWidth;
  291. const timer = setInterval(function () {
  292. const angle = 1.5 + 2 * (_this.data.time * 60 * 1000 - _this.data.mTime) / (_this.data.time * 60 * 1000);
  293. const currentTime = _this.data.mTime - 100;
  294. _this.setData({ mTime: currentTime });
  295. if (angle < 3.5) {
  296. if (currentTime % 1000 == 0) {
  297. let timeStr1 = currentTime / 1000;
  298. let timeStr2 = parseInt(timeStr1 / 60);
  299. let timeStr3 = (timeStr1 - timeStr2 * 60) >= 10 ?
  300. (timeStr1 - timeStr2 * 60) : '0' + (timeStr1 - timeStr2 * 60);
  301. timeStr2 = timeStr2 >= 10 ? timeStr2 : '0' + timeStr2;
  302. _this.setData({ timeStr: timeStr2 + ':' + timeStr3 });
  303. }
  304. const ctx = wx.createCanvasContext('progress_active');
  305. ctx.setLineWidth(lineWidth);
  306. ctx.setStrokeStyle(_this.data.theme.primaryColor);
  307. ctx.setLineCap('round');
  308. ctx.beginPath();
  309. ctx.arc(center, center, radius, 1.5 * Math.PI, angle * Math.PI, false);
  310. ctx.stroke();
  311. ctx.draw();
  312. } else {
  313. const logs = wx.getStorageSync('logs') || [];
  314. logs.unshift({
  315. date: util.formatTime(new Date),
  316. task: _this.data.currentTask,
  317. cate: _this.data.cateActive,
  318. time: _this.data.time
  319. });
  320. wx.setStorageSync('logs', logs);
  321. _this.setData({
  322. timeStr: '00:00',
  323. okShow: true,
  324. pauseShow: false,
  325. continueCancleShow: false
  326. });
  327. clearInterval(timer);
  328. }
  329. }, 100);
  330. _this.setData({ timer: timer });
  331. },
  332. showPhotoConfirm: function() {
  333. wx.showModal({
  334. title: '打卡拍照',
  335. content: '开始专注前需要拍照打卡确认',
  336. confirmText: '拍照',
  337. cancelText: '暂不',
  338. success: (res) => {
  339. if (res.confirm) {
  340. this.checkCameraPermission();
  341. } else {
  342. // 取消拍照则重置界面
  343. this.resetTimerUI();
  344. wx.showToast({ title: '拍照打卡已取消', icon: 'none' });
  345. }
  346. }
  347. });
  348. },
  349. checkCameraPermission: function() {
  350. wx.getSetting({
  351. success: (res) => {
  352. if (!res.authSetting['scope.camera']) {
  353. wx.authorize({
  354. scope: 'scope.camera',
  355. success: () => this.takePhoto(),
  356. fail: () => {
  357. wx.showModal({
  358. title: '权限申请',
  359. content: '需要授予相机权限才能拍照打卡',
  360. success: (res) => {
  361. if (res.confirm) wx.openSetting();
  362. else this.resetTimerUI(); // 拒绝权限则重置界面
  363. }
  364. });
  365. }
  366. });
  367. } else {
  368. this.takePhoto();
  369. }
  370. }
  371. });
  372. },
  373. takePhoto: function () {
  374. wx.chooseImage({
  375. count: 1,
  376. sourceType: ['camera'],
  377. success: (res) => {
  378. const photoPath = res.tempFilePaths[0];
  379. this.setData({ photoPath });
  380. wx.setStorageSync('checkInPhoto', photoPath);
  381. wx.showToast({ title: '拍照成功' });
  382. // 拍照成功后才开始计时
  383. this.startTimer();
  384. },
  385. fail: (err) => {
  386. console.error('拍照失败', err);
  387. wx.showToast({ title: '拍照失败', icon: 'none' });
  388. this.resetTimerUI(); // 拍照失败则重置界面
  389. }
  390. });
  391. },
  392. startTimer: function() {
  393. // 记录开始时间和预计结束时间
  394. const now = Date.now();
  395. this.setData({
  396. isTimerRunning: true,
  397. startTime: now,
  398. endTime: now + this.data.remainingTime
  399. });
  400. // 更新计时器显示
  401. this.updateTimer();
  402. },
  403. updateTimer: function() {
  404. if (!this.data.isTimerRunning) return;
  405. const now = Date.now();
  406. let remaining = this.data.endTime - now;
  407. // 处理计时结束
  408. if (remaining <= 0) {
  409. this.finishTimer();
  410. return;
  411. }
  412. // 更新剩余时间显示
  413. const minutes = Math.floor(remaining / 60000);
  414. const seconds = Math.floor((remaining % 60000) / 1000);
  415. const timeStr = `${minutes >= 10 ? minutes : '0' + minutes}:${seconds >= 10 ? seconds : '0' + seconds}`;
  416. this.setData({
  417. remainingTime: remaining,
  418. timeStr: timeStr
  419. });
  420. // 更新进度环
  421. this.drawActive();
  422. // 每秒更新一次
  423. this.timerInterval = setTimeout(() => {
  424. this.updateTimer();
  425. }, 1000);
  426. },
  427. // 重置计时器UI
  428. resetTimerUI: function() {
  429. this.setData({
  430. clockShow: false,
  431. isTimerRunning: false,
  432. photoPath: ''
  433. });
  434. clearTimeout(this.timerInterval);
  435. },
  436. // 完成计时
  437. finishTimer: function() {
  438. this.setData({
  439. isTimerRunning: false,
  440. timeStr: '00:00'
  441. });
  442. clearTimeout(this.timerInterval);
  443. // 显示完成提示
  444. wx.showToast({
  445. title: '专注完成!',
  446. icon: 'success',
  447. duration: 2000
  448. });
  449. },
  450. pause: function () {
  451. clearInterval(this.data.timer);
  452. this.setData({
  453. pauseShow: false,
  454. continueCancleShow: true,
  455. okShow: false
  456. });
  457. },
  458. continue: function () {
  459. this.drawActive();
  460. this.setData({
  461. pauseShow: true,
  462. continueCancleShow: false,
  463. okShow: false
  464. });
  465. },
  466. cancle: function () {
  467. clearInterval(this.data.timer);
  468. this.setData({
  469. pauseShow: true,
  470. continueCancleShow: false,
  471. okShow: false,
  472. clockShow: false
  473. });
  474. },
  475. ok: function () {
  476. clearInterval(this.data.timer);
  477. this.setData({
  478. pauseShow: true,
  479. continueCancleShow: false,
  480. okShow: false,
  481. clockShow: false
  482. });
  483. }
  484. })