Skip to content

自定义协议加载本地文件

以下两种方法都可行的(自行选择一种就行)

在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:;">

文件选择器加载

结合主进程、渲染进程和 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>

程序员小洛文档