同一页面使用多个富文本编辑器:独立字段与表单提交

构建双编辑器表单,使用唯一 ID、独立 HTML 字段、安全预览及确认后重置。
每个字段保留独立实例
商品表单可能同时需要简短摘要和详细说明。为每个容器创建编辑器,将引用保存在映射中。样式、核心与插件只加载一次。不要重复容器 ID,也不要用同一个变量覆盖所有实例。独立实例代表不同文档,不是共享文档或多根编辑器。
运行完整双编辑器示例
通过应用提供编辑器资源。示例用 textContent 将最新 HTML 显示为 JSON,不用 innerHTML,也不保存或发送请求。只恢复摘要不会影响详情;恢复两个字段前需要确认。编辑后会清除预览,避免将旧预览误认为已保存内容。
<link rel="stylesheet" href="/richtexteditor/rte_theme_default.css">
<script src="/richtexteditor/rte.js"></script>
<script src="/richtexteditor/plugins/all_plugins.js"></script>
<style>body{font:16px/1.6 system-ui;max-width:960px;margin:24px auto;padding:0 16px}section{min-width:0;margin-block:20px}.editor-scroll{max-width:100%;overflow-x:auto}button{min-height:44px;padding:8px 16px;margin:8px 8px 0 0}pre{white-space:pre-wrap;overflow-wrap:anywhere}</style>
<h1>Two editors, two independent fields</h1>
<form id="product-form">
<section aria-labelledby="summary-heading">
<h2 id="summary-heading">Summary</h2>
<div class="editor-scroll"><div id="summary-editor"></div></div>
<button type="button" id="restore-summary">Restore summary</button>
</section>
<section aria-labelledby="details-heading">
<h2 id="details-heading">Details</h2>
<div class="editor-scroll"><div id="details-editor"></div></div>
</section>
<button type="submit">Inspect both fields</button>
<button type="reset">Restore both fields</button>
<p id="status" role="status" aria-live="polite"></p>
<pre id="payload"></pre>
</form>
<script>
// Load the assets once. Give every host its own unique ID and instance.
const initial = {
summary: "<p>A short <strong>product summary</strong>.</p>",
details: "<h2>Details</h2><p>A separate document with its own formatting.</p>"
};
const editors = {
summary: new RichTextEditor("#summary-editor", { toolbar: "full", height: "240px" }),
details: new RichTextEditor("#details-editor", { toolbar: "full", height: "320px" })
};
const form = document.getElementById("product-form");
const status = document.getElementById("status");
const output = document.getElementById("payload");
function resetFields() {
for (const key of Object.keys(editors)) editors[key].setHTMLCode(initial[key]);
}
resetFields();
function invalidatePreview() {
output.textContent = "";
status.textContent = "Draft changed. Inspect again to see current values.";
}
for (const instance of Object.values(editors)) instance.attachEvent("change", invalidatePreview);
document.getElementById("restore-summary").addEventListener("click", () => {
if (!confirm("Restore only the summary?")) return;
editors.summary.setHTMLCode(initial.summary);
invalidatePreview();
});
form.addEventListener("reset", event => {
if (!confirm("Restore both documents?")) { event.preventDefault(); return; }
resetFields();
output.textContent = "";
status.textContent = "Both initial documents restored. Nothing saved.";
});
function collect() {
return Object.fromEntries(Object.entries(editors).map(([key, instance]) => [key, instance.getHTMLCode()]));
}
form.addEventListener("submit", event => {
event.preventDefault();
// Read both live instances now; never use whichever editor last had focus.
output.textContent = JSON.stringify(collect(), null, 2);
status.textContent = "Current fields shown below. No request sent.";
});
window.multipleDemo = { editors, initial, collect };
</script>
分别保存并验证字段
服务器接口使用 summary 和 details 等稳定字段名。提交时读取两个实例,不依赖用户最后聚焦的位置。真实接口必须验证记录权限,并分别验证和清理 HTML;浏览器预览不是安全措施。任一字段不合法时保留两个草稿,指出问题字段,并明确是否需要原子保存。
协调自动保存与重置
每个文档使用不同的自动保存键,或者将两个字段作为同一条带版本的记录保存。共用本地存储键可能互相覆盖。重置浏览器实例不会取消服务器写入、删除上传文件或提供恢复备份。启用自动保存前先明确队列和版本策略。
验收清单
分别输入内容,切换焦点后检查两个字段。恢复一个字段,确认另一个字段的 HTML 完全不变。取消整体重置,保留两个草稿。测试长 JSON、表情、键盘操作和 320px 布局。大量完整工具栏会增加内存和空间开销,初始化几十个实例前应测量真实表单。