mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-06 01:56:37 +08:00
fix(ui): improve background handling and PWA update prompts
- Add adaptive solid color background support for dark/light themes. - Replace mesh gradient with semi-transparent blurred background images. - Fix background scrolling issue on mobile browsers. - Remove borders from specific cards for consistent UI styling. - Prevent caching of PWA service worker files in Nginx. - Introduce interactive toast prompt for PWA updates instead of silent refresh.
This commit is contained in:
@@ -43,6 +43,14 @@ http {
|
|||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
index index.html;
|
index index.html;
|
||||||
|
|
||||||
|
# PWA 和 Service Worker 不缓存
|
||||||
|
location ~* (sw\.js|service-worker\.js|workbox-.*\.js|manifest\.webmanifest)$ {
|
||||||
|
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||||
|
add_header Pragma "no-cache";
|
||||||
|
add_header Expires "0";
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
|
||||||
# 静态文件缓存
|
# 静态文件缓存
|
||||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||||||
expires 30d;
|
expires 30d;
|
||||||
|
|||||||
@@ -95,11 +95,28 @@ function App() {
|
|||||||
const updateBg = () => {
|
const updateBg = () => {
|
||||||
const customBg = siteConfig.app_bg_image;
|
const customBg = siteConfig.app_bg_image;
|
||||||
if (customBg) {
|
if (customBg) {
|
||||||
document.documentElement.style.setProperty("--custom-bg-image", `url(${customBg})`);
|
if (customBg === "theme") {
|
||||||
document.documentElement.classList.add("has-custom-bg");
|
document.documentElement.style.removeProperty("--custom-bg-image");
|
||||||
|
document.documentElement.style.removeProperty("--custom-bg-color");
|
||||||
|
document.documentElement.classList.add("has-theme-bg");
|
||||||
|
document.documentElement.classList.remove("has-custom-bg");
|
||||||
|
} else if (customBg.startsWith("http") || customBg.startsWith("data:") || customBg.startsWith("/") || customBg.startsWith("blob:")) {
|
||||||
|
document.documentElement.style.setProperty("--custom-bg-image", `url(${customBg})`);
|
||||||
|
document.documentElement.style.setProperty("--custom-bg-color", "transparent");
|
||||||
|
document.documentElement.classList.add("has-custom-bg");
|
||||||
|
document.documentElement.classList.remove("has-theme-bg");
|
||||||
|
} else {
|
||||||
|
// Assume solid color like "#ffffff", "white", etc.
|
||||||
|
document.documentElement.style.setProperty("--custom-bg-image", "none");
|
||||||
|
document.documentElement.style.setProperty("--custom-bg-color", customBg);
|
||||||
|
document.documentElement.classList.add("has-custom-bg");
|
||||||
|
document.documentElement.classList.remove("has-theme-bg");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.style.removeProperty("--custom-bg-image");
|
document.documentElement.style.removeProperty("--custom-bg-image");
|
||||||
|
document.documentElement.style.removeProperty("--custom-bg-color");
|
||||||
document.documentElement.classList.remove("has-custom-bg");
|
document.documentElement.classList.remove("has-custom-bg");
|
||||||
|
document.documentElement.classList.remove("has-theme-bg");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
updateBg();
|
updateBg();
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export const ThemeSettings: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="border border-gray-200 dark:border-gray-700">
|
<Card className="shadow-md">
|
||||||
<CardBody className="p-6">
|
<CardBody className="p-6">
|
||||||
<h2 className="text-lg font-medium text-gray-900 dark:text-white mb-5">
|
<h2 className="text-lg font-medium text-gray-900 dark:text-white mb-5">
|
||||||
主题设置
|
主题设置
|
||||||
|
|||||||
@@ -1,12 +1,42 @@
|
|||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import { BrowserRouter } from "react-router-dom";
|
import { BrowserRouter } from "react-router-dom";
|
||||||
import { registerSW } from "virtual:pwa-register";
|
import { registerSW } from "virtual:pwa-register";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
import App from "./App.tsx";
|
import App from "./App.tsx";
|
||||||
import { Provider } from "./provider.tsx";
|
import { Provider } from "./provider.tsx";
|
||||||
import "@/styles/globals.css";
|
import "@/styles/globals.css";
|
||||||
|
|
||||||
registerSW({ immediate: true });
|
const updateSW = registerSW({
|
||||||
|
immediate: true,
|
||||||
|
onNeedRefresh() {
|
||||||
|
toast(
|
||||||
|
(t) => (
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<span className="text-sm font-medium text-foreground">发现新版本,是否立即刷新以应用更新?</span>
|
||||||
|
<div className="flex gap-2 justify-end">
|
||||||
|
<button
|
||||||
|
className="px-3 py-1.5 text-xs font-medium bg-primary text-primary-foreground hover:bg-primary/90 rounded-md transition-colors"
|
||||||
|
onClick={() => {
|
||||||
|
updateSW(true);
|
||||||
|
toast.dismiss(t.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
刷新
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="px-3 py-1.5 text-xs font-medium bg-default-200 text-default-700 hover:bg-default-300 rounded-md transition-colors"
|
||||||
|
onClick={() => toast.dismiss(t.id)}
|
||||||
|
>
|
||||||
|
稍后
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
{ duration: Infinity, position: "bottom-right" }
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
|
|||||||
@@ -621,10 +621,13 @@ export default function ConfigPage() {
|
|||||||
|
|
||||||
const renderBgImageUploader = () => {
|
const renderBgImageUploader = () => {
|
||||||
const bgImage = configs["app_bg_image"] || "";
|
const bgImage = configs["app_bg_image"] || "";
|
||||||
|
const isImage = bgImage.startsWith("http") || bgImage.startsWith("data:") || bgImage.startsWith("/") || bgImage.startsWith("blob:");
|
||||||
|
const isTheme = bgImage === "theme";
|
||||||
|
const isSolidColor = bgImage && !isImage && !isTheme;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4 w-full">
|
<div className="flex flex-col gap-4 w-full">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex flex-wrap items-center gap-4">
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
@@ -638,7 +641,23 @@ export default function ConfigPage() {
|
|||||||
onPress={() => bgImageFileInputRef.current?.click()}
|
onPress={() => bgImageFileInputRef.current?.click()}
|
||||||
isLoading={bgImageUploading}
|
isLoading={bgImageUploading}
|
||||||
>
|
>
|
||||||
选择图片
|
上传图片
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
variant="flat"
|
||||||
|
onPress={() => handleConfigChange("app_bg_image", "theme")}
|
||||||
|
isDisabled={bgImageUploading || isTheme}
|
||||||
|
>
|
||||||
|
自适应纯色 (跟随深色模式)
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="default"
|
||||||
|
variant="flat"
|
||||||
|
onPress={() => handleConfigChange("app_bg_image", "#ffffff")}
|
||||||
|
isDisabled={bgImageUploading || bgImage === "#ffffff"}
|
||||||
|
>
|
||||||
|
白色纯色
|
||||||
</Button>
|
</Button>
|
||||||
{bgImage && (
|
{bgImage && (
|
||||||
<Button
|
<Button
|
||||||
@@ -647,11 +666,12 @@ export default function ConfigPage() {
|
|||||||
onPress={() => handleConfigChange("app_bg_image", "")}
|
onPress={() => handleConfigChange("app_bg_image", "")}
|
||||||
isDisabled={bgImageUploading}
|
isDisabled={bgImageUploading}
|
||||||
>
|
>
|
||||||
清除背景
|
恢复默认
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{bgImage && (
|
|
||||||
|
{bgImage && isImage && (
|
||||||
<div className="relative rounded-xl overflow-hidden border border-divider">
|
<div className="relative rounded-xl overflow-hidden border border-divider">
|
||||||
<img
|
<img
|
||||||
src={bgImage}
|
src={bgImage}
|
||||||
@@ -663,6 +683,23 @@ export default function ConfigPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{bgImage && isTheme && (
|
||||||
|
<div className="relative rounded-xl border border-divider bg-background h-32 flex items-center justify-center">
|
||||||
|
<span className="text-foreground text-sm font-medium">当前为自适应纯色背景</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{bgImage && isSolidColor && (
|
||||||
|
<div
|
||||||
|
className="relative rounded-xl border border-divider h-32 flex items-center justify-center"
|
||||||
|
style={{ backgroundColor: bgImage }}
|
||||||
|
>
|
||||||
|
<span className="text-gray-500 bg-white/80 dark:bg-black/80 px-2 py-1 rounded text-sm font-medium border border-gray-200 dark:border-gray-800 shadow-sm">
|
||||||
|
纯色背景 ({bgImage})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -327,7 +327,7 @@ export default function LimitPage() {
|
|||||||
<StaggerList className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4">
|
<StaggerList className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4">
|
||||||
{filteredRules.map((rule) => (
|
{filteredRules.map((rule) => (
|
||||||
<StaggerItem key={rule.id}>
|
<StaggerItem key={rule.id}>
|
||||||
<Card className="shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden h-full">
|
<Card className="shadow-sm overflow-hidden h-full">
|
||||||
<CardHeader className="pb-2 md:pb-2">
|
<CardHeader className="pb-2 md:pb-2">
|
||||||
<div className="flex justify-between items-start w-full">
|
<div className="flex justify-between items-start w-full">
|
||||||
<div>
|
<div>
|
||||||
@@ -413,7 +413,7 @@ export default function LimitPage() {
|
|||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
/* 空状态 */
|
/* 空状态 */
|
||||||
<Card className="shadow-sm border border-gray-200 dark:border-gray-700 bg-default-50/50">
|
<Card className="shadow-sm bg-default-50/50">
|
||||||
<CardBody className="text-center py-20 flex flex-col items-center justify-center min-h-[240px]">
|
<CardBody className="text-center py-20 flex flex-col items-center justify-center min-h-[240px]">
|
||||||
<h3 className="text-xl font-medium text-foreground tracking-tight mb-2">
|
<h3 className="text-xl font-medium text-foreground tracking-tight mb-2">
|
||||||
暂无限速规则
|
暂无限速规则
|
||||||
|
|||||||
@@ -227,24 +227,56 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bg-mesh-gradient {
|
.bg-mesh-gradient {
|
||||||
background: radial-gradient(at 0% 0%, #ff9a9e 0%, transparent 50%),
|
|
||||||
radial-gradient(at 100% 0%, #fecfef 0%, transparent 50%),
|
|
||||||
radial-gradient(at 100% 100%, #c2e9fb 0%, transparent 50%),
|
|
||||||
radial-gradient(at 0% 100%, #a1c4fd 0%, transparent 50%);
|
|
||||||
background-color: #f2f2f7;
|
background-color: #f2f2f7;
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .bg-mesh-gradient {
|
.bg-mesh-gradient::before {
|
||||||
background: radial-gradient(at 0% 0%, #4a2b30 0%, transparent 50%),
|
content: "";
|
||||||
radial-gradient(at 100% 0%, #493b45 0%, transparent 50%),
|
position: fixed;
|
||||||
radial-gradient(at 100% 100%, #354756 0%, transparent 50%),
|
top: 0;
|
||||||
radial-gradient(at 0% 100%, #2f394f 0%, transparent 50%);
|
left: 0;
|
||||||
background-color: #1c1c1e;
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: -1;
|
||||||
|
pointer-events: none;
|
||||||
|
background: url('https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?q=80&w=2564&auto=format&fit=crop') no-repeat center center;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .bg-mesh-gradient {
|
||||||
|
background-color: #0b1020;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .bg-mesh-gradient::before {
|
||||||
|
background: url('https://images.unsplash.com/photo-1557682250-33bd709cbe85?q=80&w=2564&auto=format&fit=crop') no-repeat center center;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.has-theme-bg .bg-mesh-gradient {
|
||||||
|
background-image: none !important;
|
||||||
|
background-color: #f6f7fb !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark.has-theme-bg .bg-mesh-gradient {
|
||||||
|
background-color: #0b1020 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.has-theme-bg .bg-mesh-gradient::before {
|
||||||
|
background-image: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root.has-custom-bg .bg-mesh-gradient {
|
:root.has-custom-bg .bg-mesh-gradient {
|
||||||
background: var(--custom-bg-image) no-repeat center center fixed !important;
|
background-image: none !important;
|
||||||
|
background-color: var(--custom-bg-color, transparent) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.has-custom-bg .bg-mesh-gradient::before {
|
||||||
|
background-image: var(--custom-bg-image, none) !important;
|
||||||
background-size: cover !important;
|
background-size: cover !important;
|
||||||
|
background-position: center center !important;
|
||||||
|
background-repeat: no-repeat !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export default defineConfig({
|
|||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
react(),
|
||||||
VitePWA({
|
VitePWA({
|
||||||
registerType: "autoUpdate",
|
registerType: "prompt",
|
||||||
injectRegister: "auto",
|
injectRegister: "auto",
|
||||||
includeAssets: ["favicon.ico", "apple-touch-icon.png"],
|
includeAssets: ["favicon.ico", "apple-touch-icon.png"],
|
||||||
manifest: {
|
manifest: {
|
||||||
|
|||||||
Reference in New Issue
Block a user