index.tsx 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  1. /*
  2. * @Author: code4eat awesomedema@gmail.com
  3. * @Date: 2023-01-04 14:12:31
  4. * @LastEditors: code4eat awesomedema@gmail.com
  5. * @LastEditTime: 2024-09-06 11:13:27
  6. * @FilePath: /BudgetManaSystem/src/pages/budgetMana/oneBatch/index.tsx
  7. * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  8. */
  9. import BMSPagecontainer from '@/components/BMSPageContainer'
  10. import { BMSTable } from '@/components/BMSTable';
  11. import { getComputeDate } from '@/pages/Home/service';
  12. import { ActionType, ProColumns } from '@ant-design/pro-components';
  13. import { message, Modal, Popover, Table, Tabs, Tooltip } from 'antd';
  14. import { useEffect, useRef, useState } from 'react';
  15. import { caculate, checkRequest, getCheckType, getCurrentCheckStatus, getCurrentZhileiCheckStatus, getData, getZhileiList } from './service';
  16. import './style.less';
  17. import { create, all, number } from 'mathjs'
  18. import * as XLSX from 'xlsx';
  19. import exportTableToMultiExcel from '@/utils/tableToMultiHeaderExcel';
  20. import { getJiezhuanStatus } from '../monthlySet/service';
  21. import { formatMoneyNumber } from '@/utils/format';
  22. import { createFromIconfontCN } from '@ant-design/icons';
  23. const IconFont = createFromIconfontCN({
  24. scriptUrl: '',
  25. });
  26. const config = {
  27. number: 'number',
  28. precision: 14
  29. }
  30. const math = create(all, config as any);
  31. type JsonStructure = {
  32. code: string;
  33. name: string;
  34. expand?: number;
  35. childTitle?: JsonStructure[];
  36. };
  37. type Column = {
  38. title: string | JSX.Element;
  39. dataIndex: string;
  40. key: string;
  41. ellipsis: boolean;
  42. width: number;
  43. renderText?: any,
  44. children?: Column[];
  45. };
  46. let checkStatusArr: number[] = []
  47. const OneBatch = () => {
  48. const [tableColumn, set_tableColumn] = useState<ProColumns[] | any[]>([]);
  49. const [columnsForExcel, set_columnsForExcel] = useState<ProColumns[] | any[]>([]);
  50. const [tableData, set_tableData] = useState<any[]>([]);
  51. const [currentComputeDate, set_currentComputeDate] = useState<string | undefined>();
  52. const [currentTabKey, set_currentTabKey] = useState('1');
  53. const [tabs, set_Tabs] = useState<{
  54. name: any; value: string
  55. }[]>([]);
  56. const [tableDataFilterParams, set_tableDataFilterParams] = useState<any | undefined>({ reportCode: undefined });
  57. const [auditType, set_auditType] = useState('0');
  58. const [ifShowTip, set_ifShowTip] = useState(false);
  59. const tableRef = useRef<ActionType>();
  60. const [tableH, set_tableH] = useState(0);
  61. const [reportTitle, set_reportTitle] = useState('');
  62. const [checkType, set_checkType] = useState<string | undefined>(undefined); // 一次分配审核模式,1职类一起审核 2职类分开审核
  63. const [disAccount, set_disAccount] = useState(false);
  64. const [ifBanAllAction, set_ifBanAllAction] = useState(true); //是否掩藏所有操作
  65. const onTabChange = (activeKey: string) => {
  66. set_currentTabKey(activeKey);
  67. set_tableDataFilterParams({
  68. ...tableDataFilterParams,
  69. reportCode: activeKey
  70. });
  71. }
  72. const [expandedkeys, set_expandedkeys] = useState<string[]>([]);
  73. const convertToColumnsFunc = (json: JsonStructure[], ifRender?: boolean, level: number = 1): Column[] => {
  74. return json.map((item) => {
  75. let column: Column = {
  76. title: item.name,
  77. dataIndex: `key-${item.code}`,
  78. key: `key-${item.code}`,
  79. ellipsis: true,
  80. width: 200,
  81. renderText(text: any) {
  82. function formatMoney(num: number) {
  83. if (typeof num !== 'number' || isNaN(num)) {
  84. return '-';
  85. }
  86. return new Intl.NumberFormat('en-US', { minimumFractionDigits: 0, maximumSignificantDigits: 10 }).format(num);
  87. }
  88. return formatMoney(text)
  89. }
  90. };
  91. const isExpanded = expandedkeys.includes(item.code);
  92. if (item.expand === 1 && item.childTitle && item.childTitle.length > 0) {
  93. column.title = ifRender ? (
  94. <div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'center', alignContent: 'center' }}>
  95. <span style={{ display: 'inline-block', maxWidth: '80%', minWidth: 140, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{item.name}</span>
  96. <div className='expandIcon' onClick={() => {
  97. if (isExpanded) {
  98. let _expandedkeys = [...expandedkeys];
  99. _expandedkeys.splice(expandedkeys.findIndex(a => a == item.code), 1);
  100. set_expandedkeys([..._expandedkeys]);
  101. } else {
  102. set_expandedkeys([...expandedkeys, item.code]);
  103. }
  104. }}>
  105. {isExpanded ? '-' : '+'}
  106. </div>
  107. </div>
  108. ) : item.name;
  109. if (isExpanded) {
  110. column.children = convertToColumnsFunc(item.childTitle, ifRender, level + 1);
  111. }
  112. }
  113. return column;
  114. });
  115. }
  116. useEffect(() => {
  117. tableRef.current?.reload();
  118. }, [expandedkeys])
  119. const getTableData = async (params: any, sort: any, filter: any) => {
  120. const { reportCode = 1 } = params;
  121. const resp: any = await getData(
  122. currentComputeDate as string,
  123. reportCode
  124. );
  125. if (resp) {
  126. const { title, assignmentData, reportName } = resp;
  127. const columns = convertToColumnsFunc(title, true);
  128. const columnsForExcel = convertToColumnsFunc(title, false);
  129. set_tableColumn([
  130. {
  131. title: '核算单元代码',
  132. dataIndex: 'unitCode',
  133. key: 'unitCode',
  134. width: 140,
  135. fixed: 'left',
  136. ellipsis: true,
  137. sorter: (a, b) => {
  138. return b.unitCode.localeCompare(a.unitCode)
  139. },
  140. },
  141. {
  142. title: '核算单元',
  143. dataIndex: 'unitName',
  144. key: 'unitName',
  145. width: 140,
  146. fixed: 'left',
  147. ellipsis: true,
  148. sorter: (a, b) => {
  149. return b.unitName.localeCompare(a.unitName)
  150. },
  151. },
  152. ...columns, {
  153. title: '总奖金',
  154. dataIndex: 'totalScore',
  155. key: 'totalScore',
  156. width: 140,
  157. fixed: 'right',
  158. ellipsis: true,
  159. renderText(text, record, index, action) {
  160. return formatMoneyNumber(text)
  161. },
  162. }]);
  163. set_columnsForExcel([
  164. {
  165. title: '核算单元代码',
  166. dataIndex: 'unitCode',
  167. key: 'unitCode',
  168. width: 140,
  169. fixed: 'left',
  170. ellipsis: true,
  171. sorter: (a, b) => {
  172. return b.unitCode.localeCompare(a.unitCode)
  173. },
  174. },
  175. {
  176. title: '核算单元',
  177. dataIndex: 'unitName',
  178. key: 'unitName',
  179. width: 140,
  180. fixed: 'left',
  181. ellipsis: true,
  182. sorter: (a, b) => {
  183. return b.unitName.localeCompare(a.unitName)
  184. },
  185. },
  186. ...columnsForExcel, {
  187. title: '总奖金',
  188. dataIndex: 'totalScore',
  189. key: 'totalScore',
  190. width: 140,
  191. fixed: 'right',
  192. ellipsis: true,
  193. renderText(text, record, index, action) {
  194. return formatMoneyNumber(text)
  195. },
  196. }]);
  197. // set_tableColumn([...mockColumns]);
  198. // set_columnsForExcel([...mockColumns]);
  199. const data = assignmentData.map((item: any) => {
  200. let rowData: { [key: string]: any } = {};
  201. for (let index = 0; index < item.value.length; index++) {
  202. for (const key in item.value[index]) {
  203. if (key != 'code') {
  204. rowData[`key-${item.value[index].code}`] = item.value[index].value
  205. }
  206. }
  207. }
  208. return { ...item, ...rowData, id: item.id, columns }
  209. });
  210. set_tableData(data);
  211. set_reportTitle(reportName);
  212. return {
  213. data,
  214. success: true
  215. }
  216. }
  217. return []
  218. }
  219. const checkHandle = async (type: string) => {
  220. if (!checkType) return;
  221. const resp = await checkRequest({
  222. computeDate: currentComputeDate as string,
  223. auditType: type == '0' ? '1' : '0', //审核类型 1审核 0取消审核
  224. reportCode: checkType == '2' ? currentTabKey : undefined
  225. }, checkType);
  226. if (resp) {
  227. if (type == '0') {
  228. message.success('审核提交成功!');
  229. set_auditType('1');
  230. }
  231. if (type == '1') {
  232. message.success('取消审核提交成功!');
  233. set_auditType('0');
  234. }
  235. }
  236. return resp;
  237. }
  238. const getCurrentComputeDate = async () => {
  239. const resp = await getComputeDate();
  240. set_currentComputeDate(resp);
  241. }
  242. const getTabList = async () => {
  243. const resp = await getZhileiList();
  244. if (resp) {
  245. set_Tabs(resp.list);
  246. }
  247. }
  248. const confirmCaculateHandle = async () => {
  249. const resp = await caculate(currentComputeDate as string);
  250. if (resp) {
  251. tableRef.current?.reload();
  252. }
  253. }
  254. const getCheckStatus = async (computeDate: string, code?: string) => {
  255. if (checkType == '1') {
  256. const resp = await getCurrentCheckStatus(computeDate);
  257. if (resp) {
  258. set_auditType('1'); //0 未审核 1 已审核
  259. } else {
  260. set_auditType('0');
  261. }
  262. }
  263. if (checkType == '2') {
  264. const resp = await getCurrentZhileiCheckStatus(computeDate, code ? code : currentTabKey);
  265. if (resp) {
  266. set_auditType('1'); //0 未审核 1 已审核
  267. } else {
  268. set_auditType('0');
  269. }
  270. }
  271. }
  272. const generateFunc = () => {
  273. Modal.confirm({
  274. title: '注意',
  275. okText: '确定',
  276. cancelText: '',
  277. closable: true,
  278. content: '计算会覆盖原有数据,确定要继续计算操作?',
  279. onOk: () => confirmCaculateHandle()
  280. })
  281. }
  282. const exportHandle = async (flag: number) => {
  283. if (!flag) {
  284. exportTableToMultiExcel([{ data: tableData, columns: columnsForExcel, sheetName: reportTitle }], `${reportTitle}.xlsx`, true);
  285. } else {
  286. const sheetsData = [];
  287. for (const tab of tabs) {
  288. const resp: any = await getData(currentComputeDate as string, Number(tab.value));
  289. if (resp) {
  290. const { title, assignmentData } = resp;
  291. const columnsForExcel = convertToColumnsFunc(title, false);
  292. const columns = [
  293. {
  294. title: '核算单元代码',
  295. dataIndex: 'unitCode',
  296. key: 'unitCode',
  297. width: 140,
  298. fixed: 'left',
  299. ellipsis: true,
  300. sorter: (a: { unitCode: any; }, b: { unitCode: string; }) => {
  301. return b.unitCode.localeCompare(a.unitCode)
  302. },
  303. },
  304. {
  305. title: '核算单元',
  306. dataIndex: 'unitName',
  307. key: 'unitName',
  308. width: 140,
  309. fixed: 'left',
  310. ellipsis: true,
  311. sorter: (a: { unitName: any; }, b: { unitName: string; }) => {
  312. return b.unitName.localeCompare(a.unitName)
  313. },
  314. },
  315. ...columnsForExcel, {
  316. title: '总奖金',
  317. dataIndex: 'totalScore',
  318. key: 'totalScore',
  319. width: 140,
  320. fixed: 'right',
  321. ellipsis: true,
  322. renderText(text: number, record: any, index: any, action: any) {
  323. return formatMoneyNumber(text)
  324. },
  325. }];
  326. const data = assignmentData.map((item: any) => {
  327. let rowData: { [key: string]: any } = {};
  328. for (let index = 0; index < item.value.length; index++) {
  329. for (const key in item.value[index]) {
  330. if (key !== 'code') {
  331. rowData[`key-${item.value[index].code}`] = item.value[index].value;
  332. }
  333. }
  334. }
  335. return { ...item, ...rowData, id: item.id, columns };
  336. });
  337. sheetsData.push({ data, columns, sheetName: tab.name });
  338. }
  339. }
  340. exportTableToMultiExcel(sheetsData, `一次分配报表数据.xlsx`, true);
  341. }
  342. }
  343. const handleResize = (e: any) => {
  344. const wH = e.target.innerHeight;
  345. const tableHeight = wH - 290;
  346. set_tableH(tableHeight);
  347. }
  348. const getCheckTypeHandle = async () => {
  349. const resp = await getCheckType();
  350. if (resp && resp.list.length > 0) {
  351. set_checkType(resp.list[0].value);
  352. }
  353. }
  354. function doResize() {
  355. setTimeout(() => {
  356. const ev: any = new Event('resize');
  357. window.dispatchEvent(ev);
  358. }, 0)
  359. }
  360. useEffect(() => {
  361. if (tabs.length > 0) {
  362. set_currentTabKey(tabs[0].value);
  363. }
  364. }, [tabs]);
  365. useEffect(() => {
  366. if (currentComputeDate && checkType == '2') {
  367. getCheckStatus(currentComputeDate as string);
  368. }
  369. }, [currentTabKey]);
  370. useEffect(() => {
  371. if (checkType == '2') {
  372. checkStatusArr = [];
  373. tabs.forEach(async (item) => {
  374. const resp = await getCurrentZhileiCheckStatus(currentComputeDate as string, item.value);
  375. checkStatusArr.push(resp);
  376. if (checkStatusArr.length == tabs.length) {
  377. const total = [...checkStatusArr].reduce((prev, cur) => prev + cur, 0);
  378. total > 0 ? set_disAccount(true) : set_disAccount(false)
  379. }
  380. })
  381. }
  382. }, [auditType]);
  383. const getJiezhuanStatusHandle = async () => {
  384. const resp = await getJiezhuanStatus(currentComputeDate as string);
  385. if (resp == 2) {
  386. set_ifBanAllAction(true);
  387. } else {
  388. set_ifBanAllAction(false);
  389. }
  390. }
  391. useEffect(() => {
  392. if (currentComputeDate && checkType) {
  393. getCheckStatus(currentComputeDate);
  394. getJiezhuanStatusHandle();
  395. }
  396. }, [currentComputeDate, checkType]);
  397. useEffect(() => {
  398. getCurrentComputeDate();
  399. window.addEventListener('resize', (e) => handleResize(e)) //监听窗口大小改变
  400. doResize();
  401. getTabList();
  402. getCheckTypeHandle();
  403. return () => {
  404. window.removeEventListener('resize', (e) => handleResize(e))
  405. }
  406. }, [])
  407. return (
  408. <BMSPagecontainer className='OneBatch' title={`核算年月:${currentComputeDate}`}>
  409. <div className='btnGroup'>
  410. {
  411. !ifBanAllAction && (
  412. <Popover open={ifShowTip} content={'当前处于审核中,无法操作!'} >
  413. <div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
  414. <div className={(auditType == '0' && !disAccount) ? 'caculateBtn' : 'caculateBtn disabled'}
  415. onMouseEnter={() => auditType == '0' ? set_ifShowTip(false) : set_ifShowTip(true)}
  416. onMouseLeave={() => set_ifShowTip(false)}
  417. onClick={() => (auditType == '0' && !disAccount) ? generateFunc() : () => { }}>计算</div>
  418. <div className={auditType == '0' ? 'caculateBtn' : 'caculateBtn disabled'}
  419. style={{ marginRight: 8 }}
  420. onMouseEnter={() => auditType == '0' ? set_ifShowTip(false) : set_ifShowTip(true)}
  421. onMouseLeave={() => set_ifShowTip(false)}
  422. onClick={() => auditType == '0' ? exportHandle(1) : () => { }}>
  423. 导出全部
  424. <Tooltip title="将所有职类的数据导出到一个excel文件并分sheet页放置">
  425. <IconFont type="iconshuoming" style={{ fontSize: 16, paddingLeft: 10 }} />
  426. </Tooltip>
  427. </div>
  428. <div className={auditType == '0' ? 'caculateBtn' : 'caculateBtn disabled'}
  429. onMouseEnter={() => auditType == '0' ? set_ifShowTip(false) : set_ifShowTip(true)}
  430. onMouseLeave={() => set_ifShowTip(false)}
  431. onClick={() => auditType == '0' ? exportHandle(0) : () => { }}>
  432. 导出当前
  433. <Tooltip title={tabs.length>0?`将${(tabs.filter((a)=>a.value == currentTabKey))[0].name}的数据导出到excel文件`:''}>
  434. <IconFont type="iconshuoming" style={{ fontSize: 16, paddingLeft: 10 }} />
  435. </Tooltip>
  436. </div>
  437. </div>
  438. </Popover>
  439. )
  440. }
  441. {!ifBanAllAction && <div className='checkBtn' onClick={() => checkHandle(`${auditType}`)}>{auditType == '0' ? '审核' : '取消审核'}</div>}
  442. </div>
  443. <div className='content'>
  444. <Tabs
  445. defaultActiveKey='1'
  446. onChange={onTabChange}
  447. items={[
  448. ...tabs.map(a => ({ label: a.name, key: a.value }))
  449. ]}
  450. />
  451. <div className='tabContent'>
  452. {currentComputeDate && <BMSTable actionRef={tableRef} rowKey='unitCode' bordered pagination={false} columns={tableColumn as ProColumns[]}
  453. params={tableDataFilterParams}
  454. scroll={{ x: 140 * tableColumn.length, y: tableH }}
  455. request={(params, sort, filter) => getTableData(params, sort, filter)}
  456. summary={(pageData) => {
  457. const Caculate = ({ colData }: { colData: any }) => {
  458. const { dataIndex, children } = colData;
  459. if (children) {
  460. return children.map((child: any, index: number) => (
  461. <Caculate key={index} colData={child} />
  462. ));
  463. }
  464. const total = pageData.reduce((prev, cur) => {
  465. //return prev + cur[`${dataIndex}`]
  466. if (typeof cur[`${dataIndex}`] == 'number') {
  467. return math.add(prev, cur[`${dataIndex}`] as number);
  468. }
  469. }, 0);
  470. return dataIndex == "unitName" ? <div style={{ textAlign: 'left' }}>合计</div> : (
  471. //<span>{typeof total == 'number'?math.format(total, {precision: 14}) : '合计'}</span>
  472. <span style={{ textAlign: 'left' }}>{typeof total == 'number' ? Number(total.toFixed(4)) : '-'}</span>
  473. )
  474. }
  475. const renderSummaryRow = (columns: any[]): React.ReactNode[] => {
  476. return columns.map((colData, index) => {
  477. if (colData.children) {
  478. return renderSummaryRow(colData.children);
  479. } else {
  480. return (
  481. <Table.Summary.Cell key={index} index={index} align='center' rowSpan={1}>
  482. <Caculate colData={colData} />
  483. </Table.Summary.Cell>
  484. );
  485. }
  486. });
  487. };
  488. return (
  489. <Table.Summary fixed>
  490. <Table.Summary.Row>
  491. {renderSummaryRow(tableColumn)}
  492. </Table.Summary.Row>
  493. </Table.Summary>
  494. );
  495. }}
  496. />}
  497. </div>
  498. </div>
  499. </BMSPagecontainer>
  500. )
  501. }
  502. export default OneBatch