在线演示

原生 input

零 UI 库依赖,3 列基础示例,使用原生 <input> 元素渲染每列。

训练任务
训练集群
框架版本
查看数据
[]
查看代码
import { useState } from 'react';
import { CascadingInput } from 'react-cascading-input';
import 'react-cascading-input/styles';
import type { ColumnConfig } from 'react-cascading-input';

const columns: ColumnConfig[] = [
  {
    title: '训练任务',
    dataIndex: 'product',
    width: 120,
    hasAdd: true,
    render: ({ value, onChange, title }) => (
      <input
        style={{ width: '100%', boxSizing: 'border-box', padding: '4px 8px',
                 border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14, height: 32 }}
        placeholder={`请输入${title}`}
        value={value}
        onChange={(e) => onChange(e.target.value)}
      />
    ),
  },
  {
    title: '训练集群',
    dataIndex: 'region',
    width: 120,
    hasAdd: true,
    render: ({ value, onChange, title }) => (
      <input
        style={{ width: '100%', boxSizing: 'border-box', padding: '4px 8px',
                 border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14, height: 32 }}
        placeholder={`请输入${title}`}
        value={value}
        onChange={(e) => onChange(e.target.value)}
      />
    ),
  },
  {
    title: '框架版本',
    dataIndex: 'spec',
    width: 180,
    hasAdd: false,
    render: ({ value, onChange, title }) => (
      <input
        style={{ width: '100%', boxSizing: 'border-box', padding: '4px 8px',
                 border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14, height: 32 }}
        placeholder={`请输入${title}`}
        value={value}
        onChange={(e) => onChange(e.target.value)}
      />
    ),
  },
];

export function App() {
  const [value, setValue] = useState<any[]>([]);
  return <CascadingInput columns={columns} value={value} onChange={setValue} />;
}

混合 input + select

同一组件中混合使用 <input><select>,展示 render prop 的灵活性。4 层嵌套,训练集群和框架版本使用下拉选择。同时演示了 levelisLeafnode 等 CellRenderProps 参数的实际用法:通过 level 给不同层级设置不同边框色,通过 node.children 显示子节点数量,通过 isLeaf 给末列加浅灰背景。

训练任务
训练集群
框架版本
训练阶段
查看数据
[]
查看代码
import { useState } from 'react';
import { CascadingInput } from 'react-cascading-input';
import 'react-cascading-input/styles';
import type { ColumnConfig } from 'react-cascading-input';

const selectStyle: React.CSSProperties = {
  width: '100%', boxSizing: 'border-box', padding: '4px 8px',
  border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14, height: 32, background: '#fff',
};

const inputStyle: React.CSSProperties = {
  width: '100%', boxSizing: 'border-box', padding: '4px 8px',
  border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14, height: 32,
};

/** 不同层级的边框色,通过 level 区分视觉深度 */
const levelColors = ['#d9d9d9', '#91d5ff', '#b7eb8f', '#ffd591'];

