Electron-vite【實戰】MD 編輯器 -- 文件列表(含右鍵快捷菜單,重命名文件,刪除本地文件,打開本地目錄等)

最終效果

在這里插入圖片描述

頁面

src/renderer/src/App.vue

    <div class="dirPanel"><div class="panelTitle">文件列表</div><div class="searchFileBox"><Icon class="searchFileInputIcon" icon="material-symbols-light:search" /><inputv-model="searchFileKeyWord"class="searchFileInput"type="text"placeholder="請輸入文件名"/><Iconv-show="searchFileKeyWord"class="clearSearchFileInputBtn"icon="codex:cross"@click="clearSearchFileInput"/></div><div class="dirListBox"><divv-for="(item, index) in fileList_filtered":id="`file-${index}`":key="item.filePath"class="dirItem"spellcheck="false":class="currentFilePath === item.filePath ? 'activeDirItem' : ''":contenteditable="item.editable"@click="openFile(item)"@contextmenu.prevent="showContextMenu(item.filePath)"@blur="saveFileName(item, index)"@keydown.enter.prevent="saveFileName_enter(index)">{{ item.fileName.slice(0, -3) }}</div></div></div>

相關樣式

.dirPanel {width: 200px;border: 1px solid gray;
}
.dirListBox {padding: 0px 10px 10px 10px;
}
.dirItem {padding: 6px;font-size: 12px;cursor: pointer;border-radius: 4px;margin-bottom: 6px;
}
.searchFileBox {display: flex;align-items: center;justify-content: center;padding: 10px;
}
.searchFileInput {display: block;font-size: 12px;padding: 4px 20px;
}
.searchFileInputIcon {position: absolute;font-size: 16px;transform: translateX(-80px);
}
.clearSearchFileInputBtn {position: absolute;cursor: pointer;font-size: 16px;transform: translateX(77px);
}
.panelTitle {font-size: 16px;font-weight: bold;text-align: center;background-color: #f0f0f0;height: 34px;line-height: 34px;
}

相關依賴

實現圖標

npm i --save-dev @iconify/vue

導入使用

import { Icon } from '@iconify/vue'

搜索圖標
https://icon-sets.iconify.design/?query=home

常規功能

文件搜索

根據搜索框的值 searchFileKeyWord 的變化動態計算 computed 過濾文件列表 fileList 得到 fileList_filtered ,頁面循環遍歷渲染 fileList_filtered

const fileList = ref<FileItem[]>([])
const searchFileKeyWord = ref('')
const fileList_filtered = computed(() => {return fileList.value.filter((file) => {return file.filePath.toLowerCase().includes(searchFileKeyWord.value.toLowerCase())})
})

文件搜索框的清空按鈕點擊事件

const clearSearchFileInput = (): void => {searchFileKeyWord.value = ''
}

當前文件高亮

const currentFilePath = ref('')
:class="currentFilePath === item.filePath ? 'activeDirItem' : ''"
.activeDirItem {background-color: #e4e4e4;
}

切換打開的文件

點擊文件列表的文件名稱,打開對應的文件

@click="openFile(item)"
const openFile = (item: FileItem): void => {markdownContent.value = item.contentcurrentFilePath.value = item.filePath
}

右鍵快捷菜單

@contextmenu.prevent="showContextMenu(item.filePath)"
const showContextMenu = (filePath: string): void => {window.electron.ipcRenderer.send('showContextMenu', filePath)// 隱藏其他右鍵菜單 -- 不能同時有多個右鍵菜單顯示hide_editor_contextMenu()
}

觸發創建右鍵快捷菜單

src/main/ipc.ts

import { createContextMenu } from './menu'
  ipcMain.on('showContextMenu', (_e, filePath) => {createContextMenu(mainWindow, filePath)})

執行創建右鍵快捷菜單

src/main/menu.ts

