From 2e4f2a8a98c591ee89149da8c39e93e56046aa6d Mon Sep 17 00:00:00 2001 From: Montia37 Date: Fri, 15 Aug 2025 04:46:19 +0800 Subject: [PATCH] =?UTF-8?q?feat(theme):=20=E5=B0=9D=E8=AF=95=E9=80=82?= =?UTF-8?q?=E9=85=8D=201.0.5=20=E6=9B=B4=E6=96=B0=EF=BC=8C=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E8=83=8C=E6=99=AF=E8=87=AA=E5=AE=9A=E4=B9=89=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增主题配置项,允许用户通过 Komari 后台自定义背景图片、切换时间和过渡效果。 - 更新 GitHub Actions 发布流程,改用 `softprops/action-gh-release@v2` 以简化发布和资产上传步骤。 - 为国旗图片添加懒加载(`loading="lazy"`),优化页面加载性能。 --- .github/workflows/build.yaml | 19 ++--- komari-theme.json | 39 ++++++++++- src/components/sections/Background.ts | 99 +++++++++++++++++++++++++++ src/components/sections/Flag.tsx | 1 + src/config/default.ts | 5 ++ src/index.css | 16 +++++ src/main.tsx | 5 ++ src/types/node.d.ts | 1 + src/utils/formatHelper.ts | 6 +- 9 files changed, 170 insertions(+), 21 deletions(-) create mode 100644 src/components/sections/Background.ts create mode 100644 src/config/default.ts 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}天`;