| @@ -0,0 +1,3 @@ | |||||
| .ant-table .ant-table-cell .kf-table-col-title { | |||||
| margin-bottom: 0; | |||||
| } | |||||
| @@ -0,0 +1,32 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2025-03-11 10:52:23 | |||||
| * @Description: 用于内容可变的表格类标题 | |||||
| */ | |||||
| import { Typography } from 'antd'; | |||||
| import classNames from 'classnames'; | |||||
| import './index.less'; | |||||
| type TableColTitleProps = { | |||||
| /** 标题 */ | |||||
| title: string; | |||||
| /** 自定义类名 */ | |||||
| className?: string; | |||||
| /** 自定义样式 */ | |||||
| style?: React.CSSProperties; | |||||
| }; | |||||
| function TableColTitle({ title, className, style }: TableColTitleProps) { | |||||
| return ( | |||||
| <Typography.Paragraph | |||||
| ellipsis={{ tooltip: title }} | |||||
| className={classNames('kf-table-col-title', className)} | |||||
| style={style} | |||||
| > | |||||
| {title} | |||||
| </Typography.Paragraph> | |||||
| ); | |||||
| } | |||||
| export default TableColTitle; | |||||
| @@ -69,35 +69,30 @@ function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps | |||||
| dataIndex: 'accuracy', | dataIndex: 'accuracy', | ||||
| key: 'accuracy', | key: 'accuracy', | ||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| ellipsis: { showTitle: false }, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '耗时', | title: '耗时', | ||||
| dataIndex: 'duration', | dataIndex: 'duration', | ||||
| key: 'duration', | key: 'duration', | ||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| ellipsis: { showTitle: false }, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '训练损失', | title: '训练损失', | ||||
| dataIndex: 'train_loss', | dataIndex: 'train_loss', | ||||
| key: 'train_loss', | key: 'train_loss', | ||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| ellipsis: { showTitle: false }, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '特征处理', | title: '特征处理', | ||||
| dataIndex: 'feature', | dataIndex: 'feature', | ||||
| key: 'feature', | key: 'feature', | ||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| ellipsis: { showTitle: false }, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '算法', | title: '算法', | ||||
| dataIndex: 'althorithm', | dataIndex: 'althorithm', | ||||
| key: 'althorithm', | key: 'althorithm', | ||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| ellipsis: { showTitle: false }, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '状态', | title: '状态', | ||||
| @@ -26,7 +26,7 @@ | |||||
| .startTime { | .startTime { | ||||
| .singleLine(); | .singleLine(); | ||||
| width: calc(20% + 10px); | |||||
| width: 200px; | |||||
| } | } | ||||
| .status { | .status { | ||||
| @@ -8,7 +8,7 @@ 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, Checkbox, ConfigProvider, Tooltip } from 'antd'; | |||||
| import { App, Button, Checkbox, ConfigProvider, Typography } from 'antd'; | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useEffect, useMemo } from 'react'; | import { useEffect, useMemo } from 'react'; | ||||
| import { ExperimentListType, experimentListConfig } from '../ExperimentList/config'; | import { ExperimentListType, experimentListConfig } from '../ExperimentList/config'; | ||||
| @@ -159,9 +159,9 @@ function ExperimentInstanceComponent({ | |||||
| {elapsedTime(item.create_time, item.finish_time)} | {elapsedTime(item.create_time, item.finish_time)} | ||||
| </div> | </div> | ||||
| <div className={styles.startTime}> | <div className={styles.startTime}> | ||||
| <Tooltip title={formatDate(item.create_time)}> | |||||
| <span>{formatDate(item.create_time)}</span> | |||||
| </Tooltip> | |||||
| <Typography.Text ellipsis={{ tooltip: formatDate(item.create_time) }}> | |||||
| {formatDate(item.create_time)} | |||||
| </Typography.Text> | |||||
| </div> | </div> | ||||
| <div className={styles.statusBox}> | <div className={styles.statusBox}> | ||||
| <img | <img | ||||
| @@ -261,16 +261,13 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| dataIndex: config.descProperty, | dataIndex: config.descProperty, | ||||
| key: 'ml_description', | key: 'ml_description', | ||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| ellipsis: { showTitle: false }, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '创建时间', | title: '创建时间', | ||||
| dataIndex: 'update_time', | dataIndex: 'update_time', | ||||
| key: 'update_time', | key: 'update_time', | ||||
| width: '20%', | |||||
| render: tableCellRender(true, TableCellValueType.Date), | |||||
| ellipsis: { showTitle: false }, | |||||
| width: 200, | |||||
| render: tableCellRender(false, TableCellValueType.Date), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '最近五次运行状态', | title: '最近五次运行状态', | ||||
| @@ -58,7 +58,7 @@ function ResourceVersion({ resourceType, info }: ResourceVersionProps) { | |||||
| title: '文件大小', | title: '文件大小', | ||||
| dataIndex: 'file_size', | dataIndex: 'file_size', | ||||
| key: 'file_size', | key: 'file_size', | ||||
| render: tableCellRender(), | |||||
| render: tableCellRender(false), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '更新时间', | title: '更新时间', | ||||
| @@ -99,7 +99,13 @@ function ResourceVersion({ resourceType, info }: ResourceVersionProps) { | |||||
| </Button> | </Button> | ||||
| </Flex> | </Flex> | ||||
| </Flex> | </Flex> | ||||
| <Table columns={columns} dataSource={fileList} pagination={false} rowKey="url" /> | |||||
| <Table | |||||
| columns={columns} | |||||
| dataSource={fileList} | |||||
| pagination={false} | |||||
| rowKey="url" | |||||
| tableLayout="fixed" | |||||
| /> | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -34,13 +34,13 @@ | |||||
| border-left: none !important; | border-left: none !important; | ||||
| } | } | ||||
| } | } | ||||
| .ant-table-tbody-virtual::after { | |||||
| border-bottom: none !important; | |||||
| } | |||||
| .ant-table-footer { | .ant-table-footer { | ||||
| padding: 0; | padding: 0; | ||||
| border: none !important; | border: none !important; | ||||
| } | } | ||||
| .ant-table-column-title { | |||||
| min-width: 0; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -4,6 +4,7 @@ | |||||
| * @Description: 实验对比 | * @Description: 实验对比 | ||||
| */ | */ | ||||
| import TableColTitle from '@/components/TableColTitle'; | |||||
| import { | import { | ||||
| getExpEvaluateInfosReq, | getExpEvaluateInfosReq, | ||||
| getExpMetricsReq, | getExpMetricsReq, | ||||
| @@ -13,7 +14,7 @@ import { tableSorter } from '@/utils'; | |||||
| import { to } from '@/utils/promise'; | 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 } from 'antd'; | |||||
| import classNames from 'classnames'; | 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'; | ||||
| @@ -154,7 +155,6 @@ function ExperimentComparison() { | |||||
| fixed: 'left', | fixed: 'left', | ||||
| align: 'center', | align: 'center', | ||||
| render: tableCellRender(true, TableCellValueType.Array), | render: tableCellRender(true, TableCellValueType.Array), | ||||
| ellipsis: { showTitle: false }, | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| @@ -162,17 +162,12 @@ function ExperimentComparison() { | |||||
| title: `${config.title}参数`, | title: `${config.title}参数`, | ||||
| align: 'center', | align: 'center', | ||||
| children: paramsNames.map((name) => ({ | children: paramsNames.map((name) => ({ | ||||
| title: ( | |||||
| <Tooltip title={name}> | |||||
| <span>{name}</span> | |||||
| </Tooltip> | |||||
| ), | |||||
| title: <TableColTitle title={name} />, | |||||
| dataIndex: ['params', name], | dataIndex: ['params', name], | ||||
| key: name, | key: name, | ||||
| width: 120, | |||||
| width: 150, | |||||
| align: 'center', | align: 'center', | ||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| ellipsis: { showTitle: false }, | |||||
| sorter: (a, b) => tableSorter(a.params?.[name], b.params?.[name]), | sorter: (a, b) => tableSorter(a.params?.[name], b.params?.[name]), | ||||
| showSorterTooltip: false, | showSorterTooltip: false, | ||||
| })), | })), | ||||
| @@ -181,17 +176,12 @@ function ExperimentComparison() { | |||||
| title: `${config.title}指标`, | title: `${config.title}指标`, | ||||
| align: 'center', | align: 'center', | ||||
| children: metricsNames.map((name) => ({ | children: metricsNames.map((name) => ({ | ||||
| title: ( | |||||
| <Tooltip title={name}> | |||||
| <span>{name}</span> | |||||
| </Tooltip> | |||||
| ), | |||||
| title: <TableColTitle title={name} />, | |||||
| dataIndex: ['metrics', name], | dataIndex: ['metrics', name], | ||||
| key: name, | key: name, | ||||
| width: 120, | |||||
| width: 150, | |||||
| align: 'center', | align: 'center', | ||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| ellipsis: { showTitle: false }, | |||||
| sorter: (a, b) => tableSorter(a.metrics?.[name], b.metrics?.[name]), | sorter: (a, b) => tableSorter(a.metrics?.[name], b.metrics?.[name]), | ||||
| showSorterTooltip: false, | showSorterTooltip: false, | ||||
| })), | })), | ||||
| @@ -13,7 +13,7 @@ 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, Checkbox, ConfigProvider, Tooltip } from 'antd'; | |||||
| import { App, Button, Checkbox, ConfigProvider, Typography } from 'antd'; | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useEffect, useMemo } from 'react'; | import { useEffect, useMemo } from 'react'; | ||||
| import TensorBoardStatusCell from '../TensorBoardStatus'; | import TensorBoardStatusCell from '../TensorBoardStatus'; | ||||
| @@ -186,9 +186,9 @@ function ExperimentInstanceComponent({ | |||||
| <div className={styles.description}> | <div className={styles.description}> | ||||
| <div style={{ width: '50%' }}>{elapsedTime(item.create_time, item.finish_time)}</div> | <div style={{ width: '50%' }}>{elapsedTime(item.create_time, item.finish_time)}</div> | ||||
| <div style={{ width: '50%' }} className={styles.startTime}> | <div style={{ width: '50%' }} className={styles.startTime}> | ||||
| <Tooltip title={formatDate(item.create_time)}> | |||||
| <span>{formatDate(item.create_time)}</span> | |||||
| </Tooltip> | |||||
| <Typography.Text ellipsis={{ tooltip: formatDate(item.create_time) }}> | |||||
| {formatDate(item.create_time)} | |||||
| </Typography.Text> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div className={styles.statusBox}> | <div className={styles.statusBox}> | ||||
| @@ -383,7 +383,7 @@ function Experiment() { | |||||
| title: '实验名称', | title: '实验名称', | ||||
| dataIndex: 'name', | dataIndex: 'name', | ||||
| key: 'name', | key: 'name', | ||||
| render: tableCellRender(), | |||||
| render: tableCellRender(false), | |||||
| width: '16%', | width: '16%', | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -400,7 +400,6 @@ function Experiment() { | |||||
| dataIndex: 'description', | dataIndex: 'description', | ||||
| key: 'description', | key: 'description', | ||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| ellipsis: { showTitle: false }, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '最近五次运行状态', | title: '最近五次运行状态', | ||||
| @@ -1,14 +1,15 @@ | |||||
| import InfoGroup from '@/components/InfoGroup'; | import InfoGroup from '@/components/InfoGroup'; | ||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import TableColTitle from '@/components/TableColTitle'; | |||||
| import TrialStatusCell from '@/pages/HyperParameter/components/TrialStatusCell'; | import TrialStatusCell from '@/pages/HyperParameter/components/TrialStatusCell'; | ||||
| import { HyperParameterFile, HyperParameterTrial } from '@/pages/HyperParameter/types'; | import { HyperParameterFile, HyperParameterTrial } from '@/pages/HyperParameter/types'; | ||||
| import { getExpMetricsReq } from '@/services/hyperParameter'; | import { getExpMetricsReq } from '@/services/hyperParameter'; | ||||
| import { downLoadZip } from '@/utils/downloadfile'; | import { downLoadZip } from '@/utils/downloadfile'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | import tableCellRender, { TableCellValueType } from '@/utils/table'; | ||||
| import { App, Button, Table, Tooltip, Tree, type TableProps, type TreeDataNode } from 'antd'; | |||||
| import { App, Button, Table, Tree, type TableProps, type TreeDataNode } from 'antd'; | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useState } from 'react'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| const { DirectoryTree } = Tree; | const { DirectoryTree } = Tree; | ||||
| @@ -22,13 +23,16 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) { | |||||
| const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); | const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); | ||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| const [tableData, setTableData] = useState<HyperParameterTrial[]>([]); | const [tableData, setTableData] = useState<HyperParameterTrial[]>([]); | ||||
| const [loading, setLoading] = useState(true); | |||||
| const [loading, setLoading] = useState(false); | |||||
| // 防止 Tabs 卡顿 | // 防止 Tabs 卡顿 | ||||
| setTimeout(() => { | |||||
| setTableData(trialList); | |||||
| setLoading(false); | |||||
| }, 100); | |||||
| useEffect(() => { | |||||
| setLoading(true); | |||||
| setTimeout(() => { | |||||
| setTableData(trialList); | |||||
| setLoading(false); | |||||
| }, 500); | |||||
| }, []); | |||||
| // 计算 column | // 计算 column | ||||
| const first: HyperParameterTrial | undefined = trialList[0]; | const first: HyperParameterTrial | undefined = trialList[0]; | ||||
| @@ -43,6 +47,7 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) { | |||||
| dataIndex: 'index', | dataIndex: 'index', | ||||
| key: 'index', | key: 'index', | ||||
| width: 100, | width: 100, | ||||
| fixed: 'left', | |||||
| render: (_text, record, index: number) => { | render: (_text, record, index: number) => { | ||||
| return ( | return ( | ||||
| <div className={styles['cell-index']}> | <div className={styles['cell-index']}> | ||||
| @@ -53,28 +58,36 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) { | |||||
| }, | }, | ||||
| }, | }, | ||||
| { | { | ||||
| title: '运行次数', | |||||
| dataIndex: 'training_iteration', | |||||
| key: 'training_iteration', | |||||
| width: 120, | |||||
| render: tableCellRender(false), | |||||
| }, | |||||
| { | |||||
| title: '平均时长(秒)', | |||||
| dataIndex: 'time_avg', | |||||
| key: 'time_avg', | |||||
| width: 150, | |||||
| render: tableCellRender(false, TableCellValueType.Custom, { | |||||
| format: (value = 0) => Number(value).toFixed(2), | |||||
| }), | |||||
| ellipsis: { showTitle: false }, | |||||
| }, | |||||
| { | |||||
| title: '状态', | |||||
| dataIndex: 'status', | |||||
| key: 'status', | |||||
| width: 120, | |||||
| render: TrialStatusCell, | |||||
| title: '基本信息', | |||||
| align: 'center', | |||||
| children: [ | |||||
| { | |||||
| title: '运行次数', | |||||
| dataIndex: 'training_iteration', | |||||
| key: 'training_iteration', | |||||
| width: 120, | |||||
| fixed: 'left', | |||||
| render: tableCellRender(false), | |||||
| }, | |||||
| { | |||||
| title: '平均时长(秒)', | |||||
| dataIndex: 'time_avg', | |||||
| key: 'time_avg', | |||||
| width: 150, | |||||
| fixed: 'left', | |||||
| render: tableCellRender(false, TableCellValueType.Custom, { | |||||
| format: (value = 0) => Number(value).toFixed(2), | |||||
| }), | |||||
| }, | |||||
| { | |||||
| title: '状态', | |||||
| dataIndex: 'status', | |||||
| key: 'status', | |||||
| width: 120, | |||||
| fixed: 'left', | |||||
| render: TrialStatusCell, | |||||
| }, | |||||
| ], | |||||
| }, | }, | ||||
| ]; | ]; | ||||
| @@ -85,18 +98,12 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) { | |||||
| key: 'config', | key: 'config', | ||||
| align: 'center', | align: 'center', | ||||
| children: paramsNames.map((name) => ({ | children: paramsNames.map((name) => ({ | ||||
| title: ( | |||||
| <Tooltip title={name}> | |||||
| <span>{name}</span> | |||||
| </Tooltip> | |||||
| ), | |||||
| title: <TableColTitle title={name} />, | |||||
| dataIndex: ['config', name], | dataIndex: ['config', name], | ||||
| key: name, | key: name, | ||||
| width: 120, | width: 120, | ||||
| align: 'center', | align: 'center', | ||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| ellipsis: { showTitle: false }, | |||||
| showSorterTooltip: false, | |||||
| })), | })), | ||||
| }); | }); | ||||
| } | } | ||||
| @@ -108,18 +115,12 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) { | |||||
| key: 'metrics', | key: 'metrics', | ||||
| align: 'center', | align: 'center', | ||||
| children: metricNames.map((name) => ({ | children: metricNames.map((name) => ({ | ||||
| title: ( | |||||
| <Tooltip title={name}> | |||||
| <span>{name}</span> | |||||
| </Tooltip> | |||||
| ), | |||||
| title: <TableColTitle title={name} />, | |||||
| dataIndex: ['metric_analysis', name], | dataIndex: ['metric_analysis', name], | ||||
| key: name, | key: name, | ||||
| width: 120, | width: 120, | ||||
| align: 'center', | align: 'center', | ||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| ellipsis: { showTitle: false }, | |||||
| showSorterTooltip: false, | |||||
| })), | })), | ||||
| }); | }); | ||||
| } | } | ||||
| @@ -1,10 +1,11 @@ | |||||
| import TableColTitle from '@/components/TableColTitle'; | |||||
| import { | import { | ||||
| getReqParamName, | getReqParamName, | ||||
| type FormParameter, | type FormParameter, | ||||
| } from '@/pages/HyperParameter/components/CreateForm/utils'; | } from '@/pages/HyperParameter/components/CreateForm/utils'; | ||||
| import { HyperParameterData } from '@/pages/HyperParameter/types'; | import { HyperParameterData } from '@/pages/HyperParameter/types'; | ||||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | import tableCellRender, { TableCellValueType } from '@/utils/table'; | ||||
| import { Table, Tooltip, type TableProps } from 'antd'; | |||||
| import { Table, type TableProps } from 'antd'; | |||||
| import { useMemo } from 'react'; | import { useMemo } from 'react'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -43,16 +44,14 @@ function ParameterInfo({ info }: ParameterInfoProps) { | |||||
| dataIndex: 'name', | dataIndex: 'name', | ||||
| key: 'type', | key: 'type', | ||||
| width: '40%', | width: '40%', | ||||
| render: tableCellRender(true), | |||||
| ellipsis: { showTitle: false }, | |||||
| render: tableCellRender('auto'), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '参数类型', | title: '参数类型', | ||||
| dataIndex: 'type', | dataIndex: 'type', | ||||
| key: 'type', | key: 'type', | ||||
| width: '20%', | width: '20%', | ||||
| render: tableCellRender(true), | |||||
| ellipsis: { showTitle: false }, | |||||
| render: tableCellRender(false), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '取值范围', | title: '取值范围', | ||||
| @@ -64,7 +63,6 @@ function ParameterInfo({ info }: ParameterInfoProps) { | |||||
| return JSON.stringify(value); | return JSON.stringify(value); | ||||
| }, | }, | ||||
| }), | }), | ||||
| ellipsis: { showTitle: false }, | |||||
| }, | }, | ||||
| ]; | ]; | ||||
| @@ -72,16 +70,11 @@ function ParameterInfo({ info }: ParameterInfoProps) { | |||||
| runParameters.length > 0 | runParameters.length > 0 | ||||
| ? parameters.map(({ name }) => { | ? parameters.map(({ name }) => { | ||||
| return { | return { | ||||
| title: ( | |||||
| <Tooltip title={name}> | |||||
| <span>{name}</span> | |||||
| </Tooltip> | |||||
| ), | |||||
| title: <TableColTitle title={name} />, | |||||
| dataIndex: name, | dataIndex: name, | ||||
| key: name, | key: name, | ||||
| width: 150, | width: 150, | ||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| ellipsis: { showTitle: false }, | |||||
| }; | }; | ||||
| }) | }) | ||||
| : []; | : []; | ||||
| @@ -89,7 +82,14 @@ function ParameterInfo({ info }: ParameterInfoProps) { | |||||
| return ( | return ( | ||||
| <div className={styles['parameter-info']}> | <div className={styles['parameter-info']}> | ||||
| <div className={styles['parameter-info__title']}>超参数</div> | <div className={styles['parameter-info__title']}>超参数</div> | ||||
| <Table dataSource={parameters} columns={columns} rowKey="name" bordered pagination={false} /> | |||||
| <Table | |||||
| dataSource={parameters} | |||||
| columns={columns} | |||||
| rowKey="name" | |||||
| bordered | |||||
| pagination={false} | |||||
| tableLayout="fixed" | |||||
| /> | |||||
| <div className={styles['parameter-info__title']}>手动运行超参数</div> | <div className={styles['parameter-info__title']}>手动运行超参数</div> | ||||
| <Table | <Table | ||||
| dataSource={runParameters} | dataSource={runParameters} | ||||
| @@ -98,6 +98,7 @@ function ParameterInfo({ info }: ParameterInfoProps) { | |||||
| bordered | bordered | ||||
| pagination={false} | pagination={false} | ||||
| scroll={{ x: '100%' }} | scroll={{ x: '100%' }} | ||||
| tableLayout="fixed" | |||||
| /> | /> | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| @@ -192,7 +192,6 @@ function MirrorList() { | |||||
| key: 'description', | key: 'description', | ||||
| width: '35%', | width: '35%', | ||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| ellipsis: { showTitle: false }, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '创建时间', | title: '创建时间', | ||||
| @@ -21,6 +21,9 @@ | |||||
| border-left: none !important; | border-left: none !important; | ||||
| } | } | ||||
| } | } | ||||
| .ant-table-column-title { | |||||
| min-width: 0; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,10 +1,11 @@ | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import TableColTitle from '@/components/TableColTitle'; | |||||
| import { useCheck } from '@/hooks'; | import { useCheck } from '@/hooks'; | ||||
| import { getModelPageVersionsReq, getModelVersionsMetricsReq } from '@/services/dataset'; | import { getModelPageVersionsReq, getModelVersionsMetricsReq } from '@/services/dataset'; | ||||
| import { tableSorter } from '@/utils'; | import { tableSorter } from '@/utils'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import tableCellRender from '@/utils/table'; | import tableCellRender from '@/utils/table'; | ||||
| import { Checkbox, Table, Tooltip, type TablePaginationConfig, type TableProps } from 'antd'; | |||||
| import { Checkbox, Flex, Table, type TablePaginationConfig, type TableProps } from 'antd'; | |||||
| import { useEffect, useMemo, useState } from 'react'; | import { useEffect, useMemo, useState } from 'react'; | ||||
| import MetricsChart, { MetricsChatData } from '../MetricsChart'; | import MetricsChart, { MetricsChatData } from '../MetricsChart'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -174,17 +175,12 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr | |||||
| title: `训练参数`, | title: `训练参数`, | ||||
| align: 'center', | align: 'center', | ||||
| children: paramsNames.map((name) => ({ | children: paramsNames.map((name) => ({ | ||||
| title: ( | |||||
| <Tooltip title={name}> | |||||
| <span>{name}</span> | |||||
| </Tooltip> | |||||
| ), | |||||
| title: <TableColTitle title={name} />, | |||||
| dataIndex: ['params', name], | dataIndex: ['params', name], | ||||
| key: name, | key: name, | ||||
| width: 120, | |||||
| width: 150, | |||||
| align: 'center', | align: 'center', | ||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| ellipsis: { showTitle: false }, | |||||
| sorter: (a, b) => tableSorter(a.params?.[name], b.params?.[name]), | sorter: (a, b) => tableSorter(a.params?.[name], b.params?.[name]), | ||||
| showSorterTooltip: false, | showSorterTooltip: false, | ||||
| })), | })), | ||||
| @@ -197,14 +193,14 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr | |||||
| indeterminate={metricsIndeterminate} | indeterminate={metricsIndeterminate} | ||||
| onChange={checkAllMetrics} | onChange={checkAllMetrics} | ||||
| disabled={metricsNames.length === 0} | disabled={metricsNames.length === 0} | ||||
| ></Checkbox>{' '} | |||||
| <span>训练指标</span> | |||||
| ></Checkbox> | |||||
| <span style={{ marginLeft: 4 }}>训练指标</span> | |||||
| </div> | </div> | ||||
| ), | ), | ||||
| align: 'center', | align: 'center', | ||||
| children: metricsNames.map((name) => ({ | children: metricsNames.map((name) => ({ | ||||
| title: ( | title: ( | ||||
| <div> | |||||
| <Flex align="center"> | |||||
| <Checkbox | <Checkbox | ||||
| checked={isSingleMetricsChecked(name)} | checked={isSingleMetricsChecked(name)} | ||||
| onChange={(e) => { | onChange={(e) => { | ||||
| @@ -212,18 +208,15 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr | |||||
| checkSingleMetrics(name); | checkSingleMetrics(name); | ||||
| }} | }} | ||||
| onClick={(e) => e.stopPropagation()} | onClick={(e) => e.stopPropagation()} | ||||
| ></Checkbox>{' '} | |||||
| <Tooltip title={name}> | |||||
| <span>{name}</span> | |||||
| </Tooltip> | |||||
| </div> | |||||
| ></Checkbox> | |||||
| <TableColTitle style={{ marginLeft: 4 }} title={name} /> | |||||
| </Flex> | |||||
| ), | ), | ||||
| dataIndex: ['metrics', name], | dataIndex: ['metrics', name], | ||||
| key: name, | key: name, | ||||
| width: 120, | |||||
| width: 150, | |||||
| align: 'center', | align: 'center', | ||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| ellipsis: { showTitle: false }, | |||||
| sorter: (a, b) => tableSorter(a.metrics?.[name], b.metrics?.[name]), | sorter: (a, b) => tableSorter(a.metrics?.[name], b.metrics?.[name]), | ||||
| showSorterTooltip: false, | showSorterTooltip: false, | ||||
| })), | })), | ||||
| @@ -253,6 +246,8 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr | |||||
| }} | }} | ||||
| onChange={handleTableChange} | onChange={handleTableChange} | ||||
| rowKey="name" | rowKey="name" | ||||
| tableLayout="fixed" | |||||
| scroll={{ x: '100%' }} | |||||
| /> | /> | ||||
| </div> | </div> | ||||
| <div className={styles['model-metrics__chart']}> | <div className={styles['model-metrics__chart']}> | ||||
| @@ -285,7 +285,6 @@ function ServiceInfo() { | |||||
| key: 'model', | key: 'model', | ||||
| width: '20%', | width: '20%', | ||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| ellipsis: { showTitle: false }, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '状态', | title: '状态', | ||||
| @@ -300,7 +299,6 @@ function ServiceInfo() { | |||||
| key: 'image', | key: 'image', | ||||
| width: '20%', | width: '20%', | ||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| ellipsis: { showTitle: false }, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '副本数量', | title: '副本数量', | ||||
| @@ -317,7 +315,6 @@ function ServiceInfo() { | |||||
| render: tableCellRender(true, TableCellValueType.Custom, { | render: tableCellRender(true, TableCellValueType.Custom, { | ||||
| format: getResourceDescription, | format: getResourceDescription, | ||||
| }), | }), | ||||
| ellipsis: { showTitle: false }, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| @@ -152,6 +152,7 @@ const Pipeline = () => { | |||||
| title: '流水线名称', | title: '流水线名称', | ||||
| dataIndex: 'name', | dataIndex: 'name', | ||||
| key: 'name', | key: 'name', | ||||
| width: '50%', | |||||
| render: tableCellRender(false, TableCellValueType.Link, { | render: tableCellRender(false, TableCellValueType.Link, { | ||||
| onClick: gotoDetail, | onClick: gotoDetail, | ||||
| }), | }), | ||||
| @@ -160,19 +161,21 @@ const Pipeline = () => { | |||||
| title: '流水线描述', | title: '流水线描述', | ||||
| dataIndex: 'description', | dataIndex: 'description', | ||||
| key: 'description', | key: 'description', | ||||
| width: '50%', | |||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| ellipsis: { showTitle: false }, | |||||
| }, | }, | ||||
| { | { | ||||
| title: '创建时间', | title: '创建时间', | ||||
| dataIndex: 'create_time', | dataIndex: 'create_time', | ||||
| key: 'create_time', | key: 'create_time', | ||||
| width: 180, | |||||
| render: tableCellRender(false, TableCellValueType.Date), | render: tableCellRender(false, TableCellValueType.Date), | ||||
| }, | }, | ||||
| { | { | ||||
| title: '修改时间', | title: '修改时间', | ||||
| dataIndex: 'update_time', | dataIndex: 'update_time', | ||||
| key: 'update_time', | key: 'update_time', | ||||
| width: 180, | |||||
| render: tableCellRender(false, TableCellValueType.Date), | render: tableCellRender(false, TableCellValueType.Date), | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -4,7 +4,7 @@ import * as BasicInfoStories from './BasicInfo.stories'; | |||||
| // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export | // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export | ||||
| const meta = { | const meta = { | ||||
| title: 'Components/BasicTableInfo 表格基本信息', | |||||
| title: 'Components/BasicTableInfo 基本信息表格版', | |||||
| component: BasicTableInfo, | component: BasicTableInfo, | ||||
| parameters: { | parameters: { | ||||
| // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout | // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout | ||||
| @@ -31,7 +31,7 @@ export const RightArrow = () => <svg | |||||
| <path d="m11.1 7.35-5.5 5.5a.5.5 0 0 1-.7-.7L10.04 7 4.9 1.85a.5.5 0 1 1 .7-.7l5.5 5.5c.2.2.2.5 0 .7Z" /> | <path d="m11.1 7.35-5.5 5.5a.5.5 0 0 1-.7-.7L10.04 7 4.9 1.85a.5.5 0 1 1 .7-.7l5.5 5.5c.2.2.2.5 0 .7Z" /> | ||||
| </svg> | </svg> | ||||
| <Meta title="Configure your project" /> | |||||
| <Meta title="Documentation/Storybook" /> | |||||
| <div className="sb-container"> | <div className="sb-container"> | ||||
| <div className='sb-section-title'> | <div className='sb-section-title'> | ||||
| @@ -1,53 +0,0 @@ | |||||
| import type { Meta, StoryObj } from '@storybook/react'; | |||||
| import { fn } from '@storybook/test'; | |||||
| import { Button } from './Button'; | |||||
| // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export | |||||
| const meta = { | |||||
| title: 'Example/Button', | |||||
| component: Button, | |||||
| parameters: { | |||||
| // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout | |||||
| layout: 'centered', | |||||
| }, | |||||
| // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs | |||||
| tags: ['autodocs'], | |||||
| // More on argTypes: https://storybook.js.org/docs/api/argtypes | |||||
| argTypes: { | |||||
| backgroundColor: { control: 'color' }, | |||||
| }, | |||||
| // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args | |||||
| args: { onClick: fn() }, | |||||
| } satisfies Meta<typeof Button>; | |||||
| export default meta; | |||||
| type Story = StoryObj<typeof meta>; | |||||
| // More on writing stories with args: https://storybook.js.org/docs/writing-stories/args | |||||
| export const Primary: Story = { | |||||
| args: { | |||||
| primary: true, | |||||
| label: 'Button', | |||||
| }, | |||||
| }; | |||||
| export const Secondary: Story = { | |||||
| args: { | |||||
| label: 'Button', | |||||
| }, | |||||
| }; | |||||
| export const Large: Story = { | |||||
| args: { | |||||
| size: 'large', | |||||
| label: 'Button', | |||||
| }, | |||||
| }; | |||||
| export const Small: Story = { | |||||
| args: { | |||||
| size: 'small', | |||||
| label: 'Button', | |||||
| }, | |||||
| }; | |||||
| @@ -1,37 +0,0 @@ | |||||
| import React from 'react'; | |||||
| import './button.css'; | |||||
| export interface ButtonProps { | |||||
| /** Is this the principal call to action on the page? */ | |||||
| primary?: boolean; | |||||
| /** What background color to use */ | |||||
| backgroundColor?: string; | |||||
| /** How large should the button be? */ | |||||
| size?: 'small' | 'medium' | 'large'; | |||||
| /** Button contents */ | |||||
| label: string; | |||||
| /** Optional click handler */ | |||||
| onClick?: () => void; | |||||
| } | |||||
| /** Primary UI component for user interaction */ | |||||
| export const Button = ({ | |||||
| primary = false, | |||||
| size = 'medium', | |||||
| backgroundColor, | |||||
| label, | |||||
| ...props | |||||
| }: ButtonProps) => { | |||||
| const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary'; | |||||
| return ( | |||||
| <button | |||||
| type="button" | |||||
| className={['storybook-button', `storybook-button--${size}`, mode].join(' ')} | |||||
| style={{ backgroundColor }} | |||||
| {...props} | |||||
| > | |||||
| {label} | |||||
| </button> | |||||
| ); | |||||
| }; | |||||
| @@ -1,33 +0,0 @@ | |||||
| import type { Meta, StoryObj } from '@storybook/react'; | |||||
| import { fn } from '@storybook/test'; | |||||
| import { Header } from './Header'; | |||||
| const meta = { | |||||
| title: 'Example/Header', | |||||
| component: Header, | |||||
| // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs | |||||
| tags: ['autodocs'], | |||||
| parameters: { | |||||
| // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout | |||||
| layout: 'fullscreen', | |||||
| }, | |||||
| args: { | |||||
| onLogin: fn(), | |||||
| onLogout: fn(), | |||||
| onCreateAccount: fn(), | |||||
| }, | |||||
| } satisfies Meta<typeof Header>; | |||||
| export default meta; | |||||
| type Story = StoryObj<typeof meta>; | |||||
| export const LoggedIn: Story = { | |||||
| args: { | |||||
| user: { | |||||
| name: 'Jane Doe', | |||||
| }, | |||||
| }, | |||||
| }; | |||||
| export const LoggedOut: Story = {}; | |||||
| @@ -1,56 +0,0 @@ | |||||
| import React from 'react'; | |||||
| import { Button } from './Button'; | |||||
| import './header.css'; | |||||
| type User = { | |||||
| name: string; | |||||
| }; | |||||
| export interface HeaderProps { | |||||
| user?: User; | |||||
| onLogin?: () => void; | |||||
| onLogout?: () => void; | |||||
| onCreateAccount?: () => void; | |||||
| } | |||||
| export const Header = ({ user, onLogin, onLogout, onCreateAccount }: HeaderProps) => ( | |||||
| <header> | |||||
| <div className="storybook-header"> | |||||
| <div> | |||||
| <svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> | |||||
| <g fill="none" fillRule="evenodd"> | |||||
| <path | |||||
| d="M10 0h12a10 10 0 0110 10v12a10 10 0 01-10 10H10A10 10 0 010 22V10A10 10 0 0110 0z" | |||||
| fill="#FFF" | |||||
| /> | |||||
| <path | |||||
| d="M5.3 10.6l10.4 6v11.1l-10.4-6v-11zm11.4-6.2l9.7 5.5-9.7 5.6V4.4z" | |||||
| fill="#555AB9" | |||||
| /> | |||||
| <path | |||||
| d="M27.2 10.6v11.2l-10.5 6V16.5l10.5-6zM15.7 4.4v11L6 10l9.7-5.5z" | |||||
| fill="#91BAF8" | |||||
| /> | |||||
| </g> | |||||
| </svg> | |||||
| <h1>Acme</h1> | |||||
| </div> | |||||
| <div> | |||||
| {user ? ( | |||||
| <> | |||||
| <span className="welcome"> | |||||
| Welcome, <b>{user.name}</b>! | |||||
| </span> | |||||
| <Button size="small" onClick={onLogout} label="Log out" /> | |||||
| </> | |||||
| ) : ( | |||||
| <> | |||||
| <Button size="small" onClick={onLogin} label="Log in" /> | |||||
| <Button primary size="small" onClick={onCreateAccount} label="Sign up" /> | |||||
| </> | |||||
| )} | |||||
| </div> | |||||
| </div> | |||||
| </header> | |||||
| ); | |||||
| @@ -1,32 +0,0 @@ | |||||
| import type { Meta, StoryObj } from '@storybook/react'; | |||||
| import { expect, userEvent, within } from '@storybook/test'; | |||||
| import { Page } from './Page'; | |||||
| const meta = { | |||||
| title: 'Example/Page', | |||||
| component: Page, | |||||
| parameters: { | |||||
| // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout | |||||
| layout: 'fullscreen', | |||||
| }, | |||||
| } satisfies Meta<typeof Page>; | |||||
| export default meta; | |||||
| type Story = StoryObj<typeof meta>; | |||||
| export const LoggedOut: Story = {}; | |||||
| // More on component testing: https://storybook.js.org/docs/writing-tests/component-testing | |||||
| export const LoggedIn: Story = { | |||||
| play: async ({ canvasElement }) => { | |||||
| const canvas = within(canvasElement); | |||||
| const loginButton = canvas.getByRole('button', { name: /Log in/i }); | |||||
| await expect(loginButton).toBeInTheDocument(); | |||||
| await userEvent.click(loginButton); | |||||
| await expect(loginButton).not.toBeInTheDocument(); | |||||
| const logoutButton = canvas.getByRole('button', { name: /Log out/i }); | |||||
| await expect(logoutButton).toBeInTheDocument(); | |||||
| }, | |||||
| }; | |||||
| @@ -1,73 +0,0 @@ | |||||
| import React from 'react'; | |||||
| import { Header } from './Header'; | |||||
| import './page.css'; | |||||
| type User = { | |||||
| name: string; | |||||
| }; | |||||
| export const Page: React.FC = () => { | |||||
| const [user, setUser] = React.useState<User>(); | |||||
| return ( | |||||
| <article> | |||||
| <Header | |||||
| user={user} | |||||
| onLogin={() => setUser({ name: 'Jane Doe' })} | |||||
| onLogout={() => setUser(undefined)} | |||||
| onCreateAccount={() => setUser({ name: 'Jane Doe' })} | |||||
| /> | |||||
| <section className="storybook-page"> | |||||
| <h2>Pages in Storybook</h2> | |||||
| <p> | |||||
| We recommend building UIs with a{' '} | |||||
| <a href="https://componentdriven.org" target="_blank" rel="noopener noreferrer"> | |||||
| <strong>component-driven</strong> | |||||
| </a>{' '} | |||||
| process starting with atomic components and ending with pages. | |||||
| </p> | |||||
| <p> | |||||
| Render pages with mock data. This makes it easy to build and review page states without | |||||
| needing to navigate to them in your app. Here are some handy patterns for managing page | |||||
| data in Storybook: | |||||
| </p> | |||||
| <ul> | |||||
| <li> | |||||
| Use a higher-level connected component. Storybook helps you compose such data from the | |||||
| "args" of child component stories | |||||
| </li> | |||||
| <li> | |||||
| Assemble data in the page component from your services. You can mock these services out | |||||
| using Storybook. | |||||
| </li> | |||||
| </ul> | |||||
| <p> | |||||
| Get a guided tutorial on component-driven development at{' '} | |||||
| <a href="https://storybook.js.org/tutorials/" target="_blank" rel="noopener noreferrer"> | |||||
| Storybook tutorials | |||||
| </a> | |||||
| . Read more in the{' '} | |||||
| <a href="https://storybook.js.org/docs" target="_blank" rel="noopener noreferrer"> | |||||
| docs | |||||
| </a> | |||||
| . | |||||
| </p> | |||||
| <div className="tip-wrapper"> | |||||
| <span className="tip">Tip</span> Adjust the width of the canvas with the{' '} | |||||
| <svg width="10" height="10" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"> | |||||
| <g fill="none" fillRule="evenodd"> | |||||
| <path | |||||
| d="M1.5 5.2h4.8c.3 0 .5.2.5.4v5.1c-.1.2-.3.3-.4.3H1.4a.5.5 0 01-.5-.4V5.7c0-.3.2-.5.5-.5zm0-2.1h6.9c.3 0 .5.2.5.4v7a.5.5 0 01-1 0V4H1.5a.5.5 0 010-1zm0-2.1h9c.3 0 .5.2.5.4v9.1a.5.5 0 01-1 0V2H1.5a.5.5 0 010-1zm4.3 5.2H2V10h3.8V6.2z" | |||||
| id="a" | |||||
| fill="#999" | |||||
| /> | |||||
| </g> | |||||
| </svg> | |||||
| Viewports addon in the toolbar | |||||
| </div> | |||||
| </section> | |||||
| </article> | |||||
| ); | |||||
| }; | |||||
| @@ -1,30 +0,0 @@ | |||||
| .storybook-button { | |||||
| display: inline-block; | |||||
| cursor: pointer; | |||||
| border: 0; | |||||
| border-radius: 3em; | |||||
| font-weight: 700; | |||||
| line-height: 1; | |||||
| font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; | |||||
| } | |||||
| .storybook-button--primary { | |||||
| background-color: #555ab9; | |||||
| color: white; | |||||
| } | |||||
| .storybook-button--secondary { | |||||
| box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset; | |||||
| background-color: transparent; | |||||
| color: #333; | |||||
| } | |||||
| .storybook-button--small { | |||||
| padding: 10px 16px; | |||||
| font-size: 12px; | |||||
| } | |||||
| .storybook-button--medium { | |||||
| padding: 11px 20px; | |||||
| font-size: 14px; | |||||
| } | |||||
| .storybook-button--large { | |||||
| padding: 12px 24px; | |||||
| font-size: 16px; | |||||
| } | |||||
| @@ -1,32 +0,0 @@ | |||||
| .storybook-header { | |||||
| display: flex; | |||||
| justify-content: space-between; | |||||
| align-items: center; | |||||
| border-bottom: 1px solid rgba(0, 0, 0, 0.1); | |||||
| padding: 15px 20px; | |||||
| font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; | |||||
| } | |||||
| .storybook-header svg { | |||||
| display: inline-block; | |||||
| vertical-align: top; | |||||
| } | |||||
| .storybook-header h1 { | |||||
| display: inline-block; | |||||
| vertical-align: top; | |||||
| margin: 6px 0 6px 10px; | |||||
| font-weight: 700; | |||||
| font-size: 20px; | |||||
| line-height: 1; | |||||
| } | |||||
| .storybook-header button + button { | |||||
| margin-left: 10px; | |||||
| } | |||||
| .storybook-header .welcome { | |||||
| margin-right: 10px; | |||||
| color: #333; | |||||
| font-size: 14px; | |||||
| } | |||||
| @@ -1,68 +0,0 @@ | |||||
| .storybook-page { | |||||
| margin: 0 auto; | |||||
| padding: 48px 20px; | |||||
| max-width: 600px; | |||||
| color: #333; | |||||
| font-size: 14px; | |||||
| line-height: 24px; | |||||
| font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; | |||||
| } | |||||
| .storybook-page h2 { | |||||
| display: inline-block; | |||||
| vertical-align: top; | |||||
| margin: 0 0 4px; | |||||
| font-weight: 700; | |||||
| font-size: 32px; | |||||
| line-height: 1; | |||||
| } | |||||
| .storybook-page p { | |||||
| margin: 1em 0; | |||||
| } | |||||
| .storybook-page a { | |||||
| color: inherit; | |||||
| } | |||||
| .storybook-page ul { | |||||
| margin: 1em 0; | |||||
| padding-left: 30px; | |||||
| } | |||||
| .storybook-page li { | |||||
| margin-bottom: 8px; | |||||
| } | |||||
| .storybook-page .tip { | |||||
| display: inline-block; | |||||
| vertical-align: top; | |||||
| margin-right: 10px; | |||||
| border-radius: 1em; | |||||
| background: #e7fdd8; | |||||
| padding: 4px 12px; | |||||
| color: #357a14; | |||||
| font-weight: 700; | |||||
| font-size: 11px; | |||||
| line-height: 12px; | |||||
| } | |||||
| .storybook-page .tip-wrapper { | |||||
| margin-top: 40px; | |||||
| margin-bottom: 40px; | |||||
| font-size: 13px; | |||||
| line-height: 20px; | |||||
| } | |||||
| .storybook-page .tip-wrapper svg { | |||||
| display: inline-block; | |||||
| vertical-align: top; | |||||
| margin-top: 3px; | |||||
| margin-right: 4px; | |||||
| width: 12px; | |||||
| height: 12px; | |||||
| } | |||||
| .storybook-page .tip-wrapper svg path { | |||||
| fill: #1ea7fd; | |||||
| } | |||||
| @@ -6,7 +6,7 @@ | |||||
| import { isEmpty } from '@/utils'; | import { isEmpty } from '@/utils'; | ||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { Tooltip } from 'antd'; | |||||
| import { Tooltip, TooltipProps, Typography } from 'antd'; | |||||
| import dayjs from 'dayjs'; | import dayjs from 'dayjs'; | ||||
| export enum TableCellValueType { | export enum TableCellValueType { | ||||
| @@ -65,7 +65,7 @@ function formatArray(property?: string): TableCellFormatter { | |||||
| } | } | ||||
| function tableCellRender<T>( | function tableCellRender<T>( | ||||
| ellipsis: boolean = false, | |||||
| ellipsis: boolean | TooltipProps | 'auto' = false, | |||||
| type: TableCellValueType = TableCellValueType.Text, | type: TableCellValueType = TableCellValueType.Text, | ||||
| options?: TableCellValueOptions<T>, | options?: TableCellValueOptions<T>, | ||||
| ) { | ) { | ||||
| @@ -92,41 +92,83 @@ function tableCellRender<T>( | |||||
| break; | break; | ||||
| } | } | ||||
| if (ellipsis && text) { | |||||
| if (ellipsis === 'auto' && text) { | |||||
| return renderCell(type, text, 'auto', record, options?.onClick); | |||||
| } else if (ellipsis && text) { | |||||
| const tooltipProps = typeof ellipsis === 'object' ? ellipsis : {}; | |||||
| const { overlayStyle, ...rest } = tooltipProps; | |||||
| return ( | return ( | ||||
| <Tooltip title={text} placement="topLeft" overlayStyle={{ maxWidth: '400px' }}> | |||||
| {renderCell(text, type === TableCellValueType.Link, record, options?.onClick)} | |||||
| <Tooltip {...rest} overlayStyle={{ maxWidth: 400, ...overlayStyle }} title={text}> | |||||
| {renderCell(type, text, true, record, options?.onClick)} | |||||
| </Tooltip> | </Tooltip> | ||||
| ); | ); | ||||
| } else { | } else { | ||||
| return renderCell(text, type === TableCellValueType.Link, record, options?.onClick); | |||||
| return renderCell(type, text, false, record, options?.onClick); | |||||
| } | } | ||||
| }; | }; | ||||
| } | } | ||||
| function renderCell<T>( | function renderCell<T>( | ||||
| type: TableCellValueType, | |||||
| text: any | undefined | null, | text: any | undefined | null, | ||||
| isLink: boolean, | |||||
| ellipsis: boolean | 'auto', | |||||
| record: T, | record: T, | ||||
| onClick?: (record: T, e: React.MouseEvent) => void, | onClick?: (record: T, e: React.MouseEvent) => void, | ||||
| ) { | ) { | ||||
| return isLink ? renderLink(text, record, onClick) : renderText(text); | |||||
| } | |||||
| function renderText(text: any | undefined | null) { | |||||
| return <span>{!isEmpty(text) ? text : '--'}</span>; | |||||
| return type === TableCellValueType.Link | |||||
| ? renderLink(text, ellipsis, record, onClick) | |||||
| : renderText(text, ellipsis); | |||||
| } | } | ||||
| function renderLink<T>( | function renderLink<T>( | ||||
| text: any | undefined | null, | text: any | undefined | null, | ||||
| ellipsis: boolean | 'auto', | |||||
| record: T, | record: T, | ||||
| onClick?: (record: T, e: React.MouseEvent) => void, | onClick?: (record: T, e: React.MouseEvent) => void, | ||||
| ) { | ) { | ||||
| return ( | return ( | ||||
| <a className="kf-table-row-link" onClick={(e) => onClick?.(record, e)}> | <a className="kf-table-row-link" onClick={(e) => onClick?.(record, e)}> | ||||
| {text} | |||||
| {renderText(text, ellipsis)} | |||||
| </a> | </a> | ||||
| ); | ); | ||||
| } | } | ||||
| function renderText(text: any | undefined | null, ellipsis: boolean | 'auto') { | |||||
| if (ellipsis === 'auto') { | |||||
| return ( | |||||
| <Typography.Paragraph | |||||
| style={{ marginBottom: 0 }} | |||||
| ellipsis={{ | |||||
| tooltip: { | |||||
| title: text, | |||||
| destroyTooltipOnHide: true, | |||||
| overlayStyle: { maxWidth: 400 }, | |||||
| }, | |||||
| }} | |||||
| > | |||||
| {!isEmpty(text) ? text : '--'} | |||||
| </Typography.Paragraph> | |||||
| ); | |||||
| } | |||||
| return ( | |||||
| <span | |||||
| style={ | |||||
| ellipsis | |||||
| ? { | |||||
| whiteSpace: 'nowrap', | |||||
| overflow: 'hidden', | |||||
| textOverflow: 'ellipsis', | |||||
| wordBreak: 'break-all', | |||||
| display: 'inline-block', | |||||
| maxWidth: '100%', | |||||
| } | |||||
| : undefined | |||||
| } | |||||
| > | |||||
| {!isEmpty(text) ? text : '--'} | |||||
| </span> | |||||
| ); | |||||
| } | |||||
| export default tableCellRender; | export default tableCellRender; | ||||