HTML 块脚本可以调用 JS 片段中的函数吗

我想在列表中创建多个计时器,效果如下

timer1.png

html 块脚本如下

<div> <style> .scoped-timer { padding: 10px; border: 1px solid #ccc; border-radius: 4px; margin: 8px 0; background: #f0f8ff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); font-family: sans-serif; font-size: 14px; color: #333; } .scoped-timer .timer-controls { display: flex; gap: 8px; margin-bottom: 6px; } .scoped-timer button { padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; } .scoped-timer .start-btn { background-color: #28a745; /* Green */ color: white; } .scoped-timer .stop-btn { background-color: #dc3545; /* Red */ color: white; } .scoped-timer button:disabled { background-color: #ccc; cursor: not-allowed; } .scoped-timer .row { display: flex; gap: 20px; margin-top: 4px; } </style> <div class="scoped-timer"> <div class="timer-controls"> <button class="start-btn" onclick="handleStart(this)">开始</button> <button class="stop-btn" onclick="handleStop(this)">停止</button> </div> <div class="row"> <div>开始时间:<span class="start-time">--</span></div> <div>停止时间:<span class="stop-time">--</span></div> </div> <div class="row"> <div>本次时长(分钟):<span class="current-duration">0.00</span></div> <div>累计时长(分钟):<span class="total-duration">0.00</span></div> </div> </div> </div>

并且已经开启了** 设置 - 编辑器 - 允许执行 HTML 块内脚本**

HTML 块内脚本主要就是两个按钮,如下

<div class="timer-controls"> <button class="start-btn" onclick="handleStart(this)">开始</button> <button class="stop-btn" onclick="handleStop(this)">停止</button> </div>

分别调用了两个函数 handleStart(this)handleStop(this)。而这两个函数我是写在自定义的代码片段,JS 中,如下

(() => { window.timerStates = window.timerStates || new Map(); function handleStart(button) { const container = button.closest('.scoped-timer'); const stopBtn = container.querySelector(".stop-btn"); const startTimeSpan = container.querySelector(".start-time"); const currentSpan = container.querySelector(".current-duration"); const containerId = container.closest('[data-type="NodeList"]').dataset.nodeId + '_' + Array.from(container.closest('[data-type="NodeList"]').children).indexOf(container.closest('.li')); if (!timerStates.has(containerId)) { const start = Date.now(); if (startTimeSpan.textContent === "--") { startTimeSpan.textContent = new Date().toLocaleString(); } const state = { startTime: start, elapsed: 0, interval: setInterval(() => { const now = (Date.now() - state.startTime) / 60000 + state.elapsed; currentSpan.textContent = now.toFixed(2); }, 60000) }; timerStates.set(containerId, state); button.textContent = "暂停"; } else { const state = timerStates.get(containerId); state.elapsed += (Date.now() - state.startTime) / 60000; clearInterval(state.interval); timerStates.delete(containerId); button.textContent = "开始"; } } function handleStop(button) { const container = button.closest('.scoped-timer'); const startBtn = container.querySelector(".start-btn"); const stopTimeSpan = container.querySelector(".stop-time"); const currentSpan = container.querySelector(".current-duration"); const totalSpan = container.querySelector(".total-duration"); const containerId = container.closest('[data-type="NodeList"]').dataset.nodeId + '_' + Array.from(container.closest('[data-type="NodeList"]').children).indexOf(container.closest('.li')); const state = timerStates.get(containerId); if (state) { state.elapsed += (Date.now() - state.startTime) / 60000; clearInterval(state.interval); currentSpan.textContent = state.elapsed.toFixed(2); timerStates.delete(containerId); } stopTimeSpan.textContent = new Date().toLocaleString(); startBtn.disabled = true; button.disabled = true; const listRoot = container.closest('[data-type="NodeList"]'); let total = 0; listRoot.querySelectorAll('.current-duration').forEach(span => { total += parseFloat(span.textContent || '0'); }); totalSpan.textContent = total.toFixed(2); } })();

handleStart(this)handleStop(this) 这两个函数是存在的,且 JS 片段也打开了,为什么我在文档中点击按钮,报错说方法不存在

timer2.png

为什么会这样呢?我可以怎么修改,使计时器生效


我这个定时器最后效果如图:

  1. 在页面没有重新加载的时候,是可以记录任务花费的时间,任务的开始时间,本次时长,结束时间和累计时长都记录在计时器下面的一个列表项中。
    timer4.png
  2. 但是一旦页面重新加载,那么最后一个记录任务的开始时间,本次时长,结束时间和累计时长的列表项的值消失了,如图
    timer5.png
  3. 我想页面重新加载,以前的记录不会消失,不知道该怎么做,我也不知道为什么一个列表项内容消失了,一个没有。代码都是一样的

新的定时器代码如下

html 模块代码如下

<div> <style> .scoped-timer { padding: 0; border: none; background: none; font-family: sans-serif; font-size: 14px; color: #333; } .scoped-timer .timer-controls { display: flex; gap: 45px; margin-bottom: 6px; } .scoped-timer button { padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; } .scoped-timer .start-btn { background-color: #28a745; /* Green */ color: white; } .scoped-timer .stop-btn { background-color: #dc3545; /* Red */ color: white; } .scoped-timer button:disabled { background-color: #ccc; cursor: not-allowed; } </style> <div class="scoped-timer"> <div class="timer-controls"> <button class="start-btn" onclick="handleStart(this)">开始</button> <button class="stop-btn" onclick="handleStop(this)">停止</button> </div> </div> </div>

新的 JS 代码片段如下,

window.timerStates = window.timerStates || new Map(); function handleStart(button) { const container = button.closest('.scoped-timer'); const host = container.getRootNode()?.host; // const containerId = host?.closest('[data-type="NodeList"]').dataset.nodeId + '_' + Array.from(host?.closest('[data-type="NodeList"]').children).indexOf(host?.closest('.li')); // console.log(containerId); const listItem = host?.closest('.li'); const listNode = host?.closest('[data-type="NodeList"]'); const nodeId = listNode.dataset.nodeId; const siblings = Array.from(listNode.querySelectorAll('.li')); const index = siblings.indexOf(listItem); const dataItem = siblings[index + 1]; const dataContent = dataItem?.querySelector('[data-type="NodeParagraph"] > div[contenteditable]'); const containerId = nodeId + '_' + index; console.log(containerId); if (!window.timerStates.has(containerId)) { const startTime = Date.now(); const state = { startTime, elapsed: 0, interval: setInterval(() => { const minutes = ((Date.now() - state.startTime) / 60000 + state.elapsed).toFixed(1); if (dataContent) updateDataContent(dataContent, null, minutes, null); }, 60000) }; if (dataContent) updateDataContent(dataContent, new Date().toLocaleString()); window.timerStates.set(containerId, state); button.textContent = '暂停'; } else { const state = window.timerStates.get(containerId); state.elapsed += (Date.now() - state.startTime) / 60000; clearInterval(state.interval); window.timerStates.delete(containerId); button.textContent = '开始'; } } function handleStop(button) { const container = button.closest('.scoped-timer'); const host = container.getRootNode()?.host; // const containerId = host?.closest('[data-type="NodeList"]').dataset.nodeId + '_' + Array.from(host?.closest('[data-type="NodeList"]').children).indexOf(host?.closest('.li')); // console.log(containerId); const startBtn = container.querySelector(".start-btn"); const listItem = host?.closest('.li'); const listNode = host?.closest('[data-type="NodeList"]'); const nodeId = listNode.dataset.nodeId; const siblings = Array.from(listNode.querySelectorAll('.li')); const index = siblings.indexOf(listItem); const dataItem = siblings[index + 1]; const dataContent = dataItem?.querySelector('[data-type="NodeParagraph"] > div[contenteditable]'); const containerId = nodeId + '_' + index; console.log(containerId); const state = window.timerStates.get(containerId); let current = 0; if (state) { console.log("清除计时器"); current = state.elapsed + (Date.now() - state.startTime) / 60000; clearInterval(state.interval); window.timerStates.delete(containerId); } if (dataContent) { let total = current; for (let i = 0; i < index; i++) { const contentDiv = siblings[i + 1]?.querySelector('[data-type="NodeParagraph"] > div[contenteditable]'); if (contentDiv) { const match = contentDiv.textContent.match(/累计时长(分钟): (\d+(\.\d+)?)/); if (match) total += parseFloat(match[1]); } } updateDataContent(dataContent, null, current.toFixed(1), new Date().toLocaleString(), total.toFixed(1)); } startBtn.disabled = true; button.disabled = true; } function updateDataContent(div, start = null, current = null, stop = null, total = null) { let content = div.innerText || ""; // console.log(content); function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } function replaceOrAppend(label, value) { const escapedLabel = escapeRegExp(label); // "用户\\(名称\\)" const regex = new RegExp(`${escapedLabel}: [^\s\n]*`); const str = `${label}: ${value}`; if (regex.test(content)) { content = content.replace(regex, str); } else { // content += ` ${str}`; // 检查字符串是否以"停止时间"开头 if (str.startsWith("停止时间")) { // 添加shift+回车(用换行符表示)后接str content += '\n' + str; } else { // 添加空格后接str content += ` ${str}`; } } } if (start !== null) replaceOrAppend("开始时间", start); if (current !== null) replaceOrAppend("本次时长(分钟)", current); if (stop !== null) replaceOrAppend("停止时间", stop); if (total !== null) replaceOrAppend("累计时长(分钟)", total); div.innerText = content.trim(); }
  • 思源笔记

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

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

    25923 引用 • 107436 回帖 • 1 关注
  • Q&A

    提问之前请先看《提问的智慧》,好的问题比好的答案更有价值。

    9951 引用 • 45204 回帖 • 77 关注
2 操作
amykiki 在 2025-05-30 21:22:02 更新了该帖
amykiki 在 2025-05-30 21:19:55 更新了该帖

相关帖子

被采纳的回答
  • 因为你把函数封装到闭包 (() => {})() 中了,外部是无法读取到闭包中的函数或变量的。

    解决方法,1 去掉闭包 2 把闭包中的函数暴露到外部,比如 window.xxx=xxx

    另外,你代码中的 const containerId = container.closest('') 是无法获取到祖先元素的,因为 container 在 shadow 内部,需要用 const host = container.getRootNode()?.host; 然后 host.closest('')即可。

    下面是我改好的代码,放到 js 代码片段中即可。

    window.timerStates = window.timerStates || new Map(); function handleStart(button) { const container = button.closest('.scoped-timer'); const stopBtn = container.querySelector(".stop-btn"); const startTimeSpan = container.querySelector(".start-time"); const currentSpan = container.querySelector(".current-duration"); const host = container.getRootNode()?.host; const containerId = host?.closest('[data-type="NodeList"]').dataset.nodeId + '_' + Array.from(host?.closest('[data-type="NodeList"]').children).indexOf(host?.closest('.li')); if (!timerStates.has(containerId)) { const start = Date.now(); if (startTimeSpan.textContent === "--") { startTimeSpan.textContent = new Date().toLocaleString(); } const state = { startTime: start, elapsed: 0, interval: setInterval(() => { const now = (Date.now() - state.startTime) / 60000 + state.elapsed; currentSpan.textContent = now.toFixed(2); }, 60000) }; timerStates.set(containerId, state); button.textContent = "暂停"; } else { const state = timerStates.get(containerId); state.elapsed += (Date.now() - state.startTime) / 60000; clearInterval(state.interval); timerStates.delete(containerId); button.textContent = "开始"; } } function handleStop(button) { const container = button.closest('.scoped-timer'); const startBtn = container.querySelector(".start-btn"); const stopTimeSpan = container.querySelector(".stop-time"); const currentSpan = container.querySelector(".current-duration"); const totalSpan = container.querySelector(".total-duration"); const host = container.getRootNode()?.host; const containerId = host?.getRootNode()?.host?.closest('[data-type="NodeList"]').dataset.nodeId + '_' + Array.from(host?.closest('[data-type="NodeList"]').children).indexOf(host?.closest('.li')); const state = timerStates.get(containerId); if (state) { state.elapsed += (Date.now() - state.startTime) / 60000; clearInterval(state.interval); currentSpan.textContent = state.elapsed.toFixed(2); timerStates.delete(containerId); } stopTimeSpan.textContent = new Date().toLocaleString(); startBtn.disabled = true; button.disabled = true; const listRoot = host?.closest('[data-type="NodeList"]'); let total = 0; listRoot.querySelectorAll('protyle-html').forEach(protyleHtml => { const span = protyleHtml.shadowRoot.querySelector('.current-duration'); total += parseFloat(span.textContent || '0'); }); totalSpan.textContent = total.toFixed(2); }

欢迎来到这里!

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

注册 关于
请输入回帖内容 ...
  • 代码块新增 updateBlock 函数,然后,updateDataContent 函数最后增加 updateBlock(div);即可,如下代码所示

    function updateDataContent(div, start = null, current = null, stop = null, total = null) { //原来的代码... // 新增更新块代码 updateBlock(div); } async function updateBlock(node) { if(!node.matches('[data-node-id][data-type]')) { node = node.closest('[data-node-id][data-type]'); } await requestApi('/api/block/updateBlock', { "dataType": "dom", "data": node.outerHTML, "id": node.dataset.nodeId }); async function requestApi(url, data, method = 'POST') { return await (await fetch(url, {method: method, body: JSON.stringify(data||{})})).json(); } }
    2 回复
    1 操作
    wilsons 在 2025-05-31 07:31:11 更新了该回帖
  • 其他回帖
  • amykiki

    大佬,新版的代码见问题贴,回复里贴不下了

    1 回复
    1 操作
    amykiki 在 2025-05-30 21:20:29 更新了该回帖
  • amykiki

    大佬,按照你给的代码,html 内容块内容能保存了,也实现了我期望的功能,再次感谢

  • amykiki

    大佬,在你的帮助下,我这个自定义列表计时器总算完成了,刚刚试了下,功能一切正常,万分感谢

  • 查看全部回帖

推荐标签 标签

  • AngularJS

    AngularJS 诞生于 2009 年,由 Misko Hevery 等人创建,后为 Google 所收购。是一款优秀的前端 JS 框架,已经被用于 Google 的多款产品当中。AngularJS 有着诸多特性,最为核心的是:MVC、模块化、自动化双向数据绑定、语义化标签、依赖注入等。2.0 版本后已经改名为 Angular。

    12 引用 • 50 回帖 • 511 关注
  • Flutter

    Flutter 是谷歌的移动 UI 框架,可以快速在 iOS 和 Android 上构建高质量的原生用户界面。 Flutter 可以与现有的代码一起工作,它正在被越来越多的开发者和组织使用,并且 Flutter 是完全免费、开源的。

    39 引用 • 92 回帖 • 8 关注
  • ActiveMQ

    ActiveMQ 是 Apache 旗下的一款开源消息总线系统,它完整实现了 JMS 规范,是一个企业级的消息中间件。

    19 引用 • 13 回帖 • 677 关注
  • 工具

    子曰:“工欲善其事,必先利其器。”

    298 引用 • 763 回帖
  • 深度学习

    深度学习(Deep Learning)是机器学习的分支,是一种试图使用包含复杂结构或由多重非线性变换构成的多个处理层对数据进行高层抽象的算法。

    43 引用 • 44 回帖 • 2 关注
  • OpenCV
    15 引用 • 36 回帖 • 7 关注
  • SMTP

    SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。SMTP 协议属于 TCP/IP 协议簇,它帮助每台计算机在发送或中转信件时找到下一个目的地。

    4 引用 • 18 回帖 • 630 关注
  • RYMCU

    RYMCU 致力于打造一个即严谨又活泼、专业又不失有趣,为数百万人服务的开源嵌入式知识学习交流平台。

    4 引用 • 6 回帖 • 57 关注
  • Sandbox

    如果帖子标签含有 Sandbox ,则该帖子会被视为“测试帖”,主要用于测试社区功能,排查 bug 等,该标签下内容不定期进行清理。

    435 引用 • 1238 回帖 • 592 关注
  • Maven

    Maven 是基于项目对象模型(POM)、通过一小段描述信息来管理项目的构建、报告和文档的软件项目管理工具。

    188 引用 • 319 回帖 • 245 关注
  • 博客

    记录并分享人生的经历。

    273 引用 • 2388 回帖 • 1 关注
  • Postman

    Postman 是一款简单好用的 HTTP API 调试工具。

    4 引用 • 3 回帖 • 4 关注
  • 架构

    我们平时所说的“架构”主要是指软件架构,这是有关软件整体结构与组件的抽象描述,用于指导软件系统各个方面的设计。另外还有“业务架构”、“网络架构”、“硬件架构”等细分领域。

    142 引用 • 442 回帖 • 3 关注
  • WebComponents

    Web Components 是 W3C 定义的标准,它给了前端开发者扩展浏览器标签的能力,可以方便地定制可复用组件,更好的进行模块化开发,解放了前端开发者的生产力。

    1 引用 • 8 关注
  • 书籍

    宋真宗赵恒曾经说过:“书中自有黄金屋,书中自有颜如玉。”

    80 引用 • 396 回帖
  • etcd

    etcd 是一个分布式、高可用的 key-value 数据存储,专门用于在分布式系统中保存关键数据。

    6 引用 • 26 回帖 • 544 关注
  • 安装

    你若安好,便是晴天。

    132 引用 • 1184 回帖
  • 学习

    “梦想从学习开始,事业从实践起步” —— 习近平

    172 引用 • 534 回帖
  • OpenShift

    红帽提供的 PaaS 云,支持多种编程语言,为开发人员提供了更为灵活的框架、存储选择。

    14 引用 • 20 回帖 • 663 关注
  • 设计模式

    设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

    201 引用 • 120 回帖 • 3 关注
  • NetBeans

    NetBeans 是一个始于 1997 年的 Xelfi 计划,本身是捷克布拉格查理大学的数学及物理学院的学生计划。此计划延伸而成立了一家公司进而发展这个商用版本的 NetBeans IDE,直到 1999 年 Sun 买下此公司。Sun 于次年(2000 年)六月将 NetBeans IDE 开源,直到现在 NetBeans 的社群依然持续增长。

    78 引用 • 102 回帖 • 708 关注
  • Access
    1 引用 • 3 回帖 • 3 关注
  • Gitea

    Gitea 是一个开源社区驱动的轻量级代码托管解决方案,后端采用 Go 编写,采用 MIT 许可证。

    5 引用 • 16 回帖 • 1 关注
  • DevOps

    DevOps(Development 和 Operations 的组合词)是一组过程、方法与系统的统称,用于促进开发(应用程序/软件工程)、技术运营和质量保障(QA)部门之间的沟通、协作与整合。

    59 引用 • 25 回帖 • 4 关注
  • SVN

    SVN 是 Subversion 的简称,是一个开放源代码的版本控制系统,相较于 RCS、CVS,它采用了分支管理系统,它的设计目标就是取代 CVS。

    29 引用 • 98 回帖 • 693 关注
  • 快应用

    快应用 是基于手机硬件平台的新型应用形态;标准是由主流手机厂商组成的快应用联盟联合制定;快应用标准的诞生将在研发接口、能力接入、开发者服务等层面建设标准平台;以平台化的生态模式对个人开发者和企业开发者全品类开放。

    15 引用 • 127 回帖 • 2 关注
  • 服务器

    服务器,也称伺服器,是提供计算服务的设备。由于服务器需要响应服务请求,并进行处理,因此一般来说服务器应具备承担服务并且保障服务的能力。

    125 引用 • 585 回帖