• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SkylerHu / antd-restful / #3

10 Jul 2025 02:38PM UTC coverage: 77.983%. First build
#3

push

web-flow
feat: 扩展组件支持远程获取数据 (#1)

扩展组件支持远程获取数据

1016 of 1392 branches covered (72.99%)

Branch coverage included in aggregate %.

1352 of 1644 new or added lines in 28 files covered. (82.24%)

1389 of 1692 relevant lines covered (82.09%)

26.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.52
/src/components/formitems/ExpansionView.jsx
1
import React, { useEffect, useRef, useState } from "react";
2
import PropTypes from "prop-types";
3
import { Alert, Input, Space, Spin } from "antd";
4
import braceExpansion from "brace-expansion";
5
import { dequal as deepEqual } from "dequal";
6
import { READ_ONLY_CLASS } from "src/common/constants";
7
import { commonFormat } from "src/common/parser";
8
import { isArray, isBlank, isFunction } from "src/common/typeTools";
9
import LongText from "src/components/LongText";
10
import { useDeepCompareMemoize } from "src/hooks";
11
import { formatRequestError, useSafeRequest } from "src/requests";
12

13
/**
14
 * 在表单中需要配合特定的 validator: 需要校验 !isBlank(input) && !loading && !error 才算是预期的结果
15
 */
16
const ExpansionView = ({
3✔
17
  style,
18
  className,
19
  value,
20
  onChange,
21

22
  enableBraceExpansion = false,
15✔
23

24
  restful,
25
  reqConfig,
26
  inputKey = "input",
15✔
27
  baseParams,
28
  inputMinEnter = 1,
20✔
29
  valueTemplate,
30
  disabled = false,
20✔
31
  readOnly = false,
18✔
32
  longTextProps,
33
  longErrorProps,
34
  antdSpaceProps,
35
  antdInputProps,
36
  antdAlertProps,
37
}) => {
38
  const [makeRequest] = useSafeRequest();
20✔
39
  const reqConfigRef = useRef(reqConfig);
20✔
40

41
  const [inputValue, setInputValue] = useState();
20✔
42
  const [outputData, setOutputData] = useState({ output: value?.output, error: value?.error });
20✔
43
  const [loading, setLoading] = useState(false);
20✔
44

45
  const currentValueRef = useRef();
20✔
46

47
  useEffect(() => {
20✔
48
    setInputValue((oldV) => (oldV === value?.input ? oldV : value?.input));
5✔
49
  }, [value]);
50

51
  useEffect(() => {
20✔
52
    const newV = { input: inputValue, ...outputData, loading };
20✔
53
    if (isFunction(onChange) && !deepEqual(currentValueRef.current, newV)) {
20!
NEW
54
      currentValueRef.current = newV;
×
NEW
55
      onChange(newV);
×
56
    }
57
  }, [outputData, onChange, inputValue, loading]);
58

59
  const memBaseParams = useDeepCompareMemoize(baseParams);
20✔
60

61
  useEffect(() => {
20✔
62
    // 搜索值变化时
63
    if (disabled || readOnly || (!restful && !enableBraceExpansion)) {
13✔
64
      return;
2✔
65
    }
66
    if (isBlank(inputValue)) {
11✔
67
      // 清空输出和错误
68
      setOutputData({ output: null, error: null });
8✔
69
      return;
8✔
70
    }
71

72
    if (restful && inputValue.length < inputMinEnter) {
3!
NEW
73
      return;
×
74
    }
75

76
    let reqV = enableBraceExpansion ? braceExpansion(inputValue) : inputValue;
3✔
77
    if (valueTemplate) {
3!
NEW
78
      if (isArray(reqV)) {
×
NEW
79
        reqV = reqV.map((v) => commonFormat(valueTemplate, { ...memBaseParams, value: v }));
×
80
      } else {
NEW
81
        reqV = commonFormat(valueTemplate, { ...memBaseParams, value: reqV });
×
82
      }
83
    }
84
    if (!restful) {
3✔
85
      // 无需远程请求,直接设置输出
86
      setOutputData({ output: reqV, error: null });
1✔
87
      return;
1✔
88
    }
89

90
    makeRequest({ delay: 200, key: "expansion" })
2✔
91
      .post(restful, { ...memBaseParams, [inputKey]: reqV }, { disableNotiError: true, ...reqConfigRef.current })
92
      .then((response) => {
93
        const { output, error } = response.data;
1✔
94
        setOutputData({ output, error });
1✔
95
      })
96
      .catch((error) => {
97
        const { message, description } = formatRequestError(error);
1✔
98
        const errorText = `${message}\n${description}`;
1✔
99
        const outputData = { output: null, error: errorText };
1✔
100
        setOutputData({ ...outputData });
1✔
101
      })
102
      .finally(() => setLoading(false));
2✔
103
  }, [
104
    restful,
105
    disabled,
106
    inputValue,
107
    inputKey,
108
    memBaseParams,
109
    readOnly,
110
    enableBraceExpansion,
111
    makeRequest,
112
    inputMinEnter,
113
    valueTemplate,
114
  ]);
115

116
  return (
20✔
117
    <Space
118
      direction="vertical"
119
      style={{ width: "100%", ...style }}
120
      className={readOnly ? (className ? `${className} ${READ_ONLY_CLASS}` : READ_ONLY_CLASS) : className}
22!
121
      {...antdSpaceProps}
122
    >
123
      {!readOnly ? (
20✔
124
        <Input
125
          value={inputValue}
126
          disabled={disabled}
127
          readOnly={readOnly}
128
          {...antdInputProps}
129
          onChange={(e) => setInputValue(e.target.value)}
3✔
130
        />
131
      ) : (
132
        inputValue
133
      )}
134
      <Spin spinning={loading}>
135
        {outputData?.error && (
21✔
136
          <Alert
137
            message={<LongText value={outputData.error} {...longTextProps} {...longErrorProps} />}
138
            type="error"
139
            closable={false}
140
            {...antdAlertProps}
141
          />
142
        )}
143
        {outputData?.output && !outputData?.error && (
24✔
144
          <Alert
145
            message={<LongText value={outputData.output} {...longTextProps} />}
146
            type="success"
147
            closable={false}
148
            {...antdAlertProps}
149
          />
150
        )}
151
      </Spin>
152
    </Space>
153
  );
154
};
155

156
ExpansionView.propTypes = {
3✔
157
  style: PropTypes.object,
158
  className: PropTypes.string,
159

160
  value: PropTypes.shape({
161
    input: PropTypes.string,
162
    output: PropTypes.any,
163
    error: PropTypes.string,
164
  }),
165
  onChange: PropTypes.func,
166

167
  // 开启后,支持 brace-expansion 的语法输入, as known from sh/bash, in JavaScript.
168
  // https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html
169
  enableBraceExpansion: PropTypes.bool,
170

171
  restful: PropTypes.string,
172
  // axios的配置
173
  reqConfig: PropTypes.object,
174
  // 输入的值作为value,inputKey 是请求的key,请求到后端
175
  inputKey: PropTypes.string,
176
  // 输入最小长度; 仅在 restful 有值时有效;有些场景下输入字符较少请求后端无意义
177
  inputMinEnter: PropTypes.number,
178
  // 请求的额外参数
179
  baseParams: PropTypes.object,
180
  // 输出值的模板,{value}则是输入的值,其余key值从 baseParams 中获取;
181
  valueTemplate: PropTypes.string,
182

183
  longTextProps: PropTypes.object,
184
  longErrorProps: PropTypes.object,
185
  // 原生组件属性
186
  disabled: PropTypes.bool,
187
  readOnly: PropTypes.bool,
188
  antdSpaceProps: PropTypes.object,
189
  antdInputProps: PropTypes.object,
190
  antdAlertProps: PropTypes.object,
191
};
192

193
export default ExpansionView;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc