Electron for 鸿蒙PC - 图像插入与HTML导出功能适配完整方案
本文记录了将Abricotine适配到鸿蒙PC平台时遇到的图像插入和HTML导出功能问题及解决方案。主要挑战包括:Electron渲染进程不支持prompt()函数导致无法通过URL插入图像,以及HTML导出功能的路径格式和权限问题。文章详细介绍了两种图像插入方式(本地文件和URL)的实现方法,重点阐述了通过纯前端模态对话框替代prompt()的技术方案,包括对话框的创建、样式设置和交互逻辑。同时
前言
在将 Abricotine 适配到鸿蒙 PC 平台时,图像插入和 HTML 导出功能遇到了多个适配问题:Electron 渲染进程不支持 prompt() 函数,导致无法通过 URL 插入图像;HTML 导出功能需要处理路径格式、权限问题等。本文将详细记录我们如何通过纯前端模态对话框实现图像 URL 输入,以及如何完善 HTML 导出功能的适配。
关键词:鸿蒙PC、Electron适配、图像插入、HTML导出、纯前端对话框、prompt替代方案
目录
问题现象与分析
1.1 问题现象
问题1:无法通过 URL 插入图像
在鸿蒙 PC 上,当用户尝试通过 URL 插入图像时:
用户操作:点击"插入图像" → 选择"通过URL"
预期:弹出输入框让用户输入图像URL
实际:❌ prompt() 函数不存在,报错或无法使用
错误信息:
ReferenceError: prompt is not defined
问题2:HTML 导出功能不完善
HTML 导出功能在鸿蒙 PC 上遇到:
- 路径格式处理错误
- 权限问题导致文件夹创建失败
- 图片复制失败
1.2 根本原因
Electron 渲染进程限制:
- ❌ 不支持
prompt()函数(浏览器 API 限制) - ❌ 不支持
alert()和confirm()(部分平台) - ✅ 需要使用纯前端方案替代
鸿蒙 PC 特殊限制:
- 文件系统权限限制
- 路径格式特殊(
file://docs/格式) - 文件夹创建权限问题
图像插入的两种实现方式
2.1 方式一:从电脑选择文件插入
实现函数:imageFromComputer
代码位置:commands.js 第418-420行
// commands.js
imageFromComputer: function(win, abrDoc, cm) {
abrDoc.insertImage();
}
功能说明:
- ✅ 调用
abrDoc.insertImage()函数 - ✅ 打开文件选择对话框
- ✅ 用户选择本地图像文件
- ✅ 自动插入到编辑器中
使用场景:
- 插入本地存储的图像文件
- 从电脑选择图片插入到文档
工作流程:
用户点击"从电脑插入图像"
↓
abrDoc.insertImage() 被调用
↓
打开文件选择对话框
↓
用户选择图像文件
↓
图像插入到编辑器
2.2 方式二:通过 URL 插入图像
实现函数:format("image", url)
代码位置:commands.js 第297-318行
// commands.js
format: function (win, abrDoc, cm, param) {
// ⚠️ HarmonyOS: 当插入图像时,如果是 "image" 参数且没有提供 URL,弹出对话框让用户输入 URL
// Electron 渲染进程不支持 prompt(),使用纯前端模态对话框
if (param === "image") {
var promptTitle = abrDoc.localizer.get("insert-image") || "插入图像";
var that = this;
// 使用异步方式显示输入对话框
this._showInputDialogAsync(promptTitle, "http://", function(imageUrl) {
if (imageUrl && imageUrl.trim() !== "" && imageUrl.trim() !== "http://") {
// 用户输入了有效的 URL,使用该 URL 插入图像
cm.format("image", imageUrl.trim());
}
// 如果用户取消或输入为空,不执行任何操作
});
return;
}
if (typeof param !== "undefined") {
cm.format(param);
}
}
功能说明:
- ✅ 检测到
param === "image"时,显示输入对话框 - ✅ 用户输入图像 URL
- ✅ 验证 URL 有效性
- ✅ 调用
cm.format("image", url)插入图像
使用场景:
- 插入网络上的图像(通过 URL)
- 插入远程服务器上的图像
- 插入在线图片链接
工作流程:
用户点击"通过URL插入图像"
↓
format("image") 被调用
↓
显示纯前端输入对话框
↓
用户输入图像URL
↓
验证URL有效性
↓
cm.format("image", url) 插入图像
纯前端模态对话框实现
3.1 为什么需要纯前端对话框
Electron 渲染进程限制:
- ❌
prompt()函数不存在 - ❌
alert()和confirm()在某些平台不可用 - ✅ 必须使用纯 HTML/CSS/JavaScript 实现
方案优势:
- ✅ 完全可控的 UI 样式
- ✅ 支持自定义验证逻辑
- ✅ 跨平台兼容性好
- ✅ 用户体验更好
3.2 完整实现代码
_showInputDialogAsync 函数:commands.js 第321-414行
// commands.js
// ⚠️ HarmonyOS: 纯前端异步输入对话框实现(替代 prompt)
_showInputDialogAsync: function (title, defaultValue, callback) {
defaultValue = defaultValue || "";
callback = callback || function() {};
// 1. 创建模态框背景(遮罩层)
var overlay = document.createElement("div");
overlay.style.cssText = "position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 10000; display: flex; align-items: center; justify-content: center;";
// 2. 创建对话框容器
var dialog = document.createElement("div");
dialog.style.cssText = "background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); min-width: 400px; max-width: 90%;";
// 3. 创建标题
var titleEl = document.createElement("div");
titleEl.textContent = title;
titleEl.style.cssText = "font-size: 16px; font-weight: bold; margin-bottom: 15px; color: #333; white-space: pre-line;";
// 4. 创建输入框
var input = document.createElement("input");
input.type = "text";
input.value = defaultValue;
input.style.cssText = "width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; box-sizing: border-box; margin-bottom: 15px;";
// ⚠️ HarmonyOS: 根据标题设置不同的占位符
var placeholder = "请输入";
if (title.indexOf("文件名模板") !== -1 || title.indexOf("文件名") !== -1) {
placeholder = "例如:image_1.jpg 或 photo_copy.jpg";
} else if (title.indexOf("URL") !== -1 || title.indexOf("url") !== -1 || title.indexOf("图像") !== -1) {
placeholder = "请输入图片 URL";
}
input.placeholder = placeholder;
// 5. 创建按钮容器
var buttonContainer = document.createElement("div");
buttonContainer.style.cssText = "display: flex; justify-content: flex-end; gap: 10px;";
// 6. 创建取消按钮
var cancelBtn = document.createElement("button");
cancelBtn.textContent = "取消";
cancelBtn.style.cssText = "padding: 8px 16px; border: 1px solid #ccc; background: white; border-radius: 4px; cursor: pointer; font-size: 14px;";
// 7. 创建确定按钮
var okBtn = document.createElement("button");
okBtn.textContent = "确定";
okBtn.style.cssText = "padding: 8px 16px; border: none; background: #007AFF; color: white; border-radius: 4px; cursor: pointer; font-size: 14px;";
// 8. 组装对话框
dialog.appendChild(titleEl);
dialog.appendChild(input);
buttonContainer.appendChild(cancelBtn);
buttonContainer.appendChild(okBtn);
dialog.appendChild(buttonContainer);
overlay.appendChild(dialog);
document.body.appendChild(overlay);
// 9. 聚焦输入框
input.focus();
input.select();
// 10. 清理函数
var cleanup = function() {
if (overlay.parentNode) {
document.body.removeChild(overlay);
}
};
// 11. 确认函数
var confirm = function() {
var value = input.value;
cleanup();
callback(value);
};
// 12. 取消函数
var cancel = function() {
cleanup();
callback(null);
};
// 13. 绑定事件
okBtn.onclick = confirm;
cancelBtn.onclick = cancel;
overlay.onclick = function(e) {
if (e.target === overlay) {
cancel(); // 点击背景关闭对话框
}
};
// 14. 键盘事件处理
input.onkeydown = function(e) {
if (e.key === "Enter") {
e.preventDefault();
confirm();
} else if (e.key === "Escape") {
e.preventDefault();
cancel();
}
};
}
3.3 对话框特性
UI 特性:
- ✅ 模态背景:半透明黑色遮罩层,阻止背景交互
- ✅ 居中显示:对话框在屏幕中央显示
- ✅ 响应式设计:最小宽度 400px,最大宽度 90%
- ✅ 现代化样式:圆角、阴影、渐变背景
交互特性:
- ✅ 自动聚焦:对话框打开时自动聚焦输入框
- ✅ 自动选中:自动选中默认值,方便用户修改
- ✅ 键盘支持:Enter 确认,Escape 取消
- ✅ 点击背景关闭:点击遮罩层关闭对话框
智能占位符:
- ✅ 根据标题自动设置占位符
- ✅ 文件名模板:显示示例格式
- ✅ URL 输入:提示输入图片 URL
HTML导出功能适配
4.1 导出功能调用
实现函数:exportHtml
代码位置:commands.js 第102-128行
// commands.js
exportHtml: function(win, abrDoc, cm, param) {
console.log('[HarmonyOS Renderer] commands.exportHtml called');
console.log('[HarmonyOS Renderer] param:', param);
if (!abrDoc) {
console.error('[HarmonyOS Renderer] ❌ abrDoc is null or undefined');
return;
}
if (typeof abrDoc.exportHtml !== 'function') {
console.error('[HarmonyOS Renderer] ❌ abrDoc.exportHtml is not a function');
return;
}
try {
console.log('[HarmonyOS Renderer] Calling abrDoc.exportHtml with param:', param);
abrDoc.exportHtml(param);
console.log('[HarmonyOS Renderer] abrDoc.exportHtml called successfully');
} catch (error) {
console.error('[HarmonyOS Renderer] ❌ Error calling abrDoc.exportHtml:', error);
console.error('[HarmonyOS Renderer] Error stack:', error.stack);
}
}
功能说明:
- ✅ 调用
abrDoc.exportHtml()函数 - ✅ 传递参数(模板名称、目标路径、选项等)
- ✅ 完善的错误处理和日志
4.2 导出功能核心实现
核心函数:exportHtml(在 export-html.js 中)
关键适配点:
- 路径处理优化:
// export-html.js 第74-83行
// ⚠️ HarmonyOS: 确保 destPath 是绝对路径
var destPathAbs = parsePath(destPath).isAbsolute ? destPath : pathModule.resolve(destPath);
var destDir = parsePath(destPathAbs).dirname;
var destBasename = parsePath(destPathAbs).basename;
// 移除扩展名,创建文件夹名
var folderBasename = destBasename.replace(/\.[^/.]+$/, "");
var exportFolder = pathModule.join(destDir, folderBasename + "_files");
var assetsPath = "./" + folderBasename + "_files";
// 更新 destPath 为绝对路径
destPath = destPathAbs;
- 权限检查和降级策略:
// export-html.js 第121-133行
// ⚠️ HarmonyOS: 尝试创建文件夹,如果失败则跳过图片复制
var createdExportFolder = files.createDir(exportFolder);
var createdImgDir = files.createDir(imgDirAbs);
// ⚠️ HarmonyOS: 如果文件夹创建失败(权限问题),跳过图片复制和路径更新
if (!createdExportFolder || !createdImgDir) {
console.warn('[HarmonyOS Export] ⚠️ Failed to create directories (permission denied), skipping image copy');
console.warn('[HarmonyOS Export] Images will not be copied, keeping original image URLs in HTML');
// ⚠️ 关键:如果无法创建文件夹,保持图片的原始路径(URL),不更新为相对路径
// 这样浏览器仍然可以从原始 URL 加载图片
} else {
// 正常复制图片...
}
- 图片复制策略:
// export-html.js 第147-172行
// 复制图片到导出文件夹
abrDoc.imageImport(imgDirAbs, {
copyRemote: options.copyImagesRemote !== false,
updateEditor: false,
showDialog: false
});
// 更新 HTML 中的图片路径
var re = /(<img[^>]+src=['"])([^">]+)(['"])/gmi;
htmlContent = htmlContent.replace(re, function (str, p1, p2, p3) {
if (options.copyImagesRemote === false && isUrl(p2)) {
return str; // 不复制远程图片
}
var basename = parsePath(p2).basename;
var newPath = pathModule.join(assetsPath, "images", basename).replace(/\\/g, '/');
return p1 + newPath + p3;
});
4.3 导出选项处理
toggleCopyImagesOnHtmlExport 函数:切换导出时是否复制图片
代码位置:commands.js 第130-155行
// commands.js
toggleCopyImagesOnHtmlExport: function(win, abrDoc, cm) {
console.log('[HarmonyOS Commands] toggleCopyImagesOnHtmlExport called');
// ⚠️ HarmonyOS: 修复逻辑反了的问题
// 当用户点击 checkbox 时,Electron 会自动切换菜单项的 checked 状态
// 我们需要读取当前的配置值,然后设置为相反的值
abrDoc.getConfig("copy-images-on-html-export", function(value) {
var currentValue = value !== false; // 默认值为 true
var newValue = !currentValue;
console.log('[HarmonyOS Commands] Current copy-images-on-html-export value:', currentValue);
console.log('[HarmonyOS Commands] New value:', newValue);
// 设置新值
// menu 参数应该是菜单项的 id,即 "menu-copy-images-on-html-export"
abrDoc.setConfig("copy-images-on-html-export", newValue, null, "menu-copy-images-on-html-export");
});
}
最佳实践与注意事项
5.1 纯前端对话框最佳实践
推荐做法:
// ✅ 好:使用异步回调模式
_showInputDialogAsync(title, defaultValue, function(value) {
if (value) {
// 处理用户输入
}
});
// ❌ 不好:使用同步 prompt(不可用)
var value = prompt(title, defaultValue); // 在 Electron 渲染进程中不可用
注意事项:
- ✅ 使用异步回调模式,不阻塞 UI
- ✅ 提供清理函数,确保对话框正确移除
- ✅ 支持键盘快捷键(Enter/Escape)
- ✅ 点击背景关闭对话框
5.2 图像插入最佳实践
推荐做法:
// ✅ 好:两种方式都支持
// 方式1:从电脑选择
imageFromComputer: function(win, abrDoc, cm) {
abrDoc.insertImage();
}
// 方式2:通过URL
format: function(win, abrDoc, cm, param) {
if (param === "image") {
// 显示输入对话框
this._showInputDialogAsync(title, "http://", function(url) {
if (url) {
cm.format("image", url);
}
});
}
}
注意事项:
- ✅ 验证 URL 有效性
- ✅ 处理用户取消的情况
- ✅ 提供清晰的提示信息
5.3 HTML导出最佳实践
推荐做法:
// ✅ 好:完善的错误处理和降级策略
if (!createdExportFolder || !createdImgDir) {
// 降级:不复制图片,保持原始URL
console.warn('Permission denied, skipping image copy');
} else {
// 正常处理:复制图片
}
// ❌ 不好:直接失败,不处理
files.createDir(exportFolder); // 可能抛出异常
注意事项:
- ✅ 检查权限后再操作
- ✅ 失败时使用降级策略
- ✅ 保持功能可用性(即使权限不足)
常见问题解答
Q1: 为什么不能使用 prompt()?
A: Electron 渲染进程不支持浏览器的一些 API,包括 prompt()、alert() 和 confirm()。必须使用纯前端方案替代。
Q2: 纯前端对话框的性能如何?
A: 性能很好。纯前端对话框使用原生 DOM API,性能开销很小,响应速度快。
Q3: HTML 导出时图片无法显示怎么办?
A: 如果权限不足无法创建文件夹,图片会保持原始 URL。确保网络连接正常,浏览器可以从原始 URL 加载图片。
Q4: 如何自定义对话框样式?
A: 修改 _showInputDialogAsync 函数中的 CSS 样式字符串,可以自定义对话框的外观。
总结与展望
7.1 核心要点总结
通过本文的深入分析,我们了解到:
-
图像插入的两种方式:
- 从电脑选择文件插入
- 通过 URL 插入(使用纯前端对话框)
-
纯前端对话框实现:完全替代
prompt(),提供更好的用户体验 -
HTML 导出功能适配:路径处理、权限检查、降级策略
7.2 技术价值
这个解决方案不仅解决了图像插入和 HTML 导出问题,还带来了以下好处:
- ✅ 更好的用户体验:纯前端对话框比原生 prompt 更美观
- ✅ 跨平台兼容:不依赖平台特定的 API
- ✅ 功能完整性:即使权限不足也能部分工作
7.3 适用场景
这套方案适用于:
- ✅ 所有需要用户输入的 Electron 应用
- ✅ 需要替代 prompt/alert/confirm 的应用
- ✅ 在鸿蒙 PC 上运行的 Electron 应用
- ✅ 需要 HTML 导出功能的应用
相关资源
Electron 官方文档:
MDN 文档:
HarmonyOS 官方文档:
鸿蒙PC开发资源:
更多推荐


所有评论(0)