Add self-updating service

This commit is contained in:
2025-07-03 15:21:01 +08:00
parent 81eb2c94ac
commit ebee33ea7c
22 changed files with 1565 additions and 465 deletions

View File

@@ -1,10 +1,9 @@
<script setup lang="ts">
import {useI18n} from 'vue-i18n';
import {onMounted, onUnmounted, ref, watch} from 'vue';
import {onMounted, onUnmounted, ref, watch, computed} from 'vue';
import {useConfigStore} from '@/stores/configStore';
import {useEditorStore} from '@/stores/editorStore';
import {useUpdateStore} from '@/stores/updateStore';
import {useDocumentStore} from '@/stores/documentStore';
import * as runtime from '@wailsio/runtime';
import {useRouter} from 'vue-router';
import BlockLanguageSelector from './BlockLanguageSelector.vue';
@@ -16,7 +15,6 @@ import {formatBlockContent} from '@/views/editor/extensions/codeblock/formatCode
const editorStore = useEditorStore();
const configStore = useConfigStore();
const updateStore = useUpdateStore();
const documentStore = useDocumentStore();
const {t} = useI18n();
const router = useRouter();
@@ -154,6 +152,25 @@ watch(isLoaded, async (loaded) => {
await setWindowAlwaysOnTop(true);
}
});
const handleUpdateButtonClick = async () => {
if (updateStore.hasUpdate && !updateStore.isUpdating && !updateStore.updateSuccess) {
// 开始下载更新
await updateStore.applyUpdate();
} else if (updateStore.updateSuccess) {
// 更新成功后,点击重启
await updateStore.restartApplication();
}
};
// 更新按钮标题计算属性
const updateButtonTitle = computed(() => {
if (updateStore.isChecking) return t('settings.checking');
if (updateStore.isUpdating) return t('settings.updating');
if (updateStore.updateSuccess) return t('settings.updateSuccessRestartRequired');
if (updateStore.hasUpdate) return `${t('settings.newVersionAvailable')}: ${updateStore.updateResult?.latestVersion || ''}`;
return '';
});
</script>
<template>
@@ -203,14 +220,41 @@ watch(isLoaded, async (loaded) => {
</svg>
</div>
<!-- 更新提示图标 -->
<!-- 更新按钮 - 根据状态显示不同图标 -->
<div
v-if="updateStore.hasUpdate"
v-if="updateStore.hasUpdate || updateStore.isChecking || updateStore.isUpdating || updateStore.updateSuccess"
class="update-button"
:title="`发现新版本 ${updateStore.updateResult?.latestVer || ''}`"
@click="updateStore.openReleaseURL"
:class="{
'checking': updateStore.isChecking,
'updating': updateStore.isUpdating,
'success': updateStore.updateSuccess,
'available': updateStore.hasUpdate && !updateStore.isUpdating && !updateStore.updateSuccess
}"
:title="updateButtonTitle"
@click="handleUpdateButtonClick"
>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none"
<!-- 检查更新中 -->
<svg v-if="updateStore.isChecking" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="rotating">
<path d="M21 12a9 9 0 1 1-6.219-8.56"/>
</svg>
<!-- 下载更新中 -->
<svg v-else-if="updateStore.isUpdating" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="rotating">
<path d="M21 12a9 9 0 1 1-6.219-8.56"></path>
<path d="M12 2a10 10 0 1 0 10 10"></path>
</svg>
<!-- 更新成功等待重启 -->
<svg v-else-if="updateStore.updateSuccess" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="pulsing">
<path d="M18.36 6.64a9 9 0 1 1-12.73 0"></path>
<line x1="12" y1="2" x2="12" y2="12"></line>
</svg>
<!-- 有更新可用 -->
<svg v-else xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/>
<polyline points="7.5,10.5 12,15 16.5,10.5"/>
@@ -282,8 +326,7 @@ watch(isLoaded, async (loaded) => {
cursor: help;
}
/* 更新提示按钮样式 */
/* 更新按钮样式 */
.update-button {
cursor: pointer;
display: flex;
@@ -293,17 +336,58 @@ watch(isLoaded, async (loaded) => {
height: 20px;
padding: 2px;
border-radius: 3px;
background-color: rgba(76, 175, 80, 0.1);
transition: all 0.2s ease;
animation: pulse 2s infinite;
&:hover {
background-color: rgba(76, 175, 80, 0.2);
transform: scale(1.05);
/* 有更新可用状态 */
&.available {
background-color: rgba(76, 175, 80, 0.1);
animation: pulse 2s infinite;
svg {
stroke: #4caf50;
}
&:hover {
background-color: rgba(76, 175, 80, 0.2);
transform: scale(1.05);
}
}
/* 检查更新中状态 */
&.checking {
background-color: rgba(255, 193, 7, 0.1);
svg {
stroke: #ffc107;
}
}
/* 更新下载中状态 */
&.updating {
background-color: rgba(33, 150, 243, 0.1);
svg {
stroke: #2196f3;
}
}
/* 更新成功状态 */
&.success {
background-color: rgba(156, 39, 176, 0.1);
svg {
stroke: #9c27b0;
}
}
svg {
stroke: #4caf50;
/* 旋转动画 */
.rotating {
animation: rotate 1.5s linear infinite;
}
/* 脉冲动画 */
.pulsing {
animation: pulse-strong 1.2s ease-in-out infinite;
}
@keyframes pulse {
@@ -314,6 +398,26 @@ watch(isLoaded, async (loaded) => {
opacity: 0.7;
}
}
@keyframes pulse-strong {
0%, 100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.1);
opacity: 0.8;
}
}
@keyframes rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
}
/* 窗口置顶图标按钮样式 */