FlowModal.js 14 KB

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