2025-08-19 14:33:59 +08:00
|
|
|
|
import { dialog, nativeTheme, shell } from 'electron'
|
|
|
|
|
|
import { CheckFileOrDirExist, CopyFileOrFolder } from '../../../define/Tools/file'
|
|
|
|
|
|
import path from 'path'
|
2025-09-12 14:52:28 +08:00
|
|
|
|
import fs from 'fs/promises'
|
2025-08-19 14:33:59 +08:00
|
|
|
|
import { errorMessage, successMessage } from '../../../public/generalTools'
|
|
|
|
|
|
import { ErrorItem, SuccessItem } from '@/define/model/generalResponse'
|
2025-09-12 14:52:28 +08:00
|
|
|
|
import { t } from '@/i18n'
|
2025-08-19 14:33:59 +08:00
|
|
|
|
|
|
|
|
|
|
/** 打开指定的文件夹的方法 */
|
|
|
|
|
|
export type OpenFolderParams = {
|
|
|
|
|
|
/** 是不是基于项目文件,是的话,会在项目文件夹的基础上进行拼接 */
|
|
|
|
|
|
baseProject: boolean
|
|
|
|
|
|
/** 判断是不是打开父文件夹 */
|
2025-09-12 14:52:28 +08:00
|
|
|
|
dirFolder: boolean
|
2025-08-19 14:33:59 +08:00
|
|
|
|
/** 文件路径,baseProject 为false,需要设置完整的文件路径 */
|
|
|
|
|
|
folderPath: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 一些对electron接口的封装,配合业务逻辑 */
|
|
|
|
|
|
export default class ElectronInterface {
|
2025-09-12 14:52:28 +08:00
|
|
|
|
constructor() { }
|
2025-08-19 14:33:59 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 打开指定的文件,试用默认的打开方式
|
|
|
|
|
|
* @param value
|
|
|
|
|
|
* @returns
|
|
|
|
|
|
*/
|
|
|
|
|
|
public async OpenFile(value: string): Promise<ErrorItem | SuccessItem> {
|
|
|
|
|
|
if (!(await CheckFileOrDirExist(value))) {
|
2025-09-12 14:52:28 +08:00
|
|
|
|
return errorMessage(t("目的文件/文件夹不存在,{data}", {
|
|
|
|
|
|
data: value
|
|
|
|
|
|
}), 'SystemIpc_OPEN_FILE')
|
2025-08-19 14:33:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
await shell.openPath(value)
|
2025-09-12 14:52:28 +08:00
|
|
|
|
return successMessage(null, t("打开文件/文件夹成功", {
|
|
|
|
|
|
data: value
|
|
|
|
|
|
}), 'SystemIpc_OPEN_FILE')
|
2025-08-19 14:33:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 深度复制文件夹内容到目标文件夹
|
|
|
|
|
|
* @param source 源文件夹路径
|
|
|
|
|
|
* @param destination 目标文件夹路径
|
|
|
|
|
|
* @returns
|
|
|
|
|
|
*/
|
|
|
|
|
|
public async CopyFolderContents(
|
|
|
|
|
|
source: string,
|
|
|
|
|
|
destination: string
|
|
|
|
|
|
): Promise<ErrorItem | SuccessItem> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 使用更完善的复制方法
|
|
|
|
|
|
await CopyFileOrFolder(source, destination, false)
|
|
|
|
|
|
|
2025-09-12 14:52:28 +08:00
|
|
|
|
return successMessage(null, t('复制文件夹成功'), 'SystemIpc_COPY_FOLDER_CONTENTS')
|
2025-08-19 14:33:59 +08:00
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
return errorMessage(
|
2025-09-12 14:52:28 +08:00
|
|
|
|
t("复制文件夹失败,{error}", {
|
|
|
|
|
|
error: error.message
|
|
|
|
|
|
}),
|
2025-08-19 14:33:59 +08:00
|
|
|
|
'SystemIpc_COPY_FOLDER_CONTENTS'
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 打开对应的文件夹
|
|
|
|
|
|
* @param params
|
|
|
|
|
|
* @returns
|
|
|
|
|
|
*/
|
|
|
|
|
|
public async OpenFolder(params: OpenFolderParams) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
let openFolder = ''
|
|
|
|
|
|
if (params.baseProject) {
|
|
|
|
|
|
openFolder = path.join(global.config.project_path, params.folderPath)
|
|
|
|
|
|
}
|
2025-09-12 14:52:28 +08:00
|
|
|
|
if (params.dirFolder) {
|
2025-08-19 14:33:59 +08:00
|
|
|
|
openFolder = path.dirname(params.folderPath)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!openFolder) {
|
|
|
|
|
|
openFolder = params.folderPath
|
|
|
|
|
|
}
|
|
|
|
|
|
// 判断文件夹是不是存在
|
|
|
|
|
|
let isExist = await CheckFileOrDirExist(openFolder)
|
|
|
|
|
|
if (!isExist) {
|
2025-09-12 14:52:28 +08:00
|
|
|
|
throw new Error(t("目的文件/文件夹不存在,{data}", {
|
|
|
|
|
|
data: openFolder
|
|
|
|
|
|
}))
|
2025-08-19 14:33:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
shell.openPath(openFolder)
|
2025-09-12 14:52:28 +08:00
|
|
|
|
return successMessage(null, t('打开文件/文件夹成功'))
|
2025-08-19 14:33:59 +08:00
|
|
|
|
} catch (error: any) {
|
2025-09-12 14:52:28 +08:00
|
|
|
|
return errorMessage(t("打卡开文件/文件夹失败,{error}", {
|
|
|
|
|
|
error: error.message
|
|
|
|
|
|
}), 'SystemIpc_OPEN_FOLDER')
|
2025-08-19 14:33:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 选择单个指定文件后缀的文件
|
|
|
|
|
|
* @param value 后缀列表
|
|
|
|
|
|
*/
|
|
|
|
|
|
public async SelectSingleFile(value: string[]): Promise<SuccessItem | ErrorItem> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
let { filePaths } = await dialog.showOpenDialog({
|
|
|
|
|
|
properties: ['openFile'],
|
|
|
|
|
|
filters: [{ name: 'fileName', extensions: value }]
|
|
|
|
|
|
})
|
|
|
|
|
|
if (filePaths.length === 0) {
|
2025-09-12 14:52:28 +08:00
|
|
|
|
throw new Error(t('没有选择的文件或文件夹'))
|
2025-08-19 14:33:59 +08:00
|
|
|
|
}
|
2025-09-12 14:52:28 +08:00
|
|
|
|
return successMessage(filePaths[0], t("选择文件/文件夹成功"), 'SystemIpc_SelectSingleFile')
|
2025-08-19 14:33:59 +08:00
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
return errorMessage(
|
2025-09-12 14:52:28 +08:00
|
|
|
|
t("选择文件/文件夹失败,{error}", {
|
|
|
|
|
|
error: error.message
|
|
|
|
|
|
}),
|
2025-08-19 14:33:59 +08:00
|
|
|
|
'SystemIpc_SelectSingleFile'
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 选择多个指定文件后缀的文件
|
|
|
|
|
|
* @param value 文件后缀列表
|
|
|
|
|
|
* @returns
|
|
|
|
|
|
*/
|
|
|
|
|
|
public async SelectMultipleFile(value: string[]): Promise<SuccessItem | ErrorItem> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { filePaths } = await dialog.showOpenDialog({
|
|
|
|
|
|
properties: ['openFile', 'multiSelections'],
|
|
|
|
|
|
filters: [{ name: 'fileName', extensions: value }]
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if (filePaths.length === 0) {
|
2025-09-12 14:52:28 +08:00
|
|
|
|
throw new Error(t('没有选择的文件或文件夹'))
|
2025-08-19 14:33:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-12 14:52:28 +08:00
|
|
|
|
return successMessage(filePaths, t("选择文件/文件夹成功"), 'SystemIpc_SelectMultipleFile')
|
2025-08-19 14:33:59 +08:00
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
console.error('选择文件错误:', error) // 记录错误日志
|
|
|
|
|
|
return errorMessage(
|
2025-09-12 14:52:28 +08:00
|
|
|
|
t("选择文件/文件夹失败,{error}", {
|
|
|
|
|
|
error: error.message
|
|
|
|
|
|
}),
|
2025-08-19 14:33:59 +08:00
|
|
|
|
'SystemIpc_SelectMultipleFile'
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 切换主题
|
|
|
|
|
|
* @param theme 主题名称
|
|
|
|
|
|
* @returns 返回当前是否使用深色模式
|
|
|
|
|
|
*/
|
|
|
|
|
|
public async ToggleTheme(theme: string) {
|
|
|
|
|
|
if (theme != 'light' && theme != 'dark') {
|
|
|
|
|
|
nativeTheme.themeSource = 'system'
|
|
|
|
|
|
} else {
|
|
|
|
|
|
nativeTheme.themeSource = theme
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nativeTheme.shouldUseDarkColors
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 选择单个文件夹
|
|
|
|
|
|
* @param defaultPath 可选配置
|
|
|
|
|
|
* @returns 成功返回选择的文件夹路径,失败返回错误信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
public async SelectSingleFolder(defaultPath: string): Promise<SuccessItem | ErrorItem> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { filePaths } = await dialog.showOpenDialog({
|
|
|
|
|
|
properties: ['openDirectory'],
|
|
|
|
|
|
defaultPath: defaultPath,
|
2025-09-12 14:52:28 +08:00
|
|
|
|
title: t('选择文件夹'),
|
|
|
|
|
|
buttonLabel: t('选择文件夹')
|
2025-08-19 14:33:59 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if (filePaths.length === 0) {
|
2025-09-12 14:52:28 +08:00
|
|
|
|
throw new Error(t('没有选择的文件或文件夹'))
|
2025-08-19 14:33:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-12 14:52:28 +08:00
|
|
|
|
return successMessage(filePaths[0], t("选择文件/文件夹成功"), 'SystemIpc_SelectSingleFolder')
|
2025-08-19 14:33:59 +08:00
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
return errorMessage(
|
2025-09-12 14:52:28 +08:00
|
|
|
|
t("选择文件/文件夹失败,{error}", {
|
|
|
|
|
|
error: error.message
|
|
|
|
|
|
}),
|
2025-08-19 14:33:59 +08:00
|
|
|
|
'SystemIpc_SelectSingleFolder'
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 选择文件夹或指定后缀的文件
|
|
|
|
|
|
* @param extensions 文件后缀列表(可选)
|
|
|
|
|
|
* @returns
|
|
|
|
|
|
*/
|
|
|
|
|
|
public async SelectFolderOrFile(extensions?: string[]): Promise<SuccessItem | ErrorItem> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 使用消息框让用户选择类型
|
|
|
|
|
|
const choice = await dialog.showMessageBox({
|
|
|
|
|
|
type: 'question',
|
2025-09-12 14:52:28 +08:00
|
|
|
|
title: t('选择类型'),
|
|
|
|
|
|
message: t('请选择要选择的类型:'),
|
|
|
|
|
|
buttons: [t('选择文件'), t('选择文件夹'), t('取消')],
|
2025-08-19 14:33:59 +08:00
|
|
|
|
defaultId: 0,
|
|
|
|
|
|
cancelId: 2
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if (choice.response === 2) {
|
2025-09-12 14:52:28 +08:00
|
|
|
|
throw new Error(t("取消操作"))
|
2025-08-19 14:33:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (choice.response === 0) {
|
|
|
|
|
|
// 选择文件
|
|
|
|
|
|
const result = await dialog.showOpenDialog({
|
|
|
|
|
|
properties: ['openFile'],
|
|
|
|
|
|
filters:
|
|
|
|
|
|
extensions && extensions.length > 0
|
|
|
|
|
|
? [
|
2025-09-12 14:52:28 +08:00
|
|
|
|
{ name: 'Audio Files', extensions },
|
|
|
|
|
|
{ name: 'All Files', extensions: ['*'] }
|
|
|
|
|
|
]
|
2025-08-19 14:33:59 +08:00
|
|
|
|
: [{ name: 'All Files', extensions: ['*'] }],
|
2025-09-12 14:52:28 +08:00
|
|
|
|
title: t('选择文件')
|
2025-08-19 14:33:59 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if (result.filePaths.length === 0) {
|
2025-09-12 14:52:28 +08:00
|
|
|
|
throw new Error(t('没有选择的文件或文件夹'))
|
2025-08-19 14:33:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-12 14:52:28 +08:00
|
|
|
|
return successMessage(result.filePaths[0], t("选择文件/文件夹成功"), 'SystemIpc_SelectFolderOrFile')
|
2025-08-19 14:33:59 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// 选择文件夹
|
|
|
|
|
|
const result = await dialog.showOpenDialog({
|
|
|
|
|
|
properties: ['openDirectory'],
|
2025-09-12 14:52:28 +08:00
|
|
|
|
title: t('选择文件夹')
|
2025-08-19 14:33:59 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if (result.filePaths.length === 0) {
|
2025-09-12 14:52:28 +08:00
|
|
|
|
throw new Error(t('没有选择的文件或文件夹'))
|
2025-08-19 14:33:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-12 14:52:28 +08:00
|
|
|
|
return successMessage(result.filePaths[0], t("选择文件/文件夹成功"), 'SystemIpc_SelectFolderOrFile')
|
2025-08-19 14:33:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
console.error('选择文件或文件夹错误:', error)
|
|
|
|
|
|
return errorMessage(
|
2025-09-12 14:52:28 +08:00
|
|
|
|
t("选择文件/文件夹失败,{error}", {
|
|
|
|
|
|
error: error.message
|
|
|
|
|
|
}),
|
2025-08-19 14:33:59 +08:00
|
|
|
|
'SystemIpc_SelectFolderOrFile'
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 打开指定的URL
|
|
|
|
|
|
* @param url
|
|
|
|
|
|
*/
|
|
|
|
|
|
public OpenUrl(url: string) {
|
|
|
|
|
|
shell.openExternal(url)
|
|
|
|
|
|
}
|
2025-09-12 14:52:28 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 读取文件内容(仅支持常见的文本格式)
|
|
|
|
|
|
* @param filePath 文件路径
|
|
|
|
|
|
* @returns 返回文件内容或错误信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
public async ReadTextFile(filePath: string): Promise<SuccessItem | ErrorItem> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 定义支持的文本文件格式
|
|
|
|
|
|
const supportedExtensions = [
|
|
|
|
|
|
'.txt', '.json', '.xml', '.html', '.htm', '.css', '.js', '.ts',
|
|
|
|
|
|
'.jsx', '.tsx', '.vue', '.md', '.yml', '.yaml', '.csv', '.log',
|
|
|
|
|
|
'.ini', '.conf', '.config', '.py', '.java', '.c', '.cpp', '.h',
|
|
|
|
|
|
'.php', '.rb', '.go', '.rs', '.swift', '.kt', '.scala', '.sql',
|
|
|
|
|
|
'.sh', '.bat', '.ps1', '.dockerfile', '.gitignore', '.env'
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
// 检查文件是否存在
|
|
|
|
|
|
if (!(await CheckFileOrDirExist(filePath))) {
|
|
|
|
|
|
throw new Error(t('文件不存在'))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取文件扩展名
|
|
|
|
|
|
const ext = path.extname(filePath).toLowerCase()
|
|
|
|
|
|
|
|
|
|
|
|
// 检查文件格式是否支持
|
|
|
|
|
|
if (!supportedExtensions.includes(ext)) {
|
|
|
|
|
|
throw new Error(t("不支持的文件格式: {ext}。支持的格式: {supportedExt}", {
|
|
|
|
|
|
ext: ext,
|
|
|
|
|
|
supportedExt: supportedExtensions.join(', ')
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 读取文件内容
|
|
|
|
|
|
const content = await fs.readFile(filePath, 'utf-8')
|
|
|
|
|
|
|
|
|
|
|
|
return successMessage(
|
|
|
|
|
|
{
|
|
|
|
|
|
content: content,
|
|
|
|
|
|
filePath: filePath,
|
|
|
|
|
|
size: Buffer.byteLength(content, 'utf-8'),
|
|
|
|
|
|
extension: ext
|
|
|
|
|
|
},
|
|
|
|
|
|
t('读取文件成功!'),
|
|
|
|
|
|
'SystemIpc_ReadTextFile'
|
|
|
|
|
|
)
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
console.error('读取文件错误:', error)
|
|
|
|
|
|
return errorMessage(
|
|
|
|
|
|
t("读取文件失败,{error}", {
|
|
|
|
|
|
error: error.message
|
|
|
|
|
|
}),
|
|
|
|
|
|
'SystemIpc_ReadTextFile'
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-19 14:33:59 +08:00
|
|
|
|
}
|