Browse Source

feat: 添加面包屑

pull/105/head
cp3hnu 1 year ago
parent
commit
e9abd18df4
12 changed files with 256 additions and 61 deletions
  1. +1
    -1
      react-ui/config/defaultSettings.ts
  2. +46
    -7
      react-ui/config/routes.ts
  3. +25
    -18
      react-ui/src/app.tsx
  4. +103
    -0
      react-ui/src/components/KFBreadcrumb/index.tsx
  5. +1
    -0
      react-ui/src/components/ParameterInput/index.less
  6. +10
    -0
      react-ui/src/components/RightContent/index.less
  7. +24
    -29
      react-ui/src/components/RightContent/index.tsx
  8. +16
    -1
      react-ui/src/global.less
  9. +1
    -1
      react-ui/src/iconfont/iconfont.js
  10. +8
    -0
      react-ui/src/overrides.less
  11. +11
    -4
      react-ui/src/pages/Experiment/Info/index.jsx
  12. +10
    -0
      react-ui/src/types.ts

+ 1
- 1
react-ui/config/defaultSettings.ts View File

@@ -10,7 +10,7 @@ const Settings: ProLayoutProps & {
navTheme: 'light',
// 拂晓蓝
colorPrimary: '#1664ff',
layout: 'mix',
// layout: 'mix',
contentWidth: 'Fluid',
fixedHeader: false,
fixSiderbar: false,


+ 46
- 7
react-ui/config/routes.ts View File

@@ -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',
},
];

+ 25
- 18
react-ui/src/app.tsx View File

@@ -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;
};

+ 103
- 0
react-ui/src/components/KFBreadcrumb/index.tsx View File

@@ -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;

+ 1
- 0
react-ui/src/components/ParameterInput/index.less View File

@@ -4,6 +4,7 @@
padding: 4px 11px;
border: 1px solid #d9d9d9;
border-radius: 6px;
cursor: pointer;

&:hover {
border-color: @primary-color;


+ 10
- 0
react-ui/src/components/RightContent/index.less View File

@@ -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;
}

+ 24
- 29
react-ui/src/components/RightContent/index.tsx View File

@@ -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>


+ 16
- 1
react-ui/src/global.less View File

@@ -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;
}

+ 1
- 1
react-ui/src/iconfont/iconfont.js
File diff suppressed because it is too large
View File


+ 8
- 0
react-ui/src/overrides.less View File

@@ -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;
}

+ 11
- 4
react-ui/src/pages/Experiment/Info/index.jsx View File

@@ -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();
});
};



+ 10
- 0
react-ui/src/types.ts View File

@@ -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 = {


Loading…
Cancel
Save