antd#Cascader TypeScript Examples
The following examples show how to use
antd#Cascader.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: Location.tsx From gant-design with MIT License | 6 votes |
render() {
const { onEnter, className, popupClassName, wrapperRef, ...props } = this.props
return (
<Cascader
{...props}
className={classnames('gant-location-cascader', className)}
changeOnSelect
ref={wrapperRef}
popupClassName={classnames('gant-location-cascader-popup', popupClassName)}
showSearch={{
filter: (value, paths) => paths.some((option: any) => (option.label).toLowerCase().indexOf(value.toLowerCase()) > -1)
}}
/>
)
}
Example #2
Source File: utils.tsx From antdp with MIT License | 6 votes |
getItem = ({ attr, type, inputNode }: {
attr?: Partial<ItemChildAttr<any, any>>;
type?: ItemChildType;
inputNode?: ((...arg: any[]) => React.ReactNode) | React.ReactNode;
}) => {
let renderItem = undefined;
if (type === 'Input') {
const inputAttr = attr as InputProps;
renderItem = <Input {...inputAttr} />;
} else if (type === 'TextArea') {
const inputAttr = attr as TextAreaProps;
renderItem = <Input.TextArea {...inputAttr} />;
} else if (type === 'InputNumber') {
const inputAttr = attr as InputNumberProps;
renderItem = <InputNumber {...inputAttr} />;
} else if (type === 'AutoComplete') {
const inputAttr = attr as AutoCompleteProps;
renderItem = <AutoComplete {...inputAttr} />;
} else if (type === 'Cascader') {
const inputAttr = attr as CascaderProps;
renderItem = <Cascader {...inputAttr} />;
} else if (type === 'DatePicker') {
const inputAttr = attr as DatePickerProps;
renderItem = <DatePicker {...inputAttr} />;
} else if (type === 'Rate') {
const inputAttr = attr as RateProps;
renderItem = <Rate {...inputAttr} />;
} else if (type === 'Slider') {
const inputAttr = attr as SliderSingleProps;
renderItem = <Slider {...inputAttr} />;
} else if (type === 'TreeSelect') {
const inputAttr = attr as TreeSelectProps<any>;
renderItem = <TreeSelect {...inputAttr} />;
} else if (type === 'Select') {
const inputAttr = attr as SelectProps<any>;
renderItem = <Select {...inputAttr} />;
} else if (type === 'Checkbox') {
const inputAttr = attr as CheckboxGroupProps;
renderItem = <Checkbox.Group {...inputAttr} />;
} else if (type === 'Mentions') {
const inputAttr = attr as MentionProps;
renderItem = <Mentions {...inputAttr} />;
} else if (type === 'Radio') {
const inputAttr = attr as RadioProps;
renderItem = <Radio.Group {...inputAttr} />;
} else if (type === 'Switch') {
const inputAttr = attr as SwitchProps;
renderItem = <Switch {...inputAttr} />;
} else if (type === 'TimePicker') {
const inputAttr = attr as TimePickerProps;
renderItem = <TimePicker {...inputAttr} />;
} else if (type === 'Upload') {
const inputAttr = attr as UploadProps;
renderItem = <Upload {...inputAttr} />;
} else if (type === 'RangePicker') {
const inputAttr = attr as RangePickerProps;
renderItem = <RangePicker {...inputAttr} />;
} else if (type === 'Custom') {
renderItem = inputNode;
}
return renderItem;
}
Example #3
Source File: AssistViewFields.tsx From datart with Apache License 2.0 | 5 votes |
AssistViewFields: React.FC<AssistViewFieldsProps> = memo(
({ onChange, value: propsValue, getViewOption }) => {
const tc = useI18NPrefix(`viz.control`);
const [val, setVal] = useState<string[]>([]);
const { orgId } = useContext(BoardContext);
const [options, setOptions] = useState<CascaderOptionType[]>([]);
useEffect(() => {
setVal(propsValue || []);
}, [onChange, propsValue]);
const setViews = useCallback(
async orgId => {
try {
const { data } = await request2<ViewSimple[]>(
`/views?orgId=${orgId}`,
);
const views: CascaderOptionType[] = data.map(item => {
return {
value: item.id,
label: item.name,
isLeaf: false,
};
});
if (Array.isArray(propsValue) && propsValue.length) {
const children = await getViewOption(propsValue[0]);
views.forEach(view => {
if (view.value === propsValue[0]) {
view.children = children;
}
});
}
setOptions([...views]);
} catch (error) {
errorHandle(error);
throw error;
}
},
[getViewOption, propsValue],
);
useEffect(() => {
setViews(orgId);
}, [setViews, orgId]);
const loadData = useCallback(
async (selectedOptions: CascaderOptionType[]) => {
const targetOption = selectedOptions[selectedOptions.length - 1];
targetOption.loading = true;
const children = await getViewOption(targetOption.value as string);
targetOption.children = children;
targetOption.loading = false;
const nextOptions = [...options].map(item => {
if (item.value === targetOption.value) {
return targetOption;
} else {
return item;
}
});
setOptions(nextOptions);
},
[options, getViewOption],
);
const optionChange = value => {
setVal(value);
onChange?.(value || []);
};
return (
<Cascader
allowClear
placeholder={tc('selectViewField')}
options={options}
onChange={optionChange}
value={[...val]}
style={{ margin: '6px 0' }}
loadData={loadData as any}
/>
);
},
)
Example #4
Source File: moduleSelector.tsx From erda-ui with GNU Affero General Public License v3.0 | 5 votes |
ModuleSelector = ({ type, api, dataHandler, query, onChange, viewProps }: IProps) => {
const [modules, timeSpan, chosenApp] = monitorCommonStore.useStore((s) => [
s.modules,
s.globalTimeSelectSpan.range,
s.chosenApp,
]);
const { getModules } = monitorCommonStore.effects;
const { clearModules, changeChosenModules } = monitorCommonStore.reducers;
useEffectOnce(() => {
onGetModules();
return () => {
clearModules({ type });
};
});
React.useEffect(() => {
const { startTimeMs, endTimeMs } = timeSpan;
const filter_application_id = get(chosenApp, 'id');
onGetModules({ start: startTimeMs, end: endTimeMs, filter_application_id });
}, [timeSpan, chosenApp]);
const onGetModules = (extendQuery?: any) => {
const { startTimeMs, endTimeMs } = timeSpan;
const filter_application_id = get(chosenApp, 'id');
const finalQuery = { ...query, start: startTimeMs, end: endTimeMs, filter_application_id, ...extendQuery };
finalQuery.filter_application_id && getModules({ api, query: finalQuery, dataHandler, type }); // 有appId才发起请求
};
const displayRender = (label: string[]) => {
if (!(label[1] && label[2])) return '';
return `${label[1]} / ${label[2]}`;
};
const onChangeChosenModules = (val: string[]) => {
changeChosenModules({ chosenModule: last(val), type });
};
const module = modules[type];
return (
<Cascader
options={module}
displayRender={displayRender}
expandTrigger="hover"
className="condition-selector"
onChange={onChange || onChangeChosenModules}
placeholder={i18n.t('msp:please select module')}
{...viewProps}
/>
);
}
Example #5
Source File: index.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
RenderFormItem = ({
form,
label,
labelTip,
name,
type,
initialValue = null,
size = 'default',
required = true,
pattern = null,
message = i18n.t('common:please fill in the format correctly'),
itemProps = {},
extraProps = {},
className = '',
rules = [],
config,
options = [],
addOne,
dropOne,
getComp,
suffix = null,
formItemLayout,
formLayout,
tailFormItemLayout,
noColon = false,
isTailLayout, // no label, put some offset to align right part
readOnly,
readOnlyRender,
}: IFormItem) => {
let ItemComp = null;
const specialConfig: any = {};
let _type = type;
if (typeof getComp === 'function') {
_type = 'custom';
}
let action = i18n.t('common:input');
switch (_type) {
case 'select':
if (itemProps.mode === 'multiple') {
specialConfig.valuePropType = 'array';
}
ItemComp = (
<ClassWrapper>
<SelectComp options={options} size={size} {...itemProps} />
</ClassWrapper>
);
action = i18n.t('common:select');
break;
case 'tagsSelect':
if (itemProps.mode === 'multiple') {
specialConfig.valuePropType = 'array';
}
ItemComp = (
<ClassWrapper>
<TagsSelect options={options} size={size} {...itemProps} />
</ClassWrapper>
);
action = i18n.t('common:select');
break;
case 'inputNumber':
ItemComp = (
<InputNumber {...itemProps} className={classnames('input-with-icon', itemProps.className)} size={size} />
);
break;
case 'textArea':
ItemComp = <TextArea {...itemProps} className={classnames('input-with-icon', itemProps.className)} />;
break;
case 'switch':
specialConfig.valuePropName = 'checked';
specialConfig.valuePropType = 'boolean';
ItemComp = <Switch {...itemProps} />;
action = i18n.t('common:select');
break;
case 'radioGroup':
ItemComp = (
<Radio.Group buttonStyle="solid" {...itemProps} size={size}>
{typeof options === 'function'
? options()
: options.map((single) => (
<Radio.Button key={single.value} value={`${single.value}`} disabled={!!single.disabled}>
{single.name}
</Radio.Button>
))}
</Radio.Group>
);
action = i18n.t('common:select');
break;
case 'checkbox':
if (itemProps.options) {
specialConfig.valuePropName = 'value';
specialConfig.valuePropType = 'array';
ItemComp = <Checkbox.Group {...itemProps} />;
} else {
specialConfig.valuePropName = 'checked';
specialConfig.valuePropType = 'boolean';
const { text = '', ...checkboxProps } = itemProps;
ItemComp = <Checkbox {...checkboxProps}>{text}</Checkbox>;
}
action = i18n.t('common:select');
break;
case 'datePicker':
ItemComp = (
<DatePicker className="w-full" allowClear={false} format="YYYY-MM-DD" showTime={false} {...itemProps} />
);
break;
case 'dateRange':
ItemComp = (
<ClassWrapper>
<DateRange {...itemProps} />
</ClassWrapper>
);
break;
case 'custom':
// getFieldDecorator不能直接包裹FunctionalComponent,see https://github.com/ant-design/ant-design/issues/11324
ItemComp = <ClassWrapper {...itemProps}>{(getComp as Function)({ form })}</ClassWrapper>;
if (readOnly && readOnlyRender) {
ItemComp = <CustomRender readOnlyRender={readOnlyRender} />;
}
break;
case 'cascader':
specialConfig.valuePropType = 'array';
ItemComp = <Cascader {...itemProps} options={options} />;
break;
case 'input':
default:
ItemComp = <Input {...itemProps} className={classnames('input-with-icon', itemProps.className)} size={size} />;
if (readOnly) {
ItemComp = readOnlyRender ? <CustomRender readOnlyRender={readOnlyRender} /> : <InputReadOnly />;
}
break;
}
const layout =
label === undefined
? fullWrapperCol
: isTailLayout
? tailFormItemLayout || defalutTailFormItemLayout
: formLayout === 'horizontal'
? formItemLayout || defalutFormItemLayout
: null;
// generate rules
if (required && !rules.some((r) => r.required === true)) {
if (typeof label === 'string' && label.length) {
const hasColon = !noColon && (label.endsWith(':') || label.endsWith(':'));
rules.push({
required,
message: `${i18n.t('common:please')}${action}${hasColon ? label.slice(0, label.length - 1) : label}`,
});
} else if (label) {
rules.push({
required,
message: i18n.t('can not be empty'),
});
}
}
if (pattern && !rules.some((r) => r.pattern && r.pattern.source === pattern.source)) {
rules.push({ pattern, message });
}
// generate config
const itemConfig = {
rules,
...specialConfig,
...config,
};
if (initialValue !== null) {
switch (itemConfig.valuePropType) {
case 'boolean':
itemConfig.initialValue = !!initialValue;
break;
case 'array':
itemConfig.initialValue = initialValue;
break;
default:
itemConfig.initialValue = initialValue.toString();
}
}
const _label = labelTip ? (
<span>
{label}
<Tooltip title={labelTip}>
<ErdaIcon type="help" className="align-middle text-icon" />
</Tooltip>
</span>
) : (
label
);
return (
<FormItem
label={_label}
{...layout}
className={`${itemProps.type === 'hidden' ? 'hidden' : ''} ${className}`}
required={!readOnly && required}
>
<FormItem
name={typeof name === 'string' && name?.includes('.') ? name.split('.') : name}
noStyle
{...extraProps}
{...itemConfig}
>
{ItemComp}
</FormItem>
{suffix}
{addOne ? <ErdaIcon type="add-one" className="render-form-op" onClick={() => addOne(name)} /> : null}
{dropOne ? <ErdaIcon type="reduce-one" className="render-form-op" onClick={() => dropOne(name)} /> : null}
</FormItem>
);
}
Example #6
Source File: index.tsx From jetlinks-ui-antd with MIT License | 4 votes |
Save: React.FC<Props> = props => {
const initState: State = {
protocolSupports: [],
protocolTransports: [],
organizationList: [],
categoryLIst: [],
classifiedData: {},
storagePolicy: '',
};
const { getFieldDecorator, setFieldsValue } = props.form;
// 消息协议
const [protocolSupports, setProtocolSupports] = useState(initState.protocolSupports);
// 消息协议
const [organizationList, setOrganizationList] = useState(initState.organizationList);
// 传输协议
const [protocolTransports, setProtocolTransports] = useState(initState.protocolTransports);
const [categoryLIst, setCategoryLIst] = useState(initState.categoryLIst);
const [photoUrl, setPhotoUrl] = useState(props.data?.photoUrl);
const [classifiedVisible, setClassifiedVisible] = useState(false);
const [classifiedData, setClassifiedData] = useState(initState.classifiedData);
const [storagePolicy, setStoragePolicy] = useState<any[]>([]);
const [checkStorage, setCheckStorage] = useState<any>(initState.storagePolicy);
const onMessageProtocolChange = (value: string) => {
// 获取链接协议
apis.deviceProdcut
.protocolTransports(value)
.then(e => {
if (e.status === 200) {
setProtocolTransports(e.result);
}
})
.catch(() => {});
};
const setCategory = (list:any) =>{
let idList: string[] = [];
const pathList = treeTool.findPath(list, function (n: any) {
return n.id == props.data.classifiedId
}); // pathList所有父级data组成的
if (pathList != null && pathList.length > 0) {
idList = pathList.map((n: any) => n.id);// idList即为所求的上级所有ID
}
setFieldsValue({classifiedId: idList});
};
useEffect(() => {
apis.deviceProdcut
.protocolSupport()
.then(e => {
if (e.status === 200) {
setProtocolSupports(e.result);
}
})
.catch(() => {});
apis.deviceProdcut
.queryOrganization()
.then((res: any) => {
if (res.status === 200) {
let orgList: any = [];
res.result.map((item: any) => {
orgList.push({ id: item.id, pId: item.parentId, value: item.id, title: item.name });
});
setOrganizationList(orgList);
}
})
.catch(() => {});
apis.deviceProdcut
.deviceCategoryTree(encodeQueryParam({paging: false, sorts: {field: 'id', order: 'desc'}}))
.then((response: any) => {
if (response.status === 200) {
setCategoryLIst(response.result);
setCategory(response.result);
}
})
.catch(() => {
});
// if (systemVersion === 'pro') {
apis.deviceProdcut.storagePolicy().then(res => {
if (res.status === 200) {
setStoragePolicy(res.result);
}
});
// }
if (props.data && props.data.messageProtocol) {
onMessageProtocolChange(props.data.messageProtocol);
}
}, []);
const basicForm: FormItemConfig[] = [
{
label: '产品ID',
key: 'id',
styles: {
lg: { span: 8 },
md: { span: 12 },
sm: { span: 24 },
},
options: {
initialValue: props.data?.id,
rules: [{ required: true, message: '请输入产品ID' }],
},
component: <Input placeholder="请输入产品ID " disabled={!!props.data?.id} />,
},
{
label: '产品名称',
key: 'name',
options: {
rules: [{ required: true, message: '请选择产品名称' }],
initialValue: props.data?.name,
},
styles: {
xl: { span: 8 },
lg: { span: 8 },
md: { span: 12 },
sm: { span: 24 },
},
component: <Input style={{ width: '100%' }} placeholder="请输入" />,
},
{
label: '所属品类',
key: 'classifiedId',
options: {
rules: [{ required: true, message: '请选择所属品类' }],
},
styles: {
xl: { span: 8 },
lg: { span: 8 },
md: { span: 12 },
sm: { span: 24 },
},
component: (
<Cascader
fieldNames={{ label: 'name', value: 'id', children: 'children' }}
options={categoryLIst}
popupVisible={false}
onChange={value => {
if (value.length === 0) {
setClassifiedData({});
}
}}
onClick={() => {
setClassifiedVisible(true);
}}
placeholder="点击选择品类"
/>
),
},
{
label: '所属机构',
key: 'orgId',
options: {
initialValue: props.data?.orgId,
},
styles: {
xl: { span: 8 },
lg: { span: 10 },
md: { span: 24 },
sm: { span: 24 },
},
component: (
<TreeSelect
allowClear
treeDataSimpleMode
showSearch
placeholder="所属机构"
treeData={organizationList}
treeNodeFilterProp="title"
searchPlaceholder="根据机构名称模糊查询"
/>
),
},
{
label: '消息协议',
key: 'messageProtocol',
options: {
rules: [{ required: true, message: '请选择消息协议' }],
initialValue: props.data?.messageProtocol,
},
styles: {
xl: { span: 8 },
lg: { span: 8 },
md: { span: 12 },
sm: { span: 24 },
},
component: (
<Select
placeholder="请选择"
onChange={(value: string) => {
onMessageProtocolChange(value);
}}
>
{protocolSupports.map(e => (
<Select.Option value={e.id} key={e.id}>
{e.name}
</Select.Option>
))}
</Select>
),
},
{
label: '传输协议',
key: 'transportProtocol',
options: {
rules: [{ required: true, message: '请选择传输协议' }],
initialValue: props.data?.transportProtocol,
},
styles: {
xl: { span: 8 },
lg: { span: 10 },
md: { span: 24 },
sm: { span: 24 },
},
component: (
<Select placeholder="请选择">
{protocolTransports.map(e => (
<Select.Option value={e.id} key={e.id}>
{e.name}
</Select.Option>
))}
</Select>
),
},
{
label: (
<span>
存储策略
<Tooltip
title={
checkStorage.description
? checkStorage.description
: '使用指定的存储策略来存储设备数据'
}
>
<Icon type="question-circle-o" />
</Tooltip>
</span>
),
key: 'storePolicy',
options: {
initialValue: props.data?.storePolicy,
},
styles: {
xl: { span: 8 },
lg: { span: 10 },
md: { span: 24 },
sm: { span: 24 },
},
component: (
<Select
onChange={e => setCheckStorage(storagePolicy.find(i => i.id === e))}
placeholder="请选择"
>
{storagePolicy.map(e => (
<Select.Option value={e.id} key={e.id}>
{e.name}
</Select.Option>
))}
</Select>
),
},
{
label: '设备类型',
key: 'deviceType',
options: {
rules: [{ required: true, message: '请选择设备类型' }],
initialValue:
typeof props.data?.deviceType === 'string'
? props.data?.deviceType
: (props.data?.deviceType || {}).value,
},
styles: {
lg: { span: 8 },
md: { span: 12 },
sm: { span: 24 },
},
component: (
<Radio.Group>
<Radio value="device">直连设备</Radio>
<Radio value="childrenDevice">网关子设备</Radio>
<Radio value="gateway">网关设备</Radio>
</Radio.Group>
),
},
{
label: '描述',
key: 'describe',
styles: {
xl: { span: 24 },
lg: { span: 24 },
md: { span: 24 },
sm: { span: 24 },
},
options: {
initialValue: props.data?.describe,
},
component: <Input.TextArea rows={3} placeholder="请输入描述" />,
},
];
const saveData = () => {
const { form } = props;
form.validateFields((err, fileValue) => {
if (err) return;
if (!fileValue.orgId) {
fileValue.orgId = '';
}
const protocol: Partial<ProtocolItem> =
protocolSupports.find(i => i.id === fileValue.messageProtocol) || {};
props.save({
...fileValue,
photoUrl,
protocolName: protocol.name,
classifiedId: classifiedData.id,
classifiedName: classifiedData.name,
});
});
};
const uploadProps: UploadProps = {
action: '/jetlinks/file/static',
headers: {
'X-Access-Token': getAccessToken(),
},
showUploadList: false,
onChange(info) {
if (info.file.status === 'done') {
setPhotoUrl(info.file.response.result);
message.success('上传成功');
}
},
};
return (
<Drawer
visible
title={`${props.data?.id ? '编辑' : '新增'}产品`}
width={500}
onClose={() => props.close()}
closable
>
<Form labelCol={{ span: 6 }} wrapperCol={{ span: 18 }}>
<Card title="基本信息" style={{ marginBottom: 20 }} bordered={false}>
<Form.Item label="图标">
<>
<div className={styles.avatar}>
<Avatar size={80} src={photoUrl || props.data?.photoUrl || productImg} />
</div>
<Upload {...uploadProps} showUploadList={false}>
<Button>
<UploadOutlined />
更换图片
</Button>
</Upload>
</>
</Form.Item>
<Row gutter={16}>
{basicForm.map(item => (
<Col key={item.key}>
<Form.Item label={item.label}>
{getFieldDecorator(item.key, item.options)(item.component)}
</Form.Item>
</Col>
))}
{/* {(systemVersion === 'pro' ? basicForm : basicForm.filter(i => i.key !== 'storePolicy')).map(item => (
<Col
key={item.key}
>
<Form.Item label={item.label}>
{getFieldDecorator(item.key, item.options)(item.component)}
</Form.Item>
</Col>
))} */}
</Row>
</Card>
</Form>
<div
style={{
position: 'absolute',
right: 0,
bottom: 0,
width: '100%',
borderTop: '1px solid #e9e9e9',
padding: '10px 16px',
background: '#fff',
textAlign: 'right',
}}
>
<Button
onClick={() => {
props.close();
}}
style={{ marginRight: 8 }}
>
关闭
</Button>
<Button
onClick={() => {
saveData();
}}
type="primary"
>
保存
</Button>
</div>
{classifiedVisible && (
<Classified
choice={(item: any) => {
const categoryId = item.categoryId;
setFieldsValue({ classifiedId: categoryId });
setClassifiedData(item);
setClassifiedVisible(false);
}}
close={() => {
setClassifiedVisible(false);
}}
data={classifiedData}
/>
)}
</Drawer>
);
}
Example #7
Source File: index.tsx From jetlinks-ui-antd with MIT License | 4 votes |
Save: React.FC<Props> = props => {
const initState: State = {
protocolSupports: [],
protocolTransports: [],
organizationList: [],
configName: '',
configForm: [],
classified: [],
classifiedData: {},
defaultMetadata: '{"events":[],"properties":[],"functions":[],"tags":[]}',
};
const systemVersion = localStorage.getItem('system-version');
const {getFieldDecorator, setFieldsValue} = props.form;
// 消息协议
const [protocolSupports, setProtocolSupports] = useState(initState.protocolSupports);
// 消息协议
const [organizationList, setOrganizationList] = useState(initState.organizationList);
// 传输协议
const [protocolTransports, setProtocolTransports] = useState(initState.protocolTransports);
const [classified, setClassified] = useState(initState.classified);
const [classifiedData, setClassifiedData] = useState(initState.classifiedData);
//默认物模型
const [defaultMetadata, setDefaultMetadata] = useState(initState.defaultMetadata);
const [photoUrl, setPhotoUrl] = useState(props.data?.photoUrl);
const [classifiedVisible, setClassifiedVisible] = useState(false);
const [storagePolicy, setStoragePolicy] = useState<any[]>([]);
const [checkStorage, setCheckStorage] = useState<any>({});
const onMessageProtocolChange = (value: string) => {
// 获取链接协议
apis.deviceProdcut
.protocolTransports(value)
.then(e => {
if (e.status === 200) {
setProtocolTransports(e.result);
}
})
.catch(() => {
});
};
const getDefaultModel = (id: string, transport: string) => {
apis.deviceProdcut
.getDefaultModel(id, transport)
.then(res => {
if (res.status === 200) {
if (res.result === '{}') {
setDefaultMetadata('{"events":[],"properties":[],"functions":[],"tags":[]}');
} else {
setDefaultMetadata(res.result);
}
} else {
setDefaultMetadata('{"events":[],"properties":[],"functions":[],"tags":[]}');
}
})
.catch(() => {
setDefaultMetadata('{"events":[],"properties":[],"functions":[],"tags":[]}');
});
};
useEffect(() => {
apis.deviceProdcut
.protocolSupport()
.then(e => {
if (e.status === 200) {
setProtocolSupports(e.result);
}
})
.catch(() => {
});
apis.deviceProdcut
.deviceCategoryTree(encodeQueryParam({paging: false, sorts: {field: 'id', order: 'desc'}}))
.then((response: any) => {
if (response.status === 200) {
setClassified(response.result);
}
})
.catch(() => {
});
apis.deviceProdcut
.queryOrganization()
.then((res: any) => {
if (res.status === 200) {
let orgList: any = [];
res.result.map((item: any) => {
orgList.push({id: item.id, pId: item.parentId, value: item.id, title: item.name});
});
setOrganizationList(orgList);
}
})
.catch(() => {
});
// if (systemVersion === 'pro') {
apis.deviceProdcut.storagePolicy().then(res => {
if (res.status === 200) {
setStoragePolicy(res.result);
}
});
// }
if (props.data && props.data.messageProtocol) {
onMessageProtocolChange(props.data.messageProtocol);
}
}, []);
const basicForm: FormItemConfig[] = [
{
label: '产品ID',
key: 'id',
styles: {
lg: {span: 8},
md: {span: 12},
sm: {span: 24},
},
options: {
initialValue: props.data?.id,
rules: [
{required: true, message: '请输入产品ID'},
{max: 64, message: '产品ID不超过64个字符'},
{
pattern: new RegExp(/^[0-9a-zA-Z_\-]+$/, 'g'),
message: '产品ID只能由数字、字母、下划线、中划线组成',
},
],
},
component: <Input placeholder="请输入产品ID " disabled={!!props.data?.id}/>,
},
{
label: '产品名称',
key: 'name',
options: {
rules: [
{required: true, message: '请输入产品名称'},
{max: 200, message: '产品名称不超过200个字符'},
],
initialValue: props.data?.name,
},
styles: {
xl: {span: 8},
lg: {span: 8},
md: {span: 12},
sm: {span: 24},
},
component: <Input style={{width: '100%'}} maxLength={200} placeholder="请输入"/>,
},
{
label: '所属品类',
key: 'classifiedId',
options: {
rules: [{required: true, message: '请选择所属品类'}],
},
styles: {
xl: {span: 8},
lg: {span: 8},
md: {span: 12},
sm: {span: 24},
},
component: (
<Cascader
fieldNames={{label: 'name', value: 'id', children: 'children'}}
options={classified}
popupVisible={false}
onChange={value => {
if (value.length === 0) {
setClassifiedData({});
}
}}
onClick={() => {
setClassifiedVisible(true);
}}
placeholder="点击选择品类"
/>
),
},
{
label: '所属机构',
key: 'orgId',
options: {
initialValue: props.data?.orgId,
},
styles: {
xl: {span: 8},
lg: {span: 10},
md: {span: 24},
sm: {span: 24},
},
component: (
<TreeSelect
allowClear
treeDataSimpleMode
showSearch
placeholder="所属机构"
treeData={organizationList}
treeNodeFilterProp="title"
searchPlaceholder="根据机构名称模糊查询"
/>
),
},
{
label: '消息协议',
key: 'messageProtocol',
options: {
rules: [{required: true, message: '请选择消息协议'}],
initialValue: props.data?.messageProtocol,
},
styles: {
xl: {span: 8},
lg: {span: 8},
md: {span: 12},
sm: {span: 24},
},
component: (
<Select
placeholder="请选择"
showSearch
optionFilterProp='label'
onChange={(value: string) => {
onMessageProtocolChange(value);
}}
>
{protocolSupports.map(e => (
<Select.Option value={e.id} key={e.id} label={e.name}>
{e.name}
</Select.Option>
))}
</Select>
),
},
{
label: '传输协议',
key: 'transportProtocol',
options: {
rules: [{required: true, message: '请选择传输协议'}],
initialValue: props.data?.transportProtocol,
},
styles: {
xl: {span: 8},
lg: {span: 10},
md: {span: 24},
sm: {span: 24},
},
component: (
<Select
placeholder="请选择"
showSearch
optionFilterProp='label'
onChange={(value: string) => {
if (
value !== '' &&
value !== undefined &&
props.form.getFieldsValue().messageProtocol !== '' &&
props.form.getFieldsValue().messageProtocol !== undefined
) {
getDefaultModel(props.form.getFieldsValue().messageProtocol, value);
}
}}
>
{protocolTransports.map(e => (
<Select.Option value={e.id} key={e.id} label={e.name}>
{e.name}
</Select.Option>
))}
</Select>
),
},
{
label: (
<span>
存储策略
<Tooltip
title={
checkStorage.description
? checkStorage.description
: '使用指定的存储策略来存储设备数据'
}
>
<Icon type="question-circle-o"/>
</Tooltip>
</span>
),
key: 'storePolicy',
options: {},
styles: {
xl: {span: 8},
lg: {span: 10},
md: {span: 24},
sm: {span: 24},
},
component: (
<Select
onChange={e => setCheckStorage(storagePolicy.find(i => i.id === e))}
placeholder="请选择"
>
{storagePolicy.map(e => (
<Select.Option value={e.id} key={e.id}>
{e.name}
</Select.Option>
))}
</Select>
),
},
{
label: '设备类型',
key: 'deviceType',
options: {
rules: [{required: true, message: '请选择设备类型'}],
initialValue:
typeof props.data?.deviceType === 'string'
? props.data?.deviceType
: (props.data?.deviceType || {}).value,
},
styles: {
lg: {span: 8},
md: {span: 12},
sm: {span: 24},
},
component: (
<Radio.Group>
<Radio value="device">直连设备</Radio>
<Radio value="childrenDevice">网关子设备</Radio>
<Radio value="gateway">网关设备</Radio>
</Radio.Group>
),
},
{
label: '描述',
key: 'describe',
styles: {
xl: {span: 24},
lg: {span: 24},
md: {span: 24},
sm: {span: 24},
},
options: {
initialValue: props.data?.describe,
},
component: <Input.TextArea rows={4} placeholder="请输入描述"/>,
},
];
const saveData = () => {
const {form} = props;
form.validateFields((err, fileValue) => {
if (err) return;
if (!fileValue.orgId) {
fileValue.orgId = '';
}
const protocol: Partial<ProtocolItem> =
protocolSupports.find(i => i.id === fileValue.messageProtocol) || {};
apis.deviceProdcut
.saveDeviceProduct({
state: 0,
...fileValue,
photoUrl,
metadata: defaultMetadata, //'{"events":[],"properties":[],"functions":[],"tags":[]}',
protocolName: protocol.name,
classifiedId: classifiedData.id,
classifiedName: classifiedData.name,
})
.then((response: any) => {
if (response.status === 200) {
message.success('保存成功');
router.push(`/device/product/save/${response.result.id}`);
}
})
.catch(() => {
});
});
};
const uploadProps: UploadProps = {
action: '/jetlinks/file/static',
headers: {
'X-Access-Token': getAccessToken(),
},
showUploadList: false,
onChange(info) {
if (info.file.status === 'done') {
setPhotoUrl(info.file.response.result);
message.success('上传成功');
}
},
};
return (
<PageHeaderWrapper>
<Card title="基本信息" bordered={false}>
<div className={styles.right}>
<Spin spinning={false}>
<div className={styles.baseView}>
<div className={styles.left}>
<Form labelCol={{span: 5}} wrapperCol={{span: 16}}>
<Row gutter={16}>
{basicForm.map(item => (
<Col key={item.key}>
<Form.Item label={item.label}>
{getFieldDecorator(item.key, item.options)(item.component)}
</Form.Item>
</Col>
))}
{/* {(systemVersion === 'pro' ? basicForm : basicForm.filter(i => i.key !== 'storePolicy')).map(item => (
<Col key={item.key}>
<Form.Item label={item.label}>
{getFieldDecorator(item.key, item.options)(item.component)}
</Form.Item>
</Col>
))} */}
</Row>
</Form>
</div>
<div className={styles.right}>
<>
<div className={styles.avatar_title}>图标</div>
<div className={styles.avatar}>
<Avatar size={144} src={photoUrl || props.data?.photoUrl || productImg}/>
</div>
<Upload {...uploadProps} showUploadList={false}>
<div className={styles.button_view}>
<Button>
<UploadOutlined/>
更换图片
</Button>
</div>
</Upload>
</>
</div>
</div>
<div
style={{
position: 'absolute',
right: 0,
bottom: 0,
height: 32,
lineHeight: 4,
width: '100%',
borderTop: '1px solid #e9e9e9',
paddingRight: 16,
background: '#fff',
textAlign: 'right',
}}
>
<Button
onClick={() => {
router.push(`/device/product`);
}}
style={{marginRight: 8}}
>
返回
</Button>
<Button
onClick={() => {
saveData();
}}
type="primary"
>
保存
</Button>
</div>
</Spin>
</div>
</Card>
{classifiedVisible && (
<Classified
choice={(item: any) => {
const categoryId = item.categoryId;
setFieldsValue({classifiedId: categoryId});
setClassifiedData(item);
setClassifiedVisible(false);
}}
close={() => {
setClassifiedVisible(false);
}}
data={classifiedData}
/>
)}
</PageHeaderWrapper>
);
}
Example #8
Source File: Debugger.tsx From jetlinks-ui-antd with MIT License | 4 votes |
Debugger: React.FC<Props> = (props) => {
const logColumn: ColumnProps<any>[] = [
{
dataIndex: 'type',
title: '类型',
},
{
dataIndex: 'date',
title: '时间',
},
{
dataIndex: 'content',
title: '内容',
},
];
const tabList = [
{
key: 'debugger',
tab: '测试设备',
},
{
key: 'mock',
tab: '模拟设备',
},
];
const logData = [
{
id: '11',
type: '接受设备消息',
date: '2019/08/12 12:12:15',
content: '{"messageId":12188976213,"properties":{"memory":"78MB"}}',
},
{
id: '22',
type: '发送到设备',
date: '2019/08/12 12:12:14',
content: '{"properties":["memory"]}',
},
]
const options = [
{
value: 'properties',
label: '属性',
children: [
{
value: 'cpu',
label: 'CPU使用率',
children: [
{
value: 'read',
label: '读取',
},
{
value: 'edit',
label: '修改',
},
],
},
{
value: 'rom',
label: '内存占用',
children: [
{
value: 'read',
label: '读取',
},
{
value: 'edit',
label: '修改',
},
],
},
{
value: 'disk',
label: '磁盘空间',
children: [
{
value: 'read',
label: '读取',
},
{
value: 'edit',
label: '修改',
},
],
},
],
},
{
value: 'function',
label: '功能',
children: [
{
value: 'all',
label: '全部',
},
],
},
{
value: 'events',
label: '事件',
children: [],
},
];
return (
<div>
<Row gutter={24}>
<Col span={8}>
<Card
tabList={tabList}
>
<div>
<Row gutter={24} className={styles.debuggerCascader}>
<Cascader
placeholder={'类型/属性/操作'}
options={options}
/>
</Row>
<Row gutter={24} style={{marginTop: 15}}>
<Input.TextArea
rows={10}
placeholder='{
"type":"readProperty",
"properties":["memory"]
}' />
</Row>
<Row style={{ marginTop: 15 }}>
<Button style={{marginRight: 5}} type="primary">发送到设备</Button>
<Button>重置</Button>
</Row>
</div>
</Card>
</Col>
<Col span={16}>
<Card
title="调试日志"
extra={
<div>
自动刷新
<Switch />
<Divider type="vertical" />
<Button style={{ marginRight: 5 }}>刷新</Button>
<Button type="primary" >清空</Button>
</div>
}
>
<Table
columns={logColumn}
dataSource={logData}
rowKey={record => record.id}
/>
</Card>
</Col>
</Row>
</div >
);
}
Example #9
Source File: appGroupSelector.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
AppGroupSelector = ({ api, type, dataHandler, viewProps }: IProps) => {
const [appGroup, timeSpan, chosenApp, chosenAppGroup, lastChosenAppGroup] = monitorCommonStore.useStore((s) => [
s.appGroup,
s.globalTimeSelectSpan.range,
s.chosenApp,
s.chosenAppGroup,
s.lastChosenAppGroup,
]);
const { getAppGroup } = monitorCommonStore.effects;
const { changeChosenAppGroup, clearAppGroup } = monitorCommonStore.reducers;
useEffectOnce(() => {
getGroup();
return () => {
clearAppGroup({ type });
};
});
React.useEffect(() => {
getGroup({ timeSpan, chosenApp });
}, [timeSpan, chosenApp]);
const onChangeChosenAppGroup = (val: string[]) => {
changeChosenAppGroup({ chosenAppGroup: val, type });
};
// 获取上次选中的group,是否在当前group总数据内,若有,则默认选中上次选择
const getLastChosenMatch = (_appGroup: any[], _type: string, lastGroup: any[] | undefined) => {
if (isEmpty(lastGroup)) return undefined;
const curAppGroup = _appGroup[_type];
const curGroup = curAppGroup && curAppGroup.data;
const [chosen1, chosen2]: any[] = lastGroup || [];
const matchChosen1 = find(curGroup, (g) => g.value === chosen1);
let val;
// lastChoosenAppGroup值可能为[runtime]或[runtime,service];
// 若有runtime,再匹配该runtime下的children(service),若无runtime,则不再匹配
if (matchChosen1) {
val = [matchChosen1.value];
const matchChosen2 = find(matchChosen1.children, (g) => g.value === chosen2);
if (matchChosen2) {
val.push(matchChosen2.value);
}
}
return val;
};
const displayRender = (label: string[]) => {
if (label.length > 1) {
return `${cutStr(label[0], 8)} / ${cutStr(label[1], 8)}`;
} else {
return label;
}
};
const getGroup = (extendQuery = {}) => {
const totalQuery = { timeSpan, chosenApp, ...extendQuery } as any;
let finalQuery = {};
const filter_application_id = get(chosenApp, 'id');
if (isFunction(totalQuery.query)) {
finalQuery = totalQuery.query({ timeSpan, chosenApp });
} else {
const { startTimeMs, endTimeMs } = timeSpan;
finalQuery = { ...totalQuery.query, start: startTimeMs, end: endTimeMs, filter_application_id };
}
filter_application_id && getAppGroup({ api, query: finalQuery, dataHandler, type }); // 有appId才发起请求
};
const data = appGroup[type] || [];
const lastMatchChosen = getLastChosenMatch(appGroup, type, lastChosenAppGroup);
const value = chosenAppGroup[type] || lastMatchChosen;
React.useEffect(() => {
const loaded = data && data.loading === false;
loaded && changeChosenAppGroup && changeChosenAppGroup(value);
}, [value]);
return (
<Cascader
options={get(data, 'data')}
value={value}
changeOnSelect
displayRender={displayRender}
expandTrigger="hover"
className="condition-selector"
onChange={onChangeChosenAppGroup}
placeholder={i18n.t('msp:Please select')}
{...viewProps}
/>
);
}
Example #10
Source File: analysis.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
Analysis = () => { const [services, runningList, historyList, historyPaging] = jvmStore.useStore((s) => [ s.services, s.runningList, s.historyList, s.historyPaging, ]); const addonDetail = addonStore.useStore((s) => s.addonDetail); const insId = addonDetail.realInstanceId; const runningTimer = React.useRef(-1); const pendingTimer = React.useRef(-1); const [{ idList, isPending, isLoadHistory }, updater] = useUpdate({ idList: [] as string[], isPending: false, isLoadHistory: false, }); const getRunningList = React.useCallback(() => { jvmStore.getProfileList({ insId, state: ProfileStateMap.RUNNING, isHistory: false }).then(() => { runningTimer.current = window.setTimeout(() => { getRunningList(); }, 15 * 1000); }); }, [insId]); const getHistoryList = React.useCallback( (q = {}) => { updater.isLoadHistory(true); jvmStore .getProfileList({ insId, isHistory: true, state: [ProfileStateMap.COMPLETED, ProfileStateMap.FAILED, ProfileStateMap.TERMINATING], ...q, }) .finally(() => { updater.isLoadHistory(false); }); }, [insId, updater], ); React.useEffect(() => { if (addonDetail.realInstanceId) { jvmStore.getServiceInsList(addonDetail.realInstanceId); } getRunningList(); getHistoryList(); return () => { clearTimeout(runningTimer.current); clearTimeout(pendingTimer.current); }; }, [addonDetail, getHistoryList, getRunningList, insId]); const onChange = (values: any) => { updater.idList(values.ids); }; const rollingState = React.useCallback( (s) => { jvmStore.getProfileStatus({ insId, profileId: s.id }).then((res) => { switch (res.state) { case 'pending': pendingTimer.current = window.setTimeout(() => { rollingState(res); }, 5000); break; // case ProfileStateMap.COMPLETED: // case ProfileStateMap.TERMINATING: case ProfileStateMap.RUNNING: goTo(`./${res.id}`); break; case ProfileStateMap.FAILED: message.error(res.message); break; default: break; } }); }, [insId], ); const startProfile = () => { const [applicationId, serviceId, serviceInstanceId] = idList; jvmStore .startProfile({ insId, applicationId, serviceId, serviceInstanceId, }) .then((s) => { updater.isPending(true); rollingState(s); }); }; const getCols = (isHistory: boolean) => { const cols: Array<ColumnProps<JVM.ProfileItem>> = [ { title: i18n.t('dop:application / service / instance name'), dataIndex: 'serviceInstanceName', key: 'serviceInstanceName', render: (_, record) => `${record.applicationName} / ${record.applicationName} / ${record.serviceInstanceName}`, }, { title: i18n.t('dop:analyze id'), dataIndex: 'profiling', key: 'profiling', render: (v) => <Tooltip title={v}>{v}</Tooltip>, }, { title: i18n.t('common:state'), dataIndex: ['state', 'state'], key: 'state.state', width: 160, render: (v) => { return ( { [ProfileStateMap.PENDING]: i18n.t('dop:attaching to process'), [ProfileStateMap.RUNNING]: i18n.t('In Progress'), [ProfileStateMap.COMPLETED]: i18n.t('dop:Completed'), [ProfileStateMap.FAILED]: i18n.t('failed'), [ProfileStateMap.TERMINATING]: i18n.t('dop:Terminated'), }[v] || null ); }, }, { title: i18n.t('Creation time'), dataIndex: 'createTime', key: 'createTime', width: 200, render: (v) => formatTime(v, 'YYYY-MM-DD HH:mm:ss'), }, isHistory ? { title: i18n.t('common:End time'), dataIndex: 'finishTime', key: 'finishTime', width: 180, render: (v) => formatTime(v, 'YYYY-MM-DD HH:mm:ss'), } : { title: i18n.t('dop:started at'), key: 'startFrom', width: 120, render: (v) => fromNow(v), }, { title: i18n.t('operations'), width: 80, render: (record: JVM.ProfileItem) => { return ( <div className="table-operations"> <span className="table-operations-btn" onClick={() => goTo(`./${record.profiling}`)}> {i18n.t('common:view')} </span> </div> ); }, }, ]; return cols; }; return ( <div className="jvm-profile"> <Spin spinning={isPending} tip={i18n.t('dop:attaching to process')}> <div className="px-5 pt-5 pb-1 mb-5 bg-white border-all"> <FilterGroup list={[ { label: i18n.t('dop:select instance'), name: 'ids', type: 'custom', placeholder: '选择后进行分析', Comp: <Cascader options={services} expandTrigger="hover" style={{ width: 400 }} />, }, ]} onChange={onChange} > <Button type="primary" disabled={!idList.length} onClick={startProfile}> {i18n.t('dop:start analysis')} </Button> </FilterGroup> </div> </Spin> <SimplePanel title={i18n.t('dop:analyzing')} className="block"> <Table dataSource={runningList} columns={getCols(false)} rowKey="profiling" pagination={false} scroll={{ x: 900 }} /> </SimplePanel> <SimplePanel title={i18n.t('dop:historical analysis')} className="block mt-5"> <Table dataSource={historyList} columns={getCols(true)} rowKey="profiling" loading={isLoadHistory} pagination={{ current: historyPaging.pageNo, pageSize: historyPaging.pageSize, total: historyPaging.total, onChange: (no: number) => getHistoryList({ pageNo: no }), }} scroll={{ x: 900 }} /> </SimplePanel> </div> ); }
Example #11
Source File: GeneralCascader.tsx From next-basics with GNU General Public License v3.0 | 4 votes |
export function LegacyGeneralCascader(
props: GeneralCascaderProps,
ref: React.Ref<any>
): React.ReactElement {
const {
limit,
allowClear,
disabled,
formElement,
expandTrigger,
fieldNames,
notFoundContent,
placeholder,
popupPlacement,
showSearch,
size,
suffixIcon,
} = props;
const filter = (inputValue: string, path: CascaderOptionType[]): boolean => {
const label = props.fieldNames.label;
const filterValues = inputValue
.split(" ")
.filter((item) => item)
.map((item) => item.toLocaleLowerCase());
for (let j = 0; j < filterValues.length; j++) {
if (
!path.some((option) =>
(option[label] as string).toLowerCase().includes(filterValues[j])
)
) {
return false;
}
}
return true;
};
const [options, setOptions] = useState(props.options);
useEffect(() => {
setOptions(props.options);
}, [props.options]);
const setChildrenOption = (
curOptionData: ProcessedOptionData,
childrenOptions: CascaderOptionType[]
): void => {
const targetOption = getTargetOption(
fieldNames,
curOptionData.selectedOptions,
options
);
if (targetOption) {
targetOption.loading = false;
targetOption[fieldNames.children] = childrenOptions;
setOptions([...options]);
}
};
useImperativeHandle(ref, () => ({
setChildrenOption,
}));
const handleLoadingData = (selectedOptions: CascaderOptionType[]): void => {
const targetOption = selectedOptions[selectedOptions.length - 1];
targetOption.loading = true;
props.onLoadingData?.(selectedOptions);
};
const handlerDisplayRender = (
label: string[],
selectedOptions: CascaderOptionType[]
): string => {
/**
* https://github.com/ant-design/ant-design/issues/27541
* https://github.com/ant-design/ant-design/issues/6761
* 对于有动态加载项的特殊处理,编辑模式下值回填的时候找不到 label 值时以 value 值来展示
*/
if (selectedOptions.some((item) => item.isLeaf === false)) {
const selectedValues: string[] =
props.name && formElement
? formElement.formUtils.getFieldValue(props.name)
: props.value;
return selectedValues
?.map(
(value) =>
selectedOptions.find((option) => option[fieldNames.value] === value)
?.label || value
)
?.join(" / ");
}
return label.join(" / ");
};
return (
<FormItemWrapper {...props}>
<Cascader
popupClassName={style.cascaderOption}
value={props.name && formElement ? undefined : props.value}
options={options}
allowClear={allowClear}
disabled={disabled}
expandTrigger={expandTrigger}
fieldNames={fieldNames}
notFoundContent={notFoundContent}
placeholder={placeholder}
popupPlacement={popupPlacement}
showSearch={showSearch && { limit, filter }}
size={size}
style={props.style}
suffixIcon={suffixIcon && <LegacyIcon type={suffixIcon} />}
onChange={(value, selectedOptions) =>
props.onChange?.(value, selectedOptions)
}
loadData={handleLoadingData}
displayRender={handlerDisplayRender}
/>
</FormItemWrapper>
);
}
Example #12
Source File: GeneralCascader.spec.tsx From next-basics with GNU General Public License v3.0 | 4 votes |
describe("GeneralCascader", () => {
it("should work", () => {
const changeMock = jest.fn();
const props = {
fieldNames: { label: "label", value: "value", children: "children" },
showSearch: true,
suffixIcon: "search",
onChange: changeMock,
options: [
{
value: "zhejiang",
label: "Zhejiang",
children: [
{
value: "hangzhou",
label: "Hangzhou",
children: [
{
value: "xihu",
label: "West Lake",
},
],
},
],
},
{
value: "jiangsu",
label: "Jiangsu",
children: [
{
value: "nanjing",
label: "Nanjing",
children: [
{
value: "zhonghuamen",
label: "Zhong Hua Men",
},
],
},
],
},
],
};
const wrapper = mount(<GeneralCascader {...props} />);
expect(wrapper.find(LegacyIcon).prop("type")).toEqual("search");
wrapper.find(Cascader).invoke("onChange")(
["zhejiang", "hangzhou", "xihu"],
[{}]
);
expect(changeMock.mock.calls[0][0]).toEqual([
"zhejiang",
"hangzhou",
"xihu",
]);
wrapper.find("input").invoke("onChange")({ target: { value: "Jiangsu" } });
wrapper.find("Trigger").simulate("click");
wrapper.update();
expect(wrapper.find(".ant-cascader-menu-item-keyword").text()).toEqual(
"Jiangsu"
);
wrapper.setProps({
name: "cascader",
formElement: {
formUtils: {
getFieldDecorator: () => (comp: React.Component) => comp,
},
},
});
});
it("custom display value", () => {
const props = {
fieldNames: { label: "label", value: "value", children: "children" },
options: [
{
value: "zhejiang",
label: "Zhejiang",
isLeaf: false,
},
{
value: "jiangsu",
label: "Jiangsu",
isLeaf: false,
},
],
value: ["zhejiang", "hanzhou"],
};
const wrapper = mount(<GeneralCascader {...props} />);
expect(wrapper.find(".ant-cascader-picker-label").text()).toEqual(
"Zhejiang / hanzhou"
);
});
it("custom display value with formElement wrapper", async () => {
const props = {
fieldNames: { label: "label", value: "value", children: "children" },
options: [
{
value: "zhejiang",
label: "Zhejiang",
isLeaf: false,
},
{
value: "jiangsu",
label: "Jiangsu",
isLeaf: false,
},
],
};
const wrapper = mount(<GeneralCascader {...props} />);
wrapper.setProps({
name: "city",
formElement: {
formUtils: {
getFieldDecorator: () => (comp: React.Component) => comp,
getFieldValue: jest.fn().mockReturnValue(["jiangsu", "nanjing"]),
},
},
});
const result = wrapper.find(Cascader).invoke("displayRender")(
["jiangshu"],
[
{
value: "jiangsu",
label: "Jiangsu",
isLeaf: false,
},
]
);
expect(result).toEqual("Jiangsu / nanjing");
});
it("should dynamic loading data", () => {
const mockLoadingFn = jest.fn();
const ref: React.Ref<any> = React.createRef();
const props = {
fieldNames: { label: "label", value: "value", children: "children" },
options: [
{
value: "zhejiang",
label: "Zhejiang",
isLeaf: false,
},
{
value: "jiangsu",
label: "Jiangsu",
isLeaf: false,
},
],
value: ["zhejiang", "hanzhou"],
};
const wrapper = mount(
<GeneralCascader {...props} onLoadingData={mockLoadingFn} ref={ref} />
);
wrapper.find(Cascader).invoke("loadData")([
{
value: "zhejiang",
label: "Zhejiang",
isLeaf: false,
},
]);
expect(mockLoadingFn).toHaveBeenCalledWith([
{
value: "zhejiang",
label: "Zhejiang",
isLeaf: false,
loading: true,
},
]);
ref.current.setChildrenOption(
{
selectedOptions: [
{
value: "zhejiang",
label: "Zhejiang",
isLeaf: false,
},
],
},
[
{ name: "nanjing", label: "Nanjing" },
{ name: "ningbo", value: "Ningbo" },
]
);
wrapper.update();
expect(wrapper.find(Cascader).prop("options")).toEqual([
{
children: [
{ label: "Nanjing", name: "nanjing" },
{ name: "ningbo", value: "Ningbo" },
],
isLeaf: false,
label: "Zhejiang",
loading: false,
value: "zhejiang",
},
{ isLeaf: false, label: "Jiangsu", value: "jiangsu" },
]);
});
});
Example #13
Source File: ColumnComponent.tsx From next-basics with GNU General Public License v3.0 | 4 votes |
export function ColumnComponent(
props: ColumnComponentProps
): React.ReactElement {
const { column, field, rowIndex, hasLabel, formValue } = props;
const { label, name } = column;
const { name: fieldName, ...restField } = field;
const rowValue = formValue?.[rowIndex];
const labelNode = useMemo(
() => hasLabel && rowIndex === 0 && <div>{label}</div>,
[label, rowIndex, hasLabel]
);
const disabled = useMemo(
() => getRealValue(column.props?.disabled, [rowValue, rowIndex]),
[column.props?.disabled, rowValue, rowIndex]
);
const rules = useMemo(
() =>
column.rules?.map((rule) => {
if (typeof rule.validator === "function") {
return {
message: rule.message,
validator: partial(rule.validator, _, _, _, {
formValue,
rowValue,
rowIndex,
}),
};
}
if (rule.unique) {
return {
validator: (rule: any, value: any, cb: any) => {
if (!isNil(value) && value !== "") {
const valueList = formValue?.map((row) => row[name]);
const matchList = valueList?.filter(
(v, i) => isEqual(v, value) && i !== rowIndex
);
matchList?.length && cb(rule.message);
}
cb();
},
message: rule.message,
};
}
return rule;
}),
[column.rules, formValue, name, rowIndex, rowValue]
);
switch (column.type) {
case "input": {
const { placeholder, type, maxLength, allowClear } = column.props || {};
return (
<Form.Item
{...restField}
label={labelNode}
name={[fieldName, name]}
rules={rules}
>
<Input
style={{ width: "100%" }}
placeholder={placeholder}
disabled={disabled}
type={type}
maxLength={maxLength}
allowClear={allowClear}
/>
</Form.Item>
);
}
case "inputNumber": {
const { placeholder, min, max, step, precision } = column.props || {};
return (
<Form.Item
{...restField}
label={labelNode}
name={[fieldName, name]}
rules={rules}
>
<InputNumber
style={{ width: "100%" }}
placeholder={placeholder}
min={min}
max={max}
step={step}
precision={precision}
disabled={disabled}
/>
</Form.Item>
);
}
case "inputPassword": {
const { placeholder, visibilityToggle } = column.props || {};
return (
<Form.Item
{...restField}
label={labelNode}
name={[fieldName, name]}
rules={rules}
>
<Input.Password
style={{ width: "100%" }}
placeholder={placeholder}
disabled={disabled}
visibilityToggle={visibilityToggle}
/>
</Form.Item>
);
}
case "select": {
const {
placeholder,
allowClear,
mode,
options = [],
showSearch,
groupBy,
tokenSeparators,
maxTagCount,
popoverPositionType,
} = column.props || {};
const searchProps = showSearch
? {
showSearch: true,
filterOption: (input: string, option: any) => {
return option.label
?.toLowerCase()
.includes(input.trim().toLowerCase());
},
}
: {
showSearch: false,
};
return (
<Form.Item
{...restField}
label={labelNode}
name={[fieldName, name]}
rules={rules}
>
<Select
style={{ width: "100%" }}
placeholder={placeholder}
disabled={disabled}
allowClear={allowClear}
mode={mode}
tokenSeparators={tokenSeparators}
maxTagCount={maxTagCount}
{...(popoverPositionType === "parent"
? {
getPopupContainer: (triggerNode) => triggerNode.parentElement,
}
: {})}
{...searchProps}
>
{groupBy ? getOptsGroups(options, groupBy) : getOptions(options)}
</Select>
</Form.Item>
);
}
case "cascader": {
const {
placeholder,
allowClear,
options,
expandTrigger,
popupPlacement,
showSearch,
fieldNames,
} = column.props || {};
return (
<Form.Item
{...restField}
label={labelNode}
name={[fieldName, name]}
rules={rules}
>
<Cascader
style={{ width: "100%" }}
placeholder={placeholder}
allowClear={allowClear}
disabled={disabled}
expandTrigger={expandTrigger}
popupPlacement={popupPlacement}
options={options}
showSearch={showSearch}
fieldNames={fieldNames}
/>
</Form.Item>
);
}
default: {
return null;
}
}
}
Example #14
Source File: ColumnComponent.spec.tsx From next-basics with GNU General Public License v3.0 | 4 votes |
describe("ColumnComponent", () => {
it("default should work", () => {
const wrapper = shallow(
<ColumnComponent column={{} as Column} field={field} />
);
expect(wrapper.find(Form.Item)).toHaveLength(0);
});
it("input should work", () => {
const wrapper = shallow(
<ColumnComponent column={inputColumn} field={field} />
);
expect(wrapper.find(Input)).toHaveLength(1);
});
it("select should work", () => {
const wrapper = shallow(
<ColumnComponent column={selectColumn} field={field} />
);
expect(wrapper.find(Select)).toHaveLength(1);
expect(wrapper.find(Select.Option)).toHaveLength(1);
expect(wrapper.find(Select.OptGroup)).toHaveLength(0);
wrapper.setProps({
column: {
...selectColumn,
props: {
...selectColumn.props,
groupBy: "label",
popoverPositionType: "parent",
},
},
});
wrapper.update();
expect(wrapper.find(Select.Option)).toHaveLength(1);
expect(wrapper.find(Select.OptGroup)).toHaveLength(1);
expect(wrapper.find(Select).prop("filterOption")).toBeFalsy();
wrapper.setProps({
column: {
...selectColumn,
props: { ...selectColumn.props, showSearch: true },
},
});
wrapper.update();
expect(wrapper.find(Select).prop("filterOption")).not.toBeFalsy();
});
it("inputNumber should work", () => {
const wrapper = shallow(
<ColumnComponent column={inputNumberColumn} field={field} />
);
expect(wrapper.find(InputNumber)).toHaveLength(1);
});
it("inputPassword should work", () => {
const wrapper = shallow(
<ColumnComponent column={inputPasswordColumn} field={field} />
);
expect(wrapper.find(Input.Password)).toHaveLength(1);
});
it("cascader should work", () => {
const wrapper = shallow(
<ColumnComponent column={cascaderColumn} field={field} />
);
expect(wrapper.find(Cascader)).toHaveLength(1);
});
it("label should work", () => {
const wrapper = shallow(
<ColumnComponent
column={inputColumn}
field={field}
rowIndex={0}
hasLabel={true}
/>
);
expect(wrapper.find(Form.Item).prop("label")).toBeTruthy();
wrapper.setProps({
rowIndex: 1,
hasLabel: true,
});
expect(wrapper.find(Form.Item).prop("label")).toBeFalsy();
wrapper.setProps({
rowIndex: 1,
hasLabel: false,
});
expect(wrapper.find(Form.Item).prop("label")).toBeFalsy();
wrapper.setProps({
rowIndex: 0,
hasLabel: false,
});
expect(wrapper.find(Form.Item).prop("label")).toBeFalsy();
});
it("disabled should work", () => {
const wrapper = shallow(
<ColumnComponent
column={inputColumn}
field={field}
rowIndex={0}
hasLabel={true}
/>
);
wrapper.setProps({
column: {
...inputColumn,
props: {
...inputColumn.props,
disabled: (row: Record<string, any>, index: number) =>
row?.input === "input",
},
},
});
expect(wrapper.find(Input).prop("disabled")).toBeFalsy();
wrapper.setProps({
formValue: [{ input: "input" }],
});
expect(wrapper.find(Input).prop("disabled")).toBeTruthy();
});
it("unique should work", () => {
const column = {
...inputColumn,
rules: [
{ unique: true, message: "unique" },
{ required: true, message: "这个是必填项" },
],
};
const wrapper = shallow(
<ColumnComponent
column={column}
field={field}
rowIndex={0}
hasLabel={true}
formValue={[{ input: "a" }, { input: "a" }]}
/>
);
const validatorFn = jest.fn();
const customValidator = wrapper.find(Form.Item).prop("rules")[0].validator;
customValidator({ message: "unique" }, "a", validatorFn);
expect(validatorFn).toBeCalledWith("unique");
});
it("validator should work", () => {
const column = {
...inputColumn,
rules: [
{
validator: (rule, value, cb, fullValue) => cb(fullValue),
},
],
};
const formValue = [{ input: "a" }, { input: "b" }];
const rowIndex = 0;
const wrapper = shallow(
<ColumnComponent
column={column}
field={field}
rowIndex={rowIndex}
hasLabel={true}
formValue={formValue}
/>
);
const validatorFn = jest.fn();
const customValidator = wrapper.find(Form.Item).prop("rules")[0].validator;
customValidator({ message: "validator" }, "a", validatorFn);
expect(validatorFn).toBeCalledWith({
formValue,
rowIndex,
rowValue: formValue[rowIndex],
});
});
});
Example #15
Source File: DynamicFormItem.tsx From next-basics with GNU General Public License v3.0 | 4 votes |
export function RowFormItem(props: RowFormItemProps): React.ReactElement {
const { row, form, columns, prefixId, rowIndex } = props;
const { getFieldDecorator } = form;
// 后台搜索中
const [fetching, setFetching] = useState(false);
const [userList, setUserList] = useState([]);
const handleChange = (value: any, column: FormItemColumnsProps) => {
props.onChange?.(value, column.name);
};
const customizeFilter = (label: string) => {
label = label || "label";
return function (inputValue: string, path: CascaderOptionType[]) {
const filterValues = inputValue
.split(" ")
.filter((item) => item)
.map((item) => item.toLocaleLowerCase());
for (let j = 0; j < filterValues.length; j++) {
if (
!path.some((option) =>
(option[label] as string).toLowerCase().includes(filterValues[j])
)
) {
return false;
}
}
return true;
};
};
const fetchInstanceList = async (objectId: "USER", keyword: string) => {
return (
await InstanceApi_postSearch(objectId, {
page: 1,
page_size: 20,
fields: {
name: true,
},
query: {
$or: map(uniq(["name"]), (v) => ({
[v]: { $like: `%${keyword}%` },
})),
},
})
).list;
};
const searchUser = async (value: string) => {
setFetching(true);
const data = await fetchInstanceList("USER", value);
setUserList(data);
setFetching(false);
};
const handleSearchUser = (value: string) => {
searchUser(value);
};
const handleFocus = () => {
handleSearchUser("");
};
const renderComponent = (column: FormItemColumnsProps) => {
const { inputProps = {}, selectProps = {}, cascaderProps = {} } = column;
if (column.type === "select") {
return (
<Select
dropdownClassName={style.select}
style={{ width: "100%" }}
onChange={(value) => handleChange(value, column)}
placeholder={selectProps.placeholder}
disabled={
selectProps.disabled || selectProps.disabledHandler?.(row, rowIndex)
}
mode={selectProps.mode}
optionFilterProp="children"
allowClear={selectProps.allowClear}
maxTagCount={selectProps.maxTagCount}
showSearch
{...(selectProps.popoverPositionType === "parent"
? { getPopupContainer: (triggerNode) => triggerNode.parentElement }
: {})}
>
{selectProps.options?.map((option) => (
<Select.Option
key={option.value}
value={option.value}
className={style.option}
>
{option.label}
</Select.Option>
))}
</Select>
);
} else if (column.type === "userSelect") {
return (
<Select
dropdownClassName={style.select}
style={{ width: "100%" }}
onChange={(value) => handleChange(value, column)}
placeholder={selectProps.placeholder}
disabled={
selectProps.disabled || selectProps.disabledHandler?.(row, rowIndex)
}
onSearch={debounce((value) => {
handleSearchUser(value as string);
}, 500)}
onFocus={handleFocus}
loading={fetching}
mode={selectProps.mode}
optionFilterProp="children"
allowClear={selectProps.allowClear}
showSearch
>
{userList.map((d) => (
<Select.Option value={d.name} key={d.name} className={style.option}>
{d.name}
</Select.Option>
))}
</Select>
);
} else if (column.type === "cascader") {
return (
<Cascader
style={{ width: "100%" }}
options={cascaderProps.options}
onChange={(value) => handleChange(value, column)}
placeholder={cascaderProps.placeholder}
disabled={
cascaderProps.disabled ||
cascaderProps.disabledHandler?.(row, rowIndex)
}
fieldNames={cascaderProps.fieldNames}
expandTrigger={cascaderProps.expandTrigger}
allowClear={cascaderProps.allowClear}
showSearch={
cascaderProps.showSearch && {
limit: cascaderProps.limit,
filter: customizeFilter(cascaderProps?.fieldNames?.label),
}
}
/>
);
} else if (column.type === "password") {
return (
<Input.Password
onChange={(e) => handleChange(e.target.value, column)}
{...inputProps}
autoComplete="new-password"
disabled={
inputProps.disabled || inputProps.disabledHandler?.(row, rowIndex)
}
/>
);
} else if (column.type === "inputNumber") {
return (
<InputNumber
style={{ width: "100%" }}
onChange={(value) => handleChange(value, column)}
placeholder={inputProps.placeholder}
disabled={
inputProps.disabled || inputProps.disabledHandler?.(row, rowIndex)
}
max={inputProps.max}
min={inputProps.min}
step={inputProps.step}
/>
);
} else {
return (
<Input
onChange={(e) => handleChange(e.target.value, column)}
placeholder={inputProps.placeholder}
type={inputProps.type}
disabled={
inputProps.disabled || inputProps.disabledHandler?.(row, rowIndex)
}
/>
);
}
};
return (
<>
{columns.map((item, index) => {
return (
<Col style={{ flex: item.flex ?? "1", minWidth: 0 }} key={index}>
<Form.Item>
{getFieldDecorator(`${prefixId}.${item.name}`, {
initialValue: row[item.name],
rules: item.rules,
})(renderComponent(item))}
</Form.Item>
</Col>
);
})}
</>
);
}
Example #16
Source File: index.tsx From S2 with MIT License | 4 votes |
AdvancedSort: React.FC<AdvancedSortProps> = ({
sheet,
className,
icon,
text,
ruleText,
dimensions,
ruleOptions,
sortParams,
onSortOpen,
onSortConfirm,
}) => {
const [isSortVisible, setIsModalVisible] = useState(false);
const [isCustomVisible, setIsCustomVisible] = useState(false);
const [ruleList, setRuleList] = useState([]);
const [rules, setRules] = useState([]);
const [manualDimensionList, setManualDimensionList] = useState([]);
const [dimensionList, setDimensionList] = useState([]);
const [sortBy, setSortBy] = useState([]);
const [currentDimension, setCurrentDimension] = useState<Dimension>();
const [form] = Form.useForm();
const SORT_RULE_OPTIONS = React.useMemo(getSortRuleOptions, []);
const SORT_METHOD = React.useMemo(getSortMethod, []);
const handleModal = () => {
setIsModalVisible(!isSortVisible);
};
const sortClick = () => {
if (onSortOpen) {
onSortOpen();
}
handleModal();
};
const handleCustom = () => {
setIsCustomVisible(!isCustomVisible);
};
const handleDimension = (dimension) => {
if (!find(ruleList, (item) => item.field === dimension.field)) {
setCurrentDimension(dimension);
setRuleList([...ruleList, dimension]);
}
setDimensionList(
filter(dimensionList, (item) => item.field !== dimension.field),
);
};
const handleCustomSort = (dimension, splitOrders) => {
handleCustom();
setCurrentDimension(dimension);
if (splitOrders) {
setSortBy(uniq(splitOrders));
} else {
setSortBy(
uniq(
find(manualDimensionList, (item) => item.field === dimension.field)
?.list || [],
),
);
}
};
const customSort = () => {
handleCustom();
const currentFieldValue = form.getFieldsValue([currentDimension?.field]);
currentFieldValue.sortBy = sortBy;
form.setFieldsValue({ [currentDimension?.field]: currentFieldValue });
const newRuleList = map(ruleList, (item) => {
if (item.field === currentDimension?.field) {
return {
...item,
rule: 'sortBy',
sortBy,
sortMethod: '',
sortByMeasure: '',
};
}
return item;
});
setRuleList(newRuleList);
};
const customCancel = () => {
handleCustom();
};
const deleteRule = (dimension) => {
setRuleList(filter(ruleList, (item) => item.field !== dimension.field));
setDimensionList([...dimensionList, dimension]);
};
const onFinish = () => {
const ruleValue = form.getFieldsValue();
const { values = [] } = sheet.dataCfg.fields;
const ruleValues = [];
const currentSortParams = [];
forEach(keys(ruleValue), (item) => {
const { sortMethod, rule = [], sortBy: currentSortBy } = ruleValue[item];
const current: SortParam = { sortFieldId: item };
if (rule[0] === 'sortByMeasure' || rule[1]) {
// 如果不是数值 key ,则按照汇总值排序
if (!includes(values, rule[1])) {
current.sortByMeasure = TOTAL_VALUE;
} else {
current.sortByMeasure = rule[1];
}
current.sortMethod = sortMethod;
current.query = {
$$extra$$: rule[1],
};
} else if (rule[0] === 'sortBy') {
current.sortBy = currentSortBy;
} else {
current.sortMethod = sortMethod;
}
ruleValues.push({ field: item, ...ruleValue[item] });
currentSortParams.push(current);
});
if (onSortConfirm) {
onSortConfirm(ruleValues, currentSortParams);
}
handleModal();
};
const getDimensionList = (list) => {
return filter(
list,
(item) => !find(sortParams, (i) => i.sortFieldId === item.field),
);
};
const getManualDimensionList = () => {
if (dimensions) {
return dimensions;
}
const { fields = {} } = sheet.dataCfg || {};
const { rows = [], columns = [] } = fields;
return map([...rows, ...columns], (item) => {
return {
field: item,
name: sheet.dataSet.getFieldName(item),
list: sheet.dataSet.getDimensionValues(item),
};
});
};
const getRuleOptions = () => {
if (ruleOptions) {
return ruleOptions;
}
return map(SORT_RULE_OPTIONS, (item) => {
if (item.value === 'sortByMeasure') {
const { values } = sheet.dataCfg.fields || {};
item.children = map(values, (vi) => {
return { label: sheet.dataSet.getFieldName(vi), value: vi };
});
}
return item;
});
};
const getRuleList = () => {
return map(sortParams, (item) => {
const {
sortFieldId,
sortMethod,
sortBy: currentSortBy,
sortByMeasure,
} = item;
let rule: string[];
if (currentSortBy) {
rule = ['sortBy'];
} else if (sortByMeasure) {
rule = ['sortByMeasure', sortByMeasure];
} else {
rule = ['sortMethod'];
}
return {
field: sortFieldId,
name: sheet.dataSet.getFieldName(sortFieldId),
rule,
sortMethod,
sortBy: currentSortBy,
sortByMeasure,
};
});
};
const renderSide = () => {
return (
<Sider width={120} className={`${ADVANCED_SORT_PRE_CLS}-sider-layout`}>
<div className={`${ADVANCED_SORT_PRE_CLS}-title`}>
{i18n('可选字段')}
</div>
<div>
{map(dimensionList, (item) => {
return (
<div
className={`${ADVANCED_SORT_PRE_CLS}-dimension-item`}
key={item.field}
onClick={() => {
handleDimension(item);
}}
>
{item.name}
</div>
);
})}
</div>
</Sider>
);
};
const renderContent = () => {
return (
<Content className={`${ADVANCED_SORT_PRE_CLS}-content-layout`}>
<div className={`${ADVANCED_SORT_PRE_CLS}-title`}>
{ruleText || i18n('按以下规则进行排序(优先级由低到高)')}
</div>
<Form
form={form}
name="form"
className={`${ADVANCED_SORT_PRE_CLS}-custom-form`}
>
{map(ruleList, (item) => {
const {
field,
name,
rule,
sortMethod,
sortBy: currentSortBy,
} = item || {};
return (
<Form.Item name={field} key={field}>
<Form.Item name={[field, 'name']} initialValue={name} noStyle>
<Select
className={`${ADVANCED_SORT_PRE_CLS}-select`}
size="small"
/>
</Form.Item>
<span className={`${ADVANCED_SORT_PRE_CLS}-field-prefix`}>
{i18n('按')}
</span>
<Form.Item
name={[field, 'rule']}
initialValue={rule || ['sortMethod']}
noStyle
>
<Cascader
options={rules}
expandTrigger="hover"
size="small"
allowClear={false}
/>
</Form.Item>
<Form.Item shouldUpdate noStyle>
{({ getFieldValue }) => {
return !isEqual(getFieldValue([field, 'rule']), [
'sortBy',
]) ? (
<Form.Item
shouldUpdate
noStyle
name={[field, 'sortMethod']}
initialValue={toUpper(sortMethod) || 'ASC'}
>
<Radio.Group
className={`${ADVANCED_SORT_PRE_CLS}-rule-end`}
>
{map(SORT_METHOD, (i) => {
return (
<Radio value={i.value} key={i.value}>
{i.name}
</Radio>
);
})}
</Radio.Group>
</Form.Item>
) : (
<>
<a
className={`${ADVANCED_SORT_PRE_CLS}-rule-end`}
onClick={() => {
handleCustomSort(item, currentSortBy);
}}
>
{i18n('设置顺序')}
</a>
<Form.Item
noStyle
name={[field, 'sortBy']}
initialValue={currentSortBy}
/>
</>
);
}}
</Form.Item>
<DeleteOutlined
className={`${ADVANCED_SORT_PRE_CLS}-rule-end-delete`}
onClick={() => {
deleteRule(item);
}}
/>
</Form.Item>
);
})}
</Form>
</Content>
);
};
useEffect(() => {
if (isSortVisible) {
const initRuleList = getRuleList();
const manualDimensions = getManualDimensionList();
const initDimensionList = getDimensionList(manualDimensions);
const initRuleOptions = getRuleOptions();
setRuleList(initRuleList);
setManualDimensionList(manualDimensions);
setDimensionList(initDimensionList);
setRules(initRuleOptions);
}
}, [isSortVisible]);
return (
<div className={cx(ADVANCED_SORT_PRE_CLS, className)}>
<Button
onClick={sortClick}
icon={icon || <SortIcon />}
size="small"
className={`${ADVANCED_SORT_PRE_CLS}-btn`}
>
{text || i18n('高级排序')}
</Button>
<Modal
title={text || i18n('高级排序')}
visible={isSortVisible}
onOk={onFinish}
onCancel={handleModal}
okText={i18n('确定')}
cancelText={i18n('取消')}
destroyOnClose
className={`${ADVANCED_SORT_PRE_CLS}-modal`}
>
<Layout>
{renderSide()}
{renderContent()}
</Layout>
</Modal>
<Modal
title={i18n('手动排序')}
visible={isCustomVisible}
onOk={customSort}
onCancel={customCancel}
okText={i18n('确定')}
cancelText={i18n('取消')}
destroyOnClose
className={`${ADVANCED_SORT_PRE_CLS}-custom-modal`}
>
<CustomSort splitOrders={sortBy} setSplitOrders={setSortBy} />
</Modal>
</div>
);
}
Example #17
Source File: index.tsx From antdp with MIT License | 4 votes |
QuickForm: QuickFormComponent = (props, ref) => {
const {
collapseAttributes,
panelAttributes,
visible = false,
type = 'cardform',
extra,
formDatas,
colspan = 3,
header,
defaultFormLayout = 'vertical',
defaultFormItemLayout = formDefaultFormItemLayout,
size = 'default',
formHide,
initialHide,
...otherProps
} = props;
const [hide] = useFormItemHide(formHide)
hide.setInitialValues(initialHide || {}, true)
const HideFormItemDoM = []; // 隐藏的表单
const FormItemDoM = [];
let rowcolspan: string | any; // col 里的布局
let formitemlayout: string | any; // formitem 的布局
for (var i = 0; i < formDatas.length; i++) {
if (formDatas[i].hideInForm) {
HideFormItemDoM.push(formDatas[i]);
} else {
FormItemDoM.push(formDatas[i]);
}
}
// 计算一个row里排几个表单;
const result = [];
for (let i = 0, j = FormItemDoM.length; i < j; i++) {
if (FormItemDoM[i].full) {
result.push(FormItemDoM.slice(i, i + 1));
} else {
if (FormItemDoM[i + 1] && FormItemDoM[i + 1].full) {
result.push(FormItemDoM.slice(i, i + 1));
} else if (FormItemDoM[i].defaultcolspan) {
result.push(FormItemDoM.slice(i, i + FormItemDoM[i].defaultcolspan));
i = i + FormItemDoM[i].defaultcolspan - 1;
} else {
result.push(FormItemDoM.slice(i, i + colspan));
i = i + colspan - 1;
}
}
}
// 渲染成表单;
const CollapseFormDoM = (item: any, idx: React.Key | null | undefined) => {
const {
label,
name,
attributes,
type,
options,
onlyimg,
defaultFormItemLayout,
full,
defaultRowColspan,
hideInForm,
descItem,
render,
// 用于判断是否需要进行隐藏显示 (在组件外层包裹一层组件用于控制item显示和隐藏)
isHide,
...otherts
} = item;
const dataList = options || [];
const optionDatas =
dataList &&
dataList.length > 0 &&
dataList.map(
(
{ value, label, ...others }: any,
_idx: React.Key | null | undefined,
) => {
if (type === 'select' || type === 'Select') {
return (
<Option value={value} key={_idx} {...others}>
{label}
</Option>
);
} else if (type === 'radio' || type === 'Radio') {
return (
<Radio.Button value={value} key={_idx} {...others}>
{label}
</Radio.Button>
);
}
},
);
const selectOption = optionDatas ? optionDatas : [];
const rowcolspan_num = [
colLayout_one,
colLayout_two,
colLayout_third,
colLayout_fourth,
];
const formitemlayout_num = [
fromItemLayout_conspan_one,
fromItemLayout_conspan_two,
fromItemLayout_conspan_third,
fromItemLayout_conspan_fourth,
];
if (colspan && full) {
rowcolspan = colLayout_one;
if (colspan === 3 || colspan === 4) {
if (props.defaultFormItemLayout) {
// 如果FormCollapse组件上带有defaulFormItemLayout参数
formitemlayout = props.defaultFormItemLayout;
// eslint-disable-next-line max-depth
if (item.defaultFormItemLayout || item.defaultRowColspan) {
// 如果FormCollapse组件内部的某个小组件带有defaulFormItemLayout参数
formitemlayout = item.defaultFormItemLayout;
rowcolspan = item.defaultRowColspan; // 单独的表单col 布局
}
} else if (item.defaultFormItemLayout || item.defaultRowColspan) {
//FormCollapse组件内部只有某个小组件带了defaulFormItemLayout参数
formitemlayout = item.defaultFormItemLayout;
rowcolspan = item.defaultRowColspan; // 单独的表单col 布局
} else {
formitemlayout = fromItemLayout_third_row;
}
} else {
formitemlayout = fromItemLayout_two_row;
}
} else {
rowcolspan = rowcolspan_num[colspan - 1];
if (props.defaultFormItemLayout) {
formitemlayout = props.defaultFormItemLayout;
if (item.defaultFormItemLayout || item.defaultRowColspan) {
// 如果FormCollapse组件内部的某个小组件带有defaultFormItemLayout参数
formitemlayout = item.defaultFormItemLayout;
rowcolspan = item.defaultRowColspan; // 单独的表单col 布局
}
} else if (item.defaultFormItemLayout || item.defaultRowColspan) {
formitemlayout =
item.defaultFormItemLayout || formitemlayout_num[colspan - 1];
rowcolspan = item.defaultRowColspan; // 单独的表单col 布局
} else {
formitemlayout = formitemlayout_num[colspan - 1];
}
}
// 上传图片的按钮展示
const uploadButtonDom = () => {
if (item.attributes.listType === 'picture-card') {
if (item.attributes.imageUrl && item.attributes.imageUrl !== '') {
return (
<img
src={item.attributes.imageUrl}
alt="avatar"
style={{ width: '100%' }}
/>
);
} else if (item.attributes.fileList) {
// 上传的图片大于或等于8张时 并且 没有onlyimg参数,显示icon上传按钮
if (item.attributes.fileList.length >= 8 && !onlyimg) {
return (
<div>
{item.attributes.loading === 'loading' ? (
<LoadingOutlined />
) : (
<PlusOutlined />
)}
<div className="ant-upload-text">上传</div>
</div>
);
// 上传的图片大于或等于maxCount张时 并且 有onlyimg参数,不显示上传按钮
} else if (item.attributes.maxCount && item.attributes.fileList.length >= item.attributes.maxCount && onlyimg) {
return null;
}
return (
<div>
{item.attributes.loading === 'loading' ? (
<LoadingOutlined />
) : (
<PlusOutlined />
)}
<div className="ant-upload-text">上传</div>
</div>
);
}
} else {
return (
<div>
<Button>
<UploadOutlined />
上传
</Button>
</div>
);
}
};
let renderItem = (
<Col
key={idx}
style={{
display: item.hideInForm ? 'none' : 'block',
padding:
defaultFormLayout && defaultFormLayout === 'vertical'
? '0px 12px 8px 12px'
: '0',
}}
className={
defaultFormLayout && defaultFormLayout === 'vertical'
? 'antdp-FormCol'
: ''
}
{...rowcolspan}
>
<FormItem
className="antdp-FormItem"
colon={false}
label={label}
name={name}
{...(defaultFormLayout && defaultFormLayout === 'vertical'
? null
: formitemlayout)}
{...otherts}
>
{name ? (
(() => {
// 组件基础参数
const componentprams = {
size: size ? size : 'small',
...attributes,
};
if (type === 'select' || type === 'Select') {
return (
<Select
dropdownMatchSelectWidth={false}
allowClear
placeholder={
attributes && attributes.disabled ? '' : `请选择${label} `
}
{...componentprams}
>
{selectOption}
</Select>
);
} else if (type === 'radio' || type === 'Radio') {
return (
<Radio.Group size={size ? size : 'small'} {...attributes}>
{selectOption}
</Radio.Group>
);
} else if (type === 'datePicker' || type === 'DatePicker') {
return (
<DatePicker
locale={locale}
style={{ width: '100%' }}
placeholder={
attributes && attributes.disabled ? '' : `请选择${label} `
}
{...componentprams}
/>
);
} else if (type === 'monthPicker' || type === 'MonthPicker') {
return (
<MonthPicker
locale={locale}
style={{ width: '100%' }}
placeholder={
attributes && attributes.disabled ? '' : `请选择${label} `
}
{...componentprams}
/>
);
} else if (type === 'rangePicker' || type === 'RangePicker') {
return (
<RangePicker
locale={locale}
style={{ width: '100%' }}
{...componentprams}
/>
);
} else if (
type === 'timepicker' ||
type === 'timePicker' ||
type === 'TimePicker'
) {
return (
<TimePicker
locale={locale}
style={{ width: '100%' }}
placeholder={
attributes && attributes.disabled ? '' : `请选择${label} `
}
{...componentprams}
/>
);
} else if (type === 'cascader' || type === 'Cascader') {
return (
<Cascader
placeholder={
attributes && attributes.disabled ? '' : `请选择${label} `
}
{...componentprams}
/>
);
} else if (type === 'textarea' || type === 'TextArea') {
return (
<Input.TextArea
placeholder={
attributes && attributes.disabled ? '' : `请输入${label} `
}
{...componentprams}
/>
);
} else if (type === 'inputNumber' || type === 'InputNumber') {
return (
<InputNumber
placeholder={
attributes && attributes.disabled ? '' : `请输入${label} `
}
style={{ width: '100%' }}
{...componentprams}
/>
);
} else if (type === 'treeSelect' || type === 'TreeSelect') {
return (
<TreeSelect
placeholder={
attributes && attributes.disabled ? '' : `请选择${label} `
}
{...componentprams}
/>
);
} else if (type === 'checkbox' || type === 'Checkbox') {
if (
(item.options && item.options.length > 0) ||
(item.option && item.option.length > 0)
) {
return (
<Checkbox.Group
options={item.options || item.option}
{...attributes}
/>
);
}
return (
<Checkbox {...attributes}>
{label || item.checkboxLable}
</Checkbox>
);
} else if (type === 'UploadGrid' || type === 'uploadGrid') {
return (
<UploadGrid {...attributes}>{uploadButtonDom()}</UploadGrid>
);
} else if (type === 'autoComplete' || type === 'AutoComplete') {
return (
<AutoComplete
placeholder={
attributes && attributes.disabled ? '' : `请输入${label} `
}
{...componentprams}
/>
);
} else if (type === 'Password') {
return (
<Input.Password
placeholder={
attributes && attributes.disabled ? '' : `请输入${label} `
}
{...componentprams}
/>
);
} else if (type === 'inputCount' || type === 'InputCount') {
return (
<InputCount
placeholder={
attributes && attributes.disabled ? '' : `请输入${label} `
}
{...attributes}
/>
);
} else if (type === 'render') {
return render && render
} else {
if (
(attributes && attributes.type === 'Search') ||
type === 'InputSearch'
) {
const suffix = (
<AudioOutlined
style={{
fontSize: 16,
color: '#fff',
}}
/>
);
return (
<Search
suffix={suffix}
placeholder={
attributes && attributes.disabled
? ''
: `请输入${label} `
}
{...componentprams}
/>
);
}
return (
<Input
placeholder={
attributes && attributes.disabled ? '' : `请输入${label} `
}
{...componentprams}
/>
);
}
})()
) : (
<Input
placeholder={
attributes && attributes.disabled ? '' : `请输入${label} `
}
size={size}
{...attributes}
/>
)}
</FormItem>
</Col>
)
if (isHide && name) {
return (
<Hide key={idx} name={name}>
{renderItem}
</Hide>
);
}
return renderItem;
};
// 隐藏的表单集合
const hideCollapseForm = HideFormItemDoM.map((item, idx) =>
CollapseFormDoM(item, idx),
);
// 表单集合
const CollapseForm = result.map((it, indix) => {
return (
<Row key={indix}>
{it.map((item, idx) => {
return CollapseFormDoM(item, idx);
})}
</Row>
);
});
// Form+表单集合
const FormDom = (
<HideContext.Provider value={hide} >
<ConfigProvider locale={zhCN}>
<Form
layout={defaultFormLayout ? defaultFormLayout : 'horizontal'}
ref={ref}
{...(defaultFormLayout && defaultFormLayout === 'vertical'
? null
: formitemlayout)}
{...otherProps}
>
<Row>{hideCollapseForm}</Row>
<div>{CollapseForm}</div>
</Form>
</ConfigProvider>
</HideContext.Provider>
);
// type 为 modal时没有折叠,没有标题,直接显示form表单内容
if (type === 'modal') {
return <div style={{ margin: -10 }}>{FormDom}</div>
}
// type 为CardPro 带标题
if (type === 'CardPro') {
return (
<CardPro title={header}>
<div className="antdp-FormBox">{FormDom}</div>
</CardPro>
);
}
// type 为cardform 时 显示表单,分割线 分离每个表单
if (type === 'cardform') {
return (
<div>
<h3 className="antdp-FormTitle">{header}</h3>
{FormDom}
<Divider type="horizontal" className="antdp-FormDivider" />
</div>
);
}
return (
<Collapse
defaultActiveKey={!visible ? ['1'] : ''}
{...collapseAttributes}
className="antdp-mb10"
>
<Panel header={header} key="1" {...panelAttributes} extra={extra}>
{FormDom}
</Panel>
</Collapse>
);
}