Browse Source

feat: 修改FormInfo & ParameterSelect 组件

pull/174/head
cp3hnu 11 months ago
parent
commit
3b6d948c3e
7 changed files with 156 additions and 53 deletions
  1. +20
    -6
      react-ui/src/components/FormInfo/index.tsx
  2. +41
    -24
      react-ui/src/components/ParameterSelect/index.tsx
  3. +8
    -6
      react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx
  4. +13
    -4
      react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx
  5. +18
    -1
      react-ui/src/stories/FormInfo.stories.tsx
  6. +53
    -9
      react-ui/src/stories/ParameterSelect.stories.tsx
  7. +3
    -3
      react-ui/src/utils/format.ts

+ 20
- 6
react-ui/src/components/FormInfo/index.tsx View File

@@ -1,5 +1,5 @@
import { formatEnum } from '@/utils/format'; import { formatEnum } from '@/utils/format';
import { Typography } from 'antd';
import { Typography, type SelectProps } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import './index.less'; import './index.less';


@@ -13,7 +13,9 @@ type FormInfoProps = {
/** 是否是下拉框 */ /** 是否是下拉框 */
select?: boolean; select?: boolean;
/** 下拉框数据 */ /** 下拉框数据 */
options?: { label: string; value: any }[];
options?: SelectProps['options'];
/** 自定义节点 label、value 的字段 */
fieldNames?: SelectProps['fieldNames'];
/** 自定义类名 */ /** 自定义类名 */
className?: string; className?: string;
/** 自定义样式 */ /** 自定义样式 */
@@ -26,17 +28,29 @@ type FormInfoProps = {
function FormInfo({ function FormInfo({
value, value,
valuePropName, valuePropName,
className,
select,
textArea = false,
select = false,
options, options,
fieldNames,
className,
style, style,
textArea = false,
}: FormInfoProps) { }: FormInfoProps) {
let showValue = value; let showValue = value;
if (value && typeof value === 'object' && valuePropName) { if (value && typeof value === 'object' && valuePropName) {
showValue = value[valuePropName]; showValue = value[valuePropName];
} else if (select === true && options) { } else if (select === true && options) {
showValue = formatEnum(options)(value);
let _options: SelectProps['options'] = options;
if (fieldNames) {
_options = options.map((v) => {
return {
...v,
label: fieldNames.label && v[fieldNames.label],
value: fieldNames.value && v[fieldNames.value],
options: fieldNames.options && v[fieldNames.options],
};
});
}
showValue = formatEnum(_options)(value);
} }


return ( return (


+ 41
- 24
react-ui/src/components/ParameterSelect/index.tsx View File

@@ -7,43 +7,50 @@
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { Select, type SelectProps } from 'antd'; import { Select, type SelectProps } from 'antd';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import FormInfo from '../FormInfo';
import { paramSelectConfig } from './config'; import { paramSelectConfig } from './config';


/** 值类型 */
export type ParameterSelectValue = {
/** 类型,参数名是和后台保持一致的 */
item_type: 'dataset' | 'model' | 'service' | 'resource';
/** 值 */
value?: any;
/** 占位符 */
placeholder?: string;
/** 其它属性 */
type ParameterSelectObject = {
value: any;
[key: string]: any; [key: string]: any;
}; };


interface ParameterSelectProps extends SelectProps { interface ParameterSelectProps extends SelectProps {
/** 类型 */
dataType: 'dataset' | 'model' | 'service' | 'resource';
/** 是否只是展示信息 */
isInfo?: boolean;
/** 值 */ /** 值 */
value?: ParameterSelectValue;
value?: string | ParameterSelectObject;
/** 修改后回调 */ /** 修改后回调 */
onChange?: (value: ParameterSelectValue) => void;
onChange?: (value: string | ParameterSelectObject) => void;
} }


/** 参数选择器,支持资源规格、数据集、模型、服务 */ /** 参数选择器,支持资源规格、数据集、模型、服务 */
function ParameterSelect({ value, onChange, ...rest }: ParameterSelectProps) {
function ParameterSelect({
dataType,
isInfo = false,
value,
onChange,
...rest
}: ParameterSelectProps) {
const [options, setOptions] = useState([]); const [options, setOptions] = useState([]);
const valueNotNullable = value ?? ({} as ParameterSelectValue);
const { item_type } = valueNotNullable;
const propsConfig = paramSelectConfig[item_type];
const propsConfig = paramSelectConfig[dataType];
const valueText = typeof value === 'object' && value !== null ? value.value : value;


useEffect(() => { useEffect(() => {
getSelectOptions(); getSelectOptions();
}, []); }, []);


const hangleChange = (e: string) => {
onChange?.({
...valueNotNullable,
value: e,
});
const handleChange = (text: string) => {
if (typeof value === 'object' && value !== null) {
onChange?.({
...value,
value: text,
});
} else {
onChange?.(text);
}
}; };


// 获取下拉数据 // 获取下拉数据
@@ -58,16 +65,26 @@ function ParameterSelect({ value, onChange, ...rest }: ParameterSelectProps) {
} }
}; };


if (isInfo) {
return (
<FormInfo
select
value={valueText}
options={options}
fieldNames={propsConfig?.fieldNames}
></FormInfo>
);
}

return ( return (
<Select <Select
{...rest} {...rest}
placeholder={valueNotNullable.placeholder || rest.placeholder}
filterOption={propsConfig?.filterOption} filterOption={propsConfig?.filterOption}
options={options} options={options}
fieldNames={propsConfig?.fieldNames} fieldNames={propsConfig?.fieldNames}
value={valueNotNullable.value}
optionFilterProp={propsConfig.optionFilterProp}
onChange={hangleChange}
optionFilterProp={propsConfig?.optionFilterProp}
value={valueText}
onChange={handleChange}
showSearch showSearch
allowClear allowClear
/> />


+ 8
- 6
react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx View File

@@ -3,7 +3,7 @@ import ParameterSelect from '@/components/ParameterSelect';
import SubAreaTitle from '@/components/SubAreaTitle'; import SubAreaTitle from '@/components/SubAreaTitle';
import { useComputingResource } from '@/hooks/resource'; import { useComputingResource } from '@/hooks/resource';
import { PipelineNodeModelSerialize } from '@/types'; import { PipelineNodeModelSerialize } from '@/types';
import { Form, Select } from 'antd';
import { Form } from 'antd';
import styles from './index.less'; import styles from './index.less';


type ExperimentParameterProps = { type ExperimentParameterProps = {
@@ -100,7 +100,7 @@ function ExperimentParameter({ nodeData }: ExperimentParameterProps) {
</Form.Item> </Form.Item>


<Form.Item label="启动命令" name="command"> <Form.Item label="启动命令" name="command">
<FormInfo multiline />
<FormInfo textArea />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="资源规格" label="资源规格"
@@ -112,9 +112,9 @@ function ExperimentParameter({ nodeData }: ExperimentParameterProps) {
}, },
]} ]}
> >
<Select
<FormInfo
select
options={resourceStandardList} options={resourceStandardList}
disabled
fieldNames={{ fieldNames={{
label: 'description', label: 'description',
value: 'standard', value: 'standard',
@@ -125,7 +125,7 @@ function ExperimentParameter({ nodeData }: ExperimentParameterProps) {
<FormInfo /> <FormInfo />
</Form.Item> </Form.Item>
<Form.Item label="环境变量" name="env_variables"> <Form.Item label="环境变量" name="env_variables">
<FormInfo multiline />
<FormInfo textArea />
</Form.Item> </Form.Item>
{controlStrategyList.map((item) => ( {controlStrategyList.map((item) => (
<Form.Item key={item.key} name={['control_strategy', item.key]} label={item.value.label}> <Form.Item key={item.key} name={['control_strategy', item.key]} label={item.value.label}>
@@ -146,7 +146,9 @@ function ExperimentParameter({ nodeData }: ExperimentParameterProps) {
rules={[{ required: item.value.require ? true : false }]} rules={[{ required: item.value.require ? true : false }]}
> >
{item.value.type === 'select' ? ( {item.value.type === 'select' ? (
<ParameterSelect disabled />
['dataset', 'model', 'service', 'resource'].includes(item.value.item_type) ? (
<ParameterSelect dataType={item.value.item_type as any} isInfo />
) : null
) : ( ) : (
<FormInfo valuePropName="showValue" /> <FormInfo valuePropName="showValue" />
)} )}


+ 13
- 4
react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx View File

@@ -502,7 +502,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
label={getLabel(item, 'control_strategy')} label={getLabel(item, 'control_strategy')}
rules={getFormRules(item)} rules={getFormRules(item)}
> >
<ParameterInput allowClear></ParameterInput>
<ParameterInput placeholder={item.value.placeholder} allowClear></ParameterInput>
</Form.Item> </Form.Item>
))} ))}
{/* 输入参数 */} {/* 输入参数 */}
@@ -523,9 +523,18 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
<div className={styles['pipeline-drawer__ref-row']}> <div className={styles['pipeline-drawer__ref-row']}>
<Form.Item name={['in_parameters', item.key]} rules={getFormRules(item)} noStyle> <Form.Item name={['in_parameters', item.key]} rules={getFormRules(item)} noStyle>
{item.value.type === 'select' ? ( {item.value.type === 'select' ? (
<ParameterSelect />
['dataset', 'model', 'service', 'resource'].includes(item.value.item_type) ? (
<ParameterSelect
dataType={item.value.item_type as any}
placeholder={item.value.placeholder}
/>
) : null
) : ( ) : (
<ParameterInput canInput={canInput(item.value)} allowClear></ParameterInput>
<ParameterInput
canInput={canInput(item.value)}
placeholder={item.value.placeholder}
allowClear
></ParameterInput>
)} )}
</Form.Item> </Form.Item>
{item.value.type === 'ref' && ( {item.value.type === 'ref' && (
@@ -563,7 +572,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
label={getLabel(item, 'out_parameters')} label={getLabel(item, 'out_parameters')}
rules={getFormRules(item)} rules={getFormRules(item)}
> >
<ParameterInput allowClear></ParameterInput>
<ParameterInput placeholder={item.value.placeholder} allowClear></ParameterInput>
</Form.Item> </Form.Item>
))} ))}
</> </>


+ 18
- 1
react-ui/src/stories/FormInfo.stories.tsx View File

@@ -38,7 +38,7 @@ export const InForm: Story = {
<Form <Form
name="form" name="form"
style={{ width: 300 }} style={{ width: 300 }}
labelCol={{ flex: '100px' }}
labelCol={{ flex: '150px' }}
initialValues={{ initialValues={{
text: '文本', text: '文本',
large_text: large_text:
@@ -49,6 +49,7 @@ export const InForm: Story = {
showValue: '对象文本', showValue: '对象文本',
}, },
select_text: 1, select_text: 1,
select_map_text: 1,
ant_input_text: ant_input_text:
'超长文本超长文本超长文本超长文本超长文本超长文本超长文本超长文本超长文本超长文本超长文本', '超长文本超长文本超长文本超长文本超长文本超长文本超长文本超长文本超长文本超长文本超长文本',
ant_select_text: 1, ant_select_text: 1,
@@ -82,6 +83,22 @@ export const InForm: Story = {
]} ]}
/> />
</Form.Item> </Form.Item>
<Form.Item label="模拟 Select Map" name="select_map_text">
<FormInfo
select
options={[
{
otherLabel:
'超长文本超长文本超长文本超长文本超长文本超长文本超长文本超长文本超长文本超长文本超长文本',
otherValue: 1,
},
]}
fieldNames={{
label: 'otherLabel',
value: 'otherValue',
}}
/>
</Form.Item>
<Form.Item label="Input" name="ant_input_text"> <Form.Item label="Input" name="ant_input_text">
<Input disabled /> <Input disabled />
</Form.Item> </Form.Item>


+ 53
- 9
react-ui/src/stories/ParameterSelect.stories.tsx View File

@@ -2,7 +2,7 @@ import ParameterSelect, { ParameterSelectValue } from '@/components/ParameterSel
import { useArgs } from '@storybook/preview-api'; import { useArgs } from '@storybook/preview-api';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { fn } from '@storybook/test'; import { fn } from '@storybook/test';
import { Col, Form, Row } from 'antd';
import { Button, Col, Form, Row } from 'antd';
import { http, HttpResponse } from 'msw'; import { http, HttpResponse } from 'msw';
import { computeResourceData, datasetListData, modelListData, serviceListData } from './mockData'; import { computeResourceData, datasetListData, modelListData, serviceListData } from './mockData';


@@ -46,12 +46,32 @@ type Story = StoryObj<typeof meta>;
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args // More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
export const Primary: Story = { export const Primary: Story = {
args: { args: {
value: {
item_type: 'dataset',
placeholder: '请选择数据集',
},
placeholder: '请选择',
dataType: 'dataset',
style: { width: 400 },
size: 'large',
},
render: function Render(args) {
const [{ value }, updateArgs] = useArgs();
function handleChange(value?: ParameterSelectValue) {
updateArgs({ value: value });
args.onChange?.(value);
}

return <ParameterSelect {...args} value={value} onChange={handleChange}></ParameterSelect>;
},
};

/** 值可以是一个对象,典型的是流水线节点对象 **PipelineNodeModelParameter** */
export const Object: Story = {
args: {
placeholder: '请选择',
dataType: 'dataset',
style: { width: 400 }, style: { width: 400 },
size: 'large', size: 'large',
value: {
value: undefined,
},
}, },
render: function Render(args) { render: function Render(args) {
const [{ value }, updateArgs] = useArgs(); const [{ value }, updateArgs] = useArgs();
@@ -65,6 +85,9 @@ export const Primary: Story = {
}; };


export const InForm: Story = { export const InForm: Story = {
args: {
dataType: 'dataset',
},
render: ({ onChange }) => { render: ({ onChange }) => {
return ( return (
<Form <Form
@@ -72,54 +95,75 @@ export const InForm: Story = {
labelCol={{ flex: '80px' }} labelCol={{ flex: '80px' }}
labelAlign="left" labelAlign="left"
size="large" size="large"
onFinish={(values) => {
console.log('onFinish', values);
}}
autoComplete="off" autoComplete="off"
initialValues={{ initialValues={{
dataset: { dataset: {
type: 'select',
item_type: 'dataset', item_type: 'dataset',
placeholder: '请选择数据集', placeholder: '请选择数据集',
label: '数据集',
}, },
model: { model: {
type: 'select',
item_type: 'model', item_type: 'model',
placeholder: '请选择模型', placeholder: '请选择模型',
label: '模型',
}, },
service: { service: {
type: 'select',
item_type: 'service', item_type: 'service',
placeholder: '请选择服务', placeholder: '请选择服务',
label: '服务',
}, },
resource: { resource: {
type: 'select',
item_type: 'resource', item_type: 'resource',
placeholder: '请选择计算资源', placeholder: '请选择计算资源',
label: '计算资源',
}, },
test: '1234',
}} }}
> >
<Row gutter={8}> <Row gutter={8}>
<Col span={10}> <Col span={10}>
<Form.Item label="数据集" name="dataset"> <Form.Item label="数据集" name="dataset">
<ParameterSelect onChange={onChange} />
<ParameterSelect dataType="dataset" placeholder="请选择数据集" onChange={onChange} />
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
<Row gutter={8}> <Row gutter={8}>
<Col span={10}> <Col span={10}>
<Form.Item label="模型" name="model"> <Form.Item label="模型" name="model">
<ParameterSelect onChange={onChange} />
<ParameterSelect dataType="model" placeholder="请选择模型" onChange={onChange} />
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
<Row gutter={8}> <Row gutter={8}>
<Col span={10}> <Col span={10}>
<Form.Item label="服务" name="service"> <Form.Item label="服务" name="service">
<ParameterSelect onChange={onChange} />
<ParameterSelect dataType="service" placeholder="请选择服务" onChange={onChange} />
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
<Row gutter={8}> <Row gutter={8}>
<Col span={10}> <Col span={10}>
<Form.Item label="计算资源" name="resource"> <Form.Item label="计算资源" name="resource">
<ParameterSelect onChange={onChange} />
<ParameterSelect
dataType="resource"
placeholder="请选择计算资源"
onChange={onChange}
/>
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
<Row gutter={8}>
<Button htmlType="submit" type="primary">
提交
</Button>
</Row>
</Form> </Form>
); );
}, },


+ 3
- 3
react-ui/src/utils/format.ts View File

@@ -122,14 +122,14 @@ export const formatBoolean = (value: boolean): string => {
return value ? '是' : '否'; return value ? '是' : '否';
}; };


type FormatEnumFunc = (value: string | number) => string;
type FormatEnumFunc = (value: string | number) => React.ReactNode;


// 格式化枚举 // 格式化枚举
export const formatEnum = ( export const formatEnum = (
options: { value: string | number; label: string }[],
options: { value?: string | number | null; label?: React.ReactNode }[],
): FormatEnumFunc => { ): FormatEnumFunc => {
return (value: string | number) => { return (value: string | number) => {
const option = options.find((item) => item.value === value); const option = options.find((item) => item.value === value);
return option ? option.label : '--';
return option && option.label ? option.label : '--';
}; };
}; };

Loading…
Cancel
Save