ソースを参照

feat(report): 新增DRG报表与虚拟化优化\n\n- DRG成本明细/成本构成明细/科室DRG成本构成明细 接入策略与接口映射\n- deptDiseaseCostCompositionDetail 行虚拟化(rc-virtual-list)与横向滚动\n- 统一金额/占比渲染,修正表头为‘病种成本’\n- 导出与检索:DRG系列加入导出与‘DRG名称’搜索\n- 菜单:standardCostReport 复用 costAccounting 图标 + 目录默认图标

code4eat 3 日 前
コミット
ca2bd9ec18

+ 9 - 1
src/app.tsx

@@ -540,6 +540,10 @@ export const layout = ({ initialState, setInitialState }: { initialState: any, s
                 if (node.path == '/costAccounting') {
                   node.icon = <Icon component={() => <IconFont type='icon-jixiaoguanli' />} />;
                 }
+                // 新增:standardCostReport 使用与 /costAccounting 相同的 icon
+                if (node.path == '/standardCostReport') {
+                  node.icon = <Icon component={() => <IconFont type='icon-jixiaoguanli' />} />;
+                }
                 if (node.path == '/reportExport') {
                   node.icon = <Icon component={() => <IconFont type='icon-jixiaoguanli' />} />;
                 }
@@ -550,13 +554,17 @@ export const layout = ({ initialState, setInitialState }: { initialState: any, s
                   node.icon = <Icon component={() => <IconFont type='icon-baobiaochaxun' />} />;
                 }
               }
+              // 目录默认 icon:无 icon 且有子节点时使用文件夹图标
+              if (!node.icon && Array.isArray(node.children) && node.children.length > 0) {
+                node.icon = <FolderOutlined />;
+              }
               if (node.children) {
                 node.children.forEach((child: any) => addIconToPath(child, paths));
               }
             }
 
             _menu.forEach((item: any) => {
-              addIconToPath(item, ['/home', '/baseInfoMana', '/costAccounting', '/monthlyInfoSearch', '/static', '/reportExport', '/costLibraryManagement', '/baseSetting']);
+              addIconToPath(item, ['/home', '/baseInfoMana', '/costAccounting', '/standardCostReport', '/monthlyInfoSearch', '/static', '/reportExport', '/costLibraryManagement', '/baseSetting']);
             });
 
             const { newTree, firstLeafNode, firstLeafNodePath } = transformTree(_menu);

+ 170 - 0
src/components/VirtualTableBody.tsx

@@ -0,0 +1,170 @@
+import React, { CSSProperties, UIEvent, useEffect, useMemo, useRef, useState } from 'react';
+import VirtualList from 'rc-virtual-list';
+import { formatMoneyNumber, formatToPercentage } from '@/utils/format';
+
+type AnyColumn = {
+  title?: any;
+  dataIndex?: string | number;
+  key?: string;
+  width?: number;
+  align?: 'left' | 'center' | 'right';
+  children?: AnyColumn[];
+};
+
+type BodyInfo = {
+  scrollbarSize: number;
+  ref: any;
+  onScroll: (params: { scrollLeft?: number; scrollTop?: number }) => void;
+};
+
+function flattenLeafColumns(columns: AnyColumn[]): AnyColumn[] {
+  const leaves: AnyColumn[] = [];
+  const walk = (cols: AnyColumn[]) => {
+    (cols || []).forEach((c) => {
+      if (Array.isArray(c.children) && c.children.length > 0) {
+        walk(c.children);
+      } else {
+        leaves.push(c);
+      }
+    });
+  };
+  walk(columns || []);
+  return leaves;
+}
+
+export function buildVirtualBody(options: {
+  columns: AnyColumn[];
+  rowHeight?: number;
+  height: number;
+  width: number;
+}) {
+  const { columns, rowHeight = 44, height, width } = options;
+
+  const leafColumns = flattenLeafColumns(columns);
+  const columnWidths = leafColumns.map((c) => (typeof c.width === 'number' ? c.width : 120));
+
+  const VirtualBody: React.FC<{ rawData: any[]; info: BodyInfo }> = ({ rawData, info }) => {
+    // 外层滚动容器(承担横向滚动同步)
+    const outerRef = useRef<HTMLDivElement | null>(null);
+    const listRef = useRef<any>(null);
+
+    const [connectObject] = useState(() => {
+      const obj: any = {};
+      Object.defineProperty(obj, 'scrollLeft', {
+        get: () => null,
+        set: (scrollLeft: number) => {
+          if (outerRef.current) {
+            outerRef.current.scrollLeft = scrollLeft;
+          }
+        },
+      });
+      return obj;
+    });
+
+    useEffect(() => {
+      if (info && info.ref) {
+        info.ref.current = connectObject;
+      }
+      // eslint-disable-next-line react-hooks/exhaustive-deps
+    }, [rawData, leafColumns.length, height, width]);
+
+    const getColumnWidth = (index: number) => columnWidths[index] || 120;
+
+    const cellStyleBase: CSSProperties = useMemo(() => ({
+      boxSizing: 'border-box',
+      padding: '8px 8px',
+      borderTop: '1px solid rgb(218 226 242)',
+      overflow: 'hidden',
+      whiteSpace: 'nowrap',
+      textOverflow: 'ellipsis',
+    }), []);
+
+    const Row = ({ index }: { index: number; style?: CSSProperties }) => {
+      const record = rawData[index] || {};
+      return (
+        <div className="virtual-table-row" style={{ width, display: 'flex' }}>
+          {leafColumns.map((col, columnIndex) => {
+            const dataIndex = col?.dataIndex as any;
+            const raw = record?.[dataIndex];
+            const align = col?.align || 'left';
+            const w = getColumnWidth(columnIndex);
+            // 统一渲染:优先使用列自带的 renderText;否则根据标题或 dataIndex 判断金额/占比
+            let text: any = raw;
+            try {
+              const titleStr = typeof col?.title === 'string' ? col.title : '';
+              const isRatio = String(dataIndex || '').toLowerCase().endsWith('ratio') || String(titleStr).includes('占比');
+              if (typeof (col as any).renderText === 'function') {
+                text = (col as any).renderText(raw, record);
+              } else if (isRatio) {
+                text = formatToPercentage(raw);
+              } else if (typeof raw === 'number') {
+                text = formatMoneyNumber(raw, { decimalPlaces: 2, useThousandSeparator: true });
+              }
+            } catch {}
+            return (
+              <div
+                key={String(col.key || dataIndex || columnIndex)}
+                className="virtual-table-cell"
+                style={{ ...cellStyleBase, textAlign: align as any, width: w, flex: '0 0 auto' }}
+              >
+                {text as any}
+              </div>
+            );
+          })}
+        </div>
+      );
+    };
+
+    const handleScroll = (e: UIEvent<HTMLDivElement>) => {
+      const target = e.currentTarget;
+      info?.onScroll?.({ scrollLeft: target.scrollLeft });
+    };
+
+    const handleWheelCapture = (e: React.WheelEvent<HTMLDivElement>) => {
+      // 将横向滚动手势映射到外层容器,实现“在中间区域也能横向滚动”
+      if (!outerRef.current) return;
+      const hasHorizontal = Math.abs(e.deltaX) > 0 || e.shiftKey;
+      if (!hasHorizontal) return;
+      const container = outerRef.current;
+      const max = (container.scrollWidth || 0) - (container.clientWidth || 0);
+      if (max <= 0) return;
+      const next = Math.max(0, Math.min(max, container.scrollLeft + (e.deltaX || (e.deltaY > 0 ? 40 : -40))));
+      if (next !== container.scrollLeft) {
+        container.scrollLeft = next;
+        try { info?.onScroll?.({ scrollLeft: next }); } catch {}
+        e.preventDefault();
+      }
+    };
+
+    return (
+      <div style={{ width: '100%', overflowX: 'auto' }} ref={outerRef} onScroll={handleScroll} onWheelCapture={handleWheelCapture}>
+        <div style={{ width }}>
+          <VirtualList
+            ref={listRef}
+            data={rawData}
+            height={height}
+            itemHeight={rowHeight}
+            itemKey={(item: any, idx: number) => String(item?.id ?? item?.key ?? idx)}
+            style={{ overflowX: 'hidden' }}
+            onScroll={(e: any) => {
+              try { info?.onScroll?.({ scrollTop: e.scrollTop }); } catch {}
+            }}
+          >
+            {(item: any, index: number) => (
+              <Row index={index} />
+            )}
+          </VirtualList>
+        </div>
+      </div>
+    );
+  };
+
+  // antd Table 期望的 components.body 签名:(rawData, info) => ReactNode
+  return (rawData: any[], info: BodyInfo) => {
+    return <VirtualBody rawData={rawData} info={info} />;
+  };
+}
+
+export default buildVirtualBody;
+
+

+ 1061 - 0
src/pages/costAccounting/calcPageTemplate/columns.tsx

@@ -989,6 +989,1067 @@ export const costShareReportTable: ProColumns[] = [
     },
 ];
 
