任务管理数据库模版分享

起因

最近看了《防弹笔记法》,书中所有任务都是在对应的项目笔记中,把任务都记录在笔记里,这样可以方便溯源整个任务的上下文,减少任务上下文的切换,我以前一直用滴答清单进行管理,但是项目笔记又在思源中,两个软件来回切换也是有点难受,所以干脆尝试 all in siyuan 了,下面模版就是成果。

模版介绍

目前这套模版我优化起码半个月,最麻烦的是各种模版列代码的逻辑优化,此处感谢 deepseek 以及 F 佬的帖子思源模板功能新人指南:模板语法 + 函数 + md 块语法 - 链滴,只需要把帖子里的语法喂给 AI 基本就能慢慢帮我写好。

以下是模版文档,根据对应思源版本下载导入即可使用,无需任何其他插件,只依赖思源本身的数据库。

任务管理数据库模版.sy.zip

(已更新支持 3.1.29 的列表块格式,并支持识别文档二级标题为项目列名称,就是比如项目文档名称下有二级标题,在这个二级标题下的任务块,数据库的项目列也会同步显示这个二级标题名称,这样就多了一个层级了。另外已修复相同字数文档名颜色区分不明显问题)

具体用法还是看下面图片和视频,并且在模版内的每个字段上我都添加了备注,鼠标移动上去就能看到对应备注信息。

image.png

功能演示视频:

看完上面视频应该能基本了解用法,我是把任务块添加至数据库,然后任务块的状态可以通过模版列自动同步至数据库,这样的好处是任务块状态和数据库内保持一致,如果是单独在数据库新建复选框,那就要点两次完成了,非常不优雅,当然现在受制于思源的文档刷新,无法实现数据库模版列内点完成同步至任务,必须通过悬浮窗进去点击完成,但我觉得完全能接受,如果不喜欢可以自己换成复选框完成,不管笔记内任务的状态。

以下是模版列实现自动操作的步骤。

  • 自动获取任务列表所在的文档标题
  • 自动获取任务列表完成状态
  • 自动计算距离开始日、截止日还有多久
  • 自动显示未添加日期待定的任务

更快的任务添加方式之 Supertag

以上视频演示的是通过标签将任务添加至数据库,我认为这个方式比去块菜单里添加至数据库快许多,只需要添加以下 JS 代码即可实现,注意数据库名称要和标签名称完全一致。非常感谢 @qiancang 大佬的这个帖子的灵感 [Quicker] 思源笔记 SuperTag - 链滴 以及 @wilsons 大佬的 JS 代码编写和帮助(让大佬帮我改了 N 次代码),让这一切可以得以顺利实现。

