mock平台

ProjectData.js 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. import React, { PureComponent as Component } from 'react';
  2. import {
  3. Upload,
  4. Icon,
  5. message,
  6. Select,
  7. Tooltip,
  8. Button,
  9. Spin,
  10. Switch,
  11. Modal,
  12. Radio,
  13. Input,
  14. Checkbox
  15. } from 'antd';
  16. import PropTypes from 'prop-types';
  17. import { connect } from 'react-redux';
  18. import './ProjectData.scss';
  19. import axios from 'axios';
  20. import URL from 'url';
  21. const Dragger = Upload.Dragger;
  22. import { saveImportData } from '../../../../reducer/modules/interface';
  23. import { fetchUpdateLogData } from '../../../../reducer/modules/news.js';
  24. import { handleSwaggerUrlData } from '../../../../reducer/modules/project';
  25. const Option = Select.Option;
  26. const confirm = Modal.confirm;
  27. const plugin = require('client/plugin.js');
  28. const RadioGroup = Radio.Group;
  29. const importDataModule = {};
  30. const exportDataModule = {};
  31. const HandleImportData = require('common/HandleImportData');
  32. function handleExportRouteParams(url, status, isWiki) {
  33. if (!url) {
  34. return;
  35. }
  36. let urlObj = URL.parse(url, true),
  37. query = {};
  38. query = Object.assign(query, urlObj.query, { status, isWiki });
  39. return URL.format({
  40. pathname: urlObj.pathname,
  41. query
  42. });
  43. }
  44. // exportDataModule.pdf = {
  45. // name: 'Pdf',
  46. // route: '/api/interface/download_crx',
  47. // desc: '导出项目接口文档为 pdf 文件'
  48. // }
  49. @connect(
  50. state => {
  51. return {
  52. curCatid: -(-state.inter.curdata.catid),
  53. basePath: state.project.currProject.basepath,
  54. updateLogList: state.news.updateLogList,
  55. swaggerUrlData: state.project.swaggerUrlData
  56. };
  57. },
  58. {
  59. saveImportData,
  60. fetchUpdateLogData,
  61. handleSwaggerUrlData
  62. }
  63. )
  64. class ProjectData extends Component {
  65. constructor(props) {
  66. super(props);
  67. this.state = {
  68. selectCatid: '',
  69. menuList: [],
  70. curImportType: 'swagger',
  71. curExportType: null,
  72. showLoading: false,
  73. dataSync: 'merge',
  74. exportContent: 'all',
  75. isSwaggerUrl: false,
  76. swaggerUrl: '',
  77. isWiki: false
  78. };
  79. }
  80. static propTypes = {
  81. match: PropTypes.object,
  82. curCatid: PropTypes.number,
  83. basePath: PropTypes.string,
  84. saveImportData: PropTypes.func,
  85. fetchUpdateLogData: PropTypes.func,
  86. updateLogList: PropTypes.array,
  87. handleSwaggerUrlData: PropTypes.func,
  88. swaggerUrlData: PropTypes.string
  89. };
  90. componentWillMount() {
  91. axios.get(`/api/interface/getCatMenu?project_id=${this.props.match.params.id}`).then(data => {
  92. if (data.data.errcode === 0) {
  93. let menuList = data.data.data;
  94. this.setState({
  95. menuList: menuList,
  96. selectCatid: menuList[0]._id
  97. });
  98. }
  99. });
  100. plugin.emitHook('import_data', importDataModule);
  101. plugin.emitHook('export_data', exportDataModule, this.props.match.params.id);
  102. }
  103. selectChange(value) {
  104. this.setState({
  105. selectCatid: +value
  106. });
  107. }
  108. uploadChange = info => {
  109. const status = info.file.status;
  110. if (status !== 'uploading') {
  111. console.log(info.file, info.fileList);
  112. }
  113. if (status === 'done') {
  114. message.success(`${info.file.name} 文件上传成功`);
  115. } else if (status === 'error') {
  116. message.error(`${info.file.name} 文件上传失败`);
  117. }
  118. };
  119. handleAddInterface = async res => {
  120. return await HandleImportData(
  121. res,
  122. this.props.match.params.id,
  123. this.state.selectCatid,
  124. this.state.menuList,
  125. this.props.basePath,
  126. this.state.dataSync,
  127. message.error,
  128. message.success,
  129. () => this.setState({ showLoading: false })
  130. );
  131. };
  132. // 本地文件上传
  133. handleFile = info => {
  134. if (!this.state.curImportType) {
  135. return message.error('请选择导入数据的方式');
  136. }
  137. if (this.state.selectCatid) {
  138. this.setState({ showLoading: true });
  139. let reader = new FileReader();
  140. reader.readAsText(info.file);
  141. reader.onload = async res => {
  142. res = await importDataModule[this.state.curImportType].run(res.target.result);
  143. if (this.state.dataSync === 'merge') {
  144. // 开启同步
  145. this.showConfirm(res);
  146. } else {
  147. // 未开启同步
  148. await this.handleAddInterface(res);
  149. }
  150. };
  151. } else {
  152. message.error('请选择上传的默认分类');
  153. }
  154. };
  155. showConfirm = async res => {
  156. let that = this;
  157. let typeid = this.props.match.params.id;
  158. let apiCollections = res.apis.map(item => {
  159. return {
  160. method: item.method,
  161. path: item.path
  162. };
  163. });
  164. let result = await this.props.fetchUpdateLogData({
  165. type: 'project',
  166. typeid,
  167. apis: apiCollections
  168. });
  169. let domainData = result.payload.data.data;
  170. const ref = confirm({
  171. title: '您确认要进行数据同步????',
  172. width: 600,
  173. okType: 'danger',
  174. iconType: 'exclamation-circle',
  175. className: 'dataImport-confirm',
  176. okText: '确认',
  177. cancelText: '取消',
  178. content: (
  179. <div className="postman-dataImport-modal">
  180. <div className="postman-dataImport-modal-content">
  181. {domainData.map((item, index) => {
  182. return (
  183. <div key={index} className="postman-dataImport-show-diff">
  184. <span className="logcontent" dangerouslySetInnerHTML={{ __html: item.content }} />
  185. </div>
  186. );
  187. })}
  188. </div>
  189. <p className="info">温馨提示: 数据同步后,可能会造成原本的修改数据丢失</p>
  190. </div>
  191. ),
  192. async onOk() {
  193. await that.handleAddInterface(res);
  194. },
  195. onCancel() {
  196. that.setState({ showLoading: false, dataSync: 'normal' });
  197. ref.destroy();
  198. }
  199. });
  200. };
  201. handleImportType = val => {
  202. this.setState({
  203. curImportType: val,
  204. isSwaggerUrl: false
  205. });
  206. };
  207. handleExportType = val => {
  208. this.setState({
  209. curExportType: val,
  210. isWiki: false
  211. });
  212. };
  213. // 处理导入信息同步
  214. onChange = checked => {
  215. this.setState({
  216. dataSync: checked
  217. });
  218. };
  219. // 处理swagger URL 导入
  220. handleUrlChange = checked => {
  221. this.setState({
  222. isSwaggerUrl: checked
  223. });
  224. };
  225. // 记录输入的url
  226. swaggerUrlInput = url => {
  227. this.setState({
  228. swaggerUrl: url
  229. });
  230. };
  231. // url导入上传
  232. onUrlUpload = async () => {
  233. if (!this.state.curImportType) {
  234. return message.error('请选择导入数据的方式');
  235. }
  236. if (!this.state.swaggerUrl) {
  237. return message.error('url 不能为空');
  238. }
  239. if (this.state.selectCatid) {
  240. this.setState({ showLoading: true });
  241. try {
  242. // 处理swagger url 导入
  243. await this.props.handleSwaggerUrlData(this.state.swaggerUrl);
  244. // let result = json5_parse(this.props.swaggerUrlData)
  245. let res = await importDataModule[this.state.curImportType].run(this.props.swaggerUrlData);
  246. if (this.state.dataSync === 'merge') {
  247. // merge
  248. this.showConfirm(res);
  249. } else {
  250. // 未开启同步
  251. await this.handleAddInterface(res);
  252. }
  253. } catch (e) {
  254. this.setState({ showLoading: false });
  255. message.error(e.message);
  256. }
  257. } else {
  258. message.error('请选择上传的默认分类');
  259. }
  260. };
  261. // 处理导出接口是全部还是公开
  262. handleChange = e => {
  263. this.setState({ exportContent: e.target.value });
  264. };
  265. // 处理是否开启wiki导出
  266. handleWikiChange = e => {
  267. this.setState({
  268. isWiki: e.target.checked
  269. });
  270. };
  271. /**
  272. *
  273. *
  274. * @returns
  275. * @memberof ProjectData
  276. */
  277. render() {
  278. const uploadMess = {
  279. name: 'interfaceData',
  280. multiple: true,
  281. showUploadList: false,
  282. action: '/api/interface/interUpload',
  283. customRequest: this.handleFile,
  284. onChange: this.uploadChange
  285. };
  286. let exportUrl =
  287. this.state.curExportType &&
  288. exportDataModule[this.state.curExportType] &&
  289. exportDataModule[this.state.curExportType].route;
  290. let exportHref = handleExportRouteParams(
  291. exportUrl,
  292. this.state.exportContent,
  293. this.state.isWiki
  294. );
  295. // console.log('inter', this.state.exportContent);
  296. return (
  297. <div className="g-row">
  298. <div className="m-panel">
  299. <div className="postman-dataImport">
  300. <div className="dataImportCon">
  301. <div>
  302. <h3>
  303. 数据导入&nbsp;
  304. <a
  305. target="_blank"
  306. rel="noopener noreferrer"
  307. href="https://hellosean1025.github.io/yapi/documents/data.html"
  308. >
  309. <Tooltip title="点击查看文档">
  310. <Icon type="question-circle-o" />
  311. </Tooltip>
  312. </a>
  313. </h3>
  314. </div>
  315. <div className="dataImportTile">
  316. <Select
  317. placeholder="请选择导入数据的方式"
  318. value={this.state.curImportType}
  319. onChange={this.handleImportType}
  320. >
  321. {Object.keys(importDataModule).map(name => {
  322. return (
  323. <Option key={name} value={name}>
  324. {importDataModule[name].name}
  325. </Option>
  326. );
  327. })}
  328. </Select>
  329. </div>
  330. <div className="catidSelect">
  331. <Select
  332. value={this.state.selectCatid + ''}
  333. showSearch
  334. style={{ width: '100%' }}
  335. placeholder="请选择数据导入的默认分类"
  336. optionFilterProp="children"
  337. onChange={this.selectChange.bind(this)}
  338. filterOption={(input, option) =>
  339. option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
  340. }
  341. >
  342. {this.state.menuList.map((item, key) => {
  343. return (
  344. <Option key={key} value={item._id + ''}>
  345. {item.name}
  346. </Option>
  347. );
  348. })}
  349. </Select>
  350. </div>
  351. <div className="dataSync">
  352. <span className="label">
  353. 数据同步&nbsp;
  354. <Tooltip
  355. title={
  356. <div>
  357. <h3 style={{ color: 'white' }}>普通模式</h3>
  358. <p>不导入已存在的接口</p>
  359. <br />
  360. <h3 style={{ color: 'white' }}>智能合并</h3>
  361. <p>
  362. 已存在的接口,将合并返回数据的 response,适用于导入了 swagger
  363. 数据,保留对数据结构的改动
  364. </p>
  365. <br />
  366. <h3 style={{ color: 'white' }}>完全覆盖</h3>
  367. <p>不保留旧数据,完全使用新数据,适用于接口定义完全交给后端定义</p>
  368. </div>
  369. }
  370. >
  371. <Icon type="question-circle-o" />
  372. </Tooltip>{' '}
  373. </span>
  374. <Select value={this.state.dataSync} onChange={this.onChange}>
  375. <Option value="normal">普通模式</Option>
  376. <Option value="good">智能合并</Option>
  377. <Option value="merge">完全覆盖</Option>
  378. </Select>
  379. {/* <Switch checked={this.state.dataSync} onChange={this.onChange} /> */}
  380. </div>
  381. {this.state.curImportType === 'swagger' && (
  382. <div className="dataSync">
  383. <span className="label">
  384. 开启url导入&nbsp;
  385. <Tooltip title="swagger url 导入">
  386. <Icon type="question-circle-o" />
  387. </Tooltip>{' '}
  388. &nbsp;&nbsp;
  389. </span>
  390. <Switch checked={this.state.isSwaggerUrl} onChange={this.handleUrlChange} />
  391. </div>
  392. )}
  393. {this.state.isSwaggerUrl ? (
  394. <div className="import-content url-import-content">
  395. <Input
  396. placeholder="http://demo.swagger.io/v2/swagger.json"
  397. onChange={e => this.swaggerUrlInput(e.target.value)}
  398. />
  399. <Button
  400. type="primary"
  401. className="url-btn"
  402. onClick={this.onUrlUpload}
  403. loading={this.state.showLoading}
  404. >
  405. 上传
  406. </Button>
  407. </div>
  408. ) : (
  409. <div className="import-content">
  410. <Spin spinning={this.state.showLoading} tip="上传中...">
  411. <Dragger {...uploadMess}>
  412. <p className="ant-upload-drag-icon">
  413. <Icon type="inbox" />
  414. </p>
  415. <p className="ant-upload-text">点击或者拖拽文件到上传区域</p>
  416. <p
  417. className="ant-upload-hint"
  418. onClick={e => {
  419. e.stopPropagation();
  420. }}
  421. dangerouslySetInnerHTML={{
  422. __html: this.state.curImportType
  423. ? importDataModule[this.state.curImportType].desc
  424. : null
  425. }}
  426. />
  427. </Dragger>
  428. </Spin>
  429. </div>
  430. )}
  431. </div>
  432. <div
  433. className="dataImportCon"
  434. style={{
  435. marginLeft: '20px',
  436. display: Object.keys(exportDataModule).length > 0 ? '' : 'none'
  437. }}
  438. >
  439. <div>
  440. <h3>数据导出</h3>
  441. </div>
  442. <div className="dataImportTile">
  443. <Select placeholder="请选择导出数据的方式" onChange={this.handleExportType}>
  444. {Object.keys(exportDataModule).map(name => {
  445. return (
  446. <Option key={name} value={name}>
  447. {exportDataModule[name].name}
  448. </Option>
  449. );
  450. })}
  451. </Select>
  452. </div>
  453. <div className="dataExport">
  454. <RadioGroup defaultValue="all" onChange={this.handleChange}>
  455. <Radio value="all">全部接口</Radio>
  456. <Radio value="open">公开接口</Radio>
  457. </RadioGroup>
  458. </div>
  459. <div className="export-content">
  460. {this.state.curExportType ? (
  461. <div>
  462. <p className="export-desc">{exportDataModule[this.state.curExportType].desc}</p>
  463. <a
  464. target="_blank"
  465. rel="noopener noreferrer"
  466. href={exportHref}>
  467. <Button className="export-button" type="primary" size="large">
  468. {' '}
  469. 导出{' '}
  470. </Button>
  471. </a>
  472. <Checkbox
  473. checked={this.state.isWiki}
  474. onChange={this.handleWikiChange}
  475. className="wiki-btn"
  476. disabled={this.state.curExportType === 'json'}
  477. >
  478. 添加wiki&nbsp;
  479. <Tooltip title="开启后 html 和 markdown 数据导出会带上wiki数据">
  480. <Icon type="question-circle-o" />
  481. </Tooltip>{' '}
  482. </Checkbox>
  483. </div>
  484. ) : (
  485. <Button disabled className="export-button" type="primary" size="large">
  486. {' '}
  487. 导出{' '}
  488. </Button>
  489. )}
  490. </div>
  491. </div>
  492. </div>
  493. </div>
  494. </div>
  495. );
  496. }
  497. }
  498. export default ProjectData;