| @@ -345,7 +345,6 @@ export default [ | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| { | { | ||||
| name: '应用开发', | name: '应用开发', | ||||
| path: '/appsDeployment', | path: '/appsDeployment', | ||||
| @@ -498,6 +497,18 @@ export default [ | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| { | |||||
| name: '算力积分', | |||||
| path: '/points', | |||||
| routes: [ | |||||
| { | |||||
| name: '算力积分', | |||||
| path: '', | |||||
| key: 'points', | |||||
| component: './Points/index', | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| { | { | ||||
| path: '*', | path: '*', | ||||
| layout: false, | layout: false, | ||||
| @@ -1,5 +1,23 @@ | |||||
| @font-face { | @font-face { | ||||
| font-family: Alibaba; | |||||
| src: url('./ALIBABA-PUHUITI-MEDIUM.TTF'); | |||||
| font-display: swap; | |||||
| } | |||||
| font-family: Alibaba; | |||||
| src: url('./ALIBABA-PUHUITI-MEDIUM.TTF'); | |||||
| font-display: swap; | |||||
| } | |||||
| @font-face { | |||||
| font-family: 'TaoBaoMaiCaiTi'; | |||||
| src: url('./TaoBaoMaiCaiTi-Regular.woff2') format('woff2'), /* 最优先使用 woff2 */ | |||||
| url('./TaoBaoMaiCaiTi-Regular.woff') format('woff'), /* 兼容性较好的 woff */ | |||||
| url('./TaoBaoMaiCaiTi-Regular.ttf') format('truetype'), /* ttf 作为备选 */ | |||||
| url('./TaoBaoMaiCaiTi-Regular.otf') format('opentype'); /* otf 作为最后选项 */ | |||||
| font-display: swap; /* 优化页面加载时的字体显示 */ | |||||
| } | |||||
| @font-face { | |||||
| font-family: 'DingTalk-JinBuTi'; | |||||
| src: url('./DingTalk-JinBuTi.woff2') format('woff2'), /* 最优先使用 woff2 */ | |||||
| url('./DingTalk-JinBuTi.woff') format('woff'), /* 兼容性较好的 woff */ | |||||
| url('./DingTalk-JinBuTi.ttf') format('truetype'); /* ttf 作为备选 */ | |||||
| font-display: swap; /* 优化页面加载时的字体显示 */ | |||||
| } | |||||
| @@ -0,0 +1,31 @@ | |||||
| .statistics { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| padding: 20px 0; | |||||
| background-color: @background-color; | |||||
| border-radius: 8px; | |||||
| &__item { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| flex-direction: column; | |||||
| align-items: center; | |||||
| &--border { | |||||
| border-right: 1px solid @border-color; | |||||
| } | |||||
| &__value { | |||||
| margin-bottom: 8px; | |||||
| color: @text-color; | |||||
| font-weight: bold; | |||||
| font-size: 30px; | |||||
| } | |||||
| &__title { | |||||
| color: #999999; | |||||
| font-size: @font-size-input; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,38 @@ | |||||
| import classNames from 'classnames'; | |||||
| import styles from './index.less'; | |||||
| type StatisticsProps = { | |||||
| remaining?: number; | |||||
| consuming?: number; | |||||
| }; | |||||
| function Statistics({ remaining, consuming }: StatisticsProps) { | |||||
| const items = [ | |||||
| { | |||||
| title: '当前可用算力积分(分)', | |||||
| value: remaining ?? '-', | |||||
| }, | |||||
| { | |||||
| title: '总消耗算力积分(分)', | |||||
| value: consuming ?? '-', | |||||
| }, | |||||
| ]; | |||||
| return ( | |||||
| <div className={styles['statistics']}> | |||||
| {items.map((item, index) => ( | |||||
| <div | |||||
| key={item.title} | |||||
| className={classNames(styles['statistics__item'], { | |||||
| [styles['statistics__item--border']]: index === 0, | |||||
| })} | |||||
| > | |||||
| <span className={styles['statistics__item__value']}>{item.value}</span> | |||||
| <span className={styles['statistics__item__title']}>{item.title}</span> | |||||
| </div> | |||||
| ))} | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default Statistics; | |||||
| @@ -0,0 +1,19 @@ | |||||
| .points-detail { | |||||
| height: 100%; | |||||
| &__content { | |||||
| height: calc(100% - 60px); | |||||
| margin-top: 10px; | |||||
| padding: 20px 30px 0; | |||||
| background-color: white; | |||||
| border-radius: 10px; | |||||
| &__top { | |||||
| width: 100%; | |||||
| } | |||||
| &__table { | |||||
| height: calc(100% - 117px - 28px); | |||||
| margin-top: 28px; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,256 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-16 13:58:08 | |||||
| * @Description: 模型部署服务列表 | |||||
| */ | |||||
| import PageTitle from '@/components/PageTitle'; | |||||
| import { getPointsConsumptionReq, getPointsStatisticsReq } from '@/services/points'; | |||||
| import themes from '@/styles/theme.less'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import statusTableCell from '@/utils/statusTableCell'; | |||||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||||
| import { Link } from '@umijs/max'; | |||||
| import { Table, type TablePaginationConfig, type TableProps } from 'antd'; | |||||
| import classNames from 'classnames'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import Statistics from './components/Statistics'; | |||||
| import styles from './index.less'; | |||||
| enum TaskType { | |||||
| DevEnvironment = 'dev_environment', | |||||
| Workflow = 'workflow', | |||||
| Ray = 'ray', | |||||
| Service = 'service', | |||||
| } | |||||
| const taskTypeOptions = [ | |||||
| { | |||||
| value: 'dev_environment', | |||||
| label: '开发环境', | |||||
| }, | |||||
| { | |||||
| value: 'workflow', | |||||
| label: '实验', | |||||
| }, | |||||
| { | |||||
| value: 'ray', | |||||
| label: '超参数自动寻优', | |||||
| }, | |||||
| { | |||||
| value: 'service', | |||||
| label: '服务', | |||||
| }, | |||||
| ]; | |||||
| export type PointsData = { | |||||
| computing_resource_id: number; | |||||
| credit_per_hour: number; // 每小时消耗的积分 | |||||
| deduce_credit: number; | |||||
| deduce_last_time: string; | |||||
| description: string; | |||||
| id: number; | |||||
| node_id: string; | |||||
| start_time: string; | |||||
| state: number; | |||||
| workflow_id?: number; // 流水线id | |||||
| task_id: number; // 实验id | |||||
| task_ins_id: number; // 实例id | |||||
| task_type: string; | |||||
| user_id: number; | |||||
| }; | |||||
| export type PointsStatistics = { | |||||
| deduceCredit: number; | |||||
| userCredit: number; | |||||
| }; | |||||
| // 格式化任务 | |||||
| const formatTask = (value: string, record: PointsData) => { | |||||
| let url; | |||||
| switch (record.task_type) { | |||||
| case TaskType.DevEnvironment: | |||||
| url = `/developmentEnvironment`; | |||||
| break; | |||||
| case TaskType.Workflow: | |||||
| if (record.workflow_id && record.task_ins_id) { | |||||
| url = `/pipeline/experiment/instance/${record.workflow_id}/${record.task_ins_id}`; | |||||
| } | |||||
| break; | |||||
| case TaskType.Ray: | |||||
| if (record.task_id && record.task_ins_id) { | |||||
| url = `/pipeline/hyperparameter/instance/${record.task_id}/${record.task_ins_id}`; | |||||
| } | |||||
| break; | |||||
| case TaskType.Service: | |||||
| if (record.task_id && record.task_ins_id) { | |||||
| url = `/dataset/modelDeployment/serviceInfo/${record.task_id}/versionInfo/${record.task_ins_id}`; | |||||
| } | |||||
| break; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| if (url) { | |||||
| return <Link to={url}>{value}</Link>; | |||||
| } else { | |||||
| return <span>{value}</span>; | |||||
| } | |||||
| }; | |||||
| function PointsDetail() { | |||||
| const [tableData, setTableData] = useState<PointsData[]>([]); | |||||
| const [total, setTotal] = useState(0); | |||||
| const [statistics, setStatistics] = useState<PointsStatistics>(); | |||||
| const [pagination, setPagination] = useState<TablePaginationConfig>({ | |||||
| current: 1, | |||||
| pageSize: 10, | |||||
| }); | |||||
| useEffect(() => { | |||||
| // 获取积分统计 | |||||
| const getPointsStatistics = async () => { | |||||
| const [res] = await to(getPointsStatisticsReq()); | |||||
| if (res && res.data) { | |||||
| setStatistics(res.data); | |||||
| } | |||||
| }; | |||||
| getPointsStatistics(); | |||||
| }, []); | |||||
| useEffect(() => { | |||||
| // 获取积分消费明细 | |||||
| const getPointsConsumption = async () => { | |||||
| const params: Record<string, any> = { | |||||
| page: pagination.current! - 1, | |||||
| size: pagination.pageSize, | |||||
| }; | |||||
| const [res] = await to(getPointsConsumptionReq(params)); | |||||
| if (res && res.data) { | |||||
| const { content = [], totalElements = 0 } = res.data; | |||||
| setTableData(content); | |||||
| setTotal(totalElements); | |||||
| } | |||||
| }; | |||||
| getPointsConsumption(); | |||||
| }, [pagination]); | |||||
| // 分页切换 | |||||
| const handleTableChange: TableProps<PointsData>['onChange'] = ( | |||||
| pagination, | |||||
| _filters, | |||||
| _sorter, | |||||
| { action }, | |||||
| ) => { | |||||
| if (action === 'paginate') { | |||||
| setPagination(pagination); | |||||
| } | |||||
| }; | |||||
| const columns: TableProps<PointsData>['columns'] = [ | |||||
| { | |||||
| title: '序号', | |||||
| dataIndex: 'index', | |||||
| key: 'index', | |||||
| width: 80, | |||||
| render: tableCellRender(false, TableCellValueType.Index, { | |||||
| page: pagination.current! - 1, | |||||
| pageSize: pagination.pageSize!, | |||||
| }), | |||||
| }, | |||||
| { | |||||
| title: '任务', | |||||
| dataIndex: 'task_name', | |||||
| key: 'task_name', | |||||
| width: '15%', | |||||
| render: formatTask, | |||||
| }, | |||||
| { | |||||
| title: '任务类型', | |||||
| dataIndex: 'task_type', | |||||
| key: 'task_type', | |||||
| width: '10%', | |||||
| render: statusTableCell(taskTypeOptions), | |||||
| }, | |||||
| { | |||||
| title: '积分/小时', | |||||
| dataIndex: 'credit_per_hour', | |||||
| key: 'credit_per_hour', | |||||
| width: '10%', | |||||
| render: tableCellRender(), | |||||
| }, | |||||
| { | |||||
| title: '消耗的积分(分)', | |||||
| dataIndex: 'deduce_credit', | |||||
| key: 'deduce_credit', | |||||
| width: '12%', | |||||
| render: tableCellRender(), | |||||
| }, | |||||
| { | |||||
| title: '描述', | |||||
| dataIndex: 'description', | |||||
| key: 'description', | |||||
| render: tableCellRender(true), | |||||
| }, | |||||
| { | |||||
| title: '开始时间', | |||||
| dataIndex: 'start_time', | |||||
| key: 'start_time', | |||||
| width: '15%', | |||||
| render: tableCellRender(false, TableCellValueType.Date), | |||||
| }, | |||||
| { | |||||
| title: '状态', | |||||
| dataIndex: 'state', | |||||
| key: 'state', | |||||
| width: '8%', | |||||
| render: statusTableCell([ | |||||
| { | |||||
| value: 0, | |||||
| label: '已结束', | |||||
| color: themes['textColor'], | |||||
| }, | |||||
| { | |||||
| value: 1, | |||||
| label: '进行中', | |||||
| color: themes['primaryColor'], | |||||
| }, | |||||
| ]), | |||||
| }, | |||||
| ]; | |||||
| return ( | |||||
| <div className={styles['points-detail']}> | |||||
| <PageTitle title="算力积分明细"></PageTitle> | |||||
| <div className={styles['points-detail__content']}> | |||||
| <div className={styles['points-detail__content__top']}> | |||||
| <Statistics | |||||
| remaining={statistics?.userCredit} | |||||
| consuming={statistics?.deduceCredit} | |||||
| ></Statistics> | |||||
| </div> | |||||
| <div | |||||
| className={classNames('vertical-scroll-table', styles['points-detail__content__table'])} | |||||
| > | |||||
| <Table | |||||
| dataSource={tableData} | |||||
| columns={columns} | |||||
| scroll={{ y: 'calc(100% - 55px)' }} | |||||
| pagination={{ | |||||
| ...pagination, | |||||
| total: total, | |||||
| showSizeChanger: true, | |||||
| showQuickJumper: true, | |||||
| showTotal: () => `共${total}条`, | |||||
| }} | |||||
| onChange={handleTableChange} | |||||
| rowKey="id" | |||||
| /> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default PointsDetail; | |||||
| @@ -2,6 +2,7 @@ import { DictValueEnumObj } from '@/components/DictTag'; | |||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { | import { | ||||
| ProForm, | ProForm, | ||||
| ProFormDigit, | |||||
| ProFormRadio, | ProFormRadio, | ||||
| ProFormSelect, | ProFormSelect, | ||||
| ProFormText, | ProFormText, | ||||
| @@ -64,6 +65,7 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||||
| remark: props.values.remark, | remark: props.values.remark, | ||||
| gitLinkUsername: props.values.gitLinkUsername, | gitLinkUsername: props.values.gitLinkUsername, | ||||
| gitLinkPassword: props.values.gitLinkPassword, | gitLinkPassword: props.values.gitLinkPassword, | ||||
| credit: props.values.credit, | |||||
| }); | }); | ||||
| }, [form, props, statusOptions]); | }, [form, props, statusOptions]); | ||||
| @@ -219,12 +221,7 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||||
| autoComplete: 'new-password', | autoComplete: 'new-password', | ||||
| }} | }} | ||||
| allowClear | allowClear | ||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: <FormattedMessage id="请输入密码!" defaultMessage="请输入密码!" />, | |||||
| }, | |||||
| ]} | |||||
| rules={props.values.userId ? [] : [{ required: true, message: '请输入密码!' }]} | |||||
| /> | /> | ||||
| <ProFormSelect | <ProFormSelect | ||||
| valueEnum={sexOptions} | valueEnum={sexOptions} | ||||
| @@ -304,6 +301,18 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||||
| }} | }} | ||||
| rules={props.values.userId ? [] : [{ required: true, message: '请输入 Git 密码!' }]} | rules={props.values.userId ? [] : [{ required: true, message: '请输入 Git 密码!' }]} | ||||
| /> | /> | ||||
| <ProFormDigit | |||||
| name="credit" | |||||
| label="算力积分" | |||||
| placeholder="请输入算力积分" | |||||
| colProps={{ xs: 24, md: 12, xl: 12 }} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入算力积分', | |||||
| }, | |||||
| ]} | |||||
| /> | |||||
| <ProFormTextArea | <ProFormTextArea | ||||
| name="remark" | name="remark" | ||||
| label={intl.formatMessage({ | label={intl.formatMessage({ | ||||
| @@ -259,6 +259,11 @@ const UserTableList: React.FC = () => { | |||||
| dataIndex: 'phonenumber', | dataIndex: 'phonenumber', | ||||
| valueType: 'text', | valueType: 'text', | ||||
| }, | }, | ||||
| { | |||||
| title: <FormattedMessage id="system.user.credit" defaultMessage="算力积分" />, | |||||
| dataIndex: 'credit', | |||||
| valueType: 'text', | |||||
| }, | |||||
| { | { | ||||
| title: <FormattedMessage id="system.user.status" defaultMessage="帐号状态" />, | title: <FormattedMessage id="system.user.status" defaultMessage="帐号状态" />, | ||||
| dataIndex: 'status', | dataIndex: 'status', | ||||
| @@ -1,4 +1,5 @@ | |||||
| .experiment-chart { | .experiment-chart { | ||||
| flex: none; | |||||
| width: 295px; | width: 295px; | ||||
| min-width: 295px; | min-width: 295px; | ||||
| height: 140px; | height: 140px; | ||||
| @@ -1,8 +1,8 @@ | |||||
| .experiment-table { | .experiment-table { | ||||
| flex: 1; | flex: 1; | ||||
| min-width: 500px; | |||||
| min-width: 378px; | |||||
| height: 140px; | height: 140px; | ||||
| padding: 12px 24px; | |||||
| padding: 12px; | |||||
| background: @workspace-background; | background: @workspace-background; | ||||
| border-radius: 4px; | border-radius: 4px; | ||||
| @@ -32,15 +32,15 @@ | |||||
| } | } | ||||
| &__status { | &__status { | ||||
| width: 20%; | |||||
| width: 15%; | |||||
| } | } | ||||
| &__duration { | &__duration { | ||||
| width: 30%; | |||||
| width: 25%; | |||||
| } | } | ||||
| &__date { | &__date { | ||||
| width: calc(50% - 60px); | |||||
| width: calc(60% - 60px); | |||||
| } | } | ||||
| &__operation { | &__operation { | ||||
| @@ -1,16 +1,12 @@ | |||||
| .total-statistics { | .total-statistics { | ||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| justify-content: center; | |||||
| width: 400px; | |||||
| height: 140px; | height: 140px; | ||||
| background: @workspace-background; | |||||
| border-radius: 4px; | |||||
| padding: 0 16px; | |||||
| &__icon { | &__icon { | ||||
| width: 85px; | |||||
| height: 80px; | |||||
| margin-right: 40px; | |||||
| width: 63px; | |||||
| margin-right: 15px; | |||||
| } | } | ||||
| &__title { | &__title { | ||||
| @@ -20,18 +16,10 @@ | |||||
| font-size: @font-size-content; | font-size: @font-size-content; | ||||
| } | } | ||||
| &__title-shadow { | |||||
| position: absolute; | |||||
| bottom: 6px; | |||||
| left: 0; | |||||
| width: 79px; | |||||
| height: 6px; | |||||
| background: linear-gradient(87.07deg, rgba(22, 100, 255, 0.6) 0%, rgba(22, 100, 255, 0) 100%); | |||||
| } | |||||
| &__count { | &__count { | ||||
| color: @text-color; | color: @text-color; | ||||
| font-weight: 700; | font-weight: 700; | ||||
| font-size: 25px; | font-size: 25px; | ||||
| font-family: DingTalk-JinBuTi; | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,3 +1,4 @@ | |||||
| import { Flex } from 'antd'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type TotalStatisticsProps = { | type TotalStatisticsProps = { | ||||
| @@ -11,13 +12,12 @@ function TotalStatistics({ icon = '', title = '', count = 0, style }: TotalStati | |||||
| return ( | return ( | ||||
| <div className={styles['total-statistics']} style={style}> | <div className={styles['total-statistics']} style={style}> | ||||
| <img className={styles['total-statistics__icon']} src={icon} draggable={false} alt="" /> | <img className={styles['total-statistics__icon']} src={icon} draggable={false} alt="" /> | ||||
| <div> | |||||
| <Flex vertical align="center"> | |||||
| <div className={styles['total-statistics__title']}> | <div className={styles['total-statistics__title']}> | ||||
| <span>{title}</span> | <span>{title}</span> | ||||
| <div className={styles['total-statistics__title-shadow']}></div> | |||||
| </div> | </div> | ||||
| <div className={styles['total-statistics__count']}>{count ?? '--'}</div> | <div className={styles['total-statistics__count']}>{count ?? '--'}</div> | ||||
| </div> | |||||
| </Flex> | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -0,0 +1,36 @@ | |||||
| .user-points { | |||||
| display: flex; | |||||
| flex: none; | |||||
| flex-direction: column; | |||||
| align-items: center; | |||||
| width: 326px; | |||||
| height: 228px; | |||||
| .backgroundFullImage(url(@/assets/img/user-points-bg.png)); | |||||
| &__label { | |||||
| margin-top: 60px; | |||||
| color: @text-color; | |||||
| font-size: @font-size-title; | |||||
| font-family: DingTalk-JinBuTi; | |||||
| } | |||||
| &__value { | |||||
| margin-top: 8px; | |||||
| margin-bottom: 12px; | |||||
| color: @primary-color; | |||||
| font-size: 36px; | |||||
| font-family: DingTalk-JinBuTi; | |||||
| line-height: 43px; | |||||
| } | |||||
| &__button { | |||||
| padding: 8px 20px; | |||||
| color: @primary-color; | |||||
| font-size: @font-size-content; | |||||
| background: rgba(255, 255, 255, 0.3); | |||||
| border: 1px solid #ffffff; | |||||
| border-radius: 8px; | |||||
| cursor: pointer; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,40 @@ | |||||
| import { PointsStatistics } from '@/pages/Points/index'; | |||||
| import { getPointsStatisticsReq } from '@/services/points'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { useNavigate } from '@umijs/max'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import styles from './index.less'; | |||||
| function UserPoints() { | |||||
| const [statistics, setStatistics] = useState<PointsStatistics>(); | |||||
| const navigate = useNavigate(); | |||||
| useEffect(() => { | |||||
| // 获取积分统计 | |||||
| const getPointsStatistics = async () => { | |||||
| const [res] = await to(getPointsStatisticsReq()); | |||||
| if (res && res.data) { | |||||
| setStatistics(res.data); | |||||
| } | |||||
| }; | |||||
| getPointsStatistics(); | |||||
| }, []); | |||||
| return ( | |||||
| <div className={styles['user-points']}> | |||||
| <span className={styles['user-points__label']}>当前可用算力积分</span> | |||||
| <span className={styles['user-points__value']}>{statistics?.userCredit ?? '--'}</span> | |||||
| <div | |||||
| className={styles['user-points__button']} | |||||
| onClick={() => { | |||||
| navigate('/points'); | |||||
| }} | |||||
| > | |||||
| 查看详情 | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default UserPoints; | |||||
| @@ -6,6 +6,7 @@ | |||||
| background: linear-gradient(#ecf2fe, #f9fafb); | background: linear-gradient(#ecf2fe, #f9fafb); | ||||
| &__overview { | &__overview { | ||||
| flex: 1; | |||||
| gap: 15px; | gap: 15px; | ||||
| margin-bottom: 16px; | margin-bottom: 16px; | ||||
| padding: 20px 30px; | padding: 20px 30px; | ||||
| @@ -21,11 +22,19 @@ | |||||
| &__content { | &__content { | ||||
| display: flex; | display: flex; | ||||
| flex-wrap: wrap; | |||||
| gap: 15px; | gap: 15px; | ||||
| align-items: center; | align-items: center; | ||||
| @media screen and (max-width: 1800px) { | |||||
| flex-wrap: wrap; | |||||
| &__statistics { | |||||
| flex: none; | |||||
| background: linear-gradient( | |||||
| 123.08deg, | |||||
| rgba(138, 138, 138, 0.06) 1.32%, | |||||
| rgba(22, 100, 255, 0.02) 58.35% | |||||
| ); | |||||
| border-radius: 4px; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -2,6 +2,7 @@ import { useDraggable } from '@/hooks/draggable'; | |||||
| import { getWorkspaceOverviewReq } from '@/services/workspace'; | import { getWorkspaceOverviewReq } from '@/services/workspace'; | ||||
| import { ExperimentInstance } from '@/types'; | import { ExperimentInstance } from '@/types'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { Divider, Flex } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import Draggable from 'react-draggable'; | import Draggable from 'react-draggable'; | ||||
| import AssetsManagement from './components/AssetsManagement'; | import AssetsManagement from './components/AssetsManagement'; | ||||
| @@ -10,6 +11,7 @@ import ExperitableTable from './components/ExperimentTable'; | |||||
| import QuickStart from './components/QuickStart'; | import QuickStart from './components/QuickStart'; | ||||
| import RobotFrame from './components/RobotFrame'; | import RobotFrame from './components/RobotFrame'; | ||||
| import TotalStatistics from './components/TotalStatistics'; | import TotalStatistics from './components/TotalStatistics'; | ||||
| import UserPoints from './components/UserPoints'; | |||||
| import UserSpace from './components/UserSpace'; | import UserSpace from './components/UserSpace'; | ||||
| import WorkspaceIntro from './components/WorkspaceIntro'; | import WorkspaceIntro from './components/WorkspaceIntro'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -28,6 +30,7 @@ function Workspace() { | |||||
| setRobotFrameVisible((prev) => !prev), | setRobotFrameVisible((prev) => !prev), | ||||
| ); | ); | ||||
| const users: number[] = new Array(8).fill(0); | const users: number[] = new Array(8).fill(0); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| getWorkspaceOverview(); | getWorkspaceOverview(); | ||||
| }, []); | }, []); | ||||
| @@ -43,27 +46,38 @@ function Workspace() { | |||||
| return ( | return ( | ||||
| <div className={styles.workspace}> | <div className={styles.workspace}> | ||||
| <WorkspaceIntro></WorkspaceIntro> | <WorkspaceIntro></WorkspaceIntro> | ||||
| <div className={styles['workspace__overview']}> | |||||
| <div className={styles['workspace__overview__title']}>运行概览</div> | |||||
| <div className={styles['workspace__overview__content']}> | |||||
| <TotalStatistics | |||||
| icon={require('@/assets/img/workspace-pipeline.png')} | |||||
| title="流水线总数" | |||||
| count={overviewData?.workflowCount} | |||||
| /> | |||||
| <TotalStatistics | |||||
| icon={require('@/assets/img/workspace-experiment.png')} | |||||
| title="正在运行实例总数" | |||||
| count={overviewData?.runningExperimentInsCount} | |||||
| /> | |||||
| <ExperitableTable | |||||
| tableData={overviewData?.latestExperimentInsList || []} | |||||
| ></ExperitableTable> | |||||
| {overviewData?.experimentInsStatus && ( | |||||
| <ExperimentChart chartData={overviewData?.experimentInsStatus}></ExperimentChart> | |||||
| )} | |||||
| <Flex gap={'0 15px'} wrap> | |||||
| <div className={styles['workspace__overview']}> | |||||
| <div className={styles['workspace__overview__title']}>运行概览</div> | |||||
| <div className={styles['workspace__overview__content']}> | |||||
| <Flex align="center" className={styles['workspace__overview__content__statistics']}> | |||||
| <TotalStatistics | |||||
| icon={require('@/assets/img/workspace-pipeline.png')} | |||||
| title="流水线总数" | |||||
| count={overviewData?.workflowCount} | |||||
| /> | |||||
| <Divider | |||||
| type="vertical" | |||||
| dashed | |||||
| style={{ color: '1px dashed rgba(96, 107, 122, 0.19)', height: 68 }} | |||||
| /> | |||||
| <TotalStatistics | |||||
| icon={require('@/assets/img/workspace-experiment.png')} | |||||
| title="正在运行实例总数" | |||||
| count={overviewData?.runningExperimentInsCount} | |||||
| /> | |||||
| </Flex> | |||||
| <ExperitableTable | |||||
| tableData={overviewData?.latestExperimentInsList || []} | |||||
| ></ExperitableTable> | |||||
| {overviewData?.experimentInsStatus && ( | |||||
| <ExperimentChart chartData={overviewData?.experimentInsStatus}></ExperimentChart> | |||||
| )} | |||||
| </div> | |||||
| </div> | </div> | ||||
| </div> | |||||
| <UserPoints /> | |||||
| </Flex> | |||||
| <div className={styles['workspace__quick-start']}> | <div className={styles['workspace__quick-start']}> | ||||
| <QuickStart></QuickStart> | <QuickStart></QuickStart> | ||||
| <div className={styles['workspace__user']}> | <div className={styles['workspace__user']}> | ||||
| @@ -0,0 +1,22 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2025-03-20 13:48:53 | |||||
| * @Description: 积分 | |||||
| */ | |||||
| import { request } from '@umijs/max'; | |||||
| // 分页查询积分消费明细 | |||||
| export function getPointsConsumptionReq(params: any) { | |||||
| return request(`/api/mmp/computingResource/resouceOccupy`, { | |||||
| method: 'GET', | |||||
| params, | |||||
| }); | |||||
| } | |||||
| // 分页查询积分消费明细 | |||||
| export function getPointsStatisticsReq() { | |||||
| return request(`/api/mmp/computingResource/credit`, { | |||||
| method: 'GET', | |||||
| }); | |||||
| } | |||||
| @@ -69,6 +69,14 @@ | |||||
| -webkit-line-clamp: @line; | -webkit-line-clamp: @line; | ||||
| } | } | ||||
| // 背景 | |||||
| .backgroundFullImage(@url) { | |||||
| background-image: @url; | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100% 100%; | |||||
| } | |||||
| // 导出变量 | // 导出变量 | ||||
| :export { | :export { | ||||
| primaryColor: @primary-color; | primaryColor: @primary-color; | ||||
| @@ -21,6 +21,7 @@ declare namespace API.System { | |||||
| remark: string; | remark: string; | ||||
| gitLinkUsername?: string; | gitLinkUsername?: string; | ||||
| gitLinkPassword?: string; | gitLinkPassword?: string; | ||||
| credit?: number; | |||||
| } | } | ||||
| export interface UserListParams { | export interface UserListParams { | ||||
| @@ -0,0 +1,23 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2025-03-20 14:33:01 | |||||
| * @Description: 通用的 Table 状态单元格 | |||||
| */ | |||||
| export type StatusInfo = { | |||||
| value: number | string; | |||||
| label: string; | |||||
| color?: string; | |||||
| }; | |||||
| function statusTableCell(infos: StatusInfo[]) { | |||||
| return function (status?: string | number | null) { | |||||
| const info = infos.find((item) => item.value === status); | |||||
| if (status === null || status === undefined || !info) { | |||||
| return <span>--</span>; | |||||
| } | |||||
| return <span style={{ color: info.color }}>{info.label}</span>; | |||||
| }; | |||||
| } | |||||
| export default statusTableCell; | |||||
| @@ -1,7 +1,7 @@ | |||||
| /* | /* | ||||
| * @Author: 赵伟 | * @Author: 赵伟 | ||||
| * @Date: 2024-06-26 10:05:52 | * @Date: 2024-06-26 10:05:52 | ||||
| * @Description: Table cell 自定义 render | |||||
| * @Description: Table Cell 自定义 render | |||||
| */ | */ | ||||
| import { isEmpty } from '@/utils'; | import { isEmpty } from '@/utils'; | ||||