FlowModal.js 17 KB

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