对于思源数据库添加数据要从最后点击进行添加,感觉有点麻烦,用过飞书表格之后他有一个提交表单,特别方便,然后想移植到思源笔记上,现在终于实现了。
使用了思源官方挂件“按钮”。
首先,介绍一下我的表单设置
<div class="protyle-wysiwyg protyle-wysiwyg--attr" spellcheck="false" contenteditable="true" style="padding: 16px 96px 272px;" data-doc-type="NodeDocument" data-realwidth="585" data-readonly="false"><div data-subtype="h3" data-node-id="20250911135114-pu4ctnh" data-node-index="0" data-type="NodeHeading" class="h3" updated="20250912104425"><div contenteditable="true" spellcheck="false">主键:</div><div class="protyle-attr" contenteditable="false"></div></div><div data-node-id="20250912102432-2cfi7ug" data-node-index="1" data-type="NodeCodeBlock" class="code-block" updated="20250912104425"><div class="protyle-action"><span class="protyle-action--first protyle-action__language" contenteditable="false">主键</span><span class="fn__flex-1"></span><span class="b3-tooltips__nw b3-tooltips protyle-icon protyle-icon--first protyle-action__copy" aria-label="复制"><svg><use xlink:href="#iconCopy"></use></svg></span><span class="b3-tooltips__nw b3-tooltips protyle-icon protyle-icon--last protyle-action__menu" aria-label="更多"><svg><use xlink:href="#iconMore"></use></svg></span></div><div class="hljs" data-render="true" style="display: block;"><div class="fn__none"></div><div contenteditable="true" style="flex: 1 1 0%; white-space: pre; word-break: initial; font-variant-ligatures: none;" spellcheck="false">
</div></div><div class="protyle-attr" contenteditable="false"></div></div><div data-subtype="h3" data-node-id="20250911135319-05ej2ad" data-node-index="2" data-type="NodeHeading" class="h3" updated="20250912103703"><div contenteditable="true" spellcheck="false">日期(XXXX-XX-XX,不填默认当前日期):</div><div class="protyle-attr" contenteditable="false"></div></div><div data-node-id="20250911135336-d7ypbzv" data-node-index="1" data-type="NodeCodeBlock" class="code-block" updated="20250912103703"><div class="protyle-action"><span class="protyle-action--first protyle-action__language" contenteditable="false">日期</span><span class="fn__flex-1"></span><span class="b3-tooltips__nw b3-tooltips protyle-icon protyle-icon--first protyle-action__copy" aria-label="复制"><svg><use xlink:href="#iconCopy"></use></svg></span><span class="b3-tooltips__nw b3-tooltips protyle-icon protyle-icon--last protyle-action__menu" aria-label="更多"><svg><use xlink:href="#iconMore"></use></svg></span></div><div class="hljs" data-render="true" style="display: block;"><div class="fn__none"></div><div contenteditable="true" style="flex: 1 1 0%; white-space: pre; word-break: initial; font-variant-ligatures: none;" spellcheck="false">
</div></div><div class="protyle-attr" contenteditable="false"></div></div><div data-subtype="h3" data-node-id="20250911135353-qilr4h3" data-node-index="4" data-type="NodeHeading" class="h3" updated="20250912104427"><div contenteditable="true" spellcheck="false">单选:</div><div class="protyle-attr" contenteditable="false"></div></div><div data-node-id="20250911135439-cs5xi8a" data-node-index="5" data-type="NodeBlockquote" class="bq" updated="20250912104427"><div data-subtype="t" data-node-id="20250911135458-t7dgs5w" data-type="NodeList" class="list" updated="20250912104427"><div data-marker="*" data-subtype="t" data-node-id="20250911135501-bbm5ua3" data-node-index="1" data-type="NodeListItem" class="li" updated="20250912104806"><div class="protyle-action protyle-action--task" draggable="true"><svg><use xlink:href="#iconUncheck"></use></svg></div><div data-node-id="20250912104806-lr8bidl" data-type="NodeParagraph" class="p" updated="20250912104806"><div contenteditable="true" spellcheck="false">个人吃饭</div><div class="protyle-attr" contenteditable="false"></div></div><div class="protyle-attr" contenteditable="false"></div></div><div data-marker="*" data-subtype="t" data-node-id="20250911135510-d0xgae5" data-type="NodeListItem" class="li" updated="20250911171204"><div class="protyle-action protyle-action--task" draggable="true"><svg><use xlink:href="#iconUncheck"></use></svg></div><div data-node-id="20250911135510-za6k2zo" data-type="NodeParagraph" class="p" updated="20250911135516"><div contenteditable="true" spellcheck="false">多人聚餐</div><div class="protyle-attr" contenteditable="false"></div></div><div class="protyle-attr" contenteditable="false"></div></div><div data-marker="*" data-subtype="t" data-node-id="20250911135516-1hgh62z" data-type="NodeListItem" class="li" updated="20250912104153"><div class="protyle-action protyle-action--task" draggable="true"><svg><use xlink:href="#iconUncheck"></use></svg></div><div data-node-id="20250912104153-krt0l0r" data-type="NodeParagraph" class="p" updated="20250912104153"><div contenteditable="true" spellcheck="false">网络购物</div><div class="protyle-attr" contenteditable="false"></div></div><div class="protyle-attr" contenteditable="false"></div></div><div data-marker="*" data-subtype="t" data-node-id="20250911135521-2pyfuc2" data-type="NodeListItem" class="li" updated="20250912104153"><div class="protyle-action protyle-action--task" draggable="true"><svg><use xlink:href="#iconUncheck"></use></svg></div><div data-node-id="20250912104153-paioqwv" data-type="NodeParagraph" class="p" updated="20250912104153"><div contenteditable="true" spellcheck="false">红包转账</div><div class="protyle-attr" contenteditable="false"></div></div><div class="protyle-attr" contenteditable="false"></div></div><div data-marker="*" data-subtype="t" data-node-id="20250911135527-9ay8kpp" data-type="NodeListItem" class="li" updated="20250912103623"><div class="protyle-action protyle-action--task" draggable="true"><svg><use xlink:href="#iconUncheck"></use></svg></div><div data-node-id="20250912103623-8mv6r76" data-type="NodeParagraph" class="p" updated="20250912103623"><div contenteditable="true" spellcheck="false">交通住宿</div><div class="protyle-attr" contenteditable="false"></div></div><div class="protyle-attr" contenteditable="false"></div></div><div data-marker="*" data-subtype="t" data-node-id="20250911135531-aozy2nu" data-type="NodeListItem" class="li" updated="20250912103623"><div class="protyle-action protyle-action--task" draggable="true"><svg><use xlink:href="#iconUncheck"></use></svg></div><div data-node-id="20250912103623-2msvvym" data-type="NodeParagraph" class="p" updated="20250912103623"><div contenteditable="true" spellcheck="false">出行旅游</div><div class="protyle-attr" contenteditable="false"></div></div><div class="protyle-attr" contenteditable="false"></div></div><div data-marker="*" data-subtype="t" data-node-id="20250911135535-fz9deav" data-type="NodeListItem" class="li" updated="20250912095426"><div class="protyle-action protyle-action--task" draggable="true"><svg><use xlink:href="#iconUncheck"></use></svg></div><div data-node-id="20250911135535-4iooegx" data-type="NodeParagraph" class="p" updated="20250911135540"><div contenteditable="true" spellcheck="false">虚拟充值</div><div class="protyle-attr" contenteditable="false"></div></div><div class="protyle-attr" contenteditable="false"></div></div><div data-marker="*" data-subtype="t" data-node-id="20250911135540-28t6uqx" data-type="NodeListItem" class="li" updated="20250912103528"><div class="protyle-action protyle-action--task" draggable="true"><svg><use xlink:href="#iconUncheck"></use></svg></div><div data-node-id="20250912103528-qny0poa" data-type="NodeParagraph" class="p" updated="20250912103528"><div contenteditable="true" spellcheck="false">其他</div><div class="protyle-attr" contenteditable="false"></div></div><div class="protyle-attr" contenteditable="false"></div></div><div class="protyle-attr" contenteditable="false"></div></div><div class="protyle-attr" contenteditable="false"></div></div><div data-subtype="h3" data-node-id="20250911135626-baosp0c" data-node-index="6" data-type="NodeHeading" class="h3" updated="20250912104428"><div contenteditable="true" spellcheck="false">数字:</div><div class="protyle-attr" contenteditable="false"></div></div><div data-node-id="20250911135647-qx73tr9" data-node-index="1" data-type="NodeCodeBlock" class="code-block" updated="20250912104428"><div class="protyle-action"><span class="protyle-action--first protyle-action__language" contenteditable="false">数字</span><span class="fn__flex-1"></span><span class="b3-tooltips__nw b3-tooltips protyle-icon protyle-icon--first protyle-action__copy" aria-label="复制"><svg><use xlink:href="#iconCopy"></use></svg></span><span class="b3-tooltips__nw b3-tooltips protyle-icon protyle-icon--last protyle-action__menu" aria-label="更多"><svg><use xlink:href="#iconMore"></use></svg></span></div><div class="hljs" data-render="true" style="display: block;"><div class="fn__none"></div><div contenteditable="true" style="flex: 1 1 0%; white-space: pre; word-break: initial; font-variant-ligatures: none;" spellcheck="false">
</div></div><div class="protyle-attr" contenteditable="false"></div></div><div data-subtype="h3" data-node-id="20250911135632-p0gdr5c" data-node-index="8" data-type="NodeHeading" class="h3" updated="20250912104536"><div contenteditable="true" spellcheck="false">返现:</div><div class="protyle-attr" contenteditable="false"></div></div><div data-node-id="20250911135702-h56lxky" data-node-index="1" data-type="NodeCodeBlock" class="code-block" updated="20250912104429"><div class="protyle-action"><span class="protyle-action--first protyle-action__language" contenteditable="false">返现</span><span class="fn__flex-1"></span><span class="b3-tooltips__nw b3-tooltips protyle-icon protyle-icon--first protyle-action__copy" aria-label="复制"><svg><use xlink:href="#iconCopy"></use></svg></span><span class="b3-tooltips__nw b3-tooltips protyle-icon protyle-icon--last protyle-action__menu" aria-label="更多"><svg><use xlink:href="#iconMore"></use></svg></span></div><div class="hljs" data-render="true" style="display: block;"><div class="fn__none"></div><div contenteditable="true" style="flex: 1 1 0%; white-space: pre; word-break: initial; font-variant-ligatures: none;" spellcheck="false">
</div></div><div class="protyle-attr" contenteditable="false"></div></div><div data-node-id="20250912104536-rt5i7pd" data-node-index="10" data-type="NodeWidget" class="iframe" updated="20250912104536" data-subtype="widget"><div class="iframe-content"><iframe src="/widgets/Button/" data-subtype="widget" border="0" frameborder="no" framespacing="0" allowfullscreen="true" style="width: 109px; height: 65px;"></iframe><span class="protyle-action__drag" contenteditable="false"></span></div><div class="protyle-attr" contenteditable="false"></div></div><div data-node-id="20250912104805-3q8gseu" data-type="NodeParagraph" class="p"><div contenteditable="true" spellcheck="false"></div><div class="protyle-attr" contenteditable="false"></div></div></div>
大概是这样子的

然后我的数据库设置是

表单和数据库的位置关系应该没有影响,但是我把数据库放在了表单的子文档

这样每次填写完消费记录点击按钮,的时候执行代码,做到提交信息到数据库,然后并清除表单块的信息,方便下次填写。
运行结果


代码如下
// 编辑return修改按钮名称
function getButtonName() {
return "提交"; // 按钮显示文字
}
// 按钮按下时执行
function executeScriptFunctions() {
const token = "jpotli7es6bkshts";
// 需要清空内容的代码块(就是你设置的填写数据的块,取决于你有多少个要填写的项)
const codeBlocks = [
"99999999999999-aaaaaaa",
"99999999999999-aaaaaaa",
"99999999999999-aaaaaaa",
"99999999999999-aaaaaaa"
];
// 单选列表块(取消勾选,这个是单选列表,单独摘出来处理)
const listBlockId = "99999999999999-aaaaaaa";
// ------------------ 工具函数 ------------------
function normalizeDateStr(dateStr) {
if (!dateStr || dateStr.trim() === "") return null;
const match = dateStr.trim().match(/^(\d{4})[-/](\d{1,2})[-/](\d{1,2})$/);
if (!match) return null;
const year = match[1];
const month = String(parseInt(match[2], 10)).padStart(2, '0');
const day = String(parseInt(match[3], 10)).padStart(2, '0');
return `${year}-${month}-${day}`;
}
function dateToTimestamp(dateStr) {
if (!dateStr || dateStr.trim() === "") return new Date().getTime();
const normalized = normalizeDateStr(dateStr);
if (!normalized) return null;
const [year, month, day] = normalized.split('-').map(Number);
return new Date(year, month - 1, day, 12, 0, 0).getTime();
}
//读取文档内容
async function getRawNoteContent(docId) {
try {
const res = await fetch("http://127.0.0.1:6806/api/filetree/getDoc", {
method: "POST",
headers: { "Content-Type": "application/json", "Authorization": `Token ${token}` },
body: JSON.stringify({ id: docId, mode: 0 })
});
const data = await res.json();
if (data.code !== 0) throw new Error(data.msg);
return data.data.content;
} catch (err) {
console.error("获取原始笔记失败:", err.message);
return null;
}
}
function parseRawContent(rawHtml) {
const parser = new DOMParser();
const doc = parser.parseFromString(rawHtml, 'text/html');
const result = {};
const headings = doc.querySelectorAll('[data-type="NodeHeading"][data-subtype="h3"]');
headings.forEach(heading => {
const title = heading.querySelector('[contenteditable="true"]')?.textContent.trim() || "未知标题";
const contentBlock = heading.nextElementSibling;
if (!contentBlock) { result[title] = ""; return; }
let content;
const type = contentBlock.getAttribute('data-type');
if (type === "NodeCodeBlock") {
content = contentBlock.querySelector('[contenteditable="true"]')?.textContent.trim() || "";
} else if (type === "NodeBlockquote") {
const items = contentBlock.querySelectorAll('[data-type="NodeListItem"]');
content = Array.from(items).map(item => ({
text: item.querySelector('[contenteditable="true"]')?.textContent.trim() || "未知选项",
isSelected: item.classList.contains('protyle-task--done')
}));
} else {
content = contentBlock.textContent.trim() || "";
}
result[title] = content;
});
return result;
}
async function clearCodeBlock(blockId) {
const res = await fetch("http://127.0.0.1:6806/api/block/getBlockDOM", {
method: "POST",
headers: { "Content-Type": "application/json", "Authorization": `Token ${token}` },
body: JSON.stringify({ id: blockId })
});
const data = await res.json();
if (data.code !== 0) { console.error("获取代码块失败:", blockId); return; }
const parser = new DOMParser();
const doc = parser.parseFromString(data.data.dom, "text/html");
const editable = doc.querySelector('div.hljs > div[contenteditable="true"]');
if (editable) editable.textContent = "";
const updateRes = await fetch("http://127.0.0.1:6806/api/block/updateBlock", {
method: "POST",
headers: { "Content-Type": "application/json", "Authorization": `Token ${token}` },
body: JSON.stringify({ id: blockId, data: doc.body.innerHTML, dataType: "dom" })
});
const result = await updateRes.json();
if (result.code === 0) console.log(`✅ 清空代码块 ${blockId} 成功`);
}
async function uncheckListItems(listBlockId) {
const res = await fetch("http://127.0.0.1:6806/api/block/getBlockDOM", {
method: "POST",
headers: { "Content-Type": "application/json", "Authorization": `Token ${token}` },
body: JSON.stringify({ id: listBlockId })
});
const data = await res.json();
if (data.code !== 0) return;
const parser = new DOMParser();
const doc = parser.parseFromString(data.data.dom, "text/html");
const items = doc.querySelectorAll('[data-type="NodeListItem"].protyle-task--done');
for (const item of items) {
const text = item.querySelector('[contenteditable="true"]')?.textContent || "";
await fetch("http://127.0.0.1:6806/api/block/updateBlock", {
method: "POST",
headers: { "Content-Type": "application/json", "Authorization": `Token ${token}` },
body: JSON.stringify({ id: item.getAttribute("data-node-id"), data: `- [ ] ${text}`, dataType: "markdown" })
});
}
console.log("✅ 已取消单选列表勾选项");
}
async function insertDataToDB(parsedData, avID) {
const keysRes = await fetch("http://127.0.0.1:6806/api/av/getAttributeViewKeysByAvID", {
method: "POST",
headers: { "Content-Type": "application/json", "Authorization": `Token ${token}` },
body: JSON.stringify({ avID })
});
const keysData = await keysRes.json();
if (keysData.code !== 0) { console.error("获取列信息失败"); return; }
const keyMap = {};
keysData.data.forEach(col => keyMap[col.name] = { id: col.id, type: col.type });
const radioOptions = parsedData["单选:"] || [];
const selected = radioOptions.find(opt => opt.isSelected)?.text || "";
const dateKey = Object.keys(parsedData).find(k => k.includes("日期")) || "";
const dateTimestamp = dateToTimestamp(parsedData[dateKey] || "");
const blocksValues = [[
{ keyID: keyMap["主键"].id, block: { content: parsedData["主键:"] || "" } },
{ keyID: keyMap["单选"].id, mSelect: selected ? [{ content: selected }] : [] },
{ keyID: keyMap["数字"].id, number: { content: Number(parsedData["数字:"] || 0), isNotEmpty: !!parsedData["数字:"] } },
{ keyID: keyMap["日期"].id, date: { content: dateTimestamp, isNotEmpty: true } },
{ keyID: keyMap["返现"].id, number: { content: Number(parsedData["返现:"] || 0), isNotEmpty: !!parsedData["返现:"] } }
]];
const res = await fetch("http://127.0.0.1:6806/api/av/appendAttributeViewDetachedBlocksWithValues", {
method: "POST",
headers: { "Content-Type": "application/json", "Authorization": `Token ${token}` },
body: JSON.stringify({ avID, blocksValues })
});
const result = await res.json();
if (result.code === 0) {
console.log("✅ 数据插入成功!");
// 数据插入成功后再清空代码块并取消勾选
for (const id of codeBlocks) await clearCodeBlock(id);
await uncheckListItems(listBlockId);
alert("提交成功!");
} else {
console.error("❌ 数据插入失败", result);
}
}
// ------------------ 主流程 ------------------
// docId是你的表单的文档ID,acID是你的消费数据库id(不是块id)
(async () => {
const docId = "99999999999999-aaaaaaa";
const avID = "99999999999999-aaaaaaa";
const rawHtml = await getRawNoteContent(docId);
if (!rawHtml) return;
const parsedData = parseRawContent(rawHtml);
console.log("解析后的数据:", parsedData);
await insertDataToDB(parsedData, avID);
})();
}

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