mock平台

InterfaceMenu.js 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. import React, { PureComponent as Component } from 'react';
  2. import { connect } from 'react-redux';
  3. import PropTypes from 'prop-types';
  4. import {
  5. fetchInterfaceListMenu,
  6. fetchInterfaceList,
  7. fetchInterfaceCatList,
  8. fetchInterfaceData,
  9. deleteInterfaceData,
  10. deleteInterfaceCatData,
  11. initInterface
  12. } from '../../../../reducer/modules/interface.js';
  13. import { getProject } from '../../../../reducer/modules/project.js';
  14. import { Input, Icon, Button, Modal, message, Tree, Tooltip } from 'antd';
  15. import AddInterfaceForm from './AddInterfaceForm';
  16. import AddInterfaceCatForm from './AddInterfaceCatForm';
  17. import axios from 'axios';
  18. import { Link, withRouter } from 'react-router-dom';
  19. import produce from 'immer';
  20. import { arrayChangeIndex } from '../../../../common.js';
  21. import './interfaceMenu.scss';
  22. const confirm = Modal.confirm;
  23. const TreeNode = Tree.TreeNode;
  24. const headHeight = 240; // menu顶部到网页顶部部分的高度
  25. @connect(
  26. state => {
  27. return {
  28. list: state.inter.list,
  29. inter: state.inter.curdata,
  30. curProject: state.project.currProject,
  31. expands: []
  32. };
  33. },
  34. {
  35. fetchInterfaceListMenu,
  36. fetchInterfaceData,
  37. deleteInterfaceCatData,
  38. deleteInterfaceData,
  39. initInterface,
  40. getProject,
  41. fetchInterfaceCatList,
  42. fetchInterfaceList
  43. }
  44. )
  45. class InterfaceMenu extends Component {
  46. static propTypes = {
  47. match: PropTypes.object,
  48. inter: PropTypes.object,
  49. projectId: PropTypes.string,
  50. list: PropTypes.array,
  51. fetchInterfaceListMenu: PropTypes.func,
  52. curProject: PropTypes.object,
  53. fetchInterfaceData: PropTypes.func,
  54. addInterfaceData: PropTypes.func,
  55. deleteInterfaceData: PropTypes.func,
  56. initInterface: PropTypes.func,
  57. history: PropTypes.object,
  58. router: PropTypes.object,
  59. getProject: PropTypes.func,
  60. fetchInterfaceCatList: PropTypes.func,
  61. fetchInterfaceList: PropTypes.func
  62. };
  63. /**
  64. * @param {String} key
  65. */
  66. changeModal = (key, status) => {
  67. //visible add_cat_modal_visible change_cat_modal_visible del_cat_modal_visible
  68. let newState = {};
  69. newState[key] = status;
  70. this.setState(newState);
  71. };
  72. handleCancel = () => {
  73. this.setState({
  74. visible: false
  75. });
  76. };
  77. constructor(props) {
  78. super(props);
  79. this.state = {
  80. curKey: null,
  81. visible: false,
  82. delIcon: null,
  83. curCatid: null,
  84. add_cat_modal_visible: false,
  85. change_cat_modal_visible: false,
  86. del_cat_modal_visible: false,
  87. curCatdata: {},
  88. expands: null,
  89. list: []
  90. };
  91. }
  92. handleRequest() {
  93. this.props.initInterface();
  94. this.getList();
  95. }
  96. async getList() {
  97. let r = await this.props.fetchInterfaceListMenu(this.props.projectId);
  98. this.setState({
  99. list: r.payload.data.data
  100. });
  101. }
  102. componentWillMount() {
  103. this.handleRequest();
  104. }
  105. componentWillReceiveProps(nextProps) {
  106. if (this.props.list !== nextProps.list) {
  107. // console.log('next', nextProps.list)
  108. this.setState({
  109. list: nextProps.list
  110. });
  111. }
  112. }
  113. onSelect = selectedKeys => {
  114. const { history, match } = this.props;
  115. let curkey = selectedKeys[0];
  116. if (!curkey || !selectedKeys) {
  117. return false;
  118. }
  119. let basepath = '/project/' + match.params.id + '/interface/api';
  120. if (curkey === 'root') {
  121. history.push(basepath);
  122. } else {
  123. history.push(basepath + '/' + curkey);
  124. }
  125. this.setState({
  126. expands: null
  127. });
  128. };
  129. changeExpands = () => {
  130. this.setState({
  131. expands: null
  132. });
  133. };
  134. handleAddInterface = (data, cb) => {
  135. data.project_id = this.props.projectId;
  136. axios.post('/api/interface/add', data).then(res => {
  137. if (res.data.errcode !== 0) {
  138. return message.error(res.data.errmsg);
  139. }
  140. message.success('接口添加成功');
  141. let interfaceId = res.data.data._id;
  142. this.props.history.push('/project/' + this.props.projectId + '/interface/api/' + interfaceId);
  143. this.getList();
  144. this.setState({
  145. visible: false
  146. });
  147. if (cb) {
  148. cb();
  149. }
  150. });
  151. };
  152. handleAddInterfaceCat = data => {
  153. data.project_id = this.props.projectId;
  154. axios.post('/api/interface/add_cat', data).then(res => {
  155. if (res.data.errcode !== 0) {
  156. return message.error(res.data.errmsg);
  157. }
  158. message.success('接口分类添加成功');
  159. this.getList();
  160. this.props.getProject(data.project_id);
  161. this.setState({
  162. add_cat_modal_visible: false
  163. });
  164. });
  165. };
  166. handleChangeInterfaceCat = data => {
  167. data.project_id = this.props.projectId;
  168. let params = {
  169. catid: this.state.curCatdata._id,
  170. name: data.name,
  171. desc: data.desc
  172. };
  173. axios.post('/api/interface/up_cat', params).then(res => {
  174. if (res.data.errcode !== 0) {
  175. return message.error(res.data.errmsg);
  176. }
  177. message.success('接口分类更新成功');
  178. this.getList();
  179. this.props.getProject(data.project_id);
  180. this.setState({
  181. change_cat_modal_visible: false
  182. });
  183. });
  184. };
  185. showConfirm = data => {
  186. let that = this;
  187. let id = data._id;
  188. let catid = data.catid;
  189. const ref = confirm({
  190. title: '您确认删除此接口????',
  191. content: '温馨提示:接口删除后,无法恢复',
  192. okText: '确认',
  193. cancelText: '取消',
  194. async onOk() {
  195. await that.props.deleteInterfaceData(id, that.props.projectId);
  196. await that.getList();
  197. await that.props.fetchInterfaceCatList({ catid });
  198. ref.destroy();
  199. that.props.history.push(
  200. '/project/' + that.props.match.params.id + '/interface/api/cat_' + catid
  201. );
  202. },
  203. onCancel() {
  204. ref.destroy();
  205. }
  206. });
  207. };
  208. showDelCatConfirm = catid => {
  209. let that = this;
  210. const ref = confirm({
  211. title: '确定删除此接口分类吗?',
  212. content: '温馨提示:该操作会删除该分类下所有接口,接口删除后无法恢复',
  213. okText: '确认',
  214. cancelText: '取消',
  215. async onOk() {
  216. await that.props.deleteInterfaceCatData(catid, that.props.projectId);
  217. await that.getList();
  218. // await that.props.getProject(that.props.projectId)
  219. await that.props.fetchInterfaceList({ project_id: that.props.projectId });
  220. that.props.history.push('/project/' + that.props.match.params.id + '/interface/api');
  221. ref.destroy();
  222. },
  223. onCancel() {}
  224. });
  225. };
  226. copyInterface = async id => {
  227. let interfaceData = await this.props.fetchInterfaceData(id);
  228. // let data = JSON.parse(JSON.stringify(interfaceData.payload.data.data));
  229. // data.title = data.title + '_copy';
  230. // data.path = data.path + '_' + Date.now();
  231. let data = interfaceData.payload.data.data;
  232. let newData = produce(data, draftData => {
  233. draftData.title = draftData.title + '_copy';
  234. draftData.path = draftData.path + '_' + Date.now();
  235. });
  236. axios.post('/api/interface/add', newData).then(async res => {
  237. if (res.data.errcode !== 0) {
  238. return message.error(res.data.errmsg);
  239. }
  240. message.success('接口添加成功');
  241. let interfaceId = res.data.data._id;
  242. await this.getList();
  243. this.props.history.push('/project/' + this.props.projectId + '/interface/api/' + interfaceId);
  244. this.setState({
  245. visible: false
  246. });
  247. });
  248. };
  249. enterItem = id => {
  250. this.setState({ delIcon: id });
  251. };
  252. leaveItem = () => {
  253. this.setState({ delIcon: null });
  254. };
  255. onFilter = e => {
  256. this.setState({
  257. filter: e.target.value,
  258. list: JSON.parse(JSON.stringify(this.props.list))
  259. });
  260. };
  261. onExpand = e => {
  262. this.setState({
  263. expands: e
  264. });
  265. };
  266. onDrop = async e => {
  267. const dropCatIndex = e.node.props.pos.split('-')[1] - 1;
  268. const dragCatIndex = e.dragNode.props.pos.split('-')[1] - 1;
  269. if (dropCatIndex < 0 || dragCatIndex < 0) {
  270. return;
  271. }
  272. const { list } = this.props;
  273. const dropCatId = this.props.list[dropCatIndex]._id;
  274. const id = e.dragNode.props.eventKey;
  275. const dragCatId = this.props.list[dragCatIndex]._id;
  276. const dropPos = e.node.props.pos.split('-');
  277. const dropIndex = Number(dropPos[dropPos.length - 1]);
  278. const dragPos = e.dragNode.props.pos.split('-');
  279. const dragIndex = Number(dragPos[dragPos.length - 1]);
  280. if (id.indexOf('cat') === -1) {
  281. if (dropCatId === dragCatId) {
  282. // 同一个分类下的接口交换顺序
  283. let colList = list[dropCatIndex].list;
  284. let changes = arrayChangeIndex(colList, dragIndex, dropIndex);
  285. axios.post('/api/interface/up_index', changes).then();
  286. } else {
  287. await axios.post('/api/interface/up', { id, catid: dropCatId });
  288. }
  289. const { projectId, router } = this.props;
  290. this.props.fetchInterfaceListMenu(projectId);
  291. this.props.fetchInterfaceList({ project_id: projectId });
  292. if (router && isNaN(router.params.actionId)) {
  293. // 更新分类list下的数据
  294. let catid = router.params.actionId.substr(4);
  295. this.props.fetchInterfaceCatList({ catid });
  296. }
  297. } else {
  298. // 分类之间拖动
  299. let changes = arrayChangeIndex(list, dragIndex - 1, dropIndex - 1);
  300. axios.post('/api/interface/up_cat_index', changes).then();
  301. this.props.fetchInterfaceListMenu(this.props.projectId);
  302. }
  303. };
  304. // 数据过滤
  305. filterList = list => {
  306. let that = this;
  307. let arr = [];
  308. let menuList = produce(list, draftList => {
  309. draftList.filter(item => {
  310. let interfaceFilter = false;
  311. // arr = [];
  312. if (item.name.indexOf(that.state.filter) === -1) {
  313. item.list = item.list.filter(inter => {
  314. if (
  315. inter.title.indexOf(that.state.filter) === -1 &&
  316. inter.path.indexOf(that.state.filter) === -1
  317. ) {
  318. return false;
  319. }
  320. //arr.push('cat_' + inter.catid)
  321. interfaceFilter = true;
  322. return true;
  323. });
  324. arr.push('cat_' + item._id);
  325. return interfaceFilter === true;
  326. }
  327. arr.push('cat_' + item._id);
  328. return true;
  329. });
  330. });
  331. return { menuList, arr };
  332. };
  333. render() {
  334. const matchParams = this.props.match.params;
  335. // let menuList = this.state.list;
  336. const searchBox = (
  337. <div className="interface-filter">
  338. <Input onChange={this.onFilter} value={this.state.filter} placeholder="搜索接口" />
  339. <Button
  340. type="primary"
  341. onClick={() => this.changeModal('add_cat_modal_visible', true)}
  342. className="btn-filter"
  343. >
  344. 添加分类
  345. </Button>
  346. {this.state.visible ? (
  347. <Modal
  348. title="添加接口"
  349. visible={this.state.visible}
  350. onCancel={() => this.changeModal('visible', false)}
  351. footer={null}
  352. className="addcatmodal"
  353. >
  354. <AddInterfaceForm
  355. catdata={this.props.curProject.cat}
  356. catid={this.state.curCatid}
  357. onCancel={() => this.changeModal('visible', false)}
  358. onSubmit={this.handleAddInterface}
  359. />
  360. </Modal>
  361. ) : (
  362. ''
  363. )}
  364. {this.state.add_cat_modal_visible ? (
  365. <Modal
  366. title="添加分类"
  367. visible={this.state.add_cat_modal_visible}
  368. onCancel={() => this.changeModal('add_cat_modal_visible', false)}
  369. footer={null}
  370. className="addcatmodal"
  371. >
  372. <AddInterfaceCatForm
  373. onCancel={() => this.changeModal('add_cat_modal_visible', false)}
  374. onSubmit={this.handleAddInterfaceCat}
  375. />
  376. </Modal>
  377. ) : (
  378. ''
  379. )}
  380. {this.state.change_cat_modal_visible ? (
  381. <Modal
  382. title="修改分类"
  383. visible={this.state.change_cat_modal_visible}
  384. onCancel={() => this.changeModal('change_cat_modal_visible', false)}
  385. footer={null}
  386. className="addcatmodal"
  387. >
  388. <AddInterfaceCatForm
  389. catdata={this.state.curCatdata}
  390. onCancel={() => this.changeModal('change_cat_modal_visible', false)}
  391. onSubmit={this.handleChangeInterfaceCat}
  392. />
  393. </Modal>
  394. ) : (
  395. ''
  396. )}
  397. </div>
  398. );
  399. const defaultExpandedKeys = () => {
  400. const { router, inter, list } = this.props,
  401. rNull = { expands: [], selects: [] };
  402. if (list.length === 0) {
  403. return rNull;
  404. }
  405. if (router) {
  406. if (!isNaN(router.params.actionId)) {
  407. if (!inter || !inter._id) {
  408. return rNull;
  409. }
  410. return {
  411. expands: this.state.expands ? this.state.expands : ['cat_' + inter.catid],
  412. selects: [inter._id + '']
  413. };
  414. } else {
  415. let catid = router.params.actionId.substr(4);
  416. return {
  417. expands: this.state.expands ? this.state.expands : ['cat_' + catid],
  418. selects: ['cat_' + catid]
  419. };
  420. }
  421. } else {
  422. return {
  423. expands: this.state.expands ? this.state.expands : ['cat_' + list[0]._id],
  424. selects: ['root']
  425. };
  426. }
  427. };
  428. const itemInterfaceCreate = item => {
  429. return (
  430. <TreeNode
  431. title={
  432. <div
  433. className="container-title"
  434. onMouseEnter={() => this.enterItem(item._id)}
  435. onMouseLeave={this.leaveItem}
  436. >
  437. <Link
  438. className="interface-item"
  439. onClick={e => e.stopPropagation()}
  440. to={'/project/' + matchParams.id + '/interface/api/' + item._id}
  441. >
  442. {item.title}
  443. </Link>
  444. <div className="btns">
  445. <Tooltip title="删除接口">
  446. <Icon
  447. type="delete"
  448. className="interface-delete-icon"
  449. onClick={e => {
  450. e.stopPropagation();
  451. this.showConfirm(item);
  452. }}
  453. style={{ display: this.state.delIcon == item._id ? 'block' : 'none' }}
  454. />
  455. </Tooltip>
  456. <Tooltip title="复制接口">
  457. <Icon
  458. type="copy"
  459. className="interface-delete-icon"
  460. onClick={e => {
  461. e.stopPropagation();
  462. this.copyInterface(item._id);
  463. }}
  464. style={{ display: this.state.delIcon == item._id ? 'block' : 'none' }}
  465. />
  466. </Tooltip>
  467. </div>
  468. {/*<Dropdown overlay={menu(item)} trigger={['click']} onClick={e => e.stopPropagation()}>
  469. <Icon type='ellipsis' className="interface-delete-icon" style={{ opacity: this.state.delIcon == item._id ? 1 : 0 }}/>
  470. </Dropdown>*/}
  471. </div>
  472. }
  473. key={'' + item._id}
  474. />
  475. );
  476. };
  477. let currentKes = defaultExpandedKeys();
  478. let menuList;
  479. if (this.state.filter) {
  480. let res = this.filterList(this.state.list);
  481. menuList = res.menuList;
  482. currentKes.expands = res.arr;
  483. } else {
  484. menuList = this.state.list;
  485. }
  486. return (
  487. <div>
  488. {searchBox}
  489. {menuList.length > 0 ? (
  490. <div
  491. className="tree-wrappper"
  492. style={{ maxHeight: parseInt(document.body.clientHeight) - headHeight + 'px' }}
  493. >
  494. <Tree
  495. className="interface-list"
  496. defaultExpandedKeys={currentKes.expands}
  497. defaultSelectedKeys={currentKes.selects}
  498. expandedKeys={currentKes.expands}
  499. selectedKeys={currentKes.selects}
  500. onSelect={this.onSelect}
  501. onExpand={this.onExpand}
  502. draggable
  503. onDrop={this.onDrop}
  504. >
  505. <TreeNode
  506. className="item-all-interface"
  507. title={
  508. <Link
  509. onClick={e => {
  510. e.stopPropagation();
  511. this.changeExpands();
  512. }}
  513. to={'/project/' + matchParams.id + '/interface/api'}
  514. >
  515. <Icon type="folder" style={{ marginRight: 5 }} />
  516. 全部接口
  517. </Link>
  518. }
  519. key="root"
  520. />
  521. {menuList.map(item => {
  522. return (
  523. <TreeNode
  524. title={
  525. <div
  526. className="container-title"
  527. onMouseEnter={() => this.enterItem(item._id)}
  528. onMouseLeave={this.leaveItem}
  529. >
  530. <Link
  531. className="interface-item"
  532. onClick={e => {
  533. e.stopPropagation();
  534. this.changeExpands();
  535. }}
  536. to={'/project/' + matchParams.id + '/interface/api/cat_' + item._id}
  537. >
  538. <Icon type="folder-open" style={{ marginRight: 5 }} />
  539. {item.name}
  540. </Link>
  541. <div className="btns">
  542. <Tooltip title="删除分类">
  543. <Icon
  544. type="delete"
  545. className="interface-delete-icon"
  546. onClick={e => {
  547. e.stopPropagation();
  548. this.showDelCatConfirm(item._id);
  549. }}
  550. style={{ display: this.state.delIcon == item._id ? 'block' : 'none' }}
  551. />
  552. </Tooltip>
  553. <Tooltip title="修改分类">
  554. <Icon
  555. type="edit"
  556. className="interface-delete-icon"
  557. style={{ display: this.state.delIcon == item._id ? 'block' : 'none' }}
  558. onClick={e => {
  559. e.stopPropagation();
  560. this.changeModal('change_cat_modal_visible', true);
  561. this.setState({
  562. curCatdata: item
  563. });
  564. }}
  565. />
  566. </Tooltip>
  567. <Tooltip title="添加接口">
  568. <Icon
  569. type="plus"
  570. className="interface-delete-icon"
  571. style={{ display: this.state.delIcon == item._id ? 'block' : 'none' }}
  572. onClick={e => {
  573. e.stopPropagation();
  574. this.changeModal('visible', true);
  575. this.setState({
  576. curCatid: item._id
  577. });
  578. }}
  579. />
  580. </Tooltip>
  581. </div>
  582. {/*<Dropdown overlay={menu(item)} trigger={['click']} onClick={e => e.stopPropagation()}>
  583. <Icon type='ellipsis' className="interface-delete-icon" />
  584. </Dropdown>*/}
  585. </div>
  586. }
  587. key={'cat_' + item._id}
  588. className={`interface-item-nav ${item.list.length ? '' : 'cat_switch_hidden'}`}
  589. >
  590. {item.list.map(itemInterfaceCreate)}
  591. </TreeNode>
  592. );
  593. })}
  594. </Tree>
  595. </div>
  596. ) : null}
  597. </div>
  598. );
  599. }
  600. }
  601. export default withRouter(InterfaceMenu);