")
this.isSendingUpdate = false;
this.schedulePolling();
}
} catch (e) {
log.err(e)
}
}
// --- Input Strategies ---
async fillInput(inputConfig, text) {
log.info("fillInput is called")
if (!inputConfig) return false;
const { selector, method } = inputConfig;
const el = document.querySelector(selector);
if (!el) { log.err(`输入框未找到: ${selector}`); return false; }
el.focus();
try {
switch (method) {
case 'react': await this.setInputReact(el, text); break;
case 'lexical': await this.setInputPaste(el, text); break;
case 'div': el.innerHTML = text.split("\n").map(i => `${this.escapeHtml(i)}
`).join(""); el.dispatchEvent(new InputEvent('input', { bubbles: true })); break;
case 'paste': await this.setInputPaste(el, text); break;
case 'gemini': el.textContent = text; el.dispatchEvent(new InputEvent('input', { bubbles: true })); break;
case 'textarea': await this.setInputTextarea(el, text); break;
case 'vue': await this.setInputVue(el, text); break;
case 'standard': default: await this.setInputStandard(el, text); break;
}
await this.sleep(100);
el.dispatchEvent(new Event('input', { bubbles: true }));
return true;
} catch (e) { log.err(`填充错误 ${method}`, e); return false; }
}
// Input Helpers
escapeHtml(str) { return str.replace(/[&<>"']/g, m => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[m]); }
async setInputStandard(el, text) {
// 获取 input 输入框的dom对象
var inputNode = el
if (!inputNode) { return }
inputNode.value = text;
// plus
try {
inputNode.innerHTML = text.split("\n").map(i => `${escapeHtml(i)}
`).join("\n");
} catch { }
// 设置输入框的 input 事件
var event = new InputEvent('input', {
'bubbles': true,
'cancelable': true,
});
inputNode.dispatchEvent(event);
}
async setInputTextarea(el, text) {
const textarea = el
const props = Object.values(textarea)[1]
// 获取目标 DOM 节点(假设 temp2 是 DOM 元素引用)
const targetElement = textarea;
// 创建伪事件对象
const e = {
target: targetElement,
currentTarget: targetElement,
type: 'change',
};
// 手动设置值(需同时更新 DOM 和 React 状态)
targetElement.value = text;
// 触发 React 的 onChange 处理
await props.onChange(e);
}
async setInputReact(el, text) {
const key = Object.keys(el).find(k => k.startsWith('__reactProps'));
if (key) {
const props = el[key];
// Try standard onChange or value tracker
if (props.onChange) {
props.onChange({ target: { value: text }, currentTarget: { value: text } });
}
// Fallback for some Ant Design or specific wrappers (like Monica)
if (props.children && props.children.props && props.children.props.onChange) {
props.children.props.onChange({ target: { value: text } });
}
} else {
// Fallback to standard if React props not found
await this.setInputStandard(el, text);
}
}
async setInputPaste(el, text) { const dt = new DataTransfer(); dt.setData('text/plain', text); el.dispatchEvent(new ClipboardEvent('paste', { bubbles: true, cancelable: true, clipboardData: dt })); }
async setInputVue(el, text) { try { document.querySelector(".question-input")?.__vue__?.onStopStream(); if (el.__vue__?.onChange) await el.__vue__.onChange(text.slice(0, 10000)); } catch (e) { } }
async clickSend(sendSelector, messageSelector) {
const btn = document.querySelector(sendSelector);
if (!btn) return false;
// 🔥🔥🔥 核心修改:在点击之前,必须无条件掐断所有连接!
// 只有这样才能腾出浏览器的网络线程给 ChatGPT 主请求
this.killPoll();
// 标记为正在发送,防止 schedulePolling 中的定时器意外启动 Poll
this.isSendingUpdate = true;
const getCount = () => document.querySelectorAll(messageSelector).length;
const initialCount = getCount();
const maxRetries = 20;
log.ui(`准备发送,当前消息数: ${initialCount}`);
// 点击操作
btn.click();
btn.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
btn.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
// 循环检查
for (let i = 0; i < maxRetries; i++) {
const currentCount = getCount();
log.ui(`检测状态 ${initialCount} -> ${currentCount}`);
if (currentCount > initialCount) {
log.ui(`发送成功 ${initialCount} -> ${currentCount}`);
this.isSendingUpdate = false;
this.schedulePolling();
return true;
}
if (i === 5) {
log.warn("尝试补刀点击");
btn.click();
}
await this.sleep(500);
}
log.err("发送失败 (DOM 未变化)");
// 即使失败也要解除静默,否则脚本就死锁了
this.isSendingUpdate = false;
this.schedulePolling();
return false;
}
async uploadFile(base64String, fileName) {
try {
const fileConfig = this.config.input.file || { method: "input", selector: "input[type=file]" };
console.log(fileConfig)
const { method, selector, timeout } = fileConfig;
const fileType = this.getFileType(fileName);
const fileContent = this.base64ToArrayBuffer(base64String);
const file = new File([fileContent], fileName, { type: fileType });
log.info(`上传文件: ${fileName} (${method})`);
if (method === "input") {
const dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
let fileInput;
const fileInputs = document.querySelectorAll(selector);
if (fileInputs.length === 1) fileInput = fileInputs[0];
else fileInput = [...fileInputs].find(i => i.accept.includes(fileType) || i.multiple) || fileInputs[0];
if (fileInput) {
fileInput.files = dataTransfer.files;
fileInput.dispatchEvent(new Event('change', { bubbles: true }));
}
} else if (method === "drag") {
const dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
const dropZone = document.querySelector(selector); // 使用提供的选择器查找拖放区域
const dragStartEvent = new DragEvent("dragstart", {
bubbles: true,
dataTransfer: dataTransfer,
cancelable: true
});
const dropEvent = new DragEvent("drop", {
bubbles: true,
dataTransfer: dataTransfer,
cancelable: true
});
dropZone.dispatchEvent(dragStartEvent);
dropZone.dispatchEvent(dropEvent);
} else if (method === "paste") {
const dt = new DataTransfer();
dt.items.add(file);
const pasteEvent = new ClipboardEvent("paste", { bubbles: true, cancelable: true });
Object.defineProperty(pasteEvent, "clipboardData", { value: dt });
const target = document.querySelector(selector);
if (target) { target.focus(); target.dispatchEvent(pasteEvent); }
}
if (timeout) await this.sleep(timeout);
} catch (e) { log.err("文件上传失败", e); }
}
sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
getFileType(fileName) {
if (fileName.endsWith("pdf")) return "application/pdf";
if (fileName.endsWith("png")) return "image/png";
if (fileName.endsWith("jpg") || fileName.endsWith("jpeg")) return "image/jpeg";
if (fileName.endsWith("txt") || fileName.endsWith("md")) return "text/plain";
if (fileName.endsWith("html")) return "text/html";
return "application/octet-stream";
}
base64ToArrayBuffer(base64) {
const binaryString = window.atob(base64);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) { bytes[i] = binaryString.charCodeAt(i); }
return bytes.buffer;
}
}
// class NoticeManager {
// constructor() {
// this.overlay = null;
// this.box = null;
// this.contentWrap = null;
// this.itv = null; // 保存倒计时定时器
// this.closeTimer = null; // 保存关闭定时器
// this.resolveActive = null;// 保存当前处于活跃状态的 Promise resolve 函数
// this._injectStyle();
// }
// // 1. 注入 CSS 样式 (仅首次运行)
// _injectStyle() {
// const styleId = 'custom-confirm-style';
// if (document.getElementById(styleId)) return;
// const style = document.createElement('style');
// style.id = styleId;
// style.innerHTML = `
// .conf-overlay {
// position: fixed; top: 20px; left: 50%; transform: translateX(-50%);
// z-index: 2147483647; pointer-events: none;
// }
// .conf-box {
// pointer-events: auto; background: #fff; padding: 8px 16px;
// border-radius: 50px; display: flex; align-items: center; justify-content: center;
// box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
// border: 1px solid rgba(0, 0, 0, 0.06);
// animation: slideInTop 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.15) forwards;
// transition: width 0.35s cubic-bezier(0.4, 0, 0.2, 1);
// box-sizing: border-box; overflow: hidden;
// white-space: nowrap;
// }
// .conf-box.exit {
// animation: slideOutTop 0.4s cubic-bezier(0.6, -0.28, 0.735, 0.045) forwards;
// }
// @keyframes slideInTop { from { transform: translateY(-100px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
// @keyframes slideOutTop { from { transform: translateY(0); opacity: 1; } to { transform: translateY(-100px); opacity: 0; } }
// @keyframes fadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
// .conf-content { display: flex; align-items: center; gap: 14px; width: max-content; }
// .conf-text { color: #1f2937; font-size: 14px; font-weight: 500; }
// .conf-btns { display: flex; gap: 8px; align-items: center; }
// .conf-btn {
// cursor: pointer; border: none; height: 32px; border-radius: 16px;
// display: flex; align-items: center; justify-content: center;
// transition: all 0.2s; font-size: 13px; font-weight: 500; gap: 4px;
// }
// .conf-cancel { background: #f3f4f6; color: #4b5563; padding: 0 14px; }
// .conf-cancel:hover { background: #e5e7eb; color: #111827; }
// .conf-ok { background: #ef4444; color: #fff; padding: 0 14px; }
// .conf-ok:hover { background: #dc2626; transform: scale(1.02); box-shadow: 0 4px 10px rgba(239, 68, 68, 0.3); }
// .t-val { font-size: 11px; opacity: 0.8; margin-left: 2px; }
// `;
// document.head.appendChild(style);
// }
// // 2. 清理历史状态 (防止多次调用造成冲突)
// _clearState() {
// clearInterval(this.itv);
// clearTimeout(this.closeTimer);
// if (this.resolveActive) {
// this.resolveActive(false); // 如果有未完成的交互,默认作废并返回 false
// this.resolveActive = null;
// }
// }
// // 3. 核心:处理 DOM 复用与宽度过渡动画
// _renderContent(html) {
// if (!this.overlay || !document.body.contains(this.overlay)) {
// // -- 首次创建 DOM --
// this.overlay = document.createElement('div');
// this.overlay.className = 'conf-overlay';
// this.overlay.innerHTML = `
// `;
// document.body.appendChild(this.overlay);
// this.box = this.overlay.querySelector('.conf-box');
// this.contentWrap = this.overlay.querySelector('.conf-content');
// } else {
// // -- 复用 DOM 并触发宽度过渡动画 --
// this.box.classList.remove('exit'); // 取消可能正在进行的退出动画
// const oldWidth = this.box.offsetWidth;
// this.box.style.width = oldWidth + 'px'; // 锁死旧宽度
// this.contentWrap.innerHTML = html; // 注入新内容
// this.box.style.width = 'auto';
// const newWidth = this.box.offsetWidth; // 获取新内容的自然宽度
// this.box.style.width = oldWidth + 'px';
// this.box.offsetHeight; // 强制重绘
// this.box.style.width = newWidth + 'px'; // 设置新宽度,触发 CSS transition
// // 动画结束后解除宽度硬编码,以防万一
// setTimeout(() => { if (this.box) this.box.style.width = 'auto'; }, 350);
// }
// }
// // 4. 关闭动画
// close() {
// if (this.overlay && this.box) {
// this.box.classList.add('exit');
// setTimeout(() => {
// if (this.overlay && this.overlay.parentNode) {
// this.overlay.parentNode.removeChild(this.overlay);
// }
// this.overlay = null;
// }, 400);
// }
// }
// // ================= 公开方法 =================
// /**
// * 单纯消息弹窗 (Info/Success/Error)
// * @param {Object} options
// */
// message(options = {}) {
// this._clearState();
// console.log("message", options)
// return new Promise(resolve => {
// const { text = "操作成功", type = "success", duration = 1500, timeout = 0 } = options;
// let svgIcon = ""
// if (type === 'success') {
// svgIcon = ``
// } else if (type == "fail" || type == "cancel") {
// svgIcon = ``;
// } else if (type == "waiting" || type == "timeout" || type == "ok") {
// svgIcon = ``
// }
// const html = `
//
// ${svgIcon}
// ${text}
//
`;
// setTimeout(() => {
// this._renderContent(html);
// // 倒计时后自动关闭
// this.closeTimer = setTimeout(() => {
// this.close();
// resolve(true);
// }, duration);
// }, timeout)
// });
// }
// /**
// * 确认弹窗消息
// * @param {Object} options
// */
// confirm(options = {}) {
// this._clearState();
// return new Promise(resolve => {
// this.resolveActive = resolve; // 记录当前的 resolve
// const {
// text = "确认执行此操作?",
// okBtnText = "确认",
// cancelBtnText = "取消",
// msgOk = "操作成功",
// msgCancel = "已取消操作",
// msgTimeout = "已自动确认",
// timeout = 5
// } = options;
// const html = `
// ${text}
//
//
//
//
`;
// this._renderContent(html);
// // 绑定事件
// const btnOk = this.contentWrap.querySelector('.conf-ok');
// const btnCancel = this.contentWrap.querySelector('.conf-cancel');
// const timerText = this.contentWrap.querySelector('.t-val');
// let timeLeft = timeout;
// // 处理确认/取消逻辑 (重点:直接复用自身的 message 方法)
// const handleResult = (type) => {
// clearInterval(this.itv); // 1. 停止倒计时 (不要调用 this._clearState())
// this.resolveActive = null; // 2. 核心修复:解除绑定,防止被后续的 _clearState 误杀
// let isSuccess = false;
// let resultMsg = "";
// if (type === 'ok') { resultMsg = msgOk; isSuccess = true; }
// else if (type === 'cancel') { resultMsg = msgCancel; isSuccess = false; }
// else if (type === 'timeout') { resultMsg = msgTimeout; isSuccess = true; }
// // 3. 调用 message 时,它内部的 _clearState 就不会影响当前的 resolve 了
// this.message({ text: resultMsg, type: type, duration: 1500, timeout: 0 })
// .then(() => resolve(isSuccess));
// };
// this.itv = setInterval(() => {
// timeLeft--;
// if (timeLeft <= 0) handleResult('timeout');
// else if (timerText) timerText.innerText = `${timeLeft}s`;
// }, 1000);
// if (btnOk) btnOk.onclick = () => handleResult('ok');
// if (btnCancel) btnCancel.onclick = () => handleResult('cancel');
// });
// }
// }
class NoticeManager {
constructor() {
this.overlay = null;
this.box = null;
this.contentWrap = null;
this.itv = null;
this.closeTimer = null;
this.resolveActive = null;
this._injectStyle();
}
// ================= 1. 纯净 DOM 构建引擎 (免疫一切拦截) =================
_el(tag, attrs = {}, children = []) {
// 自动识别 SVG 标签,使用正确的命名空间创建
const isSvg = ['svg', 'path', 'polyline', 'circle', 'line'].includes(tag);
const el = isSvg
? document.createElementNS('http://www.w3.org/2000/svg', tag)
: document.createElement(tag);
for (const [key, value] of Object.entries(attrs)) {
if (key === 'className') el.setAttribute('class', value);
else if (key === 'style') el.style.cssText = value;
else el.setAttribute(key, value);
}
const kids = Array.isArray(children) ? children : [children];
for (const child of kids) {
if (typeof child === 'string') el.appendChild(document.createTextNode(child));
else if (child instanceof Node) el.appendChild(child);
}
return el;
}
// ================= 2. 注入 CSS (使用文本节点,告别 innerHTML) =================
_injectStyle() {
const styleId = 'custom-confirm-style';
if (document.getElementById(styleId)) return;
const style = document.createElement('style');
style.id = styleId;
const cssText = `
.conf-overlay { position: fixed; top: 20px; left: 50%; transform: translateX(-50%); z-index: 2147483647; pointer-events: none; }
.conf-box { pointer-events: auto; background: #fff; padding: 8px 16px; border-radius: 50px; display: flex; align-items: center; justify-content: center; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); border: 1px solid rgba(0, 0, 0, 0.06); animation: slideInTop 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.15) forwards; transition: width 0.35s cubic-bezier(0.4, 0, 0.2, 1); box-sizing: border-box; overflow: hidden; white-space: nowrap; }
.conf-box.exit { animation: slideOutTop 0.4s cubic-bezier(0.6, -0.28, 0.735, 0.045) forwards; }
@keyframes slideInTop { from { transform: translateY(-100px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
@keyframes slideOutTop { from { transform: translateY(0); opacity: 1; } to { transform: translateY(-100px); opacity: 0; } }
@keyframes fadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
.conf-content { display: flex; align-items: center; gap: 14px; width: max-content; }
.conf-text { color: #1f2937; font-size: 14px; font-weight: 500; }
.conf-btns { display: flex; gap: 8px; align-items: center; }
.conf-btn { cursor: pointer; border: none; height: 32px; border-radius: 16px; display: flex; align-items: center; justify-content: center; transition: all 0.2s; font-size: 13px; font-weight: 500; gap: 4px; }
.conf-cancel { background: #f3f4f6; color: #4b5563; padding: 0 14px; }
.conf-cancel:hover { background: #e5e7eb; color: #111827; }
.conf-ok { background: #ef4444; color: #fff; padding: 0 14px; }
.conf-ok:hover { background: #dc2626; transform: scale(1.02); box-shadow: 0 4px 10px rgba(239, 68, 68, 0.3); }
.t-val { font-size: 11px; opacity: 0.8; margin-left: 2px; }
`;
style.appendChild(document.createTextNode(cssText));
document.head.appendChild(style);
}
_clearState() {
clearInterval(this.itv);
clearTimeout(this.closeTimer);
if (this.resolveActive) {
this.resolveActive(false);
this.resolveActive = null;
}
}
// ================= 3. 核心:处理 DOM 复用与宽度过渡动画 =================
// 此时接收的是原生 Node 节点,而非 HTML 字符串
_renderContent(nodeMap) {
if (!this.overlay || !document.body.contains(this.overlay)) {
this.contentWrap = this._el('div', { className: 'conf-content' }, [nodeMap]);
this.box = this._el('div', { className: 'conf-box' }, [this.contentWrap]);
this.overlay = this._el('div', { className: 'conf-overlay' }, [this.box]);
document.body.appendChild(this.overlay);
} else {
this.box.classList.remove('exit');
const oldWidth = this.box.offsetWidth;
this.box.style.width = oldWidth + 'px';
// 安全清空旧节点,追加新节点
while (this.contentWrap.firstChild) this.contentWrap.removeChild(this.contentWrap.firstChild);
this.contentWrap.appendChild(nodeMap);
this.box.style.width = 'auto';
const newWidth = this.box.offsetWidth;
this.box.style.width = oldWidth + 'px';
this.box.offsetHeight; // 强制重绘
this.box.style.width = newWidth + 'px';
setTimeout(() => { if (this.box) this.box.style.width = 'auto'; }, 350);
}
}
close() {
if (this.overlay && this.box) {
this.box.classList.add('exit');
setTimeout(() => {
if (this.overlay && this.overlay.parentNode) {
this.overlay.parentNode.removeChild(this.overlay);
}
this.overlay = null;
}, 400);
}
}
// ================= 4. 公开方法 =================
message(options = {}) {
this._clearState();
console.log("message", options);
return new Promise(resolve => {
const { text = "操作成功", type = "success", duration = 1500, timeout = 0 } = options;
// 根据状态构建你自定义的 SVG 图标
let svgNode;
if (type === 'success') {
svgNode = this._el('svg', { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "#10b981", "stroke-width": "2.5", "stroke-linecap": "round", "stroke-linejoin": "round" }, [
this._el('polyline', { points: "20 6 9 17 4 12" })
]);
} else if (type === "fail" || type === "cancel") {
svgNode = this._el('svg', { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "#ef4444", "stroke-width": "2.5", "stroke-linecap": "round", "stroke-linejoin": "round" }, [
this._el('circle', { cx: "12", cy: "12", r: "10" }),
this._el('line', { x1: "15", y1: "9", x2: "9", y2: "15" }),
this._el('line', { x1: "9", y1: "9", x2: "15", y2: "15" })
]);
} else if (type === 'ok' || type === 'timeout'){
// 等待 / 超时 (你的灰色向日葵/时钟图标)
svgNode = this._el('svg', { viewBox: "0 0 1024 1024", width: "20", height: "20" }, [
this._el('path', { fill: "#666666", d: "M329.3 513.2c0-20-16.2-36.2-36.2-36.2H121.6c-20 0-36.2 16.2-36.2 36.2 0 20 16.2 36.2 36.2 36.2h171.5c20 0 36.2-16.2 36.2-36.2zM902.4 477H730.9c-20 0-36.2 16.2-36.2 36.2 0 20 16.2 36.2 36.2 36.2h171.5c20 0 36.2-16.2 36.2-36.2 0.1-20-16.2-36.2-36.2-36.2zM512.4 706.4c-20 0-36.2 16.2-36.2 36.2V902c0 20 16.2 36.2 36.2 36.2 20 0 36.2-16.2 36.2-36.2V742.7c0-20-16.2-36.3-36.2-36.3zM512.4 85.7c-20 0-36.2 16.2-36.2 36.2v171.5c0 20 16.2 36.2 36.2 36.2 20 0 36.2-16.2 36.2-36.2V121.9c0-20-16.2-36.2-36.2-36.2zM330.8 640.6L209.6 761.8c-14.1 14.1-14.1 37.1 0 51.2 7.1 7.1 16.3 10.6 25.6 10.6s18.5-3.5 25.6-10.6l121.3-121.3c14.1-14.1 14.1-37.1 0-51.2-14.2-14.1-37.1-14.1-51.3 0.1zM665.4 393.4c9.3 0 18.5-3.5 25.6-10.6l121.3-121.3c14.1-14.1 14.1-37.1 0-51.2-14.1-14.1-37.1-14.1-51.2 0L639.8 331.5c-14.1 14.1-14.1 37.1 0 51.2 7.1 7.1 16.3 10.7 25.6 10.7zM700.5 649c-14.1-14.1-37.1-14.1-51.2 0-14.1 14.1-14.1 37.1 0 51.2L762 813c7.1 7.1 16.3 10.6 25.6 10.6s18.5-3.5 25.6-10.6c14.1-14.1 14.1-37.1 0-51.2L700.5 649zM260.8 211.3c-14.1-14.1-37.1-14.1-51.2 0-14.1 14.1-14.1 37.1 0 51.2l121.3 121.3c7.1 7.1 16.3 10.6 25.6 10.6s18.5-3.5 25.6-10.6c14.1-14.1 14.1-37.1 0-51.2L260.8 211.3z" })
]);
}
// 构建 DOM: text
const contentNode = this._el('div', { style: "display:flex; align-items:center; gap:8px; animation: fadeIn 0.3s ease forwards; padding: 2px 6px;" }, [
svgNode,
this._el('span', { style: "color:#374151; font-size:15px; font-weight:600;" }, text)
]);
setTimeout(() => {
this._renderContent(contentNode);
this.closeTimer = setTimeout(() => {
this.close();
resolve(true);
}, duration);
}, timeout);
});
}
confirm(options = {}) {
this._clearState();
return new Promise(resolve => {
this.resolveActive = resolve;
const {
text = "确认执行此操作?",
okBtnText = "确认",
cancelBtnText = "取消",
msgOk = "操作成功",
msgCancel = "已取消操作",
msgTimeout = "已自动确认",
timeout = 5
} = options;
// 取消按钮
const btnCancel = this._el('button', { className: 'conf-btn conf-cancel' }, cancelBtnText);
// 确认按钮 (包含 SVG 和倒计时)
const timerTextNode = document.createTextNode(`${timeout}s`);
const timerSpan = this._el('span', { className: 't-val' }, timerTextNode);
const okSvg = this._el('svg', { width: "14", height: "14", viewBox: "0 0 1024 1024" }, [
this._el('path', { fill: "currentColor", d: "M939.36 218.912a32 32 0 0 1 45.856 44.672l-538.016 552a32 32 0 0 1-43.776 1.92L63.872 526.048a32 32 0 1 1 41.696-48.544l316.768 271.936L939.36 218.88z" })
]);
const btnOk = this._el('button', { className: 'conf-btn conf-ok' }, [okSvg, " " + okBtnText + " ", timerSpan]);
// 组装整体布局
const layoutNode = this._el('div', { style: 'display:flex; align-items:center; gap:14px;' }, [
this._el('div', { className: 'conf-text' }, text),
this._el('div', { className: 'conf-btns' }, [btnCancel, btnOk])
]);
this._renderContent(layoutNode);
let timeLeft = timeout;
const handleResult = (type) => {
clearInterval(this.itv);
this.resolveActive = null;
let isSuccess = false;
let resultMsg = "";
if (type === 'ok') { resultMsg = msgOk; isSuccess = true; }
else if (type === 'cancel') { resultMsg = msgCancel; isSuccess = false; }
else if (type === 'timeout') { resultMsg = msgTimeout; isSuccess = true; }
// 将原状态直接传递给 message 方法
this.message({ text: resultMsg, type: type, duration: 1500, timeout: 0 })
.then(() => resolve(isSuccess));
};
this.itv = setInterval(() => {
timeLeft--;
if (timeLeft <= 0) {
handleResult('timeout');
} else {
timerTextNode.nodeValue = `${timeLeft}s`;
}
}, 1000);
btnOk.onclick = () => handleResult('ok');
btnCancel.onclick = () => handleResult('cancel');
});
}
}
// 导出单例,确保全局共用同一个弹窗实例
const notify = new NoticeManager();
const connector = new Connector();
document.addEventListener("DOMContentLoaded", async () => {
// 假设 connector 对象存在
if (!(await connector.acquireLock())) {
const result = await notify.confirm({
text: `Connector: 是否连接到 ${connector.config.name}`,
okBtnText: "连接",
cancelBtnText: "暂不",
msgOk: "连接中...",
msgCancel: "未连接",
msgTimeout: "自动连接中...",
timeout: 5
});
if (result) {
console.log("执行联动操作...");
connector.forceAcquireLock();
connector.isRunning = true;
await connector.handshake();
}
}
})
})();