/* * @Author: code4eat awesomedema@gmail.com * @Date: 2023-03-03 11:30:33 * @LastEditors: code4eat awesomedema@gmail.com * @LastEditTime: 2024-09-06 13:57:10 * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/pubDicTypeMana/index.tsx * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE */ import KCIMPagecontainer from '@/components/KCIMPageContainer'; import { KCIMTable } from '@/components/KCIMTable'; import { createFromIconfontCN } from '@ant-design/icons'; import { ActionType, ProFormDependency, ProFormInstance, ProFormText, ProFormSelect, ProFormDigit } from '@ant-design/pro-components'; import { ModalForm } from '@ant-design/pro-form' import { ProColumns } from '@ant-design/pro-table'; import { Modal, message, Drawer, Tabs, Input, DatePicker,Popover } from 'antd'; import { Key, useEffect, useRef, useState } from 'react'; import * as XLSX from 'xlsx'; import { saveAs } from 'file-saver'; import moment from 'moment'; import 'moment/locale/zh-cn'; import locale from 'antd/es/date-picker/locale/zh_CN'; import { addData, computeProfitReq, copyDataToSelectedType, delData, editData, getReportDataReq, getReportProjectSettingList, getResponsibleCenters, saveReportRelation } from './service'; import './style.less'; import TableTransfer from './transform'; import React from 'react'; import { cleanTree, getStringWidth } from '@/utils/tooljs'; import { getDicDataBySysId } from '@/services/getDic'; import { KCIMLeftList } from '@/components/KCIMLeftList'; import { formatMoneyNumber } from '@/utils/format'; import { useModel } from '@umijs/max'; const IconFont = createFromIconfontCN({ scriptUrl: '', }); let currentRow: any = undefined; function findAllParents(tree: any[]) { let parents: any[] = []; // 递归函数来遍历树并找到所有父节点 function traverse(nodes: any[]) { for (const node of nodes) { // 检查节点是否有子节点 if (node.children && node.children.length > 0) { parents.push(node); // 添加到父节点列表 traverse(node.children); // 递归遍历子节点 } } } traverse(tree); // 开始遍历树 return parents; // 返回所有父节点的数组 } function countLeafNodes(trees:any[]) { let leafCount = 0; // 遍历集合中的每棵树 for (let i = 0; i < trees.length; i++) { leafCount += countLeafNodesRecursive(trees[i]); } return leafCount; } function countLeafNodesRecursive(node:any) { // 如果当前节点没有子节点,说明它是一个叶子节点 if (!node.children || node.children.length === 0) { return 1; } let leafCount = 0; // 递归计算每个子节点的叶子节点数 for (let i = 0; i < node.children.length; i++) { leafCount += countLeafNodesRecursive(node.children[i]); } return leafCount; } function searchTree(tree: any[], searchTerm: string) { // 定义结果数组 let results = []; // 定义递归函数来搜索匹配的节点 function searchNode(node: any) { // 创建一个变量来标记当前节点或其子节点是否匹配 let isMatch = false; // 检查当前节点的 name 或 code 是否包含搜索词 if (node.reportName.includes(searchTerm)) { isMatch = true; } // 复制当前节点,避免修改原始数据 let newNode = { ...node, children: [] }; // 如果有子节点,递归搜索每个子节点 if (node.children) { for (let child of node.children) { let childMatch = searchNode(child); // 如果子节点或其子树匹配,添加到新节点的子节点数组中 if (childMatch) { newNode.children.push(childMatch); isMatch = true; } } } // 如果当前节点或其任何子节点匹配,返回新节点 // 如果children为空,则不包含children属性 if (isMatch) { if (newNode.children.length === 0) { delete newNode.children; } return newNode; } else { return null; } } // 遍历树的每个顶级节点 for (let node of tree) { let result = searchNode(node); if (result) { results.push(result); } } return results; } function processTree(originalData: any[]) { return originalData.map(node => { // 深复制当前节点 const newNode = JSON.parse(JSON.stringify(node)); // 如果当前节点有profitList,处理它 if (newNode.profitList && Array.isArray(newNode.profitList)) { newNode.profitList.forEach((profit: any) => { // 添加新的键值对到新节点 newNode[`${profit.reportId}`] = formatMoneyNumber(profit.value); }); // 如果不需要保留profitList,可以删除 // delete newNode.profitList; } // 如果节点有子节点,递归处理子节点 if (node.child && Array.isArray(node.child)) { newNode.children = processTree(node.child); } return newNode; }); } // 递归函数,用于处理多层级标题 function generateColumns(item: any, titleIndex = 0) { const column: any = { title: item.reportName, dataIndex: `${item.reportId}`, key: `${item.reportId}`, // align: 'center', }; if (item.childTitle && Array.isArray(item.childTitle) && item.childTitle.length > 0) { column.children = item.childTitle.map((a: any, aindex: number) => generateColumns(a, titleIndex + 1)); } return column; } // 主函数,生成表格列 const generateTableColumns = (title: any[]) => { return title.map((item: any, titleIndex: number) => generateColumns(item, titleIndex)); }; export default function DepartmentCostCalc() { const [tableDataFilterParams, set_tableDataFilterParams] = useState({ reportType: 0 }); const tableRef = useRef(); const formRef = useRef(); const [tabs, set_tabs] = useState([]); const { initialState,setInitialState } = useModel('@@initialState'); const [computeDate, set_computeDate] = useState(initialState?initialState.computeDate:''); const [responsibleCenters, set_responsibleCenters] = useState([]); const [currentTabKey, set_currentTabKey] = useState(undefined); const [currentTab, set_currentTab] = useState(undefined); const [currentSelectedRespon, set_currentSelectedRespon] = useState(undefined); const [tableDataSearchKeywords, set_tableDataSearchKeywords] = useState(''); const [allParentsKeys, set_allParentsKeys] = useState([]); const [drawerTableVisible, set_drawerTableVisible] = useState(false); const [dataSource, set_dataSource] = useState([]); const [tableColumns, set_tableColumns] = useState([]); const columns: ProColumns[] = [ { title: '报表项目名称', dataIndex: 'reportName', width: '50%', renderText(text, record, index, action) { const {description} = record; return description?{text}:text }, }, { title: '金额(元)', align:'right', dataIndex: 'amount', renderText(num,record) { if (record.children) { return }else{ return formatMoneyNumber(num); } }, }, { title: '占比', align:'right', dataIndex: 'percent', renderText(num,record) { if (record.children) { return }else{ return `${((num * 100).toFixed(2))}%` } }, }, ] const getTableData = async (params: any) => { const { responsibilityCode, filter = undefined } = params; if (!responsibilityCode) return [] const resp = await getReportProjectSettingList({ ...params }); if (resp) { if (filter) { const filterData = searchTree(resp, filter); const allParents = findAllParents(filterData); set_allParentsKeys([...(allParents.map((a: any) => a.id))]) return { data: filterData, success: true, } } const allParents = findAllParents(resp); set_allParentsKeys([...(allParents.map((a: any) => a.id))]) return { data: resp, success: true, } } return [] } const onTabChanged = (key: Key) => { set_currentTabKey(key); const needItem = tabs.filter((a) => a.key == key); if (needItem.length > 0) set_currentTab(needItem[0]) } const getTabs = async () => { const { systemId } = JSON.parse((localStorage.getItem('currentSelectedTab')) as string) const resp = await getDicDataBySysId(systemId, 'PROFIT_REPORT_TYPE'); if (resp) { const { dataVoList } = resp; const tempArr = dataVoList.map((a: any) => ({ label: a.name, key: Number(a.code), value: a.value })); const arr = (tempArr.filter((a: any) => a.value != '2')); set_tabs([...arr]); set_currentTabKey(arr[0].key); set_currentTab(arr[0]); } } const getResponsibleCenterList = async (reportType: string) => { const resp = await getResponsibleCenters(reportType); if (resp) { set_responsibleCenters(resp); } } const onLeftChange = (currentSelected: any) => { set_currentSelectedRespon(currentSelected); } const tableDataSearchHandle = (paramName: string) => { set_tableDataFilterParams({ ...tableDataFilterParams, [`${paramName}`]: tableDataSearchKeywords }) } const computeProfitHandle = async () => { Modal.confirm({ title: '注意', content: '计算操作会覆盖当月已计算的数据,是否继续操作?', okText: '确定', cancelText: '取消', onOk: async (...args) => { const resp = await computeProfitReq(computeDate, currentTabKey); if (resp) { message.success('操作成功!'); tableRef.current?.reload(); } }, }) } const openTableDataDrawer = async () => { set_drawerTableVisible(true); const resp = await getReportDataReq(currentTabKey, computeDate); if (resp) { const { title = [], data = [] } = resp; const defaultColumns = [{ title: '科室名称', dataIndex: 'responsibilityName', key: 'responsibilityName', width: 220, fixed: 'left' }]; const tableColumns = generateTableColumns(title); const dataSource = processTree(data); set_tableColumns([...defaultColumns, ...tableColumns]); set_dataSource(dataSource); // console.log({ columns: [...defaultColumns, ...tableColumns], dataSource }) } } const getHeaderRows = (columns: any[], level = 0, headerRows:any[] = [], maxLevel = 0) => { headerRows[level] = headerRows[level] || []; columns.forEach((col: { title: any; children: any; }) => { const colSpan = getColSpan(col); headerRows[level].push({ title: col.title, colSpan, rowSpan: col.children ? 1 : maxLevel - level }); if (col.children) { getHeaderRows(col.children, level + 1, headerRows, maxLevel); } else { // 填充空白单元格 for (let i = level + 1; i < maxLevel; i++) { headerRows[i] = headerRows[i] || []; headerRows[i].push({ title: '', colSpan: 1, rowSpan: 1 }); } } }); return headerRows; }; const getColSpan:any = (col: { children: any[]; }) => { if (!col.children) return 1; return col.children.reduce((sum, child) => sum + getColSpan(child), 0); }; const getMaxLevel = (col: any) => { if (!col.children) return 1; return 1 + Math.max(...col.children.map(getMaxLevel)); }; const extractLeafColumns = (columns: any[]) => { let leafColumns: any[] = []; columns.forEach(col => { if (col.children) { leafColumns = leafColumns.concat(extractLeafColumns(col.children)); } else { leafColumns.push(col); } }); return leafColumns; }; const addRowWithIndentation = (record: any, level: number, leafColumns: any[], worksheetData: any[]) => { const row = leafColumns.map(col => record[col.dataIndex] ?? ''); row[0] = ' '.repeat(level * 4) + row[0]; // 在第一列前添加缩进空格以表示层级 worksheetData.push(row); if (record.children) { record.children.forEach((child: any) => addRowWithIndentation(child, level + 1, leafColumns, worksheetData)); } }; const handleExport = () => { try { const workbook = XLSX.utils.book_new(); const worksheetData: any[] = []; // 获取最大层级 const maxLevel = tableColumns.reduce((max, col) => Math.max(max, getMaxLevel(col)), 0); // 生成多层级表头 const headerRows = getHeaderRows(tableColumns, 0, [], maxLevel); // 构建表头行 headerRows.forEach((row: any, rowIndex) => { const rowData: string[] = []; row.forEach((cell: { title: any; colSpan: number; rowSpan: number; }) => { rowData.push(cell.title); for (let i = 1; i < cell.colSpan; i++) { rowData.push(''); } }); worksheetData.push(rowData); }); // 填充单层表头的空白行 if (maxLevel > 1) { const numColumns = headerRows[0].reduce((sum: any, cell: { colSpan: any; }) => sum + cell.colSpan, 0); for (let i = 1; i < maxLevel; i++) { while (worksheetData[i].length < numColumns) { worksheetData[i].push(''); } } } // 提取最内层表头列 const leafColumns = extractLeafColumns(tableColumns); // 添加数据并处理树结构 dataSource.forEach(record => addRowWithIndentation(record, 0, leafColumns, worksheetData)); const worksheet = XLSX.utils.aoa_to_sheet(worksheetData); // 初始化合并单元格数组 worksheet['!merges'] = worksheet['!merges'] || []; // 合并单元格 headerRows.forEach((row: any, rowIndex) => { let colIndex = 0; row.forEach((cell: { colSpan: number; rowSpan: number; }) => { if (cell.colSpan > 1 || cell.rowSpan > 1) { worksheet['!merges'].push({ s: { r: rowIndex, c: colIndex }, e: { r: rowIndex + cell.rowSpan - 1, c: colIndex + cell.colSpan - 1 } }); } colIndex += cell.colSpan; }); }); // 设置单元格对齐方式 Object.keys(worksheet).forEach(cell => { if (cell[0] !== '!') { worksheet[cell].s = { alignment: { vertical: 'center', horizontal: 'center' } }; } }); XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1'); const wbout = XLSX.write(workbook, { bookType: 'xlsx', type: 'binary' }); const s2ab = (s: string) => { const buf = new ArrayBuffer(s.length); const view = new Uint8Array(buf); for (let i = 0; i < s.length; i++) view[i] = s.charCodeAt(i) & 0xFF; return buf; }; saveAs(new Blob([s2ab(wbout)], { type: 'application/octet-stream' }), currentTab ? `${currentTab.label}.xlsx` : 'table_data.xlsx'); } catch (error) { console.error('Export failed:', error); } }; useEffect(() => { if (computeDate && currentTabKey != undefined) { getResponsibleCenterList(currentTabKey); } }, [computeDate, currentTabKey]); useEffect(() => { if (currentSelectedRespon) { set_tableDataFilterParams({ ...tableDataFilterParams, responsibilityCode: currentSelectedRespon.responsibilityCode, reportType: currentTabKey, computeDate: computeDate }) } }, [currentSelectedRespon]) useEffect(() => { getTabs(); }, []) return (
{currentTab ? currentTab.label : ''}(单位:元)
set_drawerTableVisible(false)}>关闭 handleExport()}>导出
核算年月: { set_computeDate(dateString); setInitialState((s:any)=>({...s,computeDate: dateString,})) set_tableDataFilterParams({ ...tableDataFilterParams, computeDate: dateString, }); }} picker="month" locale={locale} defaultValue={moment(computeDate, 'YYYY-MM')} format="YYYY-MM" placeholder="选择年月" />
0 ? tabs[0].key : undefined} items={tabs} key={'key'} onChange={(key) => onTabChanged(key)} />
检索: tableDataSearchHandle('filter')} /> } onChange={(e) => { set_tableDataSearchKeywords(e.target.value); if (e.target.value.length == 0) { set_tableDataFilterParams({ ...tableDataFilterParams, filter: '' }); } }} onPressEnter={(e) => { set_tableDataFilterParams({ ...tableDataFilterParams, filter: ((e.target) as HTMLInputElement).value }); }} />
openTableDataDrawer()}>报表数据 computeProfitHandle()}>计算
(record.children ? 'has-children hover-row' : 'hover-row')} expandable={{ defaultExpandAllRows: true, expandedRowKeys: allParentsKeys, onExpand(expanded, record) { const { id } = record; if (!expanded) { const expandedKeys = allParentsKeys.filter(a => a != id); set_allParentsKeys([...expandedKeys]); } else { set_allParentsKeys([...allParentsKeys, id]); } }, }} columns={columns as ProColumns[]} scroll={{ y: `calc(100vh - 302px)` }} actionRef={tableRef} rowKey='id' params={tableDataFilterParams} request={(params) => getTableData(params)} />
) }