+// 医院科室直接成本表(医疗成本)
+export const deptDirectMedicalCost: ProColumns[] = [
+    {
+        title: '科室名称',
+        dataIndex: 'responsibilityName',
+        width: 180,
+        fixed: 'left' as any,
+    },
+    {
+        title: '人员经费(1)',
+        dataIndex: 'personnelExpense',
+        align: 'right',
+        renderText(num, record) {
+            const { decimalPlace } = record || {};
+            return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: true });
+        },
+    },
+    {
+        title: '卫生材料费(2)',
+        dataIndex: 'healthMaterialFee',
+        align: 'right',
+        renderText(num, record) {
+            const { decimalPlace } = record || {};
+            return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: true });
+        },
+    },
+    {
+        title: '药品费(3)',
+        dataIndex: 'drugFee',
+        align: 'right',
+        renderText(num, record) {
+            const { decimalPlace } = record || {};
+            return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: true });
+        },
+    },
+    {
+        title: '固定资产折旧费(4)',
+        dataIndex: 'fixedAssetDepreciation',
+        align: 'right',
+        renderText(num, record) {
+            const { decimalPlace } = record || {};
+            return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: true });
+        },
+    },
+    {
+        title: '无形资产摊销费(5)',
+        dataIndex: 'intangibleAssetAmortization',
+        align: 'right',
+        renderText(num, record) {
+            const { decimalPlace } = record || {};
+            return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: true });
+        },
+    },
+    {
+        title: '提取医疗风险基金(6)',
+        dataIndex: 'medicalRiskFundExtraction',
+        align: 'right',
+        renderText(num, record) {
+            const { decimalPlace } = record || {};
+            return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: true });
+        },
+    },
+    {
+        title: '其他医疗费用(7)',
+        dataIndex: 'otherMedicalExpenses',
+        align: 'right',
+        renderText(num, record) {
+            const { decimalPlace } = record || {};
+            return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: true });
+        },
+    },
+    {
+        title: '合计(8)=(1)+(2)+(3)+(4)+(5)+(6)+(7)',
+        dataIndex: 'total',
+        width: 220,
+        fixed: 'right' as any,
+        align: 'right',
+        renderText(num, record) {
+            const { decimalPlace, permil } = record || {};
+            return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true });
+        },
+    },
+];
+
+// 医院科室直接成本表(全成本)
+export const deptFullDirectCost: ProColumns[] = [
+    {
+        title: '科室名称',
+        dataIndex: 'responsibilityName',
+        width: 180,
+        fixed: 'left' as any,
+    },
+    {
+        title: '医疗成本合计(1)',
+        dataIndex: 'medicalCostTotal',
+        align: 'right',
+        renderText(num, record) {
+            const { decimalPlace } = record || {};
+            return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: true });
+        },
+    },
+    {
+        title: '财政项目拨款经费形成的各项费用(2)',
+        dataIndex: 'financialProjectFunds',
+        align: 'right',
+        renderText(num, record) {
+            const { decimalPlace } = record || {};
+            return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: true });
+        },
+    },
+    {
+        title: '非同级财政拨款项目经费形成的各项费用(3)',
+        dataIndex: 'nonPeerFinancialFunds',
+        align: 'right',
+        renderText(num, record) {
+            const { decimalPlace } = record || {};
+            return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: true });
+        },
+    },
+    {
+        title: '医疗全成本合计(4)=(1)+(2)+(3)',
+        dataIndex: 'medicalTotalCost',
+        align: 'right',
+        renderText(num, record) {
+            const { decimalPlace } = record || {};
+            return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: true });
+        },
+    },
+    {
+        title: '科教经费形成的各项费用(5)',
+        dataIndex: 'educationalExpenses',
+        align: 'right',
+        renderText(num, record) {
+            const { decimalPlace } = record || {};
+            return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: true });
+        },
+    },
+    {
+        title: '资产处置费用、上缴上级费用、对附属单位补助费用、其他费用等(6)',
+        dataIndex: 'assetDisposalFees',
+        align: 'right',
+        renderText(num, record) {
+            const { decimalPlace } = record || {};
+            return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: true });
+        },
+    },
+    {
+        title: '医院全成本(7)=(4)+(5)+(6)',
+        dataIndex: 'hospitalTotalCost',
+        width: 240,
+        fixed: 'right' as any,
+        align: 'right',
+        renderText(num, record) {
+            const { decimalPlace, permil } = record || {};
+            return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true });
+        },
+    },
+];
+
+// 医院科室成本分摊汇总表
+export const deptCostAllocationSummary: ProColumns[] = [
+    {
+        title: '科室名称',
+        dataIndex: 'responsibilityName',
+        width: 180,
+        fixed: 'left' as any,
+    },
+    {
+        title: '医疗成本(1)=(2)+(3)',
+        dataIndex: 'medicalCost',
+        align: 'right',
+        renderText(num, record) {
+            const { decimalPlace } = record || {};
+            return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: true });
+        },
+    },
+    {
+        title: '直接成本(2)',
+        dataIndex: 'directCost',
+        align: 'right',
+        renderText(num, record) {
+            const { decimalPlace } = record || {};
+            return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: true });
+        },
+    },
+    {
+        title: '间接成本',
+        children: [
+            { title: '小计(3)=(4)+(5)+(6)', dataIndex: 'subtotal', align: 'right', renderText: (n: any, r: any) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '行政后勤分摊(4)', dataIndex: 'allocatedAdminCost', align: 'right', renderText: (n: any, r: any) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '医疗辅助分摊(5)', dataIndex: 'allocatedSupportCost', align: 'right', renderText: (n: any, r: any) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '医疗技术分摊(6)', dataIndex: 'allocatedTechCost', align: 'right', renderText: (n: any, r: any) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+        ] as any,
+    },
+];
+
+// 医院诊次成本构成表
+export const hospitalVisitCostComposition: ProColumns[] = [
+    {
+        title: '成本项目',
+        dataIndex: 'costItem',
+        width: 220,
+        fixed: 'left' as any,
+    },
+    {
+        title: '每诊次成本',
+        children: [
+            {
+                title: '医疗成本',
+                dataIndex: 'medicalCost',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '医疗全成本',
+                dataIndex: 'medicalFullCost',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '医院全成本',
+                dataIndex: 'hospitalFullCost',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+        ] as any,
+    },
+];
+
+// 医院科室诊次成本表
+export const hospitalDeptVisitCost: ProColumns[] = [
+    {
+        title: '科室编码',
+        dataIndex: 'responsibilityCode',
+        width: 140,
+        fixed: 'left' as any,
+    },
+    {
+        title: '科室名称',
+        dataIndex: 'responsibilityName',
+        width: 180,
+        fixed: 'left' as any,
+    },
+    {
+        title: '每诊次成本',
+        children: [
+            {
+                title: '服务量',
+                dataIndex: 'serviceCount',
+                align: 'right',
+            },
+            {
+                title: '医疗成本',
+                dataIndex: 'medicalCost',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '医疗全成本',
+                dataIndex: 'medicalFullCost',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '医院全成本',
+                dataIndex: 'hospitalFullCost',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+        ] as any,
+    },
+];
+
+// 医院床日成本构成表
+export const hospitalBedDayCostComposition: ProColumns[] = [
+    {
+        title: '成本项目',
+        dataIndex: 'costItem',
+        width: 220,
+        fixed: 'left' as any,
+    },
+    {
+        title: '每床日成本',
+        children: [
+            {
+                title: '医疗成本',
+                dataIndex: 'medicalCost',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '医疗全成本',
+                dataIndex: 'medicalFullCost',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '医院全成本',
+                dataIndex: 'hospitalFullCost',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+        ] as any,
+    },
+];
+
+// 医院医疗服务项目成本汇总表
+export const hospitalServiceProjectCost: ProColumns[] = [
+    {
+        title: '成本项目',
+        dataIndex: 'itemName',
+        width: 260,
+        fixed: 'left' as any,
+        renderText: (_: any, record: any) => {
+            const { itemCode, itemName } = record || {};
+            return itemCode ? `[${itemCode}]${itemName || ''}` : (itemName || '');
+        },
+    },
+    {
+        title: '医疗成本',
+        dataIndex: 'medicalCost',
+        align: 'right',
+        renderText(num: any, record: any) {
+            const { decimalPlace, permil } = record || {};
+            return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+        },
+    },
+    {
+        title: '医疗全成本',
+        dataIndex: 'medicalFullCost',
+        align: 'right',
+        renderText(num: any, record: any) {
+            const { decimalPlace, permil } = record || {};
+            return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+        },
+    },
+    {
+        title: '医院全成本',
+        dataIndex: 'hospitalFullCost',
+        align: 'right',
+        renderText(num: any, record: any) {
+            const { decimalPlace, permil } = record || {};
+            return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+        },
+    },
+];
+
+// 医院医疗服务项目成本明细表
+export const medicalServiceCostDetail: ProColumns[] = [
+    {
+        title: '项目编码',
+        dataIndex: 'itemCode',
+        width: 140,
+        fixed: 'left' as any,
+    },
+    {
+        title: '项目名称',
+        dataIndex: 'itemName',
+        width: 220,
+        fixed: 'left' as any,
+    },
+    {
+        title: '服务量',
+        dataIndex: 'serviceVolume',
+        align: 'right',
+    },
+    {
+        title: '每项目成本',
+        children: [
+            {
+                title: '医疗成本',
+                dataIndex: 'medicalCost',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '医疗全成本',
+                dataIndex: 'medicalFullCost',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '医院全成本',
+                dataIndex: 'hospitalFullCost',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+        ] as any,
+    },
+];
+
+// 医院病种成本构成明细表
+export const diseaseCostCompositionDetail: ProColumns[] = [
+    {
+        title: '病种编码',
+        dataIndex: 'itemCode',
+        width: 140,
+        fixed: 'left' as any,
+    },
+    {
+        title: '病种名称',
+        dataIndex: 'itemName',
+        width: 220,
+        fixed: 'left' as any,
+    },
+    {
+        title: '病种成本',
+        children: [
+            {
+                title: '金额',
+                dataIndex: 'totalCost',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+        ] as any,
+    },
+    {
+        title: '人员经费',
+        children: [
+            {
+                title: '金额',
+                dataIndex: 'personnelExpense',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '占比',
+                dataIndex: 'personnelExpenseRatio',
+                align: 'right',
+                renderText: (n: any) => formatToPercentage(n),
+            },
+        ] as any,
+    },
+    {
+        title: '卫生材料费',
+        children: [
+            {
+                title: '金额',
+                dataIndex: 'medicalMaterialExpense',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '占比',
+                dataIndex: 'medicalMaterialExpenseRatio',
+                align: 'right',
+                renderText: (n: any) => formatToPercentage(n),
+            },
+        ] as any,
+    },
+    {
+        title: '药品费',
+        children: [
+            {
+                title: '金额',
+                dataIndex: 'drugExpense',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '占比',
+                dataIndex: 'drugExpenseRatio',
+                align: 'right',
+                renderText: (n: any) => formatToPercentage(n),
+            },
+        ] as any,
+    },
+    {
+        title: '固定资产折旧费',
+        children: [
+            {
+                title: '金额',
+                dataIndex: 'fixedAssetDepreciation',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '占比',
+                dataIndex: 'fixedAssetDepreciationRatio',
+                align: 'right',
+                renderText: (n: any) => formatToPercentage(n),
+            },
+        ] as any,
+    },
+    {
+        title: '无形资产摊销费',
+        children: [
+            {
+                title: '金额',
+                dataIndex: 'intangibleAssetAmortization',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '占比',
+                dataIndex: 'intangibleAssetAmortizationRatio',
+                align: 'right',
+                renderText: (n: any) => formatToPercentage(n),
+            },
+        ] as any,
+    },
+    {
+        title: '提取医疗风险基金',
+        children: [
+            {
+                title: '金额',
+                dataIndex: 'medicalRiskFund',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '占比',
+                dataIndex: 'medicalRiskFundRatio',
+                align: 'right',
+                renderText: (n: any) => formatToPercentage(n),
+            },
+        ] as any,
+    },
+    {
+        title: '其他医疗费用',
+        children: [
+            {
+                title: '金额',
+                dataIndex: 'otherMedicalExpenses',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '占比',
+                dataIndex: 'otherMedicalExpensesRatio',
+                align: 'right',
+                renderText: (n: any) => formatToPercentage(n),
+            },
+        ] as any,
+    },
+];
+
+// 医院病种成本明细表
+export const diseaseCostDetail: ProColumns[] = [
+    {
+        title: '病种编码',
+        dataIndex: 'itemCode',
+        width: 140,
+        fixed: 'left' as any,
+    },
+    {
+        title: '病种名称',
+        dataIndex: 'itemName',
+        width: 220,
+        fixed: 'left' as any,
+    },
+    {
+        title: '服务量',
+        dataIndex: 'serviceVolume',
+        align: 'right',
+    },
+    {
+        title: '每病种成本',
+        children: [
+            {
+                title: '医疗成本',
+                dataIndex: 'medicalCost',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '医疗全成本',
+                dataIndex: 'medicalFullCost',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '医院全成本',
+                dataIndex: 'hospitalFullCost',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+        ] as any,
+    },
+];
+
+// 医院DRG成本明细表
+export const drgCostDetail: ProColumns[] = [
+    {
+        title: 'DRG编码',
+        dataIndex: 'itemCode',
+        width: 140,
+        fixed: 'left' as any,
+    },
+    {
+        title: 'DRG名称',
+        dataIndex: 'itemName',
+        width: 320,
+        fixed: 'left' as any,
+    },
+    {
+        title: '服务量',
+        dataIndex: 'serviceVolume',
+        align: 'right',
+    },
+    {
+        title: '每DRG成本',
+        children: [
+            {
+                title: '医疗成本',
+                dataIndex: 'medicalCost',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '医疗全成本',
+                dataIndex: 'medicalFullCost',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '医院全成本',
+                dataIndex: 'hospitalFullCost',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+        ] as any,
+    },
+];
+
+// 医院DRG成本构成明细表
+export const drgCostCompositionDetail: ProColumns[] = [
+    {
+        title: 'DRG编码',
+        dataIndex: 'itemCode',
+        width: 140,
+        fixed: 'left' as any,
+    },
+    {
+        title: 'DRG名称',
+        dataIndex: 'itemName',
+        width: 320,
+        fixed: 'left' as any,
+    },
+    {
+        title: '病种成本',
+        dataIndex: 'totalCost',
+        align: 'right',
+        renderText(num: any, record: any) {
+            const { decimalPlace, permil } = record || {};
+            return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+        },
+    },
+    {
+        title: '人员经费',
+        children: [
+            {
+                title: '金额',
+                dataIndex: 'personnelExpense',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '占比',
+                dataIndex: 'personnelExpenseRatio',
+                align: 'right',
+                renderText: (n: any) => formatToPercentage(n),
+            },
+        ] as any,
+    },
+    {
+        title: '卫生材料费',
+        children: [
+            {
+                title: '金额',
+                dataIndex: 'medicalMaterialExpense',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '占比',
+                dataIndex: 'medicalMaterialExpenseRatio',
+                align: 'right',
+                renderText: (n: any) => formatToPercentage(n),
+            },
+        ] as any,
+    },
+    {
+        title: '药品费',
+        children: [
+            {
+                title: '金额',
+                dataIndex: 'drugExpense',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '占比',
+                dataIndex: 'drugExpenseRatio',
+                align: 'right',
+                renderText: (n: any) => formatToPercentage(n),
+            },
+        ] as any,
+    },
+    {
+        title: '固定资产折旧费',
+        children: [
+            {
+                title: '金额',
+                dataIndex: 'fixedAssetDepreciation',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '占比',
+                dataIndex: 'fixedAssetDepreciationRatio',
+                align: 'right',
+                renderText: (n: any) => formatToPercentage(n),
+            },
+        ] as any,
+    },
+    {
+        title: '无形资产摊销费',
+        children: [
+            {
+                title: '金额',
+                dataIndex: 'intangibleAssetAmortization',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '占比',
+                dataIndex: 'intangibleAssetAmortizationRatio',
+                align: 'right',
+                renderText: (n: any) => formatToPercentage(n),
+            },
+        ] as any,
+    },
+    {
+        title: '提取医疗风险基金',
+        children: [
+            {
+                title: '金额',
+                dataIndex: 'medicalRiskFund',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '占比',
+                dataIndex: 'medicalRiskFundRatio',
+                align: 'right',
+                renderText: (n: any) => formatToPercentage(n),
+            },
+        ] as any,
+    },
+    {
+        title: '其他医疗费用',
+        children: [
+            {
+                title: '金额',
+                dataIndex: 'otherMedicalExpenses',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '占比',
+                dataIndex: 'otherMedicalExpensesRatio',
+                align: 'right',
+                renderText: (n: any) => formatToPercentage(n),
+            },
+        ] as any,
+    },
+];
+
+// 医院科室床日成本表
+export const hospitalDeptBedDayCost: ProColumns[] = [
+    {
+        title: '科室编码',
+        dataIndex: 'responsibilityCode',
+        width: 140,
+        fixed: 'left' as any,
+    },
+    {
+        title: '科室名称',
+        dataIndex: 'responsibilityName',
+        width: 180,
+        fixed: 'left' as any,
+    },
+    {
+        title: '服务量',
+        dataIndex: 'serviceCount',
+        align: 'right',
+    },
+    {
+        title: '每床日成本',
+        children: [
+            {
+                title: '医疗成本',
+                dataIndex: 'medicalCost',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '医疗全成本',
+                dataIndex: 'medicalFullCost',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+            {
+                title: '医院全成本',
+                dataIndex: 'hospitalFullCost',
+                align: 'right',
+                renderText(num: any, record: any) {
+                    const { decimalPlace, permil } = record || {};
+                    return formatMoneyNumber(num, { decimalPlaces: (typeof decimalPlace === 'number') ? decimalPlace : 2, useThousandSeparator: permil === 1 || permil === true || true });
+                },
+            },
+        ] as any,
+    },
+];
+
+// 临床服务类科室全成本(医疗成本)
+export const clinicalDeptMedicalCost: ProColumns[] = [
+    {
+        title: '科室名称',
+        dataIndex: 'responsibilityName',
+        width: 180,
+        fixed: 'left' as any,
+    },
+    {
+        title: '人员经费(1)',
+        children: [
+            { title: '直接成本', dataIndex: 'personnelDirectCost', align: 'right', renderText: (n: string | number | null | undefined, r: { decimalPlace: number | undefined; }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '间接成本', dataIndex: 'personnelIndirectCost', align: 'right', renderText: (n: string | number | null | undefined, r: { decimalPlace: number | undefined; }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '全成本', dataIndex: 'personnelTotalCost', align: 'right', renderText: (n: string | number | null | undefined, r: { decimalPlace: number | undefined; }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+        ] as any,
+    },
+    {
+        title: '卫生材料费(2)',
+        children: [
+            { title: '直接成本', dataIndex: 'healthMaterialDirectCost', align: 'right', renderText: (n: string | number | null | undefined, r: { decimalPlace: number | undefined; }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '间接成本', dataIndex: 'healthMaterialIndirectCost', align: 'right', renderText: (n: string | number | null | undefined, r: { decimalPlace: number | undefined; }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '全成本', dataIndex: 'healthMaterialTotalCost', align: 'right', renderText: (n: string | number | null | undefined, r: { decimalPlace: number | undefined; }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+        ] as any,
+    },
+    {
+        title: '药品费(3)',
+        children: [
+            { title: '直接成本', dataIndex: 'medicineDirectCost', align: 'right', renderText: (n: string | number | null | undefined, r: { decimalPlace: number | undefined; }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '间接成本', dataIndex: 'medicineIndirectCost', align: 'right', renderText: (n: string | number | null | undefined, r: { decimalPlace: number | undefined; }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '全成本', dataIndex: 'medicineTotalCost', align: 'right', renderText: (n: string | number | null | undefined, r: { decimalPlace: number | undefined; }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+        ] as any,
+    },
+    {
+        title: '固定资产折旧费(4)',
+        children: [
+            { title: '直接成本', dataIndex: 'fixedAssetDepreciationDirect', align: 'right', renderText: (n: string | number | null | undefined, r: { decimalPlace: number | undefined; }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '间接成本', dataIndex: 'fixedAssetDepreciationIndirect', align: 'right', renderText: (n: string | number | null | undefined, r: { decimalPlace: number | undefined; }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '全成本', dataIndex: 'fixedAssetDepreciationTotal', align: 'right', renderText: (n: string | number | null | undefined, r: { decimalPlace: number | undefined; }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+        ] as any,
+    },
+    {
+        title: '无形资产摊销费(5)',
+        children: [
+            { title: '直接成本', dataIndex: 'intangibleAssetAmortizationDirect', align: 'right', renderText: (n: string | number | null | undefined, r: { decimalPlace: number | undefined; }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '间接成本', dataIndex: 'intangibleAssetAmortizationIndirect', align: 'right', renderText: (n: string | number | null | undefined, r: { decimalPlace: number | undefined; }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '全成本', dataIndex: 'intangibleAssetAmortizationTotal', align: 'right', renderText: (n: string | number | null | undefined, r: { decimalPlace: number | undefined; }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+        ] as any,
+    },
+    {
+        title: '提取医疗风险基金(6)',
+        children: [
+            { title: '直接成本', dataIndex: 'medicalRiskReserveDirect', align: 'right', renderText: (n: string | number | null | undefined, r: { decimalPlace: number | undefined; }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '间接成本', dataIndex: 'medicalRiskReserveIndirect', align: 'right', renderText: (n: string | number | null | undefined, r: { decimalPlace: number | undefined; }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '全成本', dataIndex: 'medicalRiskReserveTotalCost', align: 'right', renderText: (n: string | number | null | undefined, r: { decimalPlace: number | undefined; }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+        ] as any,
+    },
+    {
+        title: '其他医疗费用(7)',
+        children: [
+            { title: '直接成本', dataIndex: 'otherMedicalExpensesDirect', align: 'right', renderText: (n: string | number | null | undefined, r: { decimalPlace: number | undefined; }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '间接成本', dataIndex: 'otherMedicalExpensesIndirect', align: 'right', renderText: (n: string | number | null | undefined, r: { decimalPlace: number | undefined; }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '全成本', dataIndex: 'otherMedicalExpensesTotalCost', align: 'right', renderText: (n: string | number | null | undefined, r: { decimalPlace: number | undefined; }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+        ] as any,
+    },
+    {
+        title: '合计(8)=(1)+(2)+(3)+(4)+(5)+(6)+(7)',
+        children: [
+            { title: '直接成本', dataIndex: 'totalDirectCost', align: 'right', width: 130, fixed: 'right' as any, renderText: (n: string | number | null | undefined, r: { decimalPlace: number | undefined; }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '间接成本', dataIndex: 'totalIndirectCost', align: 'right', width: 130, fixed: 'right' as any, renderText: (n: string | number | null | undefined, r: { decimalPlace: number | undefined; }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '全成本', dataIndex: 'totalCost', align: 'right', width: 150, fixed: 'right' as any, renderText: (n: string | number | null | undefined, r: { decimalPlace: number | undefined; }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+        ] as any,
+    },
+];
+
+// 临床服务类科室全成本(全成本)
+export const clinicalDeptFullCost: ProColumns[] = [
+    {
+        title: '科室名称',
+        dataIndex: 'responsibilityName',
+        width: 180,
+        fixed: 'left' as any,
+    },
+    {
+        title: '医疗成本合计(1)',
+        children: [
+            { title: '直接成本', dataIndex: 'medicalCostTotalDirect', align: 'right', width: 130, renderText: (n: string | number | null | undefined, r: { decimalPlace?: number }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '间接成本', dataIndex: 'medicalCostTotalIndirect', align: 'right', width: 130, renderText: (n: string | number | null | undefined, r: { decimalPlace?: number }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '全成本', dataIndex: 'medicalCostTotal', align: 'right', width: 150, renderText: (n: string | number | null | undefined, r: { decimalPlace?: number }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+        ] as any,
+    },
+    {
+        title: '财政项目拨款经费形成的各项费用(2)',
+        children: [
+            { title: '直接成本', dataIndex: 'financialProjectFundsDirect', align: 'right', width: 130, renderText: (n: string | number | null | undefined, r: { decimalPlace?: number }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '间接成本', dataIndex: 'financialProjectFundsIndirect', align: 'right', width: 130, renderText: (n: string | number | null | undefined, r: { decimalPlace?: number }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '全成本', dataIndex: 'financialProjectFunds', align: 'right', width: 150, renderText: (n: string | number | null | undefined, r: { decimalPlace?: number }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+        ] as any,
+    },
+    {
+        title: '费统计财政拨款项目经费形成的各项费用(3)',
+        children: [
+            { title: '直接成本', dataIndex: 'nonPeerFinancialFundsDirect', align: 'right', width: 130, renderText: (n: string | number | null | undefined, r: { decimalPlace?: number }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '间接成本', dataIndex: 'nonPeerFinancialFundsIndirect', align: 'right', width: 130, renderText: (n: string | number | null | undefined, r: { decimalPlace?: number }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '全成本', dataIndex: 'nonPeerFinancialFunds', align: 'right', width: 150, renderText: (n: string | number | null | undefined, r: { decimalPlace?: number }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+        ] as any,
+    },
+    {
+        title: '医疗全成本合计(4)=(1)+(2)+(3)',
+        children: [
+            { title: '直接成本', dataIndex: 'medicalTotalCostDirect', align: 'right', width: 130, renderText: (n: string | number | null | undefined, r: { decimalPlace?: number }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '间接成本', dataIndex: 'medicalTotalCostIndirect', align: 'right', width: 130, renderText: (n: string | number | null | undefined, r: { decimalPlace?: number }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '全成本', dataIndex: 'medicalTotalCost', align: 'right', width: 150, renderText: (n: string | number | null | undefined, r: { decimalPlace?: number }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+        ] as any,
+    },
+    {
+        title: '科教经费形成的各项费用(5)',
+        children: [
+            { title: '直接成本', dataIndex: 'educationalExpensesDirect', align: 'right', width: 130, renderText: (n: string | number | null | undefined, r: { decimalPlace?: number }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '间接成本', dataIndex: 'educationalExpensesIndirect', align: 'right', width: 130, renderText: (n: string | number | null | undefined, r: { decimalPlace?: number }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '全成本', dataIndex: 'educationalExpenses', align: 'right', width: 150, renderText: (n: string | number | null | undefined, r: { decimalPlace?: number }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+        ] as any,
+    },
+    {
+        title: '资产处置费、上缴上级费用、对附属单位补助费用、其他费用等(6)',
+        children: [
+            { title: '直接成本', dataIndex: 'assetDisposalFeesDirect', align: 'right', width: 130, renderText: (n: string | number | null | undefined, r: { decimalPlace?: number }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '间接成本', dataIndex: 'assetDisposalFeesIndirect', align: 'right', width: 130, renderText: (n: string | number | null | undefined, r: { decimalPlace?: number }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '全成本', dataIndex: 'assetDisposalFees', align: 'right', width: 150, renderText: (n: string | number | null | undefined, r: { decimalPlace?: number }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+        ] as any,
+    },
+    {
+        title: '医院全成本(7)=(4)+(5)+(6)',
+        children: [
+            { title: '直接成本', dataIndex: 'hospitalTotalCostDirect', align: 'right', width: 130, fixed: 'right' as any, renderText: (n: string | number | null | undefined, r: { decimalPlace?: number }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '间接成本', dataIndex: 'hospitalTotalCostIndirect', align: 'right', width: 130, fixed: 'right' as any, renderText: (n: string | number | null | undefined, r: { decimalPlace?: number }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+            { title: '全成本', dataIndex: 'hospitalTotalCost', align: 'right', width: 160, fixed: 'right' as any, renderText: (n: string | number | null | undefined, r: { decimalPlace?: number }) => formatMoneyNumber(n, { decimalPlaces: (typeof r?.decimalPlace === 'number') ? r.decimalPlace : 2, useThousandSeparator: true }) },
+        ] as any,
+    },
+];
+
 export const departmentOperatingReport: ProColumns[] = [
     {
         title: '年份',

ファイルの差分が大きいため隠しています
+ 488 - 447
src/pages/costAccounting/calcPageTemplate/index.tsx


+ 127 - 35
src/pages/costAccounting/calcPageTemplate/service.ts

@@ -10,6 +10,7 @@
 
 
 import axios from 'axios';
+import { downloadExcel } from '@/utils/download';
 import { request } from 'umi';
 
 //获取table列表数据
@@ -168,6 +169,129 @@ export const getCalcPageTableData = (params: any) => {
       params:{...next}
     });
   }
+  // 医院科室直接成本表(医疗成本)
+  if(calcPageKey == 'deptDirectMedicalCost'){
+    return request('/costAccount/standardReport/getDeptDirectMedicalCost', {
+      method: 'GET',
+      params:{...next}
+    });
+  }
+  if(calcPageKey == 'deptFullDirectCost'){
+    return request('/costAccount/standardReport/getDeptFullDirectCost', {
+      method: 'GET',
+      params:{...next}
+    });
+  }
+  if(calcPageKey == 'clinicalDeptMedicalCost'){
+    return request('/costAccount/standardReport/getClinicalDeptMedicalCost', {
+      method: 'GET',
+      params:{...next}
+    });
+  }
+  if(calcPageKey == 'clinicalDeptFullCost'){
+    return request('/costAccount/standardReport/getClinicalDeptFullCost', {
+      method: 'GET',
+      params:{...next}
+    });
+  }
+  // 临床服务类科室全成本构成分析表
+  if(calcPageKey == 'clinicalDeptFullCostAnalysis'){
+    return request('/costAccount/standardReport/getClinicalDeptFullCostAnalysis', {
+      method: 'GET',
+      params:{...next}
+    });
+  }
+  // 医院科室成本分摊汇总表
+  if(calcPageKey == 'deptCostAllocationSummary'){
+    return request('/costAccount/standardReport/getHospitalDeptCostAllocation', {
+      method: 'GET',
+      params:{...next}
+    });
+  }
+  // 医院诊次成本构成表
+  if(calcPageKey == 'hospitalVisitCostComposition'){
+    return request('/costAccount/standardReport/getHospitalVisitCostComposition', {
+      method: 'GET',
+      params:{...next}
+    });
+  }
+  // 医院科室诊次成本表
+  if(calcPageKey == 'hospitalDeptVisitCost'){
+    return request('/costAccount/standardReport/getHospitalDeptVisitCost', {
+      method: 'GET',
+      params:{...next}
+    });
+  }
+  // 医院床日成本构成表
+  if(calcPageKey == 'hospitalBedDayCostComposition'){
+    return request('/costAccount/standardReport/getHospitalBedDayCostComposition', {
+      method: 'GET',
+      params:{...next}
+    });
+  }
+  // 医院科室床日成本表
+  if(calcPageKey == 'hospitalDeptBedDayCost'){
+    return request('/costAccount/standardReport/getHospitalDeptBedDayCost', {
+      method: 'GET',
+      params:{...next}
+    });
+  }
+  // 医院医疗服务项目成本汇总表
+  if(calcPageKey == 'hospitalServiceProjectCost'){
+    return request('/costAccount/standardReport/getHospitalServiceProjectCost', {
+      method: 'GET',
+      params:{...next}
+    });
+  }
+  // 医院医疗服务项目成本明细表
+  if(calcPageKey == 'medicalServiceCostDetail'){
+    return request('/costAccount/standardReport/getMedicalServiceCostDetail', {
+      method: 'GET',
+      params:{...next}
+    });
+  }
+  // 医院病种成本明细表
+  if(calcPageKey == 'diseaseCostDetail'){
+    return request('/costAccount/standardReport/getDiseaseCostDetail', {
+      method: 'GET',
+      params:{...next}
+    });
+  }
+  // 医院DRG成本明细表
+  if(calcPageKey == 'drgCostDetail'){
+    return request('/costAccount/standardReport/getDrgCostDetail', {
+      method: 'GET',
+      params:{...next}
+    });
+  }
+  // 医院DRG成本构成明细表
+  if(calcPageKey == 'drgCostCompositionDetail'){
+    return request('/costAccount/standardReport/getDrgCostCompositionDetail', {
+      method: 'GET',
+      params:{...next}
+    });
+  }
+  // 病种成本构成明细表
+  if(calcPageKey == 'diseaseCostCompositionDetail'){
+    return request('/costAccount/standardReport/getDiseaseCostCompositionDetail', {
+      method: 'GET',
+      params:{...next}
+    });
+  }
+  // 服务单元病种成本构成明细表
+  if(calcPageKey == 'deptDiseaseCostCompositionDetail'){
+    return request('/costAccount/standardReport/getDeptDiseaseCostCompositionDetail', {
+      method: 'GET',
+      params:{...next}
+    });
+  }
+  // 医院服务单元DRG成本构成明细表
+  if(calcPageKey == 'deptDrgCostCompositionDetail'){
+    return request('/costAccount/standardReport/getDeptDrgCostCompositionDetail', {
+      method: 'GET',
+      params:{...next}
+    });
+  }
 };
 
 //计算
@@ -362,41 +486,9 @@ export const getUserHasReports = () => {
 
 
 //下载excel
-export const downloadTemplateReq = (pathStr: string,data:any) => {
-
-
-  let path = `/gateway${pathStr}`;
-
-  const userData = localStorage.getItem('userData');
-  const { token = '' } = JSON.parse(userData as string);
-
-  axios({
-    method: 'get',
-    url: path,
-    responseType: 'blob',
-    headers: { token },
-    params:data
-  })
-    .then(function (response) {
-      //console.log({ 'chunk': response });
-      const filename = decodeURI(response.headers["content-disposition"]);
-      const objectUrl = URL.createObjectURL(
-        new Blob([response.data], {
-          type: 'application/vnd.ms-excel',
-        })
-      )
-      const link = document.createElement('a')
-      // 设置导出的文件名称
-      link.download = `${filename}`
-      link.style.display = 'none'
-      link.href = objectUrl
-      link.click()
-      document.body.appendChild(link)
-
-    });
-
-
-
+export const downloadTemplateReq = async (pathStr: string, data: any) => {
+  // 统一封装下载,减少页面内样板
+  return downloadExcel({ path: pathStr, params: data });
 }
 
 

+ 540 - 0
src/pages/costAccounting/calcPageTemplate/strategy.tsx

@@ -0,0 +1,540 @@
+/*
+ * 策略注册表(扩展版,含列增强)
+ */
+import React from 'react';
+import { ProColumns } from '@ant-design/pro-components';
+import {
+  unitPersonnelCostCalc,
+  patientCostCalc,
+  medicalMaterialCostCalc,
+  chargeItemCostCalc,
+  diseaseCostCalculation,
+  DRG_DIPCostCalc,
+  clinicalPathway,
+  medicalOrderItem,
+  incomeCollection,
+  costShare,
+  beforeCollectionSearch,
+  afterCollectionSearch,
+  beforeCostShareSearch,
+  afterCostShareSearch,
+  departmentCostCalculate,
+  wholeHospCostCalculate,
+  projectShareParamsCalc,
+  costShareReportTable,
+  departmentOperatingReport,
+  wholeHospOperatingReport,
+  deptDirectMedicalCost,
+  deptFullDirectCost,
+  clinicalDeptMedicalCost,
+  clinicalDeptFullCost,
+} from './columns';
+import {
+  unitPersonnelCostCalcFilterConf,
+  patientCostCalcFilterConf,
+  medicalMaterialCostCalcFilterConf,
+  chargeItemCostCalcFilterConf,
+  diseaseCostCalculationFilterConf,
+  DRG_DIPCostCalcFilterConf,
+  clinicalPathwayFilterConf,
+  medicalOrderItemFilterConf,
+  beforeCollectionSearchFilterConf,
+  afterCollectionSearchFilterConf,
+  beforeCostShareSearchFilterConf,
+  afterCostShareSearchFilterConf,
+  projectShareParamsCalcFilterConf,
+  projectCostCalcFilterConf,
+  PatientItemCalcFilterConf,
+  standardItemCostCalcFilterConf,
+  patientStandItemCostCalcFilterConf,
+  standItemShareCostCalcFilterConf,
+  departmentCostCalculateFilterConf,
+  deptDirectMedicalCostFilterConf,
+  deptFullDirectCostFilterConf,
+  clinicalDeptMedicalCostFilterConf,
+  clinicalDeptFullCostFilterConf,
+} from './config';
+import { getParamsDataBySysId } from '@/services/getDic';
+
+export interface CalcPageStrategy {
+  key: string;
+  buildColumns: () => ProColumns[];
+  filterConf: any[];
+  scrollX: number;
+  showCalcButton: boolean;
+  enhanceColumns?: (columns: ProColumns[], ctx: any) => ProColumns[];
+  resolveButtons?: (ctx: any) => { [k: string]: boolean };
+  tablePropsBuilder?: (ctx: any) => any;
+  onMount?: (ctx: any) => Promise<void> | void;
+}
+
+const STRATEGIES: Record<string, CalcPageStrategy> = {
+  unitPersonnelCostCalc: {
+    key: 'unitPersonnelCostCalc',
+    buildColumns: () => unitPersonnelCostCalc,
+    filterConf: unitPersonnelCostCalcFilterConf,
+    scrollX: 1000,
+    showCalcButton: true,
+  },
+  patientCostCalc: {
+    key: 'patientCostCalc',
+    buildColumns: () => patientCostCalc,
+    filterConf: patientCostCalcFilterConf,
+    scrollX: 1500,
+    showCalcButton: true,
+  },
+  medicalMaterialCostCalc: {
+    key: 'medicalMaterialCostCalc',
+    buildColumns: () => medicalMaterialCostCalc,
+    filterConf: medicalMaterialCostCalcFilterConf,
+    scrollX: 1200,
+    showCalcButton: true,
+  },
+  chargeItemCostCalc: {
+    key: 'chargeItemCostCalc',
+    buildColumns: () => chargeItemCostCalc,
+    filterConf: chargeItemCostCalcFilterConf,
+    scrollX: 1500,
+    showCalcButton: true,
+  },
+  diseaseCostCalculation: {
+    key: 'diseaseCostCalculation',
+    buildColumns: () => diseaseCostCalculation,
+    filterConf: diseaseCostCalculationFilterConf,
+    scrollX: 1500,
+    showCalcButton: true,
+  },
+  DRG_DIPCostCalc: {
+    key: 'DRG_DIPCostCalc',
+    buildColumns: () => DRG_DIPCostCalc,
+    filterConf: DRG_DIPCostCalcFilterConf,
+    scrollX: 1500,
+    showCalcButton: true,
+  },
+  clinicalPathway: {
+    key: 'clinicalPathway',
+    buildColumns: () => clinicalPathway,
+    filterConf: clinicalPathwayFilterConf,
+    scrollX: 1500,
+    showCalcButton: true,
+  },
+  medicalOrderItem: {
+    key: 'medicalOrderItem',
+    buildColumns: () => medicalOrderItem,
+    filterConf: medicalOrderItemFilterConf,
+    scrollX: 1500,
+    showCalcButton: true,
+  },
+  beforeCollectionSearch: {
+    key: 'beforeCollectionSearch',
+    buildColumns: () => beforeCollectionSearch,
+    filterConf: beforeCollectionSearchFilterConf,
+    scrollX: 1500,
+    showCalcButton: false,
+  },
+  afterCollectionSearch: {
+    key: 'afterCollectionSearch',
+    buildColumns: () => afterCollectionSearch,
+    filterConf: afterCollectionSearchFilterConf,
+    scrollX: 1300,
+    showCalcButton: false,
+  },
+  incomeCollection: {
+    key: 'incomeCollection',
+    buildColumns: () => incomeCollection,
+    filterConf: [],
+    scrollX: 600,
+    showCalcButton: false,
+    enhanceColumns: (cols, ctx) => {
+      const { onIncomeAction } = ctx;
+      return [
+        ...cols,
+        {
+          title: '操作',
+          dataIndex: 'option',
+          width: 90,
+          render: (_: any, record: any) => (
+            <a style={{ fontSize: 14 }} onClick={() => onIncomeAction(record.isCollection, record)}>
+              {record.isCollection ? '撤销归集' : '开始归集'}
+            </a>
+          ),
+        } as ProColumns,
+      ];
+    },
+  },
+  costShare: {
+    key: 'costShare',
+    buildColumns: () => costShare,
+    filterConf: [],
+    scrollX: 600,
+    showCalcButton: false,
+    enhanceColumns: (cols, ctx) => {
+      const { Popconfirm, onShareAction } = ctx;
+      return [
+        ...cols,
+        {
+          title: '操作',
+          align: 'right',
+          dataIndex: 'option',
+          width: '15%',
+          render: (_: any, record: any) =>
+            !record.isAllocation ? (
+              <a style={{ fontSize: 14 }} onClick={() => onShareAction(record.isAllocation, record)}>
+                {record.isAllocation ? '撤销' : '分摊'}
+              </a>
+            ) : (
+              [
+                <Popconfirm title={`是否确认撤销?`} key="del" onConfirm={() => onShareAction(record.isAllocation, record)}>
+                  <a style={{ fontSize: 14 }}>{record.isAllocation ? '撤销' : '分摊'}</a>
+                </Popconfirm>,
+              ]
+            ),
+        } as ProColumns,
+      ];
+    },
+  },
+  beforeCostShareSearch: {
+    key: 'beforeCostShareSearch',
+    buildColumns: () => beforeCostShareSearch,
+    filterConf: beforeCostShareSearchFilterConf,
+    scrollX: 1300,
+    showCalcButton: false,
+  },
+  afterCostShareSearch: {
+    key: 'afterCostShareSearch',
+    buildColumns: () => afterCostShareSearch,
+    filterConf: afterCostShareSearchFilterConf,
+    scrollX: 1000,
+    showCalcButton: false,
+  },
+  departmentCostCalculate: {
+    key: 'departmentCostCalculate',
+    buildColumns: () => departmentCostCalculate,
+    filterConf: departmentCostCalculateFilterConf,
+    scrollX: 1200,
+    showCalcButton: true,
+  },
+  wholeHospCostCalculate: {
+    key: 'wholeHospCostCalculate',
+    buildColumns: () => wholeHospCostCalculate,
+    filterConf: [],
+    scrollX: 1200,
+    showCalcButton: true,
+    onMount: async (ctx: any) => {
+      try {
+        const raw = localStorage.getItem('currentSelectedTab');
+        const parsed = raw ? JSON.parse(raw) : null;
+        const systemId = parsed?.systemId;
+        if (!systemId) return;
+        const resp = await getParamsDataBySysId(systemId, '1851077044079824896');
+        const showPercent = resp && resp.value == '1';
+        if (!showPercent && Array.isArray(ctx?.columns)) {
+          const filtered = (ctx.columns as ProColumns[]).filter((c: any) => c?.title !== '占比');
+          ctx.setColumns && ctx.setColumns(filtered);
+        }
+      } catch {}
+    }
+  },
+  costShareReportTable: {
+    key: 'costShareReportTable',
+    buildColumns: () => costShareReportTable,
+    filterConf: [],
+    scrollX: 1200,
+    showCalcButton: false,
+    enhanceColumns: (cols, ctx) => {
+      const { downloadTemplateReq } = ctx;
+      return [
+        ...cols,
+        {
+          title: '操作',
+          dataIndex: 'option',
+          width: '15%',
+          render: (_: any, record: any) => [
+            <a style={{ fontSize: 14 }} onClick={async () => {
+              const { year, month, shareLevel, shareLevelId } = record;
+              await downloadTemplateReq('/costAccount/excel/getShareReportTemplate', { year, month, levelSort: shareLevel, shareLevelId })
+                .catch(() => {});
+            }}>下载</a>,
+          ],
+        } as ProColumns,
+      ];
+    },
+  },
+  departmentOperatingReport: {
+    key: 'departmentOperatingReport',
+    buildColumns: () => departmentOperatingReport,
+    filterConf: [],
+    scrollX: 1000,
+    showCalcButton: false,
+    enhanceColumns: (cols) => [
+      ...cols,
+      {
+        title: '操作',
+        dataIndex: 'option',
+        width: 100,
+        render: (_: any, record: any) => [
+          <a style={{ fontSize: 14 }} onClick={async () => window.open(record.fileUrl || '')}>下载</a>,
+        ],
+      } as ProColumns,
+    ],
+  },
+  wholeHospOperatingReport: {
+    key: 'wholeHospOperatingReport',
+    buildColumns: () => wholeHospOperatingReport,
+    filterConf: [],
+    scrollX: 1000,
+    showCalcButton: false,
+    enhanceColumns: (cols) => [
+      ...cols,
+      {
+        title: '操作',
+        dataIndex: 'option',
+        width: 100,
+        render: (_: any, record: any) => [
+          <a style={{ fontSize: 14 }} onClick={async () => window.open(record.fileUrl || '')}>下载</a>,
+        ],
+      } as ProColumns,
+    ],
+  },
+  projectShareParamsCalc: {
+    key: 'projectShareParamsCalc',
+    buildColumns: () => projectShareParamsCalc,
+    filterConf: projectShareParamsCalcFilterConf,
+    scrollX: 1600,
+    showCalcButton: true,
+  },
+  standardItemCostCalc: {
+    key: 'standardItemCostCalc',
+    buildColumns: () => [],
+    filterConf: standardItemCostCalcFilterConf,
+    scrollX: 1500,
+    showCalcButton: true,
+  },
+  patientStandItemCostCalc: {
+    key: 'patientStandItemCostCalc',
+    buildColumns: () => [],
+    filterConf: patientStandItemCostCalcFilterConf,
+    scrollX: 1500,
+    showCalcButton: true,
+  },
+  standItemShareCostCalc: {
+    key: 'standItemShareCostCalc',
+    buildColumns: () => [],
+    filterConf: standItemShareCostCalcFilterConf,
+    scrollX: 1500,
+    showCalcButton: true,
+  },
+  patientItemCalc: {
+    key: 'patientItemCalc',
+    buildColumns: () => [],
+    filterConf: PatientItemCalcFilterConf,
+    scrollX: 1500,
+    showCalcButton: true,
+  },
+  projectCostCalc: {
+    key: 'projectCostCalc',
+    buildColumns: () => [],
+    filterConf: projectCostCalcFilterConf,
+    scrollX: 1200,
+    showCalcButton: false,
+    resolveButtons: (ctx: any) => {
+      try {
+        const has = ctx?.pagePermissions?.has?.('calculate');
+        return { calculate: !!has };
+      } catch {
+        return { calculate: false };
+      }
+    },
+  },
+  // 医院科室直接成本表(医疗成本)
+  deptDirectMedicalCost: {
+    key: 'deptDirectMedicalCost',
+    buildColumns: () => deptDirectMedicalCost,
+    filterConf: deptDirectMedicalCostFilterConf,
+    scrollX: 1450,
+    showCalcButton: false,
+    tablePropsBuilder: () => ({ pagination: false,scroll: { y:662 } }),
+  },
+  // 医院科室直接成本表(全成本)
+  deptFullDirectCost: {
+    key: 'deptFullDirectCost',
+    buildColumns: () => deptFullDirectCost,
+    filterConf: deptFullDirectCostFilterConf,
+    scrollX: 1800,
+    showCalcButton: false,
+    tablePropsBuilder: () => ({ pagination: false, scroll: { y: 645 } }),
+  },
+  // 临床服务类科室全成本(医疗成本)
+  clinicalDeptMedicalCost: {
+    key: 'clinicalDeptMedicalCost',
+    buildColumns: () => clinicalDeptMedicalCost,
+    filterConf: clinicalDeptMedicalCostFilterConf,
+    scrollX: 3000,
+    showCalcButton: false,
+    tablePropsBuilder: () => ({ pagination: false, scroll: { y: 645 } }),
+  },
+  // 临床服务类科室全成本(全成本)
+  clinicalDeptFullCost: {
+    key: 'clinicalDeptFullCost',
+    buildColumns: () => clinicalDeptFullCost,
+    filterConf: clinicalDeptFullCostFilterConf,
+    scrollX: 3000,
+    showCalcButton: false,
+    tablePropsBuilder: () => ({ pagination: false, scroll: { y: 629 } }),
+  },
+  // 医院科室成本分摊汇总表(静态列定义,服务端仅返回数据)
+  deptCostAllocationSummary: {
+    key: 'deptCostAllocationSummary',
+    buildColumns: () => (require('./columns') as any).deptCostAllocationSummary,
+    filterConf: [
+      { type: 'input', label: '科室名称:', placeholder: '请输入', key: 'departmentName' }
+    ],
+    scrollX: 1600,
+    showCalcButton: false,
+    tablePropsBuilder: () => ({ pagination: false, scroll: { y: 610 } }),
+  },
+  // 临床服务类科室全成本构成分析表(动态表头+数据)
+  clinicalDeptFullCostAnalysis: {
+    key: 'clinicalDeptFullCostAnalysis',
+    // 列由接口 title 动态生成,这里返回空数组占位
+    buildColumns: () => [],
+    // 仅时间筛选,无额外条件
+    filterConf: [],
+    scrollX: 800,
+    showCalcButton: false,
+    tablePropsBuilder: () => ({ pagination: false, scroll: { y: 645 } }),
+  },
+  // 医院诊次成本构成表
+  hospitalVisitCostComposition: {
+    key: 'hospitalVisitCostComposition',
+    buildColumns: () => (require('./columns') as any).hospitalVisitCostComposition,
+    filterConf: [],
+    scrollX: 1200,
+    showCalcButton: false,
+    tablePropsBuilder: () => ({ pagination: false, scroll: { y: 645 } }),
+  },
+  // 医院科室诊次成本表
+  hospitalDeptVisitCost: {
+    key: 'hospitalDeptVisitCost',
+    buildColumns: () => (require('./columns') as any).hospitalDeptVisitCost,
+    filterConf: [
+      { type: 'input', label: '科室名称:', placeholder: '请输入', key: 'departmentName' }
+    ],
+    scrollX: 1200,
+    showCalcButton: false,
+    tablePropsBuilder: () => ({ pagination: false, scroll: { y: 645 } }),
+  },
+  // 医院科室床日成本表
+  hospitalDeptBedDayCost: {
+    key: 'hospitalDeptBedDayCost',
+    buildColumns: () => (require('./columns') as any).hospitalDeptBedDayCost,
+    filterConf: [
+      { type: 'input', label: '科室名称:', placeholder: '请输入', key: 'departmentName' }
+    ],
+    scrollX: 1200,
+    showCalcButton: false,
+    tablePropsBuilder: () => ({ pagination: false, scroll: { y: 645 } }),
+  },
+  // 医院床日成本构成表
+  hospitalBedDayCostComposition: {
+    key: 'hospitalBedDayCostComposition',
+    buildColumns: () => (require('./columns') as any).hospitalBedDayCostComposition,
+    filterConf: [],
+    scrollX: 1200,
+    showCalcButton: false,
+    tablePropsBuilder: () => ({ pagination: false, scroll: { y: 645 } }),
+  },
+  // 医院医疗服务项目成本汇总表
+  hospitalServiceProjectCost: {
+    key: 'hospitalServiceProjectCost',
+    buildColumns: () => (require('./columns') as any).hospitalServiceProjectCost,
+    filterConf: [],
+    scrollX: 1200,
+    showCalcButton: false,
+    tablePropsBuilder: () => ({ pagination: false, scroll: { y: 700 } }),
+  },
+  // 医院医疗服务项目成本明细表
+  medicalServiceCostDetail: {
+    key: 'medicalServiceCostDetail',
+    buildColumns: () => (require('./columns') as any).medicalServiceCostDetail,
+    filterConf: [],
+    scrollX: 1200,
+    showCalcButton: false,
+    tablePropsBuilder: () => ({ pagination: false, scroll: { y: 661 } }),
+  },
+  // 医院病种成本明细表
+  diseaseCostDetail: {
+    key: 'diseaseCostDetail',
+    buildColumns: () => (require('./columns') as any).diseaseCostDetail,
+    filterConf: [
+      { type: 'input', label: '病种名称:', placeholder: '请输入', key: 'itemName' }
+    ],
+    scrollX: 1200,
+    showCalcButton: false,
+    tablePropsBuilder: () => ({ pagination: false, scroll: { y: 661 } }),
+  },
+  // 病种成本构成明细表
+  diseaseCostCompositionDetail: {
+    key: 'diseaseCostCompositionDetail',
+    buildColumns: () => (require('./columns') as any).diseaseCostCompositionDetail,
+    filterConf: [
+      { type: 'input', label: '病种名称:', placeholder: '请输入', key: 'itemName' }
+    ],
+    scrollX: 2200,
+    showCalcButton: false,
+    tablePropsBuilder: () => ({ pagination: false, scroll: { y: 661 } }),
+  },
+  // 医院DRG成本明细表
+  drgCostDetail: {
+    key: 'drgCostDetail',
+    buildColumns: () => (require('./columns') as any).drgCostDetail,
+    filterConf: [
+      { type: 'input', label: 'DRG名称:', placeholder: '请输入', key: 'itemName' }
+    ],
+    scrollX: 1200,
+    showCalcButton: false,
+    tablePropsBuilder: () => ({ pagination: false, scroll: { y: 661 } }),
+  },
+  // 医院DRG成本构成明细表
+  drgCostCompositionDetail: {
+    key: 'drgCostCompositionDetail',
+    buildColumns: () => (require('./columns') as any).drgCostCompositionDetail,
+    filterConf: [
+      { type: 'input', label: 'DRG名称:', placeholder: '请输入', key: 'itemName' }
+    ],
+    scrollX: 2200,
+    showCalcButton: false,
+    tablePropsBuilder: () => ({ pagination: false, scroll: { y: 661 } }),
+  },
+  // 医院服务单元DRG成本构成明细表(动态多级表头+数据)
+  deptDrgCostCompositionDetail: {
+    key: 'deptDrgCostCompositionDetail',
+    buildColumns: () => [],
+    filterConf: [
+      { type: 'input', label: 'DRG名称:', placeholder: '请输入', key: 'itemName' }
+    ],
+    scrollX: 1200,
+    showCalcButton: false,
+    tablePropsBuilder: () => ({ pagination: false, scroll: { y: 661 } }),
+  },
+  // 服务单元病种成本构成明细表(动态表头+数据)
+  deptDiseaseCostCompositionDetail: {
+    key: 'deptDiseaseCostCompositionDetail',
+    // 列由接口 title 动态生成,这里返回空数组占位
+    buildColumns: () => [],
+    filterConf: [
+      { type: 'input', label: '病种名称:', placeholder: '请输入', key: 'itemName' }
+    ],
+    scrollX: 1200,
+    showCalcButton: false,
+    tablePropsBuilder: () => ({ pagination: false, scroll: { y: 661 } }),
+  },
+};
+
+export const getCalcPageStrategy = (calcPageKey?: string) => {
+  if (!calcPageKey) return undefined;
+  return STRATEGIES[calcPageKey];
+};
+
+
+

+ 40 - 0
src/utils/download.ts

@@ -0,0 +1,40 @@
+/*
+ * 下载封装:统一 token 注入与错误处理
+ */
+
+import axios from 'axios';
+
+export interface DownloadParams {
+  path: string;
+  params?: any;
+}
+
+export async function downloadExcel({ path, params }: DownloadParams) {
+  try {
+    const userData = localStorage.getItem('userData');
+    const { token = '' } = userData ? JSON.parse(userData) : { token: '' };
+    const url = `/gateway${path}`;
+
+    const response = await axios({
+      method: 'get',
+      url,
+      responseType: 'blob',
+      headers: { token },
+      params,
+    });
+
+    const filename = decodeURI(response.headers['content-disposition'] || 'download.xlsx');
+    const objectUrl = URL.createObjectURL(new Blob([response.data], { type: 'application/vnd.ms-excel' }));
+    const link = document.createElement('a');
+    link.download = `${filename}`;
+    link.style.display = 'none';
+    link.href = objectUrl;
+    link.click();
+    document.body.appendChild(link);
+  } catch (err) {
+    // 统一在调用方做 message.error,避免此处直接耦合 UI
+    throw err;
+  }
+}
+
+

+ 119 - 0
src/utils/exporter.ts

@@ -0,0 +1,119 @@
+/*
+ * 导出相关工具(最小落地版)
+ * 仅抽离现有 calcPageTemplate 的导出格式化逻辑,避免页面过重
+ */
+
+import { ProColumns } from '@ant-design/pro-components';
+import exportTableToExcel from './tableToExcel';
+import { formatMoneyNumber, formatToPercentage } from './format';
+
+export interface ExportOptions {
+  // 页面 key,用于处理特殊格式化
+  calcPageKey?: string;
+  // 是否为树形数据时需要缩进的列
+  indentColumn?: string;
+}
+
+/**
+ * 根据当前列与数据,进行必要的格式化与树形缩进,并导出为 Excel
+ * 注意:本方法内的“wholeHospCostCalculate”逻辑与原实现保持一致,避免行为变化
+ */
+export function exportCalcPageDataToExcel(
+  columns: ProColumns[],
+  totalData: any[],
+  options: ExportOptions = {}
+) {
+  const { calcPageKey, indentColumn } = options;
+
+  // 生成表头
+  const headers: { [key: string]: any } = {};
+  columns.forEach((a: any) => {
+    headers[`${a.dataIndex}`] = a.title;
+  });
+
+  // 列配置索引,便于判断数值列
+  const columnMap = new Map<string, any>();
+  (columns as any[])?.forEach?.((c) => {
+    if (c && c.dataIndex) columnMap.set(String(c.dataIndex), c);
+  });
+
+  // 递归处理数据(支持 children),并为 indentColumn 添加缩进
+  const data: any[] = [];
+  const processData = (items: any[], level: number = 0) => {
+    items.forEach(item => {
+      const row: { [key: string]: any } = {};
+      Object.keys(headers).forEach(key => {
+        if (indentColumn && key === indentColumn) {
+          // 使用 Unicode 不可见空格来缩进
+          row[`${key}`] = `${'\u00A0'.repeat(level * 4)}${item[`${key}`]}`;
+        } else if (item.children && item.children.length > 0 && (calcPageKey !== 'wholeHospCostCalculate')) {
+          // 非全院损益时,有 children 的父节点导出为空字符串
+          row[`${key}`] = '';
+        } else {
+          if (calcPageKey === 'wholeHospCostCalculate') {
+            if (key === 'amount') {
+              const { decimalPlace, permil, dataType, children, calcType } = item;
+              if (children && children.length > 0 && calcType === '0') {
+                row[`${key}`] = '';
+              } else if (dataType === 2) {
+                row[`${key}`] = typeof item[`${key}`] === 'number' && !isNaN(item[`${key}`])
+                  ? `${(item[`${key}`] * 100).toFixed(decimalPlace || 2)}%`
+                  : item[`${key}`];
+              } else {
+                const formatOptions = {
+                  decimalPlaces: decimalPlace !== undefined ? decimalPlace : 2,
+                  useThousandSeparator: permil === 1,
+                };
+                row[`${key}`] = formatMoneyNumber(item[`${key}`], formatOptions);
+              }
+            } else if (key === 'percent') {
+              const { children, calcType } = item;
+              row[`${key}`] = (children && children.length > 0 && calcType === '0')
+                ? ''
+                : formatToPercentage(item[`${key}`]);
+            } else {
+              row[`${key}`] = item[`${key}`];
+            }
+          } else if (calcPageKey === 'deptDirectMedicalCost' || calcPageKey === 'deptFullDirectCost' || calcPageKey === 'hospitalVisitCostComposition' || calcPageKey === 'hospitalDeptVisitCost' || calcPageKey === 'deptCostAllocationSummary' || calcPageKey === 'hospitalBedDayCostComposition') {
+            // 这两个页面导出需要默认启用千分号,并按行的 decimalPlace 控制小数位
+            const col = columnMap.get(key);
+            const isNumericColumn = col && (col.align === 'right');
+            if (isNumericColumn) {
+              const decimalPlaces = (typeof item?.decimalPlace === 'number') ? item.decimalPlace : 2;
+              row[`${key}`] = formatMoneyNumber(item[`${key}`], { decimalPlaces, useThousandSeparator: true });
+            } else {
+              row[`${key}`] = item[`${key}`];
+            }
+          } else {
+            row[`${key}`] = item[`${key}`];
+          }
+        }
+      });
+
+      data.push(row);
+      if (item.children && item.children.length > 0) {
+        processData(item.children, level + 1);
+      }
+    });
+  };
+
+  processData(totalData);
+
+  // 根据页面 key 设定导出文件名
+  const fileNameMap: Record<string, string> = {
+    wholeHospCostCalculate: '全院损益报表',
+    projectCostCalc: '项目成本计算',
+    deptDirectMedicalCost: '医院科室直接成本表(医疗成本)',
+    deptFullDirectCost: '医院科室直接成本表(全成本)',
+    clinicalDeptMedicalCost: '临床服务类科室全成本(医疗成本)',
+    clinicalDeptFullCost: '临床服务类科室全成本(全成本)',
+    clinicalDeptFullCostAnalysis: '临床服务类科室全成本构成分析表',
+    deptCostAllocationSummary: '医院科室成本分摊汇总表',
+    hospitalVisitCostComposition: '医院诊次成本构成表',
+    hospitalBedDayCostComposition: '医院床日成本构成表',
+  };
+  const fileName = (calcPageKey && fileNameMap[calcPageKey]) ? fileNameMap[calcPageKey] : '项目成本计算';
+  exportTableToExcel(data, columns as any[], fileName);
+}
+
+

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません