| @@ -156,4 +156,5 @@ export default defineConfig({ | |||||
| }, | }, | ||||
| javascriptEnabled: true, | 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", | "fabric": "^5.3.0", | ||||
| "highlight.js": "^11.7.0", | "highlight.js": "^11.7.0", | ||||
| "lodash": "^4.17.21", | "lodash": "^4.17.21", | ||||
| "moment": "^2.30.1", | |||||
| "omit.js": "^2.0.2", | "omit.js": "^2.0.2", | ||||
| "pnpm": "^8.9.0", | "pnpm": "^8.9.0", | ||||
| "query-string": "^8.1.0", | "query-string": "^8.1.0", | ||||
| @@ -5,10 +5,7 @@ | |||||
| padding: 20px @content-padding; | padding: 20px @content-padding; | ||||
| background-color: white; | background-color: white; | ||||
| border: 1px solid @border-color-base; | border: 1px solid @border-color-base; | ||||
| border-top: none; | |||||
| border-radius: 0 0 4px 4px; | border-radius: 0 0 4px 4px; | ||||
| &&--scroll { | |||||
| padding: 0; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -4,21 +4,27 @@ import './index.less'; | |||||
| type InfoGroupProps = { | type InfoGroupProps = { | ||||
| title: string; | title: string; | ||||
| contentScroll?: boolean; // 内容是否需要滚动,如果可以滚动,则取消 padding | |||||
| height?: string | number; // 如果要纵向滚动,需要设置高度 | |||||
| width?: string | number; // 如果要横向滚动,需要设置宽度 | |||||
| className?: string; | className?: string; | ||||
| style?: React.CSSProperties; | style?: React.CSSProperties; | ||||
| children?: React.ReactNode; | 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 ( | return ( | ||||
| <div className={classNames('kf-info-group', className)} style={style}> | <div className={classNames('kf-info-group', className)} style={style}> | ||||
| <InfoGroupTitle title={title} /> | <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} | {children} | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -1,15 +1,28 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-10-10 08:51:41 | |||||
| * @Description: 资源规格 hook | |||||
| */ | |||||
| import { getComputingResourceReq } from '@/services/pipeline'; | import { getComputingResourceReq } from '@/services/pipeline'; | ||||
| import computingResourceState, { setComputingResource } from '@/state/computingResourceStore'; | |||||
| import { ComputingResource } from '@/types'; | import { ComputingResource } from '@/types'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { type SelectProps } from 'antd'; | import { type SelectProps } from 'antd'; | ||||
| import { useCallback, useEffect, useState } from 'react'; | import { useCallback, useEffect, useState } from 'react'; | ||||
| import { useSnapshot } from 'umi'; | |||||
| // 获取资源规格 | // 获取资源规格 | ||||
| export function useComputingResource() { | export function useComputingResource() { | ||||
| const [resourceStandardList, setResourceStandardList] = useState<ComputingResource[]>([]); | const [resourceStandardList, setResourceStandardList] = useState<ComputingResource[]>([]); | ||||
| const computingResourceSnap = useSnapshot(computingResourceState); | |||||
| useEffect(() => { | 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)); | const [res] = await to(getComputingResourceReq(params)); | ||||
| if (res && res.data && res.data.content) { | if (res && res.data && res.data.content) { | ||||
| setResourceStandardList(res.data.content); | setResourceStandardList(res.data.content); | ||||
| setComputingResource(res.data.content); | |||||
| } | } | ||||
| }, []); | }, []); | ||||
| @@ -7,10 +7,9 @@ | |||||
| border-radius: 10px; | border-radius: 10px; | ||||
| &__download { | &__download { | ||||
| display: flex; | |||||
| align-items: center; | |||||
| height: 34px; | |||||
| margin-bottom: 16px; | |||||
| padding-top: 16px; | |||||
| padding-bottom: 16px; | |||||
| padding-left: @content-padding; | padding-left: @content-padding; | ||||
| color: @text-color; | color: @text-color; | ||||
| font-size: 13px; | font-size: 13px; | ||||
| @@ -18,15 +17,14 @@ | |||||
| border-radius: 4px; | border-radius: 4px; | ||||
| &__btn { | &__btn { | ||||
| margin-left: 22px; | |||||
| display: block; | |||||
| height: 36px; | |||||
| margin-top: 15px; | |||||
| font-size: 14px; | |||||
| } | } | ||||
| } | } | ||||
| &__text { | &__text { | ||||
| width: 100%; | |||||
| height: 420px; | |||||
| padding: 20px @content-padding; | |||||
| overflow: auto; | |||||
| white-space: pre-wrap; | white-space: pre-wrap; | ||||
| } | } | ||||
| @@ -1,5 +1,4 @@ | |||||
| import InfoGroup from '@/components/InfoGroup'; | import InfoGroup from '@/components/InfoGroup'; | ||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { getFileReq } from '@/services/file'; | import { getFileReq } from '@/services/file'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { Button, Image } from 'antd'; | import { Button, Image } from 'antd'; | ||||
| @@ -38,28 +37,10 @@ function ExperimentResult({ fileUrl, imageUrl, modelPath }: ExperimentResultProp | |||||
| return ( | return ( | ||||
| <div className={styles['experiment-result']}> | <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> | <div className={styles['experiment-result__text']}>{result}</div> | ||||
| </InfoGroup> | </InfoGroup> | ||||
| <InfoGroup title="可视化结果" style={{ marginTop: '16px' }}> | |||||
| <InfoGroup title="可视化结果" style={{ margin: '16px 0' }}> | |||||
| <div className={styles['experiment-result__images']}> | <div className={styles['experiment-result__images']}> | ||||
| <Image.PreviewGroup | <Image.PreviewGroup | ||||
| preview={{ | preview={{ | ||||
| @@ -80,6 +61,21 @@ function ExperimentResult({ fileUrl, imageUrl, modelPath }: ExperimentResultProp | |||||
| </Image.PreviewGroup> | </Image.PreviewGroup> | ||||
| </div> | </div> | ||||
| </InfoGroup> | </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> | </div> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -3,6 +3,12 @@ | |||||
| .ant-drawer-body { | .ant-drawer-body { | ||||
| overflow-y: hidden; | overflow-y: hidden; | ||||
| } | } | ||||
| .ant-drawer-close { | |||||
| position: absolute; | |||||
| top: 16px; | |||||
| right: 16px; | |||||
| } | |||||
| } | } | ||||
| &__tabs { | &__tabs { | ||||
| @@ -2,7 +2,7 @@ import { ExperimentStatus } from '@/enums'; | |||||
| import { experimentStatusInfo } from '@/pages/Experiment/status'; | import { experimentStatusInfo } from '@/pages/Experiment/status'; | ||||
| import { PipelineNodeModelSerialize } from '@/types'; | import { PipelineNodeModelSerialize } from '@/types'; | ||||
| import { elapsedTime, formatDate } from '@/utils/date'; | 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 { Drawer, Tabs } from 'antd'; | ||||
| import { useMemo } from 'react'; | import { useMemo } from 'react'; | ||||
| import ExperimentParameter from '../ExperimentParameter'; | import ExperimentParameter from '../ExperimentParameter'; | ||||
| @@ -95,10 +95,11 @@ const ExperimentDrawer = ({ | |||||
| return ( | return ( | ||||
| <Drawer | <Drawer | ||||
| rootStyle={{ marginTop: '55px' }} | |||||
| title="任务执行详情" | title="任务执行详情" | ||||
| placement="right" | placement="right" | ||||
| getContainer={false} | getContainer={false} | ||||
| closeIcon={false} | |||||
| closeIcon={<CloseOutlined className={styles['experiment-drawer__close']} />} | |||||
| onClose={onClose} | onClose={onClose} | ||||
| open={open} | open={open} | ||||
| width={520} | width={520} | ||||
| @@ -49,7 +49,7 @@ const GlobalParamsDrawer = forwardRef( | |||||
| return ( | return ( | ||||
| <Drawer | <Drawer | ||||
| rootStyle={{ marginTop: '45px' }} | |||||
| rootStyle={{ marginTop: '55px' }} | |||||
| title="全局参数" | title="全局参数" | ||||
| placement="right" | placement="right" | ||||
| closeIcon={false} | closeIcon={false} | ||||
| @@ -335,7 +335,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| <Drawer | <Drawer | ||||
| title="编辑任务" | title="编辑任务" | ||||
| placement="right" | placement="right" | ||||
| rootStyle={{ marginTop: '52px' }} | |||||
| rootStyle={{ marginTop: '55px' }} | |||||
| getContainer={false} | getContainer={false} | ||||
| closeIcon={false} | closeIcon={false} | ||||
| onClose={onClose} | onClose={onClose} | ||||
| @@ -235,7 +235,7 @@ const UserTableList: React.FC = () => { | |||||
| const columns: ProColumns<API.System.User>[] = [ | const columns: ProColumns<API.System.User>[] = [ | ||||
| { | { | ||||
| title: <FormattedMessage id="system.user.user_id" defaultMessage="用户编号" />, | title: <FormattedMessage id="system.user.user_id" defaultMessage="用户编号" />, | ||||
| dataIndex: 'deptId', | |||||
| dataIndex: 'userId', | |||||
| valueType: 'text', | 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; | |||||