| @@ -156,4 +156,5 @@ export default defineConfig({ | |||
| }, | |||
| javascriptEnabled: true, | |||
| }, | |||
| valtio: {}, | |||
| }); | |||
| @@ -1,176 +0,0 @@ | |||
| import { Request, Response } from 'express'; | |||
| import moment from 'moment'; | |||
| import { parse } from 'url'; | |||
| // mock tableListDataSource | |||
| const genList = (current: number, pageSize: number) => { | |||
| const tableListDataSource: API.RuleListItem[] = []; | |||
| for (let i = 0; i < pageSize; i += 1) { | |||
| const index = (current - 1) * 10 + i; | |||
| tableListDataSource.push({ | |||
| key: index, | |||
| disabled: i % 6 === 0, | |||
| href: 'https://ant.design', | |||
| avatar: [ | |||
| 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', | |||
| 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', | |||
| ][i % 2], | |||
| name: `TradeCode ${index}`, | |||
| owner: '曲丽丽', | |||
| desc: '这是一段描述', | |||
| callNo: Math.floor(Math.random() * 1000), | |||
| status: Math.floor(Math.random() * 10) % 4, | |||
| updatedAt: moment().format('YYYY-MM-DD'), | |||
| createdAt: moment().format('YYYY-MM-DD'), | |||
| progress: Math.ceil(Math.random() * 100), | |||
| }); | |||
| } | |||
| tableListDataSource.reverse(); | |||
| return tableListDataSource; | |||
| }; | |||
| let tableListDataSource = genList(1, 100); | |||
| function getRule(req: Request, res: Response, u: string) { | |||
| let realUrl = u; | |||
| if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') { | |||
| realUrl = req.url; | |||
| } | |||
| const { current = 1, pageSize = 10 } = req.query; | |||
| const params = parse(realUrl, true).query as unknown as API.PageParams & | |||
| API.RuleListItem & { | |||
| sorter: any; | |||
| filter: any; | |||
| }; | |||
| let dataSource = [...tableListDataSource].slice( | |||
| ((current as number) - 1) * (pageSize as number), | |||
| (current as number) * (pageSize as number), | |||
| ); | |||
| if (params.sorter) { | |||
| const sorter = JSON.parse(params.sorter); | |||
| dataSource = dataSource.sort((prev, next) => { | |||
| let sortNumber = 0; | |||
| (Object.keys(sorter) as Array<keyof API.RuleListItem>).forEach((key) => { | |||
| let nextSort = next?.[key] as number; | |||
| let preSort = prev?.[key] as number; | |||
| if (sorter[key] === 'descend') { | |||
| if (preSort - nextSort > 0) { | |||
| sortNumber += -1; | |||
| } else { | |||
| sortNumber += 1; | |||
| } | |||
| return; | |||
| } | |||
| if (preSort - nextSort > 0) { | |||
| sortNumber += 1; | |||
| } else { | |||
| sortNumber += -1; | |||
| } | |||
| }); | |||
| return sortNumber; | |||
| }); | |||
| } | |||
| if (params.filter) { | |||
| const filter = JSON.parse(params.filter as any) as { | |||
| [key: string]: string[]; | |||
| }; | |||
| if (Object.keys(filter).length > 0) { | |||
| dataSource = dataSource.filter((item) => { | |||
| return (Object.keys(filter) as Array<keyof API.RuleListItem>).some((key) => { | |||
| if (!filter[key]) { | |||
| return true; | |||
| } | |||
| if (filter[key].includes(`${item[key]}`)) { | |||
| return true; | |||
| } | |||
| return false; | |||
| }); | |||
| }); | |||
| } | |||
| } | |||
| if (params.name) { | |||
| dataSource = dataSource.filter((data) => data?.name?.includes(params.name || '')); | |||
| } | |||
| const result = { | |||
| data: dataSource, | |||
| total: tableListDataSource.length, | |||
| success: true, | |||
| pageSize, | |||
| current: parseInt(`${params.current}`, 10) || 1, | |||
| }; | |||
| return res.json(result); | |||
| } | |||
| function postRule(req: Request, res: Response, u: string, b: Request) { | |||
| let realUrl = u; | |||
| if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') { | |||
| realUrl = req.url; | |||
| } | |||
| const body = (b && b.body) || req.body; | |||
| const { method, name, desc, key } = body; | |||
| switch (method) { | |||
| /* eslint no-case-declarations:0 */ | |||
| case 'delete': | |||
| tableListDataSource = tableListDataSource.filter((item) => key.indexOf(item.key) === -1); | |||
| break; | |||
| case 'post': | |||
| (() => { | |||
| const i = Math.ceil(Math.random() * 10000); | |||
| const newRule: API.RuleListItem = { | |||
| key: tableListDataSource.length, | |||
| href: 'https://ant.design', | |||
| avatar: [ | |||
| 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', | |||
| 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', | |||
| ][i % 2], | |||
| name, | |||
| owner: '曲丽丽', | |||
| desc, | |||
| callNo: Math.floor(Math.random() * 1000), | |||
| status: Math.floor(Math.random() * 10) % 2, | |||
| updatedAt: moment().format('YYYY-MM-DD'), | |||
| createdAt: moment().format('YYYY-MM-DD'), | |||
| progress: Math.ceil(Math.random() * 100), | |||
| }; | |||
| tableListDataSource.unshift(newRule); | |||
| return res.json(newRule); | |||
| })(); | |||
| return; | |||
| case 'update': | |||
| (() => { | |||
| let newRule = {}; | |||
| tableListDataSource = tableListDataSource.map((item) => { | |||
| if (item.key === key) { | |||
| newRule = { ...item, desc, name }; | |||
| return { ...item, desc, name }; | |||
| } | |||
| return item; | |||
| }); | |||
| return res.json(newRule); | |||
| })(); | |||
| return; | |||
| default: | |||
| break; | |||
| } | |||
| const result = { | |||
| list: tableListDataSource, | |||
| pagination: { | |||
| total: tableListDataSource.length, | |||
| }, | |||
| }; | |||
| res.json(result); | |||
| } | |||
| export default { | |||
| 'GET /api/rule': getRule, | |||
| 'POST /api/rule': postRule, | |||
| }; | |||
| @@ -67,7 +67,6 @@ | |||
| "fabric": "^5.3.0", | |||
| "highlight.js": "^11.7.0", | |||
| "lodash": "^4.17.21", | |||
| "moment": "^2.30.1", | |||
| "omit.js": "^2.0.2", | |||
| "pnpm": "^8.9.0", | |||
| "query-string": "^8.1.0", | |||
| @@ -5,10 +5,7 @@ | |||
| padding: 20px @content-padding; | |||
| background-color: white; | |||
| border: 1px solid @border-color-base; | |||
| border-top: none; | |||
| border-radius: 0 0 4px 4px; | |||
| &&--scroll { | |||
| padding: 0; | |||
| } | |||
| } | |||
| } | |||
| @@ -4,21 +4,27 @@ import './index.less'; | |||
| type InfoGroupProps = { | |||
| title: string; | |||
| contentScroll?: boolean; // 内容是否需要滚动,如果可以滚动,则取消 padding | |||
| height?: string | number; // 如果要纵向滚动,需要设置高度 | |||
| width?: string | number; // 如果要横向滚动,需要设置宽度 | |||
| className?: string; | |||
| style?: React.CSSProperties; | |||
| children?: React.ReactNode; | |||
| }; | |||
| function InfoGroup({ title, contentScroll = false, className, style, children }: InfoGroupProps) { | |||
| function InfoGroup({ title, height, width, className, style, children }: InfoGroupProps) { | |||
| const contentStyle: React.CSSProperties = {}; | |||
| if (height) { | |||
| contentStyle.height = height; | |||
| contentStyle.overflowY = 'auto'; | |||
| } | |||
| if (width) { | |||
| contentStyle.width = width; | |||
| contentStyle.overflowX = 'auto'; | |||
| } | |||
| return ( | |||
| <div className={classNames('kf-info-group', className)} style={style}> | |||
| <InfoGroupTitle title={title} /> | |||
| <div | |||
| className={classNames('kf-info-group__content', { | |||
| 'kf-info-group__content--scroll': contentScroll, | |||
| })} | |||
| > | |||
| <div style={contentStyle} className={'kf-info-group__content'}> | |||
| {children} | |||
| </div> | |||
| </div> | |||
| @@ -1,15 +1,28 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-10-10 08:51:41 | |||
| * @Description: 资源规格 hook | |||
| */ | |||
| import { getComputingResourceReq } from '@/services/pipeline'; | |||
| import computingResourceState, { setComputingResource } from '@/state/computingResourceStore'; | |||
| import { ComputingResource } from '@/types'; | |||
| import { to } from '@/utils/promise'; | |||
| import { type SelectProps } from 'antd'; | |||
| import { useCallback, useEffect, useState } from 'react'; | |||
| import { useSnapshot } from 'umi'; | |||
| // 获取资源规格 | |||
| export function useComputingResource() { | |||
| const [resourceStandardList, setResourceStandardList] = useState<ComputingResource[]>([]); | |||
| const computingResourceSnap = useSnapshot(computingResourceState); | |||
| useEffect(() => { | |||
| getComputingResource(); | |||
| if (computingResourceSnap.computingResource.length > 0) { | |||
| setResourceStandardList(computingResourceSnap.computingResource as ComputingResource[]); | |||
| } else { | |||
| getComputingResource(); | |||
| } | |||
| }, []); | |||
| // 获取资源规格列表数据 | |||
| @@ -22,6 +35,7 @@ export function useComputingResource() { | |||
| const [res] = await to(getComputingResourceReq(params)); | |||
| if (res && res.data && res.data.content) { | |||
| setResourceStandardList(res.data.content); | |||
| setComputingResource(res.data.content); | |||
| } | |||
| }, []); | |||
| @@ -7,10 +7,9 @@ | |||
| border-radius: 10px; | |||
| &__download { | |||
| display: flex; | |||
| align-items: center; | |||
| height: 34px; | |||
| margin-bottom: 16px; | |||
| padding-top: 16px; | |||
| padding-bottom: 16px; | |||
| padding-left: @content-padding; | |||
| color: @text-color; | |||
| font-size: 13px; | |||
| @@ -18,15 +17,14 @@ | |||
| border-radius: 4px; | |||
| &__btn { | |||
| margin-left: 22px; | |||
| display: block; | |||
| height: 36px; | |||
| margin-top: 15px; | |||
| font-size: 14px; | |||
| } | |||
| } | |||
| &__text { | |||
| width: 100%; | |||
| height: 420px; | |||
| padding: 20px @content-padding; | |||
| overflow: auto; | |||
| white-space: pre-wrap; | |||
| } | |||
| @@ -1,5 +1,4 @@ | |||
| import InfoGroup from '@/components/InfoGroup'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { getFileReq } from '@/services/file'; | |||
| import { to } from '@/utils/promise'; | |||
| import { Button, Image } from 'antd'; | |||
| @@ -38,28 +37,10 @@ function ExperimentResult({ fileUrl, imageUrl, modelPath }: ExperimentResultProp | |||
| return ( | |||
| <div className={styles['experiment-result']}> | |||
| {modelPath && ( | |||
| <div className={styles['experiment-result__download']}> | |||
| <span style={{ marginRight: '12px', color: '#606b7a' }}>文件名</span> | |||
| <span>save_model.joblib</span> | |||
| <Button | |||
| type="link" | |||
| className={styles['experiment-result__download__btn']} | |||
| icon={<KFIcon type="icon-a-xiazai1" />} | |||
| size="small" | |||
| iconPosition="end" | |||
| onClick={() => { | |||
| window.location.href = modelPath; | |||
| }} | |||
| > | |||
| 模型下载 | |||
| </Button> | |||
| </div> | |||
| )} | |||
| <InfoGroup title="实验结果" contentScroll> | |||
| <InfoGroup title="实验结果" height={420} width="100%"> | |||
| <div className={styles['experiment-result__text']}>{result}</div> | |||
| </InfoGroup> | |||
| <InfoGroup title="可视化结果" style={{ marginTop: '16px' }}> | |||
| <InfoGroup title="可视化结果" style={{ margin: '16px 0' }}> | |||
| <div className={styles['experiment-result__images']}> | |||
| <Image.PreviewGroup | |||
| preview={{ | |||
| @@ -80,6 +61,21 @@ function ExperimentResult({ fileUrl, imageUrl, modelPath }: ExperimentResultProp | |||
| </Image.PreviewGroup> | |||
| </div> | |||
| </InfoGroup> | |||
| {modelPath && ( | |||
| <div className={styles['experiment-result__download']}> | |||
| <span style={{ marginRight: '12px', color: '#606b7a' }}>文件名</span> | |||
| <span>save_model.joblib</span> | |||
| <Button | |||
| type="primary" | |||
| className={styles['experiment-result__download__btn']} | |||
| onClick={() => { | |||
| window.location.href = modelPath; | |||
| }} | |||
| > | |||
| 模型下载 | |||
| </Button> | |||
| </div> | |||
| )} | |||
| </div> | |||
| ); | |||
| } | |||
| @@ -3,6 +3,12 @@ | |||
| .ant-drawer-body { | |||
| overflow-y: hidden; | |||
| } | |||
| .ant-drawer-close { | |||
| position: absolute; | |||
| top: 16px; | |||
| right: 16px; | |||
| } | |||
| } | |||
| &__tabs { | |||
| @@ -2,7 +2,7 @@ import { ExperimentStatus } from '@/enums'; | |||
| import { experimentStatusInfo } from '@/pages/Experiment/status'; | |||
| import { PipelineNodeModelSerialize } from '@/types'; | |||
| import { elapsedTime, formatDate } from '@/utils/date'; | |||
| import { DatabaseOutlined, ProfileOutlined } from '@ant-design/icons'; | |||
| import { CloseOutlined, DatabaseOutlined, ProfileOutlined } from '@ant-design/icons'; | |||
| import { Drawer, Tabs } from 'antd'; | |||
| import { useMemo } from 'react'; | |||
| import ExperimentParameter from '../ExperimentParameter'; | |||
| @@ -95,10 +95,11 @@ const ExperimentDrawer = ({ | |||
| return ( | |||
| <Drawer | |||
| rootStyle={{ marginTop: '55px' }} | |||
| title="任务执行详情" | |||
| placement="right" | |||
| getContainer={false} | |||
| closeIcon={false} | |||
| closeIcon={<CloseOutlined className={styles['experiment-drawer__close']} />} | |||
| onClose={onClose} | |||
| open={open} | |||
| width={520} | |||
| @@ -49,7 +49,7 @@ const GlobalParamsDrawer = forwardRef( | |||
| return ( | |||
| <Drawer | |||
| rootStyle={{ marginTop: '45px' }} | |||
| rootStyle={{ marginTop: '55px' }} | |||
| title="全局参数" | |||
| placement="right" | |||
| closeIcon={false} | |||
| @@ -335,7 +335,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||
| <Drawer | |||
| title="编辑任务" | |||
| placement="right" | |||
| rootStyle={{ marginTop: '52px' }} | |||
| rootStyle={{ marginTop: '55px' }} | |||
| getContainer={false} | |||
| closeIcon={false} | |||
| onClose={onClose} | |||
| @@ -235,7 +235,7 @@ const UserTableList: React.FC = () => { | |||
| const columns: ProColumns<API.System.User>[] = [ | |||
| { | |||
| title: <FormattedMessage id="system.user.user_id" defaultMessage="用户编号" />, | |||
| dataIndex: 'deptId', | |||
| dataIndex: 'userId', | |||
| valueType: 'text', | |||
| }, | |||
| { | |||
| @@ -0,0 +1,16 @@ | |||
| import { ComputingResource } from '@/types'; | |||
| import { proxy } from 'umi'; | |||
| type ComputingResourceStore = { | |||
| computingResource: ComputingResource[]; | |||
| }; | |||
| const state = proxy<ComputingResourceStore>({ | |||
| computingResource: [], | |||
| }); | |||
| export const setComputingResource = (computingResource: ComputingResource[]) => { | |||
| state.computingResource = computingResource; | |||
| }; | |||
| export default state; | |||