// 思源通过标签插入当前块到数据库(SuperTag) // 功能:给块设置标签,将块添加到标签同名的数据库。 // 说明; // 1、数据库名称需要与标签同名,名称需含前后#,如 #笔记软件# // 2、如果有多个同名数据库,只会将块添加到其中一个,所以不要建立同名数据库 // 3、需要提前建立数据库才能添加成功 // version:0.0.4 // 更新记录 // 0.0.2 增加数据库同名文档标签即可把文档添加到标签同名数据库中(文档标签这里指文档头部的添加标签)。 // 0.0.3 增加可删除数据库引用文本中的标签名选项isShowTagNameInAvCell // 0.0.4 改进当标签在列表中时数据库插入列表项 // 根据qiancang大佬的帖子实现 https://ld246.com/article/1731945645865 (()=>{ // 添加tag后多少毫秒添加当前块到数据库 // 不宜设置过小,过小可能导致标签被插入一半 const delay = 1000; // 是否开启文档标签插入同名数据库(文档标签这里指文档头部的添加标签),true开启,false不开启 const enableDocTagToAv = true; // 是否在数据库列表中显示标签名(数据库名),true显示,false不显示(注意,文档块引用不会添加标签名,实现较麻烦暂不支持) const isShowTagNameInAvCell = false; // 发布服务立即返回 if(siyuan.config.readonly) return; // 监听tag输入 observeTagSpans(async (tagEl, tagType) => { // 如果未开启文档标签插入同名数据库,当为文档标签时返回 if(!enableDocTagToAv && tagType === 'doc-tag') return; // 去掉零宽度字符&ZeroWithSpace; const tag = tagEl?.textContent?.replace(/[\u200B-\u200D\uFEFF]/g, '')?.trim(); if(!tag) return; // 获取数据库信息 const av = await getAvByName(tag); if(!av) return; const avId = av.avID; if(!avId) return; const avBlockID = av.blockID; if(!avBlockID) return; // 获取文档块信息 let block; if(tagType === 'doc-tag') { // 如果头部标签,返回文档id const blockParent = tagEl.closest('div.protyle-top'); if(!blockParent) return; block = blockParent.querySelector('.protyle-title'); } else { // 如果块标签,返回块id(监听元素的临时块) block = tagEl.closest('div[data-node-id][data-type]'); if(!block) return; // 获取文档中的block结点 block = document.querySelector('div[data-node-id="'+(block?.dataset?.nodeId||'')+'"]'); if(!block) return; // 判断是否在列表元素内,数据库插入列表项 const listItemNode = block.closest('div[data-node-id][data-type="NodeListItem"]'); if(listItemNode) block = listItemNode; } const blockId = block?.dataset?.nodeId; if(!blockId) return; // 添加块到数据库 await sleep(delay || 500); addBlocksToAv(blockId, avId, avBlockID); }); // 如果不在数据库中显示标签名则删除标签名(注意,文档块引用不会添加标签名,实现较麻烦暂不支持) if(!isShowTagNameInAvCell) { observeElementCreation( document.body, '.av__row:not(.av__row--header) [data-dtype="block"] [data-type="block-ref"]', async ref => { if(!/\s?#.*?#/i.test(ref.textContent)) return; ref.textContent = ref.textContent.replace(/\s?#.*?#/ig, ''); } ); } // 插入块到数据库 async function addBlocksToAv(blockIds, avId, avBlockID) { blockIds = typeof blockIds === 'string' ? [blockIds] : blockIds; const srcs = blockIds.map(blockId => ({ "id": blockId, "isDetached": false, })); const input = { "avID": avId, "blockID": avBlockID, 'srcs': srcs } const result = await fetchSyncPost('/api/av/addAttributeViewBlocks', input); if(!result || result.code !== 0) console.error(result); } // 通过该tag查询数据库 async function getAvByName(name) { const result = await fetchSyncPost('/api/av/searchAttributeView', { "keyword": name }); if(!result || result.code !== 0 || !result?.data?.results || result?.data?.results?.length === 0) return null; for (const av of result.data.results) { if (av.avName === name || av.avName === `#${name}#`) { return av; } } return null; } // 请求api // returnType json返回json格式,text返回文本格式 async function fetchSyncPost(url, data, returnType = 'json') { const init = { method: "POST", }; if (data) { if (data instanceof FormData) { init.body = data; } else { init.body = JSON.stringify(data); } } try { const res = await fetch(url, init); const res2 = returnType === 'json' ? await res.json() : await res.text(); return res2; } catch(e) { console.log(e); return returnType === 'json' ? {code:e.code||1, msg: e.message||"", data: null} : ""; } } // 延迟执行 function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // 监听tag被添加 function observeTagSpans(callback) { // 创建一个观察者实例并传入回调函数 const observer = new MutationObserver((mutationsList, observer) => { for (const mutation of mutationsList) { if (mutation.type === 'childList') { // 检查新增的节点 for (const node of mutation.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE && node.tagName.toLowerCase() === 'span' && node.getAttribute('data-type') === 'tag') { // 块标签调用回调函数 callback(node, 'block-tag'); } else if(node.nodeType === Node.ELEMENT_NODE && node.tagName.toLowerCase() === 'div' && node.classList?.contains('b3-chip') && node.getAttribute('data-type') === 'open-search') { // 文档头部标签调用回调函数 callback(node, 'doc-tag'); } } } } }); // 配置观察选项: const config = { childList: true, // 观察子节点的变化(添加/删除) subtree: true // 观察所有后代节点 }; // 选择需要观察变动的节点 const targetNode = document.body; // 或者选择更具体的父节点以减少性能消耗 // 开始观察目标节点 observer.observe(targetNode, config); // 返回一个取消观察的方法 return () => observer.disconnect(); } // 监听元素被创建 function observeElementCreation(parentNode, selector, onElementCreated) { // 配置观察器选项 const config = { childList: true, // 观察直接子节点的添加和移除 subtree: true // 观察所有后代节点 }; // 当检测到变动时执行的回调函数 const callback = function(mutationsList, observer) { for (let mutation of mutationsList) { if (mutation.type === 'childList') { mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE) { // 使用 querySelectorAll 查找所有符合条件的新元素 const elements = node.querySelectorAll(selector); elements.forEach(element => { onElementCreated(element); // 调用外部提供的回调函数 }); } }); } } }; // 创建一个观察器实例并传入回调函数 const observer = new MutationObserver(callback); // 开始观察目标节点 observer.observe(parentNode, config); // 返回一个函数来停止观察 return () => observer.disconnect(); } })();

