feat: 新增应用更新功能和关于页面

- 集成 Tauri updater 实现应用版本检查与更新
- 添加关于页面,展示项目介绍、使用流程和版本信息
- 新增顶部栏检查更新和 GitHub 入口
- 完善国际化文案和更新弹窗组件
This commit is contained in:
Yuhang Wu
2026-10-14 10:13:31 +08:00
parent f6d8d783b4
commit 746a4be2bc
15 changed files with 663 additions and 7 deletions
+153
View File
@@ -0,0 +1,153 @@
<template>
<a-modal
:open="store.modalOpen"
:title="modalTitle"
:closable="!store.installing"
:mask-closable="false"
:keyboard="!store.installing"
:width="520"
centered
wrap-class-name="update-modal-wrap"
@cancel="onCancel"
>
<!-- 俏皮的问候 + 版本变化 -->
<div class="update-hero">
<span class="update-hero-emoji">🎉</span>
<div class="min-w-0 flex-1">
<p class="m-0 text-sm font-medium">{{ t("update.hello") }}</p>
<div class="mt-2 flex flex-wrap items-center gap-1.5 text-xs">
<a-tag class="!me-0">v{{ info?.currentVersion }}</a-tag>
<ArrowRightOutlined class="text-[10px] opacity-50" />
<a-tag color="green" class="!me-0">v{{ info?.version }}</a-tag>
<span v-if="releaseDate" class="ml-1 opacity-50">{{ releaseDate }}</span>
</div>
</div>
</div>
<p class="update-tip">{{ t("update.tip") }}</p>
<!-- 更新日志 -->
<div class="update-log-header">
<BulbOutlined />
<span>{{ t("update.log") }}</span>
</div>
<div class="update-log">{{ logText }}</div>
<!-- 下载 / 安装进度 -->
<div v-if="store.installing" class="update-progress">
<a-progress :percent="store.progress" :stroke-color="'#6366f1'" />
<p class="m-0 text-xs opacity-60">{{ t("update.downloading") }}</p>
</div>
<!-- 底部操作:忽略该版本 / 取消 / 确认更新 -->
<template #footer>
<div class="update-footer">
<a-button v-if="!store.installing" type="text" size="small" @click="onIgnore">
{{ t("update.ignore") }}
</a-button>
<div class="ml-auto flex items-center gap-2">
<a-button :disabled="store.installing" @click="onCancel">
{{ t("common.cancel") }}
</a-button>
<a-button type="primary" :loading="store.installing" @click="onConfirm">
{{ t("update.confirm") }}
</a-button>
</div>
</div>
</template>
</a-modal>
</template>
<script setup lang="ts">
import dayjs from "dayjs"
import { ArrowRightOutlined, BulbOutlined } from "@ant-design/icons-vue"
import { useI18n } from "vue-i18n"
import { useUpdateStore } from "@/stores/update"
const { t } = useI18n()
const store = useUpdateStore()
const info = computed(() => store.info)
const modalTitle = computed(() => t("update.title", { version: info.value?.version ?? "" }))
const releaseDate = computed(() =>
info.value?.date ? dayjs(info.value.date).format("YYYY-MM-DD") : "",
)
const logText = computed(() => info.value?.body?.trim() || t("update.emptyLog"))
function onCancel() {
store.cancel()
}
function onIgnore() {
store.ignoreVersion()
}
function onConfirm() {
store.install()
}
</script>
<style scoped lang="scss">
.update-hero {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 14px;
border-radius: 12px;
background: linear-gradient(135deg, rgba(99, 102, 241, 0.14), rgba(34, 211, 238, 0.14));
}
.dark .update-hero {
background: linear-gradient(135deg, rgba(99, 102, 241, 0.22), rgba(34, 211, 238, 0.18));
}
.update-hero-emoji {
font-size: 26px;
line-height: 1;
}
.update-tip {
margin: 14px 2px 10px;
font-size: 12px;
opacity: 0.65;
}
.update-log-header {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 6px;
font-size: 13px;
font-weight: 600;
}
.update-log {
white-space: pre-wrap;
word-break: break-word;
padding: 12px 14px;
border-radius: 10px;
border: 1px solid rgba(15, 23, 42, 0.08);
background: rgba(148, 163, 184, 0.12);
font-size: 13px;
line-height: 1.7;
opacity: 0.9;
}
.dark .update-log {
border-color: rgba(255, 255, 255, 0.1);
background: rgba(148, 163, 184, 0.14);
}
.update-progress {
margin-top: 14px;
}
.update-footer {
display: flex;
align-items: center;
gap: 8px;
}
</style>