const createContextMenu = (mainWindow: BrowserWindow, filePath: string): void => {const template = [{label: '重命名',click: async () => {mainWindow.webContents.send('do-rename-file', filePath)}},{ type: 'separator' }, // 添加分割線{label: '移除',click: async () => {mainWindow.webContents.send('removeOut-fileList', filePath)}},{label: '清空文件列表',click: async () => {mainWindow.webContents.send('clear-fileList')}},{ type: 'separator' }, // 添加分割線{label: '打開所在目錄',click: async () => {// 打開目錄shell.openPath(path.dirname(filePath))}},{ type: 'separator' }, // 添加分割線{label: '刪除',click: async () => {try {// 顯示確認對話框const { response } = await dialog.showMessageBox(mainWindow, {type: 'question',buttons: ['確定', '取消'],title: '確認刪除',message: `確定要刪除文件 ${path.basename(filePath)} 嗎?`})if (response === 0) {// 用戶點擊確定,刪除本地文件await fs.unlink(filePath)// 通知渲染進程文件已刪除mainWindow.webContents.send('delete-file', filePath)}} catch {dialog.showMessageBox(mainWindow, {type: 'error',title: '刪除失敗',message: `刪除文件 ${path.basename(filePath)} 時出錯,請稍后重試。`})}}}]const menu = Menu.buildFromTemplate(template as MenuItemConstructorOptions[])menu.popup({ window: mainWindow })
}
export { createMenu, createContextMenu }

隱藏其他右鍵菜單

// 隱藏編輯器右鍵菜單
const hide_editor_contextMenu = (): void => {if (isMenuVisible.value) {isMenuVisible.value = false}
}

重命名文件

在這里插入圖片描述

實現思路

  1. 點擊右鍵快捷菜單的“重命名”
  2. 將被點擊的文件列表項的 contenteditable 變為 true,使其成為一個可編輯的div
  3. 全選文件列表項內的文本
  4. 輸入新的文件名
  5. 在失去焦點/按Enter鍵時,開始嘗試保存文件名
  6. 若新文件名與舊文件名相同,則直接將被點擊的文件列表項的 contenteditable 變為 false
  7. 若新文件名與本地文件名重復,則彈窗提示該文件名已存在,需換其他文件名
  8. 若新文件名合規,則執行保存文件名
  9. 被點擊的文件列表項的 contenteditable 變為 false

src/renderer/src/App.vue

  window.electron.ipcRenderer.on('do-rename-file', (_, filePath) => {fileList_filtered.value.forEach(async (file, index) => {// 找到要重命名的文件if (file.filePath === filePath) {// 將被點擊的文件列表項的 contenteditable 變為 true,使其成為一個可編輯的divfile.editable = true// 等待 DOM 更新await nextTick()// 全選文件列表項內的文本let divElement = document.getElementById(`file-${index}`)if (divElement) {const range = document.createRange()range.selectNodeContents(divElement) // 選擇 div 內所有內容const selection = window.getSelection()if (selection) {selection.removeAllRanges() // 清除現有選擇selection.addRange(range) // 添加新選擇divElement.focus() // 聚焦到 div}}}})})
          @blur="saveFileName(item, index)"@keydown.enter.prevent="saveFileName_enter(index)"
// 重命名文件時,保存文件名
const saveFileName = async (item: FileItem, index: number): Promise<void> => {// 獲取新的文件名,若新文件名為空,則命名為 '無標題'let newFileName = document.getElementById(`file-${index}`)?.textContent?.trim() || '無標題'// 若新文件名與舊文件名相同,則直接將被點擊的文件列表項的 contenteditable 變為 falseif (newFileName === item.fileName.replace('.md', '')) {item.editable = falsereturn}// 拼接新的文件路徑const newFilePath = item.filePath.replace(item.fileName, `${newFileName}.md`)// 開始嘗試保存文件名const error = await window.electron.ipcRenderer.invoke('rename-file', {oldFilePath: item.filePath,newFilePath,newFileName})if (error) {// 若重命名報錯,則重新聚焦,讓用戶重新輸入文件名document.getElementById(`file-${index}`)?.focus()} else {// 沒報錯,則重命名成功,更新當前文件路徑,文件列表中的文件名,文件路徑,將被點擊的文件列表項的 contenteditable 變為 falseif (currentFilePath.value === item.filePath) {currentFilePath.value = newFilePath}item.fileName = `${newFileName}.md`item.filePath = newFilePathitem.editable = false}
}
// 按回車時,直接失焦,觸發失焦事件執行保存文件名
const saveFileName_enter = (index: number): void => {document.getElementById(`file-${index}`)?.blur()
}

src/main/ipc.ts

  • 檢查新文件名是否包含非法字符 (\ / : * ? " < > |)
  • 檢查新文件名是否在本地已存在
  • 檢查合規,則重命名文件
  ipcMain.handle('rename-file', async (_e, { oldFilePath, newFilePath, newFileName }) => {// 檢查新文件名是否包含非法字符(\ / : * ? " < > |)if (/[\\/:*?"<>|]/.test(newFileName)) {return await dialog.showMessageBox(mainWindow, {type: 'error',title: '重命名失敗',message: `文件名稱 ${newFileName} 包含非法字符,請重新輸入。`})}try {await fs.access(newFilePath)// 若未拋出異常,說明文件存在return await dialog.showMessageBox(mainWindow, {type: 'error',title: '重命名失敗',message: `文件 ${path.basename(newFilePath)} 已存在,請選擇其他名稱。`})} catch {// 若拋出異常,說明文件不存在,可以進行重命名操作return await fs.rename(oldFilePath, newFilePath)}})

移除文件

將文件從文件列表中移除(不會刪除文件)

  window.electron.ipcRenderer.on('removeOut-fileList', (_, filePath) => {// 過濾掉要刪除的文件fileList.value = fileList.value.filter((file) => {return file.filePath !== filePath})// 若移除的當前打開的文件if (currentFilePath.value === filePath) {// 若移除目標文件后,還有其他文件,則打開第一個文件if (fileList_filtered.value.length > 0) {openFile(fileList_filtered.value[0])} else {// 若移除目標文件后,沒有其他文件,則清空內容和路徑markdownContent.value = ''currentFilePath.value = ''}}})

清空文件列表

  window.electron.ipcRenderer.on('clear-fileList', () => {fileList.value = []markdownContent.value = ''currentFilePath.value = ''})

用資源管理器打開文件所在目錄

在這里插入圖片描述
直接用 shell 打開

src/main/menu.ts

    {label: '打開所在目錄',click: async () => {shell.openPath(path.dirname(filePath))}},

刪除文件

src/main/menu.ts

    {label: '刪除',click: async () => {try {// 顯示確認對話框const { response } = await dialog.showMessageBox(mainWindow, {type: 'question',buttons: ['確定', '取消'],title: '確認刪除',message: `確定要刪除文件 ${path.basename(filePath)} 嗎?`})if (response === 0) {// 用戶點擊確定,刪除本地文件await fs.unlink(filePath)// 通知渲染進程,將文件從列表中移除mainWindow.webContents.send('removeOut-fileList', filePath)}} catch {dialog.showMessageBox(mainWindow, {type: 'error',title: '刪除失敗',message: `刪除文件 ${path.basename(filePath)} 時出錯,請稍后重試。`})}}}

src/renderer/src/App.vue

同移除文件

  window.electron.ipcRenderer.on('removeOut-fileList', (_, filePath) => {// 過濾掉要刪除的文件fileList.value = fileList.value.filter((file) => {return file.filePath !== filePath})// 若移除的當前打開的文件if (currentFilePath.value === filePath) {// 若移除目標文件后,還有其他文件,則打開第一個文件if (fileList_filtered.value.length > 0) {openFile(fileList_filtered.value[0])} else {// 若移除目標文件后,沒有其他文件,則清空內容和路徑markdownContent.value = ''currentFilePath.value = ''}}})

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/85219.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/85219.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/85219.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Remote Sensing投稿記錄(投稿郵箱寫錯、申請大修延期...)風雨波折投稿路

歷時近一個半月&#xff0c;我中啦&#xff01; RS是中科院二區&#xff0c;2023-2024影響因子4.2&#xff0c;五年影響因子4.9。 投稿前特意查了下預警&#xff0c;發現近五年都不在預警名單中&#xff0c;甚至最新中科院SCI分區&#xff08;2025年3月&#xff09;在各小類上…

吉林第三屆全國龍舟邀請賽(大安站)激情開賽

龍舟競渡處,瑞氣滿湖光。5月31日&#xff0c;金蛇獻瑞龍舞九州2025年全國龍舟大聯動-中國吉林第三屆全國龍舟邀請賽(大安站)“嫩江灣杯”白城市全民健身龍舟賽在吉林大安嫩江灣國家5A級旅游區玉龍湖拉開帷幕。 上午9時&#xff0c;伴隨著激昂的音樂&#xff0c;活力四射的青春舞…

華為OD機試真題——通過軟盤拷貝文件(2025A卷:200分)Java/python/JavaScript/C++/C語言/GO六種最佳實現

2025 A卷 200分 題型 本文涵蓋詳細的問題分析、解題思路、代碼實現、代碼詳解、測試用例以及綜合分析; 并提供Java、python、JavaScript、C++、C語言、GO六種語言的最佳實現方式! 本文收錄于專欄:《2025華為OD真題目錄+全流程解析/備考攻略/經驗分享》 華為OD機試真題《通過…

一起學數據結構和算法(二)| 數組(線性結構)

數組&#xff08;Array&#xff09; 數組是最基礎的數據結構&#xff0c;在內存中連續存儲&#xff0c;支持隨機訪問。適用于需要頻繁按索引訪問元素的場景。 簡介 數組是一種線性結構&#xff0c;將相同類型的元素存儲在連續的內存空間中。每個元素通過其索引值&#xff08;數…

ZYNQ sdk lwip配置UDP組播收發數據

?? 一、顛覆認知:組播 vs 單播 vs 廣播 通信方式目標設備網絡負載典型應用場景單播1對1O(n)SSH遠程登錄廣播1對全網O(1)ARP地址解析組播1對N組O(1)視頻會議/物聯網群控創新價值:在智能工廠中,ZYNQ通過組播同時控制100臺AGV小車,比傳統單播方案降低92%網絡流量! ?? 二、…

機器學習:欠擬合、過擬合、正則化

本文目錄&#xff1a; 一、欠擬合二、過擬合三、擬合問題原因及解決辦法四、正則化&#xff1a;盡量減少高次冪特征的影響&#xff08;一&#xff09;L1正則化&#xff08;二&#xff09;L2正則化&#xff08;三&#xff09;L1正則化與L2正則化的對比 五、正好擬合代碼&#xf…

Linux命令之ausearch命令

一、命令簡介 ausearch 是 Linux 審計系統 (auditd) 中的一個實用工具,用于搜索審計日志中的事件。它是審計框架的重要組成部分,可以幫助系統管理員分析系統活動和安全事件。 二、使用示例 1、安裝ausearch命令 Ubuntu系統安裝ausearch命令,安裝后啟動服務。 root@testser…

mac電腦安裝nvm

方案一、常規安裝 下載安裝腳本&#xff1a;在終端中執行以下命令來下載并運行 NVM 的安裝腳本3&#xff1a; bash curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.39.5/install.sh | bash配置環境變量&#xff1a;安裝完成后&#xff0c;需要配置環境變量。如…

Excel 操作 轉圖片,轉pdf等

方式一 spire.xls.free&#xff08;沒找設置分辨率的方法&#xff09; macOs開發Java GUI程序提示缺少字體問題解決 Spire.XLS&#xff1a;一款Excel處理神器_spire.xls免費版和收費版的區別-CSDN博客 官方文檔 Spire.XLS for Java 中文教程 <dependency><groupI…

oracle goldengate實現遠程抽取postgresql 到 postgresql的實時同步【絕對無坑版,親測流程驗證】

oracle goldengate實現postgresql 到 postgresql的實時同步 源端&#xff1a;postgresql1 -> postgresql2 流復制主備同步 目標端&#xff1a;postgresql 數據庫版本&#xff1a;postgresql 12.14 ogg版本&#xff1a;21.3 架構圖&#xff1a; 數據庫安裝以及流復制主備…

2.從0開始搭建vue項目(node.js,vue3,Ts,ES6)

從“0到跑起來一個 Vue 項目”&#xff0c;重點是各個工具之間的關聯關系、職責邊界和技術演化脈絡。 從你寫代碼 → 到代碼能跑起來 → 再到代碼可以部署上線&#xff0c;每一步都有不同的工具參與。 &#x1f63a;&#x1f63a;1. 安裝 Node.js —— 萬事的根基 Node.js 是…

MQTT的Thingsboards的使用

訪問云服務 https://thingsboard.cloud/ 新建一個設備 彈出 默認是mosquittor的客戶端。 curl -v -X POST http://thingsboard.cloud/api/v1/tnPrO76AxF3TAyOblf9x/telemetry --header Content-Type:application/json --data "{temperature:25}" 換成MQTTX的客戶…

金磚國家人工智能高級別論壇在巴西召開,華院計算應邀出席并發表主題演講

當地時間5月20日&#xff0c;由中華人民共和國工業和信息化部&#xff0c;巴西發展、工業、貿易與服務部&#xff0c;巴西公共服務管理和創新部以及巴西科技創新部聯合舉辦的金磚國家人工智能高級別論壇&#xff0c;在巴西首都巴西利亞舉行。 中華人民共和國工業和信息化部副部…

BLE協議全景圖:從0開始理解低功耗藍牙

BLE(Bluetooth Low Energy)作為一種針對低功耗場景優化的通信協議,已經廣泛應用于智能穿戴、工業追蹤、智能家居、醫療設備等領域。 本文是《BLE 協議實戰詳解》系列的第一篇,將從 BLE 的發展歷史、協議棧結構、核心機制和應用領域出發,為后續工程實戰打下全面認知基礎。 …

表單校驗代碼和樹形結構值傳遞錯誤解決

表單校驗代碼&#xff0c;兩種方式校驗&#xff0c;自定義的一種校驗&#xff0c;與element-ui組件原始的el-form表單的校驗不一樣&#xff0c;需要傳遞props和rules過去校驗 const nextStep () > {const data taskMsgInstance.value.formDataif(data.upGradeOrg ) {elm…

vscode 配置 QtCreat Cmake項目

1.vscode安裝CmakeTool插件并配置QT中cmake的路徑&#xff0c;不止這一處 2.cmake生成器使用Ninja&#xff08;Ninja在安裝QT時需要勾選&#xff09;&#xff0c;可以解決[build] cc1plus.exe: error: too many filenames given; type ‘cc1plus.exe --help’ for usage 編譯時…

關于數據倉庫、數據湖、數據平臺、數據中臺和湖倉一體的概念和區別

我們談論數據中臺之前&#xff0c; 我們也聽到過數據平臺、數據倉庫、數據湖、湖倉一體的相關概念&#xff0c;它們都與數據有關系&#xff0c;但他們和數據中臺有什么樣的區別&#xff0c; 下面我們將圍繞數據平臺、數據倉庫、數據湖和數據中臺的區別進行介紹。 一、相關概念…

WIN11+eclipse搭建java開發環境

環境搭建&#xff08;WIN11ECLIPSE&#xff09; 安裝JAVA JDK https://www.oracle.com/cn/java/technologies/downloads/#jdk24安裝eclipse https://www.eclipse.org/downloads/ 注意&#xff1a;eclipse下載時指定aliyun的軟件源&#xff0c;后面安裝會快一些。默認是jp漢化e…

通義靈碼深度實戰測評:從零構建智能家居控制中樞,體驗AI編程新范式

一、項目背景&#xff1a;零基礎挑戰全棧智能家居系統 目標&#xff1a;開發具備設備控制、環境感知、用戶習慣學習的智能家居控制中樞&#xff08;PythonFlaskMQTTReact&#xff09; 挑戰點&#xff1a; 需集成硬件通信(MQTT)、Web服務(Flask)、前端交互(React) 調用天氣AP…

【Python進階】CPython

目錄 ?? 前言??? 技術背景與價值?? 當前技術痛點??? 解決方案概述?? 目標讀者說明?? 一、技術原理剖析?? 核心架構圖解?? 核心作用講解?? 關鍵技術模塊說明?? Python實現對比??? 二、實戰演示?? 環境配置要求?? 核心代碼實現案例1:查看字節碼案例…