FlowModal.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. import React, { useEffect, useState, useRef, useMemo, memo } from 'react';
  2. import {
  3. Modal,
  4. Input,
  5. Select,
  6. List,
  7. Row,
  8. Col,
  9. Table,
  10. message,
  11. Steps,
  12. Space,
  13. Button,
  14. Popover,
  15. Cascader,
  16. AutoComplete,
  17. Spin,
  18. } from 'antd';
  19. import Flow from '@/components/Flow/index';
  20. import { connect } from 'dva';
  21. import { GetTokenFromUrl, getToken } from '@/utils/utils';
  22. import { MODELS, useXFlowApp, useModelAsync } from '@antv/xflow';
  23. import { CheckOutlined } from '@ant-design/icons';
  24. import {
  25. queryDelPurchaseExcel,
  26. queryDingInstanceDetail,
  27. queryRecordSheet,
  28. queryTrySeal,
  29. queryVserionByNode,
  30. } from '@/services/boom';
  31. import { async } from '@antv/x6/lib/registry/marker/async';
  32. import VersionModal from './VersionModal';
  33. const { Option } = Select;
  34. const { Step } = Steps;
  35. const { TextArea } = Input;
  36. const localData = JSON.parse(localStorage.ggDetaiData || '{}');
  37. const PAGE_SIZE = 8;
  38. let controller = new AbortController();
  39. // 提交
  40. function FlowModal(props) {
  41. let token = getToken();
  42. const SELECT_TYPE = {
  43. NAME: '0',
  44. TYPE: '1',
  45. CREATOR: '2',
  46. };
  47. const {
  48. visible,
  49. version,
  50. onClose,
  51. onChangeVersion,
  52. form,
  53. loading,
  54. flowDetail,
  55. dispatch,
  56. isOut,
  57. onCommit,
  58. commitLoading,
  59. currentUser,
  60. typeOptions,
  61. userList,
  62. versionList,
  63. templateId,
  64. } = props;
  65. const [data, setData] = useState([]);
  66. const [showData, setShowData] = useState([]);
  67. const [nodeLoading, setNodeLoading] = useState(false);
  68. const [pageSize, setPageSize] = useState(PAGE_SIZE);
  69. const [stepsData, setStepsData] = useState([]);
  70. const [versionVisible, setVersionVisible] = useState(false);
  71. const [selectType, setSelectType] = useState(SELECT_TYPE.NAME);
  72. const [inputValue, setInputValue] = useState('');
  73. const [sealLoading, setSealLoading] = useState(false);
  74. // const [currentTempNodeId, setCurrentTempNodeId] = useState();
  75. const currentTempNodeId = useRef();
  76. const graphData = useMemo(() => {
  77. if (!flowDetail) return;
  78. let nodes = flowDetail.nodes?.map(item => ({
  79. ...item,
  80. isCheck: item.Id == version.template_node_id,
  81. }));
  82. return {
  83. nodes,
  84. edges: flowDetail.edges,
  85. };
  86. }, [flowDetail, version.template_node_id]);
  87. useEffect(() => {
  88. if (!visible) updateSteps([]);
  89. }, [visible, version]);
  90. useEffect(() => {
  91. if (stepsData.length <= 0) {
  92. setPageSize(PAGE_SIZE);
  93. } else {
  94. setPageSize(PAGE_SIZE - stepsData.length);
  95. }
  96. }, [stepsData]);
  97. const handleSelectNode = args => {
  98. const id = args.nodeId || args.nodeIds[0];
  99. if (!id) return;
  100. let node = graphData.nodes.find(item => item.id == id);
  101. initData(node.Id);
  102. };
  103. const onDelVersion = data => {
  104. Modal.confirm({
  105. title: '提示',
  106. content: `是否确认删除清单?`,
  107. okText: '确定',
  108. cancelText: '取消',
  109. onOk: async () => {
  110. const res = await queryDelPurchaseExcel(data);
  111. if (res.code == 200) {
  112. message.success('删除成功');
  113. dispatch({
  114. type: 'xflow/queryBoomFlowDetail',
  115. payload: {
  116. id: templateId,
  117. },
  118. });
  119. }
  120. },
  121. });
  122. };
  123. const initData = async template_node_id => {
  124. if (controller) {
  125. // 中止上一次请求
  126. controller.abort();
  127. }
  128. try {
  129. setNodeLoading(true);
  130. controller = new AbortController();
  131. const res = await queryVserionByNode(
  132. {
  133. template_node_id: template_node_id,
  134. },
  135. controller.signal
  136. );
  137. controller = null;
  138. let data = [];
  139. if (!res.data.excel_version_tree) setData([]);
  140. res.data.excel_version_tree?.map(arr => {
  141. if (res.data.flow_id) {
  142. data = [...data, { ...arr, flow_id: res.data.flow_id }];
  143. } else {
  144. data = [...data, arr];
  145. }
  146. });
  147. data.sort((a, b) => b.id - a.id);
  148. data.forEach((item, id) => {
  149. //解决key报错问题
  150. data[id].key = `${id}-${item.name}`;
  151. item.isParent = true;
  152. });
  153. console.log(data);
  154. currentTempNodeId.current = template_node_id;
  155. setData(data);
  156. } catch (error) {
  157. console.log(error);
  158. }
  159. setNodeLoading(false);
  160. updateSteps([]);
  161. };
  162. const updateSteps = (data, curNodeId) => {
  163. let newData = [];
  164. let set = new Set();
  165. data.forEach(item => set.add(item.template_node_id));
  166. let list = [...set];
  167. if (set.has(curNodeId)) {
  168. set.delete(curNodeId);
  169. list = [curNodeId, ...set];
  170. }
  171. let dataList = list.map(template_node_id => {
  172. let itemDataList = data.filter(item => item.template_node_id == template_node_id);
  173. let curid = 0;
  174. let status = 'process';
  175. itemDataList.forEach(item => {
  176. if (item.audit_status == 3) curid++;
  177. if (item.audit_status == 2) status = 'error';
  178. });
  179. let curNode = flowDetail.nodes.find(item => item.Id == itemDataList[0].template_node_id);
  180. const seqList = itemDataList[0].FlowInfo.FlowNodes.filter(
  181. item => item.template_node_id == template_node_id
  182. ).sort((a, b) => a.seq - b.seq);
  183. let obj = {
  184. status,
  185. current: curid,
  186. list: seqList,
  187. name: curNode?.label || itemDataList[0].FlowInfo.name,
  188. };
  189. return obj;
  190. });
  191. setStepsData(dataList);
  192. };
  193. const handleChangeClick = item => {
  194. let file = isOut ? 'list' : 'detail';
  195. let type = item.flow_id ? '/queryAuditRecord' : '/queryAuditExcel';
  196. console.log(`${file}${type}`, item);
  197. dispatch({
  198. type: `${file}${type}`,
  199. payload: {
  200. excel_id: item.id,
  201. pageSize: 100,
  202. },
  203. callback: res => {
  204. updateSteps(res, item.template_node_id);
  205. },
  206. });
  207. };
  208. const columns = useMemo(() => {
  209. return [
  210. {
  211. title: '名称',
  212. width: '20%',
  213. render: item => (
  214. <span style={{ color: item.audit_status != 0 ? '#9b9b9b' : '' }}>
  215. {item.id == version.id && !item.isParent && (
  216. <CheckOutlined style={{ marginRight: 10 }} />
  217. )}
  218. {item.version_no && !item.children?.length
  219. ? `${item.version_name}.${item.version_no}`
  220. : item.version_name}
  221. </span>
  222. ),
  223. },
  224. {
  225. title: '创建人',
  226. width: '15%',
  227. render: item => {
  228. return (
  229. item.isParent && (
  230. <span>{userList.find(cur => cur.ID == item.author)?.CName || '-'}</span>
  231. )
  232. );
  233. },
  234. },
  235. {
  236. title: '分类',
  237. width: '15%',
  238. render: item => {
  239. return (
  240. item.isParent && (
  241. <span>{typeOptions.find(cur => cur.id == item.classify_id)?.name}</span>
  242. )
  243. );
  244. },
  245. },
  246. {
  247. title: '状态',
  248. width: '15%',
  249. render: item => {
  250. if (!item.flow_id && item.isParent) return;
  251. let style = { color: getColor(item) };
  252. let txt = '';
  253. let dom = '';
  254. switch (item.audit_status) {
  255. case 0:
  256. txt = '未提交';
  257. break;
  258. case 1:
  259. txt = '待审批';
  260. break;
  261. case 2:
  262. txt = '已拒绝';
  263. break;
  264. case 3:
  265. txt = '已通过';
  266. break;
  267. case 4:
  268. txt = '已提交';
  269. break;
  270. }
  271. if (item.status == 1) txt = '已失效';
  272. // 显示拒绝原因
  273. if (item.audit_comment) {
  274. dom = (
  275. <Popover content={item.audit_comment} title="原因">
  276. {txt}
  277. </Popover>
  278. );
  279. } else {
  280. dom = txt;
  281. }
  282. return item.audit_status != 0 ? (
  283. <Button onClick={() => handleChangeClick(item)}>{dom}</Button>
  284. ) : (
  285. <span style={style}>{dom}</span>
  286. );
  287. },
  288. },
  289. {
  290. title: '印章申请',
  291. width: '15%',
  292. render: item => {
  293. if (!item.flow_id && item.isParent) return;
  294. let txt = '';
  295. //申请印章成功 1 默认0 失败2
  296. switch (item.is_seal_succeed) {
  297. case 0:
  298. txt = '-';
  299. break;
  300. case 1:
  301. txt = '成功';
  302. break;
  303. case 2:
  304. txt = '失败';
  305. break;
  306. }
  307. return (
  308. <Space>
  309. <span>{txt}</span>
  310. {item.is_seal_succeed == 2 && <a onClick={() => handleRetryClick(item.id)}>重试</a>}
  311. </Space>
  312. );
  313. },
  314. },
  315. {
  316. title: '操作',
  317. width: '20%',
  318. render: item =>
  319. (item.flow_id || !item.isParent) &&
  320. item.id != version.id && (
  321. <Space>
  322. <a
  323. onClick={() => {
  324. console.log(item);
  325. onChangeVersion(item);
  326. onClose();
  327. }}
  328. >
  329. 加载
  330. </a>
  331. {item.audit_status == 0 &&
  332. item.author == currentUser.ID && ( //自己创建的&&未提交的清单自己可以删除
  333. <a
  334. onClick={() => {
  335. onDelVersion({ excel_id: item.id });
  336. }}
  337. >
  338. 删除
  339. </a>
  340. )}
  341. </Space>
  342. ),
  343. },
  344. ];
  345. }, [version]);
  346. const handleRetryClick = async id => {
  347. setNodeLoading(true);
  348. const res = await queryTrySeal({ excel_id: id });
  349. setNodeLoading(false);
  350. if (res.data?.errcode != 0) {
  351. message.error(res.data?.errmsg);
  352. } else {
  353. message.success('用印成功');
  354. if (currentTempNodeId.current) initData(currentTempNodeId.current);
  355. }
  356. };
  357. const onChange = value => {
  358. updateSteps([]);
  359. };
  360. useEffect(() => {
  361. if (!inputValue) {
  362. setShowData(data);
  363. return;
  364. }
  365. let resultData = [...data];
  366. switch (selectType) {
  367. case SELECT_TYPE.NAME:
  368. resultData = data.filter(item => item.version_name.includes(inputValue));
  369. break;
  370. case SELECT_TYPE.TYPE:
  371. resultData = data.filter(item => item.classify_id == inputValue);
  372. break;
  373. case SELECT_TYPE.CREATOR:
  374. resultData = data.filter(item => item.AuthorInfo?.CName.includes(inputValue));
  375. break;
  376. }
  377. setShowData(resultData);
  378. }, [inputValue, data]);
  379. return (
  380. <>
  381. <Modal
  382. // confirmLoading={loading}
  383. destroyOnClose
  384. title="流程图"
  385. visible={visible}
  386. onCancel={() => {
  387. setSelectType(SELECT_TYPE.NAME);
  388. setInputValue('');
  389. onClose();
  390. }}
  391. footer={false}
  392. width="98%"
  393. // bodyStyle={{ maxHeight: '660px', overflow: 'auto' }}
  394. >
  395. <Row gutter={8}>
  396. <Col span={14}>
  397. <Flow meta={{ type: 'view' }} flowDetail={graphData} onSelectNode={handleSelectNode} />
  398. </Col>
  399. <Col span={10}>
  400. <div style={{ fontSize: '16px', marginBottom: '10px' }}>清单列表</div>
  401. <div style={{ display: 'flex', justifyContent: 'space-between' }}>
  402. <div style={{ width: '60%' }}>
  403. <Select
  404. defaultValue={selectType}
  405. style={{ width: '30%' }}
  406. onChange={value => {
  407. setSelectType(value);
  408. setInputValue('');
  409. }}
  410. >
  411. <Option value={SELECT_TYPE.NAME}>名称:</Option>
  412. <Option value={SELECT_TYPE.TYPE}>分类:</Option>
  413. <Option value={SELECT_TYPE.CREATOR}>创建人:</Option>
  414. </Select>
  415. {selectType != SELECT_TYPE.TYPE && (
  416. <Input
  417. style={{ width: '70%' }}
  418. placeholder="请输入"
  419. value={inputValue}
  420. onChange={e => setInputValue(e.target.value)}
  421. />
  422. )}
  423. {selectType == SELECT_TYPE.TYPE && (
  424. <Select
  425. showSearch
  426. allowClear
  427. style={{ width: '70%' }}
  428. placeholder="请选择分类"
  429. options={typeOptions}
  430. onChange={id => setInputValue(id)}
  431. filterOption={(input, option) =>
  432. (option?.label ?? '').toLowerCase().includes(input.toLowerCase())
  433. }
  434. />
  435. )}
  436. </div>
  437. {isOut && (
  438. <Button type="primary" onClick={() => setVersionVisible(true)}>
  439. 新建清单
  440. </Button>
  441. )}
  442. </div>
  443. <div style={{ width: '100%', marginTop: '10px' }}>
  444. <Table
  445. columns={columns}
  446. dataSource={showData}
  447. loading={nodeLoading}
  448. bordered={false}
  449. pagination={{ position: ['none', 'none'], pageSize: 999, onChange }}
  450. scroll={{ y: '460px' }}
  451. // childrenColumnName="none"
  452. // expandable={{
  453. // expandedRowRender: record => (
  454. // <Table
  455. // columns={columns}
  456. // dataSource={record.children}
  457. // pagination={{ position: ['none', 'none'] }}
  458. // />
  459. // ),
  460. // rowExpandable: record => record.children?.length > 0,
  461. // }}
  462. />
  463. </div>
  464. {/* <Spin spinning={loading.global}> */}
  465. {stepsData.map((item, idx) => (
  466. <div key={`${item.name}_${idx}`} style={{ marginBottom: '20px' }}>
  467. <div style={{ marginBottom: '4px' }}>{item.name}</div>
  468. <Steps size="small" current={item.current} status={item.status}>
  469. {item.list.map(node => (
  470. <Step
  471. key={`${node.id}_${node.node}`}
  472. title={node.node}
  473. description={`审批人:${node.AuditorUser?.CName || '-'}`}
  474. />
  475. ))}
  476. </Steps>
  477. </div>
  478. ))}
  479. {/* </Spin> */}
  480. </Col>
  481. </Row>
  482. </Modal>
  483. <VersionModal
  484. typeOptions={typeOptions}
  485. visible={versionVisible}
  486. version={version}
  487. versionList={versionList}
  488. flowDetail={flowDetail}
  489. onClose={() => setVersionVisible(false)}
  490. onOk={values => {
  491. onCommit?.(values, () => {
  492. setVersionVisible(false);
  493. });
  494. }}
  495. loading={commitLoading}
  496. />
  497. </>
  498. );
  499. }
  500. const getColor = item => {
  501. let color = '';
  502. switch (item.audit_status) {
  503. case 2:
  504. // 审批拒绝
  505. color = '#f5222d';
  506. break;
  507. case 3:
  508. // 审批成功
  509. color = '#7cb305';
  510. break;
  511. case 4:
  512. // 历史清单
  513. color = '#9b9b9b';
  514. break;
  515. default:
  516. break;
  517. }
  518. return color;
  519. };
  520. export default connect(({ loading, user }) => ({
  521. loading,
  522. currentUser: user.currentUser,
  523. userList: user.list,
  524. }))(FlowModal);
  525. // export default FlowModal;