| @@ -0,0 +1 @@ | |||
| save-prefix=~ | |||
| @@ -60,7 +60,7 @@ | |||
| "@antv/hierarchy": "^0.6.12", | |||
| "@types/crypto-js": "^4.2.2", | |||
| "@umijs/route-utils": "^4.0.1", | |||
| "antd": "^5.4.4", | |||
| "antd": "~5.21.4", | |||
| "classnames": "^2.3.2", | |||
| "crypto-js": "^4.2.0", | |||
| "echarts": "^5.5.0", | |||
| @@ -111,7 +111,7 @@ | |||
| "umi-presets-pro": "^2.0.0" | |||
| }, | |||
| "engines": { | |||
| "node": ">=12.0.0" | |||
| "node": ">=16.14.0" | |||
| }, | |||
| "create-umi": { | |||
| "ignoreScript": [ | |||
| @@ -20,7 +20,7 @@ | |||
| height: 40px; | |||
| padding: 0 30px; | |||
| font-size: @font-size-content; | |||
| border-radius: 10px; | |||
| border-radius: 6px; | |||
| } | |||
| .ant-btn-default { | |||
| border-color: transparent; | |||
| @@ -168,7 +168,7 @@ | |||
| height: 40px; | |||
| padding: 0 30px; | |||
| font-size: @font-size-content; | |||
| border-radius: 10px; | |||
| border-radius: 6px; | |||
| } | |||
| .ant-btn-default { | |||
| border-color: transparent; | |||
| @@ -14,10 +14,30 @@ | |||
| &__table { | |||
| height: calc(100% - 60px); | |||
| padding: 20px 30px 0; | |||
| padding: 20px 30px; | |||
| background-color: white; | |||
| 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 { | |||
| .ant-table-container { | |||
| border: none !important; | |||
| @@ -34,6 +54,13 @@ | |||
| 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 { | |||
| getExpEvaluateInfosReq, | |||
| getExpMetricsReq, | |||
| @@ -8,7 +14,6 @@ import { to } from '@/utils/promise'; | |||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||
| import { useSearchParams } from '@umijs/max'; | |||
| import { App, Button, Table, /* TablePaginationConfig,*/ TableProps, Tooltip } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { useEffect, useMemo, useState } from 'react'; | |||
| import ExperimentStatusCell from '../components/ExperimentStatusCell'; | |||
| import { ComparisonType, comparisonConfig } from './config'; | |||
| @@ -26,40 +31,57 @@ type TableData = { | |||
| 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() { | |||
| const [searchParams] = useSearchParams(); | |||
| const comparisonType = searchParams.get('type') as ComparisonType; | |||
| const experimentId = searchParams.get('id'); | |||
| const [tableData, setTableData] = useState<TableData[]>([]); | |||
| // const [cacheState, setCacheState] = useCacheState(); | |||
| // const [total, setTotal] = useState(0); | |||
| const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); | |||
| // const [loading, setLoading] = useState(false); | |||
| const { message } = App.useApp(); | |||
| 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(() => { | |||
| getComparisonData(); | |||
| }, [experimentId]); | |||
| // 获取对比数据列表 | |||
| const getComparisonData = async () => { | |||
| // setLoading(true); | |||
| const getComparisonData = async (offset: string = '') => { | |||
| const request = | |||
| 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) { | |||
| // 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 | |||
| @@ -80,17 +102,10 @@ function ExperimentComparison() { | |||
| getExpMetrics(); | |||
| }; | |||
| // 分页切换 | |||
| // const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => { | |||
| // if (action === 'paginate') { | |||
| // setPagination(pagination); | |||
| // } | |||
| // // console.log(pagination, filters, sorter, action); | |||
| // }; | |||
| // 选择行 | |||
| const rowSelection: TableProps['rowSelection'] = { | |||
| type: 'checkbox', | |||
| columnWidth: 48, | |||
| fixed: 'left', | |||
| selectedRowKeys, | |||
| 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]; | |||
| return [ | |||
| { | |||
| @@ -192,29 +220,15 @@ function ExperimentComparison() { | |||
| 可视化对比 | |||
| </Button> | |||
| </div> | |||
| <div | |||
| className={classNames( | |||
| 'vertical-scroll-table-no-page', | |||
| styles['experiment-comparison__table'], | |||
| )} | |||
| > | |||
| <div className={styles['experiment-comparison__table']} ref={tableRef}> | |||
| <Table | |||
| dataSource={tableData} | |||
| columns={columns} | |||
| rowSelection={rowSelection} | |||
| scroll={{ y: 'calc(100% - 110px)', x: '100%' }} | |||
| scroll={{ y: tableHeight - 150, x: tableWidth - 60 }} | |||
| pagination={false} | |||
| bordered={true} | |||
| virtual | |||
| // onScroll={handleTableScroll} | |||
| // loading={loading} | |||
| // pagination={{ | |||
| // ...pagination, | |||
| // total: total, | |||
| // showSizeChanger: true, | |||
| // showQuickJumper: true, | |||
| // }} | |||
| // onChange={handleTableChange} | |||
| onScroll={handleTableScroll} | |||
| rowKey="run_id" | |||
| /> | |||
| </div> | |||
| @@ -126,7 +126,7 @@ function AddExperimentModal({ | |||
| <Button key="cancel" onClick={onCancel}> | |||
| 取消 | |||
| </Button>, | |||
| <Button key="submit" type="primary" onClick={() => handleRun(false)}> | |||
| <Button key="submit" type={isAdd ? 'primary' : 'default'} onClick={() => handleRun(false)}> | |||
| 确定 | |||
| </Button>, | |||
| ]; | |||
| @@ -47,13 +47,11 @@ | |||
| &__robot-img { | |||
| position: fixed; | |||
| right: 30px; | |||
| bottom: 20px; | |||
| right: 20px; | |||
| bottom: 90px; | |||
| z-index: 99; | |||
| width: 64px; | |||
| height: 64px; | |||
| background-color: white; | |||
| border-radius: 10px; | |||
| width: 56px; | |||
| height: 56px; | |||
| cursor: pointer; | |||
| } | |||
| } | |||
| @@ -188,4 +188,227 @@ declare namespace API { | |||
| filter?: 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; | |||
| }; | |||
| } | |||