冲突解决

This commit is contained in:
sjy
2026-08-13 10:02:22 +08:00
10 changed files with 238 additions and 129 deletions
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -27,7 +27,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
<title>民众智创</title>
<script type="module" crossorigin src="/assets/index-xGZWod_P.js"></script>
<script type="module" crossorigin src="/assets/index-CGl8RZCw.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DSYnuUvx.css">
</head>
<body>
+4
View File
@@ -114,6 +114,10 @@ if (!res.ok) {
if (typeof parsed?.detail === 'string') {
msg = parsed.detail;
}
// 检测"被踢出"错误 — 单设备登录互斥
if (res.status === 401 && typeof parsed?.detail === 'string' && parsed.detail.includes('其他设备登录')) {
window.dispatchEvent(new CustomEvent('kicked-out', { detail: { message: parsed.detail } }));
}
if (res.status === 401 && !options.skipAuthRedirect) {
clearToken();
window.location.href = '/login';
@@ -0,0 +1,43 @@
import React from 'react';
import { Modal, Typography, Button } from 'antd';
import { ExclamationCircleOutlined } from '@ant-design/icons';
import { clearToken } from '../api/client';
interface KickedOutModalProps {
visible: boolean;
}
const KickedOutModal: React.FC<KickedOutModalProps> = ({ visible }) => {
const handleOk = () => {
clearToken();
window.location.href = '/login';
};
return (
<Modal
open={visible}
closable={false}
maskClosable={false}
footer={null}
centered
width={400}
style={{ textAlign: 'center' }}
wrapClassName="kicked-out-modal"
>
<div style={{ padding: '24px 0 16px' }}>
<ExclamationCircleOutlined style={{ fontSize: 48, color: '#f59e0b', marginBottom: 16 }} />
<Typography.Title level={4} style={{ marginBottom: 12, color: '#1e293b' }}>
</Typography.Title>
<Typography.Paragraph style={{ color: '#64748b', fontSize: 14, marginBottom: 24 }}>
</Typography.Paragraph>
<Button type="primary" size="large" block onClick={handleOk} style={{ borderRadius: 8 }}>
</Button>
</div>
</Modal>
);
};
export default KickedOutModal;
@@ -64,6 +64,7 @@ import { getMenuConfigs, getCreditProductCatalog, getPaymentMethods, createRecha
import type { CreditProduct, CreditProductCatalog } from '../../types';
import NotificationPopup from '../NotificationPopup';
import ActivityBanner from './ActivityBanner';
import KickedOutModal from '../KickedOutModal';
import './AppLayout.css';
@@ -498,6 +499,11 @@ const AppLayout: React.FC = () => {
}
return logo;
});
const [siteInfoLoading, setSiteInfoLoading] = useState(!localStorage.getItem('siteInfo'));
const [bannerVisible, setBannerVisible] = useState(false);
const [kickedOutVisible, setKickedOutVisible] = useState(false);
// 资源存储容量(从 getUser().resource_capacity 获取)
const [resourceCapacity, setResourceCapacity] = useState<{
enabled: boolean;
usedBytes: number;
@@ -511,6 +517,13 @@ const AppLayout: React.FC = () => {
const [isMobile, setIsMobile] = useState(false);
const [operationManualUrl, setOperationManualUrl] = useState('');
// 监听"被踢出"事件 — 单设备登录互斥
useEffect(() => {
const handler = () => setKickedOutVisible(true);
window.addEventListener('kicked-out', handler);
return () => window.removeEventListener('kicked-out', handler);
}, []);
useEffect(() => {
getSiteInfo().then(info => {
const name = info.siteName || '智创';
@@ -1024,7 +1037,8 @@ const AppLayout: React.FC = () => {
flexDirection: 'column',
overflow: 'hidden',
}}>
<ActivityBanner onVisibilityChange={() => {}} />
<ActivityBanner onVisibilityChange={(v) => setBannerVisible(v)} />
<KickedOutModal visible={kickedOutVisible} />
<div style={{ display: 'flex', position: 'relative', padding: 4, gap: 12, flex: 1, minHeight: 0, overflow: 'hidden' }}>
<div className="desktop-sidebar" style={{
width: sidebarW, position: 'sticky', top: 4, alignSelf: 'flex-start', zIndex: 100,