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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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}&nbsp;
      <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 vote down vote up
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>
          存储策略&nbsp;
          <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 vote down vote up
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>
          存储策略&nbsp;
          <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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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>
  );
}