diff --git a/src/components/PageTransition/index.less b/src/components/PageTransition/index.less deleted file mode 100644 index 37fd1b7..0000000 --- a/src/components/PageTransition/index.less +++ /dev/null @@ -1,67 +0,0 @@ -// 简化版页面过渡动画 -.simple-page-transition { - // 默认不播放动画,只有带animate类时才播放 - &.animate { - animation: pageEnter 0.5s cubic-bezier(0.4, 0, 0.2, 1); - // 移除will-change避免HMR时样式冲突 - // will-change: opacity, transform; - } - - // 动画结束后重置will-change,避免持续占用GPU资源 - &.animate:not(:hover):not(:focus) { - animation-fill-mode: forwards; - } -} - -@keyframes pageEnter { - from { - opacity: 0; - transform: translateY(30px) scale(0.96); - // filter: blur(4px); // 注释掉filter属性,可能导致HMR问题 - } - to { - opacity: 1; - transform: translateY(0) scale(1); - // filter: blur(0); // 注释掉filter属性 - } -} - -// 复杂版页面过渡动画 -.page-transition { - position: relative; - height: 100%; - - .transition-content { - height: 100%; - - &.fadeIn { - animation: fadeIn 0.3s ease-in-out forwards; - } - - &.fadeOut { - animation: fadeOut 0.3s ease-in-out forwards; - } - } -} - -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -@keyframes fadeOut { - from { - opacity: 1; - transform: translateY(0); - } - to { - opacity: 0; - transform: translateY(-10px); - } -} \ No newline at end of file diff --git a/src/components/PageTransition/index.tsx b/src/components/PageTransition/index.tsx deleted file mode 100644 index 498f84b..0000000 --- a/src/components/PageTransition/index.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { CSSTransition, TransitionGroup } from 'react-transition-group'; -import { useLocation } from 'umi'; -import './index.less'; - -export interface PageTransitionProps { - children: React.ReactNode; - type?: 'fade' | 'slide' | 'scale'; - duration?: number; - className?: string; -} - -const PageTransition: React.FC = ({ - children, - type = 'fade', - duration = 300, - className = '', -}) => { - const location = useLocation(); - const [displayLocation, setDisplayLocation] = useState(location); - const [transitionStage, setTransitionStage] = useState('fadeIn'); - - useEffect(() => { - if (location !== displayLocation) setTransitionStage('fadeOut'); - }, [location, displayLocation]); - - return ( -
-
{ - if (transitionStage === 'fadeOut') { - setDisplayLocation(location); - setTransitionStage('fadeIn'); - } - }} - > - {children} -
-
- ); -}; - -// 简化版本 - 禁用所有动画 -export const SimplePageTransition: React.FC<{ - children: React.ReactNode; - className?: string; - enableAnimation?: boolean; - onAnimationEnd?: () => void; -}> = ({ - children, - className = '', - enableAnimation = false, // 强制禁用动画 - onAnimationEnd, -}) => { - // 立即执行回调,不等待动画 - const handleAnimationEnd = () => { - if (onAnimationEnd) { - onAnimationEnd(); - } - }; - - // 立即执行动画结束回调 - React.useEffect(() => { - if (onAnimationEnd) { - onAnimationEnd(); - } - }, [onAnimationEnd]); - - return ( -
- {children} -
- ); - }; - -export default PageTransition; \ No newline at end of file diff --git a/src/components/SkeletonLoading/index.less b/src/components/SkeletonLoading/index.less deleted file mode 100644 index e319965..0000000 --- a/src/components/SkeletonLoading/index.less +++ /dev/null @@ -1,163 +0,0 @@ -.skeleton-page-container { - padding: 24px; - min-height: 100vh; - background: #f5f5f5; - - .skeleton-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 24px; - padding: 16px 24px; - background: white; - border-radius: 6px; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06); - - .skeleton-header-actions { - display: flex; - gap: 8px; - } - } - - .skeleton-content { - .skeleton-filter-bar { - display: flex; - align-items: center; - margin-bottom: 16px; - padding: 16px 24px; - background: white; - border-radius: 6px; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06); - } - - .skeleton-table-card { - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06); - } - } -} - -.skeleton-table-container { - padding: 16px; - - .skeleton-table-header { - display: flex; - align-items: center; - padding: 12px 0; - border-bottom: 1px solid #f0f0f0; - margin-bottom: 16px; - } - - .skeleton-table-row { - display: flex; - align-items: center; - padding: 12px 0; - border-bottom: 1px solid #f9f9f9; - - &:last-child { - border-bottom: none; - } - } -} - -.skeleton-card-container { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); - gap: 16px; - padding: 16px; - - .skeleton-card { - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06); - } -} - -.skeleton-form-container { - padding: 24px; - - .skeleton-form-title { - margin-bottom: 24px; - padding-bottom: 16px; - border-bottom: 1px solid #f0f0f0; - } - - .skeleton-form-row { - display: flex; - align-items: center; - margin-bottom: 16px; - - .skeleton-form-label { - width: 120px; - margin-right: 16px; - } - - .skeleton-form-control { - flex: 1; - } - } - - .skeleton-form-actions { - margin-top: 32px; - padding-top: 16px; - border-top: 1px solid #f0f0f0; - text-align: center; - } -} - -// 骨架屏动画优化 -.ant-skeleton-element { - .ant-skeleton-input { - border-radius: 4px; - } - - .ant-skeleton-button { - border-radius: 4px; - } -} - -// 响应式设计 -@media (max-width: 768px) { - .skeleton-page-container { - padding: 12px; - - .skeleton-header { - flex-direction: column; - gap: 12px; - align-items: stretch; - - .skeleton-header-actions { - justify-content: center; - } - } - - .skeleton-content { - .skeleton-filter-bar { - flex-direction: column; - gap: 12px; - align-items: stretch; - - .ant-skeleton-input { - width: 100% !important; - } - } - } - } - - .skeleton-card-container { - grid-template-columns: 1fr; - padding: 12px; - } - - .skeleton-form-container { - padding: 12px; - - .skeleton-form-row { - flex-direction: column; - align-items: stretch; - - .skeleton-form-label { - width: 100%; - margin-right: 0; - margin-bottom: 8px; - } - } - } -} \ No newline at end of file diff --git a/src/components/SkeletonLoading/index.tsx b/src/components/SkeletonLoading/index.tsx deleted file mode 100644 index f7eed92..0000000 --- a/src/components/SkeletonLoading/index.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import React from 'react'; -import { Skeleton, Card } from 'antd'; -import './index.less'; - -interface SkeletonLoadingProps { - type?: 'page' | 'table' | 'card' | 'form'; - rows?: number; -} - -const SkeletonLoading: React.FC = ({ - type = 'page', - rows = 3 -}) => { - const renderPageSkeleton = () => ( -
-
- -
- - -
-
- -
-
- - - -
- - - - -
-
- ); - - const renderTableSkeleton = () => ( -
-
- {Array.from({ length: 5 }).map((_, index) => ( - - ))} -
- {Array.from({ length: rows }).map((_, index) => ( -
- {Array.from({ length: 5 }).map((_, cellIndex) => ( - - ))} -
- ))} -
- ); - - const renderCardSkeleton = () => ( -
- {Array.from({ length: rows }).map((_, index) => ( - - - - ))} -
- ); - - const renderFormSkeleton = () => ( -
- -
- -
- {Array.from({ length: rows }).map((_, index) => ( -
-
- -
-
- -
-
- ))} -
- - -
-
-
- ); - - switch (type) { - case 'table': - return renderTableSkeleton(); - case 'card': - return renderCardSkeleton(); - case 'form': - return renderFormSkeleton(); - case 'page': - default: - return renderPageSkeleton(); - } -}; - -export default SkeletonLoading; \ No newline at end of file diff --git a/src/components/SmartLoading/index.tsx b/src/components/SmartLoading/index.tsx deleted file mode 100644 index d5756bf..0000000 --- a/src/components/SmartLoading/index.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { useLocation } from 'umi'; -import SkeletonLoading from '../SkeletonLoading'; -import { routePreloader } from '@/utils/routePreloader'; - -interface SmartLoadingProps { - fallback?: React.ReactNode; - enablePreload?: boolean; -} - -/** - * 智能加载组件 - * 根据路由类型自动选择合适的骨架屏,并支持路由预加载 - */ -const SmartLoading: React.FC = ({ - fallback, - enablePreload = true -}) => { - const [loadingType, setLoadingType] = useState<'page' | 'table' | 'form' | 'card'>('page'); - const [shouldShow, setShouldShow] = useState(false); - const [location, setLocation] = useState(null); - - // 安全获取location,避免在路由未准备好时出错 - try { - const currentLocation = useLocation(); - if (!location) { - setLocation(currentLocation); - } - } catch (error) { - // 如果useLocation失败,使用window.location作为fallback - if (!location && typeof window !== 'undefined') { - setLocation({ pathname: window.location.pathname }); - } - } - - useEffect(() => { - // 对于页面切换,立即显示骨架屏,但对于初始加载延迟显示 - const isInitialLoad = !location?.pathname || location.pathname === '/'; - - if (isInitialLoad) { - // 初始加载时延迟显示,避免刷新时闪烁 - const timer = setTimeout(() => { - setShouldShow(true); - }, 200); - return () => clearTimeout(timer); - } else { - // 页面切换时立即显示,保持骨架屏效果 - setShouldShow(true); - } - }, [location?.pathname]); - - useEffect(() => { - if (!location?.pathname) return; - - // 根据路径判断页面类型,选择合适的骨架屏 - const path = location.pathname; - - if (path.includes('list') || path.includes('table')) { - setLoadingType('table'); - } else if (path.includes('form') || path.includes('edit') || path.includes('add')) { - setLoadingType('form'); - } else if (path.includes('card') || path.includes('dashboard')) { - setLoadingType('card'); - } else { - setLoadingType('page'); - } - - // 预加载相关路由(降低优先级,避免影响主流程) - if (enablePreload) { - setTimeout(() => { - routePreloader.preloadBasedOnUserBehavior(path); - }, 500); - } - }, [location?.pathname, enablePreload]); - - // 如果提供了自定义fallback,使用它 - if (fallback) { - return <>{fallback}; - } - - // 延迟显示,避免闪烁 - if (!shouldShow) { - return null; - } - - // 根据页面类型返回对应的骨架屏 - return ; -}; - -/** - * 根据加载类型获取合适的行数 - */ -function getRowsByType(type: string): number { - switch (type) { - case 'table': - return 8; // 表格通常显示更多行 - case 'form': - return 5; // 表单通常5-6个字段 - case 'card': - return 6; // 卡片网格通常6个 - case 'page': - default: - return 4; // 默认4行 - } -} - -export default SmartLoading; \ No newline at end of file diff --git a/src/components/TabVirtualizer/index.less b/src/components/TabVirtualizer/index.less deleted file mode 100644 index 7f8785c..0000000 --- a/src/components/TabVirtualizer/index.less +++ /dev/null @@ -1,79 +0,0 @@ -.tab-virtualizer { - height: 100%; - width: 100%; - - &.loading { - display: flex; - align-items: center; - justify-content: center; - min-height: 400px; - } - - &.unloaded { - display: flex; - align-items: center; - justify-content: center; - min-height: 400px; - background: #fafafa; - - .unloaded-placeholder { - text-align: center; - padding: 40px 20px; - - .placeholder-icon { - margin-bottom: 20px; - } - - .placeholder-content { - h3 { - color: #666; - font-size: 18px; - margin-bottom: 8px; - font-weight: 500; - } - - .placeholder-desc { - color: #999; - font-size: 14px; - margin-bottom: 12px; - line-height: 1.5; - } - - .last-access-time { - color: #bbb; - font-size: 12px; - margin-bottom: 20px; - } - - .reload-btn { - min-width: 120px; - height: 36px; - border-radius: 6px; - box-shadow: 0 2px 4px rgba(24, 144, 255, 0.2); - - &:hover { - transform: translateY(-1px); - box-shadow: 0 4px 8px rgba(24, 144, 255, 0.3); - transition: all 0.2s ease; - } - } - } - } - } - - &.loaded { - // 正常加载状态的样式 - animation: fadeIn 0.3s ease-in-out; - } -} - -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} \ No newline at end of file diff --git a/src/components/TabVirtualizer/index.tsx b/src/components/TabVirtualizer/index.tsx deleted file mode 100644 index 971da65..0000000 --- a/src/components/TabVirtualizer/index.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import React, { useEffect, useRef } from 'react'; -import { Button } from 'antd'; -import { ReloadOutlined, ClockCircleOutlined } from '@ant-design/icons'; -import PageLoading from '../PageLoading'; -import './index.less'; - -export interface TabVirtualizerProps { - children: React.ReactNode; - tabKey: string; - isActive: boolean; - isLoaded: boolean; - isLoading: boolean; - lastAccessTime?: number; - onReload: (tabKey: string) => void; - className?: string; -} - -const TabVirtualizer: React.FC = ({ - children, - tabKey, - isActive, - isLoaded, - isLoading, - lastAccessTime, - onReload, - className = '', -}) => { - const containerRef = useRef(null); - - // 格式化最后访问时间 - const formatLastAccessTime = (timestamp?: number) => { - if (!timestamp) return ''; - const now = Date.now(); - const diff = now - timestamp; - const minutes = Math.floor(diff / 60000); - const hours = Math.floor(minutes / 60); - - if (hours > 0) { - return `${hours}小时前访问`; - } else if (minutes > 0) { - return `${minutes}分钟前访问`; - } else { - return '刚刚访问'; - } - }; - - // 如果正在加载,显示加载状态 - if (isLoading) { - return ( -
- -
- ); - } - - // 如果未加载(被卸载),显示占位符 - if (!isLoaded) { - return ( -
-
-
- -
-
-

页面已休眠

-

- 为了优化内存使用,此页面内容已被暂时卸载 -

- {lastAccessTime && ( -

- {formatLastAccessTime(lastAccessTime)} -

- )} - -
-
-
- ); - } - - // 正常渲染内容 - return ( -
- {children} -
- ); -}; - -export default TabVirtualizer; \ No newline at end of file