feat(company): add expiry warning banner, route wrapper, and conditional UI for expired companies
- Add CompanyExpireWarning component to layout header for expiring subscription alerts - Add CompanyExpire wrapper to guard rules and data-source routes - Refactor CompanyExpireGuard to support optional fullscreen mode via prop - Add responsive Popover support to VoiceCheck for mobile breakpoints - Hide rule CRUD action buttons when company validity is expired - Update profile page to display expiry status alongside validity date - Update i18n strings for clearer expiry messaging Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -52,7 +52,7 @@ export const roleRoutes = [
|
|||||||
meta: {
|
meta: {
|
||||||
permissions: ['rule:list'],
|
permissions: ['rule:list'],
|
||||||
},
|
},
|
||||||
wrappers: ['@/wrappers/noBindDataSource'],
|
wrappers: ['@/wrappers/CompanyExpire', '@/wrappers/noBindDataSource'],
|
||||||
component: './rules',
|
component: './rules',
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
@@ -166,6 +166,7 @@ export const roleRoutes = [
|
|||||||
meta: {
|
meta: {
|
||||||
permissions: ['data-source:list'],
|
permissions: ['data-source:list'],
|
||||||
},
|
},
|
||||||
|
wrappers: ['@/wrappers/CompanyExpire'],
|
||||||
component: './data-source/index',
|
component: './data-source/index',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { errorConfig } from './requestErrorConfig';
|
|||||||
import { getDeviceId } from './utils/fp';
|
import { getDeviceId } from './utils/fp';
|
||||||
import './utils/sentry';
|
import './utils/sentry';
|
||||||
import CompanyExpireGuard from './components/CompanyExpireGuard';
|
import CompanyExpireGuard from './components/CompanyExpireGuard';
|
||||||
|
import CompanyExpireWarning from './components/CompanyExpireWarning';
|
||||||
import GlobalErrorBoundary from './components/GlobalErrorBoundary';
|
import GlobalErrorBoundary from './components/GlobalErrorBoundary';
|
||||||
import PermissionGuard from './components/PermissionGuard';
|
import PermissionGuard from './components/PermissionGuard';
|
||||||
import SSEController from './components/SSEController';
|
import SSEController from './components/SSEController';
|
||||||
@@ -140,6 +141,7 @@ export const layout: RunTimeLayoutConfig = ({ initialState }) => {
|
|||||||
},
|
},
|
||||||
actionsRender: (_layoutProps) => {
|
actionsRender: (_layoutProps) => {
|
||||||
return [
|
return [
|
||||||
|
<CompanyExpireWarning key="CompanyExpireWarning" />,
|
||||||
<VoiceCheck key="VoiceCheck" />,
|
<VoiceCheck key="VoiceCheck" />,
|
||||||
needChangePass && <ChangePassTips key="ChangePassTips" />,
|
needChangePass && <ChangePassTips key="ChangePassTips" />,
|
||||||
// <ThemeSwitch
|
// <ThemeSwitch
|
||||||
|
|||||||
@@ -3,27 +3,18 @@ import { logoutAndToLoginPage } from '@/access';
|
|||||||
import useUserInfo from '@/hooks/useUserInfo';
|
import useUserInfo from '@/hooks/useUserInfo';
|
||||||
import { $t } from '@/utils/i18n';
|
import { $t } from '@/utils/i18n';
|
||||||
|
|
||||||
const CompanyExpireGuard: React.FC = () => {
|
const fullscreenClass =
|
||||||
|
'fixed-all-center flex align-center justify-center bg-white';
|
||||||
|
const CompanyExpireGuard: React.FC<{ fullscreen?: boolean }> = ({
|
||||||
|
fullscreen = true,
|
||||||
|
}) => {
|
||||||
const { userInfo } = useUserInfo();
|
const { userInfo } = useUserInfo();
|
||||||
const isExpired = userInfo?.company?.validityStatus === 2;
|
const isExpired = userInfo?.company?.validityStatus === 2;
|
||||||
|
|
||||||
if (!isExpired) return null;
|
if (!isExpired) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={fullscreen ? fullscreenClass : ''}>
|
||||||
style={{
|
|
||||||
position: 'fixed',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
zIndex: 9999,
|
|
||||||
background: '#fff',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Result
|
<Result
|
||||||
status="warning"
|
status="warning"
|
||||||
title={$t('pages.company.expireTitle')}
|
title={$t('pages.company.expireTitle')}
|
||||||
|
|||||||
63
src/components/CompanyExpireWarning/index.tsx
Normal file
63
src/components/CompanyExpireWarning/index.tsx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { ClockCircleOutlined } from '@ant-design/icons';
|
||||||
|
import { useBreakpoint } from '@ant-design/pro-components';
|
||||||
|
import { history as umiHistory, useAccess } from '@umijs/max';
|
||||||
|
import { Alert, Popover, Space } from 'antd';
|
||||||
|
import useUserInfo from '@/hooks/useUserInfo';
|
||||||
|
import { $t } from '@/utils/i18n';
|
||||||
|
import { utcInstance as dayjs } from '@/utils/timeFormat';
|
||||||
|
|
||||||
|
const CompanyExpireWarning: React.FC = () => {
|
||||||
|
const { isGlobalCompany } = useAccess();
|
||||||
|
const { userInfo } = useUserInfo();
|
||||||
|
const screens = useBreakpoint();
|
||||||
|
const { validityEndTime, validityStatus } = userInfo?.company || {};
|
||||||
|
if (isGlobalCompany) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const expireInDays = dayjs(`${validityEndTime}T23:59:59`).diff(
|
||||||
|
dayjs().utc(),
|
||||||
|
'day',
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
expireInDays,
|
||||||
|
validityEndTime,
|
||||||
|
dayjs().utc().format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const tipsNode = (
|
||||||
|
<Alert
|
||||||
|
message={
|
||||||
|
validityStatus === 2
|
||||||
|
? $t('pages.company.expireTitle')
|
||||||
|
: expireInDays <= 0
|
||||||
|
? $t('pages.company.expireToday')
|
||||||
|
: `${$t('pages.company.expireInDays', { days: Math.abs(expireInDays) })}`
|
||||||
|
}
|
||||||
|
type={validityStatus === 2 ? 'error' : 'warning'}
|
||||||
|
showIcon
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Space align="center" onClick={() => umiHistory.push('/profile')}>
|
||||||
|
{['xs', 'sm'].includes(screens || '') ? (
|
||||||
|
<Popover
|
||||||
|
content={tipsNode}
|
||||||
|
styles={{
|
||||||
|
body: { padding: 0 },
|
||||||
|
}}
|
||||||
|
zIndex={999}
|
||||||
|
defaultOpen={true}
|
||||||
|
>
|
||||||
|
<ClockCircleOutlined
|
||||||
|
className="flex align-center"
|
||||||
|
style={{ color: '#f59e0b', fontSize: 20 }}
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
|
) : (
|
||||||
|
tipsNode
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default CompanyExpireWarning;
|
||||||
@@ -1,6 +1,17 @@
|
|||||||
import { ChromeOutlined, SoundOutlined } from '@ant-design/icons';
|
import { ChromeOutlined, SoundOutlined } from '@ant-design/icons';
|
||||||
|
import { useBreakpoint } from '@ant-design/pro-components';
|
||||||
import { getLocale, Icon } from '@umijs/max';
|
import { getLocale, Icon } from '@umijs/max';
|
||||||
import { Alert, Divider, Image, Modal, Space, Tabs, Typography } from 'antd';
|
import {
|
||||||
|
Alert,
|
||||||
|
Divider,
|
||||||
|
Flex,
|
||||||
|
Image,
|
||||||
|
Modal,
|
||||||
|
Popover,
|
||||||
|
Space,
|
||||||
|
Tabs,
|
||||||
|
Typography,
|
||||||
|
} from 'antd';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import useRichI18n from '@/hooks/useRichI18n';
|
import useRichI18n from '@/hooks/useRichI18n';
|
||||||
import { checkAudioPermission } from '@/utils';
|
import { checkAudioPermission } from '@/utils';
|
||||||
@@ -8,9 +19,9 @@ import bus from '@/utils/eventBus';
|
|||||||
import { $t } from '@/utils/i18n';
|
import { $t } from '@/utils/i18n';
|
||||||
|
|
||||||
const { Paragraph } = Typography;
|
const { Paragraph } = Typography;
|
||||||
const ChangePassTips: React.FC = () => {
|
const VoiceCheck: React.FC = () => {
|
||||||
const { richFormat } = useRichI18n();
|
const { richFormat } = useRichI18n();
|
||||||
|
const screens = useBreakpoint();
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [hasAudioPermission, setHasAudioPermission] = useState(true);
|
const [hasAudioPermission, setHasAudioPermission] = useState(true);
|
||||||
|
|
||||||
@@ -69,25 +80,46 @@ const ChangePassTips: React.FC = () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
const tipsNode = (
|
||||||
|
<>
|
||||||
|
<Alert
|
||||||
|
message={$t('pages.voiceCheck.alertMessage')}
|
||||||
|
type="warning"
|
||||||
|
showIcon
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Space align="center" onClick={() => setVisible(true)}>
|
<Flex align="center" onClick={() => setVisible(true)}>
|
||||||
{!hasAudioPermission && (
|
{!hasAudioPermission &&
|
||||||
<>
|
(['xs', 'sm'].includes(screens || '') ? (
|
||||||
<Alert
|
<Popover
|
||||||
message={$t('pages.voiceCheck.alertMessage')}
|
content={tipsNode}
|
||||||
type="warning"
|
styles={{
|
||||||
showIcon
|
body: { padding: 0 },
|
||||||
/>
|
}}
|
||||||
<Icon
|
zIndex={999}
|
||||||
icon="local:silence"
|
defaultOpen={true}
|
||||||
height="20px"
|
>
|
||||||
fill="#f59e0b"
|
<Icon
|
||||||
className="flex align-center"
|
icon="local:silence"
|
||||||
/>
|
height="20px"
|
||||||
</>
|
fill="#f59e0b"
|
||||||
)}
|
className="flex align-center"
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{tipsNode}
|
||||||
|
<Icon
|
||||||
|
icon="local:silence"
|
||||||
|
height="20px"
|
||||||
|
fill="#f59e0b"
|
||||||
|
className="flex align-center"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
))}
|
||||||
{/* {hasAudioPermission ? (
|
{/* {hasAudioPermission ? (
|
||||||
<Icon
|
<Icon
|
||||||
icon="local:voice"
|
icon="local:voice"
|
||||||
@@ -103,7 +135,7 @@ const ChangePassTips: React.FC = () => {
|
|||||||
className="flex align-center"
|
className="flex align-center"
|
||||||
/>
|
/>
|
||||||
)} */}
|
)} */}
|
||||||
</Space>
|
</Flex>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
title={
|
title={
|
||||||
@@ -154,4 +186,4 @@ const ChangePassTips: React.FC = () => {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default ChangePassTips;
|
export default VoiceCheck;
|
||||||
|
|||||||
@@ -135,6 +135,16 @@ ol {
|
|||||||
padding-inline: 15px;
|
padding-inline: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fixed-all-center {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.ant-table {
|
.ant-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export default {
|
|||||||
'pages.company.expireSubTitle':
|
'pages.company.expireSubTitle':
|
||||||
"Your company's license has expired. All features are temporarily locked. Please contact your sales representative to renew.",
|
"Your company's license has expired. All features are temporarily locked. Please contact your sales representative to renew.",
|
||||||
'pages.company.expireInDays': 'Subscription expires in {days} days',
|
'pages.company.expireInDays': 'Subscription expires in {days} days',
|
||||||
'pages.company.expireToday': 'Expires Today',
|
'pages.company.expireToday': 'Subscription expiring soon; it expires today.',
|
||||||
'pages.totalDataSourceCount': 'Total Data Sources',
|
'pages.totalDataSourceCount': 'Total Data Sources',
|
||||||
'pages.activeDataSourceCount': 'Active Data Sources',
|
'pages.activeDataSourceCount': 'Active Data Sources',
|
||||||
'pages.mt4DataSourceCount': 'MT4 Data Sources',
|
'pages.mt4DataSourceCount': 'MT4 Data Sources',
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export default {
|
|||||||
'pages.company.expireSubTitle':
|
'pages.company.expireSubTitle':
|
||||||
'您的公司在该系统的使用授权已到期,各项功能已暂时锁定。请联系商务代表进行续费操作以恢复访问。',
|
'您的公司在该系统的使用授权已到期,各项功能已暂时锁定。请联系商务代表进行续费操作以恢复访问。',
|
||||||
'pages.company.expireInDays': '订阅即将到期,剩余 {days}天',
|
'pages.company.expireInDays': '订阅即将到期,剩余 {days}天',
|
||||||
'pages.company.expireToday': '今天到期',
|
'pages.company.expireToday': '订阅即将到期,今天到期',
|
||||||
'pages.totalDataSourceCount': '数据源总数',
|
'pages.totalDataSourceCount': '数据源总数',
|
||||||
'pages.activeDataSourceCount': '活跃数据源',
|
'pages.activeDataSourceCount': '活跃数据源',
|
||||||
'pages.mt4DataSourceCount': 'MT4 数据源',
|
'pages.mt4DataSourceCount': 'MT4 数据源',
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
Row,
|
Row,
|
||||||
Space,
|
Space,
|
||||||
Tag,
|
Tag,
|
||||||
|
Typography,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { clearAccessToken } from '@/access';
|
import { clearAccessToken } from '@/access';
|
||||||
@@ -22,6 +23,8 @@ import useUserInfo from '@/hooks/useUserInfo';
|
|||||||
import { changeUserPass } from '@/services/api';
|
import { changeUserPass } from '@/services/api';
|
||||||
import { $t } from '@/utils/i18n';
|
import { $t } from '@/utils/i18n';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
const Profile: React.FC = () => {
|
const Profile: React.FC = () => {
|
||||||
const { userInfo, setUserInfo } = useUserInfo();
|
const { userInfo, setUserInfo } = useUserInfo();
|
||||||
const { isGlobalCompany } = useAccess();
|
const { isGlobalCompany } = useAccess();
|
||||||
@@ -77,13 +80,20 @@ const Profile: React.FC = () => {
|
|||||||
items.push({
|
items.push({
|
||||||
key: '6',
|
key: '6',
|
||||||
span: 'filled',
|
span: 'filled',
|
||||||
label: $t('pages.company.permanent'),
|
label: $t('table.validityEndTime'),
|
||||||
children: userCompany?.validityEndTime
|
children: userCompany?.validityEndTime ? (
|
||||||
? userCompany.validityEndTime
|
<Space>
|
||||||
: $t('pages.company.permanent'),
|
{userCompany.validityEndTime}
|
||||||
|
<Text type="danger" style={{ fontSize: '12px' }}>
|
||||||
|
{$t('pages.company.expired')}
|
||||||
|
</Text>
|
||||||
|
</Space>
|
||||||
|
) : (
|
||||||
|
$t('pages.company.permanent')
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [userInfo]);
|
}, [userCompany, isGlobalCompany]);
|
||||||
|
|
||||||
const changePasswordSuccessHadle = async () => {
|
const changePasswordSuccessHadle = async () => {
|
||||||
let countdown = 5;
|
let countdown = 5;
|
||||||
|
|||||||
@@ -253,62 +253,64 @@ const RuleCommon: React.FC = () => {
|
|||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!isGlobalCompany && hasPerms('rule:edit') && (
|
{!isGlobalCompany &&
|
||||||
<>
|
hasPerms('rule:edit') &&
|
||||||
<Divider style={{ margin: '14px 0' }} />
|
companyInfo?.validityStatus !== 2 && (
|
||||||
<Flex justify="end">
|
<>
|
||||||
<Space wrap style={{ justifyContent: 'flex-end' }}>
|
<Divider style={{ margin: '14px 0' }} />
|
||||||
<Button
|
<Flex justify="end">
|
||||||
variant="solid"
|
<Space wrap style={{ justifyContent: 'flex-end' }}>
|
||||||
color={rule.status === 2 ? 'cyan' : 'orange'}
|
<Button
|
||||||
disabled={
|
variant="solid"
|
||||||
![1, 2].includes(rule.status) || ruleStatusHandleLoading
|
color={rule.status === 2 ? 'cyan' : 'orange'}
|
||||||
}
|
disabled={
|
||||||
onClick={() => handleToggleStatus(rule)}
|
![1, 2].includes(rule.status) || ruleStatusHandleLoading
|
||||||
>
|
|
||||||
{rule.status === 1
|
|
||||||
? $t('pages.rules.disable')
|
|
||||||
: $t('pages.rules.enable')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
disabled={
|
|
||||||
![1, 2].includes(rule.status) || ruleStatusHandleLoading
|
|
||||||
}
|
|
||||||
onClick={() => {
|
|
||||||
setAction('edit');
|
|
||||||
setFormValues(rule);
|
|
||||||
setVisible(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{$t('table.action.edit')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
color="danger"
|
|
||||||
variant="solid"
|
|
||||||
disabled={
|
|
||||||
![1, 2].includes(rule.status) || ruleStatusHandleLoading
|
|
||||||
}
|
|
||||||
onClick={() => handleDelete(rule)}
|
|
||||||
>
|
|
||||||
{$t('table.action.delete')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
icon={<CopyOutlined />}
|
|
||||||
onClick={() => {
|
|
||||||
if (dataSourceList.length < 2) {
|
|
||||||
message.warning($t('pages.rules.cloneRuleWarning'));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
setFormValues(rule);
|
onClick={() => handleToggleStatus(rule)}
|
||||||
setCopyVisible(true);
|
>
|
||||||
}}
|
{rule.status === 1
|
||||||
>
|
? $t('pages.rules.disable')
|
||||||
{$t('pages.rules.clone')}
|
: $t('pages.rules.enable')}
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
<Button
|
||||||
</Flex>
|
disabled={
|
||||||
</>
|
![1, 2].includes(rule.status) || ruleStatusHandleLoading
|
||||||
)}
|
}
|
||||||
|
onClick={() => {
|
||||||
|
setAction('edit');
|
||||||
|
setFormValues(rule);
|
||||||
|
setVisible(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{$t('table.action.edit')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="danger"
|
||||||
|
variant="solid"
|
||||||
|
disabled={
|
||||||
|
![1, 2].includes(rule.status) || ruleStatusHandleLoading
|
||||||
|
}
|
||||||
|
onClick={() => handleDelete(rule)}
|
||||||
|
>
|
||||||
|
{$t('table.action.delete')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
icon={<CopyOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
if (dataSourceList.length < 2) {
|
||||||
|
message.warning($t('pages.rules.cloneRuleWarning'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setFormValues(rule);
|
||||||
|
setCopyVisible(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{$t('pages.rules.clone')}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</ProCard>
|
</ProCard>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -438,34 +440,36 @@ const RuleCommon: React.FC = () => {
|
|||||||
icon={<RedoOutlined />}
|
icon={<RedoOutlined />}
|
||||||
onClick={() => refreshRules(1)}
|
onClick={() => refreshRules(1)}
|
||||||
/>
|
/>
|
||||||
{!isGlobalCompany && hasPerms('rule:edit') && (
|
{!isGlobalCompany &&
|
||||||
<>
|
hasPerms('rule:edit') &&
|
||||||
<Button
|
companyInfo?.validityStatus !== 2 && (
|
||||||
key="1"
|
<>
|
||||||
type="primary"
|
<Button
|
||||||
onClick={() => {
|
key="1"
|
||||||
setAction('add');
|
type="primary"
|
||||||
setFormValues({});
|
onClick={() => {
|
||||||
setVisible(true);
|
setAction('add');
|
||||||
}}
|
setFormValues({});
|
||||||
>
|
setVisible(true);
|
||||||
<PlusOutlined />
|
}}
|
||||||
{`${$t('table.action.add')}${$t('menu.rule')}`}
|
>
|
||||||
</Button>
|
<PlusOutlined />
|
||||||
<Button
|
{`${$t('table.action.add')}${$t('menu.rule')}`}
|
||||||
icon={<CopyOutlined />}
|
</Button>
|
||||||
onClick={() => {
|
<Button
|
||||||
if (dataSourceList.length < 2) {
|
icon={<CopyOutlined />}
|
||||||
message.warning($t('pages.rules.cloneRuleWarning'));
|
onClick={() => {
|
||||||
return;
|
if (dataSourceList.length < 2) {
|
||||||
}
|
message.warning($t('pages.rules.cloneRuleWarning'));
|
||||||
setMultiCopyVisible(true);
|
return;
|
||||||
}}
|
}
|
||||||
>
|
setMultiCopyVisible(true);
|
||||||
{$t('pages.clones.title')}
|
}}
|
||||||
</Button>
|
>
|
||||||
</>
|
{$t('pages.clones.title')}
|
||||||
)}
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
</Space>
|
</Space>
|
||||||
|
|
||||||
|
|||||||
13
src/wrappers/CompanyExpire.tsx
Normal file
13
src/wrappers/CompanyExpire.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { Outlet, useAccess } from '@umijs/max';
|
||||||
|
import CompanyExpireGuard from '@/components/CompanyExpireGuard';
|
||||||
|
import useUserInfo from '@/hooks/useUserInfo';
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const { isGlobalCompany } = useAccess();
|
||||||
|
const { userInfo } = useUserInfo();
|
||||||
|
const { company } = userInfo || {};
|
||||||
|
if (!isGlobalCompany && company?.validityStatus === 2) {
|
||||||
|
return <CompanyExpireGuard fullscreen={false} />;
|
||||||
|
}
|
||||||
|
return <Outlet />;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user