diff --git a/backend/routes/streams.py b/backend/routes/streams.py
index 47459a8..d1831fe 100644
--- a/backend/routes/streams.py
+++ b/backend/routes/streams.py
@@ -205,6 +205,7 @@ async def catalog_events(request: Request, db: AsyncSession = Depends(get_db)):
async def events():
previous = None
while not await request.is_disconnected():
+ db.expire_all()
streams = await StreamService(db).get_public_streams()
payload = json.dumps(
[_serialize(stream, include_private=False) for stream in streams],
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 69c448b..5dbc426 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,8 +1,9 @@
import { Component, lazy, Suspense, useState, useEffect, type ErrorInfo, type ReactNode } from 'react'
-import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
+import { BrowserRouter, Routes, Route, Navigate, useLocation } from 'react-router-dom'
import { Button, ConfigProvider, Result } from 'antd'
import zhCN from 'antd/locale/zh_CN'
import theme from './theme/anthropic'
+import './pageTransitions.css'
import { BrandProvider, useBrand } from './brand/BrandContext'
import { verifyToken } from './api/auth'
import { getToken } from './api/client'
@@ -60,6 +61,8 @@ function AdminGuard({ children }: { children: React.ReactNode }) {
}
function AppRoutes() {
+ const location = useLocation()
+ const routeTransitionKey = location.pathname.startsWith('/admin') ? '/admin' : location.pathname
const { brand } = useBrand()
useEffect(() => { document.title = brand.brand_name }, [brand.brand_name])
return (
@@ -98,4 +101,4 @@ export default function App() {
)
-}
+}
\ No newline at end of file
diff --git a/frontend/src/admin/AdminLayout.tsx b/frontend/src/admin/AdminLayout.tsx
index 85f2134..d0cefe6 100644
--- a/frontend/src/admin/AdminLayout.tsx
+++ b/frontend/src/admin/AdminLayout.tsx
@@ -1,5 +1,5 @@
import { Suspense, useEffect, useState } from 'react'
-import { Outlet, useNavigate, useLocation } from 'react-router-dom'
+import { Outlet, useNavigate, useLocation , useOutlet } from 'react-router-dom'
import { Layout, Menu, Button, Typography } from 'antd'
import {
DashboardOutlined, VideoCameraOutlined, PlusOutlined, MonitorOutlined,
@@ -35,6 +35,7 @@ export default function AdminLayout() {
const [collapsed, setCollapsed] = useState(false)
const navigate = useNavigate()
const location = useLocation()
+ const outlet = useOutlet()
const selectedKey = menuItems.find(
item => location.pathname === item.key || (location.pathname === '/' && item.key === '/admin')
@@ -78,9 +79,11 @@ export default function AdminLayout() {
} onClick={handleLogout}>退出登录
-
+
+ {outlet}
+
)
-}
+}
\ No newline at end of file
diff --git a/frontend/src/pageTransitions.css b/frontend/src/pageTransitions.css
new file mode 100644
index 0000000..6385155
--- /dev/null
+++ b/frontend/src/pageTransitions.css
@@ -0,0 +1,28 @@
+.app-route-transition,
+.admin-page-transition {
+ min-height: 100%;
+ animation: page-fade-slide-in 220ms cubic-bezier(0.22, 1, 0.36, 1) both;
+ will-change: opacity, transform;
+}
+
+.admin-page-transition {
+ transform-origin: top center;
+}
+
+@keyframes page-fade-slide-in {
+ from {
+ opacity: 0;
+ transform: translateY(14px) scale(0.995);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .app-route-transition,
+ .admin-page-transition {
+ animation: none;
+ }
+}