diff --git a/.husky/pre-commit b/.husky/pre-commit index c37466e..b244197 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,10 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" +# 生成版本号 +pnpm gen:version + +# 自动添加修改的版本文件 +git add src/lib/version.ts + npx lint-staged \ No newline at end of file diff --git a/VERSION.txt b/VERSION.txt new file mode 100644 index 0000000..c004e9c --- /dev/null +++ b/VERSION.txt @@ -0,0 +1 @@ +20250728004740 \ No newline at end of file diff --git a/package.json b/package.json index 03d919b..9f0add8 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "format:check": "prettier -c .", "gen:runtime": "node scripts/convert-config.js", "gen:manifest": "node scripts/generate-manifest.js", + "gen:version": "node scripts/generate-version.js", "postbuild": "echo 'Build completed - sitemap generation disabled'", "prepare": "husky install", "pages:build": "pnpm gen:runtime && pnpm gen:manifest && next build && npx @cloudflare/next-on-pages --experimental-minify" @@ -88,4 +89,4 @@ ] }, "packageManager": "pnpm@10.12.4+sha512.5ea8b0deed94ed68691c9bad4c955492705c5eeb8a87ef86bc62c74a26b037b08ff9570f108b2e4dbd1dd1a9186fea925e527f141c648e85af45631074680184" -} \ No newline at end of file +} diff --git a/scripts/generate-version.js b/scripts/generate-version.js new file mode 100644 index 0000000..fb946ae --- /dev/null +++ b/scripts/generate-version.js @@ -0,0 +1,45 @@ +#!/usr/bin/env node +/* eslint-disable */ + +const fs = require('fs'); +const path = require('path'); + +// 获取当前时间并格式化为 YYYYMMDDHHMMSS 格式 +function generateVersion() { + const now = new Date(); + + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const hours = String(now.getHours()).padStart(2, '0'); + const minutes = String(now.getMinutes()).padStart(2, '0'); + const seconds = String(now.getSeconds()).padStart(2, '0'); + + const version = `${year}${month}${day}${hours}${minutes}${seconds}`; + + return version; +} + +// 生成版本号 +const currentVersion = generateVersion(); + +// 读取现有的 version.ts 文件 +const versionFilePath = path.join(__dirname, '..', 'src', 'lib', 'version.ts'); +let fileContent = fs.readFileSync(versionFilePath, 'utf8'); + +// 使用正则表达式替换 CURRENT_VERSION 的值 +const updatedContent = fileContent.replace( + /const CURRENT_VERSION = '.*?'/, + `const CURRENT_VERSION = '${currentVersion}'` +); + +// 写入更新后的内容 +fs.writeFileSync(versionFilePath, updatedContent, 'utf8'); + +// 将版本号写入根目录下的 VERSION.txt 文件 +const versionTxtPath = path.join(__dirname, '..', 'VERSION.txt'); +fs.writeFileSync(versionTxtPath, currentVersion, 'utf8'); + +console.log(`版本号已更新为: ${currentVersion}`); +console.log(`文件已更新: ${versionFilePath}`); +console.log(`VERSION.txt 已更新: ${versionTxtPath}`); diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 4eac8de..0f58d68 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -5,9 +5,55 @@ import { useRouter, useSearchParams } from 'next/navigation'; import { Suspense, useEffect, useState } from 'react'; +import { checkForUpdates, CURRENT_VERSION } from '@/lib/version'; + import { useSite } from '@/components/SiteProvider'; import { ThemeToggle } from '@/components/ThemeToggle'; +// 版本显示组件 +function VersionDisplay() { + const [hasUpdate, setHasUpdate] = useState(false); + const [isChecking, setIsChecking] = useState(true); + + useEffect(() => { + const checkUpdate = async () => { + try { + const updateAvailable = await checkForUpdates(); + setHasUpdate(updateAvailable); + } catch (_) { + // do nothing + } finally { + setIsChecking(false); + } + }; + + checkUpdate(); + }, []); + + return ( + + ); +} + function LoginPageClient() { const router = useRouter(); const searchParams = useSearchParams(); @@ -170,6 +216,9 @@ function LoginPageClient() { )} + + {/* 版本信息显示 */} + ); } diff --git a/src/components/UserMenu.tsx b/src/components/UserMenu.tsx index 1fab54c..72d1e3f 100644 --- a/src/components/UserMenu.tsx +++ b/src/components/UserMenu.tsx @@ -8,6 +8,7 @@ import { useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import { getAuthInfoFromBrowserCookie } from '@/lib/auth'; +import { checkForUpdates, CURRENT_VERSION } from '@/lib/version'; interface AuthInfo { username?: string; @@ -37,6 +38,9 @@ export const UserMenu: React.FC = () => { const [passwordLoading, setPasswordLoading] = useState(false); const [passwordError, setPasswordError] = useState(''); + // 版本检查相关状态 + const [hasUpdate, setHasUpdate] = useState(false); + // 确保组件已挂载 useEffect(() => { setMounted(true); @@ -104,6 +108,20 @@ export const UserMenu: React.FC = () => { } }, []); + // 版本检查 + useEffect(() => { + const checkUpdate = async () => { + try { + const updateAvailable = await checkForUpdates(); + setHasUpdate(updateAvailable); + } catch (error) { + console.warn('版本检查失败:', error); + } + }; + + checkUpdate(); + }, []); + const handleMenuClick = () => { setIsOpen(!isOpen); }; @@ -375,6 +393,24 @@ export const UserMenu: React.FC = () => { 登出 + + {/* 分割线 */} +
+ + {/* 版本信息 */} + @@ -672,13 +708,18 @@ export const UserMenu: React.FC = () => { return ( <> - +
+ + {hasUpdate && ( +
+ )} +
{/* 使用 Portal 将菜单面板渲染到 document.body */} {isOpen && mounted && createPortal(menuPanel, document.body)} diff --git a/src/lib/version.ts b/src/lib/version.ts new file mode 100644 index 0000000..3854ea6 --- /dev/null +++ b/src/lib/version.ts @@ -0,0 +1,90 @@ +/* eslint-disable no-console */ + +'use client'; + +const CURRENT_VERSION = '20250728004845'; + +// 远程版本检查URL配置 +const VERSION_CHECK_URLS = [ + 'https://ghfast.top/raw.githubusercontent.com/senshinya/MoonTV/main/VERSION.txt', + 'https://raw.githubusercontent.com/senshinya/MoonTV/main/VERSION.txt', +]; + +/** + * 检查是否有新版本可用 + * @returns Promise - true表示有新版本,false表示当前版本是最新的 + */ +export async function checkForUpdates(): Promise { + try { + // 尝试从主要URL获取版本信息 + const primaryVersion = await fetchVersionFromUrl(VERSION_CHECK_URLS[0]); + if (primaryVersion) { + return compareVersions(primaryVersion); + } + + // 如果主要URL失败,尝试备用URL + const backupVersion = await fetchVersionFromUrl(VERSION_CHECK_URLS[1]); + if (backupVersion) { + return compareVersions(backupVersion); + } + + // 如果两个URL都失败,返回false(假设当前版本是最新的) + return false; + } catch (error) { + console.error('版本检查失败:', error); + return false; + } +} + +/** + * 从指定URL获取版本信息 + * @param url - 版本信息URL + * @returns Promise - 版本字符串或null + */ +async function fetchVersionFromUrl(url: string): Promise { + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 5000); // 5秒超时 + + const response = await fetch(url, { + method: 'GET', + signal: controller.signal, + headers: { + 'Content-Type': 'text/plain', + }, + }); + + clearTimeout(timeoutId); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const version = await response.text(); + return version.trim(); + } catch (error) { + console.warn(`从 ${url} 获取版本信息失败:`, error); + return null; + } +} + +/** + * 比较版本号 + * @param remoteVersion - 远程版本号 + * @returns boolean - true表示远程版本更新 + */ +function compareVersions(remoteVersion: string): boolean { + try { + // 将版本号转换为数字进行比较 + const current = parseInt(CURRENT_VERSION, 10); + const remote = parseInt(remoteVersion, 10); + + return remote !== current; + } catch (error) { + console.error('版本比较失败:', error); + return false; + } +} + +// 导出当前版本号供其他地方使用 +export { CURRENT_VERSION };