| @@ -1,5 +1,5 @@ | |||
| import { formatEnum } from '@/utils/format'; | |||
| import { Typography } from 'antd'; | |||
| import { Typography, type SelectProps } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import './index.less'; | |||
| @@ -13,7 +13,9 @@ type FormInfoProps = { | |||
| /** 是否是下拉框 */ | |||
| select?: boolean; | |||
| /** 下拉框数据 */ | |||
| options?: { label: string; value: any }[]; | |||
| options?: SelectProps['options']; | |||
| /** 自定义节点 label、value 的字段 */ | |||
| fieldNames?: SelectProps['fieldNames']; | |||
| /** 自定义类名 */ | |||
| className?: string; | |||
| /** 自定义样式 */ | |||
| @@ -26,17 +28,29 @@ type FormInfoProps = { | |||
| function FormInfo({ | |||
| value, | |||
| valuePropName, | |||
| className, | |||
| select, | |||
| textArea = false, | |||
| select = false, | |||
| options, | |||
| fieldNames, | |||
| className, | |||
| style, | |||
| textArea = false, | |||
| }: FormInfoProps) { | |||
| let showValue = value; | |||
| if (value && typeof value === 'object' && valuePropName) { | |||
| showValue = value[valuePropName]; | |||
| } 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 ( | |||
| @@ -7,43 +7,50 @@ | |||
| import { to } from '@/utils/promise'; | |||
| import { Select, type SelectProps } from 'antd'; | |||
| import { useEffect, useState } from 'react'; | |||
| import FormInfo from '../FormInfo'; | |||
| import { paramSelectConfig } from './config'; | |||
| /** 值类型 */ | |||
| export type ParameterSelectValue = { | |||
| /** 类型,参数名是和后台保持一致的 */ | |||
| item_type: 'dataset' | 'model' | 'service' | 'resource'; | |||
| /** 值 */ | |||
| value?: any; | |||
| /** 占位符 */ | |||
| placeholder?: string; | |||
| /** 其它属性 */ | |||
| type ParameterSelectObject = { | |||
| value: any; | |||
| [key: string]: any; | |||
| }; | |||
| 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 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(() => { | |||
| 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 ( | |||
| <Select | |||
| {...rest} | |||
| placeholder={valueNotNullable.placeholder || rest.placeholder} | |||
| filterOption={propsConfig?.filterOption} | |||
| options={options} | |||
| fieldNames={propsConfig?.fieldNames} | |||
| value={valueNotNullable.value} | |||
| optionFilterProp={propsConfig.optionFilterProp} | |||
| onChange={hangleChange} | |||
| optionFilterProp={propsConfig?.optionFilterProp} | |||
| value={valueText} | |||
| onChange={handleChange} | |||
| showSearch | |||
| allowClear | |||
| /> | |||
| @@ -3,7 +3,7 @@ import ParameterSelect from '@/components/ParameterSelect'; | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { useComputingResource } from '@/hooks/resource'; | |||
| import { PipelineNodeModelSerialize } from '@/types'; | |||
| import { Form, Select } from 'antd'; | |||
| import { Form } from 'antd'; | |||
| import styles from './index.less'; | |||
| type ExperimentParameterProps = { | |||
| @@ -100,7 +100,7 @@ function ExperimentParameter({ nodeData }: ExperimentParameterProps) { | |||
| </Form.Item> | |||
| <Form.Item label="启动命令" name="command"> | |||
| <FormInfo multiline /> | |||
| <FormInfo textArea /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="资源规格" | |||
| @@ -112,9 +112,9 @@ function ExperimentParameter({ nodeData }: ExperimentParameterProps) { | |||
| }, | |||
| ]} | |||
| > | |||
| <Select | |||
| <FormInfo | |||
| select | |||
| options={resourceStandardList} | |||
| disabled | |||
| fieldNames={{ | |||
| label: 'description', | |||
| value: 'standard', | |||
| @@ -125,7 +125,7 @@ function ExperimentParameter({ nodeData }: ExperimentParameterProps) { | |||
| <FormInfo /> | |||
| </Form.Item> | |||
| <Form.Item label="环境变量" name="env_variables"> | |||
| <FormInfo multiline /> | |||
| <FormInfo textArea /> | |||
| </Form.Item> | |||
| {controlStrategyList.map((item) => ( | |||
| <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 }]} | |||
| > | |||
| {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" /> | |||
| )} | |||
| @@ -502,7 +502,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||
| label={getLabel(item, 'control_strategy')} | |||
| rules={getFormRules(item)} | |||
| > | |||
| <ParameterInput allowClear></ParameterInput> | |||
| <ParameterInput placeholder={item.value.placeholder} allowClear></ParameterInput> | |||
| </Form.Item> | |||
| ))} | |||
| {/* 输入参数 */} | |||
| @@ -523,9 +523,18 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||
| <div className={styles['pipeline-drawer__ref-row']}> | |||
| <Form.Item name={['in_parameters', item.key]} rules={getFormRules(item)} noStyle> | |||
| {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> | |||
| {item.value.type === 'ref' && ( | |||
| @@ -563,7 +572,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||
| label={getLabel(item, 'out_parameters')} | |||
| rules={getFormRules(item)} | |||
| > | |||
| <ParameterInput allowClear></ParameterInput> | |||
| <ParameterInput placeholder={item.value.placeholder} allowClear></ParameterInput> | |||
| </Form.Item> | |||
| ))} | |||
| </> | |||
| @@ -38,7 +38,7 @@ export const InForm: Story = { | |||
| <Form | |||
| name="form" | |||
| style={{ width: 300 }} | |||
| labelCol={{ flex: '100px' }} | |||
| labelCol={{ flex: '150px' }} | |||
| initialValues={{ | |||
| text: '文本', | |||
| large_text: | |||
| @@ -49,6 +49,7 @@ export const InForm: Story = { | |||
| showValue: '对象文本', | |||
| }, | |||
| select_text: 1, | |||
| select_map_text: 1, | |||
| ant_input_text: | |||
| '超长文本超长文本超长文本超长文本超长文本超长文本超长文本超长文本超长文本超长文本超长文本', | |||
| ant_select_text: 1, | |||
| @@ -82,6 +83,22 @@ export const InForm: Story = { | |||
| ]} | |||
| /> | |||
| </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"> | |||
| <Input disabled /> | |||
| </Form.Item> | |||
| @@ -2,7 +2,7 @@ import ParameterSelect, { ParameterSelectValue } from '@/components/ParameterSel | |||
| import { useArgs } from '@storybook/preview-api'; | |||
| import type { Meta, StoryObj } from '@storybook/react'; | |||
| import { fn } from '@storybook/test'; | |||
| import { Col, Form, Row } from 'antd'; | |||
| import { Button, Col, Form, Row } from 'antd'; | |||
| import { http, HttpResponse } from 'msw'; | |||
| 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 | |||
| export const Primary: Story = { | |||
| 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 }, | |||
| size: 'large', | |||
| value: { | |||
| value: undefined, | |||
| }, | |||
| }, | |||
| render: function Render(args) { | |||
| const [{ value }, updateArgs] = useArgs(); | |||
| @@ -65,6 +85,9 @@ export const Primary: Story = { | |||
| }; | |||
| export const InForm: Story = { | |||
| args: { | |||
| dataType: 'dataset', | |||
| }, | |||
| render: ({ onChange }) => { | |||
| return ( | |||
| <Form | |||
| @@ -72,54 +95,75 @@ export const InForm: Story = { | |||
| labelCol={{ flex: '80px' }} | |||
| labelAlign="left" | |||
| size="large" | |||
| onFinish={(values) => { | |||
| console.log('onFinish', values); | |||
| }} | |||
| autoComplete="off" | |||
| initialValues={{ | |||
| dataset: { | |||
| type: 'select', | |||
| item_type: 'dataset', | |||
| placeholder: '请选择数据集', | |||
| label: '数据集', | |||
| }, | |||
| model: { | |||
| type: 'select', | |||
| item_type: 'model', | |||
| placeholder: '请选择模型', | |||
| label: '模型', | |||
| }, | |||
| service: { | |||
| type: 'select', | |||
| item_type: 'service', | |||
| placeholder: '请选择服务', | |||
| label: '服务', | |||
| }, | |||
| resource: { | |||
| type: 'select', | |||
| item_type: 'resource', | |||
| placeholder: '请选择计算资源', | |||
| label: '计算资源', | |||
| }, | |||
| test: '1234', | |||
| }} | |||
| > | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item label="数据集" name="dataset"> | |||
| <ParameterSelect onChange={onChange} /> | |||
| <ParameterSelect dataType="dataset" placeholder="请选择数据集" onChange={onChange} /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item label="模型" name="model"> | |||
| <ParameterSelect onChange={onChange} /> | |||
| <ParameterSelect dataType="model" placeholder="请选择模型" onChange={onChange} /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item label="服务" name="service"> | |||
| <ParameterSelect onChange={onChange} /> | |||
| <ParameterSelect dataType="service" placeholder="请选择服务" onChange={onChange} /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item label="计算资源" name="resource"> | |||
| <ParameterSelect onChange={onChange} /> | |||
| <ParameterSelect | |||
| dataType="resource" | |||
| placeholder="请选择计算资源" | |||
| onChange={onChange} | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Button htmlType="submit" type="primary"> | |||
| 提交 | |||
| </Button> | |||
| </Row> | |||
| </Form> | |||
| ); | |||
| }, | |||
| @@ -122,14 +122,14 @@ export const formatBoolean = (value: boolean): string => { | |||
| return value ? '是' : '否'; | |||
| }; | |||
| type FormatEnumFunc = (value: string | number) => string; | |||
| type FormatEnumFunc = (value: string | number) => React.ReactNode; | |||
| // 格式化枚举 | |||
| export const formatEnum = ( | |||
| options: { value: string | number; label: string }[], | |||
| options: { value?: string | number | null; label?: React.ReactNode }[], | |||
| ): FormatEnumFunc => { | |||
| return (value: string | number) => { | |||
| const option = options.find((item) => item.value === value); | |||
| return option ? option.label : '--'; | |||
| return option && option.label ? option.label : '--'; | |||
| }; | |||
| }; | |||