Skip to content

专注主题开发 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()

  1. doing.getState(): 从 Doing App 获取当前的所有状态信息。
  2. doing.call(): 向 Doing App 发送一个指令,要求执行某个操作。
  3. 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 对象。

何时调用?

  1. 页面首次加载时: 在 initializeApp 这样的初始化函数中调用,获取初始数据来渲染界面的第一次展示。
  2. 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]);
}

最佳实践与建议

  1. 模块化 UI 更新: 你的 window.onUpdate 函数是所有状态更新的入口。建议在这个函数里,将 state 对象传递给多个专门负责更新不同 UI 板块的子函数(如 updateTimerUI, updateSidebarUI 等)。这使得代码更清晰、更易于维护。

  2. 利用 DOMContentLoaded: 将你的应用初始化逻辑(如缓存 DOM 元素、绑定事件监听器)放在 DOMContentLoaded 事件的回调中,确保在操作 DOM 之前,所有元素都已加载完毕。

  3. 国际化 (i18n): 如果你希望你的主题被更多人使用,可以像示例代码一样,通过动态加载语言 JS 文件和使用 data-translate-key 属性来实现多语言支持。


通过理解和运用以上文件结构及API,您就可以开始构建功能丰富、交互流畅的个性化专注主题了。记得在开发过程中频繁测试,并关注Doing开发者文档的更新~ 💡