{renderStatusBadge(reviewPoint.status, reviewPoint.result)}
diff --git a/app/components/ui/LoadingBar.tsx b/app/components/ui/LoadingBar.tsx
new file mode 100644
index 0000000..9f47890
--- /dev/null
+++ b/app/components/ui/LoadingBar.tsx
@@ -0,0 +1,131 @@
+import { useState, useEffect } from 'react';
+import { createPortal } from 'react-dom';
+
+interface LoadingBarProps {
+ isVisible: boolean;
+ isComplete?: boolean;
+ height?: number;
+ className?: string;
+}
+
+// 加载进度条状态管理
+let showLoadingBar: (show: boolean, completeFirst?: boolean) => void = () => {};
+let loadingBarVisible = false;
+
+export function LoadingBar({
+ isVisible,
+ isComplete = false,
+ height = 2,
+ className = ''
+}: LoadingBarProps) {
+ // 当组件挂载时,进行进度的随机增长动画
+ const [progress, setProgress] = useState(0);
+
+ // 处理完成状态
+ useEffect(() => {
+ if (isComplete) {
+ // 立即将进度设为100%
+ setProgress(100);
+ }
+ }, [isComplete]);
+
+ useEffect(() => {
+ let interval: NodeJS.Timeout;
+
+ if (isVisible && progress < 90) {
+ // 随机增长进度,模拟加载 - 加速更新频率
+ interval = setInterval(() => {
+ setProgress(prev => {
+ // 进度越大,增长越慢
+ const increment = Math.random() * (20 - prev / 8);
+ return Math.min(prev + increment, 90);
+ });
+ }, 300); // 更高频率的更新
+ } else if (!isVisible) {
+ // 隐藏时重置进度
+ setProgress(0);
+ }
+
+ return () => {
+ if (interval) clearInterval(interval);
+ };
+ }, [isVisible, progress]);
+
+ // 当isVisible变化时,立即触发显示或隐藏效果
+ useEffect(() => {
+ if (isVisible) {
+ setProgress(20); // 立刻显示更多进度
+ } else if (progress > 0) {
+ // 如果已经有进度了,先快速完成进度,然后再隐藏
+ setProgress(100);
+ const timeout = setTimeout(() => {
+ setProgress(0);
+ }, 300); // 更快的隐藏
+ return () => clearTimeout(timeout);
+ }
+ }, [isVisible]);
+
+ if (!isVisible && progress === 0) return null;
+
+ return (
+
+
0 ? 1 : 0,
+ transition: 'width 0.3s ease-out, opacity 0.2s ease-in-out',
+ background: 'linear-gradient(90deg, var(--color-primary) 0%, var(--color-primary-hover) 50%, var(--color-primary) 100%)',
+ boxShadow: '0 0 10px rgba(0, 104, 74, 0.7)'
+ }}
+ />
+
+ );
+}
+
+// 创建一个全局加载进度条容器
+function LoadingBarContainer() {
+ const [visible, setVisible] = useState(loadingBarVisible);
+ const [completeBeforeHide, setCompleteBeforeHide] = useState(false);
+
+ // 保存引用以便外部调用
+ useEffect(() => {
+ showLoadingBar = (show: boolean, completeFirst: boolean = false) => {
+ if (!show && completeFirst) {
+ // 当隐藏时,先触发完成动画,再隐藏
+ setCompleteBeforeHide(true);
+ // 延迟隐藏,以让完成动画显示出来
+ setTimeout(() => {
+ loadingBarVisible = false;
+ setVisible(false);
+ setCompleteBeforeHide(false);
+ }, 500);
+ } else {
+ loadingBarVisible = show;
+ setVisible(show);
+ if (!show) {
+ setCompleteBeforeHide(false);
+ }
+ }
+ };
+ }, []);
+
+ // 使用Portal确保加载进度条总是渲染在body末尾
+ return typeof window !== 'undefined'
+ ? createPortal(
+
,
+ document.body
+ )
+ : null;
+}
+
+// 导出全局控制函数
+export const loadingBarService = {
+ show: () => showLoadingBar(true),
+ hide: () => showLoadingBar(false, true),
+ toggle: () => showLoadingBar(!loadingBarVisible),
+ isVisible: () => loadingBarVisible
+};
+
+export default LoadingBarContainer;
\ No newline at end of file
diff --git a/app/root.tsx b/app/root.tsx
index e4269b4..3f0b239 100644
--- a/app/root.tsx
+++ b/app/root.tsx
@@ -19,6 +19,7 @@ import "remixicon/fonts/remixicon.css";
import styles from "~/styles/main.css?url";
import messageModalStyles from "~/styles/components/message-modal.css?url";
import toastStyles from "~/styles/components/toast.css?url";
+import LoadingBarContainer from "~/components/ui/LoadingBar";
// 添加客户端hydration错误处理
// if (typeof window !== "undefined") {
@@ -87,6 +88,7 @@ export default function App() {
+