| @@ -1,13 +1,47 @@ | |||||
| .parameter-range { | .parameter-range { | ||||
| width: 300px; | |||||
| &__list { | |||||
| width: 100%; | |||||
| max-height: 300px; | |||||
| overflow-x: visible; | |||||
| overflow-y: auto; | |||||
| width: 360px; | |||||
| &__type { | |||||
| margin-bottom: 10px; | |||||
| color: @text-color-secondary; | |||||
| font-size: @font-size-input; | |||||
| &::before { | |||||
| display: inline-block; | |||||
| color: @error-color; | |||||
| font-size: 14px; | |||||
| font-family: SimSun, sans-serif; | |||||
| line-height: 1; | |||||
| content: '*'; | |||||
| margin-inline-end: 4px; | |||||
| } | |||||
| } | |||||
| &__desc { | |||||
| margin-bottom: 20px; | |||||
| padding: 4px 8px; | |||||
| color: @text-color-tertiary; | |||||
| font-size: 13px; | |||||
| background: rgba(62, 96, 163, 0.05); | |||||
| border-radius: 6px; | |||||
| } | } | ||||
| &__button { | |||||
| margin-bottom: 0; | |||||
| text-align: center; | |||||
| &__form { | |||||
| width: 100%; | |||||
| &__list { | |||||
| width: 100%; | |||||
| max-height: 300px; | |||||
| overflow-x: visible; | |||||
| overflow-y: auto; | |||||
| } | |||||
| &__space { | |||||
| flex: none; | |||||
| width: 22px; | |||||
| color: @text-color-tertiary; | |||||
| font-size: @font-size-input; | |||||
| line-height: 32px; | |||||
| text-align: center; | |||||
| } | |||||
| &__button { | |||||
| width: 100%; | |||||
| margin-bottom: 0; | |||||
| text-align: center; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,16 +1,16 @@ | |||||
| import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; | import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; | ||||
| import { Button, Flex, Form, Input, InputNumber } from 'antd'; | import { Button, Flex, Form, Input, InputNumber } from 'antd'; | ||||
| import { ParameterType, getFormOptions } from '../utils'; | |||||
| import { ParameterType, getFormOptions, parameterTooltip } from '../utils'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type ParameterRangeProps = { | type ParameterRangeProps = { | ||||
| type?: ParameterType; | |||||
| type: ParameterType; | |||||
| value?: any[]; | value?: any[]; | ||||
| onCancel?: () => void; | onCancel?: () => void; | ||||
| onConfirm?: (value: any[]) => void; | onConfirm?: (value: any[]) => void; | ||||
| }; | }; | ||||
| function ParameterRange({ type, value, onCancel, onConfirm }: ParameterRangeProps) { | |||||
| function ParameterRange({ type, value, onConfirm }: ParameterRangeProps) { | |||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const isList = type === ParameterType.Choice || type === ParameterType.Grid; | const isList = type === ParameterType.Choice || type === ParameterType.Grid; | ||||
| const formOptions = getFormOptions(type, value); | const formOptions = getFormOptions(type, value); | ||||
| @@ -33,108 +33,119 @@ function ParameterRange({ type, value, onCancel, onConfirm }: ParameterRangeProp | |||||
| }; | }; | ||||
| return ( | return ( | ||||
| <Form | |||||
| labelCol={{ flex: '70px' }} | |||||
| wrapperCol={{ flex: '1' }} | |||||
| labelAlign="left" | |||||
| form={form} | |||||
| onFinish={handleFinish} | |||||
| size="middle" | |||||
| autoComplete="off" | |||||
| scrollToFirstError | |||||
| initialValues={initialValues} | |||||
| className={styles['parameter-range']} | |||||
| > | |||||
| {isList ? ( | |||||
| <div className={styles['parameter-range__list']}> | |||||
| <Form.List name="list"> | |||||
| {(fields, { add, remove }) => ( | |||||
| <> | |||||
| {fields.map(({ key, name, ...restField }, index) => ( | |||||
| <Flex key={key} align="center"> | |||||
| <Form.Item | |||||
| style={{ flex: 1, minWidth: 0 }} | |||||
| {...restField} | |||||
| name={[name, 'value']} | |||||
| rules={[{ required: true, message: '必填' }]} | |||||
| > | |||||
| <Input placeholder="请输入" allowClear /> | |||||
| </Form.Item> | |||||
| <Flex | |||||
| style={{ | |||||
| marginLeft: '10px', | |||||
| marginBottom: '20px', | |||||
| flex: 'none', | |||||
| width: '66px', | |||||
| }} | |||||
| align="center" | |||||
| > | |||||
| <Button | |||||
| shape="circle" | |||||
| size="middle" | |||||
| type="text" | |||||
| disabled={fields.length === 1} | |||||
| icon={<MinusCircleOutlined />} | |||||
| onClick={() => remove(name)} | |||||
| ></Button> | |||||
| {index === fields.length - 1 && ( | |||||
| <div className={styles['parameter-range']}> | |||||
| <div className={styles['parameter-range__type']}>{type}</div> | |||||
| <div className={styles['parameter-range__desc']}>{parameterTooltip[type]}</div> | |||||
| <Form | |||||
| // labelCol={{ flex: '0' }} | |||||
| // wrapperCol={{ flex: '1' }} | |||||
| labelAlign="left" | |||||
| form={form} | |||||
| onFinish={handleFinish} | |||||
| size="middle" | |||||
| autoComplete="off" | |||||
| scrollToFirstError | |||||
| initialValues={initialValues} | |||||
| layout={isList ? 'horizontal' : 'inline'} | |||||
| className={styles['parameter-range__form']} | |||||
| > | |||||
| {isList ? ( | |||||
| <div className={styles['parameter-range__form__list']}> | |||||
| <Form.List name="list"> | |||||
| {(fields, { add, remove }) => ( | |||||
| <> | |||||
| {fields.map(({ key, name, ...restField }, index) => ( | |||||
| <Flex key={key} align="center"> | |||||
| <Form.Item | |||||
| style={{ flex: 1, minWidth: 0 }} | |||||
| {...restField} | |||||
| name={[name, 'value']} | |||||
| rules={[{ required: true, message: '必填' }]} | |||||
| > | |||||
| <Input placeholder="请输入" allowClear /> | |||||
| </Form.Item> | |||||
| <Flex | |||||
| style={{ | |||||
| marginLeft: '10px', | |||||
| marginBottom: '20px', | |||||
| flex: 'none', | |||||
| width: '66px', | |||||
| }} | |||||
| align="center" | |||||
| > | |||||
| <Button | <Button | ||||
| shape="circle" | shape="circle" | ||||
| size="middle" | size="middle" | ||||
| type="text" | type="text" | ||||
| onClick={() => add()} | |||||
| icon={<PlusCircleOutlined />} | |||||
| disabled={fields.length === 1} | |||||
| icon={<MinusCircleOutlined />} | |||||
| onClick={() => remove(name)} | |||||
| ></Button> | ></Button> | ||||
| )} | |||||
| {index === fields.length - 1 && ( | |||||
| <Button | |||||
| shape="circle" | |||||
| size="middle" | |||||
| type="text" | |||||
| onClick={() => add()} | |||||
| icon={<PlusCircleOutlined />} | |||||
| ></Button> | |||||
| )} | |||||
| </Flex> | |||||
| </Flex> | </Flex> | ||||
| </Flex> | |||||
| ))} | |||||
| {fields.length === 0 && ( | |||||
| <Form.Item className={styles['add-weight']}> | |||||
| <Button | |||||
| className={styles['add-weight__button']} | |||||
| color="primary" | |||||
| variant="dashed" | |||||
| onClick={() => add()} | |||||
| block | |||||
| icon={<PlusCircleOutlined />} | |||||
| > | |||||
| 添加 | |||||
| </Button> | |||||
| ))} | |||||
| {fields.length === 0 && ( | |||||
| <Form.Item className={styles['add-weight']}> | |||||
| <Button | |||||
| className={styles['add-weight__button']} | |||||
| color="primary" | |||||
| variant="dashed" | |||||
| onClick={() => add()} | |||||
| block | |||||
| icon={<PlusCircleOutlined />} | |||||
| > | |||||
| 添加 | |||||
| </Button> | |||||
| </Form.Item> | |||||
| )} | |||||
| </> | |||||
| )} | |||||
| </Form.List> | |||||
| </div> | |||||
| ) : ( | |||||
| <Flex align="start" style={{ width: '100%', marginBottom: '20px' }}> | |||||
| {formOptions.map((item, index) => { | |||||
| return ( | |||||
| <> | |||||
| <Form.Item | |||||
| key={item.name} | |||||
| name={item.name} | |||||
| style={{ flex: 1, marginInlineEnd: 0 }} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: `必填`, | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <InputNumber style={{ width: '100%' }} placeholder={item.name} /> | |||||
| </Form.Item> | </Form.Item> | ||||
| )} | |||||
| </> | |||||
| )} | |||||
| </Form.List> | |||||
| </div> | |||||
| ) : ( | |||||
| formOptions.map((item) => { | |||||
| return ( | |||||
| <Form.Item | |||||
| key={item.name} | |||||
| label={item.label} | |||||
| name={item.name} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: `请输入${item.label}`, | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <InputNumber style={{ width: '100%' }} placeholder={`请输入${item.label}`} /> | |||||
| </Form.Item> | |||||
| ); | |||||
| }) | |||||
| )} | |||||
| <Form.Item className={styles['parameter-range__button']}> | |||||
| <Button type="default" htmlType="button" onClick={onCancel}> | |||||
| 取消 | |||||
| </Button> | |||||
| <Button type="primary" htmlType="submit" style={{ marginLeft: '20px' }}> | |||||
| 确定 | |||||
| </Button> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| {index !== formOptions.length - 1 && ( | |||||
| <span className={styles['parameter-range__form__space']}> | |||||
| {index === 0 ? '-' : ' '} | |||||
| </span> | |||||
| )} | |||||
| </> | |||||
| ); | |||||
| })} | |||||
| </Flex> | |||||
| )} | |||||
| <Form.Item layout="horizontal" className={styles['parameter-range__form__button']}> | |||||
| <Button type="primary" htmlType="submit" style={{ width: '100%', height: '36px' }}> | |||||
| 确定 | |||||
| </Button> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| </div> | |||||
| ); | ); | ||||
| } | } | ||||
| @@ -1,14 +1,23 @@ | |||||
| .parameter-range { | .parameter-range { | ||||
| border-radius: 18px; | |||||
| box-shadow: 0px 3px 10px rgba(22, 100, 255, 0.15); | |||||
| :global { | :global { | ||||
| .ant-popover-inner { | |||||
| padding: 20px 20px 12px; | |||||
| } | |||||
| .ant-popconfirm-description { | |||||
| padding-top: 20px; | |||||
| } | |||||
| .ant-popover-content { | |||||
| .ant-popover-inner { | |||||
| width: 400px; | |||||
| padding: 20px 20px 12px; | |||||
| background-image: url(@/assets/img/popover-bg.png); | |||||
| background-repeat: no-repeat; | |||||
| background-position: top left; | |||||
| background-size: 100% auto; | |||||
| } | |||||
| .ant-popconfirm-description { | |||||
| margin-top: 20px; | |||||
| } | |||||
| .ant-popconfirm-buttons { | |||||
| display: none; | |||||
| .ant-popconfirm-buttons { | |||||
| display: none; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -58,3 +67,17 @@ | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| .parameter-range-title { | |||||
| color: @text-color; | |||||
| font-weight: 500; | |||||
| font-size: @font-size-content; | |||||
| } | |||||
| .parameter-range-title-icon { | |||||
| color: @text-color-secondary; | |||||
| &:hover { | |||||
| color: @text-color; | |||||
| } | |||||
| } | |||||
| @@ -1,6 +1,6 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { isEmpty } from '@/utils'; | import { isEmpty } from '@/utils'; | ||||
| import { Popconfirm, Typography } from 'antd'; | |||||
| import { Flex, Popconfirm, Typography } from 'antd'; | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useEffect, useRef, useState } from 'react'; | import { useEffect, useRef, useState } from 'react'; | ||||
| import ParameterRange from '../ParameterRange'; | import ParameterRange from '../ParameterRange'; | ||||
| @@ -8,7 +8,7 @@ import { ParameterType } from '../utils'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type ParameterRangeProps = { | type ParameterRangeProps = { | ||||
| type?: ParameterType; | |||||
| type: ParameterType; | |||||
| value?: any[]; | value?: any[]; | ||||
| onChange?: (value: any[]) => void; | onChange?: (value: any[]) => void; | ||||
| }; | }; | ||||
| @@ -58,18 +58,11 @@ function PopParameterRange({ type, value, onChange }: ParameterRangeProps) { | |||||
| <div ref={popconfirmRef}> | <div ref={popconfirmRef}> | ||||
| <Popconfirm | <Popconfirm | ||||
| id="pop-parameter" | id="pop-parameter" | ||||
| title="参数范围" | |||||
| title={<PopconfirmTitle title="参数范围" onClose={handleCancel} />} | |||||
| disabled={disabled} | disabled={disabled} | ||||
| description={ | description={ | ||||
| <ParameterRange | |||||
| type={type} | |||||
| value={value} | |||||
| onCancel={handleCancel} | |||||
| onConfirm={handleConfirm} | |||||
| ></ParameterRange> | |||||
| <ParameterRange type={type} value={value} onConfirm={handleConfirm}></ParameterRange> | |||||
| } | } | ||||
| okText="确定" | |||||
| cancelText="取消" | |||||
| overlayClassName={styles['parameter-range']} | overlayClassName={styles['parameter-range']} | ||||
| icon={null} | icon={null} | ||||
| open={open} | open={open} | ||||
| @@ -95,4 +88,18 @@ function PopParameterRange({ type, value, onChange }: ParameterRangeProps) { | |||||
| ); | ); | ||||
| } | } | ||||
| function PopconfirmTitle({ title, onClose }: { title: string; onClose: () => void }) { | |||||
| return ( | |||||
| <Flex align="center" justify="space-between"> | |||||
| <span className={styles['parameter-range-title']}>{title}</span> | |||||
| <KFIcon | |||||
| className={styles['parameter-range-title-icon']} | |||||
| type="icon-guanbi" | |||||
| font={17} | |||||
| onClick={onClose} | |||||
| /> | |||||
| </Flex> | |||||
| ); | |||||
| } | |||||
| export default PopParameterRange; | export default PopParameterRange; | ||||
| @@ -41,7 +41,7 @@ | |||||
| &::before { | &::before { | ||||
| display: inline-block; | display: inline-block; | ||||
| color: #c73131; | |||||
| color: @error-color; | |||||
| font-size: 14px; | font-size: 14px; | ||||
| font-family: SimSun, sans-serif; | font-family: SimSun, sans-serif; | ||||
| line-height: 1; | line-height: 1; | ||||
| @@ -38,6 +38,27 @@ export const axParameterOptions = ['fixed', 'range', 'choice'].map((name) => ({ | |||||
| value: name, | value: name, | ||||
| })); | })); | ||||
| export const parameterTooltip: Record<ParameterType, string> = { | |||||
| [ParameterType.Uniform]: '在 low 和 high 之间均匀采样浮点数', | |||||
| [ParameterType.QUniform]: '在 low 和 high 之间均匀采样浮点数,四舍五入到 q 的倍数', | |||||
| [ParameterType.LogUniform]: '在 low 和 high 之间均匀采样浮点数,对数空间采样', | |||||
| [ParameterType.QLogUniform]: | |||||
| '在 low 和 high 之间均匀采样浮点数,对数空间采样并四舍五入到 q 的倍数', | |||||
| [ParameterType.Randn]: '在均值为 m,方差为 s 的正态分布中进行随机浮点数抽样', | |||||
| [ParameterType.QRandn]: | |||||
| '在均值为 m,方差为 s 的正态分布中进行随机浮点数抽样,四舍五入到 q 的倍数', | |||||
| [ParameterType.RandInt]: '在 low(包括)到 high(不包括)之间均匀采样整数', | |||||
| [ParameterType.QRandInt]: | |||||
| '在 low(包括)到 high(不包括)之间均匀采样整数,四舍五入到 q 的倍数(包括 high)', | |||||
| [ParameterType.LogRandInt]: '在 low(包括)到 high(不包括)之间对数空间上均匀采样整数', | |||||
| [ParameterType.QLogRandInt]: | |||||
| '在 low(包括)到 high(不包括)之间对数空间上均匀采样整数,并四舍五入到 q 的倍数', | |||||
| [ParameterType.Choice]: '从指定的选项中采样一个选项', | |||||
| [ParameterType.Grid]: '对选项进行网格搜索,每个值都将被采样', | |||||
| [ParameterType.Range]: '在 low 和 high 范围内采样取值', | |||||
| [ParameterType.Fixed]: '固定取值', | |||||
| }; | |||||
| export type ParameterData = { | export type ParameterData = { | ||||
| label: string; | label: string; | ||||
| name: string; | name: string; | ||||
| @@ -69,12 +90,12 @@ export const getFormOptions = (type?: ParameterType, value?: number[]): Paramete | |||||
| case ParameterType.Range: | case ParameterType.Range: | ||||
| return [ | return [ | ||||
| { | { | ||||
| name: 'min', | |||||
| name: 'low', | |||||
| label: '最小值', | label: '最小值', | ||||
| value: numbers?.[0], | value: numbers?.[0], | ||||
| }, | }, | ||||
| { | { | ||||
| name: 'max', | |||||
| name: 'high', | |||||
| label: '最大值', | label: '最大值', | ||||
| value: numbers?.[1], | value: numbers?.[1], | ||||
| }, | }, | ||||
| @@ -85,12 +106,12 @@ export const getFormOptions = (type?: ParameterType, value?: number[]): Paramete | |||||
| case ParameterType.QLogRandInt: | case ParameterType.QLogRandInt: | ||||
| return [ | return [ | ||||
| { | { | ||||
| name: 'min', | |||||
| name: 'low', | |||||
| label: '最小值', | label: '最小值', | ||||
| value: numbers?.[0], | value: numbers?.[0], | ||||
| }, | }, | ||||
| { | { | ||||
| name: 'max', | |||||
| name: 'high', | |||||
| label: '最大值', | label: '最大值', | ||||
| value: numbers?.[1], | value: numbers?.[1], | ||||
| }, | }, | ||||
| @@ -103,12 +124,12 @@ export const getFormOptions = (type?: ParameterType, value?: number[]): Paramete | |||||
| case ParameterType.Randn: | case ParameterType.Randn: | ||||
| return [ | return [ | ||||
| { | { | ||||
| name: 'mean', | |||||
| name: 'm', | |||||
| label: '均值', | label: '均值', | ||||
| value: numbers?.[0], | value: numbers?.[0], | ||||
| }, | }, | ||||
| { | { | ||||
| name: 'std', | |||||
| name: 's', | |||||
| label: '方差', | label: '方差', | ||||
| value: numbers?.[1], | value: numbers?.[1], | ||||
| }, | }, | ||||
| @@ -116,12 +137,12 @@ export const getFormOptions = (type?: ParameterType, value?: number[]): Paramete | |||||
| case ParameterType.QRandn: | case ParameterType.QRandn: | ||||
| return [ | return [ | ||||
| { | { | ||||
| name: 'mean', | |||||
| name: 'm', | |||||
| label: '均值', | label: '均值', | ||||
| value: numbers?.[0], | value: numbers?.[0], | ||||
| }, | }, | ||||
| { | { | ||||
| name: 'std', | |||||
| name: 's', | |||||
| label: '方差', | label: '方差', | ||||
| value: numbers?.[1], | value: numbers?.[1], | ||||
| }, | }, | ||||