fix(home): prioritize agreement before changelog

Run the automatic changelog check only after the current agreement is already accepted or the user accepts it during startup. This prevents upgraded users from seeing overlapping modals and marking release notes as read too early.
This commit is contained in:
digua committed 2026-07-30 16:08:55 +08:00
1 parent fef493155f
commit 917740ca07
5 files changed
+52 -16

No files matched your search

+5
View File
@@ -13,6 +13,10 @@ const { t } = useI18n()
const settingsStore = useSettingsStore()
const { locale } = storeToRefs(settingsStore)
const emit = defineEmits<{
accepted: []
}>()
const AGREEMENT_VERSION = '3.0'
const AGREEMENT_KEY = 'chatlab_agreement_version'
@@ -51,6 +55,7 @@ const renderedContent = computed(() => md.render(agreementText.value))
function handleAgree() {
localStorage.setItem(AGREEMENT_KEY, AGREEMENT_VERSION)
isOpen.value = false
emit('accepted')
}
function handleDisagree() {
+2 -12
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, watch, onMounted } from 'vue'
import { ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { storeToRefs } from 'pinia'
import { useSettingsStore } from '@/stores/settings'
@@ -36,7 +36,6 @@ const hoveredVersion = ref<string | null>(null)
const currentAppVersion = ref<string | null>(null)
const CHANGELOG_READ_KEY = 'chatlab_changelog_read_version'
const AGREEMENT_KEY = 'chatlab_agreement_version'
// summary 的白名单配置(可按需扩展)
const SUMMARY_SANITIZE_OPTIONS = {
@@ -133,10 +132,6 @@ function markVersionAsRead(version: string) {
// 检查是否需要显示新版本日志(冷启动时自动检查)
async function checkNewVersion() {
try {
if (!localStorage.getItem(AGREEMENT_KEY)) {
return
}
// 1. 获取当前软件版本号
const rawVersion = await usePlatformService().getVersion()
const currentVersion = normalizeChangelogVersion(rawVersion)
@@ -224,12 +219,7 @@ function getLatestVersion() {
return changelogs.value[0]?.version || null
}
// 组件挂载时检查新版本
onMounted(() => {
checkNewVersion()
})
defineExpose({ open, openWithData, close, fetchChangelogs, getLatestVersion })
defineExpose({ open, openWithData, close, fetchChangelogs, getLatestVersion, checkNewVersion })
</script>
<template>
+15 -1
View File
@@ -1,7 +1,7 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { resolveLanguageBootstrap } from './onboardingFlow'
import { resolveLanguageBootstrap, resolvePostLanguageBootstrap } from './onboardingFlow'
test('新用户没有保存语言时应先打开语言选择,不继续后续弹窗流程', () => {
assert.deepEqual(resolveLanguageBootstrap(''), {
@@ -16,3 +16,17 @@ test('已有保存语言时应跳过语言选择并继续后续弹窗流程', ()
shouldContinue: true,
})
})
test('已接受旧版协议的用户应优先确认当前协议,不检查更新日志', () => {
assert.deepEqual(resolvePostLanguageBootstrap(true), {
shouldOpenAgreement: true,
shouldCheckChangelog: false,
})
})
test('已接受当前协议的用户才可以检查更新日志', () => {
assert.deepEqual(resolvePostLanguageBootstrap(false), {
shouldOpenAgreement: false,
shouldCheckChangelog: true,
})
})
+16
View File
@@ -3,6 +3,11 @@ export interface LanguageBootstrapResult {
shouldContinue: boolean
}
export interface PostLanguageBootstrapResult {
shouldOpenAgreement: boolean
shouldCheckChangelog: boolean
}
/**
* 根据后端保存的语言配置决定首次启动弹窗流程。
* 空语言代表新用户,需要先完成语言选择,避免用户协议和语言弹窗同时打开。
@@ -14,3 +19,14 @@ export function resolveLanguageBootstrap(savedLocale: string | null | undefined)
shouldContinue: hasSavedLocale,
}
}
/**
* Keep the mandatory agreement ahead of optional startup content.
* Changelog checks may resume only after the current agreement is accepted.
*/
export function resolvePostLanguageBootstrap(needsAgreement: boolean): PostLanguageBootstrapResult {
return {
shouldOpenAgreement: needsAgreement,
shouldCheckChangelog: !needsAgreement,
}
}
+14 -3
View File
@@ -6,6 +6,7 @@ import { getChatlabSiteLocalePath } from '@/utils/chatlabSiteLocale'
import logoSvg from '@/assets/images/logo.svg'
import LanguageSelectModal from '@/components/home/LanguageSelectModal.vue'
import AgreementModal from '@/components/home/AgreementModal.vue'
import { resolvePostLanguageBootstrap } from '@/components/home/onboardingFlow'
import MigrationModal from './components/MigrationModal.vue'
import ImportArea from '@/components/import/ImportArea.vue'
import ImportTabSelector from './components/import/ImportTabSelector.vue'
@@ -35,11 +36,21 @@ const agreementModalRef = ref<InstanceType<typeof AgreementModal> | null>(null)
// 语言选择完成后,检查是否需要显示协议弹窗
function onLanguageSelectDone() {
if (agreementModalRef.value?.needsAgreement()) {
agreementModalRef.value.open()
const agreementModal = agreementModalRef.value
if (!agreementModal) return
const bootstrap = resolvePostLanguageBootstrap(agreementModal.needsAgreement())
if (bootstrap.shouldOpenAgreement) {
agreementModal.open()
} else if (bootstrap.shouldCheckChangelog) {
changelogModalRef.value?.checkNewVersion()
}
}
function onAgreementAccepted() {
changelogModalRef.value?.checkNewVersion()
}
// 打开版本日志弹窗(手动点击时调用)
async function openChangelog() {
changelogModalRef.value?.open()
@@ -120,7 +131,7 @@ const tutorialExportUrl = computed(() => {
<LanguageSelectModal @done="onLanguageSelectDone" />
<!-- 用户协议弹窗 -->
<AgreementModal ref="agreementModalRef" />
<AgreementModal ref="agreementModalRef" @accepted="onAgreementAccepted" />
<!-- 数据库迁移弹窗 -->
<MigrationModal />