修复ui问题

This commit is contained in:
qaq
2025-08-20 15:34:05 +08:00
parent c43e0fecdc
commit 8dc171fe4b
20 changed files with 13515 additions and 273861 deletions
+1
View File
@@ -38,6 +38,7 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.7.1' implementation 'androidx.appcompat:appcompat:1.7.1'
implementation 'com.google.android.material:material:1.12.0' implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.2.1' implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
implementation 'androidx.webkit:webkit:1.8.0'
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.3.0' androidTestImplementation 'androidx.test.ext:junit:1.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0'
@@ -1,4 +1,4 @@
import { d as domAnimation } from "./index-jxskBxXq.js"; import { d as domAnimation } from "./index-BZa3KfLh.js";
"use client"; "use client";
var index_default = domAnimation; var index_default = domAnimation;
export { export {
@@ -1,18 +0,0 @@
;
(function () {
System.register(['./index-legacy-OLEVtAva.js'], function (exports, module) {
'use strict';
var domAnimation;
return {
setters: [module => {
domAnimation = module.d;
}],
execute: function () {
"use client";
var index_default = exports("default", domAnimation);
}
};
});
})();
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3 -8
View File
@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="./vite.svg" /> <link rel="icon" type="image/svg+xml" href="./vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>哆啦A梦</title> <title>哆啦A梦</title>
<script> <script>
// 防止暗黑模式闪烁:在页面加载前立即应用系统主题 // 防止暗黑模式闪烁:在页面加载前立即应用系统主题
@@ -122,15 +122,10 @@
name="viewport" name="viewport"
/> />
<link href="./assets/favicon-DI5FVxIz.ico" rel="icon" /> <link href="./assets/favicon-DI5FVxIz.ico" rel="icon" />
<script type="module" crossorigin src="./assets/index-jxskBxXq.js"></script> <script type="module" crossorigin src="./assets/index-BZa3KfLh.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-DXlBHwCy.css"> <link rel="stylesheet" crossorigin href="./assets/index-Bf4rZ0gS.css">
<script type="module">import.meta.url;import("_").catch(()=>1);(async function*(){})().next();if(location.protocol!="file:"){window.__vite_is_modern_browser=true}</script>
<script type="module">!function(){if(window.__vite_is_modern_browser)return;console.warn("vite: loading legacy chunks, syntax error above and the same error below should be ignored");var e=document.getElementById("vite-legacy-polyfill"),n=document.createElement("script");n.src=e.src,n.onload=function(){System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))},document.body.appendChild(n)}();</script>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script nomodule>!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",(function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()}),!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();</script>
<script nomodule crossorigin id="vite-legacy-polyfill" src="./assets/polyfills-legacy-sZVJv9Pp.js"></script>
<script nomodule crossorigin id="vite-legacy-entry" data-src="./assets/index-legacy-OLEVtAva.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
</body> </body>
</html> </html>
@@ -1,35 +1,41 @@
package com.doraemon package com.doraemon
import android.graphics.Color import android.graphics.Color
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.WindowInsetsController import android.view.WindowInsetsController
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.webkit.WebView import android.webkit.*
import android.webkit.WebViewClient import androidx.webkit.WebViewAssetLoader
import android.webkit.WebSettings
import android.content.res.Configuration import android.content.res.Configuration
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private lateinit var webView: WebView private lateinit var webView: WebView
private lateinit var assetLoader: WebViewAssetLoader
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
webView = findViewById(R.id.webView) webView = findViewById(R.id.webView)
WebView.setWebContentsDebuggingEnabled(true)
setupAssetLoader()
setupWebView() setupWebView()
loadUrl() loadUrl()
// 设置状态栏和导航栏颜色跟随系统主题
setupSystemBars() setupSystemBars()
// 设置根布局背景色
setupRootBackground() setupRootBackground()
} }
private fun setupAssetLoader() {
assetLoader = WebViewAssetLoader.Builder()
.addPathHandler("/assets/", WebViewAssetLoader.AssetsPathHandler(this))
.build()
}
private fun setupWebView() { private fun setupWebView() {
val settings = webView.settings val settings = webView.settings
settings.javaScriptEnabled = true settings.javaScriptEnabled = true
@@ -40,28 +46,22 @@ class MainActivity : AppCompatActivity() {
settings.setSupportZoom(true) settings.setSupportZoom(true)
settings.builtInZoomControls = true settings.builtInZoomControls = true
settings.displayZoomControls = false settings.displayZoomControls = false
// 允许加载本地文件 // 隐藏滚动条但不影响滚动
settings.allowFileAccess = true webView.isVerticalScrollBarEnabled = false
settings.allowContentAccess = true webView.isHorizontalScrollBarEnabled = false
settings.allowFileAccessFromFileURLs = true webView.scrollBarStyle = View.SCROLLBARS_INSIDE_OVERLAY
settings.allowUniversalAccessFromFileURLs = true
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// 设置混合内容模式 settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
settings.mixedContentMode = android.webkit.WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
} }
// 设置WebView背景色跟随系统主题
val isDarkTheme = resources.configuration.uiMode and val isDarkTheme = resources.configuration.uiMode and
android.content.res.Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_MASK ==
android.content.res.Configuration.UI_MODE_NIGHT_YES Configuration.UI_MODE_NIGHT_YES
webView.setBackgroundColor(if (isDarkTheme) Color.BLACK else Color.WHITE)
if (isDarkTheme) {
webView.setBackgroundColor(Color.BLACK)
} else {
webView.setBackgroundColor(Color.WHITE)
}
// 添加JavaScript接口 // 添加JavaScript接口
webView.addJavascriptInterface(object { webView.addJavascriptInterface(object {
@@ -138,6 +138,19 @@ class MainActivity : AppCompatActivity() {
}, "AndroidInterface") }, "AndroidInterface")
webView.webViewClient = object : WebViewClient() { webView.webViewClient = object : WebViewClient() {
override fun shouldInterceptRequest(
view: WebView?,
request: WebResourceRequest?
): WebResourceResponse? {
val response = request?.url?.let { assetLoader.shouldInterceptRequest(it) }
// 👉 fallback: 如果没有匹配静态文件,返回 index.html
return response ?: if (request?.url?.path?.startsWith("/assets/") == true) {
assetLoader.shouldInterceptRequest(
Uri.parse("https://appassets.androidplatform.net/assets/index.html")
)
} else null
}
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
url?.let { view?.loadUrl(it) } url?.let { view?.loadUrl(it) }
return true return true
@@ -146,48 +159,36 @@ class MainActivity : AppCompatActivity() {
} }
private fun loadUrl() { private fun loadUrl() {
webView.getSettings().setJavaScriptEnabled(true); webView.loadUrl("https://appassets.androidplatform.net/assets/index.html")
webView.getSettings().setAllowFileAccess(true); // 允许访问 assets
webView.getSettings().setAllowFileAccessFromFileURLs(true); // Android 4.1+
webView.getSettings().setAllowUniversalAccessFromFileURLs(true); // Android 4.1+
webView.loadUrl("file:///android_asset/index.html")
} }
private fun setupSystemBars() { private fun setupSystemBars() {
// 检测当前是否为暗色主题
val isDarkTheme = resources.configuration.uiMode and val isDarkTheme = resources.configuration.uiMode and
android.content.res.Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_MASK ==
android.content.res.Configuration.UI_MODE_NIGHT_YES Configuration.UI_MODE_NIGHT_YES
if (isDarkTheme) { if (isDarkTheme) {
// 暗色主题:深色状态栏,浅色文字
window.statusBarColor = Color.BLACK window.statusBarColor = Color.BLACK
window.navigationBarColor = Color.BLACK window.navigationBarColor = Color.BLACK
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.insetsController?.setSystemBarsAppearance( window.insetsController?.setSystemBarsAppearance(
0, // 清除浅色状态栏标志 0,
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS or WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS or
WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
) )
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
window.decorView.systemUiVisibility = window.decorView.systemUiVisibility =
window.decorView.systemUiVisibility and window.decorView.systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
window.decorView.systemUiVisibility = window.decorView.systemUiVisibility =
window.decorView.systemUiVisibility and window.decorView.systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
} }
} }
} else { } else {
// 浅色主题:浅色状态栏,深色文字
window.statusBarColor = Color.WHITE window.statusBarColor = Color.WHITE
window.navigationBarColor = Color.WHITE window.navigationBarColor = Color.WHITE
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.insetsController?.setSystemBarsAppearance( window.insetsController?.setSystemBarsAppearance(
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS or WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS or
@@ -214,7 +215,6 @@ class MainActivity : AppCompatActivity() {
override fun onConfigurationChanged(newConfig: Configuration) { override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig) super.onConfigurationChanged(newConfig)
// 当系统主题变化时,重新设置状态栏、WebView背景色和根布局背景色
setupSystemBars() setupSystemBars()
setupWebViewBackground() setupWebViewBackground()
setupRootBackground() setupRootBackground()
@@ -222,26 +222,16 @@ class MainActivity : AppCompatActivity() {
private fun setupWebViewBackground() { private fun setupWebViewBackground() {
val isDarkTheme = resources.configuration.uiMode and val isDarkTheme = resources.configuration.uiMode and
android.content.res.Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_MASK ==
android.content.res.Configuration.UI_MODE_NIGHT_YES Configuration.UI_MODE_NIGHT_YES
webView.setBackgroundColor(if (isDarkTheme) Color.BLACK else Color.WHITE)
if (isDarkTheme) {
webView.setBackgroundColor(Color.BLACK)
} else {
webView.setBackgroundColor(Color.WHITE)
}
} }
private fun setupRootBackground() { private fun setupRootBackground() {
val isDarkTheme = resources.configuration.uiMode and val isDarkTheme = resources.configuration.uiMode and
android.content.res.Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_MASK ==
android.content.res.Configuration.UI_MODE_NIGHT_YES Configuration.UI_MODE_NIGHT_YES
val rootView = findViewById<View>(android.R.id.content)
val rootView = findViewById<android.view.View>(android.R.id.content) rootView.setBackgroundColor(if (isDarkTheme) Color.BLACK else Color.WHITE)
if (isDarkTheme) {
rootView.setBackgroundColor(Color.BLACK)
} else {
rootView.setBackgroundColor(Color.WHITE)
}
} }
} }
+1 -1
View File
@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>哆啦A梦</title> <title>哆啦A梦</title>
<script> <script>
// 防止暗黑模式闪烁:在页面加载前立即应用系统主题 // 防止暗黑模式闪烁:在页面加载前立即应用系统主题
+1 -2
View File
@@ -69,7 +69,6 @@
"@types/react-dom": "18.3.0", "@types/react-dom": "18.3.0",
"@typescript-eslint/eslint-plugin": "8.31.1", "@typescript-eslint/eslint-plugin": "8.31.1",
"@typescript-eslint/parser": "8.31.1", "@typescript-eslint/parser": "8.31.1",
"@vitejs/plugin-legacy": "^5.4.3",
"@vitejs/plugin-react": "4.4.1", "@vitejs/plugin-react": "4.4.1",
"eslint": "9.25.1", "eslint": "9.25.1",
"eslint-config-prettier": "9.1.0", "eslint-config-prettier": "9.1.0",
@@ -84,7 +83,7 @@
"postcss": "8.5.6", "postcss": "8.5.6",
"prettier": "3.5.3", "prettier": "3.5.3",
"typescript": "5.6.3", "typescript": "5.6.3",
"vite": "5.2", "vite": "5.4.11",
"vite-tsconfig-paths": "4.3.2" "vite-tsconfig-paths": "4.3.2"
} }
} }
+10 -8
View File
@@ -1,4 +1,4 @@
import { Route, Routes } from "react-router-dom"; import { Route, Routes, useNavigate } from "react-router-dom";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import IndexPage from "@/pages/index"; import IndexPage from "@/pages/index";
@@ -58,17 +58,18 @@ const useH5Mode = () => {
return isH5; return isH5;
}; };
// 简化的路由保护组件 - 使用浏览器重定向避免React循环 // 简化的路由保护组件 - 使用 React Router 导航避免循环
const ProtectedRoute = ({ children, useSimpleLayout = false }: { children: React.ReactNode, useSimpleLayout?: boolean }) => { const ProtectedRoute = ({ children, useSimpleLayout = false }: { children: React.ReactNode, useSimpleLayout?: boolean }) => {
const authenticated = isLoggedIn(); const authenticated = isLoggedIn();
const isH5 = useH5Mode(); const isH5 = useH5Mode();
const navigate = useNavigate();
useEffect(() => { useEffect(() => {
if (!authenticated) { if (!authenticated) {
// 使用浏览器原生重定向,避免React渲染循环 // 使用 React Router 导航,避免无限跳转
window.location.replace('/'); navigate('/', { replace: true });
} }
}, [authenticated]); }, [authenticated, navigate]);
if (!authenticated) { if (!authenticated) {
return ( return (
@@ -95,13 +96,14 @@ const ProtectedRoute = ({ children, useSimpleLayout = false }: { children: React
// 登录页面路由组件 - 已登录则重定向到dashboard // 登录页面路由组件 - 已登录则重定向到dashboard
const LoginRoute = () => { const LoginRoute = () => {
const authenticated = isLoggedIn(); const authenticated = isLoggedIn();
const navigate = useNavigate();
useEffect(() => { useEffect(() => {
if (authenticated) { if (authenticated) {
// 使用浏览器原生重定向 // 使用 React Router 导航,避免无限跳转
window.location.replace('/dashboard'); navigate('/dashboard', { replace: true });
} }
}, [authenticated]); }, [authenticated, navigate]);
if (authenticated) { if (authenticated) {
return ( return (
+16 -4
View File
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate, useLocation } from 'react-router-dom';
import { Button } from "@heroui/button"; import { Button } from "@heroui/button";
import { Logo } from '@/components/icons'; import { Logo } from '@/components/icons';
@@ -11,15 +11,27 @@ export default function H5SimpleLayout({
children: React.ReactNode; children: React.ReactNode;
}) { }) {
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation();
// 路由切换时回到顶部,避免上一页滚动位置保留
React.useEffect(() => {
try {
window.scrollTo({ top: 0, left: 0, behavior: 'auto' });
} catch (e) {
window.scrollTo(0, 0);
}
document.body.scrollTop = 0;
document.documentElement.scrollTop = 0;
}, [location.pathname]);
const handleBack = () => { const handleBack = () => {
navigate('/profile'); navigate('/profile');
}; };
return ( return (
<div className="flex flex-col h-screen bg-gray-100 dark:bg-black"> <div className="flex flex-col min-h-screen bg-gray-100 dark:bg-black">
{/* 顶部导航栏 */} {/* 顶部导航栏 */}
<header className="bg-white dark:bg-black shadow-sm border-b border-gray-200 dark:border-gray-600 h-14 flex items-center justify-between px-4 relative z-10"> <header className="bg-white dark:bg-black shadow-sm border-b border-gray-200 dark:border-gray-600 h-14 safe-top flex-shrink-0 flex items-center justify-between px-4 relative z-10">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button <Button
isIconOnly isIconOnly
@@ -40,7 +52,7 @@ export default function H5SimpleLayout({
</header> </header>
{/* 主内容区域 */} {/* 主内容区域 */}
<main className="flex-1 overflow-y-auto bg-gray-100 dark:bg-black"> <main className="flex-1 bg-gray-100 dark:bg-black pb-0">
{children} {children}
</main> </main>
</div> </div>
+18 -4
View File
@@ -98,10 +98,21 @@ export default function H5Layout({
!item.adminOnly || isAdmin !item.adminOnly || isAdmin
); );
// 路由切换时回到页面顶部,避免上一页的滚动位置遗留
useEffect(() => {
try {
window.scrollTo({ top: 0, left: 0, behavior: 'auto' });
} catch (e) {
window.scrollTo(0, 0);
}
document.body.scrollTop = 0;
document.documentElement.scrollTop = 0;
}, [location.pathname]);
return ( return (
<div className="flex flex-col h-screen bg-gray-100 dark:bg-black"> <div className="flex flex-col min-h-screen bg-gray-100 dark:bg-black">
{/* 顶部导航栏 */} {/* 顶部导航栏 */}
<header className="bg-white dark:bg-black shadow-sm border-b border-gray-200 dark:border-gray-600 h-14 flex items-center justify-between px-4 relative z-10"> <header className="bg-white dark:bg-black shadow-sm border-b border-gray-200 dark:border-gray-600 h-14 safe-top flex-shrink-0 flex items-center justify-between px-4 relative z-10">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Logo size={20} /> <Logo size={20} />
<h1 className="text-sm font-bold text-foreground">{siteConfig.name}</h1> <h1 className="text-sm font-bold text-foreground">{siteConfig.name}</h1>
@@ -112,12 +123,15 @@ export default function H5Layout({
</header> </header>
{/* 主内容区域 */} {/* 主内容区域 */}
<main className="flex-1 overflow-y-auto bg-gray-100 dark:bg-black"> <main className="flex-1 bg-gray-100 dark:bg-black">
{children} {children}
</main> </main>
{/* 用于给固定 Tabbar 腾出空间的占位元素 */}
<div aria-hidden className="h-16 safe-bottom" />
{/* 底部Tabbar */} {/* 底部Tabbar */}
<nav className="bg-white dark:bg-black border-t border-gray-200 dark:border-gray-600 h-16 flex items-center justify-around px-2 relative z-10"> <nav className="bg-white dark:bg-black border-t border-gray-200 dark:border-gray-600 h-16 safe-bottom flex-shrink-0 flex items-center justify-around px-2 fixed bottom-0 left-0 right-0 z-30">
{filteredTabItems.map((item) => { {filteredTabItems.map((item) => {
const isActive = location.pathname === item.path; const isActive = location.pathname === item.path;
return ( return (
+3 -21
View File
@@ -1,34 +1,16 @@
import ReactDOM from "react-dom/client"; import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom"; import { MemoryRouter } from "react-router-dom";
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";
ReactDOM.createRoot(document.getElementById("root")!).render( ReactDOM.createRoot(document.getElementById("root")!).render(
<BrowserRouter> <MemoryRouter>
<Provider> <Provider>
<App /> <App />
</Provider> </Provider>
</BrowserRouter> </MemoryRouter>
); );
/**
import ReactDOM from "react-dom/client";
import { HashRouter } from "react-router-dom";
import App from "./App.tsx";
import { Provider } from "./provider.tsx";
import "@/styles/globals.css";
ReactDOM.createRoot(document.getElementById("root")!).render(
<HashRouter>
<Provider>
<App />
</Provider>
</HashRouter>
);
*/
+4 -2
View File
@@ -1,4 +1,5 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { Button } from "@heroui/button"; import { Button } from "@heroui/button";
import { Card, CardBody, CardHeader } from "@heroui/card"; import { Card, CardBody, CardHeader } from "@heroui/card";
import { Input } from "@heroui/input"; import { Input } from "@heroui/input";
@@ -121,6 +122,7 @@ const getInitialConfigs = (): Record<string, string> => {
}; };
export default function ConfigPage() { export default function ConfigPage() {
const navigate = useNavigate();
const initialConfigs = getInitialConfigs(); const initialConfigs = getInitialConfigs();
const [configs, setConfigs] = useState<Record<string, string>>(initialConfigs); const [configs, setConfigs] = useState<Record<string, string>>(initialConfigs);
const [loading, setLoading] = useState(Object.keys(initialConfigs).length === 0); // 如果有缓存数据,不显示loading const [loading, setLoading] = useState(Object.keys(initialConfigs).length === 0); // 如果有缓存数据,不显示loading
@@ -132,10 +134,10 @@ export default function ConfigPage() {
useEffect(() => { useEffect(() => {
if (!isAdmin()) { if (!isAdmin()) {
toast.error('权限不足,只有管理员可以访问此页面'); toast.error('权限不足,只有管理员可以访问此页面');
window.location.href = '/dashboard'; navigate('/dashboard', { replace: true });
return; return;
} }
}, []); }, [navigate]);
// 加载配置数据(优先从缓存) // 加载配置数据(优先从缓存)
const loadConfigs = async (currentConfigs?: Record<string, string>) => { const loadConfigs = async (currentConfigs?: Record<string, string>) => {
+1 -1
View File
@@ -95,7 +95,7 @@ export default function ProfilePage() {
// 退出登录 // 退出登录
const handleLogout = () => { const handleLogout = () => {
safeLogout(); safeLogout();
window.location.href = '/'; navigate('/', { replace: true });
}; };
// 密码表单验证 // 密码表单验证
+1 -1
View File
@@ -119,7 +119,7 @@ export const SettingsPage = () => {
loadPanelAddresses(); loadPanelAddresses();
} }
} catch (error) { } catch (error) {
toast.error('添加失败'); toast.error('添加失败' + error);
} }
}; };
+25
View File
@@ -7,6 +7,16 @@ html, body {
overflow-x: hidden; overflow-x: hidden;
} }
/* 现代浏览器使用 100dvh,避免地址栏收起/展开导致的布局跳动 */
@supports (height: 100dvh) {
.h-screen {
height: 100dvh;
}
.min-h-screen {
min-height: 100dvh;
}
}
/* 解决移动端100vh问题 */ /* 解决移动端100vh问题 */
@supports (-webkit-touch-callout: none) { @supports (-webkit-touch-callout: none) {
.h-screen { .h-screen {
@@ -30,6 +40,21 @@ html, body {
} }
/* 安全区域变量与工具类(适配刘海/底部手势条) */
:root {
--safe-area-top: env(safe-area-inset-top, 0px);
--safe-area-bottom: env(safe-area-inset-bottom, 0px);
}
.safe-top {
padding-top: var(--safe-area-top);
}
.safe-bottom {
padding-bottom: var(--safe-area-bottom);
}
/* 验证码弹层动画 */ /* 验证码弹层动画 */
.captcha-modal-enter { .captcha-modal-enter {
animation: captchaModalIn 0.3s ease-out forwards; animation: captchaModalIn 0.3s ease-out forwards;
-5
View File
@@ -1,13 +1,9 @@
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import react from "@vitejs/plugin-react"; import react from "@vitejs/plugin-react";
import path from "path"; import path from "path";
import legacy from "@vitejs/plugin-legacy";
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
legacy({
targets: ['defaults', 'not IE 11']
}),
react() react()
], ],
base: './', base: './',
@@ -21,7 +17,6 @@ export default defineConfig({
host: '0.0.0.0' host: '0.0.0.0'
}, },
build: { build: {
target: ['es2015', 'chrome63'],
outDir: 'dist', outDir: 'dist',
sourcemap: false, sourcemap: false,
minify: false, minify: false,