| @@ -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 ( | ||||
| @@ -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 | ||||
| /> | /> | ||||
| @@ -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" /> | ||||
| )} | )} | ||||
| @@ -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> | ||||
| ))} | ))} | ||||
| </> | </> | ||||
| @@ -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> | ||||
| @@ -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> | ||||
| ); | ); | ||||
| }, | }, | ||||
| @@ -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 : '--'; | |||||
| }; | }; | ||||
| }; | }; | ||||