此为代码原始链接:思源/思源通过标签插入当前块到数据库 SuperTag.js · wish/mysoft - Gitee.com

更更快的任务添加方式之右击块标直接添加

详见以下链接,配置好后将添加任务数据库放到块标菜单里,用鼠标点两下就能添加,比用键盘打标签还要更快,也非常推荐。

[js] 添加文档 / 块到指定数据库 - 链滴

未来展望

至此基本就介绍完了,剩下的遗憾主要是没有其他更直观好看的视图,比如看板视图、分组功能等,希望 D 大 V 姐加把劲,争取明年补上 😂

END

  • 数据库

    据说 99% 的性能瓶颈都在数据库。

    345 引用 • 751 回帖
  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    25901 引用 • 107336 回帖
  • 模版
    2 引用 • 39 回帖
  • 任务管理
    2 引用 • 40 回帖

相关帖子

优质回帖
  • 牛的。再发展发展都可以卖模板了 👍

    分享一下我的任务管理方法:

    1. 把事项都列在一个叫“最近任务”的文档,从来不关页签,每天打开思源首先看到待办事项。
    2. 简单的事项使用嵌套的列表,列表项内补充相关信息,然后折叠列表项有需要再展开看;复杂的事项或者是其他项目中的事项就单独建一个文档然后在“最近任务”中引用。
    3. 通过两种块背景颜色来标注事项的紧迫性;
    4. 通过直觉判断任务重要性,想做哪个就做哪个,不能再拖延的时候我肯定会做。

    image.png

  • powehi 2 赞同 via macOS

    好好牛,如果删除标签能够删除数据库记录就更好了

  • wilsons 1

    非常棒!👍

    思源从此有任务管理了 😄

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • 5kyfkr

    Use `-` marker for lists when exporting Markdown · Issue #14680 · siyuan-note/siyuan

    由于思源 3.1.29 任务块由* 变为-,模板语法有更新,已使用旧版模板的请按下图操作修改任务完成状态识别模板语法,把*改成-即可,或者整个替换为图片后的代码(同时兼容两种格式)

    image.png

    .action{ if index . "custom-avs" } .action{ $id := .id } .action{ $process := printf "%v" (index . "状态") } .action{ $completion := printf "%s%s" "完" "成" } .action{ $done := len (queryBlocks "SELECT * FROM blocks WHERE id = '?' AND (markdown LIKE '- [x]%' OR markdown LIKE '* [x]%') " $id) } .action{ if and (or (eq $done 1) (or (contains $process $completion) (eq $process "放弃"))) (not (eq $process "")) } ✅完成 .action{ else } 🔲 .action{ end } .action{ else } .action{ $process := printf "%v" (index . "状态") } .action{ $completion := printf "%s%s" "完" "成" } .action{ if and (or (contains $process $completion) (eq $process "放弃")) (not (eq $process "")) } ✅完成 .action{ else } 🔲 .action{ end } .action{ end }
    1 回复
    1 操作
    5kyfkr 在 2025-05-07 09:39:35 更新了该回帖
  • 其他回帖
  • 5kyfkr

    用满血 DS 和通义疯狂优化了几十版终于搞定了,把下面的模板代码复制替换进去就行,颜色可以自己微调饱和度亮度,比如饱和度 20-100,就调代码最后两个数字 20 和 80,80 代表 20+80 饱和度就是最高的 100。

    <!-- 查询 h2 类型的祖先块 --> .action{ $h2Blocks := queryBlocks `WITH RECURSIVE ancestors AS ( SELECT * FROM blocks WHERE id = '?' UNION ALL SELECT b.* FROM blocks b INNER JOIN ancestors a ON b.id = a.parent_id ) SELECT * FROM ancestors WHERE type = 'h' AND subtype = 'h2'` .id } <!-- 查询根文档 --> .action{ $blocks := queryBlocks "SELECT * FROM blocks WHERE id IN (SELECT root_id FROM blocks WHERE id='?')" .id } .action{ if not (empty $blocks) } .action{ $blockContenta := (first $blocks).Content | trim } <!-- 仅基于文档名生成哈希 --> .action{ $hashSeed := 0 } .action{ range $idx, $char := splitList "" $blockContenta } <!-- 使用字符长度 + ASCII 值作为唯一标识 --> .action{ $charLen := len $char } .action{ $charASCII := index $char 0 } .action{ $charCode := add (mul $charLen 1000) $charASCII } <!-- 多层非线性扰动 --> .action{ $term1 := mul (pow 101 (mod $idx 7)) $charCode } .action{ $term2 := mul (pow 67 $idx) (mod $charCode 29) } .action{ $term3 := mul (pow 43 (mod $idx 5)) (mod $charCode 19) } .action{ $diff := sub $term1 $term2 } .action{ $hashSeed = add $hashSeed (add (mul $term1 $term3) (mul $diff 5)) } .action{ end } <!-- 最终哈希合成 --> .action{ $finalHash := mod $hashSeed 4294967296 } <!-- 色相生成:直接映射到 0-360 --> .action{ $hue := mod $finalHash 360 } <!-- 饱和度优化:范围 20%~100% --> .action{ $saturation := mod (div $finalHash 360) 20 | add 80 } <!-- 亮度优化:范围 5%~40% --> .action{ $lightness := mod (div $finalHash 100000) 5 | add 35 } <!-- 最终颜色 --> .action{ $color := printf "hsl(%d, %d%%, %d%%)" $hue $saturation $lightness } <!-- 显示结果 --> .action{ if not (empty $h2Blocks) } .action{ $h2Content := (first $h2Blocks).Content | trim } <span style="color: .action{$color};">.action{$blockContenta}</span><span style="color: #777;">-.action{$h2Content}</span> .action{ else } <span style="color: .action{$color};">.action{$blockContenta}</span> .action{ end } <!-- 如果没有根文档 --> .action{ else } 无 .action{ end }
    1 操作
    5kyfkr 在 2025-05-15 16:52:33 更新了该回帖
  • 大佬,为啥这个识别任务列表状态的代码,识别不了

    52GZB3AWE6M0XJU.jpg

    1 回复
    思源版本是 3.1.28 是吧,要确认下是任务块添加进数据库而不是任务块里面的文字添加进数据库,两个是有区别的,就是要右击块标添加进数据库
    5kyfkr
  • MasterYS 1 评论

    刚开始用思源。就卡在怎么做任务管理,就遇到这套模板了,首先谢谢 LZ,然后我换了个思路用这个管理器,不知道能不能解决下面各位的问题。

    不用主键管理任务块,而是直接用文本或者文档块替代,这样的话写作型任务开始的时候,就从文本新建块引来建文档块写作。完成后直接在数据库修改状态。非写作类的,就等处理完,直接修改状态。

    目前应该是足够我使用了,用一个月试一下

    图片.png

    当然可以这样用,这种就是传统数据库管理任务的办法,我这个主要解决了文档里任务块的状态与数据库同步
    5kyfkr
  • 查看全部回帖