当前位置: 首页 > news >正文

Electron-vite【实战】MD 编辑器 -- 系统菜单(含菜单封装,新建文件,打开文件,打开文件夹,保存文件,退出系统)

最终效果

在这里插入图片描述

整体架构

src/main/index.ts

import { createMenu } from './menu'

在 const mainWindow 后

  // 加载菜单createMenu(mainWindow)

src/main/menu.ts

import { BrowserWindow, Menu, MenuItem, MenuItemConstructorOptions, dialog, shell } from 'electron'
import fs from 'fs/promises'
import path from 'path'
import { FileItem } from '../types'
// 系统菜单
const createMenu = (mainWindow: BrowserWindow): void => {const menuTemplate: (MenuItemConstructorOptions | MenuItem)[] = [{label: '文件',submenu: []}]const menu: Menu = Menu.buildFromTemplate(menuTemplate)Menu.setApplicationMenu(menu)
}

submenu 内添加自定义的菜单

src/types.ts

export interface FileItem {content: stringfileName: stringfilePath: stringeditable?: boolean
}

新建文件

在这里插入图片描述

src/main/menu.ts

        {label: '新建',accelerator: 'CmdOrCtrl+N',click: async () => {const { canceled, filePath } = await dialog.showSaveDialog({filters: [{name: 'Markdown Files',extensions: ['md']}]})if (!canceled) {try {await fs.writeFile(filePath, '')mainWindow.webContents.send('open-file', {content: '',filePath: filePath,fileName: path.basename(filePath)})} catch (error) {console.error('创建文件时出错:', error)}}}},

src/renderer/src/App.vue

  window.electron.ipcRenderer.on('open-file', (_, { content, fileName, filePath }) => {markdownContent.value = contentcurrentFilePath.value = filePathif (!isFileExists(filePath)) {fileList.value.unshift({content,fileName,filePath})}})

打开文件

在这里插入图片描述

src/main/menu.ts

        {label: '打开文件',accelerator: 'CmdOrCtrl+O',click: async () => {const { canceled, filePaths } = await dialog.showOpenDialog(mainWindow, {filters: [{ name: 'Markdown Files', extensions: ['md', 'markdown'] }],properties: ['openFile']})if (!canceled) {const content = await fs.readFile(filePaths[0], 'utf-8')mainWindow.webContents.send('open-file', {content,filePath: filePaths[0],fileName: path.basename(filePaths[0])})}return null}},

src/renderer/src/App.vue

  window.electron.ipcRenderer.on('open-file', (_, { content, fileName, filePath }) => {markdownContent.value = contentcurrentFilePath.value = filePathif (!isFileExists(filePath)) {fileList.value.unshift({content,fileName,filePath})}})

打开文件夹

src/main/menu.ts

        {label: '打开文件夹',accelerator: 'CmdOrCtrl+K',click: async () => {const { canceled, filePaths } = await dialog.showOpenDialog(mainWindow, {properties: ['openDirectory']})if (!canceled) {const folderPath = filePaths[0]try {const files = await fs.readdir(folderPath)const mdFiles = files.filter((file) =>['.md', '.markdown'].includes(path.extname(file)))const fileList: FileItem[] = []for (const mdFile of mdFiles) {const filePath = path.join(folderPath, mdFile)const content = await fs.readFile(filePath, 'utf-8')fileList.push({content,filePath,fileName: mdFile})}mainWindow.webContents.send('open-dir', fileList)mainWindow.webContents.send('open-file', fileList[0])} catch (error) {console.error('读取文件夹失败:', error)}}return null}},

src/renderer/src/App.vue

  window.electron.ipcRenderer.on('open-dir', (_, newFileList) => {// 使用 splice 方法更新数组fileList.value.splice(0, fileList.value.length, ...newFileList)})
  window.electron.ipcRenderer.on('open-file', (_, { content, fileName, filePath }) => {markdownContent.value = contentcurrentFilePath.value = filePathif (!isFileExists(filePath)) {fileList.value.unshift({content,fileName,filePath})}})

保存

src/main/menu.ts

        {label: '保存',accelerator: 'CmdOrCtrl+S',click: () => {mainWindow.webContents.send('save-file')}},

src/renderer/src/App.vue

  window.electron.ipcRenderer.on('save-file', () => {const content = markdownContent.valueif (currentFilePath.value) {// 存在文件路径时,保存文件const filePath = currentFilePath.value// 更新文件列表内容fileList.value.forEach((file) => {if (file.filePath === filePath) {file.content = content}})window.electron.ipcRenderer.send('save-file', { content, filePath })} else {// 无文件路径时,新建文件window.electron.ipcRenderer.send('new-file', content)}})

src/main/ipc.ts

  // 处理新建文件请求ipcMain.on('new-file', async (_e, content) => {const { canceled, filePath } = await dialog.showSaveDialog({filters: [{name: 'Markdown Files',extensions: ['md']}]})if (!canceled) {try {await fs.writeFile(filePath, content)mainWindow.webContents.send('open-file', {content: content,filePath: filePath,fileName: path.basename(filePath)})} catch (error) {console.error('创建文件时出错:', error)}}})// 处理保存文件请求ipcMain.on('save-file', async (_e, data) => {try {await fs.writeFile(data.filePath, data.content, 'utf-8')} catch (error) {console.error('保存文件失败:', error)}})

ipc.ts 的架构

src/main/index.ts

import { setupIPC } from './ipc'
setupIPC(mainWindow)

src/main/ipc.ts

import { ipcMain, BrowserWindow, shell, dialog } from 'electron'
import fs from 'fs/promises'
import path from 'path'
import { createContextMenu } from './menu'
export function setupIPC(mainWindow: BrowserWindow): void {// IPC相关代码
}

退出

src/main/menu.ts

        {label: '退出',role: 'quit'}

相关文章:

  • 软件测评中心如何确保软件品质?需求分析与测试计划很关键
  • Linux研学-环境搭建
  • .NET 查找 DLL 的路径顺序
  • Netty 实战篇:为 Netty RPC 框架引入调用链追踪,实现链路透明化
  • 基于原生JavaScript前端和 Flask 后端的Todo 应用
  • YOLOv8目标检测实战-(TensorRT原生API搭建网络和使用Parser搭建网络)
  • DeepSeek-R1-0528-Qwen3-8B 本地ollama离线运行使用和llamafactory lora微调
  • 在 ODROID-H3+ 上安装 Win11 系统
  • NHANES指标推荐:CQI
  • 数据共享交换平台之文件交换
  • 历年上海交通大学计算机保研上机真题
  • 通过实时动作捕捉加速人形机器人训练
  • 避免空值判断
  • 使用Haproxy搭建web群集
  • JavaScript正则表达式
  • CppCon 2014 学习第5天:Where did my performance go
  • 睿抗机器人开发者大赛CAIP-编程技能赛-历年真题 解题报告汇总 | 珂学家
  • shell编程笔记
  • 放假带出门的充电宝买哪种好用耐用?倍思超能充35W了解一下!
  • [NOIP 2001 普及组] 求先序排列 Java
  • 建设银行个人网站个人客户/湖南株洲疫情最新情况
  • 网站建设杭州哪家便宜/百度推广有效果吗
  • 17岁日本免费完整版观看/厦门seo公司到1火星
  • 山东最新资讯/seo专业推广
  • wordpress微信域名回调/百度关键词搜索引擎排名优化
  • wordpress 图片加水印/seo网络推广方法