feat: cloud options
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
import ComponentsMap from '@/components/seal-form/config/components';
|
||||
import { SealFormItemProps } from '@/components/seal-form/types';
|
||||
import { Form } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
interface FieldItemProps extends SealFormItemProps {
|
||||
widget: keyof typeof ComponentsMap;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const FieldItem: React.FC<FieldItemProps> = (props) => {
|
||||
const { name, widget, required = [], ...rest } = props;
|
||||
|
||||
const Component = ComponentsMap[widget];
|
||||
|
||||
return <Form.Item name={name}></Form.Item>;
|
||||
};
|
||||
|
||||
export default FieldItem;
|
||||
@@ -0,0 +1,45 @@
|
||||
import ComponentsMap from '@/components/seal-form/config/components';
|
||||
import { FormWidgetProps } from '../config/types';
|
||||
|
||||
const FormWidget: React.FC<
|
||||
FormWidgetProps & {
|
||||
onChange?: (data: any) => void;
|
||||
}
|
||||
> = ({
|
||||
widget,
|
||||
title: label,
|
||||
required,
|
||||
placeholder,
|
||||
options,
|
||||
description,
|
||||
enum: enumValues,
|
||||
style,
|
||||
value,
|
||||
min,
|
||||
max,
|
||||
checked,
|
||||
onChange
|
||||
}) => {
|
||||
const Component = ComponentsMap[widget];
|
||||
|
||||
const optionList = enumValues?.map((item: string | number) => ({
|
||||
label: item,
|
||||
value: item
|
||||
}));
|
||||
|
||||
return Component ? (
|
||||
<Component
|
||||
{...{ label, required, placeholder, description, min, max }}
|
||||
options={options || optionList}
|
||||
value={value}
|
||||
checked={checked}
|
||||
style={{
|
||||
width: '100%',
|
||||
...style
|
||||
}}
|
||||
onChange={onChange}
|
||||
/>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default FormWidget;
|
||||
@@ -0,0 +1,136 @@
|
||||
import Wrapper from '@/components/label-selector/wrapper';
|
||||
import { MinusOutlined } from '@ant-design/icons';
|
||||
import { Button } from 'antd';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import FormWidget from './form-widget';
|
||||
|
||||
interface ListMapProps {
|
||||
dataList: any[];
|
||||
label?: React.ReactNode;
|
||||
btnText?: string;
|
||||
properties: Record<string, any>;
|
||||
onChange?: (data: any) => void;
|
||||
}
|
||||
|
||||
interface ListItemProps {
|
||||
schemaList: any[];
|
||||
data: Record<string, any>;
|
||||
onChange?: (data: any) => void;
|
||||
}
|
||||
|
||||
const RowWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
`;
|
||||
|
||||
const WidgetBox = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const ListItem: React.FC<ListItemProps> = ({ schemaList, data, onChange }) => {
|
||||
const handleValueChange = (name: string, target: any) => {
|
||||
if (target?.target?.type === 'checkbox') {
|
||||
const checked = target.target?.checked;
|
||||
onChange?.({ [name]: checked });
|
||||
} else {
|
||||
const value = target?.target ? target.target.value : target;
|
||||
onChange?.({ [name]: value });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{schemaList.map((schema: any) => (
|
||||
<FormWidget
|
||||
widget={schema.type}
|
||||
{...schema}
|
||||
key={schema.name}
|
||||
value={data?.[schema.name]}
|
||||
checked={data?.[schema.name]}
|
||||
onChange={(target) => handleValueChange(schema.name, target)}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const ListMap: React.FC<ListMapProps> = ({
|
||||
dataList = [],
|
||||
label,
|
||||
btnText,
|
||||
properties = {},
|
||||
onChange
|
||||
}) => {
|
||||
const [items, setItems] = React.useState(dataList || []);
|
||||
|
||||
const schemaList = useMemo(() => {
|
||||
const list = Object.entries(properties).map(([key, value]) => ({
|
||||
...value,
|
||||
name: key
|
||||
}));
|
||||
return list;
|
||||
}, [properties]);
|
||||
|
||||
const handleOnAdd = () => {
|
||||
const keys = Object.keys(properties);
|
||||
setItems([
|
||||
...items,
|
||||
{ ...keys.reduce((acc, key) => ({ ...acc, [key]: '' }), {}) }
|
||||
]);
|
||||
};
|
||||
|
||||
const handleDelete = (index: number) => {
|
||||
const newItems = items.filter((_, i) => i !== index);
|
||||
setItems(newItems);
|
||||
onChange?.(newItems);
|
||||
};
|
||||
|
||||
const handleItemChange = (index: number, data: { [key: string]: any }) => {
|
||||
const newItems = [...items];
|
||||
newItems[index] = { ...newItems[index], ...data };
|
||||
setItems(newItems);
|
||||
onChange?.(newItems);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!dataList.length) {
|
||||
handleOnAdd();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Wrapper label={label} btnText={btnText} onAdd={handleOnAdd}>
|
||||
{items.map((item, index) => (
|
||||
<RowWrapper key={index}>
|
||||
<WidgetBox>
|
||||
<ListItem
|
||||
schemaList={schemaList}
|
||||
data={item}
|
||||
onChange={(value) => handleItemChange(index, value)}
|
||||
/>
|
||||
</WidgetBox>
|
||||
<Button
|
||||
size="small"
|
||||
type="default"
|
||||
shape="circle"
|
||||
style={{
|
||||
width: 24,
|
||||
marginLeft: 10,
|
||||
flex: 'none'
|
||||
}}
|
||||
icon={<MinusOutlined />}
|
||||
onClick={() => handleDelete(index)}
|
||||
/>
|
||||
</RowWrapper>
|
||||
))}
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default ListMap;
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
|
||||
// refer to json schema
|
||||
export interface FieldSchema {
|
||||
type: 'string' | 'number' | 'boolean' | 'object' | 'array';
|
||||
title?: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
properties?: Record<string, FieldSchema>;
|
||||
default?: any;
|
||||
enum?: string[];
|
||||
minItems?: number;
|
||||
maxItems?: number;
|
||||
items?: FieldSchema[];
|
||||
widget?: string;
|
||||
min?: number;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export interface FormWidgetProps {
|
||||
widget: 'Input' | 'Select' | 'Checkbox' | 'InputNumber';
|
||||
name: string;
|
||||
title?: string;
|
||||
required?: boolean;
|
||||
placeholder?: string;
|
||||
options?: { label: string; value: string | number }[];
|
||||
description?: string;
|
||||
enum?: (string | number)[];
|
||||
style?: React.CSSProperties;
|
||||
value?: any;
|
||||
checked?: boolean;
|
||||
min?: number;
|
||||
max?: number;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { useMemo } from 'react';
|
||||
import { FieldSchema } from '../config/types';
|
||||
|
||||
interface ParsedField {
|
||||
name: (string | number)[];
|
||||
schema: FieldSchema;
|
||||
}
|
||||
|
||||
const parseSchema = (
|
||||
schema: Record<string, FieldSchema>,
|
||||
parentName: (string | number)[] = []
|
||||
): ParsedField[] => {
|
||||
const fields: ParsedField[] = [];
|
||||
|
||||
Object.entries(schema).forEach(([key, fieldSchema]) => {
|
||||
const currentName = [...parentName, key];
|
||||
if (fieldSchema.type === 'object' && fieldSchema.properties) {
|
||||
fields.push(...parseSchema(fieldSchema.properties, currentName));
|
||||
} else if (fieldSchema.type === 'array' && fieldSchema.items) {
|
||||
fields.push({ name: currentName, schema: fieldSchema });
|
||||
} else {
|
||||
fields.push({ name: currentName, schema: fieldSchema });
|
||||
}
|
||||
});
|
||||
|
||||
return fields;
|
||||
};
|
||||
|
||||
const useParsedFields = (schema: Record<string, FieldSchema>) => {
|
||||
return useMemo(() => parseSchema(schema), [schema]);
|
||||
};
|
||||
|
||||
export default useParsedFields;
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Form } from 'antd';
|
||||
import React from 'react';
|
||||
import { FieldSchema } from './config/types';
|
||||
|
||||
interface DynamicFormProps {
|
||||
schema: FieldSchema;
|
||||
onSubmit: (values: any) => void;
|
||||
}
|
||||
|
||||
const DynamicForm: React.FC<DynamicFormProps> = ({ schema, onSubmit }) => {
|
||||
const form = Form.useFormInstance();
|
||||
|
||||
const handleFinish = (values: any) => {
|
||||
onSubmit(values);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form form={form} onFinish={handleFinish}>
|
||||
{/* Render form fields based on schema */}
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DynamicForm;
|
||||
@@ -1,8 +1,6 @@
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React, { useRef } from 'react';
|
||||
import React from 'react';
|
||||
import LabelItem from './label-item';
|
||||
import Wrapper from './wrapper';
|
||||
interface LabelSelectorProps {
|
||||
@@ -31,8 +29,6 @@ const Inner: React.FC<LabelSelectorProps> = ({
|
||||
description
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
const boxRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const updateLabels = (list: { key: string; value: string }[]) => {
|
||||
const newLabels = _.reduce(
|
||||
@@ -75,7 +71,12 @@ const Inner: React.FC<LabelSelectorProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Wrapper label={label} description={description}>
|
||||
<Wrapper
|
||||
label={label}
|
||||
description={description}
|
||||
onAdd={handleAddLabel}
|
||||
btnText={btnText}
|
||||
>
|
||||
<>
|
||||
{labelList?.map((item: any, index: number) => {
|
||||
return (
|
||||
@@ -91,26 +92,9 @@ const Inner: React.FC<LabelSelectorProps> = ({
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<div className="flex justify-center">
|
||||
<Button
|
||||
ref={buttonRef}
|
||||
type="text"
|
||||
block
|
||||
style={{
|
||||
marginTop: 16,
|
||||
backgroundColor: 'var(--ant-color-fill-secondary)'
|
||||
}}
|
||||
onClick={handleAddLabel}
|
||||
>
|
||||
<PlusOutlined className="font-size-14" />{' '}
|
||||
{intl.formatMessage({
|
||||
id: btnText || 'common.button.addSelector'
|
||||
})}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Inner);
|
||||
export default Inner;
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
.wrapper {
|
||||
position: relative;
|
||||
padding: 14px;
|
||||
padding-top: 34px;
|
||||
border: 1px solid var(--ant-color-border);
|
||||
border-radius: var(--border-radius-base);
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
|
||||
:global {
|
||||
.label {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
line-height: 1;
|
||||
top: 12px;
|
||||
color: var(--ant-color-text-tertiary);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,54 @@
|
||||
import LabelInfo from '@/components/seal-form/components/label-info';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button } from 'antd';
|
||||
import React from 'react';
|
||||
import styles from './styles/wrapper.less';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper: React.FC<{
|
||||
interface WrapperProps {
|
||||
label?: React.ReactNode;
|
||||
description?: React.ReactNode;
|
||||
labelExtra?: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}> = ({ children, label, description, labelExtra, ...rest }) => {
|
||||
btnText?: string;
|
||||
onAdd?: () => void;
|
||||
button?: React.ReactNode;
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
position: relative;
|
||||
padding: 14px;
|
||||
padding-top: 34px;
|
||||
border: 1px solid var(--ant-color-border);
|
||||
border-radius: var(--border-radius-base);
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
.label {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
line-height: 1;
|
||||
top: 12px;
|
||||
color: var(--ant-color-text-tertiary);
|
||||
}
|
||||
`;
|
||||
|
||||
const ButtonWrapper = styled.div`
|
||||
margin-top: 16px;
|
||||
`;
|
||||
|
||||
const Wrapper: React.FC<WrapperProps> = ({
|
||||
children,
|
||||
label,
|
||||
description,
|
||||
labelExtra,
|
||||
onAdd,
|
||||
btnText,
|
||||
button
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
return (
|
||||
<div className={styles['wrapper']}>
|
||||
<Container>
|
||||
{label && (
|
||||
<span className="label">
|
||||
<LabelInfo
|
||||
@@ -19,10 +58,19 @@ const Wrapper: React.FC<{
|
||||
></LabelInfo>
|
||||
</span>
|
||||
)}
|
||||
{React.isValidElement(children)
|
||||
? React.cloneElement(children, { ...rest })
|
||||
: children}
|
||||
</div>
|
||||
{children}
|
||||
<ButtonWrapper>
|
||||
{button || (
|
||||
<Button variant="filled" color="default" block onClick={onAdd}>
|
||||
<PlusOutlined className="font-size-14" />
|
||||
{btnText ||
|
||||
intl.formatMessage({
|
||||
id: 'common.button.addSelector'
|
||||
})}
|
||||
</Button>
|
||||
)}
|
||||
</ButtonWrapper>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import Wrapper from '../label-selector/wrapper';
|
||||
@@ -34,7 +32,6 @@ const ListInput: React.FC<ListInputProps> = (props) => {
|
||||
} = props;
|
||||
const [list, setList] = React.useState<{ value: string; uid: number }[]>([]);
|
||||
const countRef = React.useRef(0);
|
||||
const buttonRef = React.useRef<HTMLButtonElement>(null);
|
||||
|
||||
const updateCountRef = () => {
|
||||
countRef.current = countRef.current + 1;
|
||||
@@ -65,9 +62,6 @@ const ListInput: React.FC<ListInputProps> = (props) => {
|
||||
uid: countRef.current
|
||||
});
|
||||
setList(values);
|
||||
// setTimeout(() => {
|
||||
// buttonRef.current?.scrollIntoView?.({ behavior: 'smooth' });
|
||||
// }, 100);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -85,7 +79,13 @@ const ListInput: React.FC<ListInputProps> = (props) => {
|
||||
}, [dataList]);
|
||||
|
||||
return (
|
||||
<Wrapper label={label} description={description} labelExtra={labelExtra}>
|
||||
<Wrapper
|
||||
label={label}
|
||||
description={description}
|
||||
labelExtra={labelExtra}
|
||||
onAdd={handleOnAdd}
|
||||
btnText={btnText}
|
||||
>
|
||||
<>
|
||||
{_.map(list, (item: any, index: number) => {
|
||||
return (
|
||||
@@ -100,26 +100,9 @@ const ListInput: React.FC<ListInputProps> = (props) => {
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<div className="flex justify-center">
|
||||
<Button
|
||||
ref={buttonRef}
|
||||
variant="filled"
|
||||
color="default"
|
||||
block
|
||||
style={{
|
||||
marginTop: 16
|
||||
}}
|
||||
onClick={handleOnAdd}
|
||||
>
|
||||
<PlusOutlined className="font-size-14" />{' '}
|
||||
{intl.formatMessage({
|
||||
id: btnText
|
||||
})}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ListInput);
|
||||
export default ListInput;
|
||||
|
||||
@@ -85,7 +85,7 @@ export const FilterBar: React.FC<FilterBarProps> = (props) => {
|
||||
handleDeleteByBatch,
|
||||
handleClickPrimary,
|
||||
rowSelection,
|
||||
actionItems,
|
||||
actionItems = [],
|
||||
selectOptions,
|
||||
showSelect,
|
||||
buttonText,
|
||||
|
||||
@@ -22,6 +22,7 @@ const SealInput: React.FC<InputProps & SealFormItemProps> = (props) => {
|
||||
trim = true,
|
||||
loading,
|
||||
labelExtra,
|
||||
style,
|
||||
...rest
|
||||
} = props;
|
||||
const [isFocus, setIsFocus] = useState(false);
|
||||
@@ -73,7 +74,7 @@ const SealInput: React.FC<InputProps & SealFormItemProps> = (props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<InputWrapper>
|
||||
<InputWrapper style={style}>
|
||||
<Wrapper
|
||||
status={checkStatus || status}
|
||||
label={label}
|
||||
|
||||
Reference in New Issue
Block a user