优化IPC通信暴露方法的结构和类型,增加文件夹选择与文件列出基础方法

This commit is contained in:
YILS
2025-07-20 17:30:24 +08:00
parent e73ac7c9e2
commit 7f221d4ee2
12 changed files with 146 additions and 64 deletions

View File

@@ -23,12 +23,20 @@ declare namespace NodeJS {
// 在渲染器进程中使用,在 `preload.ts` 中暴露方法
interface Window {
ipcRenderer: import('electron').IpcRenderer
ipcRenderer: Pick<import('electron').IpcRenderer, 'on' | 'once' | 'off' | 'send' | 'invoke'>
electron: {
isWinMaxed: () => Promise<boolean>
winMin: () => void
winMax: () => void
winClose: () => void
selectFolder: (params: import('./types').SelectFolderParams) => Promise<string>
listFilesFromFolder: (params: import('./types').ListFilesFromFolderParams) => Promise<string[]>
}
sqlite: {
query: (param: import('./sqlite/types').queryParam) => Promise<any>
insert: (param: import('./sqlite/types').insertParam) => Promise<any>
update: (param: import('./sqlite/types').updateParam) => Promise<any>
delete: (param: import('./sqlite/types').deleteParam) => Promise<any>
bulkInsertOrUpdate: (param: import('./sqlite/types').bulkInsertOrUpdateParam) => Promise<any>
query: (param: import('./sqlite/types').queryParams) => Promise<any>
insert: (param: import('./sqlite/types').insertParams) => Promise<any>
update: (param: import('./sqlite/types').updateParams) => Promise<any>
delete: (param: import('./sqlite/types').deleteParams) => Promise<any>
bulkInsertOrUpdate: (param: import('./sqlite/types').bulkInsertOrUpdateParams) => Promise<any>
}
}

View File

@@ -1,8 +1,10 @@
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { BrowserWindow, ipcMain } from 'electron'
import { queryParam, insertParam, updateParam, deleteParam } from './sqlite/types'
import { BrowserWindow, ipcMain, dialog, app } from 'electron'
import { queryParams, insertParams, updateParams, deleteParams } from './sqlite/types'
import { sqBulkInsertOrUpdate, sqDelete, sqInsert, sqQuery, sqUpdate } from './sqlite'
import { ListFilesFromFolderParams, SelectFolderParams } from './types'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
@@ -18,8 +20,29 @@ process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL
: RENDERER_DIST
export default function initIPC(win: BrowserWindow) {
// sqlite 查询
ipcMain.handle('sqlite-query', (_event, params: queryParams) => {
return sqQuery(params)
})
// sqlite 插入
ipcMain.handle('sqlite-insert', (_event, params: insertParams) => {
return sqInsert(params)
})
// sqlite 更新
ipcMain.handle('sqlite-update', (_event, params: updateParams) => {
return sqUpdate(params)
})
// sqlite 删除
ipcMain.handle('sqlite-delete', (_event, params: deleteParams) => {
return sqDelete(params)
})
// sqlite 批量插入或更新
ipcMain.handle('sqlite-bulk-insert-or-update', (_event, params: any) => {
return sqBulkInsertOrUpdate(params)
})
// 是否最大化
ipcMain.handle('win-maxed', () => {
ipcMain.handle('is-win-maxed', () => {
return win?.isMaximized()
})
//最小化
@@ -39,24 +62,23 @@ export default function initIPC(win: BrowserWindow) {
win?.close()
})
// sqlite 查询
ipcMain.handle('sqlite-query', (_event, params: queryParam) => {
return sqQuery(params)
// 选择文件夹
ipcMain.handle('select-folder', async (_event, params?: SelectFolderParams) => {
const result = await dialog.showOpenDialog(win, {
properties: ['openDirectory'],
title: params?.title || '选择文件夹',
defaultPath: params?.defaultPath || app.getPath('downloads'), // 默认打开 Downloads
})
if (!result.canceled && result.filePaths.length > 0) {
return result.filePaths[0] // 返回绝对路径
}
return null
})
// sqlite 插入
ipcMain.handle('sqlite-insert', async (_event, params: insertParam) => {
return await sqInsert(params)
})
// sqlite 更新
ipcMain.handle('sqlite-update', async (_event, params: updateParam) => {
return await sqUpdate(params)
})
// sqlite 删除
ipcMain.handle('sqlite-delete', async (_event, params: deleteParam) => {
return await sqDelete(params)
})
// sqlite 批量插入或更新
ipcMain.handle('sqlite-bulk-insert-or-update', async (_event, params: any) => {
return await sqBulkInsertOrUpdate(params)
// 读取文件夹内所有文件
ipcMain.handle('list-files-from-folder', async (_event, params: ListFilesFromFolderParams) => {
const files = await fs.promises.readdir(params.folderPath, { withFileTypes: true })
return files.filter((file) => file.isFile()).map((file) => file.name)
})
}

View File

@@ -1,5 +1,6 @@
import { ipcRenderer, contextBridge } from 'electron'
import { queryParam, insertParam, updateParam, deleteParam } from './sqlite/types'
import { queryParams, insertParams, updateParams, deleteParams } from './sqlite/types'
import { ListFilesFromFolderParams, SelectFolderParams } from './types'
// --------- 向界面渲染进程暴露某些API ---------
@@ -26,20 +27,20 @@ contextBridge.exposeInMainWorld('ipcRenderer', {
},
})
contextBridge.exposeInMainWorld('sqlite', {
query: async (param: queryParam) => {
return await ipcRenderer.invoke('sqlite-query', param)
},
insert: async (param: insertParam) => {
return await ipcRenderer.invoke('sqlite-insert', param)
},
update: async (param: updateParam) => {
return await ipcRenderer.invoke('sqlite-update', param)
},
delete: async (param: deleteParam) => {
return await ipcRenderer.invoke('sqlite-delete', param)
},
bulkInsertOrUpdate: async (param: any) => {
return await ipcRenderer.invoke('sqlite-bulk-insert-or-update', param)
},
contextBridge.exposeInMainWorld('electron', {
isWinMaxed: () => ipcRenderer.invoke('is-win-maxed'),
winMin: () => ipcRenderer.send('win-min'),
winMax: () => ipcRenderer.send('win-max'),
winClose: () => ipcRenderer.send('win-close'),
selectFolder: (params: SelectFolderParams) => ipcRenderer.invoke('select-folder', params),
listFilesFromFolder: (params: ListFilesFromFolderParams) =>
ipcRenderer.invoke('list-files-from-folder', params),
})
contextBridge.exposeInMainWorld('sqlite', {
query: (params: queryParams) => ipcRenderer.invoke('sqlite-query', params),
insert: (params: insertParams) => ipcRenderer.invoke('sqlite-insert', params),
update: (params: updateParams) => ipcRenderer.invoke('sqlite-update', params),
delete: (params: deleteParams) => ipcRenderer.invoke('sqlite-delete', params),
bulkInsertOrUpdate: (params: any) => ipcRenderer.invoke('sqlite-bulk-insert-or-update', params),
})

View File

@@ -1,7 +1,7 @@
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import BetterSqlite3 from 'better-sqlite3'
import { queryParam, insertParam, updateParam, deleteParam, bulkInsertOrUpdateParam } from './types'
import { queryParams, insertParams, updateParams, deleteParams, bulkInsertOrUpdateParams } from './types'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
process.env.APP_ROOT = path.join(__dirname, '..')
@@ -53,7 +53,7 @@ class Database {
})
}
query(param: queryParam): Promise<any[]> {
query(param: queryParams): Promise<any[]> {
return new Promise<any[]>((resolve, _reject) => {
const stmt = this.db.prepare(param.sql)
const rows = param.params ? stmt.all(...param.params) : stmt.all()
@@ -61,7 +61,7 @@ class Database {
})
}
insert(param: insertParam): Promise<number> {
insert(param: insertParams): Promise<number> {
return new Promise<number>((resolve, _reject) => {
const keys = Object.keys(param.data)
const values = Object.values(param.data)
@@ -79,7 +79,7 @@ class Database {
return result.length > 0
}
update(param: updateParam): Promise<number> {
update(param: updateParams): Promise<number> {
return new Promise<number>((resolve, _reject) => {
const entries = Object.entries(param.data)
.map(([key, _value]) => `${key} = ?`)
@@ -93,7 +93,7 @@ class Database {
})
}
delete(param: deleteParam): Promise<void> {
delete(param: deleteParams): Promise<void> {
return new Promise<void>((resolve, _reject) => {
const sql = `DELETE FROM ${param.table} WHERE ${param.condition}`
const stmt = this.db.prepare(sql)
@@ -102,7 +102,7 @@ class Database {
})
}
async bulkInsertOrUpdate(param: bulkInsertOrUpdateParam): Promise<void> {
async bulkInsertOrUpdate(param: bulkInsertOrUpdateParams): Promise<void> {
return new Promise<void>((resolve, _reject) => {
const keys = Object.keys(param.data[0])
const placeholders = keys.map(() => '?').join(',')

View File

@@ -1,25 +1,25 @@
export interface queryParam {
export interface queryParams {
sql: string
params?: any[]
}
export interface insertParam {
export interface insertParams {
table: string
data: { [key: string]: any }
}
export interface updateParam {
export interface updateParams {
table: string
data: { [key: string]: any }
condition: string
}
export interface deleteParam {
export interface deleteParams {
table: string
condition: string
}
export interface bulkInsertOrUpdateParam {
export interface bulkInsertOrUpdateParams {
table: string
data: any[]
}

8
electron/types.ts Normal file
View File

@@ -0,0 +1,8 @@
export interface SelectFolderParams {
title?: string
defaultPath?: string
}
export interface ListFilesFromFolderParams {
folderPath: string
}

View File

@@ -32,6 +32,7 @@
"devDependencies": {
"@mdi/font": "^7.4.47",
"@types/better-sqlite3": "^7.6.13",
"@types/wicg-file-system-access": "^2023.10.6",
"@vitejs/plugin-vue": "^6.0.0",
"cross-env": "^7.0.3",
"electron": "^22.3.27",

8
pnpm-lock.yaml generated
View File

@@ -48,6 +48,9 @@ importers:
'@types/better-sqlite3':
specifier: ^7.6.13
version: 7.6.13
'@types/wicg-file-system-access':
specifier: ^2023.10.6
version: 2023.10.6
'@vitejs/plugin-vue':
specifier: ^6.0.0
version: 6.0.0(vite@7.0.3(@types/node@24.0.12)(jiti@2.4.2)(sass@1.89.2))(vue@3.5.17(typescript@5.6.2))
@@ -792,6 +795,9 @@ packages:
'@types/web-bluetooth@0.0.21':
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
'@types/wicg-file-system-access@2023.10.6':
resolution: {integrity: sha512-YO/183gNRzZFSdKu+ikkD7ambAj4PhgjFAF2A/Mw/7wroSF6ne8r804RkpZzqrJ/F6DO2/IYlQF/ULOZ/bhKyA==}
'@types/yauzl@2.10.3':
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
@@ -3310,6 +3316,8 @@ snapshots:
'@types/web-bluetooth@0.0.21': {}
'@types/wicg-file-system-access@2023.10.6': {}
'@types/yauzl@2.10.3':
dependencies:
'@types/node': 16.18.126

0
src/global.d.ts vendored Normal file
View File

View File

@@ -29,17 +29,17 @@ const route = useRoute()
const windowIsMaxed = ref(false)
window.addEventListener('resize', async () => {
windowIsMaxed.value = await window.ipcRenderer.invoke('win-maxed')
windowIsMaxed.value = await window.electron.isWinMaxed()
})
const handleMin = () => {
window.ipcRenderer.send('win-min')
window.electron.winMin()
}
const handleMax = () => {
window.ipcRenderer.send('win-max')
window.electron.winMax()
}
const handleClose = () => {
window.ipcRenderer.send('win-close')
window.electron.winClose()
}
</script>

View File

@@ -10,15 +10,19 @@ export const useAppStore = defineStore(
apiUrl: '',
apiKey: '',
})
const updateLLMConfig = (newConfig: typeof llmConfig.value) => {
llmConfig.value = newConfig
}
const videoAssetsFolder = ref('')
const videoExportFolder = ref('')
return {
prompt,
llmConfig,
updateLLMConfig,
videoAssetsFolder,
videoExportFolder,
}
},
{

View File

@@ -2,8 +2,15 @@
<div class="w-full h-full">
<v-sheet class="h-full p-2 flex flex-col" border rounded>
<div class="flex gap-2">
<v-text-field label="分镜视频素材文件夹" density="compact"></v-text-field>
<v-btn class="mt-[2px]" prepend-icon="mdi-folder-open"> 选择 </v-btn>
<v-text-field
:model-value="appStore.videoAssetsFolder"
label="分镜视频素材文件夹"
density="compact"
readonly
></v-text-field>
<v-btn class="mt-[2px]" prepend-icon="mdi-folder-open" @click="handleSelectFolder">
选择
</v-btn>
</div>
<div class="flex-1 h-full w-full border">
@@ -14,14 +21,37 @@
</div>
<div class="my-2">
<v-btn prepend-icon="mdi-refresh" block> 刷新素材库 </v-btn>
<v-btn prepend-icon="mdi-refresh" block @click="refreshAssets"> 刷新素材库 </v-btn>
</div>
</v-sheet>
</div>
</template>
<script lang="ts" setup>
//
import { useAppStore } from '@/store'
import { ref } from 'vue'
const appStore = useAppStore()
const handleSelectFolder = async () => {
const folderPath = await window.electron.selectFolder({
title: '选择分镜素材文件夹',
defaultPath: appStore.videoAssetsFolder,
})
if (folderPath) {
appStore.videoAssetsFolder = folderPath
}
console.log('用户选择的文件夹绝对路径:', folderPath)
}
const videoAssets = ref([])
const refreshAssets = async () => {
console.log('刷新素材库')
const assets = await window.electron.listFilesFromFolder({
folderPath: appStore.videoAssetsFolder,
})
console.log(`素材:`, assets)
}
</script>
<style lang="scss" scoped>