fix(更新日志): 面板随指定版本下拉联动;本机已是最新时展示本机条目

- 「指定版本更新」下拉选中版本时日志切到该版本自身的条目(升级/回退统一语义),
  未收录显示"暂无更新说明"(区块不消失),选回空项回默认区间;
  列表加载后自动默认选中第一条不触发切换
- 本机版本==线上最新时回退显示本机版本条目(原来整块消失),灰度/回滚仍隐藏
- 精简 4.0.20/4.0.21 超长文案,修复既有红测试(更新说明限 40 字)
This commit is contained in:
2026-09-16 01:02:13 +08:00
parent 79b5d40327
commit d189d94c3b
7 changed files with 152 additions and 17 deletions
@@ -23,7 +23,7 @@
</button>
</div>
<div class="update-hint">{{ hint }}</div>
<UpdateLogList :entries="changelog" />
<UpdateLogList :entries="changelog" :empty-text="changelogEmptyText" />
<UpdateProgressBar :progress="progress" />
<div v-if="hasUpdate || canDownload" style="margin-top: 4px;">
<button type="button" class="btn-download-update" :disabled="updating" @click="doUpdate">
@@ -38,6 +38,7 @@
:updating="updating"
@reload="loadVersions(true)"
@update="doUpdateVersion"
@select="setSelectedVersion"
/>
</div>
<NotificationBell theme="light" />
@@ -92,6 +93,8 @@ const {
checked,
progress,
changelog,
changelogEmptyText,
setSelectedVersion,
versions,
versionListLoading,
versionListError,
@@ -31,7 +31,7 @@
</button>
</div>
<div class="update-hint">{{ hint }}</div>
<UpdateLogList :entries="changelog" />
<UpdateLogList :entries="changelog" :empty-text="changelogEmptyText" />
<UpdateProgressBar :progress="progress" />
<VersionPicker
:versions="versions"
@@ -41,6 +41,7 @@
:updating="updating"
@reload="loadVersions(true)"
@update="doUpdateVersion"
@select="setSelectedVersion"
/>
</div>
</div>
@@ -148,6 +149,8 @@ const {
checked,
progress,
changelog,
changelogEmptyText,
setSelectedVersion,
versions,
versionListLoading,
versionListError,
+23 -4
View File
@@ -28,14 +28,14 @@ export const CLIENT_CHANGELOG: ClientChangelogEntry[] = [
version: '4.0.21',
date: '2026-09-15',
items: [
'新增设备标识文件:客户端程序目录会生成 code.txt,遇到问题时把文件里的标识发给客服,可快速查本机日志',
'客户端目录会生成 code.txt,遇到问题时把发给客服,可快速查本机日志',
],
},
{
version: '4.0.20',
date: '2026-09-15',
items: [
'新增运行日志自动同步:本地日志会(压缩后)上传到云端后台,遇到问题客服可远程排查,不用手动发日志文件(云端保留 7 天)',
'本地日志会自动上传到云端(保留 7 天),遇到问题客服可远程排查,不用手动发日志',
],
},
{
@@ -214,12 +214,15 @@ export function newestChangelogVersion(entries: ClientChangelogEntry[] = CLIENT_
*
* - latestVersion 为空(检测未完成/失败)时按数据文件里最新版本兜底;
* - currentVersion 为空(旧客户端无版本桥)时只给最新版本一条,不把整段历史倒给用户;
* - 本机版本已高于线上(灰度/回滚)时返回空数组,由调用方隐藏该区块
* - 本机版本已高于线上(灰度/回滚)时返回空数组,由调用方隐藏该区块
* - includeCurrent 为 true(面板展示用)时,本机 == 线上(无更新可看)退回展示
* 本机版本自身的条目——刚升级完的用户最想知道"这个版本改了什么"。
*/
export function selectChangelogEntries(options: {
currentVersion?: string
latestVersion?: string
entries?: ClientChangelogEntry[]
includeCurrent?: boolean
} = {}): ClientChangelogEntry[] {
const entries = options.entries ?? CLIENT_CHANGELOG
if (!entries.length) return []
@@ -231,5 +234,21 @@ export function selectChangelogEntries(options: {
const newest = inRange.find((entry) => compareVersions(entry.version, upper) === 0)
return [newest ?? inRange[0]]
}
return inRange.filter((entry) => compareVersions(entry.version, current) > 0)
const newer = inRange.filter((entry) => compareVersions(entry.version, current) > 0)
if (newer.length || !options.includeCurrent) return newer
// 本机 == 线上:展示本机版本条目;本机高于线上时 inRange 里没有它,仍为空(不展示低版本日志)
return inRange.filter((entry) => compareVersions(entry.version, current) === 0)
}
/**
* 取指定版本自身的条目("指定版本更新"下拉选中时展示,升级/回退/同版统一语义)。
* 版本未收录或版本号为空时返回空数组——不倒推相邻版本,避免张冠李戴。
*/
export function selectVersionEntry(
version: string,
entries: ClientChangelogEntry[] = CLIENT_CHANGELOG,
): ClientChangelogEntry[] {
const target = normVersion(version)
if (!target) return []
return entries.filter((entry) => compareVersions(entry.version, target) === 0)
}
@@ -1,7 +1,8 @@
<template>
<div v-if="entries.length" class="update-log">
<div v-if="entries.length || emptyText" class="update-log">
<div class="update-log-title">更新日志</div>
<div class="update-log-scroll">
<div v-if="!entries.length" class="update-log-empty">{{ emptyText }}</div>
<div v-else class="update-log-scroll">
<div v-for="entry in entries" :key="entry.version" class="update-log-entry">
<div class="update-log-head">
<span class="update-log-version">v{{ entry.version }}</span>
@@ -22,10 +23,11 @@
* 内容来自 shared/client-changelog(发版时按 git 历史整理追加),
* 由 useVersionUpdate 按"本机版本 → 最新版本"区间筛好后传入;
* 条目多时区域内滚动,避免把面板撑长。
* emptyText:选中版本未收录说明时的兜底文案(非空则保留标题与提示,避免区块整块消失)。
*/
import type { ClientChangelogEntry } from '@/shared/client-changelog'
defineProps<{ entries: ClientChangelogEntry[] }>()
defineProps<{ entries: ClientChangelogEntry[]; emptyText?: string }>()
</script>
<style scoped>
@@ -43,6 +45,12 @@ defineProps<{ entries: ClientChangelogEntry[] }>()
margin-bottom: 6px;
}
.update-log-empty {
font-size: 12.5px;
line-height: 1.6;
color: #8293a5;
}
.update-log-scroll {
max-height: 176px;
overflow-y: auto;
@@ -11,7 +11,12 @@
</div>
<div v-if="error" class="version-picker-error">{{ error }}</div>
<template v-else>
<select v-model="selected" class="version-picker-select" :disabled="loading || !versions.length">
<select
v-model="selected"
class="version-picker-select"
:disabled="loading || !versions.length"
@change="emit('select', selected)"
>
<option value="">{{ versions.length ? '请选择要安装的版本' : '暂无可选版本' }}</option>
<option v-for="item in versions" :key="item.version" :value="item.version">
v{{ item.version }}
@@ -35,6 +40,7 @@
* 只负责选择与提示;版本列表加载、下载安装流程都在 useVersionUpdate 里
* (列表项与判定文案见 shared/utils/version-picker)。
* 选到低于本机的版本时给出"回退操作"提示,实际确认框由 doUpdateVersion 再提示一次。
* 用户改选时 emit('select', version),页面转发给 useVersionUpdate 以切换更新日志。
*/
import { computed, ref, watch } from 'vue'
import {
@@ -55,6 +61,8 @@ const props = defineProps<{
const emit = defineEmits<{
(e: 'update', item: ClientVersionItem): void
(e: 'reload'): void
/** 用户在下拉里改选版本(含选回空项):父组件据此把更新日志切到该版本 */
(e: 'select', version: string): void
}>()
const selected = ref('')
@@ -70,13 +78,16 @@ const tip = computed(() =>
selected.value ? describeTargetVersion(kind.value, props.currentVersion || '', selected.value) : '',
)
// 列表刷新后选中项可能已不存在(版本被删除):清空选择,避免误装;
// 无选中时默认选列表第一条(列表按版本号倒序,第一条即最新版)
// 列表刷新后选中项可能已不存在(版本被删除):清空选择并通知父组件回退日志
// 避免日志还停留在已下架的版本上;
// 无选中时默认选列表第一条(列表按版本号倒序,第一条即最新版)——这只是给「更新到此版本」
// 一个默认目标,不触发 select 事件,更新日志保持默认视图(用户主动改选才切换)
watch(
() => props.versions,
(list) => {
if (selected.value && !list.some((item) => item.version === selected.value)) {
selected.value = ''
emit('select', '')
}
if (!selected.value && list.length) {
selected.value = list[0].version
@@ -8,7 +8,7 @@
import { computed, ref } from 'vue'
import { getPywebviewApi } from '@/shared/bridges/pywebview'
import { selectChangelogEntries, type ClientChangelogEntry } from '@/shared/client-changelog'
import { selectChangelogEntries, selectVersionEntry, type ClientChangelogEntry } from '@/shared/client-changelog'
import { compareVersions, normVersion } from '@/shared/utils/version-compare'
import {
classifyTargetVersion,
@@ -261,9 +261,33 @@ export function useVersionUpdate() {
// 进度条的百分比/文案由 UpdateProgressBar 组件从 progress 自行派生(见 shared/utils/update-progress
const progress = computed(() => updateProgress.value)
// 更新日志:本机版本之后到最新版本之间的条目(文案见 shared/client-changelog
const changelog = computed<ClientChangelogEntry[]>(() =>
selectChangelogEntries({ currentVersion: currentVersion.value, latestVersion: latestVersion.value }),
/**
* 「指定版本更新」下拉里用户主动选中的版本(空串=未主动选,按默认区间展示)。
* 只由下拉的用户交互驱动:列表加载后的默认选中不改变日志区块,保持默认视图稳定。
*/
const selectedVersion = ref('')
function setSelectedVersion(version: string) {
selectedVersion.value = normVersion(version)
}
// 更新日志:下拉选中了版本 → 显示该版本自身的条目(升级/回退统一语义);
// 否则本机版本之后到最新版本之间的条目(本机已是最新时展示本机版本的条目),
// 文案见 shared/client-changelog
const changelog = computed<ClientChangelogEntry[]>(() => {
if (selectedVersion.value) return selectVersionEntry(selectedVersion.value)
return selectChangelogEntries({
currentVersion: currentVersion.value,
latestVersion: latestVersion.value,
includeCurrent: true,
})
})
// 选中版本未收录更新说明时的兜底文案(区块保留标题,避免"选了版本日志反而整块消失")
const changelogEmptyText = computed(() =>
selectedVersion.value && !changelog.value.length
? `v${selectedVersion.value} 暂无更新说明`
: '',
)
/**
@@ -291,6 +315,8 @@ export function useVersionUpdate() {
checked,
progress,
changelog,
changelogEmptyText,
setSelectedVersion,
versions,
versionListLoading,
versionListError,
+66 -1
View File
@@ -5,6 +5,7 @@ import {
CLIENT_CHANGELOG,
newestChangelogVersion,
selectChangelogEntries,
selectVersionEntry,
type ClientChangelogEntry,
} from '../src/shared/client-changelog.ts'
@@ -62,13 +63,57 @@ test('更新日志:落后多个版本时逐版本列出(含中间版本)',
assert.deepEqual(entries.map((entry) => entry.version), ['4.1.0', '4.0.11', '4.0.9'])
})
test('更新日志:已是最新版本时没有条目(面板隐藏该区块', () => {
test('更新日志:已是最新版本且不带 includeCurrent 时没有条目(确认框语义', () => {
assert.deepEqual(
selectChangelogEntries({ entries: FIXTURE, currentVersion: '4.0.11', latestVersion: '4.0.11' }),
[],
)
})
test('更新日志:面板展示(includeCurrent)在本机已是最新时给出本机版本条目', () => {
const entries = selectChangelogEntries({
entries: FIXTURE,
currentVersion: '4.0.11',
latestVersion: '4.0.11',
includeCurrent: true,
})
assert.deepEqual(entries.map((entry) => entry.version), ['4.0.11'])
})
test('更新日志:面板展示在有更新时行为不变(只给本机之后的条目)', () => {
const entries = selectChangelogEntries({
entries: FIXTURE,
currentVersion: '4.0.11',
latestVersion: '4.1.0',
includeCurrent: true,
})
assert.deepEqual(entries.map((entry) => entry.version), ['4.1.0'])
})
test('更新日志:面板展示在本机高于线上(灰度/回滚)时仍不列条目', () => {
assert.deepEqual(
selectChangelogEntries({
entries: FIXTURE,
currentVersion: '4.1.0',
latestVersion: '4.0.11',
includeCurrent: true,
}),
[],
)
})
test('更新日志:面板展示在本机版本未被收录时不倒推旧条目', () => {
assert.deepEqual(
selectChangelogEntries({
entries: FIXTURE,
currentVersion: '4.0.10',
latestVersion: '4.0.10',
includeCurrent: true,
}),
[],
)
})
test('更新日志:本机版本高于线上(灰度/回滚)不列条目', () => {
assert.deepEqual(
selectChangelogEntries({ entries: FIXTURE, currentVersion: '4.1.0', latestVersion: '4.0.11' }),
@@ -104,3 +149,23 @@ test('更新日志:版本号前缀 v 不影响筛选', () => {
const entries = selectChangelogEntries({ entries: FIXTURE, currentVersion: 'v4.0.4', latestVersion: 'v4.0.11' })
assert.deepEqual(entries.map((entry) => entry.version), ['4.0.11', '4.0.9'])
})
test('指定版本日志:取选中版本自身的条目(升级/回退/同版统一语义)', () => {
assert.deepEqual(
selectVersionEntry('4.0.9', FIXTURE).map((entry) => entry.version),
['4.0.9'],
'回退到旧版本时也要能看到该版本的更新说明',
)
assert.deepEqual(selectVersionEntry('4.1.0', FIXTURE).map((entry) => entry.version), ['4.1.0'])
})
test('指定版本日志:v 前缀与版本号为空', () => {
assert.deepEqual(selectVersionEntry('v4.0.11', FIXTURE).map((entry) => entry.version), ['4.0.11'])
assert.deepEqual(selectVersionEntry('', FIXTURE), [])
assert.deepEqual(selectVersionEntry('v', FIXTURE), [])
})
test('指定版本日志:未收录的版本不倒推相邻条目', () => {
assert.deepEqual(selectVersionEntry('4.0.10', FIXTURE), [], '4.0.10 未收录,不能拿 4.0.9 的说明顶替')
assert.deepEqual(selectVersionEntry('4.2.0', FIXTURE), [])
})