| @@ -10,7 +10,7 @@ const Settings: ProLayoutProps & { | |||||
| navTheme: 'light', | navTheme: 'light', | ||||
| // 拂晓蓝 | // 拂晓蓝 | ||||
| colorPrimary: '#1664ff', | colorPrimary: '#1664ff', | ||||
| layout: 'mix', | |||||
| // layout: 'mix', | |||||
| contentWidth: 'Fluid', | contentWidth: 'Fluid', | ||||
| fixedHeader: false, | fixedHeader: false, | ||||
| fixSiderbar: false, | fixSiderbar: false, | ||||
| @@ -14,11 +14,7 @@ export default [ | |||||
| { | { | ||||
| path: '/', | path: '/', | ||||
| redirect: '/workspace', | redirect: '/workspace', | ||||
| }, | |||||
| { | |||||
| path: '*', | |||||
| layout: false, | |||||
| component: './404', | |||||
| breadcrumb: '工作空间', | |||||
| }, | }, | ||||
| { | { | ||||
| path: '/user', | path: '/user', | ||||
| @@ -65,41 +61,52 @@ export default [ | |||||
| { | { | ||||
| name: 'pipeline', | name: 'pipeline', | ||||
| path: '/pipeline', | path: '/pipeline', | ||||
| breadcrumb: '流水线', | |||||
| routes: [ | routes: [ | ||||
| { | |||||
| path: '', | |||||
| redirect: '/pipeline/template', | |||||
| }, | |||||
| { | { | ||||
| name: '流水线模板', | name: '流水线模板', | ||||
| path: 'template', | |||||
| path: '/pipeline/template', | |||||
| routes: [ | routes: [ | ||||
| { | { | ||||
| name: '流水线模板', | name: '流水线模板', | ||||
| path: '', | path: '', | ||||
| component: './Pipeline/index', | component: './Pipeline/index', | ||||
| breadcrumb: '流水线模板', | |||||
| }, | }, | ||||
| { | { | ||||
| name: '流水线详情', | name: '流水线详情', | ||||
| path: ':id', | path: ':id', | ||||
| component: './Pipeline/editPipeline/index', | component: './Pipeline/editPipeline/index', | ||||
| breadcrumb: '流水线详情', | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| { | { | ||||
| name: '实验', | name: '实验', | ||||
| path: 'experiment', | path: 'experiment', | ||||
| breadcrumb: '实验', | |||||
| routes: [ | routes: [ | ||||
| { | { | ||||
| name: '实验', | name: '实验', | ||||
| path: '', | path: '', | ||||
| component: './Experiment/index', | component: './Experiment/index', | ||||
| breadcrumb: '实验', | |||||
| }, | }, | ||||
| { | { | ||||
| name: '实验训练', | name: '实验训练', | ||||
| path: ':workflowId/:id', | path: ':workflowId/:id', | ||||
| component: './Experiment/Info/index', | component: './Experiment/Info/index', | ||||
| breadcrumb: '实验训练', | |||||
| }, | }, | ||||
| { | { | ||||
| name: '实验对比', | name: '实验对比', | ||||
| path: 'compare', | path: 'compare', | ||||
| component: './Experiment/Comparison/index', | component: './Experiment/Comparison/index', | ||||
| breadcrumb: '实验对比', | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| @@ -146,6 +153,10 @@ export default [ | |||||
| name: 'dataset', | name: 'dataset', | ||||
| path: '/dataset', | path: '/dataset', | ||||
| routes: [ | routes: [ | ||||
| { | |||||
| path: '', | |||||
| redirect: '/dataset/dataset', | |||||
| }, | |||||
| { | { | ||||
| name: '数据集', | name: '数据集', | ||||
| path: 'dataset', | path: 'dataset', | ||||
| @@ -201,7 +212,6 @@ export default [ | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| { | { | ||||
| name: 'workspace', | name: 'workspace', | ||||
| path: '/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', | name: 'monitor', | ||||
| path: '/monitor', | path: '/monitor', | ||||
| @@ -298,4 +332,9 @@ export default [ | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| { | |||||
| path: '*', | |||||
| layout: false, | |||||
| component: './404', | |||||
| }, | |||||
| ]; | ]; | ||||
| @@ -20,18 +20,14 @@ import { | |||||
| import './styles/menu.less'; | import './styles/menu.less'; | ||||
| export { requestConfig as request } from './requestConfig'; | export { requestConfig as request } from './requestConfig'; | ||||
| // const isDev = process.env.NODE_ENV === 'development'; | // const isDev = process.env.NODE_ENV === 'development'; | ||||
| import { type GlobalInitialState } from '@/types'; | |||||
| import { menuItemRender } from '@/utils/menuRender'; | import { menuItemRender } from '@/utils/menuRender'; | ||||
| import { gotoLoginPage } from './utils/ui'; | import { gotoLoginPage } from './utils/ui'; | ||||
| /** | /** | ||||
| * @see https://umijs.org/zh-CN/plugins/plugin-initial-state | * @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 () => { | const fetchUserInfo = async () => { | ||||
| try { | try { | ||||
| const response = await getUserInfo(); | const response = await getUserInfo(); | ||||
| @@ -56,18 +52,20 @@ export async function getInitialState(): Promise<{ | |||||
| fetchUserInfo, | fetchUserInfo, | ||||
| currentUser, | currentUser, | ||||
| settings: defaultSettings as Partial<LayoutSettings>, | settings: defaultSettings as Partial<LayoutSettings>, | ||||
| collapsed: false, | |||||
| }; | }; | ||||
| } | } | ||||
| return { | return { | ||||
| fetchUserInfo, | fetchUserInfo, | ||||
| settings: defaultSettings as Partial<LayoutSettings>, | settings: defaultSettings as Partial<LayoutSettings>, | ||||
| collapsed: false, | |||||
| }; | }; | ||||
| } | } | ||||
| // ProLayout 支持的api https://procomponents.ant.design/components/layout | // ProLayout 支持的api https://procomponents.ant.design/components/layout | ||||
| export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | ||||
| return { | return { | ||||
| rightContentRender: () => <RightContent />, | |||||
| rightContentRender: false, | |||||
| waterMarkProps: { | waterMarkProps: { | ||||
| // content: initialState?.currentUser?.nickName, | // content: initialState?.currentUser?.nickName, | ||||
| }, | }, | ||||
| @@ -91,7 +89,6 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||||
| return getRemoteMenu(); | return getRemoteMenu(); | ||||
| }, | }, | ||||
| }, | }, | ||||
| // footerRender: () => <Footer />, | |||||
| onPageChange: () => { | onPageChange: () => { | ||||
| const { location } = history; | const { location } = history; | ||||
| // 如果没有登录,重定向到 login | // 如果没有登录,重定向到 login | ||||
| @@ -127,14 +124,20 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||||
| // </Link>, | // </Link>, | ||||
| // ] | // ] | ||||
| // : [], | // : [], | ||||
| menuHeaderRender: false, | |||||
| // 自定义 403 页面 | // 自定义 403 页面 | ||||
| // unAccessible: <div>unAccessible</div>, | // unAccessible: <div>unAccessible</div>, | ||||
| // 增加一个 loading 的状态 | // 增加一个 loading 的状态 | ||||
| childrenRender: (children) => { | childrenRender: (children) => { | ||||
| // if (initialState?.loading) return <PageLoading />; | // 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: { | menuProps: { | ||||
| onClick: () => { | onClick: () => { | ||||
| // 点击菜单项,删除所有的页面 state 缓存 | // 点击菜单项,删除所有的页面 state 缓存 | ||||
| @@ -233,20 +236,24 @@ export const antd: RuntimeAntdConfig = (memo) => { | |||||
| memo.theme.components.Tabs = { | memo.theme.components.Tabs = { | ||||
| titleFontSize: 16, | titleFontSize: 16, | ||||
| }; | }; | ||||
| memo.theme.components.Form = { | memo.theme.components.Form = { | ||||
| labelColor: 'rgba(29, 29, 32, 0.8);', | 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.cssVar = true; | ||||
| // memo.theme.hashed = false; | // memo.theme.hashed = false; | ||||
| // memo.appConfig = { | |||||
| // message: { | |||||
| // // 配置 message 最大显示数,超过限制时,最早的消息会被自动关闭 | |||||
| // maxCount: 3, | |||||
| // }, | |||||
| // }; | |||||
| memo.appConfig = { | |||||
| message: { | |||||
| // 配置 message 最大显示数,超过限制时,最早的消息会被自动关闭 | |||||
| maxCount: 3, | |||||
| }, | |||||
| }; | |||||
| return memo; | 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; | padding: 4px 11px; | ||||
| border: 1px solid #d9d9d9; | border: 1px solid #d9d9d9; | ||||
| border-radius: 6px; | border-radius: 6px; | ||||
| cursor: pointer; | |||||
| &:hover { | &:hover { | ||||
| border-color: @primary-color; | 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 { useModel } from '@umijs/max'; | ||||
| import React from 'react'; | 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 Avatar from './AvatarDropdown'; | ||||
| import styles from './index.less'; | |||||
| // import { SelectLang } from '@umijs/max'; | // import { SelectLang } from '@umijs/max'; | ||||
| export type SiderTheme = 'light' | 'dark'; | export type SiderTheme = 'light' | 'dark'; | ||||
| const GlobalHeaderRight: React.FC = () => { | 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) { | if (!initialState || !initialState.settings) { | ||||
| return null; | return null; | ||||
| } | } | ||||
| const handleMenuCollapse = () => { | |||||
| setInitialState((preInitialState) => ({ | |||||
| ...preInitialState, | |||||
| collapsed: !preInitialState?.collapsed, | |||||
| })); | |||||
| }; | |||||
| return ( | return ( | ||||
| <div className={className}> | |||||
| <div className={styles['right-content']}> | |||||
| {/* <span | {/* <span | ||||
| className={actionClassName} | className={actionClassName} | ||||
| onClick={() => { | onClick={() => { | ||||
| @@ -49,6 +34,16 @@ const GlobalHeaderRight: React.FC = () => { | |||||
| > | > | ||||
| <QuestionCircleOutlined /> | <QuestionCircleOutlined /> | ||||
| </span> */} | </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} /> | <Avatar menu={true} /> | ||||
| {/* <SelectLang className={actionClassName} /> */} | {/* <SelectLang className={actionClassName} /> */} | ||||
| </div> | </div> | ||||
| @@ -76,7 +76,7 @@ body { | |||||
| } | } | ||||
| .ant-pro-layout .ant-layout-sider.ant-pro-sider { | .ant-pro-layout .ant-layout-sider.ant-pro-sider { | ||||
| height: 100vh; | height: 100vh; | ||||
| padding-top: 56px; | |||||
| // padding-top: 56px; | |||||
| } | } | ||||
| .ant-pro-layout .ant-pro-layout-container { | .ant-pro-layout .ant-pro-layout-container { | ||||
| height: 100vh; | 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; | 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 bindEvents = () => { | ||||
| const closeDrawer = () => { | |||||
| closePropsDrawer(); | |||||
| setTimeout(() => { | |||||
| setExperimentNodeData(null); | |||||
| }, 200); | |||||
| }; | |||||
| graph.on('node:click', (e) => { | graph.on('node:click', (e) => { | ||||
| if (e.target.get('name') !== 'anchor-point' && e.item) { | if (e.target.get('name') !== 'anchor-point' && e.item) { | ||||
| const model = e.item.getModel(); | const model = e.item.getModel(); | ||||
| @@ -452,10 +459,10 @@ function ExperimentText() { | |||||
| graph.setItemState(e.item, 'hover', false); | graph.setItemState(e.item, 'hover', false); | ||||
| }); | }); | ||||
| graph.on('canvas:click', (e) => { | 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 { 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 = { | export type PipelineGlobalParam = { | ||||