index.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. import React, { useEffect, useRef, useState } from 'react';
  2. import { BMSTable } from '@/components/BMSTable';
  3. import { createFromIconfontCN } from '@ant-design/icons';
  4. import { ProColumns } from '@ant-design/pro-components';
  5. import { Tabs, InputNumber, message } from 'antd';
  6. import { debounce } from 'lodash';
  7. import { Key } from 'react';
  8. import { getUnitRetainListReq, getUnitTypes, saveUnitRetainReq } from '../../service';
  9. import './style.less';
  10. import TableSelecter from './tableSelector';
  11. const IconFont = createFromIconfontCN({
  12. scriptUrl: '',
  13. });
  14. export const Distribute = ({ computeDate }: { computeDate: string }) => {
  15. const [tabs, set_tabs] = useState<any[]>([]);
  16. const [editable, set_editable] = useState(false);
  17. const [dataSource, set_dataSource] = useState<any[]>([]);
  18. const [currentTab, set_currentTab] = useState<undefined | string>(undefined);
  19. const [tableColumns, set_tableColumns] = useState<ProColumns[]>([]);
  20. const [titles, set_titles] = useState<any[]>([]);
  21. const [tableSelecterVisible, set_tableSelecterVisible] = useState(false);
  22. const [currentBlock, set_currentBlock] = useState<any>(undefined);
  23. const [inputNum, set_inputNum] = useState(0);
  24. const [currentEdit, set_currentEdit] = useState({ colCode: '', num: 0, rowCode: '' });
  25. const [slectedRows, set_slectedRows] = useState<any[]>([]);
  26. const [editMode, set_editMode] = useState(false);
  27. const wrapContainerRef = useRef<HTMLDivElement>(null);
  28. const tableBodyRef = useRef<HTMLDivElement>(null);
  29. const getTab = async () => {
  30. const resp = await getUnitTypes();
  31. if (resp) {
  32. const arr = resp.list.map((a: any, index: number) => ({
  33. label: a.name,
  34. key: a.code,
  35. }));
  36. set_tabs([...arr]);
  37. if (arr.length > 0) set_currentTab(arr[0].key);
  38. }
  39. };
  40. const onTabChange = (tabKey: string) => {
  41. set_currentTab(tabKey);
  42. };
  43. const getTableData = async (unitType: string) => {
  44. if (!unitType) return;
  45. const resp = await getUnitRetainListReq(computeDate, unitType);
  46. if (resp) {
  47. const { title = [], remainData = [], data = [] } = resp;
  48. set_titles(
  49. title.map((a: any) => {
  50. const value = remainData.filter((b: any) => a.code == b.code)[0].value;
  51. return {
  52. ...a,
  53. value,
  54. };
  55. })
  56. );
  57. const arr = title.map((a: any) => ({
  58. title: a.name,
  59. dataIndex: `${a.code}`,
  60. key: `${a.code}`,
  61. width: 230,
  62. renderText(num: number, record: any) {
  63. const InputComponent = () => {
  64. const [inputNum, set_inputNum] = useState(0);
  65. const handleChange = (num: number | null) => {
  66. set_inputNum(num ? num : 0);
  67. };
  68. const onBlurhandle = () => {
  69. set_currentEdit({ colCode: a.code, num: inputNum, rowCode: record.unitCode });
  70. };
  71. return (
  72. <InputNumber
  73. style={{ width: '100%' }}
  74. disabled={!editMode || a.code == 'totalBonus'}
  75. precision={2}
  76. min={0}
  77. defaultValue={num}
  78. onBlur={onBlurhandle}
  79. onChange={handleChange}
  80. />
  81. );
  82. };
  83. return <InputComponent />;
  84. },
  85. }));
  86. set_tableColumns([
  87. {
  88. title: '核算单元名称',
  89. dataIndex: 'unitName',
  90. key: 'unitName',
  91. width: 182,
  92. ellipsis: true,
  93. fixed: 'left',
  94. },
  95. ...arr,
  96. ]);
  97. const tableData = data.map((a: any) => {
  98. let obj: any = {};
  99. let totalCount = 0;
  100. title.forEach((b: any) => {
  101. const { data: rowData } = a;
  102. const needItem = rowData.filter((c: any) => c.code == b.code);
  103. obj[`${b.code}`] = needItem.length > 0 ? needItem[0].value : 0;
  104. if (b.code != 'totalBonus') {
  105. totalCount = totalCount + (needItem.length > 0 ? needItem[0].value : 0);
  106. }
  107. });
  108. return {
  109. ...a,
  110. ...obj,
  111. totalBonus: totalCount,
  112. };
  113. });
  114. set_dataSource([...tableData]);
  115. }
  116. };
  117. const tableSelecterCommit = (keys: Key[], rows: any[]) => {
  118. set_slectedRows([...slectedRows, ...rows]);
  119. if (currentBlock.code == 'totalBonus') {
  120. const newTitles = titles.map((a) => {
  121. if (a.code == currentBlock.code) {
  122. return {
  123. ...a,
  124. value: rows.reduce((prev: number, cur: any) => prev + cur.retainBonus, 0),
  125. };
  126. } else {
  127. const needArr = rows.filter((b) => b.retainCode == a.code);
  128. return {
  129. ...a,
  130. value: needArr.length > 0 ? needArr.reduce((prev: number, cur: any) => prev + cur.retainBonus, 0) : 0,
  131. };
  132. }
  133. });
  134. set_titles([...newTitles]);
  135. } else {
  136. const amount = rows.reduce((prev: number, cur: any) => prev + cur.retainBonus, 0);
  137. const newTitles = titles.map((a) => {
  138. if (a.code == currentBlock.code) {
  139. return {
  140. ...a,
  141. value: a.value + amount,
  142. };
  143. } else {
  144. return a;
  145. }
  146. });
  147. const titlesvalueCount = newTitles.reduce((prev: number, cur: any) => {
  148. if (cur.code != 'totalBonus') {
  149. return prev + cur.value;
  150. } else {
  151. return prev;
  152. }
  153. }, 0);
  154. set_titles(
  155. newTitles.map((a: any) => {
  156. if (a.code == 'totalBonus') {
  157. return { ...a, value: titlesvalueCount };
  158. } else {
  159. return a;
  160. }
  161. })
  162. );
  163. }
  164. set_tableSelecterVisible(false);
  165. };
  166. const saveHandle = async () => {
  167. const reaultData = dataSource.map((a) => {
  168. return {
  169. unitCode: a.unitCode,
  170. unitName: a.unitName,
  171. data: titles.map((b) => ({ code: b.code, value: a[`${b.code}`] })),
  172. };
  173. });
  174. const arr = titles.map((a) => {
  175. const ids = slectedRows.filter((b) => b.retainCode == a.code).map((c) => c.id);
  176. return {
  177. retainCode: a.code,
  178. retainIds: [...new Set(ids)],
  179. };
  180. });
  181. const result = {
  182. data: reaultData,
  183. remainData: titles.map((a) => ({ code: a.code, value: a.value })),
  184. historyRetain: arr,
  185. };
  186. const resp = await saveUnitRetainReq(result);
  187. if (resp) {
  188. message.success('保存成功!');
  189. set_editMode(false);
  190. set_slectedRows([]);
  191. }
  192. };
  193. useEffect(() => {
  194. getTableData(currentTab as string);
  195. }, [editMode]);
  196. useEffect(() => {
  197. // 更新 titles
  198. const newTitles = titles.map((b) => {
  199. if (b.code === currentEdit.colCode) {
  200. return { ...b, value: b.value - currentEdit.num };
  201. } else {
  202. return b;
  203. }
  204. });
  205. const titlesvalueCount = newTitles.reduce((prev: number, cur: any) => {
  206. if (cur.code != 'totalBonus') {
  207. return prev + cur.value;
  208. } else {
  209. return prev;
  210. }
  211. }, 0);
  212. set_titles(
  213. newTitles.map((a: any) => {
  214. if (a.code == 'totalBonus') {
  215. return { ...a, value: titlesvalueCount };
  216. } else {
  217. return a;
  218. }
  219. })
  220. );
  221. // 基于更新后的 titles 同时更新 dataSource
  222. const updatedDataSource = dataSource.map((item) => {
  223. if (item.unitCode == currentEdit.rowCode) {
  224. let total = 0;
  225. titles.forEach((a) => {
  226. if (a.code != 'totalBonus') {
  227. if (a.code == currentEdit.colCode) {
  228. total = total + currentEdit.num;
  229. } else {
  230. total = total + item[`${a.code}`];
  231. }
  232. }
  233. });
  234. return { ...item, [`${currentEdit.colCode}`]: currentEdit.num, totalBonus: total };
  235. } else {
  236. return item;
  237. }
  238. });
  239. set_dataSource(updatedDataSource);
  240. }, [currentEdit]);
  241. useEffect(() => {
  242. if (currentTab) {
  243. getTableData(currentTab);
  244. }
  245. }, [currentTab]);
  246. useEffect(() => {
  247. getTab();
  248. }, []);
  249. useEffect(() => {
  250. if (titles.length > 0) {
  251. const handleScroll = () => {
  252. if (wrapContainerRef.current && tableBody) {
  253. wrapContainerRef.current.scrollLeft = tableBody.scrollLeft;
  254. }
  255. };
  256. const tableBody = document.querySelector('.bms-ant-table-body') as HTMLElement;
  257. // 获取 bms-ant-table-cell 元素
  258. const td = document.querySelector('.bms-ant-table-cell') as HTMLElement;
  259. const fixedTd = document.querySelector('.bms-ant-table-cell-fix-left') as HTMLElement;
  260. if (td) {
  261. // 获取元素的样式
  262. const style = getComputedStyle(td);
  263. // 获取元素的宽度,包含 padding 和 border
  264. const paddingLeft = parseFloat(style.paddingLeft);
  265. const paddingRight = parseFloat(style.paddingRight);
  266. const borderLeftWidth = parseFloat(style.borderLeftWidth);
  267. const borderRightWidth = parseFloat(style.borderRightWidth);
  268. const tdWidth = td.clientWidth + paddingLeft + paddingRight + borderLeftWidth + borderRightWidth;
  269. // console.log('bms-ant-table-cell 的实际宽度(包含 padding 和 border):', tdWidth);
  270. } else {
  271. // console.log('没有找到 .bms-ant-table-cell 元素');
  272. }
  273. if (fixedTd) {
  274. // 获取元素的样式
  275. const style = getComputedStyle(fixedTd);
  276. // 获取元素的宽度,包含 padding 和 border
  277. const paddingLeft = parseFloat(style.paddingLeft);
  278. const paddingRight = parseFloat(style.paddingRight);
  279. const borderLeftWidth = parseFloat(style.borderLeftWidth);
  280. const borderRightWidth = parseFloat(style.borderRightWidth);
  281. const fixedTdWidth = fixedTd.clientWidth + paddingLeft + paddingRight + borderLeftWidth + borderRightWidth;
  282. // console.log('bms-ant-table-cell-fix-left 的实际宽度(包含 padding 和 border):', fixedTdWidth);
  283. } else {
  284. // console.log('没有找到 .bms-ant-table-cell-fix-left 元素');
  285. }
  286. if (tableBody) {
  287. tableBody.addEventListener('scroll', handleScroll);
  288. }
  289. // 清理函数,用于移除事件监听器
  290. return () => {
  291. if (tableBody) {
  292. tableBody.removeEventListener('scroll', handleScroll);
  293. }
  294. };
  295. }
  296. }, [titles]);
  297. return (
  298. <div className="Distribute">
  299. <TableSelecter
  300. onVisibleChange={(bool) => set_tableSelecterVisible(bool)}
  301. title="添加保留考核奖金"
  302. rowKey={'id'}
  303. defaultSelectedKeys={slectedRows.map((a: any) => a.id)}
  304. record={{ ...currentBlock, currentTab }}
  305. open={tableSelecterVisible}
  306. onFinish={tableSelecterCommit}
  307. />
  308. <div className="Distribute_header">
  309. <span className="title">保留奖金分配</span>
  310. {!editMode && (
  311. <span className="editBtn" onClick={() => set_editMode(true)}>
  312. <IconFont style={{ color: '#3376FE', cursor: 'pointer', marginRight: 4, fontSize: 16 }} type={'iconbianji'} />
  313. 开始分配
  314. </span>
  315. )}
  316. {editMode && (
  317. <div className="saveBtn">
  318. <span className="cancel" onClick={() => set_editMode(false)}>
  319. 取消
  320. </span>
  321. <span className="save" onClick={() => saveHandle()}>
  322. 保存
  323. </span>
  324. </div>
  325. )}
  326. </div>
  327. {tabs.length > 0 && <Tabs defaultActiveKey={currentTab} onChange={onTabChange} items={tabs} />}
  328. <div className="rowOne">
  329. <div className="blockHeader">
  330. <img src={require('../../../../../../static/distribute.png')} alt="" />
  331. <div className="detail">
  332. <span className="name">可分配额</span>
  333. <span className="sub">对总额进行分配</span>
  334. </div>
  335. </div>
  336. <div className="wrap" ref={wrapContainerRef}>
  337. {titles.map((a, index) => (
  338. <div className="block" key={index}>
  339. <div className="left">
  340. <span className="label">{a.name}(元)</span>
  341. <span className="value">{a.value}</span>
  342. </div>
  343. <div className="icon">
  344. <IconFont
  345. style={{ color: '#17181A', cursor: 'pointer' }}
  346. type={'icon-zhuijia'}
  347. onClick={() => {
  348. set_currentBlock(a);
  349. set_tableSelecterVisible(true);
  350. }}
  351. />
  352. </div>
  353. </div>
  354. ))}
  355. </div>
  356. </div>
  357. {titles.length > 0 && (
  358. <div className="table-body">
  359. <BMSTable
  360. className="Distribute_table"
  361. dataSource={dataSource}
  362. scroll={{ x: titles.length * 220 + 192, y: editMode ? 440 : 470 }}
  363. pagination={false}
  364. rowKey="unitCode"
  365. columns={[...tableColumns]}
  366. />
  367. </div>
  368. )}
  369. </div>
  370. );
  371. };