Index.js 19 KB

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