Index.js 20 KB

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