feat: implement auto-update functionality with GitHub integration

- Added AutoUpdater class to manage update checks and downloads.
- Integrated auto-updater in the main process after the main window is created.
- Enhanced preload script to expose update-related IPC channels.
- Created UpdateDialog component to handle user interactions for updates.
- Implemented state management for update status using Pinia store.
- Added localization support for update messages in multiple languages.
- Documented auto-update configuration steps for private GitHub repositories.
- Created .env.example for GitHub token configuration.
This commit is contained in:
zhanggy committed 2025-10-28 22:53:26 +08:00
1 parent ac5488a21d
commit 0eb2dcf8c2
22 files changed
+882 -92

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
# GitHub Personal Access Token for private repository access
# Required for auto-updates from private repositories
GH_TOKEN=
# For private repositories, you might also need:
# GITHUB_TOKEN= # Alternative token name that some tools might use
+1
View File
@@ -31,6 +31,7 @@ Quick Start Templates for **[Vite](https://vitejs.dev)** + **[Vue 3](https://vue
- ⚡️ Basic layout manager
- ⚡️ Global state management through the Pinia store
- ⚡️ Quick support through the GitHub community and official documentation
- ⚡️ Auto-update support (including private repositories with proper authentication)
## [Documentation (Getting Started)](https://vutron.cdget.com/installation-and-build/getting-started)
+11 -2
View File
@@ -2,12 +2,23 @@
const dotenv = require('dotenv')
const packageJson = require('../../package.json')
dotenv.config()
const baseConfig = {
productName: packageJson.name,
appId: packageJson.appId,
asar: true,
extends: null,
compression: 'maximum',
publish: [
{
provider: 'github',
owner: 'Jasery',
repo: 'gdt-tool',
releaseType: 'release',
token: process.env.GH_TOKEN || '' // 可选:从环境变量获取 GitHub Token,私有仓库必需
}
],
artifactName: '${productName} ${version}_${arch}.${ext}',
directories: {
output: './release/${version}'
@@ -90,8 +101,6 @@ const baseConfig = {
}
}
dotenv.config()
baseConfig.copyright = `ⓒ ${new Date().getFullYear()} $\{author}`
baseConfig.files = [
/* A list of files not to be included in the build. */
+61
View File
@@ -0,0 +1,61 @@
# 自动更新配置
本项目使用 `electron-updater` 库实现自动更新功能,支持从私有 GitHub 仓库获取更新。
## 私有仓库配置
要启用私有仓库的自动更新功能,请按以下步骤操作:
### 1. 创建 GitHub Personal Access Token
1. 访问 GitHub 设置页面 (https://github.com/settings/tokens)
2. 点击 "Generate new token" (Classic tokens)
3. 选择适当的权限(至少需要 `repo` 权限来访问私有仓库)
4. 生成并复制 token
### 2. 配置环境变量
在项目根目录创建 `.env` 文件:
```bash
cp .env.example .env
```
编辑 `.env` 文件,添加你的 GitHub token:
```env
GH_TOKEN=your_github_personal_access_token
```
### 3. 构建配置
构建配置文件 `buildAssets/builder/config.js` 已经配置为使用环境变量中的 token:
```javascript
publish: [
{
provider: 'github',
owner: 'your_username',
repo: 'your_repository',
releaseType: 'release',
token: process.env.GH_TOKEN || '' // 从环境变量获取
}
]
```
## 发布更新
1. 更新 `package.json` 中的版本号
2. 提交更改并推送至 GitHub
3. 创建新的 GitHub Release
4. 运行构建命令:
```bash
pnpm build:all
```
## 注意事项
- 请确保 GitHub token 权限最小化,仅授予必要的访问权限
- 对于分发给公众的私有应用,用户需要有访问仓库的权限才能下载更新
- 在 CI/CD 环境中,确保在环境变量中配置了相应的 token
+1 -1
View File
@@ -10,7 +10,7 @@
"private": true,
"repository": {
"type": "git",
"url": "https://github.com/jooy2/vutron.git"
"url": "https://github.com/Jasery/gdt-tool.git"
},
"bugs": {
"url": "https://github.com/jooy2/vutron/issues"
+89
View File
@@ -0,0 +1,89 @@
import { dialog, ipcMain, app } from 'electron'
import { autoUpdater } from 'electron-updater'
import log from 'electron-log/main'
// 配置日志
autoUpdater.logger = log
log.transports.file.level = 'info'
export class AutoUpdater {
private mainWindow: Electron.BrowserWindow | null = null
constructor(mainWindow: Electron.BrowserWindow) {
this.mainWindow = mainWindow
// 配置更新检查间隔
autoUpdater.autoDownload = false
autoUpdater.allowPrerelease = false
autoUpdater.channel = 'latest'
// 设置事件监听器
this.setupEventListeners()
}
private setupEventListeners() {
// 检查更新
autoUpdater.on('checking-for-update', () => {
log.info('正在检查更新...')
this.mainWindow?.webContents.send('update-checking')
})
// 发现新版本
autoUpdater.on('update-available', (info) => {
log.info('发现新版本:', info.version)
this.mainWindow?.webContents.send('update-available', info)
})
// 没有新版本
autoUpdater.on('update-not-available', (info) => {
log.info('当前已是最新版本')
this.mainWindow?.webContents.send('update-not-available', info)
})
// 更新下载进度
autoUpdater.on('download-progress', (progress) => {
log.info('下载进度:', progress.percent, '%')
this.mainWindow?.webContents.send('update-download-progress', progress)
})
// 更新下载完成
autoUpdater.on('update-downloaded', (info) => {
log.info('更新下载完成')
this.mainWindow?.webContents.send('update-downloaded', info)
// 提示用户是否重启应用安装更新
dialog.showMessageBox({
type: 'info',
title: '应用更新',
message: `新版本 ${info.version} 已下载`,
detail: '是否现在重启应用以安装更新?',
buttons: ['稍后重启', '立即重启'],
defaultId: 1,
cancelId: 0
}).then((result) => {
if (result.response === 1) {
// 安装更新并重启应用
setImmediate(() => {
autoUpdater.quitAndInstall()
})
}
})
})
// 更新错误
autoUpdater.on('error', (error) => {
log.error('自动更新出错:', error)
this.mainWindow?.webContents.send('update-error', error)
})
}
public checkForUpdates() {
log.info('开始检查更新...')
autoUpdater.checkForUpdates()
}
public downloadUpdate() {
log.info('开始下载更新...')
autoUpdater.downloadUpdate()
}
}
+80
View File
@@ -8,6 +8,7 @@ import {
ensureDirSync,
copyFileSync
} from 'fs-extra'
import { autoUpdater } from 'electron-updater'
/*
* IPC Communications
@@ -222,5 +223,84 @@ export default class IPCs {
}
}
)
// 检查更新
ipcMain.handle('msgCheckForUpdates', async () => {
if (Constants.IS_DEV_ENV) {
// 在开发环境中模拟更新检查
console.log('开发环境中模拟检查更新...')
return { updateAvailable: false, currentVersion: Constants.APP_VERSION }
}
return new Promise((resolve) => {
// 监听更新事件
const removeListeners = () => {
autoUpdater.removeListener('update-available', onUpdateAvailable)
autoUpdater.removeListener('update-not-available', onUpdateNotAvailable)
autoUpdater.removeListener('error', onError)
}
const onUpdateAvailable = () => {
removeListeners()
resolve({ updateAvailable: true, currentVersion: Constants.APP_VERSION })
}
const onUpdateNotAvailable = () => {
removeListeners()
resolve({ updateAvailable: false, currentVersion: Constants.APP_VERSION })
}
const onError = (error: Error) => {
removeListeners()
console.error('检查更新时出错:', error)
resolve({ updateAvailable: false, currentVersion: Constants.APP_VERSION, error: error.message })
}
// 添加事件监听器
autoUpdater.once('update-available', onUpdateAvailable)
autoUpdater.once('update-not-available', onUpdateNotAvailable)
autoUpdater.once('error', onError)
// 开始检查更新
autoUpdater.checkForUpdates()
})
})
// 下载更新
ipcMain.handle('msgDownloadUpdate', async () => {
if (Constants.IS_DEV_ENV) {
// 在开发环境中模拟下载更新
console.log('开发环境中模拟下载更新...')
return { success: true }
}
try {
await autoUpdater.downloadUpdate()
return { success: true }
} catch (error) {
console.error('下载更新时出错:', error)
return { success: false, error: error instanceof Error ? error.message : String(error) }
}
})
// 安装更新
ipcMain.handle('msgInstallUpdate', async () => {
if (Constants.IS_DEV_ENV) {
// 在开发环境中模拟安装更新
console.log('开发环境中模拟安装更新...')
return { success: true }
}
try {
// 立即退出并安装更新
setImmediate(() => {
autoUpdater.quitAndInstall()
})
return { success: true }
} catch (error) {
console.error('安装更新时出错:', error)
return { success: false, error: error instanceof Error ? error.message : String(error) }
}
})
}
}
+7
View File
@@ -3,9 +3,11 @@ import Constants from './utils/Constants'
import { createErrorWindow, createMainWindow } from './MainRunner'
import log from 'electron-log/main'
import { join } from 'path'
import { AutoUpdater } from './AutoUpdater'
let mainWindow
let errorWindow
let autoUpdater: AutoUpdater | null = null
const initializeMainLogger = () => {
log.initialize({
@@ -52,6 +54,11 @@ app.on('ready', async () => {
initializeMainLogger()
mainWindow = await createMainWindow()
// Initialize auto updater after main window is created
if (!Constants.IS_DEV_ENV) {
autoUpdater = new AutoUpdater(mainWindow)
}
})
app.on('activate', async () => {
+12 -2
View File
@@ -14,9 +14,19 @@ const mainAvailChannels: string[] = [
'msgLoadData',
'msgSaveData',
'msgExecuteTemplate',
'msgFindFilesInFolder'
'msgFindFilesInFolder',
'msgCheckForUpdates',
'msgDownloadUpdate',
'msgInstallUpdate'
]
const rendererAvailChannels: string[] = [
'update-checking',
'update-available',
'update-not-available',
'update-download-progress',
'update-downloaded',
'update-error'
]
const rendererAvailChannels: string[] = []
contextBridge.exposeInMainWorld('mainApi', {
send: (channel: string, ...data: any[]): void => {
+67
View File
@@ -1,6 +1,72 @@
<script setup lang="tsx">
import { DefaultLayout } from '@/renderer/components/layout'
import GlobalDialog from '@/renderer/components/GlobalDialog.vue'
import UpdateDialog from '@/renderer/components/UpdateDialog.vue'
import { onMounted } from 'vue'
import { useAppStore } from '@/renderer/store/app'
// 使用应用商店来管理应用状态
const appStore = useAppStore()
onMounted(() => {
// 在应用启动时检查更新
setTimeout(() => {
checkForUpdates()
}, 3000) // 延迟3秒后检查更新,避免影响应用启动速度
// 监听主进程发来的更新相关事件
window.mainApi.on('update-available', (_, info) => {
appStore.setUpdateStatus('available', info.version)
})
window.mainApi.on('update-not-available', (_, info) => {
appStore.setUpdateStatus('not-available', info.version)
})
window.mainApi.on('update-error', (_, error) => {
appStore.setUpdateStatus('error', undefined, error.message || String(error))
})
window.mainApi.on('update-download-progress', (_, progress) => {
// 这个事件通过pinia状态管理,不需要在这里单独处理
console.log('下载进度:', progress)
})
window.mainApi.on('update-downloaded', (_, info) => {
appStore.setUpdateStatus('downloaded')
console.log('更新已下载,准备安装:', info)
})
})
const checkForUpdates = async () => {
try {
// 显示检查更新状态
appStore.setUpdateStatus('checking')
// 调用主进程检查更新
const result = await window.mainApi.invoke('msgCheckForUpdates')
if (result.error) {
console.error('检查更新时出错:', result.error)
appStore.setUpdateStatus('error', result.error)
return
}
if (result.updateAvailable) {
// 发现新版本,显示更新提示
appStore.setUpdateStatus('available', result.currentVersion)
} else {
// 当前已是最新版本
appStore.setUpdateStatus('not-available', result.currentVersion)
}
} catch (error) {
console.error('检查更新时出错:', error)
appStore.setUpdateStatus(
'error',
error instanceof Error ? error.message : String(error)
)
}
}
</script>
<template>
@@ -8,6 +74,7 @@ import GlobalDialog from '@/renderer/components/GlobalDialog.vue'
<router-view />
</DefaultLayout>
<GlobalDialog />
<UpdateDialog />
</template>
<style>
+196
View File
@@ -0,0 +1,196 @@
<template>
<v-dialog
v-model="dialogVisible"
max-width="500"
persistent
:scrim="false"
>
<v-card v-if="updateStatus.status === 'available'">
<v-card-title class="headline">
{{ t('update.newVersionAvailable') }}
</v-card-title>
<v-card-text>
<div>{{ t('update.currentVersion') }}: {{ updateStatus.currentVersion }}</div>
<div>{{ t('update.latestVersion') }}: {{ latestVersion }}</div>
<div class="mt-3">{{ t('update.updateDescription') }}</div>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="primary"
variant="text"
@click="skipUpdate"
>
{{ t('update.skipUpdate') }}
</v-btn>
<v-btn
color="primary"
variant="flat"
@click="downloadUpdate"
:loading="isDownloading"
>
{{ t('update.downloadUpdate') }}
</v-btn>
</v-card-actions>
</v-card>
<v-card v-else-if="updateStatus.status === 'downloading'">
<v-card-title class="headline">
{{ t('update.downloadingUpdate') }}
</v-card-title>
<v-card-text>
<v-progress-linear
:model-value="downloadProgress"
color="primary"
height="25"
rounded
>
<strong>{{ Math.round(downloadProgress) }}%</strong>
</v-progress-linear>
<div class="mt-3">{{ t('update.downloadProgressText') }}</div>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="primary"
variant="text"
@click="cancelDownload"
:disabled="isDownloadComplete"
>
{{ t('common.cancel') }}
</v-btn>
</v-card-actions>
</v-card>
<v-card v-else-if="updateStatus.status === 'downloaded'">
<v-card-title class="headline">
{{ t('update.updateDownloaded') }}
</v-card-title>
<v-card-text>
{{ t('update.updateReadyToInstall') }}
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="primary"
variant="text"
@click="laterInstall"
>
{{ t('update.laterInstall') }}
</v-btn>
<v-btn
color="primary"
variant="flat"
@click="installUpdate"
>
{{ t('update.installNow') }}
</v-btn>
</v-card-actions>
</v-card>
<v-card v-else-if="updateStatus.status === 'error'">
<v-card-title class="headline">
{{ t('update.updateError') }}
</v-card-title>
<v-card-text>
{{ updateStatus.error }}
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="primary"
variant="flat"
@click="closeDialog"
>
{{ t('common.ok') }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script setup lang="ts">
import { computed, onMounted, onUnmounted } from 'vue'
import { useAppStore, UpdateStatus } from '@/renderer/store/app'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const appStore = useAppStore()
// 从store获取状态
const updateStatus = computed(() => appStore.updateStatus)
const downloadProgress = computed(() => appStore.downloadProgress)
// 对话框显示状态
const dialogVisible = computed({
get: () => {
return appStore.updateDialogVisible ||
updateStatus.value.status === 'available' ||
updateStatus.value.status === 'downloading' ||
updateStatus.value.status === 'downloaded' ||
(updateStatus.value.status === 'error' && !!updateStatus.value.error)
},
set: (value: boolean) => {
if (value) {
appStore.showUpdateDialog()
} else {
appStore.hideUpdateDialog()
}
}
})
const latestVersion = computed(() => {
// 这里应该从API获取最新版本号,暂时使用固定值
return 'v' + (updateStatus.value.currentVersion || '1.0.0').replace(/^v?/, '').split('.').map((n, i) =>
i === 2 ? (parseInt(n) + 1).toString() : n
).join('.')
})
const isDownloading = computed(() => updateStatus.value.status === 'downloading')
const isDownloadComplete = computed(() => downloadProgress.value >= 100)
// 方法
const downloadUpdate = async () => {
await appStore.startUpdateDownload()
}
const skipUpdate = () => {
appStore.setUpdateStatus('not-available', updateStatus.value.currentVersion)
dialogVisible.value = false
}
const cancelDownload = () => {
// 取消下载逻辑
appStore.setUpdateStatus('not-available', updateStatus.value.currentVersion)
dialogVisible.value = false
}
const laterInstall = () => {
dialogVisible.value = false
}
const installUpdate = () => {
// 安装更新,这需要触发quitAndInstall
window.mainApi.invoke('msgInstallUpdate')
dialogVisible.value = false
}
const closeDialog = () => {
appStore.setUpdateStatus('idle')
dialogVisible.value = false
}
// 监听更新下载进度
onMounted(() => {
// 监听主进程发来的更新相关事件
window.mainApi.on('update-download-progress', (_, progress) => {
appStore.setDownloadProgress(progress.percent)
})
})
onUnmounted(() => {
// 移除事件监听器
window.mainApi.off('update-download-progress', () => {})
})
</script>
</template>
+35 -14
View File
@@ -1,21 +1,42 @@
{
"desc": {
"welcome-title": "Hallo Vutron! Alles ist bereit.",
"welcome-desc": "Sie können jetzt plattformübergreifende Webanwendungen schreiben. Sehen Sie Ihre Änderungen in Echtzeit und erstellen Sie mit einem einzigen Befehl auf mehreren Plattformen. Wenn Sie damit noch nicht vertraut sind, können Sie Hilfe von der Dokumentationsseite unten erhalten oder vorgefertigten Beispielcode ausführen.",
"second-desc": "Auf den zweiten Bildschirm verschoben! Hier gibt es keine!",
"selected-file": "Ausgewählte Datei: {filePath}."
"welcome-title": "안녕하세요! 템플릿 제너레이터",
"welcome-desc": "이제 파일 템플릿을 생성하고 관리할 수 있습니다. 실시간으로 변경 사항을 확인하고 템플릿을 사용하여 파일과 디렉토리를 일괄 생성할 수 있습니다.",
"second-desc": "두 번째 화면으로 이동했습니다! 여기에는 아무것도 없습니다!",
"selected-file": "선택된 파일: {filePath}."
},
"title": {
"main": "Hauptbildschirm",
"second": "Zweiter Bildschirm",
"error": "Ein Fehler ist aufgetreten"
"main": "템플릿 제너레이터",
"second": "두 번째 화면",
"error": "알 수 없는 오류",
"templates": "템플릿 목록",
"create-template": "템플릿 생성"
},
"menu": {
"change-theme": "Thema wechseln",
"change-language": "Sprache ändern",
"increase-count": "Zählerstand um 1 erhöhen",
"documentation": "dokumentieren",
"github": "Quellcode",
"open-file": "Öffnen Sie eine Textdatei"
"change-theme": "테마 변경",
"change-language": "언어 변경",
"increase-count": "카운트 1 증가",
"documentation": "문서",
"github": "소스 코드",
"open-file": "텍스트 파일 열기"
},
"update": {
"newVersionAvailable": "Neue Version verfügbar",
"currentVersion": "Aktuelle Version",
"latestVersion": "Neueste Version",
"updateDescription": "Eine neue Version der Anwendung ist verfügbar. Möchten Sie das Update herunterladen und installieren?",
"skipUpdate": "Dieses Update überspringen",
"downloadUpdate": "Update herunterladen",
"downloadingUpdate": "Update wird heruntergeladen",
"downloadProgressText": "Das Update wird heruntergeladen. Bitte warten...",
"updateReadyToInstall": "Das Update wurde heruntergeladen und ist zur Installation bereit. Die Anwendung wird nach der Installation neu gestartet.",
"laterInstall": "Später installieren",
"installNow": "Jetzt installieren",
"updateError": "Update-Fehler",
"updateDownloaded": "Update heruntergeladen"
},
"common": {
"ok": "OK",
"cancel": "Abbrechen"
}
}
}
+19
View File
@@ -19,5 +19,24 @@
"documentation": "Documentation",
"github": "Source Code",
"open-file": "Open a text file"
},
"update": {
"newVersionAvailable": "New Version Available",
"currentVersion": "Current Version",
"latestVersion": "Latest Version",
"updateDescription": "A new version of the application is available. Would you like to download and install the update?",
"skipUpdate": "Skip This Update",
"downloadUpdate": "Download Update",
"downloadingUpdate": "Downloading Update",
"downloadProgressText": "The update is being downloaded. Please wait...",
"updateReadyToInstall": "The update has been downloaded and is ready to install. The application will restart after installation.",
"laterInstall": "Install Later",
"installNow": "Install Now",
"updateError": "Update Error",
"updateDownloaded": "Update Downloaded"
},
"common": {
"ok": "OK",
"cancel": "Cancel"
}
}
+33 -12
View File
@@ -1,21 +1,42 @@
{
"desc": {
"welcome-title": "Hola Vutron! Todo está listo.",
"welcome-desc": "Ahora puede escribir aplicaciones web multiplataforma. Vea sus cambios en tiempo real y cree en múltiples plataformas con un solo comando. Si aún no está familiarizado con él, puede obtener ayuda en la página de documentación a continuación o ejecutar un código de ejemplo preescrito.",
"second-desc": "¡Se ha movido a la segunda pantalla! ¡Aquí no hay nada!",
"welcome-title": "¡Hola! Generador de plantillas",
"welcome-desc": "Ahora puedes crear y administrar plantillas de archivos. Vea los cambios en tiempo real y genere archivos y directorios por lotes utilizando plantillas.",
"second-desc": "¡Has pasado a la segunda pantalla! No hay nada aquí!",
"selected-file": "Archivo seleccionado: {filePath}."
},
"title": {
"main": "Pantalla Principal",
"second": "Segunda Pantalla",
"error": "Se produjo un error"
"main": "Generador de plantillas",
"second": "Segunda pantalla",
"error": "Error desconocido",
"templates": "Lista de plantillas",
"create-template": "Crear plantilla"
},
"menu": {
"change-theme": "Cambiar de Tema",
"change-language": "Cambiar Idioma",
"increase-count": "Aumentar la cuenta en 1",
"change-theme": "Cambiar tema",
"change-language": "Cambiar idioma",
"increase-count": "Incrementar contador en 1",
"documentation": "Documentación",
"github": "Código Fuente",
"open-file": "Abrir un archivo de texto"
"github": "Código fuente",
"open-file": "Abrir archivo de texto"
},
"update": {
"newVersionAvailable": "Nueva versión disponible",
"currentVersion": "Versión actual",
"latestVersion": "Última versión",
"updateDescription": "Una nueva versión de la aplicación está disponible. ¿Desea descargar e instalar la actualización?",
"skipUpdate": "Omitir esta actualización",
"downloadUpdate": "Descargar actualización",
"downloadingUpdate": "Descargando actualización",
"downloadProgressText": "La actualización se está descargando. Por favor, espere...",
"updateReadyToInstall": "La actualización se ha descargado y está lista para instalarse. La aplicación se reiniciará después de la instalación.",
"laterInstall": "Instalar más tarde",
"installNow": "Instalar ahora",
"updateError": "Error de actualización",
"updateDownloaded": "Actualización descargada"
},
"common": {
"ok": "Aceptar",
"cancel": "Cancelar"
}
}
}
+31 -10
View File
@@ -1,21 +1,42 @@
{
"desc": {
"welcome-title": "Bonjour Vutron ! Tout est prêt.",
"welcome-desc": "Vous pouvez maintenant écrire des applications Web multiplateformes. Visualisez vos modifications en temps réel et créez sur plusieurs plates-formes avec une seule commande. Si vous ne le connaissez pas encore, vous pouvez obtenir de l'aide sur la page de documentation ci-dessous ou exécuter un exemple de code pré-écrit.",
"second-desc": "Déplacé vers le deuxième écran! Il n'y en a pas ici !",
"selected-file": "Fichier sélectionné: {filePath}."
"welcome-title": "Bonjour ! Générateur de modèles",
"welcome-desc": "Vous pouvez maintenant créer et gérer des modèles de fichiers. Visualisez les changements en temps réel et générez des fichiers et des répertoires par lots à l'aide de modèles.",
"second-desc": "Vous êtes passé à la deuxième écran ! Il n'y a rien ici !",
"selected-file": "Fichier sélectionné : {filePath}."
},
"title": {
"main": "écran principal",
"main": "Générateur de modèles",
"second": "Deuxième écran",
"error": "Erreur est survenue"
"error": "Erreur inconnue",
"templates": "Liste des modèles",
"create-template": "Créer un modèle"
},
"menu": {
"change-theme": "Change le thème",
"change-theme": "Changer de thème",
"change-language": "Changer de langue",
"increase-count": "Augmenter le nombre de 1",
"documentation": "Document",
"increase-count": "Incrémenter le compteur de 1",
"documentation": "Documentation",
"github": "Code source",
"open-file": "Ouvrir un fichier texte"
},
"update": {
"newVersionAvailable": "Nouvelle version disponible",
"currentVersion": "Version actuelle",
"latestVersion": "Dernière version",
"updateDescription": "Une nouvelle version de l'application est disponible. Voulez-vous télécharger et installer la mise à jour ?",
"skipUpdate": "Ignorer cette mise à jour",
"downloadUpdate": "Télécharger la mise à jour",
"downloadingUpdate": "Téléchargement de la mise à jour",
"downloadProgressText": "La mise à jour est en cours de téléchargement. Veuillez patienter...",
"updateReadyToInstall": "La mise à jour a été téléchargée et est prête à être installée. L'application redémarrera après l'installation.",
"laterInstall": "Installer plus tard",
"installNow": "Installer maintenant",
"updateError": "Erreur de mise à jour",
"updateDownloaded": "Mise à jour téléchargée"
},
"common": {
"ok": "OK",
"cancel": "Annuler"
}
}
}
+32 -11
View File
@@ -1,21 +1,42 @@
{
"desc": {
"welcome-title": "こんにちはVutron! すべての準備が完了しました。",
"welcome-desc": "これで、クロスプラットフォームのWebアプリケーションを作成できます。 変更内容をリアルタイムで確認し、一度のコマンドで複数のプラットフォームにビルドします。 まだ慣れていない場合は、以下のドキュメントページでヘルプを入手するか、事前に作成されたサンプルコードを実行してみてください。",
"welcome-title": "こんにちは! テンプレートジェネレーター",
"welcome-desc": "ファイルテンプレートを作成および管理できるようになりました。変更をリアルタイムで確認し、テンプレートを使用してファイルやディレクトリを一括生成できます。",
"second-desc": "2番目の画面に移動しました! ここには何もありません!",
"selected-file": "選択したファイル: {filePath}."
"selected-file": "選択されたファイル: {filePath}."
},
"title": {
"main": "メイン画面",
"second": "2番目の画面",
"error": "エラー発生"
"main": "テンプレートジェネレーター",
"second": "第二画面",
"error": "不明なエラー",
"templates": "テンプレート一覧",
"create-template": "テンプレート作成"
},
"menu": {
"change-theme": "テーマの変更",
"change-language": "言語の変更",
"increase-count": "カウント1増加",
"documentation": "文書",
"change-theme": "テーマ変更",
"change-language": "言語変更",
"increase-count": "カウントを1増やす",
"documentation": "ドキュメント",
"github": "ソースコード",
"open-file": "テキストファイルを開く"
},
"update": {
"newVersionAvailable": "新しいバージョンが利用可能",
"currentVersion": "現在のバージョン",
"latestVersion": "最新バージョン",
"updateDescription": "アプリケーションの新しいバージョンが利用可能です。アップデートをダウンロードしてインストールしますか?",
"skipUpdate": "このアップデートをスキップ",
"downloadUpdate": "アップデートをダウンロード",
"downloadingUpdate": "アップデートをダウンロード中",
"downloadProgressText": "アップデートをダウンロードしています。しばらくお待ちください...",
"updateReadyToInstall": "アップデートがダウンロードされ、インストールの準備ができました。インストール後、アプリケーションが再起動されます。",
"laterInstall": "後でインストール",
"installNow": "今すぐインストール",
"updateError": "アップデートエラー",
"updateDownloaded": "アップデートがダウンロードされました"
},
"common": {
"ok": "OK",
"cancel": "キャンセル"
}
}
}
+30 -9
View File
@@ -1,21 +1,42 @@
{
"desc": {
"welcome-title": "안녕 Vutron! 모든 준비를 마쳤습니다.",
"welcome-desc": "이제 크로스플랫폼 웹 애플리케이션을 작성할 수 있습니다. 변경사항을 실시간으로 확인하고 한 번의 명령으로 여러 플랫폼으로 빌드하세요. 아직 익숙하지 않다면, 아래 문서 페이지에서 도움을 받아보거나 사전에 작성된 예제 코드를 실행해볼 수 있습니다.",
"second-desc": "두번째 화면으로 이동하였습니다! 여기는 아무 것도 없습니다!",
"selected-file": "선택한 파일: {filePath}."
"welcome-title": "안녕하세요! 템플릿 제너레이터",
"welcome-desc": "이제 파일 템플릿을 생성하고 관리할 수 있습니다. 실시간으로 변경 사항을 확인하고 템플릿을 사용하여 파일과 디렉토리를 일괄 생성할 수 있습니다.",
"second-desc": "두 번째 화면으로 이동했습니다! 여기에는 아무것도 없습니다!",
"selected-file": "선택된 파일: {filePath}."
},
"title": {
"main": "메인 화면",
"second": "두번째 화면",
"error": "에러 발생"
"main": "템플릿 제너레이터",
"second": "두 번째 화면",
"error": "알 수 없는 오류",
"templates": "템플릿 목록",
"create-template": "템플릿 생성"
},
"menu": {
"change-theme": "테마 변경",
"change-language": "언어 변경",
"increase-count": "카운트 1 증가",
"documentation": "문서",
"github": "소스코드",
"github": "소스 코드",
"open-file": "텍스트 파일 열기"
},
"update": {
"newVersionAvailable": "새로운 버전 사용 가능",
"currentVersion": "현재 버전",
"latestVersion": "최신 버전",
"updateDescription": "애플리케이션의 새로운 버전을 사용할 수 있습니다. 업데이트를 다운로드하고 설치하시겠습니까?",
"skipUpdate": "이 업데이트 건너뛰기",
"downloadUpdate": "업데이트 다운로드",
"downloadingUpdate": "업데이트 다운로드 중",
"downloadProgressText": "업데이트를 다운로드하는 중입니다. 잠시만 기다려주세요...",
"updateReadyToInstall": "업데이트가 다운로드되었으며 설치 준비가 되었습니다. 설치 후 애플리케이션이 다시 시작됩니다.",
"laterInstall": "나중에 설치",
"installNow": "지금 설치",
"updateError": "업데이트 오류",
"updateDownloaded": "업데이트 다운로드됨"
},
"common": {
"ok": "확인",
"cancel": "취소"
}
}
}
+31 -10
View File
@@ -1,21 +1,42 @@
{
"desc": {
"welcome-title": "Hallo Vutron! Alles is klaar.",
"welcome-desc": "U kunt nu platformonafhankelijke webapplicaties schrijven. Bekijk wijzigingen in realtime en bouw met één opdracht naar meerdere platforms. Als u hiermee nog niet bekend bent, kunt u hulp krijgen via de onderstaande documentatiepagina, of een vooraf geschreven voorbeeldcode uitvoeren.",
"second-desc": "U bent naar het tweede scherm gegaan! Hier is niks!",
"selected-file": "Geselecteerde bestand: {filePath}."
"welcome-title": "Hallo! Sjabloon Generator",
"welcome-desc": "U kunt nu bestandssjablonen maken en beheren. Bekijk wijzigingen in real-time en genereer batch bestanden en mappen met behulp van sjablonen.",
"second-desc": "U bent verplaatst naar het tweede scherm! Er is hier niets!",
"selected-file": "Geselecteerd bestand: {filePath}."
},
"title": {
"main": "Hoofdscherm",
"main": "Sjabloon Generator",
"second": "Tweede scherm",
"error": "Onbekende Error"
"error": "Onbekende fout",
"templates": "Sjablonenlijst",
"create-template": "Sjabloon maken"
},
"menu": {
"change-theme": "Thema veranderen",
"change-theme": "Thema wijzigen",
"change-language": "Taal wijzigen",
"increase-count": "Één stap optellen",
"increase-count": "Teller met 1 verhogen",
"documentation": "Documentatie",
"github": "Broncode",
"open-file": "Open een bestand"
"open-file": "Tekstbestand openen"
},
"update": {
"newVersionAvailable": "Nieuwe versie beschikbaar",
"currentVersion": "Huidige versie",
"latestVersion": "Laatste versie",
"updateDescription": "Een nieuwe versie van de applicatie is beschikbaar. Wilt u de update downloaden en installeren?",
"skipUpdate": "Deze update overslaan",
"downloadUpdate": "Update downloaden",
"downloadingUpdate": "Update wordt gedownload",
"downloadProgressText": "De update wordt gedownload. Even geduld a.u.b....",
"updateReadyToInstall": "De update is gedownload en klaar voor installatie. De applicatie zal opnieuw opstarten na de installatie.",
"laterInstall": "Later installeren",
"installNow": "Nu installeren",
"updateError": "Update fout",
"updateDownloaded": "Update gedownload"
},
"common": {
"ok": "OK",
"cancel": "Annuleren"
}
}
}
+33 -12
View File
@@ -1,21 +1,42 @@
{
"desc": {
"welcome-title": "Olá Vutron! Tudo está pronto.",
"welcome-desc": "Agora você pode escrever aplicativos da Web de plataforma cruzada. Veja suas alterações em tempo real e crie para várias plataformas com um único comando. Se ainda não estiver familiarizado com ele, você pode obter ajuda na página de documentação abaixo ou executar algum código de exemplo pré-escrito.",
"second-desc": "Movido para a segunda tela! Não há nenhum aqui!",
"welcome-title": "Olá! Gerador de modelos",
"welcome-desc": "Agora você pode criar e gerenciar modelos de arquivos. Veja as alterações em tempo real e gere arquivos e diretórios em lote usando modelos.",
"second-desc": "Você mudou para a segunda tela! Não há nada aqui!",
"selected-file": "Arquivo selecionado: {filePath}."
},
"title": {
"main": "Tela principal",
"main": "Gerador de modelos",
"second": "Segunda tela",
"error": "Erro desconhecido"
"error": "Erro desconhecido",
"templates": "Lista de modelos",
"create-template": "Criar modelo"
},
"menu": {
"change-theme": "Mudar Tema",
"change-language": "Mudar Idioma",
"increase-count": "Aumentar a contagem em 1",
"documentation": "Documento",
"github": "Código fonte",
"open-file": "Abra um arquivo de texto"
"change-theme": "Alterar tema",
"change-language": "Alterar idioma",
"increase-count": "Incrementar contador em 1",
"documentation": "Documentação",
"github": "Código-fonte",
"open-file": "Abrir arquivo de texto"
},
"update": {
"newVersionAvailable": "Nova versão disponível",
"currentVersion": "Versão atual",
"latestVersion": "Última versão",
"updateDescription": "Uma nova versão do aplicativo está disponível. Deseja baixar e instalar a atualização?",
"skipUpdate": "Ignorar esta atualização",
"downloadUpdate": "Baixar atualização",
"downloadingUpdate": "Baixando atualização",
"downloadProgressText": "A atualização está sendo baixada. Aguarde...",
"updateReadyToInstall": "A atualização foi baixada e está pronta para instalação. O aplicativo reiniciará após a instalação.",
"laterInstall": "Instalar mais tarde",
"installNow": "Instalar agora",
"updateError": "Erro na atualização",
"updateDownloaded": "Atualização baixada"
},
"common": {
"ok": "OK",
"cancel": "Cancelar"
}
}
}
+30 -9
View File
@@ -1,21 +1,42 @@
{
"desc": {
"welcome-title": "Привет Vutron! Все готово.",
"welcome-desc": "Теперь вы можете писать кроссплатформенные веб-приложения. Просматривайте свои изменения в режиме реального времени и выполняйте сборку для нескольких платформ с помощью одной команды. Если вы еще не знакомы с ним, вы можете получить помощь на странице документации ниже или запустить предварительно написанный пример кода.",
"second-desc": "Перенесено на второй экран! Здесь их нет!",
"welcome-title": "Привет! Генератор шаблонов",
"welcome-desc": "Теперь вы можете создавать и управлять файловыми шаблонами. Просматривайте изменения в реальном времени и создавайте файлы и каталоги пакетно с помощью шаблонов.",
"second-desc": "Вы перешли на второй экран! Здесь ничего нет!",
"selected-file": "Выбранный файл: {filePath}."
},
"title": {
"main": "Главный экран",
"main": "Генератор шаблонов",
"second": "Второй экран",
"error": "Неизвестная ошибка"
"error": "Неизвестная ошибка",
"templates": "Список шаблонов",
"create-template": "Создать шаблон"
},
"menu": {
"change-theme": "Поменять тему",
"change-theme": "Изменить тему",
"change-language": "Изменить язык",
"increase-count": "Увеличить количество на 1",
"documentation": "Документ",
"increase-count": "Увеличить счетчик на 1",
"documentation": "Документация",
"github": "Исходный код",
"open-file": "Открыть текстовый файл"
},
"update": {
"newVersionAvailable": "Доступна новая версия",
"currentVersion": "Текущая версия",
"latestVersion": "Последняя версия",
"updateDescription": "Доступна новая версия приложения. Хотите загрузить и установить обновление?",
"skipUpdate": "Пропустить это обновление",
"downloadUpdate": "Загрузить обновление",
"downloadingUpdate": "Загрузка обновления",
"downloadProgressText": "Идет загрузка обновления. Пожалуйста, подождите...",
"updateReadyToInstall": "Обновление загружено и готово к установке. Приложение перезапустится после установки.",
"laterInstall": "Установить позже",
"installNow": "Установить сейчас",
"updateError": "Ошибка обновления",
"updateDownloaded": "Обновление загружено"
},
"common": {
"ok": "OK",
"cancel": "Отмена"
}
}
}
+19
View File
@@ -19,5 +19,24 @@
"documentation": "文档",
"github": "源代码",
"open-file": "打开文本文件"
},
"update": {
"newVersionAvailable": "有新版本可用",
"currentVersion": "当前版本",
"latestVersion": "最新版本",
"updateDescription": "应用程序的新版本已经可用。您想下载并安装更新吗?",
"skipUpdate": "跳过此更新",
"downloadUpdate": "下载更新",
"downloadingUpdate": "正在下载更新",
"downloadProgressText": "正在下载更新,请稍候...",
"updateReadyToInstall": "更新已下载并准备安装。安装后应用程序将会重启。",
"laterInstall": "稍后安装",
"installNow": "立即安装",
"updateError": "更新错误",
"updateDownloaded": "更新已下载"
},
"common": {
"ok": "确定",
"cancel": "取消"
}
}
+58
View File
@@ -0,0 +1,58 @@
import { defineStore } from 'pinia'
export interface UpdateStatus {
status: 'idle' | 'checking' | 'available' | 'not-available' | 'downloading' | 'downloaded' | 'error'
currentVersion?: string
error?: string
progress?: number
}
export const useAppStore = defineStore('app', {
state: () => ({
updateStatus: {
status: 'idle' as const
} as UpdateStatus,
updateDialogVisible: false,
downloadProgress: 0
}),
getters: {
isUpdateAvailable: (state) => state.updateStatus.status === 'available',
isCheckingForUpdates: (state) => state.updateStatus.status === 'checking',
hasUpdateError: (state) => state.updateStatus.status === 'error'
},
actions: {
setUpdateStatus(status: UpdateStatus['status'], currentVersion?: string, error?: string) {
this.updateStatus = { status, currentVersion, error }
},
showUpdateDialog() {
this.updateDialogVisible = true
},
hideUpdateDialog() {
this.updateDialogVisible = false
},
setDownloadProgress(progress: number) {
this.downloadProgress = progress
},
// 启动更新下载
async startUpdateDownload() {
try {
this.setUpdateStatus('downloading')
const result = await window.mainApi.invoke('msgDownloadUpdate')
if (result.success) {
this.setUpdateStatus('downloaded')
} else {
this.setUpdateStatus('error', undefined, result.error)
}
} catch (error) {
this.setUpdateStatus('error', undefined, error instanceof Error ? error.message : String(error))
}
}
}
})