外观
专注主题开发 Beta版本
更新时间:2025年8月19日
成功导入或创建专注主题项目后,您将在指定的开发文件夹中看到以下标准文件和目录结构。这些构成了专注主题(.dft
文件,本质上是一个ZIP压缩包)的核心内容。
MyFocusTheme/
├── info.json # 主题元数据与配置定义
├── main.html # 主题核心布局与逻辑文件
└── preview.png # 主题预览图 (通常由Doing自动生成)
资源引用建议
开发者需要特别注意,为了保证主题的安全性和性能:所有主题内使用的资源(包括图片、字体、脚本、样式表等)建议包含在.dft
文件包内,并通过相对路径引用。不建议从外部网络加载任何资源或访问本地文件系统(Doing平台提供的API除外)。
专注主题的核心理念是状态与界面的分离。Doing 应用本身负责所有的核心逻辑,如计时、数据统计、待办事项管理等。而专注主题,作为一个独立的网页,则专门负责展示这些数据,并响应用户的操作。
这种模式将赋予您极大的自由度,你可以利用任何你熟悉的 Web 技术和库来构建一个美观、独特且功能丰富的专注界面。
核心概念:通信桥梁
你的网页(专注主题)运行在一个由 Doing 提供的 WebView 环境中。为了让网页能和 App 交互,Doing 提供了两个核心的全局 JavaScript 函数:doing.getState()
和 doing.call()
。
doing.getState()
: 从 Doing App 获取当前的所有状态信息。doing.call()
: 向 Doing App 发送一个指令,要求执行某个操作。window.onUpdate()
: 这是一个由 Doing App 调用的回调函数。当 App 内部状态发生变化时(例如计时器走了1秒),App 会主动调用这个函数,通知你的网页更新数据、重新渲染。
接下来,我们将详细介绍这几个部分。
1. 配置文件: info.json
每个专注主题都必须包含一个 info.json
文件,内含关于这个主题的基本信息。
例如:
json
{
"id": "zzz66d35-8048-46c8-8627-ac8d89a80ce9",
"sceneRenderVersion": 2,
"entryPoint": "main.html",
"name": "标准 (Beta)",
"description": "采用全新引擎的番茄工作法/正计时专注专注主题",
"author": { "id": "10949", "name": "干物 檬" },
"price": 0,
"category": "focus",
"tags": ["预置", "专注", "番茄工作法", "正计时"],
"version": 20250806,
"status": "beta",
"display": {
"themeColor": "#FF9800",
"systemUI": { "showStatusBar": false }
},
"capabilities": { "appGroup": "" },
"permissions": []
}
关键字段说明:
id
: 主题的全局唯一标识符。Doing 通常会在创建主题时自动生成这个ID。它是一个UUID格式的字符串。sceneRenderVersion
: 渲染引擎版本,目前请固定为2
。entryPoint
: 主题的入口 HTML 文件名,例如"main.html"
(目前版本请保持为main.html)。name
: 主题的名称,会显示在 Doing 的主题商店和选择器中。description
: 主题的简短描述。author
: 作者信息。version
: 主题的版本号。必须为数字,如20250818
。display.themeColor
: 主题的主题色。这个颜色会用于主题商店的预览卡片背景。display.systemUI.showStatusBar
: 是否在专注时显示设备的状态栏。false
表示隐藏状态栏,实现沉浸式体验。tags
: 描述主题特性的标签,便于分类和搜索。
2. 获取状态: doing.getState()
这是与 Doing App 进行数据交互最重要的一步。调用此函数会返回一个包含当前所有相关状态的 JSON 对象。
何时调用?
- 页面首次加载时: 在
initializeApp
这样的初始化函数中调用,获取初始数据来渲染界面的第一次展示。 window.onUpdate()
被触发时: 这是最常见的用法。App 通知你状态变了,你立即调用getState()
获取最新的数据包,然后更新你的 DOM。
返回数据结构详解:
以下是 doing.getState()
可能返回的数据结构示例和解释。注意:某些字段可能根据用户的具体情况和 App 版本而有所不同或不存在,请务必做好空值判断。
javascript
// 示例: const state = doing.getState();
{
// 1. 当前专注模式的配置信息
"focusMode": {
"name": "开发模式", // 专注模式名称
"workDuration": 25, // 工作时长 (分钟)
"restDuration": 5, // 休息时长 (分钟)
"repeat": 1, // 重复次数
"color": "#FF9800FF" // 主题色
},
// 2. 实时计时器信息
"timer": {
"currentRemainOrPastTimeStr": "24 : 57", // 格式化后的剩余/已用时间字符串
"currentRemainOrPastTimeInMillisecond": 1497130, // 剩余/已用时间 (毫秒)
"currentTotalTimeInMillisecond": 1500000, // 当前阶段总时长 (毫秒)
"currentTotalFocusTime": 2972, // 本次专注已累计的总专注时长 (毫秒)
"isFinished": false, // 是否已完成
"focusProcess": [ // 专注过程记录
{ "type": "work", "time": 1755524203683, "data": {} }
// ... 更多过程,如 pause, rest, finish
]
},
// 3. 应用的实时状态
"status": {
"focusStatus": "work", // "work", "rest", "pause"
"dnd": "false", // 是否开启勿扰模式 (字符串 "true" 或 "false")
"keepScreenOn": "true", // 是否保持屏幕常亮 (字符串 "true" 或 "false")
"fullscreen": "false", // 是否全屏 (字符串 "true" 或 "false")
"floatingWindow": "false" // 是否小窗模式 (字符串 "true" 或 "false")
},
// 4. 设备信息
"device": {
"platform": "macos", // "android", "ios", "windows", "macos", "linux"
"battery": { // 电池信息
"level": 100, // 电量百分比
"status": "full" // "charging", "discharging", "full", "unknown"
},
"time": 1755524206655, // 当前设备时间戳 (毫秒)
"safeArea": { // 安全区域边距 (用于适配刘海屏等)
"top": 20, "bottom": 10, "left": 0, "right": 0
}
},
// 5. 资源信息
"res": {
"motto": "涧影见松竹,潭香闻芰荷。" // 随机引言
},
// 6. 统计数据
"statistic": {
"todayFocusTime": 3003866, // 今日总专注时长 (毫秒)
"todayFocusRecord": [ // 今日专注记录列表
{
"data": "{\"name\":\"开发模式\", ...}", // 内嵌的 JSON 字符串,需要 JSON.parse()
"statisticData": { "time": 1029307 } // 本条记录的专注时长
}
]
},
// 7. 待办事项
"schedule": {
"todayTodo": [ // 今日待办列表
{
"uuid": "8ce33cdf-...",
"data": "{\"status\":\"done\",\"content\":\"写文档\"}" // 内嵌的 JSON 字符串,需要 JSON.parse()
}
]
},
// 8. 用户偏好设置
"preference": {
"showFocusUpperRightWidgetTodayRemainTime": "true", // 是否显示右上角今日剩余时间为“今日剩余...”
"showFocusUpperRightWidgetTodayRemainTimeAlways": "false", // 是否总是显示今日剩余时间
"24HourSystem": "true", // 是否使用24小时制 (字符串)
"autoAODMode": "true", // 是否自动进入待机模式 (字符串)
"locale": "zh-CN", // 应用语言环境
"strictMode": "true", // 是否开启严格模式 (字符串 "true" 或 "false")
"strictModeWhiteList": "", // 严格模式白名单 (逗号分隔的应用包名列表)
"showFocusPause": "true", // 是否显示专注暂停按钮 (字符串 "true" 或 "false")
}
}
3. 发送指令: doing.call()
当你需要主题对用户的操作做出反应时(例如点击一个“暂停”按钮),就需要使用 doing.call()
来通知 Doing App 执行相应的动作。
doing.call()
接收一个数组作为参数,格式通常为 [group, action, parameters]
。
group
: 功能分组,例如'action'
,'function'
,'schedule'
等。action
: 具体的操作名称。parameters
: (可选) 传递给该操作的参数。
可用函数列表:
action
(核心操作)
["action", "exit"]
: 退出专注模式。["action", "togglePause"]
: 切换暂停/继续状态。["action", "toggleKeepScreenOn"]
: 切换是否保持屏幕常亮。["action", "toggleDND"]
: 切换是否开启勿扰模式。["action", "toggleFloating"]
: 切换小窗模式 (Android/PC)。["action", "toggleFullscreen"]
: 切换全屏模式 (PC)。["action", "rotateScreen"]
: 旋转屏幕 (Android)。["action", "minimizeFocus"]
: 最小化专注窗口。["action", "startDragging"]
: 允许用户拖动窗口 (PC)。["action", "continuePeriod"]
: (番茄钟)跳过休息,直接进入下一个工作时段。
function
(调用 App 内置功能/对话框)
["function", "showWhiteNoiseDialog"]
: 显示白噪音选择对话框。["function", "showEventDialog"]
: 显示关联待办/清单的对话框。["function", "showInstantRecordDialog"]
: 显示快速记录想法的对话框。
schedule
(待办事项相关)
["schedule", "toggleTodoStatus", todo.uuid]
: 切换指定uuid
的待办事项的完成状态。uuid
可以从getState().schedule.todayTodo
数组中的每个todo
对象里获取。
data
(应用数据读写 - 高级)
["data", "getAppData", key]
: 获取由本主题存储的自定义数据。["data", "setAppData", { "key": "your_key", "value": "your_value" }]
: 存储自定义数据。["data", "deleteAppData", key]
: 删除自定义数据。
代码示例:
javascript
// 假设 el.btnClose 是关闭按钮的 DOM 元素
el.btnClose.addEventListener('click', () => {
// 点击按钮时,告诉 Doing App 退出专注
doing.call(["action", "exit"]);
});
// 切换一个待办事项的状态
function toggleMyTodo(todo_uuid) {
doing.call(["schedule", "toggleTodoStatus", todo_uuid]);
}
最佳实践与建议
模块化 UI 更新: 你的
window.onUpdate
函数是所有状态更新的入口。建议在这个函数里,将state
对象传递给多个专门负责更新不同 UI 板块的子函数(如updateTimerUI
,updateSidebarUI
等)。这使得代码更清晰、更易于维护。利用
DOMContentLoaded
: 将你的应用初始化逻辑(如缓存 DOM 元素、绑定事件监听器)放在DOMContentLoaded
事件的回调中,确保在操作 DOM 之前,所有元素都已加载完毕。国际化 (i18n): 如果你希望你的主题被更多人使用,可以像示例代码一样,通过动态加载语言 JS 文件和使用
data-translate-key
属性来实现多语言支持。
通过理解和运用以上文件结构及API,您就可以开始构建功能丰富、交互流畅的个性化专注主题了。记得在开发过程中频繁测试,并关注Doing开发者文档的更新~ 💡