const columns: ColumnConfig[] = [
  {
    title: '训练任务',
    dataIndex: 'product',
    width: 120,
    hasAdd: true,
    render: ({ value, onChange, title, level }) => (
      <input
        style={{ ...inputStyle, borderColor: levelColors[level] || '#d9d9d9' }}
        placeholder={`请输入${title}`}
        value={value}
        onChange={(e) => onChange(e.target.value)}
      />
    ),
  },
  {
    title: '训练集群',
    dataIndex: 'region',
    width: 130,
    hasAdd: true,
    render: ({ value, onChange, title, level, node }) => (
      <div style={{ position: 'relative' }}>
        <select
          style={{ ...selectStyle, borderColor: levelColors[level] || '#d9d9d9' }}
          value={value || ''}
          onChange={(e) => onChange(e.target.value)}
        >
          <option value="">请选择{title}</option>
          <option value="beijing">北京</option>
          <option value="shanghai">上海</option>
          <option value="guangzhou">广州</option>
        </select>
        {node.children && node.children.length > 0 && (
          <span style={{ position: 'absolute', right: 24, top: 8, fontSize: 11, color: '#999', pointerEvents: 'none' }}>
            {node.children.length}          </span>
        )}
      </div>
    ),
  },
  {
    title: '框架版本',
    dataIndex: 'framework',
    width: 130,
    hasAdd: true,
    render: ({ value, onChange, title, level }) => (
      <select
        style={{ ...selectStyle, borderColor: levelColors[level] || '#d9d9d9' }}
        value={value || ''}
        onChange={(e) => onChange(e.target.value)}
      >
        <option value="">请选择{title}</option>
        <option value="pytorch">PyTorch</option>
        <option value="tensorflow">TensorFlow</option>
        <option value="mindspore">MindSpore</option>
      </select>
    ),
  },
  {
    title: '训练阶段',
    dataIndex: 'stage',
    width: 120,
    hasAdd: false,
    render: ({ value, onChange, title, level, isLeaf }) => (
      <select
        style={{
          ...selectStyle,
          borderColor: levelColors[level] || '#d9d9d9',
          background: isLeaf ? '#fafafa' : '#fff',
        }}
        value={value || ''}
        onChange={(e) => onChange(e.target.value)}
      >
        <option value="">请选择{title}</option>
        <option value="preprocess">预处理</option>
        <option value="training">训练中</option>
        <option value="eval">评估</option>
      </select>
    ),
  },
];

export function App() {
  const [value, setValue] = useState<any[]>([]);
  return <CascadingInput columns={columns} value={value} onChange={setValue} />;
}

5 层深度嵌套

5 列层级嵌套,验证组件在更深层级下的连线表现和数据管理。

项目
环境
服务
实例
配置
查看数据
[]
查看代码
import { useState } from 'react';
import { CascadingInput } from 'react-cascading-input';
import 'react-cascading-input/styles';
import type { ColumnConfig } from 'react-cascading-input';

const inputStyle: React.CSSProperties = {
  width: '100%', boxSizing: 'border-box', padding: '4px 8px',
  border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14, height: 32,
};

const mkInput = (title: string, dataIndex: string, width: number, hasAdd: boolean): ColumnConfig => ({
  title, dataIndex, width, hasAdd,
  render: ({ value, onChange }) => (
    <input style={inputStyle} placeholder={`请输入${title}`} value={value} onChange={(e) => onChange(e.target.value)} />
  ),
});

const columns: ColumnConfig[] = [
  mkInput('项目', 'project', 100, true),
  mkInput('环境', 'env', 100, true),
  mkInput('服务', 'service', 100, true),
  mkInput('实例', 'instance', 100, true),
  mkInput('配置', 'config', 120, false),
];

export function App() {
  const [value, setValue] = useState<any[]>([]);
  return <CascadingInput columns={columns} value={value} onChange={setValue} />;
}

自定义操作按钮

通过 addRenderdeleteRender 自定义"添加"和"删除"按钮的样式,按钮位置由组件固定控制(添加在单元格下方,删除在行末尾)。

训练任务
训练集群
框架版本
查看数据
[]
查看代码
import { useState } from 'react';
import { CascadingInput } from 'react-cascading-input';
import 'react-cascading-input/styles';
import type { ColumnConfig } from 'react-cascading-input';

const addBtnStyle: React.CSSProperties = {
  padding: '2px 12px', border: '1px solid #1890ff', borderRadius: 4,
  background: '#e6f7ff', color: '#1890ff', fontSize: 12, cursor: 'pointer',
};

const deleteBtnStyle: React.CSSProperties = {
  padding: '2px 12px', border: '1px solid #ff4d4f', borderRadius: 4,
  background: '#fff1f0', color: '#ff4d4f', fontSize: 12, cursor: 'pointer',
};

