Bläddra i källkod

fixed:调整组织切换,无默认值时的报错

code4eat 6 månader sedan
förälder
incheckning
edc61abdf7

+ 35 - 0
.eslintrc.js

@@ -0,0 +1,35 @@
+module.exports = {
+  extends: [
+    'eslint:recommended',
+    'plugin:@typescript-eslint/recommended',
+    'plugin:react/recommended',
+    'plugin:react-hooks/recommended',
+    'prettier'
+  ],
+  plugins: ['@typescript-eslint', 'react', 'react-hooks', 'prettier'],
+  env: {
+    browser: true,
+    es2021: true,
+    node: true
+  },
+  settings: {
+    react: {
+      version: 'detect'
+    }
+  },
+  parser: '@typescript-eslint/parser',
+  parserOptions: {
+    ecmaFeatures: {
+      jsx: true
+    },
+    ecmaVersion: 12,
+    sourceType: 'module'
+  },
+  rules: {
+    'prettier/prettier': 'error',
+    'react/react-in-jsx-scope': 'off',
+    '@typescript-eslint/explicit-module-boundary-types': 'off',
+    '@typescript-eslint/no-explicit-any': 'warn',
+    'react/prop-types': 'off'
+  }
+}; 

+ 8 - 2
.prettierrc

@@ -1,7 +1,13 @@
 {
+  "semi": true,
   "singleQuote": true,
-  "trailingComma": "all",
-  "printWidth": 200,
+  "trailingComma": "es5",
+  "printWidth": 100,
+  "tabWidth": 2,
+  "useTabs": false,
+  "bracketSpacing": true,
+  "arrowParens": "avoid",
+  "endOfLine": "lf",
   "overrides": [
     {
       "files": ".prettierrc",

+ 188 - 1
README.md

@@ -1,6 +1,193 @@
+# 医院中台系统
 
+![项目Logo](./public/projectlogo.jpg)
 
-![image-20211126105817314](./public/projectlogo.jpg)
+## 项目简介
+
+医院中台系统是一个基于 React 和 TypeScript 开发的现代化医疗管理平台。该系统采用微前端架构,提供统一的用户界面和业务功能。
+
+## 技术栈
+
+- React 18
+- TypeScript
+- UmiJS 3
+- Ant Design 4
+- Monaco Editor
+- 微前端架构(qiankun)
+
+## 开发环境要求
+
+- Node.js >= 14
+- Yarn >= 1.22
+- Git
+
+## 快速开始
+
+### 安装依赖
+
+```bash
+yarn install
+```
+
+### 开发环境运行
+
+```bash
+# 包含 mock 数据
+yarn start
+
+# 开发环境(使用真实接口)
+yarn start:dev
+```
+
+### 构建生产环境
+
+```bash
+yarn build
+```
+
+### 代码质量检查
+
+```bash
+# 运行 ESLint 检查
+yarn lint
+
+# 自动修复 ESLint 问题
+yarn lint:fix
+
+# 运行 Prettier 格式化
+yarn prettier
+```
+
+## 项目结构
+
+```
+.
+├── config/                 # 项目配置文件
+├── mock/                   # Mock 数据
+├── public/                 # 静态资源
+├── src/                    # 源代码
+│   ├── components/        # 公共组件
+│   ├── layouts/          # 布局组件
+│   ├── pages/            # 页面组件
+│   ├── services/         # API 服务
+│   └── utils/            # 工具函数
+├── .eslintrc.js          # ESLint 配置
+├── .prettierrc           # Prettier 配置
+├── package.json          # 项目依赖
+└── tsconfig.json         # TypeScript 配置
+```
+
+## 开发规范
+
+### 代码风格
+
+- 使用 ESLint 和 Prettier 进行代码格式化
+- 遵循 TypeScript 严格模式
+- 使用函数组件和 Hooks
+- 遵循 React 最佳实践
+
+### Git 提交规范
+
+提交信息格式:
+```
+<type>(<scope>): <subject>
+
+<body>
+
+<footer>
+```
+
+type 类型:
+- feat: 新功能
+- fix: 修复
+- docs: 文档
+- style: 格式
+- refactor: 重构
+- test: 测试
+- chore: 构建过程或辅助工具的变动
+
+### 分支管理
+
+- master: 主分支
+- dev: 开发分支
+- feature/*: 功能分支
+- hotfix/*: 紧急修复分支
+
+## 部署
+
+### 环境要求
+
+- Node.js >= 14
+- Nginx >= 1.18
+
+### 部署步骤
+
+1. 构建项目
+```bash
+yarn build
+```
+
+2. 配置 Nginx
+```nginx
+server {
+    listen 80;
+    server_name your-domain.com;
+
+    location / {
+        root /path/to/dist;
+        try_files $uri $uri/ /index.html;
+    }
+}
+```
+
+## 常见问题
+
+### 1. 安装依赖失败
+
+解决方案:
+```bash
+# 清除 yarn 缓存
+yarn cache clean
+
+# 重新安装依赖
+yarn install
+```
+
+### 2. 开发环境接口代理
+
+在 `config/proxy.ts` 中配置代理规则:
+```typescript
+export default {
+  dev: {
+    '/api/': {
+      target: 'http://your-api-server',
+      changeOrigin: true,
+      pathRewrite: { '^/api': '' },
+    },
+  },
+};
+```
+
+## 贡献指南
+
+1. Fork 项目
+2. 创建功能分支
+3. 提交变更
+4. 发起 Pull Request
+
+## 版本历史
+
+- v1.0.0 (2023-12-01)
+  - 初始版本发布
+  - 基础功能实现
+
+## 维护者
+
+- 开发团队
+
+## 许可证
+
+MIT License
 
 
 

+ 2 - 1
config/config.ts

@@ -1,7 +1,7 @@
 /*
  * @Author: your name
  * @Date: 2022-01-07 10:04:20
- * @LastEditTime: 2025-03-27 11:12:39
+ * @LastEditTime: 2025-04-08 11:45:42
  * @LastEditors: code4eat awesomedema@gmail.com
  * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  * @FilePath: /KC-MiddlePlatform/config/config.ts
@@ -11,6 +11,7 @@ import { defineConfig } from 'umi';
 
 import proxy from './proxy';
 
+
 const { REACT_APP_ENV } = process.env;
 
 export default defineConfig({

+ 4 - 4
config/proxy.ts

@@ -2,7 +2,7 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2024-04-09 18:07:34
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2025-03-27 14:03:32
+ * @LastEditTime: 2025-05-14 15:20:33
  * @FilePath: /KC-MiddlePlatform/config/proxy.ts
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -11,13 +11,13 @@ const proxy: { [key: string]: any } = {
   dev: {
     '/gateway': {
       //target: 'http://47.96.149.190:5000',
-      target: 'http://120.27.235.181:5000',
-      //target: 'http://platform.pre.bs.qjczt.com:5000',
+      //target: 'http://120.27.235.181:5000',
+      target: 'http://platform.pre.bs.qjczt.com:5000',
       changeOrigin: true,
       // pathRewrite: { '^/master': '' },
     },
     '/api': {
-      target: 'http://localhost:8088',
+      target: 'http://120.27.235.181:8088',
       changeOrigin: true,
     },
     '/ai': {

+ 6 - 0
coverage/clover.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<coverage generated="1745983496334" clover="3.2.0">
+  <project timestamp="1745983496334" name="All files">
+    <metrics statements="0" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="0" coveredmethods="0" elements="0" coveredelements="0" complexity="0" loc="0" ncloc="0" packages="0" files="0" classes="0"/>
+  </project>
+</coverage>

+ 1 - 0
coverage/coverage-final.json

@@ -0,0 +1 @@
+{}

+ 224 - 0
coverage/lcov-report/base.css

@@ -0,0 +1,224 @@
+body, html {
+  margin:0; padding: 0;
+  height: 100%;
+}
+body {
+    font-family: Helvetica Neue, Helvetica, Arial;
+    font-size: 14px;
+    color:#333;
+}
+.small { font-size: 12px; }
+*, *:after, *:before {
+  -webkit-box-sizing:border-box;
+     -moz-box-sizing:border-box;
+          box-sizing:border-box;
+  }
+h1 { font-size: 20px; margin: 0;}
+h2 { font-size: 14px; }
+pre {
+    font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
+    margin: 0;
+    padding: 0;
+    -moz-tab-size: 2;
+    -o-tab-size:  2;
+    tab-size: 2;
+}
+a { color:#0074D9; text-decoration:none; }
+a:hover { text-decoration:underline; }
+.strong { font-weight: bold; }
+.space-top1 { padding: 10px 0 0 0; }
+.pad2y { padding: 20px 0; }
+.pad1y { padding: 10px 0; }
+.pad2x { padding: 0 20px; }
+.pad2 { padding: 20px; }
+.pad1 { padding: 10px; }
+.space-left2 { padding-left:55px; }
+.space-right2 { padding-right:20px; }
+.center { text-align:center; }
+.clearfix { display:block; }
+.clearfix:after {
+  content:'';
+  display:block;
+  height:0;
+  clear:both;
+  visibility:hidden;
+  }
+.fl { float: left; }
+@media only screen and (max-width:640px) {
+  .col3 { width:100%; max-width:100%; }
+  .hide-mobile { display:none!important; }
+}
+
+.quiet {
+  color: #7f7f7f;
+  color: rgba(0,0,0,0.5);
+}
+.quiet a { opacity: 0.7; }
+
+.fraction {
+  font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
+  font-size: 10px;
+  color: #555;
+  background: #E8E8E8;
+  padding: 4px 5px;
+  border-radius: 3px;
+  vertical-align: middle;
+}
+
+div.path a:link, div.path a:visited { color: #333; }
+table.coverage {
+  border-collapse: collapse;
+  margin: 10px 0 0 0;
+  padding: 0;
+}
+
+table.coverage td {
+  margin: 0;
+  padding: 0;
+  vertical-align: top;
+}
+table.coverage td.line-count {
+    text-align: right;
+    padding: 0 5px 0 20px;
+}
+table.coverage td.line-coverage {
+    text-align: right;
+    padding-right: 10px;
+    min-width:20px;
+}
+
+table.coverage td span.cline-any {
+    display: inline-block;
+    padding: 0 5px;
+    width: 100%;
+}
+.missing-if-branch {
+    display: inline-block;
+    margin-right: 5px;
+    border-radius: 3px;
+    position: relative;
+    padding: 0 4px;
+    background: #333;
+    color: yellow;
+}
+
+.skip-if-branch {
+    display: none;
+    margin-right: 10px;
+    position: relative;
+    padding: 0 4px;
+    background: #ccc;
+    color: white;
+}
+.missing-if-branch .typ, .skip-if-branch .typ {
+    color: inherit !important;
+}
+.coverage-summary {
+  border-collapse: collapse;
+  width: 100%;
+}
+.coverage-summary tr { border-bottom: 1px solid #bbb; }
+.keyline-all { border: 1px solid #ddd; }
+.coverage-summary td, .coverage-summary th { padding: 10px; }
+.coverage-summary tbody { border: 1px solid #bbb; }
+.coverage-summary td { border-right: 1px solid #bbb; }
+.coverage-summary td:last-child { border-right: none; }
+.coverage-summary th {
+  text-align: left;
+  font-weight: normal;
+  white-space: nowrap;
+}
+.coverage-summary th.file { border-right: none !important; }
+.coverage-summary th.pct { }
+.coverage-summary th.pic,
+.coverage-summary th.abs,
+.coverage-summary td.pct,
+.coverage-summary td.abs { text-align: right; }
+.coverage-summary td.file { white-space: nowrap;  }
+.coverage-summary td.pic { min-width: 120px !important;  }
+.coverage-summary tfoot td { }
+
+.coverage-summary .sorter {
+    height: 10px;
+    width: 7px;
+    display: inline-block;
+    margin-left: 0.5em;
+    background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
+}
+.coverage-summary .sorted .sorter {
+    background-position: 0 -20px;
+}
+.coverage-summary .sorted-desc .sorter {
+    background-position: 0 -10px;
+}
+.status-line {  height: 10px; }
+/* yellow */
+.cbranch-no { background: yellow !important; color: #111; }
+/* dark red */
+.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
+.low .chart { border:1px solid #C21F39 }
+.highlighted,
+.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{
+  background: #C21F39 !important;
+}
+/* medium red */
+.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
+/* light red */
+.low, .cline-no { background:#FCE1E5 }
+/* light green */
+.high, .cline-yes { background:rgb(230,245,208) }
+/* medium green */
+.cstat-yes { background:rgb(161,215,106) }
+/* dark green */
+.status-line.high, .high .cover-fill { background:rgb(77,146,33) }
+.high .chart { border:1px solid rgb(77,146,33) }
+/* dark yellow (gold) */
+.status-line.medium, .medium .cover-fill { background: #f9cd0b; }
+.medium .chart { border:1px solid #f9cd0b; }
+/* light yellow */
+.medium { background: #fff4c2; }
+
+.cstat-skip { background: #ddd; color: #111; }
+.fstat-skip { background: #ddd; color: #111 !important; }
+.cbranch-skip { background: #ddd !important; color: #111; }
+
+span.cline-neutral { background: #eaeaea; }
+
+.coverage-summary td.empty {
+    opacity: .5;
+    padding-top: 4px;
+    padding-bottom: 4px;
+    line-height: 1;
+    color: #888;
+}
+
+.cover-fill, .cover-empty {
+  display:inline-block;
+  height: 12px;
+}
+.chart {
+  line-height: 0;
+}
+.cover-empty {
+    background: white;
+}
+.cover-full {
+    border-right: none !important;
+}
+pre.prettyprint {
+    border: none !important;
+    padding: 0 !important;
+    margin: 0 !important;
+}
+.com { color: #999 !important; }
+.ignore-none { color: #999; font-weight: normal; }
+
+.wrapper {
+  min-height: 100%;
+  height: auto !important;
+  height: 100%;
+  margin: 0 auto -48px;
+}
+.footer, .push {
+  height: 48px;
+}

+ 87 - 0
coverage/lcov-report/block-navigation.js

@@ -0,0 +1,87 @@
+/* eslint-disable */
+var jumpToCode = (function init() {
+    // Classes of code we would like to highlight in the file view
+    var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no'];
+
+    // Elements to highlight in the file listing view
+    var fileListingElements = ['td.pct.low'];
+
+    // We don't want to select elements that are direct descendants of another match
+    var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > `
+
+    // Selecter that finds elements on the page to which we can jump
+    var selector =
+        fileListingElements.join(', ') +
+        ', ' +
+        notSelector +
+        missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b`
+
+    // The NodeList of matching elements
+    var missingCoverageElements = document.querySelectorAll(selector);
+
+    var currentIndex;
+
+    function toggleClass(index) {
+        missingCoverageElements
+            .item(currentIndex)
+            .classList.remove('highlighted');
+        missingCoverageElements.item(index).classList.add('highlighted');
+    }
+
+    function makeCurrent(index) {
+        toggleClass(index);
+        currentIndex = index;
+        missingCoverageElements.item(index).scrollIntoView({
+            behavior: 'smooth',
+            block: 'center',
+            inline: 'center'
+        });
+    }
+
+    function goToPrevious() {
+        var nextIndex = 0;
+        if (typeof currentIndex !== 'number' || currentIndex === 0) {
+            nextIndex = missingCoverageElements.length - 1;
+        } else if (missingCoverageElements.length > 1) {
+            nextIndex = currentIndex - 1;
+        }
+
+        makeCurrent(nextIndex);
+    }
+
+    function goToNext() {
+        var nextIndex = 0;
+
+        if (
+            typeof currentIndex === 'number' &&
+            currentIndex < missingCoverageElements.length - 1
+        ) {
+            nextIndex = currentIndex + 1;
+        }
+
+        makeCurrent(nextIndex);
+    }
+
+    return function jump(event) {
+        if (
+            document.getElementById('fileSearch') === document.activeElement &&
+            document.activeElement != null
+        ) {
+            // if we're currently focused on the search input, we don't want to navigate
+            return;
+        }
+
+        switch (event.which) {
+            case 78: // n
+            case 74: // j
+                goToNext();
+                break;
+            case 66: // b
+            case 75: // k
+            case 80: // p
+                goToPrevious();
+                break;
+        }
+    };
+})();
+window.addEventListener('keydown', jumpToCode);

BIN
coverage/lcov-report/favicon.png


+ 101 - 0
coverage/lcov-report/index.html

@@ -0,0 +1,101 @@
+
+<!doctype html>
+<html lang="en">
+
+<head>
+    <title>Code coverage report for All files</title>
+    <meta charset="utf-8" />
+    <link rel="stylesheet" href="prettify.css" />
+    <link rel="stylesheet" href="base.css" />
+    <link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <style type='text/css'>
+        .coverage-summary .sorter {
+            background-image: url(sort-arrow-sprite.png);
+        }
+    </style>
+</head>
+    
+<body>
+<div class='wrapper'>
+    <div class='pad1'>
+        <h1>All files</h1>
+        <div class='clearfix'>
+            
+            <div class='fl pad1y space-right2'>
+                <span class="strong">Unknown% </span>
+                <span class="quiet">Statements</span>
+                <span class='fraction'>0/0</span>
+            </div>
+        
+            
+            <div class='fl pad1y space-right2'>
+                <span class="strong">Unknown% </span>
+                <span class="quiet">Branches</span>
+                <span class='fraction'>0/0</span>
+            </div>
+        
+            
+            <div class='fl pad1y space-right2'>
+                <span class="strong">Unknown% </span>
+                <span class="quiet">Functions</span>
+                <span class='fraction'>0/0</span>
+            </div>
+        
+            
+            <div class='fl pad1y space-right2'>
+                <span class="strong">Unknown% </span>
+                <span class="quiet">Lines</span>
+                <span class='fraction'>0/0</span>
+            </div>
+        
+            
+        </div>
+        <p class="quiet">
+            Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
+        </p>
+        <template id="filterTemplate">
+            <div class="quiet">
+                Filter:
+                <input type="search" id="fileSearch">
+            </div>
+        </template>
+    </div>
+    <div class='status-line medium'></div>
+    <div class="pad1">
+<table class="coverage-summary">
+<thead>
+<tr>
+   <th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
+   <th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
+   <th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
+   <th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
+   <th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
+   <th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
+   <th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
+   <th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
+   <th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
+   <th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
+</tr>
+</thead>
+<tbody></tbody>
+</table>
+</div>
+                <div class='push'></div><!-- for sticky footer -->
+            </div><!-- /wrapper -->
+            <div class='footer quiet pad2 space-top1 center small'>
+                Code coverage generated by
+                <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
+                at 2025-04-30T03:24:56.319Z
+            </div>
+        <script src="prettify.js"></script>
+        <script>
+            window.onload = function () {
+                prettyPrint();
+            };
+        </script>
+        <script src="sorter.js"></script>
+        <script src="block-navigation.js"></script>
+    </body>
+</html>
+    

+ 1 - 0
coverage/lcov-report/prettify.css

@@ -0,0 +1 @@
+.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 0
coverage/lcov-report/prettify.js


BIN
coverage/lcov-report/sort-arrow-sprite.png


+ 196 - 0
coverage/lcov-report/sorter.js

@@ -0,0 +1,196 @@
+/* eslint-disable */
+var addSorting = (function() {
+    'use strict';
+    var cols,
+        currentSort = {
+            index: 0,
+            desc: false
+        };
+
+    // returns the summary table element
+    function getTable() {
+        return document.querySelector('.coverage-summary');
+    }
+    // returns the thead element of the summary table
+    function getTableHeader() {
+        return getTable().querySelector('thead tr');
+    }
+    // returns the tbody element of the summary table
+    function getTableBody() {
+        return getTable().querySelector('tbody');
+    }
+    // returns the th element for nth column
+    function getNthColumn(n) {
+        return getTableHeader().querySelectorAll('th')[n];
+    }
+
+    function onFilterInput() {
+        const searchValue = document.getElementById('fileSearch').value;
+        const rows = document.getElementsByTagName('tbody')[0].children;
+        for (let i = 0; i < rows.length; i++) {
+            const row = rows[i];
+            if (
+                row.textContent
+                    .toLowerCase()
+                    .includes(searchValue.toLowerCase())
+            ) {
+                row.style.display = '';
+            } else {
+                row.style.display = 'none';
+            }
+        }
+    }
+
+    // loads the search box
+    function addSearchBox() {
+        var template = document.getElementById('filterTemplate');
+        var templateClone = template.content.cloneNode(true);
+        templateClone.getElementById('fileSearch').oninput = onFilterInput;
+        template.parentElement.appendChild(templateClone);
+    }
+
+    // loads all columns
+    function loadColumns() {
+        var colNodes = getTableHeader().querySelectorAll('th'),
+            colNode,
+            cols = [],
+            col,
+            i;
+
+        for (i = 0; i < colNodes.length; i += 1) {
+            colNode = colNodes[i];
+            col = {
+                key: colNode.getAttribute('data-col'),
+                sortable: !colNode.getAttribute('data-nosort'),
+                type: colNode.getAttribute('data-type') || 'string'
+            };
+            cols.push(col);
+            if (col.sortable) {
+                col.defaultDescSort = col.type === 'number';
+                colNode.innerHTML =
+                    colNode.innerHTML + '<span class="sorter"></span>';
+            }
+        }
+        return cols;
+    }
+    // attaches a data attribute to every tr element with an object
+    // of data values keyed by column name
+    function loadRowData(tableRow) {
+        var tableCols = tableRow.querySelectorAll('td'),
+            colNode,
+            col,
+            data = {},
+            i,
+            val;
+        for (i = 0; i < tableCols.length; i += 1) {
+            colNode = tableCols[i];
+            col = cols[i];
+            val = colNode.getAttribute('data-value');
+            if (col.type === 'number') {
+                val = Number(val);
+            }
+            data[col.key] = val;
+        }
+        return data;
+    }
+    // loads all row data
+    function loadData() {
+        var rows = getTableBody().querySelectorAll('tr'),
+            i;
+
+        for (i = 0; i < rows.length; i += 1) {
+            rows[i].data = loadRowData(rows[i]);
+        }
+    }
+    // sorts the table using the data for the ith column
+    function sortByIndex(index, desc) {
+        var key = cols[index].key,
+            sorter = function(a, b) {
+                a = a.data[key];
+                b = b.data[key];
+                return a < b ? -1 : a > b ? 1 : 0;
+            },
+            finalSorter = sorter,
+            tableBody = document.querySelector('.coverage-summary tbody'),
+            rowNodes = tableBody.querySelectorAll('tr'),
+            rows = [],
+            i;
+
+        if (desc) {
+            finalSorter = function(a, b) {
+                return -1 * sorter(a, b);
+            };
+        }
+
+        for (i = 0; i < rowNodes.length; i += 1) {
+            rows.push(rowNodes[i]);
+            tableBody.removeChild(rowNodes[i]);
+        }
+
+        rows.sort(finalSorter);
+
+        for (i = 0; i < rows.length; i += 1) {
+            tableBody.appendChild(rows[i]);
+        }
+    }
+    // removes sort indicators for current column being sorted
+    function removeSortIndicators() {
+        var col = getNthColumn(currentSort.index),
+            cls = col.className;
+
+        cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
+        col.className = cls;
+    }
+    // adds sort indicators for current column being sorted
+    function addSortIndicators() {
+        getNthColumn(currentSort.index).className += currentSort.desc
+            ? ' sorted-desc'
+            : ' sorted';
+    }
+    // adds event listeners for all sorter widgets
+    function enableUI() {
+        var i,
+            el,
+            ithSorter = function ithSorter(i) {
+                var col = cols[i];
+
+                return function() {
+                    var desc = col.defaultDescSort;
+
+                    if (currentSort.index === i) {
+                        desc = !currentSort.desc;
+                    }
+                    sortByIndex(i, desc);
+                    removeSortIndicators();
+                    currentSort.index = i;
+                    currentSort.desc = desc;
+                    addSortIndicators();
+                };
+            };
+        for (i = 0; i < cols.length; i += 1) {
+            if (cols[i].sortable) {
+                // add the click event handler on the th so users
+                // dont have to click on those tiny arrows
+                el = getNthColumn(i).querySelector('.sorter').parentElement;
+                if (el.addEventListener) {
+                    el.addEventListener('click', ithSorter(i));
+                } else {
+                    el.attachEvent('onclick', ithSorter(i));
+                }
+            }
+        }
+    }
+    // adds sorting functionality to the UI
+    return function() {
+        if (!getTable()) {
+            return;
+        }
+        cols = loadColumns();
+        loadData();
+        addSearchBox();
+        addSortIndicators();
+        enableUI();
+    };
+})();
+
+window.addEventListener('load', addSorting);

+ 0 - 0
coverage/lcov.info


+ 56 - 56
package.json

@@ -1,57 +1,57 @@
 {
-  "private": true,
-  "scripts": {
-    "start": "umi dev",
-    "start:dev": "cross-env REACT_APP_ENV=dev UMI_ENV=dev umi dev",
-    "build": "umi build",
-    "postinstall": "umi generate tmp",
-    "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
-    "test": "umi-test",
-    "test:coverage": "umi-test --coverage"
-  },
-  "gitHooks": {
-    "pre-commit": "lint-staged"
-  },
-  "lint-staged": {
-    "*.{js,jsx,less,md,json}": [
-      "prettier --write"
-    ],
-    "*.ts?(x)": [
-      "prettier --parser=typescript --write"
-    ]
-  },
-  "dependencies": {
-    "@ant-design/charts": "^1.2.13",
-    "@ant-design/icons": "^4.5.0",
-    "@ant-design/pro-card": "^1.14.17",
-    "@ant-design/pro-descriptions": "^1.6.8",
-    "@ant-design/pro-form": "^1.70.3",
-    "@ant-design/pro-layout": "^6.38.13",
-    "@ant-design/pro-table": "^2.30.8",
-    "@monaco-editor/react": "^4.4.5",
-    "@superset-ui/embedded-sdk": "^0.1.3",
-    "antd": "^4.21.6",
-    "axios": "^1.3.4",
-    "cross-env": "^7.0.3",
-    "lodash": "^4.17.21",
-    "password-quality-calculator": "^1.0.4",
-    "react": "16.x",
-    "react-dev-inspector": "^1.1.1",
-    "react-dom": "16.x",
-    "react-markdown": "^8.0.0",
-    "umi": "^3.5.20"
-  },
-  "devDependencies": {
-    "@types/express": "^4.17.13",
-    "@types/react": "^16.0.0",
-    "@types/react-dom": "^16.0.0",
-    "@umijs/plugin-qiankun": "^2.35.4",
-    "@umijs/preset-react": "^2.1.1",
-    "@umijs/test": "^3.5.20",
-    "express": "^4.17.1",
-    "lint-staged": "^10.0.7",
-    "prettier": "^2.2.0",
-    "typescript": "^4.1.2",
-    "yorkie": "^2.0.0"
-  }
-}
+    "private": true,
+    "scripts": {
+      "start": "umi dev",
+      "start:dev": "cross-env REACT_APP_ENV=dev UMI_ENV=dev umi dev",
+      "build": "umi build",
+      "postinstall": "umi generate tmp",
+      "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
+      "test": "umi-test",
+      "test:coverage": "umi-test --coverage"
+    },
+    "gitHooks": {
+      "pre-commit": "lint-staged"
+    },
+    "lint-staged": {
+      "*.{js,jsx,less,md,json}": [
+        "prettier --write"
+      ],
+      "*.ts?(x)": [
+        "prettier --parser=typescript --write"
+      ]
+    },
+    "dependencies": {
+      "@ant-design/charts": "^1.2.13",
+      "@ant-design/icons": "^4.5.0",
+      "@ant-design/pro-card": "^1.14.17",
+      "@ant-design/pro-descriptions": "^1.6.8",
+      "@ant-design/pro-form": "^1.70.3",
+      "@ant-design/pro-layout": "^6.38.13",
+      "@ant-design/pro-table": "^2.30.8",
+      "@monaco-editor/react": "^4.4.5",
+      "@superset-ui/embedded-sdk": "^0.1.3",
+      "antd": "^4.21.6",
+      "axios": "^1.3.4",
+      "cross-env": "^7.0.3",
+      "lodash": "^4.17.21",
+      "password-quality-calculator": "^1.0.4",
+      "react": "16.x",
+      "react-dev-inspector": "^1.1.1",
+      "react-dom": "16.x",
+      "react-markdown": "^8.0.0",
+      "umi": "^3.5.20"
+    },
+    "devDependencies": {
+      "@types/express": "^4.17.13",
+      "@types/react": "^16.0.0",
+      "@types/react-dom": "^16.0.0",
+      "@umijs/plugin-qiankun": "^2.35.4",
+      "@umijs/preset-react": "^2.1.1",
+      "@umijs/test": "^3.5.20",
+      "express": "^4.17.1",
+      "lint-staged": "^10.0.7",
+      "prettier": "^2.2.0",
+      "typescript": "^4.1.2",
+      "yorkie": "^2.0.0"
+    }
+  }

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 3
public/zhongtaiC.js


+ 145 - 145
src/app.tsx

@@ -5,176 +5,175 @@ import { RequestConfig, history } from 'umi';
 import type { RequestOptionsInit } from 'umi-request';
 import { UserDataType, getQiankunMicroApps } from '@/service/login';
 
-import { BasicLayoutProps } from '@ant-design/pro-layout';
 import { logoutHandle } from './global';
 import { KcimCenterSysId, Platforms } from './constant';
 import { SpacicalPageParamsType } from './typings';
 import { createFromIconfontCN } from '@ant-design/icons';
 import { changeFavicon, getHospType, handleLogin } from './pages/login';
 import { getSysParamsByCode } from './service';
-import '../public/zhongtaiC';
 
-window.isParentApp = true;
+// 全局变量声明
+declare global {
+  interface Window {
+    isParentApp?: boolean;
+  }
+}
 
 const loginPath = '/login';
 
-let hospSign: string = ''; //医院标识
-
-if (history && history.location.query) {
-  hospSign = history.location.query.hospSign as string;
-  if (!hospSign) {
-    const localHospSign = localStorage.getItem('hospSign');
-    hospSign = localHospSign ? localHospSign : '';
+// 获取医院标识
+const getHospSign = (): string => {
+  if (history?.location.query) {
+    const queryHospSign = history.location.query.hospSign as string;
+    if (queryHospSign) return queryHospSign;
   }
-}
+  return localStorage.getItem('hospSign') || '';
+};
+
+const hospSign = getHospSign();
 
 const IconFont = createFromIconfontCN({
-  scriptUrl: '',
-});
+  scriptUrl: '/zhongtaiC.js',
+}); 
 
 /** 获取用户信息比较慢的时候会展示一个 loading */
 export const initialStateConfig = {
   loading: <PageLoading />,
 };
 
-type InitialStateType = {
+interface InitialStateType {
   navData: any[];
-  menuData: any[]; //中台菜单
-  customerType: string; //客户类型
-  userData?: UserDataType; //登录返回用户信息
-  userInfo: any; //当前用户详细信息
-  systemLists?: TopBar.Tab[]; //当前医院可选子系统列表
-  openedSysLists?: TopBar.Tab[]; //当前已打开的系统列表
-  currentSelectedSys?: TopBar.Tab; //当前选中的tab
+  menuData: any[]; // 中台菜单
+  customerType: string; // 客户类型
+  userData?: UserDataType; // 登录返回用户信息
+  userInfo: any; // 当前用户详细信息
+  systemLists?: TopBar.Tab[]; // 当前医院可选子系统列表
+  openedSysLists?: TopBar.Tab[]; // 当前已打开的系统列表
+  currentSelectedSys?: TopBar.Tab; // 当前选中的tab
   logout?: () => Promise<boolean>;
   childAppIsShowMenu?: boolean;
   pageTitle: string;
   spacicalPageParamsType?: SpacicalPageParamsType[];
   getHospSubSystemListFunc?: () => Promise<any[]>;
-};
+}
 
 interface MyRequestOptions extends RequestOptionsInit {
   prefix?: string; // 自定义前缀
 }
 
-export async function getInitialState(): Promise<InitialStateType> {
-  const fetchUserInfo = async () => {
-    try {
-      const queryParams = new URLSearchParams(location.search);
-      const importUserData = queryParams.get('userData');
-      const env = queryParams.get('env');
-
-      if (importUserData) {
-        localStorage.setItem('userData', importUserData);
-        return JSON.parse(importUserData);
-      }
+/**
+ * 获取用户信息
+ */
+const fetchUserInfo = async (): Promise<UserDataType | undefined> => {
+  try {
+    const queryParams = new URLSearchParams(location.search);
+    const importUserData = queryParams.get('userData');
+    const env = queryParams.get('env');
 
-      const userData = localStorage.getItem('userData');
+    if (importUserData) {
+      localStorage.setItem('userData', importUserData);
+      return JSON.parse(importUserData);
+    }
 
-      if (userData) {
-        if (env === 'demo') {
-          history.replace('/index');
-        }
-        return JSON.parse(userData);
-      }
-      // 如果没有 userData 且 env 参数为 demo,则发起一个请求
+    const userData = localStorage.getItem('userData');
+    if (userData) {
       if (env === 'demo') {
-        // 获取 hospSign 参数
-
-        const hospSign = queryParams.get('hospSign');
-        // 发起请求的代码,根据实际需求调整
-        const resp = await handleLogin('demo', '123456', hospSign as string);
-        if (resp) {
-          history.replace('/index');
-          return resp;
-        }
+        history.replace('/index');
       }
+      return JSON.parse(userData);
+    }
 
-      throw Error;
-    } catch (error) {
-      const currentUrlParams = new URLSearchParams(window.location.search);
-      const hospSign = 'yourDefaultHospSign'; // 定义默认的 hospSign,如果需要的话
-
-      // 如果 hospSign 已存在,保留其值,否则添加新的 hospSign 参数
-      if (!currentUrlParams.has('hospSign')) {
-        currentUrlParams.append('hospSign', hospSign);
+    if (env === 'demo') {
+      const hospSign = queryParams.get('hospSign');
+      const resp = await handleLogin('demo', '123456', hospSign || '');
+      if (resp) {
+        history.replace('/index');
+        return resp;
       }
+    }
+
+    throw new Error('No user data found');
+  } catch (error) {
+    const currentUrlParams = new URLSearchParams(window.location.search);
+    const defaultHospSign = 'yourDefaultHospSign';
 
-      history.push(`${loginPath}?${currentUrlParams.toString()}`);
+    if (!currentUrlParams.has('hospSign')) {
+      currentUrlParams.append('hospSign', defaultHospSign);
     }
+
+    history.push(`${loginPath}?${currentUrlParams.toString()}`);
     return undefined;
-  };
+  }
+};
 
-  //获取当前账号所有子应用列表
-  const getHospSubSystemListFunc = async () => {
-    // const data = await getHospSubSystemList();
-    // if (data) {
-    //   const _data = data.map((t) => ({
-    //     ...t,
-    //     icon: getAppIcon(t.name),
-    //     path: t.path,
-    //   }));
-
-    //   return _data;
-    // }
-    return [];
-  };
+/**
+ * 获取医院子系统列表
+ */
+const getHospSubSystemListFunc = async (): Promise<any[]> => {
+  // TODO: 实现获取子系统列表的逻辑
+  return [];
+};
 
+export async function getInitialState(): Promise<InitialStateType> {
   const logout = logoutHandle;
   const userData = await fetchUserInfo();
 
-  let customerType = undefined;
-
+  let customerType: string | undefined;
   if (userData?.token) {
     customerType = await getHospType();
   }
 
   let systemLists: userRelationInfo.OwnAppsItem[] = [];
-  // let navData:any[] = [];
   if (userData) {
     systemLists = await getHospSubSystemListFunc();
-    // navData = await getUserPlatformNav();
   }
 
   const localInitData = localStorage.getItem('initialState');
   const currentSelectedTab = localStorage.getItem('currentSelectedTab');
-  const { loginPic } = JSON.parse(localStorage.getItem('currentSelectedSubHop') as string);
-  const userInfo = JSON.parse(localStorage.getItem('userInfo') as string);
-
-  if (loginPic.length > 0) {
-    const arr = loginPic.split('|');
-    if (arr.length == 2) {
-      changeFavicon(arr[1]);
+  const currentSelectedSubHop = localStorage.getItem('currentSelectedSubHop');
+  const userInfo = localStorage.getItem('userInfo');
+
+  if (currentSelectedSubHop) {
+    const { loginPic } = JSON.parse(currentSelectedSubHop);
+    if (loginPic) {
+      const [_, favicon] = loginPic.split('|');
+      if (favicon) {
+        changeFavicon(favicon);
+      }
     }
   }
 
   return {
-    currentSelectedSys: currentSelectedTab != null ? JSON.parse(currentSelectedTab) : undefined,
+    currentSelectedSys: currentSelectedTab ? JSON.parse(currentSelectedTab) : undefined,
     openedSysLists: [],
-    ...JSON.parse(localInitData ? localInitData : '{}'), //覆盖,恢复tab状态
+    ...(localInitData ? JSON.parse(localInitData) : {}),
     userData,
     logout,
     pageTitle: '欢迎进入医管平台',
     favicon: '',
     customerType,
-    userInfo,
+    userInfo: userInfo ? JSON.parse(userInfo) : null,
     spacicalPageParamsType: [],
     getHospSubSystemListFunc,
-    systemLists: systemLists,
+    systemLists,
   };
 }
 
+/**
+ * 请求拦截器
+ */
 const requestInterceptorsHandle = (url: string, options: MyRequestOptions) => {
   const prefix = options.prefix || '/gateway';
   const userData = localStorage.getItem('userData');
-  let authHeader = { token: '' };
+  const authHeader = { token: '' };
 
   if (userData) {
     const { token } = JSON.parse(userData);
-    if (url.indexOf('/centerSys/route/list') == -1) {
+    if (!url.includes('/centerSys/route/list')) {
       authHeader.token = token;
     }
   }
+
   const finalUrl = url.startsWith(prefix) ? url : `${prefix}${url}`;
 
   return {
@@ -183,25 +182,19 @@ const requestInterceptorsHandle = (url: string, options: MyRequestOptions) => {
   };
 };
 
+/**
+ * 响应拦截器
+ */
 const responseInterceptorsHandle = async (response: Response, options: RequestOptionsInit) => {
   const { url, method } = options;
-  const _response: {
-    errorMessage: any;
-    message: any;
-    data?: any;
-    status: number;
-    errorCode?: number;
-    success?: boolean;
-    msg?: string;
-  } = await response.clone().json();
-
-  if (_response.errorCode == 401) {
+  const _response = await response.clone().json();
+
+  if (_response.errorCode === 401) {
     Modal.confirm({
       title: '抱歉,你的登录已过期,请重新登录!',
       okText: '确定',
       cancelText: '取消',
       maskClosable: false,
-      // cancelButtonProps:
       onOk: () => {
         logoutHandle();
         return Promise.resolve(true);
@@ -210,41 +203,37 @@ const responseInterceptorsHandle = async (response: Response, options: RequestOp
     return;
   }
 
-  if (_response.status == 200) {
-    if (url != '/centerSys/menu/checkKeygen' && method == 'POST') {
+  if (_response.status === 200) {
+    if (url !== '/centerSys/menu/checkKeygen' && method === 'POST') {
       message.success({
-        content: `操作成功!`,
+        content: '操作成功!',
         duration: 1,
       });
     }
 
-    if (_response.data != null || _response.data != undefined) {
-      return _response.data;
-    } else {
-      return true;
-    }
-  } else {
-    if ((_response.msg && _response.msg.indexOf('Token') != -1) || (_response.msg && _response.msg.indexOf('token') != -1)) {
-      Modal.confirm({
-        title: '抱歉,你的登录已过期,请重新登录!',
-        okText: '确定',
-        cancelText: '取消',
-        maskClosable: false,
-        // cancelButtonProps:
-        onOk: () => {
-          logoutHandle();
-          return Promise.resolve(true);
-        },
-      });
-      return;
-    } else {
-      notification.error({
-        message: '错误:',
-        description: `${_response.msg ? _response.msg : _response.message ? _response.message : _response.errorMessage}`,
-      });
-    }
-    return false;
+    return _response.data ?? true;
   }
+
+  const errorMessage = _response.msg || _response.message || _response.errorMessage;
+  if (errorMessage?.toLowerCase().includes('token')) {
+    Modal.confirm({
+      title: '抱歉,你的登录已过期,请重新登录!',
+      okText: '确定',
+      cancelText: '取消',
+      maskClosable: false,
+      onOk: () => {
+        logoutHandle();
+        return Promise.resolve(true);
+      },
+    });
+    return;
+  }
+
+  notification.error({
+    message: '错误:',
+    description: errorMessage,
+  });
+  return false;
 };
 
 interface ErrorInfoStructure {
@@ -356,10 +345,10 @@ export const qiankun = async () => {
         //   name: 'microApp', // 唯一 id
         //   entry: '//localhost:8808', // 开发
         // },
-        {
-          name: 'nursingWorkersManaSystem', // 唯一 id
-          entry: '//116.169.61.56:29528', //测试
-        },
+        // {
+        //   name: 'nursingWorkersManaSystem', // 唯一 id
+        //   entry: '//116.169.61.56:29528', //测试
+        // },
         // {
         //   name: 'reviewMana', // 唯一 id
         //   entry: '//120.27.235.181:5000/pfmview/',
@@ -368,20 +357,20 @@ export const qiankun = async () => {
         // },
         {
           name: 'budgetManaSystem', // 唯一 id
-          entry: '//localhost:8002',
-          //entry: '//120.27.235.181:5000/perform/',  //开发
+          //entry: '//localhost:8002',
+          entry: '//120.27.235.181:5000/perform/',  //开发
           //entry: '//47.96.149.190:5000/perform/', //演示
           //entry: '//198.198.203.161:5000/perform/', //淮南
         },
-        // {
-        //   name: 'pfmBackMana', // 唯一 id
-        //   entry: '//localhost:8001'
-        //   //entry: '//120.27.235.181:5000/pfmManager/', // 开发
-        // },
+        {
+          name: 'pfmBackMana', // 唯一 id
+          //entry: '//localhost:8001'
+          entry: '//120.27.235.181:5000/pfmManager/', // 开发
+        },
         {
           name: 'CostAccountingSys', // 唯一 id
-          //entry: '//localhost:8001',
-          entry: '//120.27.235.181:5000/costAccount/', // 开发
+          entry: '//localhost:8001',
+          //entry: '//120.27.235.181:5000/costAccount/', // 开发
         },
         {
           name: 'MediResourceManaSys', // 唯一 id
@@ -474,7 +463,18 @@ export function patchRoutes({ routes }: { routes: any }) {
   treeLoop(routes[0]);
 }
 
+// 定义布局组件的类型
+interface BasicLayoutProps {
+  headerRender?: boolean;
+  rightContentRender?: () => React.ReactNode;
+  footerRender?: () => React.ReactNode;
+  onPageChange?: () => void;
+  menuHeaderRender?: React.ReactNode;
+}
+
+// 布局组件配置
 export const layout = ({ initialState }: { initialState?: InitialStateType }): BasicLayoutProps => {
+  // 解构初始状态数据,设置默认值
   const { navData = [], menuData = [], customerType = '' } = initialState || {};
 
   return {

+ 2 - 1
src/components/intelligenceBot/Chat/index.tsx

@@ -85,6 +85,7 @@ interface BotMessageContentProps {
 }
 
 const BotMessageContent: React.FC<BotMessageContentProps> = React.memo(({ thinkContent, content, charts, onShowDetail, msg }) => {
+ 
   return (
     <div className="bot-message-text">
       <div className="think-text">{thinkContent}</div>
@@ -96,7 +97,7 @@ const BotMessageContent: React.FC<BotMessageContentProps> = React.memo(({ thinkC
               <IconFont type={'iconchakan'} style={{ fontSize: 16, paddingRight: 4 }} />
               详情
             </div>
-            {charts.type === 'LineChart' && <Line {...charts} />}
+            {(charts.type === 'LineChart' && charts) && <Line {...charts} />}
             {charts.type === 'ColumnChart' && <Column {...charts} />}
             {charts.type === 'PieChart' && <Pie {...charts} />}
           </div>

+ 9 - 3
src/components/intelligenceBot/index.tsx

@@ -65,6 +65,11 @@ const AiChat: React.FC = () => {
         body: JSON.stringify({ question: question }),
       });
 
+
+      if (!response.ok) {
+        throw new Error(`服务器错误: ${response.status} ${response.statusText}`);
+      }
+
       if (!response.body) {
         throw new Error('No response body');
       }
@@ -186,7 +191,7 @@ const AiChat: React.FC = () => {
         }
         return updated;
       });
-    } catch (error) {
+    } catch (error: any) {
       console.error('请求错误:', error);
       setMessages((prev) => {
         const updated = [...prev];
@@ -194,7 +199,7 @@ const AiChat: React.FC = () => {
         if (lastIndex >= 0 && updated[lastIndex].sender === 'bot') {
           updated[lastIndex] = {
             sender: 'bot',
-            content: '请求失败,请稍后重试。',
+            content: `请求失败: ${error.message || '未知错误,请稍后重试'}`,
             loading: false,
           };
         }
@@ -259,6 +264,7 @@ const AiChat: React.FC = () => {
     textArea.style.background = 'transparent';
     document.body.appendChild(textArea);
     textArea.select();
+    
     try {
       const successful = document.execCommand('copy');
       if (successful) console.log('复制成功');
@@ -304,7 +310,7 @@ const AiChat: React.FC = () => {
         onClose={closeDrawer}
         open={visible}
         destroyOnClose
-        width={detailData ? '90%' : 560}
+        width={detailData ? '94%' : 560}
         mask={false}
       >
         <div className="chat-header">

+ 5 - 4
src/components/intelligenceBot/style.less

@@ -135,9 +135,8 @@
           }
         }
         .chat-detail {
-          display: flex;
-          flex: 1;
-          width: 100%;
+          
+          width:calc(100% - 552px);
           height: 100%;
           background: #ffffff;
           border-radius: 8px;
@@ -181,8 +180,10 @@
               padding: 8px;
               padding-left: 18px;
               overflow-y: scroll;
+              overflow-x: scroll;
               .charts {
-                min-width: 640px;
+                width: 100%;
+                // min-width: 640px;
               }
             }
           }

+ 1 - 1
src/components/topBar/index.tsx

@@ -483,7 +483,7 @@ const TopBar: React.FC<TopBarType> = (props) => {
           {showGroupList && (
             <div className="group-list-wrapper" ref={GroupListWrapperRef}>
               {groupList.map((a, index) => (
-                <div key={index} className={currentActivedGroup.orgId == a.orgId ? `group-list actived` : 'group-list'} onClick={() => switchGroupHandle(a)}>
+                <div key={index} className={currentActivedGroup?.orgId === a.orgId ? `group-list actived` : 'group-list'} onClick={() => switchGroupHandle(a)}>
                   {a.orgName}
                 </div>
               ))}

+ 16 - 2
src/global.less

@@ -9,6 +9,9 @@ body {
   overflow: hidden;
   zoom: unset !important;
   background-color: transparent;
+  &>#root-master {
+    height: 100%;
+  }
 }
 
 input {
@@ -20,9 +23,20 @@ input {
 }
 
 //overwrite antd-pro css
-
+.kcmp-ant-pro-page-container {
+  height: 100%;
+  .kcmp-ant-pro-grid-content {
+    height: 100%;
+    .kcmp-ant-pro-grid-content-children {
+      height: 100%;
+      .kcmp-ant-pro-page-container-children-content {
+        height: 100%;
+      }
+    }
+  }
+}
 .kcmp-ant-pro-page-container-warp {
-  display: none !important;
+  display: none !important;  
 }
 
 .kcmp-ant-pro-sider-light {

+ 4 - 3
src/global.tsx

@@ -1,7 +1,7 @@
 /*
  * @Author: your name
  * @Date: 2021-12-21 11:05:37
- * @LastEditTime: 2024-03-28 18:04:34
+ * @LastEditTime: 2025-05-13 16:37:55
  * @LastEditors: code4eat awesomedema@gmail.com
  * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  * @FilePath: /KC-MiddlePlatform/src/global.tsx
@@ -21,8 +21,9 @@ export const logoutHandle = async () => {
   console.log('logoutHandle');
   localStorage.removeItem('userData');
   localStorage.removeItem('initialState');
-  // localStorage.removeItem('currentSelectedTab');
-  //localStorage.removeItem('isChildShowMenu');
+  localStorage.removeItem('userInfo');
+  localStorage.removeItem('currentSelectedTab');
+  localStorage.removeItem('isChildShowMenu');
 
   localStorage.removeItem('selectedKeys');
   localStorage.removeItem('openKeys');

+ 10 - 9
src/layouts/index.tsx

@@ -1,7 +1,7 @@
 /*
  * @Author: your name
  * @Date: 2021-11-09 13:56:33
- * @LastEditTime: 2025-03-13 10:35:15
+ * @LastEditTime: 2025-04-27 13:43:23
  * @LastEditors: code4eat awesomedema@gmail.com
  * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  * @FilePath: /KC-MiddlePlatform/src/layouts/index.tsx
@@ -149,11 +149,8 @@ export default function Layout({ children, location, route, history, match }: IR
 
   const queryParams = new URLSearchParams(location.search);
   const noTopBar = queryParams.get('noTopbar');
-  const [ifStartChat, set_ifStartChat] = useState(false);
-  const [messages, setMessages] = useState<Message[]>([]);
-  const [inputValue, setInputValue] = useState<string>('');
-  const [loading, setLoading] = useState<boolean>(false);
-  const chatContainerRef = useRef<HTMLDivElement>(null);
+  const [ifShowAiBtn, set_ifShowAiBtn] = useState(false);
+
 
   const getNavData = async () => {
     const nav = await getUserPlatformNav();
@@ -173,6 +170,9 @@ export default function Layout({ children, location, route, history, match }: IR
         if (item.code == '1739123252502073344') {
           item.value && set_topBarTitle(item.value);
         }
+        if (item.code == '1907252043454746624') {
+          item.value && set_ifShowAiBtn(item.value == '1');
+        }
       });
     }
   };
@@ -182,6 +182,8 @@ export default function Layout({ children, location, route, history, match }: IR
     set_emptyPageContent(menuItem.description);
   };
 
+
+
   useEffect(() => {
     if (location.pathname != '/login') {
       getNavData();
@@ -231,7 +233,7 @@ export default function Layout({ children, location, route, history, match }: IR
         margin: 0,
       }}
       menuRender={false}
-      menuItemRender={(item, dom) => {
+      menuItemRender={(item, dom) => { 
         return (
           <a
             onClick={() => {
@@ -279,8 +281,7 @@ export default function Layout({ children, location, route, history, match }: IR
       )}
       {!isEmpty && (
         <div className="platform-children" style={{ height: noTopBar == 'true' ? 'auto' : `calc(100vh - 48px)`, minWidth: 1280 }}>
-          <AiChat />
-
+          {(window.self === window.top&&ifShowAiBtn)&&<AiChat />}
           <>{children}</>
         </div>
       )}

+ 11 - 7
src/pages/platform/_layout.tsx

@@ -1,7 +1,7 @@
 /*
  * @Author: your name
  * @Date: 2022-01-06 15:25:39
- * @LastEditTime: 2025-03-20 14:55:04
+ * @LastEditTime: 2025-05-13 16:44:15
  * @LastEditors: code4eat awesomedema@gmail.com
  * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  * @FilePath: /KC-MiddlePlatform/src/pages/platform/_layout.tsx
@@ -136,7 +136,7 @@ export default function Layout({ children, location, route, history, match, ...r
 
   const queryParams = new URLSearchParams(location.search);
   const noMenu = queryParams.get('noMenu');
-
+  const noTopBar = queryParams.get('noTopbar');
   const moreConfig: { menuRender?: any } = {};
 
   const { pathname } = location;
@@ -166,7 +166,11 @@ export default function Layout({ children, location, route, history, match, ...r
 
   useEffect(() => {
     const isShowMenu = localStorage.getItem('isChildShowMenu');
-    set_isShowPageMenu(isShowMenu == 'true');
+    if (isShowMenu) {
+      set_isShowPageMenu(isShowMenu == 'true');
+    } else {
+      set_isShowPageMenu(true);
+    }
   });
 
   useEffect(() => {
@@ -336,7 +340,7 @@ export default function Layout({ children, location, route, history, match, ...r
   return (
     <ProLayout
       style={{
-        height: 'calc(100vh - 50px)',
+        height:noTopBar?'100%': 'calc(100vh - 50px)',
       }}
       //iconfontUrl="//at.alicdn.com/t/font_8d5l8fzk5b87iudi.js"
       logoStyle={{
@@ -392,7 +396,7 @@ export default function Layout({ children, location, route, history, match, ...r
           localStorage.setItem('openKeys', JSON.stringify(keys));
         },
       }}
-      menu={{
+      menu={(isShowPageMenu&&!noMenu)?{
         autoClose: false,
         request: async () => {
           if (initialState && initialState.currentSelectedSys) {
@@ -521,7 +525,7 @@ export default function Layout({ children, location, route, history, match, ...r
           }
           return [];
         },
-      }}
+      }:false}
       menuRender={(props: any, defaultDom) => {
         return (
           <div style={isShowPageMenu && noMenu != 'true' ? {} : { display: 'inline-block', width: 0, overflow: 'hidden' }}>
@@ -577,7 +581,7 @@ export default function Layout({ children, location, route, history, match, ...r
             <title>{initialState?.customerType == '2' ? currentHospName : '精益管理中台'}</title>
           </Helmet>
 
-          <div className="page" style={{ height: 'calc(100vh - 80px)', overflowY: 'scroll' }}>
+          <div className="page" style={noTopBar?{height:'100%',overflowY: 'scroll'}:{ height: 'calc(100vh - 80px)', overflowY: 'scroll' }}>
             {children}
           </div>
         </PageContainer>

+ 24 - 6
src/pages/platform/setting/departmentMana/index.tsx

@@ -2,7 +2,7 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2023-03-03 11:30:33
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2025-01-14 10:30:57
+ * @LastEditTime: 2025-05-13 16:51:44
  * @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
  */
@@ -14,7 +14,7 @@ import { getAllHosp } from '@/service/hospList';
 import { downloadTemplateReq } from '@/utils';
 import { ModalForm, ProFormSelect, ProFormText, ProFormTextArea } from '@ant-design/pro-form';
 import { ProColumns } from '@ant-design/pro-table';
-import { message, Modal, Popconfirm } from 'antd';
+import { Divider, message, Modal, Popconfirm, Tooltip } from 'antd';
 import FormItem from 'antd/lib/form/FormItem';
 import { useEffect, useState } from 'react';
 import { addData, delData, editData, getDepartmentData, getDepartmentType, getRelaHosp, importDepartmentData } from './service';
@@ -91,11 +91,29 @@ export default function DepartmentMana() {
       width: 120,
       valueType: 'option',
       render: (_: any, record: any) => {
+        const getEditBtn = () => {
+          const { isEdit } = record;
+          if (!isEdit) {
+            return [
+              <UpDataActBtn key={'act'} record={record} type="EDIT" />,
+              <Popconfirm title="是否确认删除?" key="del" onConfirm={() => delTableData(record)}>
+                <a>删除</a>
+              </Popconfirm>,
+            ];
+          } else {
+            return [
+              <Tooltip title="第三方科室不可编辑!">
+                <span style={{ cursor: 'not-allowed', color: 'rgb(176 181 189)' }}>编辑</span>
+              </Tooltip>,
+              <Divider key="1" type="vertical" style={{ margin: '0 3px' }} />,
+              <Tooltip title="第三方科室不可删除!">
+                <span style={{ cursor: 'not-allowed', color: 'rgb(176 181 189)' }}>删除</span>
+              </Tooltip>,
+            ];
+          }
+        };
         return [
-          <UpDataActBtn key={'act'} record={record} type="EDIT" />,
-          <Popconfirm title="是否确认删除?" key="del" onConfirm={() => delTableData(record)}>
-            <a>删除</a>
-          </Popconfirm>,
+             ...(getEditBtn())
         ];
       },
     },

+ 8 - 9
src/pages/platform/setting/embeddedDashboard/index.tsx

@@ -6,22 +6,21 @@ function MyEmbeddedDashboard() {
   // 从 URL 中获取仪表板 ID
   const paths = window.location.pathname.split('/').filter((item) => item);
   const dashboardId = paths[paths.length - 1];
-  console.log('paths', paths);
-  console.log('dashboardId:', dashboardId);
+  const host = window.location.hostname;
 
   // 封装获取 Guest Token 的异步函数
   const getGuestToken = async () => {
     try {
       // 1. 登录,获取 access_token
-      const loginResponse = await fetch('/api/v1/security/login', {
+      const loginResponse = await fetch(`http://${host}:8088/api/v1/security/login`, {
         method: 'POST',
         headers: {
           'Content-Type': 'application/json',
         },
         body: JSON.stringify({
-          username: 'admin',
-          password: 'admin',
-          provider: 'db', // 根据你的实际配置,比如 db 或者其他
+          username: 'kcim',
+          password: 'kcim123456',
+          provider: 'db', 
           refresh: true,
         }),
       });
@@ -33,7 +32,7 @@ function MyEmbeddedDashboard() {
       }
 
       // 2. 使用 access_token 调用 /api/v1/security/guest_token/ 获取 Guest Token
-      const guestTokenResponse = await fetch('/api/v1/security/guest_token/', {
+      const guestTokenResponse = await fetch(`http://${host}:8088/api/v1/security/guest_token/`, {
         method: 'POST',
         headers: {
           'Content-Type': 'application/json',
@@ -77,7 +76,7 @@ function MyEmbeddedDashboard() {
     if (dashboardId) {
       embedDashboard({
         id: dashboardId,
-        supersetDomain: 'http://localhost:8088', // 你的 Superset 服务地址
+        supersetDomain: `http://${host}:8088`, 
         mountPoint: document.getElementById('dashboard-container') as HTMLElement,
         dashboardUiConfig: {
           hideTitle: true,
@@ -97,7 +96,7 @@ function MyEmbeddedDashboard() {
 
   return (
     <div className="my-embedded-dashboard">
-      <div className="dashboard" id="dashboard-container" style={{ width: '100%', height: '80vh' }} />
+      <div className="dashboard" id="dashboard-container"  />
     </div>
   );
 }

+ 8 - 0
src/pages/platform/setting/embeddedDashboard/style.less

@@ -1,5 +1,13 @@
 .my-embedded-dashboard {
+  width: 100%;
+  height: 100%;
+  border-radius: 4px;
+  overflow: hidden;
   .dashboard {
+    width: 100%;
+    height: 100%;
+    border-radius: 4px;
+    overflow: hidden;
     & > iframe {
       width: 100%;
       height: 100%;

+ 50 - 0
src/pages/platform/setting/indicatorMana/frameContainer/index.tsx

@@ -0,0 +1,50 @@
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2025-04-07 17:34:56
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2025-04-08 14:33:52
+ * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/indicatorMana/frameContainer/index.tsx
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
+import React, { useState } from 'react';
+
+const FrameComponent = ({ link }:{link:string}) => {
+  const [loading, setLoading] = useState(true);
+
+  const handleLoad = () => {
+    setLoading(false);
+  };
+
+  return (
+    <div style={{ position: 'relative', width: '100%', height: '100%',borderRadius:4,overflow:'hidden' }}>
+      {loading && (
+        <div
+          style={{
+            position: 'absolute',
+            top: 0,
+            left: 0,
+            width: '100%',
+            height: '100%',
+            display: 'flex',
+            alignItems: 'center',
+            justifyContent: 'center',
+            background: '#fff',
+            zIndex: 1,
+          }}
+        >
+          加载中...
+        </div>
+      )}
+      <iframe
+        src={link}
+        title="Dynamic Frame"
+        width="100%"
+        height="100%"
+        style={{ border: 'none' }}
+        onLoad={handleLoad}
+      />
+    </div>
+  );
+};
+
+export default FrameComponent;

+ 106 - 4
src/pages/platform/setting/indicatorMana/index.tsx

@@ -2,19 +2,21 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2022-07-12 11:14:21
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2025-02-28 14:52:26
+ * @LastEditTime: 2025-04-25 17:40:50
  * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/indicatorMana/index.tsx
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
 
 import { ActionType, ProColumns } from '@ant-design/pro-table';
 import React, { useEffect, useRef, useState } from 'react';
-import { Dropdown, Empty, Input, Popconfirm, Tabs } from 'antd';
+import { Dropdown, Empty, Input, Popconfirm, Tabs, Modal, Form, message } from 'antd';
+import { ModalForm, ProFormInstance, ProFormText, ProFormTextArea } from '@ant-design/pro-form';
 
 import DrawerForm from './DrawerForm/drawer';
 import { createFromIconfontCN, DownOutlined } from '@ant-design/icons';
 import {
   addIndicatorManaList,
+  applyOnlineReportMap,
   delIndicatorManaList,
   editIndicatorManaList,
   getIndicatorCateList,
@@ -32,6 +34,7 @@ import KCTable from '@/components/kcTable';
 import { KCInput } from '@/components/KCInput';
 import generateTableData from '../dataFilling/fillingMana/generateTableData';
 import { DATAFILL_PERIODTYPE } from '../dataFilling/fillingMana';
+import FrameComponent from './frameContainer';
 
 const defaultYear = new Date().getFullYear();
 let table_columns: any[] = [];
@@ -67,6 +70,9 @@ const IndicatorMana = () => {
   const [currentTab, set_currentTab] = useState('1');
   const { indicatorType: indicatorUrlType }: { id: string; indicatorType: string } = history.location.query;
 
+  const [reportModalVisible, setReportModalVisible] = useState(false);
+  const reportForm = useRef<ProFormInstance>();
+
   const columns: ProColumns<any>[] = [
     {
       title: '指标名称',
@@ -479,10 +485,99 @@ const IndicatorMana = () => {
     getIndicatorCateTree();
   }, []);
 
+  // // 当弹窗可见性变化时,处理表单回显
+  // useEffect(() => {
+  //   console.log('Modal visibility changed:', reportModalVisible); // 日志 1: 检查可见性变化
+  //   if (reportModalVisible && currentEditRow) {
+  //     console.log('Attempting to set form values. currentEditRow:', currentEditRow); // 日志 2: 检查 currentEditRow 内容
+  //     const valuesToSet = {
+  //       reportId: currentEditRow.onlineReportCode,
+  //       remark: currentEditRow.onlineRemark
+  //     };
+  //     console.log('Values to set:', valuesToSet); // 日志 3: 检查要设置的值
+  //     try {
+  //       reportForm?.current?.setFieldsValue(valuesToSet);
+  //       console.log('setFieldsValue called successfully.'); // 日志 4: 确认调用成功
+  //     } catch (e) {
+  //       console.error('Error calling setFieldsValue:', e); // 日志 5: 检查调用是否出错
+  //     }
+  //   } else if (reportModalVisible) {
+  //       console.log('Modal is visible, but currentEditRow is missing or empty:', currentEditRow); // 日志 6: 检查 currentEditRow 是否为空
+  //   }
+  //   // ModalForm 的 destroyOnClose 会处理关闭时的重置
+  // }, [reportModalVisible, currentEditRow, reportForm]); // 依赖项
+
+  // 打开报告管理弹窗
+  const openReportModal = () => {
+    setReportModalVisible(true);
+  };
+
+  // 处理报告表单提交
+  const handleReportSubmit = async (values: any) => {
+    try {
+      const resp = await applyOnlineReportMap({
+        code: currentEditRow.code,
+        onlineReportCode: values.reportId,
+        onlineRemark: values.remark
+      });
+      if (resp) {
+        message.success('提交成功');
+        set_currentEditRow((prevRow: any) => ({
+          ...prevRow,
+          onlineRemark:values.remark,
+          onlineReportCode: values.reportId
+        }));
+        return true; // 提交成功后关闭弹窗
+      }
+    } catch (error) {
+      console.error('表单验证或提交失败:', error);
+      message.error('提交失败'); // 添加错误提示
+      return false; // 提交失败,保持弹窗打开
+    }
+    // 如果 API 调用失败 (resp 为 false),显式返回 false
+    message.error('提交失败');
+    return false;
+  };
+
   return (
     <div className="IndicatorMana">
       <DrawerForm actType={drawerActype} visible={drawerVisible} onVisibleChange={onVisibleChangeHandle} onFinish={(data) => drawerFormCommitHanndle(data)} record={currentEditRowData} />
 
+      {/* 报告管理弹窗 */}
+      <ModalForm
+        title="报告管理"
+        visible={reportModalVisible}
+        onVisibleChange={setReportModalVisible}
+        onFinish={handleReportSubmit}
+        width={500}
+        initialValues={currentEditRow&&{reportId: currentEditRow.onlineReportCode,
+          remark: currentEditRow.onlineRemark}}
+        formRef={reportForm}
+        modalProps={{ 
+          destroyOnClose: true,
+          bodyStyle: {
+            maxHeight: '72vh',
+            overflowY: 'auto',
+            overflowX: 'hidden'
+          }
+        }}
+      >
+        <ProFormText
+          name="reportId"
+          label="关联报告ID"
+          placeholder="请输入"
+          rules={[{ required: true, message: '请输入关联报告ID' }]}
+        />
+        <ProFormTextArea
+          name="remark"
+          label="备注说明"
+          placeholder="请输入"
+          fieldProps={{
+            rows: 4,
+          }}
+        />
+      </ModalForm>
+
       {actionType == 'DETAIL' && (
         <div className="FillingMana-detail">
           <div className="FillingMana-detail-header">
@@ -508,8 +603,15 @@ const IndicatorMana = () => {
 
           {currentBigTab === '1' && (
             <div className="currentBigTab1content">
-              <img src={require('../../../../../public/images/empty_bigger.png')} alt="" />
-              <div className="detail-noAuthRecord-title">暂无内容</div>
+              {/* <img src={require('../../../../../public/images/empty_bigger.png')} alt="" />
+              <div className="detail-noAuthRecord-title">暂无内容</div> */}
+              <div className='currentBigTab1content_header'>
+                <span>{currentEditRow?.onlineRemark}</span>
+                <div className='btn' onClick={openReportModal}>
+                  <IconFont type='iconpeizhi' /> 报告管理
+                </div>
+              </div>
+              {(currentEditRow&&currentEditRow.onlineReportCode)&&<FrameComponent link={`/platform/setting/embeddedDashboard/${currentEditRow.onlineReportCode}?noTopbar=true&noMenu=true`} />}
             </div>
           )}
 

+ 54 - 37
src/pages/platform/setting/indicatorMana/style.less

@@ -89,51 +89,68 @@
     }
 
     .currentBigTab1content {
-      display: flex;
-      width: 100%;
+
       height: calc(100vh - 210px);
-      flex-direction: column;
-      justify-content: center;
-      align-items: center;
       padding: 16px;
       background: #ffffff;
       border-radius: 4px;
 
-      & > img {
-        width: 160px;
-        height: 140px;
-      }
+      // & > img {
+      //   width: 160px;
+      //   height: 140px;
+      // }
 
-      .detail-noAuthRecord-title {
-        font-weight: 500;
-        font-size: 24px;
-        height: 24px;
-        color: #17181a;
-        line-height: 24px;
-        margin-top: 24px;
-        margin-bottom: 16px;
-      }
+      // .detail-noAuthRecord-title {
+      //   font-weight: 500;
+      //   font-size: 24px;
+      //   height: 24px;
+      //   color: #17181a;
+      //   line-height: 24px;
+      //   margin-top: 24px;
+      //   margin-bottom: 16px;
+      // }
 
-      .detail-noAuthRecord-title-sub {
-        font-weight: 400;
-        font-size: 14px;
-        height: 14px;
-        color: #7a8599;
-        line-height: 14px;
-      }
+      // .detail-noAuthRecord-title-sub {
+      //   font-weight: 400;
+      //   font-size: 14px;
+      //   height: 14px;
+      //   color: #7a8599;
+      //   line-height: 14px;
+      // }
+
+      // .backBtn {
+      //   width: 56px;
+      //   height: 24px;
+      //   text-align: center;
+      //   margin-top: 24px;
+      //   background: #3377ff;
+      //   border-radius: 4px;
+      //   font-weight: 400;
+      //   font-size: 14px;
+      //   color: #ffffff;
+      //   line-height: 24px;
+      //   cursor: pointer;
+      // }
 
-      .backBtn {
-        width: 56px;
-        height: 24px;
-        text-align: center;
-        margin-top: 24px;
-        background: #3377ff;
-        border-radius: 4px;
-        font-weight: 400;
-        font-size: 14px;
-        color: #ffffff;
-        line-height: 24px;
-        cursor: pointer;
+      .currentBigTab1content_header {
+        width: 100%;
+        display: flex;
+        flex-direction: row;
+        justify-content: space-between;
+        align-items: center;
+        margin-bottom: 16px;
+        &>span {
+            font-size: 16px;
+        }
+        .btn {
+          cursor: pointer;
+          padding: 2px 6px;
+          font-size: 16px;
+          color: #3376fe;
+          border-radius: 4px;
+          text-align: center;
+          border: 1px solid #3376fe;
+        }
       }
     }
 

+ 2 - 2
src/pages/platform/setting/userManage/index.tsx

@@ -1,7 +1,7 @@
 /*
  * @Author: your name
  * @Date: 2022-01-11 09:43:18
- * @LastEditTime: 2025-01-14 10:50:18
+ * @LastEditTime: 2025-05-13 15:32:58
  * @LastEditors: code4eat awesomedema@gmail.com
  * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  * @FilePath: /KC-MiddlePlatform/src/pages/platform/userManage/index.tsx
@@ -261,7 +261,7 @@ const UserManage: FC<PageProps> = ({ userManageModel: state, dispatch }) => {
           } = values;
 
           let formData = new FormData();
-          console.log({ values });
+
           formData.append('file', fileList[0].originFileObj);
 
           dispatch &&

+ 11 - 1
src/service/indicator.ts

@@ -2,7 +2,7 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2022-07-15 10:13:10
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2025-03-07 13:42:09
+ * @LastEditTime: 2025-04-25 10:07:12
  * @FilePath: /KC-MiddlePlatform/src/service/indicator.ts
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -99,3 +99,13 @@ export const getIndicatorCateList_old = async (params?: { menuCode: string }) =>
     params: params,
   });
 };
+
+
+//编辑指标绑定的报告
+
+export const applyOnlineReportMap = async (params?: { code: string,onlineReportCode:string,onlineRemark:string }) => {
+  return request<any[]>('/centerSys/indicator/applyOnlineReportMap', {
+    method: 'POST',
+    data: params,
+  });
+};

+ 168 - 92
src/utils.ts

@@ -1,22 +1,26 @@
 /*
  * @Author: your name
  * @Date: 2022-01-13 17:09:05
- * @LastEditTime: 2023-11-09 15:38:06
+ * @LastEditTime: 2025-04-17 11:01:43
  * @LastEditors: code4eat awesomedema@gmail.com
  * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  * @FilePath: /KC-MiddlePlatform/src/utils.js
  */
 
-import axios from 'axios';
+import axios, { AxiosResponse } from 'axios';
 import { Key } from 'react';
 
-export const randomString = (length: number) => {
+/**
+ * 生成指定长度的随机字符串
+ * @param length 字符串长度
+ * @returns 随机字符串
+ */
+export const randomString = (length: number): string => {
   const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_=-';
-  let result = '';
-  for (let i = length; i > 0; --i) {
-    result += chars[Math.floor(Math.random() * chars.length)];
-  }
-  return result;
+  return Array.from(
+    { length },
+    () => chars[Math.floor(Math.random() * chars.length)]
+  ).join('');
 };
 
 export interface TreeItemType {
@@ -24,66 +28,100 @@ export interface TreeItemType {
   [key: string]: any;
 }
 
-//获取树结构指定的值集合
-export const getValsFromTree = (data: TreeItemType[], key: string) => {
-  let result: any[] = [];
-  function looper(data: TreeItemType[], key: string) {
-    data.forEach((t) => {
-      if (t[key]) {
-        result.push(t[key]);
+/**
+ * 从树结构中获取指定键的所有值
+ * @param data 树结构数据
+ * @param key 要获取的键名
+ * @returns 值的数组
+ */
+export const getValsFromTree = (data: TreeItemType[], key: string): any[] => {
+  const result: any[] = [];
+  
+  const traverse = (nodes: TreeItemType[]): void => {
+    nodes.forEach((node) => {
+      if (node[key]) {
+        result.push(node[key]);
       }
-      if (t.children && t.children.length > 0) {
-        looper(t.children, key);
+      if (node.children?.length) {
+        traverse(node.children);
       }
     });
-  }
-  looper(data, key);
+  };
+
+  traverse(data);
   return result;
 };
 
 export const searchTree = (treeData: TreeItemType[]) => {};
 
-export const downloadTemplateReq = (path: string) => {
-  const userData = localStorage.getItem('userData');
-  const { token = '' } = JSON.parse(userData as any);
-
-  axios({
-    method: 'get',
-    url: `/gateway/${path}`,
-    responseType: 'blob',
-    headers: { token },
-  }).then(function (response: any) {
-    //console.log({ 'chunk': response });
+/**
+ * 下载模板文件
+ * @param path 文件路径
+ */
+export const downloadTemplateReq = async (path: string): Promise<void> => {
+  try {
+    const userData = localStorage.getItem('userData');
+    const { token = '' } = JSON.parse(userData || '{}');
+
+    const response: AxiosResponse = await axios({
+      method: 'get',
+      url: `/gateway/${path}`,
+      responseType: 'blob',
+      headers: { token },
+    });
+
     const filename = decodeURI(response.headers['content-disposition']);
-    const objectUrl = URL.createObjectURL(
-      new Blob([response.data], {
-        type: 'application/vnd.ms-excel',
-      }),
-    );
+    const blob = new Blob([response.data], {
+      type: 'application/vnd.ms-excel',
+    });
+    const objectUrl = URL.createObjectURL(blob);
+    
     const link = document.createElement('a');
-    // 设置导出的文件名称
-    link.download = `${filename}` + '.xls';
-    link.style.display = 'none';
+    link.download = `${filename}.xls`;
     link.href = objectUrl;
-    link.click();
+    link.style.display = 'none';
     document.body.appendChild(link);
-  });
+    link.click();
+    document.body.removeChild(link);
+    URL.revokeObjectURL(objectUrl);
+  } catch (error) {
+    console.error('下载模板失败:', error);
+    throw error;
+  }
 };
 
-let parent: any = undefined;
-
-export const getDeepestTreeData: any = (tree: any, childrenName: string) => {
-  if (tree[`${childrenName}`] && tree[`${childrenName}`].length > 0) {
-    parent = tree;
-    return getDeepestTreeData(tree[`${childrenName}`][0], childrenName);
-  }
+/**
+ * 获取树结构中最深的节点及其父节点
+ * @param tree 树结构
+ * @param childrenName 子节点属性名
+ * @returns [最深节点, 父节点]
+ */
+export const getDeepestTreeData = <T extends { [key: string]: any }>(
+  tree: T,
+  childrenName: string
+): [T, T | undefined] => {
+  let parent: T | undefined;
+  
+  const traverse = (node: T): T => {
+    if (node[childrenName]?.length) {
+      parent = node;
+      return traverse(node[childrenName][0]);
+    }
+    return node;
+  };
 
-  return [tree, parent];
+  return [traverse(tree), parent];
 };
 
-export const uniqueFunc = (arr: any[], uniId: string) => {
-  const res = new Map();
-  return arr.filter((item) => !res.has(item[uniId]) && res.set(item[uniId], 1));
+/**
+ * 数组去重
+ * @param arr 数组
+ * @param uniId 唯一标识字段
+ * @returns 去重后的数组
+ */
+export const uniqueFunc = <T extends { [key: string]: any }>(arr: T[], uniId: string): T[] => {
+  const uniqueMap = new Map();
+  return arr.filter((item) => !uniqueMap.has(item[uniId]) && uniqueMap.set(item[uniId], 1));
 };
 
 //获取树结构的所有叶子节点
@@ -93,30 +131,45 @@ interface TreeNode {
   child?: TreeNode[];
 }
 
+/**
+ * 获取树的所有叶子节点
+ * @param node 树节点
+ * @param leaves 叶子节点数组
+ * @returns 叶子节点数组
+ */
 export const getLeafNodes = (node: TreeNode, leaves: TreeNode[] = []): TreeNode[] => {
-  if (!node.child || node.child.length === 0) {
+  if (!node.child?.length) {
     leaves.push(node);
   } else {
-    for (const child of node.child) {
-      getLeafNodes(child, leaves);
-    }
+    node.child.forEach((child) => getLeafNodes(child, leaves));
   }
   return leaves;
 };
 
 //根据树结构中的某个属性的集合,获取对应的节点,返回一个集合
 
-export const findNodesBySomes = (node: TreeNode, keys: Set<string | number>, str: string): TreeNode[] => {
-  let results: TreeNode[] = [];
+/**
+ * 根据属性值查找节点
+ * @param node 树节点
+ * @param keys 属性值集合
+ * @param str 属性名
+ * @returns 匹配的节点数组
+ */
+export const findNodesBySomes = (
+  node: TreeNode,
+  keys: Set<string | number>,
+  str: string
+): TreeNode[] => {
+  const results: TreeNode[] = [];
 
-  if (keys.has(node[`${str}`])) {
+  if (keys.has(node[str])) {
     results.push(node);
   }
 
-  if (node.child) {
-    for (let child of node.child) {
-      results = results.concat(findNodesBySomes(child, keys, str));
-    }
+  if (node.child?.length) {
+    node.child.forEach((child) => {
+      results.push(...findNodesBySomes(child, keys, str));
+    });
   }
 
   return results;
@@ -124,59 +177,82 @@ export const findNodesBySomes = (node: TreeNode, keys: Set<string | number>, str
 
 //更改树结构集合的子集属性名
 
-export const renameChildListToChildren = (nodes: any[], key: string) => {
+/**
+ * 重命名子节点属性
+ * @param nodes 节点数组
+ * @param key 要重命名的属性名
+ * @returns 处理后的节点数组
+ */
+export const renameChildListToChildren = <T extends { [key: string]: any; children?: T[] }>(
+  nodes: T[],
+  key: string
+): T[] => {
   return nodes.map((node) => {
-    // 创建当前节点的副本
     const newNode = { ...node };
-
-    // 如果当前节点有 childList,则重命名为 children,并递归处理子节点
-    if (newNode[`${key}`] && newNode[`${key}`].length > 0) {
-      newNode.children = renameChildListToChildren(newNode[`${key}`], key);
+    if (newNode[key]?.length) {
+      newNode.children = renameChildListToChildren(newNode[key], key);
     }
-
     return newNode;
   });
 };
 
 //获取目标节点的所有父节点,且以集合的方式返回
 
-export const findParents = (tree: TreeNode[], targetNodeKeyName: string, targetNodeKeyVal: Key, path: TreeNode[] = []): TreeNode[] | null => {
+/**
+ * 查找节点的所有父节点
+ * @param tree 树结构
+ * @param targetNodeKeyName 目标节点键名
+ * @param targetNodeKeyVal 目标节点键值
+ * @param path 路径
+ * @returns 父节点数组或null
+ */
+export const findParents = (
+  tree: TreeNode[],
+  targetNodeKeyName: string,
+  targetNodeKeyVal: Key,
+  path: TreeNode[] = []
+): TreeNode[] | null => {
   for (const node of tree) {
-    // 如果找到目标节点,返回包含所有父节点的路径
-    if (node[`${targetNodeKeyName}`] === targetNodeKeyVal) {
+    if (node[targetNodeKeyName] === targetNodeKeyVal) {
       return [...path, node];
     }
 
-    // 如果当前节点有子节点,递归搜索子节点
-    if (node.children && node.children.length > 0) {
+    if (node.children?.length) {
       const result = findParents(node.children, targetNodeKeyName, targetNodeKeyVal, [...path, node]);
-      if (result) {
-        return result;
-      }
+      if (result) return result;
     }
   }
-
-  // 如果在当前分支找不到目标节点,返回 null
   return null;
 };
 
 //搜索树结果
 
-export const searchTreeAndKeepStructure = (nodes: any[], keyName: string, keyVal: any) => {
-  let result: any[] = [];
-
-  nodes.forEach((node) => {
-    // 检查当前节点是否匹配
-    if (node[`${keyName}`].indexOf(keyVal) != -1) {
-      result.push(node.children && node.children.length ? { ...node, children: searchTreeAndKeepStructure(node.children, keyName, keyVal) } : { ...node });
-    } else if (node.children && node.children.length) {
-      // 检查子节点
-      let children = searchTreeAndKeepStructure(node.children, keyName, keyVal);
+/**
+ * 搜索树结构并保持结构
+ * @param nodes 节点数组
+ * @param keyName 键名
+ * @param keyVal 键值
+ * @returns 匹配的节点数组
+ */
+export const searchTreeAndKeepStructure = <T extends { [key: string]: any }>(
+  nodes: T[],
+  keyName: string,
+  keyVal: any
+): T[] => {
+  return nodes.reduce<T[]>((result, node) => {
+    if (String(node[keyName]).includes(String(keyVal))) {
+      result.push({
+        ...node,
+        children: node.children?.length
+          ? searchTreeAndKeepStructure(node.children, keyName, keyVal)
+          : undefined,
+      });
+    } else if (node.children?.length) {
+      const children = searchTreeAndKeepStructure(node.children, keyName, keyVal);
       if (children.length) {
         result.push({ ...node, children });
       }
     }
-  });
-
-  return result;
+    return result;
+  }, []);
 };

Vissa filer visades inte eftersom för många filer har ändrats