feat: custom table with row children
This commit is contained in:
@@ -1,3 +1,42 @@
|
|||||||
# README
|
## Installation
|
||||||
|
|
||||||
gpustack ui
|
1. [Nodejs](https://nodejs.org/en) 16.0+(with NPM)
|
||||||
|
|
||||||
|
If you're on Mac
|
||||||
|
|
||||||
|
```
|
||||||
|
brew install node
|
||||||
|
```
|
||||||
|
|
||||||
|
2. [pnpm](https://pnpm.io/installation#using-npm)
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install -g pnpm
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Setup
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/seal-io/gpustack-ui/
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Install dependencies
|
||||||
|
|
||||||
|
```
|
||||||
|
cd gpustack-ui
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. Run development server (at http://localhost:9000)
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
2. build release
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|||||||
+29
-3
@@ -1,12 +1,38 @@
|
|||||||
import { RequestConfig } from '@umijs/max';
|
import { RequestConfig, history } from '@umijs/max';
|
||||||
import { requestConfig } from './request-config';
|
import { requestConfig } from './request-config';
|
||||||
|
import { queryCurrentUserState } from './services/profile/apis';
|
||||||
|
|
||||||
|
const loginPath = '/login';
|
||||||
// 运行时配置
|
// 运行时配置
|
||||||
|
|
||||||
// 全局初始化数据配置,用于 Layout 用户信息和权限初始化
|
// 全局初始化数据配置,用于 Layout 用户信息和权限初始化
|
||||||
// 更多信息见文档:https://umijs.org/docs/api/runtime-config#getinitialstate
|
// 更多信息见文档:https://umijs.org/docs/api/runtime-config#getinitialstate
|
||||||
export async function getInitialState(): Promise<{ name: string }> {
|
export async function getInitialState() {
|
||||||
console.log('getInitialState+++++++++++++++');
|
// 如果不是登录页面,执行
|
||||||
|
const { location } = history;
|
||||||
|
|
||||||
|
const fetchUserInfo = async () => {
|
||||||
|
try {
|
||||||
|
const data = await queryCurrentUserState({
|
||||||
|
skipErrorHandler: true
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
history.push(loginPath);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (![loginPath].includes(location.pathname)) {
|
||||||
|
const currentUser = await fetchUserInfo();
|
||||||
|
return {
|
||||||
|
fetchUserInfo,
|
||||||
|
name: 'admin',
|
||||||
|
...currentUser
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
|
fetchUserInfo,
|
||||||
name: 'admin'
|
name: 'admin'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
import React, { useContext } from 'react';
|
||||||
|
import RowContext from '../row-context';
|
||||||
|
import '../styles/cell.less';
|
||||||
|
import { SealColumnProps } from '../types';
|
||||||
|
|
||||||
|
const SealColumn: React.FC<SealColumnProps> = (props) => {
|
||||||
|
const row: Record<string, any> = useContext(RowContext);
|
||||||
|
const { dataIndex, render, align } = props;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames('cell', {
|
||||||
|
'cell-left': align === 'left',
|
||||||
|
'cell-center': align === 'center',
|
||||||
|
'cell-right': align === 'right'
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<span className="cell-content">
|
||||||
|
{render
|
||||||
|
? render(row[dataIndex], { ...row, dataIndex })
|
||||||
|
: row[dataIndex]}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(SealColumn);
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
import '../styles/header.less';
|
||||||
|
import { TableHeaderProps } from '../types';
|
||||||
|
|
||||||
|
const TableHeader: React.FC<TableHeaderProps> = (props) => {
|
||||||
|
const { title, style, align, firstCell, lastCell } = props;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{ ...style }}
|
||||||
|
className={classNames('table-header', {
|
||||||
|
'table-header-left': align === 'left',
|
||||||
|
'table-header-center': align === 'center',
|
||||||
|
'table-header-right': align === 'right',
|
||||||
|
'table-header-first': firstCell,
|
||||||
|
'table-header-last': lastCell
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className="table-header-cell">{title}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(TableHeader);
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
import { DownOutlined, RightOutlined } from '@ant-design/icons';
|
||||||
|
import { Button, Checkbox, Col, Row } from 'antd';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import RowContext from '../row-context';
|
||||||
|
import { RowContextProps, SealTableProps } from '../types';
|
||||||
|
|
||||||
|
const TableRow: React.FC<
|
||||||
|
RowContextProps &
|
||||||
|
Omit<SealTableProps, 'dataSource' | 'loading' | 'children' | 'empty'>
|
||||||
|
> = (props) => {
|
||||||
|
const {
|
||||||
|
record,
|
||||||
|
rowIndex,
|
||||||
|
expandable,
|
||||||
|
rowSelection,
|
||||||
|
rowKey,
|
||||||
|
columns,
|
||||||
|
onExpand
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
const [checked, setChecked] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (rowSelection) {
|
||||||
|
const { selectedRowKeys } = rowSelection;
|
||||||
|
if (selectedRowKeys.includes(record[rowKey])) {
|
||||||
|
setChecked(true);
|
||||||
|
} else {
|
||||||
|
setChecked(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [rowSelection]);
|
||||||
|
|
||||||
|
const handleRowExpand = () => {
|
||||||
|
setExpanded(!expanded);
|
||||||
|
onExpand?.(!expanded, record);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectChange = (e: any) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
// update selectedRowKeys
|
||||||
|
rowSelection?.onChange(
|
||||||
|
_.uniq([...rowSelection?.selectedRowKeys, record[rowKey]])
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// update selectedRowKeys
|
||||||
|
rowSelection?.onChange(
|
||||||
|
rowSelection?.selectedRowKeys.filter((key) => key !== record[rowKey])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderRowPrefix = () => {
|
||||||
|
if (expandable && rowSelection) {
|
||||||
|
return (
|
||||||
|
<div className="row-prefix-wrapper">
|
||||||
|
<span style={{ marginRight: 5 }}>
|
||||||
|
{_.isBoolean(expandable) ? (
|
||||||
|
<Button type="text" size="small" onClick={handleRowExpand}>
|
||||||
|
{expanded ? <DownOutlined /> : <RightOutlined />}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
expandable
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<Checkbox onChange={handleSelectChange} checked={checked}></Checkbox>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (expandable) {
|
||||||
|
return (
|
||||||
|
<div className="row-prefix-wrapper">
|
||||||
|
{_.isBoolean(expandable) ? (
|
||||||
|
<Button type="text" size="small" onClick={handleRowExpand}>
|
||||||
|
{expanded ? <DownOutlined /> : <RightOutlined />}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
expandable
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (rowSelection) {
|
||||||
|
return (
|
||||||
|
<div className="row-prefix-wrapper">
|
||||||
|
{
|
||||||
|
<Checkbox
|
||||||
|
onChange={handleSelectChange}
|
||||||
|
checked={checked}
|
||||||
|
></Checkbox>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RowContext.Provider value={{ ...record, rowIndex }}>
|
||||||
|
<div className="row-box">
|
||||||
|
<div
|
||||||
|
className={classNames('row-wrapper', {
|
||||||
|
'row-wrapper-selected': checked
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{renderRowPrefix()}
|
||||||
|
<Row className="seal-table-row">
|
||||||
|
{React.Children.map(columns, (child) => {
|
||||||
|
const { props: columnProps } = child as any;
|
||||||
|
if (React.isValidElement(child)) {
|
||||||
|
return (
|
||||||
|
<Col
|
||||||
|
key={`${columnProps.dataIndex}-${rowIndex}`}
|
||||||
|
span={columnProps.span}
|
||||||
|
>
|
||||||
|
{child}
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <Col span={columnProps.span}></Col>;
|
||||||
|
})}
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
{expanded && (
|
||||||
|
<div className="expanded-row">
|
||||||
|
<div className="expanded-row-content">rowchildren</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</RowContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TableRow;
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
import { RightOutlined } from '@ant-design/icons';
|
||||||
|
import { Button, Checkbox, Col, Empty, Row, Spin } from 'antd';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import TableHeader from './components/table-header';
|
||||||
|
import TableRow from './components/table-row';
|
||||||
|
import './styles/index.less';
|
||||||
|
import { SealColumnProps, SealTableProps } from './types';
|
||||||
|
|
||||||
|
const SealTable: React.FC<SealTableProps> = (props) => {
|
||||||
|
const { children, rowKey, onExpand, loading, expandable, rowSelection } =
|
||||||
|
props;
|
||||||
|
|
||||||
|
const [selectAll, setSelectAll] = useState(false);
|
||||||
|
const [indeterminate, setIndeterminate] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (rowSelection) {
|
||||||
|
const { selectedRowKeys } = rowSelection;
|
||||||
|
if (selectedRowKeys.length === 0) {
|
||||||
|
setSelectAll(false);
|
||||||
|
setIndeterminate(false);
|
||||||
|
} else if (selectedRowKeys.length === props.dataSource.length) {
|
||||||
|
setSelectAll(true);
|
||||||
|
setIndeterminate(false);
|
||||||
|
} else {
|
||||||
|
setSelectAll(false);
|
||||||
|
setIndeterminate(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [rowSelection]);
|
||||||
|
|
||||||
|
const handleSelectAllChange = (e: any) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
// update selectedRowKeys
|
||||||
|
rowSelection?.onChange(props.dataSource.map((record) => record[rowKey]));
|
||||||
|
setSelectAll(true);
|
||||||
|
setIndeterminate(false);
|
||||||
|
} else {
|
||||||
|
// update selectedRowKeys
|
||||||
|
rowSelection?.onChange([]);
|
||||||
|
setSelectAll(false);
|
||||||
|
setIndeterminate(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const renderHeaderPrefix = () => {
|
||||||
|
if (expandable && rowSelection) {
|
||||||
|
return (
|
||||||
|
<div className="header-row-prefix-wrapper">
|
||||||
|
<span style={{ marginRight: 5, padding: '0 14px' }}></span>
|
||||||
|
<Checkbox
|
||||||
|
onChange={handleSelectAllChange}
|
||||||
|
indeterminate={indeterminate}
|
||||||
|
checked={selectAll}
|
||||||
|
></Checkbox>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (expandable) {
|
||||||
|
return (
|
||||||
|
<div className="header-row-prefix-wrapper">
|
||||||
|
{_.isBoolean(expandable) ? (
|
||||||
|
<Button type="text" size="small">
|
||||||
|
<RightOutlined />
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
expandable
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (rowSelection) {
|
||||||
|
return (
|
||||||
|
<div className="header-row-prefix-wrapper">{<Checkbox></Checkbox>}</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderHeader = () => {
|
||||||
|
return (
|
||||||
|
<div className="header-row-wrapper">
|
||||||
|
{renderHeaderPrefix()}
|
||||||
|
<Row className="row">
|
||||||
|
{React.Children.map(props.children, (child, i) => {
|
||||||
|
const { props: columnProps } = child as any;
|
||||||
|
const { title, align, span, headerStyle } =
|
||||||
|
columnProps as SealColumnProps;
|
||||||
|
if (React.isValidElement(child)) {
|
||||||
|
return (
|
||||||
|
<Col span={span}>
|
||||||
|
<TableHeader
|
||||||
|
title={title}
|
||||||
|
style={headerStyle}
|
||||||
|
firstCell={i === 0}
|
||||||
|
lastCell={i === props.children.length - 1}
|
||||||
|
></TableHeader>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})}
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderContent = () => {
|
||||||
|
if (!props.dataSource.length) {
|
||||||
|
return <Empty></Empty>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="seal-table-content">
|
||||||
|
{props.dataSource.map((item, index) => {
|
||||||
|
return (
|
||||||
|
<TableRow
|
||||||
|
record={item}
|
||||||
|
rowIndex={index}
|
||||||
|
columns={children}
|
||||||
|
key={item[rowKey]}
|
||||||
|
rowSelection={rowSelection}
|
||||||
|
expandable={expandable}
|
||||||
|
rowKey={rowKey}
|
||||||
|
onExpand={onExpand}
|
||||||
|
></TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="seal-table-container">
|
||||||
|
{renderHeader()}
|
||||||
|
{loading ? (
|
||||||
|
<div className="spin">
|
||||||
|
<Spin></Spin>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
renderContent()
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SealTable;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const RowContext = React.createContext({});
|
||||||
|
|
||||||
|
export default RowContext;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
.cell {
|
||||||
|
padding: var(--ant-table-cell-padding-block)
|
||||||
|
var(--ant-table-cell-padding-inline);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
height: 70px;
|
||||||
|
&-left {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
&-right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
&-center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
.table-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
padding-inline: var(--ant-table-cell-padding-inline);
|
||||||
|
border-right: 1px solid var(--ant-table-header-split-color);
|
||||||
|
|
||||||
|
&-last {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
&-cell {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
&-left {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
&-right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
&-center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
.seal-table-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
.header-row-wrapper {
|
||||||
|
height: 40px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
.header-row-prefix-wrapper {
|
||||||
|
padding-left: var(--ant-table-cell-padding-inline);
|
||||||
|
}
|
||||||
|
.row {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.row-box {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-radius: var(--ant-table-header-border-radius);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.expanded-row {
|
||||||
|
background-color: var(--color-white-1);
|
||||||
|
padding: 16px 20px;
|
||||||
|
border: 1px solid var(--color-fill-1);
|
||||||
|
border-top: 0;
|
||||||
|
border-radius: 0 0 var(--ant-table-header-border-radius)
|
||||||
|
var(--ant-table-header-border-radius);
|
||||||
|
}
|
||||||
|
.row-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--color-fill-1);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--ant-table-row-hover-bg);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
&-selected {
|
||||||
|
background-color: var(--ant-table-row-selected-bg);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--ant-table-row-selected-hover-bg);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.row-prefix-wrapper {
|
||||||
|
padding-left: var(--ant-table-cell-padding-inline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.seal-table-row {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.spin {
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
min-height: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export interface SealColumnProps {
|
||||||
|
title: string;
|
||||||
|
render?: (text: any, record: any) => React.ReactNode;
|
||||||
|
dataIndex: string;
|
||||||
|
width?: number;
|
||||||
|
span: number;
|
||||||
|
align?: 'left' | 'center' | 'right';
|
||||||
|
headerStyle?: React.CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TableHeaderProps {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
title: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
firstCell?: boolean;
|
||||||
|
lastCell?: boolean;
|
||||||
|
align?: 'left' | 'center' | 'right';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RowSelectionProps {
|
||||||
|
selectedRowKeys: React.Key[];
|
||||||
|
onChange: (selectedRowKeys: React.Key[]) => void;
|
||||||
|
}
|
||||||
|
export interface SealTableProps {
|
||||||
|
rowSelection?: RowSelectionProps;
|
||||||
|
children: React.ReactNode[];
|
||||||
|
empty?: React.ReactNode;
|
||||||
|
expandable?: React.ReactNode;
|
||||||
|
dataSource: any[];
|
||||||
|
loading?: boolean;
|
||||||
|
onExpand?: (expanded: boolean, record: any) => void;
|
||||||
|
rowKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RowContextProps {
|
||||||
|
record: Record<string, any>;
|
||||||
|
columns: React.ReactNode[];
|
||||||
|
rowIndex: number;
|
||||||
|
}
|
||||||
@@ -15,6 +15,13 @@ html {
|
|||||||
--ant-color-fill-secondary: rgba(0, 0, 0, 0.06);
|
--ant-color-fill-secondary: rgba(0, 0, 0, 0.06);
|
||||||
--table-td-radius: 24px;
|
--table-td-radius: 24px;
|
||||||
--checkbox-border-radius: 4px;
|
--checkbox-border-radius: 4px;
|
||||||
|
--ant-table-cell-padding-inline: 16px;
|
||||||
|
--ant-table-cell-padding-block: 16px;
|
||||||
|
--ant-table-header-border-radius: 16px;
|
||||||
|
--ant-table-header-split-color: #f0f0f0;
|
||||||
|
--ant-table-row-selected-bg: #f0fff6;
|
||||||
|
--ant-table-row-selected-hover-bg: #d8f2e4;
|
||||||
|
--ant-table-row-hover-bg: #e6e6e6;
|
||||||
// --color-text-1: #000;
|
// --color-text-1: #000;
|
||||||
|
|
||||||
--seal-transition-func: cubic-bezier(0, 0, 1, 1);
|
--seal-transition-func: cubic-bezier(0, 0, 1, 1);
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ export default (props: any) => {
|
|||||||
loading: false,
|
loading: false,
|
||||||
setInitialState: null
|
setInitialState: null
|
||||||
};
|
};
|
||||||
|
console.log('initialInfo==========', initialInfo);
|
||||||
const { initialState, loading, setInitialState } = initialInfo;
|
const { initialState, loading, setInitialState } = initialInfo;
|
||||||
|
|
||||||
const userConfig = {
|
const userConfig = {
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import SealTable from '@/components/seal-table';
|
||||||
|
import SealColumn from '@/components/seal-table/components/seal-column';
|
||||||
|
import useTableRowSelection from '@/hooks/use-table-row-selection';
|
||||||
|
|
||||||
|
const Table = () => {
|
||||||
|
const dataSource = [{ name: 'test' }, { name: 'test2' }];
|
||||||
|
const rowSelection = useTableRowSelection();
|
||||||
|
return (
|
||||||
|
<SealTable
|
||||||
|
dataSource={dataSource}
|
||||||
|
rowKey="name"
|
||||||
|
loading={false}
|
||||||
|
rowSelection={rowSelection}
|
||||||
|
expandable={true}
|
||||||
|
>
|
||||||
|
<SealColumn title="Name" dataIndex="name" key="name" span={12} />
|
||||||
|
<SealColumn title="Status" dataIndex="status" key="status" span={12} />
|
||||||
|
</SealTable>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Table;
|
||||||
@@ -4,8 +4,14 @@ import { Tabs } from 'antd';
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import GPUs from './components/gpus';
|
import GPUs from './components/gpus';
|
||||||
import Nodes from './components/nodes';
|
import Nodes from './components/nodes';
|
||||||
|
import Test from './components/test';
|
||||||
|
|
||||||
const items: TabsProps['items'] = [
|
const items: TabsProps['items'] = [
|
||||||
|
{
|
||||||
|
key: 'test',
|
||||||
|
label: 'Test',
|
||||||
|
children: <Test />
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'nodes',
|
key: 'nodes',
|
||||||
label: 'Nodes',
|
label: 'Nodes',
|
||||||
@@ -18,7 +24,7 @@ const items: TabsProps['items'] = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
const Resources = () => {
|
const Resources = () => {
|
||||||
const [activeKey, setActiveKey] = useState('nodes');
|
const [activeKey, setActiveKey] = useState('test');
|
||||||
|
|
||||||
const handleChangeTab = (key: string) => {
|
const handleChangeTab = (key: string) => {
|
||||||
setActiveKey(key);
|
setActiveKey(key);
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import PageTools from '@/components/page-tools';
|
import PageTools from '@/components/page-tools';
|
||||||
|
import SealTable from '@/components/seal-table';
|
||||||
|
import SealColumn from '@/components/seal-table/components/seal-column';
|
||||||
import { PageAction } from '@/config';
|
import { PageAction } from '@/config';
|
||||||
import type { PageActionType } from '@/config/types';
|
import type { PageActionType } from '@/config/types';
|
||||||
import useTableRowSelection from '@/hooks/use-table-row-selection';
|
import useTableRowSelection from '@/hooks/use-table-row-selection';
|
||||||
@@ -208,11 +210,12 @@ const Models: React.FC = () => {
|
|||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
></PageTools>
|
></PageTools>
|
||||||
<Table
|
<SealTable
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
|
expandable={true}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
pagination={{
|
pagination={{
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
@@ -224,11 +227,12 @@ const Models: React.FC = () => {
|
|||||||
onChange: handlePageChange
|
onChange: handlePageChange
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Column
|
<SealColumn
|
||||||
title="Model Name"
|
title="Model Name"
|
||||||
dataIndex="name"
|
dataIndex="name"
|
||||||
key="name"
|
key="name"
|
||||||
width={400}
|
width={400}
|
||||||
|
span={8}
|
||||||
render={(text, record) => {
|
render={(text, record) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -243,7 +247,8 @@ const Models: React.FC = () => {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Column
|
<SealColumn
|
||||||
|
span={8}
|
||||||
title="Create Time"
|
title="Create Time"
|
||||||
dataIndex="created_at"
|
dataIndex="created_at"
|
||||||
key="createTime"
|
key="createTime"
|
||||||
@@ -255,7 +260,8 @@ const Models: React.FC = () => {
|
|||||||
return dayjs(val).format('YYYY-MM-DD HH:mm:ss');
|
return dayjs(val).format('YYYY-MM-DD HH:mm:ss');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Column
|
<SealColumn
|
||||||
|
span={8}
|
||||||
title="Operation"
|
title="Operation"
|
||||||
key="operation"
|
key="operation"
|
||||||
render={(text, record) => {
|
render={(text, record) => {
|
||||||
@@ -282,7 +288,7 @@ const Models: React.FC = () => {
|
|||||||
) : null;
|
) : null;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Table>
|
</SealTable>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
<AddModal
|
<AddModal
|
||||||
open={openAddModal}
|
open={openAddModal}
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ const Models: React.FC = () => {
|
|||||||
<Column
|
<Column
|
||||||
title="Operation"
|
title="Operation"
|
||||||
key="operation"
|
key="operation"
|
||||||
render={(text, record) => {
|
render={(text, record: ListItem) => {
|
||||||
return (
|
return (
|
||||||
<Space>
|
<Space>
|
||||||
<Tooltip title="编辑">
|
<Tooltip title="编辑">
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export const requestConfig: RequestConfig = {
|
|||||||
console.log('errorThrower+++++++++++++++', res);
|
console.log('errorThrower+++++++++++++++', res);
|
||||||
},
|
},
|
||||||
errorHandler: (error: any, opts: any) => {
|
errorHandler: (error: any, opts: any) => {
|
||||||
|
if (opts?.skipErrorHandler) throw error;
|
||||||
const { message: errorMessage, response } = error;
|
const { message: errorMessage, response } = error;
|
||||||
const errMsg = response?.data?.message || errorMessage;
|
const errMsg = response?.data?.message || errorMessage;
|
||||||
message.error(errMsg);
|
message.error(errMsg);
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { request } from '@umijs/max';
|
||||||
|
|
||||||
|
export async function queryCurrentUserState(opts?: Record<string, any>) {
|
||||||
|
return request(`/users/me`, {
|
||||||
|
method: 'GET',
|
||||||
|
...opts
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user