Appearance
自定义协议加载本地文件
以下两种方法都可行的(自行选择一种就行)
在 electron 中是有一个安全机制的,他不能直接访问电脑本地的文件,但是可以使用自定义协议来加载,使用 local:// 前缀(这个前缀可以自定义)
前提:如果加载很大的文件就要小心了,因为自定义协议的一次性读取可能压力很大。可以考虑在协议处理中实现流式传输,不过会比较复杂。
自定义协议
主要使用protocol模块,在主进程文件main/index.js文件的app.whenReady()中注册处理函数
js
// main.js (主进程文件)
const { app, BrowserWindow, protocol } = require("electron");
const path = require("path");
const fs = require("fs");
// 1. 在应用准备就绪前,声明协议的权限
app.whenReady().then(() => {
protocol.registerFileProtocol("local", (request, callback) => {
try {
// 去除协议头,获取文件路径
let filePath = request.url.replace("local://", "");
// 对路径进行解码和规范化处理
filePath = decodeURIComponent(filePath);
filePath = path.normalize(filePath);
// 安全检查:确保文件存在
if (fs.existsSync(filePath)) {
callback(filePath);
} else {
callback({ error: -6 }); // FILE_NOT_FOUND
}
} catch (error) {
console.error("Protocol error:", error);
callback({ error: -2 }); // FAILED
}
});
// 创建窗口等后续操作
createWindow();
});// main.js (主进程文件)
const { app, BrowserWindow, protocol } = require("electron");
const path = require("path");
const fs = require("fs");
// 1. 在应用准备就绪前,声明协议的权限
app.whenReady().then(() => {
protocol.registerFileProtocol("local", (request, callback) => {
try {
// 去除协议头,获取文件路径
let filePath = request.url.replace("local://", "");
// 对路径进行解码和规范化处理
filePath = decodeURIComponent(filePath);
filePath = path.normalize(filePath);
// 安全检查:确保文件存在
if (fs.existsSync(filePath)) {
callback(filePath);
} else {
callback({ error: -6 }); // FILE_NOT_FOUND
}
} catch (error) {
console.error("Protocol error:", error);
callback({ error: -2 }); // FAILED
}
});
// 创建窗口等后续操作
createWindow();
});vue 组件中注册自定义协议
vue
<script setup>
const imageURL = "local://C:/Users/luoluo/Desktop/640.png"; //window电脑路径
//如果是mac电脑路径:local:///Users/luoluo/Desktop/640.png
</script>
<template>
<img alt="logo" class="logo" :src="imageURL" />
</template><script setup>
const imageURL = "local://C:/Users/luoluo/Desktop/640.png"; //window电脑路径
//如果是mac电脑路径:local:///Users/luoluo/Desktop/640.png
</script>
<template>
<img alt="logo" class="logo" :src="imageURL" />
</template>index.html文件的内容安全策略也要加上自定义协议local:;
html
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; img-src 'self' data: local:;"
/><meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; img-src 'self' data: local:;"
/>第二种方案:handle(),这个 registerFileProtocol(已废弃)
这个 handle()方法很坑,我找了全网,没一个是对的,其实很多情况都是在net.fetch()这个有问题,
当 net.fetch 尝试访问一个 file:// URL 但因权限问题而无法访问时,就经常会报这个 400 (Bad Request) 错误。
图片展示问题:每次都是这样的问题(有时候根本不知道哪里的权限问题,无从下手)
.png)
protocol.registerSchemesAsPrivileged([]),垃圾玩意不行的,测试过无数次,但是没用
正确的方法
js
// main.js (主进程文件)
const { app, BrowserWindow, protocol, net } = require("electron");
const path = require("path");
const fs = require("fs");
const url = require("url");
// 1. 在应用准备就绪前,声明协议的权限
app.whenReady().then(() => {
protocol.handle("local", (request) => {
const filePath = decodeURI(request.url.slice("local://".length));
const absolutePath = path.isAbsolute(filePath)
? filePath
: path.join(__dirname, filePath);
return net.fetch(url.pathToFileURL(absolutePath).toString());
});
// 创建窗口等后续操作
createWindow();
});// main.js (主进程文件)
const { app, BrowserWindow, protocol, net } = require("electron");
const path = require("path");
const fs = require("fs");
const url = require("url");
// 1. 在应用准备就绪前,声明协议的权限
app.whenReady().then(() => {
protocol.handle("local", (request) => {
const filePath = decodeURI(request.url.slice("local://".length));
const absolutePath = path.isAbsolute(filePath)
? filePath
: path.join(__dirname, filePath);
return net.fetch(url.pathToFileURL(absolutePath).toString());
});
// 创建窗口等后续操作
createWindow();
});文件选择器加载
结合主进程、渲染进程和 Vue 组件,动态加载用户选择的图片。
js
// main.js (主进程文件)
const { app, BrowserWindow, protocol, ipcMain } = require('electron')
const path = require('path')
const fs = require('fs')
// 1. 在应用准备就绪前,声明协议的权限
app.whenReady().then(() => {
protocol.registerFileProtocol('local', (request, callback) => {
try {
// 去除协议头,获取文件路径
let filePath = request.url.replace('local://', '')
// 对路径进行解码和规范化处理
filePath = decodeURIComponent(filePath)
filePath = path.normalize(filePath)
// 安全检查:确保文件存在
if (fs.existsSync(filePath)) {
callback(filePath)
} else {
callback({ error: -6 }) // FILE_NOT_FOUND
}
} catch (error) {
console.error('Protocol error:', error)
callback({ error: -2 }) // FAILED
}
})
ipcMain.handle('select-image', async () => {
return 'C:/Users/luoluo/Desktop/640.png' // 返回选择的图片路径
})
// 创建窗口等后续操作
createWindow()
})// main.js (主进程文件)
const { app, BrowserWindow, protocol, ipcMain } = require('electron')
const path = require('path')
const fs = require('fs')
// 1. 在应用准备就绪前,声明协议的权限
app.whenReady().then(() => {
protocol.registerFileProtocol('local', (request, callback) => {
try {
// 去除协议头,获取文件路径
let filePath = request.url.replace('local://', '')
// 对路径进行解码和规范化处理
filePath = decodeURIComponent(filePath)
filePath = path.normalize(filePath)
// 安全检查:确保文件存在
if (fs.existsSync(filePath)) {
callback(filePath)
} else {
callback({ error: -6 }) // FILE_NOT_FOUND
}
} catch (error) {
console.error('Protocol error:', error)
callback({ error: -2 }) // FAILED
}
})
ipcMain.handle('select-image', async () => {
return 'C:/Users/luoluo/Desktop/640.png' // 返回选择的图片路径
})
// 创建窗口等后续操作
createWindow()
})预加载脚本中暴露 IPC 通信方法
预加载脚本 (preload.js) 中,暴露一个安全的 selectImage 方法给渲染进程。
js
// 在预加载脚本中 (preload.js)
const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld("electronAPI", {
selectImage: () => ipcRenderer.invoke("select-image"),
});// 在预加载脚本中 (preload.js)
const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld("electronAPI", {
selectImage: () => ipcRenderer.invoke("select-image"),
});vue 组件
vue
<script setup>
import { ref } from "vue";
const image = ref("");
const imageShow = ref(false);
const loadImage = async () => {
imageShow.value = true;
const imagePath = await window.electronAPI.selectImage();
image.value = `local://${imagePath}`;
};
</script>
<template>
<button @click="loadImage">加载图片</button>
<img :src="image" v-if="imageShow" alt="选择的图片" />
</template><script setup>
import { ref } from "vue";
const image = ref("");
const imageShow = ref(false);
const loadImage = async () => {
imageShow.value = true;
const imagePath = await window.electronAPI.selectImage();
image.value = `local://${imagePath}`;
};
</script>
<template>
<button @click="loadImage">加载图片</button>
<img :src="image" v-if="imageShow" alt="选择的图片" />
</template>
小洛的前端技术博客