diff --git a/react-ui/config/defaultSettings.ts b/react-ui/config/defaultSettings.ts index 7c1cfbd5..97a26343 100644 --- a/react-ui/config/defaultSettings.ts +++ b/react-ui/config/defaultSettings.ts @@ -19,7 +19,6 @@ const Settings: ProLayoutProps & { title: '智能软件开发平台', pwa: true, logo: '/assets/images/left-top-logo.png', - iconfontUrl: '//at.alicdn.com/t/c/font_4511326_1cmi0j3dj1x.js', token: { // 参见ts声明,demo 见文档,通过token 修改样式 //https://procomponents.ant.design/components/layout#%E9%80%9A%E8%BF%87-token-%E4%BF%AE%E6%94%B9%E6%A0%B7%E5%BC%8F diff --git a/react-ui/config/routes.ts b/react-ui/config/routes.ts index cd81e412..200bbd20 100644 --- a/react-ui/config/routes.ts +++ b/react-ui/config/routes.ts @@ -67,24 +67,36 @@ export default [ path: '/pipeline', routes: [ { - name: '流水线', - path: '/pipeline/pipelineText', - component: './Pipeline/index', - }, - { - name: '训练', - path: '/pipeline/pytorchtext/:id/:name', - component: './Pipeline/editPipeline/index', + name: '流水线模板', + path: 'template', + routes: [ + { + name: '流水线模板', + path: '', + component: './Pipeline/index', + }, + { + name: '流水线详情', + path: ':id/:name', + component: './Pipeline/editPipeline/index', + }, + ], }, { name: '实验', - path: '/pipeline/experimentText', - component: './Experiment/index', - }, - { - name: '实验训练', - path: '/pipeline/experimentPytorchtext/:workflowId/:id', - component: './Experiment/experimentText/index', + path: 'experiment', + routes: [ + { + name: '实验', + path: '', + component: './Experiment/index', + }, + { + name: '实验训练', + path: ':workflowId/:id', + component: './Experiment/training/index', + }, + ], }, ], }, @@ -131,7 +143,7 @@ export default [ { name: '数据集简介', path: ':id', - component: './Dataset/datasetIntro', + component: './Dataset/intro', }, ], }, @@ -147,7 +159,7 @@ export default [ { name: '模型简介', path: ':id', - component: './Model/modelIntro', + component: './Model/intro', }, ], }, @@ -158,17 +170,17 @@ export default [ { name: '镜像列表', path: '', - component: './Mirror/list', + component: './Mirror/List', }, { name: '镜像详情', path: ':id', - component: './Mirror/info', + component: './Mirror/Info', }, { name: '创建镜像', path: 'create', - component: './Mirror/create', + component: './Mirror/Create', }, ], }, @@ -188,14 +200,23 @@ export default [ ], }, { - name: 'modelDseployment', - path: '/modelDseployment', + name: 'modelDeployment', + path: '/modelDeployment', routes: [ { - name: '模型部署', + name: '模型列表', path: '', - key: 'modelDseployment', - component: './missingPage.jsx', + component: './ModelDeployment/List', + }, + { + name: '镜像详情', + path: ':id', + component: './ModelDeployment/Info', + }, + { + name: '创建镜像', + path: 'create', + component: './ModelDeployment/Create', }, ], }, diff --git a/react-ui/public/assets/images/compoent-icon-6.png b/react-ui/public/assets/images/compoent-icon-6.png deleted file mode 100644 index 47e7fa9b..00000000 Binary files a/react-ui/public/assets/images/compoent-icon-6.png and /dev/null differ diff --git a/react-ui/public/assets/images/icon/流水线-1.png b/react-ui/public/assets/images/icon/流水线-1.png deleted file mode 100644 index 7fbb9266..00000000 Binary files a/react-ui/public/assets/images/icon/流水线-1.png and /dev/null differ diff --git a/react-ui/public/assets/images/mindspore模型转换.png b/react-ui/public/assets/images/mindspore模型转换.png deleted file mode 100644 index 73620da3..00000000 Binary files a/react-ui/public/assets/images/mindspore模型转换.png and /dev/null differ diff --git a/react-ui/public/assets/images/pipelieEditIcon.png b/react-ui/public/assets/images/pipelieEditIcon.png deleted file mode 100644 index 936bf97c..00000000 Binary files a/react-ui/public/assets/images/pipelieEditIcon.png and /dev/null differ diff --git a/react-ui/public/assets/images/pipeline-edit-icon.png b/react-ui/public/assets/images/pipeline-edit-icon.png deleted file mode 100644 index 936bf97c..00000000 Binary files a/react-ui/public/assets/images/pipeline-edit-icon.png and /dev/null differ diff --git a/react-ui/public/assets/images/pytorch推理.png b/react-ui/public/assets/images/pytorch推理.png deleted file mode 100644 index 11f2d37f..00000000 Binary files a/react-ui/public/assets/images/pytorch推理.png and /dev/null differ diff --git a/react-ui/public/assets/images/pytorch训练.png b/react-ui/public/assets/images/pytorch训练.png deleted file mode 100644 index 81677987..00000000 Binary files a/react-ui/public/assets/images/pytorch训练.png and /dev/null differ diff --git a/react-ui/public/assets/images/sjj-icon-1.png b/react-ui/public/assets/images/sjj-icon-1.png deleted file mode 100644 index 3f2e7d4f..00000000 Binary files a/react-ui/public/assets/images/sjj-icon-1.png and /dev/null differ diff --git a/react-ui/public/assets/images/tensorflow模型转换.png b/react-ui/public/assets/images/tensorflow模型转换.png deleted file mode 100644 index 4e58c5d7..00000000 Binary files a/react-ui/public/assets/images/tensorflow模型转换.png and /dev/null differ diff --git a/react-ui/public/assets/images/发送通知.png b/react-ui/public/assets/images/发送通知.png deleted file mode 100644 index 9e4d563a..00000000 Binary files a/react-ui/public/assets/images/发送通知.png and /dev/null differ diff --git a/react-ui/src/app.tsx b/react-ui/src/app.tsx index a4642589..7d928861 100644 --- a/react-ui/src/app.tsx +++ b/react-ui/src/app.tsx @@ -17,9 +17,10 @@ import { patchRouteWithRemoteMenus, setRemoteMenu, } from './services/session'; +import './styles/menu.less'; export { requestConfig as request } from './requestConfig'; // const isDev = process.env.NODE_ENV === 'development'; - +import { menuItemRender } from '@/utils/menuRender'; /** * @see https://umijs.org/zh-CN/plugins/plugin-initial-state * */ @@ -139,10 +140,8 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { onClick: () => { // 点击菜单项,删除所有的页面 state 缓存 removeAllPageCacheState(); + // console.log('click menu'); }, - // onSelect: (e) => { - // console.log(e); - // }, }, ...initialState?.settings, token: { @@ -150,60 +149,49 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { colorTextMenu: themes['textColor'], colorTextMenuSelected: themes['primaryColor'], colorTextMenuActive: themes['primaryColor'], + colorTextMenuItemHover: 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 ( - // - // - // {itemProps.name} - // - // ); - // }, + menuItemRender: menuItemRender(false), + subMenuItemRender: menuItemRender(true), }; }; export const onRouteChange: RuntimeConfig['onRouteChange'] = async (e) => { const { location } = e; const menus = getRemoteMenu(); - console.log('onRouteChange', e); + // console.log('onRouteChange', e); if (menus === null && location.pathname !== PageEnum.LOGIN) { - console.log('refresh'); history.go(0); } }; export const patchRoutes: RuntimeConfig['patchRoutes'] = (e) => { - console.log('patchRoutes', e); + //console.log('patchRoutes', e); }; export const patchClientRoutes: RuntimeConfig['patchClientRoutes'] = (e) => { - console.log('patchClientRoutes', e); + //console.log('patchClientRoutes', e); patchRouteWithRemoteMenus(e.routes); }; export function render(oldRender: () => void) { - console.log('render'); + //console.log('render'); const token = getAccessToken(); if (!token || token?.length === 0) { oldRender(); return; } - getRoutersInfo().then((res) => { - setRemoteMenu(res); - oldRender(); - }); + getRoutersInfo() + .then((res) => { + setRemoteMenu(res); + oldRender(); + }) + .catch(() => { + oldRender(); + }); } // 主题修改 @@ -215,6 +203,7 @@ export const antd: RuntimeAntdConfig = (memo) => { colorError: themes['errorColor'], colorWarning: themes['warningColor'], colorLink: themes['primaryColor'], + colorText: themes['textColor'], }; memo.theme.components ??= {}; memo.theme.components.Tabs = {}; @@ -229,10 +218,14 @@ export const antd: RuntimeAntdConfig = (memo) => { defaultActiveBorderColor: 'rgba(22, 100, 255, 0.75)', defaultActiveColor: themes['primaryColor'], contentFontSize: parseInt(themes['fontSize']), - controlHeight: 34, }; memo.theme.components.Input = { - inputFontSize: parseInt(themes['fontSize']), + inputFontSize: parseInt(themes['fontSizeInput']), + inputFontSizeLG: parseInt(themes['fontSizeInputLg']), + paddingBlockLG: 10, + }; + memo.theme.components.Select = { + singleItemHeightLG: 46, }; memo.theme.components.Table = { headerBg: 'rgba(242, 244, 247, 0.36)', @@ -241,6 +234,11 @@ export const antd: RuntimeAntdConfig = (memo) => { memo.theme.components.Tabs = { titleFontSize: 16, }; + + memo.theme.components.Form = { + labelColor: 'rgba(29, 29, 32, 0.8);', + }; + memo.theme.cssVar = true; // memo.theme.hashed = false; diff --git a/react-ui/src/assets/img/model-deployment.png b/react-ui/src/assets/img/model-deployment.png new file mode 100644 index 00000000..bf8511c8 Binary files /dev/null and b/react-ui/src/assets/img/model-deployment.png differ diff --git a/react-ui/src/components/CommonTableCell/index.tsx b/react-ui/src/components/CommonTableCell/index.tsx index 65a1dff2..afa5d8d3 100644 --- a/react-ui/src/components/CommonTableCell/index.tsx +++ b/react-ui/src/components/CommonTableCell/index.tsx @@ -4,8 +4,22 @@ * @Description: 自定义 Table 单元格,没有数据时展示 -- */ -function CommonTableCell(text?: string | null) { +import { Tooltip } from 'antd'; + +function renderCell(text?: string | null) { return {text ?? '--'}; } +function CommonTableCell(ellipsis: boolean = false) { + if (ellipsis) { + return (text?: string | null) => ( + + {renderCell(text)} + + ); + } else { + return renderCell; + } +} + export default CommonTableCell; diff --git a/react-ui/src/components/DateTableCell/index.tsx b/react-ui/src/components/DateTableCell/index.tsx index 0b4efe93..ea629ba7 100644 --- a/react-ui/src/components/DateTableCell/index.tsx +++ b/react-ui/src/components/DateTableCell/index.tsx @@ -4,6 +4,7 @@ * @Description: 自定义 Table 日期类单元格 */ +import { formatDate } from '@/utils/date'; import dayjs from 'dayjs'; function DateTableCell(text?: string | null) { @@ -13,7 +14,7 @@ function DateTableCell(text?: string | null) { if (!dayjs(text).isValid()) { return 无效的日期; } - return {dayjs(text).format('YYYY-MM-DD HH:mm:ss')}; + return {formatDate(text)}; } export default DateTableCell; diff --git a/react-ui/src/components/KFIcon/index.tsx b/react-ui/src/components/KFIcon/index.tsx index e3951928..65239957 100644 --- a/react-ui/src/components/KFIcon/index.tsx +++ b/react-ui/src/components/KFIcon/index.tsx @@ -3,6 +3,7 @@ * @Date: 2024-04-17 12:53:06 * @Description: */ +import '@/iconfont/iconfont-menu.js'; import '@/iconfont/iconfont.js'; import { createFromIconfontCN } from '@ant-design/icons'; diff --git a/react-ui/src/components/KFModal/index.less b/react-ui/src/components/KFModal/index.less index 57153a58..5e1f1239 100644 --- a/react-ui/src/components/KFModal/index.less +++ b/react-ui/src/components/KFModal/index.less @@ -23,8 +23,6 @@ border-radius: 10px; } .ant-btn-default { - color: @text-color; - background: rgba(22, 100, 255, 0.06); border-color: transparent; } .ant-btn + .ant-btn { diff --git a/react-ui/src/components/KFModal/index.tsx b/react-ui/src/components/KFModal/index.tsx index 0105324f..8fea54bc 100644 --- a/react-ui/src/components/KFModal/index.tsx +++ b/react-ui/src/components/KFModal/index.tsx @@ -12,11 +12,12 @@ import './index.less'; export interface KFModalProps extends ModalProps { image?: string; } -function KFModal({ title, image, children, className = '', ...rest }: KFModalProps) { +function KFModal({ title, image, children, className = '', centered, ...rest }: KFModalProps) { return ( } > {children} diff --git a/react-ui/src/components/KFRadio/index.less b/react-ui/src/components/KFRadio/index.less index aff74e90..455492a4 100644 --- a/react-ui/src/components/KFRadio/index.less +++ b/react-ui/src/components/KFRadio/index.less @@ -23,6 +23,11 @@ &--active { color: @primary-color; border: 1px solid @primary-color; + + &:hover { + color: @primary-color; + border: 1px solid @primary-color; + } } & + & { diff --git a/react-ui/src/components/PageTitle/index.less b/react-ui/src/components/PageTitle/index.less index 80d18169..d120009b 100644 --- a/react-ui/src/components/PageTitle/index.less +++ b/react-ui/src/components/PageTitle/index.less @@ -3,5 +3,8 @@ align-items: center; height: 50px; padding-left: 30px; - background-image: url('../../assets/img/page-title-bg.png'); + background-image: url(@/assets/img/page-title-bg.png); + background-repeat: no-repeat; + background-position: top center; + background-size: 100%; } diff --git a/react-ui/src/components/ParameterInput/index.less b/react-ui/src/components/ParameterInput/index.less new file mode 100644 index 00000000..2d4f0489 --- /dev/null +++ b/react-ui/src/components/ParameterInput/index.less @@ -0,0 +1,64 @@ +.parameter-input { + width: 100%; + min-width: 0; + padding: 4px 11px; + border: 1px solid #d9d9d9; + border-radius: 6px; + + &:hover { + border-color: @primary-color; + } + + &__content { + display: flex; + align-items: center; + width: fit-content; + max-width: 100%; + min-height: 22px; + padding: 0 8px; + color: .addAlpha(@text-color, 0.8) []; + background-color: rgba(0, 0, 0, 0.06); + border-radius: 4px; + + &__value { + .singleLine(); + margin-right: 8px; + font-size: @font-size-input; + line-height: 1.5714285714285714; + } + + &__close-icon { + font-size: 10px; + + &:hover { + color: #000; + } + } + } + + &__placeholder { + min-height: 22px; + color: rgba(0, 0, 0, 0.25); + font-size: @font-size-input; + line-height: 1.5714285714285714; + } +} + +.parameter-input.parameter-input--large { + padding: 10px 11px; + font-size: @font-size-input-lg; + + .parameter-input__placeholder { + font-size: @font-size-input-lg; + line-height: 1.5; + } + + .parameter-input__content__value { + font-size: @font-size-input-lg; + line-height: 1.5; + } + + .parameter-input__content__close-icon { + font-size: 12px; + } +} diff --git a/react-ui/src/components/ParameterInput/index.tsx b/react-ui/src/components/ParameterInput/index.tsx new file mode 100644 index 00000000..27ba2856 --- /dev/null +++ b/react-ui/src/components/ParameterInput/index.tsx @@ -0,0 +1,106 @@ +import { CloseOutlined } from '@ant-design/icons'; +import { Input } from 'antd'; +import classNames from 'classnames'; +import './index.less'; + +type ParameterInputData = { + value?: any; + showValue?: any; + fromSelect?: boolean; +} & Record; + +interface ParameterInputProps { + value?: ParameterInputData; + onChange?: (value: ParameterInputData) => void; + onClick?: () => void; + canInput?: boolean; + textArea?: boolean; + placeholder?: string; + allowClear?: boolean; + className?: string; + style?: React.CSSProperties; + size?: 'middle' | 'small' | 'large'; + disabled?: boolean; +} + +function ParameterInput({ + value, + onChange, + onClick, + canInput = true, + textArea = false, + placeholder, + allowClear, + className, + style, + size = 'middle', + disabled = false, + ...rest +}: ParameterInputProps) { + // console.log('ParameterInput', value); + + const valueObj = + typeof value === 'string' ? { value: value, fromSelect: false, showValue: value } : value; + if (valueObj && !valueObj.showValue) { + valueObj.showValue = valueObj.value; + } + const isSelect = valueObj?.fromSelect; + const InputComponent = textArea ? Input.TextArea : Input; + + return ( + <> + {(isSelect || !canInput) && !disabled ? ( +
+ {valueObj?.showValue ? ( +
+ {valueObj?.showValue} + { + e.stopPropagation(); + onChange?.({ + ...valueObj, + fromSelect: false, + value: undefined, + showValue: undefined, + }); + }} + /> +
+ ) : ( +
{placeholder}
+ )} +
+ ) : ( + + onChange?.({ + ...valueObj, + fromSelect: false, + value: e.target.value, + showValue: e.target.value, + }) + } + /> + )} + + ); +} + +export default ParameterInput; diff --git a/react-ui/src/components/RightContent/index.tsx b/react-ui/src/components/RightContent/index.tsx index 70e57e23..75d86186 100644 --- a/react-ui/src/components/RightContent/index.tsx +++ b/react-ui/src/components/RightContent/index.tsx @@ -2,6 +2,7 @@ import { useEmotionCss } from '@ant-design/use-emotion-css'; import { useModel } from '@umijs/max'; import React from 'react'; import Avatar from './AvatarDropdown'; +// import { SelectLang } from '@umijs/max'; export type SiderTheme = 'light' | 'dark'; diff --git a/react-ui/src/enums/index.ts b/react-ui/src/enums/index.ts index 0c0c7b81..c0238791 100644 --- a/react-ui/src/enums/index.ts +++ b/react-ui/src/enums/index.ts @@ -4,9 +4,27 @@ export enum CommonTabKeys { Public = 'Public', // 公开 } -// 镜像状态 +// 镜像版本状态 export enum MirrorVersionStatus { Available = 'available', // 可用 Building = 'building', // 构建中 Failed = 'failed', // 构建中 } + +// 模型部署状态 +export enum ModelDeploymentStatus { + Init = 'Init', // 启动中 + Running = 'Running', // 运行中 + Stopped = 'Stopped', // 已停止 + Failed = 'Failed', // 失败 + Pending = 'Pending', // 挂起中 +} + +export const modelDeploymentStatusOptions = [ + { label: '全部', value: '' }, + { label: '启动中', value: ModelDeploymentStatus.Init }, + { label: '运行中', value: ModelDeploymentStatus.Running }, + { label: '已停止', value: ModelDeploymentStatus.Stopped }, + { label: '失败', value: ModelDeploymentStatus.Failed }, + { label: '挂起中', value: ModelDeploymentStatus.Pending }, +]; diff --git a/react-ui/src/hooks/resource.ts b/react-ui/src/hooks/resource.ts new file mode 100644 index 00000000..d3957bb7 --- /dev/null +++ b/react-ui/src/hooks/resource.ts @@ -0,0 +1,46 @@ +import { getComputingResourceReq } from '@/services/pipeline'; +import { ComputingResource } from '@/types'; +import { to } from '@/utils/promise'; +import { type SelectProps } from 'antd'; +import { useCallback, useEffect, useState } from 'react'; + +// 获取资源规格 +export function useComputingResource() { + const [resourceStandardList, setResourceStandardList] = useState([]); + + useEffect(() => { + getComputingResource(); + }, []); + + // 获取资源规格列表数据 + const getComputingResource = useCallback(async () => { + const params = { + page: 0, + size: 1000, + resource_type: '', + }; + const [res] = await to(getComputingResourceReq(params)); + if (res && res.data && res.data.content) { + setResourceStandardList(res.data.content); + } + }, []); + + // 过滤资源规格 + const filterResourceStandard: SelectProps['filterOption'] = + useCallback((input: string, option?: ComputingResource) => { + return ( + option?.computing_resource?.toLocaleLowerCase()?.includes(input.toLocaleLowerCase()) ?? + false + ); + }, []); + + // 根据 standard 获取 description + const getDescription = useCallback( + (standard: string) => { + return resourceStandardList.find((item) => item.standard === standard)?.description; + }, + [resourceStandardList], + ); + + return [resourceStandardList, filterResourceStandard, getDescription] as const; +} diff --git a/react-ui/src/hooks/sessionStorage.ts b/react-ui/src/hooks/sessionStorage.ts new file mode 100644 index 00000000..5c765370 --- /dev/null +++ b/react-ui/src/hooks/sessionStorage.ts @@ -0,0 +1,19 @@ +import { getSessionStorageItem, removeSessionStorageItem } from '@/utils/sessionStorage'; +import { useEffect, useState } from 'react'; + +// 获取缓存数据 +export function useSessionStorage(key: string, isObject: boolean, initialValue: T) { + const [storage, setStorage] = useState(initialValue); + + useEffect(() => { + const res = getSessionStorageItem(key, isObject); + if (res) { + setStorage(res); + } + return () => { + removeSessionStorageItem(key); + }; + }, []); + + return [storage]; +} diff --git a/react-ui/src/iconfont/iconfont-menu.js b/react-ui/src/iconfont/iconfont-menu.js new file mode 100644 index 00000000..211a58a7 --- /dev/null +++ b/react-ui/src/iconfont/iconfont-menu.js @@ -0,0 +1 @@ +window._iconfont_svg_string_4511326='',function(t){var a=(a=document.getElementsByTagName("script"))[a.length-1],l=a.getAttribute("data-injectcss"),a=a.getAttribute("data-disable-injectsvg");if(!a){var h,i,o,c,e,m=function(a,l){l.parentNode.insertBefore(a,l)};if(l&&!t.__iconfont__svg__cssinject__){t.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}h=function(){var a,l=document.createElement("div");l.innerHTML=t._iconfont_svg_string_4511326,(l=l.getElementsByTagName("svg")[0])&&(l.setAttribute("aria-hidden","true"),l.style.position="absolute",l.style.width=0,l.style.height=0,l.style.overflow="hidden",l=l,(a=document.body).firstChild?m(l,a.firstChild):a.appendChild(l))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(h,0):(i=function(){document.removeEventListener("DOMContentLoaded",i,!1),h()},document.addEventListener("DOMContentLoaded",i,!1)):document.attachEvent&&(o=h,c=t.document,e=!1,n(),c.onreadystatechange=function(){"complete"==c.readyState&&(c.onreadystatechange=null,p())})}function p(){e||(e=!0,o())}function n(){try{c.documentElement.doScroll("left")}catch(a){return void setTimeout(n,50)}p()}}(window); \ No newline at end of file diff --git a/react-ui/src/iconfont/iconfont.js b/react-ui/src/iconfont/iconfont.js index 77f8486f..e135846d 100644 --- a/react-ui/src/iconfont/iconfont.js +++ b/react-ui/src/iconfont/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_4511447='',function(t){var a=(a=document.getElementsByTagName("script"))[a.length-1],h=a.getAttribute("data-injectcss"),a=a.getAttribute("data-disable-injectsvg");if(!a){var l,i,v,o,z,m=function(a,h){h.parentNode.insertBefore(a,h)};if(h&&!t.__iconfont__svg__cssinject__){t.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}l=function(){var a,h=document.createElement("div");h.innerHTML=t._iconfont_svg_string_4511447,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(a=document.body).firstChild?m(h,a.firstChild):a.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(i=function(){document.removeEventListener("DOMContentLoaded",i,!1),l()},document.addEventListener("DOMContentLoaded",i,!1)):document.attachEvent&&(v=l,o=t.document,z=!1,p(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,n())})}function n(){z||(z=!0,v())}function p(){try{o.documentElement.doScroll("left")}catch(a){return void setTimeout(p,50)}n()}}(window); \ No newline at end of file +window._iconfont_svg_string_4511447='',function(t){var a=(a=document.getElementsByTagName("script"))[a.length-1],h=a.getAttribute("data-injectcss"),a=a.getAttribute("data-disable-injectsvg");if(!a){var l,v,z,i,o,m=function(a,h){h.parentNode.insertBefore(a,h)};if(h&&!t.__iconfont__svg__cssinject__){t.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}l=function(){var a,h=document.createElement("div");h.innerHTML=t._iconfont_svg_string_4511447,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(a=document.body).firstChild?m(h,a.firstChild):a.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(v=function(){document.removeEventListener("DOMContentLoaded",v,!1),l()},document.addEventListener("DOMContentLoaded",v,!1)):document.attachEvent&&(z=l,i=t.document,o=!1,d(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,p())})}function p(){o||(o=!0,z())}function d(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(d,50)}p()}}(window); \ No newline at end of file diff --git a/react-ui/src/overrides.less b/react-ui/src/overrides.less index e962088a..61102f12 100644 --- a/react-ui/src/overrides.less +++ b/react-ui/src/overrides.less @@ -57,11 +57,6 @@ overflow-y: auto; } -// Input -.ant-input-textarea-affix-wrapper.ant-input-affix-wrapper { - padding: 0; -} - // Modal .ant-modal { .ant-modal-close { @@ -81,18 +76,24 @@ } } + .ant-form-item .ant-form-item-label > label { + font-size: @font-size; + } + + // 输入框高度为46px .ant-input-affix-wrapper { - height: 46px; - padding: 1px 11px; + padding-top: 2px; + padding-bottom: 2px; + + .ant-input { + height: 40px; + } } + // 选择框高度为46px .ant-select-single { height: 46px; } - - .ant-select-single .ant-select-selector .ant-select-selection-placeholder { - line-height: 46px; - } } // Confirm Modal @@ -128,8 +129,6 @@ border-radius: 10px; } .ant-btn-default { - color: @text-color; - background: rgba(22, 100, 255, 0.06); border-color: transparent; } .ant-btn + .ant-btn { @@ -137,3 +136,20 @@ } } } + +// 表单类型为large时,font-size为15px +.ant-form-large { + .ant-form-item-label { + label { + font-size: @font-size; + } + } +} + +// 取消 hover 颜色变化 +.ant-menu .ant-menu-title-content { + transition: color 0s; + a { + transition: color 0s; + } +} diff --git a/react-ui/src/pages/Dataset/components/AddDatasetModal/index.less b/react-ui/src/pages/Dataset/components/AddDatasetModal/index.less new file mode 100644 index 00000000..66022fdd --- /dev/null +++ b/react-ui/src/pages/Dataset/components/AddDatasetModal/index.less @@ -0,0 +1,9 @@ +.upload-tip { + margin-top: 5px; + color: @error-color; +} + +.upload-button { + height: 46px; + font-size: 15px; +} diff --git a/react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx b/react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx new file mode 100644 index 00000000..177bf2a0 --- /dev/null +++ b/react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx @@ -0,0 +1,199 @@ +import { getAccessToken } from '@/access'; +import { DictValueEnumObj } from '@/components/DictTag'; +import KFIcon from '@/components/KFIcon'; +import KFModal from '@/components/KFModal'; +import { addDatesetAndVesion } from '@/services/dataset/index.js'; +import { getDictSelectOption } from '@/services/system/dict'; +import { to } from '@/utils/promise'; +import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; +import { + App, + Button, + Form, + Input, + Radio, + Select, + Upload, + UploadFile, + type ModalProps, + type UploadProps, +} from 'antd'; +import { omit } from 'lodash'; +import { useEffect, useState } from 'react'; +import { CategoryData } from '../../types'; +import styles from './index.less'; + +interface AddDatasetModalProps extends Omit { + typeList: CategoryData[]; + tagList: CategoryData[]; + onOk: () => void; +} + +function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) { + const [uuid] = useState(Date.now()); + const [clusterOptions, setClusterOptions] = useState([]); + const { message } = App.useApp(); + + useEffect(() => { + getClusterOptions(); + }, []); + + // 上传组件参数 + const uploadProps: UploadProps = { + action: '/api/mmp/dataset/upload', + headers: { + Authorization: getAccessToken() || '', + }, + defaultFileList: [], + }; + + // 获取集群版本数据 + const getClusterOptions = async () => { + const [res] = await to(getDictSelectOption('available_cluster')); + if (res) { + setClusterOptions(res); + } + }; + + // 上传请求 + const createDataset = async (params: any) => { + const [res] = await to(addDatesetAndVesion(params)); + if (res) { + message.success('创建成功'); + onOk?.(); + } + }; + + // 提交 + const onFinish = (formData: any) => { + const fileList: UploadFile[] = formData['fileList'] ?? []; + if (validateUploadFiles(fileList)) { + const params = { + ...omit(formData, ['fileList']), + dataset_version_vos: fileList.map((item) => { + const data = item.response?.data?.[0] ?? {}; + return { + file_name: data.fileName, + file_size: data.fileSize, + url: data.url, + }; + }), + }; + createDataset(params); + } + }; + + return ( + +
+ + + + + + + + + + + + + + + + + + + + {/* + + 仅自己可见 + 工作空间可见 + + */} + + + + + + + + +
+
+ ); +} + +export default AddModelModal; diff --git a/react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx b/react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx new file mode 100644 index 00000000..c00569f7 --- /dev/null +++ b/react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx @@ -0,0 +1,170 @@ +import { getAccessToken } from '@/access'; +import KFIcon from '@/components/KFIcon'; +import KFModal from '@/components/KFModal'; +import { ResourceType, resourceConfig } from '@/pages/Dataset/types'; +import { to } from '@/utils/promise'; +import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; +import { + App, + Button, + Form, + Input, + Upload, + UploadFile, + type ModalProps, + type UploadProps, +} from 'antd'; +import { omit } from 'lodash'; +import { useState } from 'react'; +import styles from '../AddDatasetModal/index.less'; + +interface AddVersionModalProps extends Omit { + resourceType: ResourceType; + resourceId: number; + initialName: string; + onOk: () => void; +} + +function AddVersionModal({ + resourceType, + resourceId, + initialName, + onOk, + ...rest +}: AddVersionModalProps) { + const [uuid] = useState(Date.now()); + const { message } = App.useApp(); + + // 上传组件参数 + const uploadProps: UploadProps = { + action: resourceConfig[resourceType].uploadAction, + headers: { + Authorization: getAccessToken() || '', + }, + defaultFileList: [], + }; + + // 上传请求 + const createDatasetVersion = async (params: any) => { + const request = resourceConfig[resourceType].addVersionReq; + const [res] = await to(request(params)); + if (res) { + message.success('创建成功'); + onOk?.(); + } + }; + + // 提交 + const onFinish = (formData: any) => { + const fileList: UploadFile[] = formData['fileList'] ?? []; + if (validateUploadFiles(fileList)) { + const otherParams = omit(formData, ['fileList']); + const params = fileList.map((item) => { + const data = item.response?.data?.[0] ?? {}; + return { + ...otherParams, + [resourceConfig[resourceType].idParamKey]: resourceId, + file_name: data.fileName, + file_size: data.fileSize, + url: data.url, + }; + }); + createDatasetVersion(params); + } + }; + + const name = resourceConfig[resourceType].name; + const accept = resourceConfig[resourceType].uploadAccept; + return ( + +
+ + + + + + + + + + + + + {resourceType === ResourceType.Dataset && ( +
只允许上传.zip格式文件
+ )} +
+
+
+
+ ); +} + +export default AddVersionModal; diff --git a/react-ui/src/pages/Dataset/components/CategoryItem/index.less b/react-ui/src/pages/Dataset/components/CategoryItem/index.less new file mode 100644 index 00000000..3b9f2320 --- /dev/null +++ b/react-ui/src/pages/Dataset/components/CategoryItem/index.less @@ -0,0 +1,41 @@ +.category-item { + display: flex; + flex-direction: column; + align-items: center; + width: 92px; + height: 62px; + padding: 11px 8px 7px; + color: @text-color; + font-size: 12px; + border: 1px solid rgba(22, 100, 255, 0.05); + border-radius: 4px; + cursor: pointer; + + &__icon { + display: block; + } + &__active-icon { + display: none; + } + &__name { + width: 100%; + margin-top: 4px; + overflow: hidden; + white-space: nowrap; + text-align: center; + text-overflow: ellipsis; + } + + &:hover, + &--active { + background: rgba(22, 100, 255, 0.03); + border: 1px solid @primary-color; + + .category-item__icon { + display: none; + } + .category-item__active-icon { + display: block; + } + } +} diff --git a/react-ui/src/pages/Dataset/components/CategoryItem/index.tsx b/react-ui/src/pages/Dataset/components/CategoryItem/index.tsx new file mode 100644 index 00000000..23f8ce5f --- /dev/null +++ b/react-ui/src/pages/Dataset/components/CategoryItem/index.tsx @@ -0,0 +1,37 @@ +import classNames from 'classnames'; +import { CategoryData, ResourceType, resourceConfig } from '../../types'; +import styles from './index.less'; + +type CategoryItemProps = { + resourceType: ResourceType; + item: CategoryData; + isSelected: boolean; + onClick: (item: CategoryData) => void; +}; + +function CategoryItem({ resourceType, item, isSelected, onClick }: CategoryItemProps) { + return ( +
onClick(item)} + > + + + {item.name} +
+ ); +} + +export default CategoryItem; diff --git a/react-ui/src/pages/Dataset/components/CategoryList/index.less b/react-ui/src/pages/Dataset/components/CategoryList/index.less new file mode 100644 index 00000000..5f925607 --- /dev/null +++ b/react-ui/src/pages/Dataset/components/CategoryList/index.less @@ -0,0 +1,22 @@ +.category-list { + width: 340px; + height: 100%; + margin-right: 10px; + padding: 15px 0; + background: white; + border-radius: 4px; + box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); + + &__content { + height: calc(100% - 32px - 15px); + margin-top: 15px; + padding-left: 20px; + overflow-y: auto; + + &__title { + margin-bottom: 15px; + color: @text-color; + font-size: 14px; + } + } +} diff --git a/react-ui/src/pages/Dataset/components/CategoryList/index.tsx b/react-ui/src/pages/Dataset/components/CategoryList/index.tsx new file mode 100644 index 00000000..28a8de66 --- /dev/null +++ b/react-ui/src/pages/Dataset/components/CategoryList/index.tsx @@ -0,0 +1,71 @@ +import { Flex, Input } from 'antd'; +import { CategoryData, ResourceType, resourceConfig } from '../../types'; +import CategoryItem from '../CategoryItem'; +import styles from './index.less'; + +export type CategoryValue = { + dataType: number | undefined; + dataTag: number | undefined; +}; + +type CategoryProps = { + resourceType: ResourceType; // 资源类型,数据集还是模型 + typeList: CategoryData[]; + tagList: CategoryData[]; + activeType?: number; + activeTag?: number; + onTypeSelect: (value: CategoryData) => void; + onTagSelect: (value: CategoryData) => void; + onSearch: (value: string) => void; +}; + +function CategoryList({ + resourceType, + typeList, + tagList, + activeType, + activeTag, + onTypeSelect, + onTagSelect, + onSearch, +}: CategoryProps) { + return ( +
+
+ +
+
+
+ {resourceConfig[resourceType].typeTitle} +
+ + {typeList?.map((item) => ( + + ))} + +
+ {resourceConfig[resourceType].tagTitle} +
+ + {tagList?.map((item) => ( + + ))} + +
+
+ ); +} + +export default CategoryList; diff --git a/react-ui/src/pages/Dataset/components/ResourceList/index.less b/react-ui/src/pages/Dataset/components/ResourceList/index.less new file mode 100644 index 00000000..91407e80 --- /dev/null +++ b/react-ui/src/pages/Dataset/components/ResourceList/index.less @@ -0,0 +1,39 @@ +.resource-list { + display: flex; + flex: 1; + flex-direction: column; + height: 100%; + padding: 20px 0; + background: white; + box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); + + &__header { + display: flex; + align-items: center; + justify-content: space-between; + height: 32px; + margin-bottom: 30px; + padding: 0 30px; + color: @text-color; + font-size: 15px; + } + + &__content { + display: flex; + flex: 1; + flex-wrap: wrap; + gap: 20px; + align-content: flex-start; + width: 100%; + margin-bottom: 30px; + padding: 0 30px; + overflow-y: auto; + } + + :global { + .ant-pagination { + margin-right: 30px; + text-align: right; + } + } +} diff --git a/react-ui/src/pages/Dataset/components/ResourceList/index.tsx b/react-ui/src/pages/Dataset/components/ResourceList/index.tsx new file mode 100644 index 00000000..208e18c5 --- /dev/null +++ b/react-ui/src/pages/Dataset/components/ResourceList/index.tsx @@ -0,0 +1,211 @@ +import KFIcon from '@/components/KFIcon'; +import { CommonTabKeys } from '@/enums'; +import AddModelModal from '@/pages/Dataset/components/AddModelModal'; +import { openAntdModal } from '@/utils/modal'; +import { to } from '@/utils/promise'; +import { modalConfirm } from '@/utils/ui'; +import { useNavigate } from '@umijs/max'; +import { App, Button, Input, Pagination, PaginationProps } from 'antd'; +import { Ref, forwardRef, useEffect, useImperativeHandle, useState } from 'react'; +import { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../types'; +import AddDatasetModal from '../AddDatasetModal'; +import ResourceItem from '../Resourcetem'; +import styles from './index.less'; + +export type ResourceListRef = { + reset: () => void; +}; + +type ResourceListProps = { + resourceType: ResourceType; + dataType?: number; + dataTag?: number; + isPublic: boolean; + typeList: CategoryData[]; + tagList: CategoryData[]; + initialSearchText?: string; + initialPagination?: PaginationProps; + setCacheState: (state?: any) => void; +}; + +function ResourceList( + { + resourceType, + dataType, + dataTag, + typeList, + tagList, + isPublic, + initialSearchText, + initialPagination, + setCacheState, + }: ResourceListProps, + ref: Ref, +) { + const navigate = useNavigate(); + const [dataList, setDataList] = useState([]); + const [total, setTotal] = useState(0); + const [pagination, setPagination] = useState( + initialPagination ?? { + current: 1, + pageSize: 20, + }, + ); + const [searchText, setSearchText] = useState(initialSearchText); + const [inputText, setInputText] = useState(initialSearchText); + const { message } = App.useApp(); + + useEffect(() => { + getDataList(); + }, [resourceType, dataType, dataTag, pagination, searchText, isPublic]); + + useImperativeHandle( + ref, + () => { + return { + reset: () => { + setPagination({ + current: 1, + pageSize: 20, + }); + setSearchText(''); + setInputText(''); + setDataList([]); + }, + }; + }, + [], + ); + + // 获取数据请求 + const getDataList = async () => { + const params = { + page: pagination.current! - 1, + size: pagination.pageSize, + [resourceConfig[resourceType].typeParamKey]: dataType, + [resourceConfig[resourceType].tagParamKey]: dataTag, + available_range: isPublic ? 1 : 0, + name: searchText !== '' ? searchText : undefined, + }; + const request = resourceConfig[resourceType].getList; + const [res] = await to(request(params)); + if (res && res.data && res.data.content) { + setDataList(res.data.content); + setTotal(res.data.totalElements); + } + }; + + // 删除请求 + const deleteRecord = async (id: number) => { + const request = resourceConfig[resourceType].deleteRecord; + const [res] = await to(request(id)); + if (res) { + getDataList(); + message.success('删除成功'); + } + }; + + // 搜索 + const handleSearch = (value: string) => { + setSearchText(value); + }; + + // 删除 + const handleRemove = (record: ResourceData) => { + modalConfirm({ + title: resourceConfig[resourceType].deleteModalTitle, + onOk: () => { + deleteRecord(record.id); + }, + }); + }; + + // 跳转 + const handleClick = (record: ResourceData) => { + setCacheState({ + activeTab: isPublic ? CommonTabKeys.Public : CommonTabKeys.Private, + pagination, + searchText, + activeType: dataType, + activeTag: dataTag, + }); + if (resourceType === ResourceType.Dataset) { + navigate(`/dataset/dataset/${record.id}?isPublic=${isPublic}`); + } else { + navigate(`/dataset/model/${record.id}?isPublic=${isPublic}`); + } + }; + + // 分页切换 + const handlePageChange: PaginationProps['onChange'] = (page, pageSize) => { + setPagination({ + current: page, + pageSize: pageSize, + }); + }; + + // 新建弹框 + const showModal = () => { + const Modal = resourceType === ResourceType.Dataset ? AddDatasetModal : AddModelModal; + const { close } = openAntdModal(Modal, { + typeList: typeList, + tagList: tagList, + onOk: () => { + getDataList(); + close(); + }, + }); + }; + + return ( +
+
+ 数据总数:{total}个 +
+ setInputText(e.target.value)} + value={inputText} + /> + {!isPublic && ( + + )} +
+
+
+ {dataList?.map((item) => ( + + ))} +
+ +
+ ); +} + +export default forwardRef(ResourceList); diff --git a/react-ui/src/pages/Dataset/components/ResourcePage/index.less b/react-ui/src/pages/Dataset/components/ResourcePage/index.less new file mode 100644 index 00000000..000c6132 --- /dev/null +++ b/react-ui/src/pages/Dataset/components/ResourcePage/index.less @@ -0,0 +1,11 @@ +.resource-page { + height: 100%; + &__tabs-container { + height: 50px; + padding-left: 27px; + background-image: url(@/assets/img/page-title-bg.png); + background-repeat: no-repeat; + background-position: top center; + background-size: 100% 100%; + } +} diff --git a/react-ui/src/pages/Dataset/components/ResourcePage/index.tsx b/react-ui/src/pages/Dataset/components/ResourcePage/index.tsx new file mode 100644 index 00000000..9e4dff88 --- /dev/null +++ b/react-ui/src/pages/Dataset/components/ResourcePage/index.tsx @@ -0,0 +1,113 @@ +import { CommonTabKeys } from '@/enums'; +import { useCacheState } from '@/hooks/pageCacheState'; +import { getAssetIcon } from '@/services/dataset/index.js'; +import { to } from '@/utils/promise'; +import { Flex, Tabs, type TabsProps } from 'antd'; +import { useEffect, useRef, useState } from 'react'; +import { CategoryData, ResourceType, resourceConfig } from '../../types'; +import CategoryList from '../CategoryList'; +import ResourceList, { ResourceListRef } from '../ResourceList'; +import styles from './index.less'; + +type ResourcePageProps = { + resourceType: ResourceType; // 资源类型,数据集还是模型 +}; + +function ResourcePage({ resourceType }: ResourcePageProps) { + const [cacheState, setCacheState] = useCacheState(); + const [activeTab, setActiveTab] = useState(cacheState?.activeTab ?? CommonTabKeys.Public); + const [typeList, setTypeList] = useState([]); + const [tagList, setTagList] = useState([]); + const [activeType, setActiveType] = useState(cacheState?.activeType); + const [activeTag, setActiveTag] = useState(cacheState?.activeTag); + const dataListRef = useRef(null); + + useEffect(() => { + getAssetIconList(); + }, []); + + // 分类搜索 + const handleCategorySearch = (value: string) => { + getAssetIconList(value); + }; + + // 选择类型 + const chooseType = (record: CategoryData) => { + setActiveType((prev) => (prev === record.id ? undefined : record.id)); + }; + + // 选择 Tag + const chooseTag = (record: CategoryData) => { + setActiveTag((prev) => (prev === record.id ? undefined : record.id)); + }; + + // 获取分类 + const getAssetIconList = async (name: string = '') => { + const params = { + name: name, + page: 0, + size: 10000, + }; + const [res] = await to(getAssetIcon(params)); + if (res && res.data && res.data.content) { + const { content } = res.data; + setTypeList( + content.filter( + (item: CategoryData) => + Number(item.category_id) === resourceConfig[resourceType].typeValue, + ), + ); + setTagList( + content.filter( + (item: CategoryData) => + Number(item.category_id) === resourceConfig[resourceType].tagValue, + ), + ); + } + }; + + // 切换 Tab,重置数据 + const hanleTabChange: TabsProps['onChange'] = (value) => { + dataListRef.current?.reset(); + setActiveTab(value); + }; + + const isPublic = activeTab === CommonTabKeys.Public; + return ( +
+
+ +
+ + + + +
+ ); +} + +export default ResourcePage; diff --git a/react-ui/src/pages/Dataset/components/Resourcetem/index.less b/react-ui/src/pages/Dataset/components/Resourcetem/index.less new file mode 100644 index 00000000..edd97f85 --- /dev/null +++ b/react-ui/src/pages/Dataset/components/Resourcetem/index.less @@ -0,0 +1,61 @@ +.resource-item { + position: relative; + width: calc(25% - 15px); + padding: 20px; + background: white; + border: 1px solid #eaeaea; + border-radius: 4px; + cursor: pointer; + + @media screen and (max-width: 1860px) { + & { + width: calc(33.33% - 13.33px); + } + } + + &__name { + position: relative; + display: inline-block; + height: 24px; + margin: 0 10px 0 0 !important; + color: @text-color; + font-size: 16px; + } + + &__description { + height: 44px; + margin-bottom: 20px; + color: @text-color-secondary; + font-size: 14px; + .multiLine(2); + } + &__time { + display: flex; + flex: 0 1 content; + align-items: center; + width: 100%; + color: #808080; + font-size: 13px; + } + + &:hover { + border-color: @primary-color; + box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1); + + .resource-item__name { + color: @primary-color; + } + } +} + +.resource-item__name { + &::after { + position: absolute; + top: 14px; + left: 0; + width: 100%; + height: 6px; + background: linear-gradient(to right, rgba(22, 100, 255, 0.3) 0, rgba(22, 100, 255, 0) 100%); + content: ''; + } +} diff --git a/react-ui/src/pages/Dataset/components/Resourcetem/index.tsx b/react-ui/src/pages/Dataset/components/Resourcetem/index.tsx new file mode 100644 index 00000000..b8ad9750 --- /dev/null +++ b/react-ui/src/pages/Dataset/components/Resourcetem/index.tsx @@ -0,0 +1,54 @@ +import clock from '@/assets/img/clock.png'; +import creatByImg from '@/assets/img/creatBy.png'; +import KFIcon from '@/components/KFIcon'; +import { formatDate } from '@/utils/date'; +import { Button, Flex, Typography } from 'antd'; +import { ResourceData } from '../../types'; +import styles from './index.less'; + +type ResourceItemProps = { + item: ResourceData; + isPublic: boolean; + onRemove: (item: ResourceData) => void; + onClick: (item: ResourceData) => void; +}; + +function ResourceItem({ item, isPublic, onClick, onRemove }: ResourceItemProps) { + return ( +
onClick(item)}> + + + {item.name} + + {!isPublic && ( + + )} + +
{item.description}
+ +
+ + {item.create_by} +
+
+ + 最近更新: {formatDate(item.update_time, 'YYYY-MM-DD')} +
+
+
+ ); +} + +export default ResourceItem; diff --git a/react-ui/src/pages/Dataset/index.jsx b/react-ui/src/pages/Dataset/index.jsx index 4d39b34e..33d2844d 100644 --- a/react-ui/src/pages/Dataset/index.jsx +++ b/react-ui/src/pages/Dataset/index.jsx @@ -1,90 +1,7 @@ -import { getDatasetList } from '@/services/dataset/index.js'; -import { Form, Input, Tabs } from 'antd'; -import { useEffect, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import Styles from './index.less'; -import PersonalData from './personalData'; -import PublicData from './publicData'; -const { Search } = Input; -const { TabPane } = Tabs; -const leftdataList = [1, 2, 3]; +import ResourcePage from './components/ResourcePage'; +import { ResourceType } from './types'; -const Dataset = () => { - const [queryFlow, setQueryFlow] = useState({ - page: 0, - size: 10, - name: null, - }); - const navgite = useNavigate(); - const [isModalOpen, setIsModalOpen] = useState(false); - const [datasetList, setDatasetList] = useState([]); - const [total, setTotal] = useState(0); - const [form] = Form.useForm(); - const [dialogTitle, setDialogTitle] = useState('新建数据'); - const getDatasetlist = () => { - getDatasetList(queryFlow).then((ret) => { - console.log(ret); - if (ret.code == 200) { - setDatasetList(ret.data.content); - setTotal(ret.data.totalElements); - } - }); - }; - - const showModal = () => { - form.resetFields(); - setDialogTitle('新建数据集'); - setIsModalOpen(true); - }; - const handleOk = () => { - console.log(1111); - setIsModalOpen(false); - }; - const handleCancel = () => { - setIsModalOpen(false); - }; - const onFinish = (values) => {}; - const routeToIntro = (e, record) => { - e.stopPropagation(); - navgite({ pathname: '/dataset/dataset' }); - }; - const onFinishFailed = (errorInfo) => { - console.log('Failed:', errorInfo); - }; - useEffect(() => { - //getDatasetlist(); - return () => {}; - }, []); - return ( -
-
-
- - - - -
-
- ); +const DatasetPage = () => { + return ; }; -export default Dataset; +export default DatasetPage; diff --git a/react-ui/src/pages/Dataset/index.less b/react-ui/src/pages/Dataset/index.less deleted file mode 100644 index d270f560..00000000 --- a/react-ui/src/pages/Dataset/index.less +++ /dev/null @@ -1,337 +0,0 @@ -.datasetTopBox { - display: flex; - align-items: center; - width: 100%; - height: 49px; - padding: 0 30px; - padding-right: 30px; - font-family: 'Alibaba'; - background-image: url(/assets/images/pipeline-back.png); - background-size: 100% 100%; -} -.datasetIntroTopBox { - display: flex; - flex-direction: column; - justify-content: space-between; - width: 100%; - height: 110px; - margin-bottom: 10px; - padding: 25px 30px; - background-image: url(/assets/images/dataset-back.png); - background-size: 100% 100%; - .smallTagBox { - display: flex; - align-items: center; - color: #1664ff; - font-size: 14px; - .tagItem { - margin-right: 20px; - padding: 4px 10px; - background: rgba(22, 100, 255, 0.1); - border-radius: 4px; - } - } -} -.dataListBox { - padding: 20px 30px; - color: #1d1d20; - font-size: 16px; - font-family: alibaba; - background: #ffffff; - border-radius: 10px; - box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); - .dataButtonList { - display: flex; - align-items: center; - justify-content: space-between; - height: 32px; - margin: 24px 0 30px 0; - color: #575757; - font-size: 16px; - } -} -.datasetIntroCneterBox { - height: 77vh; - padding: 20px 30px; - background: #ffffff; - border-radius: 10px; - box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); -} -.datasetIntroTitle { - margin: 37px 0 10px 0; - color: #1d1d20; - font-size: 15px; -} -.datasetIntroText { - margin-bottom: 30px; - color: #575757; - font-size: 14px; -} -.datasetBox { - font-family: 'Alibaba'; - background: #f9fafb; - - :global { - .ant-tabs-top > .ant-tabs-nav { - margin: 0; - } - .ant-pagination { - text-align: right; - } - } -} -.datasetAllBox { - :global { - .ant-tabs-nav .ant-tabs-nav-wrap { - margin: -48px 0 0 30px; - } - } -} -.plusButton { - margin: 0 18px 0 20px; -} - -.datasetCneterBox { - display: flex; - justify-content: space-between; - width: 100%; - height: 87.5vh; - :global { - .ant-btn { - color: #1d1d20; - font-size: 14px; - } - } - .datasetCneterLeftBox { - width: 340px; - height: 100%; - margin-right: 10px; - padding-top: 15px; - font-family: 'Alibaba'; - background: #ffffff; - box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); - .custTab { - display: flex; - height: 32px; - border-bottom: 1px solid #e0eaff; - .tabItem { - width: 52px; - height: 100%; - color: #808080; - font-size: 15px; - text-align: center; - cursor: pointer; - } - } - .leftContentBox { - max-height: 80vh; - padding: 15px 20px; - overflow-x: hidden; - overflow-y: auto; - font-family: 'Alibaba'; - .itemTitle { - margin-bottom: 15px; - color: #1d1d20; - font-size: 14px; - } - .itemBox { - display: flex; - flex-wrap: wrap; - align-content: start; - width: 110%; - .messageBox { - display: flex; - flex-direction: column; - align-items: center; - justify-content: space-between; - width: 92px; - height: 62px; - margin: 0 12px 20px 0; - padding: 11px 0px 7px 0px; - color: #1d1d20; - font-size: 12px; - border: 1px solid; - border-color: rgba(22, 100, 255, 0.05); - border-radius: 4px; - cursor: pointer; - .ptIcon { - display: block; - } - .hoverIcon { - display: none; - } - .messageText { - width: 65px; - overflow: hidden; - white-space: nowrap; - text-align: center; - text-overflow: ellipsis; - } - } - .messageBox:hover { - background: rgba(22, 100, 255, 0.03); - border: 1px solid; - border-color: #1664ff; - .ptIcon { - display: none; - } - .hoverIcon { - display: block; - } - } - .active { - background: rgba(22, 100, 255, 0.03) !important; - border: 1px solid !important; - border-color: #1664ff !important; - .ptIcon { - display: none !important; - } - .hoverIcon { - display: block !important; - } - } - } - } - } - - .datasetCneterRightBox { - display: flex; - flex: 1; - flex-direction: column; - height: 100%; - overflow-y: auto; - padding: 22px 30px 26px 30px; - background: #ffffff; - box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); - .dataSource { - display: flex; - align-items: center; - justify-content: space-between; - height: 32px; - margin-bottom: 30px; - color: rgba(29, 29, 32, 0.8); - font-size: 15px; - } - .dataContent { - display: flex; - flex: 1; - flex-wrap: wrap; - align-content: flex-start; - - width: 100%; - .dataItem { - position: relative; - width: 23.8%; - height:164px; - margin: 0 20px 25px 0; - background: #ffffff; - border: 1px solid; - border-color: #eaeaea; - border-radius: 4px; - cursor: pointer; - .dropdown{ - position: absolute; - right: 20px; - top: 15px; - } - .itemText { - position: absolute; - top: 20px; - left: 20px; - height: 6px; - color: #1d1d20; - font-size: 16px; - font-family: 'Alibaba'; - line-height: 0px; - background: linear-gradient( - to right, - rgba(22, 100, 255, 0.3) 0, - rgba(22, 100, 255, 0) 100% - ); - } - .itemDescripition { - position: absolute; - top: 57px; - left: 20px; - display: -webkit-box; - padding-right: 28px; - overflow: hidden; - color: #575757; - font-size: 14px; - word-break: break-all; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - } - .itemTime { - position: absolute; - bottom: 22px; - left: 20px; - display: flex; - align-items: center; - color: #808080; - font-size: 13px; - } - .itemIcon { - position: absolute; - right: 20px; - bottom: 22px; - display: flex; - align-items: center; - color: #808080; - font-size: 13px; - } - } - .dataItem:hover { - border-color: #1664ff; - box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1); - } - .dataItem:hover .itemText { - color: #1664ff; - } - } - } -} -.tipContent{ - color: #c73131; - margin-top: 5px; -} -.modal { - :global { - .ant-modal-content { - width: 825px; - padding: 20px 67px; - background-image: url(/assets/images/modal-back.png); - background-repeat: no-repeat; - background-position: top center; - background-size: 100%; - border-radius: 21px; - } - .ant-modal-header { - margin: 20px 0; - background-color: transparent; - } - .ant-input { - height: 40px; - border-color: #e6e6e6; - } - .ant-form-item .ant-form-item-label > label { - color: rgba(29, 29, 32, 0.8); - } - .ant-modal-footer { - display: flex; - justify-content: center; - margin: 40px 0 30px 0; - } - .ant-btn { - width: 110px; - height: 40px; - font-size: 18px; - background: rgba(22, 100, 255, 0.06); - border-color: transparent; - border-radius: 10px; - } - .ant-btn-primary { - background: #1664ff; - } - } -} diff --git a/react-ui/src/pages/Dataset/datasetIntro.jsx b/react-ui/src/pages/Dataset/intro.jsx similarity index 55% rename from react-ui/src/pages/Dataset/datasetIntro.jsx rename to react-ui/src/pages/Dataset/intro.jsx index 91aee652..6d556c08 100644 --- a/react-ui/src/pages/Dataset/datasetIntro.jsx +++ b/react-ui/src/pages/Dataset/intro.jsx @@ -1,61 +1,35 @@ -import { getAccessToken } from '@/access'; import KFIcon from '@/components/KFIcon'; +import { ResourceType } from '@/pages/Dataset/types'; import { - addDatasetVersionDetail, deleteDatasetVersion, getDatasetById, getDatasetVersionIdList, getDatasetVersionsById, } from '@/services/dataset/index.js'; +import { formatDate } from '@/utils/date'; import { downLoadZip } from '@/utils/downloadfile'; -import { UploadOutlined } from '@ant-design/icons'; -import { Button, Form, Input, Modal, Select, Table, Tabs, Upload, message } from 'antd'; -import moment from 'moment'; +import { openAntdModal } from '@/utils/modal'; +import { modalConfirm } from '@/utils/ui'; +import { useParams, useSearchParams } from '@umijs/max'; +import { App, Button, Input, Select, Table, Tabs } from 'antd'; import { useEffect, useRef, useState } from 'react'; -import { useParams } from 'react-router-dom'; -import Styles from './index.less'; +import AddVersionModal from './components/AddVersionModal'; +import Styles from './intro.less'; const { Search } = Input; const { TabPane } = Tabs; const Dataset = () => { - const props = { - action: '/api/mmp/dataset/upload', - // headers: { - // 'X-Requested-With': null - // }, - headers: { - Authorization: getAccessToken(), - 'X-Requested-With': null, - }, - onChange({ file, fileList }) { - if (file.status !== 'uploading') { - console.log(file, fileList); - setFormList( - fileList.map((item) => { - return { - ...form.getFieldsValue(), - dataset_id: locationParams.id, - file_name: item.response.code === 200 ? item.response.data[0].fileName : null, - file_size: item.response.code === 200 ? item.response.data[0].fileSize : null, - url: item.response.code === 200 ? item.response.data[0].url : null, - }; - }), - ); - } - }, - defaultFileList: [], - }; - const [form] = Form.useForm(); + const { message } = App.useApp(); const [formList, setFormList] = useState([]); - const [dialogTitle, setDialogTitle] = useState('新建版本'); - const [isModalOpen, setIsModalOpen] = useState(false); const [datasetDetailObj, setDatasetDetailObj] = useState({}); const [version, setVersion] = useState(null); const [versionList, setVersionList] = useState([]); const locationParams = useParams(); //新版本获取路由参数接口 + const [searchParams] = useSearchParams(); const [wordList, setWordList] = useState([]); const [activeTabKey, setActiveTabKey] = useState('1'); - const [uuid, setUuid] = useState(Date.now()); + const isPublic = searchParams.get('isPublic') === 'true'; + const getDatasetByDetail = () => { getDatasetById(locationParams.id).then((ret) => { console.log(ret); @@ -77,6 +51,9 @@ const Dataset = () => { ); setVersion(ret.data[0]); getDatasetVersions({ version: ret.data[0], dataset_id: locationParams.id }); + } else { + setVersion(null); + setWordList([]); } }); }; @@ -86,37 +63,21 @@ const Dataset = () => { return () => {}; }, []); const showModal = () => { - form.resetFields(); - form.setFieldsValue({ name: datasetDetailObj.name }); - - setDialogTitle('创建新版本'); - setUuid(Date.now()); - setIsModalOpen(true); - }; - const handleCancel = () => { - setIsModalOpen(false); - }; - const handleExport = async () => { - const hide = message.loading('正在下载'); - hide(); - downLoadZip(`/api/mmp/dataset/downloadAllFiles`, { dataset_id: locationParams.id, version }); + const { close } = openAntdModal(AddVersionModal, { + resourceType: ResourceType.Dataset, + resourceId: locationParams.id, + initialName: datasetDetailObj.name, + onOk: () => { + getDatasetVersionList(); + close(); + }, + }); }; - const deleteDataset = () => { - Modal.confirm({ - title: ( -
- -
删除后,该数据集版本将不可恢复
-
- ), - content:
是否确认删除?
, - okText: '确认', - cancelText: '取消', + const deleteDataset = () => { + modalConfirm({ + title: '删除后,该数据集版本将不可恢复', + content: '是否确认删除?', onOk: () => { deleteDatasetVersion({ dataset_id: locationParams.id, version }).then((ret) => { getDatasetVersionList(); @@ -125,19 +86,26 @@ const Dataset = () => { }, }); }; - const onFinish = (values) => { - addDatasetVersionDetail(formList).then((ret) => { - getDatasetVersionList(); - setIsModalOpen(false); - message.success('创建成功'); - }); - }; // 获取版本下的文件列表 const getDatasetVersions = (params) => { getDatasetVersionIdList(params).then((res) => { setWordList(res?.data?.content ?? []); }); }; + + const handleExport = async () => { + const hide = message.loading('正在下载'); + hide(); + downLoadZip(`/api/mmp/dataset/downloadAllFiles`, { dataset_id: locationParams.id, version }); + }; + + const downloadAlone = (e, record) => { + console.log(record); + const hide = message.loading('正在下载'); + hide(); + downLoadZip(`/api/mmp/dataset/download/${record.id}`); + }; + const handleChange = (value) => { console.log(value); if (value) { @@ -147,15 +115,7 @@ const Dataset = () => { setVersion(null); } }; - const onFinishFailed = (errorInfo) => { - console.log('Failed:', errorInfo); - }; - const downloadAlone = (e, record) => { - console.log(record); - const hide = message.loading('正在下载'); - hide(); - downLoadZip(`/api/mmp/dataset/download/${record.id}`); - }; + const columns = [ { title: '序号', @@ -187,7 +147,7 @@ const Dataset = () => { title: '更新时间', dataIndex: 'update_time', key: 'update_time', - render: (text) => {moment(text).format('YYYY-MM-DD HH:mm:ss')}, + render: (text) => {formatDate(text)}, }, { title: '操作', @@ -264,15 +224,17 @@ const Dataset = () => {
- + {!isPublic && ( + + )}
- - - {dialogTitle} - - } - open={isModalOpen} - className={Styles.modal} - okButtonProps={{ - htmlType: 'submit', - form: 'form', - }} - onCancel={handleCancel} - > -
- - - - - - - - - - - - -
只允许上传.zip,.tgz格式文件
-
-
-
-
); }; diff --git a/react-ui/src/pages/Dataset/intro.less b/react-ui/src/pages/Dataset/intro.less new file mode 100644 index 00000000..b36af2cf --- /dev/null +++ b/react-ui/src/pages/Dataset/intro.less @@ -0,0 +1,82 @@ +.datasetIntroTopBox { + display: flex; + flex-direction: column; + justify-content: space-between; + width: 100%; + height: 110px; + margin-bottom: 10px; + padding: 25px 30px; + background-image: url(/assets/images/dataset-back.png); + background-repeat: no-repeat; + background-position: top center; + background-size: 100% 100%; + + .smallTagBox { + display: flex; + align-items: center; + color: #1664ff; + font-size: 14px; + .tagItem { + margin-right: 20px; + padding: 4px 10px; + background: rgba(22, 100, 255, 0.1); + border-radius: 4px; + } + } +} +.dataListBox { + padding: 20px 30px; + color: #1d1d20; + font-size: 16px; + font-family: alibaba; + background: #ffffff; + border-radius: 10px; + box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); + .dataButtonList { + display: flex; + align-items: center; + justify-content: space-between; + height: 32px; + margin: 24px 0 30px 0; + color: #575757; + font-size: 16px; + } +} +.datasetIntroCneterBox { + height: 77vh; + padding: 20px 30px; + background: #ffffff; + border-radius: 10px; + box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); +} +.datasetIntroTitle { + margin: 37px 0 10px 0; + color: #1d1d20; + font-size: 15px; +} +.datasetIntroText { + margin-bottom: 30px; + color: #575757; + font-size: 14px; +} +.datasetBox { + background: #f9fafb; + + :global { + .ant-tabs-top > .ant-tabs-nav { + margin: 0; + } + .ant-pagination { + text-align: right; + } + } +} + +.plusButton { + margin: 0 18px 0 20px; +} + +.tipContent { + margin-top: 5px; + color: #c73131; +} diff --git a/react-ui/src/pages/Dataset/personalData.jsx b/react-ui/src/pages/Dataset/personalData.jsx deleted file mode 100644 index 0d126025..00000000 --- a/react-ui/src/pages/Dataset/personalData.jsx +++ /dev/null @@ -1,479 +0,0 @@ -import { getAccessToken } from '@/access'; -import clock from '@/assets/img/clock.png'; -import creatByImg from '@/assets/img/creatBy.png'; -import deleteIcon from '@/assets/img/delete-icon.png'; -import KFIcon from '@/components/KFIcon'; -import { - addDatesetAndVesion, - deleteDataset, - getAssetIcon, - getDatasetList, -} from '@/services/dataset/index.js'; -import { getDictSelectOption } from '@/services/system/dict'; -import { modalConfirm } from '@/utils/ui'; -import { UploadOutlined } from '@ant-design/icons'; -import { Button, Form, Input, Modal, Pagination, Radio, Select, Upload } from 'antd'; -import moment from 'moment'; -import React, { useEffect, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; - -import './index.less'; -import Styles from './index.less'; -const { Search } = Input; -const leftdataList = [1, 2, 3]; - -const PublicData = (React.FC = () => { - const props = { - action: '/api/mmp/dataset/upload', - // headers: { - // 'X-Requested-With': null - // }, - headers: { - Authorization: getAccessToken(), - 'X-Requested-With': null, - }, - onChange({ file, fileList }) { - if (file.status !== 'uploading') { - console.log(file, fileList); - form.setFieldsValue({ - dataset_version_vos: fileList.map((item) => { - const data = item.response.data[0]; - return { - file_name: data.fileName, - file_size: data.fileSize, - url: data.url, - }; - }), - }); - } - }, - defaultFileList: [], - }; - const [queryFlow, setQueryFlow] = useState({ - page: 0, - size: 20, - name: null, - available_range: 0, - }); - const [iconParams, setIconParams] = useState({ - name: null, - page: 0, - size: 10000, - }); - const [activeType, setActiveType] = useState(null); - const [activeTag, setActiveTag] = useState(null); - const [datasetTypeList, setDatasetTypeList] = useState([]); - const [datasetDirectionList, setDatasetDirectionList] = useState([]); - const navgite = useNavigate(); - const [clusterOptions, setClusterOptions] = useState([]); - const [isModalOpen, setIsModalOpen] = useState(false); - const [datasetList, setDatasetList] = useState([]); - const [total, setTotal] = useState(0); - const [form] = Form.useForm(); - const [dialogTitle, setDialogTitle] = useState('新建数据'); - const [uuid, setUuid] = useState(Date.now()); - const getDatasetlist = (queryFlow) => { - getDatasetList(queryFlow).then((ret) => { - console.log(ret); - if (ret.code == 200) { - setDatasetList(ret.data.content); - setTotal(ret.data.totalElements); - } - }); - }; - - const showModal = () => { - form.resetFields(); - setDialogTitle('新建数据集'); - setUuid(Date.now()); - setIsModalOpen(true); - }; - const getAssetIconList = (params) => { - getAssetIcon(params).then((ret) => { - console.log(ret); - if (ret.code == 200 && ret.data.content && ret.data.content.length > 0) { - setDatasetTypeList(ret.data.content.filter((item) => item.category_id == 1)); - setDatasetDirectionList(ret.data.content.filter((item) => item.category_id == 2)); - } else { - setDatasetTypeList([]); - setDatasetDirectionList([]); - } - }); - }; - const onSearch = (values) => { - console.log(values); - getAssetIconList({ ...iconParams, name: values }); - }; - const nameSearch = (values) => { - console.log(values); - getDatasetlist({ ...queryFlow, name: values }); - }; - const handleOk = () => { - console.log(1111); - setIsModalOpen(false); - }; - const handleCancel = () => { - setIsModalOpen(false); - }; - const chooseDatasetType = (val, item) => { - console.log(val, item); - if (item.id == queryFlow.data_type) { - setActiveType(''); - setQueryFlow({ ...queryFlow, data_type: null }); - getDatasetlist({ ...queryFlow, data_type: null }); - } else { - setActiveType(item.id); - setQueryFlow({ ...queryFlow, data_type: item.id }); - getDatasetlist({ ...queryFlow, data_type: item.id }); - } - // setQueryFlow({...queryFlow,data_type:item.path},()=>{ - // getDatasetlist() - // }) - }; - const chooseDatasetTag = (val, item) => { - console.log(val, item); - if (item.id == queryFlow.data_tag) { - setActiveTag(''); - setQueryFlow({ ...queryFlow, data_tag: null }); - getDatasetlist({ ...queryFlow, data_tag: null }); - } else { - setActiveTag(item.id); - setQueryFlow({ ...queryFlow, data_tag: item.id }); - getDatasetlist({ ...queryFlow, data_tag: item.id }); - } - // setQueryFlow({...queryFlow,data_type:item.path},()=>{ - // getDatasetlist() - // }) - }; - const onFinish = (values) => { - addDatesetAndVesion(values).then((ret) => { - console.log(ret); - setIsModalOpen(false); - getDatasetlist(queryFlow); - }); - }; - const routeToIntro = (e, record) => { - e.stopPropagation(); - console.log(record); - navgite({ pathname: `/dataset/dataset/${record.id}` }); - }; - const onFinishFailed = (errorInfo) => { - console.log('Failed:', errorInfo); - }; - const onPageChange = (pageNum, pageSize) => { - console.log(pageNum, pageSize); - setQueryFlow({ ...queryFlow, page: pageNum - 1, size: pageSize }); - getDatasetlist({ ...queryFlow, page: pageNum - 1, size: pageSize }); - }; - useEffect(() => { - getDictSelectOption('available_cluster').then((data) => { - setClusterOptions(data); - }); - getAssetIconList(iconParams); - getDatasetlist(queryFlow); - return () => {}; - }, []); - return ( - <> -
-
-
- -
分类
-
- {datasetTypeList && datasetTypeList.length > 0 - ? datasetTypeList.map((item) => { - return ( -
-
{ - chooseDatasetType(e, item); - }} - > - - - {item.name} -
-
- ); - }) - : ''} -
-
研究方向/应用领域
-
- {datasetDirectionList && datasetDirectionList.length > 0 - ? datasetDirectionList.map((item) => { - return ( -
-
{ - chooseDatasetTag(e, item); - }} - > - - - {item.name} -
-
- ); - }) - : ''} -
-
-
-
-
- 数据总数:{total}个 -
- - -
-
-
- {datasetList && datasetList.length > 0 - ? datasetList.map((item) => { - return ( -
routeToIntro(e, item)}> - {item.name} - { - e.stopPropagation(); - modalConfirm({ - title: '确定删除该条数据集实例吗?', - onOk: () => { - deleteDataset(item.id).then((ret) => { - if (ret.code === 200) { - message.success('删除成功'); - getModelLists(queryFlow); - } else { - message.error(ret.msg); - } - }); - }, - }); - }} - className={Styles.dropdown} - style={{ width: '17px', marginRight: '6px' }} - src={deleteIcon} - alt="" - /> -
{item.description}
- -
- - {item.create_by} -
-
- - 最近更新: {moment(item.update_time).format('YYYY-MM-DD')} -
-
- ); - }) - : ''} - {/* Demo */} -
- -
-
- - - {dialogTitle} - - } - open={isModalOpen} - className={Styles.modal} - okButtonProps={{ - htmlType: 'submit', - form: 'form', - }} - onCancel={handleCancel} - > -
- - - - - - - - { - return { value: item.id, label: item.name }; - })} - /> - - - - - - - 仅自己可见 - 工作空间可见 - - - - - -
只允许上传.zip,.tgz格式文件
-
-
-
-
- - ); -}); -export default PublicData; diff --git a/react-ui/src/pages/Dataset/publicData.jsx b/react-ui/src/pages/Dataset/publicData.jsx deleted file mode 100644 index bf5980d4..00000000 --- a/react-ui/src/pages/Dataset/publicData.jsx +++ /dev/null @@ -1,284 +0,0 @@ -import clock from '@/assets/img/clock.png'; -import creatByImg from '@/assets/img/creatBy.png'; -import deleteIcon from '@/assets/img/delete-icon.png'; -import { deleteDataset, getAssetIcon, getDatasetList } from '@/services/dataset/index.js'; -import { modalConfirm } from '@/utils/ui'; -import { Form, Input, Pagination } from 'antd'; -import moment from 'moment'; -import { useEffect, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import Styles from './index.less'; -const { Search } = Input; -const leftdataList = [1, 2, 3]; - -const PublicData = () => { - const [queryFlow, setQueryFlow] = useState({ - page: 0, - size: 10, - name: null, - available_range: 1, - }); - const [iconParams, setIconParams] = useState({ - name: null, - page: 0, - size: 10000, - }); - const navgite = useNavigate(); - const [datasetTypeList, setDatasetTypeList] = useState([]); - const [datasetDirectionList, setDatasetDirectionList] = useState([]); - const [activeType, setActiveType] = useState(null); - const [activeTag, setActiveTag] = useState(null); - const [isModalOpen, setIsModalOpen] = useState(false); - const [datasetList, setDatasetList] = useState([]); - const [total, setTotal] = useState(0); - const [form] = Form.useForm(); - const [dialogTitle, setDialogTitle] = useState('新建数据'); - const getDatasetlist = (queryFlow) => { - getDatasetList(queryFlow).then((ret) => { - console.log(ret); - if (ret.code == 200) { - setDatasetList(ret.data.content); - setTotal(ret.data.totalElements); - } - }); - }; - const onSearch = (values) => { - console.log(values); - getAssetIconList({ ...iconParams, name: values }); - }; - const getAssetIconList = (params) => { - getAssetIcon(params).then((ret) => { - console.log(ret); - if (ret.code == 200 && ret.data.content && ret.data.content.length > 0) { - setDatasetTypeList(ret.data.content.filter((item) => item.category_id == 1)); - setDatasetDirectionList(ret.data.content.filter((item) => item.category_id == 2)); - } else { - setDatasetTypeList([]); - setDatasetDirectionList([]); - } - }); - }; - const nameSearch = (values) => { - console.log(values); - getDatasetlist({ ...queryFlow, name: values }); - }; - const showModal = () => { - form.resetFields(); - setDialogTitle('新建数据集'); - setIsModalOpen(true); - }; - const handleOk = () => { - console.log(1111); - setIsModalOpen(false); - }; - const handleCancel = () => { - setIsModalOpen(false); - }; - const chooseDatasetType = (val, item) => { - console.log(val, item); - if (item.id == queryFlow.data_type) { - setActiveType(''); - setQueryFlow({ ...queryFlow, data_type: null }); - getDatasetlist({ ...queryFlow, data_type: null }); - } else { - setActiveType(item.id); - setQueryFlow({ ...queryFlow, data_type: item.id }); - getDatasetlist({ ...queryFlow, data_type: item.id }); - } - // setQueryFlow({...queryFlow,data_type:item.path},()=>{ - // getDatasetlist() - // }) - }; - const chooseDatasetTag = (val, item) => { - console.log(val, item); - if (item.id == queryFlow.data_tag) { - setActiveTag(''); - setQueryFlow({ ...queryFlow, data_tag: null }); - getDatasetlist({ ...queryFlow, data_tag: null }); - } else { - setActiveTag(item.id); - setQueryFlow({ ...queryFlow, data_tag: item.id }); - getDatasetlist({ ...queryFlow, data_tag: item.id }); - } - // setQueryFlow({...queryFlow,data_type:item.path},()=>{ - // getDatasetlist() - // }) - }; - - const routeToIntro = (e, record) => { - e.stopPropagation(); - console.log(record); - navgite({ pathname: `/dataset/dataset/${record.id}` }); - }; - const onFinishFailed = (errorInfo) => { - console.log('Failed:', errorInfo); - }; - const onPageChange = (pageNum, pageSize) => { - console.log(pageNum, pageSize); - setQueryFlow({ ...queryFlow, page: pageNum - 1, size: pageSize }); - getDatasetlist({ ...queryFlow, page: pageNum - 1, size: pageSize }); - }; - useEffect(() => { - getAssetIconList(iconParams); - getDatasetlist(queryFlow); - return () => {}; - }, []); - return ( - <> -
-
-
- -
分类
-
- {datasetTypeList && datasetTypeList.length > 0 - ? datasetTypeList.map((item) => { - return ( -
-
{ - chooseDatasetType(e, item); - }} - > - - - {item.name} -
-
- ); - }) - : ''} -
-
研究方向/应用领域
-
- {datasetDirectionList && datasetDirectionList.length > 0 - ? datasetDirectionList.map((item) => { - return ( -
-
{ - chooseDatasetTag(e, item); - }} - > - - - {item.name} -
-
- ); - }) - : ''} -
-
-
-
-
- 数据总数:{total}个 -
- -
-
-
- {datasetList && datasetList.length > 0 - ? datasetList.map((item) => { - return ( -
routeToIntro(e, item)}> - {item.name} - { - e.stopPropagation(); - modalConfirm({ - title: '确定删除该条数据集实例吗?', - onOk: () => { - deleteDataset(item.id).then((ret) => { - if (ret.code === 200) { - message.success('删除成功'); - getModelLists(queryFlow); - } else { - message.error(ret.msg); - } - }); - }, - }); - }} - className={Styles.dropdown} - style={{ width: '17px', marginRight: '6px' }} - src={deleteIcon} - alt="" - /> -
{item.description}
-
- - {item.create_by} -
-
- - 最近更新: {moment(item.update_time).format('YYYY-MM-DD')} -
-
- ); - }) - : ''} - {/* Demo */} -
- -
-
- - ); -}; -export default PublicData; diff --git a/react-ui/src/pages/Dataset/types.tsx b/react-ui/src/pages/Dataset/types.tsx new file mode 100644 index 00000000..5f3c5f1e --- /dev/null +++ b/react-ui/src/pages/Dataset/types.tsx @@ -0,0 +1,129 @@ +import KFIcon from '@/components/KFIcon'; +import { CommonTabKeys } from '@/enums'; +import { + addDatasetVersionDetail, + addModelsVersionDetail, + deleteDataset, + deleteModel, + getDatasetList, + getDatasetVersionIdList, + getDatasetVersionsById, + getModelList, + getModelVersionIdList, + getModelVersionsById, +} from '@/services/dataset/index.js'; +import type { TabsProps } from 'antd'; + +export enum ResourceType { + Model = 'Model', // 模型 + Dataset = 'Dataset', // 数据集 +} + +type ResourceTypeInfo = { + getList: (params: any) => Promise; + getVersions: (params: any) => Promise; + getFiles: (params: any) => Promise; + deleteRecord: (params: any) => Promise; + name: string; + typeParamKey: string; + tagParamKey: string; + fileReqParamKey: 'models_id' | 'dataset_id'; + tabItems: TabsProps['items']; + typeTitle: string; + tagTitle: string; + typeValue: number; // 从 getAssetIcon 接口获取特定值的数据为 type 分类 (category_id === typeValue) + tagValue: number; // 从 getAssetIcon 接口获取特定值的数据为 tag 分类(category_id === tagValue) + iconPathPrefix: string; // 图标路径前缀 + deleteModalTitle: string; // 删除弹框的title + addBtnTitle: string; // 新增按钮的title + addVersionReq: (params: any) => Promise; + idParamKey: string; + uploadAction: string; + uploadAccept?: string; +}; + +export const resourceConfig: Record = { + [ResourceType.Dataset]: { + getList: getDatasetList, + getVersions: getDatasetVersionsById, + getFiles: getDatasetVersionIdList, + deleteRecord: deleteDataset, + name: '数据集', + typeParamKey: 'data_type', + tagParamKey: 'data_tag', + fileReqParamKey: 'dataset_id', + tabItems: [ + { + key: CommonTabKeys.Public, + label: '数据广场', + icon: , + }, + { + key: CommonTabKeys.Private, + label: '个人数据', + icon: , + }, + ], + typeTitle: '分类', + tagTitle: '研究方向/应用领域', + typeValue: 1, + tagValue: 2, + iconPathPrefix: 'dataset', + deleteModalTitle: '确定删除该条数据集实例吗?', + addBtnTitle: '新建数据集', + addVersionReq: addDatasetVersionDetail, + idParamKey: 'dataset_id', + uploadAction: '/api/mmp/dataset/upload', + uploadAccept: '.zip,.tgz', + }, + [ResourceType.Model]: { + getList: getModelList, + getVersions: getModelVersionsById, + getFiles: getModelVersionIdList, + deleteRecord: deleteModel, + name: '模型', + typeParamKey: 'model_type', + tagParamKey: 'model_tag', + fileReqParamKey: 'models_id', + tabItems: [ + { + key: CommonTabKeys.Public, + label: '模型广场', + icon: , + }, + { + key: CommonTabKeys.Private, + label: '个人模型', + icon: , + }, + ], + typeTitle: '模型框架', + tagTitle: '模型能力', + typeValue: 3, + tagValue: 4, + iconPathPrefix: 'model', + deleteModalTitle: '确定删除该条模型实例吗?', + addBtnTitle: '新建模型', + addVersionReq: addModelsVersionDetail, + idParamKey: 'models_id', + uploadAction: '/api/mmp/models/upload', + uploadAccept: undefined, + }, +}; + +// 分类数据 +export type CategoryData = { + id: number; + category_id: number; + name: string; + path: string; +}; + +// 数据类型 +export type ResourceData = { + id: number; + name: string; + description: string; + create_by: string; + update_time: string; +}; diff --git a/react-ui/src/pages/DatasetPreparation/DatasetAnnotation/index.tsx b/react-ui/src/pages/DatasetPreparation/DatasetAnnotation/index.tsx index a1f017e6..385be971 100644 --- a/react-ui/src/pages/DatasetPreparation/DatasetAnnotation/index.tsx +++ b/react-ui/src/pages/DatasetPreparation/DatasetAnnotation/index.tsx @@ -16,7 +16,7 @@ function DatasetAnnotation() { }; return (
- {iframeUrl && } +
); } diff --git a/react-ui/src/pages/Experiment/components/AddExperimentModal/index.less b/react-ui/src/pages/Experiment/components/AddExperimentModal/index.less new file mode 100644 index 00000000..eec152a7 --- /dev/null +++ b/react-ui/src/pages/Experiment/components/AddExperimentModal/index.less @@ -0,0 +1,9 @@ +.modal { + .global_param_item { + max-height: 230px; + padding: 24px 12px 0; + overflow-y: auto; + border: 1px solid #e6e6e6; + border-radius: 6px; + } +} diff --git a/react-ui/src/pages/Experiment/experimentText/addExperimentModal.tsx b/react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx similarity index 97% rename from react-ui/src/pages/Experiment/experimentText/addExperimentModal.tsx rename to react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx index f5b2bfc4..71ec2f06 100644 --- a/react-ui/src/pages/Experiment/experimentText/addExperimentModal.tsx +++ b/react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx @@ -4,7 +4,7 @@ import KFModal from '@/components/KFModal'; import { type PipelineGlobalParam } from '@/types'; import { Form, Input, Radio, Select, type FormRule } from 'antd'; import { useState } from 'react'; -import styles from './addExperimentModal.less'; +import styles from './index.less'; type FormData = { name?: string; @@ -97,6 +97,11 @@ function AddExperimentModal({ wrapperCol: { span: 20 }, }; + const paramLayout = { + labelCol: { span: 8 }, + wrapperCol: { span: 16 }, + }; + // 除了流水线选择发生变化 const handleWorkflowChange = (id: string | number) => { const pipeline: Workflow | undefined = workflowList.find((v) => v.id === id); @@ -187,7 +192,7 @@ function AddExperimentModal({ fields.map(({ key, name, ...restField }) => ( ({ key, value }), + ); + + // 输入参数 + const inParametersList = Object.entries(nodeData.in_parameters ?? {}).map(([key, value]) => ({ + key, + value, + })); + + // 输出参数 + const outParametersList = Object.entries(nodeData.out_parameters ?? {}).map(([key, value]) => ({ + key, + value, + })); + + return ( +
+
+ +
+ + + + + + +
+ +
+ + + + + + + + +