Files
XsinfoApi/electron/main.ts
T
2026-07-02 23:51:05 +08:00

203 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { app, BrowserWindow, ipcMain, dialog, shell } from 'electron'
import { join, dirname } from 'node:path'
import { promises as fs } from 'node:fs'
import { existsSync } from 'node:fs'
let win: BrowserWindow | null = null
const VITE_DEV_SERVER_URL = process.env.VITE_DEV_SERVER_URL
function createWindow() {
win = new BrowserWindow({
width: 1440,
height: 900,
minWidth: 1100,
minHeight: 700,
title: 'XsinfoApi',
backgroundColor: '#0a1428',
titleBarStyle: 'hidden',
titleBarOverlay: {
color: '#0a1428',
symbolColor: '#7eb6ff',
height: 36
},
webPreferences: {
preload: join(__dirname, 'preload.js'),
sandbox: false,
contextIsolation: true
}
})
if (VITE_DEV_SERVER_URL) {
win.loadURL(VITE_DEV_SERVER_URL)
} else {
win.loadFile(join(__dirname, '../dist/index.html'))
}
win.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url)
return { action: 'deny' }
})
}
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
// ============ 项目工作区 IPC(关联 git 下的 json ============
const WORKSPACE_FILE = '.xsinfo-workspace.json'
interface WorkspaceMeta {
rootDir: string | null
}
async function readWorkspaceMeta(): Promise<WorkspaceMeta> {
const metaPath = join(app.getPath('userData'), WORKSPACE_FILE)
if (!existsSync(metaPath)) return { rootDir: null }
try {
const raw = await fs.readFile(metaPath, 'utf-8')
return JSON.parse(raw)
} catch {
return { rootDir: null }
}
}
async function writeWorkspaceMeta(meta: WorkspaceMeta): Promise<void> {
const metaPath = join(app.getPath('userData'), WORKSPACE_FILE)
await fs.writeFile(metaPath, JSON.stringify(meta, null, 2), 'utf-8')
}
// 选择工作区目录(一个 git 项目)
ipcMain.handle('workspace:pick', async () => {
const res = await dialog.showOpenDialog({
title: '选择 API 项目工作区 (git 仓库)',
properties: ['openDirectory']
})
if (res.canceled || res.filePaths.length === 0) return null
const root = res.filePaths[0]
await writeWorkspaceMeta({ rootDir: root })
return root
})
ipcMain.handle('workspace:get', async () => {
const meta = await readWorkspaceMeta()
return meta.rootDir
})
ipcMain.handle('workspace:clear', async () => {
await writeWorkspaceMeta({ rootDir: null })
return null
})
// 在工作区根下创建约定目录 xsinfo-data/{env.json, apis/...}
ipcMain.handle('workspace:init', async (_e, rootDir: string) => {
const dataDir = join(rootDir, 'xsinfo-data')
const apisDir = join(dataDir, 'apis')
await fs.mkdir(apisDir, { recursive: true })
const envFile = join(dataDir, 'env.json')
if (!existsSync(envFile)) {
const initial = {
activeEnv: 'dev',
envs: [
{
id: 'dev',
name: '开发环境',
baseUrl: 'http://localhost:8080',
variables: [{ key: 'token', value: 'dev-token' }]
},
{
id: 'prod',
name: '生产环境',
baseUrl: 'https://api.example.com',
variables: [{ key: 'token', value: '' }]
}
]
}
await fs.writeFile(envFile, JSON.stringify(initial, null, 2), 'utf-8')
}
return true
})
// 读取 env.json
ipcMain.handle('env:read', async (_e, rootDir: string) => {
const file = join(rootDir, 'xsinfo-data', 'env.json')
if (!existsSync(file)) return null
const raw = await fs.readFile(file, 'utf-8')
return JSON.parse(raw)
})
ipcMain.handle('env:write', async (_e, rootDir: string, data: unknown) => {
const file = join(rootDir, 'xsinfo-data', 'env.json')
await fs.mkdir(join(rootDir, 'xsinfo-data'), { recursive: true })
await fs.writeFile(file, JSON.stringify(data, null, 2), 'utf-8')
return true
})
// API 列表 / 树 (apifox 风格:project.json + 各分组下子文件)
ipcMain.handle('api:project:read', async (_e, rootDir: string) => {
const file = join(rootDir, 'xsinfo-data', 'project.json')
if (!existsSync(file)) return null
const raw = await fs.readFile(file, 'utf-8')
return JSON.parse(raw)
})
ipcMain.handle('api:project:write', async (_e, rootDir: string, data: unknown) => {
const dir = join(rootDir, 'xsinfo-data', 'apis')
const file = join(rootDir, 'xsinfo-data', 'project.json')
await fs.mkdir(dir, { recursive: true })
await fs.writeFile(file, JSON.stringify(data, null, 2), 'utf-8')
return true
})
ipcMain.handle('api:read', async (_e, rootDir: string, relativePath: string) => {
if (relativePath.includes('..')) throw new Error('非法路径')
const file = join(rootDir, 'xsinfo-data', relativePath.replace(/\\/g, '/'))
if (!existsSync(file)) return null
const raw = await fs.readFile(file, 'utf-8')
return JSON.parse(raw)
})
ipcMain.handle('api:write', async (_e, rootDir: string, relativePath: string, data: unknown) => {
if (relativePath.includes('..')) throw new Error('非法路径')
const file = join(rootDir, 'xsinfo-data', relativePath.replace(/\\/g, '/'))
await fs.mkdir(dirname(file), { recursive: true })
await fs.writeFile(file, JSON.stringify(data, null, 2), 'utf-8')
return true
})
// 在目录下新增空白接口文件
ipcMain.handle('api:create', async (_e, rootDir: string, relativeDir: string, apiData: any) => {
const dir = join(rootDir, 'xsinfo-data', 'apis', relativeDir.replace(/\\/g, '/'))
await fs.mkdir(dir, { recursive: true })
const id = `api-${Date.now()}`
const fileName = `${id}.json`
const file = join(dir, fileName)
const payload = {
id,
name: apiData.name || '新建接口',
method: apiData.method || 'GET',
url: apiData.url || '/',
...apiData
}
await fs.writeFile(file, JSON.stringify(payload, null, 2), 'utf-8')
return { id, filePath: `apis/${relativeDir}/${fileName}`.replace(/\\/g, '/') }
})
// 在工作区打开系统资源管理器
ipcMain.handle('shell:openDir', async (_e, dir: string) => {
await shell.openPath(dir)
return true
})
// 健康检查
ipcMain.handle('app:ping', async () => 'pong')