const columns: ColumnConfig[] = [
  {
    title: '训练任务',
    dataIndex: 'product',
    width: 120,
    hasAdd: true,
    addRender: ({ onClick }) => (
      <button type="button" style={addBtnStyle} onClick={onClick}>+ 添加</button>
    ),
    deleteRender: ({ onClick }) => (
      <button type="button" style={deleteBtnStyle} onClick={onClick}>删除</button>
    ),
    render: ({ value, onChange, title }) => (
      <input
        style={{ width: '100%', padding: '4px 8px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14, height: 32 }}
        placeholder={`请输入${title}`}
        value={value}
        onChange={(e) => onChange(e.target.value)}
      />
    ),
  },
  {
    title: '训练集群',
    dataIndex: 'region',
    width: 120,
    hasAdd: true,
    addRender: ({ onClick }) => (
      <button type="button" style={addBtnStyle} onClick={onClick}>+ 添加</button>
    ),
    deleteRender: ({ onClick }) => (
      <button type="button" style={deleteBtnStyle} onClick={onClick}>删除</button>
    ),
    render: ({ value, onChange, title }) => (
      <input
        style={{ width: '100%', padding: '4px 8px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14, height: 32 }}
        placeholder={`请输入${title}`}
        value={value}
        onChange={(e) => onChange(e.target.value)}
      />
    ),
  },
  {
    title: '框架版本',
    dataIndex: 'spec',
    width: 180,
    hasAdd: false,
    deleteRender: ({ onClick }) => (
      <button type="button" style={deleteBtnStyle} onClick={onClick}>删除</button>
    ),
    render: ({ value, onChange, title }) => (
      <input
        style={{ width: '100%', padding: '4px 8px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14, height: 32 }}
        placeholder={`请输入${title}`}
        value={value}
        onChange={(e) => onChange(e.target.value)}
      />
    ),
  },
];

export function App() {
  const [value, setValue] = useState<any[]>([]);
  return <CascadingInput columns={columns} value={value} onChange={setValue} />;
}

连线样式定制

实时切换连线样式(贝塞尔曲线 / 折线)、颜色、粗细,以及开启溯源动画(线条上粒子从子节点流向父节点)。

训练任务
训练集群
框架版本
查看数据
[]
查看代码
import { useState } from 'react';
import { CascadingInput } from 'react-cascading-input';
import 'react-cascading-input/styles';
import type { ColumnConfig } from 'react-cascading-input';

const columns: ColumnConfig[] = [
  {
    title: '训练任务', dataIndex: 'product', width: 120, hasAdd: true,
    render: ({ value, onChange }) => (
      <input placeholder="请输入训练任务" value={value} onChange={(e) => onChange(e.target.value)} />
    ),
  },
  {
    title: '训练集群', dataIndex: 'region', width: 120, hasAdd: true,
    render: ({ value, onChange }) => (
      <input placeholder="请输入训练集群" value={value} onChange={(e) => onChange(e.target.value)} />
    ),
  },
  {
    title: '框架版本', dataIndex: 'spec', width: 180, hasAdd: false,
    render: ({ value, onChange }) => (
      <input placeholder="请输入框架版本" value={value} onChange={(e) => onChange(e.target.value)} />
    ),
  },
];

export function App() {
  const [value, setValue] = useState<any[]>([]);
  const [lineStyle, setLineStyle] = useState<'curve' | 'straight'>('curve');
  const [lineColor, setLineColor] = useState('#d9d9d9');
  const [lineWidth, setLineWidth] = useState(1.5);
  const [showSource, setShowSource] = useState(false);

  const line = { style: lineStyle, color: lineColor, width: lineWidth, showSource };

  return (
    <>
      <div style={{ marginBottom: 16, display: 'flex', gap: 16, alignItems: 'center' }}>
        <label>
          连线样式:
          <select value={lineStyle} onChange={(e) => setLineStyle(e.target.value as 'curve' | 'straight')}>
            <option value="curve">贝塞尔曲线</option>
            <option value="straight">折线</option>
          </select>
        </label>
        <label>
          连线颜色:
          <input type="color" value={lineColor} onChange={(e) => setLineColor(e.target.value)} />
        </label>
        <label>
          连线粗细:
          <input type="range" min={0.5} max={4} step={0.5} value={lineWidth}
                 onChange={(e) => setLineWidth(Number(e.target.value))} />
        </label>
        <label>
          <input type="checkbox" checked={showSource} onChange={(e) => setShowSource(e.target.checked)} />
          溯源动画
        </label>
      </div>

      <CascadingInput
        columns={columns}
        value={value}
        onChange={setValue}
        line={line}
      />
    </>
  );
}