code4eat 2 달 전
부모
커밋
7b62ea8ff5

BIN
.DS_Store


BIN
BI大屏_web_old.zip


+ 48 - 0
README.md

@@ -0,0 +1,48 @@
+# MediScreen 项目说明
+
+本项目基于 `@umijs/max` 与 `Ant Design` 构建,为药房运营数据展示的可视化大屏。下文说明包含环境依赖、开发调试、构建部署以及常见问题,方便新的接手人快速上手。
+
+## 环境要求
+- Node.js:推荐 18.x LTS(至少 16.x)
+- 包管理器:建议使用 `npm`(仓库已附带 `package-lock.json`,如需使用 `yarn` 请保持版本一致)
+- 操作系统:macOS / Linux / Windows(需具备基础的 Git 与终端环境)
+
+## 快速开始
+1. **安装依赖**
+   ```bash
+   npm install
+   ```
+   安装过程中会自动执行 `umi setup`(来自 `postinstall`),用于初始化 Umi 所需的插件和类型。
+2. **本地开发**
+   ```bash
+   npm run dev
+   ```
+   默认在 `http://localhost:8000` 提供热更新开发服务。
+3. **接口配置**
+   - 所有数据请求通过 `src/utils/request.ts` 中封装的 `umi-request` 发送,目标网关请在对应文件或 `.env`/`.umirc.ts` 中调整。
+   - 如需切换后端地址,可在 `.umirc.ts` 的 `proxy` 或 `define` 配置里自定义常量。
+
+## 构建与部署
+- **构建生产包**
+  ```bash
+  npm run build
+  ```
+  构建结果会输出到 `dist/`(默认 `.gitignore` 已忽略)。将 `dist` 目录部署到任一静态资源服务器(Nginx、OSS 等)即可。
+- **部署注意事项**
+  - 确保目标环境存在与后端接口一致的域名或反向代理设置。
+  - 若依赖自定义字体文件(`src/assets`),部署时需一并保留。
+
+## 项目结构速览
+- `src/pages/index`:药房首页大屏(包含业务逻辑、接口请求与样式)
+- `src/pages/fenfa`:设备信息页
+- `src/components`:复用图表、进度条、动画等 UI 组件
+- `src/utils/request.ts`:请求封装及全局拦截
+- `config/` + `.umirc.ts`:Umi 配置(路由、构建、代理等)
+
+## 常见问题
+- **依赖安装失败**:确认 Node 版本满足要求并清理本地缓存 `npm cache clean --force` 再重试。
+- **接口请求报错**:检查后端地址、网络连通性以及本地 `proxy` 设置。
+- **构建产物显示异常**:确保部署的静态资源服务器开启 `gzip` 与正确的 `Content-Type`,并清除浏览器缓存。
+
+如需二次开发,建议先阅读 `src/pages/index/index.tsx` 了解数据流,再结合组件目录梳理复用能力,尽量提取可复用逻辑、避免重复代码。
+

+ 35 - 16
src/components/BarChartComponent/index.tsx

@@ -2,16 +2,16 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2024-07-02 17:50:08
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-08-29 14:49:54
+ * @LastEditTime: 2024-12-17 17:00:48
  * @FilePath: /MediScreen/src/components/BarChartComponent/index.tsx
- * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ * @Description: BarChartComponent示例代码
  */
 import React, { useEffect, useState } from 'react';
 import './style.less';
 
 interface BarChartItem {
   label: string;
-  value: number;
+  value: number | null; // 如果可能为null,请声明为 number | null
   unit: string;
 }
 
@@ -25,30 +25,49 @@ const BarChartComponent: React.FC<BarChartComponentProps> = ({ data }) => {
   const [animatedWidths, setAnimatedWidths] = useState<number[]>([]);
 
   useEffect(() => {
+    if (data.length === 0) {
+      setAnimatedWidths([]);
+      return;
+    }
+
+    const values = data.map(d => d.value || 0); // 如果为 null 则赋值为0
+    const maxValue = Math.max(...values);
+
     // 设置初始宽度为0
     setAnimatedWidths(new Array(data.length).fill(0));
     // 模拟数据加载并设置实际宽度
     setTimeout(() => {
-      setAnimatedWidths(data.map(item => (item.value / Math.max(...data.map(d => d.value))) * maxBarWidth));
+      setAnimatedWidths(values.map(v => (v / maxValue) * maxBarWidth));
     }, 100);
   }, [data]);
 
+  if (data.length === 0) {
+    return <div className="bar-chart">暂无数据</div>;
+  }
+
   return (
     <div className="bar-chart">
-      {data.map((item, index) => (
-        <div className="bar-chart-item" key={index}>
-          <div className="bar-chart-label">{item.label}</div>
-          <div className="bar-chart-bar-wrapper">
-            <div
-              className="bar-chart-bar"
-              style={{ width: `${animatedWidths[index]}px` }}
-            ></div>
-            <div className="bar-chart-value">
-              {item.value.toLocaleString()}
+      {data.map((item, index) => {
+        // 处理可能的null值,若为null则显示"N/A"
+        const displayValue = (typeof item.value === 'number' && !isNaN(item.value))
+          ? item.value.toLocaleString()
+          : 'N/A';
+
+        return (
+          <div className="bar-chart-item" key={index}>
+            <div className="bar-chart-label">{item.label}</div>
+            <div className="bar-chart-bar-wrapper">
+              <div
+                className="bar-chart-bar"
+                style={{ width: `${animatedWidths[index]}px` }}
+              ></div>
+              <div className="bar-chart-value">
+                {displayValue}{item.value !== null ? ` ${item.unit}` : ''}
+              </div>
             </div>
           </div>
-        </div>
-      ))}
+        );
+      })}
     </div>
   );
 };

+ 2 - 2
src/components/BarChartComponent/style.less

