Initial Commit
@@ -0,0 +1,7 @@
|
||||
/.nyc_output
|
||||
/coverage
|
||||
/node_modules
|
||||
/tests/fixtures
|
||||
/sign
|
||||
/dist
|
||||
/build
|
||||
@@ -0,0 +1,7 @@
|
||||
/.nyc_output
|
||||
/coverage
|
||||
/node_modules
|
||||
/sign
|
||||
/dist
|
||||
/build
|
||||
.husky/
|
||||
@@ -0,0 +1,16 @@
|
||||
node_modules
|
||||
build
|
||||
dist
|
||||
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
*.log
|
||||
*.iml
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
.nyc_output
|
||||
/coverage
|
||||
|
||||
/logs
|
||||
@@ -0,0 +1,22 @@
|
||||
module.exports = {
|
||||
printWidth: 100, // 指定代码长度,超出换行
|
||||
tabWidth: 2, // tab 键的宽度
|
||||
useTabs: false, // 使用空格替代tab
|
||||
semi: false, // 结尾加上分号
|
||||
singleQuote: false, // 使用单引号
|
||||
quoteProps: "consistent", // 要求对象字面量属性是否使用引号包裹,(‘as-needed’: 没有特殊要求,禁止使用,'consistent': 保持一致 , preserve: 不限制,想用就用)
|
||||
trailingComma: "none", // 不添加对象和数组最后一个元素的逗号
|
||||
bracketSpacing: false, // 对象中对空格和空行进行处理
|
||||
jsxBracketSameLine: false, // 在多行JSX元素的最后一行追加 >
|
||||
requirePragma: false, // 是否严格按照文件顶部的特殊注释格式化代码
|
||||
insertPragma: false, // 是否在格式化的文件顶部插入Pragma标记,以表明该文件被prettier格式化过了
|
||||
proseWrap: "preserve", // 按照文件原样折行
|
||||
htmlWhitespaceSensitivity: "ignore", // html文件的空格敏感度,控制空格是否影响布局
|
||||
endOfLine: "auto", // 结尾是 \n \r \n\r auto
|
||||
overrides: [
|
||||
{
|
||||
files: "*.ux",
|
||||
options: {parser: "vue"}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
module.exports = {
|
||||
extends: [
|
||||
"stylelint-config-standard",
|
||||
"stylelint-config-recess-order"
|
||||
// "stylelint-selector-bem-pattern"
|
||||
],
|
||||
ignoreFiles: ["node_modules", "test", "dist", "**/*.js"],
|
||||
rules: {
|
||||
"no-descending-specificity": null,
|
||||
"color-hex-case": "lower",
|
||||
"color-hex-length": "short",
|
||||
"at-rule-no-unknown": null,
|
||||
"block-no-empty": null,
|
||||
"selector-pseudo-class-no-unknown": [
|
||||
true,
|
||||
{
|
||||
ignorePseudoClasses: ["blur"]
|
||||
}
|
||||
],
|
||||
"property-no-unknown": [
|
||||
true,
|
||||
{
|
||||
ignoreProperties: [
|
||||
"placeholder-color",
|
||||
"gradient-start",
|
||||
"gradient-center",
|
||||
"gradient-end",
|
||||
"caret-color",
|
||||
"selected-color",
|
||||
"block-color"
|
||||
]
|
||||
}
|
||||
],
|
||||
"max-line-length": null,
|
||||
// "indentation": 2,
|
||||
// "no-empty-source": null,
|
||||
"selector-type-no-unknown": [
|
||||
true,
|
||||
{
|
||||
ignoreTypes: ["selected-color", "block-color"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
module.exports = {
|
||||
extends: ["@commitlint/config-conventional"],
|
||||
rules: {
|
||||
"type-enum": [
|
||||
2,
|
||||
"always",
|
||||
[
|
||||
"bug", // 此项特别针对bug号,用于向测试反馈bug列表的bug修改情况
|
||||
"feat", // 新功能(feature)
|
||||
"fix", // 修补bug
|
||||
"docs", // 文档(documentation)
|
||||
"style", // 格式(不影响代码运行的变动)
|
||||
"refactor", // 重构(即不是新增功能,也不是修改bug的代码变动)
|
||||
"test", // 增加测试
|
||||
"chore", // 构建过程或辅助工具的变动
|
||||
"revert", // feat(pencil): add ‘graphiteWidth’ option (撤销之前的commit)
|
||||
"merge" // 合并分支, 例如: merge(前端页面): feature-xxxx修改线程地址
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
npx husky install
|
||||
npx husky add .husky/pre-commit 'npx lint-staged'
|
||||
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit ${1}'
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "tmt-clock",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"engines": {
|
||||
"node": ">=8.10"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "aiot start --watch",
|
||||
"build": "aiot build",
|
||||
"release": "aiot release",
|
||||
"lint": "eslint --format codeframe --fix --ext .ux,.js src/"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ux,js}": [
|
||||
"prettier --write",
|
||||
"eslint --format codeframe --fix",
|
||||
"git add"
|
||||
],
|
||||
"*.{less,css}": [
|
||||
"prettier --write",
|
||||
"stylelint --fix --custom-syntax postcss-less",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aiot-toolkit/jsc": "^1.0.3",
|
||||
"aiot-toolkit": "^2.0.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<script>
|
||||
import audio from "@system.audio"
|
||||
|
||||
export default {
|
||||
onCreate() {
|
||||
audio.src = "/common/blank.wav"
|
||||
audio.loop = true
|
||||
audio.volume = 0
|
||||
audio.play()
|
||||
console.log("app created")
|
||||
},
|
||||
onDestroy() {
|
||||
audio.stop()
|
||||
console.log("app destroyed")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
After Width: | Height: | Size: 367 B |
|
After Width: | Height: | Size: 537 B |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 313 B |
|
After Width: | Height: | Size: 353 B |
|
After Width: | Height: | Size: 345 B |
|
After Width: | Height: | Size: 252 B |
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1 @@
|
||||
{"a":{"b":"default hello"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"a":{"b":"hello"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"a":{"b":"你好"}}
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"package": "cn.seedsoft.tmtclock",
|
||||
"name": "番茄钟",
|
||||
"versionName": "1.0.0",
|
||||
"versionCode": 1,
|
||||
"minPlatformVersion": 1000,
|
||||
"icon": "/common/logo.png",
|
||||
"deviceTypeList": [
|
||||
"watch"
|
||||
],
|
||||
"features": [
|
||||
{
|
||||
"name": "system.router"
|
||||
},
|
||||
{
|
||||
"name": "system.device"
|
||||
},
|
||||
{
|
||||
"name": "system.file"
|
||||
},
|
||||
{
|
||||
"name": "system.vibrator"
|
||||
},
|
||||
{
|
||||
"name": "system.audio"
|
||||
}
|
||||
],
|
||||
"config": {
|
||||
"logLevel": "log",
|
||||
"designWidth": "device-width",
|
||||
"background": {
|
||||
"features": ["system.audio"]
|
||||
}
|
||||
},
|
||||
"router": {
|
||||
"entry": "pages/index",
|
||||
"pages": {
|
||||
"pages/index": {
|
||||
"component": "index"
|
||||
},
|
||||
"pages/detail": {
|
||||
"component": "detail"
|
||||
},
|
||||
"pages/s4": {
|
||||
"component": "s4"
|
||||
},
|
||||
"pages/s5/home": {
|
||||
"component": "home"
|
||||
},
|
||||
"pages/s5/picker": {
|
||||
"component": "picker"
|
||||
},
|
||||
"pages/s5/timer": {
|
||||
"component": "timer"
|
||||
},
|
||||
"pages/5/home": {
|
||||
"component": "home"
|
||||
},
|
||||
"pages/5/picker": {
|
||||
"component": "picker"
|
||||
},
|
||||
"pages/5/timer": {
|
||||
"component": "timer"
|
||||
},
|
||||
"pages/s4-picker": {
|
||||
"component": "s4-picker"
|
||||
},
|
||||
"pages/s4-timer": {
|
||||
"component": "s4-timer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<div class="home-page">
|
||||
<text class="page-title">欢迎使用</text>
|
||||
<text class="clock-label">{{ time }}</text>
|
||||
|
||||
<image class="logo" src="/common/logo.png"></image>
|
||||
<text class="main-title">番茄钟</text>
|
||||
<div class="settings-entry" onclick="openPicker">
|
||||
<text class="settings-label">设置时长</text>
|
||||
<image class="chevron" src="/common/icons/MdiChevronRight.png"></image>
|
||||
</div>
|
||||
|
||||
<div class="start-button" onclick="startTimer">
|
||||
<image class="button-background" src="/common/icons/button.png"></image>
|
||||
<image class="play-icon" src="/common/icons/MdiPlay.png"></image>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import router from "@system.router"
|
||||
import file from "@system.file"
|
||||
|
||||
export default {
|
||||
private: {
|
||||
time: "00:00",
|
||||
timer: null,
|
||||
duration: "m:20"
|
||||
},
|
||||
|
||||
onInit() {
|
||||
this.loadDuration()
|
||||
this.updateTime()
|
||||
this.timer = setInterval(() => this.updateTime(), 1000)
|
||||
},
|
||||
|
||||
onDestroy() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
},
|
||||
|
||||
updateTime() {
|
||||
const now = new Date()
|
||||
const pad = (value) => (value < 10 ? `0${value}` : `${value}`)
|
||||
|
||||
this.time = `${pad(now.getHours())}:${pad(now.getMinutes())}`
|
||||
},
|
||||
|
||||
openPicker() {
|
||||
file.readText({
|
||||
uri: "internal://files/focus-duration.txt",
|
||||
success: (data) => this.openPickerWithDuration(data.text || "m:20"),
|
||||
fail: () => this.openPickerWithDuration(this.duration || "m:20")
|
||||
})
|
||||
},
|
||||
|
||||
openPickerWithDuration(duration) {
|
||||
router.push({
|
||||
uri: "/pages/5/picker",
|
||||
params: {
|
||||
duration: duration
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
startTimer() {
|
||||
file.readText({
|
||||
uri: "internal://files/focus-duration.txt",
|
||||
success: (data) => this.openTimer(data.text || "m:20"),
|
||||
fail: () => this.openTimer(this.duration || "m:20")
|
||||
})
|
||||
},
|
||||
|
||||
openTimer(duration) {
|
||||
router.push({
|
||||
uri: "/pages/5/timer",
|
||||
params: {
|
||||
duration: duration
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
loadDuration() {
|
||||
file.readText({
|
||||
uri: "internal://files/focus-duration.txt",
|
||||
success: (data) => {
|
||||
if (data.text) {
|
||||
this.duration = data.text
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
this.duration = "m:20"
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.home-page {
|
||||
position: relative;
|
||||
width: 432px;
|
||||
height: 514px;
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
position: absolute;
|
||||
left: 33px;
|
||||
top: 19px;
|
||||
width: 255px;
|
||||
height: 45px;
|
||||
color: #ffffff;
|
||||
font-size: 30px;
|
||||
font-weight: 500;
|
||||
line-height: 44.8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.clock-label {
|
||||
position: absolute;
|
||||
left: 276px;
|
||||
top: 19px;
|
||||
width: 130px;
|
||||
height: 45px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 30px;
|
||||
font-weight: 500;
|
||||
line-height: 33.6px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.logo {
|
||||
position: absolute;
|
||||
left: 152px;
|
||||
top: 145px;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
position: absolute;
|
||||
left: 106px;
|
||||
top: 279px;
|
||||
width: 220px;
|
||||
height: 54px;
|
||||
color: #ffffff;
|
||||
font-size: 40px;
|
||||
font-weight: 500;
|
||||
line-height: 53.04px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.settings-entry {
|
||||
position: absolute;
|
||||
left: 152px;
|
||||
top: 333px;
|
||||
width: 135px;
|
||||
height: 36px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.settings-label {
|
||||
width: 114px;
|
||||
height: 36px;
|
||||
color: #ffffff;
|
||||
font-size: 28px;
|
||||
font-weight: 500;
|
||||
line-height: 37.13px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.chevron {
|
||||
position: absolute;
|
||||
left: 114px;
|
||||
top: 7px;
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
.start-button {
|
||||
position: absolute;
|
||||
left: 141px;
|
||||
top: 431px;
|
||||
width: 151px;
|
||||
height: 69px;
|
||||
}
|
||||
|
||||
.button-background {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
width: 151px;
|
||||
height: 69px;
|
||||
}
|
||||
|
||||
.play-icon {
|
||||
position: absolute;
|
||||
left: 53px;
|
||||
top: 13px;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<div class="picker-page">
|
||||
<text class="page-title">设置时长</text>
|
||||
<text class="clock-label">{{ time }}</text>
|
||||
|
||||
<div class="picker-stage">
|
||||
<picker
|
||||
class="duration-picker"
|
||||
type="text"
|
||||
range="{{durations}}"
|
||||
selected="{{selectedIndex}}"
|
||||
onchange="onDurationChange"
|
||||
></picker>
|
||||
</div>
|
||||
|
||||
<div class="confirm-button" onclick="confirmPicker">
|
||||
<image class="button-background" src="/common/icons/button.png"></image>
|
||||
<image class="check-icon" src="/common/icons/check.png"></image>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import router from "@system.router"
|
||||
import file from "@system.file"
|
||||
|
||||
export default {
|
||||
protected: {
|
||||
duration: "m:20"
|
||||
},
|
||||
|
||||
private: {
|
||||
time: "00:00",
|
||||
timer: null,
|
||||
durations: [
|
||||
"5秒", "5分钟", "20分钟", "40分钟", "1小时", "2小时",
|
||||
"5秒", "5分钟", "20分钟", "40分钟", "1小时", "2小时",
|
||||
"5秒", "5分钟", "20分钟", "40分钟", "1小时", "2小时",
|
||||
"5秒", "5分钟", "20分钟", "40分钟", "1小时", "2小时",
|
||||
"5秒", "5分钟", "20分钟", "40分钟", "1小时", "2小时"
|
||||
],
|
||||
selectedIndex: 20,
|
||||
selectedDurationConfig: "m:20"
|
||||
},
|
||||
|
||||
onInit() {
|
||||
this.selectedIndex = this.indexForDuration(this.duration)
|
||||
this.selectedDurationConfig = this.durationConfig(this.durations[this.selectedIndex])
|
||||
this.updateTime()
|
||||
this.timer = setInterval(() => this.updateTime(), 1000)
|
||||
},
|
||||
|
||||
onDestroy() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
},
|
||||
|
||||
updateTime() {
|
||||
const now = new Date()
|
||||
const pad = (value) => (value < 10 ? `0${value}` : `${value}`)
|
||||
this.time = `${pad(now.getHours())}:${pad(now.getMinutes())}`
|
||||
},
|
||||
|
||||
onDurationChange(event) {
|
||||
this.selectedIndex = event.newSelected
|
||||
const label = this.durations[event.newSelected] || event.newValue
|
||||
this.selectedDurationConfig = this.durationConfig(label)
|
||||
},
|
||||
|
||||
confirmPicker() {
|
||||
file.writeText({
|
||||
uri: "internal://files/focus-duration.txt",
|
||||
text: this.selectedDurationConfig,
|
||||
success: () => router.back(),
|
||||
fail: () => router.back()
|
||||
})
|
||||
},
|
||||
|
||||
durationValue(label) {
|
||||
if (label === "5秒") return 5
|
||||
if (label === "1小时") return 60
|
||||
if (label === "2小时") return 120
|
||||
return parseInt(label, 10)
|
||||
},
|
||||
|
||||
durationConfig(label) {
|
||||
return label === "5秒" ? "s:5" : `m:${this.durationValue(label)}`
|
||||
},
|
||||
|
||||
indexForDuration(config) {
|
||||
const labels = ["s:5", "m:5", "m:20", "m:40", "m:60", "m:120"]
|
||||
const normalized = config && config.indexOf(":") < 0 ? `m:${config}` : config
|
||||
const offset = labels.indexOf(normalized)
|
||||
return 12 + (offset >= 0 ? offset : 2)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.picker-page {
|
||||
position: relative;
|
||||
width: 432px;
|
||||
height: 514px;
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
position: absolute;
|
||||
left: 33px;
|
||||
top: 19px;
|
||||
width: 255px;
|
||||
height: 45px;
|
||||
color: #ffffff;
|
||||
font-size: 30px;
|
||||
font-weight: 500;
|
||||
line-height: 44.8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.clock-label {
|
||||
position: absolute;
|
||||
left: 276px;
|
||||
top: 19px;
|
||||
width: 130px;
|
||||
height: 45px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 30px;
|
||||
font-weight: 500;
|
||||
line-height: 33.6px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.duration-picker {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
width: 228px;
|
||||
height: 162px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 40px;
|
||||
selected-color: #ffffff;
|
||||
selected-font-size: 40px;
|
||||
}
|
||||
|
||||
.picker-stage {
|
||||
position: absolute;
|
||||
left: 102px;
|
||||
top: 176px;
|
||||
width: 228px;
|
||||
height: 162px;
|
||||
}
|
||||
|
||||
.confirm-button {
|
||||
position: absolute;
|
||||
left: 141px;
|
||||
top: 431px;
|
||||
width: 151px;
|
||||
height: 69px;
|
||||
}
|
||||
|
||||
.button-background {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
width: 151px;
|
||||
height: 69px;
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
position: absolute;
|
||||
left: 56px;
|
||||
top: 16px;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,304 @@
|
||||
<template>
|
||||
<swiper class="timer-swiper" vertical="true" indicator="false" loop="false">
|
||||
<div class="timer-page">
|
||||
<text class="page-title">{{isResting ? "休息中" : "专注中"}}</text>
|
||||
<text class="clock-label">{{ time }}</text>
|
||||
<text class="countdown">{{ countdown }}</text>
|
||||
<text class="focus-label">{{isResting ? "休息中" : "专注中"}}</text>
|
||||
|
||||
<div class="stop-button" onclick="stopTimer">
|
||||
<image class="button-background" src="/common/icons/button_grey.png"></image>
|
||||
<image class="stop-icon" src="/common/icons/stop.png"></image>
|
||||
</div>
|
||||
<div class="pause-button" onclick="togglePause">
|
||||
<image class="button-background" src="{{isResting ? '/common/icons/button_yellow.png' : '/common/icons/button2.png'}}"></image>
|
||||
<image class="pause-icon" src="{{paused ? '/common/icons/MdiPlay.png' : '/common/icons/pause.png'}}"></image>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-page">
|
||||
<text class="page-title">专注统计</text>
|
||||
<text class="clock-label">{{ time }}</text>
|
||||
<text class="stats-title focus-title">累计专注</text>
|
||||
<text class="stats-value focus-value">{{ focusTotalText }}</text>
|
||||
<text class="stats-title rest-title">累计休息</text>
|
||||
<text class="stats-value rest-value">{{ restTotalText }}</text>
|
||||
</div>
|
||||
</swiper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import router from "@system.router"
|
||||
import file from "@system.file"
|
||||
import vibrator from "@system.vibrator"
|
||||
|
||||
export default {
|
||||
protected: { duration: "m:20" },
|
||||
private: {
|
||||
time: "00:00",
|
||||
countdown: "20:00",
|
||||
clockTimer: null,
|
||||
countdownTimer: null,
|
||||
remaining: 1200,
|
||||
paused: false,
|
||||
isResting: false,
|
||||
focusDurationSeconds: 1200,
|
||||
focusTotalSeconds: 0,
|
||||
restTotalSeconds: 0,
|
||||
focusTotalText: "00:00:00",
|
||||
restTotalText: "00:00:00"
|
||||
},
|
||||
|
||||
onInit() {
|
||||
this.loadStats()
|
||||
this.startCountdown(this.durationSeconds(this.duration))
|
||||
this.updateTime()
|
||||
this.clockTimer = setInterval(() => this.updateTime(), 1000)
|
||||
},
|
||||
|
||||
onDestroy() {
|
||||
clearInterval(this.clockTimer)
|
||||
clearInterval(this.countdownTimer)
|
||||
},
|
||||
|
||||
startCountdown(seconds) {
|
||||
clearInterval(this.countdownTimer)
|
||||
this.focusDurationSeconds = seconds > 0 ? seconds : 1200
|
||||
this.remaining = this.focusDurationSeconds
|
||||
this.isResting = false
|
||||
this.paused = false
|
||||
this.updateCountdown()
|
||||
this.countdownTimer = setInterval(() => this.tick(), 1000)
|
||||
},
|
||||
|
||||
startRest() {
|
||||
this.remaining = 300
|
||||
this.isResting = true
|
||||
this.paused = true
|
||||
this.updateCountdown()
|
||||
},
|
||||
|
||||
updateTime() {
|
||||
const now = new Date()
|
||||
const pad = (value) => (value < 10 ? `0${value}` : `${value}`)
|
||||
this.time = `${pad(now.getHours())}:${pad(now.getMinutes())}`
|
||||
},
|
||||
|
||||
tick() {
|
||||
if (this.paused || this.remaining <= 0) return
|
||||
this.remaining -= 1
|
||||
if (this.isResting) this.restTotalSeconds += 1
|
||||
else this.focusTotalSeconds += 1
|
||||
this.updateStats()
|
||||
this.persistStats()
|
||||
this.updateCountdown()
|
||||
if (this.remaining === 0) {
|
||||
if (this.isResting) {
|
||||
vibrator.vibrate({ mode: "short" })
|
||||
this.startCountdown(this.focusDurationSeconds)
|
||||
} else {
|
||||
vibrator.vibrate({ mode: "long" })
|
||||
this.startRest()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
durationSeconds(config) {
|
||||
if (config && config.indexOf("s:") === 0) return parseInt(config.substring(2), 10) || 5
|
||||
if (config && config.indexOf("m:") === 0) return (parseInt(config.substring(2), 10) || 20) * 60
|
||||
return (parseInt(config, 10) || 20) * 60
|
||||
},
|
||||
|
||||
updateCountdown() {
|
||||
const minutes = Math.floor(this.remaining / 60)
|
||||
const seconds = this.remaining % 60
|
||||
const pad = (value) => (value < 10 ? `0${value}` : `${value}`)
|
||||
this.countdown = `${pad(minutes)}:${pad(seconds)}`
|
||||
},
|
||||
|
||||
updateStats() {
|
||||
this.focusTotalText = this.formatDuration(this.focusTotalSeconds)
|
||||
this.restTotalText = this.formatDuration(this.restTotalSeconds)
|
||||
},
|
||||
|
||||
formatDuration(totalSeconds) {
|
||||
const hours = Math.floor(totalSeconds / 3600)
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60)
|
||||
const seconds = totalSeconds % 60
|
||||
const pad = (value) => (value < 10 ? `0${value}` : `${value}`)
|
||||
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`
|
||||
},
|
||||
|
||||
loadStats() {
|
||||
file.readText({
|
||||
uri: "internal://files/focus-stats.json",
|
||||
success: (data) => {
|
||||
try {
|
||||
const stats = JSON.parse(data.text)
|
||||
if (stats.date === this.today()) {
|
||||
this.focusTotalSeconds = parseInt(stats.focus, 10) || 0
|
||||
this.restTotalSeconds = parseInt(stats.rest, 10) || 0
|
||||
}
|
||||
} catch (error) {}
|
||||
this.updateStats()
|
||||
},
|
||||
fail: () => this.updateStats()
|
||||
})
|
||||
},
|
||||
|
||||
persistStats() {
|
||||
file.writeText({
|
||||
uri: "internal://files/focus-stats.json",
|
||||
text: JSON.stringify({
|
||||
date: this.today(),
|
||||
focus: this.focusTotalSeconds,
|
||||
rest: this.restTotalSeconds
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
today() {
|
||||
const now = new Date()
|
||||
const pad = (value) => (value < 10 ? `0${value}` : `${value}`)
|
||||
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`
|
||||
},
|
||||
|
||||
togglePause() {
|
||||
this.paused = !this.paused
|
||||
},
|
||||
|
||||
stopTimer() {
|
||||
router.back()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.timer-swiper,
|
||||
.timer-page,
|
||||
.stats-page {
|
||||
width: 432px;
|
||||
height: 514px;
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.timer-page,
|
||||
.stats-page {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
position: absolute;
|
||||
left: 33px;
|
||||
top: 19px;
|
||||
width: 255px;
|
||||
height: 45px;
|
||||
color: #ffffff;
|
||||
font-size: 30px;
|
||||
font-weight: 500;
|
||||
line-height: 44.8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.clock-label {
|
||||
position: absolute;
|
||||
left: 276px;
|
||||
top: 19px;
|
||||
width: 130px;
|
||||
height: 45px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 30px;
|
||||
font-weight: 500;
|
||||
line-height: 33.6px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.countdown {
|
||||
position: absolute;
|
||||
left: 66px;
|
||||
top: 189px;
|
||||
width: 300px;
|
||||
height: 97px;
|
||||
color: #ffffff;
|
||||
font-size: 64px;
|
||||
font-weight: 500;
|
||||
line-height: 84.86px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.focus-label {
|
||||
position: absolute;
|
||||
left: 66px;
|
||||
top: 287px;
|
||||
width: 300px;
|
||||
height: 38px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 32px;
|
||||
font-weight: 500;
|
||||
line-height: 42.43px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stop-button,
|
||||
.pause-button {
|
||||
position: absolute;
|
||||
top: 435px;
|
||||
width: 140px;
|
||||
height: 69px;
|
||||
}
|
||||
|
||||
.stop-button { left: 66px; }
|
||||
.pause-button { left: 226px; }
|
||||
|
||||
.button-background {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
width: 140px;
|
||||
height: 69px;
|
||||
}
|
||||
|
||||
.stop-icon {
|
||||
position: absolute;
|
||||
left: 59px;
|
||||
top: 23px;
|
||||
width: 22px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.pause-icon {
|
||||
position: absolute;
|
||||
left: 53px;
|
||||
top: 16px;
|
||||
width: 35px;
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
.stats-title,
|
||||
.stats-value {
|
||||
position: absolute;
|
||||
left: 66px;
|
||||
width: 300px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stats-title {
|
||||
height: 43px;
|
||||
font-size: 32px;
|
||||
font-weight: 500;
|
||||
line-height: 42.43px;
|
||||
}
|
||||
|
||||
.stats-value {
|
||||
height: 50px;
|
||||
color: #ffffff;
|
||||
font-size: 40px;
|
||||
font-weight: 500;
|
||||
line-height: 53.04px;
|
||||
}
|
||||
|
||||
.focus-title { top: 161px; color: rgba(112, 119, 255, 1); }
|
||||
.focus-value { top: 204px; }
|
||||
.rest-title { top: 260px; color: rgba(149, 168, 0, 1); }
|
||||
.rest-value { top: 303px; }
|
||||
</style>
|
||||
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div class="demo-page">
|
||||
<text class="title">{{ text }}</text>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
private: {
|
||||
text: "欢迎打开详情页"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.demo-page {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div class="demo-page">
|
||||
<text class="title">正在识别设备屏幕</text>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import router from "@system.router"
|
||||
import device from "@system.device"
|
||||
|
||||
export default {
|
||||
onInit() {
|
||||
device.getInfo({
|
||||
success: (info) => {
|
||||
if (info.screenWidth === 432 && info.screenHeight === 514) {
|
||||
router.replace({
|
||||
uri: "/pages/5/home"
|
||||
})
|
||||
} else if (info.screenWidth === 480 && info.screenHeight === 480) {
|
||||
router.replace({
|
||||
uri: "/pages/s5/home"
|
||||
})
|
||||
} else if (info.screenWidth === 466 && info.screenHeight === 466) {
|
||||
router.replace({
|
||||
uri: "/pages/s4"
|
||||
})
|
||||
} else {
|
||||
router.replace({
|
||||
uri: "/pages/detail"
|
||||
})
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
router.replace({
|
||||
uri: "/pages/detail"
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.demo-page {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<div class="picker-page">
|
||||
<text class="clock-label">{{ time }}</text>
|
||||
<text class="page-title">设置时长</text>
|
||||
|
||||
<div class="picker-stage">
|
||||
<picker
|
||||
class="duration-picker"
|
||||
type="text"
|
||||
range="{{durations}}"
|
||||
selected="{{selectedIndex}}"
|
||||
onchange="onDurationChange"
|
||||
></picker>
|
||||
</div>
|
||||
|
||||
<div class="confirm-button" onclick="confirmPicker">
|
||||
<image class="button-background" src="/common/icons/button.png"></image>
|
||||
<image class="check-icon" src="/common/icons/check.png"></image>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import router from "@system.router"
|
||||
import file from "@system.file"
|
||||
|
||||
export default {
|
||||
protected: {
|
||||
duration: "m:20"
|
||||
},
|
||||
|
||||
private: {
|
||||
time: "00:00",
|
||||
timer: null,
|
||||
durations: [
|
||||
"5秒", "5分钟", "20分钟", "40分钟", "1小时", "2小时",
|
||||
"5秒", "5分钟", "20分钟", "40分钟", "1小时", "2小时",
|
||||
"5秒", "5分钟", "20分钟", "40分钟", "1小时", "2小时",
|
||||
"5秒", "5分钟", "20分钟", "40分钟", "1小时", "2小时",
|
||||
"5秒", "5分钟", "20分钟", "40分钟", "1小时", "2小时"
|
||||
],
|
||||
selectedIndex: 20,
|
||||
selectedDuration: 1200,
|
||||
selectedDurationConfig: "m:20"
|
||||
},
|
||||
|
||||
onInit() {
|
||||
this.selectedIndex = this.indexForDuration(this.duration)
|
||||
this.selectedDuration = this.durationValue(this.durations[this.selectedIndex])
|
||||
this.selectedDurationConfig = this.durationConfig(this.durations[this.selectedIndex])
|
||||
this.updateTime()
|
||||
this.timer = setInterval(() => this.updateTime(), 1000)
|
||||
},
|
||||
|
||||
onDestroy() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
},
|
||||
|
||||
updateTime() {
|
||||
const now = new Date()
|
||||
const pad = (value) => (value < 10 ? `0${value}` : `${value}`)
|
||||
|
||||
this.time = `${pad(now.getHours())}:${pad(now.getMinutes())}`
|
||||
},
|
||||
|
||||
onDurationChange(event) {
|
||||
this.selectedIndex = event.newSelected
|
||||
const label = this.durations[event.newSelected] || event.newValue
|
||||
this.selectedDuration = this.durationValue(label)
|
||||
this.selectedDurationConfig = this.durationConfig(label)
|
||||
},
|
||||
|
||||
confirmPicker() {
|
||||
file.writeText({
|
||||
uri: "internal://files/focus-duration.txt",
|
||||
text: this.selectedDurationConfig,
|
||||
success: () => router.back(),
|
||||
fail: () => router.back()
|
||||
})
|
||||
},
|
||||
|
||||
durationValue(label) {
|
||||
if (label === "5秒") {
|
||||
return 5
|
||||
}
|
||||
if (label === "1小时") {
|
||||
return 60
|
||||
}
|
||||
if (label === "2小时") {
|
||||
return 120
|
||||
}
|
||||
return parseInt(label, 10)
|
||||
},
|
||||
|
||||
durationConfig(label) {
|
||||
return label === "5秒" ? "s:5" : `m:${this.durationValue(label)}`
|
||||
},
|
||||
|
||||
indexForDuration(config) {
|
||||
const labels = ["s:5", "m:5", "m:20", "m:40", "m:60", "m:120"]
|
||||
const legacyConfig = config && config.indexOf(":") < 0
|
||||
? `m:${config}`
|
||||
: config
|
||||
const offset = labels.indexOf(legacyConfig)
|
||||
return 12 + (offset >= 0 ? offset : 2)
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.picker-page {
|
||||
position: relative;
|
||||
width: 466px;
|
||||
height: 466px;
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.clock-label {
|
||||
position: absolute;
|
||||
left: 169px;
|
||||
top: 0px;
|
||||
width: 130px;
|
||||
height: 45px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
line-height: 33.6px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
position: absolute;
|
||||
left: 107px;
|
||||
top: 34px;
|
||||
width: 255px;
|
||||
height: 45px;
|
||||
color: #ffffff;
|
||||
font-size: 30px;
|
||||
font-weight: 500;
|
||||
line-height: 44.8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.duration-picker {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 228px;
|
||||
height: 162px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 40px;
|
||||
selected-color: #ffffff;
|
||||
selected-font-size: 40px;
|
||||
}
|
||||
|
||||
.picker-stage {
|
||||
position: absolute;
|
||||
left: 119px;
|
||||
top: 152px;
|
||||
width: 228px;
|
||||
height: 162px;
|
||||
}
|
||||
|
||||
.confirm-button {
|
||||
position: absolute;
|
||||
left: 158px;
|
||||
top: 391px;
|
||||
width: 151px;
|
||||
height: 69px;
|
||||
}
|
||||
|
||||
.button-background {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
width: 151px;
|
||||
height: 69px;
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
position: absolute;
|
||||
left: 56px;
|
||||
top: 16px;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,400 @@
|
||||
<template>
|
||||
<swiper class="timer-swiper" vertical="true" indicator="false" loop="false">
|
||||
<div class="timer-page">
|
||||
<text class="clock-label">{{ time }}</text>
|
||||
<text class="page-title">{{isResting ? "休息中" : "专注中"}}</text>
|
||||
|
||||
<text class="countdown">{{ countdown }}</text>
|
||||
<text class="focus-label">{{isResting ? "休息中" : "专注中"}}</text>
|
||||
|
||||
<div class="stop-button" onclick="stopTimer">
|
||||
<image class="stop-background" src="/common/icons/button_grey.png"></image>
|
||||
<image class="stop-icon" src="/common/icons/stop.png"></image>
|
||||
</div>
|
||||
<div class="pause-button" onclick="togglePause">
|
||||
<image
|
||||
class="pause-background"
|
||||
src="{{isResting ? '/common/icons/button_yellow.png' : '/common/icons/button2.png'}}"
|
||||
></image>
|
||||
<image
|
||||
class="pause-icon"
|
||||
src="{{paused ? '/common/icons/MdiPlay.png' : '/common/icons/pause.png'}}"
|
||||
></image>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-page">
|
||||
<text class="stats-clock-label">{{ time }}</text>
|
||||
<text class="stats-page-title">专注统计</text>
|
||||
|
||||
<text class="focus-total-title">累计专注</text>
|
||||
<text class="focus-total-value">{{ focusTotalText }}</text>
|
||||
<text class="rest-total-title">累计休息</text>
|
||||
<text class="rest-total-value">{{ restTotalText }}</text>
|
||||
</div>
|
||||
</swiper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import router from "@system.router"
|
||||
import file from "@system.file"
|
||||
import vibrator from "@system.vibrator"
|
||||
|
||||
export default {
|
||||
protected: {
|
||||
duration: "m:20"
|
||||
},
|
||||
|
||||
private: {
|
||||
time: "00:00",
|
||||
countdown: "20:00",
|
||||
clockTimer: null,
|
||||
countdownTimer: null,
|
||||
remaining: 1200,
|
||||
paused: false,
|
||||
isResting: false,
|
||||
focusDurationSeconds: 1200,
|
||||
focusTotalSeconds: 0,
|
||||
restTotalSeconds: 0,
|
||||
focusTotalText: "00:00:00",
|
||||
restTotalText: "00:00:00"
|
||||
},
|
||||
|
||||
onInit() {
|
||||
this.loadStats()
|
||||
const duration = this.durationSeconds(this.duration)
|
||||
if (duration > 0) {
|
||||
this.startCountdown(duration)
|
||||
} else {
|
||||
this.loadDuration()
|
||||
}
|
||||
this.updateTime()
|
||||
this.clockTimer = setInterval(() => this.updateTime(), 1000)
|
||||
},
|
||||
|
||||
onDestroy() {
|
||||
clearInterval(this.clockTimer)
|
||||
clearInterval(this.countdownTimer)
|
||||
},
|
||||
|
||||
loadDuration() {
|
||||
file.readText({
|
||||
uri: "internal://files/focus-duration.txt",
|
||||
success: (data) => this.startCountdown(this.durationSeconds(data.text)),
|
||||
fail: () => this.startCountdown(1200)
|
||||
})
|
||||
},
|
||||
|
||||
startCountdown(seconds) {
|
||||
clearInterval(this.countdownTimer)
|
||||
this.focusDurationSeconds = seconds > 0 ? seconds : 1200
|
||||
this.remaining = this.focusDurationSeconds
|
||||
this.isResting = false
|
||||
this.paused = false
|
||||
this.updateCountdown()
|
||||
this.countdownTimer = setInterval(() => this.tick(), 1000)
|
||||
},
|
||||
|
||||
startRest() {
|
||||
this.remaining = 5 * 60
|
||||
this.isResting = true
|
||||
this.paused = true
|
||||
this.updateCountdown()
|
||||
},
|
||||
|
||||
updateTime() {
|
||||
const now = new Date()
|
||||
const pad = (value) => (value < 10 ? `0${value}` : `${value}`)
|
||||
this.time = `${pad(now.getHours())}:${pad(now.getMinutes())}`
|
||||
},
|
||||
|
||||
tick() {
|
||||
if (this.paused || this.remaining <= 0) {
|
||||
return
|
||||
}
|
||||
this.remaining -= 1
|
||||
if (this.isResting) {
|
||||
this.restTotalSeconds += 1
|
||||
} else {
|
||||
this.focusTotalSeconds += 1
|
||||
}
|
||||
this.updateStats()
|
||||
this.persistStats()
|
||||
this.updateCountdown()
|
||||
if (this.remaining === 0) {
|
||||
if (this.isResting) {
|
||||
vibrator.vibrate({
|
||||
mode: "short"
|
||||
})
|
||||
this.startCountdown(this.focusDurationSeconds)
|
||||
} else {
|
||||
vibrator.vibrate({
|
||||
mode: "long"
|
||||
})
|
||||
this.startRest()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
updateCountdown() {
|
||||
const minutes = Math.floor(this.remaining / 60)
|
||||
const seconds = this.remaining % 60
|
||||
const pad = (value) => (value < 10 ? `0${value}` : `${value}`)
|
||||
this.countdown = `${pad(minutes)}:${pad(seconds)}`
|
||||
},
|
||||
|
||||
durationSeconds(config) {
|
||||
if (config && config.indexOf("s:") === 0) {
|
||||
return parseInt(config.substring(2), 10) || 5
|
||||
}
|
||||
if (config && config.indexOf("m:") === 0) {
|
||||
return (parseInt(config.substring(2), 10) || 20) * 60
|
||||
}
|
||||
return (parseInt(config, 10) || 20) * 60
|
||||
},
|
||||
|
||||
updateStats() {
|
||||
this.focusTotalText = this.formatDuration(this.focusTotalSeconds)
|
||||
this.restTotalText = this.formatDuration(this.restTotalSeconds)
|
||||
},
|
||||
|
||||
formatDuration(totalSeconds) {
|
||||
const hours = Math.floor(totalSeconds / 3600)
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60)
|
||||
const seconds = totalSeconds % 60
|
||||
const pad = (value) => (value < 10 ? `0${value}` : `${value}`)
|
||||
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`
|
||||
},
|
||||
|
||||
loadStats() {
|
||||
file.readText({
|
||||
uri: "internal://files/focus-stats.json",
|
||||
success: (data) => {
|
||||
try {
|
||||
const stats = JSON.parse(data.text)
|
||||
if (stats.date === this.today()) {
|
||||
this.focusTotalSeconds = parseInt(stats.focus, 10) || 0
|
||||
this.restTotalSeconds = parseInt(stats.rest, 10) || 0
|
||||
}
|
||||
this.updateStats()
|
||||
} catch (error) {
|
||||
this.updateStats()
|
||||
}
|
||||
},
|
||||
fail: () => this.updateStats()
|
||||
})
|
||||
},
|
||||
|
||||
persistStats() {
|
||||
file.writeText({
|
||||
uri: "internal://files/focus-stats.json",
|
||||
text: JSON.stringify({
|
||||
date: this.today(),
|
||||
focus: this.focusTotalSeconds,
|
||||
rest: this.restTotalSeconds
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
today() {
|
||||
const now = new Date()
|
||||
const pad = (value) => (value < 10 ? `0${value}` : `${value}`)
|
||||
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`
|
||||
},
|
||||
|
||||
togglePause() {
|
||||
this.paused = !this.paused
|
||||
},
|
||||
|
||||
stopTimer() {
|
||||
router.back()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.timer-swiper {
|
||||
width: 466px;
|
||||
height: 466px;
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.timer-page {
|
||||
position: relative;
|
||||
width: 466px;
|
||||
height: 466px;
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.clock-label {
|
||||
position: absolute;
|
||||
left: 169px;
|
||||
top: 0px;
|
||||
width: 130px;
|
||||
height: 45px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
line-height: 33.6px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
position: absolute;
|
||||
left: 107px;
|
||||
top: 34px;
|
||||
width: 255px;
|
||||
height: 45px;
|
||||
color: #ffffff;
|
||||
font-size: 30px;
|
||||
font-weight: 500;
|
||||
line-height: 44.8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.countdown {
|
||||
position: absolute;
|
||||
left: 83px;
|
||||
top: 165px;
|
||||
width: 300px;
|
||||
height: 97px;
|
||||
color: #ffffff;
|
||||
font-size: 64px;
|
||||
font-weight: 500;
|
||||
line-height: 84.86px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.focus-label {
|
||||
position: absolute;
|
||||
left: 83px;
|
||||
top: 263px;
|
||||
width: 300px;
|
||||
height: 38px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 32px;
|
||||
font-weight: 500;
|
||||
line-height: 42.43px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stop-button,
|
||||
.pause-button {
|
||||
position: absolute;
|
||||
top: 358px;
|
||||
width: 140px;
|
||||
height: 69px;
|
||||
}
|
||||
|
||||
.stop-button {
|
||||
left: 83px;
|
||||
}
|
||||
|
||||
.pause-button {
|
||||
left: 243px;
|
||||
}
|
||||
|
||||
.stop-background,
|
||||
.pause-background {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
width: 140px;
|
||||
height: 69px;
|
||||
}
|
||||
|
||||
.stop-icon {
|
||||
position: absolute;
|
||||
left: 59px;
|
||||
top: 23px;
|
||||
width: 22px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.pause-icon {
|
||||
position: absolute;
|
||||
left: 53px;
|
||||
top: 16px;
|
||||
width: 35px;
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
.stats-page {
|
||||
position: relative;
|
||||
width: 466px;
|
||||
height: 466px;
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.stats-clock-label {
|
||||
position: absolute;
|
||||
left: 169px;
|
||||
top: 0px;
|
||||
width: 130px;
|
||||
height: 45px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
line-height: 33.6px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stats-page-title {
|
||||
position: absolute;
|
||||
left: 107px;
|
||||
top: 34px;
|
||||
width: 255px;
|
||||
height: 45px;
|
||||
color: #ffffff;
|
||||
font-size: 30px;
|
||||
font-weight: 500;
|
||||
line-height: 44.8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.focus-total-title,
|
||||
.rest-total-title,
|
||||
.focus-total-value,
|
||||
.rest-total-value {
|
||||
position: absolute;
|
||||
left: 83px;
|
||||
width: 300px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.focus-total-title {
|
||||
top: 134px;
|
||||
height: 43px;
|
||||
color: rgba(112, 119, 255, 1);
|
||||
font-size: 32px;
|
||||
font-weight: 500;
|
||||
line-height: 42.43px;
|
||||
}
|
||||
|
||||
.focus-total-value {
|
||||
top: 177px;
|
||||
height: 50px;
|
||||
color: #ffffff;
|
||||
font-size: 40px;
|
||||
font-weight: 500;
|
||||
line-height: 53.04px;
|
||||
}
|
||||
|
||||
.rest-total-title {
|
||||
top: 233px;
|
||||
height: 43px;
|
||||
color: rgba(149, 168, 0, 1);
|
||||
font-size: 32px;
|
||||
font-weight: 500;
|
||||
line-height: 42.43px;
|
||||
}
|
||||
|
||||
.rest-total-value {
|
||||
top: 276px;
|
||||
height: 50px;
|
||||
color: #ffffff;
|
||||
font-size: 40px;
|
||||
font-weight: 500;
|
||||
line-height: 53.04px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,214 @@
|
||||
<template>
|
||||
<div class="home-page">
|
||||
<text class="clock-label">{{ time }}</text>
|
||||
<text class="page-title">欢迎使用</text>
|
||||
|
||||
<image class="logo" src="/common/logo.png"></image>
|
||||
<text class="main-title">番茄钟</text>
|
||||
<div class="settings-entry" onclick="openPicker">
|
||||
<text class="settings-label">设置时长</text>
|
||||
<image class="chevron" src="/common/icons/MdiChevronRight.png"></image>
|
||||
</div>
|
||||
|
||||
<div class="start-button" onclick="startTimer">
|
||||
<image class="button-background" src="/common/icons/button.png"></image>
|
||||
<image class="play-icon" src="/common/icons/MdiPlay.png"></image>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import router from "@system.router"
|
||||
import file from "@system.file"
|
||||
|
||||
export default {
|
||||
private: {
|
||||
time: "00:00",
|
||||
timer: null,
|
||||
duration: 20
|
||||
},
|
||||
|
||||
onInit() {
|
||||
this.loadDuration()
|
||||
this.updateTime()
|
||||
this.timer = setInterval(() => this.updateTime(), 1000)
|
||||
},
|
||||
|
||||
onDestroy() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
},
|
||||
|
||||
updateTime() {
|
||||
const now = new Date()
|
||||
const pad = (value) => (value < 10 ? `0${value}` : `${value}`)
|
||||
|
||||
this.time = `${pad(now.getHours())}:${pad(now.getMinutes())}`
|
||||
},
|
||||
|
||||
openPicker() {
|
||||
file.readText({
|
||||
uri: "internal://files/focus-duration.txt",
|
||||
success: (data) => {
|
||||
this.openPickerWithDuration(data.text || "m:20")
|
||||
},
|
||||
fail: () => {
|
||||
this.openPickerWithDuration("m:20")
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
openPickerWithDuration(duration) {
|
||||
router.push({
|
||||
uri: "/pages/s4-picker",
|
||||
params: {
|
||||
duration: `${duration}`
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
startTimer() {
|
||||
file.readText({
|
||||
uri: "internal://files/focus-duration.txt",
|
||||
success: (data) => {
|
||||
this.openTimer(data.text || "m:20")
|
||||
},
|
||||
fail: () => {
|
||||
this.openTimer("m:20")
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
openTimer(duration) {
|
||||
router.push({
|
||||
uri: "/pages/s4-timer",
|
||||
params: {
|
||||
duration: duration
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
loadDuration() {
|
||||
file.readText({
|
||||
uri: "internal://files/focus-duration.txt",
|
||||
success: (data) => {
|
||||
if (data.text) {
|
||||
this.duration = data.text
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
this.duration = "m:20"
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.home-page {
|
||||
position: relative;
|
||||
width: 466px;
|
||||
height: 466px;
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.clock-label {
|
||||
position: absolute;
|
||||
left: 169px;
|
||||
top: 0px;
|
||||
width: 130px;
|
||||
height: 45px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
line-height: 33.6px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
position: absolute;
|
||||
left: 107px;
|
||||
top: 34px;
|
||||
width: 255px;
|
||||
height: 45px;
|
||||
color: #ffffff;
|
||||
font-size: 30px;
|
||||
font-weight: 500;
|
||||
line-height: 44.8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
position: absolute;
|
||||
left: 170px;
|
||||
top: 121px;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
position: absolute;
|
||||
left: 124px;
|
||||
top: 255px;
|
||||
width: 220px;
|
||||
height: 54px;
|
||||
color: #ffffff;
|
||||
font-size: 40px;
|
||||
font-weight: 500;
|
||||
line-height: 53.04px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.settings-entry {
|
||||
position: absolute;
|
||||
left: 170px;
|
||||
top: 309px;
|
||||
width: 135px;
|
||||
height: 36px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.settings-label {
|
||||
width: 114px;
|
||||
height: 36px;
|
||||
color: #ffffff;
|
||||
font-size: 28px;
|
||||
font-weight: 500;
|
||||
line-height: 37.13px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.chevron {
|
||||
position: absolute;
|
||||
left: 114px;
|
||||
top: 7px;
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
.start-button {
|
||||
position: absolute;
|
||||
left: 158px;
|
||||
top: 391px;
|
||||
width: 151px;
|
||||
height: 69px;
|
||||
}
|
||||
|
||||
.button-background {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
width: 151px;
|
||||
height: 69px;
|
||||
}
|
||||
|
||||
.play-icon {
|
||||
position: absolute;
|
||||
left: 53px;
|
||||
top: 13px;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<div class="home-page">
|
||||
<text class="clock-label">{{ time }}</text>
|
||||
<text class="page-title">欢迎使用</text>
|
||||
|
||||
<image class="logo" src="/common/logo.png"></image>
|
||||
<text class="main-title">番茄钟</text>
|
||||
<div class="settings-entry" onclick="openPicker">
|
||||
<text class="settings-label">设置时长</text>
|
||||
<image class="chevron" src="/common/icons/MdiChevronRight.png"></image>
|
||||
</div>
|
||||
|
||||
<div class="start-button" onclick="startTimer">
|
||||
<image class="button-background" src="/common/icons/button.png"></image>
|
||||
<image class="play-icon" src="/common/icons/MdiPlay.png"></image>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import router from "@system.router"
|
||||
import file from "@system.file"
|
||||
|
||||
export default {
|
||||
private: {
|
||||
time: "00:00",
|
||||
timer: null,
|
||||
duration: "m:20"
|
||||
},
|
||||
|
||||
onInit() {
|
||||
this.loadDuration()
|
||||
this.updateTime()
|
||||
this.timer = setInterval(() => this.updateTime(), 1000)
|
||||
},
|
||||
|
||||
onDestroy() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
},
|
||||
|
||||
updateTime() {
|
||||
const now = new Date()
|
||||
const pad = (value) => (value < 10 ? `0${value}` : `${value}`)
|
||||
|
||||
this.time = `${pad(now.getHours())}:${pad(now.getMinutes())}`
|
||||
},
|
||||
|
||||
openPicker() {
|
||||
file.readText({
|
||||
uri: "internal://files/focus-duration.txt",
|
||||
success: (data) => this.openPickerWithDuration(data.text || "m:20"),
|
||||
fail: () => this.openPickerWithDuration(this.duration || "m:20")
|
||||
})
|
||||
},
|
||||
|
||||
openPickerWithDuration(duration) {
|
||||
router.push({
|
||||
uri: "/pages/s5/picker",
|
||||
params: {
|
||||
duration: duration
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
startTimer() {
|
||||
file.readText({
|
||||
uri: "internal://files/focus-duration.txt",
|
||||
success: (data) => this.openTimer(data.text || "m:20"),
|
||||
fail: () => this.openTimer(this.duration || "m:20")
|
||||
})
|
||||
},
|
||||
|
||||
openTimer(duration) {
|
||||
router.push({
|
||||
uri: "/pages/s5/timer",
|
||||
params: {
|
||||
duration: duration
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
loadDuration() {
|
||||
file.readText({
|
||||
uri: "internal://files/focus-duration.txt",
|
||||
success: (data) => {
|
||||
if (data.text) {
|
||||
this.duration = data.text
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
this.duration = "m:20"
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.home-page {
|
||||
position: relative;
|
||||
width: 480px;
|
||||
height: 480px;
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.clock-label {
|
||||
position: absolute;
|
||||
left: 175px;
|
||||
top: 0px;
|
||||
width: 130px;
|
||||
height: 45px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
line-height: 33.6px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
position: absolute;
|
||||
left: 114px;
|
||||
top: 41px;
|
||||
width: 255px;
|
||||
height: 45px;
|
||||
color: #ffffff;
|
||||
font-size: 30px;
|
||||
font-weight: 500;
|
||||
line-height: 44.8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
position: absolute;
|
||||
left: 176px;
|
||||
top: 128px;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
position: absolute;
|
||||
left: 131px;
|
||||
top: 262px;
|
||||
width: 220px;
|
||||
height: 54px;
|
||||
color: #ffffff;
|
||||
font-size: 40px;
|
||||
font-weight: 500;
|
||||
line-height: 53.04px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.settings-entry {
|
||||
position: absolute;
|
||||
left: 176px;
|
||||
top: 316px;
|
||||
width: 135px;
|
||||
height: 36px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.settings-label {
|
||||
width: 114px;
|
||||
height: 36px;
|
||||
color: #ffffff;
|
||||
font-size: 28px;
|
||||
font-weight: 500;
|
||||
line-height: 37.13px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.chevron {
|
||||
position: absolute;
|
||||
left: 114px;
|
||||
top: 7px;
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
.start-button {
|
||||
position: absolute;
|
||||
left: 165px;
|
||||
top: 401px;
|
||||
width: 151px;
|
||||
height: 69px;
|
||||
}
|
||||
|
||||
.button-background {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
width: 151px;
|
||||
height: 69px;
|
||||
}
|
||||
|
||||
.play-icon {
|
||||
position: absolute;
|
||||
left: 53px;
|
||||
top: 13px;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,187 @@
|
||||
<template>
|
||||
<div class="picker-page">
|
||||
<text class="clock-label">{{ time }}</text>
|
||||
<text class="page-title">设置时长</text>
|
||||
|
||||
<div class="picker-stage">
|
||||
<picker
|
||||
class="duration-picker"
|
||||
type="text"
|
||||
range="{{durations}}"
|
||||
selected="{{selectedIndex}}"
|
||||
onchange="onDurationChange"
|
||||
></picker>
|
||||
</div>
|
||||
|
||||
<div class="confirm-button" onclick="confirmPicker">
|
||||
<image class="button-background" src="/common/icons/button.png"></image>
|
||||
<image class="check-icon" src="/common/icons/check.png"></image>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import router from "@system.router"
|
||||
import file from "@system.file"
|
||||
|
||||
export default {
|
||||
protected: {
|
||||
duration: "m:20"
|
||||
},
|
||||
|
||||
private: {
|
||||
time: "00:00",
|
||||
timer: null,
|
||||
durations: [
|
||||
"5秒", "5分钟", "20分钟", "40分钟", "1小时", "2小时",
|
||||
"5秒", "5分钟", "20分钟", "40分钟", "1小时", "2小时",
|
||||
"5秒", "5分钟", "20分钟", "40分钟", "1小时", "2小时",
|
||||
"5秒", "5分钟", "20分钟", "40分钟", "1小时", "2小时",
|
||||
"5秒", "5分钟", "20分钟", "40分钟", "1小时", "2小时"
|
||||
],
|
||||
selectedIndex: 20,
|
||||
selectedDurationConfig: "m:20"
|
||||
},
|
||||
|
||||
onInit() {
|
||||
this.selectedIndex = this.indexForDuration(this.duration)
|
||||
this.selectedDurationConfig = this.durationConfig(this.durations[this.selectedIndex])
|
||||
this.updateTime()
|
||||
this.timer = setInterval(() => this.updateTime(), 1000)
|
||||
},
|
||||
|
||||
onDestroy() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
},
|
||||
|
||||
updateTime() {
|
||||
const now = new Date()
|
||||
const pad = (value) => (value < 10 ? `0${value}` : `${value}`)
|
||||
|
||||
this.time = `${pad(now.getHours())}:${pad(now.getMinutes())}`
|
||||
},
|
||||
|
||||
onDurationChange(event) {
|
||||
this.selectedIndex = event.newSelected
|
||||
const label = this.durations[event.newSelected] || event.newValue
|
||||
this.selectedDurationConfig = this.durationConfig(label)
|
||||
},
|
||||
|
||||
confirmPicker() {
|
||||
file.writeText({
|
||||
uri: "internal://files/focus-duration.txt",
|
||||
text: this.selectedDurationConfig,
|
||||
success: () => router.back(),
|
||||
fail: () => router.back()
|
||||
})
|
||||
},
|
||||
|
||||
durationValue(label) {
|
||||
if (label === "5秒") {
|
||||
return 5
|
||||
}
|
||||
if (label === "1小时") {
|
||||
return 60
|
||||
}
|
||||
if (label === "2小时") {
|
||||
return 120
|
||||
}
|
||||
return parseInt(label, 10)
|
||||
},
|
||||
|
||||
durationConfig(label) {
|
||||
return label === "5秒" ? "s:5" : `m:${this.durationValue(label)}`
|
||||
},
|
||||
|
||||
indexForDuration(config) {
|
||||
const labels = ["s:5", "m:5", "m:20", "m:40", "m:60", "m:120"]
|
||||
const legacyConfig = config && config.indexOf(":") < 0
|
||||
? `m:${config}`
|
||||
: config
|
||||
const offset = labels.indexOf(legacyConfig)
|
||||
return 12 + (offset >= 0 ? offset : 2)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.picker-page {
|
||||
position: relative;
|
||||
width: 480px;
|
||||
height: 480px;
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.clock-label {
|
||||
position: absolute;
|
||||
left: 175px;
|
||||
top: 0px;
|
||||
width: 130px;
|
||||
height: 45px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
line-height: 33.6px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
position: absolute;
|
||||
left: 114px;
|
||||
top: 41px;
|
||||
width: 255px;
|
||||
height: 45px;
|
||||
color: #ffffff;
|
||||
font-size: 30px;
|
||||
font-weight: 500;
|
||||
line-height: 44.8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.duration-picker {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
width: 228px;
|
||||
height: 162px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 40px;
|
||||
selected-color: #ffffff;
|
||||
selected-font-size: 40px;
|
||||
}
|
||||
|
||||
.picker-stage {
|
||||
position: absolute;
|
||||
left: 126px;
|
||||
top: 159px;
|
||||
width: 228px;
|
||||
height: 162px;
|
||||
}
|
||||
|
||||
.confirm-button {
|
||||
position: absolute;
|
||||
left: 165px;
|
||||
top: 401px;
|
||||
width: 151px;
|
||||
height: 69px;
|
||||
}
|
||||
|
||||
.button-background {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
width: 151px;
|
||||
height: 69px;
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
position: absolute;
|
||||
left: 56px;
|
||||
top: 16px;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,283 @@
|
||||
<template>
|
||||
<swiper class="timer-swiper" vertical="true" indicator="false" loop="false">
|
||||
<div class="timer-page">
|
||||
<text class="clock-label">{{ time }}</text>
|
||||
<text class="page-title">{{isResting ? "休息中" : "专注中"}}</text>
|
||||
<text class="countdown">{{ countdown }}</text>
|
||||
<text class="focus-label">{{isResting ? "休息中" : "专注中"}}</text>
|
||||
<div class="stop-button" onclick="stopTimer">
|
||||
<image class="button-background" src="/common/icons/button_grey.png"></image>
|
||||
<image class="stop-icon" src="/common/icons/stop.png"></image>
|
||||
</div>
|
||||
<div class="pause-button" onclick="togglePause">
|
||||
<image class="button-background" src="{{isResting ? '/common/icons/button_yellow.png' : '/common/icons/button2.png'}}"></image>
|
||||
<image class="pause-icon" src="{{paused ? '/common/icons/MdiPlay.png' : '/common/icons/pause.png'}}"></image>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats-page">
|
||||
<text class="clock-label">{{ time }}</text>
|
||||
<text class="page-title">专注统计</text>
|
||||
<text class="stats-title focus-title">累计专注</text>
|
||||
<text class="stats-value focus-value">{{ focusTotalText }}</text>
|
||||
<text class="stats-title rest-title">累计休息</text>
|
||||
<text class="stats-value rest-value">{{ restTotalText }}</text>
|
||||
</div>
|
||||
</swiper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import router from "@system.router"
|
||||
import file from "@system.file"
|
||||
import vibrator from "@system.vibrator"
|
||||
|
||||
export default {
|
||||
protected: { duration: "m:20" },
|
||||
private: {
|
||||
time: "00:00", countdown: "20:00", clockTimer: null, countdownTimer: null,
|
||||
remaining: 1200, paused: false, isResting: false, focusDurationSeconds: 1200,
|
||||
focusTotalSeconds: 0, restTotalSeconds: 0,
|
||||
focusTotalText: "00:00:00", restTotalText: "00:00:00"
|
||||
},
|
||||
|
||||
onInit() {
|
||||
this.loadStats()
|
||||
const seconds = this.durationSeconds(this.duration)
|
||||
this.startCountdown(seconds > 0 ? seconds : 1200)
|
||||
this.updateTime()
|
||||
this.clockTimer = setInterval(() => this.updateTime(), 1000)
|
||||
},
|
||||
|
||||
onDestroy() {
|
||||
clearInterval(this.clockTimer)
|
||||
clearInterval(this.countdownTimer)
|
||||
},
|
||||
|
||||
startCountdown(seconds) {
|
||||
clearInterval(this.countdownTimer)
|
||||
this.focusDurationSeconds = seconds > 0 ? seconds : 1200
|
||||
this.remaining = this.focusDurationSeconds
|
||||
this.isResting = false
|
||||
this.paused = false
|
||||
this.updateCountdown()
|
||||
this.countdownTimer = setInterval(() => this.tick(), 1000)
|
||||
},
|
||||
|
||||
startRest() {
|
||||
this.remaining = 300
|
||||
this.isResting = true
|
||||
this.paused = true
|
||||
this.updateCountdown()
|
||||
},
|
||||
|
||||
updateTime() {
|
||||
const now = new Date()
|
||||
const pad = (value) => (value < 10 ? `0${value}` : `${value}`)
|
||||
this.time = `${pad(now.getHours())}:${pad(now.getMinutes())}`
|
||||
},
|
||||
|
||||
tick() {
|
||||
if (this.paused || this.remaining <= 0) return
|
||||
this.remaining -= 1
|
||||
if (this.isResting) this.restTotalSeconds += 1
|
||||
else this.focusTotalSeconds += 1
|
||||
this.updateStats()
|
||||
this.persistStats()
|
||||
this.updateCountdown()
|
||||
if (this.remaining === 0) {
|
||||
if (this.isResting) {
|
||||
vibrator.vibrate({ mode: "short" })
|
||||
this.startCountdown(this.focusDurationSeconds)
|
||||
} else {
|
||||
vibrator.vibrate({ mode: "long" })
|
||||
this.startRest()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
durationSeconds(config) {
|
||||
if (config && config.indexOf("s:") === 0) return parseInt(config.substring(2), 10) || 5
|
||||
if (config && config.indexOf("m:") === 0) return (parseInt(config.substring(2), 10) || 20) * 60
|
||||
return (parseInt(config, 10) || 20) * 60
|
||||
},
|
||||
|
||||
updateCountdown() {
|
||||
const minutes = Math.floor(this.remaining / 60)
|
||||
const seconds = this.remaining % 60
|
||||
const pad = (value) => (value < 10 ? `0${value}` : `${value}`)
|
||||
this.countdown = `${pad(minutes)}:${pad(seconds)}`
|
||||
},
|
||||
|
||||
updateStats() {
|
||||
this.focusTotalText = this.formatDuration(this.focusTotalSeconds)
|
||||
this.restTotalText = this.formatDuration(this.restTotalSeconds)
|
||||
},
|
||||
|
||||
formatDuration(totalSeconds) {
|
||||
const hours = Math.floor(totalSeconds / 3600)
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60)
|
||||
const seconds = totalSeconds % 60
|
||||
const pad = (value) => (value < 10 ? `0${value}` : `${value}`)
|
||||
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`
|
||||
},
|
||||
|
||||
loadStats() {
|
||||
file.readText({
|
||||
uri: "internal://files/focus-stats.json",
|
||||
success: (data) => {
|
||||
try {
|
||||
const stats = JSON.parse(data.text)
|
||||
if (stats.date === this.today()) {
|
||||
this.focusTotalSeconds = parseInt(stats.focus, 10) || 0
|
||||
this.restTotalSeconds = parseInt(stats.rest, 10) || 0
|
||||
}
|
||||
} catch (error) {}
|
||||
this.updateStats()
|
||||
},
|
||||
fail: () => this.updateStats()
|
||||
})
|
||||
},
|
||||
|
||||
persistStats() {
|
||||
file.writeText({
|
||||
uri: "internal://files/focus-stats.json",
|
||||
text: JSON.stringify({
|
||||
date: this.today(),
|
||||
focus: this.focusTotalSeconds,
|
||||
rest: this.restTotalSeconds
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
today() {
|
||||
const now = new Date()
|
||||
const pad = (value) => (value < 10 ? `0${value}` : `${value}`)
|
||||
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`
|
||||
},
|
||||
|
||||
togglePause() { this.paused = !this.paused },
|
||||
stopTimer() { router.back() }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.timer-swiper, .timer-page, .stats-page {
|
||||
width: 480px;
|
||||
height: 480px;
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.timer-page, .stats-page { position: relative; }
|
||||
|
||||
.clock-label {
|
||||
position: absolute;
|
||||
left: 175px;
|
||||
top: 0px;
|
||||
width: 130px;
|
||||
height: 45px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
line-height: 33.6px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
position: absolute;
|
||||
left: 114px;
|
||||
top: 41px;
|
||||
width: 255px;
|
||||
height: 45px;
|
||||
color: #ffffff;
|
||||
font-size: 30px;
|
||||
font-weight: 500;
|
||||
line-height: 44.8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.countdown {
|
||||
position: absolute;
|
||||
left: 90px;
|
||||
top: 172px;
|
||||
width: 300px;
|
||||
height: 97px;
|
||||
color: #ffffff;
|
||||
font-size: 64px;
|
||||
font-weight: 500;
|
||||
line-height: 84.86px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.focus-label {
|
||||
position: absolute;
|
||||
left: 90px;
|
||||
top: 270px;
|
||||
width: 300px;
|
||||
height: 38px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 32px;
|
||||
font-weight: 500;
|
||||
line-height: 42.43px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stop-button, .pause-button {
|
||||
position: absolute;
|
||||
top: 373px;
|
||||
width: 140px;
|
||||
height: 69px;
|
||||
}
|
||||
|
||||
.stop-button { left: 91px; }
|
||||
.pause-button { left: 251px; }
|
||||
|
||||
.button-background {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
width: 140px;
|
||||
height: 69px;
|
||||
}
|
||||
|
||||
.stop-icon {
|
||||
position: absolute;
|
||||
left: 59px;
|
||||
top: 23px;
|
||||
width: 22px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.pause-icon {
|
||||
position: absolute;
|
||||
left: 53px;
|
||||
top: 16px;
|
||||
width: 35px;
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
.stats-title, .stats-value {
|
||||
position: absolute;
|
||||
left: 90px;
|
||||
width: 300px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stats-title {
|
||||
height: 43px;
|
||||
font-size: 32px;
|
||||
font-weight: 500;
|
||||
line-height: 42.43px;
|
||||
}
|
||||
|
||||
.stats-value {
|
||||
height: 50px;
|
||||
color: #ffffff;
|
||||
font-size: 40px;
|
||||
font-weight: 500;
|
||||
line-height: 53.04px;
|
||||
}
|
||||
|
||||
.focus-title { top: 141px; color: rgba(112, 119, 255, 1); }
|
||||
.focus-value { top: 184px; }
|
||||
.rest-title { top: 240px; color: rgba(149, 168, 0, 1); }
|
||||
.rest-value { top: 283px; }
|
||||
</style>
|
||||