| @@ -0,0 +1 @@ | |||||
| save-prefix=~ | |||||
| @@ -1,6 +1,5 @@ | |||||
| // https://umijs.org/config/ | // https://umijs.org/config/ | ||||
| import { defineConfig } from '@umijs/max'; | import { defineConfig } from '@umijs/max'; | ||||
| import { join } from 'path'; | |||||
| import defaultSettings from './defaultSettings'; | import defaultSettings from './defaultSettings'; | ||||
| import proxy from './proxy'; | import proxy from './proxy'; | ||||
| import routes from './routes'; | import routes from './routes'; | ||||
| @@ -145,20 +144,7 @@ export default defineConfig({ | |||||
| * @description 基于 openapi 的规范生成serve 和mock,能减少很多样板代码 | * @description 基于 openapi 的规范生成serve 和mock,能减少很多样板代码 | ||||
| * @doc https://pro.ant.design/zh-cn/docs/openapi/ | * @doc https://pro.ant.design/zh-cn/docs/openapi/ | ||||
| */ | */ | ||||
| openAPI: [ | |||||
| { | |||||
| requestLibPath: "import { request } from '@umijs/max'", | |||||
| // 或者使用在线的版本 | |||||
| // schemaPath: "https://gw.alipayobjects.com/os/antfincdn/M%24jrzTTYJN/oneapi.json" | |||||
| schemaPath: join(__dirname, 'oneapi.json'), | |||||
| mock: false, | |||||
| }, | |||||
| { | |||||
| requestLibPath: "import { request } from '@umijs/max'", | |||||
| schemaPath: 'https://gw.alipayobjects.com/os/antfincdn/CA1dOm%2631B/openapi.json', | |||||
| projectName: 'swagger', | |||||
| }, | |||||
| ], | |||||
| // openAPI: [], | |||||
| // mfsu: { | // mfsu: { | ||||
| // strategy: 'normal', | // strategy: 'normal', | ||||
| // }, | // }, | ||||
| @@ -1,593 +0,0 @@ | |||||
| { | |||||
| "openapi": "3.0.1", | |||||
| "info": { | |||||
| "title": "Ant Design Pro", | |||||
| "version": "1.0.0" | |||||
| }, | |||||
| "servers": [ | |||||
| { | |||||
| "url": "http://localhost:8000/" | |||||
| }, | |||||
| { | |||||
| "url": "https://localhost:8000/" | |||||
| } | |||||
| ], | |||||
| "paths": { | |||||
| "/api/currentUser": { | |||||
| "get": { | |||||
| "tags": ["api"], | |||||
| "description": "获取当前的用户", | |||||
| "operationId": "currentUser", | |||||
| "responses": { | |||||
| "200": { | |||||
| "description": "Success", | |||||
| "content": { | |||||
| "application/json": { | |||||
| "schema": { | |||||
| "$ref": "#/components/schemas/CurrentUser" | |||||
| } | |||||
| } | |||||
| } | |||||
| }, | |||||
| "401": { | |||||
| "description": "Error", | |||||
| "content": { | |||||
| "application/json": { | |||||
| "schema": { | |||||
| "$ref": "#/components/schemas/ErrorResponse" | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| }, | |||||
| "x-swagger-router-controller": "api" | |||||
| }, | |||||
| "/api/login/captcha": { | |||||
| "post": { | |||||
| "description": "发送验证码", | |||||
| "operationId": "getFakeCaptcha", | |||||
| "tags": ["login"], | |||||
| "parameters": [ | |||||
| { | |||||
| "name": "phone", | |||||
| "in": "query", | |||||
| "description": "手机号", | |||||
| "schema": { | |||||
| "type": "string" | |||||
| } | |||||
| } | |||||
| ], | |||||
| "responses": { | |||||
| "200": { | |||||
| "description": "Success", | |||||
| "content": { | |||||
| "application/json": { | |||||
| "schema": { | |||||
| "$ref": "#/components/schemas/FakeCaptcha" | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| }, | |||||
| "/api/login/outLogin": { | |||||
| "post": { | |||||
| "description": "登录接口", | |||||
| "operationId": "outLogin", | |||||
| "tags": ["login"], | |||||
| "responses": { | |||||
| "200": { | |||||
| "description": "Success", | |||||
| "content": { | |||||
| "application/json": { | |||||
| "schema": { | |||||
| "type": "object" | |||||
| } | |||||
| } | |||||
| } | |||||
| }, | |||||
| "401": { | |||||
| "description": "Error", | |||||
| "content": { | |||||
| "application/json": { | |||||
| "schema": { | |||||
| "$ref": "#/components/schemas/ErrorResponse" | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| }, | |||||
| "x-swagger-router-controller": "api" | |||||
| }, | |||||
| "/api/login/account": { | |||||
| "post": { | |||||
| "tags": ["login"], | |||||
| "description": "登录接口", | |||||
| "operationId": "login", | |||||
| "requestBody": { | |||||
| "description": "登录系统", | |||||
| "content": { | |||||
| "application/json": { | |||||
| "schema": { | |||||
| "$ref": "#/components/schemas/LoginParams" | |||||
| } | |||||
| } | |||||
| }, | |||||
| "required": true | |||||
| }, | |||||
| "responses": { | |||||
| "200": { | |||||
| "description": "Success", | |||||
| "content": { | |||||
| "application/json": { | |||||
| "schema": { | |||||
| "$ref": "#/components/schemas/LoginResult" | |||||
| } | |||||
| } | |||||
| } | |||||
| }, | |||||
| "401": { | |||||
| "description": "Error", | |||||
| "content": { | |||||
| "application/json": { | |||||
| "schema": { | |||||
| "$ref": "#/components/schemas/ErrorResponse" | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| }, | |||||
| "x-codegen-request-body-name": "body" | |||||
| }, | |||||
| "x-swagger-router-controller": "api" | |||||
| }, | |||||
| "/api/notices": { | |||||
| "summary": "getNotices", | |||||
| "description": "NoticeIconItem", | |||||
| "get": { | |||||
| "tags": ["api"], | |||||
| "operationId": "getNotices", | |||||
| "responses": { | |||||
| "200": { | |||||
| "description": "Success", | |||||
| "content": { | |||||
| "application/json": { | |||||
| "schema": { | |||||
| "$ref": "#/components/schemas/NoticeIconList" | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| }, | |||||
| "/api/rule": { | |||||
| "get": { | |||||
| "tags": ["rule"], | |||||
| "description": "获取规则列表", | |||||
| "operationId": "rule", | |||||
| "parameters": [ | |||||
| { | |||||
| "name": "current", | |||||
| "in": "query", | |||||
| "description": "当前的页码", | |||||
| "schema": { | |||||
| "type": "number" | |||||
| } | |||||
| }, | |||||
| { | |||||
| "name": "pageSize", | |||||
| "in": "query", | |||||
| "description": "页面的容量", | |||||
| "schema": { | |||||
| "type": "number" | |||||
| } | |||||
| } | |||||
| ], | |||||
| "responses": { | |||||
| "200": { | |||||
| "description": "Success", | |||||
| "content": { | |||||
| "application/json": { | |||||
| "schema": { | |||||
| "$ref": "#/components/schemas/RuleList" | |||||
| } | |||||
| } | |||||
| } | |||||
| }, | |||||
| "401": { | |||||
| "description": "Error", | |||||
| "content": { | |||||
| "application/json": { | |||||
| "schema": { | |||||
| "$ref": "#/components/schemas/ErrorResponse" | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| }, | |||||
| "post": { | |||||
| "tags": ["rule"], | |||||
| "description": "新建规则", | |||||
| "operationId": "addRule", | |||||
| "responses": { | |||||
| "200": { | |||||
| "description": "Success", | |||||
| "content": { | |||||
| "application/json": { | |||||
| "schema": { | |||||
| "$ref": "#/components/schemas/RuleListItem" | |||||
| } | |||||
| } | |||||
| } | |||||
| }, | |||||
| "401": { | |||||
| "description": "Error", | |||||
| "content": { | |||||
| "application/json": { | |||||
| "schema": { | |||||
| "$ref": "#/components/schemas/ErrorResponse" | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| }, | |||||
| "put": { | |||||
| "tags": ["rule"], | |||||
| "description": "新建规则", | |||||
| "operationId": "updateRule", | |||||
| "responses": { | |||||
| "200": { | |||||
| "description": "Success", | |||||
| "content": { | |||||
| "application/json": { | |||||
| "schema": { | |||||
| "$ref": "#/components/schemas/RuleListItem" | |||||
| } | |||||
| } | |||||
| } | |||||
| }, | |||||
| "401": { | |||||
| "description": "Error", | |||||
| "content": { | |||||
| "application/json": { | |||||
| "schema": { | |||||
| "$ref": "#/components/schemas/ErrorResponse" | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| }, | |||||
| "delete": { | |||||
| "tags": ["rule"], | |||||
| "description": "删除规则", | |||||
| "operationId": "removeRule", | |||||
| "responses": { | |||||
| "200": { | |||||
| "description": "Success", | |||||
| "content": { | |||||
| "application/json": { | |||||
| "schema": { | |||||
| "type": "object" | |||||
| } | |||||
| } | |||||
| } | |||||
| }, | |||||
| "401": { | |||||
| "description": "Error", | |||||
| "content": { | |||||
| "application/json": { | |||||
| "schema": { | |||||
| "$ref": "#/components/schemas/ErrorResponse" | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| }, | |||||
| "x-swagger-router-controller": "api" | |||||
| }, | |||||
| "/swagger": { | |||||
| "x-swagger-pipe": "swagger_raw" | |||||
| } | |||||
| }, | |||||
| "components": { | |||||
| "schemas": { | |||||
| "CurrentUser": { | |||||
| "type": "object", | |||||
| "properties": { | |||||
| "name": { | |||||
| "type": "string" | |||||
| }, | |||||
| "avatar": { | |||||
| "type": "string" | |||||
| }, | |||||
| "userid": { | |||||
| "type": "string" | |||||
| }, | |||||
| "email": { | |||||
| "type": "string" | |||||
| }, | |||||
| "signature": { | |||||
| "type": "string" | |||||
| }, | |||||
| "title": { | |||||
| "type": "string" | |||||
| }, | |||||
| "group": { | |||||
| "type": "string" | |||||
| }, | |||||
| "tags": { | |||||
| "type": "array", | |||||
| "items": { | |||||
| "type": "object", | |||||
| "properties": { | |||||
| "key": { | |||||
| "type": "string" | |||||
| }, | |||||
| "label": { | |||||
| "type": "string" | |||||
| } | |||||
| } | |||||
| } | |||||
| }, | |||||
| "notifyCount": { | |||||
| "type": "integer", | |||||
| "format": "int32" | |||||
| }, | |||||
| "unreadCount": { | |||||
| "type": "integer", | |||||
| "format": "int32" | |||||
| }, | |||||
| "country": { | |||||
| "type": "string" | |||||
| }, | |||||
| "access": { | |||||
| "type": "string" | |||||
| }, | |||||
| "geographic": { | |||||
| "type": "object", | |||||
| "properties": { | |||||
| "province": { | |||||
| "type": "object", | |||||
| "properties": { | |||||
| "label": { | |||||
| "type": "string" | |||||
| }, | |||||
| "key": { | |||||
| "type": "string" | |||||
| } | |||||
| } | |||||
| }, | |||||
| "city": { | |||||
| "type": "object", | |||||
| "properties": { | |||||
| "label": { | |||||
| "type": "string" | |||||
| }, | |||||
| "key": { | |||||
| "type": "string" | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| }, | |||||
| "address": { | |||||
| "type": "string" | |||||
| }, | |||||
| "phone": { | |||||
| "type": "string" | |||||
| } | |||||
| } | |||||
| }, | |||||
| "LoginResult": { | |||||
| "type": "object", | |||||
| "properties": { | |||||
| "status": { | |||||
| "type": "string" | |||||
| }, | |||||
| "type": { | |||||
| "type": "string" | |||||
| }, | |||||
| "currentAuthority": { | |||||
| "type": "string" | |||||
| } | |||||
| } | |||||
| }, | |||||
| "PageParams": { | |||||
| "type": "object", | |||||
| "properties": { | |||||
| "current": { | |||||
| "type": "number" | |||||
| }, | |||||
| "pageSize": { | |||||
| "type": "number" | |||||
| } | |||||
| } | |||||
| }, | |||||
| "RuleListItem": { | |||||
| "type": "object", | |||||
| "properties": { | |||||
| "key": { | |||||
| "type": "integer", | |||||
| "format": "int32" | |||||
| }, | |||||
| "disabled": { | |||||
| "type": "boolean" | |||||
| }, | |||||
| "href": { | |||||
| "type": "string" | |||||
| }, | |||||
| "avatar": { | |||||
| "type": "string" | |||||
| }, | |||||
| "name": { | |||||
| "type": "string" | |||||
| }, | |||||
| "owner": { | |||||
| "type": "string" | |||||
| }, | |||||
| "desc": { | |||||
| "type": "string" | |||||
| }, | |||||
| "callNo": { | |||||
| "type": "integer", | |||||
| "format": "int32" | |||||
| }, | |||||
| "status": { | |||||
| "type": "integer", | |||||
| "format": "int32" | |||||
| }, | |||||
| "updatedAt": { | |||||
| "type": "string", | |||||
| "format": "datetime" | |||||
| }, | |||||
| "createdAt": { | |||||
| "type": "string", | |||||
| "format": "datetime" | |||||
| }, | |||||
| "progress": { | |||||
| "type": "integer", | |||||
| "format": "int32" | |||||
| } | |||||
| } | |||||
| }, | |||||
| "RuleList": { | |||||
| "type": "object", | |||||
| "properties": { | |||||
| "data": { | |||||
| "type": "array", | |||||
| "items": { | |||||
| "$ref": "#/components/schemas/RuleListItem" | |||||
| } | |||||
| }, | |||||
| "total": { | |||||
| "type": "integer", | |||||
| "description": "列表的内容总数", | |||||
| "format": "int32" | |||||
| }, | |||||
| "success": { | |||||
| "type": "boolean" | |||||
| } | |||||
| } | |||||
| }, | |||||
| "FakeCaptcha": { | |||||
| "type": "object", | |||||
| "properties": { | |||||
| "code": { | |||||
| "type": "integer", | |||||
| "format": "int32" | |||||
| }, | |||||
| "status": { | |||||
| "type": "string" | |||||
| } | |||||
| } | |||||
| }, | |||||
| "LoginParams": { | |||||
| "type": "object", | |||||
| "properties": { | |||||
| "username": { | |||||
| "type": "string" | |||||
| }, | |||||
| "password": { | |||||
| "type": "string" | |||||
| }, | |||||
| "autoLogin": { | |||||
| "type": "boolean" | |||||
| }, | |||||
| "type": { | |||||
| "type": "string" | |||||
| } | |||||
| } | |||||
| }, | |||||
| "ErrorResponse": { | |||||
| "required": ["errorCode"], | |||||
| "type": "object", | |||||
| "properties": { | |||||
| "errorCode": { | |||||
| "type": "string", | |||||
| "description": "业务约定的错误码" | |||||
| }, | |||||
| "errorMessage": { | |||||
| "type": "string", | |||||
| "description": "业务上的错误信息" | |||||
| }, | |||||
| "success": { | |||||
| "type": "boolean", | |||||
| "description": "业务上的请求是否成功" | |||||
| } | |||||
| } | |||||
| }, | |||||
| "NoticeIconList": { | |||||
| "type": "object", | |||||
| "properties": { | |||||
| "data": { | |||||
| "type": "array", | |||||
| "items": { | |||||
| "$ref": "#/components/schemas/NoticeIconItem" | |||||
| } | |||||
| }, | |||||
| "total": { | |||||
| "type": "integer", | |||||
| "description": "列表的内容总数", | |||||
| "format": "int32" | |||||
| }, | |||||
| "success": { | |||||
| "type": "boolean" | |||||
| } | |||||
| } | |||||
| }, | |||||
| "NoticeIconItemType": { | |||||
| "title": "NoticeIconItemType", | |||||
| "description": "已读未读列表的枚举", | |||||
| "type": "string", | |||||
| "properties": {}, | |||||
| "enum": ["notification", "message", "event"] | |||||
| }, | |||||
| "NoticeIconItem": { | |||||
| "type": "object", | |||||
| "properties": { | |||||
| "id": { | |||||
| "type": "string" | |||||
| }, | |||||
| "extra": { | |||||
| "type": "string", | |||||
| "format": "any" | |||||
| }, | |||||
| "key": { "type": "string" }, | |||||
| "read": { | |||||
| "type": "boolean" | |||||
| }, | |||||
| "avatar": { | |||||
| "type": "string" | |||||
| }, | |||||
| "title": { | |||||
| "type": "string" | |||||
| }, | |||||
| "status": { | |||||
| "type": "string" | |||||
| }, | |||||
| "datetime": { | |||||
| "type": "string", | |||||
| "format": "date" | |||||
| }, | |||||
| "description": { | |||||
| "type": "string" | |||||
| }, | |||||
| "type": { | |||||
| "extensions": { | |||||
| "x-is-enum": true | |||||
| }, | |||||
| "$ref": "#/components/schemas/NoticeIconItemType" | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -27,6 +27,16 @@ export default [ | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| { | |||||
| path: '/authorize', | |||||
| layout: false, | |||||
| component: './Authorize/index', | |||||
| }, | |||||
| { | |||||
| path: '/gitlink', | |||||
| layout: true, | |||||
| component: './GitLink/index', | |||||
| }, | |||||
| { | { | ||||
| path: '/user', | path: '/user', | ||||
| layout: false, | layout: false, | ||||
| @@ -60,7 +60,7 @@ | |||||
| "@antv/hierarchy": "^0.6.12", | "@antv/hierarchy": "^0.6.12", | ||||
| "@types/crypto-js": "^4.2.2", | "@types/crypto-js": "^4.2.2", | ||||
| "@umijs/route-utils": "^4.0.1", | "@umijs/route-utils": "^4.0.1", | ||||
| "antd": "^5.4.4", | |||||
| "antd": "~5.21.4", | |||||
| "classnames": "^2.3.2", | "classnames": "^2.3.2", | ||||
| "crypto-js": "^4.2.0", | "crypto-js": "^4.2.0", | ||||
| "echarts": "^5.5.0", | "echarts": "^5.5.0", | ||||
| @@ -111,7 +111,7 @@ | |||||
| "umi-presets-pro": "^2.0.0" | "umi-presets-pro": "^2.0.0" | ||||
| }, | }, | ||||
| "engines": { | "engines": { | ||||
| "node": ">=12.0.0" | |||||
| "node": ">=16.14.0" | |||||
| }, | }, | ||||
| "create-umi": { | "create-umi": { | ||||
| "ignoreScript": [ | "ignoreScript": [ | ||||
| @@ -7,7 +7,6 @@ import defaultSettings from '../config/defaultSettings'; | |||||
| import '../public/fonts/font.css'; | import '../public/fonts/font.css'; | ||||
| import { getAccessToken } from './access'; | import { getAccessToken } from './access'; | ||||
| import './dayjsConfig'; | import './dayjsConfig'; | ||||
| import { PageEnum } from './enums/pagesEnums'; | |||||
| import './global.less'; | import './global.less'; | ||||
| import { removeAllPageCacheState } from './hooks/pageCacheState'; | import { removeAllPageCacheState } from './hooks/pageCacheState'; | ||||
| import { | import { | ||||
| @@ -23,6 +22,7 @@ export { requestConfig as request } from './requestConfig'; | |||||
| import { type GlobalInitialState } from '@/types'; | import { type GlobalInitialState } from '@/types'; | ||||
| import { menuItemRender } from '@/utils/menuRender'; | import { menuItemRender } from '@/utils/menuRender'; | ||||
| import ErrorBoundary from './components/ErrorBoundary'; | import ErrorBoundary from './components/ErrorBoundary'; | ||||
| import { needAuth } from './utils'; | |||||
| import { gotoLoginPage } from './utils/ui'; | import { gotoLoginPage } from './utils/ui'; | ||||
| /** | /** | ||||
| @@ -40,14 +40,17 @@ export async function getInitialState(): Promise<GlobalInitialState> { | |||||
| roleNames: response.user.roles, | roleNames: response.user.roles, | ||||
| } as API.CurrentUser; | } as API.CurrentUser; | ||||
| } catch (error) { | } catch (error) { | ||||
| console.error(error); | |||||
| console.error('1111', error); | |||||
| gotoLoginPage(); | gotoLoginPage(); | ||||
| } | } | ||||
| return undefined; | return undefined; | ||||
| }; | }; | ||||
| // 如果不是登录页面,执行 | // 如果不是登录页面,执行 | ||||
| const { location } = history; | const { location } = history; | ||||
| if (location.pathname !== PageEnum.LOGIN) { | |||||
| console.log('getInitialState', needAuth(location.pathname)); | |||||
| if (needAuth(location.pathname)) { | |||||
| const currentUser = await fetchUserInfo(); | const currentUser = await fetchUserInfo(); | ||||
| return { | return { | ||||
| fetchUserInfo, | fetchUserInfo, | ||||
| @@ -94,7 +97,7 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||||
| onPageChange: () => { | onPageChange: () => { | ||||
| const { location } = history; | const { location } = history; | ||||
| // 如果没有登录,重定向到 login | // 如果没有登录,重定向到 login | ||||
| if (!initialState?.currentUser && location.pathname !== PageEnum.LOGIN) { | |||||
| if (!initialState?.currentUser && needAuth(location.pathname)) { | |||||
| gotoLoginPage(); | gotoLoginPage(); | ||||
| } | } | ||||
| }, | }, | ||||
| @@ -118,18 +121,10 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||||
| width: '331px', | width: '331px', | ||||
| }, | }, | ||||
| ], | ], | ||||
| // links: isDev | |||||
| // ? [ | |||||
| // <Link key="openapi" to="/umi/plugin/openapi" target="_blank"> | |||||
| // <LinkOutlined /> | |||||
| // <span>OpenAPI 文档</span> | |||||
| // </Link>, | |||||
| // ] | |||||
| // : [], | |||||
| // 自定义 403 页面 | // 自定义 403 页面 | ||||
| // unAccessible: <div>unAccessible</div>, | // unAccessible: <div>unAccessible</div>, | ||||
| // 增加一个 loading 的状态 | |||||
| childrenRender: (children) => { | childrenRender: (children) => { | ||||
| // 增加一个 loading 的状态 | |||||
| // if (initialState?.loading) return <PageLoading />; | // if (initialState?.loading) return <PageLoading />; | ||||
| return ( | return ( | ||||
| <div className="kf-page-container"> | <div className="kf-page-container"> | ||||
| @@ -167,8 +162,8 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||||
| export const onRouteChange: RuntimeConfig['onRouteChange'] = async (e) => { | export const onRouteChange: RuntimeConfig['onRouteChange'] = async (e) => { | ||||
| const { location } = e; | const { location } = e; | ||||
| const menus = getRemoteMenu(); | const menus = getRemoteMenu(); | ||||
| // console.log('onRouteChange', e); | |||||
| if (menus === null && location.pathname !== PageEnum.LOGIN) { | |||||
| console.log('onRouteChange', menus); | |||||
| if (menus === null && needAuth(location.pathname)) { | |||||
| history.go(0); | history.go(0); | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -178,12 +173,12 @@ export const patchRoutes: RuntimeConfig['patchRoutes'] = (e) => { | |||||
| }; | }; | ||||
| export const patchClientRoutes: RuntimeConfig['patchClientRoutes'] = (e) => { | export const patchClientRoutes: RuntimeConfig['patchClientRoutes'] = (e) => { | ||||
| //console.log('patchClientRoutes', e); | |||||
| console.log('patchClientRoutes', e); | |||||
| patchRouteWithRemoteMenus(e.routes); | patchRouteWithRemoteMenus(e.routes); | ||||
| }; | }; | ||||
| export function render(oldRender: () => void) { | export function render(oldRender: () => void) { | ||||
| // console.log('render'); | |||||
| console.log('render'); | |||||
| const token = getAccessToken(); | const token = getAccessToken(); | ||||
| if (!token || token?.length === 0) { | if (!token || token?.length === 0) { | ||||
| oldRender(); | oldRender(); | ||||
| @@ -236,6 +231,7 @@ export const antd: RuntimeAntdConfig = (memo) => { | |||||
| memo.theme.components.Table = { | memo.theme.components.Table = { | ||||
| headerBg: 'rgba(242, 244, 247, 0.36)', | headerBg: 'rgba(242, 244, 247, 0.36)', | ||||
| headerBorderRadius: 4, | headerBorderRadius: 4, | ||||
| rowSelectedBg: 'rgba(22, 100, 255, 0.05)', | |||||
| }; | }; | ||||
| memo.theme.components.Tabs = { | memo.theme.components.Tabs = { | ||||
| titleFontSize: 16, | titleFontSize: 16, | ||||
| @@ -5,48 +5,49 @@ | |||||
| gap: 20px 40px; | gap: 20px 40px; | ||||
| align-items: flex-start; | align-items: flex-start; | ||||
| width: 80%; | width: 80%; | ||||
| } | |||||
| .kf-basic-info-item { | |||||
| display: flex; | |||||
| align-items: flex-start; | |||||
| width: calc(50% - 20px); | |||||
| font-size: 16px; | |||||
| line-height: 1.6; | |||||
| &__item { | |||||
| display: flex; | |||||
| align-items: flex-start; | |||||
| width: calc(50% - 20px); | |||||
| &__label { | |||||
| position: relative; | |||||
| flex: none; | |||||
| color: @text-color-secondary; | |||||
| text-align: justify; | |||||
| text-align-last: justify; | |||||
| &__label { | |||||
| position: relative; | |||||
| flex: none; | |||||
| color: @text-color-secondary; | |||||
| font-size: @font-size-content; | |||||
| line-height: 1.6; | |||||
| text-align: justify; | |||||
| text-align-last: justify; | |||||
| &::after { | |||||
| position: absolute; | |||||
| content: ':'; | |||||
| &::after { | |||||
| position: absolute; | |||||
| content: ':'; | |||||
| } | |||||
| } | } | ||||
| } | |||||
| &__list-value { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| flex-direction: column; | |||||
| gap: 5px 0; | |||||
| } | |||||
| &__value-container { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| flex-direction: column; | |||||
| gap: 5px 0; | |||||
| } | |||||
| &__value { | |||||
| flex: 1; | |||||
| margin-left: 16px; | |||||
| white-space: pre-line; | |||||
| word-break: break-all; | |||||
| } | |||||
| &__value { | |||||
| flex: 1; | |||||
| margin-left: 16px; | |||||
| font-size: @font-size-content; | |||||
| line-height: 1.6; | |||||
| word-break: break-all; | |||||
| &__text { | |||||
| color: @text-color; | |||||
| } | |||||
| &__text { | |||||
| color: @text-color; | |||||
| } | |||||
| &__link:hover { | |||||
| text-decoration: underline @underline-color; | |||||
| text-underline-offset: 3px; | |||||
| &__link:hover { | |||||
| text-decoration: underline @underline-color; | |||||
| text-underline-offset: 3px; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,5 @@ | |||||
| import { Link } from '@umijs/max'; | import { Link } from '@umijs/max'; | ||||
| import { Typography } from 'antd'; | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import './index.less'; | import './index.less'; | ||||
| @@ -11,6 +12,7 @@ export type BasicInfoLink = { | |||||
| export type BasicInfoData = { | export type BasicInfoData = { | ||||
| label: string; | label: string; | ||||
| value?: any; | value?: any; | ||||
| ellipsis?: boolean; | |||||
| format?: (_value?: any) => string | BasicInfoLink | BasicInfoLink[] | undefined; | format?: (_value?: any) => string | BasicInfoLink | BasicInfoLink[] | undefined; | ||||
| }; | }; | ||||
| @@ -18,45 +20,73 @@ type BasicInfoProps = { | |||||
| datas: BasicInfoData[]; | datas: BasicInfoData[]; | ||||
| className?: string; | className?: string; | ||||
| style?: React.CSSProperties; | style?: React.CSSProperties; | ||||
| labelWidth?: number; | |||||
| labelWidth: number; | |||||
| }; | }; | ||||
| function BasicInfo({ datas, className, style, labelWidth = 100 }: BasicInfoProps) { | |||||
| type BasicInfoItemProps = { | |||||
| data: BasicInfoData; | |||||
| labelWidth: number; | |||||
| classPrefix: string; | |||||
| }; | |||||
| type BasicInfoItemValueProps = BasicInfoLink & { | |||||
| ellipsis?: boolean; | |||||
| classPrefix: string; | |||||
| }; | |||||
| export default function BasicInfo({ datas, className, style, labelWidth }: BasicInfoProps) { | |||||
| return ( | return ( | ||||
| <div className={classNames('kf-basic-info', className)} style={style}> | <div className={classNames('kf-basic-info', className)} style={style}> | ||||
| {datas.map((item) => ( | {datas.map((item) => ( | ||||
| <BasicInfoItem key={item.label} data={item} labelWidth={labelWidth} /> | |||||
| <BasicInfoItem | |||||
| key={item.label} | |||||
| data={item} | |||||
| labelWidth={labelWidth} | |||||
| classPrefix="kf-basic-info" | |||||
| /> | |||||
| ))} | ))} | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| } | } | ||||
| type BasicInfoItemProps = { | |||||
| data: BasicInfoData; | |||||
| labelWidth?: number; | |||||
| }; | |||||
| function BasicInfoItem({ data, labelWidth = 100 }: BasicInfoItemProps) { | |||||
| const { label, value, format } = data; | |||||
| export function BasicInfoItem({ data, labelWidth, classPrefix }: BasicInfoItemProps) { | |||||
| const { label, value, format, ellipsis } = data; | |||||
| const formatValue = format ? format(value) : value; | const formatValue = format ? format(value) : value; | ||||
| const myClassName = `${classPrefix}__item`; | |||||
| let valueComponent = undefined; | let valueComponent = undefined; | ||||
| if (Array.isArray(formatValue)) { | if (Array.isArray(formatValue)) { | ||||
| valueComponent = ( | valueComponent = ( | ||||
| <div className="kf-basic-info-item__list-value"> | |||||
| <div className={`${myClassName}__value-container`}> | |||||
| {formatValue.map((item: BasicInfoLink) => ( | {formatValue.map((item: BasicInfoLink) => ( | ||||
| <BasicInfoItemValue key={item.value} value={item.value} link={item.link} url={item.url} /> | |||||
| <BasicInfoItemValue | |||||
| key={item.value} | |||||
| value={item.value} | |||||
| link={item.link} | |||||
| url={item.url} | |||||
| ellipsis={ellipsis} | |||||
| classPrefix={classPrefix} | |||||
| /> | |||||
| ))} | ))} | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| } else if (typeof formatValue === 'object' && formatValue) { | } else if (typeof formatValue === 'object' && formatValue) { | ||||
| valueComponent = ( | valueComponent = ( | ||||
| <BasicInfoItemValue value={formatValue.value} link={formatValue.link} url={formatValue.url} /> | |||||
| <BasicInfoItemValue | |||||
| value={formatValue.value} | |||||
| link={formatValue.link} | |||||
| url={formatValue.url} | |||||
| ellipsis={ellipsis} | |||||
| classPrefix={classPrefix} | |||||
| /> | |||||
| ); | ); | ||||
| } else { | } else { | ||||
| valueComponent = <BasicInfoItemValue value={formatValue} />; | |||||
| valueComponent = ( | |||||
| <BasicInfoItemValue value={formatValue} ellipsis={ellipsis} classPrefix={classPrefix} /> | |||||
| ); | |||||
| } | } | ||||
| return ( | return ( | ||||
| <div className="kf-basic-info-item" key={label}> | |||||
| <div className="kf-basic-info-item__label" style={{ width: labelWidth }}> | |||||
| <div className={myClassName} key={label}> | |||||
| <div className={`${myClassName}__label`} style={{ width: labelWidth }}> | |||||
| {label} | {label} | ||||
| </div> | </div> | ||||
| {valueComponent} | {valueComponent} | ||||
| @@ -64,35 +94,36 @@ function BasicInfoItem({ data, labelWidth = 100 }: BasicInfoItemProps) { | |||||
| ); | ); | ||||
| } | } | ||||
| type BasicInfoItemValueProps = { | |||||
| value: string; | |||||
| link?: string; | |||||
| url?: string; | |||||
| }; | |||||
| function BasicInfoItemValue({ value, link, url }: BasicInfoItemValueProps) { | |||||
| export function BasicInfoItemValue({ | |||||
| value, | |||||
| link, | |||||
| url, | |||||
| ellipsis, | |||||
| classPrefix, | |||||
| }: BasicInfoItemValueProps) { | |||||
| const myClassName = `${classPrefix}__item__value`; | |||||
| let component = undefined; | |||||
| if (url && value) { | if (url && value) { | ||||
| return ( | |||||
| <a | |||||
| className="kf-basic-info-item__value kf-basic-info-item__link" | |||||
| href={url} | |||||
| target="_blank" | |||||
| rel="noopener noreferrer" | |||||
| > | |||||
| component = ( | |||||
| <a className={`${myClassName}__link`} href={url} target="_blank" rel="noopener noreferrer"> | |||||
| {value} | {value} | ||||
| </a> | </a> | ||||
| ); | ); | ||||
| } else if (link && value) { | } else if (link && value) { | ||||
| return ( | |||||
| <Link to={link} className="kf-basic-info-item__value kf-basic-info-item__link"> | |||||
| component = ( | |||||
| <Link to={link} className={`${myClassName}__link`}> | |||||
| {value} | {value} | ||||
| </Link> | </Link> | ||||
| ); | ); | ||||
| } else { | } else { | ||||
| return ( | |||||
| <div className="kf-basic-info-item__value kf-basic-info-item__text">{value ?? '--'}</div> | |||||
| ); | |||||
| component = <span className={`${myClassName}__text`}>{value ?? '--'}</span>; | |||||
| } | } | ||||
| } | |||||
| export default BasicInfo; | |||||
| return ( | |||||
| <div className={myClassName}> | |||||
| <Typography.Text ellipsis={ellipsis ? { tooltip: value } : false}> | |||||
| {component} | |||||
| </Typography.Text> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| @@ -0,0 +1,60 @@ | |||||
| .kf-basic-table-info { | |||||
| display: flex; | |||||
| flex-direction: row; | |||||
| flex-wrap: wrap; | |||||
| align-items: stretch; | |||||
| width: 100%; | |||||
| border: 1px solid @border-color-base; | |||||
| border-bottom: none; | |||||
| border-radius: 4px; | |||||
| &__item { | |||||
| display: flex; | |||||
| align-items: stretch; | |||||
| width: 25%; | |||||
| border-bottom: 1px solid @border-color-base; | |||||
| &__label { | |||||
| flex: none; | |||||
| padding: 12px 20px; | |||||
| color: @text-color-secondary; | |||||
| font-size: 14px; | |||||
| text-align: left; | |||||
| background-color: .addAlpha(#606b7a, 0.05) []; | |||||
| } | |||||
| &__value-container { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| flex-direction: column; | |||||
| align-items: flex-start; | |||||
| min-width: 0; | |||||
| } | |||||
| &__value { | |||||
| flex: 1; | |||||
| min-width: 0; | |||||
| margin: 0 !important; | |||||
| padding: 12px 20px 4px; | |||||
| font-size: @font-size; | |||||
| word-break: break-all; | |||||
| & + & { | |||||
| padding-top: 0; | |||||
| } | |||||
| &:last-child { | |||||
| padding-bottom: 12px; | |||||
| } | |||||
| &__text { | |||||
| color: @text-color; | |||||
| } | |||||
| &__link:hover { | |||||
| text-decoration: underline @underline-color; | |||||
| text-underline-offset: 3px; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,43 @@ | |||||
| import classNames from 'classnames'; | |||||
| import { BasicInfoItem, type BasicInfoData, type BasicInfoLink } from '../BasicInfo'; | |||||
| import './index.less'; | |||||
| export type { BasicInfoData, BasicInfoLink }; | |||||
| type BasicTableInfoProps = { | |||||
| datas: BasicInfoData[]; | |||||
| className?: string; | |||||
| style?: React.CSSProperties; | |||||
| labelWidth: number; | |||||
| }; | |||||
| export default function BasicTableInfo({ | |||||
| datas, | |||||
| className, | |||||
| style, | |||||
| labelWidth, | |||||
| }: BasicTableInfoProps) { | |||||
| const remainder = datas.length % 4; | |||||
| const array = []; | |||||
| if (remainder > 0) { | |||||
| for (let i = 0; i < 4 - remainder; i++) { | |||||
| array.push({ | |||||
| label: '', | |||||
| value: '', | |||||
| }); | |||||
| } | |||||
| } | |||||
| const showDatas = [...datas, ...array]; | |||||
| return ( | |||||
| <div className={classNames('kf-basic-table-info', className)} style={style}> | |||||
| {showDatas.map((item) => ( | |||||
| <BasicInfoItem | |||||
| key={item.label} | |||||
| data={item} | |||||
| labelWidth={labelWidth} | |||||
| classPrefix="kf-basic-table-info" | |||||
| /> | |||||
| ))} | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| @@ -1,25 +0,0 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-28 14:18:11 | |||||
| * @Description: 自定义 Table 单元格,没有数据时展示 -- | |||||
| */ | |||||
| import { Tooltip } from 'antd'; | |||||
| function renderCell(text?: any | null) { | |||||
| return <span>{text ?? '--'}</span>; | |||||
| } | |||||
| function CommonTableCell(ellipsis: boolean = false) { | |||||
| if (ellipsis) { | |||||
| return (text?: any | null) => ( | |||||
| <Tooltip title={text} placement="topLeft" overlayStyle={{ maxWidth: '400px' }}> | |||||
| {renderCell(text)} | |||||
| </Tooltip> | |||||
| ); | |||||
| } else { | |||||
| return renderCell; | |||||
| } | |||||
| } | |||||
| export default CommonTableCell; | |||||
| @@ -1,20 +0,0 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-28 14:18:11 | |||||
| * @Description: 自定义 Table 日期类单元格 | |||||
| */ | |||||
| import { formatDate } from '@/utils/date'; | |||||
| import dayjs from 'dayjs'; | |||||
| function DateTableCell(text?: string | null) { | |||||
| if (text === undefined || text === null || text === '') { | |||||
| return <span>--</span>; | |||||
| } | |||||
| if (!dayjs(text).isValid()) { | |||||
| return <span>无效的日期</span>; | |||||
| } | |||||
| return <span>{formatDate(text)}</span>; | |||||
| } | |||||
| export default DateTableCell; | |||||
| @@ -12,6 +12,7 @@ export enum IframePageType { | |||||
| DatasetAnnotation = 'DatasetAnnotation', // 数据标注 | DatasetAnnotation = 'DatasetAnnotation', // 数据标注 | ||||
| AppDevelopment = 'AppDevelopment', // 应用开发 | AppDevelopment = 'AppDevelopment', // 应用开发 | ||||
| DevEnv = 'DevEnv', // 开发环境 | DevEnv = 'DevEnv', // 开发环境 | ||||
| GitLink = 'GitLink', | |||||
| } | } | ||||
| const getRequestAPI = (type: IframePageType): (() => Promise<any>) => { | const getRequestAPI = (type: IframePageType): (() => Promise<any>) => { | ||||
| @@ -26,6 +27,8 @@ const getRequestAPI = (type: IframePageType): (() => Promise<any>) => { | |||||
| code: 200, | code: 200, | ||||
| data: SessionStorage.getItem(SessionStorage.editorUrlKey) || '', | data: SessionStorage.getItem(SessionStorage.editorUrlKey) || '', | ||||
| }); | }); | ||||
| case IframePageType.GitLink: | |||||
| return () => Promise.resolve({ code: 200, data: 'http://172.20.32.201:4000' }); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -20,7 +20,7 @@ | |||||
| height: 40px; | height: 40px; | ||||
| padding: 0 30px; | padding: 0 30px; | ||||
| font-size: @font-size-content; | font-size: @font-size-content; | ||||
| border-radius: 10px; | |||||
| border-radius: 6px; | |||||
| } | } | ||||
| .ant-btn-default { | .ant-btn-default { | ||||
| border-color: transparent; | border-color: transparent; | ||||
| @@ -1,6 +1,8 @@ | |||||
| import { clearSessionToken } from '@/access'; | import { clearSessionToken } from '@/access'; | ||||
| import { setRemoteMenu } from '@/services/session'; | import { setRemoteMenu } from '@/services/session'; | ||||
| import { logout } from '@/services/system/auth'; | import { logout } from '@/services/system/auth'; | ||||
| import { ClientInfo } from '@/types'; | |||||
| import SessionStorage from '@/utils/sessionStorage'; | |||||
| import { gotoLoginPage } from '@/utils/ui'; | import { gotoLoginPage } from '@/utils/ui'; | ||||
| import { LogoutOutlined, UserOutlined } from '@ant-design/icons'; | import { LogoutOutlined, UserOutlined } from '@ant-design/icons'; | ||||
| import { setAlpha } from '@ant-design/pro-components'; | import { setAlpha } from '@ant-design/pro-components'; | ||||
| @@ -64,6 +66,11 @@ const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => { | |||||
| clearSessionToken(); | clearSessionToken(); | ||||
| setRemoteMenu(null); | setRemoteMenu(null); | ||||
| gotoLoginPage(); | gotoLoginPage(); | ||||
| const clientInfo: ClientInfo = SessionStorage.getItem(SessionStorage.clientInfoKey, true); | |||||
| if (clientInfo) { | |||||
| const { logoutUri } = clientInfo; | |||||
| location.replace(logoutUri); | |||||
| } | |||||
| }; | }; | ||||
| const actionClassName = useEmotionCss(({ token }) => { | const actionClassName = useEmotionCss(({ token }) => { | ||||
| return { | return { | ||||
| @@ -23,7 +23,7 @@ | |||||
| width: 100%; | width: 100%; | ||||
| height: 60px; | height: 60px; | ||||
| padding: 0 15px; | padding: 0 15px; | ||||
| border-bottom: 1px solid #e8e8e8; | |||||
| border-bottom: 1px solid @border-color-base; | |||||
| } | } | ||||
| &__iframe { | &__iframe { | ||||
| @@ -9,7 +9,7 @@ import './index.less'; | |||||
| type SubAreaTitleProps = { | type SubAreaTitleProps = { | ||||
| title: string; | title: string; | ||||
| image: string; | |||||
| image?: string; | |||||
| style?: React.CSSProperties; | style?: React.CSSProperties; | ||||
| className?: string; | className?: string; | ||||
| }; | }; | ||||
| @@ -17,8 +17,10 @@ type SubAreaTitleProps = { | |||||
| function SubAreaTitle({ title, image, style, className }: SubAreaTitleProps) { | function SubAreaTitle({ title, image, style, className }: SubAreaTitleProps) { | ||||
| return ( | return ( | ||||
| <div className={classNames('kf-subarea-title', className)} style={style}> | <div className={classNames('kf-subarea-title', className)} style={style}> | ||||
| <img src={image} width={14} draggable={false} alt="" /> | |||||
| <span style={{ marginLeft: '8px' }}>{title}</span> | |||||
| {image && ( | |||||
| <img src={image} width={18} draggable={false} alt="" style={{ marginRight: '8px' }} /> | |||||
| )} | |||||
| <span>{title}</span> | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -1,3 +1,4 @@ | |||||
| export enum PageEnum { | export enum PageEnum { | ||||
| LOGIN = '/user/login', | LOGIN = '/user/login', | ||||
| Authorize = '/authorize', | |||||
| } | } | ||||
| @@ -5,7 +5,7 @@ | |||||
| */ | */ | ||||
| import { FormInstance } from 'antd'; | import { FormInstance } from 'antd'; | ||||
| import { debounce } from 'lodash'; | import { debounce } from 'lodash'; | ||||
| import { useCallback, useEffect, useRef, useState } from 'react'; | |||||
| import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; | |||||
| /** | /** | ||||
| * 生成具有初始值的状态引用 | * 生成具有初始值的状态引用 | ||||
| * | * | ||||
| @@ -156,3 +156,45 @@ export const useEffectWhen = (effect: () => void, deps: React.DependencyList, wh | |||||
| } | } | ||||
| }, [when]); | }, [when]); | ||||
| }; | }; | ||||
| // 选择、全选操作 | |||||
| export const useCheck = <T>(list: T[]) => { | |||||
| const [selected, setSelected] = useState<T[]>([]); | |||||
| const checked = useMemo(() => { | |||||
| return selected.length === list.length && selected.length > 0; | |||||
| }, [selected, list]); | |||||
| const indeterminate = useMemo(() => { | |||||
| return selected.length > 0 && selected.length < list.length; | |||||
| }, [selected, list]); | |||||
| const checkAll = useCallback(() => { | |||||
| setSelected(checked ? [] : list); | |||||
| }, [list, checked]); | |||||
| const isSingleChecked = useCallback((item: T) => selected.includes(item), [selected]); | |||||
| const checkSingle = useCallback( | |||||
| (item: T) => { | |||||
| setSelected((prev) => { | |||||
| if (isSingleChecked(item)) { | |||||
| return prev.filter((i) => i !== item); | |||||
| } else { | |||||
| return [...prev, item]; | |||||
| } | |||||
| }); | |||||
| }, | |||||
| [selected, isSingleChecked], | |||||
| ); | |||||
| return [ | |||||
| selected, | |||||
| setSelected, | |||||
| checked, | |||||
| indeterminate, | |||||
| checkAll, | |||||
| isSingleChecked, | |||||
| checkSingle, | |||||
| ] as const; | |||||
| }; | |||||
| @@ -4,6 +4,7 @@ | |||||
| * @Description: 页面状态缓存,pop 回到这个页面的时候,重新构建之前的状态 | * @Description: 页面状态缓存,pop 回到这个页面的时候,重新构建之前的状态 | ||||
| */ | */ | ||||
| import { parseJsonText } from '@/utils'; | |||||
| import { useCallback, useState } from 'react'; | import { useCallback, useState } from 'react'; | ||||
| const pageKeys: string[] = []; | const pageKeys: string[] = []; | ||||
| @@ -14,11 +15,7 @@ const getCacheState = (key: string) => { | |||||
| const jsonStr = sessionStorage.getItem(key); | const jsonStr = sessionStorage.getItem(key); | ||||
| if (jsonStr) { | if (jsonStr) { | ||||
| removeCacheState(key); | removeCacheState(key); | ||||
| try { | |||||
| return JSON.parse(jsonStr); | |||||
| } catch (error) { | |||||
| return undefined; | |||||
| } | |||||
| return parseJsonText(jsonStr); | |||||
| } | } | ||||
| return undefined; | return undefined; | ||||
| }; | }; | ||||
| @@ -79,6 +79,12 @@ | |||||
| background-color: #fff; | background-color: #fff; | ||||
| } | } | ||||
| .ant-table-row-selected { | |||||
| .ant-table-cell { | |||||
| color: @primary-color; | |||||
| } | |||||
| } | |||||
| .ant-pro-page-container { | .ant-pro-page-container { | ||||
| overflow-y: auto; | overflow-y: auto; | ||||
| } | } | ||||
| @@ -162,7 +168,7 @@ | |||||
| height: 40px; | height: 40px; | ||||
| padding: 0 30px; | padding: 0 30px; | ||||
| font-size: @font-size-content; | font-size: @font-size-content; | ||||
| border-radius: 10px; | |||||
| border-radius: 6px; | |||||
| } | } | ||||
| .ant-btn-default { | .ant-btn-default { | ||||
| border-color: transparent; | border-color: transparent; | ||||
| @@ -0,0 +1,50 @@ | |||||
| import { setSessionToken } from '@/access'; | |||||
| import { loginByOauth2Req } from '@/services/auth'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { history, useModel, useSearchParams } from '@umijs/max'; | |||||
| import { message } from 'antd'; | |||||
| import { useEffect } from 'react'; | |||||
| import { flushSync } from 'react-dom'; | |||||
| import styles from './index.less'; | |||||
| function Authorize() { | |||||
| const { initialState, setInitialState } = useModel('@@initialState'); | |||||
| const [searchParams] = useSearchParams(); | |||||
| const code = searchParams.get('code'); | |||||
| const redirect = searchParams.get('redirect'); | |||||
| useEffect(() => { | |||||
| loginByOauth2(); | |||||
| }, []); | |||||
| // 登录 | |||||
| const loginByOauth2 = async () => { | |||||
| const params = { | |||||
| code, | |||||
| }; | |||||
| const [res] = await to(loginByOauth2Req(params)); | |||||
| debugger; | |||||
| if (res && res.data) { | |||||
| const { access_token, expires_in } = res.data; | |||||
| setSessionToken(access_token, access_token, expires_in); | |||||
| message.success('登录成功!'); | |||||
| await fetchUserInfo(); | |||||
| history.push(redirect || '/'); | |||||
| } | |||||
| }; | |||||
| const fetchUserInfo = async () => { | |||||
| const userInfo = await initialState?.fetchUserInfo?.(); | |||||
| if (userInfo) { | |||||
| flushSync(() => { | |||||
| setInitialState((s) => ({ | |||||
| ...s, | |||||
| currentUser: userInfo, | |||||
| })); | |||||
| }); | |||||
| } | |||||
| }; | |||||
| return <div className={styles.container}></div>; | |||||
| } | |||||
| export default Authorize; | |||||
| @@ -1,47 +1,46 @@ | |||||
| .code-config-list { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| flex-direction: column; | |||||
| .code-config { | |||||
| height: 100%; | height: 100%; | ||||
| height: 100%; | |||||
| padding: 20px 0; | |||||
| background: white; | |||||
| box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | |||||
| &__header { | |||||
| &__list { | |||||
| display: flex; | display: flex; | ||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| height: 32px; | |||||
| margin-bottom: 30px; | |||||
| padding: 0 30px; | |||||
| color: @text-color; | |||||
| font-size: 15px; | |||||
| } | |||||
| flex-direction: column; | |||||
| height: calc(100% - 60px); | |||||
| margin-top: 10px; | |||||
| padding: 30px 30px 0; | |||||
| background: white; | |||||
| border-radius: 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| &__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; | |||||
| } | |||||
| &__header { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| height: 32px; | |||||
| color: @text-color; | |||||
| font-size: 15px; | |||||
| } | |||||
| &__empty { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| align-items: center; | |||||
| justify-content: center; | |||||
| } | |||||
| &__content { | |||||
| display: flex; | |||||
| flex: 1 1 0%; | |||||
| flex-wrap: wrap; | |||||
| gap: 20px; | |||||
| align-content: flex-start; | |||||
| width: 100%; | |||||
| margin: 25px 0; | |||||
| overflow-y: auto; | |||||
| } | |||||
| &__empty { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| align-items: center; | |||||
| justify-content: center; | |||||
| } | |||||
| :global { | |||||
| .ant-pagination { | |||||
| margin-right: 30px; | |||||
| text-align: right; | |||||
| :global { | |||||
| .ant-pagination { | |||||
| margin-bottom: 25px; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,5 +1,12 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-10-10 09:55:12 | |||||
| * @Description: 代码配置 | |||||
| */ | |||||
| import KFEmpty, { EmptyType } from '@/components/KFEmpty'; | import KFEmpty, { EmptyType } from '@/components/KFEmpty'; | ||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import PageTitle from '@/components/PageTitle'; | |||||
| import { deleteCodeConfigReq, getCodeConfigListReq } from '@/services/codeConfig'; | import { deleteCodeConfigReq, getCodeConfigListReq } from '@/services/codeConfig'; | ||||
| import { getGitUrl } from '@/utils'; | import { getGitUrl } from '@/utils'; | ||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| @@ -127,64 +134,69 @@ function CodeConfigList() { | |||||
| }; | }; | ||||
| return ( | return ( | ||||
| <div className={styles['code-config-list']}> | |||||
| <div className={styles['code-config-list__header']}> | |||||
| <span>数据总数:{total} 个</span> | |||||
| <div> | |||||
| <div className={styles['code-config']}> | |||||
| <PageTitle title="代码配置"></PageTitle> | |||||
| <div className={styles['code-config__list']}> | |||||
| <div className={styles['code-config__list__header']}> | |||||
| <span>数据总数:{total} 个</span> | |||||
| <Input.Search | <Input.Search | ||||
| placeholder="按代码仓库名称筛选" | placeholder="按代码仓库名称筛选" | ||||
| allowClear | allowClear | ||||
| onSearch={handleSearch} | onSearch={handleSearch} | ||||
| style={{ | style={{ | ||||
| width: 300, | width: 300, | ||||
| marginRight: '20px', | |||||
| marginLeft: 'auto', | |||||
| }} | }} | ||||
| onChange={(e) => setInputText(e.target.value)} | onChange={(e) => setInputText(e.target.value)} | ||||
| value={inputText} | value={inputText} | ||||
| /> | /> | ||||
| <Button | <Button | ||||
| type="default" | type="default" | ||||
| style={{ marginLeft: '20px' }} | |||||
| style={{ marginRight: 0 }} | |||||
| onClick={createCodeConfig} | onClick={createCodeConfig} | ||||
| icon={<KFIcon type="icon-xinjian2" />} | icon={<KFIcon type="icon-xinjian2" />} | ||||
| > | > | ||||
| 新建代码配置 | 新建代码配置 | ||||
| </Button> | </Button> | ||||
| </div> | </div> | ||||
| </div> | |||||
| {dataList && dataList.length !== 0 && ( | |||||
| <> | |||||
| <div className={styles['code-config-list__content']}> | |||||
| {dataList.map((item) => ( | |||||
| <CodeConfigItem | |||||
| item={item} | |||||
| key={item.id} | |||||
| onRemove={handleRemove} | |||||
| onEdit={handleEdit} | |||||
| onClick={handleClick} | |||||
| /> | |||||
| ))} | |||||
| </div> | |||||
| <Pagination | |||||
| total={total} | |||||
| showSizeChanger | |||||
| defaultPageSize={20} | |||||
| pageSizeOptions={[20, 40, 60, 80, 100]} | |||||
| showQuickJumper | |||||
| onChange={handlePageChange} | |||||
| {...pagination} | |||||
| {dataList && dataList.length !== 0 && ( | |||||
| <> | |||||
| <div className={styles['code-config__list__content']}> | |||||
| {dataList.map((item) => ( | |||||
| <CodeConfigItem | |||||
| item={item} | |||||
| key={item.id} | |||||
| onRemove={handleRemove} | |||||
| onEdit={handleEdit} | |||||
| onClick={handleClick} | |||||
| /> | |||||
| ))} | |||||
| </div> | |||||
| <Pagination | |||||
| align="end" | |||||
| total={total} | |||||
| showSizeChanger | |||||
| defaultPageSize={20} | |||||
| pageSizeOptions={[20, 40, 60, 80, 100]} | |||||
| showQuickJumper | |||||
| onChange={handlePageChange} | |||||
| {...pagination} | |||||
| /> | |||||
| </> | |||||
| )} | |||||
| {dataList && dataList.length === 0 && ( | |||||
| <KFEmpty | |||||
| className={styles['code-config__list__empty']} | |||||
| type={EmptyType.NoData} | |||||
| title="暂无数据" | |||||
| content={'很抱歉,没有搜索到您想要的内容\n建议刷新试试'} | |||||
| hasFooter={true} | |||||
| onRefresh={getDataList} | |||||
| /> | /> | ||||
| </> | |||||
| )} | |||||
| {dataList && dataList.length === 0 && ( | |||||
| <KFEmpty | |||||
| className={styles['code-config-list__empty']} | |||||
| type={EmptyType.NoData} | |||||
| title="暂无数据" | |||||
| content={'很抱歉,没有搜索到您想要的内容\n建议刷新试试'} | |||||
| hasFooter={true} | |||||
| onRefresh={getDataList} | |||||
| /> | |||||
| )} | |||||
| )} | |||||
| </div> | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -2,51 +2,97 @@ | |||||
| position: relative; | position: relative; | ||||
| width: calc(25% - 15px); | width: calc(25% - 15px); | ||||
| padding: 20px; | padding: 20px; | ||||
| background: white; | |||||
| border: 1px solid #eaeaea; | |||||
| background: linear-gradient(180deg, #f7faff 0%, #ffffff 100%); | |||||
| border: 2px solid white; | |||||
| border-radius: 4px; | border-radius: 4px; | ||||
| box-shadow: 0px 3px 10px rgba(164, 169, 181, 0.13); | |||||
| cursor: pointer; | cursor: pointer; | ||||
| &:hover { | |||||
| border-color: @primary-color; | |||||
| } | |||||
| @media screen and (max-width: 1860px) { | @media screen and (max-width: 1860px) { | ||||
| & { | & { | ||||
| width: calc(33.33% - 13.33px); | width: calc(33.33% - 13.33px); | ||||
| } | } | ||||
| } | } | ||||
| &__name { | |||||
| &__icon { | |||||
| flex: none; | |||||
| width: 16px; | |||||
| height: 16px; | |||||
| margin-right: 10px; | margin-right: 10px; | ||||
| } | |||||
| &__name { | |||||
| position: relative; | |||||
| margin-right: 20px; | |||||
| margin-bottom: 0 !important; | margin-bottom: 0 !important; | ||||
| color: @text-color; | color: @text-color; | ||||
| font-weight: 500; | |||||
| font-size: 16px; | font-size: 16px; | ||||
| &::after { | |||||
| position: absolute; | |||||
| top: 14px; | |||||
| left: 0; | |||||
| width: 100%; | |||||
| height: 6px; | |||||
| background: linear-gradient( | |||||
| to right, | |||||
| .addAlpha(@primary-color, 0.4) [] 0, | |||||
| .addAlpha(@primary-color, 0) [] 100% | |||||
| ); | |||||
| content: ''; | |||||
| } | |||||
| } | |||||
| &:hover &__name { | |||||
| color: @primary-color; | |||||
| } | } | ||||
| &__tag { | &__tag { | ||||
| padding: 2px 11px; | |||||
| font-size: 12px; | |||||
| border-radius: 1000px; | |||||
| flex: none; | |||||
| padding: 1px 10px; | |||||
| font-size: 13px; | |||||
| border-radius: 2px; | |||||
| &--public { | &--public { | ||||
| color: @primary-color; | color: @primary-color; | ||||
| background-color: .addAlpha(@primary-color, 0.08) []; | |||||
| border-color: .addAlpha(@primary-color, 0.5) []; | |||||
| background-color: .addAlpha(@primary-color, 0.1) []; | |||||
| border: 1px solid .addAlpha(@primary-color, 0.5) []; | |||||
| } | } | ||||
| &--private { | &--private { | ||||
| color: @warning-color; | color: @warning-color; | ||||
| background-color: .addAlpha(@warning-color, 0.08) []; | |||||
| border-color: .addAlpha(@warning-color, 0.5) []; | |||||
| background-color: .addAlpha(@warning-color, 0.1) []; | |||||
| border: 1px solid .addAlpha(@warning-color, 0.5) []; | |||||
| } | } | ||||
| } | } | ||||
| :global { | |||||
| .ant-btn { | |||||
| flex: none; | |||||
| color: #808080; | |||||
| } | |||||
| } | |||||
| &__url-box { | |||||
| margin-bottom: 15px; | |||||
| padding: 14px; | |||||
| background-color: .addAlpha(@primary-color, 0.04) []; | |||||
| border-radius: 4px; | |||||
| } | |||||
| &__url { | &__url { | ||||
| margin-bottom: 10px; | |||||
| color: @text-color-secondary; | |||||
| margin-bottom: 15px !important; | |||||
| color: @text-color; | |||||
| font-size: 14px; | font-size: 14px; | ||||
| } | } | ||||
| &__branch { | &__branch { | ||||
| margin-bottom: 20px; | |||||
| color: @text-color-tertiary; | |||||
| color: @text-color-secondary; | |||||
| font-size: 14px; | font-size: 14px; | ||||
| } | } | ||||
| @@ -59,13 +105,4 @@ | |||||
| color: #808080; | color: #808080; | ||||
| font-size: 13px; | font-size: 13px; | ||||
| } | } | ||||
| &:hover { | |||||
| border-color: @primary-color; | |||||
| box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1); | |||||
| } | |||||
| &:hover &__name { | |||||
| color: @primary-color; | |||||
| } | |||||
| } | } | ||||
| @@ -19,6 +19,11 @@ function CodeConfigItem({ item, onClick, onEdit, onRemove }: CodeConfigItemProps | |||||
| return ( | return ( | ||||
| <div className={styles['code-config-item']} onClick={() => onClick?.(item)}> | <div className={styles['code-config-item']} onClick={() => onClick?.(item)}> | ||||
| <Flex justify="space-between" align="center" style={{ marginBottom: '20px', height: '32px' }}> | <Flex justify="space-between" align="center" style={{ marginBottom: '20px', height: '32px' }}> | ||||
| <img | |||||
| className={styles['code-config-item__icon']} | |||||
| src={require('@/assets/img/code-name-icon.png')} | |||||
| alt="" | |||||
| /> | |||||
| <Typography.Paragraph | <Typography.Paragraph | ||||
| className={styles['code-config-item__name']} | className={styles['code-config-item__name']} | ||||
| ellipsis={{ tooltip: item.code_repo_name }} | ellipsis={{ tooltip: item.code_repo_name }} | ||||
| @@ -58,17 +63,19 @@ function CodeConfigItem({ item, onClick, onEdit, onRemove }: CodeConfigItemProps | |||||
| <KFIcon type="icon-shanchu" font={17} /> | <KFIcon type="icon-shanchu" font={17} /> | ||||
| </Button> | </Button> | ||||
| </Flex> | </Flex> | ||||
| <Typography.Paragraph | |||||
| className={styles['code-config-item__url']} | |||||
| ellipsis={{ tooltip: item.git_url }} | |||||
| > | |||||
| {item.git_url} | |||||
| </Typography.Paragraph> | |||||
| <div className={styles['code-config-item__branch']}>{item.git_branch}</div> | |||||
| <div className={styles['code-config-item__url-box']}> | |||||
| <Typography.Paragraph | |||||
| className={styles['code-config-item__url']} | |||||
| ellipsis={{ tooltip: item.git_url }} | |||||
| > | |||||
| {item.git_url} | |||||
| </Typography.Paragraph> | |||||
| <div className={styles['code-config-item__branch']}>{item.git_branch}</div> | |||||
| </div> | |||||
| <Flex justify="space-between"> | <Flex justify="space-between"> | ||||
| <div className={styles['code-config-item__user']}> | <div className={styles['code-config-item__user']}> | ||||
| <img | <img | ||||
| style={{ width: '17px', marginRight: '6px' }} | |||||
| style={{ width: '16px', marginRight: '6px' }} | |||||
| src={creatByImg} | src={creatByImg} | ||||
| alt="" | alt="" | ||||
| draggable={false} | draggable={false} | ||||
| @@ -38,10 +38,6 @@ | |||||
| &__bottom { | &__bottom { | ||||
| position: relative; | position: relative; | ||||
| height: calc(100% - 135px); | height: calc(100% - 135px); | ||||
| padding: 8px 30px 20px; | |||||
| background: #ffffff; | |||||
| border-radius: 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| &__legend { | &__legend { | ||||
| position: absolute; | position: absolute; | ||||
| @@ -52,6 +48,12 @@ | |||||
| :global { | :global { | ||||
| .ant-tabs { | .ant-tabs { | ||||
| height: 100%; | height: 100%; | ||||
| .ant-tabs-nav-wrap { | |||||
| padding-top: 8px; | |||||
| padding-left: 30px; | |||||
| background-color: white; | |||||
| border-radius: 10px 10px 0 0; | |||||
| } | |||||
| .ant-tabs-content-holder { | .ant-tabs-content-holder { | ||||
| height: 100%; | height: 100%; | ||||
| .ant-tabs-content { | .ant-tabs-content { | ||||
| @@ -164,7 +164,16 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { | |||||
| key: ResourceInfoTabKeys.Introduction, | key: ResourceInfoTabKeys.Introduction, | ||||
| label: `${typeName}简介`, | label: `${typeName}简介`, | ||||
| icon: <KFIcon type="icon-moxingjianjie" />, | icon: <KFIcon type="icon-moxingjianjie" />, | ||||
| children: <ResourceIntro resourceType={resourceType} info={info}></ResourceIntro>, | |||||
| children: ( | |||||
| <ResourceIntro | |||||
| resourceType={resourceType} | |||||
| info={info} | |||||
| resourceId={resourceId} | |||||
| identifier={identifier} | |||||
| owner={owner} | |||||
| version={version} | |||||
| ></ResourceIntro> | |||||
| ), | |||||
| }, | }, | ||||
| { | { | ||||
| key: ResourceInfoTabKeys.Version, | key: ResourceInfoTabKeys.Version, | ||||
| @@ -1,10 +1,25 @@ | |||||
| .resource-intro { | .resource-intro { | ||||
| width: 100%; | width: 100%; | ||||
| margin-top: 24px; | |||||
| &__basic { | |||||
| width: 100%; | |||||
| } | |||||
| &__usage { | |||||
| width: 100%; | |||||
| &__top { | |||||
| padding: 20px 30px; | |||||
| background: white; | |||||
| border-radius: 0 0 10px 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| pre { | |||||
| margin-bottom: 0 !important; | |||||
| } | |||||
| &__title { | |||||
| margin: 15px 0; | |||||
| color: @text-color-secondary; | |||||
| font-size: 14px; | |||||
| } | |||||
| &__desc { | |||||
| color: @text-color; | |||||
| font-size: @font-size; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| import BasicInfo, { BasicInfoData } from '@/components/BasicInfo'; | |||||
| import BasicTableInfo, { BasicInfoData } from '@/components/BasicTableInfo'; | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo'; | import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo'; | ||||
| import { | import { | ||||
| @@ -8,13 +8,19 @@ import { | |||||
| ProjectDependency, | ProjectDependency, | ||||
| ResourceType, | ResourceType, | ||||
| TrainTask, | TrainTask, | ||||
| resourceConfig, | |||||
| } from '@/pages/Dataset/config'; | } from '@/pages/Dataset/config'; | ||||
| import ModelMetrics from '@/pages/Model/components/ModelMetrics'; | |||||
| import { getGitUrl } from '@/utils'; | import { getGitUrl } from '@/utils'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type ResourceIntroProps = { | type ResourceIntroProps = { | ||||
| resourceType: ResourceType; | resourceType: ResourceType; | ||||
| info: DatasetData | ModelData; | info: DatasetData | ModelData; | ||||
| resourceId: number; | |||||
| identifier: string; | |||||
| owner: string; | |||||
| version?: string; | |||||
| }; | }; | ||||
| const formatDataset = (datasets?: DatasetData[]) => { | const formatDataset = (datasets?: DatasetData[]) => { | ||||
| @@ -27,29 +33,6 @@ const formatDataset = (datasets?: DatasetData[]) => { | |||||
| })); | })); | ||||
| }; | }; | ||||
| const formatParams = (map?: Record<string, string>, space: string = '') => { | |||||
| if (!map || Object.keys(map).length === 0) { | |||||
| return undefined; | |||||
| } | |||||
| return Object.entries(map) | |||||
| .map(([key, value]) => `${space}${key} : ${value}`) | |||||
| .join('\n'); | |||||
| }; | |||||
| const formatMetrics = (map?: Record<string, string>) => { | |||||
| if (!map || Object.keys(map).length === 0) { | |||||
| return undefined; | |||||
| } | |||||
| return Object.entries(map) | |||||
| .map(([key, value]) => { | |||||
| if (typeof value === 'object' && value !== null) { | |||||
| return `${key} : \n${formatParams(value, ' ')}`; | |||||
| } | |||||
| return `${key} : ${value}`; | |||||
| }) | |||||
| .join('\n'); | |||||
| }; | |||||
| const getProjectUrl = (project?: ProjectDependency) => { | const getProjectUrl = (project?: ProjectDependency) => { | ||||
| if (!project || !project.url || !project.branch) { | if (!project || !project.url || !project.branch) { | ||||
| return undefined; | return undefined; | ||||
| @@ -93,49 +76,50 @@ const getDatasetDatas = (data: DatasetData): BasicInfoData[] => [ | |||||
| { | { | ||||
| label: '数据集名称', | label: '数据集名称', | ||||
| value: data.name, | value: data.name, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '版本', | label: '版本', | ||||
| value: data.version, | value: data.version, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '创建人', | label: '创建人', | ||||
| value: data.create_by, | value: data.create_by, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '更新时间', | label: '更新时间', | ||||
| value: data.update_time, | value: data.update_time, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '数据来源', | label: '数据来源', | ||||
| value: data.dataset_source, | value: data.dataset_source, | ||||
| format: formatSource, | format: formatSource, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '训练任务', | label: '训练任务', | ||||
| value: data.train_task, | value: data.train_task, | ||||
| format: formatTrainTask, | format: formatTrainTask, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '处理代码', | label: '处理代码', | ||||
| value: data.processing_code, | value: data.processing_code, | ||||
| format: formatProject, | format: formatProject, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '数据集分类', | label: '数据集分类', | ||||
| value: data.data_type, | value: data.data_type, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '研究方向', | label: '研究方向', | ||||
| value: data.data_tag, | value: data.data_tag, | ||||
| }, | |||||
| { | |||||
| label: '数据集描述', | |||||
| value: data.description, | |||||
| }, | |||||
| { | |||||
| label: '版本描述', | |||||
| value: data.version_desc, | |||||
| ellipsis: true, | |||||
| }, | }, | ||||
| ]; | ]; | ||||
| @@ -143,77 +127,79 @@ const getModelDatas = (data: ModelData): BasicInfoData[] => [ | |||||
| { | { | ||||
| label: '模型名称', | label: '模型名称', | ||||
| value: data.name, | value: data.name, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '版本', | label: '版本', | ||||
| value: data.version, | value: data.version, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '创建人', | label: '创建人', | ||||
| value: data.create_by, | value: data.create_by, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '更新时间', | label: '更新时间', | ||||
| value: data.update_time, | value: data.update_time, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '训练镜像', | label: '训练镜像', | ||||
| value: data.image, | value: data.image, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '训练代码', | label: '训练代码', | ||||
| value: data.project_depency, | value: data.project_depency, | ||||
| format: formatProject, | format: formatProject, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '训练数据集', | label: '训练数据集', | ||||
| value: data.train_datasets, | value: data.train_datasets, | ||||
| format: formatDataset, | format: formatDataset, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '测试数据集', | label: '测试数据集', | ||||
| value: data.test_datasets, | value: data.test_datasets, | ||||
| format: formatDataset, | format: formatDataset, | ||||
| }, | |||||
| { | |||||
| label: '参数', | |||||
| value: data.params, | |||||
| format: formatParams, | |||||
| }, | |||||
| { | |||||
| label: '指标', | |||||
| value: data.metrics, | |||||
| format: formatMetrics, | |||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '模型来源', | label: '模型来源', | ||||
| value: data.model_source, | value: data.model_source, | ||||
| format: formatSource, | format: formatSource, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '训练任务', | label: '训练任务', | ||||
| value: data.train_task, | value: data.train_task, | ||||
| format: formatTrainTask, | format: formatTrainTask, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '模型框架', | label: '模型框架', | ||||
| value: data.model_type, | value: data.model_type, | ||||
| ellipsis: true, | |||||
| }, | }, | ||||
| { | { | ||||
| label: '模型能力', | label: '模型能力', | ||||
| value: data.model_tag, | value: data.model_tag, | ||||
| }, | |||||
| { | |||||
| label: '模型描述', | |||||
| value: data.description, | |||||
| }, | |||||
| { | |||||
| label: '版本描述', | |||||
| value: data.version_desc, | |||||
| ellipsis: true, | |||||
| }, | }, | ||||
| ]; | ]; | ||||
| function ResourceIntro({ resourceType, info }: ResourceIntroProps) { | |||||
| function ResourceIntro({ | |||||
| resourceType, | |||||
| info, | |||||
| resourceId, | |||||
| identifier, | |||||
| owner, | |||||
| version, | |||||
| }: ResourceIntroProps) { | |||||
| const config = resourceConfig[resourceType]; | |||||
| const basicDatas: BasicInfoData[] = | const basicDatas: BasicInfoData[] = | ||||
| resourceType === ResourceType.Dataset | resourceType === ResourceType.Dataset | ||||
| ? getDatasetDatas(info as DatasetData) | ? getDatasetDatas(info as DatasetData) | ||||
| @@ -221,23 +207,37 @@ function ResourceIntro({ resourceType, info }: ResourceIntroProps) { | |||||
| return ( | return ( | ||||
| <div className={styles['resource-intro']}> | <div className={styles['resource-intro']}> | ||||
| <SubAreaTitle | |||||
| title="基本信息" | |||||
| image={require('@/assets/img/mirror-basic.png')} | |||||
| style={{ marginBottom: '26px' }} | |||||
| ></SubAreaTitle> | |||||
| <div className={styles['resource-intro__basic']}> | |||||
| <BasicInfo datas={basicDatas} labelWidth={86}></BasicInfo> | |||||
| <div className={styles['resource-intro__top']}> | |||||
| <SubAreaTitle | |||||
| title="基本信息" | |||||
| image={require('@/assets/img/mirror-basic.png')} | |||||
| style={{ marginBottom: '15px' }} | |||||
| ></SubAreaTitle> | |||||
| <div className={styles['resource-intro__top__basic']}> | |||||
| <BasicTableInfo datas={basicDatas} labelWidth={135}></BasicTableInfo> | |||||
| </div> | |||||
| <div className={styles['resource-intro__top__title']}>{`${config.name}描述`}</div> | |||||
| <div className={styles['resource-intro__top__desc']}>{info.description ?? '暂无描述'}</div> | |||||
| <div className={styles['resource-intro__top__title']}>版本描述</div> | |||||
| <div className={styles['resource-intro__top__desc']}>{info.version_desc ?? '暂无描述'}</div> | |||||
| <SubAreaTitle | |||||
| title="实例用法" | |||||
| image={require('@/assets/img/usage-icon.png')} | |||||
| style={{ margin: '25px 0 15px' }} | |||||
| ></SubAreaTitle> | |||||
| <div | |||||
| className={styles['resource-intro__top__usage']} | |||||
| dangerouslySetInnerHTML={{ __html: info.usage ?? '暂无实例用法' }} | |||||
| ></div> | |||||
| </div> | </div> | ||||
| <SubAreaTitle | |||||
| title="实例用法" | |||||
| image={require('@/assets/img/usage-icon.png')} | |||||
| style={{ margin: '40px 0 24px' }} | |||||
| ></SubAreaTitle> | |||||
| <div | |||||
| className={styles['resource-intro__usage']} | |||||
| dangerouslySetInnerHTML={{ __html: info.usage ?? '暂无实例用法' }} | |||||
| ></div> | |||||
| {resourceType === ResourceType.Model && version && ( | |||||
| <ModelMetrics | |||||
| resourceId={resourceId} | |||||
| identifier={identifier} | |||||
| owner={owner} | |||||
| version={version} | |||||
| ></ModelMetrics> | |||||
| )} | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -13,13 +13,37 @@ | |||||
| } | } | ||||
| } | } | ||||
| &:hover { | |||||
| border-color: @primary-color; | |||||
| box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1); | |||||
| } | |||||
| &__name { | &__name { | ||||
| position: relative; | position: relative; | ||||
| display: inline-block; | display: inline-block; | ||||
| height: 24px; | height: 24px; | ||||
| margin: 0 10px 0 0 !important; | margin: 0 10px 0 0 !important; | ||||
| color: @text-color; | color: @text-color; | ||||
| font-weight: 500; | |||||
| font-size: 16px; | font-size: 16px; | ||||
| &::after { | |||||
| position: absolute; | |||||
| top: 14px; | |||||
| left: 0; | |||||
| width: 100%; | |||||
| height: 6px; | |||||
| background: linear-gradient( | |||||
| to right, | |||||
| .addAlpha(@primary-color, 0.3) [] 0, | |||||
| .addAlpha(@primary-color, 0) [] 100% | |||||
| ); | |||||
| content: ''; | |||||
| } | |||||
| } | |||||
| &:hover &__name { | |||||
| color: @primary-color; | |||||
| } | } | ||||
| &__description { | &__description { | ||||
| @@ -37,25 +61,4 @@ | |||||
| color: #808080; | color: #808080; | ||||
| font-size: 13px; | 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: ''; | |||||
| } | |||||
| } | } | ||||
| @@ -33,7 +33,6 @@ | |||||
| :global { | :global { | ||||
| .ant-pagination { | .ant-pagination { | ||||
| margin-right: 30px; | margin-right: 30px; | ||||
| text-align: right; | |||||
| } | } | ||||
| } | } | ||||
| @@ -204,6 +204,7 @@ function ResourceList( | |||||
| ))} | ))} | ||||
| </div> | </div> | ||||
| <Pagination | <Pagination | ||||
| align="end" | |||||
| total={total} | total={total} | ||||
| showSizeChanger | showSizeChanger | ||||
| defaultPageSize={20} | defaultPageSize={20} | ||||
| @@ -1,4 +1,9 @@ | |||||
| .resource-version { | .resource-version { | ||||
| min-height: 100%; | |||||
| padding: 20px 30px; | |||||
| color: @text-color; | color: @text-color; | ||||
| font-size: @font-size-content; | font-size: @font-size-content; | ||||
| background: white; | |||||
| border-radius: 0 0 10px 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| } | } | ||||
| @@ -1,5 +1,3 @@ | |||||
| import CommonTableCell from '@/components/CommonTableCell'; | |||||
| import DateTableCell from '@/components/DateTableCell'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { | import { | ||||
| ResourceData, | ResourceData, | ||||
| @@ -8,7 +6,8 @@ import { | |||||
| resourceConfig, | resourceConfig, | ||||
| } from '@/pages/Dataset/config'; | } from '@/pages/Dataset/config'; | ||||
| import { downLoadZip } from '@/utils/downloadfile'; | import { downLoadZip } from '@/utils/downloadfile'; | ||||
| import { Button, Flex, Table } from 'antd'; | |||||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||||
| import { Button, Flex, Table, TableProps } from 'antd'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type ResourceVersionProps = { | type ResourceVersionProps = { | ||||
| @@ -38,37 +37,33 @@ function ResourceVersion({ resourceType, info }: ResourceVersionProps) { | |||||
| downLoadZip(url, { url: record.url }); | downLoadZip(url, { url: record.url }); | ||||
| }; | }; | ||||
| const columns = [ | |||||
| const columns: TableProps<ResourceFileData>['columns'] = [ | |||||
| { | { | ||||
| title: '序号', | title: '序号', | ||||
| dataIndex: 'index', | dataIndex: 'index', | ||||
| key: 'index', | key: 'index', | ||||
| width: 80, | width: 80, | ||||
| render(_text: string, _record: ResourceFileData, index: number) { | |||||
| return <span>{index + 1}</span>; | |||||
| }, | |||||
| render: tableCellRender(false, TableCellValueType.Index), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '文件名称', | title: '文件名称', | ||||
| dataIndex: 'file_name', | dataIndex: 'file_name', | ||||
| key: 'file_name', | key: 'file_name', | ||||
| render: (text: string, record: ResourceFileData) => ( | |||||
| <a className="kf-table-row-link" onClick={() => downloadAlone(record)}> | |||||
| {text} | |||||
| </a> | |||||
| ), | |||||
| render: tableCellRender(false, TableCellValueType.Link, { | |||||
| onClick: downloadAlone, | |||||
| }), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '文件大小', | title: '文件大小', | ||||
| dataIndex: 'file_size', | dataIndex: 'file_size', | ||||
| key: 'file_size', | key: 'file_size', | ||||
| render: CommonTableCell(), | |||||
| render: tableCellRender(), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '更新时间', | title: '更新时间', | ||||
| dataIndex: 'update_time', | dataIndex: 'update_time', | ||||
| key: 'update_time', | key: 'update_time', | ||||
| render: DateTableCell, | |||||
| render: tableCellRender(false, TableCellValueType.Date), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| @@ -91,7 +86,7 @@ function ResourceVersion({ resourceType, info }: ResourceVersionProps) { | |||||
| return ( | return ( | ||||
| <div className={styles['resource-version']}> | <div className={styles['resource-version']}> | ||||
| <Flex justify="space-between" align="center" style={{ margin: '30px 0' }}> | |||||
| <Flex justify="space-between" align="center" style={{ marginBottom: '20px' }}> | |||||
| <Flex align="center"> | <Flex align="center"> | ||||
| <Button | <Button | ||||
| type="default" | type="default" | ||||
| @@ -4,8 +4,6 @@ | |||||
| * @Description: 开发环境列表 | * @Description: 开发环境列表 | ||||
| */ | */ | ||||
| import CommonTableCell from '@/components/CommonTableCell'; | |||||
| import DateTableCell from '@/components/DateTableCell'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { DevEditorStatus } from '@/enums'; | import { DevEditorStatus } from '@/enums'; | ||||
| import { useCacheState } from '@/hooks/pageCacheState'; | import { useCacheState } from '@/hooks/pageCacheState'; | ||||
| @@ -19,6 +17,7 @@ import themes from '@/styles/theme.less'; | |||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import SessionStorage from '@/utils/sessionStorage'; | import SessionStorage from '@/utils/sessionStorage'; | ||||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { useNavigate } from '@umijs/max'; | import { useNavigate } from '@umijs/max'; | ||||
| import { | import { | ||||
| @@ -153,7 +152,12 @@ function EditorList() { | |||||
| }; | }; | ||||
| // 分页切换 | // 分页切换 | ||||
| const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => { | |||||
| const handleTableChange: TableProps<EditorData>['onChange'] = ( | |||||
| pagination, | |||||
| _filters, | |||||
| _sorter, | |||||
| { action }, | |||||
| ) => { | |||||
| if (action === 'paginate') { | if (action === 'paginate') { | ||||
| setPagination(pagination); | setPagination(pagination); | ||||
| } | } | ||||
| @@ -186,21 +190,21 @@ function EditorList() { | |||||
| dataIndex: 'computing_resource', | dataIndex: 'computing_resource', | ||||
| key: 'computing_resource', | key: 'computing_resource', | ||||
| width: '20%', | width: '20%', | ||||
| render: CommonTableCell(), | |||||
| render: tableCellRender(), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '创建者', | title: '创建者', | ||||
| dataIndex: 'update_by', | dataIndex: 'update_by', | ||||
| key: 'update_by', | key: 'update_by', | ||||
| width: '20%', | width: '20%', | ||||
| render: CommonTableCell(), | |||||
| render: tableCellRender(), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '创建时间', | title: '创建时间', | ||||
| dataIndex: 'create_time', | dataIndex: 'create_time', | ||||
| key: 'create_time', | key: 'create_time', | ||||
| width: '20%', | width: '20%', | ||||
| render: DateTableCell, | |||||
| render: tableCellRender(false, TableCellValueType.Date), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| @@ -14,10 +14,30 @@ | |||||
| &__table { | &__table { | ||||
| height: calc(100% - 60px); | height: calc(100% - 60px); | ||||
| padding: 20px 30px 0; | |||||
| padding: 20px 30px; | |||||
| background-color: white; | background-color: white; | ||||
| border-radius: 10px; | border-radius: 10px; | ||||
| &__footer { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| padding-top: 20px; | |||||
| color: @text-color-secondary; | |||||
| font-size: 12px; | |||||
| background-color: white; | |||||
| div { | |||||
| flex: 1; | |||||
| height: 1px; | |||||
| background-color: @border-color-base; | |||||
| } | |||||
| p { | |||||
| flex: none; | |||||
| margin: 0 8px; | |||||
| } | |||||
| } | |||||
| :global { | :global { | ||||
| .ant-table-container { | .ant-table-container { | ||||
| border: none !important; | border: none !important; | ||||
| @@ -25,7 +45,7 @@ | |||||
| .ant-table-thead { | .ant-table-thead { | ||||
| .ant-table-cell { | .ant-table-cell { | ||||
| background-color: rgb(247, 247, 247); | background-color: rgb(247, 247, 247); | ||||
| border-color: #e8e8e8 !important; | |||||
| border-color: @border-color-base !important; | |||||
| } | } | ||||
| } | } | ||||
| .ant-table-tbody { | .ant-table-tbody { | ||||
| @@ -34,6 +54,13 @@ | |||||
| border-left: none !important; | border-left: none !important; | ||||
| } | } | ||||
| } | } | ||||
| .ant-table-tbody-virtual::after { | |||||
| border-bottom: none !important; | |||||
| } | |||||
| .ant-table-footer { | |||||
| padding: 0; | |||||
| border: none !important; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,14 +1,20 @@ | |||||
| // import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-10-10 09:55:12 | |||||
| * @Description: 实验对比 | |||||
| */ | |||||
| import { useDomSize } from '@/hooks'; | |||||
| import { | import { | ||||
| getExpEvaluateInfosReq, | getExpEvaluateInfosReq, | ||||
| getExpMetricsReq, | getExpMetricsReq, | ||||
| getExpTrainInfosReq, | getExpTrainInfosReq, | ||||
| } from '@/services/experiment'; | } from '@/services/experiment'; | ||||
| import { tableSorter } from '@/utils'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import tableCellRender, { arrayFormatter, dateFormatter } from '@/utils/table'; | |||||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||||
| import { useSearchParams } from '@umijs/max'; | import { useSearchParams } from '@umijs/max'; | ||||
| import { App, Button, Table, /* TablePaginationConfig,*/ TableProps, Tooltip } from 'antd'; | import { App, Button, Table, /* TablePaginationConfig,*/ TableProps, Tooltip } from 'antd'; | ||||
| import classNames from 'classnames'; | |||||
| import { useEffect, useMemo, useState } from 'react'; | import { useEffect, useMemo, useState } from 'react'; | ||||
| import ExperimentStatusCell from '../components/ExperimentStatusCell'; | import ExperimentStatusCell from '../components/ExperimentStatusCell'; | ||||
| import { ComparisonType, comparisonConfig } from './config'; | import { ComparisonType, comparisonConfig } from './config'; | ||||
| @@ -23,43 +29,60 @@ type TableData = { | |||||
| metrics_names: string[]; | metrics_names: string[]; | ||||
| metrics: Record<string, number>; | metrics: Record<string, number>; | ||||
| params_names: string[]; | params_names: string[]; | ||||
| params: Record<string, string>; | |||||
| params: Record<string, number>; | |||||
| }; | }; | ||||
| const pageSize = 30; | |||||
| // function Footer() { | |||||
| // return ( | |||||
| // <div className={styles['experiment-comparison__table__footer']}> | |||||
| // <div></div> | |||||
| // <p>我是有底线的</p> | |||||
| // <div></div> | |||||
| // </div> | |||||
| // ); | |||||
| // } | |||||
| function ExperimentComparison() { | function ExperimentComparison() { | ||||
| const [searchParams] = useSearchParams(); | const [searchParams] = useSearchParams(); | ||||
| const comparisonType = searchParams.get('type') as ComparisonType; | const comparisonType = searchParams.get('type') as ComparisonType; | ||||
| const experimentId = searchParams.get('id'); | const experimentId = searchParams.get('id'); | ||||
| const [tableData, setTableData] = useState<TableData[]>([]); | const [tableData, setTableData] = useState<TableData[]>([]); | ||||
| // const [cacheState, setCacheState] = useCacheState(); | |||||
| // const [total, setTotal] = useState(0); | |||||
| const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); | const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); | ||||
| // const [loading, setLoading] = useState(false); | |||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| const config = useMemo(() => comparisonConfig[comparisonType], [comparisonType]); | const config = useMemo(() => comparisonConfig[comparisonType], [comparisonType]); | ||||
| // const [pagination, setPagination] = useState<TablePaginationConfig>( | |||||
| // cacheState?.pagination ?? { | |||||
| // current: 1, | |||||
| // pageSize: 10, | |||||
| // }, | |||||
| // ); | |||||
| const [tableRef, { width: tableWidth, height: tableHeight }] = useDomSize<HTMLDivElement>( | |||||
| 0, | |||||
| 0, | |||||
| [], | |||||
| ); | |||||
| const [loadCompleted, setLoadCompleted] = useState(false); | |||||
| const [loading, setLoading] = useState(false); // 避免误触发加载更多 | |||||
| useEffect(() => { | useEffect(() => { | ||||
| getComparisonData(); | getComparisonData(); | ||||
| }, [experimentId]); | }, [experimentId]); | ||||
| // 获取对比数据列表 | // 获取对比数据列表 | ||||
| const getComparisonData = async () => { | |||||
| // setLoading(true); | |||||
| const getComparisonData = async (offset: string = '') => { | |||||
| const request = | const request = | ||||
| comparisonType === ComparisonType.Train ? getExpTrainInfosReq : getExpEvaluateInfosReq; | comparisonType === ComparisonType.Train ? getExpTrainInfosReq : getExpEvaluateInfosReq; | ||||
| const [res] = await to(request(experimentId)); | |||||
| // setLoading(false); | |||||
| const [res] = await to(request(experimentId, { offset: offset, limit: pageSize })); | |||||
| if (res && res.data) { | if (res && res.data) { | ||||
| // const { content = [], totalElements = 0 } = res.data; | |||||
| setTableData(res.data); | |||||
| // setTotal(totalElements); | |||||
| setTableData((prev) => [...prev, ...res.data]); | |||||
| if (res.data.length === 0) { | |||||
| setLoadCompleted(true); | |||||
| const ele = document.getElementsByClassName('ant-table-body')[0]; | |||||
| if (ele) { | |||||
| const div = document.createElement('div'); | |||||
| div.className = styles['experiment-comparison__table__footer']; | |||||
| div.innerHTML = '<div></div><p>我是有底线的</p><div></div>'; | |||||
| ele.appendChild(div); | |||||
| } | |||||
| } | |||||
| } | } | ||||
| setLoading(false); | |||||
| }; | }; | ||||
| // 获取对比 url | // 获取对比 url | ||||
| @@ -80,17 +103,10 @@ function ExperimentComparison() { | |||||
| getExpMetrics(); | getExpMetrics(); | ||||
| }; | }; | ||||
| // 分页切换 | |||||
| // const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => { | |||||
| // if (action === 'paginate') { | |||||
| // setPagination(pagination); | |||||
| // } | |||||
| // // console.log(pagination, filters, sorter, action); | |||||
| // }; | |||||
| // 选择行 | // 选择行 | ||||
| const rowSelection: TableProps['rowSelection'] = { | |||||
| const rowSelection: TableProps<TableData>['rowSelection'] = { | |||||
| type: 'checkbox', | type: 'checkbox', | ||||
| columnWidth: 48, | |||||
| fixed: 'left', | fixed: 'left', | ||||
| selectedRowKeys, | selectedRowKeys, | ||||
| onChange: (selectedRowKeys: React.Key[]) => { | onChange: (selectedRowKeys: React.Key[]) => { | ||||
| @@ -98,11 +114,27 @@ function ExperimentComparison() { | |||||
| }, | }, | ||||
| }; | }; | ||||
| const columns: TableProps['columns'] = useMemo(() => { | |||||
| const handleTableScroll = (e: React.UIEvent<HTMLDivElement, UIEvent>) => { | |||||
| const target = e.target as HTMLDivElement; | |||||
| const { scrollTop, scrollHeight, clientHeight } = target; | |||||
| // 实现自动加载更多 | |||||
| if (!loadCompleted && !loading && scrollHeight - scrollTop - clientHeight <= 0) { | |||||
| const last = tableData[tableData.length - 1]; | |||||
| setLoading(true); | |||||
| getComparisonData(last?.run_id); | |||||
| } | |||||
| }; | |||||
| const columns: TableProps<TableData>['columns'] = useMemo(() => { | |||||
| const first: TableData | undefined = tableData[0]; | const first: TableData | undefined = tableData[0]; | ||||
| const metricsNames = first?.metrics_names ?? []; | |||||
| const paramsNames = first?.params_names ?? []; | |||||
| return [ | return [ | ||||
| { | { | ||||
| title: '基本信息', | title: '基本信息', | ||||
| align: 'center', | |||||
| children: [ | children: [ | ||||
| { | { | ||||
| title: '实例 ID', | title: '实例 ID', | ||||
| @@ -120,7 +152,7 @@ function ExperimentComparison() { | |||||
| width: 180, | width: 180, | ||||
| fixed: 'left', | fixed: 'left', | ||||
| align: 'center', | align: 'center', | ||||
| render: tableCellRender(false, dateFormatter), | |||||
| render: tableCellRender(false, TableCellValueType.Date), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '运行状态', | title: '运行状态', | ||||
| @@ -128,7 +160,7 @@ function ExperimentComparison() { | |||||
| key: 'status', | key: 'status', | ||||
| width: 100, | width: 100, | ||||
| fixed: 'left', | fixed: 'left', | ||||
| align: 'center', | |||||
| // align: 'center', | |||||
| render: ExperimentStatusCell, | render: ExperimentStatusCell, | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -138,7 +170,7 @@ function ExperimentComparison() { | |||||
| width: 180, | width: 180, | ||||
| fixed: 'left', | fixed: 'left', | ||||
| align: 'center', | align: 'center', | ||||
| render: tableCellRender(true, arrayFormatter()), | |||||
| render: tableCellRender(true, TableCellValueType.Array), | |||||
| ellipsis: { showTitle: false }, | ellipsis: { showTitle: false }, | ||||
| }, | }, | ||||
| ], | ], | ||||
| @@ -146,7 +178,7 @@ function ExperimentComparison() { | |||||
| { | { | ||||
| title: `${config.title}参数`, | title: `${config.title}参数`, | ||||
| align: 'center', | align: 'center', | ||||
| children: first?.params_names.map((name) => ({ | |||||
| children: paramsNames.map((name) => ({ | |||||
| title: ( | title: ( | ||||
| <Tooltip title={name}> | <Tooltip title={name}> | ||||
| <span>{name}</span> | <span>{name}</span> | ||||
| @@ -158,14 +190,14 @@ function ExperimentComparison() { | |||||
| align: 'center', | align: 'center', | ||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| ellipsis: { showTitle: false }, | ellipsis: { showTitle: false }, | ||||
| sorter: (a, b) => a.params[name] - b.params[name], | |||||
| sorter: (a, b) => tableSorter(a.params[name], b.params[name]), | |||||
| showSorterTooltip: false, | showSorterTooltip: false, | ||||
| })), | })), | ||||
| }, | }, | ||||
| { | { | ||||
| title: `${config.title}指标`, | title: `${config.title}指标`, | ||||
| align: 'center', | align: 'center', | ||||
| children: first?.metrics_names.map((name) => ({ | |||||
| children: metricsNames.map((name) => ({ | |||||
| title: ( | title: ( | ||||
| <Tooltip title={name}> | <Tooltip title={name}> | ||||
| <span>{name}</span> | <span>{name}</span> | ||||
| @@ -177,7 +209,7 @@ function ExperimentComparison() { | |||||
| align: 'center', | align: 'center', | ||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| ellipsis: { showTitle: false }, | ellipsis: { showTitle: false }, | ||||
| sorter: (a, b) => a.metrics[name] - b.metrics[name], | |||||
| sorter: (a, b) => tableSorter(a.metrics[name], b.metrics[name]), | |||||
| showSorterTooltip: false, | showSorterTooltip: false, | ||||
| })), | })), | ||||
| }, | }, | ||||
| @@ -191,27 +223,15 @@ function ExperimentComparison() { | |||||
| 可视化对比 | 可视化对比 | ||||
| </Button> | </Button> | ||||
| </div> | </div> | ||||
| <div | |||||
| className={classNames( | |||||
| 'vertical-scroll-table-no-page', | |||||
| styles['experiment-comparison__table'], | |||||
| )} | |||||
| > | |||||
| <div className={styles['experiment-comparison__table']} ref={tableRef}> | |||||
| <Table | <Table | ||||
| dataSource={tableData} | dataSource={tableData} | ||||
| columns={columns} | columns={columns} | ||||
| rowSelection={rowSelection} | rowSelection={rowSelection} | ||||
| scroll={{ y: 'calc(100% - 55px)', x: '100%' }} | |||||
| scroll={{ y: tableHeight - 150, x: tableWidth - 60 }} | |||||
| pagination={false} | pagination={false} | ||||
| bordered={true} | bordered={true} | ||||
| // loading={loading} | |||||
| // pagination={{ | |||||
| // ...pagination, | |||||
| // total: total, | |||||
| // showSizeChanger: true, | |||||
| // showQuickJumper: true, | |||||
| // }} | |||||
| // onChange={handleTableChange} | |||||
| onScroll={handleTableScroll} | |||||
| rowKey="run_id" | rowKey="run_id" | ||||
| /> | /> | ||||
| </div> | </div> | ||||
| @@ -3,7 +3,7 @@ import { useStateRef, useVisible } from '@/hooks'; | |||||
| import { getExperimentIns } from '@/services/experiment/index.js'; | import { getExperimentIns } from '@/services/experiment/index.js'; | ||||
| import { getWorkflowById } from '@/services/pipeline/index.js'; | import { getWorkflowById } from '@/services/pipeline/index.js'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { fittingString } from '@/utils'; | |||||
| import { fittingString, parseJsonText } from '@/utils'; | |||||
| import { elapsedTime, formatDate } from '@/utils/date'; | import { elapsedTime, formatDate } from '@/utils/date'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import G6, { Util } from '@antv/g6'; | import G6, { Util } from '@antv/g6'; | ||||
| @@ -88,7 +88,7 @@ function ExperimentText() { | |||||
| setExperimentIns(res.data); | setExperimentIns(res.data); | ||||
| const { status, nodes_status, argo_ins_ns, argo_ins_name } = res.data; | const { status, nodes_status, argo_ins_ns, argo_ins_name } = res.data; | ||||
| const workflowData = workflowRef.current; | const workflowData = workflowRef.current; | ||||
| const experimentStatusObjs = JSON.parse(nodes_status); | |||||
| const experimentStatusObjs = parseJsonText(nodes_status); | |||||
| workflowData.nodes.forEach((item) => { | workflowData.nodes.forEach((item) => { | ||||
| const experimentNode = experimentStatusObjs?.[item.id]; | const experimentNode = experimentStatusObjs?.[item.id]; | ||||
| updateWorkflowNode(item, experimentNode); | updateWorkflowNode(item, experimentNode); | ||||
| @@ -2,7 +2,8 @@ import createExperimentIcon from '@/assets/img/create-experiment.png'; | |||||
| import editExperimentIcon from '@/assets/img/edit-experiment.png'; | import editExperimentIcon from '@/assets/img/edit-experiment.png'; | ||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { type PipelineGlobalParam } from '@/types'; | import { type PipelineGlobalParam } from '@/types'; | ||||
| import { Form, Input, Radio, Select, type FormRule } from 'antd'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { Button, Form, Input, Radio, Select, type FormRule } from 'antd'; | |||||
| import { useState } from 'react'; | import { useState } from 'react'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -17,7 +18,7 @@ type AddExperimentModalProps = { | |||||
| isAdd: boolean; | isAdd: boolean; | ||||
| open: boolean; | open: boolean; | ||||
| onCancel: () => void; | onCancel: () => void; | ||||
| onFinish: () => void; | |||||
| onFinish: (values: any, isRun: boolean) => void; | |||||
| workflowList: Workflow[]; | workflowList: Workflow[]; | ||||
| initialValues: FormData; | initialValues: FormData; | ||||
| }; | }; | ||||
| @@ -113,25 +114,45 @@ function AddExperimentModal({ | |||||
| form.setFieldValue('global_param', []); | form.setFieldValue('global_param', []); | ||||
| } | } | ||||
| }; | }; | ||||
| const handleRun = async (run: boolean) => { | |||||
| const [values, error] = await to(form.validateFields()); | |||||
| if (!error && values) { | |||||
| onFinish(values, run); | |||||
| } | |||||
| }; | |||||
| const footer = [ | |||||
| <Button key="cancel" onClick={onCancel}> | |||||
| 取消 | |||||
| </Button>, | |||||
| <Button key="submit" type={isAdd ? 'primary' : 'default'} onClick={() => handleRun(false)}> | |||||
| 确定 | |||||
| </Button>, | |||||
| ]; | |||||
| if (!isAdd) { | |||||
| footer.push( | |||||
| <Button key="run" type="primary" onClick={() => handleRun(true)}> | |||||
| 确定并运行 | |||||
| </Button>, | |||||
| ); | |||||
| } | |||||
| return ( | return ( | ||||
| <KFModal | <KFModal | ||||
| className={styles['add-experiment-modal']} | className={styles['add-experiment-modal']} | ||||
| title={modalTitle} | title={modalTitle} | ||||
| image={modalIcon} | image={modalIcon} | ||||
| open={open} | open={open} | ||||
| okButtonProps={{ | |||||
| htmlType: 'submit', | |||||
| form: 'form', | |||||
| }} | |||||
| onCancel={onCancel} | onCancel={onCancel} | ||||
| destroyOnClose={true} | destroyOnClose={true} | ||||
| width={825} | width={825} | ||||
| footer={footer} | |||||
| > | > | ||||
| <Form | <Form | ||||
| name="form" | name="form" | ||||
| layout="horizontal" | layout="horizontal" | ||||
| initialValues={initialValues} | initialValues={initialValues} | ||||
| onFinish={onFinish} | |||||
| autoComplete="off" | autoComplete="off" | ||||
| form={form} | form={form} | ||||
| {...layout} | {...layout} | ||||
| @@ -4,14 +4,18 @@ | |||||
| width: 100%; | width: 100%; | ||||
| padding: 0 0 0 33px; | padding: 0 0 0 33px; | ||||
| color: @text-color; | color: @text-color; | ||||
| font-size: 15px; | |||||
| font-size: 14px; | |||||
| & > div { | & > div { | ||||
| padding: 0 16px; | padding: 0 16px; | ||||
| } | } | ||||
| .check { | |||||
| width: calc((100% + 32px + 33px) / 6.25 / 2); | |||||
| } | |||||
| .index { | .index { | ||||
| width: calc((100% + 32px + 33px) / 6.25); | |||||
| width: calc((100% + 32px + 33px) / 6.25 / 2); | |||||
| } | } | ||||
| .tensorBoard { | .tensorBoard { | ||||
| @@ -33,6 +37,7 @@ | |||||
| } | } | ||||
| .operation { | .operation { | ||||
| position: relative; | |||||
| width: 344px; | width: 344px; | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,7 +1,9 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { ExperimentStatus } from '@/enums'; | import { ExperimentStatus } from '@/enums'; | ||||
| import { useCheck } from '@/hooks'; | |||||
| import { experimentStatusInfo } from '@/pages/Experiment/status'; | import { experimentStatusInfo } from '@/pages/Experiment/status'; | ||||
| import { | import { | ||||
| deleteManyExperimentIns, | |||||
| deleteQueryByExperimentInsId, | deleteQueryByExperimentInsId, | ||||
| putQueryByExperimentInsId, | putQueryByExperimentInsId, | ||||
| } from '@/services/experiment/index.js'; | } from '@/services/experiment/index.js'; | ||||
| @@ -11,8 +13,9 @@ import { elapsedTime, formatDate } from '@/utils/date'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { DoubleRightOutlined } from '@ant-design/icons'; | import { DoubleRightOutlined } from '@ant-design/icons'; | ||||
| import { App, Button, ConfigProvider, Tooltip } from 'antd'; | |||||
| import { App, Button, Checkbox, ConfigProvider, Tooltip } from 'antd'; | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useEffect, useMemo } from 'react'; | |||||
| import TensorBoardStatusCell from '../TensorBoardStatus'; | import TensorBoardStatusCell from '../TensorBoardStatus'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -36,6 +39,25 @@ function ExperimentInstanceComponent({ | |||||
| onLoadMore, | onLoadMore, | ||||
| }: ExperimentInstanceProps) { | }: ExperimentInstanceProps) { | ||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| const allIntanceIds = useMemo(() => { | |||||
| return experimentInList?.map((item) => item.id) || []; | |||||
| }, [experimentInList]); | |||||
| const [ | |||||
| selectedIns, | |||||
| setSelectedIns, | |||||
| checked, | |||||
| indeterminate, | |||||
| checkAll, | |||||
| isSingleChecked, | |||||
| checkSingle, | |||||
| ] = useCheck(allIntanceIds); | |||||
| useEffect(() => { | |||||
| // 关闭时清空 | |||||
| if (allIntanceIds.length === 0) { | |||||
| setSelectedIns([]); | |||||
| } | |||||
| }, [experimentInList]); | |||||
| // 删除实验实例确认 | // 删除实验实例确认 | ||||
| const handleRemove = (instance: ExperimentInstance) => { | const handleRemove = (instance: ExperimentInstance) => { | ||||
| @@ -56,6 +78,26 @@ function ExperimentInstanceComponent({ | |||||
| } | } | ||||
| }; | }; | ||||
| // 批量删除实验实例确认 | |||||
| const handleDeleteAll = () => { | |||||
| modalConfirm({ | |||||
| title: '确定批量删除选中的实例吗?', | |||||
| onOk: () => { | |||||
| batchDeleteExperimentInstances(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 批量删除实验实例 | |||||
| const batchDeleteExperimentInstances = async () => { | |||||
| const [res] = await to(deleteManyExperimentIns(selectedIns)); | |||||
| if (res) { | |||||
| message.success('删除成功'); | |||||
| setSelectedIns([]); | |||||
| onRemove?.(); | |||||
| } | |||||
| }; | |||||
| // 终止实验实例 | // 终止实验实例 | ||||
| const terminateExperimentInstance = async (instance: ExperimentInstance) => { | const terminateExperimentInstance = async (instance: ExperimentInstance) => { | ||||
| const [res] = await to(putQueryByExperimentInsId(instance.id)); | const [res] = await to(putQueryByExperimentInsId(instance.id)); | ||||
| @@ -72,6 +114,9 @@ function ExperimentInstanceComponent({ | |||||
| return ( | return ( | ||||
| <div> | <div> | ||||
| <div className={styles.tableExpandBox} style={{ paddingBottom: '16px' }}> | <div className={styles.tableExpandBox} style={{ paddingBottom: '16px' }}> | ||||
| <div className={styles.check}> | |||||
| <Checkbox checked={checked} indeterminate={indeterminate} onChange={checkAll}></Checkbox> | |||||
| </div> | |||||
| <div className={styles.index}>序号</div> | <div className={styles.index}>序号</div> | ||||
| <div className={styles.tensorBoard}>可视化</div> | <div className={styles.tensorBoard}>可视化</div> | ||||
| <div className={styles.description}> | <div className={styles.description}> | ||||
| @@ -79,7 +124,21 @@ function ExperimentInstanceComponent({ | |||||
| <div style={{ width: '50%' }}>开始时间</div> | <div style={{ width: '50%' }}>开始时间</div> | ||||
| </div> | </div> | ||||
| <div className={styles.status}>状态</div> | <div className={styles.status}>状态</div> | ||||
| <div className={styles.operation}>操作</div> | |||||
| <div className={styles.operation}> | |||||
| <span>操作</span> | |||||
| {selectedIns.length > 0 && ( | |||||
| <Button | |||||
| style={{ position: 'absolute', right: '0' }} | |||||
| color="primary" | |||||
| variant="filled" | |||||
| size="small" | |||||
| onClick={handleDeleteAll} | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| )} | |||||
| </div> | |||||
| </div> | </div> | ||||
| {experimentInList.map((item, index) => ( | {experimentInList.map((item, index) => ( | ||||
| @@ -87,6 +146,12 @@ function ExperimentInstanceComponent({ | |||||
| key={item.id} | key={item.id} | ||||
| className={classNames(styles.tableExpandBox, styles.tableExpandBoxContent)} | className={classNames(styles.tableExpandBox, styles.tableExpandBoxContent)} | ||||
| > | > | ||||
| <div className={styles.check}> | |||||
| <Checkbox | |||||
| checked={isSingleChecked(item.id)} | |||||
| onChange={() => checkSingle(item.id)} | |||||
| ></Checkbox> | |||||
| </div> | |||||
| <a | <a | ||||
| className={styles.index} | className={styles.index} | ||||
| style={{ padding: '0 16px' }} | style={{ padding: '0 16px' }} | ||||
| @@ -52,7 +52,7 @@ function LogGroup({ | |||||
| const [_isMouseDown, setIsMouseDown, isMouseDownRef] = useStateRef(false); | const [_isMouseDown, setIsMouseDown, isMouseDownRef] = useStateRef(false); | ||||
| const preStatusRef = useRef<ExperimentStatus | undefined>(undefined); | const preStatusRef = useRef<ExperimentStatus | undefined>(undefined); | ||||
| const socketRef = useRef<WebSocket | undefined>(undefined); | const socketRef = useRef<WebSocket | undefined>(undefined); | ||||
| const retryRef = useRef(2); | |||||
| const retryRef = useRef(2); // 等待 2 秒,重试 2 次 | |||||
| useEffect(() => { | useEffect(() => { | ||||
| scrollToBottom(false); | scrollToBottom(false); | ||||
| @@ -142,11 +142,12 @@ function LogGroup({ | |||||
| ); | ); | ||||
| socket.addEventListener('open', () => { | socket.addEventListener('open', () => { | ||||
| // console.log('WebSocket is open now.'); | |||||
| console.log('WebSocket is open now.'); | |||||
| }); | }); | ||||
| socket.addEventListener('close', (event) => { | socket.addEventListener('close', (event) => { | ||||
| // console.log('WebSocket is closed:', event); | |||||
| console.log('WebSocket is closed:', event); | |||||
| // 有时候会出现连接失败,重试 2 次 | |||||
| if (event.code !== 1000 && retryRef.current > 0) { | if (event.code !== 1000 && retryRef.current > 0) { | ||||
| retryRef.current -= 1; | retryRef.current -= 1; | ||||
| setTimeout(() => { | setTimeout(() => { | ||||
| @@ -160,6 +161,7 @@ function LogGroup({ | |||||
| }); | }); | ||||
| socket.addEventListener('message', (event) => { | socket.addEventListener('message', (event) => { | ||||
| console.log('message received.', event); | |||||
| if (!event.data) { | if (!event.data) { | ||||
| return; | return; | ||||
| } | } | ||||
| @@ -32,8 +32,9 @@ function LogList({ | |||||
| }: LogListProps) { | }: LogListProps) { | ||||
| const [logList, setLogList] = useState<ExperimentLog[]>([]); | const [logList, setLogList] = useState<ExperimentLog[]>([]); | ||||
| const preStatusRef = useRef<ExperimentStatus | undefined>(undefined); | const preStatusRef = useRef<ExperimentStatus | undefined>(undefined); | ||||
| const retryRef = useRef(3); | |||||
| const retryRef = useRef(3); // 等待 2 秒,重试 3 次 | |||||
| // 当实例节点运行状态不是 Pending,而上一个运行状态不存在或者是 Pending 时,获取实验日志 | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if ( | if ( | ||||
| instanceNodeStatus && | instanceNodeStatus && | ||||
| @@ -5,7 +5,7 @@ | |||||
| &__label { | &__label { | ||||
| color: rgba(29, 29, 32, 0.75); | color: rgba(29, 29, 32, 0.75); | ||||
| font-size: 15px; | |||||
| font-size: 14px; | |||||
| &--running { | &--running { | ||||
| color: @success-color; | color: @success-color; | ||||
| @@ -1,4 +1,3 @@ | |||||
| import CommonTableCell from '@/components/CommonTableCell'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { ExperimentStatus, TensorBoardStatus } from '@/enums'; | import { ExperimentStatus, TensorBoardStatus } from '@/enums'; | ||||
| import { | import { | ||||
| @@ -15,6 +14,7 @@ import { | |||||
| import { getWorkflow } from '@/services/pipeline/index.js'; | import { getWorkflow } from '@/services/pipeline/index.js'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { App, Button, ConfigProvider, Dropdown, Space, Table } from 'antd'; | import { App, Button, ConfigProvider, Dropdown, Space, Table } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| @@ -60,7 +60,7 @@ function Experiment() { | |||||
| }; | }; | ||||
| useEffect(() => { | useEffect(() => { | ||||
| getList(); | |||||
| getExperimentList(); | |||||
| getWorkflowList(); | getWorkflowList(); | ||||
| return () => { | return () => { | ||||
| clearExperimentInTimers(); | clearExperimentInTimers(); | ||||
| @@ -68,7 +68,7 @@ function Experiment() { | |||||
| }, []); | }, []); | ||||
| // 获取实验列表 | // 获取实验列表 | ||||
| const getList = async () => { | |||||
| const getExperimentList = async () => { | |||||
| const params = { | const params = { | ||||
| offset: 0, | offset: 0, | ||||
| page: pageOption.current.page - 1, | page: pageOption.current.page - 1, | ||||
| @@ -228,8 +228,8 @@ function Experiment() { | |||||
| setIsModalOpen(false); | setIsModalOpen(false); | ||||
| }; | }; | ||||
| // 创建或者编辑实验接口请求 | |||||
| const handleAddExperiment = async (values) => { | |||||
| // 创建或者编辑实验 | |||||
| const handleAddExperiment = async (values, isRun) => { | |||||
| const global_param = JSON.stringify(values.global_param); | const global_param = JSON.stringify(values.global_param); | ||||
| if (!experimentId) { | if (!experimentId) { | ||||
| const params = { | const params = { | ||||
| @@ -240,7 +240,7 @@ function Experiment() { | |||||
| if (res) { | if (res) { | ||||
| message.success('新建实验成功'); | message.success('新建实验成功'); | ||||
| setIsModalOpen(false); | setIsModalOpen(false); | ||||
| getList(); | |||||
| getExperimentList(); | |||||
| } | } | ||||
| } else { | } else { | ||||
| const params = { ...values, global_param, id: experimentId }; | const params = { ...values, global_param, id: experimentId }; | ||||
| @@ -248,7 +248,12 @@ function Experiment() { | |||||
| if (res) { | if (res) { | ||||
| message.success('编辑实验成功'); | message.success('编辑实验成功'); | ||||
| setIsModalOpen(false); | setIsModalOpen(false); | ||||
| getList(); | |||||
| getExperimentList(); | |||||
| // 确定并运行 | |||||
| if (isRun) { | |||||
| runExperiment(experimentId); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -259,7 +264,7 @@ function Experiment() { | |||||
| page: current, | page: current, | ||||
| size: size, | size: size, | ||||
| }; | }; | ||||
| getList(); | |||||
| getExperimentList(); | |||||
| }; | }; | ||||
| // 运行实验 | // 运行实验 | ||||
| const runExperiment = async (id) => { | const runExperiment = async (id) => { | ||||
| @@ -273,8 +278,7 @@ function Experiment() { | |||||
| }; | }; | ||||
| // 跳转到流水线 | // 跳转到流水线 | ||||
| const gotoPipeline = (e, record) => { | |||||
| e.stopPropagation(); | |||||
| const gotoPipeline = (record) => { | |||||
| navigate({ pathname: `/pipeline/template/info/${record.workflow_id}` }); | navigate({ pathname: `/pipeline/template/info/${record.workflow_id}` }); | ||||
| }; | }; | ||||
| @@ -298,8 +302,16 @@ function Experiment() { | |||||
| } | } | ||||
| }; | }; | ||||
| // 刷新实验列表状态, | |||||
| // 目前是直接刷新实验列表,后续需要优化,只刷新状态 | |||||
| const refreshExperimentList = () => { | |||||
| getExperimentList(); | |||||
| }; | |||||
| // 实验实例终止 | // 实验实例终止 | ||||
| const handleInstanceTerminate = async (experimentIn) => { | const handleInstanceTerminate = async (experimentIn) => { | ||||
| // 刷新实验列表 | |||||
| refreshExperimentList(); | |||||
| setExperimentInList((prevList) => { | setExperimentInList((prevList) => { | ||||
| return prevList.map((item) => { | return prevList.map((item) => { | ||||
| if (item.id === experimentIn.id) { | if (item.id === experimentIn.id) { | ||||
| @@ -348,25 +360,23 @@ function Experiment() { | |||||
| title: '实验名称', | title: '实验名称', | ||||
| dataIndex: 'name', | dataIndex: 'name', | ||||
| key: 'name', | key: 'name', | ||||
| render: (text) => <div>{text}</div>, | |||||
| render: tableCellRender(), | |||||
| width: '16%', | width: '16%', | ||||
| }, | }, | ||||
| { | { | ||||
| title: '关联流水线名称', | title: '关联流水线名称', | ||||
| dataIndex: 'workflow_name', | dataIndex: 'workflow_name', | ||||
| key: 'workflow_name', | key: 'workflow_name', | ||||
| render: (text, record) => ( | |||||
| <a className="kf-table-row-link" onClick={(e) => gotoPipeline(e, record)}> | |||||
| {text} | |||||
| </a> | |||||
| ), | |||||
| render: tableCellRender(false, TableCellValueType.Link, { | |||||
| onClick: gotoPipeline, | |||||
| }), | |||||
| width: '16%', | width: '16%', | ||||
| }, | }, | ||||
| { | { | ||||
| title: '实验描述', | title: '实验描述', | ||||
| dataIndex: 'description', | dataIndex: 'description', | ||||
| key: 'description', | key: 'description', | ||||
| render: CommonTableCell(true), | |||||
| render: tableCellRender(true), | |||||
| ellipsis: { showTitle: false }, | ellipsis: { showTitle: false }, | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -395,7 +405,6 @@ function Experiment() { | |||||
| ); | ); | ||||
| }, | }, | ||||
| }, | }, | ||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| key: 'action', | key: 'action', | ||||
| @@ -452,7 +461,7 @@ function Experiment() { | |||||
| deleteExperimentById(record.id).then((ret) => { | deleteExperimentById(record.id).then((ret) => { | ||||
| if (ret.code === 200) { | if (ret.code === 200) { | ||||
| message.success('删除成功'); | message.success('删除成功'); | ||||
| getList(); | |||||
| getExperimentList(); | |||||
| } else { | } else { | ||||
| message.error(ret.msg); | message.error(ret.msg); | ||||
| } | } | ||||
| @@ -489,7 +498,10 @@ function Experiment() { | |||||
| experimentInsTotal={experimentInsTotal} | experimentInsTotal={experimentInsTotal} | ||||
| onClickInstance={(item) => gotoInstanceInfo(item, record)} | onClickInstance={(item) => gotoInstanceInfo(item, record)} | ||||
| onClickTensorBoard={handleTensorboard} | onClickTensorBoard={handleTensorboard} | ||||
| onRemove={() => refreshExperimentIns(record.id)} | |||||
| onRemove={() => { | |||||
| refreshExperimentIns(record.id); | |||||
| refreshExperimentList(); | |||||
| }} | |||||
| onTerminate={handleInstanceTerminate} | onTerminate={handleInstanceTerminate} | ||||
| onLoadMore={() => loadMoreExperimentIns()} | onLoadMore={() => loadMoreExperimentIns()} | ||||
| ></ExperimentInstance> | ></ExperimentInstance> | ||||
| @@ -0,0 +1,7 @@ | |||||
| import IframePage, { IframePageType } from '@/components/IFramePage'; | |||||
| function GitLink() { | |||||
| return <IframePage type={IframePageType.GitLink}></IframePage>; | |||||
| } | |||||
| export default GitLink; | |||||
| @@ -3,8 +3,6 @@ | |||||
| * @Date: 2024-04-16 13:58:08 | * @Date: 2024-04-16 13:58:08 | ||||
| * @Description: 镜像详情 | * @Description: 镜像详情 | ||||
| */ | */ | ||||
| import CommonTableCell from '@/components/CommonTableCell'; | |||||
| import DateTableCell from '@/components/DateTableCell'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import PageTitle from '@/components/PageTitle'; | import PageTitle from '@/components/PageTitle'; | ||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| @@ -20,6 +18,7 @@ import themes from '@/styles/theme.less'; | |||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import SessionStorage from '@/utils/sessionStorage'; | import SessionStorage from '@/utils/sessionStorage'; | ||||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { useNavigate, useParams } from '@umijs/max'; | import { useNavigate, useParams } from '@umijs/max'; | ||||
| import { | import { | ||||
| @@ -125,7 +124,12 @@ function MirrorInfo() { | |||||
| }; | }; | ||||
| // 分页切换 | // 分页切换 | ||||
| const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => { | |||||
| const handleTableChange: TableProps<MirrorVersionData>['onChange'] = ( | |||||
| pagination, | |||||
| _filters, | |||||
| _sorter, | |||||
| { action }, | |||||
| ) => { | |||||
| if (action === 'paginate') { | if (action === 'paginate') { | ||||
| setPagination(pagination); | setPagination(pagination); | ||||
| } | } | ||||
| @@ -156,13 +160,13 @@ function MirrorInfo() { | |||||
| dataIndex: 'tag_name', | dataIndex: 'tag_name', | ||||
| key: 'tag_name', | key: 'tag_name', | ||||
| width: '25%', | width: '25%', | ||||
| render: CommonTableCell(), | |||||
| render: tableCellRender(), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '镜像地址', | title: '镜像地址', | ||||
| dataIndex: 'url', | dataIndex: 'url', | ||||
| key: 'url', | key: 'url', | ||||
| render: CommonTableCell(), | |||||
| render: tableCellRender(), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '状态', | title: '状态', | ||||
| @@ -176,14 +180,14 @@ function MirrorInfo() { | |||||
| dataIndex: 'file_size', | dataIndex: 'file_size', | ||||
| key: 'file_size', | key: 'file_size', | ||||
| width: 150, | width: 150, | ||||
| render: CommonTableCell(), | |||||
| render: tableCellRender(), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '创建时间', | title: '创建时间', | ||||
| dataIndex: 'create_time', | dataIndex: 'create_time', | ||||
| key: 'create_time', | key: 'create_time', | ||||
| width: 200, | width: 200, | ||||
| render: DateTableCell, | |||||
| render: tableCellRender(false, TableCellValueType.Date), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| @@ -3,8 +3,6 @@ | |||||
| * @Date: 2024-04-16 13:58:08 | * @Date: 2024-04-16 13:58:08 | ||||
| * @Description: 镜像列表 | * @Description: 镜像列表 | ||||
| */ | */ | ||||
| import CommonTableCell from '@/components/CommonTableCell'; | |||||
| import DateTableCell from '@/components/DateTableCell'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { CommonTabKeys } from '@/enums'; | import { CommonTabKeys } from '@/enums'; | ||||
| import { useCacheState } from '@/hooks/pageCacheState'; | import { useCacheState } from '@/hooks/pageCacheState'; | ||||
| @@ -12,6 +10,7 @@ import { deleteMirrorReq, getMirrorListReq } from '@/services/mirror'; | |||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import SessionStorage from '@/utils/sessionStorage'; | import SessionStorage from '@/utils/sessionStorage'; | ||||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { useNavigate } from '@umijs/max'; | import { useNavigate } from '@umijs/max'; | ||||
| import { | import { | ||||
| @@ -156,7 +155,12 @@ function MirrorList() { | |||||
| }; | }; | ||||
| // 分页切换 | // 分页切换 | ||||
| const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => { | |||||
| const handleTableChange: TableProps<MirrorData>['onChange'] = ( | |||||
| pagination, | |||||
| _filters, | |||||
| _sorter, | |||||
| { action }, | |||||
| ) => { | |||||
| if (action === 'paginate') { | if (action === 'paginate') { | ||||
| setPagination(pagination); | setPagination(pagination); | ||||
| } | } | ||||
| @@ -169,21 +173,21 @@ function MirrorList() { | |||||
| dataIndex: 'name', | dataIndex: 'name', | ||||
| key: 'name', | key: 'name', | ||||
| width: '30%', | width: '30%', | ||||
| render: CommonTableCell(), | |||||
| render: tableCellRender(), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '版本数据', | title: '版本数据', | ||||
| dataIndex: 'version_count', | dataIndex: 'version_count', | ||||
| key: 'version_count', | key: 'version_count', | ||||
| width: '15%', | width: '15%', | ||||
| render: CommonTableCell(), | |||||
| render: tableCellRender(), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '镜像描述', | title: '镜像描述', | ||||
| dataIndex: 'description', | dataIndex: 'description', | ||||
| key: 'description', | key: 'description', | ||||
| width: '35%', | width: '35%', | ||||
| render: CommonTableCell(true), | |||||
| render: tableCellRender(true), | |||||
| ellipsis: { showTitle: false }, | ellipsis: { showTitle: false }, | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -191,7 +195,7 @@ function MirrorList() { | |||||
| dataIndex: 'create_time', | dataIndex: 'create_time', | ||||
| key: 'create_time', | key: 'create_time', | ||||
| width: '20%', | width: '20%', | ||||
| render: DateTableCell, | |||||
| render: tableCellRender(false, TableCellValueType.Date), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| @@ -0,0 +1,29 @@ | |||||
| .metrics-chart { | |||||
| width: calc((100% - 30px) / 3); | |||||
| background-color: white; | |||||
| &__title { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| height: 36px; | |||||
| padding-left: 15px; | |||||
| color: @text-color; | |||||
| font-size: 14px; | |||||
| background-color: #ebf2ff; | |||||
| img { | |||||
| width: 13px; | |||||
| height: 13px; | |||||
| margin-right: 12px; | |||||
| } | |||||
| } | |||||
| &__chart { | |||||
| width: 100%; | |||||
| height: 280px; | |||||
| background: linear-gradient(180deg, #ffffff 0%, #fdfeff 100%); | |||||
| border: 1px solid white; | |||||
| border-radius: 0 0 10px 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,174 @@ | |||||
| import * as echarts from 'echarts'; | |||||
| import { useEffect, useRef } from 'react'; | |||||
| import styles from './index.less'; | |||||
| import './tooltip.css'; | |||||
| const colors = [ | |||||
| '#0D5EF8', | |||||
| '#6AC21D', | |||||
| '#F98E1B', | |||||
| '#ECB934', | |||||
| '#8A34EC', | |||||
| '#FF1493', | |||||
| '#FFFF00', | |||||
| '#DAA520', | |||||
| '#CD853F', | |||||
| '#FF6347', | |||||
| '#808080', | |||||
| '#00BFFF', | |||||
| '#008000', | |||||
| '#00FFFF', | |||||
| '#FFFACD', | |||||
| '#FFA500', | |||||
| '#FF4500', | |||||
| '#800080', | |||||
| '#FF1493', | |||||
| '#000080', | |||||
| ]; | |||||
| const backgroundColor = new echarts.graphic.LinearGradient( | |||||
| 0, | |||||
| 0, | |||||
| 0, | |||||
| 1, | |||||
| [ | |||||
| { offset: 0, color: '#ffffff' }, | |||||
| { offset: 1, color: '#fdfeff' }, | |||||
| ], | |||||
| false, | |||||
| ); | |||||
| function getTooltip(xTitle: string, xValue: number, yTitle: string, yValue: number) { | |||||
| const str = `<div class="metrics-tooltip"> | |||||
| <span class="y-text">Y:</span> | |||||
| <span class="x-text">X:</span> | |||||
| <div class="title">${yTitle}</div> | |||||
| <div class="value">${yValue}</div> | |||||
| <div class="title" style="margin-top: 10px">${xTitle}</div> | |||||
| <div class="value">${xValue}</div> | |||||
| <div>`; | |||||
| return str; | |||||
| } | |||||
| export type MetricsChatData = { | |||||
| name: string; | |||||
| values: number[]; | |||||
| version: string; | |||||
| iters: number[]; | |||||
| }; | |||||
| export type MetricsChartProps = { | |||||
| name: string; | |||||
| chartData: MetricsChatData[]; | |||||
| }; | |||||
| function MetricsChart({ name, chartData }: MetricsChartProps) { | |||||
| const chartRef = useRef<HTMLDivElement>(null); | |||||
| const xAxisData = chartData[0]?.iters; | |||||
| const seriesData = chartData.map((item) => { | |||||
| return { | |||||
| name: item.version, | |||||
| type: 'line' as const, | |||||
| smooth: true, | |||||
| data: item.values, | |||||
| }; | |||||
| }); | |||||
| const options: echarts.EChartsOption = { | |||||
| backgroundColor: backgroundColor, | |||||
| title: { | |||||
| show: false, | |||||
| }, | |||||
| tooltip: { | |||||
| trigger: 'item', | |||||
| padding: 10, | |||||
| formatter: (params: any) => { | |||||
| const { name: xTitle, data } = params; | |||||
| return getTooltip('step', xTitle, name, data); | |||||
| }, | |||||
| }, | |||||
| legend: { | |||||
| bottom: 10, | |||||
| icon: 'rect', | |||||
| itemWidth: 10, | |||||
| itemHeight: 10, | |||||
| itemGap: 20, | |||||
| textStyle: { | |||||
| color: 'rgba(29, 29, 32, 0.75)', | |||||
| fontSize: 12, | |||||
| }, | |||||
| }, | |||||
| color: colors, | |||||
| grid: { | |||||
| left: '15', | |||||
| right: '15', | |||||
| top: '20', | |||||
| bottom: '60', | |||||
| containLabel: true, | |||||
| }, | |||||
| xAxis: { | |||||
| type: 'category', | |||||
| boundaryGap: true, | |||||
| offset: 10, | |||||
| data: xAxisData, | |||||
| axisLabel: { | |||||
| color: 'rgba(29, 29, 32, 0.75)', | |||||
| fontSize: 12, | |||||
| }, | |||||
| axisTick: { | |||||
| show: false, | |||||
| }, | |||||
| axisLine: { | |||||
| lineStyle: { | |||||
| color: '#eaeaea', | |||||
| width: 1, | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| yAxis: { | |||||
| type: 'value', | |||||
| axisLabel: { | |||||
| color: 'rgba(29, 29, 32, 0.75)', | |||||
| fontSize: 12, | |||||
| margin: 15, | |||||
| }, | |||||
| axisLine: { | |||||
| show: false, | |||||
| }, | |||||
| splitLine: { | |||||
| lineStyle: { | |||||
| color: '#e4e4e4', | |||||
| width: 1, | |||||
| type: 'dashed', | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| series: seriesData, | |||||
| }; | |||||
| useEffect(() => { | |||||
| // 创建一个echarts实例,返回echarts实例 | |||||
| const chart = echarts.init(chartRef.current); | |||||
| // 设置图表实例的配置项和数据 | |||||
| chart.setOption(options); | |||||
| // 组件卸载 | |||||
| return () => { | |||||
| // myChart.dispose() 销毁实例 | |||||
| chart.dispose(); | |||||
| }; | |||||
| }, []); | |||||
| return ( | |||||
| <div className={styles['metrics-chart']}> | |||||
| <div className={styles['metrics-chart__title']}> | |||||
| <img src={require('@/assets/img/metrics-title-icon.png')}></img> | |||||
| <span>{name}</span> | |||||
| </div> | |||||
| <div className={styles['metrics-chart__chart']} ref={chartRef}></div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default MetricsChart; | |||||
| @@ -0,0 +1,33 @@ | |||||
| .metrics-tooltip { | |||||
| width: 172px; | |||||
| padding-left: 20px; | |||||
| background-color: white; | |||||
| font-size: 12px; | |||||
| } | |||||
| .metrics-tooltip .y-text { | |||||
| position: absolute; | |||||
| left: 10px; | |||||
| top: 10px; | |||||
| } | |||||
| .metrics-tooltip .x-text { | |||||
| position: absolute; | |||||
| left: 10px; | |||||
| top: 66px; | |||||
| } | |||||
| .metrics-tooltip .title { | |||||
| color: #575757; | |||||
| overflow: hidden; | |||||
| text-overflow: ellipsis; | |||||
| white-space: nowrap; | |||||
| margin-bottom: 3px; | |||||
| } | |||||
| .metrics-tooltip .value { | |||||
| color: #1d1d20; | |||||
| overflow: hidden; | |||||
| text-overflow: ellipsis; | |||||
| white-space: nowrap; | |||||
| } | |||||
| @@ -1,11 +1,14 @@ | |||||
| .model-evolution { | .model-evolution { | ||||
| width: 100%; | width: 100%; | ||||
| height: 100%; | height: 100%; | ||||
| padding: 0 30px 20px; | |||||
| overflow-x: hidden; | overflow-x: hidden; | ||||
| background-color: white; | |||||
| background: white; | |||||
| border-radius: 0 0 10px 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| &__graph { | &__graph { | ||||
| height: calc(100%); | |||||
| height: 100%; | |||||
| background-color: @background-color; | background-color: @background-color; | ||||
| background-image: url(@/assets/img/pipeline-canvas-bg.png); | background-image: url(@/assets/img/pipeline-canvas-bg.png); | ||||
| background-size: 100% 100%; | background-size: 100% 100%; | ||||
| @@ -0,0 +1,35 @@ | |||||
| .model-metrics { | |||||
| &__table { | |||||
| margin-top: 10px; | |||||
| padding: 20px 30px 0; | |||||
| background: white; | |||||
| border-radius: 10px; | |||||
| :global { | |||||
| .ant-table-container { | |||||
| border: none !important; | |||||
| } | |||||
| .ant-table-thead { | |||||
| .ant-table-cell { | |||||
| background-color: rgb(247, 247, 247); | |||||
| border-color: @border-color-base !important; | |||||
| } | |||||
| } | |||||
| .ant-table-tbody { | |||||
| .ant-table-cell { | |||||
| border-right: none !important; | |||||
| border-left: none !important; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| &__chart { | |||||
| display: flex; | |||||
| flex-wrap: wrap; | |||||
| gap: 15px; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| margin-top: 10px; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,267 @@ | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||||
| import { useCheck } from '@/hooks'; | |||||
| import { getModelPageVersionsReq, getModelVersionsMetricsReq } from '@/services/dataset'; | |||||
| import { tableSorter } from '@/utils'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import tableCellRender from '@/utils/table'; | |||||
| import { Checkbox, Table, Tooltip, type TablePaginationConfig, type TableProps } from 'antd'; | |||||
| import { useEffect, useMemo, useState } from 'react'; | |||||
| import MetricsChart, { MetricsChatData } from '../MetricsChart'; | |||||
| import styles from './index.less'; | |||||
| enum MetricsType { | |||||
| Train = 'train', // 训练 | |||||
| Evaluate = 'evaluate', // 评估 | |||||
| } | |||||
| type TableData = { | |||||
| name: string; | |||||
| metrics_names?: string[]; | |||||
| metrics?: Record<string, number>; | |||||
| params_names?: string[]; | |||||
| params?: Record<string, number>; | |||||
| }; | |||||
| type ModelMetricsProps = { | |||||
| resourceId: number; | |||||
| identifier: string; | |||||
| owner: string; | |||||
| version: string; | |||||
| }; | |||||
| function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsProps) { | |||||
| const [pagination, setPagination] = useState<TablePaginationConfig>({ | |||||
| current: 1, | |||||
| pageSize: 10, | |||||
| }); | |||||
| const [total, setTotal] = useState(0); | |||||
| const [tableData, setTableData] = useState<TableData[]>([]); | |||||
| const [chartData, setChartData] = useState<Record<string, MetricsChatData[]> | undefined>( | |||||
| undefined, | |||||
| ); | |||||
| const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); | |||||
| // 获取所有的指标名称 | |||||
| const allMetricsNames = useMemo(() => { | |||||
| const first: TableData | undefined = tableData.find( | |||||
| (item) => item.metrics_names && item.metrics_names.length > 0, | |||||
| ); | |||||
| return first?.metrics_names ?? []; | |||||
| }, [tableData]); | |||||
| const [ | |||||
| selectedMetrics, | |||||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | |||||
| _setSelectedMetrics, | |||||
| metricsChecked, | |||||
| metricsIndeterminate, | |||||
| checkAllMetrics, | |||||
| isSingleMetricsChecked, | |||||
| checkSingleMetrics, | |||||
| ] = useCheck(allMetricsNames); | |||||
| useEffect(() => { | |||||
| getModelPageVersions(); | |||||
| }, []); | |||||
| useEffect(() => { | |||||
| if (selectedMetrics.length !== 0 && selectedRowKeys.length !== 0) { | |||||
| getModelVersionsMetrics(); | |||||
| } else { | |||||
| setChartData(undefined); | |||||
| } | |||||
| }, [selectedMetrics, selectedRowKeys]); | |||||
| useEffect(() => { | |||||
| const curRow = tableData.find((item) => item.name === version); | |||||
| if ( | |||||
| curRow && | |||||
| curRow.metrics_names && | |||||
| curRow.metrics_names.length > 0 && | |||||
| !selectedRowKeys.includes(version) | |||||
| ) { | |||||
| setSelectedRowKeys([version, ...selectedRowKeys]); | |||||
| } | |||||
| }, [version]); | |||||
| // 获取模型版本列表,带有参数和指标数据 | |||||
| const getModelPageVersions = async () => { | |||||
| const params = { | |||||
| page: pagination.current! - 1, | |||||
| size: pagination.pageSize, | |||||
| identifier: identifier, | |||||
| owner: owner, | |||||
| type: MetricsType.Train, | |||||
| }; | |||||
| const [res] = await to(getModelPageVersionsReq(params)); | |||||
| if (res && res.data) { | |||||
| const { content = [], totalElements = 0 } = res.data; | |||||
| setTableData(content); | |||||
| setTotal(totalElements); | |||||
| } | |||||
| }; | |||||
| const getModelVersionsMetrics = async () => { | |||||
| const params = { | |||||
| versions: selectedRowKeys, | |||||
| metrics: selectedMetrics, | |||||
| type: MetricsType.Train, | |||||
| identifier: identifier, | |||||
| repo_id: resourceId, | |||||
| }; | |||||
| const [res] = await to(getModelVersionsMetricsReq(params)); | |||||
| if (res && res.data) { | |||||
| setChartData(res.data); | |||||
| } | |||||
| }; | |||||
| // 分页切换 | |||||
| const handleTableChange: TableProps<TableData>['onChange'] = ( | |||||
| pagination, | |||||
| _filters, | |||||
| _sorter, | |||||
| { action }, | |||||
| ) => { | |||||
| if (action === 'paginate') { | |||||
| setPagination(pagination); | |||||
| } | |||||
| }; | |||||
| const rowSelection: TableProps<TableData>['rowSelection'] = { | |||||
| type: 'checkbox', | |||||
| fixed: 'left', | |||||
| selectedRowKeys, | |||||
| onChange: (selectedRowKeys: React.Key[]) => { | |||||
| setSelectedRowKeys(selectedRowKeys); | |||||
| }, | |||||
| getCheckboxProps: (record: TableData) => ({ | |||||
| disabled: !record.metrics_names || record.metrics_names.length === 0, | |||||
| }), | |||||
| }; | |||||
| const showTableData = useMemo(() => { | |||||
| const index = tableData.findIndex((item) => item.name === version); | |||||
| if (index !== -1) { | |||||
| const rowData = tableData[index]; | |||||
| const newTableData = tableData.filter((_, idx) => idx !== index); | |||||
| return [rowData, ...newTableData]; | |||||
| } | |||||
| }, [version, tableData]); | |||||
| // 表头 | |||||
| const columns: TableProps<TableData>['columns'] = useMemo(() => { | |||||
| const first: TableData | undefined = tableData.find( | |||||
| (item) => item.metrics_names && item.metrics_names.length > 0, | |||||
| ); | |||||
| const metricsNames = first?.metrics_names ?? []; | |||||
| const paramsNames = first?.params_names ?? []; | |||||
| return [ | |||||
| { | |||||
| title: '基本信息', | |||||
| align: 'center', | |||||
| children: [ | |||||
| { | |||||
| title: '版本号', | |||||
| dataIndex: 'name', | |||||
| key: 'name', | |||||
| width: 180, | |||||
| fixed: 'left', | |||||
| align: 'center', | |||||
| render: tableCellRender(false), | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| { | |||||
| title: `训练参数`, | |||||
| align: 'center', | |||||
| children: paramsNames.map((name) => ({ | |||||
| title: ( | |||||
| <Tooltip title={name}> | |||||
| <span>{name}</span> | |||||
| </Tooltip> | |||||
| ), | |||||
| dataIndex: ['params', name], | |||||
| key: name, | |||||
| width: 120, | |||||
| align: 'center', | |||||
| render: tableCellRender(true), | |||||
| ellipsis: { showTitle: false }, | |||||
| sorter: (a, b) => tableSorter(a.params?.[name], b.params?.[name]), | |||||
| showSorterTooltip: false, | |||||
| })), | |||||
| }, | |||||
| { | |||||
| title: () => ( | |||||
| <div> | |||||
| <Checkbox | |||||
| checked={metricsChecked} | |||||
| indeterminate={metricsIndeterminate} | |||||
| onChange={checkAllMetrics} | |||||
| disabled={metricsNames.length === 0} | |||||
| ></Checkbox>{' '} | |||||
| <span>训练指标</span> | |||||
| </div> | |||||
| ), | |||||
| align: 'center', | |||||
| children: metricsNames.map((name) => ({ | |||||
| title: ( | |||||
| <div> | |||||
| <Checkbox | |||||
| checked={isSingleMetricsChecked(name)} | |||||
| onChange={(e) => { | |||||
| e.stopPropagation(); | |||||
| checkSingleMetrics(name); | |||||
| }} | |||||
| onClick={(e) => e.stopPropagation()} | |||||
| ></Checkbox>{' '} | |||||
| <Tooltip title={name}> | |||||
| <span>{name}</span> | |||||
| </Tooltip> | |||||
| </div> | |||||
| ), | |||||
| dataIndex: ['metrics', name], | |||||
| key: name, | |||||
| width: 120, | |||||
| align: 'center', | |||||
| render: tableCellRender(true), | |||||
| ellipsis: { showTitle: false }, | |||||
| sorter: (a, b) => tableSorter(a.metrics?.[name], b.metrics?.[name]), | |||||
| showSorterTooltip: false, | |||||
| })), | |||||
| }, | |||||
| ]; | |||||
| }, [tableData, selectedMetrics]); | |||||
| return ( | |||||
| <div className={styles['model-metrics']}> | |||||
| <div className={styles['model-metrics__table']}> | |||||
| <SubAreaTitle | |||||
| title="指标参数差异对比" | |||||
| image={require('@/assets/img/model-metrics.png')} | |||||
| style={{ marginBottom: '15px' }} | |||||
| ></SubAreaTitle> | |||||
| <Table | |||||
| dataSource={showTableData} | |||||
| columns={columns} | |||||
| rowSelection={rowSelection} | |||||
| bordered={true} | |||||
| pagination={{ | |||||
| ...pagination, | |||||
| total: total, | |||||
| showSizeChanger: true, | |||||
| showQuickJumper: true, | |||||
| showTotal: () => `共${total}条`, | |||||
| }} | |||||
| onChange={handleTableChange} | |||||
| rowKey="name" | |||||
| /> | |||||
| </div> | |||||
| <div className={styles['model-metrics__chart']}> | |||||
| {chartData && | |||||
| Object.keys(chartData).map((key) => ( | |||||
| <MetricsChart key={key} name={key} chartData={chartData[key]}></MetricsChart> | |||||
| ))} | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default ModelMetrics; | |||||
| @@ -3,8 +3,6 @@ | |||||
| * @Date: 2024-04-16 13:58:08 | * @Date: 2024-04-16 13:58:08 | ||||
| * @Description: 模型部署服务列表 | * @Description: 模型部署服务列表 | ||||
| */ | */ | ||||
| import CommonTableCell from '@/components/CommonTableCell'; | |||||
| import DateTableCell from '@/components/DateTableCell'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import PageTitle from '@/components/PageTitle'; | import PageTitle from '@/components/PageTitle'; | ||||
| import { serviceTypeOptions } from '@/enums'; | import { serviceTypeOptions } from '@/enums'; | ||||
| @@ -13,6 +11,7 @@ import { deleteServiceReq, getServiceListReq } from '@/services/modelDeployment' | |||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import SessionStorage from '@/utils/sessionStorage'; | import SessionStorage from '@/utils/sessionStorage'; | ||||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { useNavigate } from '@umijs/max'; | import { useNavigate } from '@umijs/max'; | ||||
| import { | import { | ||||
| @@ -177,7 +176,12 @@ function ModelDeployment() { | |||||
| }; | }; | ||||
| // 分页切换 | // 分页切换 | ||||
| const handleTableChange: TableProps['onChange'] = (pagination, _filters, _sorter, { action }) => { | |||||
| const handleTableChange: TableProps<ServiceData>['onChange'] = ( | |||||
| pagination, | |||||
| _filters, | |||||
| _sorter, | |||||
| { action }, | |||||
| ) => { | |||||
| if (action === 'paginate') { | if (action === 'paginate') { | ||||
| setPagination(pagination); | setPagination(pagination); | ||||
| } | } | ||||
| @@ -190,42 +194,39 @@ function ModelDeployment() { | |||||
| dataIndex: 'index', | dataIndex: 'index', | ||||
| key: 'index', | key: 'index', | ||||
| width: '20%', | width: '20%', | ||||
| render(_text, _record, index) { | |||||
| return <span>{(pagination.current! - 1) * pagination.pageSize! + index + 1}</span>; | |||||
| }, | |||||
| render: tableCellRender(false, TableCellValueType.Index, { | |||||
| page: pagination.current! - 1, | |||||
| pageSize: pagination.pageSize!, | |||||
| }), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '服务名称', | title: '服务名称', | ||||
| dataIndex: 'service_name', | dataIndex: 'service_name', | ||||
| key: 'service_name', | key: 'service_name', | ||||
| width: '20%', | width: '20%', | ||||
| render: (text, record) => { | |||||
| return ( | |||||
| <a className="kf-table-row-link" onClick={() => toDetail(record)}> | |||||
| {text} | |||||
| </a> | |||||
| ); | |||||
| }, | |||||
| render: tableCellRender(false, TableCellValueType.Link, { | |||||
| onClick: toDetail, | |||||
| }), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '服务类型', | title: '服务类型', | ||||
| dataIndex: 'service_type_name', | dataIndex: 'service_type_name', | ||||
| key: 'service_type_name', | key: 'service_type_name', | ||||
| width: '20%', | width: '20%', | ||||
| render: CommonTableCell(), | |||||
| render: tableCellRender(), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '版本数量', | title: '版本数量', | ||||
| dataIndex: 'version_count', | dataIndex: 'version_count', | ||||
| key: 'version_count', | key: 'version_count', | ||||
| width: '20%', | width: '20%', | ||||
| render: CommonTableCell(), | |||||
| render: tableCellRender(), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '服务描述', | title: '服务描述', | ||||
| dataIndex: 'description', | dataIndex: 'description', | ||||
| key: 'description', | key: 'description', | ||||
| render: CommonTableCell(), | |||||
| render: tableCellRender(), | |||||
| width: '20%', | width: '20%', | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -233,7 +234,7 @@ function ModelDeployment() { | |||||
| dataIndex: 'update_time', | dataIndex: 'update_time', | ||||
| key: 'update_time', | key: 'update_time', | ||||
| width: '20%', | width: '20%', | ||||
| render: DateTableCell, | |||||
| render: tableCellRender(false, TableCellValueType.Date), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| @@ -4,7 +4,6 @@ | |||||
| * @Description: 模型部署列表 | * @Description: 模型部署列表 | ||||
| */ | */ | ||||
| import BasicInfo from '@/components/BasicInfo'; | import BasicInfo from '@/components/BasicInfo'; | ||||
| import CommonTableCell from '@/components/CommonTableCell'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import PageTitle from '@/components/PageTitle'; | import PageTitle from '@/components/PageTitle'; | ||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| @@ -19,8 +18,10 @@ import { | |||||
| } from '@/services/modelDeployment'; | } from '@/services/modelDeployment'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import SessionStorage from '@/utils/sessionStorage'; | import SessionStorage from '@/utils/sessionStorage'; | ||||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { useNavigate, useParams } from '@umijs/max'; | import { useNavigate, useParams } from '@umijs/max'; | ||||
| import { | import { | ||||
| @@ -30,7 +31,6 @@ import { | |||||
| Input, | Input, | ||||
| Select, | Select, | ||||
| Table, | Table, | ||||
| Tooltip, | |||||
| type TablePaginationConfig, | type TablePaginationConfig, | ||||
| type TableProps, | type TableProps, | ||||
| } from 'antd'; | } from 'antd'; | ||||
| @@ -38,6 +38,7 @@ import { type SearchProps } from 'antd/es/input'; | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import ServiceRunStatusCell from '../components/ModelDeployStatusCell'; | import ServiceRunStatusCell from '../components/ModelDeployStatusCell'; | ||||
| import VersionCompareModal from '../components/VersionCompareModal'; | |||||
| import { | import { | ||||
| CreateServiceVersionFrom, | CreateServiceVersionFrom, | ||||
| ServiceData, | ServiceData, | ||||
| @@ -57,6 +58,7 @@ function ServiceInfo() { | |||||
| const [inputText, setInputText] = useState(cacheState?.searchText); | const [inputText, setInputText] = useState(cacheState?.searchText); | ||||
| const [tableData, setTableData] = useState<ServiceVersionData[]>([]); | const [tableData, setTableData] = useState<ServiceVersionData[]>([]); | ||||
| const [total, setTotal] = useState(0); | const [total, setTotal] = useState(0); | ||||
| const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); | |||||
| const [pagination, setPagination] = useState<TablePaginationConfig>( | const [pagination, setPagination] = useState<TablePaginationConfig>( | ||||
| cacheState?.pagination ?? { | cacheState?.pagination ?? { | ||||
| current: 1, | current: 1, | ||||
| @@ -209,11 +211,39 @@ function ServiceInfo() { | |||||
| }; | }; | ||||
| // 分页切换 | // 分页切换 | ||||
| const handleTableChange: TableProps['onChange'] = (pagination, _filters, _sorter, { action }) => { | |||||
| const handleTableChange: TableProps<ServiceVersionData>['onChange'] = ( | |||||
| pagination, | |||||
| _filters, | |||||
| _sorter, | |||||
| { action }, | |||||
| ) => { | |||||
| if (action === 'paginate') { | if (action === 'paginate') { | ||||
| setPagination(pagination); | setPagination(pagination); | ||||
| } | } | ||||
| // console.log(pagination, filters, sorter, action); | |||||
| }; | |||||
| // 版本对比 | |||||
| const handleVersionCompare = () => { | |||||
| if (selectedRowKeys.length !== 2) { | |||||
| message.error('请选择两个版本进行对比'); | |||||
| return; | |||||
| } | |||||
| openAntdModal(VersionCompareModal, { | |||||
| version1: selectedRowKeys[0] as string, | |||||
| version2: selectedRowKeys[1] as string, | |||||
| }); | |||||
| }; | |||||
| // 选择行 | |||||
| const rowSelection: TableProps<ServiceVersionData>['rowSelection'] = { | |||||
| type: 'checkbox', | |||||
| columnWidth: 48, | |||||
| fixed: 'left', | |||||
| selectedRowKeys, | |||||
| onChange: (selectedRowKeys: React.Key[]) => { | |||||
| setSelectedRowKeys(selectedRowKeys); | |||||
| }, | |||||
| }; | }; | ||||
| const columns: TableProps<ServiceVersionData>['columns'] = [ | const columns: TableProps<ServiceVersionData>['columns'] = [ | ||||
| @@ -222,31 +252,24 @@ function ServiceInfo() { | |||||
| dataIndex: 'index', | dataIndex: 'index', | ||||
| key: 'index', | key: 'index', | ||||
| width: '20%', | width: '20%', | ||||
| render(_text, _record, index) { | |||||
| return <span>{(pagination.current! - 1) * pagination.pageSize! + index + 1}</span>; | |||||
| }, | |||||
| render: tableCellRender(false, TableCellValueType.Index, { | |||||
| page: pagination.current! - 1, | |||||
| pageSize: pagination.pageSize!, | |||||
| }), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '服务版本', | title: '服务版本', | ||||
| dataIndex: 'version', | dataIndex: 'version', | ||||
| key: 'version', | key: 'version', | ||||
| width: '20%', | width: '20%', | ||||
| render: CommonTableCell(), | |||||
| render: tableCellRender(), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '模型版本', | title: '模型版本', | ||||
| dataIndex: 'model', | |||||
| dataIndex: ['model', 'show_value'], | |||||
| key: 'model', | key: 'model', | ||||
| width: '20%', | width: '20%', | ||||
| render: (_text: string, record: ServiceVersionData) => ( | |||||
| <Tooltip | |||||
| title={record.model.show_value} | |||||
| placement="topLeft" | |||||
| overlayStyle={{ maxWidth: '400px' }} | |||||
| > | |||||
| <span>{record.model.show_value}</span> | |||||
| </Tooltip> | |||||
| ), | |||||
| render: tableCellRender(true), | |||||
| ellipsis: { showTitle: false }, | ellipsis: { showTitle: false }, | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -261,14 +284,14 @@ function ServiceInfo() { | |||||
| dataIndex: 'image', | dataIndex: 'image', | ||||
| key: 'image', | key: 'image', | ||||
| width: '20%', | width: '20%', | ||||
| render: CommonTableCell(true), | |||||
| render: tableCellRender(true), | |||||
| ellipsis: { showTitle: false }, | ellipsis: { showTitle: false }, | ||||
| }, | }, | ||||
| { | { | ||||
| title: '副本数量', | title: '副本数量', | ||||
| dataIndex: 'replicas', | dataIndex: 'replicas', | ||||
| key: 'replicas', | key: 'replicas', | ||||
| render: CommonTableCell(), | |||||
| render: tableCellRender(), | |||||
| width: '20%', | width: '20%', | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -276,15 +299,9 @@ function ServiceInfo() { | |||||
| dataIndex: 'resource', | dataIndex: 'resource', | ||||
| key: 'resource', | key: 'resource', | ||||
| width: '20%', | width: '20%', | ||||
| render: (resource: string) => ( | |||||
| <Tooltip | |||||
| title={getResourceDescription(resource)} | |||||
| placement="topLeft" | |||||
| overlayStyle={{ maxWidth: '400px' }} | |||||
| > | |||||
| <span>{resource ? getResourceDescription(resource) : '--'}</span> | |||||
| </Tooltip> | |||||
| ), | |||||
| render: tableCellRender(true, TableCellValueType.Custom, { | |||||
| format: getResourceDescription, | |||||
| }), | |||||
| ellipsis: { showTitle: false }, | ellipsis: { showTitle: false }, | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -393,13 +410,16 @@ function ServiceInfo() { | |||||
| allowClear | allowClear | ||||
| ></Select> | ></Select> | ||||
| <Button | <Button | ||||
| style={{ marginRight: '20px', marginLeft: 'auto' }} | |||||
| style={{ marginRight: '15px', marginLeft: 'auto' }} | |||||
| type="default" | type="default" | ||||
| onClick={() => createServiceVersion(ServiceOperationType.Create)} | onClick={() => createServiceVersion(ServiceOperationType.Create)} | ||||
| icon={<KFIcon type="icon-xinjian2" />} | icon={<KFIcon type="icon-xinjian2" />} | ||||
| > | > | ||||
| 新增版本 | 新增版本 | ||||
| </Button> | </Button> | ||||
| <Button style={{ marginRight: '15px' }} type="default" onClick={handleVersionCompare}> | |||||
| 版本对比 | |||||
| </Button> | |||||
| <Button | <Button | ||||
| style={{ marginRight: 0 }} | style={{ marginRight: 0 }} | ||||
| type="default" | type="default" | ||||
| @@ -424,6 +444,7 @@ function ServiceInfo() { | |||||
| showTotal: () => `共${total}条`, | showTotal: () => `共${total}条`, | ||||
| }} | }} | ||||
| onChange={handleTableChange} | onChange={handleTableChange} | ||||
| rowSelection={rowSelection} | |||||
| rowKey="id" | rowKey="id" | ||||
| /> | /> | ||||
| </div> | </div> | ||||
| @@ -0,0 +1,117 @@ | |||||
| @purple-color: #6516ff; | |||||
| .title(@color, @background) { | |||||
| width: 100%; | |||||
| margin-bottom: 20px; | |||||
| color: @color; | |||||
| font-weight: 500; | |||||
| font-size: @font-size; | |||||
| line-height: 42px; | |||||
| text-align: center; | |||||
| background: @background; | |||||
| .singleLine(); | |||||
| } | |||||
| .text() { | |||||
| margin-bottom: 20px !important; | |||||
| color: @text-color-secondary; | |||||
| font-size: 13px; | |||||
| word-break: break-all; | |||||
| .singleLine(); | |||||
| } | |||||
| .version-container(@background) { | |||||
| flex: 1; | |||||
| min-width: 0; | |||||
| background: @background; | |||||
| border-radius: 4px; | |||||
| } | |||||
| .version-compare { | |||||
| :global { | |||||
| .ant-modal-content { | |||||
| padding: 40px 40px 25px !important; | |||||
| } | |||||
| .ant-modal-header { | |||||
| margin-bottom: 20px !important; | |||||
| } | |||||
| .kf-modal-title { | |||||
| color: @text-color; | |||||
| font-weight: 500; | |||||
| font-size: 20px; | |||||
| } | |||||
| } | |||||
| &__container { | |||||
| display: flex; | |||||
| flex-wrap: nowrap; | |||||
| gap: 0 5px; | |||||
| align-items: stretch; | |||||
| height: 100%; | |||||
| } | |||||
| &__fields { | |||||
| flex: none; | |||||
| width: 117px; | |||||
| padding: 0 15px; | |||||
| background: white; | |||||
| border: 1px solid .addAlpha(@primary-color, 0.2) []; | |||||
| border-radius: 4px; | |||||
| &__title { | |||||
| margin-bottom: 20px; | |||||
| color: @text-color; | |||||
| font-size: @font-size; | |||||
| line-height: 42px; | |||||
| } | |||||
| &__text { | |||||
| .text(); | |||||
| &--different { | |||||
| color: @error-color; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__left { | |||||
| .version-container(.addAlpha(@primary-color, 0.04) []); | |||||
| &__title { | |||||
| .title(@primary-color, linear-gradient( | |||||
| 159.9deg,rgba(138, 177, 255, 0.5) 0%, | |||||
| rgba(22, 100, 255, 0.5) 100% | |||||
| )); | |||||
| } | |||||
| &__text { | |||||
| padding: 0 15px; | |||||
| text-align: center; | |||||
| .text(); | |||||
| &--different { | |||||
| color: @primary-color; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__right { | |||||
| .version-container(rgba(100, 30, 237, 0.04)); | |||||
| &__title { | |||||
| .title(@purple-color, linear-gradient( | |||||
| 159.9deg, | |||||
| rgba(193, 138, 255, 0.5) 0%, | |||||
| rgba(146, 22, 255, 0.5) 100% | |||||
| )); | |||||
| } | |||||
| &__text { | |||||
| padding: 0 15px; | |||||
| text-align: center; | |||||
| .text(); | |||||
| &--different { | |||||
| color: @purple-color; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,197 @@ | |||||
| import KFModal from '@/components/KFModal'; | |||||
| import { ServiceRunStatus } from '@/enums'; | |||||
| import { useComputingResource } from '@/hooks/resource'; | |||||
| import { type ServiceVersionData } from '@/pages/ModelDeployment/types'; | |||||
| import { getServiceVersionCompareReq } from '@/services/modelDeployment'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { Typography, type ModalProps } from 'antd'; | |||||
| import classNames from 'classnames'; | |||||
| import { useEffect, useMemo, useState } from 'react'; | |||||
| import { statusInfo } from '../ModelDeployStatusCell'; | |||||
| import styles from './index.less'; | |||||
| type CompareData = { | |||||
| differences: Record<string, any>; | |||||
| version1: ServiceVersionData; | |||||
| version2: ServiceVersionData; | |||||
| }; | |||||
| type ServiceVersionDataKey = keyof ServiceVersionData; | |||||
| type FiledType = { | |||||
| key: ServiceVersionDataKey; | |||||
| text: string; | |||||
| format?: (data: any) => any; | |||||
| }; | |||||
| interface CreateMirrorModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| version1: string; | |||||
| version2: string; | |||||
| } | |||||
| // 格式化环境变量 | |||||
| const formatEnvText = (env: Record<string, string>) => { | |||||
| if (!env || Object.keys(env).length === 0) { | |||||
| return '--'; | |||||
| } | |||||
| return Object.entries(env) | |||||
| .map(([key, value]) => `${key} = ${value}`) | |||||
| .join(','); | |||||
| }; | |||||
| function VersionCompareModal({ version1, version2, ...rest }: CreateMirrorModalProps) { | |||||
| const [compareData, setCompareData] = useState<CompareData | undefined>(undefined); | |||||
| const getResourceDescription = useComputingResource()[2]; | |||||
| const fields: FiledType[] = useMemo( | |||||
| () => [ | |||||
| { | |||||
| key: 'service_name', | |||||
| text: '服务名称', | |||||
| }, | |||||
| { | |||||
| key: 'run_state', | |||||
| text: '状态', | |||||
| format: (data: any) => { | |||||
| return data ? statusInfo[data as ServiceRunStatus].text : '--'; | |||||
| }, | |||||
| }, | |||||
| { | |||||
| key: 'image', | |||||
| text: '镜像', | |||||
| }, | |||||
| { | |||||
| key: 'code_config', | |||||
| text: '代码配置', | |||||
| format: (data: any) => { | |||||
| return data?.show_value; | |||||
| }, | |||||
| }, | |||||
| { | |||||
| key: 'model', | |||||
| text: '模型', | |||||
| format: (data: any) => { | |||||
| return data?.show_value; | |||||
| }, | |||||
| }, | |||||
| { | |||||
| key: 'resource', | |||||
| text: '资源规格', | |||||
| format: getResourceDescription, | |||||
| }, | |||||
| { | |||||
| key: 'replicas', | |||||
| text: '副本数', | |||||
| }, | |||||
| { | |||||
| key: 'mount_path', | |||||
| text: '挂载路径', | |||||
| }, | |||||
| { | |||||
| key: 'url', | |||||
| text: '服务URL', | |||||
| }, | |||||
| { | |||||
| key: 'env_variables', | |||||
| text: '环境变量', | |||||
| format: formatEnvText, | |||||
| }, | |||||
| { | |||||
| key: 'description', | |||||
| text: '描述', | |||||
| }, | |||||
| ], | |||||
| [getResourceDescription], | |||||
| ); | |||||
| useEffect(() => { | |||||
| getServiceVersionCompare(); | |||||
| }, []); | |||||
| // 获取对比数据 | |||||
| const getServiceVersionCompare = async () => { | |||||
| const params = { | |||||
| id1: version1, | |||||
| id2: version2, | |||||
| }; | |||||
| const [res] = await to(getServiceVersionCompareReq(params)); | |||||
| if (res && res.data) { | |||||
| setCompareData(res.data); | |||||
| } | |||||
| }; | |||||
| const { | |||||
| version1: v1 = {} as ServiceVersionData, | |||||
| version2: v2 = {} as ServiceVersionData, | |||||
| differences = {}, | |||||
| } = compareData || {}; | |||||
| const isDifferent = (key: ServiceVersionDataKey) => { | |||||
| const keys = Object.keys(differences); | |||||
| return keys.includes(key); | |||||
| }; | |||||
| return ( | |||||
| <KFModal | |||||
| {...rest} | |||||
| title="服务版本对比" | |||||
| width={825} | |||||
| footer={null} | |||||
| className={styles['version-compare']} | |||||
| > | |||||
| <div className={styles['version-compare__container']}> | |||||
| <div className={styles['version-compare__fields']}> | |||||
| <div className={styles['version-compare__fields__title']}>基础版本号</div> | |||||
| {fields.map(({ key, text }) => ( | |||||
| <div | |||||
| className={classNames(styles['version-compare__fields__text'], { | |||||
| [styles['version-compare__fields__text--different']]: isDifferent(key), | |||||
| })} | |||||
| key={key} | |||||
| > | |||||
| {text} | |||||
| </div> | |||||
| ))} | |||||
| </div> | |||||
| <div className={styles['version-compare__left']}> | |||||
| <div className={styles['version-compare__left__title']}>{v1.version}</div> | |||||
| {fields.map(({ key, format }) => { | |||||
| const text = format ? format(v1[key]) : v1[key]; | |||||
| return ( | |||||
| <div | |||||
| key={key} | |||||
| className={classNames(styles['version-compare__left__text'], { | |||||
| [styles['version-compare__left__text--different']]: isDifferent(key), | |||||
| })} | |||||
| > | |||||
| <Typography.Text ellipsis={{ tooltip: text }} style={{ color: 'inherit' }}> | |||||
| {text} | |||||
| </Typography.Text> | |||||
| </div> | |||||
| ); | |||||
| })} | |||||
| </div> | |||||
| <div className={styles['version-compare__right']}> | |||||
| <div className={styles['version-compare__right__title']}>{v2.version}</div> | |||||
| {fields.map(({ key, format }) => { | |||||
| const text = format ? format(v2[key]) : v2[key]; | |||||
| return ( | |||||
| <div | |||||
| key={key} | |||||
| className={classNames(styles['version-compare__right__text'], { | |||||
| [styles['version-compare__right__text--different']]: isDifferent(key), | |||||
| })} | |||||
| > | |||||
| <Typography.Text ellipsis={{ tooltip: text }} style={{ color: 'inherit' }}> | |||||
| {text} | |||||
| </Typography.Text> | |||||
| </div> | |||||
| ); | |||||
| })} | |||||
| </div> | |||||
| </div> | |||||
| </KFModal> | |||||
| ); | |||||
| } | |||||
| export default VersionCompareModal; | |||||
| @@ -2,7 +2,7 @@ import KFIcon from '@/components/KFIcon'; | |||||
| import { useStateRef, useVisible } from '@/hooks'; | import { useStateRef, useVisible } from '@/hooks'; | ||||
| import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js'; | import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { fittingString, s8 } from '@/utils'; | |||||
| import { fittingString, parseJsonText, s8 } from '@/utils'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import G6 from '@antv/g6'; | import G6 from '@antv/g6'; | ||||
| import { useNavigate, useParams } from '@umijs/max'; | import { useNavigate, useParams } from '@umijs/max'; | ||||
| @@ -130,7 +130,7 @@ const EditPipeline = () => { | |||||
| // 渲染数据 | // 渲染数据 | ||||
| const getGraphData = (data) => { | const getGraphData = (data) => { | ||||
| if (graph) { | |||||
| if (graph && data) { | |||||
| graph.data(data); | graph.data(data); | ||||
| graph.render(); | graph.render(); | ||||
| } else { | } else { | ||||
| @@ -283,7 +283,7 @@ const EditPipeline = () => { | |||||
| const { global_param, dag } = res.data; | const { global_param, dag } = res.data; | ||||
| setGlobalParam(global_param || []); | setGlobalParam(global_param || []); | ||||
| if (dag) { | if (dag) { | ||||
| getGraphData(JSON.parse(dag)); | |||||
| getGraphData(parseJsonText(dag)); | |||||
| } | } | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -20,10 +20,6 @@ | |||||
| } | } | ||||
| } | } | ||||
| .ant-pagination { | |||||
| text-align: center; | |||||
| } | |||||
| .ant-input-group-addon { | .ant-input-group-addon { | ||||
| display: none; | display: none; | ||||
| } | } | ||||
| @@ -97,6 +97,7 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { | |||||
| ))} | ))} | ||||
| </div> | </div> | ||||
| <Pagination | <Pagination | ||||
| align="center" | |||||
| total={total} | total={total} | ||||
| showSizeChanger | showSizeChanger | ||||
| defaultPageSize={20} | defaultPageSize={20} | ||||
| @@ -80,7 +80,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| out_parameters: JSON.parse(model.out_parameters), | out_parameters: JSON.parse(model.out_parameters), | ||||
| control_strategy: JSON.parse(model.control_strategy), | control_strategy: JSON.parse(model.control_strategy), | ||||
| }; | }; | ||||
| console.log('model', nodeData); | |||||
| // console.log('model', nodeData); | |||||
| setStagingItem({ | setStagingItem({ | ||||
| ...nodeData, | ...nodeData, | ||||
| }); | }); | ||||
| @@ -1,5 +1,3 @@ | |||||
| import CommonTableCell from '@/components/CommonTableCell'; | |||||
| import DateTableCell from '@/components/DateTableCell'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { | import { | ||||
| @@ -11,6 +9,7 @@ import { | |||||
| removeWorkflow, | removeWorkflow, | ||||
| } from '@/services/pipeline/index.js'; | } from '@/services/pipeline/index.js'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { App, Button, ConfigProvider, Form, Input, Space, Table } from 'antd'; | import { App, Button, ConfigProvider, Form, Input, Space, Table } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| @@ -41,8 +40,7 @@ const Pipeline = () => { | |||||
| } | } | ||||
| }); | }); | ||||
| }; | }; | ||||
| const routeToEdit = (e, record) => { | |||||
| e.stopPropagation(); | |||||
| const routeToEdit = (record) => { | |||||
| navigate({ pathname: `/pipeline/template/info/${record.id}` }); | navigate({ pathname: `/pipeline/template/info/${record.id}` }); | ||||
| }; | }; | ||||
| const showModal = () => { | const showModal = () => { | ||||
| @@ -114,38 +112,37 @@ const Pipeline = () => { | |||||
| key: 'index', | key: 'index', | ||||
| width: 120, | width: 120, | ||||
| align: 'center', | align: 'center', | ||||
| render(text, record, index) { | |||||
| return <span>{(pageOption.current.page - 1) * pageOption.current.size + index + 1}</span>; | |||||
| }, | |||||
| render: tableCellRender(false, TableCellValueType.Index, { | |||||
| page: pageOption.current.page - 1, | |||||
| pageSize: pageOption.current.size, | |||||
| }), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '流水线名称', | title: '流水线名称', | ||||
| dataIndex: 'name', | dataIndex: 'name', | ||||
| key: 'name', | key: 'name', | ||||
| render: (text, record) => ( | |||||
| <a className="kf-table-row-link" onClick={(e) => routeToEdit(e, record)}> | |||||
| {text} | |||||
| </a> | |||||
| ), | |||||
| render: tableCellRender(false, TableCellValueType.Link, { | |||||
| onClick: routeToEdit, | |||||
| }), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '流水线描述', | title: '流水线描述', | ||||
| dataIndex: 'description', | dataIndex: 'description', | ||||
| key: 'description', | key: 'description', | ||||
| render: CommonTableCell(true), | |||||
| render: tableCellRender(true), | |||||
| ellipsis: { showTitle: false }, | ellipsis: { showTitle: false }, | ||||
| }, | }, | ||||
| { | { | ||||
| title: '创建时间', | title: '创建时间', | ||||
| dataIndex: 'create_time', | dataIndex: 'create_time', | ||||
| key: 'create_time', | key: 'create_time', | ||||
| render: DateTableCell, | |||||
| render: tableCellRender(false, TableCellValueType.Date), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '修改时间', | title: '修改时间', | ||||
| dataIndex: 'update_time', | dataIndex: 'update_time', | ||||
| key: 'update_time', | key: 'update_time', | ||||
| render: DateTableCell, | |||||
| render: tableCellRender(false, TableCellValueType.Date), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| @@ -1,10 +1,12 @@ | |||||
| import { clearSessionToken, setSessionToken } from '@/access'; | import { clearSessionToken, setSessionToken } from '@/access'; | ||||
| import { getClientInfoReq } from '@/services/auth'; | |||||
| import { getCaptchaImg, login } from '@/services/system/auth'; | import { getCaptchaImg, login } from '@/services/system/auth'; | ||||
| import { safeInvoke } from '@/utils/functional'; | |||||
| import LocalStorage from '@/utils/localStorage'; | import LocalStorage from '@/utils/localStorage'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import SessionStorage from '@/utils/sessionStorage'; | |||||
| import { gotoOAuth2 } from '@/utils/ui'; | |||||
| import { history, useModel } from '@umijs/max'; | import { history, useModel } from '@umijs/max'; | ||||
| import { Button, Checkbox, Flex, Form, Image, Input, message, type InputRef } from 'antd'; | |||||
| import { Form, message, type InputRef } from 'antd'; | |||||
| import CryptoJS from 'crypto-js'; | import CryptoJS from 'crypto-js'; | ||||
| import { useEffect, useRef, useState } from 'react'; | import { useEffect, useRef, useState } from 'react'; | ||||
| import { flushSync } from 'react-dom'; | import { flushSync } from 'react-dom'; | ||||
| @@ -30,25 +32,35 @@ const Login = () => { | |||||
| const captchaInputRef = useRef<InputRef>(null); | const captchaInputRef = useRef<InputRef>(null); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| getCaptchaCode(); | |||||
| const autoLogin = LocalStorage.getItem(LocalStorage.rememberPasswordKey) ?? 'false'; | |||||
| if (autoLogin === 'true') { | |||||
| const userStorage = LocalStorage.getItem(LocalStorage.loginUserKey); | |||||
| const userJson = safeInvoke((text: string) => | |||||
| CryptoJS.AES.decrypt(text, AESKEY).toString(CryptoJS.enc.Utf8), | |||||
| )(userStorage); | |||||
| const user = safeInvoke(JSON.parse)(userJson); | |||||
| if (user && typeof user === 'object' && user.version === VERSION) { | |||||
| const { username, password } = user; | |||||
| form.setFieldsValue({ username: username, password: password, autoLogin: true }); | |||||
| } else { | |||||
| form.setFieldsValue({ username: '', password: '', autoLogin: true }); | |||||
| LocalStorage.removeItem(LocalStorage.loginUserKey); | |||||
| } | |||||
| } else { | |||||
| form.setFieldsValue({ username: '', password: '', autoLogin: false }); | |||||
| } | |||||
| // getCaptchaCode(); | |||||
| // const autoLogin = LocalStorage.getItem(LocalStorage.rememberPasswordKey) ?? 'false'; | |||||
| // if (autoLogin === 'true') { | |||||
| // const userStorage = LocalStorage.getItem(LocalStorage.loginUserKey); | |||||
| // const userJson = safeInvoke((text: string) => | |||||
| // CryptoJS.AES.decrypt(text, AESKEY).toString(CryptoJS.enc.Utf8), | |||||
| // )(userStorage); | |||||
| // const user = safeInvoke(parseJsonText)(userJson); | |||||
| // if (user && typeof user === 'object' && user.version === VERSION) { | |||||
| // const { username, password } = user; | |||||
| // form.setFieldsValue({ username: username, password: password, autoLogin: true }); | |||||
| // } else { | |||||
| // form.setFieldsValue({ username: '', password: '', autoLogin: true }); | |||||
| // LocalStorage.removeItem(LocalStorage.loginUserKey); | |||||
| // } | |||||
| // } else { | |||||
| // form.setFieldsValue({ username: '', password: '', autoLogin: false }); | |||||
| // } | |||||
| getClientInfo(); | |||||
| }, []); | }, []); | ||||
| const getClientInfo = async () => { | |||||
| const [res] = await to(getClientInfoReq()); | |||||
| if (res && res.data) { | |||||
| const clientInfo = res.data; | |||||
| SessionStorage.setItem(SessionStorage.clientInfoKey, clientInfo, true); | |||||
| gotoOAuth2(); | |||||
| } | |||||
| }; | |||||
| const getCaptchaCode = async () => { | const getCaptchaCode = async () => { | ||||
| const [res] = await to(getCaptchaImg()); | const [res] = await to(getCaptchaImg()); | ||||
| if (res) { | if (res) { | ||||
| @@ -70,6 +82,12 @@ const Login = () => { | |||||
| } | } | ||||
| }; | }; | ||||
| const handleSubmit2 = async (values: API.LoginParams) => { | |||||
| const url = | |||||
| 'http://172.20.32.106:8080/oauth/authorize?client_id=ci4s&response_type=code&grant_type=authorization_code'; | |||||
| window.location.href = url; | |||||
| }; | |||||
| // 登录 | // 登录 | ||||
| const handleSubmit = async (values: API.LoginParams) => { | const handleSubmit = async (values: API.LoginParams) => { | ||||
| const [res, error] = await to(login({ ...values, uuid })); | const [res, error] = await to(login({ ...values, uuid })); | ||||
| @@ -108,113 +126,115 @@ const Login = () => { | |||||
| } | } | ||||
| }; | }; | ||||
| return ( | |||||
| <div className={styles['user-login']}> | |||||
| <div className={styles['user-login__left']}> | |||||
| <div className={styles['user-login__left__top']}> | |||||
| <img | |||||
| src={require('@/assets/img/logo.png')} | |||||
| style={{ width: '32px', marginRight: '12px' }} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| 智能材料科研平台 | |||||
| </div> | |||||
| <div className={styles['user-login__left__title']}> | |||||
| <span>智能材料科研平台</span> | |||||
| <img | |||||
| src={require('@/assets/img/login-ai-logo.png')} | |||||
| className={styles['user-login__left__title__img']} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| </div> | |||||
| <div className={styles['user-login__left__message']}> | |||||
| <span>大语言模型运维 统一管理平台</span> | |||||
| </div> | |||||
| <img | |||||
| className={styles['user-login__left__bottom-img']} | |||||
| src={require('@/assets/img/login-left-image.png')} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| </div> | |||||
| <div className={styles['user-login__right']}> | |||||
| <div> | |||||
| <div className={styles['user-login__right__title']}> | |||||
| <span style={{ color: '#111111' }}>欢迎登录</span> | |||||
| <span>智能材料科研平台</span> | |||||
| </div> | |||||
| <div className={styles['user-login__right__content']}> | |||||
| <div className={styles['user-login__right__content__title']}>账号登录</div> | |||||
| <div className={styles['user-login__right__content__form']}> | |||||
| <Form | |||||
| labelCol={{ span: 0 }} | |||||
| wrapperCol={{ span: 24 }} | |||||
| initialValues={{ autoLogin: true }} | |||||
| onFinish={handleSubmit} | |||||
| autoComplete="off" | |||||
| form={form} | |||||
| > | |||||
| <Form.Item name="username" rules={[{ required: true, message: '请输入用户名' }]}> | |||||
| <Input | |||||
| placeholder="请输入用户名" | |||||
| prefix={<LoginInputPrefix icon={require('@/assets/img/login-user.png')} />} | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item name="password" rules={[{ required: true, message: '请输入密码' }]}> | |||||
| <Input.Password | |||||
| placeholder="请输入密码" | |||||
| prefix={<LoginInputPrefix icon={require('@/assets/img/login-password.png')} />} | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| <Flex align="start" style={{ height: '98px' }}> | |||||
| <div style={{ flex: 1 }}> | |||||
| <Form.Item name="code" rules={[{ required: true, message: '请输入验证码' }]}> | |||||
| <Input | |||||
| placeholder="请输入验证码" | |||||
| prefix={ | |||||
| <LoginInputPrefix icon={require('@/assets/img/login-captcha.png')} /> | |||||
| } | |||||
| ref={captchaInputRef} | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| </div> | |||||
| <Image | |||||
| className={styles['user-login__right__content__form__captcha']} | |||||
| src={captchaCode} | |||||
| alt="验证码" | |||||
| preview={false} | |||||
| onClick={() => getCaptchaCode()} | |||||
| /> | |||||
| </Flex> | |||||
| <Form.Item | |||||
| name="autoLogin" | |||||
| valuePropName="checked" | |||||
| labelCol={{ span: 0 }} | |||||
| wrapperCol={{ span: 16 }} | |||||
| > | |||||
| <Checkbox>记住密码</Checkbox> | |||||
| </Form.Item> | |||||
| <Form.Item labelCol={{ span: 0 }} wrapperCol={{ span: 24 }}> | |||||
| <Button type="primary" htmlType="submit"> | |||||
| 登录 | |||||
| </Button> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| return <div className={styles['user-login']}></div>; | |||||
| // return ( | |||||
| // <div className={styles['user-login']}> | |||||
| // <div className={styles['user-login__left']}> | |||||
| // <div className={styles['user-login__left__top']}> | |||||
| // <img | |||||
| // src={require('@/assets/img/logo.png')} | |||||
| // style={{ width: '32px', marginRight: '12px' }} | |||||
| // draggable={false} | |||||
| // alt="" | |||||
| // /> | |||||
| // 智能材料科研平台 | |||||
| // </div> | |||||
| // <div className={styles['user-login__left__title']}> | |||||
| // <span>智能材料科研平台</span> | |||||
| // <img | |||||
| // src={require('@/assets/img/login-ai-logo.png')} | |||||
| // className={styles['user-login__left__title__img']} | |||||
| // draggable={false} | |||||
| // alt="" | |||||
| // /> | |||||
| // </div> | |||||
| // <div className={styles['user-login__left__message']}> | |||||
| // <span>大语言模型运维 统一管理平台</span> | |||||
| // </div> | |||||
| // <img | |||||
| // className={styles['user-login__left__bottom-img']} | |||||
| // src={require('@/assets/img/login-left-image.png')} | |||||
| // draggable={false} | |||||
| // alt="" | |||||
| // /> | |||||
| // </div> | |||||
| // <div className={styles['user-login__right']}> | |||||
| // <div> | |||||
| // <div className={styles['user-login__right__title']}> | |||||
| // <span style={{ color: '#111111' }}>欢迎登录</span> | |||||
| // <span>智能材料科研平台</span> | |||||
| // </div> | |||||
| // <div className={styles['user-login__right__content']}> | |||||
| // <div className={styles['user-login__right__content__title']}>账号登录</div> | |||||
| // <div className={styles['user-login__right__content__form']}> | |||||
| // <Form | |||||
| // labelCol={{ span: 0 }} | |||||
| // wrapperCol={{ span: 24 }} | |||||
| // initialValues={{ autoLogin: true }} | |||||
| // onFinish={handleSubmit2} | |||||
| // autoComplete="off" | |||||
| // form={form} | |||||
| // > | |||||
| // <Form.Item name="username" rules={[{ required: false, message: '请输入用户名' }]}> | |||||
| // <Input | |||||
| // placeholder="请输入用户名" | |||||
| // prefix={<LoginInputPrefix icon={require('@/assets/img/login-user.png')} />} | |||||
| // allowClear | |||||
| // /> | |||||
| // </Form.Item> | |||||
| // <Form.Item name="password" rules={[{ required: false, message: '请输入密码' }]}> | |||||
| // <Input.Password | |||||
| // placeholder="请输入密码" | |||||
| // prefix={<LoginInputPrefix icon={require('@/assets/img/login-password.png')} />} | |||||
| // allowClear | |||||
| // /> | |||||
| // </Form.Item> | |||||
| // <Flex align="start" style={{ height: '98px' }}> | |||||
| // <div style={{ flex: 1 }}> | |||||
| // <Form.Item name="code" rules={[{ required: false, message: '请输入验证码' }]}> | |||||
| // <Input | |||||
| // placeholder="请输入验证码" | |||||
| // prefix={ | |||||
| // <LoginInputPrefix icon={require('@/assets/img/login-captcha.png')} /> | |||||
| // } | |||||
| // ref={captchaInputRef} | |||||
| // allowClear | |||||
| // /> | |||||
| // </Form.Item> | |||||
| // </div> | |||||
| // <Image | |||||
| // className={styles['user-login__right__content__form__captcha']} | |||||
| // src={captchaCode} | |||||
| // alt="验证码" | |||||
| // preview={false} | |||||
| // onClick={() => getCaptchaCode()} | |||||
| // /> | |||||
| // </Flex> | |||||
| // <Form.Item | |||||
| // name="autoLogin" | |||||
| // valuePropName="checked" | |||||
| // labelCol={{ span: 0 }} | |||||
| // wrapperCol={{ span: 16 }} | |||||
| // > | |||||
| // <Checkbox>记住密码</Checkbox> | |||||
| // </Form.Item> | |||||
| // <Form.Item labelCol={{ span: 0 }} wrapperCol={{ span: 24 }}> | |||||
| // <Button type="primary" htmlType="submit"> | |||||
| // 登录 | |||||
| // </Button> | |||||
| // </Form.Item> | |||||
| // </Form> | |||||
| // </div> | |||||
| // </div> | |||||
| // </div> | |||||
| // </div> | |||||
| // </div> | |||||
| // ); | |||||
| }; | }; | ||||
| export default Login; | export default Login; | ||||
| @@ -153,11 +153,11 @@ function ExperimentChart({ chartData, style }: ExperimentChartProps) { | |||||
| show: false, | show: false, | ||||
| }, | }, | ||||
| data: [ | data: [ | ||||
| { value: chartData.Failed > 0 ? chartData.Failed : null, name: '失败' }, | |||||
| { value: chartData.Succeeded > 0 ? chartData.Succeeded : null, name: '成功' }, | |||||
| { value: chartData.Terminated > 0 ? chartData.Terminated : null, name: '中止' }, | |||||
| { value: chartData.Pending > 0 ? chartData.Pending : null, name: '等待' }, | |||||
| { value: chartData.Running > 0 ? chartData.Running : null, name: '运行中' }, | |||||
| { value: chartData.Failed > 0 ? chartData.Failed : undefined, name: '失败' }, | |||||
| { value: chartData.Succeeded > 0 ? chartData.Succeeded : undefined, name: '成功' }, | |||||
| { value: chartData.Terminated > 0 ? chartData.Terminated : undefined, name: '中止' }, | |||||
| { value: chartData.Pending > 0 ? chartData.Pending : undefined, name: '等待' }, | |||||
| { value: chartData.Running > 0 ? chartData.Running : undefined, name: '运行中' }, | |||||
| ], | ], | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -47,13 +47,11 @@ | |||||
| &__robot-img { | &__robot-img { | ||||
| position: fixed; | position: fixed; | ||||
| right: 30px; | |||||
| bottom: 20px; | |||||
| right: 20px; | |||||
| bottom: 90px; | |||||
| z-index: 99; | z-index: 99; | ||||
| width: 64px; | |||||
| height: 64px; | |||||
| background-color: white; | |||||
| border-radius: 10px; | |||||
| width: 56px; | |||||
| height: 56px; | |||||
| cursor: pointer; | cursor: pointer; | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,11 +0,0 @@ | |||||
| // @ts-ignore | |||||
| /* eslint-disable */ | |||||
| import { request } from '@umijs/max'; | |||||
| /** 此处后端没有提供注释 GET /api/notices */ | |||||
| export async function getNotices(options?: { [key: string]: any }) { | |||||
| return request<API.NoticeIconList>('/api/notices', { | |||||
| method: 'GET', | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| @@ -1,12 +0,0 @@ | |||||
| // @ts-ignore | |||||
| /* eslint-disable */ | |||||
| // API 更新时间: | |||||
| // API 唯一标识: | |||||
| import * as api from './api'; | |||||
| import * as login from './login'; | |||||
| import * as rule from './rule'; | |||||
| export default { | |||||
| api, | |||||
| login, | |||||
| rule, | |||||
| }; | |||||
| @@ -1,38 +0,0 @@ | |||||
| // @ts-ignore | |||||
| /* eslint-disable */ | |||||
| import { request } from '@umijs/max'; | |||||
| /** 登录接口 POST /api/login/account */ | |||||
| export async function login(body: API.LoginParams, options?: { [key: string]: any }) { | |||||
| return request<API.LoginResult>('/api/login/account', { | |||||
| method: 'POST', | |||||
| headers: { | |||||
| 'Content-Type': 'application/json', | |||||
| }, | |||||
| data: body, | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| /** 发送验证码 POST /api/login/captcha */ | |||||
| export async function getFakeCaptcha( | |||||
| // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) | |||||
| params: API.getFakeCaptchaParams, | |||||
| options?: { [key: string]: any }, | |||||
| ) { | |||||
| return request<API.FakeCaptcha>('/api/login/captcha', { | |||||
| method: 'POST', | |||||
| params: { | |||||
| ...params, | |||||
| }, | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| /** 登录接口 POST /api/login/outLogin */ | |||||
| export async function outLogin(options?: { [key: string]: any }) { | |||||
| return request<Record<string, any>>('/api/login/outLogin', { | |||||
| method: 'POST', | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| @@ -1,42 +0,0 @@ | |||||
| // @ts-ignore | |||||
| /* eslint-disable */ | |||||
| import { request } from '@umijs/max'; | |||||
| /** 获取规则列表 GET /api/rule */ | |||||
| export async function rule( | |||||
| // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) | |||||
| params: API.ruleParams, | |||||
| options?: { [key: string]: any }, | |||||
| ) { | |||||
| return request<API.RuleList>('/api/rule', { | |||||
| method: 'GET', | |||||
| params: { | |||||
| ...params, | |||||
| }, | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| /** 新建规则 PUT /api/rule */ | |||||
| export async function updateRule(options?: { [key: string]: any }) { | |||||
| return request<API.RuleListItem>('/api/rule', { | |||||
| method: 'PUT', | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| /** 新建规则 POST /api/rule */ | |||||
| export async function addRule(options?: { [key: string]: any }) { | |||||
| return request<API.RuleListItem>('/api/rule', { | |||||
| method: 'POST', | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| /** 删除规则 DELETE /api/rule */ | |||||
| export async function removeRule(options?: { [key: string]: any }) { | |||||
| return request<Record<string, any>>('/api/rule', { | |||||
| method: 'DELETE', | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| @@ -1,114 +0,0 @@ | |||||
| declare namespace API { | |||||
| type CurrentUser = UserInfo & { | |||||
| signature?: string; | |||||
| title?: string; | |||||
| group?: string; | |||||
| tags?: { key?: string; label?: string }[]; | |||||
| notifyCount?: number; | |||||
| unreadCount?: number; | |||||
| country?: string; | |||||
| access?: string; | |||||
| geographic?: { | |||||
| province?: { label?: string; key?: string }; | |||||
| city?: { label?: string; key?: string }; | |||||
| }; | |||||
| address?: string; | |||||
| phone?: string; | |||||
| roleNames?: { | |||||
| roleName?: string; | |||||
| }[]; | |||||
| }; | |||||
| type ErrorResponse = { | |||||
| /** 业务约定的错误码 */ | |||||
| errorCode: string; | |||||
| /** 业务上的错误信息 */ | |||||
| errorMessage?: string; | |||||
| /** 业务上的请求是否成功 */ | |||||
| success?: boolean; | |||||
| }; | |||||
| type FakeCaptcha = { | |||||
| code?: number; | |||||
| status?: string; | |||||
| }; | |||||
| type getFakeCaptchaParams = { | |||||
| /** 手机号 */ | |||||
| phone?: string; | |||||
| }; | |||||
| type LoginParams = { | |||||
| username?: string; | |||||
| password?: string; | |||||
| uuid?: string; | |||||
| autoLogin?: boolean; | |||||
| type?: string; | |||||
| }; | |||||
| type LoginResult = { | |||||
| code: number; | |||||
| msg?: string; | |||||
| type?: string; | |||||
| data?: { | |||||
| access_token?: string; | |||||
| expires_in?: number; | |||||
| }; | |||||
| }; | |||||
| type NoticeIconItem = { | |||||
| id?: string; | |||||
| extra?: string; | |||||
| key?: string; | |||||
| read?: boolean; | |||||
| avatar?: string; | |||||
| title?: string; | |||||
| status?: string; | |||||
| datetime?: string; | |||||
| description?: string; | |||||
| type?: NoticeIconItemType; | |||||
| }; | |||||
| type NoticeIconItemType = 'notification' | 'message' | 'event'; | |||||
| type NoticeIconList = { | |||||
| data?: NoticeIconItem[]; | |||||
| /** 列表的内容总数 */ | |||||
| total?: number; | |||||
| success?: boolean; | |||||
| }; | |||||
| type PageParams = { | |||||
| current?: number; | |||||
| pageSize?: number; | |||||
| }; | |||||
| type RuleList = { | |||||
| data?: RuleListItem[]; | |||||
| /** 列表的内容总数 */ | |||||
| total?: number; | |||||
| success?: boolean; | |||||
| }; | |||||
| type RuleListItem = { | |||||
| key?: number; | |||||
| disabled?: boolean; | |||||
| href?: string; | |||||
| avatar?: string; | |||||
| name?: string; | |||||
| owner?: string; | |||||
| desc?: string; | |||||
| callNo?: number; | |||||
| status?: number; | |||||
| updatedAt?: string; | |||||
| createdAt?: string; | |||||
| progress?: number; | |||||
| }; | |||||
| type ruleParams = { | |||||
| /** 当前的页码 */ | |||||
| current?: number; | |||||
| /** 页面的容量 */ | |||||
| pageSize?: number; | |||||
| }; | |||||
| } | |||||
| @@ -0,0 +1,16 @@ | |||||
| import { request } from '@umijs/max'; | |||||
| // 单点登录 | |||||
| export function loginByOauth2Req(data) { | |||||
| return request(`/api/auth/loginByOauth2`, { | |||||
| method: 'POST', | |||||
| data, | |||||
| }); | |||||
| } | |||||
| // 登录获取客户端信息 | |||||
| export function getClientInfoReq() { | |||||
| return request(`/api/auth/oauth2ClientInfo`, { | |||||
| method: 'GET', | |||||
| }); | |||||
| } | |||||
| @@ -149,4 +149,20 @@ export function exportModelReq(data) { | |||||
| method: 'POST', | method: 'POST', | ||||
| data | data | ||||
| }); | }); | ||||
| } | |||||
| // 分页查询模型所有版本,带有参数和指标数据 | |||||
| export function getModelPageVersionsReq(params) { | |||||
| return request(`/api/mmp/newmodel/queryVersions`, { | |||||
| method: 'GET', | |||||
| params | |||||
| }); | |||||
| } | |||||
| // 获取模型版本指标对比 | |||||
| export function getModelVersionsMetricsReq(data) { | |||||
| return request(`/api/mmp/newmodel/queryVersionsMetrics`, { | |||||
| method: 'POST', | |||||
| data | |||||
| }); | |||||
| } | } | ||||
| @@ -40,6 +40,13 @@ export function deleteQueryByExperimentInsId(id) { | |||||
| method: 'DELETE', | method: 'DELETE', | ||||
| }); | }); | ||||
| } | } | ||||
| // 批量删除实验实例 | |||||
| export function deleteManyExperimentIns(data) { | |||||
| return request(`/api/mmp/experimentIns/batchDelete`, { | |||||
| method: 'DELETE', | |||||
| data, | |||||
| }); | |||||
| } | |||||
| // 根据id终止实验实例 | // 根据id终止实验实例 | ||||
| export function putQueryByExperimentInsId(id) { | export function putQueryByExperimentInsId(id) { | ||||
| return request(`/api/mmp/experimentIns/${id}`, { | return request(`/api/mmp/experimentIns/${id}`, { | ||||
| @@ -52,6 +59,7 @@ export function getQueryByExperimentLog(data) { | |||||
| method: 'POST', | method: 'POST', | ||||
| data, | data, | ||||
| skipErrorHandler: true, | skipErrorHandler: true, | ||||
| skipLoading: true, | |||||
| }); | }); | ||||
| } | } | ||||
| // 查询实例节点结果 | // 查询实例节点结果 | ||||
| @@ -121,16 +129,18 @@ export function getTensorBoardStatusReq(data) { | |||||
| } | } | ||||
| // 获取当前实验的模型推理指标信息 | // 获取当前实验的模型推理指标信息 | ||||
| export function getExpEvaluateInfosReq(experimentId) { | |||||
| export function getExpEvaluateInfosReq(experimentId, params) { | |||||
| return request(`/api/mmp/aim/getExpEvaluateInfos/${experimentId}`, { | return request(`/api/mmp/aim/getExpEvaluateInfos/${experimentId}`, { | ||||
| method: 'GET', | method: 'GET', | ||||
| params | |||||
| }); | }); | ||||
| } | } | ||||
| // 获取当前实验的模型训练指标信息 | // 获取当前实验的模型训练指标信息 | ||||
| export function getExpTrainInfosReq(experimentId) { | |||||
| export function getExpTrainInfosReq(experimentId, params) { | |||||
| return request(`/api/mmp/aim/getExpTrainInfos/${experimentId}`, { | return request(`/api/mmp/aim/getExpTrainInfos/${experimentId}`, { | ||||
| method: 'GET', | method: 'GET', | ||||
| params | |||||
| }); | }); | ||||
| } | } | ||||
| @@ -104,3 +104,11 @@ export function getServiceVersionLogReq(params: any) { | |||||
| params, | params, | ||||
| }); | }); | ||||
| } | } | ||||
| // 获取服务版本对比 | |||||
| export function getServiceVersionCompareReq(params: any) { | |||||
| return request(`/api/mmp/service/serviceVersionCompare`, { | |||||
| method: 'GET', | |||||
| params, | |||||
| }); | |||||
| } | |||||
| @@ -1,12 +0,0 @@ | |||||
| // @ts-ignore | |||||
| /* eslint-disable */ | |||||
| // API 更新时间: | |||||
| // API 唯一标识: | |||||
| import * as pet from './pet'; | |||||
| import * as store from './store'; | |||||
| import * as user from './user'; | |||||
| export default { | |||||
| pet, | |||||
| store, | |||||
| user, | |||||
| }; | |||||
| @@ -1,153 +0,0 @@ | |||||
| // @ts-ignore | |||||
| /* eslint-disable */ | |||||
| import { request } from '@umijs/max'; | |||||
| /** Update an existing pet PUT /pet */ | |||||
| export async function updatePet(body: API.Pet, options?: { [key: string]: any }) { | |||||
| return request<any>('/pet', { | |||||
| method: 'PUT', | |||||
| headers: { | |||||
| 'Content-Type': 'application/json', | |||||
| }, | |||||
| data: body, | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| /** Add a new pet to the store POST /pet */ | |||||
| export async function addPet(body: API.Pet, options?: { [key: string]: any }) { | |||||
| return request<any>('/pet', { | |||||
| method: 'POST', | |||||
| headers: { | |||||
| 'Content-Type': 'application/json', | |||||
| }, | |||||
| data: body, | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| /** Find pet by ID Returns a single pet GET /pet/${param0} */ | |||||
| export async function getPetById( | |||||
| // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) | |||||
| params: API.getPetByIdParams, | |||||
| options?: { [key: string]: any }, | |||||
| ) { | |||||
| const { petId: param0, ...queryParams } = params; | |||||
| return request<API.Pet>(`/pet/${param0}`, { | |||||
| method: 'GET', | |||||
| params: { ...queryParams }, | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| /** Updates a pet in the store with form data POST /pet/${param0} */ | |||||
| export async function updatePetWithForm( | |||||
| // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) | |||||
| params: API.updatePetWithFormParams, | |||||
| body: { name?: string; status?: string }, | |||||
| options?: { [key: string]: any }, | |||||
| ) { | |||||
| const { petId: param0, ...queryParams } = params; | |||||
| const formData = new FormData(); | |||||
| Object.keys(body).forEach((ele) => { | |||||
| const item = (body as any)[ele]; | |||||
| if (item !== undefined && item !== null) { | |||||
| formData.append( | |||||
| ele, | |||||
| typeof item === 'object' && !(item instanceof File) ? JSON.stringify(item) : item, | |||||
| ); | |||||
| } | |||||
| }); | |||||
| return request<any>(`/pet/${param0}`, { | |||||
| method: 'POST', | |||||
| params: { ...queryParams }, | |||||
| data: formData, | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| /** Deletes a pet DELETE /pet/${param0} */ | |||||
| export async function deletePet( | |||||
| // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) | |||||
| params: API.deletePetParams & { | |||||
| // header | |||||
| api_key?: string; | |||||
| }, | |||||
| options?: { [key: string]: any }, | |||||
| ) { | |||||
| const { petId: param0, ...queryParams } = params; | |||||
| return request<any>(`/pet/${param0}`, { | |||||
| method: 'DELETE', | |||||
| headers: {}, | |||||
| params: { ...queryParams }, | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| /** uploads an image POST /pet/${param0}/uploadImage */ | |||||
| export async function uploadFile( | |||||
| // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) | |||||
| params: API.uploadFileParams, | |||||
| body: { additionalMetadata?: string; file?: string }, | |||||
| file?: File, | |||||
| options?: { [key: string]: any }, | |||||
| ) { | |||||
| const { petId: param0, ...queryParams } = params; | |||||
| const formData = new FormData(); | |||||
| if (file) { | |||||
| formData.append('file', file); | |||||
| } | |||||
| Object.keys(body).forEach((ele) => { | |||||
| const item = (body as any)[ele]; | |||||
| if (item !== undefined && item !== null) { | |||||
| formData.append( | |||||
| ele, | |||||
| typeof item === 'object' && !(item instanceof File) ? JSON.stringify(item) : item, | |||||
| ); | |||||
| } | |||||
| }); | |||||
| return request<API.ApiResponse>(`/pet/${param0}/uploadImage`, { | |||||
| method: 'POST', | |||||
| params: { ...queryParams }, | |||||
| data: formData, | |||||
| requestType: 'form', | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| /** Finds Pets by status Multiple status values can be provided with comma separated strings GET /pet/findByStatus */ | |||||
| export async function findPetsByStatus( | |||||
| // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) | |||||
| params: API.findPetsByStatusParams, | |||||
| options?: { [key: string]: any }, | |||||
| ) { | |||||
| return request<API.Pet[]>('/pet/findByStatus', { | |||||
| method: 'GET', | |||||
| params: { | |||||
| ...params, | |||||
| }, | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| /** Finds Pets by tags Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. GET /pet/findByTags */ | |||||
| export async function findPetsByTags( | |||||
| // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) | |||||
| params: API.findPetsByTagsParams, | |||||
| options?: { [key: string]: any }, | |||||
| ) { | |||||
| return request<API.Pet[]>('/pet/findByTags', { | |||||
| method: 'GET', | |||||
| params: { | |||||
| ...params, | |||||
| }, | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| @@ -1,48 +0,0 @@ | |||||
| // @ts-ignore | |||||
| /* eslint-disable */ | |||||
| import { request } from '@umijs/max'; | |||||
| /** Returns pet inventories by status Returns a map of status codes to quantities GET /store/inventory */ | |||||
| export async function getInventory(options?: { [key: string]: any }) { | |||||
| return request<Record<string, any>>('/store/inventory', { | |||||
| method: 'GET', | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| /** Place an order for a pet POST /store/order */ | |||||
| export async function placeOrder(body: API.Order, options?: { [key: string]: any }) { | |||||
| return request<API.Order>('/store/order', { | |||||
| method: 'POST', | |||||
| data: body, | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| /** Find purchase order by ID For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions GET /store/order/${param0} */ | |||||
| export async function getOrderById( | |||||
| // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) | |||||
| params: API.getOrderByIdParams, | |||||
| options?: { [key: string]: any }, | |||||
| ) { | |||||
| const { orderId: param0, ...queryParams } = params; | |||||
| return request<API.Order>(`/store/order/${param0}`, { | |||||
| method: 'GET', | |||||
| params: { ...queryParams }, | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| /** Delete purchase order by ID For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors DELETE /store/order/${param0} */ | |||||
| export async function deleteOrder( | |||||
| // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) | |||||
| params: API.deleteOrderParams, | |||||
| options?: { [key: string]: any }, | |||||
| ) { | |||||
| const { orderId: param0, ...queryParams } = params; | |||||
| return request<any>(`/store/order/${param0}`, { | |||||
| method: 'DELETE', | |||||
| params: { ...queryParams }, | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| @@ -1,112 +0,0 @@ | |||||
| declare namespace API { | |||||
| type ApiResponse = { | |||||
| code?: number; | |||||
| type?: string; | |||||
| message?: string; | |||||
| }; | |||||
| type Category = { | |||||
| id?: number; | |||||
| name?: string; | |||||
| }; | |||||
| type deleteOrderParams = { | |||||
| /** ID of the order that needs to be deleted */ | |||||
| orderId: number; | |||||
| }; | |||||
| type deletePetParams = { | |||||
| api_key?: string; | |||||
| /** Pet id to delete */ | |||||
| petId: number; | |||||
| }; | |||||
| type deleteUserParams = { | |||||
| /** The name that needs to be deleted */ | |||||
| username: string; | |||||
| }; | |||||
| type findPetsByStatusParams = { | |||||
| /** Status values that need to be considered for filter */ | |||||
| status: ('available' | 'pending' | 'sold')[]; | |||||
| }; | |||||
| type findPetsByTagsParams = { | |||||
| /** Tags to filter by */ | |||||
| tags: string[]; | |||||
| }; | |||||
| type getOrderByIdParams = { | |||||
| /** ID of pet that needs to be fetched */ | |||||
| orderId: number; | |||||
| }; | |||||
| type getPetByIdParams = { | |||||
| /** ID of pet to return */ | |||||
| petId: number; | |||||
| }; | |||||
| type getUserByNameParams = { | |||||
| /** The name that needs to be fetched. Use user1 for testing. */ | |||||
| username: string; | |||||
| }; | |||||
| type loginUserParams = { | |||||
| /** The user name for login */ | |||||
| username: string; | |||||
| /** The password for login in clear text */ | |||||
| password: string; | |||||
| }; | |||||
| type Order = { | |||||
| id?: number; | |||||
| petId?: number; | |||||
| quantity?: number; | |||||
| shipDate?: string; | |||||
| /** Order Status */ | |||||
| status?: 'placed' | 'approved' | 'delivered'; | |||||
| complete?: boolean; | |||||
| }; | |||||
| type Pet = { | |||||
| id?: number; | |||||
| category?: Category; | |||||
| name: string; | |||||
| photoUrls: string[]; | |||||
| tags?: Tag[]; | |||||
| /** pet status in the store */ | |||||
| status?: 'available' | 'pending' | 'sold'; | |||||
| }; | |||||
| type Tag = { | |||||
| id?: number; | |||||
| name?: string; | |||||
| }; | |||||
| type updatePetWithFormParams = { | |||||
| /** ID of pet that needs to be updated */ | |||||
| petId: number; | |||||
| }; | |||||
| type updateUserParams = { | |||||
| /** name that need to be updated */ | |||||
| username: string; | |||||
| }; | |||||
| type uploadFileParams = { | |||||
| /** ID of pet to update */ | |||||
| petId: number; | |||||
| }; | |||||
| type User = { | |||||
| id?: number; | |||||
| username?: string; | |||||
| firstName?: string; | |||||
| lastName?: string; | |||||
| email?: string; | |||||
| password?: string; | |||||
| phone?: string; | |||||
| /** User Status */ | |||||
| userStatus?: number; | |||||
| }; | |||||
| } | |||||
| @@ -1,100 +0,0 @@ | |||||
| // @ts-ignore | |||||
| /* eslint-disable */ | |||||
| import { request } from '@umijs/max'; | |||||
| /** Create user This can only be done by the logged in user. POST /user */ | |||||
| export async function createUser(body: API.User, options?: { [key: string]: any }) { | |||||
| return request<any>('/user', { | |||||
| method: 'POST', | |||||
| data: body, | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| /** Get user by user name GET /user/${param0} */ | |||||
| export async function getUserByName( | |||||
| // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) | |||||
| params: API.getUserByNameParams, | |||||
| options?: { [key: string]: any }, | |||||
| ) { | |||||
| const { username: param0, ...queryParams } = params; | |||||
| return request<API.User>(`/user/${param0}`, { | |||||
| method: 'GET', | |||||
| params: { ...queryParams }, | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| /** Updated user This can only be done by the logged in user. PUT /user/${param0} */ | |||||
| export async function updateUser( | |||||
| // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) | |||||
| params: API.updateUserParams, | |||||
| body: API.User, | |||||
| options?: { [key: string]: any }, | |||||
| ) { | |||||
| const { username: param0, ...queryParams } = params; | |||||
| return request<any>(`/user/${param0}`, { | |||||
| method: 'PUT', | |||||
| params: { ...queryParams }, | |||||
| data: body, | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| /** Delete user This can only be done by the logged in user. DELETE /user/${param0} */ | |||||
| export async function deleteUser( | |||||
| // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) | |||||
| params: API.deleteUserParams, | |||||
| options?: { [key: string]: any }, | |||||
| ) { | |||||
| const { username: param0, ...queryParams } = params; | |||||
| return request<any>(`/user/${param0}`, { | |||||
| method: 'DELETE', | |||||
| params: { ...queryParams }, | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| /** Creates list of users with given input array POST /user/createWithArray */ | |||||
| export async function createUsersWithArrayInput( | |||||
| body: API.User[], | |||||
| options?: { [key: string]: any }, | |||||
| ) { | |||||
| return request<any>('/user/createWithArray', { | |||||
| method: 'POST', | |||||
| data: body, | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| /** Creates list of users with given input array POST /user/createWithList */ | |||||
| export async function createUsersWithListInput(body: API.User[], options?: { [key: string]: any }) { | |||||
| return request<any>('/user/createWithList', { | |||||
| method: 'POST', | |||||
| data: body, | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| /** Logs user into the system GET /user/login */ | |||||
| export async function loginUser( | |||||
| // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) | |||||
| params: API.loginUserParams, | |||||
| options?: { [key: string]: any }, | |||||
| ) { | |||||
| return request<string>('/user/login', { | |||||
| method: 'GET', | |||||
| params: { | |||||
| ...params, | |||||
| }, | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| /** Logs out current logged in user session GET /user/logout */ | |||||
| export async function logoutUser(options?: { [key: string]: any }) { | |||||
| return request<any>('/user/logout', { | |||||
| method: 'GET', | |||||
| ...(options || {}), | |||||
| }); | |||||
| } | |||||
| @@ -188,4 +188,227 @@ declare namespace API { | |||||
| filter?: string; | filter?: string; | ||||
| sorter?: string; | sorter?: string; | ||||
| }; | }; | ||||
| type CurrentUser = UserInfo & { | |||||
| signature?: string; | |||||
| title?: string; | |||||
| group?: string; | |||||
| tags?: { key?: string; label?: string }[]; | |||||
| notifyCount?: number; | |||||
| unreadCount?: number; | |||||
| country?: string; | |||||
| access?: string; | |||||
| geographic?: { | |||||
| province?: { label?: string; key?: string }; | |||||
| city?: { label?: string; key?: string }; | |||||
| }; | |||||
| address?: string; | |||||
| phone?: string; | |||||
| roleNames?: { | |||||
| roleName?: string; | |||||
| }[]; | |||||
| }; | |||||
| type ErrorResponse = { | |||||
| /** 业务约定的错误码 */ | |||||
| errorCode: string; | |||||
| /** 业务上的错误信息 */ | |||||
| errorMessage?: string; | |||||
| /** 业务上的请求是否成功 */ | |||||
| success?: boolean; | |||||
| }; | |||||
| type FakeCaptcha = { | |||||
| code?: number; | |||||
| status?: string; | |||||
| }; | |||||
| type getFakeCaptchaParams = { | |||||
| /** 手机号 */ | |||||
| phone?: string; | |||||
| }; | |||||
| type LoginParams = { | |||||
| username?: string; | |||||
| password?: string; | |||||
| uuid?: string; | |||||
| autoLogin?: boolean; | |||||
| type?: string; | |||||
| }; | |||||
| type LoginResult = { | |||||
| code: number; | |||||
| msg?: string; | |||||
| type?: string; | |||||
| data?: { | |||||
| access_token?: string; | |||||
| expires_in?: number; | |||||
| }; | |||||
| }; | |||||
| type NoticeIconItem = { | |||||
| id?: string; | |||||
| extra?: string; | |||||
| key?: string; | |||||
| read?: boolean; | |||||
| avatar?: string; | |||||
| title?: string; | |||||
| status?: string; | |||||
| datetime?: string; | |||||
| description?: string; | |||||
| type?: NoticeIconItemType; | |||||
| }; | |||||
| type NoticeIconItemType = 'notification' | 'message' | 'event'; | |||||
| type NoticeIconList = { | |||||
| data?: NoticeIconItem[]; | |||||
| /** 列表的内容总数 */ | |||||
| total?: number; | |||||
| success?: boolean; | |||||
| }; | |||||
| type PageParams = { | |||||
| current?: number; | |||||
| pageSize?: number; | |||||
| }; | |||||
| type RuleList = { | |||||
| data?: RuleListItem[]; | |||||
| /** 列表的内容总数 */ | |||||
| total?: number; | |||||
| success?: boolean; | |||||
| }; | |||||
| type RuleListItem = { | |||||
| key?: number; | |||||
| disabled?: boolean; | |||||
| href?: string; | |||||
| avatar?: string; | |||||
| name?: string; | |||||
| owner?: string; | |||||
| desc?: string; | |||||
| callNo?: number; | |||||
| status?: number; | |||||
| updatedAt?: string; | |||||
| createdAt?: string; | |||||
| progress?: number; | |||||
| }; | |||||
| type ruleParams = { | |||||
| /** 当前的页码 */ | |||||
| current?: number; | |||||
| /** 页面的容量 */ | |||||
| pageSize?: number; | |||||
| }; | |||||
| type ApiResponse = { | |||||
| code?: number; | |||||
| type?: string; | |||||
| message?: string; | |||||
| }; | |||||
| type Category = { | |||||
| id?: number; | |||||
| name?: string; | |||||
| }; | |||||
| type deleteOrderParams = { | |||||
| /** ID of the order that needs to be deleted */ | |||||
| orderId: number; | |||||
| }; | |||||
| type deletePetParams = { | |||||
| api_key?: string; | |||||
| /** Pet id to delete */ | |||||
| petId: number; | |||||
| }; | |||||
| type deleteUserParams = { | |||||
| /** The name that needs to be deleted */ | |||||
| username: string; | |||||
| }; | |||||
| type findPetsByStatusParams = { | |||||
| /** Status values that need to be considered for filter */ | |||||
| status: ('available' | 'pending' | 'sold')[]; | |||||
| }; | |||||
| type findPetsByTagsParams = { | |||||
| /** Tags to filter by */ | |||||
| tags: string[]; | |||||
| }; | |||||
| type getOrderByIdParams = { | |||||
| /** ID of pet that needs to be fetched */ | |||||
| orderId: number; | |||||
| }; | |||||
| type getPetByIdParams = { | |||||
| /** ID of pet to return */ | |||||
| petId: number; | |||||
| }; | |||||
| type getUserByNameParams = { | |||||
| /** The name that needs to be fetched. Use user1 for testing. */ | |||||
| username: string; | |||||
| }; | |||||
| type loginUserParams = { | |||||
| /** The user name for login */ | |||||
| username: string; | |||||
| /** The password for login in clear text */ | |||||
| password: string; | |||||
| }; | |||||
| type Order = { | |||||
| id?: number; | |||||
| petId?: number; | |||||
| quantity?: number; | |||||
| shipDate?: string; | |||||
| /** Order Status */ | |||||
| status?: 'placed' | 'approved' | 'delivered'; | |||||
| complete?: boolean; | |||||
| }; | |||||
| type Pet = { | |||||
| id?: number; | |||||
| category?: Category; | |||||
| name: string; | |||||
| photoUrls: string[]; | |||||
| tags?: Tag[]; | |||||
| /** pet status in the store */ | |||||
| status?: 'available' | 'pending' | 'sold'; | |||||
| }; | |||||
| type Tag = { | |||||
| id?: number; | |||||
| name?: string; | |||||
| }; | |||||
| type updatePetWithFormParams = { | |||||
| /** ID of pet that needs to be updated */ | |||||
| petId: number; | |||||
| }; | |||||
| type updateUserParams = { | |||||
| /** name that need to be updated */ | |||||
| username: string; | |||||
| }; | |||||
| type uploadFileParams = { | |||||
| /** ID of pet to update */ | |||||
| petId: number; | |||||
| }; | |||||
| type User = { | |||||
| id?: number; | |||||
| username?: string; | |||||
| firstName?: string; | |||||
| lastName?: string; | |||||
| email?: string; | |||||
| password?: string; | |||||
| phone?: string; | |||||
| /** User Status */ | |||||
| userStatus?: number; | |||||
| }; | |||||
| } | } | ||||
| @@ -7,6 +7,17 @@ | |||||
| import { ExperimentStatus, TensorBoardStatus } from '@/enums'; | import { ExperimentStatus, TensorBoardStatus } from '@/enums'; | ||||
| import type { Settings as LayoutSettings } from '@ant-design/pro-components'; | import type { Settings as LayoutSettings } from '@ant-design/pro-components'; | ||||
| export type ClientInfo = { | |||||
| accessTokenUri: string; | |||||
| checkTokenUri: string; | |||||
| clientId: string; | |||||
| clientSecret: string; | |||||
| loginPage: string; | |||||
| logoutUri: string; | |||||
| redirectUri: string; | |||||
| userAuthorizationUri: string; | |||||
| }; | |||||
| // 全局初始状态类型 | // 全局初始状态类型 | ||||
| export type GlobalInitialState = { | export type GlobalInitialState = { | ||||
| settings?: Partial<LayoutSettings>; | settings?: Partial<LayoutSettings>; | ||||
| @@ -14,6 +25,7 @@ export type GlobalInitialState = { | |||||
| fetchUserInfo?: () => Promise<API.CurrentUser | undefined>; | fetchUserInfo?: () => Promise<API.CurrentUser | undefined>; | ||||
| loading?: boolean; | loading?: boolean; | ||||
| collapsed?: boolean; | collapsed?: boolean; | ||||
| clientInfo?: ClientInfo; | |||||
| }; | }; | ||||
| // 流水线全局参数 | // 流水线全局参数 | ||||
| @@ -79,7 +79,7 @@ export const canBeConvertToDate = (date?: Date | string | number | null): boolea | |||||
| * @return {string} The formatted date string. | * @return {string} The formatted date string. | ||||
| */ | */ | ||||
| export const formatDate = ( | export const formatDate = ( | ||||
| date?: Date | string | number | null, | |||||
| date: Date | string | number | null | undefined, | |||||
| format: string = 'YYYY-MM-DD HH:mm:ss', | format: string = 'YYYY-MM-DD HH:mm:ss', | ||||
| ): string => { | ): string => { | ||||
| if (date === undefined || date === null || date === '') { | if (date === undefined || date === null || date === '') { | ||||
| @@ -4,6 +4,7 @@ | |||||
| * @Description: 工具类 | * @Description: 工具类 | ||||
| */ | */ | ||||
| import { PageEnum } from '@/enums/pagesEnums'; | |||||
| import G6 from '@antv/g6'; | import G6 from '@antv/g6'; | ||||
| // 生成 8 位随机数 | // 生成 8 位随机数 | ||||
| @@ -21,7 +22,7 @@ export function getNameByCode(list: any[], code: any) { | |||||
| // 解析 json 字符串 | // 解析 json 字符串 | ||||
| export function parseJsonText(text?: string | null): any | null { | export function parseJsonText(text?: string | null): any | null { | ||||
| if (!text) { | |||||
| if (text === undefined || text === null || text === '') { | |||||
| return null; | return null; | ||||
| } | } | ||||
| try { | try { | ||||
| @@ -218,3 +219,25 @@ export const getGitUrl = (url: string, branch: string): string => { | |||||
| const gitUrl = url.replace(/\.git$/, ''); | const gitUrl = url.replace(/\.git$/, ''); | ||||
| return branch ? `${gitUrl}/tree/${branch}` : gitUrl; | return branch ? `${gitUrl}/tree/${branch}` : gitUrl; | ||||
| }; | }; | ||||
| // 判断是否需要登录 | |||||
| export const needAuth = (pathname: string) => { | |||||
| return pathname !== PageEnum.LOGIN && pathname !== PageEnum.Authorize; | |||||
| }; | |||||
| // 表格排序 | |||||
| export const tableSorter = (a: any, b: any) => { | |||||
| if (b === null || b === undefined) { | |||||
| return -1; | |||||
| } | |||||
| if (a === null || a === undefined) { | |||||
| return 1; | |||||
| } | |||||
| if (typeof a === 'number' && typeof b === 'number') { | |||||
| return a - b; | |||||
| } | |||||
| if (typeof a === 'string' && typeof b === 'string') { | |||||
| return a.localeCompare(b); | |||||
| } | |||||
| return 0; | |||||
| }; | |||||
| @@ -1,3 +1,5 @@ | |||||
| import { parseJsonText } from './index'; | |||||
| export default class LocalStorage { | export default class LocalStorage { | ||||
| // 登录的用户,包括用户名、密码和版本号 | // 登录的用户,包括用户名、密码和版本号 | ||||
| static readonly loginUserKey = 'login-user'; | static readonly loginUserKey = 'login-user'; | ||||
| @@ -10,13 +12,9 @@ export default class LocalStorage { | |||||
| return jsonStr; | return jsonStr; | ||||
| } | } | ||||
| if (jsonStr) { | if (jsonStr) { | ||||
| try { | |||||
| return JSON.parse(jsonStr); | |||||
| } catch (error) { | |||||
| return undefined; | |||||
| } | |||||
| return parseJsonText(jsonStr); | |||||
| } | } | ||||
| return undefined; | |||||
| return null; | |||||
| } | } | ||||
| static setItem(key: string, state?: any, isObject: boolean = false) { | static setItem(key: string, state?: any, isObject: boolean = false) { | ||||
| @@ -1,3 +1,5 @@ | |||||
| import { parseJsonText } from './index'; | |||||
| export default class SessionStorage { | export default class SessionStorage { | ||||
| // 用于新建镜像 | // 用于新建镜像 | ||||
| static readonly mirrorNameKey = 'mirror-name'; | static readonly mirrorNameKey = 'mirror-name'; | ||||
| @@ -7,8 +9,8 @@ export default class SessionStorage { | |||||
| static readonly serviceVersionInfoKey = 'service-version-info'; | static readonly serviceVersionInfoKey = 'service-version-info'; | ||||
| // 编辑器 url | // 编辑器 url | ||||
| static readonly editorUrlKey = 'editor-url'; | static readonly editorUrlKey = 'editor-url'; | ||||
| // 数据集、模型资源 | |||||
| static readonly resourceItemKey = 'resource-item'; | |||||
| // 客户端信息 | |||||
| static readonly clientInfoKey = 'client-info'; | |||||
| static getItem(key: string, isObject: boolean = false) { | static getItem(key: string, isObject: boolean = false) { | ||||
| const jsonStr = sessionStorage.getItem(key); | const jsonStr = sessionStorage.getItem(key); | ||||
| @@ -16,13 +18,9 @@ export default class SessionStorage { | |||||
| return jsonStr; | return jsonStr; | ||||
| } | } | ||||
| if (jsonStr) { | if (jsonStr) { | ||||
| try { | |||||
| return JSON.parse(jsonStr); | |||||
| } catch (error) { | |||||
| return undefined; | |||||
| } | |||||
| return parseJsonText(jsonStr); | |||||
| } | } | ||||
| return undefined; | |||||
| return null; | |||||
| } | } | ||||
| static setItem(key: string, state?: any, isObject: boolean = false) { | static setItem(key: string, state?: any, isObject: boolean = false) { | ||||
| @@ -1,34 +1,53 @@ | |||||
| /* | /* | ||||
| * @Author: 赵伟 | * @Author: 赵伟 | ||||
| * @Date: 2024-06-26 10:05:52 | * @Date: 2024-06-26 10:05:52 | ||||
| * @Description: 列表自定义 render | |||||
| * @Description: Table cell 自定义 render | |||||
| */ | */ | ||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { Tooltip } from 'antd'; | import { Tooltip } from 'antd'; | ||||
| import dayjs from 'dayjs'; | import dayjs from 'dayjs'; | ||||
| type TableCellFormatter = (value?: any | null) => string | undefined | null; | |||||
| export enum TableCellValueType { | |||||
| Index = 'Index', | |||||
| Text = 'Text', | |||||
| Date = 'Date', | |||||
| Array = 'Array', | |||||
| Link = 'Link', | |||||
| Custom = 'Custom', | |||||
| } | |||||
| // 字符串转换函数 | |||||
| export const stringFormatter: TableCellFormatter = (value?: any | null) => { | |||||
| return value; | |||||
| export type TableCellValueOptions<T> = { | |||||
| page?: number; // 类型为 Index 时有效 | |||||
| pageSize?: number; // 类型为 Index 时有效 | |||||
| property?: string; // 类型为 Array 时有效 | |||||
| dateFormat?: string; // 类型为 Date 时有效 | |||||
| onClick?: (record: T, e: React.MouseEvent) => void; // 类型为 Link 时有效 | |||||
| format?: (value: any | undefined | null, record: T, index: number) => string | undefined | null; // 类型为 Custom 时有效 | |||||
| }; | }; | ||||
| type TableCellFormatter = (value: any | undefined | null) => string | undefined | null; | |||||
| // 日期转换函数 | // 日期转换函数 | ||||
| export const dateFormatter: TableCellFormatter = (value?: any | null) => { | |||||
| if (value === undefined || value === null || value === '') { | |||||
| return null; | |||||
| } | |||||
| if (!dayjs(value).isValid()) { | |||||
| return null; | |||||
| } | |||||
| return formatDate(value); | |||||
| }; | |||||
| function formatDateText(dateFormat?: string): TableCellFormatter { | |||||
| return (value: any | undefined | null): ReturnType<TableCellFormatter> => { | |||||
| if (value === undefined || value === null || value === '') { | |||||
| return null; | |||||
| } | |||||
| if (!dayjs(value).isValid()) { | |||||
| return null; | |||||
| } | |||||
| return formatDate(value, dateFormat); | |||||
| }; | |||||
| } | |||||
| // 数组转换函数 | |||||
| export function arrayFormatter(property?: string) { | |||||
| return (value?: any | null): ReturnType<TableCellFormatter> => { | |||||
| /** | |||||
| * 数组转换函数,将数组元素转换为字符串,用逗号分隔 | |||||
| * @param {string} property 如果数组元素是对象,那么取数组元素的某个属性 | |||||
| * @returns {TableCellFormatter} Table cell 渲染函数 | |||||
| */ | |||||
| function formatArray(property?: string): TableCellFormatter { | |||||
| return (value: any | undefined | null): ReturnType<TableCellFormatter> => { | |||||
| if ( | if ( | ||||
| value === undefined || | value === undefined || | ||||
| value === null || | value === null || | ||||
| @@ -38,31 +57,75 @@ export function arrayFormatter(property?: string) { | |||||
| return null; | return null; | ||||
| } | } | ||||
| let list = value; | |||||
| if (property && typeof value[0] === 'object') { | |||||
| list = value.map((item) => item[property]); | |||||
| } | |||||
| const list = | |||||
| property && typeof value[0] === 'object' ? value.map((item) => item[property]) : value; | |||||
| return list.join(','); | return list.join(','); | ||||
| }; | }; | ||||
| } | } | ||||
| function tableCellRender(ellipsis: boolean = false, format: TableCellFormatter = stringFormatter) { | |||||
| return (value?: any | null) => { | |||||
| const text = format(value); | |||||
| function tableCellRender<T>( | |||||
| ellipsis: boolean = false, | |||||
| type: TableCellValueType = TableCellValueType.Text, | |||||
| options?: TableCellValueOptions<T>, | |||||
| ) { | |||||
| return (value: any | undefined | null, record: T, index: number) => { | |||||
| let text = value; | |||||
| switch (type) { | |||||
| case TableCellValueType.Index: | |||||
| text = (options?.page ?? 0) * (options?.pageSize ?? 0) + index + 1; | |||||
| break; | |||||
| case TableCellValueType.Text: | |||||
| case TableCellValueType.Link: | |||||
| text = value; | |||||
| break; | |||||
| case TableCellValueType.Date: | |||||
| text = formatDateText(options?.dateFormat)(value); | |||||
| break; | |||||
| case TableCellValueType.Array: | |||||
| text = formatArray(options?.property)(value); | |||||
| break; | |||||
| case TableCellValueType.Custom: | |||||
| text = options?.format?.(value, record, index); | |||||
| break; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| if (ellipsis && text) { | if (ellipsis && text) { | ||||
| return ( | return ( | ||||
| <Tooltip title={text} placement="topLeft" overlayStyle={{ maxWidth: '400px' }}> | <Tooltip title={text} placement="topLeft" overlayStyle={{ maxWidth: '400px' }}> | ||||
| {renderCell(text)} | |||||
| {renderCell(text, type === TableCellValueType.Link, record, options?.onClick)} | |||||
| </Tooltip> | </Tooltip> | ||||
| ); | ); | ||||
| } else { | } else { | ||||
| return renderCell(text); | |||||
| return renderCell(text, type === TableCellValueType.Link, record, options?.onClick); | |||||
| } | } | ||||
| }; | }; | ||||
| } | } | ||||
| function renderCell(text?: any | null) { | |||||
| function renderCell<T>( | |||||
| text: any | undefined | null, | |||||
| isLink: boolean, | |||||
| record: T, | |||||
| onClick?: (record: T, e: React.MouseEvent) => void, | |||||
| ) { | |||||
| return isLink ? renderLink(text, record, onClick) : renderText(text); | |||||
| } | |||||
| function renderText(text: any | undefined | null) { | |||||
| return <span>{text ?? '--'}</span>; | return <span>{text ?? '--'}</span>; | ||||
| } | } | ||||
| function renderLink<T>( | |||||
| text: any | undefined | null, | |||||
| record: T, | |||||
| onClick?: (record: T, e: React.MouseEvent) => void, | |||||
| ) { | |||||
| return ( | |||||
| <a className="kf-table-row-link" onClick={(e) => onClick?.(record, e)}> | |||||
| {text} | |||||
| </a> | |||||
| ); | |||||
| } | |||||
| export default tableCellRender; | export default tableCellRender; | ||||
| @@ -6,9 +6,11 @@ | |||||
| import { PageEnum } from '@/enums/pagesEnums'; | import { PageEnum } from '@/enums/pagesEnums'; | ||||
| import { removeAllPageCacheState } from '@/hooks/pageCacheState'; | import { removeAllPageCacheState } from '@/hooks/pageCacheState'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { type ClientInfo } from '@/types'; | |||||
| import { history } from '@umijs/max'; | import { history } from '@umijs/max'; | ||||
| import { Modal, message, type ModalFuncProps, type UploadFile } from 'antd'; | import { Modal, message, type ModalFuncProps, type UploadFile } from 'antd'; | ||||
| import { closeAllModals } from './modal'; | import { closeAllModals } from './modal'; | ||||
| import SessionStorage from './sessionStorage'; | |||||
| type ModalConfirmProps = ModalFuncProps & { | type ModalConfirmProps = ModalFuncProps & { | ||||
| isDelete?: boolean; | isDelete?: boolean; | ||||
| @@ -75,7 +77,7 @@ export const gotoLoginPage = (toHome: boolean = true) => { | |||||
| const { pathname, search } = location; | const { pathname, search } = location; | ||||
| const urlParams = new URLSearchParams(); | const urlParams = new URLSearchParams(); | ||||
| urlParams.append('redirect', pathname + search); | urlParams.append('redirect', pathname + search); | ||||
| const newSearch = toHome && pathname !== '/' ? '' : urlParams.toString(); | |||||
| const newSearch = toHome || pathname === '/' ? '' : urlParams.toString(); | |||||
| // console.log('pathname', pathname); | // console.log('pathname', pathname); | ||||
| // console.log('search', search); | // console.log('search', search); | ||||
| if (pathname !== PageEnum.LOGIN) { | if (pathname !== PageEnum.LOGIN) { | ||||
| @@ -88,6 +90,15 @@ export const gotoLoginPage = (toHome: boolean = true) => { | |||||
| } | } | ||||
| }; | }; | ||||
| export const gotoOAuth2 = () => { | |||||
| const clientInfo = SessionStorage.getItem(SessionStorage.clientInfoKey, true) as ClientInfo; | |||||
| if (clientInfo) { | |||||
| const { clientId, userAuthorizationUri } = clientInfo; | |||||
| const url = `${userAuthorizationUri}?client_id=${clientId}&response_type=code&grant_type=authorization_code`; | |||||
| location.replace(url); | |||||
| } | |||||
| }; | |||||
| /** | /** | ||||
| * 验证文件上传 | * 验证文件上传 | ||||
| * | * | ||||