const { ipcRenderer } = require("electron"); const os = require("os"); const { createBrowserWindow } = utools; /** * 创建对话框窗口 * @param {object} config - 对话框配置 * @param {object} [customDialogOptions] - 自定义窗口选项 * @returns {Promise} 返回对话框结果 */ const createDialog = (config, customDialogOptions = {}) => { return new Promise((resolve) => { const dialogPath = "lib/dialog/view.html"; const preloadPath = "lib/dialog/controller.js"; const platform = os.platform(); const dialogWidth = platform === "win32" && config.type !== "textarea" ? 370 : 470; const dialogOptions = { title: config.title || "对话框", width: dialogWidth, height: 80, resizable: false, minimizable: false, maximizable: false, fullscreenable: false, skipTaskbar: true, alwaysOnTop: true, frame: false, opacity: 0, webPreferences: { preload: preloadPath, devTools: utools.isDev(), }, ...customDialogOptions, // 合并自定义选项 }; // 创建窗口 const UBrowser = createBrowserWindow(dialogPath, dialogOptions, () => { const dialogResultHandler = (event, result) => { resolve(result); // 移除监听器 ipcRenderer.removeListener("dialog-result", dialogResultHandler); UBrowser.destroy(); }; const dialogReadyHandler = (event, height) => { // 获取当前窗口位置 const bounds = UBrowser.getBounds(); // 调整y坐标,保持窗口中心点不变 const y = Math.round(bounds.y - (height - bounds.height) / 2); // 确保坐标和尺寸都是有效的整数 const newBounds = { x: Math.round(bounds.x), y: Math.max(0, y), // 确保不会超出屏幕顶部 width: dialogWidth, height: Math.round(height), }; // 设置新的位置和大小 UBrowser.setBounds(newBounds); UBrowser.setOpacity(1); ipcRenderer.removeListener("dialog-ready", dialogReadyHandler); }; // 监听子窗口返回的计算高度, 等待按钮有自己的计算逻辑 config.type !== "wait-button" && ipcRenderer.on("dialog-ready", dialogReadyHandler); // 监听子窗口返回的返回值 ipcRenderer.on("dialog-result", dialogResultHandler); // 发送配置到子窗口 ipcRenderer.sendTo(UBrowser.webContents.id, "dialog-config", { ...config, isDark: utools.isDarkColors(), platform, }); }); }); }; /** * 显示一个系统级消息框 * @param {string} content - 消息内容 * @param {string} [title] - 标题,默认为空 * @returns {Promise} Promise */ const showSystemMessageBox = async (content, title = "") => { await createDialog({ type: "message", title, content, }); }; /** * 显示一个系统级输入框组对话框 * @param {string[]|{label:string,value:string,hint:string}[]} options - 输入框配置,可以是标签数组或者带属性的对象数组 * @param {string} [title] - 标题,默认为空 * @returns {Promise} 输入的内容数组 */ const showSystemInputBox = async (options, title = "") => { // 确保 options 是数组 const optionsArray = Array.isArray(options) ? options : [options]; // 转换每个选项为正确的格式 const inputOptions = optionsArray.map((opt) => { if (typeof opt === "string") { return opt; } if (typeof opt === "object" && opt.label) { return { label: opt.label, value: opt.value || "", hint: opt.hint || "", }; } throw new Error("输入框配置格式错误"); }); return await createDialog({ type: "input", title, inputOptions, }); }; /** * 显示一个系统级确认框,返回是否点击了确认 * @param {string} content - 确认内容 * @param {string} [title] - 标题,默认为空 * @returns {Promise} 是否确认 */ const showSystemConfirmBox = async (content, title = "") => { const result = await createDialog({ type: "confirm", title, content, }); return !!result; }; /** * 显示一个系统级按钮组对话框,返回点击的按钮的索引和文本 * @param {string[]} buttons - 按钮文本数组 * @param {string} [title] - 标题,默认为空 * @returns {Promise<{id: number, text: string}>} 选择的按钮信息 */ const showSystemButtonBox = async (buttons, title = "") => { return await createDialog({ type: "buttons", title, buttons: Array.isArray(buttons) ? buttons : [buttons], }); }; /** * 显示一个系统级多行文本输入框 * @param {string} [placeholder] - 输入框的提示文本 * @param {string} [defaultText] - 输入框的默认文本,默认为空 * @returns {Promise} 编辑后的文本 */ const showSystemTextArea = async (placeholder = "请输入", defaultText = "") => { return await createDialog({ type: "textarea", placeholder, defaultText, }); }; /** * 显示一个系统级选择列表对话框 * @param {Array} items - 选项列表 * @param {object} [options] - 配置选项 * @param {string} [options.title] - 对话框标题 * @param {string} [options.placeholder] - 输入框占位符 * @param {boolean} [options.enableSearch] - 是否启用搜索 * @returns {Promise<{id: number, text: string, data: any}>} 选择的结果 */ const showSystemSelectList = async (items, options = {}) => { const { title = "请选择", placeholder = "", enableSearch = true, optionType = "plaintext", } = options; return await createDialog({ type: "select", title, placeholder, enableSearch, items, optionType, }); }; /** * 显示一个系统级等待按钮 * @param {object} options - 配置选项 * @param {string} [options.text="等待操作"] - 按钮文本 * @param {string} [options.position="bottom-right"] - 按钮位置,可选值:top-left, top-right, bottom-left, bottom-right * @param {boolean} [options.showCancel=false] - 是否显示取消按钮 * @returns {Promise} 点击确定返回true,点击取消返回false */ const showSystemWaitButton = async (options = {}) => { const { text = "等待操作", position = "bottom-right", showCancel = true, } = options; // 创建临时span计算文本宽度 const span = document.createElement("span"); span.style.font = '14px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'; span.style.visibility = "hidden"; span.style.position = "absolute"; span.textContent = text; document.body.appendChild(span); // 计算窗口尺寸 const textWidth = span.offsetWidth; document.body.removeChild(span); const dialogOptions = { width: Math.max(textWidth + 32 + (showCancel ? 25 : 0), 80), // 文本宽度 + padding + 取消按钮宽度(如果有) height: 36, opacity: 1, }; // 获取主屏幕尺寸 const primaryDisplay = utools.getPrimaryDisplay(); const { width, height } = primaryDisplay.workAreaSize; // 根据position计算窗口位置 const padding = 20; let x, y; switch (position) { case "top-left": x = padding; y = padding; break; case "top-right": x = width - dialogOptions.width - padding; y = padding; break; case "bottom-left": x = padding; y = height - dialogOptions.height - padding; break; case "bottom-right": default: x = width - dialogOptions.width - padding; y = height - dialogOptions.height - padding; break; } dialogOptions.x = x; dialogOptions.y = y; return await createDialog( { type: "wait-button", text, showCancel, }, dialogOptions ); }; module.exports = { showSystemMessageBox, showSystemInputBox, showSystemConfirmBox, showSystemButtonBox, showSystemTextArea, showSystemSelectList, showSystemWaitButton, };