取消
This commit is contained in:
@@ -224,10 +224,6 @@ export async function updateSystemConfig(id: string, value: string): Promise<voi
|
||||
await api.put(`/admin/system-configs/${id}`, { value });
|
||||
}
|
||||
|
||||
export async function regenerateDevToken(): Promise<{ token: string }> {
|
||||
return api.post('/admin/system-configs/regenerate-dev-token');
|
||||
}
|
||||
|
||||
export async function getGlobalResourceCapacity(): Promise<ResourceCapacityConfigOut> {
|
||||
return api.get('/admin/resource-capacity/global');
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Button, Card, Form, Input, InputNumber, message, Popconfirm, Select, Space, Switch, Typography, Upload,
|
||||
Button, Card, Form, Input, InputNumber, message, Select, Space, Switch, Typography, Upload,
|
||||
} from 'antd';
|
||||
import {
|
||||
SettingOutlined, SaveOutlined, UploadOutlined, FilePdfOutlined, EyeOutlined, DatabaseOutlined,
|
||||
PoweroffOutlined, ReloadOutlined, CopyOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import {
|
||||
getGlobalResourceCapacity,
|
||||
@@ -13,7 +12,6 @@ import {
|
||||
updateSystemConfig,
|
||||
uploadLogo,
|
||||
uploadPdf,
|
||||
regenerateDevToken,
|
||||
} from '../api';
|
||||
import type { ResourceCapacityUnit, SystemConfig } from '../types';
|
||||
|
||||
@@ -44,8 +42,6 @@ const AdminSettings: React.FC = () => {
|
||||
setConfigs(data);
|
||||
const formValues: Record<string, any> = {};
|
||||
data.forEach(c => { formValues[c.key] = c.value; });
|
||||
// 网站开关布尔值转换
|
||||
formValues.site_enabled = siteEnabledConfig?.value !== 'false';
|
||||
formValues.resource_capacity_enabled = capacity.enabled;
|
||||
formValues.resource_capacity_limit_value = capacity.limitValue || '1.000';
|
||||
formValues.resource_capacity_limit_unit = capacity.limitUnit || 'GB';
|
||||
@@ -67,13 +63,6 @@ const AdminSettings: React.FC = () => {
|
||||
await updateSystemConfig(config.id, String(newVal ?? ''));
|
||||
}
|
||||
}
|
||||
// 保存网站开关(布尔值转为字符串)
|
||||
if (siteEnabledConfig) {
|
||||
const newEnabled = values.site_enabled ? 'true' : 'false';
|
||||
if (newEnabled !== siteEnabledConfig.value) {
|
||||
await updateSystemConfig(siteEnabledConfig.id, newEnabled);
|
||||
}
|
||||
}
|
||||
await saveGlobalResourceCapacity({
|
||||
enabled: !!values.resource_capacity_enabled,
|
||||
limitValue: String(values.resource_capacity_limit_value ?? '1.000'),
|
||||
@@ -88,20 +77,6 @@ const AdminSettings: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleRegenerateDevToken = async () => {
|
||||
try {
|
||||
const result = await regenerateDevToken();
|
||||
message.success('开发者令牌已重新生成');
|
||||
await load();
|
||||
// 更新显示的令牌
|
||||
if (result?.token) {
|
||||
form.setFieldsValue({ site_dev_access_token: result.token });
|
||||
}
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '重新生成失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpload = async (file: File, configKey: string) => {
|
||||
setUploading(configKey);
|
||||
try {
|
||||
@@ -143,11 +118,8 @@ const AdminSettings: React.FC = () => {
|
||||
return false;
|
||||
};
|
||||
|
||||
const siteEnabledConfig = configs.find(c => c.key === 'site_enabled');
|
||||
const devTokenConfig = configs.find(c => c.key === 'site_dev_access_token');
|
||||
|
||||
const groupedConfigs: Record<string, SystemConfig[]> = {
|
||||
'站点信息': configs.filter(c => c.key.startsWith('site_') && c.key !== 'site_enabled' && c.key !== 'site_dev_access_token'),
|
||||
'站点信息': configs.filter(c => c.key.startsWith('site_')),
|
||||
'协议配置': configs.filter(c => c.key === 'user_agreement_privacy_url'),
|
||||
'SEO 设置': configs.filter(c => c.key.startsWith('seo_')),
|
||||
'用户积分配置': configs.filter(c => c.key.startsWith('user_') && c.key.includes('credits')),
|
||||
@@ -325,61 +297,6 @@ const AdminSettings: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<Form form={form} layout="vertical">
|
||||
{/* 网站访问控制 */}
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<Typography.Text strong style={{ fontSize: 14, display: 'block', marginBottom: 12, paddingBottom: 8, borderBottom: '1px solid #f0f0f5' }}>
|
||||
访问控制
|
||||
</Typography.Text>
|
||||
{siteEnabledConfig ? (
|
||||
<Form.Item
|
||||
name="site_enabled"
|
||||
label="网站访问开关"
|
||||
valuePropName="checked"
|
||||
extra="关闭后前台所有用户将被重定向到维护页面(管理员仍可登录后台管理)"
|
||||
>
|
||||
<Switch checkedChildren="网站开启" unCheckedChildren="网站关闭" />
|
||||
</Form.Item>
|
||||
) : (
|
||||
<Typography.Text type="secondary" style={{ fontSize: 13 }}>
|
||||
系统配置 site_enabled 未初始化,请通过数据库手动添加。
|
||||
</Typography.Text>
|
||||
)}
|
||||
|
||||
{devTokenConfig && (
|
||||
<Form.Item
|
||||
label="开发者访问令牌"
|
||||
extra="网站关闭时,在维护页面输入此令牌可正常访问前台内容。令牌仅管理员可见。"
|
||||
>
|
||||
<Space.Compact style={{ width: '100%' }}>
|
||||
<Input.Password
|
||||
name="site_dev_access_token"
|
||||
readOnly
|
||||
value={devTokenConfig.value}
|
||||
style={{ fontFamily: 'monospace' }}
|
||||
/>
|
||||
<Button
|
||||
icon={<CopyOutlined />}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(devTokenConfig.value);
|
||||
message.success('已复制到剪贴板');
|
||||
}}
|
||||
>
|
||||
复制
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title="重新生成令牌?"
|
||||
description="重新生成后旧令牌将立即失效,需要重新复制新令牌。"
|
||||
onConfirm={handleRegenerateDevToken}
|
||||
okText="确认"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Button icon={<ReloadOutlined />} danger>重新生成</Button>
|
||||
</Popconfirm>
|
||||
</Space.Compact>
|
||||
</Form.Item>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{Object.entries(groupedConfigs).map(([group, items]) => (
|
||||
<div key={group} style={{ marginBottom: 24 }}>
|
||||
<Typography.Text strong style={{ fontSize: 14, display: 'block', marginBottom: 12, paddingBottom: 8, borderBottom: '1px solid #f0f0f5' }}>
|
||||
|
||||
@@ -1622,41 +1622,6 @@ async def update_system_config(
|
||||
return config
|
||||
|
||||
|
||||
@router.post("/system-configs/regenerate-dev-token")
|
||||
async def regenerate_dev_token(
|
||||
admin: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""重新生成开发者访问令牌。"""
|
||||
import secrets
|
||||
result = await db.execute(
|
||||
select(SystemConfig).where(SystemConfig.key == "site_dev_access_token").limit(1)
|
||||
)
|
||||
config = result.scalar_one_or_none()
|
||||
new_token = secrets.token_urlsafe(32)
|
||||
if config:
|
||||
config.value = new_token
|
||||
else:
|
||||
config = SystemConfig(
|
||||
id=generate_id(),
|
||||
key="site_dev_access_token",
|
||||
value=new_token,
|
||||
description="开发者访问令牌(网站关闭时用于绕过限制)",
|
||||
)
|
||||
db.add(config)
|
||||
await db.flush()
|
||||
await log_operation(
|
||||
db,
|
||||
admin.id,
|
||||
admin.username,
|
||||
"重新生成开发者访问令牌",
|
||||
"POST",
|
||||
"/admin/system-configs/regenerate-dev-token",
|
||||
)
|
||||
await db.commit()
|
||||
return {"token": new_token}
|
||||
|
||||
|
||||
# ── Operation Logs ──────────────────────────────────────
|
||||
|
||||
@router.get("/operation-logs")
|
||||
|
||||
@@ -345,19 +345,12 @@ async def get_site_info(db: AsyncSession = Depends(get_db)):
|
||||
base_url = settings.BASE_URL.rstrip("/")
|
||||
return f"{base_url}{path}"
|
||||
|
||||
# 读取网站开关状态(不存在时默认开启)
|
||||
site_enabled_result = await db.execute(
|
||||
select(SystemConfig.value).where(SystemConfig.key == "site_enabled").limit(1)
|
||||
)
|
||||
site_enabled_value = site_enabled_result.scalar_one_or_none()
|
||||
|
||||
return {
|
||||
"site_name": info.get("site_name", "VideoGen.AI"),
|
||||
"site_logo": to_full_url(info.get("site_logo")),
|
||||
"user_agreement_privacy_url": to_full_url(info.get("user_agreement_privacy_url")),
|
||||
"site_copyright": info.get("site_copyright", "© 2024 民众智创 版权所有"),
|
||||
"operation_manual": info.get("operation_manual", ""),
|
||||
"site_enabled": site_enabled_value != "false",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ from app.middleware.logging import RequestLoggingMiddleware
|
||||
from app.middleware.anti_crawler import AntiCrawlerMiddleware
|
||||
from app.middleware.rate_limit import RateLimitMiddleware
|
||||
from app.middleware.request_encrypt import RequestEncryptMiddleware
|
||||
from app.middleware.site_status import SiteStatusMiddleware
|
||||
from app.services.log_config import decrypt_data
|
||||
|
||||
logging.basicConfig(level=logging.INFO if settings.DEBUG else logging.WARNING)
|
||||
@@ -30,7 +29,6 @@ async def lifespan(app: FastAPI):
|
||||
os.makedirs(settings.UPLOAD_LOCAL_PATH, exist_ok=True)
|
||||
await init_database()
|
||||
await init_redis()
|
||||
await _ensure_site_configs()
|
||||
# await _seed_data()
|
||||
|
||||
# Start task queue (handles both video and image generation)
|
||||
@@ -515,40 +513,6 @@ async def _seed_data():
|
||||
await db.commit()
|
||||
|
||||
|
||||
async def _ensure_site_configs() -> None:
|
||||
"""确保网站开关相关配置项存在(不存在则自动创建)。"""
|
||||
import secrets
|
||||
from app.models.base import async_session
|
||||
from app.models.system_config import SystemConfig
|
||||
from app.utils.id_gen import generate_id
|
||||
from sqlalchemy import select
|
||||
|
||||
async with async_session() as db:
|
||||
# site_enabled
|
||||
existing = await db.execute(
|
||||
select(SystemConfig).where(SystemConfig.key == "site_enabled").limit(1)
|
||||
)
|
||||
if not existing.scalar_one_or_none():
|
||||
db.add(SystemConfig(
|
||||
id=generate_id(),
|
||||
key="site_enabled",
|
||||
value="true",
|
||||
description="网站访问开关(true=开启,false=关闭)",
|
||||
))
|
||||
# site_dev_access_token
|
||||
existing_token = await db.execute(
|
||||
select(SystemConfig).where(SystemConfig.key == "site_dev_access_token").limit(1)
|
||||
)
|
||||
if not existing_token.scalar_one_or_none():
|
||||
db.add(SystemConfig(
|
||||
id=generate_id(),
|
||||
key="site_dev_access_token",
|
||||
value=secrets.token_urlsafe(32),
|
||||
description="开发者访问令牌(网站关闭时用于绕过限制)",
|
||||
))
|
||||
await db.commit()
|
||||
|
||||
|
||||
def create_app() -> FastAPI:
|
||||
application = FastAPI(
|
||||
title=settings.APP_NAME,
|
||||
@@ -558,8 +522,7 @@ def create_app() -> FastAPI:
|
||||
redoc_url="/internal/api-redoc",
|
||||
)
|
||||
|
||||
# Middleware (outermost first) — SiteStatus 放最外层,早于其他中间件拦截
|
||||
application.add_middleware(SiteStatusMiddleware)
|
||||
# Middleware (outermost first)
|
||||
application.add_middleware(RequestLoggingMiddleware)
|
||||
application.add_middleware(AntiCrawlerMiddleware)
|
||||
application.add_middleware(RateLimitMiddleware)
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
import logging
|
||||
import time
|
||||
|
||||
from fastapi import Request, Response
|
||||
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
logger = logging.getLogger("videogen")
|
||||
|
||||
# 网站关闭时仍然放行的路径前缀
|
||||
_WHITELIST_PREFIXES = (
|
||||
"/api/auth/", # 登录/注册/短信/验证码
|
||||
"/api/admin/", # 后台管理(确保管理员能登录并重新开启网站)
|
||||
"/api/captcha/", # 图形验证码
|
||||
"/internal/", # 健康检查/文档
|
||||
"/uploads/", # 静态文件
|
||||
"/api/generation-records/callback", # 火山引擎回调
|
||||
"/api/payments/wechat/callback", # 微信回调
|
||||
"/api/payments/alipay/callback", # 支付宝回调
|
||||
)
|
||||
|
||||
# 完全匹配的白名单路径
|
||||
_WHITELIST_EXACT = (
|
||||
"/api/auth/site-info",
|
||||
"/internal/health",
|
||||
)
|
||||
|
||||
# site_enabled 缓存有效期(秒)
|
||||
_CACHE_TTL = 5.0
|
||||
|
||||
|
||||
class SiteStatusMiddleware(BaseHTTPMiddleware):
|
||||
"""网站访问开关中间件。
|
||||
|
||||
检查 SystemConfig 中 site_enabled 的值:
|
||||
- 配置不存在或值不为 "false" → 放行
|
||||
- 值为 "false" 且不在白名单 → 检查开发者令牌,不匹配则返回 503 { code: "SITE_CLOSED" }
|
||||
"""
|
||||
|
||||
def __init__(self, app):
|
||||
super().__init__(app)
|
||||
self._cache_enabled = True # 缓存的站点状态(True=开启)
|
||||
self._cache_at: float = 0.0 # 缓存时间戳
|
||||
|
||||
async def _is_site_enabled(self) -> bool:
|
||||
"""查询 site_enabled 配置,带短缓存避免每个请求查 DB。"""
|
||||
now = time.time()
|
||||
if now - self._cache_at < _CACHE_TTL:
|
||||
return self._cache_enabled
|
||||
|
||||
try:
|
||||
from app.models.base import async_session
|
||||
from app.models.system_config import SystemConfig
|
||||
from sqlalchemy import select
|
||||
|
||||
async with async_session() as db:
|
||||
result = await db.execute(
|
||||
select(SystemConfig.value).where(SystemConfig.key == "site_enabled").limit(1)
|
||||
)
|
||||
value = result.scalar_one_or_none()
|
||||
self._cache_enabled = value != "false"
|
||||
self._cache_at = now
|
||||
except Exception:
|
||||
# 查询异常时默认放行,避免数据库故障导致全站不可用
|
||||
logger.exception("Failed to read site_enabled config, defaulting to enabled")
|
||||
self._cache_enabled = True
|
||||
self._cache_at = now
|
||||
|
||||
return self._cache_enabled
|
||||
|
||||
@staticmethod
|
||||
def _is_whitelisted(path: str) -> bool:
|
||||
"""检查路径是否在白名单中。"""
|
||||
if path in _WHITELIST_EXACT:
|
||||
return True
|
||||
return any(path.startswith(prefix) for prefix in _WHITELIST_PREFIXES)
|
||||
|
||||
@staticmethod
|
||||
async def _check_dev_token(token: str) -> bool:
|
||||
"""校验开发者访问令牌是否匹配配置。"""
|
||||
try:
|
||||
from app.models.base import async_session
|
||||
from app.models.system_config import SystemConfig
|
||||
from sqlalchemy import select
|
||||
|
||||
async with async_session() as db:
|
||||
result = await db.execute(
|
||||
select(SystemConfig.value)
|
||||
.where(SystemConfig.key == "site_dev_access_token")
|
||||
.limit(1)
|
||||
)
|
||||
expected = result.scalar_one_or_none()
|
||||
return expected is not None and token == expected
|
||||
except Exception:
|
||||
logger.exception("Failed to verify dev access token")
|
||||
return False
|
||||
|
||||
async def dispatch(
|
||||
self, request: Request, call_next: RequestResponseEndpoint
|
||||
) -> Response:
|
||||
path = request.url.path
|
||||
|
||||
# 白名单直接放行
|
||||
if self._is_whitelisted(path):
|
||||
return await call_next(request)
|
||||
|
||||
# 网站开启时放行
|
||||
if await self._is_site_enabled():
|
||||
return await call_next(request)
|
||||
|
||||
# 网站已关闭 — 校验开发者令牌
|
||||
token = request.query_params.get("dev_access") or request.headers.get("x-dev-access")
|
||||
if token and await self._check_dev_token(token):
|
||||
return await call_next(request)
|
||||
|
||||
# 拦截请求,返回维护状态
|
||||
return JSONResponse(
|
||||
status_code=503,
|
||||
content={
|
||||
"detail": {
|
||||
"code": "SITE_CLOSED",
|
||||
"message": "系统正在升级维护,请稍后再试",
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -1,11 +1,10 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
||||
import { ConfigProvider, App as AntApp, Spin } from 'antd';
|
||||
import zhCN from 'antd/locale/zh_CN';
|
||||
import dayjs from 'dayjs';
|
||||
import 'dayjs/locale/zh-cn';
|
||||
dayjs.locale('zh-cn');
|
||||
import MaintenancePage from './pages/MaintenancePage';
|
||||
import AppLayout from './components/Layout/AppLayout';
|
||||
import LoginPage from './pages/LoginPage';
|
||||
import ProjectsPage from './pages/ProjectsPage';
|
||||
@@ -62,47 +61,11 @@ const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
|
||||
|
||||
const App = () => {
|
||||
const { checkAuth } = useAuthStore();
|
||||
const [siteReady, setSiteReady] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// 处理 URL 中的开发者访问令牌
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const devToken = params.get('dev_access');
|
||||
if (devToken) {
|
||||
localStorage.setItem('dev_access_token', devToken);
|
||||
// 清除 URL 中的令牌参数
|
||||
params.delete('dev_access');
|
||||
const newSearch = params.toString();
|
||||
window.history.replaceState({}, '', window.location.pathname + (newSearch ? `?${newSearch}` : ''));
|
||||
}
|
||||
|
||||
// 检查网站状态(仅当用户没有有效 dev token 时)
|
||||
checkAuth();
|
||||
if (!localStorage.getItem('dev_access_token')) {
|
||||
import('./api').then(({ getSiteInfo }) => {
|
||||
getSiteInfo()
|
||||
.then((info) => {
|
||||
if (!info.siteEnabled && !window.location.pathname.startsWith('/maintenance')) {
|
||||
window.location.href = '/maintenance';
|
||||
}
|
||||
})
|
||||
.catch(() => { /* 请求失败时默认放行 */ })
|
||||
.finally(() => setSiteReady(true));
|
||||
});
|
||||
} else {
|
||||
setSiteReady(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 网站状态检查中显示 loading
|
||||
if (!siteReady && !localStorage.getItem('dev_access_token')) {
|
||||
return (
|
||||
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ConfigProvider
|
||||
locale={zhCN}
|
||||
@@ -130,7 +93,6 @@ const App = () => {
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="/maintenance" element={<MaintenancePage />} />
|
||||
<Route path="/join-team" element={<JoinTeamPage />} />
|
||||
<Route path="/private-portrait-authorized" element={<PrivatePortraitAuthorizeResult />} />
|
||||
<Route
|
||||
|
||||
@@ -66,12 +66,6 @@ export async function apiRequest<T>(path: string, options: RequestOptions = {}):
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
// 开发者访问令牌(网站关闭时用于绕过限制)
|
||||
const devAccessToken = localStorage.getItem('dev_access_token');
|
||||
if (devAccessToken) {
|
||||
headers['X-Dev-Access'] = devAccessToken;
|
||||
}
|
||||
|
||||
let bodyStr: string | undefined;
|
||||
if (body !== undefined) {
|
||||
const json = JSON.stringify(body);
|
||||
@@ -116,13 +110,6 @@ export async function apiRequest<T>(path: string, options: RequestOptions = {}):
|
||||
|
||||
// Handle error responses
|
||||
if (!res.ok) {
|
||||
// 网站关闭 → 跳转到维护页面
|
||||
if (res.status === 503 && parsed?.detail?.code === 'SITE_CLOSED') {
|
||||
if (!window.location.pathname.startsWith('/maintenance')) {
|
||||
window.location.href = '/maintenance';
|
||||
}
|
||||
throw new Error(parsed?.detail?.message || '系统正在升级维护');
|
||||
}
|
||||
let msg = parsed?.detail?.message || parsed?.message || `请求失败 (${res.status})`;
|
||||
if (typeof parsed?.detail === 'string') {
|
||||
msg = parsed.detail;
|
||||
|
||||
@@ -290,7 +290,7 @@ export async function verifyCaptcha(captchaId: string, x: number): Promise<strin
|
||||
return res.token;
|
||||
}
|
||||
// ── Site Info ─────────────────────────────────────────────
|
||||
export async function getSiteInfo(): Promise<{ siteName: string; siteLogo: string; userAgreementPrivacyUrl: string; siteCopyright: string; operationManual: string; siteEnabled: boolean }> {
|
||||
export async function getSiteInfo(): Promise<{ siteName: string; siteLogo: string; userAgreementPrivacyUrl: string; siteCopyright: string; operationManual: string }> {
|
||||
if (USE_MOCK) return { siteName: 'VideoGen.AI', siteLogo: '', userAgreementPrivacyUrl: '', siteCopyright: '© 2024 民众智创 版权所有', operationManual: '' };
|
||||
return api.get('/auth/site-info', false);
|
||||
}
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Input, Space, Typography, message } from 'antd';
|
||||
import { ToolOutlined, SafetyOutlined } from '@ant-design/icons';
|
||||
import { getSiteInfo } from '../api';
|
||||
|
||||
const MaintenancePage: React.FC = () => {
|
||||
const [devToken, setDevToken] = useState('');
|
||||
const [showDevInput, setShowDevInput] = useState(false);
|
||||
const [siteName, setSiteName] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
// site-info 在网站关闭时仍可访问(白名单)
|
||||
getSiteInfo().then((info) => {
|
||||
if (info.siteName) setSiteName(info.siteName);
|
||||
}).catch(() => {});
|
||||
}, []);
|
||||
|
||||
const handleDevAccess = () => {
|
||||
const token = devToken.trim();
|
||||
if (!token) {
|
||||
message.warning('请输入开发者访问令牌');
|
||||
return;
|
||||
}
|
||||
localStorage.setItem('dev_access_token', token);
|
||||
message.success('开发者访问已启用,正在刷新...');
|
||||
window.location.href = '/';
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
minHeight: '100vh',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
background: 'linear-gradient(135deg, #f8fafc 0%, #eef2ff 100%)',
|
||||
padding: 24,
|
||||
textAlign: 'center',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
{/* 开发者入口 — 右下角 */}
|
||||
<div style={{ position: 'absolute', right: 20, bottom: 20 }}>
|
||||
{!showDevInput ? (
|
||||
<Typography.Link
|
||||
onClick={() => setShowDevInput(true)}
|
||||
style={{ fontSize: 12, color: '#94a3b8' }}
|
||||
>
|
||||
<SafetyOutlined /> 开发者入口
|
||||
</Typography.Link>
|
||||
) : (
|
||||
<Space.Compact>
|
||||
<Input.Password
|
||||
placeholder="输入开发者访问令牌"
|
||||
value={devToken}
|
||||
onChange={(e) => setDevToken(e.target.value)}
|
||||
onPressEnter={handleDevAccess}
|
||||
style={{ width: 200 }}
|
||||
/>
|
||||
<Button type="primary" onClick={handleDevAccess}>
|
||||
确认
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 主内容 */}
|
||||
<div
|
||||
style={{
|
||||
width: 80,
|
||||
height: 80,
|
||||
borderRadius: 20,
|
||||
background: 'rgba(99, 102, 241, 0.1)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginBottom: 32,
|
||||
}}
|
||||
>
|
||||
<ToolOutlined style={{ fontSize: 40, color: '#6366f1' }} />
|
||||
</div>
|
||||
|
||||
{siteName && (
|
||||
<Typography.Text type="secondary" style={{ fontSize: 13, marginBottom: 8, letterSpacing: 1 }}>
|
||||
{siteName}
|
||||
</Typography.Text>
|
||||
)}
|
||||
|
||||
<Typography.Title level={2} style={{ marginBottom: 8, color: '#1e293b' }}>
|
||||
系统正在升级维护
|
||||
</Typography.Title>
|
||||
|
||||
<Typography.Paragraph type="secondary" style={{ fontSize: 15, maxWidth: 400 }}>
|
||||
为了提供更好的服务,系统正在进行升级维护。
|
||||
<br />
|
||||
请稍后再来,感谢您的理解与支持!
|
||||
</Typography.Paragraph>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MaintenancePage;
|
||||
Reference in New Issue
Block a user