Index.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747
  1. import React, { useEffect, useState, useRef, useMemo } from 'react';
  2. import { UnorderedListOutlined, PlusOutlined, InfoOutlined } from '@ant-design/icons';
  3. import { Button, Modal, message, Alert, Avatar } from 'antd';
  4. import { connect } from 'dva';
  5. import styles from './Index.less';
  6. import LuckySheet from './LuckySheet';
  7. import AuditModal from './AuditModal';
  8. // import CommentDrawer from './CommentDrawer';
  9. import RightDrawer from './RightDrawer';
  10. import CompareModal from './CompareModal';
  11. import ExportModal from './ExportModal';
  12. import FlowModal from './FlowModal';
  13. import TimeNode from './TimeNode';
  14. import VersionModal from './VersionModal';
  15. import CommitAuditModal from './CommitAuditModal';
  16. import CommentContent from '@/components/CommentContent';
  17. import MergeModal from './MergeModal';
  18. import { GetTokenFromUrl, getToken } from '@/utils/utils';
  19. import {
  20. getAuditDetail,
  21. queryDelSheetRecord,
  22. queryDetail,
  23. queryDingInstanceExecute,
  24. setLastVersion,
  25. } from '@/services/boom';
  26. import HistoryDrawer from './HistoryDrawer';
  27. import AuditFlow from './AuditFlow';
  28. import { getCurrentUser } from '@/utils/authority';
  29. import { async } from '@antv/x6/es/registry/marker/async';
  30. import FileViewerModal from '@/components/FileViewer';
  31. import PreviewFile from '@/components/PreviewFile';
  32. import FormAndFilesNode from './FormAndFilesNode';
  33. import DropdownMenu from './DropdownMenu';
  34. import CurrentInfo from './CurrentInfo';
  35. import moment from 'moment';
  36. import PsrControl from './PsrControl';
  37. function Detail(props) {
  38. const {
  39. dispatch,
  40. loading,
  41. currentUser,
  42. versionList,
  43. auditList,
  44. flowDetail,
  45. versionTree,
  46. match: { params },
  47. location: { query },
  48. instanceDetail,
  49. typeOptions,
  50. classifyList,
  51. excelFileList,
  52. comment,
  53. } = props;
  54. const [versionTreeVisible, setVersionTreeVisible] = useState(false);
  55. const [commentVisible, setCommentVisible] = useState(false);
  56. const [mergeVisible, setMergeVisible] = useState(false);
  57. const [compareVisible, setCompareVisible] = useState(false);
  58. const [exportVisible, setExportVisible] = useState(false);
  59. const [commitVisible, setCommitVisible] = useState(false);
  60. // false 关闭 1=审批通过 2=审批拒绝
  61. const [auditVisible, setAuditVisible] = useState(false);
  62. const [flowVisible, setFlowVisible] = useState(false);
  63. const [versionVisible, setVersionVisible] = useState(false);
  64. const [commitAuditVisible, setCommitAuditVisible] = useState(false);
  65. const [sheet, setSheet] = useState({});
  66. const [compareList, setCompareList] = useState([]);
  67. const [isMerge, setIsMerge] = useState(false);
  68. const [version, setVersion] = useState({});
  69. const [user, setUser] = useState([]);
  70. const [updateCount, setUpdateCount] = useState({
  71. diff: 0,
  72. add: 0,
  73. });
  74. const [saveTime, setSaveTime] = useState();
  75. const [exportDate, setExportData] = useState([]);
  76. const sheetRef = useRef();
  77. const sheetRef2 = useRef();
  78. const sheetRef3 = useRef();
  79. const fileRef = useRef();
  80. const userRef = useRef();
  81. const statusRef = useRef({
  82. edit: false,
  83. compare: false,
  84. });
  85. const cellPosition = useRef({});
  86. useEffect(() => {
  87. // if (!version.id) return
  88. // 不请求excelFileList 时清空excelFileList,否则会出现清单切换后如果attachment_id不存在,附件信息没有更新
  89. if (!version.attachment_id) {
  90. dispatch({
  91. type: 'detail/save',
  92. payload: {
  93. excelFileList: [],
  94. },
  95. });
  96. } else {
  97. dispatch({
  98. type: 'detail/QueryExcelFiles',
  99. payload: {
  100. excel_id: version.attachment_id,
  101. },
  102. });
  103. }
  104. }, [version.id]);
  105. const projectId = parseInt(params.projectId);
  106. const templateId = parseInt(params.templateId);
  107. const excelID = parseInt(query.excel_id);
  108. const TOKEN = query['JWT-TOKEN'];
  109. if (!localStorage.getItem('JWT-TOKEN')) {
  110. localStorage['JWT-TOKEN'] = TOKEN;
  111. }
  112. const flow = useMemo(() => {
  113. let data = {
  114. active: 0,
  115. active_id: null,
  116. current: 0,
  117. currentNode: {},
  118. list: {
  119. FlowNodes: [],
  120. },
  121. };
  122. if (version?.flow_id && auditList?.length > 0) {
  123. let item = auditList.find(item => item.list.id == version.flow_id);
  124. if (!item) return data;
  125. // 查询当前节点
  126. let current = item.list.FlowNodes.findIndex(node => node.seq == item.active);
  127. item.current = current == -1 ? 0 : current;
  128. // 保存当前所处节点
  129. item.currentNode = item.list.FlowNodes[item.current];
  130. data = item;
  131. }
  132. return data;
  133. }, [auditList, version]);
  134. const active_audit = flow.active_audit;
  135. const isAuditor = useMemo(() => {
  136. return active_audit == 1 && flow.currentNode?.auditor == currentUser.ID;
  137. }, [active_audit, flow, currentUser]);
  138. const onCompare = async checkSheets => {
  139. if (checkSheets) {
  140. const [sheet1, sheet2] = checkSheets;
  141. sheet1.data = (
  142. await queryDetail({
  143. excel_id: sheet1.id,
  144. })
  145. ).data;
  146. sheet2.data = (
  147. await queryDetail({
  148. excel_id: sheet2.id,
  149. })
  150. ).data;
  151. setCompareList(checkSheets);
  152. statusRef.current.compare = true;
  153. } else {
  154. let index = compareList.findIndex(item => item.id == sheet.id);
  155. // 退出比对模式
  156. if (index == 0) {
  157. sheetRef3.current.toggleCompare(false);
  158. } else if (index == 1) {
  159. sheetRef2.current.toggleCompare(false);
  160. }
  161. setIsMerge(false);
  162. setCompareList([]);
  163. setSheet({
  164. ...sheet,
  165. });
  166. setUpdateCount({
  167. diff: 0,
  168. add: 0,
  169. });
  170. statusRef.current.compare = false;
  171. }
  172. setCommentVisible(false);
  173. };
  174. const renderSheetDom = (item, index) => {
  175. return (
  176. <div key={item?.id || 'temp'} className={styles.sheetItem}>
  177. <h3>{item.version_name || item?.name}</h3>
  178. <LuckySheet
  179. className={styles.sheet}
  180. ref={!index ? sheetRef3 : sheetRef2}
  181. data={item.data || null}
  182. />
  183. </div>
  184. );
  185. };
  186. const onClickCell = (cell, position, s) => {
  187. if (cell?.cid && !statusRef.current.edit) {
  188. let payload = {
  189. sheet_id: s.order || '0',
  190. excel_id: version.id,
  191. cid: cell.cid,
  192. sheet_index: String(s.seq || 0),
  193. };
  194. cellPosition.current = {
  195. ...payload,
  196. };
  197. dispatch({
  198. type: 'detail/queryComment',
  199. payload,
  200. });
  201. }
  202. };
  203. const onCommit = params => {
  204. let currentData = sheetRef.current.getSheetJson().data;
  205. let sheets = JSON.parse(JSON.stringify(currentData));
  206. sheets.forEach(item => {
  207. delete item.data;
  208. });
  209. dispatch({
  210. type: 'detail/commitSheet',
  211. payload: {
  212. ...params,
  213. data: JSON.stringify(sheets),
  214. },
  215. callback: newVersion => {
  216. onCompare(false);
  217. setCommitVisible(false);
  218. setVersionVisible(false);
  219. changeVersion(newVersion);
  220. // 更新flow流程图
  221. dispatch({
  222. type: 'xflow/queryBoomFlowDetail',
  223. payload: {
  224. id: templateId,
  225. },
  226. });
  227. },
  228. });
  229. };
  230. // 更新表单,flag为true时不判断是否属于审批,强制更新
  231. const onUpdate = flag => {
  232. if (!flag && flow.active != 0) return;
  233. let currentData = sheetRef.current.getSheetJson().data;
  234. let sheets = JSON.parse(JSON.stringify(currentData));
  235. sheets.forEach(item => {
  236. delete item.data;
  237. });
  238. let params = {
  239. ...version,
  240. data: JSON.stringify(sheets),
  241. };
  242. dispatch({
  243. type: 'detail/saveSheet',
  244. payload: params,
  245. callback: () => {
  246. setSaveTime(moment().format('HH:mm:ss'));
  247. },
  248. });
  249. };
  250. const onDelSheet = async id => {
  251. const params = {
  252. excel_id: version.id,
  253. sheet_id: id,
  254. };
  255. await queryDelSheetRecord(params);
  256. };
  257. const onAudit = (data) => {
  258. const flowNode = flow.currentNode;
  259. dispatch({
  260. type: 'detail/approve',
  261. payload: {
  262. id: flow.active_id,
  263. project_id: projectId,
  264. flow_id: flowNode.flow_id,
  265. node_id: flowNode.seq,
  266. ...data
  267. },
  268. callback: newVersion => {
  269. setAuditVisible(false);
  270. // 更新flow流程图
  271. dispatch({
  272. type: 'xflow/queryBoomFlowDetail',
  273. payload: {
  274. id: templateId,
  275. },
  276. });
  277. if (data.audit_status == 3) {
  278. // 更新审批流
  279. dispatch({
  280. type: 'detail/queryAuditList',
  281. payload: {
  282. template_id: version.template_id,
  283. template_node_id: version.template_node_id,
  284. flow_id: version.flow_id,
  285. version_id: version.version_id,
  286. audit_series: version.audit_series,
  287. },
  288. });
  289. } else {
  290. dispatch({
  291. type: 'authList/queryAuthList',
  292. payloda: { user_id: currentUser.ID },
  293. });
  294. localStorage.excelId = newVersion.id;
  295. setVersion({
  296. ...version,
  297. flow_id: newVersion.flow_id,
  298. id: newVersion.id,
  299. });
  300. }
  301. //更新未审批列表
  302. dispatch({
  303. type: 'authList/queryAuthList',
  304. payloda: { user_id: currentUser.ID },
  305. });
  306. },
  307. });
  308. };
  309. const onMergeVersion = async sheet2 => {
  310. const sheet1 = version;
  311. if (!sheet1.data) {
  312. sheet1.data = (
  313. await queryDetail({
  314. excel_id: sheet1.id,
  315. })
  316. ).data;
  317. }
  318. if (!sheet2.data) {
  319. sheet2.data = (
  320. await queryDetail({
  321. excel_id: sheet2.id,
  322. })
  323. ).data;
  324. }
  325. setIsMerge(true);
  326. setCompareList([sheet1, sheet2]);
  327. };
  328. const handleMenuClick = e => {
  329. switch (e.key) {
  330. case 'bomDetail':
  331. // 清单
  332. setCommentVisible(true);
  333. break;
  334. case 'export':
  335. // 导出
  336. handleExportClick();
  337. break;
  338. case 'commitAudit':
  339. // 提交流转
  340. setCommitAuditVisible(true);
  341. break;
  342. case 'compare':
  343. // 比对
  344. setCompareVisible(true);
  345. break;
  346. case 'merge':
  347. // 同步清单
  348. setMergeVisible(true);
  349. break;
  350. case 'compare':
  351. // 同步
  352. onCompare(e.data);
  353. break;
  354. }
  355. };
  356. const exportExcl = files => {
  357. sheetRef.current.uploadExcel(files, () => {
  358. fileRef.current.value = null;
  359. });
  360. };
  361. //点击导出弹出选择导出列弹框
  362. const handleExportClick = () => {
  363. setExportData(sheetRef.current.getSheetJson());
  364. setExportVisible(true);
  365. };
  366. const downloadExcel = checkValue => {
  367. sheetRef.current.downloadExcel(checkValue);
  368. setExportVisible(false);
  369. };
  370. const getLoading = () => {
  371. let effects = loading.effects;
  372. return !loading.effects['detail/queryComment'] && loading.models.detail;
  373. };
  374. const changeVersion = async id => {
  375. let version;
  376. if (typeof id === 'object') {
  377. version = id;
  378. localStorage.excelId = version.id;
  379. localStorage.excelItem = JSON.stringify(version);
  380. } else {
  381. version = await getAuditDetail({ userId: currentUser.ID, excelID });
  382. if (!version) {
  383. const excelId = localStorage.excelItem
  384. ? JSON.parse(localStorage.excelItem)
  385. : localStorage.excelId;
  386. if (typeof excelId === 'object') {
  387. changeVersion(excelId);
  388. return;
  389. }
  390. }
  391. localStorage.excelId = id;
  392. }
  393. setVersion(version);
  394. setSaveTime(null);
  395. //请求历史版本
  396. dispatch({
  397. type: 'detail/queryVersionsTree',
  398. payload: {
  399. excel_id: version.id || localStorage.excelId,
  400. },
  401. });
  402. // 判断是否审批节点
  403. if (version.flow_id) {
  404. dispatch({
  405. type: 'detail/queryAuditList',
  406. payload: {
  407. template_id: version.template_id,
  408. template_node_id: version.template_node_id,
  409. flow_id: version.flow_id,
  410. version_id: version.version_id,
  411. audit_series: version.audit_series,
  412. },
  413. });
  414. }
  415. };
  416. // console.log('0--------------', currentUser);
  417. //是否展示psr表上面的按钮
  418. const showPsrBtns = useMemo(() => {
  419. let Id = version.template_node_id;
  420. const node = flowDetail.nodes.find?.(item => item.Id == Id);
  421. if (node?.node_type_psr >= 1 && node?.node_type_psr <= 4) return true;
  422. return false;
  423. }, [flowDetail, version]);
  424. const getUser = newUser => {
  425. try {
  426. if (JSON.stringify(newUser) != JSON.stringify(userRef.current)) {
  427. userRef.current = newUser;
  428. setUser(newUser);
  429. }
  430. } catch (error) {}
  431. };
  432. const handleSubmitCell = (value, callback) => {
  433. if (!value) return;
  434. dispatch({
  435. type: 'detail/addComment',
  436. payload: {
  437. ...cellPosition.current,
  438. comment: value,
  439. },
  440. callback,
  441. });
  442. };
  443. useEffect(() => {
  444. dispatch({
  445. type: 'detail/queryProjectRecord',
  446. payload: {
  447. project_id: projectId,
  448. },
  449. });
  450. dispatch({
  451. type: 'xflow/queryBoomFlowDetail',
  452. payload: {
  453. id: templateId,
  454. },
  455. });
  456. dispatch({
  457. type: 'user/getRoleList',
  458. });
  459. dispatch({
  460. type: 'user/fetch',
  461. });
  462. dispatch({
  463. type: 'user/fetchDepV2',
  464. });
  465. dispatch({
  466. type: 'detail/queryClassify',
  467. });
  468. dispatch({
  469. type: 'detail/queryBindClassify',
  470. payload: {
  471. project_id: projectId,
  472. },
  473. });
  474. // dispatch({
  475. // type: 'detail/queryListParentByUser',
  476. // payload: {
  477. // userid: currentUser.DingUserId || getCurrentUser()?.DingUserId,
  478. // },
  479. // });
  480. }, []);
  481. useEffect(() => {
  482. if (compareList.length == 2) {
  483. const callback = ({ diff, add }) => {
  484. setUpdateCount(updateCount => {
  485. return {
  486. diff: diff.length,
  487. add: updateCount.add + add.length,
  488. };
  489. });
  490. };
  491. var update1 = sheetRef3.current.toggleCompare(true, compareList[1].data, callback);
  492. var update2 = sheetRef2.current.toggleCompare(true, compareList[0].data, callback);
  493. }
  494. }, [compareList]);
  495. useEffect(() => {
  496. // if (versionList.length == 0) return;
  497. if (!currentUser.ID) return;
  498. if (!version.id) {
  499. changeVersion(excelID);
  500. } else {
  501. changeVersion(version.id);
  502. }
  503. }, [versionList, currentUser]);
  504. return (
  505. <>
  506. <div className={styles.top}>
  507. <div>
  508. <Button type="primary" style={{ marginRight: 20 }} onClick={() => setFlowVisible(true)}>
  509. 查看流程
  510. </Button>
  511. {/* 非审批节点可以创建清单 */}
  512. {flow?.active == 0 && (
  513. <Button type="primary" onClick={() => setVersionVisible(true)}>
  514. 新建清单
  515. </Button>
  516. )}
  517. <CurrentInfo version={version} flowDetail={flowDetail} />
  518. </div>
  519. <div className={styles.btns}>
  520. {saveTime && <span style={{ color: '#333', fontSize: 14 }}>上次保存时间 {saveTime}</span>}
  521. {version.audit_status === 0 && (
  522. <Button type="primary" loading={loading.effects['detail/saveSheet']} onClick={onUpdate}>
  523. 保存
  524. </Button>
  525. )}
  526. <Button
  527. type="primary"
  528. style={{ marginRight: 20 }}
  529. onClick={() => setVersionTreeVisible(true)}
  530. >
  531. 历史版本
  532. </Button>
  533. <Avatar.Group style={{ marginRight: 20 }}>
  534. {user.map((item, id) => (
  535. <Avatar
  536. key={`${id}-${item.name}`}
  537. style={{ backgroundColor: '#008dff' }}
  538. size="large"
  539. >
  540. {item.Name}
  541. </Avatar>
  542. ))}
  543. </Avatar.Group>
  544. <DropdownMenu
  545. compareList={compareList}
  546. isMerge={isMerge}
  547. version={version}
  548. flowDetail={flowDetail}
  549. classifyList={classifyList}
  550. currentUser={currentUser}
  551. isAuditor={isAuditor}
  552. flow={flow}
  553. sheetRef3={sheetRef3}
  554. onClick={handleMenuClick}
  555. setVersion={setVersion}
  556. />
  557. </div>
  558. <input
  559. type="file"
  560. ref={fileRef}
  561. style={{ display: 'none' }}
  562. onChange={e => exportExcl(e.target.files)}
  563. />
  564. </div>
  565. {showPsrBtns && <PsrControl sheetRef={sheetRef} />}
  566. <div style={{ display: 'flex' }}>
  567. <div
  568. className={styles.content}
  569. style={{
  570. width: '100%',
  571. // 合同清单先显示附件再显示清单详情
  572. flexDirection: version?.TemplateNodeInfo?.flow_id == 9 ? 'column-reverse' : 'column',
  573. }}
  574. >
  575. {/* 判断是否为比对模式 */}
  576. {compareList.length == 2 ? (
  577. <>
  578. <Alert
  579. message={`比对结果:${updateCount.diff}项差异。${updateCount.add}项新增`}
  580. type="info"
  581. />
  582. <div className={styles.sheetBox}>{compareList.map(renderSheetDom)}</div>
  583. </>
  584. ) : (
  585. <div className={styles.sheetBox}>
  586. {version.id && (
  587. <LuckySheet
  588. className={styles.sheet}
  589. ref={sheetRef}
  590. onClickCell={onClickCell}
  591. permissions={currentUser.Permission}
  592. version={version}
  593. templateId={templateId}
  594. getUser={getUser}
  595. onUpdate={onUpdate}
  596. onDelSheet={onDelSheet}
  597. />
  598. )}
  599. </div>
  600. )}
  601. <FormAndFilesNode
  602. formData={version?.formStr || version?.ding_schema}
  603. excelFileList={excelFileList}
  604. version={version}
  605. />
  606. </div>
  607. <TimeNode
  608. flow={flow}
  609. flowDetail={flowDetail}
  610. isAuditor={isAuditor}
  611. version={version}
  612. templateId={templateId}
  613. projectId={projectId}
  614. setAuditVisible={setAuditVisible}
  615. stepDirection="vertical"
  616. style={{
  617. maxWidth: '20%',
  618. display: 'inline',
  619. marginLeft: '20px',
  620. }}
  621. />
  622. </div>
  623. <CommentContent
  624. title="单元格沟通记录"
  625. comment={comment}
  626. onSubmit={handleSubmitCell}
  627. loading={loading.effects['detail/queryComment'] || loading.effects['detail/addComment']}
  628. />
  629. <HistoryDrawer
  630. versionTree={versionTree}
  631. version={version}
  632. visible={versionTreeVisible}
  633. onChangeVersion={version => changeVersion(version)}
  634. onClose={() => setVersionTreeVisible(false)}
  635. />
  636. <RightDrawer
  637. version={version}
  638. visible={commentVisible}
  639. onClose={() => setCommentVisible(false)}
  640. />
  641. <CompareModal
  642. visible={compareVisible}
  643. version={version}
  644. onClose={() => setCompareVisible(false)}
  645. onOk={onCompare}
  646. />
  647. <MergeModal
  648. visible={mergeVisible}
  649. version={version}
  650. onClose={() => setMergeVisible(false)}
  651. onOk={onMergeVersion}
  652. />
  653. <ExportModal
  654. visible={exportVisible}
  655. sheet={exportDate.data}
  656. onClose={() => setExportVisible(false)}
  657. onOk={downloadExcel}
  658. />
  659. <FlowModal
  660. typeOptions={typeOptions}
  661. flowDetail={flowDetail}
  662. visible={flowVisible}
  663. templateId={templateId}
  664. onClose={() => setFlowVisible(false)}
  665. version={version}
  666. onChangeVersion={version => changeVersion(version)}
  667. />
  668. <AuditModal
  669. loading={getLoading()}
  670. visible={auditVisible}
  671. sheetRef={sheetRef}
  672. onClose={() => setAuditVisible(false)}
  673. onOk={onAudit}
  674. flow={flow}
  675. flowDetail={flowDetail}
  676. version={version}
  677. versionList={versionList}
  678. />
  679. <VersionModal
  680. typeOptions={typeOptions}
  681. visible={versionVisible}
  682. flowDetail={flowDetail}
  683. versionList={versionList}
  684. version={version}
  685. onClose={() => setVersionVisible(false)}
  686. onOk={values => onCommit(values)}
  687. loading={getLoading()}
  688. />
  689. <CommitAuditModal
  690. visible={commitAuditVisible}
  691. version={version}
  692. onClose={() => setCommitAuditVisible(false)}
  693. luckysheet={sheetRef}
  694. templateId={templateId}
  695. />
  696. </>
  697. );
  698. }
  699. export default connect(({ detail, user, xflow, loading }) => ({
  700. flowDetail: xflow.flowDetail,
  701. auditList: detail.auditList,
  702. instanceDetail: detail.dingInstanceDetail,
  703. currentUser: user.currentUser,
  704. versionList: detail.versionList,
  705. versionTree: detail.versionTree,
  706. typeOptions: detail.typeOptions,
  707. classifyList: detail.classifyList,
  708. excelFileList: detail.excelFileList,
  709. comment: detail.comment,
  710. loading,
  711. }))(Detail);