123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641 |
- /*
- * @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<any | undefined>({ reportType: 0 });
- const tableRef = useRef<ActionType>();
- const formRef = useRef<ProFormInstance>();
- const [tabs, set_tabs] = useState<any[]>([]);
- const { initialState,setInitialState } = useModel('@@initialState');
- const [computeDate, set_computeDate] = useState<string>(initialState?initialState.computeDate:'');
- const [responsibleCenters, set_responsibleCenters] = useState<any[]>([]);
- const [currentTabKey, set_currentTabKey] = useState<any | undefined>(undefined);
- const [currentTab, set_currentTab] = useState<any | undefined>(undefined);
- const [currentSelectedRespon, set_currentSelectedRespon] = useState<any | undefined>(undefined);
- const [tableDataSearchKeywords, set_tableDataSearchKeywords] = useState('');
- const [allParentsKeys, set_allParentsKeys] = useState<Key[]>([]);
- const [drawerTableVisible, set_drawerTableVisible] = useState(false);
- const [dataSource, set_dataSource] = useState<any[]>([]);
- const [tableColumns, set_tableColumns] = useState<any[]>([]);
- const columns: ProColumns[] = [
- {
- title: '报表项目名称',
- dataIndex: 'reportName',
- width: '50%',
- renderText(text, record, index, action) {
- const {description} = record;
- return description?<Popover content={description}><span style={{cursor:'pointer'}}>{text}</span><IconFont className="hover-icon" style={{fontSize:16,color:'#17181a',paddingLeft:4,position:'relative',top:1}} type={'iconshuoming'} /></Popover>:text
- },
- },
- {
- title: '金额(元)',
- align:'right',
- dataIndex: 'amount',
- renderText(num,record) {
- if (record.children) {
- return <React.Fragment></React.Fragment>
- }else{
- return formatMoneyNumber(num);
- }
- },
- },
- {
- title: '占比',
- align:'right',
- dataIndex: 'percent',
- renderText(num,record) {
- if (record.children) {
- return <React.Fragment></React.Fragment>
- }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 (
- <KCIMPagecontainer className='DepartmentCostCalc' title={false}>
- <Drawer className='drawerTable' contentWrapperStyle={{}} bodyStyle={{ padding: 16 }} title={false} open={drawerTableVisible} width={1000} headerStyle={{ display: 'none' }}>
- <div className='header'>
- <div className='title'>{currentTab ? currentTab.label : ''}(单位:元)</div>
- <div className='btns'>
- <span onClick={() => set_drawerTableVisible(false)}>关闭</span>
- <span className='close' onClick={() => handleExport()}>导出</span>
-
- </div>
- </div>
- <KCIMTable loading={dataSource.length == 0} expandable={{ defaultExpandAllRows: true }} className='departmentCostCalcReportTable' dataSource={dataSource} bordered pagination={false} scroll={{ x:countLeafNodes(tableColumns) * 120, y:`calc(100vh - 198px)` }} columns={tableColumns as ProColumns[]} rowKey='responsibilityCode' />
- </Drawer>
- <div className='header'>
- <div className="search">
- <span>核算年月:</span>
- <DatePicker
- onChange={(data, dateString) => {
- 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="选择年月"
- />
- </div>
- </div>
- <div className='content'>
- <Tabs
- defaultActiveKey={tabs.length > 0 ? tabs[0].key : undefined}
- items={tabs}
- key={'key'}
- onChange={(key) => onTabChanged(key)}
- />
- <div className='inner'>
- <div className='left'>
- <KCIMLeftList
- fieldNames={{ title: 'responsibilityName', key: 'responsibilityCode', children: 'children' }}
- rowKey={'responsibilityCode'}
- dataSource={responsibleCenters} searchKey={'responsibilityName'}
- onChange={onLeftChange}
- contentH={`100%`}
- // placeholder={leftListSearchPlaceHolder}
- listType={'tree'}
- />
- </div>
- <div className='right'>
- <div className='toolBar'>
- <div className='filterItem' style={{ width: 228 }}>
- <span className='label' style={{ whiteSpace: 'nowrap' }}> 检索:</span>
- <Input placeholder={'报表项目代码/名称'} allowClear
- suffix={
- <IconFont type="iconsousuo" style={{ color: '#99A6BF' }} onClick={() => 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
- });
- }}
- />
- </div>
- <div className='btnGroup'>
- <span className='btn' onClick={() => openTableDataDrawer()}>报表数据</span>
- <span className='calc' onClick={() => computeProfitHandle()}>计算</span>
- </div>
- </div>
- <KCIMTable pagination={false}
- rowClassName={(record) => (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)} />
- </div>
- </div>
- </div>
- </KCIMPagecontainer>
- )
- }
|