@@ -15,8 +15,8 @@
 .bar-chart-label {
     .px-to-vw(width, 350);
     text-align: left;
-    .px-to-vw(height,24);
-    .px-to-vw(line-height,24);
+    .px-to-vw(height,27);
+    .px-to-vw(line-height,27);
     .font-size-to-vw(24);
     color: #CCE6FF;
     overflow: hidden;

+ 59 - 100
src/components/BarChartZuhe/index.tsx

@@ -1,4 +1,4 @@
-import React, { useRef, useEffect, useState } from 'react';
+import React, { useRef, useEffect, useState, useCallback } from 'react';
 
 interface WindowData {
     windowNo: string;
@@ -20,41 +20,39 @@ const BarChartZuhe: React.FC<BarChartProps> = ({ data }) => {
     const canvasRef = useRef<HTMLCanvasElement>(null);
     const containerRef = useRef<HTMLDivElement>(null);
     const [maxValue, setMaxValue] = useState(0);
-    const [animationProgress, setAnimationProgress] = useState(0); // 用于动画的进度
+    const [animationProgress, setAnimationProgress] = useState(0);
 
-    useEffect(() => {
-        if (data && Array.isArray(data.windowList)) {
-            // 计算windowList中的最大值作为maxValue
-            const maxDrug = Math.max(...data.windowList.map(item => item.noDrugCount));
-            const maxComplete = Math.max(...data.windowList.map(item => item.noCompleteCount));
-            const calculatedMaxValue = Math.max(maxDrug, maxComplete) + 20;
-            setMaxValue(calculatedMaxValue);
-
-            // 开始动画
-            setAnimationProgress(0);
-            requestAnimationFrame(animate);
-        } else {
-            console.error('Invalid data structure: ', data);
-        }
-    }, [data]);
-
-    const animate = (timestamp?: number) => {
-        setAnimationProgress((prevProgress) => {
-            const newProgress = prevProgress + 0.02; // 增加进度
-            if (newProgress >= 1) {
-                return 1; // 动画完成时,将进度设置为1
-            } else {
-                requestAnimationFrame(animate);
-                return newProgress;
+    // 默认数据,用于防止空数据报错
+    const defaultData: BarChartData = {
+        noDrugCount: 0,
+        noCompleteCount: 0,
+        windowList: [],
+    };
+
+
+    const safeData = data || defaultData;
+
+    // 动画启动
+    const startAnimation = () => {
+        let progress = 0;
+
+        const step = () => {
+            progress += 0.02;
+            if (progress <= 1) {
+                setAnimationProgress(progress);
+                requestAnimationFrame(step);
             }
-        });
+        };
+
+        requestAnimationFrame(step);
     };
 
-    const drawChart = () => {
+    // 绘制图表
+    const drawChart = useCallback(() => {
         const canvas = canvasRef.current;
         const container = containerRef.current;
 
-        if (!canvas || !container || !Array.isArray(data.windowList)) return;
+        if (!canvas || !container || !Array.isArray(safeData.windowList)) return;
 
         const ctx = canvas.getContext('2d');
         if (!ctx) return;
@@ -65,11 +63,10 @@ const BarChartZuhe: React.FC<BarChartProps> = ({ data }) => {
         canvas.width = width;
         canvas.height = height;
 
-        // 设置左右边距和中央区域
         const leftMargin = width * 0.2;
         const rightMargin = width * 0.2;
         const chartWidth = width - leftMargin - rightMargin;
-        const groupWidth = chartWidth / data.windowList.length;
+        const groupWidth = chartWidth / safeData.windowList.length;
         const barWidth = groupWidth / 6;
 
         const chartHeight = height * 0.8;
@@ -78,7 +75,6 @@ const BarChartZuhe: React.FC<BarChartProps> = ({ data }) => {
 
         ctx.clearRect(0, 0, width, height);
 
-        // 动态计算字体大小
         const fontSize = Math.min(width, height) * 0.03;
         ctx.font = `${fontSize}px Arial`;
 
@@ -95,11 +91,11 @@ const BarChartZuhe: React.FC<BarChartProps> = ({ data }) => {
             ctx.moveTo(leftMargin, y);
             ctx.lineTo(width - rightMargin, y);
             ctx.stroke();
-            ctx.fillText((i * yStep).toString(), leftMargin - 10, y + fontSize / 2);
+            ctx.fillText(((i * yStep).toFixed(2)).toString(), leftMargin - 10, y + fontSize / 2);
         }
 
-        // 绘制中央柱状图区域
-        data.windowList.forEach((group, index) => {
+        // 绘制柱状图
+        safeData.windowList.forEach((group, index) => {
             const x = leftMargin + index * groupWidth + groupWidth / 4;
 
             const noDrugCount = group.noDrugCount || 0;
@@ -134,80 +130,34 @@ const BarChartZuhe: React.FC<BarChartProps> = ({ data }) => {
 
             ctx.fillText(`窗口${group.windowNo}`, x + groupWidth / 4, chartBottom + 10);
         });
+    }, [safeData, maxValue, animationProgress]);
 
-        // 绘制左侧区域
-        drawSideBox(ctx, leftMargin * 0.25, 0, leftMargin * 0.5, chartHeight, data.noDrugCount);
-
-        // 绘制右侧区域
-        drawSideBox(ctx, width - rightMargin * 0.75, 0, rightMargin * 0.5, chartHeight, data.noCompleteCount, true);
-    };
-
-    const drawSideBox = (
-        ctx: CanvasRenderingContext2D,
-        x: number,
-        y: number,
-        width: number,
-        height: number,
-        value: number,
-        reverse: boolean = false
-    ) => {
-        // 调整背景颜色和渐变
-        const gradient = ctx.createLinearGradient(0, y, 0, y + height);
-        gradient.addColorStop(0, '#051833');  // 更深的背景色
-        gradient.addColorStop(1, '#233958');  // 渐变到较浅的颜色
-
-        // 填充柱子背景
-        ctx.fillStyle = gradient;
-        ctx.fillRect(x, y, width, height);
-
-        // 绘制圆角矩形边框
-        const borderRadius = 8;
-        ctx.strokeStyle = '#334866';  // 边框颜色
-        ctx.lineWidth = 2;  // 边框宽度
-        ctx.beginPath();
-        ctx.moveTo(x + borderRadius, y);
-        ctx.lineTo(x + width - borderRadius, y);
-        ctx.quadraticCurveTo(x + width, y, x + width, y + borderRadius);
-        ctx.lineTo(x + width, y + height - borderRadius);
-        ctx.quadraticCurveTo(x + width, y + height, x + width - borderRadius, y + height);
-        ctx.lineTo(x + borderRadius, y + height);
-        ctx.quadraticCurveTo(x, y + height, x, y + height - borderRadius);
-        ctx.lineTo(x, y + borderRadius);
-        ctx.quadraticCurveTo(x, y, x + borderRadius, y);
-        ctx.closePath();
-        ctx.stroke();
-
-        // 根据value调整顶部的数字
-        ctx.font = `bold ${width * 0.3}px Arial`;
-        ctx.fillStyle = '#FFF';
-        ctx.textAlign = 'center';
-        ctx.fillText(value.toString().padStart(3, '0'), x + width / 2, y + height * 0.15);
-
-        ctx.font = `${width * 0.13}px Arial`;
-        ctx.fillText(reverse ? '待发预警(人)' : '待配预警(人)', x + width / 2 + 3, y + height * 0.9);
-
-        // 根据数值动态绘制小块,从底部开始递增
-        const barCount = Math.min(10, Math.round((value / maxValue) * 20)); // 最大绘制10个小块
-        const barHeight = height * 0.02;  // 小块的高度
-        const barSpacing = barHeight * 0.5;  // 小块之间的间距
-        const startY = y + height * 0.75;  // 小块的起始Y坐标,从底部开始
+    // 计算最大值并触发动画
+    useEffect(() => {
+        if (safeData.windowList.length > 0) {
+            const maxDrug = Math.max(...safeData.windowList.map(item => item.noDrugCount));
+            const maxComplete = Math.max(...safeData.windowList.map(item => item.noCompleteCount));
+            setMaxValue(Math.max(maxDrug, maxComplete) + 20);
 
-        ctx.fillStyle = reverse ? '#FFDA7A' : '#80C0FF';
-        for (let i = 0; i < barCount * animationProgress; i++) {
-            ctx.fillRect(x + width * 0.1, startY - i * (barHeight + barSpacing), width * 0.8, barHeight);
+            startAnimation();
         }
-    };
+    }, [safeData]);
 
+    // 监听动画进度或窗口大小变化时重新绘制图表
     useEffect(() => {
-        data&&drawChart();
-    }, [data, maxValue, animationProgress]);
+        drawChart();
+    }, [drawChart]);
 
+    // 监听窗口大小变化
     useEffect(() => {
-        window.addEventListener('resize', drawChart);
+        const handleResize = () => drawChart();
+        const debounceResize = debounce(handleResize, 200);
+
+        window.addEventListener('resize', debounceResize);
         return () => {
-            window.removeEventListener('resize', drawChart);
+            window.removeEventListener('resize', debounceResize);
         };
-    }, []);
+    }, [drawChart]);
 
     return (
         <div ref={containerRef} style={{ width: '100%', height: '100%', position: 'relative' }}>
@@ -216,4 +166,13 @@ const BarChartZuhe: React.FC<BarChartProps> = ({ data }) => {
     );
 };
 
+// debounce 函数
+const debounce = (fn: Function, delay: number) => {
+    let timer: NodeJS.Timeout;
+    return (...args: any[]) => {
+        clearTimeout(timer);
+        timer = setTimeout(() => fn(...args), delay);
+    };
+};
+
 export default BarChartZuhe;

+ 4 - 3
src/components/CircularProgressBar/index.tsx

@@ -2,7 +2,7 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2024-07-04 15:27:08
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-08-29 11:20:36
+ * @LastEditTime: 2024-12-17 16:47:29
  * @FilePath: /MediScreen/src/components/CircularProgressBar/index.tsx
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -29,7 +29,7 @@ const CircularProgressBar: React.FC<CircularProgressBarProps> = ({
   useEffect(() => {
     setAnimatedPercentage(0);
     const timer = setTimeout(() => {
-      setAnimatedPercentage(percentage);
+      setAnimatedPercentage(!Number.isNaN(percentage)?percentage:0);
     }, 100);
 
     return () => clearTimeout(timer);
@@ -40,6 +40,7 @@ const CircularProgressBar: React.FC<CircularProgressBarProps> = ({
   const circumference = 2 * Math.PI * radius;
   const offset = circumference - (animatedPercentage / 100) * circumference;
 
+
   return (
     <div className="circular-progress-bar">
       <svg
@@ -60,7 +61,7 @@ const CircularProgressBar: React.FC<CircularProgressBarProps> = ({
           fill="transparent"
           strokeWidth={strokeWidth}
           strokeDasharray={circumference}
-          strokeDashoffset={offset}
+          strokeDashoffset={typeof offset == 'number'?offset:5}
           strokeLinecap="round"
           r={radius}
           cx={size / 2}

+ 102 - 49
src/components/TimeSeriesChart/index.tsx

@@ -9,6 +9,8 @@ interface TimeSeriesChartProps {
 
 const TimeSeriesChart: React.FC<TimeSeriesChartProps> = ({ labels, barData1, barData2 }) => {
   const canvasRef = useRef<HTMLCanvasElement>(null);
+  const animationIdRef = useRef<number>();
+  const progressRef = useRef(0);
 
   useEffect(() => {
     const canvas = canvasRef.current;
@@ -17,120 +19,171 @@ const TimeSeriesChart: React.FC<TimeSeriesChartProps> = ({ labels, barData1, bar
     const ctx = canvas.getContext('2d');
     if (!ctx) return;
 
-    let animationId: number;
-    let progress = 0;
+    // 防抖函数
+    const debounce = (func: () => void, wait: number) => {
+      let timeout: NodeJS.Timeout;
+      return () => {
+        clearTimeout(timeout);
+        timeout = setTimeout(() => func(), wait);
+      };
+    };
+
+    // 数据校验
+    if (labels.length === 0 || barData1.length === 0 || barData2.length === 0) {
+      console.warn('无数据可绘制');
+      return;
+    }
 
-    // 动态计算最大数据值
+    if (barData1.length !== labels.length || barData2.length !== labels.length) {
+      console.error('labels、barData1 和 barData2 的长度不一致');
+      return;
+    }
+
+    // 计算最大值
     const maxDataValue = Math.max(...barData1, ...barData2);
+    if (!isFinite(maxDataValue) || maxDataValue < 0) {
+      console.error('数据中可能包含非数值或者无效数值');
+      return;
+    }
 
-    const draw = () => {
-      // 获取设备像素比
+    // 如果最大值为0,表示所有数据都是0,此时可以选择不绘制或给出提示
+    if (maxDataValue === 0) {
+      console.warn('所有数据均为0或无效数据');
+      return;
+    }
+
+    const yLabels = [0, maxDataValue * 0.2, maxDataValue * 0.4, maxDataValue * 0.6, maxDataValue * 0.8, maxDataValue];
+
+    const resizeCanvas = () => {
       const dpr = window.devicePixelRatio || 1;
-      // 设置画布的尺寸
-      canvas.width = canvas.clientWidth * dpr;
-      canvas.height = canvas.clientHeight * dpr;
-      ctx.scale(dpr, dpr);
+      const rect = canvas.getBoundingClientRect();
+      canvas.width = rect.width * dpr;
+      canvas.height = rect.height * dpr;
+      ctx.setTransform(dpr, 0, 0, dpr, 0, 0); // 重置并缩放
+      progressRef.current = 0; // 重置动画进度
+      draw(); // 调整大小后重新绘制
+    };
 
+    const draw = () => {
       // 清除画布
-      ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
+      ctx.clearRect(0, 0, canvas.width, canvas.height);
 
-      // 尺寸
       const chartWidth = canvas.clientWidth;
       const chartHeight = canvas.clientHeight;
-      const leftPadding = 0;
-      const rightPadding = 0;
+      const leftPadding = 50;
+      const rightPadding = 20;
       const bottomPadding = chartHeight * 0.2;
       const topPadding = chartHeight * 0.1;
       const totalBars = labels.length;
       const barGroupWidth = (chartWidth - leftPadding - rightPadding) / totalBars;
-      const barWidth = barGroupWidth / 3.5; // 每个组合两个柱子和一个间隙
+      
+      // 避免除零
+      if (!isFinite(barGroupWidth) || barGroupWidth <= 0) {
+        console.error('barGroupWidth 计算不正确,请检查数据和尺寸设置');
+        return;
+      }
+
+      const barWidth = barGroupWidth / 3.5; 
       const maxBarHeight = chartHeight - topPadding - bottomPadding;
-      const fontSize = chartWidth * 0.02; // 使用相对单位设置字体大小
+      const fontSize = chartWidth * 0.02; 
 
       // 绘制y轴标签和网格线
-      const yLabels = [0, maxDataValue * 0.2, maxDataValue * 0.4, maxDataValue * 0.6, maxDataValue * 0.8, maxDataValue];
       yLabels.forEach(value => {
-        const y = chartHeight - bottomPadding - (value / maxDataValue) * maxBarHeight;
-        // 网格线
+        const y = topPadding + (1 - value / maxDataValue) * maxBarHeight;
         ctx.strokeStyle = '#3e4b5b';
         ctx.lineWidth = 1;
         ctx.beginPath();
         ctx.moveTo(leftPadding, y);
         ctx.lineTo(chartWidth - rightPadding, y);
         ctx.stroke();
-        // y轴标签
+
         ctx.fillStyle = '#FFFFFF';
         ctx.textAlign = 'right';
+        ctx.textBaseline = 'middle';
         ctx.font = `${fontSize * 0.6}px Arial`;
-        ctx.fillText(`${Math.round(value)}`, leftPadding - 10, y + 3);
+        ctx.fillText(`${Math.round(value * 100) / 100}`, leftPadding - 10, y);
       });
 
-      // 绘制柱形图
-      const drawBar = (value: number, index: number, barOffset: number, colorStart: string, colorEnd: string) => {
-        const barHeight = (value / maxDataValue) * maxBarHeight * progress;
+      // 绘制单个柱子的函数
+      const drawBar = (
+        value: number,
+        index: number,
+        barOffset: number,
+        colorStart: string,
+        colorEnd: string
+      ) => {
+        // 避免除零和无效值
+        if (maxDataValue === 0) return;
+
+        const barHeight = (value / maxDataValue) * maxBarHeight * progressRef.current;
         const x = leftPadding + index * barGroupWidth + barWidth * barOffset;
-        const y = chartHeight - bottomPadding - barHeight;
+        const y = topPadding + (maxBarHeight - barHeight);
 
-        // 检查 x, y, barHeight 是否为有限数字
+        // 在创建渐变前校验数值
         if (!isFinite(x) || !isFinite(y) || !isFinite(barHeight)) {
           console.error('Invalid value for gradient creation:', { x, y, barHeight });
           return;
         }
 
-        // 创建渐变颜色
         const gradient = ctx.createLinearGradient(x, y, x, y + barHeight);
         gradient.addColorStop(0, colorStart);
         gradient.addColorStop(1, colorEnd);
 
-        // 绘制柱子
         ctx.fillStyle = gradient;
         ctx.fillRect(x, y, barWidth, barHeight);
+
+        // 显示柱子值
         ctx.fillStyle = '#FFFFFF';
         ctx.textAlign = 'center';
+        ctx.textBaseline = 'bottom';
         ctx.font = `${fontSize * 0.5}px Arial`;
-        ctx.fillText(`${value}`, x + barWidth / 2, y - 10);
+        ctx.fillText(`${value}`, x + barWidth / 2, y - 5);
       };
 
+      // 绘制 barData1 的柱子
       barData1.forEach((value, index) => {
-        drawBar(value, index, 0.25, 'rgba(77, 182, 225, 1)', 'rgba(77, 182, 225, 0.2)');
+        drawBar(value, index, 0.25, 'rgba(128, 191, 255, 1)', 'rgba(128, 191, 255, 0)');
       });
 
+      // 绘制 barData2 的柱子
       barData2.forEach((value, index) => {
-        drawBar(value, index, 1.25, 'rgba(255, 215, 0, 1)', 'rgba(255, 215, 0, 0.2)');
+        drawBar(value, index, 1.25, 'rgba(255, 234, 128, 1)', 'rgba(255, 234, 128, 0)');
       });
 
-      // 绘制x轴标签
+      // 绘制 x 轴标签
       labels.forEach((label, index) => {
         const x = leftPadding + index * barGroupWidth + barGroupWidth / 2;
         ctx.fillStyle = '#FFFFFF';
         ctx.textAlign = 'center';
+        ctx.textBaseline = 'top';
         ctx.font = `${fontSize * 0.7}px Arial`;
-        ctx.fillText(label, x, chartHeight - bottomPadding + 15);
+        ctx.fillText(label, x, chartHeight - bottomPadding + 10);
       });
 
       // 动画效果
-      if (progress < 1) {
-        progress += 0.02;
-        animationId = requestAnimationFrame(draw);
-      } else {
-        cancelAnimationFrame(animationId);
+      if (progressRef.current < 1) {
+        progressRef.current += 0.02;
+        animationIdRef.current = requestAnimationFrame(draw);
       }
     };
 
-    const resizeCanvas = () => {
-      const canvasWidth = canvas.clientWidth;
-      const canvasHeight = canvas.clientHeight;
-      canvas.width = canvasWidth;
-      canvas.height = canvasHeight;
-      draw();
-    };
-
+    // 初始化尺寸并首次绘制
     resizeCanvas();
-    window.addEventListener('resize', resizeCanvas);
-    animationId = requestAnimationFrame(draw);
+
+    const handleResize = debounce(() => {
+      resizeCanvas();
+    }, 100);
+
+    window.addEventListener('resize', handleResize);
+
+    // 启动动画
+    animationIdRef.current = requestAnimationFrame(draw);
+
     return () => {
-      window.removeEventListener('resize', resizeCanvas);
-      cancelAnimationFrame(animationId);
+      window.removeEventListener('resize', handleResize);
+      if (animationIdRef.current) {
+        cancelAnimationFrame(animationIdRef.current);
+      }
     };
   }, [labels, barData1, barData2]);
 

+ 223 - 171
src/pages/fenfa/index.tsx

@@ -2,21 +2,22 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2024-06-25 17:01:46
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-08-29 17:08:04
+ * @LastEditTime: 2025-03-14 13:52:36
  * @FilePath: /MediScreen/src/layouts/index.tsx
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
 
 import styles from './index.less';
-import { useEffect, useRef, useState } from 'react';
+import { useEffect, useState } from 'react';
 import ProgressBar from '@/components/ProgressBar';
 import TimelineComponent from '@/components/TimeLine';
 import BarChartComponent from '@/components/BarChartComponent';
 import ExpiryStockProgressBar from '@/components/ExpiryStockProgressBar';
 import NumberAnimation from '@/components/NumberAnimation';
-import { Link } from 'umi';
+import { Link, useLocation } from 'umi';
 import request from '@/utils/request';
 
+// 获取当前完整时间(格式:YYYY-MM-DD HH:MM)
 function getCurrentFormattedTime() {
   const now = new Date();
   const year = now.getFullYear();
@@ -27,8 +28,17 @@ function getCurrentFormattedTime() {
   return `${year}-${month}-${day} ${hours}:${minutes}`;
 }
 
+// 获取当前日期(格式:YYYY-MM-DD)
+function getCurrentFormattedDate() {
+  const now = new Date();
+  const year = now.getFullYear();
+  const month = String(now.getMonth() + 1).padStart(2, '0');
+  const day = String(now.getDate()).padStart(2, '0');
+  return `${year}-${month}-${day}`;
+}
+
 // 提取百分比数字并转换为数字类型的函数
-const extractPercentage = (str:string) => {
+const extractPercentage = (str: string) => {
   const match = str.match(/-?\d+(\.\d+)?/); // 匹配数字(包括小数和负数)
   return match ? parseFloat(match[0]) : 0; // 将匹配到的结果转换为浮点数
 };
@@ -57,32 +67,25 @@ const useViewport = () => {
   return viewport;
 };
 
-
-
-const defaultDate = `${new Date().getFullYear()}-${(new Date().getMonth() + 1)
-  .toString()
-  .padStart(2, '0')}-${new Date().getDate().toString().padStart(2, '0')}`;
-
-
-
+const defaultDate = getCurrentFormattedDate();
 
 export default function Index() {
-
   const [time, setTime] = useState(getCurrentFormattedTime());
   const { width, height } = useViewport();
-  const [equipmentDepreciation,set_equipmentDepreciation] = useState<any>(undefined);
-  const [equipmentAdvice,set_equipmentAdvice] = useState<any>(undefined);
-  const [equipmentPrescriptionInfo,set_equipmentPrescriptionInfo] = useState<any>(undefined);
-  const [equipmentMaintenance,set_equipmentMaintenance] = useState<any>(undefined);
-  const [equipmentInspection,set_equipmentInspection] = useState<any>(undefined);
-  const [equipmentDepartmentCostAllocation,set_equipmentDepartmentCostAllocation] = useState<any>(undefined);
-  const [equipmentExpire,set_equipmentExpire] = useState<any>(undefined);
+  const [equipmentDepreciation, set_equipmentDepreciation] = useState<any>(undefined);
+  const [equipmentAdvice, set_equipmentAdvice] = useState<any>(undefined);
+  const [equipmentPrescriptionInfo, set_equipmentPrescriptionInfo] = useState<any>(undefined);
+  const [equipmentMaintenance, set_equipmentMaintenance] = useState<any>(undefined);
+  const [equipmentInspection, set_equipmentInspection] = useState<any>(undefined);
+  const [equipmentDepartmentCostAllocation, set_equipmentDepartmentCostAllocation] = useState<any>(undefined);
+  const [equipmentExpire, set_equipmentExpire] = useState<any>(undefined);
   const [refreshRates, setRefreshRates] = useState<any[]>([]);
   const [hospInfo, set_hospInfo] = useState<any>(undefined);
-  const [date, set_date] = useState<string|undefined>(undefined);
+  const [date, set_date] = useState<string | undefined>(undefined);
+  const location = useLocation();
 
   const getRefreshRates = async () => {
-    const response:any[] = await request.get(`/dashBoard/getDict?dictCode=refresh_rate`);
+    const response: any[] = await request.get(`/dashBoard/getDict?dictCode=refresh_rate`);
     if (response) {
       setRefreshRates(response);
     }
@@ -94,49 +97,55 @@ export default function Index() {
       set_hospInfo(response);
     }
   };
+
   const getDate = async () => {
     const response: any = await request.get(`/dashBoard/getSysParameter?parameterCode=DEFAULT_DATE`);
     if (response) {
-      set_date(response.value?response.value:defaultDate);
+      set_date(response.value ? response.value : defaultDate);
+    } else {
+      set_date(defaultDate);
     }
   };
 
-
-  const getEquipmentDepreciation = async () => {
+  const getEquipmentDepreciation = async (date: string) => {
     const response = await request.get(`/dashBoard/equipmentDepreciation?date=${date}`);
     if (response) {
       set_equipmentDepreciation(response);
     }
   };
-  const getEquipmentAdvice = async () => {
+
+  const getEquipmentAdvice = async (date: string) => {
     const response = await request.get(`/dashBoard/equipmentAdvice?date=${date}`);
     if (response) {
       set_equipmentAdvice(response);
     }
   };
-  const getEquipmentPrescriptionInfo = async () => {
+
+  const getEquipmentPrescriptionInfo = async (date: string) => {
     const response = await request.get(`/dashBoard/equipmentPrescriptionInfo?dateTime=${date}`);
     if (response) {
       set_equipmentPrescriptionInfo(response);
     }
   };
-  const getEquipmentMaintenance = async () => {
+
+  const getEquipmentMaintenance = async (date: string) => {
     const response = await request.get(`/dashBoard/equipmentMaintenance?date=${date}`);
     if (response) {
       set_equipmentMaintenance(response);
     }
   };
-  const getEquipmentInspection = async () => {
+
+  const getEquipmentInspection = async (date: string) => {
     const response = await request.get(`/dashBoard/equipmentInspection?date=${date}`);
     if (response) {
       set_equipmentInspection(response);
     }
   };
 
-  const getEquipmentDepartmentCostAllocation = async () => {
-    if(date){
+  const getEquipmentDepartmentCostAllocation = async (date: string) => {
+    if (date) {
       const [year, month] = date.split('-');
-      const _date =`${year}-${month}`;
+      const _date = `${year}-${month}`;
       const response = await request.get(`/dashBoard/equipmentDepartmentCostAllocation?date=${_date}`);
       if (response) {
         set_equipmentDepartmentCostAllocation(response);
@@ -144,124 +153,91 @@ export default function Index() {
     }
   };
 
-  const getEquipmentExpire = async () => {
+  const getEquipmentExpire = async (date: string) => {
     const response = await request.get(`/dashBoard/equipmentExpire?date=${date}`);
     if (response) {
       set_equipmentExpire(response);
     }
   };
 
-  // const getRefreshRate = async () => {
-  //   const response = await request.get(`/dashBoard/getDict?dictCode=refresh_rate`);
-  //   if (response) {
-  //     set_equipmentExpire(response);
-  //   }
-  // };
-  
   useEffect(() => {
-    // 获取刷新频率配置
+    // 获取刷新频率配置、医院信息及初始日期
     getRefreshRates();
     getHospInfo();
     getDate();
-  
+
     // 每分钟更新一次当前时间
-    const intervalId = setInterval(() => {
+    const timeInterval = setInterval(() => {
       setTime(getCurrentFormattedTime());
-    }, 60000); // 每分钟更新一次
-  
+    }, 60000);
     return () => {
-      clearInterval(intervalId); // 组件卸载时清除计时器
+      clearInterval(timeInterval);
     };
-  }, []); // 初次加载时执行
+  }, []);
 
+  // 新增:定时检测当前日期是否发生变化,跨天后自动更新 date 状态
+  useEffect(() => {
+    const dateInterval = setInterval(() => {
+      const currentDate = getCurrentFormattedDate();
+      if (currentDate !== date) {
+        set_date(currentDate);
+      }
+    }, 60000);
+    return () => clearInterval(dateInterval);
+  }, [date]);
 
-  useEffect(()=>{
-      // 初始加载数据
-    if(date){
-      getEquipmentDepreciation();
-      getEquipmentAdvice();
-      getEquipmentPrescriptionInfo();
-      getEquipmentMaintenance();
-      getEquipmentInspection();
-      getEquipmentDepartmentCostAllocation();
-      getEquipmentExpire();
-    }
-  },[date])
-  
   useEffect(() => {
-    // 当refreshRates获取到数据后才开始设置定时器
-    if (Object.keys(refreshRates).length > 0) {
+    // 初始加载数据
+    if (date) {
+      getEquipmentDepreciation(date);
+      getEquipmentAdvice(date);
+      getEquipmentPrescriptionInfo(date);
+      getEquipmentMaintenance(date);
+      getEquipmentInspection(date);
+      getEquipmentDepartmentCostAllocation(date);
+      getEquipmentExpire(date);
+    }
+  }, [date]);
 
-  
-      // 明确设置为 number 类型数组
+  useEffect(() => {
+    // 当 refreshRates 获取到数据后,设置定时刷新
+    if (Object.keys(refreshRates).length > 0 && date) {
       const intervalIds: number[] = [];
-  
+
       const safeInterval = (
         fn: Function,
         delay: number | undefined,
         defaultDelay: number
       ): number => {
         if (delay && !isNaN(delay) && delay > 0) {
-          return setInterval(fn, delay * 1000) as unknown as number;  // 强制转换为 number
+          return setInterval(fn, delay * 1000) as unknown as number;
         } else {
-          return setInterval(fn, defaultDelay) as unknown as number;  // 强制转换为 number
+          return setInterval(fn, defaultDelay) as unknown as number;
         }
       };
-  
+
       intervalIds.push(
-        safeInterval(
-          getEquipmentDepreciation,
-          Number(refreshRates.find((a: any) => a.code === '10')?.value),
-          600000
-        ),
-        safeInterval(
-          getEquipmentAdvice,
-          Number(refreshRates.find((a: any) => a.code === '13')?.value),
-          600000
-        ),
-        safeInterval(
-          getEquipmentPrescriptionInfo,
-          Number(refreshRates.find((a: any) => a.code === '11')?.value),
-          600000
-        ),
-        safeInterval(
-          getEquipmentMaintenance,
-          Number(refreshRates.find((a: any) => a.code === '14')?.value),
-          600000
-        ),
-        safeInterval(
-          getEquipmentInspection,
-          Number(refreshRates.find((a: any) => a.code === '15')?.value),
-          600000
-        ),
-        safeInterval(
-          getEquipmentDepartmentCostAllocation,
-          Number(refreshRates.find((a: any) => a.code === '12')?.value),
-          600000
-        ),
-        safeInterval(
-          getEquipmentExpire,
-          Number(refreshRates.find((a: any) => a.code === '16')?.value),
-          600000
-        )
+        safeInterval(() => getEquipmentDepreciation(date), Number(refreshRates.find((a: any) => a.code === '10')?.value), 600000),
+        safeInterval(() => getEquipmentAdvice(date), Number(refreshRates.find((a: any) => a.code === '13')?.value), 600000),
+        safeInterval(() => getEquipmentPrescriptionInfo(date), Number(refreshRates.find((a: any) => a.code === '11')?.value), 600000),
+        safeInterval(() => getEquipmentMaintenance(date), Number(refreshRates.find((a: any) => a.code === '14')?.value), 600000),
+        safeInterval(() => getEquipmentInspection(date), Number(refreshRates.find((a: any) => a.code === '15')?.value), 600000),
+        safeInterval(() => getEquipmentDepartmentCostAllocation(date), Number(refreshRates.find((a: any) => a.code === '12')?.value), 600000),
+        safeInterval(() => getEquipmentExpire(date), Number(refreshRates.find((a: any) => a.code === '16')?.value), 600000)
       );
-  
-      // 清除定时器
+
       return () => {
         intervalIds.forEach(clearInterval);
       };
     }
-  }, [refreshRates]); // 当refreshRates更新时执行
-  
-  
-  
+  }, [refreshRates, date]);
 
   return (
     <div className={styles.MediScreen}>
       <div className={styles.topbar}>
         <img src={require('../../images/topImg2x.png')} alt="" />
         <div className={styles.topbarInfo}>
-          <div className={styles.hospName}>{hospInfo?.value??''}</div>
+          <div className={styles.hospName}>{hospInfo?.value ?? ''}</div>
           <div className={styles.date}>{time}</div>
         </div>
       </div>
@@ -270,26 +246,40 @@ export default function Index() {
           <div className={styles.blockOne}>
             <div className={styles.blockHeader}>设备折旧</div>
             <div className={styles.rowOne}>
-              <div className={styles.iconWrap}><img src={require('../../images/¥icon.png')} alt="" /></div>
+              <div className={styles.iconWrap}>
+                <img src={require('../../images/¥icon.png')} alt="" />
+              </div>
               <div className={styles.rowDetail}>
-                <div className={styles.name}>设备原值<span>*设备随时间变化会产生折旧</span></div>
-                <div className={styles.value}><NumberAnimation value={equipmentDepreciation?.equipValue??0} /></div>
+                <div className={styles.name}>
+                  设备原值<span>*设备随时间变化会产生折旧</span>
+                </div>
+                <div className={styles.value}>
+                  <NumberAnimation value={equipmentDepreciation?.equipValue ?? 0} />
+                </div>
               </div>
             </div>
             <div className={styles.rowTwo}>
-              <div className={styles.iconWrap}><img src={require('../../images/¥icon1.png')} alt="" /></div>
+              <div className={styles.iconWrap}>
+                <img src={require('../../images/¥icon1.png')} alt="" />
+              </div>
               <div className={styles.rowDetail}>
                 <div className={styles.typeBlock}>
                   <div className={styles.label}>设备净值</div>
-                  <div className={styles.value}><NumberAnimation value={equipmentDepreciation?.netValue??0} /></div>
+                  <div className={styles.value}>
+                    <NumberAnimation value={equipmentDepreciation?.netValue ?? 0} />
+                  </div>
                 </div>
                 <div className={styles.typeBlock}>
                   <div className={styles.label}>本月折旧</div>
-                  <div className={styles.value}><NumberAnimation value={equipmentDepreciation?.curentMonthDepr??0} /></div>
+                  <div className={styles.value}>
+                    <NumberAnimation value={equipmentDepreciation?.curentMonthDepr ?? 0} />
+                  </div>
                 </div>
                 <div className={styles.typeBlock}>
                   <div className={styles.label}>累计折旧</div>
-                  <div className={styles.value}><NumberAnimation value={equipmentDepreciation?.accumulatedDepr??0} /></div>
+                  <div className={styles.value}>
+                    <NumberAnimation value={equipmentDepreciation?.accumulatedDepr ?? 0} />
+                  </div>
                 </div>
               </div>
             </div>
@@ -297,36 +287,38 @@ export default function Index() {
           <div className={styles.blockTwo}>
             <div className={styles.blockHeader}>建议补药</div>
             <div className={styles.progressWrap}>
-              {/* <ProgressBar percentage={40} totalBoxes={40} /> */}
-              <div className={styles.infoOne}>缺药率<i>{equipmentAdvice?.lackPercentDisplay??'0%'}</i></div>
-              <div className={styles.infoTwo}>补药总数<i>{equipmentAdvice?.addMedCount??0}盒</i></div>
+              <div className={styles.infoOne}>
+                缺药率<i>{equipmentAdvice?.lackPercentDisplay ?? '0%'}</i>
+              </div>
+              <div className={styles.infoTwo}>
+                补药总数<i>{equipmentAdvice?.addMedCount ?? 0}盒</i>
+              </div>
             </div>
             <div className={styles.listWrap}>
-              {[...(equipmentAdvice?equipmentAdvice.list:[])].map((_, i) => (
-                <div
-                  key={i}
-                  className={styles.list}
-                >
+              {[
+                ...(equipmentAdvice ? equipmentAdvice.list : []),
+              ].map((item, i) => (
+                <div key={i} className={styles.list}>
                   <div className={styles.number}>{i + 1 >= 10 ? i + 1 : `0${i + 1}`}</div>
                   <div className={styles.title}>
-                    <div className={styles.name}>{_.medName}</div>
-                    <div className={styles.sub}>{_.manufacturer}</div>
+                    <div className={styles.name}>{item.medName}</div>
+                    <div className={styles.sub}>{item.manufacturer}</div>
                   </div>
                   <div className={styles.al}>
                     <div className={styles.label}>库存</div>
-                    <div className={styles.value}>{_.stock}</div>
+                    <div className={styles.value}>{item.stock}</div>
                   </div>
                   <div className={styles.al}>
                     <div className={styles.label}>低限</div>
-                    <div className={styles.value}>{_.avgDay}</div>
+                    <div className={styles.value}>{item.avgDay}</div>
                   </div>
                   <div className={styles.al}>
                     <div className={styles.label}>缺药率</div>
-                    <div className={styles.value}>{_.lackPercentDisplay}</div>
+                    <div className={styles.value}>{item.lackPercentDisplay}</div>
                   </div>
                   <div className={styles.al}>
                     <div className={styles.label}>建议领药量</div>
-                    <div className={styles.value}>{_.addMedCount}</div>
+                    <div className={styles.value}>{item.addMedCount}</div>
                   </div>
                 </div>
               ))}
@@ -334,54 +326,84 @@ export default function Index() {
           </div>
         </div>
         <div className={styles.middle}>
-          <div className={styles.title}>总处方数 <NumberAnimation value={equipmentPrescriptionInfo?.totalPrescriptionCount??0} duration={2000} />张</div>
+          <div className={styles.title}>
+            总处方数{' '}
+            <NumberAnimation value={equipmentPrescriptionInfo?.totalPrescriptionCount ?? 0} duration={2000} />张
+          </div>
           <div className={styles.dataWrap}>
             <div className={styles.dataBox}>
               <div className={styles.info}>
                 <div className={styles.option}>
                   <span className={styles.label}>直发率</span>
-                  <span className={styles.value}><NumberAnimation value={extractPercentage(equipmentPrescriptionInfo?.straightPercent?.percent??'0%')} />%</span>
+                  <span className={styles.value}>
+                    <NumberAnimation
+                      value={extractPercentage(equipmentPrescriptionInfo?.straightPercent?.percent ?? '0%')}
+                    />
+                    %
+                  </span>
                 </div>
                 <div className={`${styles.option}`}>
                   <span className={styles.label}>较昨日</span>
-                  <span className={styles.value}>{equipmentPrescriptionInfo?.straightPercent?.chain??'0%'}</span>
+                  <span className={styles.value}>{equipmentPrescriptionInfo?.straightPercent?.chain ?? '0%'}</span>
                 </div>
               </div>
               <div className={styles.cardBottom}>
-                <div className={styles.a}>今日<span>{equipmentPrescriptionInfo?.straightPercent?.todayCount??0}</span></div>
-                <div className={styles.a}>昨日<span>{equipmentPrescriptionInfo?.straightPercent?.yesterdayCount??0}</span></div>
+                <div className={styles.a}>
+                  今日<span>{equipmentPrescriptionInfo?.straightPercent?.todayCount ?? 0}</span>
+                </div>
+                <div className={styles.a}>
+                  昨日<span>{equipmentPrescriptionInfo?.straightPercent?.yesterdayCount ?? 0}</span>
+                </div>
               </div>
             </div>
             <div className={styles.dataBox}>
               <div className={styles.info}>
                 <div className={styles.option}>
                   <span className={styles.label}>混发率</span>
-                  <span className={styles.value}><NumberAnimation value={extractPercentage(equipmentPrescriptionInfo?.mixPercent?.percent??'0%')} />%</span>
+                  <span className={styles.value}>
+                    <NumberAnimation
+                      value={extractPercentage(equipmentPrescriptionInfo?.mixPercent?.percent ?? '0%')}
+                    />
+                    %
+                  </span>
                 </div>
                 <div className={`${styles.option} ${styles.yellow}`}>
                   <span className={styles.label}>较昨日</span>
-                  <span className={styles.value}>{equipmentPrescriptionInfo?.mixPercent?.chain??'0%'}</span>
+                  <span className={styles.value}>{equipmentPrescriptionInfo?.mixPercent?.chain ?? '0%'}</span>
                 </div>
               </div>
               <div className={styles.cardBottom}>
-                <div className={styles.a}>今日<span>{equipmentPrescriptionInfo?.mixPercent?.todayCount??0}</span></div>
-                <div className={styles.a}>昨日<span>{equipmentPrescriptionInfo?.mixPercent?.yesterdayCount??0}</span></div>
+                <div className={styles.a}>
+                  今日<span>{equipmentPrescriptionInfo?.mixPercent?.todayCount ?? 0}</span>
+                </div>
+                <div className={styles.a}>
+                  昨日<span>{equipmentPrescriptionInfo?.mixPercent?.yesterdayCount ?? 0}</span>
+                </div>
               </div>
             </div>
             <div className={styles.dataBox}>
               <div className={styles.info}>
                 <div className={styles.option}>
                   <span className={styles.label}>机发率</span>
-                  <span className={styles.value}><NumberAnimation value={extractPercentage(equipmentPrescriptionInfo?.machinePercent?.percent??'0%')} />%</span>
+                  <span className={styles.value}>
+                    <NumberAnimation
+                      value={extractPercentage(equipmentPrescriptionInfo?.machinePercent?.percent ?? '0%')}
+                    />
+                    %
+                  </span>
                 </div>
                 <div className={`${styles.option} ${styles.yellow}`}>
                   <span className={styles.label}>较昨日</span>
-                  <span className={styles.value}>{equipmentPrescriptionInfo?.machinePercent?.chain??'0%'}</span>
+                  <span className={styles.value}>{equipmentPrescriptionInfo?.machinePercent?.chain ?? '0%'}</span>
                 </div>
               </div>
               <div className={styles.cardBottom}>
-                <div className={styles.a}>今日<span>{equipmentPrescriptionInfo?.machinePercent?.todayCount??0}</span></div>
-                <div className={styles.a}>昨日<span>{equipmentPrescriptionInfo?.machinePercent?.yesterdayCount??0}</span></div>
+                <div className={styles.a}>
+                  今日<span>{equipmentPrescriptionInfo?.machinePercent?.todayCount ?? 0}</span>
+                </div>
+                <div className={styles.a}>
+                  昨日<span>{equipmentPrescriptionInfo?.machinePercent?.yesterdayCount ?? 0}</span>
+                </div>
               </div>
             </div>
           </div>
@@ -390,47 +412,73 @@ export default function Index() {
           </div>
           <div className={styles.titleSheb}>设备保养</div>
           <div className={styles.shebeiCard}>
-            {
-              [...((equipmentMaintenance&&equipmentMaintenance.length>0)?equipmentMaintenance:[{}])].map((_, i) => (
-                <div key={i} className={_.status ? `${styles.card}` : `${styles.card} ${styles.gray}`}>
-                  <div className={_.maintainFlag?`${styles.season} ${styles.completed}`:styles.season}>{_.maintainPlanName}</div>
-                  {/* <div className={styles.number}>{`${i + 1}/4`}</div> */}
-                  <div className={styles.name}>{_.maintainFlag ? '保养已完成' : '保养未完成'}</div>
-                  <div className={styles.date}>{_.maintainPlanDate}</div>
+            {[
+              ...(equipmentMaintenance && equipmentMaintenance.length > 0 ? equipmentMaintenance : [{}]),
+            ].map((item, i) => (
+              <div key={i} className={item.status ? `${styles.card}` : `${styles.card} ${styles.gray}`}>
+                <div className={item.maintainFlag ? `${styles.season} ${styles.completed}` : styles.season}>
+                  {item.maintainPlanName}
                 </div>
-              ))
-            }
+                <div className={styles.name}>{item.maintainFlag ? '保养已完成' : '保养未完成'}</div>
+                <div className={styles.date}>{item.maintainPlanDate}</div>
+              </div>
+            ))}
           </div>
           <div className={styles.titleSheb}>设备巡检</div>
-          <div><TimelineComponent data={equipmentInspection?(equipmentInspection.map((a:any)=>({
-              date:a.inspectDate,status:a.inspectFlag,person:a.inspectUser,inspectPlanName:a.inspectPlanName
-          }))):[]} /></div>
+          <div>
+            <TimelineComponent
+              data={
+                equipmentInspection
+                  ? equipmentInspection.map((a: any) => ({
+                      date: a.inspectDate,
+                      status: a.inspectFlag,
+                      person: a.inspectUser,
+                      inspectPlanName: a.inspectPlanName,
+                    }))
+                  : []
+              }
+            />
+          </div>
         </div>
         <div className={styles.right}>
           <div className={styles.blockHeader}>成本分摊</div>
           <div className={styles.barChartWrapper}>
-            <BarChartComponent data={(equipmentDepartmentCostAllocation&&equipmentDepartmentCostAllocation.length>0)?(equipmentDepartmentCostAllocation.map((a:any)=>({label:a.deptName,value:a.sum,unit:'元'}))):[{label:'无',value:0}]} />
+            <BarChartComponent
+              data={
+                equipmentDepartmentCostAllocation && equipmentDepartmentCostAllocation.length > 0
+                  ? equipmentDepartmentCostAllocation.map((a: any) => ({
+                      label: a.deptName,
+                      value: a.sum,
+                      unit: '元',
+                    }))
+                  : [{ label: '无', value: 0 }]
+              }
+            />
           </div>
           <div className={styles.blockHeader}>临近效期</div>
           <div className={styles.progressWrapper}>
-            <div className={styles.allexpired}>全部临期<i>{equipmentExpire?.expireCount??0}盒</i></div>
-            <div className={styles.expiredper}>临期占比<i>{equipmentExpire?.expirePercentDisplay??'0%'}</i></div>
-            <div className={styles.stockCount}>当前库存<i>{equipmentExpire?.totalCount??0}盒</i></div>
-            {/* <ProgressBar percentage={40} totalBoxes={40} filledColor="rgba(242, 255, 179, 1)" unfilledColor="rgba(242,255,179,0.2)" /> */}
+            <div className={styles.allexpired}>
+              全部临期<i>{equipmentExpire?.expireCount ?? 0}盒</i>
+            </div>
+            <div className={styles.expiredper}>
+              临期占比<i>{equipmentExpire?.expirePercentDisplay ?? '0%'}</i>
+            </div>
+            <div className={styles.stockCount}>
+              当前库存<i>{equipmentExpire?.totalCount ?? 0}盒</i>
+            </div>
           </div>
           <div className={styles.listWrap}>
-            {[...(equipmentExpire?.expireList??[])].map((_, i) => (
-              <div
-                key={i}
-                className={styles.list}
-              >
+            {[
+              ...(equipmentExpire?.expireList ?? []),
+            ].map((item, i) => (
+              <div key={i} className={styles.list}>
                 <div className={styles.number}>{i + 1 >= 10 ? i + 1 : `0${i + 1}`}</div>
                 <div className={styles.title}>
-                  <div className={styles.name}>{_.drugName}</div>
-                  <div className={styles.sub}>最短有效期至{_.nearExpire}</div>
+                  <div className={styles.name}>{item.drugName}</div>
+                  <div className={styles.sub}>最短有效期至{item.nearExpire}</div>
                 </div>
                 <div className={styles.progress}>
-                  <ExpiryStockProgressBar expiryCount={_.expireStock} stockCount={_.totalStock} />
+                  <ExpiryStockProgressBar expiryCount={item.expireStock} stockCount={item.totalStock} />
                 </div>
               </div>
             ))}
@@ -439,8 +487,12 @@ export default function Index() {
       </div>
       <div className={styles.bottom}>
         <div className={styles.btnGroup}>
-          <Link to="/"><div className={location.pathname == '/' ? `${styles.btn} ${styles.active}` : `${styles.btn}`}>药房首页</div></Link>
-          <Link to="/fenfa"><div className={location.pathname == '/fenfa' ? `${styles.btn} ${styles.active}` : `${styles.btn}`}>设备信息</div></Link>
+          <Link to="/">
+            <div className={location.pathname === '/' ? `${styles.btn} ${styles.active}` : `${styles.btn}`}>药房首页</div>
+          </Link>
+          <Link to="/fenfa">
+            <div className={location.pathname === '/fenfa' ? `${styles.btn} ${styles.active}` : `${styles.btn}`}>设备信息</div>
+          </Link>
         </div>
       </div>
     </div>

+ 93 - 86
src/pages/index/index.tsx

@@ -2,7 +2,7 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2024-06-25 17:01:46
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-08-29 16:59:52
+ * @LastEditTime: 2025-03-13 18:07:26
  * @FilePath: /MediScreen/src/layouts/index.tsx
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -24,7 +24,7 @@ import request from '@/utils/request';
 import SmoothLineChart from '@/components/SmoothLineChart';
 import BarChartZuhe from '@/components/BarChartZuhe';
 
-
+// 获取当前完整时间,格式为 "YYYY-MM-DD HH:MM"
 function getCurrentFormattedTime() {
   const now = new Date();
   const year = now.getFullYear();
@@ -35,6 +35,17 @@ function getCurrentFormattedTime() {
   return `${year}-${month}-${day} ${hours}:${minutes}`;
 }
 
+// 获取当前日期,格式为 "YYYY-MM-DD"
+function getCurrentFormattedDate() {
+  const now = new Date();
+  const year = now.getFullYear();
+  const month = String(now.getMonth() + 1).padStart(2, '0');
+  const day = String(now.getDate()).padStart(2, '0');
+  return `${year}-${month}-${day}`;
+}
+
+const defaultDate = getCurrentFormattedDate();
+
 const convertObjectToArray = (dataObject: any) => {
   if (!dataObject) return [];
   return Object.keys(dataObject).map(key => ({
@@ -53,14 +64,6 @@ const convertDataToArray = (dataObject: { [x: string]: any; }) => {
   }));
 };
 
-
-const defaultDate = `${new Date().getFullYear()}-${(new Date().getMonth() + 1)
-  .toString()
-  .padStart(2, '0')}-${new Date().getDate().toString().padStart(2, '0')}`;
-
-// const date = '2024-8-23';
-
-
 export default function Index() {
 
   const [time, setTime] = useState(getCurrentFormattedTime());
@@ -76,7 +79,7 @@ export default function Index() {
   const [departmentPrescriptionTop10, set_departmentPrescriptionTop10] = useState<any>(undefined);
   const [refreshRates, setRefreshRates] = useState<any[]>([]);
   const [hospInfo, set_hospInfo] = useState<any>(undefined);
-  const [date, set_date] = useState<string|undefined>(undefined);
+  const [date, set_date] = useState<string|undefined>(defaultDate);
 
   const getRefreshRates = async () => {
     const response: any[] = await request.get(`/dashBoard/getDict?dictCode=refresh_rate`);
@@ -91,20 +94,23 @@ export default function Index() {
       set_hospInfo(response);
     }
   };
+  // 当 DEFAULT_DATE 已关闭时直接使用当前日期
   const getDate = async () => {
     const response: any = await request.get(`/dashBoard/getSysParameter?parameterCode=DEFAULT_DATE`);
     if (response) {
-      set_date(response.value?response.value:defaultDate);
+      set_date(response.value ? response.value : defaultDate);
+    } else {
+      set_date(defaultDate);
     }
   };
 
-  const getDrugsStock = async () => {
+  const getDrugsStock = async (date:string) => {
     const response = await request.get(`/dashBoard/drugStock?date=${date}`);
     if (response) {
       set_drugStock(response);
     }
   };
-  const gettaskTimeDistribution = async () => {
+  const gettaskTimeDistribution = async (date:string) => {
     const response: any = await request.get(`/dashBoard/taskTimeDistribution?date=${date}`);
     if (response) {
       const data = [response.lessFiveCount, response.lessTenCount, response.lessFifteenCount, response.lessThirtyCount, response.lessSixtyCount,]
@@ -112,22 +118,21 @@ export default function Index() {
     }
   };
 
-  const getDrugTop10 = async () => {
+  const getDrugTop10 = async (date:string) => {
     const response: any[] = await request.get(`/dashBoard/drugTop10?date=${date}`);
     if (response) {
       set_drugTop10(response);
     }
   };
 
-  const getPharmacyOperationStatistics = async () => {
-
+  const getPharmacyOperationStatistics = async (date:string) => {
     const response = await request.get(`/dashBoard/pharmacyOperationStatistics?date=${date}`);
     if (response) {
       set_pharmacyOperationStatistics(response);
     }
   };
 
-  const getWindowMonitoring = async () => {
+  const getWindowMonitoring = async (date:string) => {
     const padZero = (num: number) => (num < 10 ? '0' : '') + num;  // 补零函数
     const hours = padZero(new Date().getHours());
     const minutes = padZero(new Date().getMinutes());
@@ -139,35 +144,33 @@ export default function Index() {
     }
   };
 
-  const getPrescriptionTimeCount = async () => {
+  const getPrescriptionTimeCount = async (date:string) => {
     const response = await request.get(`/dashBoard/prescriptionTimeCount?date=${date}`);
     if (response) {
       set_prescriptionTimeCount(response);
     }
   };
 
-  const getPharmacistWorkloadStatistics = async () => {
+  const getPharmacistWorkloadStatistics = async (date:string) => {
     const response = await request.get(`/dashBoard/PharmacistWorkloadStatistics?date=${date}`);
     if (response) {
       set_pharmacistWorkloadStatistics(response);
     }
   };
 
-  const getEquipmentDrugExpire = async () => {
+  const getEquipmentDrugExpire = async (date:string) => {
     const response = await request.get(`/dashBoard/equipmentDrugExpire?date=${date}`);
     if (response) {
       set_equipmentDrugExpire(response);
     }
   };
-  const getDepartmentPrescriptionTop10 = async () => {
+  const getDepartmentPrescriptionTop10 = async (date:string) => {
     const response = await request.get(`/dashBoard/departmentPrescriptionTop10?date=${date}`);
     if (response) {
       set_departmentPrescriptionTop10(response);
     }
   };
 
-
-
   // 定义定时器的设置函数
   const safeInterval = (
     fn: Function,
@@ -182,12 +185,10 @@ export default function Index() {
   };
 
   useEffect(() => {
-    // 获取刷新频率
+    // 获取刷新频率和医院信息,并获取初始日期
     getRefreshRates();
     getHospInfo();
     getDate();
-    // 初始加载数据
-
 
     // 每分钟更新一次当前时间
     const intervalId = setInterval(() => {
@@ -197,62 +198,73 @@ export default function Index() {
     return () => {
       clearInterval(intervalId); // 组件卸载时清除计时器
     };
-  }, []); // 初次加载时执行
+  }, []);
+
+  // 定时检测日期是否跨天,若跨天则更新 date 状态
+  useEffect(() => {
+    const dateInterval = setInterval(() => {
+      const currentDate = getCurrentFormattedDate();
+      if (currentDate !== date) {
+        set_date(currentDate);
+      }
+    }, 60000); // 每60秒检测一次
+    return () => clearInterval(dateInterval);
+  }, [date]);
 
   useEffect(() => {
     if(date){
-      getDrugsStock();
-      gettaskTimeDistribution();
-      getDrugTop10();
-      getPharmacyOperationStatistics();
-      getWindowMonitoring();
-      getPrescriptionTimeCount();
-      getPharmacistWorkloadStatistics();
-      getEquipmentDrugExpire();
-      getDepartmentPrescriptionTop10();
+      getDrugsStock(date);
+      gettaskTimeDistribution(date);
+      getDrugTop10(date);
+      getPharmacyOperationStatistics(date);
+      getWindowMonitoring(date);
+      getPrescriptionTimeCount(date);
+      getPharmacistWorkloadStatistics(date);
+      getEquipmentDrugExpire(date);
+      getDepartmentPrescriptionTop10(date);
     }
     
-  }, [date])
+  }, [date]);
 
   useEffect(() => {
     // 当refreshRates获取到数据后才开始设置定时器
-    if (refreshRates.length > 0) {
+    if (refreshRates.length > 0 && date) {
 
       const intervalIds: number[] = [];
 
       intervalIds.push(
         safeInterval(
-          getDrugsStock,
+          () => getDrugsStock(date),
           Number(refreshRates.find((a: any) => a.code === '10')?.value),
           600000
         ),
         safeInterval(
-          gettaskTimeDistribution,
+          () => gettaskTimeDistribution(date),
           Number(refreshRates.find((a: any) => a.code === '13')?.value),
           600000
         ),
         safeInterval(
-          getDrugTop10,
+          () => getDrugTop10(date),
           Number(refreshRates.find((a: any) => a.code === '11')?.value),
           600000
         ),
         safeInterval(
-          getPharmacyOperationStatistics,
+          () => getPharmacyOperationStatistics(date),
           Number(refreshRates.find((a: any) => a.code === '14')?.value),
           600000
         ),
         safeInterval(
-          getWindowMonitoring,
+          () => getWindowMonitoring(date),
           Number(refreshRates.find((a: any) => a.code === '15')?.value),
           600000
         ),
         safeInterval(
-          getPrescriptionTimeCount,
+          () => getPrescriptionTimeCount(date),
           Number(refreshRates.find((a: any) => a.code === '12')?.value),
           600000
         ),
         safeInterval(
-          getPharmacistWorkloadStatistics,
+          () => getPharmacistWorkloadStatistics(date),
           Number(refreshRates.find((a: any) => a.code === '16')?.value),
           600000
         )
@@ -263,14 +275,14 @@ export default function Index() {
         intervalIds.forEach(clearInterval);
       };
     }
-  }, [refreshRates]); // 当refreshRates更新时执行
+  }, [refreshRates, date]);
 
   return (
     <div className={styles.MediScreen}>
       <div className={styles.topbar}>
         <img src={require('../../images/shouyebiaoti.png')} alt="" />
         <div className={styles.topbarInfo}>
-          <div className={styles.hospName}>{hospInfo?.value??''}</div>
+          <div className={styles.hospName}>{hospInfo?.value ?? ''}</div>
           <div className={styles.date}>{time}</div>
         </div>
       </div>
@@ -343,7 +355,7 @@ export default function Index() {
                 <div className={styles.company}>厂商</div>
               </div>
               {
-                ([...drugTop10]).slice(0, 8).map((_, i) => {
+                ([...drugTop10]).map((_, i) => {
                   return (
                     <div key={i} className={styles.list}>
                       <div className={styles.sort}>{(i + 1) >= 10 ? i + 1 : `0${i + 1}`}</div>
@@ -353,7 +365,7 @@ export default function Index() {
                     </div>
                   )
                 })
-              }s
+              }
             </div>
           </div>
         </div>
@@ -369,11 +381,16 @@ export default function Index() {
                 <div className={styles.info}>
                   <div className={styles.option}>
                     <span className={styles.label}>患者人次</span>
-                    <span className={styles.value}><NumberAnimation value={Number(pharmacyOperationStatistics?.patientInfo?.totalCount ?? 0)} /><i>人</i></span>
+                    <span className={styles.value}>
+                      <NumberAnimation value={Number(pharmacyOperationStatistics?.patientInfo?.totalCount ?? 0)} />
+                      <i>人</i>
+                    </span>
                   </div>
                   <div className={`${styles.option}`}>
                     <span className={styles.label}>预测值</span>
-                    <span className={styles.value}>{Number(pharmacyOperationStatistics?.patientInfo?.forecastCount ?? 0)}<i>人</i></span>
+                    <span className={styles.value}>
+                      {Number(pharmacyOperationStatistics?.patientInfo?.forecastCount ?? 0)}<i>人</i>
+                    </span>
                   </div>
                 </div>
                 <div className={styles.cardBottom}>
@@ -390,11 +407,16 @@ export default function Index() {
                 <div className={styles.info}>
                   <div className={styles.option}>
                     <span className={styles.label}>当日处方</span>
-                    <span className={styles.value}><NumberAnimation value={Number(pharmacyOperationStatistics?.prescription?.totalCount ?? 0)} /><i>张</i></span>
+                    <span className={styles.value}>
+                      <NumberAnimation value={Number(pharmacyOperationStatistics?.prescription?.totalCount ?? 0)} />
+                      <i>张</i>
+                    </span>
                   </div>
                   <div className={`${styles.option} ${styles.yellow}`}>
                     <span className={styles.label}>预测值</span>
-                    <span className={styles.value}>{pharmacyOperationStatistics?.prescription?.forecastCount ?? 0}<i>张</i></span>
+                    <span className={styles.value}>
+                      {pharmacyOperationStatistics?.prescription?.forecastCount ?? 0}<i>张</i>
+                    </span>
                   </div>
                 </div>
                 <div className={styles.cardBottom}>
@@ -411,11 +433,16 @@ export default function Index() {
                 <div className={styles.info}>
                   <div className={styles.option}>
                     <span className={styles.label}>当日发药药盒</span>
-                    <span className={styles.value}><NumberAnimation value={Number(pharmacyOperationStatistics?.drugInfo?.totalCount ?? 0)} /><i>盒</i></span>
+                    <span className={styles.value}>
+                      <NumberAnimation value={Number(pharmacyOperationStatistics?.drugInfo?.totalCount ?? 0)} />
+                      <i>盒</i>
+                    </span>
                   </div>
                   <div className={`${styles.option} ${styles.yellow}`}>
                     <span className={styles.label}>预测值</span>
-                    <span className={styles.value}>{pharmacyOperationStatistics?.drugInfo?.forecastCount ?? 0}<i>盒</i></span>
+                    <span className={styles.value}>
+                      {pharmacyOperationStatistics?.drugInfo?.forecastCount ?? 0}<i>盒</i>
+                    </span>
                   </div>
                 </div>
                 <div className={styles.cardBottom}>
@@ -434,7 +461,6 @@ export default function Index() {
               <div className={styles.leftBlockType}>待配人数</div>
               <div className={styles.rightBlockType}>待发人数</div>
             </div>
-            {/* <CustomComplexChart /> */}
             <BarChartZuhe data={windowMonitoring} />
           </div>
           <div className={styles.titleSheb}>当日处方分时统计</div>
@@ -443,12 +469,12 @@ export default function Index() {
               <div className={styles.leftBlockType}>已缴费处方量</div>
               <div className={styles.rightBlockType}>已发放处方量</div>
             </div>
-            <TimeSeriesChart labels={prescriptionTimeCount ? (Object.keys(prescriptionTimeCount)) : []}
-              barData1={prescriptionTimeCount ? ((Object.keys(prescriptionTimeCount)).map(label => prescriptionTimeCount[label].feeCount)) : []}
-              barData2={prescriptionTimeCount ? ((Object.keys(prescriptionTimeCount)).map(label => prescriptionTimeCount[label].completeCount)) : []}
+            <TimeSeriesChart
+              labels={prescriptionTimeCount ? Object.keys(prescriptionTimeCount) : []}
+              barData1={prescriptionTimeCount ? Object.keys(prescriptionTimeCount).map(label => prescriptionTimeCount[label].feeCount) : [20,30]}
+              barData2={prescriptionTimeCount ? Object.keys(prescriptionTimeCount).map(label => prescriptionTimeCount[label].completeCount) : [10,15]}
             />
           </div>
-
         </div>
         <div className={styles.right}>
           <div className={styles.blockHeader}>药师工作量统计</div>
@@ -473,38 +499,19 @@ export default function Index() {
             }
           </div>
           <div className={styles.blockHeader}>设备内药品效期管理</div>
-          <div className={styles.SingleBarChartComponentWrap}><SingleBarChartComponent data={convertDataToArray(equipmentDrugExpire)} /></div>
+          <div className={styles.SingleBarChartComponentWrap}>
+            <SingleBarChartComponent data={convertDataToArray(equipmentDrugExpire)} />
+          </div>
           <div className={styles.keshichufangblockHeader}>科室处方量Top</div>
           <div className={styles.barChartWrapper}>
-            <BarChartComponent data={departmentPrescriptionTop10 ? (departmentPrescriptionTop10.map((a: any) => ({ label: a.encounterOrganization, value: a.prescriptionCount, unit: '元' }))) : []} />
-          </div>
-          {/* <div className={styles.blockHeader}>临近效期</div>
-          <div className={styles.progressWrapper}>
-            <ProgressBar percentage={40} totalBoxes={40} filledColor="rgba(242, 255, 179, 1)" unfilledColor="rgba(242,255,179,0.2)" />
+            <BarChartComponent data={departmentPrescriptionTop10 ? (departmentPrescriptionTop10.map((a: any) => ({ label: a.encounterOrganization, value: a.prescriptionCount, unit: '' }))) : []} />
           </div>
-          <div className={styles.listWrap}>
-            {[...Array(10)].map((_, i) => (
-              <div
-                key={i}
-                className={styles.list}
-              >
-                <div className={styles.number}>{i + 1 >= 10 ? i + 1 : `0${i + 1}`}</div>
-                <div className={styles.title}>
-                  <div className={styles.name}>阿魏酸哌嗪片 [50mgx180.00]</div>
-                  <div className={styles.sub}>四川成都亨达制药厂</div>
-                </div>
-                <div className={styles.progress}>
-                  <ExpiryStockProgressBar expiryCount={600} stockCount={1000} />
-                </div>
-              </div>
-            ))}
-          </div> */}
         </div>
       </div>
       <div className={styles.bottom}>
         <div className={styles.btnGroup}>
-          <Link to="/"><div className={location.pathname == '/' ? `${styles.btn} ${styles.active}` : `${styles.btn}`}>药房首页</div></Link>
-          <Link to="/fenfa"><div className={location.pathname == '/fenfa' ? `${styles.btn} ${styles.active}` : `${styles.btn}`}>设备信息</div></Link>
+          <Link to="/"><div className={location.pathname === '/' ? `${styles.btn} ${styles.active}` : `${styles.btn}`}>药房首页</div></Link>
+          <Link to="/fenfa"><div className={location.pathname === '/fenfa' ? `${styles.btn} ${styles.active}` : `${styles.btn}`}>设备信息</div></Link>
         </div>
       </div>
     </div>

+ 2 - 2
src/utils/request.ts

@@ -2,7 +2,7 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2024-08-26 14:05:03
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-11-20 10:09:56
+ * @LastEditTime: 2025-03-14 13:59:18
  * @FilePath: /MediScreen/src/utils/request.ts
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -11,7 +11,7 @@ import { message } from 'antd'; // 假设你在使用 Ant Design 的 message 组
 
 const request = axios.create({
   baseURL: '/api',
-  timeout: 10000000,
+  timeout:1800000,
 });
 
 // 请求拦截器