FlowModal.js 17 KB

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