diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 187a864..f9204c8 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -81,24 +81,13 @@ jobs: - name: Create Release if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') - id: create_release - uses: actions/create-release@v1 + uses: softprops/action-gh-release@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - tag_name: ${{ github.ref }} - release_name: Release ${{ github.ref_name }} + tag_name: ${{ github.ref_name }} + name: Release ${{ github.ref_name }} body: ${{ env.CHANGELOG }} draft: false prerelease: false - - - name: Upload Release Asset - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./${{ env.ZIP_NAME }} - asset_name: ${{ env.ZIP_NAME }} - asset_content_type: application/zip + files: ${{ env.ZIP_NAME }} diff --git a/komari-theme.json b/komari-theme.json index 672e0ec..a3b8720 100644 --- a/komari-theme.json +++ b/komari-theme.json @@ -1,9 +1,42 @@ { - "name": "Komari Theme PurCarte", + "name": "Komari Theme PurCart", "short": "PurCarte", "description": "A frosted glass theme for Komari", - "version": "0.1.2", + "version": "0.1.3", "author": "Montia & Gemini", "url": "https://github.com/Montia37/Komari-theme-purcarte", - "preview": "preview.png" + "preview": "preview.png", + "configuration": { + "type": "managed", + "data": [ + { + "name": "背景", + "type": "title" + }, + { + "key": "backgroundImage", + "name": "背景图片链接", + "type": "string", + "required": false, + "default": "https://i.yon.li/w/682f73d97eade.png", + "help": "多张图片请以英文逗号分隔(eg:https://img.com/1.png,https://test.com/2.jpg)" + }, + { + "key": "switchTime", + "name": "切换时间(秒)", + "type": "number", + "required": false, + "default": 10, + "help": "背景图片切换的时间间隔,单位为秒(仅设置多张图片时生效)" + }, + { + "key": "transition", + "name": "背景切换过渡效果", + "type": "string", + "required": false, + "default": "background-image 0.8s ease-in-out", + "help": "CSS 过渡效果,用于背景图片切换时的动画效果(仅设置多张图片时生效)" + } + ] + } } \ No newline at end of file diff --git a/src/components/sections/Background.ts b/src/components/sections/Background.ts new file mode 100644 index 0000000..60f1d79 --- /dev/null +++ b/src/components/sections/Background.ts @@ -0,0 +1,99 @@ +import { useEffect, useState, useMemo } from "react"; +import { BACKGROUND } from "@/config/default"; +import type { PublicInfo } from "@/types/node.d"; + +/** + * 动态背景组件 + * 根据设定的时间间隔自动切换背景图片 + * 并预加载所有图片以提高用户体验 + * 支持自定义过渡效果和切换时间 + */ +interface ThemeSettings { + backgroundImage?: string; // 逗号分隔的背景图片URL列表 + switchTime?: number; // 背景切换时间间隔(秒) + transition?: string; // CSS过渡效果 +} + +interface BackgroundProps { + publicSettings: PublicInfo; +} + +function DynamicPseudoBackground({ publicSettings }: BackgroundProps) { + const theme = (publicSettings?.theme_settings as ThemeSettings) || {}; + + // 使用 useMemo 缓存背景图片列表,避免每次渲染时重新计算 + const imageList = useMemo(() => { + return theme.backgroundImage + ? theme.backgroundImage.split(",").map((url) => url.trim()) + : [BACKGROUND.backgroundImage]; + }, [theme.backgroundImage]); + + // 将切换时间从秒转换为毫秒 + const switchTime = useMemo(() => { + return (theme.switchTime || BACKGROUND.switchTime) * 1000; + }, [theme.switchTime]); + + const transition = useMemo(() => { + return theme.transition || BACKGROUND.transition; + }, [theme.transition]); + + const [currentImageIndex, setCurrentImageIndex] = useState(0); + const currentImageUrl = imageList[currentImageIndex]; + + // 预加载指定的图片 + const preloadImage = (url: string) => { + if (!url) return; + const img = new Image(); + img.src = url; + }; + + // 预加载所有图片,只在组件初始化或图片列表变化时执行一次 + useEffect(() => { + // 只有当有多张图片时才设置过渡效果 + if (imageList.length > 1) { + document.body.style.setProperty( + "--body-background-transition", + transition + ); + + // 预加载所有图片以提高用户体验 + imageList.forEach((url) => { + preloadImage(url); + }); + } + + // 组件卸载时清理 + return () => { + document.body.style.removeProperty("--body-background-transition"); + }; + }, [imageList, transition]); + + // 背景切换逻辑 + useEffect(() => { + // 当当前图片URL变化时,更新CSS变量 + document.body.style.setProperty( + "--body-background-url", + `url(${currentImageUrl})` + ); + + // 只有当有多张图片时才设置定时器进行轮换 + let intervalId: number | undefined; + if (imageList.length > 1) { + intervalId = window.setInterval(() => { + setCurrentImageIndex((prevIndex) => (prevIndex + 1) % imageList.length); + }, switchTime); + } + + // 清理函数,组件卸载或依赖项变化时执行 + return () => { + if (intervalId) { + clearInterval(intervalId); + } + }; + }, [currentImageUrl, imageList, switchTime]); + + // 此组件不渲染任何可见内容 + return null; +} + +export default DynamicPseudoBackground; diff --git a/src/components/sections/Flag.tsx b/src/components/sections/Flag.tsx index b6b7353..50265d9 100644 --- a/src/components/sections/Flag.tsx +++ b/src/components/sections/Flag.tsx @@ -93,6 +93,7 @@ const Flag = React.memo(({ flag, size }: FlagProps) => { src={imgSrc} alt={altText} style={{ width: "100%", height: "100%", objectFit: "contain" }} + loading="lazy" /> ); diff --git a/src/config/default.ts b/src/config/default.ts new file mode 100644 index 0000000..63f36f8 --- /dev/null +++ b/src/config/default.ts @@ -0,0 +1,5 @@ +export const BACKGROUND = { + backgroundImage: "https://i.yon.li/w/682f73d97eade.png", + switchTime: 10, // 10 seconds + transition: "background-image 0.8s ease-in-out", // CSS transition for background change +}; diff --git a/src/index.css b/src/index.css index 44c98d3..cad5fd8 100644 --- a/src/index.css +++ b/src/index.css @@ -79,6 +79,9 @@ /* Frosted Glass Variables */ --frosted-bg-light: rgba(255, 255, 255, 0.1); --frosted-border-light: rgba(255, 255, 255, 0.2); + + --body-background-url: url(""); + --body-background-transition: background-image 0.8s ease-in-out; } .dark { @@ -132,6 +135,19 @@ } } +/* 背景图片伪元素 */ +body::before { + content: ""; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: -1; + background: var(--body-background-url) center/cover no-repeat; + transition: var(--body-background-transition); +} + .striped-bg-red-translucent-diagonal { background-image: linear-gradient( 45deg, diff --git a/src/main.tsx b/src/main.tsx index 2e78076..597b1eb 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -5,6 +5,7 @@ import "./index.css"; import "@radix-ui/themes/styles.css"; import { Theme } from "@radix-ui/themes"; import { Header } from "@/components/sections/Header"; +import DynamicPseudoBackground from "@/components/sections/Background"; import { useTheme } from "@/hooks/useTheme"; import { NodeDataProvider } from "@/contexts/NodeDataContext"; import { LiveDataProvider } from "@/contexts/LiveDataContext"; @@ -45,6 +46,10 @@ const App = () => { appearance="inherit" scaling="110%" style={{ backgroundColor: "transparent" }}> + {/* 使用动态背景组件 */} + {publicSettings && ( + + )}
{ - if (bytes === 0) return isSpeed ? "0 B/s" : "0 Bytes"; + if (bytes === 0) return isSpeed ? "0 B/s" : "0 B"; const k = 1024; const dm = decimals < 0 ? 0 : decimals; const sizes = isSpeed ? ["B/s", "KB/s", "MB/s", "GB/s", "TB/s"] - : ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB"]; + : ["B", "KB", "MB", "GB", "TB", "PB", "EB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]; }; @@ -52,7 +52,7 @@ export const formatPrice = ( billingCycle: number ) => { if (price === -1) return "免费"; - if (price === 0) return "未设置"; + if (price === 0) return ""; if (!currency || !billingCycle) return "N/A"; let cycleStr = `${billingCycle}天`;