Commit 86021d8e by CaIon

Refine default web UI and backend sync handling

parent 722d0366
......@@ -17,10 +17,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { defineConfig } from 'i18next-cli';
/** @type {import('i18next-cli').I18nextToolkitConfig} */
export default defineConfig({
export default {
locales: ['zh-CN', 'zh-TW', 'en', 'fr', 'ru', 'ja', 'vi'],
extract: {
input: ['src/**/*.{js,jsx,ts,tsx}'],
......@@ -83,4 +81,4 @@ export default defineConfig({
keySeparator: false,
mergeNamespaces: true,
},
});
};
......@@ -59,7 +59,7 @@ export default defineConfig(({ envMode }) => {
},
server: {
host: '0.0.0.0',
strictPort: true,
strictPort: false,
proxy: devProxy,
},
output: {
......
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import React, { useContext, useState } from 'react';
import { Banner, Button, Modal } from '@douyinfe/semi-ui';
import { IconAlertTriangle, IconClose } from '@douyinfe/semi-icons';
import { useTranslation } from 'react-i18next';
import { UserContext } from '../../context/User';
import { confirmSwitchToDefaultFrontend } from '../../helpers';
const DISMISS_STORAGE_KEY = 'classic_frontend_deprecation_notice_dismissed_v1';
const ClassicFrontendDeprecationBanner = () => {
const { t } = useTranslation();
const [userState] = useContext(UserContext);
const [switching, setSwitching] = useState(false);
const [visible, setVisible] = useState(() => {
try {
return localStorage.getItem(DISMISS_STORAGE_KEY) !== '1';
} catch (_) {
return true;
}
});
if (!visible) {
return null;
}
const isRootUser = userState?.user?.role >= 100;
const confirmClose = () => {
Modal.confirm({
title: t('确认关闭提示'),
content: t(
'关闭后将不再显示此提示(仅对当前浏览器生效)。确定要关闭吗?',
),
okText: t('关闭提示'),
cancelText: t('取消'),
okButtonProps: {
type: 'danger',
},
onOk: () => {
try {
localStorage.setItem(DISMISS_STORAGE_KEY, '1');
} catch (_) {}
setVisible(false);
},
});
};
const switchFrontend = () => {
confirmSwitchToDefaultFrontend(t, {
onLoadingChange: setSwitching,
});
};
return (
<div className='classic-frontend-deprecation-banner'>
<div className='classic-frontend-deprecation-banner-inner'>
<Banner
type='warning'
closeIcon={null}
icon={
<IconAlertTriangle
size='large'
style={{ color: 'var(--semi-color-warning)' }}
/>
}
title={t('旧版前端即将停止维护')}
description={
<div className='classic-frontend-deprecation-body'>
<span>
{isRootUser
? t(
'你正在使用旧版前端,该版本即将停止维护,部分功能可能无法使用。建议切换到新版前端以获得完整体验。',
)
: t(
'你正在使用旧版前端,该版本即将停止维护,部分功能可能无法使用。请联系管理员切换到新版前端。',
)}
</span>
{isRootUser ? (
<Button
type='warning'
theme='solid'
size='small'
loading={switching}
onClick={switchFrontend}
>
{t('切换到新版前端')}
</Button>
) : null}
</div>
}
/>
<Button
theme='borderless'
size='small'
type='tertiary'
icon={<IconClose aria-hidden={true} />}
onClick={confirmClose}
className='classic-frontend-deprecation-close'
aria-label={t('关闭')}
/>
</div>
</div>
);
};
export default ClassicFrontendDeprecationBanner;
......@@ -22,6 +22,7 @@ import { Layout } from '@douyinfe/semi-ui';
import SiderBar from './SiderBar';
import App from '../../App';
import FooterBar from './Footer';
import ClassicFrontendDeprecationBanner from './ClassicFrontendDeprecationBanner';
import { ToastContainer } from 'react-toastify';
import ErrorBoundary from '../common/ErrorBoundary';
import React, { useContext, useEffect, useState } from 'react';
......@@ -211,6 +212,7 @@ const PageLayout = () => {
minHeight: 0,
}}
>
<ClassicFrontendDeprecationBanner />
<Content
className={isFixedLayout ? undefined : 'public-page-content'}
style={{
......
......@@ -28,7 +28,13 @@ import {
Space,
Card,
} from '@douyinfe/semi-ui';
import { API, showError, showSuccess, timestamp2string } from '../../helpers';
import {
API,
confirmSwitchToDefaultFrontend,
showError,
showSuccess,
timestamp2string,
} from '../../helpers';
import { marked } from 'marked';
import { useTranslation } from 'react-i18next';
import { StatusContext } from '../../context/Status';
......@@ -281,42 +287,12 @@ const OtherSetting = () => {
};
const switchToDefaultFrontend = () => {
Modal.confirm({
title: t('切换到新版前端'),
content: t('切换后页面会自动刷新,并进入新版前端。是否继续?'),
okText: t('确认切换'),
cancelText: t('取消'),
onOk: async () => {
try {
setLoadingInput((loadingInput) => ({
...loadingInput,
FrontendTheme: true,
}));
const res = await API.put('/api/option/', {
key: 'theme.frontend',
value: 'default',
});
const { success, message } = res.data;
if (!success) {
showError(message);
return;
}
showSuccess(t('已切换到新版前端,正在跳转首页'));
setTimeout(() => {
// 新版前端的路由与经典前端不同,原地刷新当前路径会 404,
// 因此切换后重置到首页,由后端按新主题返回对应前端。
// 使用 replace 避免在历史中留下已失效的路由,防止返回时再次 404。
window.location.replace('/');
}, 600);
} catch (error) {
console.error('切换新版前端失败', error);
showError(t('切换失败,请稍后重试'));
} finally {
confirmSwitchToDefaultFrontend(t, {
onLoadingChange: (loading) => {
setLoadingInput((loadingInput) => ({
...loadingInput,
FrontendTheme: false,
FrontendTheme: loading,
}));
}
},
});
};
......
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import React from 'react';
import { Modal } from '@douyinfe/semi-ui';
import { API } from './api';
import { showError, showSuccess } from './utils';
export function confirmSwitchToDefaultFrontend(t, options = {}) {
const { onLoadingChange } = options;
Modal.confirm({
title: t('切换到新版前端'),
content: React.createElement(
'div',
null,
React.createElement(
'div',
{ style: { marginBottom: 8 } },
t('切换后页面会自动刷新,并进入新版前端。是否继续?'),
),
React.createElement(
'div',
{ style: { color: 'var(--semi-color-text-1)' } },
t('提示:如果切换后页面无法正常渲染,请清空浏览器缓存后重试。'),
),
),
okText: t('确认切换'),
cancelText: t('取消'),
onOk: async () => {
try {
onLoadingChange?.(true);
const res = await API.put('/api/option/', {
key: 'theme.frontend',
value: 'default',
});
const { success, message } = res.data;
if (!success) {
showError(message);
return;
}
showSuccess(t('已切换到新版前端,正在跳转首页'));
setTimeout(() => {
window.location.replace('/');
}, 600);
} catch (error) {
console.error('切换新版前端失败', error);
showError(t('切换失败,请稍后重试'));
} finally {
onLoadingChange?.(false);
}
},
});
}
......@@ -30,3 +30,4 @@ export * from './boolean';
export * from './dashboard';
export * from './passkey';
export * from './statusCodeRules';
export * from './frontendTheme';
......@@ -25,8 +25,8 @@ body.sidebar-collapsed {
}
body {
font-family: Lato, 'Helvetica Neue', Arial, Helvetica, 'Microsoft YaHei',
sans-serif;
font-family:
Lato, 'Helvetica Neue', Arial, Helvetica, 'Microsoft YaHei', sans-serif;
color: var(--semi-color-text-0);
background-color: var(--semi-color-bg-0);
}
......@@ -46,6 +46,50 @@ body {
flex-direction: column;
}
.classic-frontend-deprecation-banner {
flex: 0 0 auto;
margin-top: 64px;
padding: 8px 12px 0;
}
.classic-frontend-deprecation-banner-inner {
position: relative;
}
.classic-frontend-deprecation-banner .semi-banner {
border-radius: 8px;
}
.classic-frontend-deprecation-body {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding-right: 32px;
}
.classic-frontend-deprecation-body > span {
min-width: 0;
}
.classic-frontend-deprecation-close {
position: absolute;
top: 8px;
right: 8px;
}
@media (max-width: 767px) {
.classic-frontend-deprecation-banner {
padding: 8px 8px 0;
}
.classic-frontend-deprecation-body {
align-items: flex-start;
flex-direction: column;
padding-right: 24px;
}
}
.classic-page-fill {
flex: 1 0 auto;
min-height: 100%;
......@@ -71,8 +115,8 @@ body {
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
font-family:
source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
}
/* ==================== 布局相关样式 ==================== */
......@@ -389,7 +433,9 @@ code {
line-height: 1;
background-color: var(--semi-color-fill-0);
color: var(--semi-color-text-2);
transition: background-color 0.15s ease, color 0.15s ease;
transition:
background-color 0.15s ease,
color 0.15s ease;
}
.sbg-badge-active {
......@@ -829,11 +875,8 @@ html:not(.dark) .blur-ball-teal {
inset: 0;
pointer-events: none;
z-index: 0;
background: radial-gradient(
circle at -5% -10%,
var(--pb1) 0%,
transparent 60%
),
background:
radial-gradient(circle at -5% -10%, var(--pb1) 0%, transparent 60%),
radial-gradient(circle at 105% -10%, var(--pb2) 0%, transparent 55%),
radial-gradient(circle at 5% 110%, var(--pb3) 0%, transparent 55%),
radial-gradient(circle at 105% 110%, var(--pb4) 0%, transparent 50%);
......@@ -907,7 +950,8 @@ html.dark .with-pastel-balls::before {
max-width: 80px;
}
.semi-input-prefix-text, .semi-input-suffix-text {
.semi-input-prefix-text,
.semi-input-suffix-text {
margin: 0;
}
......@@ -1045,4 +1089,6 @@ html.dark .with-pastel-balls::before {
}
}
.ec-dbcd0a3c01b55203 { forced-color-adjust: auto; }
.ec-dbcd0a3c01b55203 {
forced-color-adjust: auto;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment