first commit
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
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')
|
||||
@@ -0,0 +1,34 @@
|
||||
import { contextBridge, ipcRenderer } from 'electron'
|
||||
|
||||
const api = {
|
||||
ping: () => ipcRenderer.invoke('app:ping'),
|
||||
workspace: {
|
||||
pick: () => ipcRenderer.invoke('workspace:pick') as Promise<string | null>,
|
||||
get: () => ipcRenderer.invoke('workspace:get') as Promise<string | null>,
|
||||
clear: () => ipcRenderer.invoke('workspace:clear') as Promise<null>,
|
||||
init: (root: string) => ipcRenderer.invoke('workspace:init', root) as Promise<true>
|
||||
},
|
||||
env: {
|
||||
read: (root: string) => ipcRenderer.invoke('env:read', root) as Promise<any>,
|
||||
write: (root: string, data: any) => ipcRenderer.invoke('env:write', root, data) as Promise<true>
|
||||
},
|
||||
apiProject: {
|
||||
read: (root: string) => ipcRenderer.invoke('api:project:read', root) as Promise<any>,
|
||||
write: (root: string, data: any) => ipcRenderer.invoke('api:project:write', root, data) as Promise<true>
|
||||
},
|
||||
api: {
|
||||
read: (root: string, relativePath: string) =>
|
||||
ipcRenderer.invoke('api:read', root, relativePath) as Promise<any>,
|
||||
write: (root: string, relativePath: string, data: any) =>
|
||||
ipcRenderer.invoke('api:write', root, relativePath, data) as Promise<true>,
|
||||
create: (root: string, relativeDir: string, apiData: any) =>
|
||||
ipcRenderer.invoke('api:create', root, relativeDir, apiData) as Promise<{ id: string; filePath: string }>
|
||||
},
|
||||
shell: {
|
||||
openDir: (dir: string) => ipcRenderer.invoke('shell:openDir', dir) as Promise<true>
|
||||
}
|
||||
}
|
||||
|
||||
contextBridge.exposeInMainWorld('xs', api)
|
||||
|
||||
export type XsApi = typeof api
|
||||
Reference in New Issue
Block a user