From dfcba94cf5e231f0f43b2ac6f8465848618dc855 Mon Sep 17 00:00:00 2001 From: Jhonton Chen <504071088@qq.com> Date: Fri, 12 Jun 2026 17:57:25 +0800 Subject: [PATCH] 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 --- config/proxy.ts | 3 +- src/hooks/useUserInfo.ts | 22 +++++++++- src/locales/en_US/pages.ts | 5 +++ src/locales/zh_CN/pages.ts | 5 +++ src/pages/account/index.tsx | 11 +---- src/pages/alert/index.tsx | 11 +---- src/pages/company/Popup.tsx | 72 ++++++++++++++++++++++++++++++++- src/pages/company/index.tsx | 42 ++++++++++++++++++- src/pages/data-source/Popup.tsx | 26 ++++++------ src/pages/data-source/index.tsx | 2 + src/services/api/api.ts | 6 +++ src/services/typings.d.ts | 7 ++++ tsconfig.json | 1 + 13 files changed, 178 insertions(+), 35 deletions(-) diff --git a/config/proxy.ts b/config/proxy.ts index 5bfce15..ba63b71 100644 --- a/config/proxy.ts +++ b/config/proxy.ts @@ -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': '', diff --git a/src/hooks/useUserInfo.ts b/src/hooks/useUserInfo.ts index d399cbe..d28a06b 100644 --- a/src/hooks/useUserInfo.ts +++ b/src/hooks/useUserInfo.ts @@ -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 }; } diff --git a/src/locales/en_US/pages.ts b/src/locales/en_US/pages.ts index d87314e..f1606ea 100644 --- a/src/locales/en_US/pages.ts +++ b/src/locales/en_US/pages.ts @@ -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': diff --git a/src/locales/zh_CN/pages.ts b/src/locales/zh_CN/pages.ts index 7f1c0b8..0be036b 100644 --- a/src/locales/zh_CN/pages.ts +++ b/src/locales/zh_CN/pages.ts @@ -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': diff --git a/src/pages/account/index.tsx b/src/pages/account/index.tsx index 71fd260..85360a2 100644 --- a/src/pages/account/index.tsx +++ b/src/pages/account/index.tsx @@ -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, ], }, }, diff --git a/src/pages/alert/index.tsx b/src/pages/alert/index.tsx index 236fabc..7a03bc1 100644 --- a/src/pages/alert/index.tsx +++ b/src/pages/alert/index.tsx @@ -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 }) => { diff --git a/src/pages/company/Popup.tsx b/src/pages/company/Popup.tsx index 8c7dc45..cd086a3 100644 --- a/src/pages/company/Popup.tsx +++ b/src/pages/company/Popup.tsx @@ -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 && ( <> + + + ⚙️ {$t('pages.email.platformAccess')} + + + + +value} + /> + } + styles={{ + header: { fontSize: 14, minHeight: 40, paddingInline: 14 }, + body: { paddingInline: 14 }, + }} + > + + {({ mt4Open }) => ( + + )} + + + + + +value} + /> + } + styles={{ + header: { fontSize: 14, minHeight: 40, paddingInline: 14 }, + body: { paddingInline: 14 }, + }} + > + + {({ mt5Open }) => ( + + )} + + + + + {/* */} 📧 {$t('pages.email.notification')} diff --git a/src/pages/company/index.tsx b/src/pages/company/index.tsx index 2ef314d..f74bf6b 100644 --- a/src/pages/company/index.tsx +++ b/src/pages/company/index.tsx @@ -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 ( + + + MT4:{' '} + {mt4Open === 1 ? ( + + {sourceType4Count ?? '-'} / {mt4Count ?? '-'} + + ) : ( + $t('pages.rules.disable') + )} + + + MT5:{' '} + {mt5Open === 1 ? ( + + {sourceType5Count ?? '-'} / {mt5Count ?? '-'} + + ) : ( + $t('pages.rules.disable') + )} + + + ); + }, + }, { title: $t('table.userCount'), dataIndex: 'userCount', diff --git a/src/pages/data-source/Popup.tsx b/src/pages/data-source/Popup.tsx index d908de8..92f0ca3 100644 --- a/src/pages/data-source/Popup.tsx +++ b/src/pages/data-source/Popup.tsx @@ -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(null); @@ -41,6 +41,12 @@ export default (props: { message: $t('form.placeholder.ipAddress'), }, ]; + + const filteredUserPlatform = useMemo( + () => userPlatform.filter((v) => v?.isOpen), + [userPlatform], + ); + return ( 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 && ( diff --git a/src/pages/data-source/index.tsx b/src/pages/data-source/index.tsx index 1704abc..f2a174a 100644 --- a/src/pages/data-source/index.tsx +++ b/src/pages/data-source/index.tsx @@ -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, diff --git a/src/services/api/api.ts b/src/services/api/api.ts index acc4cc7..c230238 100644 --- a/src/services/api/api.ts +++ b/src/services/api/api.ts @@ -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", diff --git a/src/services/typings.d.ts b/src/services/typings.d.ts index b4f7866..6db8e2d 100644 --- a/src/services/typings.d.ts +++ b/src/services/typings.d.ts @@ -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 diff --git a/tsconfig.json b/tsconfig.json index 3635ece..bbc395e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "ignoreDeprecations": "6.0", "baseUrl": "./", "target": "esnext", "moduleResolution": "node",