feat: 前端更新改为定时拉取
This commit is contained in:
@@ -1 +1,2 @@
|
||||
VITE_APP_ENV='prod'
|
||||
VITE_APP_VERSION='1.0.1'
|
||||
@@ -1 +1,2 @@
|
||||
VITE_APP_ENV='dev'
|
||||
VITE_APP_VERSION='0.9.0'
|
||||
@@ -3,13 +3,16 @@ import { onMounted, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ConfigProvider } from 'ant-design-vue'
|
||||
import { useTheme } from './composables/useTheme.ts'
|
||||
import { useUpdateChecker } from './composables/useUpdateChecker.ts'
|
||||
import AppLayout from './components/AppLayout.vue'
|
||||
import TitleBar from './components/TitleBar.vue'
|
||||
import UpdateModal from './components/UpdateModal.vue'
|
||||
import zhCN from 'ant-design-vue/es/locale/zh_CN'
|
||||
import { logger } from '@/utils/logger'
|
||||
|
||||
const route = useRoute()
|
||||
const { antdTheme, initTheme } = useTheme()
|
||||
const { updateVisible, updateData, onUpdateConfirmed } = useUpdateChecker()
|
||||
|
||||
// 判断是否为初始化页面
|
||||
const isInitializationPage = computed(() => route.name === 'Initialization')
|
||||
@@ -35,6 +38,13 @@ onMounted(() => {
|
||||
<TitleBar />
|
||||
<AppLayout />
|
||||
</div>
|
||||
|
||||
<!-- 全局更新模态框 -->
|
||||
<UpdateModal
|
||||
v-model:visible="updateVisible"
|
||||
:update-data="updateData"
|
||||
@confirmed="onUpdateConfirmed"
|
||||
/>
|
||||
</ConfigProvider>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -7,5 +7,9 @@ export type UpdateCheckIn = {
|
||||
* 当前前端版本号
|
||||
*/
|
||||
current_version: string;
|
||||
/**
|
||||
* 是否强制拉取更新信息
|
||||
*/
|
||||
if_force?: boolean;
|
||||
};
|
||||
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
:width="800"
|
||||
:footer="null"
|
||||
:mask-closable="false"
|
||||
:z-index="9999"
|
||||
class="update-modal"
|
||||
>
|
||||
|
||||
<div v-if="hasUpdate" class="update-container">
|
||||
<div class="update-container">
|
||||
<!-- 更新内容展示 -->
|
||||
<div class="update-content">
|
||||
<div
|
||||
@@ -21,7 +22,7 @@
|
||||
<!-- 操作按钮 -->
|
||||
<div class="update-footer">
|
||||
<div class="update-actions">
|
||||
<a-button v-if="!downloading && !downloaded" @click="visible = false">暂不更新</a-button>
|
||||
<a-button v-if="!downloading && !downloaded" @click="handleCancel">暂不更新</a-button>
|
||||
<a-button v-if="!downloading && !downloaded" type="primary" @click="downloadUpdate">
|
||||
下载更新
|
||||
</a-button>
|
||||
@@ -40,19 +41,43 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import { Service } from '@/api/services/Service.ts'
|
||||
|
||||
const visible = ref(false)
|
||||
// Props 定义
|
||||
interface Props {
|
||||
visible: boolean
|
||||
updateData: Record<string, string[]>
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
// Emits 定义
|
||||
const emit = defineEmits<{
|
||||
confirmed: []
|
||||
'update:visible': [value: boolean]
|
||||
}>()
|
||||
|
||||
// 内部状态
|
||||
const hasUpdate = ref(false)
|
||||
const downloading = ref(false)
|
||||
const downloaded = ref(false)
|
||||
const installing = ref(false)
|
||||
const updateContent = ref("")
|
||||
const latestVersion = ref("")
|
||||
|
||||
// 计算属性 - 响应式地接收外部 visible 状态
|
||||
const visible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value: boolean) => emit('update:visible', value)
|
||||
})
|
||||
|
||||
// 计算属性 - 转换 updateData 为 markdown
|
||||
const updateContent = computed(() => {
|
||||
return updateInfoToMarkdown(props.updateData, latestVersion.value, '更新内容')
|
||||
})
|
||||
|
||||
// markdown 渲染器
|
||||
const md = new MarkdownIt({ html: true, linkify: true, typographer: true })
|
||||
const renderMarkdown = (content: string) => md.render(content)
|
||||
@@ -105,27 +130,34 @@ function updateInfoToMarkdown(
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
// 初始化弹窗流程
|
||||
const initUpdateCheck = async () => {
|
||||
try {
|
||||
const version = import.meta.env.VITE_APP_VERSION || '0.0.0'
|
||||
const response = await Service.checkUpdateApiUpdateCheckPost({ current_version: version })
|
||||
|
||||
if (response.code === 200 && response.if_need_update) {
|
||||
// 初始化时设置 hasUpdate
|
||||
const initializeModal = () => {
|
||||
if (props.updateData && Object.keys(props.updateData).length > 0) {
|
||||
hasUpdate.value = true
|
||||
latestVersion.value = response.latest_version || ''
|
||||
// ✅ 核心修改:把对象转成 Markdown 再给渲染器
|
||||
updateContent.value = updateInfoToMarkdown(
|
||||
response.update_info,
|
||||
response.latest_version,
|
||||
'更新内容'
|
||||
// 从 updateData 中提取版本信息(如果有的话)
|
||||
const versionInfo = Object.values(props.updateData).flat().find(item =>
|
||||
typeof item === 'string' && item.includes('版本')
|
||||
)
|
||||
visible.value = true
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('检查更新失败:', err)
|
||||
if (versionInfo) {
|
||||
const versionMatch = versionInfo.match(/v?(\d+\.\d+\.\d+)/)
|
||||
if (versionMatch) {
|
||||
latestVersion.value = versionMatch[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 监听 props 变化
|
||||
watch(() => props.updateData, () => {
|
||||
initializeModal()
|
||||
}, { immediate: true })
|
||||
|
||||
// 监听 visible 变化
|
||||
watch(() => props.visible, (newVisible) => {
|
||||
if (newVisible && props.updateData && Object.keys(props.updateData).length > 0) {
|
||||
hasUpdate.value = true
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// 下载更新
|
||||
const downloadUpdate = async () => {
|
||||
@@ -134,6 +166,7 @@ const downloadUpdate = async () => {
|
||||
const res = await Service.downloadUpdateApiUpdateDownloadPost()
|
||||
if (res.code === 200) {
|
||||
downloaded.value = true
|
||||
message.success('下载完成')
|
||||
} else {
|
||||
message.error(res.message || '下载失败')
|
||||
}
|
||||
@@ -153,6 +186,7 @@ const installUpdate = async () => {
|
||||
if (res.code === 200) {
|
||||
message.success('安装启动')
|
||||
visible.value = false
|
||||
emit('confirmed')
|
||||
} else {
|
||||
message.error(res.message || '安装失败')
|
||||
}
|
||||
@@ -164,9 +198,11 @@ const installUpdate = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initUpdateCheck()
|
||||
})
|
||||
// 关闭弹窗
|
||||
const handleCancel = () => {
|
||||
visible.value = false
|
||||
emit('confirmed')
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
117
frontend/src/composables/useUpdateChecker.ts
Normal file
117
frontend/src/composables/useUpdateChecker.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { Service } from '@/api'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
// 获取版本号,优先使用环境变量,否则使用一个测试版本
|
||||
const version = (import.meta as any).env.VITE_APP_VERSION || '1.0.0'
|
||||
|
||||
// 全局状态 - 在所有组件间共享
|
||||
const updateVisible = ref(false)
|
||||
const updateData = ref<Record<string, string[]>>({})
|
||||
|
||||
// 定时器相关 - 参考顶栏TitleBar.vue的实现
|
||||
const POLL_MS = 4 * 60 * 60 * 1000 // 4小时
|
||||
let updateCheckTimer: NodeJS.Timeout | null = null
|
||||
const isPolling = ref(false)
|
||||
|
||||
// 防止重复弹出的状态
|
||||
let lastShownVersion: string | null = null
|
||||
|
||||
export function useUpdateChecker() {
|
||||
|
||||
// 执行一次更新检查 - 完全参考顶栏的 pollOnce 逻辑
|
||||
const pollOnce = async () => {
|
||||
if (isPolling.value) return
|
||||
isPolling.value = true
|
||||
|
||||
try {
|
||||
const response = await Service.checkUpdateApiUpdateCheckPost({
|
||||
current_version: version,
|
||||
if_force: false, // 定时检查不强制获取,和顶栏一致
|
||||
})
|
||||
|
||||
if (response.code === 200) {
|
||||
if (response.if_need_update) {
|
||||
// 检查是否已经有更新弹窗在显示,避免重复弹出
|
||||
if (updateVisible.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否为同一版本,避免同一版本重复弹出
|
||||
if (lastShownVersion === response.latest_version) {
|
||||
return
|
||||
}
|
||||
|
||||
updateData.value = response.update_info
|
||||
updateVisible.value = true
|
||||
lastShownVersion = response.latest_version // 记录已显示的版本
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('[useUpdateChecker] 定时更新检查失败:', error?.message)
|
||||
} finally {
|
||||
isPolling.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 手动检查更新(用于设置页面按钮)
|
||||
const checkUpdate = async (silent = false, forceCheck = false) => {
|
||||
try {
|
||||
const response = await Service.checkUpdateApiUpdateCheckPost({
|
||||
current_version: version,
|
||||
if_force: forceCheck,
|
||||
})
|
||||
|
||||
if (response.code === 200) {
|
||||
if (response.if_need_update) {
|
||||
updateData.value = response.update_info
|
||||
updateVisible.value = true
|
||||
} else {
|
||||
if (!silent) {
|
||||
message.success('暂无更新~')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!silent) {
|
||||
message.error(response.message || '获取更新失败')
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('[useUpdateChecker] 手动更新检查失败:', error?.message)
|
||||
if (!silent) {
|
||||
message.error('获取更新失败!')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 确认回调
|
||||
const onUpdateConfirmed = () => {
|
||||
updateVisible.value = false
|
||||
}
|
||||
|
||||
// 组件挂载时启动检查器 - 参考顶栏的 onMounted 逻辑
|
||||
onMounted(async () => {
|
||||
// 延迟3秒后再执行首次检查,确保后端已经完全启动
|
||||
setTimeout(async () => {
|
||||
await pollOnce()
|
||||
}, 3000)
|
||||
|
||||
// 每 4 小时检查一次更新
|
||||
updateCheckTimer = setInterval(pollOnce, POLL_MS)
|
||||
})
|
||||
|
||||
// 组件卸载时清理定时器 - 参考顶栏的 onBeforeUnmount 逻辑
|
||||
onUnmounted(() => {
|
||||
if (updateCheckTimer) {
|
||||
clearInterval(updateCheckTimer)
|
||||
updateCheckTimer = null
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
updateVisible,
|
||||
updateData,
|
||||
checkUpdate,
|
||||
onUpdateConfirmed
|
||||
}
|
||||
}
|
||||
@@ -25,13 +25,6 @@
|
||||
@confirmed="onNoticeConfirmed"
|
||||
/>
|
||||
|
||||
<!-- 更新模态框 -->
|
||||
<UpdateModal
|
||||
v-model:visible="updateVisible"
|
||||
:update-data="updateData"
|
||||
@confirmed="onUpdateConfirmed"
|
||||
/>
|
||||
|
||||
<div class="content">
|
||||
<!-- 当期活动关卡 -->
|
||||
<a-card
|
||||
@@ -253,7 +246,6 @@ import { Service } from '@/api/services/Service'
|
||||
import NoticeModal from '@/components/NoticeModal.vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { API_ENDPOINTS } from '@/config/mirrors.ts'
|
||||
import UpdateModal from '@/components/UpdateModal.vue'
|
||||
|
||||
interface ActivityInfo {
|
||||
Tip: string
|
||||
@@ -308,11 +300,6 @@ const noticeVisible = ref(false)
|
||||
const noticeData = ref<Record<string, string>>({})
|
||||
const noticeLoading = ref(false)
|
||||
|
||||
// 更新相关
|
||||
const version = import.meta.env.VITE_APP_VERSION || '获取版本失败!'
|
||||
const updateVisible = ref(false)
|
||||
const updateData = ref<Record<string, string[]>>({})
|
||||
|
||||
// 获取当前活动信息
|
||||
const currentActivity = computed(() => {
|
||||
if (!activityData.value.length) return null
|
||||
@@ -492,35 +479,9 @@ const showNotice = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const checkUpdate = async () => {
|
||||
try {
|
||||
const response = await Service.checkUpdateApiUpdateCheckPost({
|
||||
current_version: version,
|
||||
})
|
||||
if (response.code === 200) {
|
||||
if (response.if_need_update) {
|
||||
updateData.value = response.update_info
|
||||
updateVisible.value = true
|
||||
} else {
|
||||
}
|
||||
} else {
|
||||
message.error(response.message || '获取更新失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取更新失败:', error)
|
||||
return '获取更新失败!'
|
||||
}
|
||||
}
|
||||
|
||||
// 确认回调
|
||||
const onUpdateConfirmed = () => {
|
||||
updateVisible.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchActivityData()
|
||||
fetchNoticeData()
|
||||
checkUpdate()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -549,11 +510,6 @@ onMounted(() => {
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
/* 公告相关样式 */
|
||||
.notice-modal {
|
||||
/* 自定义公告模态框样式 */
|
||||
}
|
||||
|
||||
.activity-card {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
@@ -221,6 +221,7 @@ const checkUpdate = async () => {
|
||||
try {
|
||||
const response = await Service.checkUpdateApiUpdateCheckPost({
|
||||
current_version: version,
|
||||
if_force: true, // 手动检查强制获取最新信息
|
||||
})
|
||||
if (response.code === 200) {
|
||||
if (response.if_need_update) {
|
||||
@@ -234,7 +235,7 @@ const checkUpdate = async () => {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取更新失败:', error)
|
||||
return '获取更新失败!'
|
||||
message.error('获取更新失败!')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user