Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

> 面向使用者的更新说明。实现细节见 `docs/CHANGELOG.md`。

## 0.1.6(2026-03-25)

### 体验改进

- **软件更新**:顶栏增加独立入口「检查更新」,与设置解耦;下载新版本时展示**进度条**(已知大小时显示百分比)。

---

## 0.1.5(2026-03-25)

### 体验改进
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@xunji/desktop",
"private": true,
"version": "0.1.5",
"version": "0.1.6",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "xunji-desktop"
version = "0.1.5"
version = "0.1.6"
description = "寻迹 - AI 编程知识管理平台"
authors = ["XunJi"]
edition = "2021"
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-cli/schema.json",
"productName": "XunJi",
"version": "0.1.5",
"version": "0.1.6",
"identifier": "com.xunji.app",
"build": {
"frontendDist": "../dist",
Expand Down
179 changes: 179 additions & 0 deletions apps/desktop/src/components/AppUpdateModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
<script setup lang="ts">
/**
* 独立「软件更新」弹窗:检查更新 + 下载进度条(与设置页解耦,入口在顶栏)。
*/
import { watch } from 'vue'
import {
NModal,
NCard,
NButton,
NSpace,
NAlert,
NProgress,
NSpin,
useMessage,
} from 'naive-ui'
import { useAppUpdater } from '../composables/useAppUpdater'

const props = defineProps<{ show: boolean }>()
const emit = defineEmits<{ 'update:show': [v: boolean] }>()

const message = useMessage()
const {
phase,
currentVersion,
busy,
downloadPercent,
downloadIndeterminate,
statusText,
errorText,
pendingUpdate,
reset,
loadCurrentVersion,
runCheck,
runInstall,
} = useAppUpdater()

watch(
() => props.show,
async (v) => {
if (v) {
reset()
await loadCurrentVersion()
}
},
)

function onClose() {
emit('update:show', false)
}

async function onCheckClick() {
await runCheck()
if (phase.value === 'uptodate') {
message.success('当前已是最新版本')
}
}

</script>

<template>
<n-modal
:show="props.show"
:mask-closable="!busy"
transform-origin="center"
@update:show="emit('update:show', $event)"
>
<n-card
style="width: 420px; max-width: 92vw"
title="软件更新"
:bordered="false"
size="huge"
role="dialog"
aria-modal="true"
>
<template #header-extra>
<n-button quaternary size="small" class="rounded-lg" :disabled="busy" @click="onClose">
<span class="i-lucide-x w-4 h-4" />
</n-button>
</template>

<div class="space-y-4">
<div class="text-sm text-neutral-600 dark:text-neutral-400">
当前版本
<span class="font-mono text-brand-600 dark:text-brand-400 ml-2">{{ currentVersion }}</span>
</div>

<n-alert v-if="errorText" type="error" :bordered="false" class="!text-xs">
{{ errorText }}
</n-alert>

<!-- 发现新版本 -->
<template v-if="phase === 'available' && pendingUpdate">
<n-alert type="info" :bordered="false" class="!text-xs">
<div class="space-y-1">
<p class="m-0 font-medium">发现新版本:{{ pendingUpdate.version }}</p>
<div
v-if="pendingUpdate.body?.trim()"
class="max-h-32 overflow-y-auto text-neutral-600 dark:text-neutral-400 whitespace-pre-wrap"
>
{{ pendingUpdate.body.trim() }}
</div>
</div>
</n-alert>
</template>

<!-- 检查中 -->
<div v-if="phase === 'checking'" class="flex items-center gap-2 py-2">
<n-spin size="small" />
<span class="text-sm text-neutral-500">正在检查更新…</span>
</div>

<!-- 下载中:有总大小时用百分比进度条;否则仅转圈 + 已接收字节 -->
<div v-if="phase === 'downloading'" class="space-y-2">
<n-progress
v-if="!downloadIndeterminate"
type="line"
:percentage="downloadPercent"
:processing="downloadPercent > 0 && downloadPercent < 100"
indicator-placement="inside"
:height="22"
border-radius="8px"
/>
<div v-else class="flex items-center gap-2 py-1">
<n-spin size="small" />
<span class="text-sm text-neutral-500">{{ statusText }}</span>
</div>
<p
v-if="statusText && !downloadIndeterminate"
class="text-xs text-neutral-500 dark:text-neutral-400 m-0"
>
{{ statusText }}
</p>
</div>

<div v-if="phase === 'installing'" class="flex items-center gap-2 py-1">
<n-spin size="small" />
<span class="text-sm text-neutral-500">正在重启应用…</span>
</div>

<n-space vertical :size="10">
<n-button
v-if="phase === 'idle' || phase === 'uptodate' || phase === 'error'"
type="primary"
secondary
block
class="rounded-lg"
:loading="busy"
@click="onCheckClick"
>
<span class="inline-flex items-center justify-center gap-2">
<span class="i-lucide-refresh-cw w-4 h-4" />
检查更新
</span>
</n-button>

<n-button
v-if="phase === 'available'"
type="primary"
block
class="rounded-lg"
:loading="busy"
@click="runInstall"
>
<span class="inline-flex items-center justify-center gap-2">
<span class="i-lucide-download-cloud w-4 h-4" />
下载并安装
</span>
</n-button>
</n-space>
</div>

<template #footer>
<div class="flex justify-end">
<n-button size="small" class="rounded-lg" :disabled="busy" @click="onClose">关闭</n-button>
</div>
</template>
</n-card>
</n-modal>
</template>
103 changes: 4 additions & 99 deletions apps/desktop/src/components/SettingsModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,15 @@
* 3. 关于寻迹
* - 应用名、版本号、Tauri 运行时版本、Bundle Identifier(来自 tauri.conf / 打包元数据)
* - 各版本变更说明:`src/data/app-changelog.md`(与根目录 `CHANGELOG.md` 同步,Vite `?raw` 打入包)
* - 软件更新见顶栏「检查更新」,本页不再重复入口
*/
import { ref, computed, watch } from 'vue'
import {
NModal, NCard, NTabs, NTabPane, NForm, NFormItem,
NInput, NInputNumber, NSelect, NSwitch, NButton,
NSpace, NAlert, NDivider, NSpin, NTag,
NDescriptions, NDescriptionsItem,
useDialog,
useMessage,
} from 'naive-ui'
import { check } from '@tauri-apps/plugin-updater'
import { relaunch } from '@tauri-apps/plugin-process'
import { getIdentifier, getTauriVersion, getVersion } from '@tauri-apps/api/app'
import MarkdownRenderer from './MarkdownRenderer.vue'
import appChangelogMd from '../data/app-changelog.md?raw'
Expand All @@ -34,9 +31,6 @@ const emit = defineEmits<{
'update:show': [v: boolean]
}>()

const message = useMessage()
const dialog = useDialog()

/** 用户可见品牌名(与窗口标题、macOS 显示名一致;GitHub Release 安装包前缀仍为 tauri.conf 的 productName「XunJi」) */
const APP_DISPLAY_NAME = '寻迹'

Expand Down Expand Up @@ -65,10 +59,6 @@ const aboutMeta = ref<{
} | null>(null)
const aboutLoading = ref(false)
const aboutError = ref('')
/** 检查/下载更新进行中(避免重复点击) */
const updateBusy = ref(false)
/** 下载进度文案,供关于页展示 */
const updateStatusText = ref('')

// 工作副本:从后端加载后复制,保存时提交
const workingConfig = ref<AppConfigDto | null>(null)
Expand Down Expand Up @@ -234,75 +224,6 @@ function onClose() {
emit('update:show', false)
}

/**
* 应用内更新:调用 tauri-plugin-updater(与 tauri.conf plugins.updater 一致)。
* 浏览器预览无 Tauri 时会抛错,提示用户在桌面端使用。
*/
async function onCheckUpdate() {
if (updateBusy.value) return
updateBusy.value = true
updateStatusText.value = ''
try {
const update = await check()
if (!update) {
message.success('当前已是最新版本')
return
}
const notes = update.body?.trim() ? `\n\n${update.body}` : ''
dialog.warning({
title: '发现新版本',
content: `最新版本:${update.version}${notes}`,
positiveText: '下载并安装',
negativeText: '稍后',
onPositiveClick: async () => {
updateBusy.value = true
let total = 0
let downloaded = 0
try {
await update.downloadAndInstall((event) => {
switch (event.event) {
case 'Started':
total = event.data.contentLength ?? 0
updateStatusText.value = '正在下载更新…'
break
case 'Progress':
downloaded += event.data.chunkLength
if (total > 0) {
const pct = Math.min(100, Math.round((downloaded / total) * 100))
updateStatusText.value = `正在下载 ${pct}%`
}
break
case 'Finished':
updateStatusText.value = '安装完成,即将重启…'
break
}
})
await relaunch()
return true
} catch (e) {
const msg = e instanceof Error ? e.message : String(e)
message.error(`更新失败:${msg}`)
console.error('[updater] download/install failed', e)
updateStatusText.value = ''
return false
} finally {
updateBusy.value = false
}
},
})
} catch (e) {
const msg = e instanceof Error ? e.message : String(e)
message.error(
msg.includes('Tauri') || msg.includes('not allowed')
? '无法检查更新:请在寻迹桌面客户端内使用(浏览器预览不支持)。'
: `检查更新失败:${msg}`,
)
console.error('[updater] check failed', e)
} finally {
updateBusy.value = false
}
}

/** 与 n-switch 的受控值一致,避免仅「翻转」与 Naive 传入的新值不同步 */
function setSourceEnabled(source: SourceConfigDto, enabled: boolean) {
source.enabled = enabled
Expand Down Expand Up @@ -631,25 +552,9 @@ const extraArgsStr = computed({
</n-descriptions-item>
</n-descriptions>

<n-space vertical :size="8" style="width: 100%">
<n-button
type="primary"
secondary
size="small"
class="settings-btn-rounded"
:loading="updateBusy"
:disabled="aboutLoading || !!aboutError"
@click="onCheckUpdate"
>
<span class="inline-flex items-center gap-1.5">
<span class="i-lucide-download-cloud w-3.5 h-3.5" />
检查更新
</span>
</n-button>
<p v-if="updateStatusText" class="text-xs text-neutral-500 dark:text-neutral-400 m-0">
{{ updateStatusText }}
</p>
</n-space>
<p class="text-xs text-neutral-500 dark:text-neutral-400 m-0">
软件更新请使用顶栏 <span class="font-medium text-neutral-600 dark:text-neutral-300">检查更新</span>。
</p>

<!-- 更新日志:Markdown 静态内容 -->
<n-divider class="!my-3" />
Expand Down
Loading
Loading