update:1.添加版本检查
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -1,11 +1,56 @@
|
|||||||
import { Typography, Space, Divider } from 'antd';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { Typography, Space, Divider, Badge, Tooltip } from 'antd';
|
||||||
import { GithubOutlined, CopyrightOutlined, HeartFilled, ClockCircleOutlined } from '@ant-design/icons';
|
import { GithubOutlined, CopyrightOutlined, HeartFilled, ClockCircleOutlined } from '@ant-design/icons';
|
||||||
import { VERSION_INFO, getVersionString } from '../config/version';
|
import { VERSION_INFO, getVersionString } from '../config/version';
|
||||||
|
import { checkLatestVersion } from '../services/versionService';
|
||||||
|
|
||||||
const { Text, Link } = Typography;
|
const { Text, Link } = Typography;
|
||||||
|
|
||||||
export default function AppFooter() {
|
export default function AppFooter() {
|
||||||
const isMobile = window.innerWidth <= 768;
|
const isMobile = window.innerWidth <= 768;
|
||||||
|
const [hasUpdate, setHasUpdate] = useState(false);
|
||||||
|
const [latestVersion, setLatestVersion] = useState('');
|
||||||
|
const [releaseUrl, setReleaseUrl] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 检查版本更新(每次都重新检查)
|
||||||
|
const checkVersion = async () => {
|
||||||
|
console.log('[页脚组件] 开始版本检查流程...');
|
||||||
|
console.log('[页脚组件] 开始从 GitHub 检查版本...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await checkLatestVersion();
|
||||||
|
console.log('[页脚组件] 版本检查结果:', result);
|
||||||
|
console.log('[页脚组件] 是否显示红点:', result.hasUpdate);
|
||||||
|
|
||||||
|
setHasUpdate(result.hasUpdate);
|
||||||
|
setLatestVersion(result.latestVersion);
|
||||||
|
setReleaseUrl(result.releaseUrl);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[页脚组件] 版本检查失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 延迟3秒后检查,避免影响首次加载
|
||||||
|
console.log('[页脚组件] 将在3秒后开始版本检查');
|
||||||
|
const timer = setTimeout(checkVersion, 3000);
|
||||||
|
return () => {
|
||||||
|
console.log('[页脚组件] 清理定时器');
|
||||||
|
clearTimeout(timer);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 点击版本号查看更新
|
||||||
|
const handleVersionClick = () => {
|
||||||
|
console.log('[页脚组件] 版本号被点击, hasUpdate:', hasUpdate, 'releaseUrl:', releaseUrl);
|
||||||
|
|
||||||
|
if (hasUpdate && releaseUrl) {
|
||||||
|
console.log('[页脚组件] 打开发布页面:', releaseUrl);
|
||||||
|
window.open(releaseUrl, '_blank');
|
||||||
|
} else {
|
||||||
|
console.log('[页脚组件] 无更新或无发布链接,不执行跳转');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -38,19 +83,25 @@ export default function AppFooter() {
|
|||||||
gap: 8,
|
gap: 8,
|
||||||
flexWrap: 'wrap'
|
flexWrap: 'wrap'
|
||||||
}}>
|
}}>
|
||||||
<Text
|
<Badge dot={hasUpdate} offset={[-8, 2]}>
|
||||||
style={{
|
<Tooltip title={hasUpdate ? `发现新版本 v${latestVersion},点击查看` : '当前版本'}>
|
||||||
fontSize: 11,
|
<Text
|
||||||
display: 'flex',
|
onClick={handleVersionClick}
|
||||||
alignItems: 'center',
|
style={{
|
||||||
gap: 4,
|
fontSize: 11,
|
||||||
color: '#fff',
|
display: 'flex',
|
||||||
textShadow: '0 1px 3px rgba(0, 0, 0, 0.3)',
|
alignItems: 'center',
|
||||||
}}
|
gap: 4,
|
||||||
>
|
color: '#fff',
|
||||||
<strong style={{ color: '#fff' }}>{VERSION_INFO.projectName}</strong>
|
textShadow: '0 1px 3px rgba(0, 0, 0, 0.3)',
|
||||||
<span>{getVersionString()}</span>
|
cursor: hasUpdate ? 'pointer' : 'default',
|
||||||
</Text>
|
}}
|
||||||
|
>
|
||||||
|
<strong style={{ color: '#fff' }}>{VERSION_INFO.projectName}</strong>
|
||||||
|
<span>{getVersionString()}</span>
|
||||||
|
</Text>
|
||||||
|
</Tooltip>
|
||||||
|
</Badge>
|
||||||
<Divider type="vertical" style={{ margin: '0 4px', borderColor: 'rgba(255, 255, 255, 0.3)' }} />
|
<Divider type="vertical" style={{ margin: '0 4px', borderColor: 'rgba(255, 255, 255, 0.3)' }} />
|
||||||
<Link
|
<Link
|
||||||
href={VERSION_INFO.githubUrl}
|
href={VERSION_INFO.githubUrl}
|
||||||
@@ -91,19 +142,36 @@ export default function AppFooter() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* 版本信息 */}
|
{/* 版本信息 */}
|
||||||
<Text
|
<Badge dot={hasUpdate} offset={[-8, 2]}>
|
||||||
style={{
|
<Tooltip title={hasUpdate ? `发现新版本 v${latestVersion},点击查看` : '当前版本'}>
|
||||||
fontSize: 12,
|
<Text
|
||||||
display: 'flex',
|
onClick={handleVersionClick}
|
||||||
alignItems: 'center',
|
style={{
|
||||||
gap: 6,
|
fontSize: 12,
|
||||||
color: '#fff',
|
display: 'flex',
|
||||||
textShadow: '0 1px 3px rgba(0, 0, 0, 0.3)',
|
alignItems: 'center',
|
||||||
}}
|
gap: 6,
|
||||||
>
|
color: '#fff',
|
||||||
<strong style={{ color: '#fff' }}>{VERSION_INFO.projectName}</strong>
|
textShadow: '0 1px 3px rgba(0, 0, 0, 0.3)',
|
||||||
<span>{getVersionString()}</span>
|
cursor: hasUpdate ? 'pointer' : 'default',
|
||||||
</Text>
|
transition: 'all 0.3s',
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
if (hasUpdate) {
|
||||||
|
e.currentTarget.style.transform = 'scale(1.05)';
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
if (hasUpdate) {
|
||||||
|
e.currentTarget.style.transform = 'scale(1)';
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<strong style={{ color: '#fff' }}>{VERSION_INFO.projectName}</strong>
|
||||||
|
<span>{getVersionString()}</span>
|
||||||
|
</Text>
|
||||||
|
</Tooltip>
|
||||||
|
</Badge>
|
||||||
|
|
||||||
{/* GitHub 链接 */}
|
{/* GitHub 链接 */}
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* 应用版本信息配置
|
* 应用版本信息配置
|
||||||
* 版本号遵循语义化版本规范 (Semantic Versioning)
|
* 版本号遵循语义化版本规范 (Semantic Versioning)
|
||||||
|
*
|
||||||
|
* 注意:版本号从 package.json 自动读取,无需手动维护
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const VERSION_INFO = {
|
export const VERSION_INFO = {
|
||||||
// 应用版本号
|
// 应用版本号(从 package.json 读取,构建时注入)
|
||||||
version: '1.0.1',
|
version: import.meta.env.VITE_APP_VERSION || '1.0.0',
|
||||||
|
|
||||||
// 构建时间(将在构建时由 Vite 注入)
|
// 构建时间(将在构建时由 Vite 注入)
|
||||||
buildTime: import.meta.env.VITE_BUILD_TIME || new Date().toISOString().split('T')[0],
|
buildTime: import.meta.env.VITE_BUILD_TIME || new Date().toISOString().split('T')[0],
|
||||||
|
|||||||
@@ -0,0 +1,192 @@
|
|||||||
|
import { VERSION_INFO } from '../config/version';
|
||||||
|
|
||||||
|
interface VersionCheckResult {
|
||||||
|
hasUpdate: boolean;
|
||||||
|
latestVersion: string;
|
||||||
|
releaseUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较版本号
|
||||||
|
* @returns -1: v1 < v2, 0: v1 = v2, 1: v1 > v2
|
||||||
|
*/
|
||||||
|
function compareVersion(v1: string, v2: string): number {
|
||||||
|
const parts1 = v1.split('.').map(Number);
|
||||||
|
const parts2 = v2.split('.').map(Number);
|
||||||
|
|
||||||
|
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
||||||
|
const num1 = parts1[i] || 0;
|
||||||
|
const num2 = parts2[i] || 0;
|
||||||
|
|
||||||
|
if (num1 < num2) return -1;
|
||||||
|
if (num1 > num2) return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用 shields.io Badge API 获取最新版本
|
||||||
|
* 优点:无 CORS 问题,自动从 GitHub 获取,无需维护
|
||||||
|
*/
|
||||||
|
export async function checkLatestVersion(): Promise<VersionCheckResult> {
|
||||||
|
console.log('[版本检查] 开始检查版本更新...');
|
||||||
|
console.log('[版本检查] 当前版本:', VERSION_INFO.version);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 使用 shields.io 的 GitHub release badge API
|
||||||
|
const badgeUrl = 'https://img.shields.io/github/v/release/xiamuceer-j/MuMuAINovel';
|
||||||
|
console.log('[版本检查] 请求 Badge API:', badgeUrl);
|
||||||
|
|
||||||
|
const response = await fetch(badgeUrl, {
|
||||||
|
method: 'GET',
|
||||||
|
cache: 'no-cache',
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('[版本检查] 响应状态:', response.status, response.statusText);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// shields.io 返回的是 SVG 格式
|
||||||
|
const svgText = await response.text();
|
||||||
|
console.log('[版本检查] 获取 SVG 成功,长度:', svgText.length);
|
||||||
|
|
||||||
|
// 从 SVG 中提取版本号
|
||||||
|
// SVG 中版本号通常在 <text> 标签内,格式如: v1.0.0 或 1.0.0
|
||||||
|
const versionRegex = /v?([\d.]+)/g;
|
||||||
|
const matches = svgText.match(versionRegex);
|
||||||
|
|
||||||
|
console.log('[版本检查] 正则匹配结果:', matches);
|
||||||
|
|
||||||
|
if (matches && matches.length > 0) {
|
||||||
|
// 通常最后一个匹配是版本号(前面的可能是标签文本)
|
||||||
|
const versionMatch = matches[matches.length - 1];
|
||||||
|
const latestVersion = versionMatch.replace('v', '');
|
||||||
|
|
||||||
|
console.log('[版本检查] 提取的版本号:', latestVersion);
|
||||||
|
|
||||||
|
// 验证版本号格式 (x.x.x)
|
||||||
|
if (/^\d+\.\d+(\.\d+)?$/.test(latestVersion)) {
|
||||||
|
const hasUpdate = compareVersion(VERSION_INFO.version, latestVersion) < 0;
|
||||||
|
|
||||||
|
console.log('[版本检查] 最新版本:', latestVersion);
|
||||||
|
console.log('[版本检查] 版本比较: 当前', VERSION_INFO.version, 'vs 最新', latestVersion);
|
||||||
|
console.log('[版本检查] 是否有更新:', hasUpdate);
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
hasUpdate,
|
||||||
|
latestVersion,
|
||||||
|
releaseUrl: `https://github.com/xiamuceer-j/MuMuAINovel/releases/tag/v${latestVersion}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('[版本检查] 检查完成,结果:', result);
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
console.warn('[版本检查] 版本号格式不正确:', latestVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打印 SVG 内容用于调试
|
||||||
|
console.log('[版本检查] SVG 内容片段:', svgText.substring(0, 500));
|
||||||
|
|
||||||
|
throw new Error('无法从 Badge API 解析版本信息');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[版本检查] 检查失败:', error);
|
||||||
|
console.error('[版本检查] 错误详情:', error instanceof Error ? error.message : String(error));
|
||||||
|
|
||||||
|
// 失败时返回无更新
|
||||||
|
return {
|
||||||
|
hasUpdate: false,
|
||||||
|
latestVersion: VERSION_INFO.version,
|
||||||
|
releaseUrl: VERSION_INFO.githubUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否应该执行版本检查(避免频繁请求)
|
||||||
|
*/
|
||||||
|
export function shouldCheckVersion(): boolean {
|
||||||
|
const lastCheck = localStorage.getItem('version_last_check');
|
||||||
|
|
||||||
|
if (!lastCheck) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastCheckTime = new Date(lastCheck).getTime();
|
||||||
|
const now = Date.now();
|
||||||
|
const sixHoursMs = 6 * 60 * 60 * 1000; // 6小时
|
||||||
|
|
||||||
|
return now - lastCheckTime >= sixHoursMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录版本检查时间
|
||||||
|
*/
|
||||||
|
export function markVersionChecked(): void {
|
||||||
|
localStorage.setItem('version_last_check', new Date().toISOString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取缓存的版本信息
|
||||||
|
*/
|
||||||
|
export function getCachedVersionInfo(): VersionCheckResult | null {
|
||||||
|
const cached = localStorage.getItem('version_check_result');
|
||||||
|
if (cached) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(cached);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存版本信息
|
||||||
|
*/
|
||||||
|
export function cacheVersionInfo(info: VersionCheckResult): void {
|
||||||
|
localStorage.setItem('version_check_result', JSON.stringify(info));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户已查看更新提示
|
||||||
|
*/
|
||||||
|
export function markUpdateViewed(version: string): void {
|
||||||
|
console.log('[版本检查] 标记版本已查看:', version);
|
||||||
|
localStorage.setItem('version_viewed', version);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查用户是否已查看此版本的更新提示
|
||||||
|
*/
|
||||||
|
export function hasViewedUpdate(version: string): boolean {
|
||||||
|
const viewedVersion = localStorage.getItem('version_viewed');
|
||||||
|
console.log('[版本检查] 检查是否已查看, 最新版本:', version, ', 已查看版本:', viewedVersion);
|
||||||
|
|
||||||
|
// 如果已查看的版本低于最新版本,应该显示红点
|
||||||
|
if (viewedVersion && version) {
|
||||||
|
const parts1 = viewedVersion.split('.').map(Number);
|
||||||
|
const parts2 = version.split('.').map(Number);
|
||||||
|
|
||||||
|
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
||||||
|
const num1 = parts1[i] || 0;
|
||||||
|
const num2 = parts2[i] || 0;
|
||||||
|
|
||||||
|
if (num1 < num2) {
|
||||||
|
console.log('[版本检查] 已查看版本低于最新版本,应显示红点');
|
||||||
|
return false; // 已查看的版本低于最新版本,需要显示红点
|
||||||
|
}
|
||||||
|
if (num1 > num2) {
|
||||||
|
console.log('[版本检查] 已查看版本高于最新版本,不显示红点');
|
||||||
|
return true; // 已查看的版本高于最新版本
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = viewedVersion === version;
|
||||||
|
console.log('[版本检查] 版本比较结果:', result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -1,11 +1,19 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
|
import { readFileSync } from 'fs'
|
||||||
|
import { resolve } from 'path'
|
||||||
|
|
||||||
|
// 读取 package.json 获取版本号
|
||||||
|
const packageJson = JSON.parse(
|
||||||
|
readFileSync(resolve(__dirname, 'package.json'), 'utf-8')
|
||||||
|
)
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
// 定义全局常量,在构建时注入
|
// 定义全局常量,在构建时注入
|
||||||
define: {
|
define: {
|
||||||
|
'import.meta.env.VITE_APP_VERSION': JSON.stringify(packageJson.version),
|
||||||
'import.meta.env.VITE_BUILD_TIME': JSON.stringify(
|
'import.meta.env.VITE_BUILD_TIME': JSON.stringify(
|
||||||
new Date().toISOString().split('T')[0]
|
new Date().toISOString().split('T')[0]
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user