From 9c9997ae64eb4cfb0a28d9b9b4068c59038b97b6 Mon Sep 17 00:00:00 2001 From: jianghao <332515344@qq.com> Date: Tue, 24 Feb 2026 10:41:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9D=E5=A7=8B=E5=8C=96=20Timeline?= =?UTF-8?q?=20=E6=A1=8C=E9=9D=A2=E5=AE=A2=E6=88=B7=E7=AB=AF=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E6=A1=86=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加 Electron 应用基础结构,包括: - package.json 配置文件 - 主进程和预加载脚本 - README 文档 - 自动更新功能 - 系统托盘和菜单 - IPC 通信机制 --- README.md | 300 +++++++++++++++++++++++++++++++++++++++++ package.json | 72 ++++++++++ src/main/main.ts | 317 ++++++++++++++++++++++++++++++++++++++++++++ src/main/preload.ts | 65 +++++++++ 4 files changed, 754 insertions(+) create mode 100644 README.md create mode 100644 package.json create mode 100644 src/main/main.ts create mode 100644 src/main/preload.ts diff --git a/README.md b/README.md new file mode 100644 index 0000000..7d2c25c --- /dev/null +++ b/README.md @@ -0,0 +1,300 @@ +# Timeline 桌面客户端 + +> 记录生活中的每一个精彩时刻 - 桌面端应用 + +## 📖 项目简介 + +Timeline 桌面客户端是基于 Electron 构建的跨平台桌面应用,支持 Windows、macOS 和 Linux 系统。提供完整的离线支持、自动更新、系统托盘等功能。 + +## 🛠️ 技术栈 + +| 技术 | 版本 | 说明 | +|-----|------|------| +| Electron | 28.x | 跨平台桌面应用框架 | +| Electron Vite | 2.x | Electron 构建工具 | +| TypeScript | 5.x | 类型安全 | +| Electron Builder | 24.x | 打包与分发 | +| Electron Updater | 6.x | 自动更新 | + +## 📁 项目结构 + +``` +timeline-desktop/ +├── src/ +│ ├── main/ # 主进程代码 +│ │ ├── main.ts # 主进程入口 +│ │ └── preload.ts # Preload 脚本 +│ └── renderer/ # 渲染进程(可选) +├── build/ # 构建资源 +│ ├── icon.ico # Windows 图标 +│ ├── icon.icns # macOS 图标 +│ └── icon.png # Linux 图标 +├── release/ # 打包输出目录 +├── package.json +├── electron.vite.config.ts # Vite 配置 +└── tsconfig.json +``` + +## 🚀 快速开始 + +### 环境要求 + +- Node.js >= 18.x +- npm >= 9.x 或 pnpm >= 8.x + +### 安装依赖 + +```bash +# 使用 npm +npm install + +# 或使用 pnpm +pnpm install +``` + +### 开发模式 + +```bash +# 启动开发服务器 +npm run dev +``` + +开发模式下,应用会自动加载 `http://localhost:8080`(Web 前端开发服务器)。 + +### 构建打包 + +```bash +# 构建所有平台 +npm run build +npm run dist + +# 仅构建 Windows +npm run dist:win + +# 仅构建 macOS +npm run dist:mac + +# 仅构建 Linux +npm run dist:linux +``` + +## 📦 打包输出 + +打包完成后,安装包位于 `release/` 目录: + +``` +release/ +├── win-unpacked/ # Windows 免安装版 +├── Timeline Setup 1.0.0.exe # Windows 安装包 +├── Timeline-1.0.0.dmg # macOS 安装包 +├── Timeline-1.0.0.AppImage # Linux AppImage +└── timeline-desktop_1.0.0_amd64.deb # Linux DEB 包 +``` + +## ⚙️ 配置说明 + +### 应用配置 (package.json) + +```json +{ + "build": { + "appId": "com.timeline.desktop", + "productName": "Timeline", + "directories": { + "output": "release" + }, + "win": { + "icon": "build/icon.ico", + "target": ["nsis"] + }, + "mac": { + "icon": "build/icon.icns", + "target": ["dmg", "zip"] + }, + "linux": { + "icon": "build/icon.png", + "target": ["AppImage", "deb"] + } + } +} +``` + +### 环境变量 + +| 变量名 | 说明 | 默认值 | +|-------|------|-------| +| NODE_ENV | 运行环境 | development | +| API_URL | API 地址 | https://api.timeline.com | + +## 🔄 自动更新 + +### 配置自动更新 + +1. 在 `package.json` 中配置发布地址: + +```json +{ + "build": { + "publish": { + "provider": "github", + "owner": "your-github-username", + "repo": "timeline-desktop" + } + } +} +``` + +2. 在 GitHub 创建 Release 并上传安装包 + +3. 应用启动时会自动检查更新 + +### 更新流程 + +``` +应用启动 → 检查更新 → 发现新版本 → 提示用户 → 下载更新 → 安装更新 +``` + +## 🔌 IPC 通信 API + +### 渲染进程可用 API + +```typescript +// 获取应用版本 +const version = await window.electronAPI.getVersion(); + +// 获取操作系统平台 +const platform = await window.electronAPI.getPlatform(); + +// 检查更新 +const updateInfo = await window.electronAPI.checkUpdate(); + +// 下载更新 +await window.electronAPI.downloadUpdate(); + +// 安装更新 +await window.electronAPI.installUpdate(); + +// 打开外部链接 +await window.electronAPI.openExternal('https://timeline.com'); + +// 监听更新事件 +window.electronAPI.onUpdateAvailable((info) => { + console.log('发现新版本:', info.version); +}); + +window.electronAPI.onUpdateDownloaded((info) => { + console.log('更新已下载:', info.version); +}); +``` + +## 🖥️ 功能特性 + +### 系统托盘 + +- 最小化到托盘 +- 托盘菜单快捷操作 +- 点击托盘图标显示窗口 + +### 应用菜单 + +- 文件操作 +- 编辑功能(撤销/重做/复制/粘贴) +- 视图控制(缩放/全屏) +- 帮助链接 + +### 安全特性 + +- Context Isolation 启用 +- Node Integration 禁用 +- 安全的 Preload 脚本 + +## 🔐 签名与公证 + +### Windows 代码签名 + +```bash +# 设置环境变量 +set CSC_LINK=path/to/certificate.pfx +set CSC_KEY_PASSWORD=your-password + +# 打包时自动签名 +npm run dist:win +``` + +### macOS 公证 + +```bash +# 设置环境变量 +set APPLE_ID=your-apple-id +set APPLE_ID_PASSWORD=app-specific-password +set APPLE_TEAM_ID=your-team-id + +# 打包并公证 +npm run dist:mac +``` + +## 📋 后续拓展计划 + +### 短期计划 (1-2 个月) + +| 功能 | 描述 | 优先级 | +|-----|------|-------| +| 离线模式 | 支持离线浏览和编辑 | P0 | +| 本地存储 | 数据本地缓存 | P0 | +| 快捷键 | 全局快捷键支持 | P1 | +| 多窗口 | 支持多窗口操作 | P1 | +| 深色模式 | 跟随系统深色模式 | P2 | + +### 中期计划 (3-6 个月) + +| 功能 | 描述 | 优先级 | +|-----|------|-------| +| 批量导入 | 批量导入本地照片/视频 | P1 | +| 本地备份 | 数据本地备份与恢复 | P1 | +| 剪贴板监听 | 自动识别剪贴板图片 | P2 | +| 文件拖放 | 拖放文件快速上传 | P2 | +| 系统通知 | 原生系统通知 | P2 | + +### 长期计划 (6-12 个月) + +| 功能 | 描述 | 优先级 | +|-----|------|-------| +| 插件系统 | 支持第三方插件 | P2 | +| 主题定制 | 自定义主题和样式 | P3 | +| 多语言 | 国际化支持 | P3 | +| 云同步 | 数据云同步 | P3 | + +## 🐛 常见问题 + +### Q: 开发模式下白屏? + +检查 Web 前端开发服务器是否启动: + +```bash +cd ../timeline-frontend +pnpm dev +``` + +### Q: 打包后应用无法启动? + +检查 `main.ts` 中的资源路径是否正确: + +```typescript +// 生产环境应使用打包后的路径 +mainWindow.loadFile(path.join(__dirname, '../renderer/index.html')); +``` + +### Q: 自动更新不生效? + +1. 检查 `publish` 配置是否正确 +2. 确保 GitHub Release 已发布 +3. 检查网络连接 + +## 📞 技术支持 + +- 问题反馈: https://github.com/timeline/timeline-desktop/issues +- 文档地址: https://docs.timeline.com/desktop + +## 📄 许可证 + +MIT License diff --git a/package.json b/package.json new file mode 100644 index 0000000..b484a77 --- /dev/null +++ b/package.json @@ -0,0 +1,72 @@ +{ + "name": "timeline-desktop", + "version": "1.0.0", + "description": "Timeline 桌面客户端 - 记录生活中的每一个精彩时刻", + "main": "dist/main.js", + "author": "Timeline Team", + "license": "MIT", + "scripts": { + "dev": "electron-vite dev", + "build": "electron-vite build", + "preview": "electron-vite preview", + "pack": "electron-builder --dir", + "dist": "electron-builder", + "dist:win": "electron-builder --win", + "dist:mac": "electron-builder --mac", + "dist:linux": "electron-builder --linux" + }, + "dependencies": { + "electron-updater": "^6.1.7", + "electron-store": "^8.1.0", + "axios": "^1.6.0", + "date-fns": "^3.2.0" + }, + "devDependencies": { + "electron": "^28.1.0", + "electron-builder": "^24.9.1", + "electron-vite": "^2.0.0", + "vite": "^5.0.0", + "typescript": "^5.3.0", + "@types/node": "^20.10.0" + }, + "build": { + "appId": "com.timeline.desktop", + "productName": "Timeline", + "directories": { + "output": "release" + }, + "files": [ + "dist/**/*", + "package.json" + ], + "mac": { + "category": "public.app-category.lifestyle", + "icon": "build/icon.icns", + "target": ["dmg", "zip"] + }, + "win": { + "icon": "build/icon.ico", + "target": [ + { + "target": "nsis", + "arch": ["x64", "ia32"] + } + ] + }, + "linux": { + "icon": "build/icon.png", + "target": ["AppImage", "deb"] + }, + "nsis": { + "oneClick": false, + "allowToChangeInstallationDirectory": true, + "createDesktopShortcut": true, + "createStartMenuShortcut": true + }, + "publish": { + "provider": "github", + "owner": "timeline", + "repo": "timeline-desktop" + } + } +} diff --git a/src/main/main.ts b/src/main/main.ts new file mode 100644 index 0000000..2781c95 --- /dev/null +++ b/src/main/main.ts @@ -0,0 +1,317 @@ +/** + * Electron 主进程 + * + * 功能描述: + * Electron 应用的主进程,负责: + * - 创建和管理窗口 + * - 处理系统菜单 + * - 自动更新 + * - IPC 通信 + * + * @author Timeline Team + * @date 2024 + */ + +import { app, BrowserWindow, ipcMain, Menu, Tray, nativeImage, shell } from 'electron'; +import { autoUpdater } from 'electron-updater'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +// 主窗口引用 +let mainWindow: BrowserWindow | null = null; +let tray: Tray | null = null; + +// 是否为开发环境 +const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged; + +// API 基础地址 +const API_URL = isDev + ? 'http://localhost:8080' + : 'https://api.timeline.com'; + +/** + * 创建主窗口 + */ +function createMainWindow(): void { + mainWindow = new BrowserWindow({ + width: 1200, + height: 800, + minWidth: 800, + minHeight: 600, + title: 'Timeline', + icon: path.join(__dirname, '../build/icon.png'), + webPreferences: { + nodeIntegration: false, + contextIsolation: true, + preload: path.join(__dirname, 'preload.js'), + webSecurity: true, + }, + frame: true, + show: false, + backgroundColor: '#ffffff', + }); + + // 加载应用 + if (isDev) { + mainWindow.loadURL('http://localhost:8000'); + mainWindow.webContents.openDevTools(); + } else { + mainWindow.loadURL(API_URL); + } + + // 窗口准备好后显示 + mainWindow.once('ready-to-show', () => { + mainWindow?.show(); + }); + + // 处理外部链接 + mainWindow.webContents.setWindowOpenHandler(({ url }) => { + shell.openExternal(url); + return { action: 'deny' }; + }); + + // 窗口关闭事件 + mainWindow.on('close', (event) => { + if (process.platform === 'darwin') { + event.preventDefault(); + mainWindow?.hide(); + } + }); + + mainWindow.on('closed', () => { + mainWindow = null; + }); +} + +/** + * 创建系统托盘 + */ +function createTray(): void { + const iconPath = path.join(__dirname, '../build/tray.png'); + const icon = nativeImage.createFromPath(iconPath); + + tray = new Tray(icon.resize({ width: 16, height: 16 })); + + const contextMenu = Menu.buildFromTemplate([ + { + label: '显示主窗口', + click: () => { + mainWindow?.show(); + mainWindow?.focus(); + } + }, + { + label: '创建时刻', + click: () => { + mainWindow?.show(); + mainWindow?.webContents.send('navigate', '/story/create'); + } + }, + { type: 'separator' }, + { + label: '退出', + click: () => { + app.quit(); + } + } + ]); + + tray.setToolTip('Timeline'); + tray.setContextMenu(contextMenu); + + tray.on('click', () => { + mainWindow?.show(); + mainWindow?.focus(); + }); +} + +/** + * 创建应用菜单 + */ +function createMenu(): void { + const template: Electron.MenuItemConstructorOptions[] = [ + { + label: 'Timeline', + submenu: [ + { role: 'about' as const, label: '关于 Timeline' }, + { type: 'separator' }, + { + label: '偏好设置', + accelerator: 'CmdOrCtrl+,', + click: () => { + mainWindow?.webContents.send('navigate', '/settings'); + } + }, + { type: 'separator' }, + { role: 'quit' as const, label: '退出' } + ] + }, + { + label: '编辑', + submenu: [ + { role: 'undo' as const, label: '撤销' }, + { role: 'redo' as const, label: '重做' }, + { type: 'separator' }, + { role: 'cut' as const, label: '剪切' }, + { role: 'copy' as const, label: '复制' }, + { role: 'paste' as const, label: '粘贴' }, + { role: 'selectAll' as const, label: '全选' } + ] + }, + { + label: '视图', + submenu: [ + { role: 'reload' as const, label: '刷新' }, + { role: 'forceReload' as const, label: '强制刷新' }, + { type: 'separator' }, + { role: 'resetZoom' as const, label: '重置缩放' }, + { role: 'zoomIn' as const, label: '放大' }, + { role: 'zoomOut' as const, label: '缩小' }, + { type: 'separator' }, + { role: 'togglefullscreen' as const, label: '全屏' } + ] + }, + { + label: '窗口', + submenu: [ + { role: 'minimize' as const, label: '最小化' }, + { role: 'close' as const, label: '关闭' } + ] + }, + { + label: '帮助', + submenu: [ + { + label: '查看文档', + click: () => shell.openExternal('https://docs.timeline.com') + }, + { + label: '反馈问题', + click: () => shell.openExternal('https://github.com/timeline/issues') + }, + { type: 'separator' }, + { + label: '检查更新', + click: () => checkForUpdates() + } + ] + } + ]; + + // 开发环境添加开发者工具菜单 + if (isDev) { + template[2].submenu?.push( + { type: 'separator' }, + { role: 'toggleDevTools' as const, label: '开发者工具' } + ); + } + + const menu = Menu.buildFromTemplate(template); + Menu.setApplicationMenu(menu); +} + +/** + * 检查更新 + */ +async function checkForUpdates(): Promise { + try { + const result = await autoUpdater.checkForUpdates(); + if (result) { + console.log('发现新版本:', result.updateInfo.version); + } + } catch (error) { + console.error('检查更新失败:', error); + } +} + +/** + * 设置自动更新 + */ +function setupAutoUpdater(): void { + autoUpdater.autoDownload = false; + autoUpdater.autoInstallOnAppQuit = true; + + autoUpdater.on('update-available', (info) => { + mainWindow?.webContents.send('update-available', info); + }); + + autoUpdater.on('update-downloaded', (info) => { + mainWindow?.webContents.send('update-downloaded', info); + }); + + autoUpdater.on('error', (error) => { + console.error('更新错误:', error); + }); +} + +/** + * 设置 IPC 通信 + */ +function setupIpc(): void { + // 获取应用版本 + ipcMain.handle('get-version', () => { + return app.getVersion(); + }); + + // 获取平台 + ipcMain.handle('get-platform', () => { + return process.platform; + }); + + // 检查更新 + ipcMain.handle('check-update', async () => { + try { + const result = await autoUpdater.checkForUpdates(); + return result?.updateInfo; + } catch (error) { + return null; + } + }); + + // 下载更新 + ipcMain.handle('download-update', async () => { + await autoUpdater.downloadUpdate(); + }); + + // 安装更新 + ipcMain.handle('install-update', () => { + autoUpdater.quitAndInstall(); + }); + + // 打开外部链接 + ipcMain.handle('open-external', (_, url: string) => { + shell.openExternal(url); + }); +} + +// 应用准备就绪 +app.whenReady().then(() => { + createMainWindow(); + createMenu(); + createTray(); + setupAutoUpdater(); + setupIpc(); + + // macOS 激活应用 + app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) { + createMainWindow(); + } else { + mainWindow?.show(); + } + }); +}); + +// 所有窗口关闭时退出(Windows/Linux) +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit(); + } +}); + +// 应用退出前清理 +app.on('before-quit', () => { + tray?.destroy(); +}); diff --git a/src/main/preload.ts b/src/main/preload.ts new file mode 100644 index 0000000..cd7dcd5 --- /dev/null +++ b/src/main/preload.ts @@ -0,0 +1,65 @@ +/** + * Electron Preload 脚本 + * + * 功能描述: + * 在渲染进程中安全地暴露主进程 API。 + * 使用 contextBridge 确保安全性。 + * + * @author Timeline Team + * @date 2024 + */ + +import { contextBridge, ipcRenderer } from 'electron'; + +/** + * 暴露给渲染进程的 API + */ +contextBridge.exposeInMainWorld('electronAPI', { + // 应用信息 + getVersion: () => ipcRenderer.invoke('get-version'), + getPlatform: () => ipcRenderer.invoke('get-platform'), + + // 更新相关 + checkUpdate: () => ipcRenderer.invoke('check-update'), + downloadUpdate: () => ipcRenderer.invoke('download-update'), + installUpdate: () => ipcRenderer.invoke('install-update'), + + // 更新事件监听 + onUpdateAvailable: (callback: (info: any) => void) => { + ipcRenderer.on('update-available', (_, info) => callback(info)); + }, + onUpdateDownloaded: (callback: (info: any) => void) => { + ipcRenderer.on('update-downloaded', (_, info) => callback(info)); + }, + + // 外部链接 + openExternal: (url: string) => ipcRenderer.invoke('open-external', url), + + // 导航事件 + onNavigate: (callback: (path: string) => void) => { + ipcRenderer.on('navigate', (_, path) => callback(path)); + }, + + // 移除监听器 + removeAllListeners: (channel: string) => { + ipcRenderer.removeAllListeners(channel); + } +}); + +// 类型声明 +declare global { + interface Window { + electronAPI: { + getVersion: () => Promise; + getPlatform: () => Promise; + checkUpdate: () => Promise; + downloadUpdate: () => Promise; + installUpdate: () => Promise; + onUpdateAvailable: (callback: (info: any) => void) => void; + onUpdateDownloaded: (callback: (info: any) => void) => void; + openExternal: (url: string) => Promise; + onNavigate: (callback: (path: string) => void) => void; + removeAllListeners: (channel: string) => void; + }; + } +}