UNPKG

yapi-vendor

Version:
1,258 lines (1,169 loc) 37.4 kB
import React, { PureComponent as Component } from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import { withRouter } from 'react-router'; import { Link } from 'react-router-dom'; //import constants from '../../../../constants/variable.js' import { Tooltip, Icon,Input, Button, Row, Col, Spin, Modal, message, Select, Switch } from 'antd'; import { fetchInterfaceColList, fetchCaseList, setColData, fetchCaseEnvList } from '../../../../reducer/modules/interfaceCol'; import HTML5Backend from 'react-dnd-html5-backend'; import { getToken, getEnv } from '../../../../reducer/modules/project'; import { DragDropContext } from 'react-dnd'; import AceEditor from 'client/components/AceEditor/AceEditor'; import * as Table from 'reactabular-table'; import * as dnd from 'reactabular-dnd'; import * as resolve from 'table-resolver'; import axios from 'axios'; import CaseReport from './CaseReport.js'; import _ from 'underscore'; import { initCrossRequest } from 'client/components/Postman/CheckCrossInstall.js'; import produce from 'immer'; import {InsertCodeMap} from 'client/components/Postman/Postman.js' const plugin = require('client/plugin.js'); const { handleParams, crossRequest, handleCurrDomain, checkNameIsExistInArray } = require('common/postmanLib.js'); const { handleParamsValue, json_parse, ArrayToObject } = require('common/utils.js'); import CaseEnv from 'client/components/CaseEnv'; import Label from '../../../../components/Label/Label.js'; const Option = Select.Option; const createContext = require('common/createContext') import copy from 'copy-to-clipboard'; const defaultModalStyle = { top: 10 } function handleReport(json) { try { return JSON.parse(json); } catch (e) { return {}; } } @connect( state => { return { interfaceColList: state.interfaceCol.interfaceColList, currColId: state.interfaceCol.currColId, currCaseId: state.interfaceCol.currCaseId, isShowCol: state.interfaceCol.isShowCol, isRander: state.interfaceCol.isRander, currCaseList: state.interfaceCol.currCaseList, currProject: state.project.currProject, token: state.project.token, envList: state.interfaceCol.envList, curProjectRole: state.project.currProject.role, projectEnv: state.project.projectEnv, curUid: state.user.uid }; }, { fetchInterfaceColList, fetchCaseList, setColData, getToken, getEnv, fetchCaseEnvList } ) @withRouter @DragDropContext(HTML5Backend) class InterfaceColContent extends Component { static propTypes = { match: PropTypes.object, interfaceColList: PropTypes.array, fetchInterfaceColList: PropTypes.func, fetchCaseList: PropTypes.func, setColData: PropTypes.func, history: PropTypes.object, currCaseList: PropTypes.array, currColId: PropTypes.number, currCaseId: PropTypes.number, isShowCol: PropTypes.bool, isRander: PropTypes.bool, currProject: PropTypes.object, getToken: PropTypes.func, token: PropTypes.string, curProjectRole: PropTypes.string, getEnv: PropTypes.func, projectEnv: PropTypes.object, fetchCaseEnvList: PropTypes.func, envList: PropTypes.array, curUid: PropTypes.number }; constructor(props) { super(props); this.reports = {}; this.records = {}; this.state = { rows: [], reports: {}, visible: false, curCaseid: null, hasPlugin: false, advVisible: false, curScript: '', enableScript: false, autoVisible: false, mode: 'html', email: false, download: false, currColEnvObj: {}, collapseKey: '1', commonSettingModalVisible: false, commonSetting: { checkHttpCodeIs200: false, checkResponseField: { name: 'code', value: '0', enable: false }, checkResponseSchema: false, checkScript:{ enable: false, content: '' } } }; this.onRow = this.onRow.bind(this); this.onMoveRow = this.onMoveRow.bind(this); } async handleColIdChange(newColId){ this.props.setColData({ currColId: +newColId, isShowCol: true, isRander: false }); let result = await this.props.fetchCaseList(newColId); if (result.payload.data.errcode === 0) { this.reports = handleReport(result.payload.data.colData.test_report); this.setState({ commonSetting:{ ...this.state.commonSetting, ...result.payload.data.colData } }) } await this.props.fetchCaseList(newColId); await this.props.fetchCaseEnvList(newColId); this.changeCollapseClose(); this.handleColdata(this.props.currCaseList); } async componentWillMount() { const result = await this.props.fetchInterfaceColList(this.props.match.params.id); await this.props.getToken(this.props.match.params.id); let { currColId } = this.props; const params = this.props.match.params; const { actionId } = params; this.currColId = currColId = +actionId || result.payload.data.data[0]._id; // this.props.history.push('/project/' + params.id + '/interface/col/' + currColId); if (currColId && currColId != 0) { await this.handleColIdChange(currColId) } this._crossRequestInterval = initCrossRequest(hasPlugin => { this.setState({ hasPlugin: hasPlugin }); }); } componentWillUnmount() { clearInterval(this._crossRequestInterval); } // 更新分类简介 handleChangeInterfaceCol = (desc, name) => { let params = { col_id: this.props.currColId, name: name, desc: desc }; axios.post('/api/col/up_col', params).then(async res => { if (res.data.errcode) { return message.error(res.data.errmsg); } let project_id = this.props.match.params.id; await this.props.fetchInterfaceColList(project_id); message.success('接口集合简介更新成功'); }); }; // 整合header信息 handleReqHeader = (project_id, req_header, case_env) => { let envItem = _.find(this.props.envList, item => { return item._id === project_id; }); let currDomain = handleCurrDomain(envItem && envItem.env, case_env); let header = currDomain.header; header.forEach(item => { if (!checkNameIsExistInArray(item.name, req_header)) { // item.abled = true; item = { ...item, abled: true }; req_header.push(item); } }); return req_header; }; handleColdata = (rows, currColEnvObj = {}) => { let that = this; let newRows = produce(rows, draftRows => { draftRows.map(item => { item.id = item._id; item._test_status = item.test_status; if(currColEnvObj[item.project_id]){ item.case_env =currColEnvObj[item.project_id]; } item.req_headers = that.handleReqHeader(item.project_id, item.req_headers, item.case_env); return item; }); }); this.setState({ rows: newRows }); }; executeTests = async () => { for (let i = 0, l = this.state.rows.length, newRows, curitem; i < l; i++) { let { rows } = this.state; let envItem = _.find(this.props.envList, item => { return item._id === rows[i].project_id; }); curitem = Object.assign( {}, rows[i], { env: envItem.env, pre_script: this.props.currProject.pre_script, after_script: this.props.currProject.after_script }, { test_status: 'loading' } ); newRows = [].concat([], rows); newRows[i] = curitem; this.setState({ rows: newRows }); let status = 'error', result; try { result = await this.handleTest(curitem); if (result.code === 400) { status = 'error'; } else if (result.code === 0) { status = 'ok'; } else if (result.code === 1) { status = 'invalid'; } } catch (e) { console.error(e); status = 'error'; result = e; } //result.body = result.data; this.reports[curitem._id] = result; this.records[curitem._id] = { status: result.status, params: result.params, body: result.res_body }; curitem = Object.assign({}, rows[i], { test_status: status }); newRows = [].concat([], rows); newRows[i] = curitem; this.setState({ rows: newRows }); } await axios.post('/api/col/up_col', { col_id: this.props.currColId, test_report: JSON.stringify(this.reports) }); }; handleTest = async interfaceData => { let requestParams = {}; let options = handleParams(interfaceData, this.handleValue, requestParams); let result = { code: 400, msg: '数据异常', validRes: [] }; await plugin.emitHook('before_col_request', Object.assign({}, options, { type: 'col', caseId: options.caseId, projectId: interfaceData.project_id, interfaceId: interfaceData.interface_id })); try { let data = await crossRequest(options, interfaceData.pre_script, interfaceData.after_script, createContext( this.props.curUid, this.props.match.params.id, interfaceData.interface_id )); options.taskId = this.props.curUid; let res = (data.res.body = json_parse(data.res.body)); result = { ...options, ...result, res_header: data.res.header, res_body: res, status: data.res.status, statusText: data.res.statusText }; await plugin.emitHook('after_col_request', result, { type: 'col', caseId: options.caseId, projectId: interfaceData.project_id, interfaceId: interfaceData.interface_id }); if (options.data && typeof options.data === 'object') { requestParams = { ...requestParams, ...options.data }; } let validRes = []; let responseData = Object.assign( {}, { status: data.res.status, body: res, header: data.res.header, statusText: data.res.statusText } ); // 断言测试 await this.handleScriptTest(interfaceData, responseData, validRes, requestParams); if (validRes.length === 0) { result.code = 0; result.validRes = [ { message: '验证通过' } ]; } else if (validRes.length > 0) { result.code = 1; result.validRes = validRes; } } catch (data) { result = { ...options, ...result, res_header: data.header, res_body: data.body || data.message, status: 0, statusText: data.message, code: 400, validRes: [ { message: data.message } ] }; } result.params = requestParams; return result; }; //response, validRes // 断言测试 handleScriptTest = async (interfaceData, response, validRes, requestParams) => { // 是否启动断言 try { let test = await axios.post('/api/col/run_script', { response: response, records: this.records, script: interfaceData.test_script, params: requestParams, col_id: this.props.currColId, interface_id: interfaceData.interface_id }); if (test.data.errcode !== 0) { test.data.data.logs.forEach(item => { validRes.push({ message: item }); }); } } catch (err) { validRes.push({ message: 'Error: ' + err.message }); } }; handleValue = (val, global) => { let globalValue = ArrayToObject(global); let context = Object.assign({}, { global: globalValue }, this.records); return handleParamsValue(val, context); }; arrToObj = (arr, requestParams) => { arr = arr || []; const obj = {}; arr.forEach(item => { if (item.name && item.enable && item.type !== 'file') { obj[item.name] = this.handleValue(item.value); if (requestParams) { requestParams[item.name] = obj[item.name]; } } }); return obj; }; onRow(row) { return { rowId: row.id, onMove: this.onMoveRow, onDrop: this.onDrop }; } onDrop = () => { let changes = []; this.state.rows.forEach((item, index) => { changes.push({ id: item._id, index: index }); }); axios.post('/api/col/up_case_index', changes).then(() => { this.props.fetchInterfaceColList(this.props.match.params.id); }); }; onMoveRow({ sourceRowId, targetRowId }) { let rows = dnd.moveRows({ sourceRowId, targetRowId })(this.state.rows); if (rows) { this.setState({ rows }); } } onChangeTest = d => { this.setState({ commonSetting: { ...this.state.commonSetting, checkScript: { ...this.state.commonSetting.checkScript, content: d.text } } }); }; handleInsertCode = code => { this.aceEditor.editor.insertCode(code); }; async componentWillReceiveProps(nextProps) { let newColId = !isNaN(nextProps.match.params.actionId) ? +nextProps.match.params.actionId : 0; if (newColId && ((this.currColId && newColId !== this.currColId) || nextProps.isRander)) { this.currColId = newColId; this.handleColIdChange(newColId) } } // 测试用例环境面板折叠 changeCollapseClose = key => { if (key) { this.setState({ collapseKey: key }); } else { this.setState({ collapseKey: '1', currColEnvObj: {} }); } }; openReport = id => { if (!this.reports[id]) { return message.warn('还没有生成报告'); } this.setState({ visible: true, curCaseid: id }); }; openAdv = id => { let findCase = _.find(this.props.currCaseList, item => item.id === id); this.setState({ enableScript: findCase.enable_script, curScript: findCase.test_script, advVisible: true, curCaseid: id }); }; handleScriptChange = d => { this.setState({ curScript: d.text }); }; handleAdvCancel = () => { this.setState({ advVisible: false }); }; handleAdvOk = async () => { const { curCaseid, enableScript, curScript } = this.state; const res = await axios.post('/api/col/up_case', { id: curCaseid, test_script: curScript, enable_script: enableScript }); if (res.data.errcode === 0) { message.success('更新成功'); } this.setState({ advVisible: false }); let currColId = this.currColId; this.props.setColData({ currColId: +currColId, isShowCol: true, isRander: false }); await this.props.fetchCaseList(currColId); this.handleColdata(this.props.currCaseList); }; handleCancel = () => { this.setState({ visible: false }); }; currProjectEnvChange = (envName, project_id) => { let currColEnvObj = { ...this.state.currColEnvObj, [project_id]: envName }; this.setState({ currColEnvObj }); // this.handleColdata(this.props.currCaseList, envName, project_id); this.handleColdata(this.props.currCaseList,currColEnvObj); }; autoTests = () => { this.setState({ autoVisible: true, currColEnvObj: {}, collapseKey: '' }); }; handleAuto = () => { this.setState({ autoVisible: false, email: false, download: false, mode: 'html', currColEnvObj: {}, collapseKey: '' }); }; copyUrl = url => { copy(url); message.success('已经成功复制到剪切板'); }; modeChange = mode => { this.setState({ mode }); }; emailChange = email => { this.setState({ email }); }; downloadChange = download => { this.setState({ download }); }; handleColEnvObj = envObj => { let str = ''; for (let key in envObj) { str += envObj[key] ? `&env_${key}=${envObj[key]}` : ''; } return str; }; handleCommonSetting = ()=>{ let setting = this.state.commonSetting; let params = { col_id: this.props.currColId, ...setting }; console.log(params) axios.post('/api/col/up_col', params).then(async res => { if (res.data.errcode) { return message.error(res.data.errmsg); } message.success('配置测试集成功'); }); this.setState({ commonSettingModalVisible: false }) } cancelCommonSetting = ()=>{ this.setState({ commonSettingModalVisible: false }) } openCommonSetting = ()=>{ this.setState({ commonSettingModalVisible: true }) } changeCommonFieldSetting = (key)=>{ return (e)=>{ let value = e; if(typeof e === 'object' && e){ value = e.target.value; } let {checkResponseField} = this.state.commonSetting; this.setState({ commonSetting: { ...this.state.commonSetting, checkResponseField: { ...checkResponseField, [key]: value } } }) } } render() { const currProjectId = this.props.currProject._id; const columns = [ { property: 'casename', header: { label: '用例名称' }, props: { style: { width: '250px' } }, cell: { formatters: [ (text, { rowData }) => { let record = rowData; return ( <Link to={'/project/' + currProjectId + '/interface/case/' + record._id}> {record.casename.length > 23 ? record.casename.substr(0, 20) + '...' : record.casename} </Link> ); } ] } }, { header: { label: 'key', formatters: [ () => { return ( <Tooltip title={ <span> {' '} 每个用例都有唯一的key,用于获取所匹配接口的响应数据,例如使用{' '} <a href="https://hellosean1025.github.io/yapi/documents/case.html#%E7%AC%AC%E4%BA%8C%E6%AD%A5%EF%BC%8C%E7%BC%96%E8%BE%91%E6%B5%8B%E8%AF%95%E7%94%A8%E4%BE%8B" className="link-tooltip" target="blank" > {' '} 变量参数{' '} </a>{' '} 功能{' '} </span> } > Key </Tooltip> ); } ] }, props: { style: { width: '100px' } }, cell: { formatters: [ (value, { rowData }) => { return <span>{rowData._id}</span>; } ] } }, { property: 'test_status', header: { label: '状态' }, props: { style: { width: '100px' } }, cell: { formatters: [ (value, { rowData }) => { let id = rowData._id; let code = this.reports[id] ? this.reports[id].code : 0; if (rowData.test_status === 'loading') { return ( <div> <Spin /> </div> ); } switch (code) { case 0: return ( <div> <Tooltip title="Pass"> <Icon style={{ color: '#00a854' }} type="check-circle" /> </Tooltip> </div> ); case 400: return ( <div> <Tooltip title="请求异常"> <Icon type="info-circle" style={{ color: '#f04134' }} /> </Tooltip> </div> ); case 1: return ( <div> <Tooltip title="验证失败"> <Icon type="exclamation-circle" style={{ color: '#ffbf00' }} /> </Tooltip> </div> ); default: return ( <div> <Icon style={{ color: '#00a854' }} type="check-circle" /> </div> ); } } ] } }, { property: 'path', header: { label: '接口路径' }, cell: { formatters: [ (text, { rowData }) => { let record = rowData; return ( <Tooltip title="跳转到对应接口"> <Link to={`/project/${record.project_id}/interface/api/${record.interface_id}`}> {record.path.length > 23 ? record.path + '...' : record.path} </Link> </Tooltip> ); } ] } }, { header: { label: '测试报告' }, props: { style: { width: '200px' } }, cell: { formatters: [ (text, { rowData }) => { let reportFun = () => { if (!this.reports[rowData.id]) { return null; } return <Button onClick={() => this.openReport(rowData.id)}>测试报告</Button>; }; return <div className="interface-col-table-action">{reportFun()}</div>; } ] } } ]; const { rows } = this.state; const components = { header: { cell: dnd.Header }, body: { row: dnd.Row } }; const resolvedColumns = resolve.columnChildren({ columns }); const resolvedRows = resolve.resolve({ columns: resolvedColumns, method: resolve.nested })( rows ); const localUrl = location.protocol + '//' + location.hostname + (location.port !== '' ? ':' + location.port : ''); let currColEnvObj = this.handleColEnvObj(this.state.currColEnvObj); const autoTestsUrl = `/api/open/run_auto_test?id=${this.props.currColId}&token=${ this.props.token }${currColEnvObj ? currColEnvObj : ''}&mode=${this.state.mode}&email=${ this.state.email }&download=${this.state.download}`; let col_name = ''; let col_desc = ''; for (var i = 0; i < this.props.interfaceColList.length; i++) { if (this.props.interfaceColList[i]._id === this.props.currColId) { col_name = this.props.interfaceColList[i].name; col_desc = this.props.interfaceColList[i].desc; break; } } return ( <div className="interface-col"> <Modal title="通用规则配置" visible={this.state.commonSettingModalVisible} onOk={this.handleCommonSetting} onCancel={this.cancelCommonSetting} width={'1000px'} style={defaultModalStyle} > <div className="common-setting-modal"> <Row className="setting-item"> <Col className="col-item" span="4"> <label>检查HttpCode:&nbsp;<Tooltip title={'检查 http code 是否为 200'}> <Icon type="question-circle-o" style={{ width: '10px' }} /> </Tooltip></label> </Col> <Col className="col-item" span="18"> <Switch onChange={e=>{ let {commonSetting} = this.state; this.setState({ commonSetting :{ ...commonSetting, checkHttpCodeIs200: e } }) }} checked={this.state.commonSetting.checkHttpCodeIs200} checkedChildren="开" unCheckedChildren="关" /> </Col> </Row> <Row className="setting-item"> <Col className="col-item" span="4"> <label>检查返回json:&nbsp;<Tooltip title={'检查接口返回数据字段值,比如检查 code 是不是等于 0'}> <Icon type="question-circle-o" style={{ width: '10px' }} /> </Tooltip></label> </Col> <Col className="col-item" span="6"> <Input value={this.state.commonSetting.checkResponseField.name} onChange={this.changeCommonFieldSetting('name')} placeholder="字段名" /> </Col> <Col className="col-item" span="6"> <Input onChange={this.changeCommonFieldSetting('value')} value={this.state.commonSetting.checkResponseField.value} placeholder="值" /> </Col> <Col className="col-item" span="6"> <Switch onChange={this.changeCommonFieldSetting('enable')} checked={this.state.commonSetting.checkResponseField.enable} checkedChildren="开" unCheckedChildren="关" /> </Col> </Row> <Row className="setting-item"> <Col className="col-item" span="4"> <label>检查返回数据结构:&nbsp;<Tooltip title={'只有 response 基于 json-schema 方式定义,该检查才会生效'}> <Icon type="question-circle-o" style={{ width: '10px' }} /> </Tooltip></label> </Col> <Col className="col-item" span="18"> <Switch onChange={e=>{ let {commonSetting} = this.state; this.setState({ commonSetting :{ ...commonSetting, checkResponseSchema: e } }) }} checked={this.state.commonSetting.checkResponseSchema} checkedChildren="开" unCheckedChildren="关" /> </Col> </Row> <Row className="setting-item"> <Col className="col-item " span="4"> <label>全局测试脚本:&nbsp;<Tooltip title={'在跑自动化测试时,优先调用全局脚本,只有全局脚本通过测试,才会开始跑case自定义的测试脚本'}> <Icon type="question-circle-o" style={{ width: '10px' }} /> </Tooltip></label> </Col> <Col className="col-item" span="14"> <div><Switch onChange={e=>{ let {commonSetting} = this.state; this.setState({ commonSetting :{ ...commonSetting, checkScript: { ...this.state.checkScript, enable: e } } }) }} checked={this.state.commonSetting.checkScript.enable} checkedChildren="开" unCheckedChildren="关" /></div> <AceEditor onChange={this.onChangeTest} className="case-script" data={this.state.commonSetting.checkScript.content} ref={aceEditor => { this.aceEditor = aceEditor; }} /> </Col> <Col span="6"> <div className="insert-code"> {InsertCodeMap.map(item => { return ( <div style={{ cursor: 'pointer' }} className="code-item" key={item.title} onClick={() => { this.handleInsertCode('\n' + item.code); }} > {item.title} </div> ); })} </div> </Col> </Row> </div> </Modal> <Row type="flex" justify="center" align="top"> <Col span={5}> <h2 className="interface-title" style={{ display: 'inline-block', margin: '8px 20px 16px 0px' }} > 测试集合&nbsp;<a target="_blank" rel="noopener noreferrer" href="https://hellosean1025.github.io/yapi/documents/case.html" > <Tooltip title="点击查看文档"> <Icon type="question-circle-o" /> </Tooltip> </a> </h2> </Col> <Col span={10}> <CaseEnv envList={this.props.envList} currProjectEnvChange={this.currProjectEnvChange} envValue={this.state.currColEnvObj} collapseKey={this.state.collapseKey} changeClose={this.changeCollapseClose} /> </Col> <Col span={9}> {this.state.hasPlugin ? ( <div style={{ float: 'right', paddingTop: '8px' }} > {this.props.curProjectRole !== 'guest' && ( <Tooltip title="在 YApi 服务端跑自动化测试,测试环境不能为私有网络,请确保 YApi 服务器可以访问到自动化测试环境domain"> <Button style={{ marginRight: '8px' }} onClick={this.autoTests} > 服务端测试 </Button> </Tooltip> )} <Button onClick={this.openCommonSetting} style={{ marginRight: '8px' }} >通用规则配置</Button> &nbsp; <Button type="primary" onClick={this.executeTests}> 开始测试 </Button> </div> ) : ( <Tooltip title="请安装 cross-request Chrome 插件"> <Button disabled type="primary" style={{ float: 'right', marginTop: '8px' }} > 开始测试 </Button> </Tooltip> )} </Col> </Row> <div className="component-label-wrapper"> <Label onChange={val => this.handleChangeInterfaceCol(val, col_name)} desc={col_desc} /> </div> <Table.Provider components={components} columns={resolvedColumns} style={{ width: '100%', borderCollapse: 'collapse' }} > <Table.Header className="interface-col-table-header" headerRows={resolve.headerRows({ columns })} /> <Table.Body className="interface-col-table-body" rows={resolvedRows} rowKey="id" onRow={this.onRow} /> </Table.Provider> <Modal title="测试报告" width="900px" style={{ minHeight: '500px' }} visible={this.state.visible} onCancel={this.handleCancel} footer={null} > <CaseReport {...this.reports[this.state.curCaseid]} /> </Modal> <Modal title="自定义测试脚本" width="660px" style={{ minHeight: '500px' }} visible={this.state.advVisible} onCancel={this.handleAdvCancel} onOk={this.handleAdvOk} maskClosable={false} > <h3> 是否开启:&nbsp; <Switch checked={this.state.enableScript} onChange={e => this.setState({ enableScript: e })} /> </h3> <AceEditor className="case-script" data={this.state.curScript} onChange={this.handleScriptChange} /> </Modal> {this.state.autoVisible && ( <Modal title="服务端自动化测试" width="780px" style={{ minHeight: '500px' }} visible={this.state.autoVisible} onCancel={this.handleAuto} className="autoTestsModal" footer={null} > <Row type="flex" justify="space-around" className="row" align="top"> <Col span={3} className="label" style={{ paddingTop: '16px' }}> 选择环境 <Tooltip title="默认使用测试用例选择的环境"> <Icon type="question-circle-o" /> </Tooltip> &nbsp;: </Col> <Col span={21}> <CaseEnv envList={this.props.envList} currProjectEnvChange={this.currProjectEnvChange} envValue={this.state.currColEnvObj} collapseKey={this.state.collapseKey} changeClose={this.changeCollapseClose} /> </Col> </Row> <Row type="flex" justify="space-around" className="row" align="middle"> <Col span={3} className="label"> 输出格式: </Col> <Col span={21}> <Select value={this.state.mode} onChange={this.modeChange}> <Option key="html" value="html"> html </Option> <Option key="json" value="json"> json </Option> </Select> </Col> </Row> <Row type="flex" justify="space-around" className="row" align="middle"> <Col span={3} className="label"> 消息通知 <Tooltip title={'测试不通过时,会给项目组成员发送消息通知'}> <Icon type="question-circle-o" style={{ width: '10px' }} /> </Tooltip> &nbsp;: </Col> <Col span={21}> <Switch checked={this.state.email} checkedChildren="开" unCheckedChildren="关" onChange={this.emailChange} /> </Col> </Row> <Row type="flex" justify="space-around" className="row" align="middle"> <Col span={3} className="label"> 下载数据 <Tooltip title={'开启后,测试数据将被下载到本地'}> <Icon type="question-circle-o" style={{ width: '10px' }} /> </Tooltip> &nbsp;: </Col> <Col span={21}> <Switch checked={this.state.download} checkedChildren="开" unCheckedChildren="关" onChange={this.downloadChange} /> </Col> </Row> <Row type="flex" justify="space-around" className="row" align="middle"> <Col span={21} className="autoTestUrl"> <a target="_blank" rel="noopener noreferrer" href={localUrl + autoTestsUrl} > {autoTestsUrl} </a> </Col> <Col span={3}> <Button className="copy-btn" onClick={() => this.copyUrl(localUrl + autoTestsUrl)}> 复制 </Button> </Col> </Row> <div className="autoTestMsg"> 注:访问该URL,可以测试所有用例,请确保YApi服务器可以访问到环境配置的 domain </div> </Modal> )} </div> ); } } export default InterfaceColContent;