mock平台

InterfaceColMenu.js 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  1. import React, { PureComponent as Component } from 'react';
  2. import { connect } from 'react-redux';
  3. import { withRouter } from 'react-router';
  4. import PropTypes from 'prop-types';
  5. import {
  6. fetchInterfaceColList,
  7. fetchInterfaceCaseList,
  8. setColData,
  9. fetchCaseList,
  10. fetchCaseData
  11. } from '../../../../reducer/modules/interfaceCol';
  12. import { fetchProjectList } from '../../../../reducer/modules/project';
  13. import axios from 'axios';
  14. import ImportInterface from './ImportInterface';
  15. import { Input, Icon, Button, Modal, message, Tooltip, Tree, Form } from 'antd';
  16. import { arrayChangeIndex } from '../../../../common.js';
  17. const TreeNode = Tree.TreeNode;
  18. const FormItem = Form.Item;
  19. const confirm = Modal.confirm;
  20. const headHeight = 240; // menu顶部到网页顶部部分的高度
  21. import './InterfaceColMenu.scss';
  22. const ColModalForm = Form.create()(props => {
  23. const { visible, onCancel, onCreate, form, title } = props;
  24. const { getFieldDecorator } = form;
  25. return (
  26. <Modal visible={visible} title={title} onCancel={onCancel} onOk={onCreate}>
  27. <Form layout="vertical">
  28. <FormItem label="集合名">
  29. {getFieldDecorator('colName', {
  30. rules: [{ required: true, message: '请输入集合命名!' }]
  31. })(<Input />)}
  32. </FormItem>
  33. <FormItem label="简介">{getFieldDecorator('colDesc')(<Input type="textarea" />)}</FormItem>
  34. </Form>
  35. </Modal>
  36. );
  37. });
  38. @connect(
  39. state => {
  40. return {
  41. interfaceColList: state.interfaceCol.interfaceColList,
  42. currCase: state.interfaceCol.currCase,
  43. isRander: state.interfaceCol.isRander,
  44. currCaseId: state.interfaceCol.currCaseId,
  45. // list: state.inter.list,
  46. // 当前项目的信息
  47. curProject: state.project.currProject
  48. // projectList: state.project.projectList
  49. };
  50. },
  51. {
  52. fetchInterfaceColList,
  53. fetchInterfaceCaseList,
  54. fetchCaseData,
  55. // fetchInterfaceListMenu,
  56. fetchCaseList,
  57. setColData,
  58. fetchProjectList
  59. }
  60. )
  61. @withRouter
  62. export default class InterfaceColMenu extends Component {
  63. static propTypes = {
  64. match: PropTypes.object,
  65. interfaceColList: PropTypes.array,
  66. fetchInterfaceColList: PropTypes.func,
  67. fetchInterfaceCaseList: PropTypes.func,
  68. // fetchInterfaceListMenu: PropTypes.func,
  69. fetchCaseList: PropTypes.func,
  70. fetchCaseData: PropTypes.func,
  71. setColData: PropTypes.func,
  72. currCaseId: PropTypes.number,
  73. history: PropTypes.object,
  74. isRander: PropTypes.bool,
  75. // list: PropTypes.array,
  76. router: PropTypes.object,
  77. currCase: PropTypes.object,
  78. curProject: PropTypes.object,
  79. fetchProjectList: PropTypes.func
  80. // projectList: PropTypes.array
  81. };
  82. state = {
  83. colModalType: '',
  84. colModalVisible: false,
  85. editColId: 0,
  86. filterValue: '',
  87. importInterVisible: false,
  88. importInterIds: [],
  89. importColId: 0,
  90. expands: null,
  91. list: [],
  92. delIcon: null,
  93. selectedProject: null
  94. };
  95. constructor(props) {
  96. super(props);
  97. }
  98. componentWillMount() {
  99. this.getList();
  100. }
  101. componentWillReceiveProps(nextProps) {
  102. if (this.props.interfaceColList !== nextProps.interfaceColList) {
  103. this.setState({
  104. list: nextProps.interfaceColList
  105. });
  106. }
  107. }
  108. async getList() {
  109. let r = await this.props.fetchInterfaceColList(this.props.match.params.id);
  110. this.setState({
  111. list: r.payload.data.data
  112. });
  113. return r;
  114. }
  115. addorEditCol = async () => {
  116. const { colName: name, colDesc: desc } = this.form.getFieldsValue();
  117. const { colModalType, editColId: col_id } = this.state;
  118. const project_id = this.props.match.params.id;
  119. let res = {};
  120. if (colModalType === 'add') {
  121. res = await axios.post('/api/col/add_col', { name, desc, project_id });
  122. } else if (colModalType === 'edit') {
  123. res = await axios.post('/api/col/up_col', { name, desc, col_id });
  124. }
  125. if (!res.data.errcode) {
  126. this.setState({
  127. colModalVisible: false
  128. });
  129. message.success(colModalType === 'edit' ? '修改集合成功' : '添加集合成功');
  130. // await this.props.fetchInterfaceColList(project_id);
  131. this.getList();
  132. } else {
  133. message.error(res.data.errmsg);
  134. }
  135. };
  136. onExpand = keys => {
  137. this.setState({ expands: keys });
  138. };
  139. onSelect = keys => {
  140. if (keys.length) {
  141. const type = keys[0].split('_')[0];
  142. const id = keys[0].split('_')[1];
  143. const project_id = this.props.match.params.id;
  144. if (type === 'col') {
  145. this.props.setColData({
  146. isRander: false
  147. });
  148. this.props.history.push('/project/' + project_id + '/interface/col/' + id);
  149. } else {
  150. this.props.setColData({
  151. isRander: false
  152. });
  153. this.props.history.push('/project/' + project_id + '/interface/case/' + id);
  154. }
  155. }
  156. this.setState({
  157. expands: null
  158. });
  159. };
  160. showDelColConfirm = colId => {
  161. let that = this;
  162. const params = this.props.match.params;
  163. confirm({
  164. title: '您确认删除此测试集合',
  165. content: '温馨提示:该操作会删除该集合下所有测试用例,用例删除后无法恢复',
  166. okText: '确认',
  167. cancelText: '取消',
  168. async onOk() {
  169. const res = await axios.get('/api/col/del_col?col_id=' + colId);
  170. if (!res.data.errcode) {
  171. message.success('删除集合成功');
  172. const result = await that.getList();
  173. const nextColId = result.payload.data.data[0]._id;
  174. that.props.history.push('/project/' + params.id + '/interface/col/' + nextColId);
  175. } else {
  176. message.error(res.data.errmsg);
  177. }
  178. }
  179. });
  180. };
  181. // 复制测试集合
  182. copyInterface = async item => {
  183. if (this._copyInterfaceSign === true) {
  184. return;
  185. }
  186. this._copyInterfaceSign = true;
  187. const { desc, project_id, _id: col_id } = item;
  188. let { name } = item;
  189. name = `${name} copy`;
  190. // 添加集合
  191. const add_col_res = await axios.post('/api/col/add_col', { name, desc, project_id });
  192. if (add_col_res.data.errcode) {
  193. message.error(add_col_res.data.errmsg);
  194. return;
  195. }
  196. const new_col_id = add_col_res.data.data._id;
  197. // 克隆集合
  198. const add_case_list_res = await axios.post('/api/col/clone_case_list', {
  199. new_col_id,
  200. col_id,
  201. project_id
  202. });
  203. this._copyInterfaceSign = false;
  204. if (add_case_list_res.data.errcode) {
  205. message.error(add_case_list_res.data.errmsg);
  206. return;
  207. }
  208. // 刷新接口列表
  209. // await this.props.fetchInterfaceColList(project_id);
  210. this.getList();
  211. this.props.setColData({ isRander: true });
  212. message.success('克隆测试集成功');
  213. };
  214. showNoDelColConfirm = () => {
  215. confirm({
  216. title: '此测试集合为最后一个集合',
  217. content: '温馨提示:建议不要删除'
  218. });
  219. };
  220. caseCopy = async caseId=> {
  221. let that = this;
  222. let caseData = await that.props.fetchCaseData(caseId);
  223. let data = caseData.payload.data.data;
  224. data = JSON.parse(JSON.stringify(data));
  225. data.casename=`${data.casename}_copy`
  226. delete data._id
  227. const res = await axios.post('/api/col/add_case',data);
  228. if (!res.data.errcode) {
  229. message.success('克隆用例成功');
  230. let colId = res.data.data.col_id;
  231. let projectId=res.data.data.project_id;
  232. await this.getList();
  233. this.props.history.push('/project/' + projectId + '/interface/col/' + colId);
  234. this.setState({
  235. visible: false
  236. });
  237. } else {
  238. message.error(res.data.errmsg);
  239. }
  240. };
  241. showDelCaseConfirm = caseId => {
  242. let that = this;
  243. const params = this.props.match.params;
  244. confirm({
  245. title: '您确认删除此测试用例',
  246. content: '温馨提示:用例删除后无法恢复',
  247. okText: '确认',
  248. cancelText: '取消',
  249. async onOk() {
  250. const res = await axios.get('/api/col/del_case?caseid=' + caseId);
  251. if (!res.data.errcode) {
  252. message.success('删除用例成功');
  253. that.getList();
  254. // 如果删除当前选中 case,切换路由到集合
  255. if (+caseId === +that.props.currCaseId) {
  256. that.props.history.push('/project/' + params.id + '/interface/col/');
  257. } else {
  258. // that.props.fetchInterfaceColList(that.props.match.params.id);
  259. that.props.setColData({ isRander: true });
  260. }
  261. } else {
  262. message.error(res.data.errmsg);
  263. }
  264. }
  265. });
  266. };
  267. showColModal = (type, col) => {
  268. const editCol =
  269. type === 'edit' ? { colName: col.name, colDesc: col.desc } : { colName: '', colDesc: '' };
  270. this.setState({
  271. colModalVisible: true,
  272. colModalType: type || 'add',
  273. editColId: col && col._id
  274. });
  275. this.form.setFieldsValue(editCol);
  276. };
  277. saveFormRef = form => {
  278. this.form = form;
  279. };
  280. selectInterface = (importInterIds, selectedProject) => {
  281. this.setState({ importInterIds, selectedProject });
  282. };
  283. showImportInterfaceModal = async colId => {
  284. // const projectId = this.props.match.params.id;
  285. // console.log('project', this.props.curProject)
  286. const groupId = this.props.curProject.group_id;
  287. await this.props.fetchProjectList(groupId);
  288. // await this.props.fetchInterfaceListMenu(projectId)
  289. this.setState({ importInterVisible: true, importColId: colId });
  290. };
  291. handleImportOk = async () => {
  292. const project_id = this.state.selectedProject || this.props.match.params.id;
  293. const { importColId, importInterIds } = this.state;
  294. const res = await axios.post('/api/col/add_case_list', {
  295. interface_list: importInterIds,
  296. col_id: importColId,
  297. project_id
  298. });
  299. if (!res.data.errcode) {
  300. this.setState({ importInterVisible: false });
  301. message.success('导入集合成功');
  302. // await this.props.fetchInterfaceColList(project_id);
  303. this.getList();
  304. this.props.setColData({ isRander: true });
  305. } else {
  306. message.error(res.data.errmsg);
  307. }
  308. };
  309. handleImportCancel = () => {
  310. this.setState({ importInterVisible: false });
  311. };
  312. filterCol = e => {
  313. const value = e.target.value;
  314. // console.log('list', this.props.interfaceColList);
  315. // const newList = produce(this.props.interfaceColList, draftList => {})
  316. // console.log('newList',newList);
  317. this.setState({
  318. filterValue: value,
  319. list: JSON.parse(JSON.stringify(this.props.interfaceColList))
  320. // list: newList
  321. });
  322. };
  323. onDrop = async e => {
  324. // const projectId = this.props.match.params.id;
  325. const { interfaceColList } = this.props;
  326. const dropColIndex = e.node.props.pos.split('-')[1];
  327. const dropColId = interfaceColList[dropColIndex]._id;
  328. const id = e.dragNode.props.eventKey;
  329. const dragColIndex = e.dragNode.props.pos.split('-')[1];
  330. const dragColId = interfaceColList[dragColIndex]._id;
  331. const dropPos = e.node.props.pos.split('-');
  332. const dropIndex = Number(dropPos[dropPos.length - 1]);
  333. const dragPos = e.dragNode.props.pos.split('-');
  334. const dragIndex = Number(dragPos[dragPos.length - 1]);
  335. if (id.indexOf('col') === -1) {
  336. if (dropColId === dragColId) {
  337. // 同一个测试集合下的接口交换顺序
  338. let caseList = interfaceColList[dropColIndex].caseList;
  339. let changes = arrayChangeIndex(caseList, dragIndex, dropIndex);
  340. axios.post('/api/col/up_case_index', changes).then();
  341. }
  342. await axios.post('/api/col/up_case', { id: id.split('_')[1], col_id: dropColId });
  343. // this.props.fetchInterfaceColList(projectId);
  344. this.getList();
  345. this.props.setColData({ isRander: true });
  346. } else {
  347. let changes = arrayChangeIndex(interfaceColList, dragIndex, dropIndex);
  348. axios.post('/api/col/up_col_index', changes).then();
  349. this.getList();
  350. }
  351. };
  352. enterItem = id => {
  353. this.setState({ delIcon: id });
  354. };
  355. leaveItem = () => {
  356. this.setState({ delIcon: null });
  357. };
  358. render() {
  359. // const { currColId, currCaseId, isShowCol } = this.props;
  360. const { colModalType, colModalVisible, importInterVisible } = this.state;
  361. const currProjectId = this.props.match.params.id;
  362. // const menu = (col) => {
  363. // return (
  364. // <Menu>
  365. // <Menu.Item>
  366. // <span onClick={() => this.showColModal('edit', col)}>修改集合</span>
  367. // </Menu.Item>
  368. // <Menu.Item>
  369. // <span onClick={() => {
  370. // this.showDelColConfirm(col._id)
  371. // }}>删除集合</span>
  372. // </Menu.Item>
  373. // <Menu.Item>
  374. // <span onClick={() => this.showImportInterface(col._id)}>导入接口</span>
  375. // </Menu.Item>
  376. // </Menu>
  377. // )
  378. // };
  379. const defaultExpandedKeys = () => {
  380. const { router, currCase, interfaceColList } = this.props,
  381. rNull = { expands: [], selects: [] };
  382. if (interfaceColList.length === 0) {
  383. return rNull;
  384. }
  385. if (router) {
  386. if (router.params.action === 'case') {
  387. if (!currCase || !currCase._id) {
  388. return rNull;
  389. }
  390. return {
  391. expands: this.state.expands ? this.state.expands : ['col_' + currCase.col_id],
  392. selects: ['case_' + currCase._id + '']
  393. };
  394. } else {
  395. let col_id = router.params.actionId;
  396. return {
  397. expands: this.state.expands ? this.state.expands : ['col_' + col_id],
  398. selects: ['col_' + col_id]
  399. };
  400. }
  401. } else {
  402. return {
  403. expands: this.state.expands ? this.state.expands : ['col_' + interfaceColList[0]._id],
  404. selects: ['root']
  405. };
  406. }
  407. };
  408. const itemInterfaceColCreate = interfaceCase => {
  409. return (
  410. <TreeNode
  411. style={{ width: '100%' }}
  412. key={'case_' + interfaceCase._id}
  413. title={
  414. <div
  415. className="menu-title"
  416. onMouseEnter={() => this.enterItem(interfaceCase._id)}
  417. onMouseLeave={this.leaveItem}
  418. title={interfaceCase.casename}
  419. >
  420. <span className="casename">{interfaceCase.casename}</span>
  421. <div className="btns">
  422. <Tooltip title="删除用例">
  423. <Icon
  424. type="delete"
  425. className="interface-delete-icon"
  426. onClick={e => {
  427. e.stopPropagation();
  428. this.showDelCaseConfirm(interfaceCase._id);
  429. }}
  430. style={{ display: this.state.delIcon == interfaceCase._id ? 'block' : 'none' }}
  431. />
  432. </Tooltip>
  433. <Tooltip title="克隆用例">
  434. <Icon
  435. type="copy"
  436. className="interface-delete-icon"
  437. onClick={e => {
  438. e.stopPropagation();
  439. this.caseCopy(interfaceCase._id);
  440. }}
  441. style={{ display: this.state.delIcon == interfaceCase._id ? 'block' : 'none' }}
  442. />
  443. </Tooltip>
  444. </div>
  445. </div>
  446. }
  447. />
  448. );
  449. };
  450. let currentKes = defaultExpandedKeys();
  451. // console.log('currentKey', currentKes)
  452. let list = this.state.list;
  453. if (this.state.filterValue) {
  454. let arr = [];
  455. list = list.filter(item => {
  456. item.caseList = item.caseList.filter(inter => {
  457. if (inter.casename.indexOf(this.state.filterValue) === -1
  458. && inter.path.indexOf(this.state.filterValue) === -1
  459. ) {
  460. return false;
  461. }
  462. return true;
  463. });
  464. arr.push('col_' + item._id);
  465. return true;
  466. });
  467. // console.log('arr', arr);
  468. if (arr.length > 0) {
  469. currentKes.expands = arr;
  470. }
  471. }
  472. // console.log('list', list);
  473. // console.log('currentKey', currentKes)
  474. return (
  475. <div>
  476. <div className="interface-filter">
  477. <Input placeholder="搜索测试集合" onChange={this.filterCol} />
  478. <Tooltip placement="bottom" title="添加集合">
  479. <Button
  480. type="primary"
  481. style={{ marginLeft: '16px' }}
  482. onClick={() => this.showColModal('add')}
  483. className="btn-filter"
  484. >
  485. 添加集合
  486. </Button>
  487. </Tooltip>
  488. </div>
  489. <div className="tree-wrapper" style={{ maxHeight: parseInt(document.body.clientHeight) - headHeight + 'px'}}>
  490. <Tree
  491. className="col-list-tree"
  492. defaultExpandedKeys={currentKes.expands}
  493. defaultSelectedKeys={currentKes.selects}
  494. expandedKeys={currentKes.expands}
  495. selectedKeys={currentKes.selects}
  496. onSelect={this.onSelect}
  497. autoExpandParent
  498. draggable
  499. onExpand={this.onExpand}
  500. onDrop={this.onDrop}
  501. >
  502. {list.map(col => (
  503. <TreeNode
  504. key={'col_' + col._id}
  505. title={
  506. <div className="menu-title">
  507. <span>
  508. <Icon type="folder-open" style={{ marginRight: 5 }} />
  509. <span>{col.name}</span>
  510. </span>
  511. <div className="btns">
  512. <Tooltip title="删除集合">
  513. <Icon
  514. type="delete"
  515. style={{ display: list.length > 1 ? '' : 'none' }}
  516. className="interface-delete-icon"
  517. onClick={() => {
  518. this.showDelColConfirm(col._id);
  519. }}
  520. />
  521. </Tooltip>
  522. <Tooltip title="编辑集合">
  523. <Icon
  524. type="edit"
  525. className="interface-delete-icon"
  526. onClick={e => {
  527. e.stopPropagation();
  528. this.showColModal('edit', col);
  529. }}
  530. />
  531. </Tooltip>
  532. <Tooltip title="导入接口">
  533. <Icon
  534. type="plus"
  535. className="interface-delete-icon"
  536. onClick={e => {
  537. e.stopPropagation();
  538. this.showImportInterfaceModal(col._id);
  539. }}
  540. />
  541. </Tooltip>
  542. <Tooltip title="克隆集合">
  543. <Icon
  544. type="copy"
  545. className="interface-delete-icon"
  546. onClick={e => {
  547. e.stopPropagation();
  548. this.copyInterface(col);
  549. }}
  550. />
  551. </Tooltip>
  552. </div>
  553. {/*<Dropdown overlay={menu(col)} trigger={['click']} onClick={e => e.stopPropagation()}>
  554. <Icon className="opts-icon" type='ellipsis'/>
  555. </Dropdown>*/}
  556. </div>
  557. }
  558. >
  559. {col.caseList.map(itemInterfaceColCreate)}
  560. </TreeNode>
  561. ))}
  562. </Tree>
  563. </div>
  564. <ColModalForm
  565. ref={this.saveFormRef}
  566. type={colModalType}
  567. visible={colModalVisible}
  568. onCancel={() => {
  569. this.setState({ colModalVisible: false });
  570. }}
  571. onCreate={this.addorEditCol}
  572. />
  573. <Modal
  574. title="导入接口到集合"
  575. visible={importInterVisible}
  576. onOk={this.handleImportOk}
  577. onCancel={this.handleImportCancel}
  578. className="import-case-modal"
  579. width={800}
  580. >
  581. <ImportInterface currProjectId={currProjectId} selectInterface={this.selectInterface} />
  582. </Modal>
  583. </div>
  584. );
  585. }
  586. }