index.jsx 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694
  1. import React, { useEffect, useRef, useState } from 'react';
  2. import { Form, Row, Col, Card, Modal, Input, Select, Upload, Button, message, Tag, Badge } from 'antd';
  3. import { connect } from 'umi';
  4. import styles from './index.less';
  5. import {
  6. ForkOutlined,
  7. PlusOutlined,
  8. ExclamationCircleFilled,
  9. UploadOutlined,
  10. EditOutlined,
  11. EyeOutlined,
  12. } from '@ant-design/icons';
  13. import * as echarts from 'echarts';
  14. import { recurrent, severity, bug_categories } from './const';
  15. import { timeToString } from '../../utils';
  16. import BugGuideTree from '../BugGuideTree';
  17. const formItemLayout = {
  18. labelCol: {
  19. span: 8,
  20. },
  21. wrapperCol: {
  22. span: 19,
  23. },
  24. };
  25. const modalFormItemLayout = {
  26. labelCol: {
  27. span: 5,
  28. },
  29. wrapperCol: {
  30. span: 19,
  31. },
  32. };
  33. const Step2 = (props) => {
  34. const [reportForm] = Form.useForm();
  35. const [addCaseForm] = Form.useForm();
  36. const [addBugForm] = Form.useForm();
  37. const [editReportForm] = Form.useForm();
  38. const {
  39. dispatch, reportCommonInfo, osType,
  40. testCaseList, caseBugList, categories, pathInfo,
  41. } = props;
  42. const [showTaskRecommendModal, setTaskRecommendModal] = useState(false);
  43. const [showAddTestCaseModal, setAddTestCaseModal] = useState(false);
  44. const [showAddBugModal, setAddTestBugModal] = useState(false);
  45. const [showEditReportModal, setEditReportModal] = useState(false);
  46. const currentTestCaseRef = useRef({});
  47. const [isAddCaseStatus, setIsAddCaseStatus] = useState(true);
  48. // const [currActiveTestCase, setCurrActiveTestCase] = useState({});
  49. const [page2List, setPage2List] = useState([]);
  50. const [page3List, setPage3List] = useState([]);
  51. const handleEditReportInfo = () => {
  52. editReportForm.validateFields().then((res)=> {
  53. let formData = new FormData();
  54. formData.append('name', res.name);
  55. formData.append('report_id', reportCommonInfo.id);
  56. formData.append('worker_id', '2');
  57. formData.append('case_take_id', '1718-1718');
  58. formData.append('device_model', res.device_model);
  59. formData.append('device_brand', res.device_brand);
  60. formData.append('device_os', res.device_os);
  61. dispatch({
  62. type: 'editReport/updateReportCommonDetail',
  63. payload: { formData }
  64. })
  65. })
  66. setEditReportModal(false)
  67. };
  68. const handleAddOrEditTestCase = () => {
  69. addCaseForm.validateFields().then((res) => {
  70. let formData = new FormData();
  71. // formData.append("id", values.reportName);
  72. formData.append('report_id', reportCommonInfo.id);
  73. formData.append('name', res.name);
  74. formData.append('front', res.front);
  75. formData.append('behind', res.behind);
  76. formData.append('description', res.description);
  77. if (!isAddCaseStatus) {
  78. //处理编辑用例
  79. formData.append('id', currentTestCaseRef.current.id);
  80. dispatch({
  81. type: 'editReport/updateTestCase',
  82. payload: {
  83. formData,
  84. report_id: reportCommonInfo.id,
  85. },
  86. }).then((res) => {
  87. if (res && res.status === 200) {
  88. message.success('修改成功!');
  89. }
  90. });
  91. } else {
  92. //处理添加用例
  93. dispatch({
  94. type: 'editReport/createTestCase',
  95. payload: {
  96. formData,
  97. report_id: reportCommonInfo.id,
  98. },
  99. }).then((res) => {
  100. if (res && res.id) {
  101. message.success('添加成功!');
  102. }
  103. });
  104. }
  105. setAddTestCaseModal(false);
  106. });
  107. };
  108. const handleAddBug = () => {
  109. addBugForm.validateFields().then((res) => {
  110. let formData = new FormData();
  111. formData.append('report_id', reportCommonInfo.id);
  112. formData.append('title', res.title);
  113. formData.append('description', res.description);
  114. formData.append('bug_category', res.bug_category);
  115. formData.append('severity', res.severity);
  116. formData.append('recurrent', res.recurrent);
  117. formData.append('parent', null);
  118. formData.append('useCase', currentTestCaseRef.current.id);
  119. formData.append('case_id', '1718');
  120. formData.append('case_take_id', '1718-1718');
  121. formData.append('worker_id', '2');
  122. formData.append('page', `${res.page1}-${res.page2}-${res.page3}`);
  123. //新建bug
  124. dispatch({
  125. type: 'editReport/createCaseBug',
  126. payload: {
  127. formData,
  128. useCase: currentTestCaseRef.current.id,
  129. },
  130. }).then(res => {
  131. setAddTestBugModal(false);
  132. });
  133. });
  134. };
  135. const handleClickTestCase = (caseItem) => {
  136. // setCurrActiveTestCase(caseItem);
  137. currentTestCaseRef.current = caseItem;
  138. dispatch({
  139. type: 'editReport/getCaseBugList',
  140. payload: caseItem.id,
  141. });
  142. };
  143. const handleEditTestCase = (item) => {
  144. setIsAddCaseStatus(false);
  145. currentTestCaseRef.current = item;
  146. addCaseForm.setFieldsValue(currentTestCaseRef.current);
  147. setAddTestCaseModal(true);
  148. };
  149. const handleClickAddCase = () => {
  150. addCaseForm.resetFields();
  151. setAddTestCaseModal(true);
  152. };
  153. const handleClickAddBug = () => {
  154. //current目前只在点击edit cease的时候会有用
  155. addBugForm.resetFields();
  156. setAddTestBugModal(true);
  157. };
  158. const handleClickRecommendBtn = () => {
  159. dispatch({
  160. type: 'editReport/getPathInfo',
  161. payload: {
  162. case_take_id: '1718-1718',
  163. report_id: reportCommonInfo.id,
  164. },
  165. });
  166. dispatch({
  167. type: 'editReport/getBugRecommendPath',
  168. payload: {
  169. case_take_id: '1718-1718',
  170. report_id: reportCommonInfo.id,
  171. },
  172. });
  173. dispatch({
  174. type: 'editReport/getBugRecommendList',
  175. payload: {
  176. case_take_id: '1718-1718',
  177. report_id: reportCommonInfo.id,
  178. },
  179. });
  180. setTaskRecommendModal(true);
  181. };
  182. const handleSelectPage1 = (val) => {
  183. let item = categories.find(x => x.item === val);
  184. setPage2List(item.children);
  185. };
  186. const handleSelectPage2 = (val) => {
  187. let item = page2List.find(x => x.item === val);
  188. setPage3List(item.children);
  189. };
  190. useEffect(() => {
  191. //判断是否已经有报告
  192. dispatch({
  193. type: 'editReport/getReportInfo',
  194. payload: {
  195. case_take_id: '1718-1718',
  196. worker_id: '2',
  197. },
  198. }).then((res) => {
  199. // console.log(res)
  200. // console.log(reportCommonInfo)
  201. //有报告,获取对应信息。没有就直接转去了第一步
  202. dispatch({
  203. type: 'editReport/getTestCaseList',
  204. payload: { report_id: reportCommonInfo.id },
  205. }).then((res) => {
  206. if (res && res.length) {
  207. currentTestCaseRef.current = res[0];
  208. dispatch({
  209. type: 'editReport/getCaseBugList',
  210. payload: currentTestCaseRef.current.id,
  211. });
  212. }
  213. });
  214. dispatch({
  215. type: 'editReport/getCategories',
  216. payload: { examId: 1718 },
  217. });
  218. });
  219. }, [dispatch, reportCommonInfo.id]);
  220. return (
  221. <div id="main">
  222. <Row gutter={10}>
  223. <Col span={23}>
  224. <Form
  225. {...formItemLayout}
  226. form={reportForm}
  227. className={styles.stepForm}
  228. hideRequiredMark
  229. >
  230. <Row gutter={10}>
  231. <Col span={5}>
  232. <Form.Item label="创建日期" rules={[{ required: true, message: '请输入报告名称' }]}>
  233. {timeToString(reportCommonInfo.create_time_millis)}
  234. </Form.Item>
  235. </Col>
  236. <Col span={5}>
  237. <Form.Item label="报告名称" rules={[{ required: true, message: '请输入报告名称' }]}>
  238. {reportCommonInfo.name}
  239. </Form.Item>
  240. </Col>
  241. <Col span={5}>
  242. <Form.Item label="设备名称" required>
  243. {reportCommonInfo.device_brand}
  244. </Form.Item>
  245. </Col>
  246. <Col span={5}>
  247. <Form.Item label="设备品牌" required>
  248. {reportCommonInfo.device_model}
  249. </Form.Item>
  250. </Col>
  251. <Col span={4}>
  252. <Form.Item label="操作系统" required>
  253. {reportCommonInfo.device_os}
  254. </Form.Item>
  255. </Col>
  256. </Row>
  257. </Form>
  258. </Col>
  259. <Col span={1}>
  260. <EditOutlined className={styles.editReportInfoIcon}
  261. onClick={() => setEditReportModal(true)}
  262. />
  263. </Col>
  264. </Row>
  265. <Card>
  266. <div className={styles.reportContainer}>
  267. <Row gutter={10}>
  268. <Col span={8}>
  269. <div gutter={10} className={styles.testCaseTitle}>
  270. <h3>测试用例列表</h3>
  271. <Button size="small" type="primary"
  272. onClick={() => {
  273. handleClickAddCase();
  274. }}>
  275. <PlusOutlined className={styles.addIcon} />
  276. 用例
  277. </Button>
  278. </div>
  279. <div className={styles.testCaseList}>
  280. {testCaseList && testCaseList.length ? testCaseList.map((item) => {
  281. return (
  282. <div
  283. className={`${styles.testCaseItem} ${currentTestCaseRef.current.id === item.id ? styles.activeCase : ''}`}
  284. key={item.id}
  285. onClick={() => {
  286. handleClickTestCase(item);
  287. }}>
  288. <Row gutter={10}>
  289. <Col span={6}>
  290. {<span
  291. className={styles.testCaseItemNo}>{`NO.${((item.id).substring((item.id.length) - 6)).toUpperCase()}`}</span>}
  292. {/*<Badge color="#2db7f5"*/}
  293. {/* text={} />*/}
  294. </Col>
  295. <Col span={15}>
  296. <span>{item.name}</span>
  297. </Col>
  298. <Col span={3}>
  299. <EditOutlined className={styles.editTestBug}
  300. onClick={() => handleEditTestCase(item)} />
  301. </Col>
  302. </Row>
  303. </div>);
  304. }) : <div>快来创建你的第一个测试用例吧~</div>}
  305. </div>
  306. </Col>
  307. <Col span={16}>
  308. <div gutter={10} className={styles.testBugTitle}>
  309. <h3>缺陷列表</h3>
  310. <div>
  311. <Button size="small" type="primary"
  312. disabled={JSON.stringify(currentTestCaseRef.current) === '{}'}
  313. onClick={() => handleClickAddBug()}>
  314. <PlusOutlined className={styles.addIcon} />
  315. 缺陷
  316. </Button>
  317. <Button size="small"
  318. className={styles.recommendBtn}
  319. disabled={JSON.stringify(currentTestCaseRef.current) === '{}'}
  320. onClick={() => handleClickRecommendBtn()}>
  321. <ForkOutlined className={styles.addIcon} />
  322. 推荐
  323. </Button>
  324. </div>
  325. </div>
  326. <div className={styles.testBugList}>
  327. {caseBugList && caseBugList.length ? caseBugList.map((item) => {
  328. return (
  329. <div className={styles.testBugItem} key={item.detail.id}>
  330. <Row gutter={10}>
  331. <Col span={12}>
  332. <div>
  333. <div className={styles.testBugItemTitleBlock}>
  334. <span className={styles.testBugItemTitle}>标题:</span>
  335. <span className={styles.testBugItemTitleDetail}>{item.detail.title}</span>
  336. <Tag color="cyan">{recurrent[item.detail.recurrent]}</Tag>
  337. <Tag color="red">{severity[item.detail.severity]}</Tag>
  338. <Tag color="geekblue">{item.detail.bug_category}</Tag>
  339. </div>
  340. <div className={styles.testBugItemTitleBlock}>
  341. <span className={styles.testBugItemTitle}>路径:</span>
  342. {item.detail.bug_page}
  343. </div>
  344. <div className={styles.testBugItemTitleBlock}>
  345. <span className={styles.testBugItemTitle}>描述:</span>
  346. {item.detail.description}
  347. </div>
  348. </div>
  349. </Col>
  350. <Col span={12} className={styles.bugImgList}>
  351. <img src={require('../../assets/report1.jpeg')} alt='bug_img' />
  352. <img src={require('../../assets/report2.jpeg')} alt='bug_img' />
  353. <img src={require('../../assets/report3.jpeg')} alt='bug_img' />
  354. </Col>
  355. </Row>
  356. </div>
  357. );
  358. }) : <div>当前用例暂无提交记录,快去创建第一个BUG吧~</div>}
  359. </div>
  360. </Col>
  361. </Row>
  362. </div>
  363. </Card>
  364. <Modal title={isAddCaseStatus ? '添加测试用例' : '编辑测试用例'} width={720}
  365. visible={showAddTestCaseModal}
  366. forceRender={true}
  367. className="addModal"
  368. footer={[
  369. <Button key='submit' type="primary" htmlType="submit" onClick={handleAddOrEditTestCase}>确定</Button>,
  370. <Button key='cancel' htmlType="button" style={{ marginLeft: '10px' }}
  371. onClick={() => {
  372. setAddTestCaseModal(false);
  373. }}>取消</Button>]}
  374. onCancel={() => {
  375. setAddTestCaseModal(false);
  376. }}
  377. >
  378. <ExclamationCircleFilled className={styles.addModalInfo} />为了评分准确,请勿提交重复测试用例
  379. <Form
  380. {...modalFormItemLayout}
  381. form={addCaseForm}
  382. layout="horizontal"
  383. className={styles.stepForm}
  384. >
  385. <Form.Item
  386. label="用例名称"
  387. name="name"
  388. rules={[
  389. {
  390. required: true,
  391. message: '请输入用例名称!',
  392. },
  393. ]}
  394. >
  395. <Input />
  396. </Form.Item>
  397. <Form.Item
  398. label="前置条件"
  399. name="front"
  400. rules={[
  401. {
  402. required: true,
  403. message: '请输入前置条件!',
  404. },
  405. ]}
  406. >
  407. <Input.TextArea autoSize={{ minRows: 3, maxRows: 999 }} />
  408. </Form.Item>
  409. <Form.Item
  410. label="测试步骤"
  411. name="behind"
  412. rules={[
  413. {
  414. required: true,
  415. message: '请输入测试步骤!',
  416. },
  417. ]}
  418. >
  419. <Input.TextArea autoSize={{ minRows: 3, maxRows: 999 }} />
  420. </Form.Item>
  421. <Form.Item
  422. label="预期结果"
  423. name="description"
  424. rules={[
  425. {
  426. required: true,
  427. message: '请输入预期结果!',
  428. },
  429. ]}
  430. >
  431. <Input.TextArea autoSize={{ minRows: 3, maxRows: 999 }} />
  432. </Form.Item>
  433. </Form>
  434. </Modal>
  435. <Modal title="添加用例缺陷" visible={showAddBugModal} width={720}
  436. forceRender={true}
  437. footer={[
  438. <Button key='submit' type="primary" htmlType="submit" onClick={handleAddBug}>确定</Button>,
  439. <Button key='cancel' htmlType="button" style={{ marginLeft: '10px' }}
  440. onClick={() => {
  441. setAddTestBugModal(false);
  442. }}>取消</Button>]}
  443. onCancel={() => {
  444. setAddTestBugModal(false);
  445. }}
  446. className={styles.bugForm}
  447. >
  448. <ExclamationCircleFilled className={styles.addModalInfo} />为了评分准确,请勿提交重复Bug
  449. <Form
  450. form={addBugForm}
  451. {...modalFormItemLayout}
  452. layout="horizontal"
  453. className={styles.stepForm}
  454. >
  455. <Form.Item
  456. label="测试标题"
  457. name="title"
  458. rules={[
  459. {
  460. required: true,
  461. message: '请输入测试标题!',
  462. },
  463. ]}
  464. >
  465. <Input.TextArea autoSize={{ minRows: 1, maxRows: 999 }} />
  466. </Form.Item>
  467. <Form.Item
  468. label="题目描述"
  469. name="description"
  470. rules={[
  471. {
  472. required: true,
  473. message: '请输入题目描述!',
  474. },
  475. ]}
  476. >
  477. <Input.TextArea autoSize={{ minRows: 2, maxRows: 999 }} />
  478. </Form.Item>
  479. <Form.Item
  480. label="三级页面"
  481. name="pages"
  482. >
  483. <Row gutter={5} className={styles.pageSelect}>
  484. <Col span={8}>
  485. <Form.Item
  486. name="page1"
  487. rules={[
  488. {
  489. required: true,
  490. message: '请输入一级页面',
  491. },
  492. ]}
  493. >
  494. <Select onSelect={(val) => {
  495. handleSelectPage1(val);
  496. }}>
  497. {categories.map((item) => {
  498. return <Select.Option value={item.item} key={item.item}>{item.item}
  499. </Select.Option>;
  500. })}
  501. </Select>
  502. </Form.Item>
  503. </Col>
  504. <Col span={8}>
  505. <Form.Item
  506. name="page2"
  507. rules={[
  508. {
  509. required: true,
  510. message: '请输入二级页面',
  511. },
  512. ]}
  513. >
  514. <Select disabled={!page2List.length}
  515. onSelect={(val) => {
  516. handleSelectPage2(val);
  517. }}>
  518. {page2List.map((item) => {
  519. return <Select.Option value={item.item} key={item.item}>{item.item}</Select.Option>;
  520. })}
  521. </Select>
  522. </Form.Item>
  523. </Col>
  524. <Col span={8}>
  525. <Form.Item
  526. name="page3"
  527. rules={[
  528. {
  529. required: true,
  530. message: '请输入三级页面',
  531. },
  532. ]}
  533. >
  534. <Select disabled={!page3List.length}>
  535. {page3List.map((item) => {
  536. return <Select.Option value={item.item} key={item.item}>{item.item}</Select.Option>;
  537. })}
  538. </Select>
  539. </Form.Item>
  540. </Col>
  541. </Row>
  542. </Form.Item>
  543. <Form.Item
  544. label="漏洞分类"
  545. name="bug_category"
  546. rules={[
  547. {
  548. required: true,
  549. message: '请选择漏洞分类!',
  550. },
  551. ]}
  552. >
  553. <Select>
  554. {bug_categories.map((item) => {
  555. return <Select.Option value={item} key={item}>{item}</Select.Option>;
  556. })}
  557. </Select>
  558. </Form.Item>
  559. <Form.Item
  560. label="严重等级"
  561. name="severity"
  562. rules={[
  563. {
  564. required: true,
  565. message: '请选择严重等级!',
  566. },
  567. ]}
  568. >
  569. <Select>
  570. {severity.map((item) => {
  571. return <Select.Option value={item} key={item}>{item}</Select.Option>;
  572. })}
  573. </Select>
  574. </Form.Item>
  575. <Form.Item
  576. label="复现程度"
  577. name="recurrent"
  578. rules={[
  579. {
  580. required: true,
  581. message: '请选择复现程度!',
  582. },
  583. ]}
  584. >
  585. <Select>
  586. {recurrent.map((item) => {
  587. return <Select.Option value={item} key={item}>{item}</Select.Option>;
  588. })}
  589. </Select>
  590. </Form.Item>
  591. {/*<Form.Item*/}
  592. {/* label="上传截图"*/}
  593. {/* name="testName"*/}
  594. {/*>*/}
  595. {/* <Upload*/}
  596. {/* action="https://www.mocky.io/v2/5cc8019d300000980a055e76"*/}
  597. {/* listType="picture"*/}
  598. {/* >*/}
  599. {/* <Button icon={<UploadOutlined />}>Upload</Button>*/}
  600. {/* </Upload>*/}
  601. {/*</Form.Item>*/}
  602. </Form>
  603. </Modal>
  604. <Modal title="修改报告基础信息" visible={showEditReportModal}
  605. footer={[
  606. <Button key='submit' type="primary" htmlType="submit"
  607. onClick={handleEditReportInfo}>确定</Button>,
  608. <Button key='cancel' htmlType="button" style={{ marginLeft: '10px' }}
  609. onClick={() => {
  610. setEditReportModal(false);
  611. }}>取消</Button>]}
  612. onCancel={() => {
  613. setEditReportModal(false);
  614. }}>
  615. <div>
  616. <Form
  617. {...modalFormItemLayout}
  618. form={editReportForm}
  619. layout="horizontal"
  620. initialValues={reportCommonInfo}
  621. >
  622. <Form.Item label="报告名称" name="name" rules={[{ required: true, message: '请输入报告名称' }]}>
  623. <Input placeholder="请输入报告名称" />
  624. </Form.Item>
  625. <Form.Item label="设备名称" name="device_model" rules={[{ required: true, message: '请输入设备品牌' }]}>
  626. <Input placeholder="请输入设备名称" />
  627. </Form.Item>
  628. <Form.Item label="设备品牌" name="device_brand" rules={[{ required: true, message: '请输入设备品牌' }]}>
  629. <Input placeholder="请输入设备品牌" />
  630. </Form.Item>
  631. <Form.Item label="操作系统" name="device_os" rules={[{ required: true, message: '请输入操作系统' }]}>
  632. <Select placeholder="请选择操作系统">
  633. {osType.map((option) => {
  634. return <Select.Option value={option} key={option}>{option}</Select.Option>;
  635. })}
  636. </Select>
  637. </Form.Item>
  638. </Form>
  639. </div>
  640. </Modal>
  641. <Modal title="任务推荐" visible={showTaskRecommendModal}
  642. destroyOnClose width={1000}
  643. onOk={() => setTaskRecommendModal(false)}
  644. onCancel={() => {
  645. setTaskRecommendModal(false);
  646. }
  647. }
  648. className="addModal">
  649. <div>
  650. <BugGuideTree />
  651. </div>
  652. </Modal>
  653. </div>
  654. );
  655. };
  656. export default connect(({ editReport, loading }) => ({
  657. submitting: loading.effects['editReport/submitStepForm'],
  658. reportCommonInfo: editReport.reportCommonInfo,
  659. testCaseList: editReport.testCaseList,
  660. caseBugList: editReport.caseBugList,
  661. categories: editReport.categories,
  662. pathInfo: editReport.pathInfo,
  663. osType: editReport.osType,
  664. }))(Step2);