index.tsx 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876
  1. /*
  2. * @Author: code4eat awesomedema@gmail.com
  3. * @Date: 2023-03-03 11:30:33
  4. * @LastEditors: code4eat awesomedema@gmail.com
  5. * @LastEditTime: 2024-12-05 15:11:30
  6. * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/pubDicTypeMana/index.tsx
  7. * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  8. */
  9. import KCIMPagecontainer from '@/components/KCIMPageContainer';
  10. import { KCIMTable } from '@/components/KCIMTable';
  11. import { createFromIconfontCN } from '@ant-design/icons';
  12. import { ActionType, ProFormInstance } from '@ant-design/pro-components';
  13. import { ProColumns } from '@ant-design/pro-table';
  14. import { Modal, message, Tabs, Input, DatePicker, Popover, Alert } from 'antd';
  15. import { Key, useEffect, useRef, useState } from 'react';
  16. import * as XLSX from 'xlsx';
  17. import { saveAs } from 'file-saver';
  18. import moment from 'moment';
  19. import 'moment/locale/zh-cn';
  20. import locale from 'antd/es/date-picker/locale/zh_CN';
  21. import { computeProfitReq, getNextLevelTableData, getReportDataReq, getReportProjectSettingList, getResponsibleCenters } from './service';
  22. import './style.less';
  23. import React from 'react';
  24. import { getDicDataBySysId, getParamsDataBySysId } from '@/services/getDic';
  25. import { KCIMLeftList } from '@/components/KCIMLeftList';
  26. import { formatMoneyNumber } from '@/utils/format';
  27. import { useModel } from '@umijs/max';
  28. import ReportExport from '../reportExport/report';
  29. import { getUserHasReports } from '../costAccounting/calcPageTemplate/service';
  30. const { RangePicker } = DatePicker;
  31. const IconFont = createFromIconfontCN({
  32. scriptUrl: '',
  33. });
  34. let currentRow: any = undefined;
  35. function findAllParents(tree: any[]) {
  36. let parents: any[] = [];
  37. // 递归函数来遍历树并找到所有父节点
  38. function traverse(nodes: any[]) {
  39. for (const node of nodes) {
  40. // 检查节点是否有子节点
  41. if (node.children && node.children.length > 0) {
  42. parents.push(node); // 添加到父节点列表
  43. traverse(node.children); // 递归遍历子节点
  44. }
  45. }
  46. }
  47. traverse(tree); // 开始遍历树
  48. return parents; // 返回所有父节点的数组
  49. }
  50. function countLeafNodesRecursive(node: any) {
  51. // 如果当前节点没有子节点,说明它是一个叶子节点
  52. if (!node.children || node.children.length === 0) {
  53. return 1;
  54. }
  55. let leafCount = 0;
  56. // 递归计算每个子节点的叶子节点数
  57. for (let i = 0; i < node.children.length; i++) {
  58. leafCount += countLeafNodesRecursive(node.children[i]);
  59. }
  60. return leafCount;
  61. }
  62. // 数据转换函数
  63. const transformData = (data: any[]) => {
  64. const resultMap: Map<string, { reportName: string; key: string; children?: any[];[key: string]: any }> = new Map();
  65. // 转换单个条目
  66. const transformEntry = (entry: any, computeDate: string) => {
  67. if (!entry) return {};
  68. const transformedEntry = { ...entry };
  69. transformedEntry[`${computeDate}_amount`] = entry.amount != null ? formatMoneyNumber(entry.amount) : null;
  70. transformedEntry[`${computeDate}_percent`] = entry.percent != null ? `${((entry.percent * 100).toFixed(2))}%` : null;
  71. return transformedEntry;
  72. };
  73. // 合并新的条目到现有条目,不覆盖已有的字段
  74. const mergeEntries = (existingEntry: any, newEntry: any) => {
  75. for (const key in newEntry) {
  76. if (!existingEntry[key] || key.endsWith('_amount') || key.endsWith('_percent')) {
  77. existingEntry[key] = newEntry[key];
  78. }
  79. }
  80. };
  81. // 递归处理每个节点及其子节点
  82. const processNode = (node: any, computeDate: string): any => {
  83. if (!node) return {};
  84. const transformedNode = transformEntry(node, computeDate);
  85. if (node.children && Array.isArray(node.children)) {
  86. const processedChildren = node.children.map((child: any) => processNode(child, computeDate));
  87. transformedNode.children = mergeChildren(transformedNode.children || [], processedChildren);
  88. }
  89. return transformedNode;
  90. };
  91. // 合并子节点,确保同一个父节点下的子节点是唯一的
  92. const mergeChildren = (existingChildren: any[], newChildren: any[]) => {
  93. newChildren.forEach((newChild) => {
  94. const existingChild = existingChildren.find((child) => child.reportName === newChild.reportName && child.key === newChild.key);
  95. if (existingChild) {
  96. mergeEntries(existingChild, newChild);
  97. if (newChild.children && Array.isArray(newChild.children)) {
  98. existingChild.children = mergeChildren(existingChild.children || [], newChild.children);
  99. }
  100. } else {
  101. existingChildren.push(newChild);
  102. }
  103. });
  104. return existingChildren;
  105. };
  106. // 主处理逻辑
  107. data.forEach((item) => {
  108. const { computeDate, profitVoList } = item;
  109. if (!profitVoList || !Array.isArray(profitVoList)) {
  110. return;
  111. }
  112. profitVoList.forEach((profit) => {
  113. if (!resultMap.has(profit.reportName)) {
  114. resultMap.set(profit.reportName, {
  115. ...transformEntry(profit, computeDate),
  116. key: profit.reportName,
  117. });
  118. }
  119. const existingEntry = resultMap.get(profit.reportName)!;
  120. const transformedProfit = processNode(profit, computeDate);
  121. mergeEntries(existingEntry, transformedProfit);
  122. if (transformedProfit.children && Array.isArray(transformedProfit.children)) {
  123. existingEntry.children = mergeChildren(existingEntry.children || [], transformedProfit.children);
  124. }
  125. });
  126. });
  127. // 确保每个层级的数据都有完整的月份字段
  128. const fillMissingMonths = (nodes: any[], computeDates: string[]) => {
  129. nodes.forEach((node) => {
  130. computeDates.forEach((date) => {
  131. if (!node.hasOwnProperty(`${date}_amount`)) {
  132. node[`${date}_amount`] = node.amount != null ? formatMoneyNumber(node.amount) : null;
  133. }
  134. if (!node.hasOwnProperty(`${date}_percent`)) {
  135. node[`${date}_percent`] = node.percent != null ? `${((node.percent * 100).toFixed(2))}%` : null;
  136. }
  137. });
  138. if (node.children && Array.isArray(node.children)) {
  139. fillMissingMonths(node.children, computeDates);
  140. if (node.children.length === 0) {
  141. delete node.children;
  142. }
  143. }
  144. });
  145. };
  146. const computeDates = data.map((item) => item.computeDate);
  147. const transformedData = Array.from(resultMap.values());
  148. fillMissingMonths(transformedData, computeDates);
  149. transformedData.forEach((node) => {
  150. if (node.children && node.children.length === 0) {
  151. delete node.children;
  152. }
  153. });
  154. // console.log({ transformedData });
  155. return transformedData;
  156. };
  157. const getNextUnexpandedKeys = (data: any[], expandedKeys: any[] = []) => {
  158. let keys: any[] = [];
  159. const traverse = (nodes: any) => {
  160. for (const node of nodes) {
  161. // 如果当前节点还没有展开,就把它加入 keys
  162. if (!expandedKeys.includes(node.id)) {
  163. keys.push(node.id);
  164. }
  165. // 如果当前节点已经展开,继续遍历子节点
  166. if (node.children && expandedKeys.includes(node.id)) {
  167. traverse(node.children);
  168. }
  169. }
  170. };
  171. traverse(data);
  172. return keys;
  173. };
  174. export default function DepartmentCostCalc() {
  175. const [tableDataFilterParams, set_tableDataFilterParams] = useState<any | undefined>({ reportType: 0 });
  176. const tableRef = useRef<ActionType>();
  177. const [tabs, set_tabs] = useState<any[]>([]);
  178. const { initialState, setInitialState } = useModel('@@initialState');
  179. const [computeRangeDate, set_computeRangeDate] = useState<string[]>(initialState ? [initialState.computeDate, initialState.computeDate] : []);
  180. const [responsibleCenters, set_responsibleCenters] = useState<any[]>([]);
  181. const [currentTabKey, set_currentTabKey] = useState<any | undefined>(undefined);
  182. const [currentTab, set_currentTab] = useState<any | undefined>(undefined);
  183. const [currentSelectedRespon, set_currentSelectedRespon] = useState<any | undefined>(undefined);
  184. const [tableDataSearchKeywords, set_tableDataSearchKeywords] = useState('');
  185. const [allParentsKeys, set_allParentsKeys] = useState<Key[]>([]);
  186. const [dataSource, set_dataSource] = useState<any[]>([]);
  187. const [calcResultText, set_calcResultText] = useState<undefined | string>(undefined);
  188. const [nextLevel, set_nextLevel] = useState<any>(undefined);
  189. const [columns, set_columns] = useState<any[]>([]);
  190. const [needShowReportCode, set_needShowReportCode] = useState('0');
  191. // 动态生成列定义函数
  192. const generateDataColumns = async (data: any[]) => {
  193. const baseColumns = [
  194. {
  195. title: '报表项目名称',
  196. dataIndex: 'reportName',
  197. key: 'reportName',
  198. width: 200,
  199. fixed: 'left'
  200. },
  201. ];
  202. let ifShowPercent = true;
  203. const { systemId } = JSON.parse(localStorage.getItem('currentSelectedTab') as string)
  204. const resp = await getParamsDataBySysId(systemId, '1851077044079824896');
  205. if (resp) {
  206. ifShowPercent = resp.value == '1' ? true : false;
  207. }
  208. // 获取所有月份并生成列
  209. const monthColumns = data.map((item) => {
  210. const month = item.computeDate;
  211. const base = [
  212. {
  213. title: '金额',
  214. dataIndex: `${month}_amount`,
  215. key: `${month}_amount`,
  216. width: 100,
  217. align: 'right',
  218. renderText(num: number, record: any) {
  219. const { calcType } = record;
  220. if (calcType == 0) {
  221. return <React.Fragment></React.Fragment>
  222. } else {
  223. if (calcType == 1 || calcType == 2 || calcType == 5) {
  224. return <span onClick={() => { set_nextLevel({ ...record, date: month }); tableRef.current?.reload() }} style={{ color: (calcType == 1 || calcType == 2 || calcType == 5) ? '#3377FF' : '#17181A', cursor: (calcType == 1 || calcType == 2 || calcType == 5) ? 'pointer' : 'default' }}>{num}</span>;
  225. } else {
  226. return num
  227. }
  228. //return formatMoneyNumber(num);
  229. }
  230. },
  231. },
  232. ]
  233. return {
  234. title: month,
  235. children: ifShowPercent ? [
  236. ...base,
  237. {
  238. title: '占比',
  239. dataIndex: `${month}_percent`,
  240. key: `${month}_percent`,
  241. width: 100,
  242. align: 'right',
  243. renderText(text: number, record: any) {
  244. const { calcType } = record;
  245. if (calcType == 0) {
  246. return <React.Fragment></React.Fragment>
  247. } else {
  248. return text
  249. }
  250. },
  251. },
  252. ] : [...base]
  253. };
  254. });
  255. set_columns([...baseColumns, ...monthColumns]);
  256. };
  257. const getIfShowReport = async () => {
  258. const { systemId } = JSON.parse(localStorage.getItem('currentSelectedTab') as string)
  259. const resp = await getParamsDataBySysId(systemId, '1845698984623083520');
  260. if (resp) {
  261. set_needShowReportCode(resp.value)
  262. }
  263. }
  264. const getTableData = async (params: any) => {
  265. const { responsibilityCode, filter = undefined } = params;
  266. if (!responsibilityCode) return []
  267. if (!nextLevel) {
  268. const resp = await getReportProjectSettingList({ ...params });
  269. if (resp) {
  270. // if (filter) {
  271. // const filterData = searchTree(resp, filter);
  272. // const allParents = findAllParents(filterData);
  273. // set_allParentsKeys([...(allParents.map((a: any) => a.id))])
  274. // return {
  275. // data: filterData,
  276. // success: true,
  277. // }
  278. // }
  279. const realData = transformData(resp);
  280. if (currentTab.value == '1') {
  281. const allParents = findAllParents(realData);
  282. set_allParentsKeys([...(allParents.map((a: any) => a.id))]);
  283. }
  284. generateDataColumns(resp)
  285. set_dataSource(realData);
  286. return {
  287. data: realData,
  288. success: true,
  289. }
  290. }
  291. }
  292. if (nextLevel) {
  293. const { reportId, calcType, date } = nextLevel;
  294. if (calcType == 1) {
  295. set_columns([
  296. {
  297. title: '会计科目代码',
  298. dataIndex: 'accountCode',
  299. key: 'accountCode',
  300. },
  301. {
  302. title: '会计项目名称',
  303. dataIndex: 'accountName',
  304. key: 'accountName',
  305. },
  306. {
  307. title: '金额',
  308. dataIndex: 'amount',
  309. key: 'amount',
  310. align: 'right',
  311. renderText(num: number, record: any) {
  312. return formatMoneyNumber(num)
  313. }
  314. }
  315. ])
  316. const resp = await getNextLevelTableData({
  317. computeDate: date,
  318. reportId,
  319. reportType: currentTabKey,
  320. responsibilityCode: currentSelectedRespon.responsibilityCode
  321. });
  322. return {
  323. data: resp ? resp : [],
  324. success: true,
  325. }
  326. }
  327. if (calcType == 2 || calcType == 5) {
  328. const columns = [
  329. {
  330. title: () => <React.Fragment></React.Fragment>,
  331. key: 'empty',
  332. children: [
  333. {
  334. title: '责任中心',
  335. dataIndex: 'responsibilityCenter',
  336. key: 'responsibilityCenter',
  337. render: (text: any, row: { responsibilityCenterRowSpan: any; }) => {
  338. return {
  339. children: text,
  340. props: {
  341. rowSpan: row.responsibilityCenterRowSpan,
  342. },
  343. };
  344. },
  345. },
  346. {
  347. title: '项目名称',
  348. dataIndex: 'projectName',
  349. key: 'projectName',
  350. render: (text: any, row: { projectNameRowSpan: any; }) => {
  351. return {
  352. children: text,
  353. props: {
  354. rowSpan: row.projectNameRowSpan,
  355. },
  356. };
  357. },
  358. },
  359. {
  360. title: '分摊参数',
  361. dataIndex: 'shareParam',
  362. key: 'shareParam',
  363. },
  364. {
  365. title: '分摊金额',
  366. dataIndex: 'shareAmount',
  367. key: 'shareAmount',
  368. align: 'right'
  369. },
  370. ]
  371. },
  372. {
  373. title: `${currentSelectedRespon.responsibilityName}`,
  374. dataIndex: `${currentSelectedRespon.responsibilityCode}`,
  375. key: `${currentSelectedRespon.responsibilityCode}`,
  376. children: [
  377. {
  378. title: '分摊参数数值',
  379. dataIndex: 'shareParamValue',
  380. key: 'shareParamValue',
  381. },
  382. {
  383. title: '分摊参数占比',
  384. dataIndex: 'shareParamRate',
  385. key: 'shareParamRate',
  386. },
  387. {
  388. title: '金额',
  389. dataIndex: 'amount',
  390. key: 'amount',
  391. },
  392. ]
  393. }
  394. ];
  395. set_columns([...columns]);
  396. const resp = await getNextLevelTableData({
  397. computeDate: date,
  398. reportId,
  399. reportType: currentTabKey,
  400. responsibilityCode: currentSelectedRespon.responsibilityCode
  401. });
  402. const processData = (data: any[]) => {
  403. const rows: any[] = [];
  404. data.forEach((item, index) => {
  405. let responseNameRowSpan = 0; // 合并相同责任中心的单元格
  406. item.dataList.forEach((dataItem: any) => {
  407. responseNameRowSpan += dataItem.shareParamData.length; // 计算需要合并的行数
  408. });
  409. let currentRow = 0; // 当前行位置
  410. item.dataList.forEach((dataItem: any) => {
  411. const dataItemRowSpan = dataItem.shareParamData.length; // 项目名称的行合并数
  412. dataItem.shareParamData.forEach((param: any, paramIndex: number) => {
  413. rows.push({
  414. key: `${item.responseCode}-${dataItem.alias}-${param.shareParamCode}`,
  415. responsibilityCenter: paramIndex === 0 && currentRow === 0 ? item.responseName : '',
  416. responsibilityCenterRowSpan: paramIndex === 0 && currentRow === 0 ? responseNameRowSpan : 0,
  417. projectName: paramIndex === 0 ? dataItem.alias : '',
  418. projectNameRowSpan: paramIndex === 0 ? dataItemRowSpan : 0,
  419. shareParam: param.shareParamName,
  420. shareAmount: formatMoneyNumber(param.shareParamAmount),
  421. shareParamValue: param.shareParamNum,
  422. shareParamRate: param.shareParamRate,
  423. amount: formatMoneyNumber(param.shareParamValue),
  424. });
  425. });
  426. currentRow++;
  427. });
  428. });
  429. return rows;
  430. };
  431. return {
  432. data: resp ? processData(resp) : [],
  433. success: true,
  434. }
  435. }
  436. }
  437. return []
  438. }
  439. const onTabChanged = (key: Key) => {
  440. set_currentTabKey(key);
  441. const needItem = tabs.filter((a) => a.key == key);
  442. if (needItem.length > 0) set_currentTab(needItem[0])
  443. }
  444. const getTabs = async () => {
  445. // const { systemId } = JSON.parse((localStorage.getItem('currentSelectedTab')) as string)
  446. // const resp = await getDicDataBySysId(systemId, 'PROFIT_REPORT_TYPE');
  447. const resp = await getUserHasReports();
  448. if (resp) {
  449. const { dataVoList } = resp;
  450. const tempArr = dataVoList.map((a: any) => ({ label: a.name, key: Number(a.code), value: a.value }));
  451. const arr = (tempArr.filter((a: any) => a.value != '2'));
  452. set_tabs([...arr]);
  453. set_currentTabKey(arr[0].key);
  454. set_currentTab(arr[0]);
  455. }
  456. }
  457. const getResponsibleCenterList = async (reportType: string) => {
  458. const resp = await getResponsibleCenters(reportType);
  459. if (resp) {
  460. set_responsibleCenters(resp);
  461. }
  462. }
  463. const onLeftChange = (currentSelected: any) => {
  464. set_currentSelectedRespon(currentSelected);
  465. }
  466. const backToHandle = () => {
  467. set_nextLevel(undefined);
  468. tableRef.current?.reload();
  469. }
  470. const tableDataSearchHandle = (paramName: string) => {
  471. set_tableDataFilterParams({
  472. ...tableDataFilterParams,
  473. [`${paramName}`]: tableDataSearchKeywords
  474. })
  475. }
  476. const getHeaderRows = (columns: any[], level = 0, headerRows: any[] = [], maxLevel = 0) => {
  477. headerRows[level] = headerRows[level] || [];
  478. columns.forEach((col: { title: any; children: any; }) => {
  479. const colSpan = getColSpan(col);
  480. headerRows[level].push({ title: col.title, colSpan, rowSpan: col.children ? 1 : maxLevel - level });
  481. if (col.children) {
  482. getHeaderRows(col.children, level + 1, headerRows, maxLevel);
  483. } else {
  484. // 填充空白单元格
  485. for (let i = level + 1; i < maxLevel; i++) {
  486. headerRows[i] = headerRows[i] || [];
  487. headerRows[i].push({ title: '', colSpan: 1, rowSpan: 1 });
  488. }
  489. }
  490. });
  491. return headerRows;
  492. };
  493. const getColSpan: any = (col: { children: any[]; }) => {
  494. if (!col.children) return 1;
  495. return col.children.reduce((sum, child) => sum + getColSpan(child), 0);
  496. };
  497. const getMaxLevel = (col: any) => {
  498. if (!col.children) return 1;
  499. return 1 + Math.max(...col.children.map(getMaxLevel));
  500. };
  501. const extractLeafColumns = (columns: any[]) => {
  502. let leafColumns: any[] = [];
  503. columns.forEach(col => {
  504. if (col.children) {
  505. leafColumns = leafColumns.concat(extractLeafColumns(col.children));
  506. } else {
  507. leafColumns.push(col);
  508. }
  509. });
  510. return leafColumns;
  511. };
  512. const addRowWithIndentation = (record: any, level: number, leafColumns: any[], worksheetData: any[]) => {
  513. const row = leafColumns.map(col => record[col.dataIndex] ?? '');
  514. row[0] = ' '.repeat(level * 4) + row[0]; // 在第一列前添加缩进空格以表示层级
  515. worksheetData.push(row);
  516. if (record.children) {
  517. record.children.forEach((child: any) => addRowWithIndentation(child, level + 1, leafColumns, worksheetData));
  518. }
  519. };
  520. const handleExport = () => {
  521. try {
  522. const workbook = XLSX.utils.book_new();
  523. const worksheetData: any[] = [];
  524. // 获取最大层级
  525. const maxLevel = columns.reduce((max, col) => Math.max(max, getMaxLevel(col)), 0);
  526. // 生成多层级表头
  527. const headerRows = getHeaderRows(columns, 0, [], maxLevel);
  528. // 构建表头行
  529. headerRows.forEach((row: any, rowIndex) => {
  530. const rowData: string[] = [];
  531. row.forEach((cell: { title: any; colSpan: number; rowSpan: number; }) => {
  532. rowData.push(cell.title);
  533. for (let i = 1; i < cell.colSpan; i++) {
  534. rowData.push('');
  535. }
  536. });
  537. worksheetData.push(rowData);
  538. });
  539. // 填充单层表头的空白行
  540. if (maxLevel > 1) {
  541. const numColumns = headerRows[0].reduce((sum: any, cell: { colSpan: any; }) => sum + cell.colSpan, 0);
  542. for (let i = 1; i < maxLevel; i++) {
  543. while (worksheetData[i].length < numColumns) {
  544. worksheetData[i].push('');
  545. }
  546. }
  547. }
  548. // 提取最内层表头列
  549. const leafColumns = extractLeafColumns(columns);
  550. // 添加数据并处理树结构
  551. dataSource.forEach(record => addRowWithIndentation(record, 0, leafColumns, worksheetData));
  552. const worksheet = XLSX.utils.aoa_to_sheet(worksheetData);
  553. // 初始化合并单元格数组
  554. worksheet['!merges'] = worksheet['!merges'] || [];
  555. // 合并单元格
  556. headerRows.forEach((row: any, rowIndex) => {
  557. let colIndex = 0;
  558. row.forEach((cell: { colSpan: number; rowSpan: number; }) => {
  559. if (cell.colSpan > 1 || cell.rowSpan > 1) {
  560. worksheet['!merges']!.push({ // 使用非空断言 '!'
  561. s: { r: rowIndex, c: colIndex },
  562. e: { r: rowIndex + cell.rowSpan - 1, c: colIndex + cell.colSpan - 1 }
  563. });
  564. }
  565. colIndex += cell.colSpan;
  566. });
  567. });
  568. // 设置单元格对齐方式
  569. Object.keys(worksheet).forEach(cell => {
  570. if (cell[0] !== '!') {
  571. worksheet[cell].s = {
  572. alignment: { vertical: 'center', horizontal: 'center' }
  573. };
  574. }
  575. });
  576. XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
  577. const wbout = XLSX.write(workbook, { bookType: 'xlsx', type: 'binary' });
  578. const s2ab = (s: string) => {
  579. const buf = new ArrayBuffer(s.length);
  580. const view = new Uint8Array(buf);
  581. for (let i = 0; i < s.length; i++) view[i] = s.charCodeAt(i) & 0xFF;
  582. return buf;
  583. };
  584. saveAs(new Blob([s2ab(wbout)], { type: 'application/octet-stream' }), currentTab ? `${currentTab.label}.xlsx` : 'table_data.xlsx');
  585. } catch (error) {
  586. console.error('Export failed:', error);
  587. }
  588. };
  589. const handleExpandNext = () => {
  590. // 当前所有未展开的节点,第一层优先展开
  591. const keysToExpand = getNextUnexpandedKeys(dataSource, allParentsKeys);
  592. set_allParentsKeys((prev) => Array.from(new Set([...prev, ...keysToExpand])));
  593. };
  594. const handleCollapseAll = () => {
  595. set_allParentsKeys([]);
  596. };
  597. useEffect(() => {
  598. if (computeRangeDate && currentTabKey != undefined) {
  599. getResponsibleCenterList(currentTabKey);
  600. set_nextLevel(undefined);
  601. set_allParentsKeys([]);
  602. }
  603. }, [computeRangeDate, currentTabKey]);
  604. useEffect(() => {
  605. if (currentSelectedRespon) {
  606. set_tableDataFilterParams({
  607. ...tableDataFilterParams,
  608. responsibilityCode: currentSelectedRespon.responsibilityCode,
  609. reportType: currentTabKey,
  610. beginComputeDate: computeRangeDate[0],
  611. endComputeDate: computeRangeDate[1],
  612. });
  613. set_nextLevel(undefined);
  614. }
  615. set_allParentsKeys([]);
  616. }, [currentSelectedRespon])
  617. useEffect(() => {
  618. getTabs();
  619. getIfShowReport();
  620. }, [])
  621. return (
  622. <KCIMPagecontainer className='DepartmentCostCalc' title={false}>
  623. <div className='header'>
  624. <div className="search">
  625. <span>核算年月:</span>
  626. <RangePicker
  627. onChange={(data, dateString) => {
  628. // console.log({data,dateString});
  629. set_computeRangeDate(dateString);
  630. setInitialState((s: any) => ({ ...s, computeDate: dateString[0], }))
  631. set_tableDataFilterParams({
  632. ...tableDataFilterParams,
  633. beginComputeDate: dateString[0],
  634. endComputeDate: dateString[1],
  635. });
  636. }}
  637. picker="month"
  638. locale={locale}
  639. autoComplete="off"
  640. defaultValue={[moment(computeRangeDate[0], 'YYYY-MM'), moment(computeRangeDate[1], 'YYYY-MM')]}
  641. format="YYYY-MM"
  642. />
  643. </div>
  644. {/* <div className='btnGoup'>
  645. <span className='onekeyCalcBtn' onClick={()=>onekeyComputeProfitHandle()}>一键计算</span>
  646. </div> */}
  647. </div>
  648. <div className='content'>
  649. <Tabs
  650. defaultActiveKey={tabs.length > 0 ? tabs[0].key : undefined}
  651. items={tabs}
  652. key={'key'}
  653. onChange={(key) => onTabChanged(key)}
  654. />
  655. {calcResultText && <Alert showIcon onClose={() => set_calcResultText(undefined)} icon={<IconFont type={(calcResultText.indexOf('失败') != -1) ? 'icon-cuowutishi' : 'icon-chenggongtishi'} />} closable style={{ padding: '4px 12px', marginBottom: 16, borderRadius: 4, border: (calcResultText.indexOf('失败') != -1) ? '1px solid #73E6BF' : '', background: (calcResultText.indexOf('失败') != -1) ? '#FFF1F3' : '#EBFFF8' }} message={calcResultText} type={calcResultText.indexOf('失败') != -1 ? 'error' : 'success'} />}
  656. <div className='inner'>
  657. <div className='left'>
  658. <KCIMLeftList
  659. fieldNames={{ title: 'responsibilityName', key: 'responsibilityCode', children: 'children' }}
  660. rowKey={'responsibilityCode'}
  661. dataSource={responsibleCenters} searchKey={'responsibilityName'}
  662. onChange={onLeftChange}
  663. contentH={`100%`}
  664. // placeholder={leftListSearchPlaceHolder}
  665. listType={'tree'}
  666. />
  667. </div>
  668. <div className='right'>
  669. <div className='toolBar'>
  670. <div className='filterItem' style={{ width: 228 }}>
  671. {/* <span className='label' style={{ whiteSpace: 'nowrap' }}> 检索:</span>
  672. <Input placeholder={'报表项目代码/名称'} allowClear autoComplete='off'
  673. suffix={
  674. <IconFont type="iconsousuo" style={{ color: '#99A6BF' }} onClick={() => tableDataSearchHandle('filter')} />
  675. }
  676. onChange={(e) => {
  677. set_tableDataSearchKeywords(e.target.value);
  678. if (e.target.value.length == 0) {
  679. set_tableDataFilterParams({
  680. ...tableDataFilterParams,
  681. filter: ''
  682. });
  683. }
  684. }}
  685. onPressEnter={(e) => {
  686. set_tableDataFilterParams({
  687. ...tableDataFilterParams,
  688. filter: ((e.target) as HTMLInputElement).value
  689. });
  690. }}
  691. /> */}
  692. {nextLevel && <div className='back' onClick={() => backToHandle()}><span><IconFont type='iconzhuye' /></span>{nextLevel.reportName}</div>}
  693. </div>
  694. {(!nextLevel||(nextLevel&&needShowReportCode == '0'))&&(
  695. <div className='btnGroup'>
  696. {/* <span className='btn' onClick={() => openTableDataDrawer()}>报表数据</span> */}
  697. <span className='btn' onClick={() => handleCollapseAll()}>全部折叠</span>
  698. <span className='btn' style={{ marginRight: 16 }} onClick={() => handleExpandNext()}>展开下一层</span>
  699. {!nextLevel && <span className='calc' onClick={() => handleExport()}>导出</span>}
  700. </div>
  701. )}
  702. </div>
  703. {needShowReportCode != '0'&&nextLevel&& (
  704. <ReportExport reportCode={needShowReportCode}
  705. tableScrollH={574}
  706. propsParams={{
  707. compute_date: nextLevel.date,
  708. reportId:nextLevel.reportId,
  709. reportType:currentTabKey,
  710. responsibilityCode: currentSelectedRespon.responsibilityCode
  711. }}
  712. />
  713. )}
  714. {(needShowReportCode == '0'||(needShowReportCode != '0'&&(!nextLevel))) && (
  715. <KCIMTable pagination={false}
  716. bordered
  717. rowClassName={(record) => (record.children ? 'has-children hover-row' : 'hover-row')}
  718. expandable={{
  719. defaultExpandAllRows: true, expandedRowKeys: allParentsKeys,
  720. onExpand(expanded, record) {
  721. const { id } = record;
  722. if (!expanded) {
  723. const expandedKeys = allParentsKeys.filter(a => a != id);
  724. set_allParentsKeys([...expandedKeys]);
  725. } else {
  726. set_allParentsKeys([...allParentsKeys, id]);
  727. }
  728. },
  729. }} columns={columns as ProColumns[]} scroll={{ y: `calc(100vh - 343px)` }} actionRef={tableRef} rowKey='id'
  730. params={tableDataFilterParams} request={(params) => getTableData(params)} />
  731. )}
  732. </div>
  733. </div>
  734. </div>
  735. </KCIMPagecontainer>
  736. )
  737. }