mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-28 07:14:40 +08:00
refactor: react router (#5073)
* refactor: react router * chore: update * fix: router * refactor: generate router children by navItems * chore: set start page when create window * docs: update UPDATELOG.md
This commit is contained in:
@@ -35,6 +35,7 @@
|
||||
- 配置重载失败时自动重启核心
|
||||
- 启用 TUN 前等待服务就绪
|
||||
- 卸载 TUN 时会先关闭
|
||||
- 优化应用启动页
|
||||
|
||||
### 🐞 修复问题
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"react-i18next": "16.1.0",
|
||||
"react-markdown": "10.1.0",
|
||||
"react-monaco-editor": "0.59.0",
|
||||
"react-router-dom": "7.9.4",
|
||||
"react-router": "^7.9.4",
|
||||
"react-virtuoso": "^4.14.1",
|
||||
"swr": "^2.3.6",
|
||||
"tauri-plugin-mihomo-api": "git+https://github.com/clash-verge-rev/tauri-plugin-mihomo",
|
||||
|
||||
17
pnpm-lock.yaml
generated
17
pnpm-lock.yaml
generated
@@ -119,8 +119,8 @@ importers:
|
||||
react-monaco-editor:
|
||||
specifier: 0.59.0
|
||||
version: 0.59.0(monaco-editor@0.54.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
react-router-dom:
|
||||
specifier: 7.9.4
|
||||
react-router:
|
||||
specifier: ^7.9.4
|
||||
version: 7.9.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
react-virtuoso:
|
||||
specifier: ^4.14.1
|
||||
@@ -3638,13 +3638,6 @@ packages:
|
||||
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
react-router-dom@7.9.4:
|
||||
resolution: {integrity: sha512-f30P6bIkmYvnHHa5Gcu65deIXoA2+r3Eb6PJIAddvsT9aGlchMatJ51GgpU470aSqRRbFX22T70yQNUGuW3DfA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
peerDependencies:
|
||||
react: '>=18'
|
||||
react-dom: '>=18'
|
||||
|
||||
react-router@7.9.4:
|
||||
resolution: {integrity: sha512-SD3G8HKviFHg9xj7dNODUKDFgpG4xqD5nhyd0mYoB5iISepuZAvzSr8ywxgxKJ52yRzf/HWtVHc9AWwoTbljvA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
@@ -8241,12 +8234,6 @@ snapshots:
|
||||
|
||||
react-refresh@0.17.0: {}
|
||||
|
||||
react-router-dom@7.9.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
|
||||
dependencies:
|
||||
react: 19.2.0
|
||||
react-dom: 19.2.0(react@19.2.0)
|
||||
react-router: 7.9.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
|
||||
react-router@7.9.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
|
||||
dependencies:
|
||||
cookie: 1.0.2
|
||||
|
||||
@@ -338,7 +338,15 @@ impl IVerge {
|
||||
pub async fn new() -> Self {
|
||||
match dirs::verge_path() {
|
||||
Ok(path) => match help::read_yaml::<IVerge>(&path).await {
|
||||
Ok(config) => config,
|
||||
Ok(mut config) => {
|
||||
// compatibility
|
||||
if let Some(start_page) = config.start_page.clone()
|
||||
&& start_page == "/home"
|
||||
{
|
||||
config.start_page = Some(String::from("/"));
|
||||
}
|
||||
config
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(target: "app", "{err}");
|
||||
Self::template()
|
||||
@@ -362,7 +370,7 @@ impl IVerge {
|
||||
env_type: Some("bash".into()),
|
||||
#[cfg(target_os = "windows")]
|
||||
env_type: Some("powershell".into()),
|
||||
start_page: Some("/home".into()),
|
||||
start_page: Some("/".into()),
|
||||
traffic_graph: Some(true),
|
||||
enable_memory_usage: Some(true),
|
||||
enable_group_icon: Some(true),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use tauri::WebviewWindow;
|
||||
|
||||
use crate::{
|
||||
config::Config,
|
||||
core::handle,
|
||||
logging_error,
|
||||
utils::{
|
||||
@@ -17,13 +18,19 @@ const MINIMAL_WIDTH: f64 = 520.0;
|
||||
const MINIMAL_HEIGHT: f64 = 520.0;
|
||||
|
||||
/// 构建新的 WebView 窗口
|
||||
pub fn build_new_window() -> Result<WebviewWindow, String> {
|
||||
pub async fn build_new_window() -> Result<WebviewWindow, String> {
|
||||
let app_handle = handle::Handle::app_handle();
|
||||
|
||||
let start_page = Config::verge()
|
||||
.await
|
||||
.latest_ref()
|
||||
.start_page
|
||||
.clone()
|
||||
.unwrap_or("/".to_string());
|
||||
match tauri::WebviewWindowBuilder::new(
|
||||
app_handle,
|
||||
"main", /* the unique window label */
|
||||
tauri::WebviewUrl::App("index.html".into()),
|
||||
tauri::WebviewUrl::App(start_page.into()),
|
||||
)
|
||||
.title("Clash Verge")
|
||||
.center()
|
||||
|
||||
@@ -328,7 +328,7 @@ impl WindowManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
match build_new_window() {
|
||||
match build_new_window().await {
|
||||
Ok(_) => {
|
||||
logging!(info, Type::Window, "新窗口创建成功");
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import { useLockFn } from "ahooks";
|
||||
import React from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useNavigate } from "react-router";
|
||||
import { delayGroup, healthcheckProxyProvider } from "tauri-plugin-mihomo-api";
|
||||
|
||||
import { EnhancedCard } from "@/components/home/enhanced-card";
|
||||
|
||||
@@ -22,7 +22,7 @@ import { useLockFn } from "ahooks";
|
||||
import dayjs from "dayjs";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useNavigate } from "react-router";
|
||||
|
||||
import { useAppData } from "@/providers/app-data-context";
|
||||
import { openWebUrl, updateProfile } from "@/services/cmds";
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
import { useLockFn } from "ahooks";
|
||||
import { useCallback, useEffect, useMemo, useReducer } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useNavigate } from "react-router";
|
||||
import useSWR from "swr";
|
||||
|
||||
import { useSystemState } from "@/hooks/use-system-state";
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
ListItemText,
|
||||
ListItemIcon,
|
||||
} from "@mui/material";
|
||||
import { useMatch, useResolvedPath, useNavigate } from "react-router-dom";
|
||||
import { useMatch, useResolvedPath, useNavigate } from "react-router";
|
||||
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
interface Props {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { DialogRef } from "@/components/base";
|
||||
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { routers } from "@/pages/_routers";
|
||||
import { navItems } from "@/pages/_routers";
|
||||
import { copyClashEnv } from "@/services/cmds";
|
||||
import { supportedLanguages } from "@/services/i18n";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
@@ -170,7 +170,7 @@ const SettingVergeBasic = ({ onError }: Props) => {
|
||||
onGuard={(e) => patchVerge({ start_page: e })}
|
||||
>
|
||||
<Select size="small" sx={{ width: 140, "> div": { py: "7.5px" } }}>
|
||||
{routers.map((page: { label: string; path: string }) => {
|
||||
{navItems.map((page: { label: string; path: string }) => {
|
||||
return (
|
||||
<MenuItem key={page.path} value={page.path}>
|
||||
{t(page.label)}
|
||||
|
||||
@@ -6,11 +6,11 @@ import { ResizeObserver } from "@juggle/resize-observer";
|
||||
import { ComposeContextProvider } from "foxact/compose-context-provider";
|
||||
import React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import { RouterProvider } from "react-router";
|
||||
import { MihomoWebSocket } from "tauri-plugin-mihomo-api";
|
||||
|
||||
import { BaseErrorBoundary } from "./components/base";
|
||||
import Layout from "./pages/_layout";
|
||||
import { router } from "./pages/_routers";
|
||||
import { AppDataProvider } from "./providers/app-data-provider";
|
||||
import { WindowProvider } from "./providers/window";
|
||||
import { initializeLanguage } from "./services/i18n";
|
||||
@@ -64,9 +64,7 @@ const initializeApp = async () => {
|
||||
<BaseErrorBoundary>
|
||||
<WindowProvider>
|
||||
<AppDataProvider>
|
||||
<BrowserRouter>
|
||||
<Layout />
|
||||
</BrowserRouter>
|
||||
<RouterProvider router={router} />
|
||||
</AppDataProvider>
|
||||
</WindowProvider>
|
||||
</BaseErrorBoundary>
|
||||
|
||||
@@ -4,14 +4,15 @@ import { listen } from "@tauri-apps/api/event";
|
||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||
import dayjs from "dayjs";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLocation, useNavigate, useRoutes } from "react-router-dom";
|
||||
import { Outlet, useNavigate } from "react-router";
|
||||
import { SWRConfig, mutate } from "swr";
|
||||
|
||||
import iconDark from "@/assets/image/icon_dark.svg?react";
|
||||
import iconLight from "@/assets/image/icon_light.svg?react";
|
||||
import LogoSvg from "@/assets/image/logo.svg?react";
|
||||
import { BaseErrorBoundary } from "@/components/base";
|
||||
import { NoticeManager } from "@/components/base/NoticeManager";
|
||||
import { WindowControls } from "@/components/controller/window-controller";
|
||||
import { LayoutItem } from "@/components/layout/layout-item";
|
||||
@@ -31,7 +32,7 @@ import { showNotice } from "@/services/noticeService";
|
||||
import { useThemeMode } from "@/services/states";
|
||||
import getSystem from "@/utils/get-system";
|
||||
|
||||
import { routers } from "./_routers";
|
||||
import { navItems } from "./_routers";
|
||||
|
||||
import "dayjs/locale/ru";
|
||||
import "dayjs/locale/zh-cn";
|
||||
@@ -161,24 +162,12 @@ const Layout = () => {
|
||||
const { t } = useTranslation();
|
||||
const { theme } = useCustomTheme();
|
||||
const { verge } = useVerge();
|
||||
const { language, start_page } = verge ?? {};
|
||||
const { language } = verge ?? {};
|
||||
const { switchLanguage } = useI18n();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const matchedElement = useRoutes(routers);
|
||||
const routersEles = useMemo(() => {
|
||||
if (!matchedElement) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<React.Fragment key={location.pathname}>{matchedElement}</React.Fragment>
|
||||
);
|
||||
}, [matchedElement, location.pathname]);
|
||||
const { addListener } = useListen();
|
||||
const initRef = useRef(false);
|
||||
const overlayRemovedRef = useRef(false);
|
||||
const lastStartPageRef = useRef<string | null>(null);
|
||||
const startPageAppliedRef = useRef(false);
|
||||
const themeReady = useMemo(() => Boolean(theme), [theme]);
|
||||
|
||||
const windowControls = useRef<any>(null);
|
||||
@@ -538,35 +527,6 @@ const Layout = () => {
|
||||
}
|
||||
}, [language, switchLanguage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!start_page) {
|
||||
lastStartPageRef.current = null;
|
||||
startPageAppliedRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const normalizedStartPage = start_page.startsWith("/")
|
||||
? start_page
|
||||
: `/${start_page}`;
|
||||
|
||||
if (lastStartPageRef.current !== normalizedStartPage) {
|
||||
lastStartPageRef.current = normalizedStartPage;
|
||||
startPageAppliedRef.current = false;
|
||||
}
|
||||
|
||||
if (startPageAppliedRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
startPageAppliedRef.current = true;
|
||||
|
||||
if (location.pathname === normalizedStartPage) {
|
||||
return;
|
||||
}
|
||||
|
||||
navigate(normalizedStartPage, { replace: true });
|
||||
}, [start_page, navigate, location.pathname]);
|
||||
|
||||
if (!themeReady) {
|
||||
return (
|
||||
<div
|
||||
@@ -584,22 +544,6 @@ const Layout = () => {
|
||||
);
|
||||
}
|
||||
|
||||
if (!routersEles) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: "100vw",
|
||||
height: "100vh",
|
||||
background: mode === "light" ? "#fff" : "#181a1b",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
color: mode === "light" ? "#333" : "#fff",
|
||||
}}
|
||||
></div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SWRConfig
|
||||
value={{
|
||||
@@ -692,7 +636,7 @@ const Layout = () => {
|
||||
</div>
|
||||
|
||||
<List className="the-menu">
|
||||
{routers.map((router) => (
|
||||
{navItems.map((router) => (
|
||||
<LayoutItem
|
||||
key={router.label}
|
||||
to={router.path}
|
||||
@@ -710,7 +654,11 @@ const Layout = () => {
|
||||
|
||||
<div className="layout-content__right">
|
||||
<div className="the-bar"></div>
|
||||
<div className="the-content">{routersEles}</div>
|
||||
<div className="the-content">
|
||||
<BaseErrorBoundary>
|
||||
<Outlet />
|
||||
</BaseErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Paper>
|
||||
|
||||
@@ -6,6 +6,7 @@ import LockOpenRoundedIcon from "@mui/icons-material/LockOpenRounded";
|
||||
import SettingsRoundedIcon from "@mui/icons-material/SettingsRounded";
|
||||
import SubjectRoundedIcon from "@mui/icons-material/SubjectRounded";
|
||||
import WifiRoundedIcon from "@mui/icons-material/WifiRounded";
|
||||
import { createBrowserRouter, RouteObject } from "react-router";
|
||||
|
||||
import ConnectionsSvg from "@/assets/image/itemicon/connections.svg?react";
|
||||
import HomeSvg from "@/assets/image/itemicon/home.svg?react";
|
||||
@@ -15,8 +16,8 @@ import ProxiesSvg from "@/assets/image/itemicon/proxies.svg?react";
|
||||
import RulesSvg from "@/assets/image/itemicon/rules.svg?react";
|
||||
import SettingsSvg from "@/assets/image/itemicon/settings.svg?react";
|
||||
import UnlockSvg from "@/assets/image/itemicon/unlock.svg?react";
|
||||
import { BaseErrorBoundary } from "@/components/base";
|
||||
|
||||
import Layout from "./_layout";
|
||||
import ConnectionsPage from "./connections";
|
||||
import HomePage from "./home";
|
||||
import LogsPage from "./logs";
|
||||
@@ -26,58 +27,67 @@ import RulesPage from "./rules";
|
||||
import SettingsPage from "./settings";
|
||||
import UnlockPage from "./unlock";
|
||||
|
||||
export const routers = [
|
||||
export const navItems = [
|
||||
{
|
||||
label: "Label-Home",
|
||||
path: "/home",
|
||||
path: "/",
|
||||
icon: [<HomeRoundedIcon key="mui" />, <HomeSvg key="svg" />],
|
||||
element: <HomePage />,
|
||||
Component: HomePage,
|
||||
},
|
||||
{
|
||||
label: "Label-Proxies",
|
||||
path: "/",
|
||||
path: "/proxies",
|
||||
icon: [<WifiRoundedIcon key="mui" />, <ProxiesSvg key="svg" />],
|
||||
element: <ProxiesPage />,
|
||||
Component: ProxiesPage,
|
||||
},
|
||||
{
|
||||
label: "Label-Profiles",
|
||||
path: "/profile",
|
||||
icon: [<DnsRoundedIcon key="mui" />, <ProfilesSvg key="svg" />],
|
||||
element: <ProfilesPage />,
|
||||
Component: ProfilesPage,
|
||||
},
|
||||
{
|
||||
label: "Label-Connections",
|
||||
path: "/connections",
|
||||
icon: [<LanguageRoundedIcon key="mui" />, <ConnectionsSvg key="svg" />],
|
||||
element: <ConnectionsPage />,
|
||||
Component: ConnectionsPage,
|
||||
},
|
||||
{
|
||||
label: "Label-Rules",
|
||||
path: "/rules",
|
||||
icon: [<ForkRightRoundedIcon key="mui" />, <RulesSvg key="svg" />],
|
||||
element: <RulesPage />,
|
||||
Component: RulesPage,
|
||||
},
|
||||
{
|
||||
label: "Label-Logs",
|
||||
path: "/logs",
|
||||
icon: [<SubjectRoundedIcon key="mui" />, <LogsSvg key="svg" />],
|
||||
element: <LogsPage />,
|
||||
Component: LogsPage,
|
||||
},
|
||||
{
|
||||
label: "Label-Unlock",
|
||||
path: "/unlock",
|
||||
icon: [<LockOpenRoundedIcon key="mui" />, <UnlockSvg key="svg" />],
|
||||
element: <UnlockPage />,
|
||||
Component: UnlockPage,
|
||||
},
|
||||
{
|
||||
label: "Label-Settings",
|
||||
path: "/settings",
|
||||
icon: [<SettingsRoundedIcon key="mui" />, <SettingsSvg key="svg" />],
|
||||
element: <SettingsPage />,
|
||||
Component: SettingsPage,
|
||||
},
|
||||
].map((router) => ({
|
||||
...router,
|
||||
element: (
|
||||
<BaseErrorBoundary key={router.label}>{router.element}</BaseErrorBoundary>
|
||||
),
|
||||
}));
|
||||
];
|
||||
|
||||
export const router = createBrowserRouter([
|
||||
{
|
||||
path: "/",
|
||||
Component: Layout,
|
||||
children: navItems.map(
|
||||
(item) =>
|
||||
({
|
||||
path: item.path,
|
||||
Component: item.Component,
|
||||
}) as RouteObject,
|
||||
),
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -32,7 +32,7 @@ import { useLockFn } from "ahooks";
|
||||
import { throttle } from "lodash-es";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useLocation } from "react-router";
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { closeAllConnections } from "tauri-plugin-mihomo-api";
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ export default defineConfig({
|
||||
if (
|
||||
id.includes("react") ||
|
||||
id.includes("react-dom") ||
|
||||
id.includes("react-router-dom")
|
||||
id.includes("react-router")
|
||||
) {
|
||||
return "react-core";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user