文件已经写好,这是纯本地版的完整 HTML,双击打开就能用。文件在 [index.html](file:///workspace/index.html)。
下面是完整代码,你可以直接全选复制保存为 `index.html`:
```html
今日吃啥 · 食谱小站
`;
var form = root.querySelector("form");
root.querySelector("h2").textContent = config.form.title;
var customFields = [];
(config.form.fields || []).forEach(function (field) {
var label = document.createElement("label");
label.textContent = field.label + (field.required ? "" : "(选填)");
var input = document.createElement("input");
input.name = field.key;input.maxLength = 500;input.required = field.required;
label.appendChild(input);form.insertBefore(label, form.querySelector(".consent"));customFields.push(field.key);
});
var trap = document.createElement("input");trap.name = "website";trap.autocomplete = "off";trap.tabIndex = -1;trap.setAttribute("aria-hidden","true");
trap.style.cssText = "position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;";form.appendChild(trap);
var button = form.querySelector("button");
var status = root.querySelector(".status");
var launcher = root.querySelector(".launcher");
var dialog = root.querySelector("dialog");
var close = root.querySelector(".close");
form.action = config.endpoint;
root.querySelector(".remake").href = config.home;
var sending = false;
var submitted = false;
function openDialog() {
if (dialog.open) return;
dialog.showModal();launcher.setAttribute("aria-expanded","true");
(submitted ? close : form.elements.email).focus({preventScroll:true});
}
launcher.addEventListener("click", openDialog);
close.addEventListener("click", function () { dialog.close(); });
dialog.addEventListener("close", function () {
if (dialog.open) return;
launcher.setAttribute("aria-expanded","false");launcher.focus({preventScroll:true});
});
dialog.addEventListener("click", function (event) {
if (event.target !== dialog) return;
var box = dialog.getBoundingClientRect();
if (event.clientX < box.left || event.clientX > box.right || event.clientY < box.top || event.clientY > box.bottom) dialog.close();
});
dialog.addEventListener("keydown", function (event) {
if (event.key !== "Tab") return;
var controls = Array.prototype.filter.call(dialog.querySelectorAll("button,input,textarea,select,a[href],[tabindex]"), function (control) {
return !control.disabled && control.tabIndex >= 0 && control.getClientRects().length;
});
if (!controls.length) return;
var index = controls.indexOf(root.activeElement);
var next = index < 0 ? (event.shiftKey ? controls.length - 1 : 0) : (index + (event.shiftKey ? -1 : 1) + controls.length) % controls.length;
event.preventDefault();controls[next].focus();
});
// Isolate widget actions but let page drags finish over the closed launcher.
["click","dblclick","contextmenu","pointerdown","pointerup","pointermove","mousedown","mouseup","mousemove","touchstart","touchmove","touchend","keydown","keyup","wheel"].forEach(function (name) {
root.addEventListener(name, function (event) {
if (dialog.open || ["click","dblclick","contextmenu","pointerdown","mousedown","touchstart","keydown","keyup"].indexOf(name) !== -1) event.stopPropagation();
});
});
function followLink(event) {
if (event.defaultPrevented || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) return;
var link = event.target && event.target.closest ? event.target.closest("a[href]") : null;
if (!link || link.hasAttribute("download") || link.target === "_blank") return;
try {
var url = new URL(link.href, location.href);
if (url.origin === location.origin && url.pathname === location.pathname && url.hash === "#" + host.id) {
event.preventDefault();openDialog();
}
} catch (error) {}
}
var positionFrame = 0;
var agentRoots = [];
function positionLauncher() {
positionFrame = 0;
if (!host.isConnected) return;
var viewport = window.visualViewport;
var width = viewport ? viewport.width : innerWidth;
var height = viewport ? viewport.height : innerHeight;
var top = viewport ? viewport.offsetTop : 0;
var left = viewport ? viewport.offsetLeft : 0;
var obstacles = [];
document.querySelectorAll("[data-bz-agent-embed],[data-bz-edit-entry]").forEach(function (node) {
if (node.shadowRoot) {
node.shadowRoot.querySelectorAll(".launcher,.floating-panel.open,.full-panel").forEach(function (control) {
var box = control.getBoundingClientRect();
if (box.width && box.height) obstacles.push(box);
});
} else {
var box = node.getBoundingClientRect();if (box.width && box.height) obstacles.push(box);
}
});
var middle = top + height / 2 - 22;
var upper = top + Math.max(12, height * .18);
var candidates = [[left + width - 56,middle],[left + width - 56,upper],[left + 12,middle],[left + 12,upper]];
var position = candidates.find(function (point) {
return !obstacles.some(function (box) { return point[0] < box.right + 8 && point[0] + 44 > box.left - 8 && point[1] < box.bottom + 8 && point[1] + 44 > box.top - 8; });
});
launcher.hidden = !position;
if (position) {
host.style.setProperty("--bz-lead-left", position[0] + "px");host.style.setProperty("--bz-lead-top", position[1] + "px");
}
dialog.style.setProperty("--bz-dialog-top", top + height / 2 + "px");
dialog.style.setProperty("--bz-dialog-height", Math.max(140, height - 24) + "px");
}
function schedulePosition() { if (!positionFrame) positionFrame = requestAnimationFrame(positionLauncher); }
document.body.appendChild(host);
document.querySelectorAll("[data-bz-agent-embed]").forEach(function (node) {
if (node.shadowRoot) { agentRoots.push(node.shadowRoot);node.shadowRoot.addEventListener("click", schedulePosition); }
});
window.addEventListener("resize", schedulePosition);
if (window.visualViewport) {
visualViewport.addEventListener("resize", schedulePosition);visualViewport.addEventListener("scroll", schedulePosition);
}
document.addEventListener("click", followLink);
host.__bzLeadCleanup = function () {
if (positionFrame) cancelAnimationFrame(positionFrame);
window.removeEventListener("resize", schedulePosition);document.removeEventListener("click", followLink);
agentRoots.forEach(function (agentRoot) { agentRoot.removeEventListener("click", schedulePosition); });
if (window.visualViewport) {
visualViewport.removeEventListener("resize", schedulePosition);visualViewport.removeEventListener("scroll", schedulePosition);
}
};
positionLauncher();
form.addEventListener("submit", async function (event) {
event.preventDefault();
if (sending || submitted || !form.reportValidity()) return;
sending = true;button.disabled = true;button.textContent = "正在提交";
status.textContent = "";status.removeAttribute("data-error");status.removeAttribute("data-success");
try {
var fields = new FormData(form);
var custom = {};customFields.forEach(function (key) { custom[key] = fields.get(key) || ""; });
var response = await fetch(config.endpoint, {
method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json"},
body:JSON.stringify({name:fields.get("name") || "",email:fields.get("email") || "",phone:fields.get("phone") || "",message:fields.get("message") || "",website:fields.get("website") || "",custom:custom})
});
var result = await response.json();
if (!response.ok || result.code !== 0 || !result.data || !result.data.submitted) throw new Error(result.message || "提交失败,请稍后重试");
submitted = true;status.setAttribute("data-success","1");status.textContent = "留言已提交";button.textContent = "已提交";
form.querySelectorAll("input,textarea").forEach(function (input) { input.disabled = true; });
} catch (error) {
status.setAttribute("data-error","1");status.textContent = error.message || "网络异常,请稍后重试";button.textContent = "提交意向";
} finally { sending = false;button.disabled = submitted; }
});
}
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", mount);
else mount();
})();