| @@ -10,7 +10,7 @@ const Settings: ProLayoutProps & { | |||
| navTheme: 'light', | |||
| // 拂晓蓝 | |||
| colorPrimary: '#1664ff', | |||
| layout: 'mix', | |||
| // layout: 'mix', | |||
| contentWidth: 'Fluid', | |||
| fixedHeader: false, | |||
| fixSiderbar: false, | |||
| @@ -14,11 +14,7 @@ export default [ | |||
| { | |||
| path: '/', | |||
| redirect: '/workspace', | |||
| }, | |||
| { | |||
| path: '*', | |||
| layout: false, | |||
| component: './404', | |||
| breadcrumb: '工作空间', | |||
| }, | |||
| { | |||
| path: '/user', | |||
| @@ -65,41 +61,52 @@ export default [ | |||
| { | |||
| name: 'pipeline', | |||
| path: '/pipeline', | |||
| breadcrumb: '流水线', | |||
| routes: [ | |||
| { | |||
| path: '', | |||
| redirect: '/pipeline/template', | |||
| }, | |||
| { | |||
| name: '流水线模板', | |||
| path: 'template', | |||
| path: '/pipeline/template', | |||
| routes: [ | |||
| { | |||
| name: '流水线模板', | |||
| path: '', | |||
| component: './Pipeline/index', | |||
| breadcrumb: '流水线模板', | |||
| }, | |||
| { | |||
| name: '流水线详情', | |||
| path: ':id', | |||
| component: './Pipeline/editPipeline/index', | |||
| breadcrumb: '流水线详情', | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| name: '实验', | |||
| path: 'experiment', | |||
| breadcrumb: '实验', | |||
| routes: [ | |||
| { | |||
| name: '实验', | |||
| path: '', | |||
| component: './Experiment/index', | |||
| breadcrumb: '实验', | |||
| }, | |||
| { | |||
| name: '实验训练', | |||
| path: ':workflowId/:id', | |||
| component: './Experiment/Info/index', | |||
| breadcrumb: '实验训练', | |||
| }, | |||
| { | |||
| name: '实验对比', | |||
| path: 'compare', | |||
| component: './Experiment/Comparison/index', | |||
| breadcrumb: '实验对比', | |||
| }, | |||
| ], | |||
| }, | |||
| @@ -146,6 +153,10 @@ export default [ | |||
| name: 'dataset', | |||
| path: '/dataset', | |||
| routes: [ | |||
| { | |||
| path: '', | |||
| redirect: '/dataset/dataset', | |||
| }, | |||
| { | |||
| name: '数据集', | |||
| path: 'dataset', | |||
| @@ -201,7 +212,6 @@ export default [ | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| name: 'workspace', | |||
| path: '/workspace', | |||
| @@ -259,6 +269,30 @@ export default [ | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| name: 'readad', | |||
| path: '/readad', | |||
| routes: [ | |||
| { | |||
| name: '资源', | |||
| path: '', | |||
| key: 'readad', | |||
| component: './missingPage.jsx', | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| name: 'compent', | |||
| path: '/compent', | |||
| routes: [ | |||
| { | |||
| name: '组件', | |||
| path: '', | |||
| key: 'compent', | |||
| component: './missingPage.jsx', | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| name: 'monitor', | |||
| path: '/monitor', | |||
| @@ -298,4 +332,9 @@ export default [ | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| path: '*', | |||
| layout: false, | |||
| component: './404', | |||
| }, | |||
| ]; | |||
| @@ -20,18 +20,14 @@ import { | |||
| import './styles/menu.less'; | |||
| export { requestConfig as request } from './requestConfig'; | |||
| // const isDev = process.env.NODE_ENV === 'development'; | |||
| import { type GlobalInitialState } from '@/types'; | |||
| import { menuItemRender } from '@/utils/menuRender'; | |||
| import { gotoLoginPage } from './utils/ui'; | |||
| /** | |||
| * @see https://umijs.org/zh-CN/plugins/plugin-initial-state | |||
| * */ | |||
| export async function getInitialState(): Promise<{ | |||
| settings?: Partial<LayoutSettings>; | |||
| currentUser?: API.CurrentUser; | |||
| loading?: boolean; | |||
| fetchUserInfo?: () => Promise<API.CurrentUser | undefined>; | |||
| }> { | |||
| // console.log('getInitialState'); | |||
| export async function getInitialState(): Promise<GlobalInitialState> { | |||
| const fetchUserInfo = async () => { | |||
| try { | |||
| const response = await getUserInfo(); | |||
| @@ -56,18 +52,20 @@ export async function getInitialState(): Promise<{ | |||
| fetchUserInfo, | |||
| currentUser, | |||
| settings: defaultSettings as Partial<LayoutSettings>, | |||
| collapsed: false, | |||
| }; | |||
| } | |||
| return { | |||
| fetchUserInfo, | |||
| settings: defaultSettings as Partial<LayoutSettings>, | |||
| collapsed: false, | |||
| }; | |||
| } | |||
| // ProLayout 支持的api https://procomponents.ant.design/components/layout | |||
| export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||
| return { | |||
| rightContentRender: () => <RightContent />, | |||
| rightContentRender: false, | |||
| waterMarkProps: { | |||
| // content: initialState?.currentUser?.nickName, | |||
| }, | |||
| @@ -91,7 +89,6 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||
| return getRemoteMenu(); | |||
| }, | |||
| }, | |||
| // footerRender: () => <Footer />, | |||
| onPageChange: () => { | |||
| const { location } = history; | |||
| // 如果没有登录,重定向到 login | |||
| @@ -127,14 +124,20 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||
| // </Link>, | |||
| // ] | |||
| // : [], | |||
| menuHeaderRender: false, | |||
| // 自定义 403 页面 | |||
| // unAccessible: <div>unAccessible</div>, | |||
| // 增加一个 loading 的状态 | |||
| childrenRender: (children) => { | |||
| // if (initialState?.loading) return <PageLoading />; | |||
| return <>{children}</>; | |||
| return ( | |||
| <div className="kf-page-container"> | |||
| <RightContent></RightContent> | |||
| <div className="kf-page-container__content">{children}</div> | |||
| </div> | |||
| ); | |||
| }, | |||
| collapsedButtonRender: false, | |||
| collapsed: initialState?.collapsed, | |||
| menuProps: { | |||
| onClick: () => { | |||
| // 点击菜单项,删除所有的页面 state 缓存 | |||
| @@ -233,20 +236,24 @@ export const antd: RuntimeAntdConfig = (memo) => { | |||
| memo.theme.components.Tabs = { | |||
| titleFontSize: 16, | |||
| }; | |||
| memo.theme.components.Form = { | |||
| labelColor: 'rgba(29, 29, 32, 0.8);', | |||
| }; | |||
| memo.theme.components.Breadcrumb = { | |||
| iconFontSize: parseInt(themes['fontSize']), | |||
| linkColor: 'rgba(29, 29, 32, 0.7)', | |||
| separatorColor: 'rgba(29, 29, 32, 0.7)', | |||
| }; | |||
| memo.theme.cssVar = true; | |||
| // memo.theme.hashed = false; | |||
| // memo.appConfig = { | |||
| // message: { | |||
| // // 配置 message 最大显示数,超过限制时,最早的消息会被自动关闭 | |||
| // maxCount: 3, | |||
| // }, | |||
| // }; | |||
| memo.appConfig = { | |||
| message: { | |||
| // 配置 message 最大显示数,超过限制时,最早的消息会被自动关闭 | |||
| maxCount: 3, | |||
| }, | |||
| }; | |||
| return memo; | |||
| }; | |||
| @@ -0,0 +1,103 @@ | |||
| import { Breadcrumb, type BreadcrumbProps } from 'antd'; | |||
| import { Link, matchPath, useLocation } from 'umi'; | |||
| // import routes from '../../../config/config'; // 导入你的路由配置 | |||
| type Route = { | |||
| path: string; | |||
| breadcrumb?: string; | |||
| routes?: Route[]; | |||
| redirect?: string; | |||
| name?: string; | |||
| component?: string; | |||
| layout?: boolean; | |||
| key?: string; | |||
| }; | |||
| const routes: Route[] = [ | |||
| { | |||
| name: 'pipeline', | |||
| path: '/pipeline', | |||
| breadcrumb: '流水线', | |||
| routes: [ | |||
| { | |||
| name: '流水线模板', | |||
| path: 'template', | |||
| breadcrumb: '流水线模板', | |||
| routes: [ | |||
| { | |||
| name: '流水线模板', | |||
| path: '', | |||
| component: './Pipeline/index', | |||
| breadcrumb: '流水线模板', | |||
| }, | |||
| { | |||
| name: '流水线详情', | |||
| path: ':id', | |||
| component: './Pipeline/editPipeline/index', | |||
| breadcrumb: '流水线详情', | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| name: '实验', | |||
| path: 'experiment', | |||
| breadcrumb: '实验', | |||
| routes: [ | |||
| { | |||
| name: '实验', | |||
| path: '', | |||
| component: './Experiment/index', | |||
| breadcrumb: '实验', | |||
| }, | |||
| { | |||
| name: '实验训练', | |||
| path: ':workflowId/:id', | |||
| component: './Experiment/Info/index', | |||
| breadcrumb: '实验训练', | |||
| }, | |||
| { | |||
| name: '实验对比', | |||
| path: 'compare', | |||
| component: './Experiment/Comparison/index', | |||
| breadcrumb: '实验对比', | |||
| }, | |||
| ], | |||
| }, | |||
| ], | |||
| }, | |||
| ]; | |||
| const KFBreadcrumb = () => { | |||
| const location = useLocation(); | |||
| const items: BreadcrumbProps['items'] = []; | |||
| // 遍历路由表,生成面包屑数据 | |||
| const generateBreadcrumbs = (pathname: string, routes: Route[], prefix: string = '') => { | |||
| for (const route of routes) { | |||
| if (route.redirect || route.layout === false || !route.path || route.path === '*') { | |||
| continue; | |||
| } | |||
| const match = matchPath( | |||
| { path: `${prefix}/${route.path}`, end: route.routes ? false : true }, | |||
| pathname, | |||
| ); | |||
| if (match) { | |||
| items.push({ | |||
| path: route.path.startsWith('/') ? route.path : `${prefix}/${route.path}`, | |||
| title: <Link to={route.path}>{route.breadcrumb}</Link>, | |||
| }); | |||
| } | |||
| if (route.routes) { | |||
| generateBreadcrumbs(pathname, route.routes, `${prefix}/${route.path}`); | |||
| } | |||
| } | |||
| }; | |||
| generateBreadcrumbs(location.pathname, routes); | |||
| // const itemRender = (route, params, routes, paths) => {}; | |||
| return <Breadcrumb items={items}></Breadcrumb>; | |||
| }; | |||
| export default KFBreadcrumb; | |||
| @@ -4,6 +4,7 @@ | |||
| padding: 4px 11px; | |||
| border: 1px solid #d9d9d9; | |||
| border-radius: 6px; | |||
| cursor: pointer; | |||
| &:hover { | |||
| border-color: @primary-color; | |||
| @@ -0,0 +1,10 @@ | |||
| .right-content { | |||
| display: flex; | |||
| gap: 8px; | |||
| align-items: center; | |||
| height: 55px; | |||
| margin-right: -10px; | |||
| padding: 0 16px; | |||
| background-color: white; | |||
| border-bottom: 1px solid #e9edf0; | |||
| } | |||
| @@ -1,46 +1,31 @@ | |||
| import { useEmotionCss } from '@ant-design/use-emotion-css'; | |||
| import { useModel } from '@umijs/max'; | |||
| import React from 'react'; | |||
| // import KFBreadcrumb from '../KFBreadcrumb'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { ProBreadcrumb } from '@ant-design/pro-components'; | |||
| import { Button } from 'antd'; | |||
| import Avatar from './AvatarDropdown'; | |||
| import styles from './index.less'; | |||
| // import { SelectLang } from '@umijs/max'; | |||
| export type SiderTheme = 'light' | 'dark'; | |||
| const GlobalHeaderRight: React.FC = () => { | |||
| const className = useEmotionCss(() => { | |||
| return { | |||
| display: 'flex', | |||
| height: '48px', | |||
| marginLeft: 'auto', | |||
| overflow: 'hidden', | |||
| gap: 8, | |||
| }; | |||
| }); | |||
| // const actionClassName = useEmotionCss(({ token }) => { | |||
| // return { | |||
| // display: 'flex', | |||
| // float: 'right', | |||
| // height: '48px', | |||
| // marginLeft: 'auto', | |||
| // overflow: 'hidden', | |||
| // cursor: 'pointer', | |||
| // padding: '0 12px', | |||
| // borderRadius: token.borderRadius, | |||
| // '&:hover': { | |||
| // backgroundColor: token.colorBgTextHover, | |||
| // }, | |||
| // }; | |||
| // }); | |||
| const { initialState } = useModel('@@initialState'); | |||
| const { initialState, setInitialState } = useModel('@@initialState'); | |||
| if (!initialState || !initialState.settings) { | |||
| return null; | |||
| } | |||
| const handleMenuCollapse = () => { | |||
| setInitialState((preInitialState) => ({ | |||
| ...preInitialState, | |||
| collapsed: !preInitialState?.collapsed, | |||
| })); | |||
| }; | |||
| return ( | |||
| <div className={className}> | |||
| <div className={styles['right-content']}> | |||
| {/* <span | |||
| className={actionClassName} | |||
| onClick={() => { | |||
| @@ -49,6 +34,16 @@ const GlobalHeaderRight: React.FC = () => { | |||
| > | |||
| <QuestionCircleOutlined /> | |||
| </span> */} | |||
| {/* <KFBreadcrumb /> */} | |||
| <Button | |||
| type="text" | |||
| style={{ marginRight: '4px' }} | |||
| icon={<KFIcon type="icon-collapsed" font={18} style={{ verticalAlign: '-3px' }} />} | |||
| onClick={handleMenuCollapse} | |||
| ></Button> | |||
| <ProBreadcrumb></ProBreadcrumb> | |||
| <Avatar menu={true} /> | |||
| {/* <SelectLang className={actionClassName} /> */} | |||
| </div> | |||
| @@ -76,7 +76,7 @@ body { | |||
| } | |||
| .ant-pro-layout .ant-layout-sider.ant-pro-sider { | |||
| height: 100vh; | |||
| padding-top: 56px; | |||
| // padding-top: 56px; | |||
| } | |||
| .ant-pro-layout .ant-pro-layout-container { | |||
| height: 100vh; | |||
| @@ -133,3 +133,18 @@ ol { | |||
| } | |||
| } | |||
| } | |||
| .kf-page-container { | |||
| height: 100%; | |||
| &__content { | |||
| height: calc(100% - 55px); | |||
| } | |||
| } | |||
| .kf-menu-collapsed { | |||
| position: fixed; | |||
| top: 0; | |||
| left: 0; | |||
| z-index: 999; | |||
| } | |||
| @@ -179,3 +179,11 @@ | |||
| transition: color 0s; | |||
| } | |||
| } | |||
| .ant-pro-sider-collapsed-button { | |||
| inset-block-start: 65px !important; | |||
| } | |||
| .ant-pro-layout .ant-pro-sider-logo > a > h1 { | |||
| margin-inline-start: 12px; | |||
| } | |||
| @@ -438,6 +438,13 @@ function ExperimentText() { | |||
| // 绑定事件 | |||
| const bindEvents = () => { | |||
| const closeDrawer = () => { | |||
| closePropsDrawer(); | |||
| setTimeout(() => { | |||
| setExperimentNodeData(null); | |||
| }, 200); | |||
| }; | |||
| graph.on('node:click', (e) => { | |||
| if (e.target.get('name') !== 'anchor-point' && e.item) { | |||
| const model = e.item.getModel(); | |||
| @@ -452,10 +459,10 @@ function ExperimentText() { | |||
| graph.setItemState(e.item, 'hover', false); | |||
| }); | |||
| graph.on('canvas:click', (e) => { | |||
| closePropsDrawer(); | |||
| setTimeout(() => { | |||
| setExperimentNodeData(null); | |||
| }, 200); | |||
| closeDrawer(); | |||
| }); | |||
| graph.on('edge:click', (e) => { | |||
| closeDrawer(); | |||
| }); | |||
| }; | |||
| @@ -5,6 +5,16 @@ | |||
| */ | |||
| import { ExperimentStatus, TensorBoardStatus } from '@/enums'; | |||
| import type { Settings as LayoutSettings } from '@ant-design/pro-components'; | |||
| // 全局初始状态类型 | |||
| export type GlobalInitialState = { | |||
| settings?: Partial<LayoutSettings>; | |||
| currentUser?: API.CurrentUser; | |||
| fetchUserInfo?: () => Promise<API.CurrentUser | undefined>; | |||
| loading?: boolean; | |||
| collapsed?: boolean; | |||
| }; | |||
| // 流水线全局参数 | |||
| export type PipelineGlobalParam = { | |||