| @@ -78,7 +78,7 @@ export default defineConfig({ | |||
| */ | |||
| title: '智能软件开发平台', | |||
| layout: { | |||
| locale: true, | |||
| locale: false, | |||
| ...defaultSettings, | |||
| }, | |||
| // keepalive: [/./], | |||
| @@ -97,10 +97,8 @@ export default defineConfig({ | |||
| * @doc https://umijs.org/docs/max/i18n | |||
| */ | |||
| locale: { | |||
| // default zh-CN | |||
| default: 'zh-CN', | |||
| antd: true, | |||
| // default true, when it is true, will use `navigator.language` overwrite default | |||
| baseNavigator: true, | |||
| }, | |||
| /** | |||
| @@ -110,6 +108,7 @@ export default defineConfig({ | |||
| */ | |||
| antd: { | |||
| configProvider: {}, | |||
| appConfig: {}, | |||
| }, | |||
| /** | |||
| * @name 网络请求配置 | |||
| @@ -13,7 +13,7 @@ | |||
| export default [ | |||
| { | |||
| path: '/', | |||
| redirect: '/account/center', | |||
| redirect: '/workspace', | |||
| }, | |||
| { | |||
| path: '*', | |||
| @@ -88,13 +88,6 @@ export default [ | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| name: 'experiment', | |||
| path: '/experiment', | |||
| routes: [ | |||
| ], | |||
| }, | |||
| { | |||
| name: 'developmentEnvironment', | |||
| path: '/developmentEnvironment', | |||
| @@ -127,14 +120,36 @@ export default [ | |||
| path: '/dataset', | |||
| routes: [ | |||
| { | |||
| name: '数据集管理', | |||
| path: '/dataset/datasetIndex', | |||
| component: './Dataset/index', | |||
| name: '数据集', | |||
| path: 'dataset', | |||
| routes: [ | |||
| { | |||
| name: '数据集列表', | |||
| path: '', | |||
| component: './Dataset/index', | |||
| }, | |||
| { | |||
| name: '数据集简介', | |||
| path: ':id', | |||
| component: './Dataset/datasetIntro', | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| name: '数据集简介', | |||
| path: '/dataset/datasetIntro/:id', | |||
| component: './Dataset/datasetIntro', | |||
| name: '模型', | |||
| path: 'model', | |||
| routes: [ | |||
| { | |||
| name: '模型列表', | |||
| path: '', | |||
| component: './Model/index', | |||
| }, | |||
| { | |||
| name: '模型简介', | |||
| path: ':id', | |||
| component: './Model/modelIntro', | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| name: '镜像', | |||
| @@ -157,19 +172,9 @@ export default [ | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| name: '模型管理', | |||
| path: '/dataset/modelIndex', | |||
| component: './Model/index', | |||
| }, | |||
| { | |||
| name: '模型简介', | |||
| path: '/dataset/modelIntro/:id', | |||
| component: './Model/modelIntro', | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| name: 'workspace', | |||
| path: '/workspace', | |||
| @@ -177,7 +182,8 @@ export default [ | |||
| { | |||
| name: '工作空间', | |||
| path: '', | |||
| component: './missingPage.jsx', | |||
| key: 'workspace', | |||
| component: './Workspace/index', | |||
| }, | |||
| ], | |||
| }, | |||
| @@ -188,11 +194,11 @@ export default [ | |||
| { | |||
| name: '模型部署', | |||
| path: '', | |||
| key: 'modelDseployment', | |||
| component: './missingPage.jsx', | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| name: 'appsDeployment', | |||
| path: '/appsDeployment', | |||
| @@ -200,6 +206,7 @@ export default [ | |||
| { | |||
| name: '应用开发', | |||
| path: '', | |||
| key: 'appsDeployment', | |||
| component: './missingPage.jsx', | |||
| }, | |||
| ], | |||
| @@ -211,6 +218,7 @@ export default [ | |||
| { | |||
| name: '监控运维', | |||
| path: '', | |||
| key: 'see', | |||
| component: './missingPage.jsx', | |||
| }, | |||
| ], | |||
| @@ -242,4 +250,16 @@ export default [ | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| name: 'docs', | |||
| path: '/docs', | |||
| routes: [ | |||
| { | |||
| name: '使用指南', | |||
| path: '', | |||
| key: 'docs', | |||
| component: './Docs/index', | |||
| }, | |||
| ], | |||
| }, | |||
| ]; | |||
| @@ -60,6 +60,7 @@ | |||
| "@umijs/route-utils": "^4.0.1", | |||
| "antd": "^5.4.4", | |||
| "classnames": "^2.3.2", | |||
| "echarts": "^5.5.0", | |||
| "fabric": "^5.3.0", | |||
| "highlight.js": "^11.7.0", | |||
| "lodash": "^4.17.21", | |||
| @@ -70,6 +71,7 @@ | |||
| "rc-menu": "^9.8.4", | |||
| "rc-util": "^5.30.0", | |||
| "react": "^18.2.0", | |||
| "react-activation": "^0.12.4", | |||
| "react-cropper": "^2.3.3", | |||
| "react-dev-inspector": "^1.8.1", | |||
| "react-dom": "^18.2.0", | |||
| @@ -1,8 +1,7 @@ | |||
| import RightContent from '@/components/RightContent'; | |||
| import themes from '@/styles/theme.less'; | |||
| import type { Settings as LayoutSettings } from '@ant-design/pro-components'; | |||
| import type { RunTimeLayoutConfig } from '@umijs/max'; | |||
| import { history } from '@umijs/max'; | |||
| import { RuntimeConfig, history } from '@umijs/max'; | |||
| import { RuntimeAntdConfig } from 'umi'; | |||
| import defaultSettings from '../config/defaultSettings'; | |||
| import '../public/fonts/font.css'; | |||
| @@ -10,6 +9,7 @@ import { getAccessToken } from './access'; | |||
| import './dayjsConfig'; | |||
| import { PageEnum } from './enums/pagesEnums'; | |||
| import './global.less'; | |||
| import { removeAllPageCacheState } from './hooks/pageCacheState'; | |||
| import { | |||
| getRemoteMenu, | |||
| getRoutersInfo, | |||
| @@ -29,20 +29,18 @@ export async function getInitialState(): Promise<{ | |||
| loading?: boolean; | |||
| fetchUserInfo?: () => Promise<API.CurrentUser | undefined>; | |||
| }> { | |||
| console.log('getInitialState'); | |||
| // console.log('getInitialState'); | |||
| const fetchUserInfo = async () => { | |||
| try { | |||
| const response = await getUserInfo({ | |||
| skipErrorHandler: true, | |||
| }); | |||
| response.user.avatar = | |||
| response.user.avatar || | |||
| 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png'; | |||
| return { | |||
| ...response.user, | |||
| avatar: response.user.avatar || require('@/assets/img/avatar-default.png'), | |||
| permissions: response.permissions, | |||
| roles: response.roles, | |||
| roleNames: response.user.roles, | |||
| } as API.CurrentUser; | |||
| } catch (error) { | |||
| console.log(error); | |||
| @@ -67,7 +65,7 @@ export async function getInitialState(): Promise<{ | |||
| } | |||
| // ProLayout 支持的api https://procomponents.ant.design/components/layout | |||
| export const layout: RunTimeLayoutConfig = ({ initialState }) => { | |||
| export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||
| return { | |||
| rightContentRender: () => <RightContent />, | |||
| waterMarkProps: { | |||
| @@ -129,7 +127,7 @@ export const layout: RunTimeLayoutConfig = ({ initialState }) => { | |||
| // </Link>, | |||
| // ] | |||
| // : [], | |||
| menuHeaderRender: undefined, | |||
| menuHeaderRender: false, | |||
| // 自定义 403 页面 | |||
| // unAccessible: <div>unAccessible</div>, | |||
| // 增加一个 loading 的状态 | |||
| @@ -137,30 +135,66 @@ export const layout: RunTimeLayoutConfig = ({ initialState }) => { | |||
| // if (initialState?.loading) return <PageLoading />; | |||
| return <>{children}</>; | |||
| }, | |||
| menuProps: { | |||
| onClick: () => { | |||
| // 点击菜单项,删除所有的页面 state 缓存 | |||
| removeAllPageCacheState(); | |||
| }, | |||
| // onSelect: (e) => { | |||
| // console.log(e); | |||
| // }, | |||
| }, | |||
| ...initialState?.settings, | |||
| token: { | |||
| sider: { | |||
| colorTextMenu: themes['textColor'], | |||
| colorTextMenuSelected: themes['primaryColor'], | |||
| colorTextMenuActive: themes['primaryColor'], | |||
| colorBgMenuItemSelected: 'rgba(197, 232, 255, 0.8)', | |||
| colorMenuBackground: themes['siderBGColor'], | |||
| }, | |||
| }, | |||
| // menuItemRender: (itemProps, defaultDom, props) => { | |||
| // console.log('menuItemProps', itemProps); | |||
| // console.log('defaultDom', defaultDom); | |||
| // console.log('props', props); | |||
| // const { pathname } = window.location; | |||
| // const isSelected = pathname === itemProps.path; | |||
| // // 根据菜单项的状态动态显示不同的 icon | |||
| // const icon = isSelected ? 'icon-developmentEnvironment-icon' : 'icon-kaifahuanjing'; | |||
| // return ( | |||
| // <Link to={itemProps.path || ''} target={itemProps.target}> | |||
| // <KFIcon type={icon} /> | |||
| // {itemProps.name} | |||
| // </Link> | |||
| // ); | |||
| // }, | |||
| }; | |||
| }; | |||
| export async function onRouteChange({ clientRoutes, location }: any) { | |||
| export const onRouteChange: RuntimeConfig['onRouteChange'] = async (e) => { | |||
| const { location } = e; | |||
| const menus = getRemoteMenu(); | |||
| // console.log('onRouteChange', clientRoutes, location, menus); | |||
| console.log('onRouteChange', e); | |||
| if (menus === null && location.pathname !== PageEnum.LOGIN) { | |||
| console.log('refresh'); | |||
| history.go(0); | |||
| } | |||
| } | |||
| }; | |||
| export function patchRoutes({ routes, routeComponents }: any) { | |||
| //console.log('patchRoutes', routes, routeComponents); | |||
| } | |||
| export const patchRoutes: RuntimeConfig['patchRoutes'] = (e) => { | |||
| console.log('patchRoutes', e); | |||
| }; | |||
| export async function patchClientRoutes({ routes }: any) { | |||
| // console.log('patchClientRoutes', routes); | |||
| patchRouteWithRemoteMenus(routes); | |||
| } | |||
| export const patchClientRoutes: RuntimeConfig['patchClientRoutes'] = (e) => { | |||
| console.log('patchClientRoutes', e); | |||
| patchRouteWithRemoteMenus(e.routes); | |||
| }; | |||
| export function render(oldRender: () => void) { | |||
| // console.log('render get routers', oldRender); | |||
| console.log('render'); | |||
| const token = getAccessToken(); | |||
| if (!token || token?.length === 0) { | |||
| oldRender(); | |||
| @@ -1,3 +1,9 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-28 14:18:11 | |||
| * @Description: 自定义 Table 单元格,没有数据时展示 -- | |||
| */ | |||
| function CommonTableCell(text?: string | null) { | |||
| return <span>{text ?? '--'}</span>; | |||
| } | |||
| @@ -1,3 +1,9 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-28 14:18:11 | |||
| * @Description: 自定义 Table 日期类单元格 | |||
| */ | |||
| import dayjs from 'dayjs'; | |||
| function DateTableCell(text?: string | null) { | |||
| @@ -5,7 +11,7 @@ function DateTableCell(text?: string | null) { | |||
| return <span>--</span>; | |||
| } | |||
| if (!dayjs(text).isValid()) { | |||
| return <span>日期无效</span>; | |||
| return <span>无效的日期</span>; | |||
| } | |||
| return <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>; | |||
| } | |||
| @@ -1,6 +1,7 @@ | |||
| import KFModal from '@/components/KFModal'; | |||
| import * as AntdIcons from '@ant-design/icons'; | |||
| import { useIntl } from '@umijs/max'; | |||
| import { Modal, Popover, Progress, Result, Spin, Tooltip, Upload } from 'antd'; | |||
| import { Popover, Progress, Result, Spin, Tooltip, Upload } from 'antd'; | |||
| import React, { useCallback, useEffect, useState } from 'react'; | |||
| import './style.less'; | |||
| @@ -134,7 +135,7 @@ const PicSearcher: React.FC = () => { | |||
| > | |||
| <AntdIcons.CameraOutlined className="icon-pic-btn" onClick={toggleModal} /> | |||
| </Popover> | |||
| <Modal | |||
| <KFModal | |||
| title={intl.formatMessage({ | |||
| id: 'app.docs.components.icon.pic-searcher.title', | |||
| defaultMessage: '信息', | |||
| @@ -228,7 +229,7 @@ const PicSearcher: React.FC = () => { | |||
| )} | |||
| </div> | |||
| </Spin> | |||
| </Modal> | |||
| </KFModal> | |||
| </div> | |||
| ); | |||
| }; | |||
| @@ -17,15 +17,16 @@ interface KFIconProps extends IconFontProps { | |||
| font?: number; | |||
| color?: string; | |||
| style?: React.CSSProperties; | |||
| className?: string; | |||
| } | |||
| function KFIcon({ type, font = 15, color = '', style = {} }: KFIconProps) { | |||
| function KFIcon({ type, font = 15, color = '', style = {}, className }: KFIconProps) { | |||
| const iconStyle = { | |||
| ...style, | |||
| fontSize: font, | |||
| color, | |||
| }; | |||
| return <Icon type={type} style={iconStyle} />; | |||
| return <Icon type={type} className={className} style={iconStyle} />; | |||
| } | |||
| export default KFIcon; | |||
| @@ -1,30 +1,29 @@ | |||
| .kf-modal { | |||
| .ant-modal-content { | |||
| padding: 20px 67px; | |||
| padding: 40px 67px; | |||
| background-image: url(/assets/images/modal-back.png); | |||
| background-repeat:no-repeat; | |||
| background-size:100%; | |||
| background-position: top center; | |||
| // background: linear-gradient(180deg, #cfdfff 0%, #d4e2ff 9.77%, #ffffff 40%, #ffffff 100%); | |||
| border-radius: 21px; | |||
| background-repeat: no-repeat; | |||
| background-position: top center; | |||
| background-size: 100%; | |||
| border-radius: 20px; | |||
| } | |||
| .ant-modal-header { | |||
| margin: 20px 0 30px; | |||
| margin-bottom: 30px; | |||
| background-color: transparent; | |||
| } | |||
| .ant-modal-footer { | |||
| display: flex; | |||
| justify-content: center; | |||
| margin: 40px 0 20px 0; | |||
| margin-top: 40px; | |||
| .ant-btn { | |||
| height: 40px; | |||
| padding: 0 30px; | |||
| font-size: 16px; | |||
| font-size: @font-size-content; | |||
| border-radius: 10px; | |||
| } | |||
| .ant-btn-default { | |||
| color: #1d1d20; | |||
| color: @text-color; | |||
| background: rgba(22, 100, 255, 0.06); | |||
| border-color: transparent; | |||
| } | |||
| @@ -32,10 +31,4 @@ background-position: top center; | |||
| margin-left: 20px; | |||
| } | |||
| } | |||
| .ant-modal-close { | |||
| top: 27px; | |||
| right: 27px; | |||
| width: 26px; | |||
| height: 26px; | |||
| } | |||
| } | |||
| @@ -5,31 +5,22 @@ | |||
| */ | |||
| import ModalTitle from '@/components/ModalTitle'; | |||
| import { ConfigProvider, Modal, theme, type ModalProps } from 'antd'; | |||
| import { Modal, type ModalProps } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { useAntdConfig } from 'umi'; | |||
| import './index.less'; | |||
| const { useToken } = theme; | |||
| export interface KFModalProps extends ModalProps { | |||
| image: string; | |||
| image?: string; | |||
| } | |||
| function KFModal({ title, image, children, className = '', ...rest }: KFModalProps) { | |||
| const { token } = useToken(); | |||
| console.log('token', token); | |||
| const antdConfig = useAntdConfig(); | |||
| console.log('antdConfig', antdConfig); | |||
| return ( | |||
| <ConfigProvider {...antdConfig}> | |||
| <Modal | |||
| className={classNames(['kf-modal', className])} | |||
| {...rest} | |||
| title={<ModalTitle title={title} image={image}></ModalTitle>} | |||
| > | |||
| {children} | |||
| </Modal> | |||
| </ConfigProvider> | |||
| <Modal | |||
| className={classNames(['kf-modal', className])} | |||
| {...rest} | |||
| title={<ModalTitle title={title} image={image}></ModalTitle>} | |||
| > | |||
| {children} | |||
| </Modal> | |||
| ); | |||
| } | |||
| @@ -1,7 +1,7 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-17 16:59:42 | |||
| * @Description: 自定义Radio | |||
| * @Description: 自定义 Radio | |||
| */ | |||
| import classNames from 'classnames'; | |||
| @@ -16,12 +16,14 @@ export type KFRadioItem = { | |||
| type KFRadioProps = { | |||
| items: KFRadioItem[]; | |||
| value?: string; | |||
| style?: React.CSSProperties; | |||
| className?: string; | |||
| onChange?: (value: string) => void; | |||
| }; | |||
| function KFRadio({ items, value, onChange }: KFRadioProps) { | |||
| function KFRadio({ items, value, style, className, onChange }: KFRadioProps) { | |||
| return ( | |||
| <span className={'kf-radio'}> | |||
| <span className={classNames('kf-radio', className)} style={style}> | |||
| {items.map((item) => { | |||
| return ( | |||
| <span | |||
| @@ -1,2 +0,0 @@ | |||
| .tabs-container { | |||
| } | |||
| @@ -1,16 +0,0 @@ | |||
| // import { Tabs } from 'antd'; | |||
| // import { useEffect, useState } from 'react'; | |||
| // import styles from './index.less'; | |||
| // function KFTabs() { | |||
| // const [iframeUrl, setIframeUrl] = useState(''); | |||
| // useEffect(() => {}, []); | |||
| // return ( | |||
| // <div className={styles}> | |||
| // <Tabs></Tabs> | |||
| // </div> | |||
| // ); | |||
| // } | |||
| // export default KFTabs; | |||
| @@ -1,11 +1,11 @@ | |||
| .modal_title { | |||
| .modal-title { | |||
| display: flex; | |||
| align-items: center; | |||
| color: @primary-color; | |||
| font-weight: 400; | |||
| font-size: 20px; | |||
| &_image { | |||
| &__image { | |||
| width: 22px; | |||
| margin-right: 10px; | |||
| } | |||
| @@ -1,15 +1,24 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-28 14:18:11 | |||
| * @Description: 自定义 Modal Title | |||
| */ | |||
| import classNames from 'classnames'; | |||
| import React from 'react'; | |||
| import styles from './index.less'; | |||
| type ModalTitleProps = { | |||
| title: React.ReactNode; | |||
| image?: string; | |||
| style?: React.CSSProperties; | |||
| className?: string; | |||
| }; | |||
| function ModalTitle({ title, image }: ModalTitleProps) { | |||
| function ModalTitle({ title, image, style, className }: ModalTitleProps) { | |||
| return ( | |||
| <div className={styles.modal_title}> | |||
| <img className={styles.modal_title_image} src={image} alt="" /> | |||
| <div className={classNames(styles['modal-title'], className)} style={style}> | |||
| {image && <img className={styles['modal-title__image']} src={image} alt="" />} | |||
| {title} | |||
| </div> | |||
| ); | |||
| @@ -2,7 +2,7 @@ import { clearSessionToken } from '@/access'; | |||
| import { setRemoteMenu } from '@/services/session'; | |||
| import { logout } from '@/services/system/auth'; | |||
| import { gotoLoginPage } from '@/utils/ui'; | |||
| import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons'; | |||
| import { LogoutOutlined, UserOutlined } from '@ant-design/icons'; | |||
| import { setAlpha } from '@ant-design/pro-components'; | |||
| import { useEmotionCss } from '@ant-design/use-emotion-css'; | |||
| import { history, useModel } from '@umijs/max'; | |||
| @@ -127,11 +127,11 @@ const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => { | |||
| icon: <UserOutlined />, | |||
| label: '个人中心', | |||
| }, | |||
| { | |||
| key: 'settings', | |||
| icon: <SettingOutlined />, | |||
| label: '个人设置', | |||
| }, | |||
| // { | |||
| // key: 'settings', | |||
| // icon: <SettingOutlined />, | |||
| // label: '个人设置', | |||
| // }, | |||
| { | |||
| type: 'divider' as const, | |||
| }, | |||
| @@ -48,7 +48,7 @@ const GlobalHeaderRight: React.FC = () => { | |||
| > | |||
| <QuestionCircleOutlined /> | |||
| </span> */} | |||
| <Avatar menu={false} /> | |||
| <Avatar menu={true} /> | |||
| {/* <SelectLang className={actionClassName} /> */} | |||
| </div> | |||
| ); | |||
| @@ -4,17 +4,19 @@ | |||
| * @Description: 分区标题 | |||
| */ | |||
| import classNames from 'classnames'; | |||
| import './index.less'; | |||
| type SubAreaTitleProps = { | |||
| title: string; | |||
| image: string; | |||
| style?: React.CSSProperties; | |||
| className?: string; | |||
| }; | |||
| function SubAreaTitle({ title, image, style }: SubAreaTitleProps) { | |||
| function SubAreaTitle({ title, image, style, className }: SubAreaTitleProps) { | |||
| return ( | |||
| <div className={'kf-subarea-title'} style={style}> | |||
| <div className={classNames('kf-subarea-title', className)} style={style}> | |||
| <img src={image} width={14} /> | |||
| <span style={{ marginLeft: '8px' }}>{title}</span> | |||
| </div> | |||
| @@ -1,6 +1,7 @@ | |||
| html, | |||
| body, | |||
| #root { | |||
| min-width: 1440px; | |||
| height: 100%; | |||
| margin: 0; | |||
| padding: 0; | |||
| @@ -20,9 +21,6 @@ body, | |||
| .ant-pro-sider.ant-layout-sider.ant-pro-sider-fixed { | |||
| left: unset; | |||
| } | |||
| .ant-layout-sider-children { | |||
| margin-top: 60px !important; | |||
| } | |||
| canvas { | |||
| display: block; | |||
| } | |||
| @@ -33,58 +31,29 @@ body { | |||
| -moz-osx-font-smoothing: grayscale; | |||
| } | |||
| .ant-pro-layout .ant-pro-layout-content { | |||
| padding: 10px; | |||
| padding: 0 10px 10px; | |||
| background-color: transparent; | |||
| } | |||
| .ant-pro-layout .ant-pro-layout-bg-list { | |||
| background: #f9fafb; | |||
| background: @background-color; | |||
| } | |||
| .ant-menu-light .ant-menu-item-selected { | |||
| background: rgba(197, 232, 255, 0.8) !important; | |||
| } | |||
| .ant-menu-light .ant-menu-item-selected .ant-pro-base-menu-inline-item-text { | |||
| // color: #1664ff; | |||
| } | |||
| .ant-pro-layout .ant-pro-sider .ant-layout-sider-children { | |||
| background: #f2f5f7; | |||
| } | |||
| .ant-pro-base-menu-inline-item-title .ant-pro-base-menu-inline-item-text { | |||
| // color: #1d1d20; | |||
| font-size: 16px; | |||
| } | |||
| // .ant-menu-light .ant-menu-item-selected{ | |||
| // color:#1664ff; | |||
| // } | |||
| .ant-pro-layout .ant-pro-sider-menu { | |||
| padding-top: 40px; | |||
| } | |||
| .ant-table-wrapper .ant-table-container table > thead > tr:first-child > *:first-child, | |||
| .ant-table-wrapper .ant-table-tbody>tr>td:first-child { | |||
| padding: 0 30px; | |||
| } | |||
| .ant-pro-global-header-logo-mix { | |||
| width: 257px; | |||
| height: 75px; | |||
| margin-left: -16px; | |||
| padding-left: 28px; | |||
| background: #f2f5f7; | |||
| border-bottom: 1px solid rgba(233, 237, 240, 1); | |||
| border-top-right-radius: 20px; | |||
| padding-left: 12px; | |||
| } | |||
| .ant-pro-layout .ant-pro-sider .ant-layout-sider-children { | |||
| border-right: unset; | |||
| border-bottom-right-radius: 20px; | |||
| } | |||
| .ant-pro-base-menu-inline { | |||
| // height: 87vh; | |||
| background: #f2f5f7; | |||
| border-radius: 0px 20px 20px 0px; | |||
| } | |||
| .ant-pro-layout .ant-pro-layout-content { | |||
| background-color: transparent; | |||
| } | |||
| .ant-drawer .ant-drawer-body { | |||
| padding: 0; | |||
| } | |||
| @@ -102,99 +71,17 @@ body { | |||
| padding: 20px 16px; | |||
| background-color: #fff; | |||
| } | |||
| // .ant-table-wrapper .ant-table { | |||
| // height: 81vh; | |||
| // // overflow-y: auto; | |||
| // } | |||
| .ant-pro-global-header-logo img { | |||
| height: 21px; | |||
| } | |||
| .ant-pro-layout .ant-layout-sider.ant-pro-sider { | |||
| height: 94vh; | |||
| height: 100vh; | |||
| padding-top: 56px; | |||
| } | |||
| .ant-pro-layout .ant-pro-layout-container { | |||
| height: 100vh; | |||
| overflow-y: hidden; | |||
| } | |||
| .ant-modal-confirm .ant-modal-confirm-paragraph { | |||
| margin: 54px 0 auto; | |||
| text-align: center; | |||
| } | |||
| .ant-modal-confirm-confirm .ant-modal-confirm-body > .anticon { | |||
| display: none; | |||
| } | |||
| .ant-modal-confirm .ant-modal-confirm-btns { | |||
| margin-top: 30px; | |||
| text-align: center; | |||
| } | |||
| .ant-modal-confirm-btns .ant-btn-default { | |||
| width: 110px; | |||
| height: 40px; | |||
| margin-right: 10px; | |||
| // color: #1d1d20; | |||
| font-size: 18px; | |||
| background: rgba(22, 100, 255, 0.06); | |||
| border-color: transparent; | |||
| border-radius: 10px; | |||
| } | |||
| .ant-modal-confirm-btns .ant-btn-default:hover { | |||
| background: rgba(22, 100, 255, 0.06); | |||
| border-color: transparent; | |||
| } | |||
| .ant-modal-confirm-btns .ant-btn-primary { | |||
| width: 110px; | |||
| height: 40px; | |||
| font-size: 18px; | |||
| border-radius: 10px; | |||
| border-radius: 10px; | |||
| } | |||
| .ant-modal .ant-input-affix-wrapper { | |||
| height: 46px; | |||
| padding: 1px 11px; | |||
| } | |||
| .ant-input-textarea-affix-wrapper.ant-input-affix-wrapper{ | |||
| padding: 0; | |||
| } | |||
| .ant-modal .ant-select-single { | |||
| height: 46px; | |||
| } | |||
| .ant-modal .ant-select-single .ant-select-selector .ant-select-selection-placeholder { | |||
| line-height: 46px; | |||
| } | |||
| .ant-menu-light.ant-menu-inline .ant-menu-item{ | |||
| color:#575757; | |||
| } | |||
| .ant-modal .ant-modal-close-x { | |||
| width: 26px; | |||
| height: 26px; | |||
| color: #272536; | |||
| border: 2px solid #272536; | |||
| border-radius: 50%; | |||
| } | |||
| .ant-modal-content { | |||
| margin-top: 50px; | |||
| margin-left: -130px; | |||
| } | |||
| .ant-modal .ant-modal-content { | |||
| padding: 0; | |||
| } | |||
| .ant-modal-confirm-body-wrapper { | |||
| height: 303px; | |||
| background-image: url(/assets/images/modal-back.png); | |||
| background-repeat: no-repeat; | |||
| background-position: top center; | |||
| background-size: 100%; | |||
| border-radius: 0; | |||
| } | |||
| .ant-modal .ant-modal-content { | |||
| border-radius: 20px; | |||
| } | |||
| .ant-modal .ant-modal-close:hover { | |||
| background-color: transparent; | |||
| } | |||
| .ant-modal .ant-modal-footer > .ant-btn + .ant-btn { | |||
| margin-left: 20px; | |||
| } | |||
| .ant-pagination .ant-pagination-item.ant-pagination-item-active { | |||
| background: @primary-color; | |||
| border-width: 0; | |||
| @@ -212,22 +99,18 @@ body { | |||
| border: 1px solid #e6e6e6; | |||
| } | |||
| // ::-webkit-scrollbar-button { | |||
| // background: #97a1bd; | |||
| // } | |||
| ::-webkit-scrollbar { | |||
| width: 9px; | |||
| border-radius: 2px; | |||
| width: 8px; | |||
| background: transparent; | |||
| } | |||
| ::-webkit-scrollbar-thumb { | |||
| // background-color: #9aa3bc!important; | |||
| width: 7px; | |||
| background: rgba(77, 87, 123, 0.5) !important; | |||
| width: 8px; | |||
| background: rgba(0, 0, 0, 0.5); | |||
| border-radius: 99px; | |||
| } | |||
| ::-webkit-scrollbar-track { | |||
| // background-color: #eaf1ff!important; | |||
| width: 9px; | |||
| background: rgba(22, 100, 255, 0.06) !important; | |||
| width: 8px; | |||
| background: transparent; | |||
| } | |||
| ul, | |||
| ol { | |||
| @@ -41,7 +41,7 @@ export function useVisible(initialValue: boolean) { | |||
| setVisible(false); | |||
| }, []); | |||
| return [visible, open, close]; | |||
| return [visible, open, close] as const; | |||
| } | |||
| type Callback<T> = (state: T) => void; | |||
| @@ -92,7 +92,7 @@ export function useDomSize<T extends HTMLElement>( | |||
| setWidth(domRef.current.offsetWidth); | |||
| } | |||
| }; | |||
| const debounceFunc = debounce(setDomHeight, 200); | |||
| const debounceFunc = debounce(setDomHeight, 100); | |||
| setDomHeight(); | |||
| window.addEventListener('resize', debounceFunc); | |||
| @@ -0,0 +1,58 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-28 11:49:48 | |||
| * @Description: 页面状态缓存,pop 回到这个页面的时候,重新构建之前的状态 | |||
| */ | |||
| import { useCallback, useState } from 'react'; | |||
| const pageKeys: string[] = []; | |||
| // let stateCaches: Record<string, any> = {}; | |||
| // 获取页面缓存 | |||
| const getCacheState = (key: string) => { | |||
| const jsonStr = sessionStorage.getItem(key); | |||
| if (jsonStr) { | |||
| removeCacheState(key); | |||
| try { | |||
| return JSON.parse(jsonStr); | |||
| } catch (error) { | |||
| return undefined; | |||
| } | |||
| } | |||
| return undefined; | |||
| }; | |||
| // 移除页面 state 缓存 | |||
| const removeCacheState = (key: string) => { | |||
| sessionStorage.removeItem(key); | |||
| const index = pageKeys.indexOf(key); | |||
| if (index !== -1) { | |||
| pageKeys.splice(index, 1); | |||
| } | |||
| }; | |||
| // 移除所有页面 state 缓存 | |||
| export const removeAllPageCacheState = () => { | |||
| pageKeys.forEach((key) => { | |||
| sessionStorage.removeItem(key); | |||
| }); | |||
| }; | |||
| export const useCacheState = () => { | |||
| const { pathname } = window.location; | |||
| const key = 'pagecache:' + pathname; | |||
| const setCacheState = useCallback( | |||
| (state?: any) => { | |||
| if (state) { | |||
| pageKeys.push(key); | |||
| sessionStorage.setItem(key, JSON.stringify(state)); | |||
| } | |||
| }, | |||
| [key], | |||
| ); | |||
| const [cacheState] = useState(() => getCacheState(key)); | |||
| return [cacheState, setCacheState] as const; | |||
| }; | |||
| @@ -52,3 +52,88 @@ | |||
| .ant-table-wrapper .ant-table-thead > tr > td { | |||
| background-color: #fff; | |||
| } | |||
| .ant-pro-page-container { | |||
| overflow-y: auto; | |||
| } | |||
| // Input | |||
| .ant-input-textarea-affix-wrapper.ant-input-affix-wrapper { | |||
| padding: 0; | |||
| } | |||
| // Modal | |||
| .ant-modal { | |||
| .ant-modal-close { | |||
| top: 27px; | |||
| right: 27px; | |||
| width: 26px; | |||
| height: 26px; | |||
| color: @text-color-secondary; | |||
| border: 2px solid @text-color-secondary; | |||
| border-radius: 50%; | |||
| &:hover, | |||
| &:active { | |||
| color: #272536; | |||
| background-color: transparent; | |||
| border: 2px solid #272536; | |||
| } | |||
| } | |||
| .ant-input-affix-wrapper { | |||
| height: 46px; | |||
| padding: 1px 11px; | |||
| } | |||
| .ant-select-single { | |||
| height: 46px; | |||
| } | |||
| .ant-select-single .ant-select-selector .ant-select-selection-placeholder { | |||
| line-height: 46px; | |||
| } | |||
| } | |||
| // Confirm Modal | |||
| .ant-modal-confirm { | |||
| .ant-modal-content { | |||
| padding: 40px 67px; | |||
| background-image: url(/assets/images/modal-back.png); | |||
| background-repeat: no-repeat; | |||
| background-position: top center; | |||
| background-size: 100%; | |||
| border-radius: 20px; | |||
| } | |||
| .ant-modal-confirm-body { | |||
| .anticon { | |||
| display: none; | |||
| } | |||
| } | |||
| .ant-modal-confirm-paragraph { | |||
| max-width: 100%; | |||
| margin-top: 27px; | |||
| text-align: center; | |||
| } | |||
| .ant-modal-confirm-btns { | |||
| margin-top: 40px; | |||
| text-align: center; | |||
| .ant-btn { | |||
| height: 40px; | |||
| padding: 0 30px; | |||
| font-size: @font-size-content; | |||
| border-radius: 10px; | |||
| } | |||
| .ant-btn-default { | |||
| color: @text-color; | |||
| background: rgba(22, 100, 255, 0.06); | |||
| border-color: transparent; | |||
| } | |||
| .ant-btn + .ant-btn { | |||
| margin-left: 20px; | |||
| } | |||
| } | |||
| } | |||
| @@ -46,7 +46,7 @@ const Dataset = () => { | |||
| const onFinish = (values) => {}; | |||
| const routeToIntro = (e, record) => { | |||
| e.stopPropagation(); | |||
| navgite({ pathname: '/dataset/datasetIntro' }); | |||
| navgite({ pathname: '/dataset/dataset' }); | |||
| }; | |||
| const onFinishFailed = (errorInfo) => { | |||
| console.log('Failed:', errorInfo); | |||
| @@ -117,14 +117,14 @@ const PublicData = (React.FC = () => { | |||
| }; | |||
| const chooseDatasetType = (val, item) => { | |||
| console.log(val, item); | |||
| if (item.path == queryFlow.data_type) { | |||
| if (item.id == queryFlow.data_type) { | |||
| setActiveType(''); | |||
| setQueryFlow({ ...queryFlow, data_type: null }); | |||
| getDatasetlist({ ...queryFlow, data_type: null }); | |||
| } else { | |||
| setActiveType(item.path); | |||
| setQueryFlow({ ...queryFlow, data_type: item.path }); | |||
| getDatasetlist({ ...queryFlow, data_type: item.path }); | |||
| setActiveType(item.id); | |||
| setQueryFlow({ ...queryFlow, data_type: item.id }); | |||
| getDatasetlist({ ...queryFlow, data_type: item.id }); | |||
| } | |||
| // setQueryFlow({...queryFlow,data_type:item.path},()=>{ | |||
| // getDatasetlist() | |||
| @@ -132,14 +132,14 @@ const PublicData = (React.FC = () => { | |||
| }; | |||
| const chooseDatasetTag = (val, item) => { | |||
| console.log(val, item); | |||
| if (item.path == queryFlow.data_tag) { | |||
| if (item.id == queryFlow.data_tag) { | |||
| setActiveTag(''); | |||
| setQueryFlow({ ...queryFlow, data_tag: null }); | |||
| getDatasetlist({ ...queryFlow, data_tag: null }); | |||
| } else { | |||
| setActiveTag(item.path); | |||
| setQueryFlow({ ...queryFlow, data_tag: item.path }); | |||
| getDatasetlist({ ...queryFlow, data_tag: item.path }); | |||
| setActiveTag(item.id); | |||
| setQueryFlow({ ...queryFlow, data_tag: item.id }); | |||
| getDatasetlist({ ...queryFlow, data_tag: item.id }); | |||
| } | |||
| // setQueryFlow({...queryFlow,data_type:item.path},()=>{ | |||
| // getDatasetlist() | |||
| @@ -155,7 +155,7 @@ const PublicData = (React.FC = () => { | |||
| const routeToIntro = (e, record) => { | |||
| e.stopPropagation(); | |||
| console.log(record); | |||
| navgite({ pathname: `/dataset/datasetIntro/${record.id}` }); | |||
| navgite({ pathname: `/dataset/dataset/${record.id}` }); | |||
| }; | |||
| const onFinishFailed = (errorInfo) => { | |||
| console.log('Failed:', errorInfo); | |||
| @@ -196,7 +196,7 @@ const PublicData = (React.FC = () => { | |||
| <div | |||
| className={[ | |||
| Styles.messageBox, | |||
| item.path === activeType ? Styles.active : null, | |||
| item.id === activeType ? Styles.active : null, | |||
| ].join(' ')} | |||
| onClick={(e) => { | |||
| chooseDatasetType(e, item); | |||
| @@ -230,7 +230,7 @@ const PublicData = (React.FC = () => { | |||
| <div | |||
| className={[ | |||
| Styles.messageBox, | |||
| item.path === activeTag ? Styles.active : null, | |||
| item.id === activeTag ? Styles.active : null, | |||
| ].join(' ')} | |||
| onClick={(e) => { | |||
| chooseDatasetTag(e, item); | |||
| @@ -76,14 +76,14 @@ const PublicData = () => { | |||
| }; | |||
| const chooseDatasetType = (val, item) => { | |||
| console.log(val, item); | |||
| if (item.path == queryFlow.data_type) { | |||
| if (item.id == queryFlow.data_type) { | |||
| setActiveType(''); | |||
| setQueryFlow({ ...queryFlow, data_type: null }); | |||
| getDatasetlist({ ...queryFlow, data_type: null }); | |||
| } else { | |||
| setActiveType(item.path); | |||
| setQueryFlow({ ...queryFlow, data_type: item.path }); | |||
| getDatasetlist({ ...queryFlow, data_type: item.path }); | |||
| setActiveType(item.id); | |||
| setQueryFlow({ ...queryFlow, data_type: item.id }); | |||
| getDatasetlist({ ...queryFlow, data_type: item.id }); | |||
| } | |||
| // setQueryFlow({...queryFlow,data_type:item.path},()=>{ | |||
| // getDatasetlist() | |||
| @@ -91,14 +91,14 @@ const PublicData = () => { | |||
| }; | |||
| const chooseDatasetTag = (val, item) => { | |||
| console.log(val, item); | |||
| if (item.path == queryFlow.data_tag) { | |||
| if (item.id == queryFlow.data_tag) { | |||
| setActiveTag(''); | |||
| setQueryFlow({ ...queryFlow, data_tag: null }); | |||
| getDatasetlist({ ...queryFlow, data_tag: null }); | |||
| } else { | |||
| setActiveTag(item.path); | |||
| setQueryFlow({ ...queryFlow, data_tag: item.path }); | |||
| getDatasetlist({ ...queryFlow, data_tag: item.path }); | |||
| setActiveTag(item.id); | |||
| setQueryFlow({ ...queryFlow, data_tag: item.id }); | |||
| getDatasetlist({ ...queryFlow, data_tag: item.id }); | |||
| } | |||
| // setQueryFlow({...queryFlow,data_type:item.path},()=>{ | |||
| // getDatasetlist() | |||
| @@ -108,7 +108,7 @@ const PublicData = () => { | |||
| const routeToIntro = (e, record) => { | |||
| e.stopPropagation(); | |||
| console.log(record); | |||
| navgite({ pathname: `/dataset/datasetIntro/${record.id}` }); | |||
| navgite({ pathname: `/dataset/dataset/${record.id}` }); | |||
| }; | |||
| const onFinishFailed = (errorInfo) => { | |||
| console.log('Failed:', errorInfo); | |||
| @@ -146,7 +146,7 @@ const PublicData = () => { | |||
| <div | |||
| className={[ | |||
| Styles.messageBox, | |||
| item.path === activeType ? Styles.active : null, | |||
| item.id === activeType ? Styles.active : null, | |||
| ].join(' ')} | |||
| onClick={(e) => { | |||
| chooseDatasetType(e, item); | |||
| @@ -180,7 +180,7 @@ const PublicData = () => { | |||
| <div | |||
| className={[ | |||
| Styles.messageBox, | |||
| item.path === activeTag ? Styles.active : null, | |||
| item.id === activeTag ? Styles.active : null, | |||
| ].join(' ')} | |||
| onClick={(e) => { | |||
| chooseDatasetTag(e, item); | |||
| @@ -0,0 +1,9 @@ | |||
| const Docs = () => { | |||
| return ( | |||
| <iframe | |||
| style={{ width: '100%', height: '100%', border: 0 }} | |||
| src={'/assets/材料科研软件平台使用文档.pdf'} | |||
| ></iframe> | |||
| ); | |||
| }; | |||
| export default Docs; | |||
| @@ -24,7 +24,7 @@ | |||
| align-items: center; | |||
| width: 100%; | |||
| padding: 0 0 0 33px; | |||
| color: #1d1d20; | |||
| color: @text-color; | |||
| font-size: 15px; | |||
| & > div { | |||
| @@ -76,16 +76,18 @@ | |||
| .statusBox:hover .statusIcon { | |||
| visibility: visible; | |||
| } | |||
| .experimentBox{ | |||
| .experimentBox { | |||
| height: calc(100% - 20px); | |||
| .experimentTable{ | |||
| .experimentTable { | |||
| height: calc(100% - 60px); | |||
| :global{ | |||
| .ant-table-wrapper .ant-table{ | |||
| :global { | |||
| .ant-table-wrapper .ant-table { | |||
| // overflow-y: auto; | |||
| height: calc(100% - 48px); | |||
| } | |||
| .ant-table-row-expand-icon-cell { | |||
| padding: 0 30px; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -16,7 +16,7 @@ export enum ExperimentStatus { | |||
| } | |||
| type ExperimentStatusKeys = keyof typeof ExperimentStatus; | |||
| type ExperimentStatusValues = (typeof ExperimentStatus)[ExperimentStatusKeys]; | |||
| export type ExperimentStatusValues = (typeof ExperimentStatus)[ExperimentStatusKeys]; | |||
| export const experimentStatusInfo: Record<ExperimentStatusValues, StatusInfo | undefined> = { | |||
| Running: { | |||
| @@ -11,7 +11,7 @@ import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { CommonTabKeys } from '@/enums'; | |||
| import { createMirrorReq } from '@/services/mirror'; | |||
| import { to } from '@/utils/promise'; | |||
| import { mirrorNameKey } from '@/utils/sessionKeys'; | |||
| import { getSessionItemThenRemove, mirrorNameKey } from '@/utils/sessionStorage'; | |||
| import { getFileListFromEvent } from '@/utils/ui'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { Button, Col, Form, Input, Row, Upload, UploadFile, message, type UploadProps } from 'antd'; | |||
| @@ -56,14 +56,11 @@ function MirrorCreate() { | |||
| }; | |||
| useEffect(() => { | |||
| const name = sessionStorage.getItem(mirrorNameKey); | |||
| const name = getSessionItemThenRemove(mirrorNameKey); | |||
| if (name) { | |||
| form.setFieldValue('name', name); | |||
| setNameDisabled(true); | |||
| } | |||
| return () => { | |||
| sessionStorage.removeItem(mirrorNameKey); | |||
| }; | |||
| }, []); | |||
| // 创建公网、本地镜像 | |||
| @@ -9,6 +9,7 @@ import KFIcon from '@/components/KFIcon'; | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { useDomSize } from '@/hooks'; | |||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||
| import { | |||
| deleteMirrorVersionReq, | |||
| getMirrorInfoReq, | |||
| @@ -16,17 +17,17 @@ import { | |||
| } from '@/services/mirror'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { to } from '@/utils/promise'; | |||
| import { mirrorNameKey } from '@/utils/sessionKeys'; | |||
| import { mirrorNameKey, setSessionStorageItem } from '@/utils/sessionStorage'; | |||
| import { modalConfirm } from '@/utils/ui'; | |||
| import { useNavigate, useParams, useSearchParams } from '@umijs/max'; | |||
| import { | |||
| App, | |||
| Button, | |||
| Col, | |||
| ConfigProvider, | |||
| Flex, | |||
| Row, | |||
| Table, | |||
| message, | |||
| type TablePaginationConfig, | |||
| type TableProps, | |||
| } from 'antd'; | |||
| @@ -56,17 +57,19 @@ function MirrorInfo() { | |||
| const navigate = useNavigate(); | |||
| const urlParams = useParams(); | |||
| const [searchParams] = useSearchParams(); | |||
| const [cacheState, setCacheState] = useCacheState(); | |||
| const [mirrorInfo, setMirrorInfo] = useState<MirrorInfoData>({}); | |||
| const [tableData, setTableData] = useState<MirrorVersionData[]>([]); | |||
| const [topRef, { height: topHeight }] = useDomSize<HTMLDivElement>(0, 0, [mirrorInfo]); | |||
| const [total, setTotal] = useState(0); | |||
| const [pagination, setPagination] = useState<TablePaginationConfig>({ | |||
| showSizeChanger: true, | |||
| showQuickJumper: true, | |||
| current: 1, | |||
| pageSize: 10, | |||
| }); | |||
| const [pagination, setPagination] = useState<TablePaginationConfig>( | |||
| cacheState?.pagination ?? { | |||
| current: 1, | |||
| pageSize: 10, | |||
| }, | |||
| ); | |||
| const isPublic = searchParams.get('isPublic') === 'true'; | |||
| const { message } = App.useApp(); | |||
| useEffect(() => { | |||
| getMirrorInfo(); | |||
| }, []); | |||
| @@ -115,7 +118,7 @@ function MirrorInfo() { | |||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||
| // 否则直接刷新这一页的数据 | |||
| // 避免回到第一页 | |||
| if (tableData.length > 1) { | |||
| if (tableData.length === 1) { | |||
| setPagination((prev) => ({ | |||
| ...prev, | |||
| current: 1, | |||
| @@ -146,7 +149,10 @@ function MirrorInfo() { | |||
| const createMirrorVersion = () => { | |||
| navigate(`/dataset/mirror/create`); | |||
| sessionStorage.setItem(mirrorNameKey, mirrorInfo.name || ''); | |||
| setSessionStorageItem(mirrorNameKey, mirrorInfo.name || ''); | |||
| setCacheState({ | |||
| pagination, | |||
| }); | |||
| }; | |||
| const columns: TableProps<MirrorVersionData>['columns'] = [ | |||
| @@ -256,13 +262,14 @@ function MirrorInfo() { | |||
| </Col> | |||
| </Row> | |||
| </div> | |||
| <Flex justify="space-between" align="center" style={{ marginTop: '40px' }}> | |||
| <Flex justify="flex-start" align="center" style={{ marginTop: '40px' }}> | |||
| <SubAreaTitle | |||
| title="镜像版本" | |||
| image={require('@/assets/img/mirror-version.png')} | |||
| ></SubAreaTitle> | |||
| {!isPublic && ( | |||
| <Button | |||
| style={{ marginRight: 0, marginLeft: 'auto' }} | |||
| type="default" | |||
| onClick={createMirrorVersion} | |||
| icon={<KFIcon type="icon-xinjian2" />} | |||
| @@ -270,6 +277,14 @@ function MirrorInfo() { | |||
| 新增镜像版本 | |||
| </Button> | |||
| )} | |||
| <Button | |||
| style={{ marginLeft: '20px' }} | |||
| type="default" | |||
| onClick={getMirrorVersionList} | |||
| icon={<KFIcon type="icon-shuaxin" />} | |||
| > | |||
| 刷新 | |||
| </Button> | |||
| </Flex> | |||
| </div> | |||
| <div | |||
| @@ -280,7 +295,7 @@ function MirrorInfo() { | |||
| dataSource={tableData} | |||
| columns={columns} | |||
| scroll={{ y: 'calc(100% - 55px)' }} | |||
| pagination={{ ...pagination, total }} | |||
| pagination={{ ...pagination, total, showSizeChanger: true, showQuickJumper: true }} | |||
| onChange={handleTableChange} | |||
| rowKey="id" | |||
| /> | |||
| @@ -7,11 +7,12 @@ import CommonTableCell from '@/components/CommonTableCell'; | |||
| import DateTableCell from '@/components/DateTableCell'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { CommonTabKeys } from '@/enums'; | |||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||
| import { deleteMirrorReq, getMirrorListReq } from '@/services/mirror'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { to } from '@/utils/promise'; | |||
| import { modalConfirm } from '@/utils/ui'; | |||
| import { useNavigate, useSearchParams } from '@umijs/max'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { | |||
| Button, | |||
| ConfigProvider, | |||
| @@ -50,20 +51,17 @@ export type MirrorData = { | |||
| function MirrorList() { | |||
| const navigate = useNavigate(); | |||
| const [searchParams, setSearchParams] = useSearchParams(); | |||
| const isPrivate = searchParams.get('isPublic') === 'false'; | |||
| const [activeTab, setActiveTab] = useState<string>( | |||
| isPrivate ? CommonTabKeys.Private : CommonTabKeys.Public, | |||
| ); | |||
| const [searchText, setSearchText] = useState(''); | |||
| const [cacheState, setCacheState] = useCacheState(); | |||
| const [activeTab, setActiveTab] = useState<string>(cacheState?.activeTab ?? CommonTabKeys.Public); | |||
| const [searchText, setSearchText] = useState(cacheState?.searchText); | |||
| const [tableData, setTableData] = useState<MirrorData[]>([]); | |||
| const [total, setTotal] = useState(0); | |||
| const [pagination, setPagination] = useState<TablePaginationConfig>({ | |||
| showSizeChanger: true, | |||
| showQuickJumper: true, | |||
| current: 1, | |||
| pageSize: 10, | |||
| }); | |||
| const [pagination, setPagination] = useState<TablePaginationConfig>( | |||
| cacheState?.pagination ?? { | |||
| current: 1, | |||
| pageSize: 10, | |||
| }, | |||
| ); | |||
| useEffect(() => { | |||
| getMirrorList(); | |||
| @@ -73,17 +71,12 @@ function MirrorList() { | |||
| const hanleTabChange: TabsProps['onChange'] = (value) => { | |||
| setSearchText(''); | |||
| setPagination({ | |||
| showSizeChanger: true, | |||
| showQuickJumper: true, | |||
| current: 1, | |||
| pageSize: 10, | |||
| }); | |||
| setTotal(0); | |||
| setTableData([]); | |||
| setActiveTab(value); | |||
| setSearchParams([['isPublic', value === CommonTabKeys.Public ? 'true' : 'false']], { | |||
| replace: true, | |||
| }); | |||
| }; | |||
| // 获取镜像列表 | |||
| const getMirrorList = async (params?: Record<string, any>) => { | |||
| @@ -131,11 +124,11 @@ function MirrorList() { | |||
| // 查看详情 | |||
| const toDetail = (record: MirrorData) => { | |||
| console.log('record', record); | |||
| navigate(`/dataset/mirror/${record.id}?isPublic=${activeTab === CommonTabKeys.Public}`, { | |||
| state: { | |||
| isPublic: activeTab === CommonTabKeys.Public, | |||
| }, | |||
| navigate(`/dataset/mirror/${record.id}?isPublic=${activeTab === CommonTabKeys.Public}`); | |||
| setCacheState({ | |||
| activeTab, | |||
| pagination, | |||
| searchText, | |||
| }); | |||
| }; | |||
| @@ -153,6 +146,11 @@ function MirrorList() { | |||
| // 创建镜像 | |||
| const createMirror = () => { | |||
| navigate(`/dataset/mirror/create`); | |||
| setCacheState({ | |||
| activeTab, | |||
| pagination, | |||
| searchText, | |||
| }); | |||
| }; | |||
| // 分页切换 | |||
| @@ -276,7 +274,12 @@ function MirrorList() { | |||
| dataSource={tableData} | |||
| columns={columns} | |||
| scroll={{ y: 'calc(100% - 55px)' }} | |||
| pagination={{ ...pagination, total: total }} | |||
| pagination={{ | |||
| ...pagination, | |||
| total: total, | |||
| showSizeChanger: true, | |||
| showQuickJumper: true, | |||
| }} | |||
| onChange={handleTableChange} | |||
| rowKey="id" | |||
| /> | |||
| @@ -46,7 +46,7 @@ const Dataset = () => { | |||
| const onFinish = (values) => {}; | |||
| const routeToIntro = (e, record) => { | |||
| e.stopPropagation(); | |||
| navgite({ pathname: '/dataset/datasetIntro' }); | |||
| navgite({ pathname: '/dataset/dataset' }); | |||
| }; | |||
| const onFinishFailed = (errorInfo) => { | |||
| console.log('Failed:', errorInfo); | |||
| @@ -121,14 +121,14 @@ const PublicData = () => { | |||
| const chooseModelType = (val, item) => { | |||
| console.log(val, item); | |||
| if (item.path == queryFlow.model_type) { | |||
| if (item.id == queryFlow.model_type) { | |||
| setActiveType(''); | |||
| setQueryFlow({ ...queryFlow, model_type: null }); | |||
| getModelLists({ ...queryFlow, model_type: null }); | |||
| } else { | |||
| setActiveType(item.path); | |||
| setQueryFlow({ ...queryFlow, model_type: item.path }); | |||
| getModelLists({ ...queryFlow, model_type: item.path }); | |||
| setActiveType(item.id); | |||
| setQueryFlow({ ...queryFlow, model_type: item.id }); | |||
| getModelLists({ ...queryFlow, model_type: item.id }); | |||
| } | |||
| // setQueryFlow({...queryFlow,data_type:item.path},()=>{ | |||
| @@ -136,14 +136,14 @@ const PublicData = () => { | |||
| // }) | |||
| }; | |||
| const chooseModelTag = (val, item) => { | |||
| if (item.path == queryFlow.model_tag) { | |||
| if (item.id == queryFlow.model_tag) { | |||
| setActiveTag(''); | |||
| setQueryFlow({ ...queryFlow, model_tag: null }); | |||
| getModelLists({ ...queryFlow, model_tag: null }); | |||
| } else { | |||
| setActiveTag(item.path); | |||
| setQueryFlow({ ...queryFlow, model_tag: item.path }); | |||
| getModelLists({ ...queryFlow, model_tag: item.path }); | |||
| setActiveTag(item.id); | |||
| setQueryFlow({ ...queryFlow, model_tag: item.id }); | |||
| getModelLists({ ...queryFlow, model_tag: item.id }); | |||
| } | |||
| // setQueryFlow({...queryFlow,data_type:item.path},()=>{ | |||
| // getDatasetlist() | |||
| @@ -152,7 +152,7 @@ const PublicData = () => { | |||
| const routeToIntro = (e, record) => { | |||
| e.stopPropagation(); | |||
| console.log(record); | |||
| navgite({ pathname: `/dataset/modelIntro/${record.id}` }); | |||
| navgite({ pathname: `/dataset/model/${record.id}` }); | |||
| }; | |||
| const onFinishFailed = (errorInfo) => { | |||
| console.log('Failed:', errorInfo); | |||
| @@ -190,7 +190,7 @@ const PublicData = () => { | |||
| <div | |||
| className={[ | |||
| Styles.messageBox, | |||
| item.path === activeType ? Styles.active : null, | |||
| item.id === activeType ? Styles.active : null, | |||
| ].join(' ')} | |||
| onClick={(e) => { | |||
| chooseModelType(e, item); | |||
| @@ -231,7 +231,7 @@ const PublicData = () => { | |||
| <div | |||
| className={[ | |||
| Styles.messageBox, | |||
| item.path === activeTag ? Styles.active : null, | |||
| item.id === activeTag ? Styles.active : null, | |||
| ].join(' ')} | |||
| onClick={(e) => { | |||
| chooseModelTag(e, item); | |||
| @@ -77,14 +77,14 @@ const PublicData = () => { | |||
| }; | |||
| const chooseModelType = (val, item) => { | |||
| console.log(val, item); | |||
| if (item.path == queryFlow.model_type) { | |||
| if (item.id == queryFlow.model_type) { | |||
| setActiveType(''); | |||
| setQueryFlow({ ...queryFlow, model_type: null }); | |||
| getModelLists({ ...queryFlow, model_type: null }); | |||
| } else { | |||
| setActiveType(item.path); | |||
| setQueryFlow({ ...queryFlow, model_type: item.path }); | |||
| getModelLists({ ...queryFlow, model_type: item.path }); | |||
| setActiveType(item.id); | |||
| setQueryFlow({ ...queryFlow, model_type: item.id }); | |||
| getModelLists({ ...queryFlow, model_type: item.id }); | |||
| } | |||
| // setQueryFlow({...queryFlow,data_type:item.path},()=>{ | |||
| @@ -92,14 +92,14 @@ const PublicData = () => { | |||
| // }) | |||
| }; | |||
| const chooseModelTag = (val, item) => { | |||
| if (item.path == queryFlow.model_tag) { | |||
| if (item.id == queryFlow.model_tag) { | |||
| setActiveTag(''); | |||
| setQueryFlow({ ...queryFlow, model_tag: null }); | |||
| getModelLists({ ...queryFlow, model_tag: null }); | |||
| } else { | |||
| setActiveTag(item.path); | |||
| setQueryFlow({ ...queryFlow, model_tag: item.path }); | |||
| getModelLists({ ...queryFlow, model_tag: item.path }); | |||
| setActiveTag(item.id); | |||
| setQueryFlow({ ...queryFlow, model_tag: item.id }); | |||
| getModelLists({ ...queryFlow, model_tag: item.id }); | |||
| } | |||
| // setQueryFlow({...queryFlow,data_type:item.path},()=>{ | |||
| // getDatasetlist() | |||
| @@ -109,7 +109,7 @@ const PublicData = () => { | |||
| const routeToIntro = (e, record) => { | |||
| e.stopPropagation(); | |||
| console.log(record); | |||
| navgite({ pathname: `/dataset/modelIntro/${record.id}` }); | |||
| navgite({ pathname: `/dataset/model/${record.id}` }); | |||
| }; | |||
| const onFinishFailed = (errorInfo) => { | |||
| console.log('Failed:', errorInfo); | |||
| @@ -147,7 +147,7 @@ const PublicData = () => { | |||
| <div | |||
| className={[ | |||
| Styles.messageBox, | |||
| item.path === activeType ? Styles.active : null, | |||
| item.id === activeType ? Styles.active : null, | |||
| ].join(' ')} | |||
| onClick={(e) => { | |||
| chooseModelType(e, item); | |||
| @@ -181,7 +181,7 @@ const PublicData = () => { | |||
| <div | |||
| className={[ | |||
| Styles.messageBox, | |||
| item.path === activeTag ? Styles.active : null, | |||
| item.id === activeTag ? Styles.active : null, | |||
| ].join(' ')} | |||
| onClick={(e) => { | |||
| chooseModelTag(e, item); | |||
| @@ -1,9 +1,9 @@ | |||
| import { DictValueEnumObj } from '@/components/DictTag'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { getValueEnumLabel } from '@/utils/options'; | |||
| import { FormattedMessage, useIntl } from '@umijs/max'; | |||
| import { Button, Descriptions, Modal } from 'antd'; | |||
| import { Button, Descriptions } from 'antd'; | |||
| import React, { useEffect } from 'react'; | |||
| /* * | |||
| * | |||
| * @author whiteshader@163.com | |||
| @@ -39,7 +39,7 @@ const OperlogForm: React.FC<OperlogFormProps> = (props) => { | |||
| }; | |||
| return ( | |||
| <Modal | |||
| <KFModal | |||
| width={800} | |||
| title={intl.formatMessage({ | |||
| id: 'monitor.job.detail', | |||
| @@ -124,7 +124,7 @@ const OperlogForm: React.FC<OperlogFormProps> = (props) => { | |||
| {values.invokeTarget} | |||
| </Descriptions.Item> | |||
| </Descriptions> | |||
| </Modal> | |||
| </KFModal> | |||
| ); | |||
| }; | |||
| @@ -11,7 +11,7 @@ import { | |||
| import { FormattedMessage, useIntl } from '@umijs/max'; | |||
| import { Form, Modal } from 'antd'; | |||
| import React, { useEffect } from 'react'; | |||
| import KFModal from '@/components/KFModal'; | |||
| /** | |||
| * 定时任务调度 Edit Form | |||
| * | |||
| @@ -66,7 +66,7 @@ const JobForm: React.FC<JobFormProps> = (props) => { | |||
| }; | |||
| return ( | |||
| <Modal | |||
| <KFModal | |||
| width={640} | |||
| title={intl.formatMessage({ | |||
| id: 'monitor.job.title', | |||
| @@ -238,7 +238,7 @@ const JobForm: React.FC<JobFormProps> = (props) => { | |||
| ]} | |||
| /> | |||
| </ProForm> | |||
| </Modal> | |||
| </KFModal> | |||
| ); | |||
| }; | |||
| @@ -1,16 +1,10 @@ | |||
| import { DictValueEnumObj } from '@/components/DictTag'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { getValueEnumLabel } from '@/utils/options'; | |||
| import { FormattedMessage, useIntl } from '@umijs/max'; | |||
| import { Descriptions, Modal } from 'antd'; | |||
| import { Descriptions } from 'antd'; | |||
| import React, { useEffect } from 'react'; | |||
| /* * | |||
| * | |||
| * @author whiteshader@163.com | |||
| * @datetime 2021/09/16 | |||
| * | |||
| * */ | |||
| export type JobLogFormValueType = Record<string, unknown> & Partial<API.Monitor.JobLog>; | |||
| export type JobLogFormProps = { | |||
| @@ -33,7 +27,7 @@ const JobLogDetailForm: React.FC<JobLogFormProps> = (props) => { | |||
| }; | |||
| return ( | |||
| <Modal | |||
| <KFModal | |||
| width={640} | |||
| title={intl.formatMessage({ | |||
| id: 'monitor.job.log.title', | |||
| @@ -95,7 +89,7 @@ const JobLogDetailForm: React.FC<JobLogFormProps> = (props) => { | |||
| {getValueEnumLabel(statusOptions, values.status, '未知')} | |||
| </Descriptions.Item> | |||
| </Descriptions> | |||
| </Modal> | |||
| </KFModal> | |||
| ); | |||
| }; | |||
| @@ -22,7 +22,7 @@ | |||
| height: 398px; | |||
| margin-right: 15px; | |||
| padding: 15px; | |||
| background-color: @background-color-primay; | |||
| background-color: @background-color-primary; | |||
| border: 1px solid @border-color; | |||
| border-radius: 8px; | |||
| @@ -31,7 +31,7 @@ | |||
| padding-left: 0; | |||
| background-color: transparent; | |||
| border-width: 0; | |||
| border-bottom: 1px solid @border-color-second; | |||
| border-bottom: 1px solid @border-color-secondary; | |||
| border-radius: 0; | |||
| } | |||
| } | |||
| @@ -40,7 +40,7 @@ | |||
| width: calc(100% - 488px - 15px); | |||
| height: 398px; | |||
| padding: 15px; | |||
| background-color: @background-color-primay; | |||
| background-color: @background-color-primary; | |||
| border: 1px solid @border-color; | |||
| border-radius: 8px; | |||
| @@ -49,7 +49,7 @@ | |||
| padding: 3px 0 6px; | |||
| color: @text-color; | |||
| font-size: @font-size; | |||
| border-bottom: 1px solid @border-color-second; | |||
| border-bottom: 1px solid @border-color-secondary; | |||
| } | |||
| &__files { | |||
| height: calc(100% - 75px); | |||
| @@ -1,4 +1,5 @@ | |||
| import { DictValueEnumObj } from '@/components/DictTag'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { | |||
| ProForm, | |||
| ProFormDigit, | |||
| @@ -7,9 +8,8 @@ import { | |||
| ProFormTextArea, | |||
| } from '@ant-design/pro-components'; | |||
| import { FormattedMessage, useIntl } from '@umijs/max'; | |||
| import { Form, Modal } from 'antd'; | |||
| import { Form } from 'antd'; | |||
| import React, { useEffect } from 'react'; | |||
| export type ConfigFormData = Record<string, unknown> & Partial<API.System.Config>; | |||
| export type ConfigFormProps = { | |||
| @@ -53,7 +53,7 @@ const ConfigForm: React.FC<ConfigFormProps> = (props) => { | |||
| }; | |||
| return ( | |||
| <Modal | |||
| <KFModal | |||
| width={640} | |||
| title={intl.formatMessage({ | |||
| id: 'system.config.title', | |||
| @@ -166,7 +166,7 @@ const ConfigForm: React.FC<ConfigFormProps> = (props) => { | |||
| ]} | |||
| /> | |||
| </ProForm> | |||
| </Modal> | |||
| </KFModal> | |||
| ); | |||
| }; | |||
| @@ -1,4 +1,5 @@ | |||
| import { DictValueEnumObj } from '@/components/DictTag'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { | |||
| ProForm, | |||
| ProFormDigit, | |||
| @@ -7,10 +8,9 @@ import { | |||
| ProFormTreeSelect, | |||
| } from '@ant-design/pro-components'; | |||
| import { FormattedMessage, useIntl } from '@umijs/max'; | |||
| import { Form, Modal } from 'antd'; | |||
| import { Form } from 'antd'; | |||
| import { DataNode } from 'antd/es/tree'; | |||
| import React, { useEffect } from 'react'; | |||
| export type DeptFormData = Record<string, unknown> & Partial<API.System.Dept>; | |||
| export type DeptFormProps = { | |||
| @@ -59,7 +59,7 @@ const DeptForm: React.FC<DeptFormProps> = (props) => { | |||
| }; | |||
| return ( | |||
| <Modal | |||
| <KFModal | |||
| width={640} | |||
| title={intl.formatMessage({ | |||
| id: 'system.dept.title', | |||
| @@ -204,7 +204,7 @@ const DeptForm: React.FC<DeptFormProps> = (props) => { | |||
| ]} | |||
| /> | |||
| </ProForm> | |||
| </Modal> | |||
| </KFModal> | |||
| ); | |||
| }; | |||
| @@ -1,4 +1,5 @@ | |||
| import { DictValueEnumObj } from '@/components/DictTag'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { | |||
| ProForm, | |||
| ProFormDigit, | |||
| @@ -7,9 +8,8 @@ import { | |||
| ProFormTextArea, | |||
| } from '@ant-design/pro-components'; | |||
| import { FormattedMessage, useIntl } from '@umijs/max'; | |||
| import { Form, Modal } from 'antd'; | |||
| import { Form } from 'antd'; | |||
| import React, { useEffect } from 'react'; | |||
| export type DictTypeFormData = Record<string, unknown> & Partial<API.System.DictType>; | |||
| export type DictTypeFormProps = { | |||
| @@ -52,7 +52,7 @@ const DictTypeForm: React.FC<DictTypeFormProps> = (props) => { | |||
| }; | |||
| return ( | |||
| <Modal | |||
| <KFModal | |||
| width={640} | |||
| title={intl.formatMessage({ | |||
| id: 'system.dict.title', | |||
| @@ -146,7 +146,7 @@ const DictTypeForm: React.FC<DictTypeFormProps> = (props) => { | |||
| ]} | |||
| /> | |||
| </ProForm> | |||
| </Modal> | |||
| </KFModal> | |||
| ); | |||
| }; | |||
| @@ -1,4 +1,5 @@ | |||
| import { DictValueEnumObj } from '@/components/DictTag'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { | |||
| ProForm, | |||
| ProFormDigit, | |||
| @@ -8,9 +9,8 @@ import { | |||
| ProFormTextArea, | |||
| } from '@ant-design/pro-components'; | |||
| import { FormattedMessage, useIntl } from '@umijs/max'; | |||
| import { Form, Modal } from 'antd'; | |||
| import { Form } from 'antd'; | |||
| import React, { useEffect } from 'react'; | |||
| export type DataFormData = Record<string, unknown> & Partial<API.System.DictData>; | |||
| export type DataFormProps = { | |||
| @@ -58,7 +58,7 @@ const DictDataForm: React.FC<DataFormProps> = (props) => { | |||
| }; | |||
| return ( | |||
| <Modal | |||
| <KFModal | |||
| width={640} | |||
| title={intl.formatMessage({ | |||
| id: 'system.dict.data.title', | |||
| @@ -246,7 +246,7 @@ const DictDataForm: React.FC<DataFormProps> = (props) => { | |||
| ]} | |||
| /> | |||
| </ProForm> | |||
| </Modal> | |||
| </KFModal> | |||
| ); | |||
| }; | |||
| @@ -1,4 +1,5 @@ | |||
| import { DictValueEnumObj } from '@/components/DictTag'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { | |||
| ProForm, | |||
| ProFormDigit, | |||
| @@ -7,9 +8,8 @@ import { | |||
| ProFormTimePicker, | |||
| } from '@ant-design/pro-components'; | |||
| import { FormattedMessage, useIntl } from '@umijs/max'; | |||
| import { Form, Modal } from 'antd'; | |||
| import { Form } from 'antd'; | |||
| import React, { useEffect } from 'react'; | |||
| export type LogininforFormData = Record<string, unknown> & Partial<API.Monitor.Logininfor>; | |||
| export type LogininforFormProps = { | |||
| @@ -53,7 +53,7 @@ const LogininforForm: React.FC<LogininforFormProps> = (props) => { | |||
| }; | |||
| return ( | |||
| <Modal | |||
| <KFModal | |||
| width={640} | |||
| title={intl.formatMessage({ | |||
| id: 'system.logininfor.title', | |||
| @@ -209,7 +209,7 @@ const LogininforForm: React.FC<LogininforFormProps> = (props) => { | |||
| ]} | |||
| /> | |||
| </ProForm> | |||
| </Modal> | |||
| </KFModal> | |||
| ); | |||
| }; | |||
| @@ -1,5 +1,6 @@ | |||
| import { DictValueEnumObj } from '@/components/DictTag'; | |||
| import IconSelector from '@/components/IconSelector'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { createIcon } from '@/utils/IconUtil'; | |||
| import { | |||
| ProForm, | |||
| @@ -10,7 +11,7 @@ import { | |||
| ProFormTreeSelect, | |||
| } from '@ant-design/pro-components'; | |||
| import { FormattedMessage, useIntl } from '@umijs/max'; | |||
| import { Form, Modal } from 'antd'; | |||
| import { Form } from 'antd'; | |||
| import { DataNode } from 'antd/es/tree'; | |||
| import React, { useEffect, useState } from 'react'; | |||
| @@ -73,7 +74,7 @@ const MenuForm: React.FC<MenuFormProps> = (props) => { | |||
| }; | |||
| return ( | |||
| <Modal | |||
| <KFModal | |||
| width={640} | |||
| title={intl.formatMessage({ | |||
| id: 'system.menu.title', | |||
| @@ -367,7 +368,7 @@ const MenuForm: React.FC<MenuFormProps> = (props) => { | |||
| }} | |||
| /> | |||
| </ProForm> | |||
| <Modal | |||
| <KFModal | |||
| width={800} | |||
| open={iconSelectorOpen} | |||
| onCancel={() => { | |||
| @@ -382,8 +383,8 @@ const MenuForm: React.FC<MenuFormProps> = (props) => { | |||
| setIconSelectorOpen(false); | |||
| }} | |||
| /> | |||
| </Modal> | |||
| </Modal> | |||
| </KFModal> | |||
| </KFModal> | |||
| ); | |||
| }; | |||
| @@ -1,4 +1,5 @@ | |||
| import { DictValueEnumObj } from '@/components/DictTag'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { | |||
| ProForm, | |||
| ProFormDigit, | |||
| @@ -8,9 +9,8 @@ import { | |||
| ProFormTextArea, | |||
| } from '@ant-design/pro-components'; | |||
| import { FormattedMessage, useIntl } from '@umijs/max'; | |||
| import { Form, Modal } from 'antd'; | |||
| import { Form } from 'antd'; | |||
| import React, { useEffect } from 'react'; | |||
| export type NoticeFormData = Record<string, unknown> & Partial<API.System.Notice>; | |||
| export type NoticeFormProps = { | |||
| @@ -55,7 +55,7 @@ const NoticeForm: React.FC<NoticeFormProps> = (props) => { | |||
| }; | |||
| return ( | |||
| <Modal | |||
| <KFModal | |||
| width={640} | |||
| title={intl.formatMessage({ | |||
| id: 'system.notice.title', | |||
| @@ -168,7 +168,7 @@ const NoticeForm: React.FC<NoticeFormProps> = (props) => { | |||
| ]} | |||
| /> | |||
| </ProForm> | |||
| </Modal> | |||
| </KFModal> | |||
| ); | |||
| }; | |||
| @@ -1,9 +1,9 @@ | |||
| import { DictValueEnumObj } from '@/components/DictTag'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { getValueEnumLabel } from '@/utils/options'; | |||
| import { FormattedMessage, useIntl } from '@umijs/max'; | |||
| import { Descriptions, Modal } from 'antd'; | |||
| import { Descriptions } from 'antd'; | |||
| import React from 'react'; | |||
| export type OperlogFormData = Record<string, unknown> & Partial<API.Monitor.Operlog>; | |||
| export type OperlogFormProps = { | |||
| @@ -28,7 +28,7 @@ const OperlogDetailForm: React.FC<OperlogFormProps> = (props) => { | |||
| }; | |||
| return ( | |||
| <Modal | |||
| <KFModal | |||
| width={640} | |||
| title={intl.formatMessage({ | |||
| id: 'monitor.operlog.title', | |||
| @@ -107,7 +107,7 @@ const OperlogDetailForm: React.FC<OperlogFormProps> = (props) => { | |||
| {values.operTime?.toString()} | |||
| </Descriptions.Item> | |||
| </Descriptions> | |||
| </Modal> | |||
| </KFModal> | |||
| ); | |||
| }; | |||
| @@ -1,4 +1,5 @@ | |||
| import { DictValueEnumObj } from '@/components/DictTag'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { | |||
| ProForm, | |||
| ProFormDigit, | |||
| @@ -7,9 +8,8 @@ import { | |||
| ProFormTextArea, | |||
| } from '@ant-design/pro-components'; | |||
| import { FormattedMessage, useIntl } from '@umijs/max'; | |||
| import { Form, Modal } from 'antd'; | |||
| import { Form } from 'antd'; | |||
| import React, { useEffect } from 'react'; | |||
| export type PostFormData = Record<string, unknown> & Partial<API.System.Post>; | |||
| export type PostFormProps = { | |||
| @@ -53,7 +53,7 @@ const PostForm: React.FC<PostFormProps> = (props) => { | |||
| }; | |||
| return ( | |||
| <Modal | |||
| <KFModal | |||
| width={640} | |||
| title={intl.formatMessage({ | |||
| id: 'system.post.title', | |||
| @@ -160,7 +160,7 @@ const PostForm: React.FC<PostFormProps> = (props) => { | |||
| ]} | |||
| /> | |||
| </ProForm> | |||
| </Modal> | |||
| </KFModal> | |||
| ); | |||
| }; | |||
| @@ -1,10 +1,10 @@ | |||
| import KFModal from '@/components/KFModal'; | |||
| import { Key, ProForm, ProFormDigit, ProFormSelect, ProFormText } from '@ant-design/pro-components'; | |||
| import { FormattedMessage, useIntl } from '@umijs/max'; | |||
| import { Checkbox, Col, Form, Modal, Row, Tree } from 'antd'; | |||
| import { Checkbox, Col, Form, Row, Tree } from 'antd'; | |||
| import { CheckboxValueType } from 'antd/es/checkbox/Group'; | |||
| import { DataNode } from 'antd/es/tree'; | |||
| import React, { useEffect, useState } from 'react'; | |||
| /* * | |||
| * | |||
| * @author whiteshader@163.com | |||
| @@ -90,7 +90,7 @@ const DataScopeForm: React.FC<DataScopeFormProps> = (props) => { | |||
| }; | |||
| return ( | |||
| <Modal | |||
| <KFModal | |||
| width={640} | |||
| title={intl.formatMessage({ | |||
| id: 'system.user.auth.role', | |||
| @@ -234,7 +234,7 @@ const DataScopeForm: React.FC<DataScopeFormProps> = (props) => { | |||
| </Row> | |||
| </ProForm.Item> | |||
| </ProForm> | |||
| </Modal> | |||
| </KFModal> | |||
| ); | |||
| }; | |||
| @@ -1,4 +1,5 @@ | |||
| import DictTag from '@/components/DictTag'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { getDictValueEnum } from '@/services/system/dict'; | |||
| import { | |||
| ActionType, | |||
| @@ -8,9 +9,7 @@ import { | |||
| RequestData, | |||
| } from '@ant-design/pro-components'; | |||
| import { FormattedMessage, useIntl } from '@umijs/max'; | |||
| import { Modal } from 'antd'; | |||
| import React, { useEffect, useRef, useState } from 'react'; | |||
| /* * | |||
| * | |||
| * @author whiteshader@163.com | |||
| @@ -90,7 +89,7 @@ const UserSelectorModal: React.FC<DataScopeFormProps> = (props) => { | |||
| ]; | |||
| return ( | |||
| <Modal | |||
| <KFModal | |||
| width={800} | |||
| title={intl.formatMessage({ | |||
| id: 'system.role.auth.user', | |||
| @@ -122,7 +121,7 @@ const UserSelectorModal: React.FC<DataScopeFormProps> = (props) => { | |||
| }, | |||
| }} | |||
| /> | |||
| </Modal> | |||
| </KFModal> | |||
| ); | |||
| }; | |||
| @@ -1,4 +1,5 @@ | |||
| import { DictValueEnumObj } from '@/components/DictTag'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { | |||
| ProForm, | |||
| ProFormDigit, | |||
| @@ -7,10 +8,9 @@ import { | |||
| ProFormTextArea, | |||
| } from '@ant-design/pro-components'; | |||
| import { FormattedMessage, useIntl } from '@umijs/max'; | |||
| import { Form, Modal } from 'antd'; | |||
| import { Form } from 'antd'; | |||
| import Tree, { DataNode } from 'antd/es/tree'; | |||
| import React, { useEffect, useState } from 'react'; | |||
| export type RoleFormData = Record<string, unknown> & Partial<API.System.Role>; | |||
| export type RoleFormProps = { | |||
| @@ -61,7 +61,7 @@ const RoleForm: React.FC<RoleFormProps> = (props) => { | |||
| }; | |||
| return ( | |||
| <Modal | |||
| <KFModal | |||
| width={640} | |||
| title={intl.formatMessage({ | |||
| id: 'system.role.title', | |||
| @@ -198,7 +198,7 @@ const RoleForm: React.FC<RoleFormProps> = (props) => { | |||
| ]} | |||
| /> | |||
| </ProForm> | |||
| </Modal> | |||
| </KFModal> | |||
| ); | |||
| }; | |||
| @@ -1,15 +1,8 @@ | |||
| import KFModal from '@/components/KFModal'; | |||
| import { ProForm, ProFormSelect } from '@ant-design/pro-components'; | |||
| import { useIntl } from '@umijs/max'; | |||
| import { Form, Modal } from 'antd'; | |||
| import { Form } from 'antd'; | |||
| import React, { useEffect } from 'react'; | |||
| /* * | |||
| * | |||
| * @author whiteshader@163.com | |||
| * @datetime 2023/02/06 | |||
| * | |||
| * */ | |||
| export type FormValueType = any & Partial<API.System.Dept>; | |||
| export type AuthRoleFormProps = { | |||
| @@ -40,7 +33,7 @@ const AuthRoleForm: React.FC<AuthRoleFormProps> = (props) => { | |||
| }; | |||
| return ( | |||
| <Modal | |||
| <KFModal | |||
| width={640} | |||
| title={intl.formatMessage({ | |||
| id: 'system.user.auth.role', | |||
| @@ -74,7 +67,7 @@ const AuthRoleForm: React.FC<AuthRoleFormProps> = (props) => { | |||
| rules={[{ required: true, message: '请选择角色!' }]} | |||
| /> | |||
| </ProForm> | |||
| </Modal> | |||
| </KFModal> | |||
| ); | |||
| }; | |||
| @@ -1,15 +1,9 @@ | |||
| import KFModal from '@/components/KFModal'; | |||
| import { ProForm, ProFormText } from '@ant-design/pro-components'; | |||
| import { useIntl } from '@umijs/max'; | |||
| import { Form, Modal } from 'antd'; | |||
| import { Form } from 'antd'; | |||
| import React from 'react'; | |||
| /* * | |||
| * | |||
| * @author whiteshader@163.com | |||
| * @datetime 2023/02/06 | |||
| * | |||
| * */ | |||
| export type FormValueType = any & Partial<API.System.User>; | |||
| export type UpdateFormProps = { | |||
| @@ -44,7 +38,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => { | |||
| }; | |||
| return ( | |||
| <Modal | |||
| <KFModal | |||
| width={640} | |||
| title={intl.formatMessage({ | |||
| id: 'system.user.reset.password', | |||
| @@ -88,7 +82,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => { | |||
| ]} | |||
| /> | |||
| </ProForm> | |||
| </Modal> | |||
| </KFModal> | |||
| ); | |||
| }; | |||
| @@ -1,4 +1,5 @@ | |||
| import { DictValueEnumObj } from '@/components/DictTag'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { | |||
| ProForm, | |||
| ProFormRadio, | |||
| @@ -8,17 +9,10 @@ import { | |||
| ProFormTreeSelect, | |||
| } from '@ant-design/pro-components'; | |||
| import { FormattedMessage, useIntl } from '@umijs/max'; | |||
| import { Form, Modal } from 'antd'; | |||
| import { Form } from 'antd'; | |||
| import { DataNode } from 'antd/es/tree'; | |||
| import React, { useEffect } from 'react'; | |||
| /* * | |||
| * | |||
| * @author whiteshader@163.com | |||
| * @datetime 2023/02/06 | |||
| * | |||
| * */ | |||
| export type UserFormData = Record<string, unknown> & Partial<API.System.User>; | |||
| export type UserFormProps = { | |||
| @@ -74,7 +68,7 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||
| }; | |||
| return ( | |||
| <Modal | |||
| <KFModal | |||
| width={640} | |||
| title={intl.formatMessage({ | |||
| id: 'system.user.title', | |||
| @@ -260,7 +254,7 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||
| ]} | |||
| /> | |||
| </ProForm> | |||
| </Modal> | |||
| </KFModal> | |||
| ); | |||
| }; | |||
| @@ -1,10 +1,10 @@ | |||
| import KFModal from '@/components/KFModal'; | |||
| import { useIntl } from '@umijs/max'; | |||
| import type { TabsProps } from 'antd'; | |||
| import { Modal, Tabs } from 'antd'; | |||
| import { Tabs } from 'antd'; | |||
| import 'highlight.js/styles/base16/material.css'; | |||
| import React, { useEffect } from 'react'; | |||
| import Highlight from 'react-highlight'; | |||
| interface PreviewTableProps { | |||
| open: boolean; | |||
| data?: any; | |||
| @@ -26,7 +26,7 @@ const PreviewTableCode: React.FC<PreviewTableProps> = (props) => { | |||
| useEffect(() => {}, []); | |||
| return ( | |||
| <Modal | |||
| <KFModal | |||
| width={900} | |||
| title={intl.formatMessage({ | |||
| id: 'gen.preview', | |||
| @@ -43,7 +43,7 @@ const PreviewTableCode: React.FC<PreviewTableProps> = (props) => { | |||
| }} | |||
| > | |||
| <Tabs defaultActiveKey="1" items={panes}></Tabs> | |||
| </Modal> | |||
| </KFModal> | |||
| ); | |||
| }; | |||
| @@ -1,3 +1,4 @@ | |||
| import KFModal from '@/components/KFModal'; | |||
| import { uploadAvatar } from '@/services/system/user'; | |||
| import { | |||
| MinusOutlined, | |||
| @@ -7,7 +8,7 @@ import { | |||
| UploadOutlined, | |||
| } from '@ant-design/icons'; | |||
| import { useIntl } from '@umijs/max'; | |||
| import { Button, Col, Modal, Row, Space, Upload, message } from 'antd'; | |||
| import { Button, Col, Row, Space, Upload, message } from 'antd'; | |||
| import React, { useEffect, useRef, useState } from 'react'; | |||
| import { Cropper } from 'react-cropper'; | |||
| import './cropper.css'; | |||
| @@ -88,7 +89,7 @@ const AvatarCropperForm: React.FC<AvatarCropperProps> = (props) => { | |||
| }; | |||
| }; | |||
| return ( | |||
| <Modal | |||
| <KFModal | |||
| width={800} | |||
| title={intl.formatMessage({ | |||
| id: 'system.user.modify_avatar', | |||
| @@ -137,7 +138,7 @@ const AvatarCropperForm: React.FC<AvatarCropperProps> = (props) => { | |||
| </Space> | |||
| </Col> | |||
| </Row> | |||
| </Modal> | |||
| </KFModal> | |||
| ); | |||
| }; | |||
| @@ -0,0 +1,54 @@ | |||
| .assets-management { | |||
| flex: 1; | |||
| width: 100%; | |||
| padding: 20px 20px 0; | |||
| background-color: white; | |||
| border-radius: 4px; | |||
| :global { | |||
| .ant-select-filled { | |||
| background-color: rgba(138, 138, 138, 0.12); | |||
| border-radius: 2px; | |||
| .ant-select-selection-item { | |||
| color: @text-color-secondary !important; | |||
| font-size: 13px; | |||
| } | |||
| } | |||
| } | |||
| &__title { | |||
| color: @text-color; | |||
| font-weight: 500; | |||
| font-size: @font-size-title; | |||
| } | |||
| &__increase { | |||
| display: inline-block; | |||
| margin-top: 12px; | |||
| margin-bottom: 30px; | |||
| padding: 2px 7px; | |||
| color: @primary-color-secondary; | |||
| font-size: 13px; | |||
| background-color: rgba(187, 210, 255, 0.29); | |||
| border-radius: 2px; | |||
| } | |||
| &__summary { | |||
| display: flex; | |||
| flex-direction: column; | |||
| width: 33.33%; | |||
| &__title { | |||
| margin-bottom: 12px; | |||
| color: @text-color-secondary; | |||
| font-size: @font-size; | |||
| } | |||
| &__value { | |||
| color: @text-color; | |||
| font-weight: 500; | |||
| font-size: 22px; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,81 @@ | |||
| import { CommonTabKeys } from '@/enums'; | |||
| import { getWorkspaceAssetCountReq } from '@/services/workspace'; | |||
| import { to } from '@/utils/promise'; | |||
| import { Flex, Select } from 'antd'; | |||
| import { useEffect, useState } from 'react'; | |||
| import styles from './index.less'; | |||
| function AssetsManagement() { | |||
| const [type, setType] = useState(CommonTabKeys.Public); | |||
| const [assetCounts, setAssetCounts] = useState<{ title: string; value: number }[]>([]); | |||
| useEffect(() => { | |||
| getWorkspacAssetCount(); | |||
| }, [type]); | |||
| // 获取工作空间资产数量 | |||
| const getWorkspacAssetCount = async () => { | |||
| const params = { | |||
| isPublic: type === CommonTabKeys.Public, | |||
| }; | |||
| const [res] = await to(getWorkspaceAssetCountReq(params)); | |||
| if (res && res.data) { | |||
| const { component, dataset, image, model, workflow } = res.data; | |||
| const items = [ | |||
| { | |||
| title: '数据集', | |||
| value: dataset, | |||
| }, | |||
| { | |||
| title: '模型', | |||
| value: model, | |||
| }, | |||
| { | |||
| title: '镜像', | |||
| value: image, | |||
| }, | |||
| { | |||
| title: '组件', | |||
| value: component, | |||
| }, | |||
| { | |||
| title: '代码配置', | |||
| value: 0, | |||
| }, | |||
| { | |||
| title: '流水线模版', | |||
| value: workflow, | |||
| }, | |||
| ]; | |||
| setAssetCounts(items); | |||
| } | |||
| }; | |||
| return ( | |||
| <div className={styles['assets-management']}> | |||
| <Flex justify="space-between"> | |||
| <div className={styles['assets-management__title']}>AI资产</div> | |||
| <Select | |||
| size="small" | |||
| value={type} | |||
| style={{ width: 70 }} | |||
| onChange={setType} | |||
| variant="filled" | |||
| options={[ | |||
| { value: CommonTabKeys.Public, label: '公开' }, | |||
| { value: CommonTabKeys.Private, label: '个人' }, | |||
| ]} | |||
| /> | |||
| </Flex> | |||
| <div className={styles['assets-management__increase']}>今日新增数量:5</div> | |||
| <Flex justify="space-between" gap="22px 0" wrap="wrap"> | |||
| {assetCounts.map((item, index) => ( | |||
| <div className={styles['assets-management__summary']} key={index}> | |||
| <div className={styles['assets-management__summary__title']}>{item.title}</div> | |||
| <div className={styles['assets-management__summary__value']}>{item.value}</div> | |||
| </div> | |||
| ))} | |||
| </Flex> | |||
| </div> | |||
| ); | |||
| } | |||
| export default AssetsManagement; | |||
| @@ -0,0 +1,7 @@ | |||
| .experiment-chart { | |||
| width: 295px; | |||
| min-width: 295px; | |||
| height: 140px; | |||
| background-color: @workspace-background; | |||
| border-radius: 4px; | |||
| } | |||
| @@ -0,0 +1,214 @@ | |||
| import themes from '@/styles/theme.less'; | |||
| import * as echarts from 'echarts'; | |||
| import React, { useEffect, useRef } from 'react'; | |||
| import styles from './index.less'; | |||
| const color1 = new echarts.graphic.LinearGradient( | |||
| 0, | |||
| 0, | |||
| 0, | |||
| 1, | |||
| [ | |||
| { offset: 0, color: '#c73131' }, | |||
| { offset: 1, color: '#ff7e96' }, | |||
| ], | |||
| false, | |||
| ); | |||
| const color2 = new echarts.graphic.LinearGradient( | |||
| 0, | |||
| 0, | |||
| 0, | |||
| 1, | |||
| [ | |||
| { offset: 0, color: '#6ac21d' }, | |||
| { offset: 1, color: '#96e850' }, | |||
| ], | |||
| false, | |||
| ); | |||
| const color3 = new echarts.graphic.LinearGradient( | |||
| 0, | |||
| 0, | |||
| 0, | |||
| 1, | |||
| [ | |||
| { offset: 0, color: '#8c8c8c' }, | |||
| { offset: 1, color: '#c8c6c6' }, | |||
| ], | |||
| false, | |||
| ); | |||
| const color4 = new echarts.graphic.LinearGradient( | |||
| 0, | |||
| 0, | |||
| 0, | |||
| 1, | |||
| [ | |||
| { offset: 0, color: '#ecb934' }, | |||
| { offset: 1, color: '#f0864d' }, | |||
| ], | |||
| false, | |||
| ); | |||
| const color5 = new echarts.graphic.LinearGradient( | |||
| 0, | |||
| 0, | |||
| 0, | |||
| 1, | |||
| [ | |||
| { offset: 0, color: '#7ea9fe' }, | |||
| { offset: 1, color: '#1664ff' }, | |||
| ], | |||
| false, | |||
| ); | |||
| const color6 = new echarts.graphic.LinearGradient( | |||
| 0, | |||
| 0, | |||
| 0, | |||
| 1, | |||
| [ | |||
| { offset: 0, color: 'rgba(255, 255, 255, 0.62)' }, | |||
| { offset: 1, color: '#ebf2ff ' }, | |||
| ], | |||
| false, | |||
| ); | |||
| export type ExperimentStatistics = { | |||
| Failed: number; | |||
| Pending: number; | |||
| Running: number; | |||
| Succeeded: number; | |||
| Terminated: number; | |||
| }; | |||
| type ExperimentChartProps = { | |||
| style?: React.CSSProperties; | |||
| chartData: ExperimentStatistics; | |||
| }; | |||
| function ExperimentChart({ chartData, style }: ExperimentChartProps) { | |||
| const chartRef = useRef<HTMLDivElement>(null); | |||
| const total = | |||
| chartData.Failed + | |||
| chartData.Pending + | |||
| chartData.Running + | |||
| chartData.Succeeded + | |||
| chartData.Terminated; | |||
| const options: echarts.EChartsOption = { | |||
| title: { | |||
| show: true, | |||
| left: '29%', | |||
| top: 'center', | |||
| textAlign: 'center', | |||
| text: [`{a|${total}}`, '{b|实验状态}'].join('\n'), | |||
| textStyle: { | |||
| rich: { | |||
| a: { | |||
| color: themes['textColor'], | |||
| fontSize: 20, | |||
| fontWeight: 700, | |||
| lineHeight: 28, | |||
| }, | |||
| b: { | |||
| color: themes['textColorSecondary'], | |||
| fontSize: 10, | |||
| fontWeight: 'normal', | |||
| }, | |||
| }, | |||
| }, | |||
| }, | |||
| tooltip: { | |||
| trigger: 'item', | |||
| }, | |||
| legend: { | |||
| top: 'center', | |||
| right: '5%', | |||
| orient: 'vertical', | |||
| icon: 'circle', | |||
| itemWidth: 6, | |||
| itemGap: 20, | |||
| height: 100, | |||
| }, | |||
| color: [color1, color2, color3, color4, color5], | |||
| series: [ | |||
| { | |||
| type: 'pie', | |||
| radius: ['70%', '80%'], | |||
| center: ['30%', '50%'], | |||
| avoidLabelOverlap: false, | |||
| padAngle: 3, | |||
| itemStyle: { | |||
| borderRadius: 3, | |||
| }, | |||
| label: { | |||
| show: false, | |||
| }, | |||
| emphasis: { | |||
| label: { | |||
| show: false, | |||
| }, | |||
| }, | |||
| labelLine: { | |||
| show: false, | |||
| }, | |||
| data: [ | |||
| { value: chartData.Failed, name: '失败' }, | |||
| { value: chartData.Succeeded, name: '成功' }, | |||
| { value: chartData.Terminated, name: '中止' }, | |||
| { value: chartData.Pending, name: '等待' }, | |||
| { value: chartData.Running, name: '运行中' }, | |||
| ], | |||
| }, | |||
| { | |||
| type: 'pie', | |||
| radius: '60%', | |||
| center: ['30%', '50%'], | |||
| avoidLabelOverlap: false, | |||
| label: { | |||
| show: false, | |||
| }, | |||
| tooltip: { | |||
| show: false, | |||
| }, | |||
| emphasis: { | |||
| label: { | |||
| show: false, | |||
| }, | |||
| disabled: true, | |||
| }, | |||
| animation: false, | |||
| labelLine: { | |||
| show: false, | |||
| }, | |||
| data: [ | |||
| { | |||
| value: 100, | |||
| itemStyle: { color: color6, borderColor: 'rgba(22, 100, 255, 0.08)', borderWidth: 1 }, | |||
| }, | |||
| ], | |||
| }, | |||
| ], | |||
| }; | |||
| useEffect(() => { | |||
| // 创建一个echarts实例,返回echarts实例 | |||
| const chart = echarts.init(chartRef.current); | |||
| // 设置图表实例的配置项和数据 | |||
| chart.setOption(options); | |||
| // 组件卸载 | |||
| return () => { | |||
| // myChart.dispose() 销毁实例 | |||
| chart.dispose(); | |||
| }; | |||
| }, []); | |||
| return ( | |||
| <div className={styles['experiment-chart']} style={style}> | |||
| <div style={{ width: '100%', height: '100%' }} ref={chartRef}></div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default ExperimentChart; | |||
| @@ -0,0 +1,58 @@ | |||
| .experiment-table { | |||
| flex: 1; | |||
| min-width: 500px; | |||
| height: 140px; | |||
| padding: 12px 24px; | |||
| background-color: @workspace-background; | |||
| border-radius: 4px; | |||
| &__header { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| margin-bottom: 4px; | |||
| color: @text-color; | |||
| font-size: @font-size; | |||
| line-height: 20px; | |||
| } | |||
| &__content { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| padding: 6px 0; | |||
| color: .addAlpha(@text-color, 0.75) []; | |||
| font-size: 14px; | |||
| line-height: 20px; | |||
| border-bottom: 1px solid rgba(234, 234, 234, 0.8); | |||
| &:last-child { | |||
| border-bottom: 0; | |||
| } | |||
| } | |||
| &__status { | |||
| width: 20%; | |||
| } | |||
| &__duration { | |||
| width: 25%; | |||
| } | |||
| &__date { | |||
| width: 35%; | |||
| } | |||
| &__operation { | |||
| width: 20%; | |||
| :global { | |||
| .ant-btn-link { | |||
| height: 20px; | |||
| padding: 0; | |||
| line-height: 20px; | |||
| border: 0; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,59 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { ExperimentStatusValues, experimentStatusInfo } from '@/pages/Experiment/status'; | |||
| import { ExperimentInstance } from '@/types'; | |||
| import { elapsedTime, formatDate } from '@/utils/date'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { Button } from 'antd'; | |||
| import styles from './index.less'; | |||
| type ExperimentTableProps = { | |||
| tableData: ExperimentInstance[]; | |||
| style?: React.CSSProperties; | |||
| }; | |||
| function ExperimentTable({ tableData = [], style }: ExperimentTableProps) { | |||
| const navgite = useNavigate(); | |||
| const gotoExperiment = (record: ExperimentInstance) => { | |||
| navgite(`/pipeline/experimentPytorchtext/${record.workflow_id}/${record.id}`); | |||
| }; | |||
| return ( | |||
| <div className={styles['experiment-table']} style={style}> | |||
| <div className={styles['experiment-table__header']}> | |||
| <div className={styles['experiment-table__status']}>状态</div> | |||
| <div className={styles['experiment-table__duration']}>运行时长</div> | |||
| <div className={styles['experiment-table__date']}>开始时间</div> | |||
| <div className={styles['experiment-table__operation']}>操作</div> | |||
| </div> | |||
| {tableData?.map((item) => ( | |||
| <div className={styles['experiment-table__content']} key={item.id}> | |||
| <div className={styles['experiment-table__status']} style={{ paddingLeft: '6.5px' }}> | |||
| <img | |||
| src={experimentStatusInfo[item.status as ExperimentStatusValues]?.icon} | |||
| width={17} | |||
| height={17} | |||
| /> | |||
| </div> | |||
| <div className={styles['experiment-table__duration']}> | |||
| {elapsedTime( | |||
| new Date(item.create_time), | |||
| item.finish_time ? new Date(item.finish_time) : new Date(), | |||
| )} | |||
| </div> | |||
| <div className={styles['experiment-table__date']}>{formatDate(item.create_time)}</div> | |||
| <div className={styles['experiment-table__operation']}> | |||
| <Button | |||
| size="small" | |||
| type="link" | |||
| icon={<KFIcon type="icon-xiangqing2" font={14} />} | |||
| onClick={() => gotoExperiment(item)} | |||
| > | |||
| 详情 | |||
| </Button> | |||
| </div> | |||
| </div> | |||
| ))} | |||
| </div> | |||
| ); | |||
| } | |||
| export default ExperimentTable; | |||
| @@ -0,0 +1,60 @@ | |||
| import styles from './index.less'; | |||
| type WorkArrowProps = { | |||
| width?: number; | |||
| height?: number; | |||
| x: number; | |||
| y: number; | |||
| arrowLeft: number; | |||
| arrorwTop: number; | |||
| borderLeft?: number; | |||
| borderTop?: number; | |||
| borderRight?: number; | |||
| borderBottom?: number; | |||
| arrrowAngle?: number; | |||
| }; | |||
| function WorkArrow({ | |||
| width = 1, | |||
| height = 1, | |||
| x, | |||
| y, | |||
| arrowLeft, | |||
| arrorwTop, | |||
| borderLeft = 0, | |||
| borderTop = 0, | |||
| borderRight = 0, | |||
| borderBottom = 0, | |||
| arrrowAngle = 0, | |||
| }: WorkArrowProps) { | |||
| return ( | |||
| <div | |||
| className={styles['work-arrow']} | |||
| style={{ | |||
| left: `${x}px`, | |||
| top: `${y}px`, | |||
| width: `${width}px`, | |||
| height: `${height}px`, | |||
| borderLeftWidth: `${borderLeft}px`, | |||
| borderTopWidth: `${borderTop}px`, | |||
| borderRightWidth: `${borderRight}px`, | |||
| borderBottomWidth: `${borderBottom}px`, | |||
| }} | |||
| > | |||
| <img | |||
| className={styles['work-arrow__img']} | |||
| src={require('@/assets/img/blue-triangle.png')} | |||
| alt="" | |||
| width={10} | |||
| height={9} | |||
| style={{ | |||
| left: `${arrowLeft}px`, | |||
| top: `${arrorwTop}px`, | |||
| transform: `rotate(${arrrowAngle}deg)`, | |||
| }} | |||
| /> | |||
| </div> | |||
| ); | |||
| } | |||
| export default WorkArrow; | |||
| @@ -0,0 +1,31 @@ | |||
| import { Button } from 'antd'; | |||
| import styles from './index.less'; | |||
| type WorkFlowProps = { | |||
| content: string; | |||
| buttonText: string; | |||
| tips?: string; | |||
| onClick?: () => void; | |||
| buttonTop?: number; | |||
| x: number; | |||
| y: number; | |||
| }; | |||
| function WorkFlow({ content, buttonText, tips, buttonTop = 20, x, y, onClick }: WorkFlowProps) { | |||
| return ( | |||
| <div className={styles['work-flow']} style={{ left: `${x}px`, top: `${y}px` }}> | |||
| {tips && <div className={styles['work-flow__tips']}>{tips}</div>} | |||
| <div className={styles['work-flow__content']}>{content}</div> | |||
| <Button | |||
| className={styles['work-flow__button']} | |||
| type="primary" | |||
| style={{ marginTop: `${buttonTop}px` }} | |||
| onClick={onClick} | |||
| > | |||
| {buttonText} | |||
| </Button> | |||
| </div> | |||
| ); | |||
| } | |||
| export default WorkFlow; | |||
| @@ -0,0 +1,96 @@ | |||
| .quick-start { | |||
| width: calc(100% - 326px); | |||
| padding: 20px 30px; | |||
| background-color: white; | |||
| border-radius: 4px; | |||
| &__title { | |||
| margin-bottom: 20px; | |||
| color: @text-color; | |||
| font-weight: 500; | |||
| font-size: @font-size-title; | |||
| } | |||
| &__content { | |||
| position: relative; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| width: 100%; | |||
| height: 610px; | |||
| background-image: url(@/assets/img/workspace-quick-start.png); | |||
| background-repeat: no-repeat; | |||
| background-position: top left; | |||
| background-size: 100% 100%; | |||
| &__canvas { | |||
| position: relative; | |||
| width: 1223px; | |||
| height: 610px; | |||
| transform-origin: center left; | |||
| &__model { | |||
| position: absolute; | |||
| top: 358px; | |||
| left: 920px; | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| color: @primary-color; | |||
| font-size: @font-size; | |||
| } | |||
| &__task { | |||
| position: absolute; | |||
| top: 110px; | |||
| left: 603px; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| width: 131px; | |||
| height: 41px; | |||
| color: @primary-color; | |||
| font-size: @font-size; | |||
| background-color: rgba(22, 100, 255, 0.05); | |||
| border: 1px dashed @primary-color; | |||
| border-radius: 4px; | |||
| box-shadow: 0px 0px 6px rgba(22, 100, 255, 0.07); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| .work-flow { | |||
| position: absolute; | |||
| width: 192px; | |||
| padding: 15px; | |||
| background-color: white; | |||
| border: 1px solid; | |||
| border-color: rgba(22, 100, 255, 0.08); | |||
| border-radius: 0px 8px 0px 0px; | |||
| box-shadow: 0px 0px 10px rgba(22, 100, 255, 0.06); | |||
| &__content { | |||
| color: @text-color-secondary; | |||
| font-size: @font-size; | |||
| } | |||
| &__tips { | |||
| position: absolute; | |||
| top: -16px; | |||
| left: 0; | |||
| padding: 4px 10px; | |||
| color: white; | |||
| font-size: @font-size; | |||
| background-color: #333333; | |||
| } | |||
| } | |||
| .work-arrow { | |||
| position: absolute; | |||
| border: 0 dashed @primary-color; | |||
| &__img { | |||
| position: absolute; | |||
| } | |||
| } | |||
| @@ -0,0 +1,149 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { debounce } from 'lodash'; | |||
| import { useEffect, useState } from 'react'; | |||
| import WorkArrow from './WorkArrow'; | |||
| import WorkFlow from './WorkFlow'; | |||
| import styles from './index.less'; | |||
| function QuickStart() { | |||
| const navgite = useNavigate(); | |||
| const [scale, setScale] = useState(1); | |||
| useEffect(() => { | |||
| const changeScale = () => { | |||
| const width = document.body.offsetWidth - 256 - 80 - 60 - 326 - 15 - 8; | |||
| const ratio = width >= 1223 ? 1 : width / 1223; | |||
| setScale(ratio); | |||
| }; | |||
| const debounceFunc = debounce(changeScale, 16); | |||
| window.addEventListener('resize', debounceFunc); | |||
| return () => { | |||
| window.removeEventListener('resize', debounceFunc); | |||
| }; | |||
| }, []); | |||
| return ( | |||
| <div className={styles['quick-start']}> | |||
| <div className={styles['quick-start__title']}>快速开始</div> | |||
| <div className={styles['quick-start__content']}> | |||
| <div | |||
| className={styles['quick-start__content__canvas']} | |||
| style={{ transform: `scale(${scale})` }} | |||
| > | |||
| <WorkFlow | |||
| content="为开发者提供数据智能标注与数据回流服务" | |||
| buttonText="数据准备" | |||
| buttonTop={40} | |||
| x={20} | |||
| y={309} | |||
| onClick={() => navgite('/datasetPreparation/datasetAnnotation')} | |||
| /> | |||
| <WorkFlow | |||
| content="为开发者提供定制化编辑器,开发者可根据自己需求选择配置,保存编译器中的调试环境为镜像供训练使用" | |||
| buttonText="开发环境" | |||
| buttonTop={20} | |||
| x={248} | |||
| y={301} | |||
| onClick={() => navgite('/developmentEnvironment')} | |||
| /> | |||
| <WorkFlow | |||
| content="为开发者提供定制化编辑器,开发者可根据自己需求选择配置,保存编译器中的调试环境为镜像供训练使用" | |||
| tips="可视化建模Designer" | |||
| buttonText="流水线" | |||
| buttonTop={20} | |||
| x={476} | |||
| y={276} | |||
| onClick={() => navgite('/pipeline/pipelineText')} | |||
| /> | |||
| <WorkFlow | |||
| content="开发者可以在这里运行流水线模板,产生实验实例,对比实验训练过程与产生的实验训练数据" | |||
| buttonText="实验" | |||
| buttonTop={40} | |||
| x={699} | |||
| y={295} | |||
| onClick={() => navgite('/pipeline/experimentText')} | |||
| /> | |||
| <WorkFlow | |||
| content="支持异构硬件(CPU/GPU)的模型加载,高吞吐,低延迟;支持大规模复杂模型的一键部署,实时弹性扩缩容;提供完整的运维监控体系。" | |||
| tips="模型在线服务" | |||
| buttonText="模型在线部署" | |||
| buttonTop={20} | |||
| x={1010} | |||
| y={263} | |||
| onClick={() => navgite('/modelDseployment')} | |||
| /> | |||
| <div className={styles['quick-start__content__canvas__model']}> | |||
| <KFIcon type="icon-moxingguanli" font={38} /> | |||
| <span>模型管理</span> | |||
| </div> | |||
| <div className={styles['quick-start__content__canvas__task']}> | |||
| <KFIcon type="icon-tiaoduguanli" font={13} style={{ marginRight: '5px' }} /> | |||
| <span>任务自动调度</span> | |||
| </div> | |||
| <WorkArrow | |||
| x={213} | |||
| y={378} | |||
| width={22} | |||
| height={1} | |||
| arrowLeft={22} | |||
| arrorwTop={-4} | |||
| borderBottom={1} | |||
| /> | |||
| <WorkArrow | |||
| x={441} | |||
| y={378} | |||
| width={22} | |||
| height={1} | |||
| arrowLeft={22} | |||
| arrorwTop={-4} | |||
| borderBottom={1} | |||
| /> | |||
| <WorkArrow | |||
| x={893} | |||
| y={378} | |||
| width={22} | |||
| height={1} | |||
| arrowLeft={22} | |||
| arrorwTop={-4} | |||
| borderBottom={1} | |||
| /> | |||
| <WorkArrow | |||
| x={974} | |||
| y={378} | |||
| width={22} | |||
| height={1} | |||
| arrowLeft={22} | |||
| arrorwTop={-4} | |||
| borderBottom={1} | |||
| /> | |||
| <WorkArrow | |||
| x={532} | |||
| y={139} | |||
| width={54} | |||
| height={125} | |||
| arrowLeft={54} | |||
| arrorwTop={-4} | |||
| borderLeft={1} | |||
| borderTop={1} | |||
| /> | |||
| <WorkArrow | |||
| x={740} | |||
| y={127} | |||
| width={49} | |||
| height={156} | |||
| arrowLeft={44} | |||
| arrorwTop={156} | |||
| arrrowAngle={90} | |||
| borderRight={1} | |||
| borderTop={1} | |||
| /> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default QuickStart; | |||
| @@ -0,0 +1,41 @@ | |||
| .total-statistics { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| width: 400px; | |||
| height: 140px; | |||
| background-color: @workspace-background; | |||
| border-radius: 4px; | |||
| &__icon { | |||
| width: 85px; | |||
| height: 80px; | |||
| margin-right: 40px; | |||
| } | |||
| &__title { | |||
| position: relative; | |||
| margin-bottom: 6px; | |||
| color: @text-color-secondary; | |||
| font-size: @font-size-content; | |||
| } | |||
| &__title-shadow { | |||
| position: absolute; | |||
| bottom: 6px; | |||
| left: 0; | |||
| width: 79px; | |||
| height: 6px; | |||
| background-color: linear-gradient( | |||
| 87.07deg, | |||
| rgba(22, 100, 255, 0.6) 0%, | |||
| rgba(22, 100, 255, 0) 100% | |||
| ); | |||
| } | |||
| &__count { | |||
| color: @text-color; | |||
| font-weight: 700; | |||
| font-size: 25px; | |||
| } | |||
| } | |||
| @@ -0,0 +1,25 @@ | |||
| import styles from './index.less'; | |||
| type TotalStatisticsProps = { | |||
| icon: string; | |||
| title: string; | |||
| count?: string | number; | |||
| style?: React.CSSProperties; | |||
| }; | |||
| function TotalStatistics({ icon = '', title = '', count = 0, style }: TotalStatisticsProps) { | |||
| return ( | |||
| <div className={styles['total-statistics']} style={style}> | |||
| <img className={styles['total-statistics__icon']} src={icon} /> | |||
| <div> | |||
| <div className={styles['total-statistics__title']}> | |||
| <span>{title}</span> | |||
| <div className={styles['total-statistics__title-shadow']}></div> | |||
| </div> | |||
| <div className={styles['total-statistics__count']}>{count ?? '--'}</div> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default TotalStatistics; | |||
| @@ -0,0 +1,65 @@ | |||
| .user-space { | |||
| margin-bottom: 16px; | |||
| padding-bottom: 20px; | |||
| background-color: white; | |||
| border-radius: 4px; | |||
| &__title { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| width: 100%; | |||
| height: 107px; | |||
| color: @text-color; | |||
| font-size: @font-size-title; | |||
| background-image: url(@/assets/img/workspace-user.png); | |||
| background-repeat: no-repeat; | |||
| background-position: top left; | |||
| background-size: 100% 100%; | |||
| } | |||
| &__avatar { | |||
| position: relative; | |||
| top: -28px; | |||
| width: 56px; | |||
| height: 56px; | |||
| } | |||
| &__name { | |||
| margin-top: -20px; | |||
| margin-bottom: 8px; | |||
| color: @text-color; | |||
| font-size: @font-size-content; | |||
| } | |||
| &__role { | |||
| display: inline-block; | |||
| padding: 1px 7px; | |||
| color: @primary-color-secondary; | |||
| font-size: 13px; | |||
| background-color: rgba(187, 210, 255, 0.29); | |||
| border-radius: 2px; | |||
| } | |||
| &__participant { | |||
| &__title { | |||
| color: @text-color-secondary; | |||
| font-size: @font-size-content; | |||
| } | |||
| &__count { | |||
| width: 24px; | |||
| height: 24px; | |||
| color: #8a8a8a; | |||
| font-size: 12px; | |||
| line-height: 24px; | |||
| text-align: center; | |||
| background-color: rgba(153, 153, 153, 0.13); | |||
| border-radius: 50%; | |||
| } | |||
| &__user { | |||
| width: 36px; | |||
| height: 36px; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,47 @@ | |||
| import { useModel } from '@umijs/max'; | |||
| import { Divider, Flex, Space } from 'antd'; | |||
| import styles from './index.less'; | |||
| type UserSpaceProps = { | |||
| users: any[]; | |||
| }; | |||
| function UserSpace({ users = [] }: UserSpaceProps) { | |||
| const { initialState } = useModel('@@initialState'); | |||
| const { currentUser } = initialState || {}; | |||
| return ( | |||
| <div className={styles['user-space']}> | |||
| <div className={styles['user-space__title']}>工作空间管理</div> | |||
| <div style={{ padding: '0 20px' }}> | |||
| <img className={styles['user-space__avatar']} src={currentUser?.avatar} alt="" /> | |||
| <div className={styles['user-space__name']}>{currentUser?.nickName}</div> | |||
| <div className={styles['user-space__role']}>{currentUser?.roleNames?.[0]?.roleName}</div> | |||
| <Divider | |||
| dashed | |||
| style={{ borderColor: 'rgba(22, 100, 255, 0.19)', margin: '20px 0' }} | |||
| ></Divider> | |||
| <div className={styles['user-space__participant']}> | |||
| <Space align="center" size={10} style={{ marginBottom: '20px' }}> | |||
| <div className={styles['user-space__participant__title']}>参与者</div> | |||
| <div className={styles['user-space__participant__count']}>8</div> | |||
| </Space> | |||
| <Flex align="center" gap={12} wrap="wrap"> | |||
| {users?.map((item, index) => { | |||
| return ( | |||
| <img | |||
| className={styles['user-space__participant__user']} | |||
| key={index} | |||
| src={require(`@/assets/img/user-avatar/${index + 1}.png`)} | |||
| alt="" | |||
| /> | |||
| ); | |||
| })} | |||
| </Flex> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default UserSpace; | |||
| @@ -0,0 +1,43 @@ | |||
| .workspace-intro { | |||
| display: flex; | |||
| align-items: center; | |||
| margin-bottom: 16px; | |||
| padding: 0 30px; | |||
| background-image: url(@/assets/img/workspace-intro.png); | |||
| background-repeat: no-repeat; | |||
| background-position: top right; | |||
| background-size: 100% 100%; | |||
| border-radius: 4px; | |||
| &__left { | |||
| padding: 30px 0 34px; | |||
| } | |||
| &__right { | |||
| display: flex; | |||
| flex: 1; | |||
| align-items: center; | |||
| justify-content: center; | |||
| } | |||
| &__title { | |||
| margin-bottom: 20px; | |||
| color: @text-color; | |||
| font-weight: 500; | |||
| font-size: 20px; | |||
| } | |||
| &__content { | |||
| max-width: 980px; | |||
| margin-bottom: 20px; | |||
| color: @text-color-secondary; | |||
| font-size: @font-size-title; | |||
| line-height: 1.8; | |||
| letter-spacing: 1px; | |||
| } | |||
| &__icon { | |||
| width: 363px; | |||
| height: 216px; | |||
| } | |||
| } | |||
| @@ -0,0 +1,43 @@ | |||
| import { Button } from 'antd'; | |||
| import styles from './index.less'; | |||
| function WorkspaceIntro() { | |||
| return ( | |||
| <div className={styles['workspace-intro']}> | |||
| <div className={styles['workspace-intro__left']}> | |||
| <div className={styles['workspace-intro__title']}>自主实验平台</div> | |||
| <div className={styles['workspace-intro__content']}> | |||
| 材料领域的自主实验系统是一种用于材料研究和开发的技术平台,它旨在提供实验数据收集、分析和可视化等功能, | |||
| 以支持材料工程师、科学家和研究人员在材料设计、性能评估和工艺优化方面的工作 | |||
| </div> | |||
| <div className={styles['workspace-intro__buttons']}> | |||
| <Button | |||
| type="primary" | |||
| style={{ marginRight: '20px' }} | |||
| icon={ | |||
| <img src={require('@/assets/img/functional-material.png')} width={19} height={19} /> | |||
| } | |||
| > | |||
| 功能材料自主实验系统 | |||
| </Button> | |||
| <Button | |||
| type="default" | |||
| icon={ | |||
| <img src={require('@/assets/img/molecular-material.png')} width={19} height={19} /> | |||
| } | |||
| > | |||
| 分子材料自主实验系统 | |||
| </Button> | |||
| </div> | |||
| </div> | |||
| <div className={styles['workspace-intro__right']}> | |||
| <img | |||
| className={styles['workspace-intro__icon']} | |||
| src={require('@/assets/img/workspace-intro-icon.png')} | |||
| /> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default WorkspaceIntro; | |||
| @@ -0,0 +1,45 @@ | |||
| .workspace { | |||
| height: 100%; | |||
| padding: 20px 30px 10px; | |||
| overflow-y: auto; | |||
| background-color: linear-gradient(#ecf2fe, #f9fafb); | |||
| &__overview { | |||
| margin-bottom: 16px; | |||
| padding: 20px 30px; | |||
| background-color: white; | |||
| border-radius: 4px; | |||
| &__title { | |||
| margin-bottom: 20px; | |||
| color: @text-color; | |||
| font-weight: 500; | |||
| font-size: @font-size-title; | |||
| } | |||
| &__content { | |||
| display: flex; | |||
| gap: 15px; | |||
| align-items: center; | |||
| @media screen and (max-width: 1800px) { | |||
| flex-wrap: wrap; | |||
| } | |||
| } | |||
| } | |||
| &__quick-start { | |||
| display: flex; | |||
| gap: 15px; | |||
| align-items: flex-start; | |||
| width: 100%; | |||
| } | |||
| &__user { | |||
| display: flex; | |||
| flex-direction: column; | |||
| width: 326px; | |||
| min-width: 326px; | |||
| height: 700px; | |||
| } | |||
| } | |||
| @@ -0,0 +1,71 @@ | |||
| import { getWorkspaceOverviewReq } from '@/services/workspace'; | |||
| import { ExperimentInstance } from '@/types'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useEffect, useState } from 'react'; | |||
| import AssetsManagement from './components/AssetsManagement'; | |||
| import ExperimentChart, { type ExperimentStatistics } from './components/ExperimentChart'; | |||
| import ExperitableTable from './components/ExperimentTable'; | |||
| import QuickStart from './components/QuickStart'; | |||
| import TotalStatistics from './components/TotalStatistics'; | |||
| import UserSpace from './components/UserSpace'; | |||
| import WorkspaceIntro from './components/WorkspaceIntro'; | |||
| import styles from './index.less'; | |||
| type OverviewData = { | |||
| workflowCount: number; | |||
| runningExperimentInsCount: number; | |||
| experimentInsStatus: ExperimentStatistics; | |||
| latestExperimentInsList: ExperimentInstance[]; | |||
| }; | |||
| function Workspace() { | |||
| const [overviewData, setOverviewData] = useState<OverviewData>(); | |||
| const users: number[] = new Array(8).fill(0); | |||
| useEffect(() => { | |||
| getWorkspaceOverview(); | |||
| }, []); | |||
| // 获取工作空间概况 | |||
| const getWorkspaceOverview = async () => { | |||
| const [res] = await to(getWorkspaceOverviewReq()); | |||
| if (res && res.data) { | |||
| setOverviewData(res.data); | |||
| } | |||
| }; | |||
| return ( | |||
| <div className={styles.workspace}> | |||
| <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> | |||
| )} | |||
| </div> | |||
| </div> | |||
| <div className={styles['workspace__quick-start']}> | |||
| <QuickStart></QuickStart> | |||
| <div className={styles['workspace__user']}> | |||
| <UserSpace users={users}></UserSpace> | |||
| <AssetsManagement></AssetsManagement> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default Workspace; | |||
| @@ -46,7 +46,8 @@ export const requestConfig: RequestConfig = { | |||
| ], | |||
| responseInterceptors: [ | |||
| (response: any) => { | |||
| const { status, data } = response; | |||
| const { status, data } = response || {}; | |||
| console.log('response2', response); | |||
| if (status >= 200 && status < 300) { | |||
| if (data && (data instanceof Blob || data.code === 200)) { | |||
| return response; | |||
| @@ -14,6 +14,9 @@ declare namespace API { | |||
| }; | |||
| address?: string; | |||
| phone?: string; | |||
| roleNames?: { | |||
| roleName?: string; | |||
| }[]; | |||
| }; | |||
| type ErrorResponse = { | |||
| @@ -1,7 +1,7 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-16 14:29:44 | |||
| * @Description: | |||
| * @Description: 镜像管理接口 | |||
| */ | |||
| import { request } from '@umijs/max'; | |||
| @@ -0,0 +1,21 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-16 14:29:44 | |||
| * @Description: 工作空间接口 | |||
| */ | |||
| import { request } from '@umijs/max'; | |||
| // 获取工作空间概况 | |||
| export function getWorkspaceOverviewReq() { | |||
| return request(`/api/mmp/workspace/overview`, { | |||
| method: 'GET', | |||
| }); | |||
| } | |||
| // 获取工作空间概况 | |||
| export function getWorkspaceAssetCountReq(params: any) { | |||
| return request(`/api/mmp/workspace/assetCount`, { | |||
| method: 'GET', | |||
| params, | |||
| }); | |||
| } | |||
| @@ -6,6 +6,7 @@ | |||
| // 颜色 | |||
| @primary-color: #1664ff; // 主色调 | |||
| @primary-color-secondary: #4e89ff; | |||
| @primary-color-hover: #69b1ff; | |||
| @background-color: #f9fafb; // 页面背景颜色 | |||
| @text-color: #1d1d20; | |||
| @@ -15,17 +16,35 @@ | |||
| @warning-color: #f98e1b; | |||
| @border-color: rgba(22, 100, 255, 0.3); | |||
| @border-color-second: rgba(22, 100, 255, 0.1); | |||
| @background-color-primay: rgba(22, 100, 255, 0.03); | |||
| @border-color-secondary: rgba(22, 100, 255, 0.1); | |||
| @background-color-primary: rgba(22, 100, 255, 0.03); | |||
| @background-color-gray: rgba(4, 3, 3, 0.06); | |||
| @heading-color: rgba(0, 0, 0, 0.85); | |||
| @input-icon-hover-color: rgba(0, 0, 0, 0.85); | |||
| @border-color-base: #d9d9d9; | |||
| @link-hover-color: #69b1ff; | |||
| @sider-background-color: #f2f5f7; | |||
| @workspace-background: linear-gradient( | |||
| 179.03deg, | |||
| rgba(138, 138, 138, 0.06) 0%, | |||
| rgba(22, 100, 255, 0.02) 100% | |||
| ); | |||
| // 字体大小 | |||
| @font-size: 15px; | |||
| @font-size-title: 18px; | |||
| @font-size-content: 16px; | |||
| // 函数 | |||
| .addAlpha(@color, @alpha) { | |||
| @red: red(@color); | |||
| @green: green(@color); | |||
| @blue: blue(@color); | |||
| @result: rgba(@red, @green, @blue, @alpha); | |||
| } | |||
| // 导出变量 | |||
| :export { | |||
| @@ -34,5 +53,7 @@ | |||
| errorColor: @error-color; | |||
| warningColor: @warning-color; | |||
| textColor: @text-color; | |||
| textColorSecondary: @text-color-secondary; | |||
| fontSize: @font-size; | |||
| siderBGColor: @sider-background-color; | |||
| } | |||
| @@ -12,3 +12,19 @@ export type PipelineGlobalParam = { | |||
| param_value: number | string | boolean; | |||
| is_sensitive: number; | |||
| }; | |||
| // 实验实例 | |||
| export type ExperimentInstance = { | |||
| id: number; | |||
| experiment_id: number; | |||
| workflow_id: number; | |||
| create_time: string; | |||
| finish_time: string; | |||
| update_time: string; | |||
| status: string; | |||
| argo_ins_name: string; | |||
| argo_ins_ns: string; | |||
| nodes_result: string; | |||
| nodes_status: string; | |||
| global_param: PipelineGlobalParam[]; | |||
| }; | |||