mock平台

Postman.js 32KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033
  1. import React, { PureComponent as Component } from 'react';
  2. import PropTypes from 'prop-types';
  3. import {
  4. Button,
  5. Input,
  6. Checkbox,
  7. Modal,
  8. Select,
  9. Spin,
  10. Icon,
  11. Collapse,
  12. Tooltip,
  13. Tabs,
  14. Switch,
  15. Row,
  16. Col,
  17. Alert
  18. } from 'antd';
  19. import constants from '../../constants/variable.js';
  20. import AceEditor from 'client/components/AceEditor/AceEditor';
  21. import _ from 'underscore';
  22. import { isJson, deepCopyJson, json5_parse } from '../../common.js';
  23. import axios from 'axios';
  24. import ModalPostman from '../ModalPostman/index.js';
  25. import CheckCrossInstall, { initCrossRequest } from './CheckCrossInstall.js';
  26. import './Postman.scss';
  27. import ProjectEnv from '../../containers/Project/Setting/ProjectEnv/index.js';
  28. import json5 from 'json5';
  29. const { handleParamsValue, ArrayToObject, schemaValidator } = require('common/utils.js');
  30. const {
  31. handleParams,
  32. checkRequestBodyIsRaw,
  33. handleContentType,
  34. crossRequest,
  35. checkNameIsExistInArray
  36. } = require('common/postmanLib.js');
  37. const createContext = require('common/createContext')
  38. const HTTP_METHOD = constants.HTTP_METHOD;
  39. const InputGroup = Input.Group;
  40. const Option = Select.Option;
  41. const Panel = Collapse.Panel;
  42. export const InsertCodeMap = [
  43. {
  44. code: 'assert.equal(status, 200)',
  45. title: '断言 httpCode 等于 200'
  46. },
  47. {
  48. code: 'assert.equal(body.code, 0)',
  49. title: '断言返回数据 code 是 0'
  50. },
  51. {
  52. code: 'assert.notEqual(status, 404)',
  53. title: '断言 httpCode 不是 404'
  54. },
  55. {
  56. code: 'assert.notEqual(body.code, 40000)',
  57. title: '断言返回数据 code 不是 40000'
  58. },
  59. {
  60. code: 'assert.deepEqual(body, {"code": 0})',
  61. title: '断言对象 body 等于 {"code": 0}'
  62. },
  63. {
  64. code: 'assert.notDeepEqual(body, {"code": 0})',
  65. title: '断言对象 body 不等于 {"code": 0}'
  66. }
  67. ];
  68. const ParamsNameComponent = props => {
  69. const { example, desc, name } = props;
  70. const isNull = !example && !desc;
  71. const TooltipTitle = () => {
  72. return (
  73. <div>
  74. {example && (
  75. <div>
  76. 示例: <span className="table-desc">{example}</span>
  77. </div>
  78. )}
  79. {desc && (
  80. <div>
  81. 备注: <span className="table-desc">{desc}</span>
  82. </div>
  83. )}
  84. </div>
  85. );
  86. };
  87. return (
  88. <div>
  89. {isNull ? (
  90. <Input disabled value={name} className="key" />
  91. ) : (
  92. <Tooltip placement="topLeft" title={<TooltipTitle />}>
  93. <Input disabled value={name} className="key" />
  94. </Tooltip>
  95. )}
  96. </div>
  97. );
  98. };
  99. ParamsNameComponent.propTypes = {
  100. example: PropTypes.string,
  101. desc: PropTypes.string,
  102. name: PropTypes.string
  103. };
  104. export default class Run extends Component {
  105. static propTypes = {
  106. data: PropTypes.object, //接口原有数据
  107. save: PropTypes.func, //保存回调方法
  108. type: PropTypes.string, //enum[case, inter], 判断是在接口页面使用还是在测试集
  109. curUid: PropTypes.number.isRequired,
  110. interfaceId: PropTypes.number.isRequired,
  111. projectId: PropTypes.number.isRequired
  112. };
  113. constructor(props) {
  114. super(props);
  115. this.state = {
  116. loading: false,
  117. resStatusCode: null,
  118. test_valid_msg: null,
  119. resStatusText: null,
  120. case_env: '',
  121. mock_verify: false,
  122. enable_script: false,
  123. test_script: '',
  124. hasPlugin: true,
  125. inputValue: '',
  126. cursurPosition: { row: 1, column: -1 },
  127. envModalVisible: false,
  128. test_res_header: null,
  129. test_res_body: null,
  130. autoPreviewHTML: true,
  131. ...this.props.data
  132. };
  133. }
  134. get testResponseBodyIsHTML() {
  135. const hd = this.state.test_res_header
  136. return hd != null
  137. && typeof hd === 'object'
  138. && String(hd['Content-Type'] || hd['content-type']).indexOf('text/html') !== -1
  139. }
  140. checkInterfaceData(data) {
  141. if (!data || typeof data !== 'object' || !data._id) {
  142. return false;
  143. }
  144. return true;
  145. }
  146. // 整合header信息
  147. handleReqHeader = (value, env) => {
  148. let index = value
  149. ? env.findIndex(item => {
  150. return item.name === value;
  151. })
  152. : 0;
  153. index = index === -1 ? 0 : index;
  154. let req_header = [].concat(this.props.data.req_headers || []);
  155. let header = [].concat(env[index].header || []);
  156. header.forEach(item => {
  157. if (!checkNameIsExistInArray(item.name, req_header)) {
  158. item = {
  159. ...item,
  160. abled: true
  161. };
  162. req_header.push(item);
  163. }
  164. });
  165. req_header = req_header.filter(item => {
  166. return item && typeof item === 'object';
  167. });
  168. return req_header;
  169. };
  170. selectDomain = value => {
  171. let headers = this.handleReqHeader(value, this.state.env);
  172. this.setState({
  173. case_env: value,
  174. req_headers: headers
  175. });
  176. };
  177. async initState(data) {
  178. if (!this.checkInterfaceData(data)) {
  179. return null;
  180. }
  181. const { req_body_other, req_body_type, req_body_is_json_schema } = data;
  182. let body = req_body_other;
  183. // 运行时才会进行转换
  184. if (
  185. this.props.type === 'inter' &&
  186. req_body_type === 'json' &&
  187. req_body_other &&
  188. req_body_is_json_schema
  189. ) {
  190. let schema = {};
  191. try {
  192. schema = json5.parse(req_body_other);
  193. } catch (e) {
  194. console.log('e', e);
  195. return;
  196. }
  197. let result = await axios.post('/api/interface/schema2json', {
  198. schema: schema,
  199. required: true
  200. });
  201. body = JSON.stringify(result.data);
  202. }
  203. let example = {}
  204. if(this.props.type === 'inter'){
  205. example = ['req_headers', 'req_query', 'req_body_form'].reduce(
  206. (res, key) => {
  207. res[key] = (data[key] || []).map(item => {
  208. if (
  209. item.type !== 'file' // 不是文件类型
  210. && (item.value == null || item.value === '') // 初始值为空
  211. && item.example != null // 有示例值
  212. ) {
  213. item.value = item.example;
  214. }
  215. return item;
  216. })
  217. return res;
  218. },
  219. {}
  220. )
  221. }
  222. this.setState(
  223. {
  224. ...this.state,
  225. test_res_header: null,
  226. test_res_body: null,
  227. ...data,
  228. ...example,
  229. req_body_other: body,
  230. resStatusCode: null,
  231. test_valid_msg: null,
  232. resStatusText: null
  233. },
  234. () => this.props.type === 'inter' && this.initEnvState(data.case_env, data.env)
  235. );
  236. }
  237. initEnvState(case_env, env) {
  238. let headers = this.handleReqHeader(case_env, env);
  239. this.setState(
  240. {
  241. req_headers: headers,
  242. env: env
  243. },
  244. () => {
  245. let s = !_.find(env, item => item.name === this.state.case_env);
  246. if (!this.state.case_env || s) {
  247. this.setState({
  248. case_env: this.state.env[0].name
  249. });
  250. }
  251. }
  252. );
  253. }
  254. componentWillMount() {
  255. this._crossRequestInterval = initCrossRequest(hasPlugin => {
  256. this.setState({
  257. hasPlugin: hasPlugin
  258. });
  259. });
  260. this.initState(this.props.data);
  261. }
  262. componentWillUnmount() {
  263. clearInterval(this._crossRequestInterval);
  264. }
  265. componentWillReceiveProps(nextProps) {
  266. if (this.checkInterfaceData(nextProps.data) && this.checkInterfaceData(this.props.data)) {
  267. if (nextProps.data._id !== this.props.data._id) {
  268. this.initState(nextProps.data);
  269. } else if (nextProps.data.interface_up_time !== this.props.data.interface_up_time) {
  270. this.initState(nextProps.data);
  271. }
  272. if (nextProps.data.env !== this.props.data.env) {
  273. this.initEnvState(this.state.case_env, nextProps.data.env);
  274. }
  275. }
  276. }
  277. handleValue(val, global) {
  278. let globalValue = ArrayToObject(global);
  279. return handleParamsValue(val, {
  280. global: globalValue
  281. });
  282. }
  283. onOpenTest = d => {
  284. this.setState({
  285. test_script: d.text
  286. });
  287. };
  288. handleInsertCode = code => {
  289. this.aceEditor.editor.insertCode(code);
  290. };
  291. handleRequestBody = d => {
  292. this.setState({
  293. req_body_other: d.text
  294. });
  295. };
  296. reqRealInterface = async () => {
  297. if (this.state.loading === true) {
  298. this.setState({
  299. loading: false
  300. });
  301. return null;
  302. }
  303. this.setState({
  304. loading: true
  305. });
  306. let options = handleParams(this.state, this.handleValue),
  307. result;
  308. try {
  309. options.taskId = this.props.curUid;
  310. result = await crossRequest(options, this.state.pre_script, this.state.after_script, createContext(
  311. this.props.curUid,
  312. this.props.projectId,
  313. this.props.interfaceId
  314. ));
  315. result = {
  316. header: result.res.header,
  317. body: result.res.body,
  318. status: result.res.status,
  319. statusText: result.res.statusText,
  320. runTime: result.runTime
  321. };
  322. } catch (data) {
  323. result = {
  324. header: data.header,
  325. body: data.body,
  326. status: null,
  327. statusText: data.message
  328. };
  329. }
  330. if (this.state.loading === true) {
  331. this.setState({
  332. loading: false
  333. });
  334. } else {
  335. return null;
  336. }
  337. let tempJson = result.body;
  338. if (tempJson && typeof tempJson === 'object') {
  339. result.body = JSON.stringify(tempJson, null, ' ');
  340. this.setState({
  341. res_body_type: 'json'
  342. });
  343. } else if (isJson(result.body)) {
  344. this.setState({
  345. res_body_type: 'json'
  346. });
  347. }
  348. // 对 返回值数据结构 和定义的 返回数据结构 进行 格式校验
  349. let validResult = this.resBodyValidator(this.props.data, result.body);
  350. if (!validResult.valid) {
  351. this.setState({ test_valid_msg: `返回参数 ${validResult.message}` });
  352. } else {
  353. this.setState({ test_valid_msg: '' });
  354. }
  355. this.setState({
  356. resStatusCode: result.status,
  357. resStatusText: result.statusText,
  358. test_res_header: result.header,
  359. test_res_body: result.body
  360. });
  361. };
  362. // 返回数据与定义数据的比较判断
  363. resBodyValidator = (interfaceData, test_res_body) => {
  364. const { res_body_type, res_body_is_json_schema, res_body } = interfaceData;
  365. let validResult = { valid: true };
  366. if (res_body_type === 'json' && res_body_is_json_schema) {
  367. const schema = json5_parse(res_body);
  368. const params = json5_parse(test_res_body);
  369. validResult = schemaValidator(schema, params);
  370. }
  371. return validResult;
  372. };
  373. changeParam = (name, v, index, key) => {
  374. key = key || 'value';
  375. const pathParam = deepCopyJson(this.state[name]);
  376. pathParam[index][key] = v;
  377. if (key === 'value') {
  378. pathParam[index].enable = !!v;
  379. }
  380. this.setState({
  381. [name]: pathParam
  382. });
  383. };
  384. changeBody = (v, index, key) => {
  385. const bodyForm = deepCopyJson(this.state.req_body_form);
  386. key = key || 'value';
  387. if (key === 'value') {
  388. bodyForm[index].enable = !!v;
  389. if (bodyForm[index].type === 'file') {
  390. bodyForm[index].value = 'file_' + index;
  391. } else {
  392. bodyForm[index].value = v;
  393. }
  394. } else if (key === 'enable') {
  395. bodyForm[index].enable = v;
  396. }
  397. this.setState({ req_body_form: bodyForm });
  398. };
  399. // 模态框的相关操作
  400. showModal = (val, index, type) => {
  401. let inputValue = '';
  402. let cursurPosition;
  403. if (type === 'req_body_other') {
  404. // req_body
  405. let editor = this.aceEditor.editor.editor;
  406. cursurPosition = editor.session.doc.positionToIndex(editor.selection.getCursor());
  407. // 获取选中的数据
  408. inputValue = this.getInstallValue(val || '', cursurPosition).val;
  409. } else {
  410. // 其他input 输入
  411. let oTxt1 = document.getElementById(`${type}_${index}`);
  412. cursurPosition = oTxt1.selectionStart;
  413. inputValue = this.getInstallValue(val || '', cursurPosition).val;
  414. // cursurPosition = {row: 1, column: position}
  415. }
  416. this.setState({
  417. modalVisible: true,
  418. inputIndex: index,
  419. inputValue,
  420. cursurPosition,
  421. modalType: type
  422. });
  423. };
  424. // 点击插入
  425. handleModalOk = val => {
  426. const { inputIndex, modalType } = this.state;
  427. if (modalType === 'req_body_other') {
  428. this.changeInstallBody(modalType, val);
  429. } else {
  430. this.changeInstallParam(modalType, val, inputIndex);
  431. }
  432. this.setState({ modalVisible: false });
  433. };
  434. // 根据鼠标位置往req_body中动态插入数据
  435. changeInstallBody = (type, value) => {
  436. const pathParam = deepCopyJson(this.state[type]);
  437. // console.log(pathParam)
  438. let oldValue = pathParam || '';
  439. let newValue = this.getInstallValue(oldValue, this.state.cursurPosition);
  440. let left = newValue.left;
  441. let right = newValue.right;
  442. this.setState({
  443. [type]: `${left}${value}${right}`
  444. });
  445. };
  446. // 获取截取的字符串
  447. getInstallValue = (oldValue, cursurPosition) => {
  448. let left = oldValue.substr(0, cursurPosition);
  449. let right = oldValue.substr(cursurPosition);
  450. let leftPostion = left.lastIndexOf('{{');
  451. let leftPostion2 = left.lastIndexOf('}}');
  452. let rightPostion = right.indexOf('}}');
  453. // console.log(leftPostion, leftPostion2,rightPostion, rightPostion2);
  454. let val = '';
  455. // 需要切除原来的变量
  456. if (leftPostion !== -1 && rightPostion !== -1 && leftPostion > leftPostion2) {
  457. left = left.substr(0, leftPostion);
  458. right = right.substr(rightPostion + 2);
  459. val = oldValue.substring(leftPostion, cursurPosition + rightPostion + 2);
  460. }
  461. return {
  462. left,
  463. right,
  464. val
  465. };
  466. };
  467. // 根据鼠标位置动态插入数据
  468. changeInstallParam = (name, v, index, key) => {
  469. key = key || 'value';
  470. const pathParam = deepCopyJson(this.state[name]);
  471. let oldValue = pathParam[index][key] || '';
  472. let newValue = this.getInstallValue(oldValue, this.state.cursurPosition);
  473. let left = newValue.left;
  474. let right = newValue.right;
  475. pathParam[index][key] = `${left}${v}${right}`;
  476. this.setState({
  477. [name]: pathParam
  478. });
  479. };
  480. // 取消参数插入
  481. handleModalCancel = () => {
  482. this.setState({ modalVisible: false, cursurPosition: -1 });
  483. };
  484. // 环境变量模态框相关操作
  485. showEnvModal = () => {
  486. this.setState({
  487. envModalVisible: true
  488. });
  489. };
  490. handleEnvOk = (newEnv, index) => {
  491. this.setState({
  492. envModalVisible: false,
  493. case_env: newEnv[index].name
  494. });
  495. };
  496. handleEnvCancel = () => {
  497. this.setState({
  498. envModalVisible: false
  499. });
  500. };
  501. render() {
  502. const {
  503. method,
  504. env,
  505. path,
  506. req_params = [],
  507. req_headers = [],
  508. req_query = [],
  509. req_body_type,
  510. req_body_form = [],
  511. loading,
  512. case_env,
  513. inputValue,
  514. hasPlugin
  515. } = this.state;
  516. // console.log(env);
  517. return (
  518. <div className="interface-test postman">
  519. {this.state.modalVisible && (
  520. <ModalPostman
  521. visible={this.state.modalVisible}
  522. handleCancel={this.handleModalCancel}
  523. handleOk={this.handleModalOk}
  524. inputValue={inputValue}
  525. envType={this.props.type}
  526. id={+this.state._id}
  527. />
  528. )}
  529. {this.state.envModalVisible && (
  530. <Modal
  531. title="环境设置"
  532. visible={this.state.envModalVisible}
  533. onOk={this.handleEnvOk}
  534. onCancel={this.handleEnvCancel}
  535. footer={null}
  536. width={800}
  537. className="env-modal"
  538. >
  539. <ProjectEnv projectId={this.props.data.project_id} onOk={this.handleEnvOk} />
  540. </Modal>
  541. )}
  542. <CheckCrossInstall hasPlugin={hasPlugin} />
  543. <div className="url">
  544. <InputGroup compact style={{ display: 'flex' }}>
  545. <Select disabled value={method} style={{ flexBasis: 60 }}>
  546. {Object.keys(HTTP_METHOD).map(name => {
  547. <Option value={name.toUpperCase()}>{name.toUpperCase()}</Option>;
  548. })}
  549. </Select>
  550. <Select
  551. value={case_env}
  552. style={{ flexBasis: 180, flexGrow: 1 }}
  553. onSelect={this.selectDomain}
  554. >
  555. {env.map((item, index) => (
  556. <Option value={item.name} key={index}>
  557. {item.name + ':' + item.domain}
  558. </Option>
  559. ))}
  560. <Option value="环境配置" disabled style={{ cursor: 'pointer', color: '#2395f1' }}>
  561. <Button type="primary" onClick={this.showEnvModal}>
  562. 环境配置
  563. </Button>
  564. </Option>
  565. </Select>
  566. <Input
  567. disabled
  568. value={path}
  569. onChange={this.changePath}
  570. spellCheck="false"
  571. style={{ flexBasis: 180, flexGrow: 1 }}
  572. />
  573. </InputGroup>
  574. <Tooltip
  575. placement="bottom"
  576. title={(() => {
  577. if (hasPlugin) {
  578. return '发送请求';
  579. } else {
  580. return '请安装 cross-request 插件';
  581. }
  582. })()}
  583. >
  584. <Button
  585. disabled={!hasPlugin}
  586. onClick={this.reqRealInterface}
  587. type="primary"
  588. style={{ marginLeft: 10 }}
  589. icon={loading ? 'loading' : ''}
  590. >
  591. {loading ? '取消' : '发送'}
  592. </Button>
  593. </Tooltip>
  594. <Tooltip
  595. placement="bottom"
  596. title={() => {
  597. return this.props.type === 'inter' ? '保存到测试集' : '更新该用例';
  598. }}
  599. >
  600. <Button onClick={this.props.save} type="primary" style={{ marginLeft: 10 }}>
  601. {this.props.type === 'inter' ? '保存' : '更新'}
  602. </Button>
  603. </Tooltip>
  604. </div>
  605. <Collapse defaultActiveKey={['0', '1', '2', '3']} bordered={true}>
  606. <Panel
  607. header="PATH PARAMETERS"
  608. key="0"
  609. className={req_params.length === 0 ? 'hidden' : ''}
  610. >
  611. {req_params.map((item, index) => {
  612. return (
  613. <div key={index} className="key-value-wrap">
  614. {/* <Tooltip
  615. placement="topLeft"
  616. title={<TooltipContent example={item.example} desc={item.desc} />}
  617. >
  618. <Input disabled value={item.name} className="key" />
  619. </Tooltip> */}
  620. <ParamsNameComponent example={item.example} desc={item.desc} name={item.name} />
  621. <span className="eq-symbol">=</span>
  622. <Input
  623. value={item.value}
  624. className="value"
  625. onChange={e => this.changeParam('req_params', e.target.value, index)}
  626. placeholder="参数值"
  627. id={`req_params_${index}`}
  628. addonAfter={
  629. <Icon
  630. type="edit"
  631. onClick={() => this.showModal(item.value, index, 'req_params')}
  632. />
  633. }
  634. />
  635. </div>
  636. );
  637. })}
  638. <Button
  639. style={{ display: 'none' }}
  640. type="primary"
  641. icon="plus"
  642. onClick={this.addPathParam}
  643. >
  644. 添加Path参数
  645. </Button>
  646. </Panel>
  647. <Panel
  648. header="QUERY PARAMETERS"
  649. key="1"
  650. className={req_query.length === 0 ? 'hidden' : ''}
  651. >
  652. {req_query.map((item, index) => {
  653. return (
  654. <div key={index} className="key-value-wrap">
  655. {/* <Tooltip
  656. placement="topLeft"
  657. title={<TooltipContent example={item.example} desc={item.desc} />}
  658. >
  659. <Input disabled value={item.name} className="key" />
  660. </Tooltip> */}
  661. <ParamsNameComponent example={item.example} desc={item.desc} name={item.name} />
  662. &nbsp;
  663. {item.required == 1 ? (
  664. <Checkbox className="params-enable" checked={true} disabled />
  665. ) : (
  666. <Checkbox
  667. className="params-enable"
  668. checked={item.enable}
  669. onChange={e =>
  670. this.changeParam('req_query', e.target.checked, index, 'enable')
  671. }
  672. />
  673. )}
  674. <span className="eq-symbol">=</span>
  675. <Input
  676. value={item.value}
  677. className="value"
  678. onChange={e => this.changeParam('req_query', e.target.value, index)}
  679. placeholder="参数值"
  680. id={`req_query_${index}`}
  681. addonAfter={
  682. <Icon
  683. type="edit"
  684. onClick={() => this.showModal(item.value, index, 'req_query')}
  685. />
  686. }
  687. />
  688. </div>
  689. );
  690. })}
  691. <Button style={{ display: 'none' }} type="primary" icon="plus" onClick={this.addQuery}>
  692. 添加Query参数
  693. </Button>
  694. </Panel>
  695. <Panel header="HEADERS" key="2" className={req_headers.length === 0 ? 'hidden' : ''}>
  696. {req_headers.map((item, index) => {
  697. return (
  698. <div key={index} className="key-value-wrap">
  699. {/* <Tooltip
  700. placement="topLeft"
  701. title={<TooltipContent example={item.example} desc={item.desc} />}
  702. >
  703. <Input disabled value={item.name} className="key" />
  704. </Tooltip> */}
  705. <ParamsNameComponent example={item.example} desc={item.desc} name={item.name} />
  706. <span className="eq-symbol">=</span>
  707. <Input
  708. value={item.value}
  709. disabled={!!item.abled}
  710. className="value"
  711. onChange={e => this.changeParam('req_headers', e.target.value, index)}
  712. placeholder="参数值"
  713. id={`req_headers_${index}`}
  714. addonAfter={
  715. !item.abled && (
  716. <Icon
  717. type="edit"
  718. onClick={() => this.showModal(item.value, index, 'req_headers')}
  719. />
  720. )
  721. }
  722. />
  723. </div>
  724. );
  725. })}
  726. <Button style={{ display: 'none' }} type="primary" icon="plus" onClick={this.addHeader}>
  727. 添加Header
  728. </Button>
  729. </Panel>
  730. <Panel
  731. header={
  732. <div style={{ display: 'flex', justifyContent: 'space-between' }}>
  733. <Tooltip title="F9 全屏编辑">BODY(F9)</Tooltip>
  734. </div>
  735. }
  736. key="3"
  737. className={
  738. HTTP_METHOD[method].request_body &&
  739. ((req_body_type === 'form' && req_body_form.length > 0) || req_body_type !== 'form')
  740. ? 'POST'
  741. : 'hidden'
  742. }
  743. >
  744. <div
  745. style={{ display: checkRequestBodyIsRaw(method, req_body_type) ? 'block' : 'none' }}
  746. >
  747. {req_body_type === 'json' && (
  748. <div className="adv-button">
  749. <Button
  750. onClick={() => this.showModal(this.state.req_body_other, 0, 'req_body_other')}
  751. >
  752. 高级参数设置
  753. </Button>
  754. <Tooltip title="高级参数设置只在json字段值中生效">
  755. {' '}
  756. <Icon type="question-circle-o" />
  757. </Tooltip>
  758. </div>
  759. )}
  760. <AceEditor
  761. className="pretty-editor"
  762. ref={editor => (this.aceEditor = editor)}
  763. data={this.state.req_body_other}
  764. mode={req_body_type === 'json' ? null : 'text'}
  765. onChange={this.handleRequestBody}
  766. fullScreen={true}
  767. />
  768. </div>
  769. {HTTP_METHOD[method].request_body &&
  770. req_body_type === 'form' && (
  771. <div>
  772. {req_body_form.map((item, index) => {
  773. return (
  774. <div key={index} className="key-value-wrap">
  775. {/* <Tooltip
  776. placement="topLeft"
  777. title={<TooltipContent example={item.example} desc={item.desc} />}
  778. >
  779. <Input disabled value={item.name} className="key" />
  780. </Tooltip> */}
  781. <ParamsNameComponent
  782. example={item.example}
  783. desc={item.desc}
  784. name={item.name}
  785. />
  786. &nbsp;
  787. {item.required == 1 ? (
  788. <Checkbox className="params-enable" checked={true} disabled />
  789. ) : (
  790. <Checkbox
  791. className="params-enable"
  792. checked={item.enable}
  793. onChange={e => this.changeBody(e.target.checked, index, 'enable')}
  794. />
  795. )}
  796. <span className="eq-symbol">=</span>
  797. {item.type === 'file' ? (
  798. '因Chrome最新版安全策略限制,不再支持文件上传'
  799. // <Input
  800. // type="file"
  801. // id={'file_' + index}
  802. // onChange={e => this.changeBody(e.target.value, index, 'value')}
  803. // multiple
  804. // className="value"
  805. // />
  806. ) : (
  807. <Input
  808. value={item.value}
  809. className="value"
  810. onChange={e => this.changeBody(e.target.value, index)}
  811. placeholder="参数值"
  812. id={`req_body_form_${index}`}
  813. addonAfter={
  814. <Icon
  815. type="edit"
  816. onClick={() => this.showModal(item.value, index, 'req_body_form')}
  817. />
  818. }
  819. />
  820. )}
  821. </div>
  822. );
  823. })}
  824. <Button
  825. style={{ display: 'none' }}
  826. type="primary"
  827. icon="plus"
  828. onClick={this.addBody}
  829. >
  830. 添加Form参数
  831. </Button>
  832. </div>
  833. )}
  834. {HTTP_METHOD[method].request_body &&
  835. req_body_type === 'file' && (
  836. <div>
  837. <Input type="file" id="single-file" />
  838. </div>
  839. )}
  840. </Panel>
  841. </Collapse>
  842. <Tabs size="large" defaultActiveKey="res" className="response-tab">
  843. <Tabs.TabPane tab="Response" key="res">
  844. <Spin spinning={this.state.loading}>
  845. <h2
  846. style={{ display: this.state.resStatusCode ? '' : 'none' }}
  847. className={
  848. 'res-code ' +
  849. (this.state.resStatusCode >= 200 &&
  850. this.state.resStatusCode < 400 &&
  851. !this.state.loading
  852. ? 'success'
  853. : 'fail')
  854. }
  855. >
  856. {this.state.resStatusCode + ' ' + this.state.resStatusText}
  857. </h2>
  858. <div>
  859. <a rel="noopener noreferrer" target="_blank" href="https://juejin.im/post/5c888a3e5188257dee0322af">YApi 新版如何查看 http 请求数据</a>
  860. </div>
  861. {this.state.test_valid_msg && (
  862. <Alert
  863. message={
  864. <span>
  865. Warning &nbsp;
  866. <Tooltip title="针对定义为 json schema 的返回数据进行格式校验">
  867. <Icon type="question-circle-o" />
  868. </Tooltip>
  869. </span>
  870. }
  871. type="warning"
  872. showIcon
  873. description={this.state.test_valid_msg}
  874. />
  875. )}
  876. <div className="container-header-body">
  877. <div className="header">
  878. <div className="container-title">
  879. <h4>Headers</h4>
  880. </div>
  881. <AceEditor
  882. callback={editor => {
  883. editor.renderer.setShowGutter(false);
  884. }}
  885. readOnly={true}
  886. className="pretty-editor-header"
  887. data={this.state.test_res_header}
  888. mode="json"
  889. />
  890. </div>
  891. <div className="resizer">
  892. <div className="container-title">
  893. <h4 style={{ visibility: 'hidden' }}>1</h4>
  894. </div>
  895. </div>
  896. <div className="body">
  897. <div className="container-title">
  898. <h4>Body</h4>
  899. <Checkbox
  900. checked={this.state.autoPreviewHTML}
  901. onChange={e => this.setState({ autoPreviewHTML: e.target.checked })}>
  902. <span>自动预览HTML</span>
  903. </Checkbox>
  904. </div>
  905. {
  906. this.state.autoPreviewHTML && this.testResponseBodyIsHTML
  907. ? <iframe
  908. className="pretty-editor-body"
  909. srcDoc={this.state.test_res_body}
  910. />
  911. : <AceEditor
  912. readOnly={true}
  913. className="pretty-editor-body"
  914. data={this.state.test_res_body}
  915. mode={handleContentType(this.state.test_res_header)}
  916. />
  917. }
  918. </div>
  919. </div>
  920. </Spin>
  921. </Tabs.TabPane>
  922. {this.props.type === 'case' ? (
  923. <Tabs.TabPane
  924. className="response-test"
  925. tab={<Tooltip title="测试脚本,可断言返回结果,使用方法请查看文档">Test</Tooltip>}
  926. key="test"
  927. >
  928. <h3 style={{ margin: '5px' }}>
  929. &nbsp;是否开启:&nbsp;
  930. <Switch
  931. checked={this.state.enable_script}
  932. onChange={e => this.setState({ enable_script: e })}
  933. />
  934. </h3>
  935. <p style={{ margin: '10px' }}>注:Test 脚本只有做自动化测试才执行</p>
  936. <Row>
  937. <Col span="18">
  938. <AceEditor
  939. onChange={this.onOpenTest}
  940. className="case-script"
  941. data={this.state.test_script}
  942. ref={aceEditor => {
  943. this.aceEditor = aceEditor;
  944. }}
  945. />
  946. </Col>
  947. <Col span="6">
  948. <div className="insert-code">
  949. {InsertCodeMap.map(item => {
  950. return (
  951. <div
  952. style={{ cursor: 'pointer' }}
  953. className="code-item"
  954. key={item.title}
  955. onClick={() => {
  956. this.handleInsertCode('\n' + item.code);
  957. }}
  958. >
  959. {item.title}
  960. </div>
  961. );
  962. })}
  963. </div>
  964. </Col>
  965. </Row>
  966. </Tabs.TabPane>
  967. ) : null}
  968. </Tabs>
  969. </div>
  970. );
  971. }
  972. }