feat(platform): add platform access control with quota management
- Add userPlatform hook to derive available platforms from company config - Add platform access section in company popup with MT4/MT5 switch and quota limits - Add platform access column in company list showing quota usage - Replace hardcoded platform options with dynamic userPlatform in account/alert/data-source pages - Add getPlatformList API and extend CompanyListItem typings - Add i18n translations for platform access related keys - Update dev proxy target port to 8082 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,8 @@ export default {
|
||||
'/api': {
|
||||
// The address to proxy
|
||||
// target: 'http://2333z061l7.51mypc.cn',
|
||||
target: 'http://172.20.177.121:8080',
|
||||
// target: 'http://172.20.177.121:8080',
|
||||
target: 'http://172.20.177.121:8082',
|
||||
changeOrigin: true,
|
||||
pathRewrite: {
|
||||
'^/api': '',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useModel } from '@umijs/max';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export default function useUserInfo() {
|
||||
const { initialState, setInitialState } = useModel('@@initialState');
|
||||
@@ -14,5 +15,24 @@ export default function useUserInfo() {
|
||||
setUserInfo(info);
|
||||
};
|
||||
|
||||
return { userInfo, refreshUserInfo, setUserInfo };
|
||||
const userPlatform = useMemo(() => {
|
||||
const company = userInfo?.company;
|
||||
const originalPlatform = [
|
||||
{
|
||||
label: 'MT5',
|
||||
value: 5,
|
||||
isOpen: company?.mt5Open === 1,
|
||||
},
|
||||
{
|
||||
label: 'MT4',
|
||||
value: 4,
|
||||
isOpen: company?.mt4Open === 1,
|
||||
},
|
||||
];
|
||||
|
||||
// return originalPlatform.filter((item) => item.isOpen);
|
||||
return originalPlatform;
|
||||
}, [userInfo]);
|
||||
|
||||
return { userInfo, refreshUserInfo, setUserInfo, userPlatform };
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ export default {
|
||||
'common.loading': 'Loading...',
|
||||
'common.content.noRules': 'No rules available',
|
||||
'common.content.noData': 'No data available',
|
||||
'table.platformAccess': 'Platform Quota',
|
||||
'table.group': 'Group',
|
||||
'table.operatorRecords': 'Operator Records',
|
||||
'table.operatorAction': 'Operator Action',
|
||||
@@ -603,6 +604,10 @@ export default {
|
||||
'Enter verified sender emails (comma separated)',
|
||||
'pages.email.status.normal': 'Active',
|
||||
'pages.email.status.disabled': 'Inactive',
|
||||
'pages.email.platformAccess': ' Platform Access',
|
||||
'pages.email.platformAccessEnable': ' Access',
|
||||
'pages.email.platformMax': 'Quota Limit',
|
||||
'pages.email.platformAssigned': 'Assigned',
|
||||
'pages.email.notification': 'Email Notification Config',
|
||||
'pages.email.notificationEmail': 'Notification Email',
|
||||
'pages.email.notificationEmail.tips':
|
||||
|
||||
@@ -2,6 +2,7 @@ export default {
|
||||
'common.loading': '加载中...',
|
||||
'common.content.noRules': '暂无规则',
|
||||
'common.content.noData': '暂无数据',
|
||||
'table.platformAccess': '平台配额',
|
||||
'table.group': '所属组',
|
||||
'table.operatorRecords': '操作记录',
|
||||
'table.operatorAction': '操作动作',
|
||||
@@ -569,6 +570,10 @@ export default {
|
||||
'pages.email.availableEmails.tips': '在服务商已验证的邮箱,多个用逗号分隔',
|
||||
'pages.email.status.normal': '正常',
|
||||
'pages.email.status.disabled': '停用',
|
||||
'pages.email.platformAccess': '平台接入权限',
|
||||
'pages.email.platformAccessEnable': '接入',
|
||||
'pages.email.platformMax': '最大数量上限',
|
||||
'pages.email.platformAssigned': '已配',
|
||||
'pages.email.notification': '邮件通知配置',
|
||||
'pages.email.notificationEmail': '通知邮箱',
|
||||
'pages.email.notificationEmail.tips':
|
||||
|
||||
@@ -21,7 +21,7 @@ const maxAlertHideCount = 9999;
|
||||
|
||||
const Account: React.FC = () => {
|
||||
const { isGlobalCompany } = useAccess();
|
||||
const { userInfo } = useUserInfo();
|
||||
const { userInfo, userPlatform } = useUserInfo();
|
||||
const companyInfo = userInfo?.company;
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
@@ -122,14 +122,7 @@ const Account: React.FC = () => {
|
||||
label: $t('table.all'),
|
||||
value: '',
|
||||
},
|
||||
// {
|
||||
// label: 'MT4',
|
||||
// value: '4',
|
||||
// },
|
||||
{
|
||||
label: 'MT5',
|
||||
value: '5',
|
||||
},
|
||||
...userPlatform,
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -64,7 +64,7 @@ const Alert: React.FC = () => {
|
||||
const [dataSourceList, setDataSourceList] = useState<
|
||||
API.DataSourceListItem[]
|
||||
>([]);
|
||||
const { userInfo } = useUserInfo();
|
||||
const { userInfo, userPlatform } = useUserInfo();
|
||||
const userDataSourceList = userInfo?.userDataSourceList;
|
||||
const companyInfo = userInfo?.company;
|
||||
|
||||
@@ -257,14 +257,7 @@ const Alert: React.FC = () => {
|
||||
label: $t('table.all'),
|
||||
value: '',
|
||||
},
|
||||
// {
|
||||
// label: 'MT4',
|
||||
// value: 4,
|
||||
// },
|
||||
{
|
||||
label: 'MT5',
|
||||
value: 5,
|
||||
},
|
||||
...userPlatform,
|
||||
],
|
||||
},
|
||||
render: (_, { platform }) => {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import {
|
||||
ModalForm,
|
||||
ProFormDependency,
|
||||
ProFormDigit,
|
||||
type ProFormInstance,
|
||||
ProFormSelect,
|
||||
ProFormSwitch,
|
||||
ProFormText,
|
||||
} from '@ant-design/pro-components';
|
||||
import { useAccess } from '@umijs/max';
|
||||
import { Divider, Space, Typography } from 'antd';
|
||||
import { Card, Col, Divider, Row, Space, Typography } from 'antd';
|
||||
import { useRef } from 'react';
|
||||
import useUserInfo from '@/hooks/useUserInfo';
|
||||
import {
|
||||
@@ -110,6 +111,75 @@ export default (props: {
|
||||
{formValues?.id !== void 0 && (
|
||||
<>
|
||||
<Divider size="small" />
|
||||
|
||||
<Text strong style={{ lineHeight: '40px', fontSize: 16 }}>
|
||||
⚙️ {$t('pages.email.platformAccess')}
|
||||
</Text>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Card
|
||||
title={`MT4 ${$t('pages.email.platformAccessEnable')}`}
|
||||
extra={
|
||||
<ProFormSwitch
|
||||
name="mt4Open"
|
||||
noStyle
|
||||
transform={(value) => +value}
|
||||
/>
|
||||
}
|
||||
styles={{
|
||||
header: { fontSize: 14, minHeight: 40, paddingInline: 14 },
|
||||
body: { paddingInline: 14 },
|
||||
}}
|
||||
>
|
||||
<ProFormDependency name={['mt4Open']}>
|
||||
{({ mt4Open }) => (
|
||||
<ProFormDigit
|
||||
name="mt4Count"
|
||||
label={`${$t('pages.email.platformMax')}`}
|
||||
min={0}
|
||||
step={1}
|
||||
disabled={!mt4Open}
|
||||
formItemProps={{
|
||||
style: { marginBottom: 0 },
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ProFormDependency>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Card
|
||||
title={`MT5 ${$t('pages.email.platformAccessEnable')}`}
|
||||
extra={
|
||||
<ProFormSwitch
|
||||
name="mt5Open"
|
||||
noStyle
|
||||
transform={(value) => +value}
|
||||
/>
|
||||
}
|
||||
styles={{
|
||||
header: { fontSize: 14, minHeight: 40, paddingInline: 14 },
|
||||
body: { paddingInline: 14 },
|
||||
}}
|
||||
>
|
||||
<ProFormDependency name={['mt5Open']}>
|
||||
{({ mt5Open }) => (
|
||||
<ProFormDigit
|
||||
name="mt5Count"
|
||||
label={`${$t('pages.email.platformMax')}`}
|
||||
min={0}
|
||||
step={1}
|
||||
disabled={!mt5Open}
|
||||
formItemProps={{
|
||||
style: { marginBottom: 0 },
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ProFormDependency>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
{/* <Divider size="small" /> */}
|
||||
<Text strong style={{ lineHeight: '40px', fontSize: 16 }}>
|
||||
📧 {$t('pages.email.notification')}
|
||||
</Text>
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
type ProColumns,
|
||||
ProTable,
|
||||
} from '@ant-design/pro-components';
|
||||
import { Badge, Button, Col, Row } from 'antd';
|
||||
import { Badge, Button, Col, Row, Space } from 'antd';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import HeadStatisticCard, {
|
||||
type CardInfo,
|
||||
@@ -69,6 +69,46 @@ const Company: React.FC = () => {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: $t('table.platformAccess'),
|
||||
dataIndex: 'platformAccess',
|
||||
render: (
|
||||
_,
|
||||
{
|
||||
mt4Count,
|
||||
mt4Open,
|
||||
mt5Count,
|
||||
mt5Open,
|
||||
sourceType4Count,
|
||||
sourceType5Count,
|
||||
},
|
||||
) => {
|
||||
return (
|
||||
<Space direction="vertical" size={4}>
|
||||
<MyTag color={mt4Open === 1 ? '#818cf8' : '#94a3b8'}>
|
||||
MT4:{' '}
|
||||
{mt4Open === 1 ? (
|
||||
<span>
|
||||
{sourceType4Count ?? '-'} / {mt4Count ?? '-'}
|
||||
</span>
|
||||
) : (
|
||||
$t('pages.rules.disable')
|
||||
)}
|
||||
</MyTag>
|
||||
<MyTag color={mt5Open === 1 ? '#3b82f6' : '#94a3b8'}>
|
||||
MT5:{' '}
|
||||
{mt5Open === 1 ? (
|
||||
<span>
|
||||
{sourceType5Count ?? '-'} / {mt5Count ?? '-'}
|
||||
</span>
|
||||
) : (
|
||||
$t('pages.rules.disable')
|
||||
)}
|
||||
</MyTag>
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: $t('table.userCount'),
|
||||
dataIndex: 'userCount',
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
} from '@ant-design/pro-components';
|
||||
import { useAccess } from '@umijs/max';
|
||||
import { Divider, type FormInstance, Typography } from 'antd';
|
||||
import { useRef } from 'react';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import useUserInfo from '@/hooks/useUserInfo';
|
||||
import { preventScientificNotation } from '@/utils';
|
||||
import { $t } from '@/utils/i18n';
|
||||
@@ -22,7 +22,7 @@ export default (props: {
|
||||
}) => {
|
||||
const { visible, formValues, onSubmit, onDone, companyList } = props;
|
||||
|
||||
const { userInfo } = useUserInfo();
|
||||
const { userInfo, userPlatform } = useUserInfo();
|
||||
const { isGlobalCompany } = useAccess();
|
||||
const globalCompanyId = isGlobalCompany ? userInfo?.companyId : 0;
|
||||
const formRef = useRef<FormInstance>(null);
|
||||
@@ -41,6 +41,12 @@ export default (props: {
|
||||
message: $t('form.placeholder.ipAddress'),
|
||||
},
|
||||
];
|
||||
|
||||
const filteredUserPlatform = useMemo(
|
||||
() => userPlatform.filter((v) => v?.isOpen),
|
||||
[userPlatform],
|
||||
);
|
||||
|
||||
return (
|
||||
<ModalForm
|
||||
width="600px"
|
||||
@@ -50,7 +56,10 @@ export default (props: {
|
||||
formValues?.id
|
||||
? formValues
|
||||
: {
|
||||
sourceType: 5,
|
||||
sourceType:
|
||||
filteredUserPlatform.length > 0
|
||||
? filteredUserPlatform[0].value
|
||||
: null,
|
||||
}
|
||||
}
|
||||
open={visible}
|
||||
@@ -78,16 +87,7 @@ export default (props: {
|
||||
label={$t('form.dataSource.type')}
|
||||
allowClear={false}
|
||||
rules={[{ required: true, message: $t('form.required') }]}
|
||||
options={[
|
||||
// {
|
||||
// value: 4,
|
||||
// label: 'MT4',
|
||||
// },
|
||||
{
|
||||
value: 5,
|
||||
label: 'MT5',
|
||||
},
|
||||
]}
|
||||
options={filteredUserPlatform}
|
||||
/>
|
||||
|
||||
{isGlobalCompany && (
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
dataSourceHandle,
|
||||
getCompanyList,
|
||||
getDataSourcePages,
|
||||
getPlatformList,
|
||||
} from '@/services/api';
|
||||
import { $t } from '@/utils/i18n';
|
||||
import Popup from './Popup';
|
||||
@@ -49,6 +50,7 @@ const DataSource: React.FC = () => {
|
||||
setCompanyList(data || []);
|
||||
});
|
||||
}
|
||||
getPlatformList();
|
||||
}, []);
|
||||
const [cardInfo, setCardInfo] = useState({
|
||||
// mt4Count: 0,
|
||||
|
||||
@@ -612,6 +612,12 @@ export async function closeSSEWithApi(options?: ObjType) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function getPlatformList(options?: ObjType) {
|
||||
return request("/system/platform-list", {
|
||||
method: "GET",
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
export async function getDashboardTotal(options?: ObjType) {
|
||||
return request("/dashboard/total", {
|
||||
method: "GET",
|
||||
|
||||
7
src/services/typings.d.ts
vendored
7
src/services/typings.d.ts
vendored
@@ -113,6 +113,13 @@ declare namespace API {
|
||||
fromName: string | null;
|
||||
mailHostConfigId: number | string | null;
|
||||
mailStatus: number | null;
|
||||
|
||||
mt4Count: number;
|
||||
mt4Open: number;
|
||||
mt5Count: number;
|
||||
mt5Open: number;
|
||||
sourceType4Count: number;
|
||||
sourceType5Count: number;
|
||||
};
|
||||
|
||||
// "DataSource" Type
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"ignoreDeprecations": "6.0",
|
||||
"baseUrl": "./",
|
||||
"target": "esnext",
|
||||
"moduleResolution": "node",
|
||||
|
||||
Reference in New Issue
Block a user