| @@ -0,0 +1 @@ | |||||
| save-prefix=~ | |||||
| @@ -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": [ | ||||
| @@ -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; | ||||
| @@ -168,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; | ||||
| @@ -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; | ||||
| @@ -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,4 +1,10 @@ | |||||
| // import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-10-10 09:55:12 | |||||
| * @Description: 实验对比 | |||||
| */ | |||||
| import { useDomSize } from '@/hooks'; | |||||
| import { | import { | ||||
| getExpEvaluateInfosReq, | getExpEvaluateInfosReq, | ||||
| getExpMetricsReq, | getExpMetricsReq, | ||||
| @@ -8,7 +14,6 @@ import { to } from '@/utils/promise'; | |||||
| import tableCellRender, { TableCellValueType } 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'; | ||||
| @@ -26,40 +31,57 @@ type TableData = { | |||||
| params: Record<string, number>; | 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, { offset: '', limit: 50 })); | |||||
| // 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 +102,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['rowSelection'] = { | ||||
| type: 'checkbox', | type: 'checkbox', | ||||
| columnWidth: 48, | |||||
| fixed: 'left', | fixed: 'left', | ||||
| selectedRowKeys, | selectedRowKeys, | ||||
| onChange: (selectedRowKeys: React.Key[]) => { | onChange: (selectedRowKeys: React.Key[]) => { | ||||
| @@ -98,7 +113,20 @@ function ExperimentComparison() { | |||||
| }, | }, | ||||
| }; | }; | ||||
| const columns: TableProps<TableData>['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['columns'] = useMemo(() => { | |||||
| const first: TableData | undefined = tableData[0]; | const first: TableData | undefined = tableData[0]; | ||||
| return [ | return [ | ||||
| { | { | ||||
| @@ -192,29 +220,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% - 110px)', x: '100%' }} | |||||
| scroll={{ y: tableHeight - 150, x: tableWidth - 60 }} | |||||
| pagination={false} | pagination={false} | ||||
| bordered={true} | bordered={true} | ||||
| virtual | |||||
| // onScroll={handleTableScroll} | |||||
| // loading={loading} | |||||
| // pagination={{ | |||||
| // ...pagination, | |||||
| // total: total, | |||||
| // showSizeChanger: true, | |||||
| // showQuickJumper: true, | |||||
| // }} | |||||
| // onChange={handleTableChange} | |||||
| onScroll={handleTableScroll} | |||||
| rowKey="run_id" | rowKey="run_id" | ||||
| /> | /> | ||||
| </div> | </div> | ||||
| @@ -126,7 +126,7 @@ function AddExperimentModal({ | |||||
| <Button key="cancel" onClick={onCancel}> | <Button key="cancel" onClick={onCancel}> | ||||
| 取消 | 取消 | ||||
| </Button>, | </Button>, | ||||
| <Button key="submit" type="primary" onClick={() => handleRun(false)}> | |||||
| <Button key="submit" type={isAdd ? 'primary' : 'default'} onClick={() => handleRun(false)}> | |||||
| 确定 | 确定 | ||||
| </Button>, | </Button>, | ||||
| ]; | ]; | ||||
| @@ -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; | ||||
| } | } | ||||
| } | } | ||||
| @@ -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; | |||||
| }; | |||||
| } | } | ||||