恢复到没有加载效果的 除了效果别的还都是新的

This commit is contained in:
ylj20011123 2025-09-16 09:48:19 +08:00
parent b07d192d20
commit 14b175f28c
7 changed files with 0 additions and 699 deletions

View File

@ -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);
}
}

View File

@ -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<PageTransitionProps> = ({
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 (
<div className={`page-transition ${type}-transition ${className}`}>
<div
className={`transition-content ${transitionStage}`}
onAnimationEnd={() => {
if (transitionStage === 'fadeOut') {
setDisplayLocation(location);
setTransitionStage('fadeIn');
}
}}
>
{children}
</div>
</div>
);
};
// 简化版本 - 禁用所有动画
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 (
<div className={`simple-page-transition ${className}`}>
{children}
</div>
);
};
export default PageTransition;

View File

@ -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;
}
}
}
}

View File

@ -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<SkeletonLoadingProps> = ({
type = 'page',
rows = 3
}) => {
const renderPageSkeleton = () => (
<div className="skeleton-page-container">
<div className="skeleton-header">
<Skeleton.Input style={{ width: 200, height: 32 }} active />
<div className="skeleton-header-actions">
<Skeleton.Button style={{ width: 80 }} active />
<Skeleton.Button style={{ width: 80 }} active />
</div>
</div>
<div className="skeleton-content">
<div className="skeleton-filter-bar">
<Skeleton.Input style={{ width: 150, marginRight: 16 }} active />
<Skeleton.Input style={{ width: 150, marginRight: 16 }} active />
<Skeleton.Button style={{ width: 60 }} active />
</div>
<Card className="skeleton-table-card">
<Skeleton active paragraph={{ rows: 6 }} />
</Card>
</div>
</div>
);
const renderTableSkeleton = () => (
<div className="skeleton-table-container">
<div className="skeleton-table-header">
{Array.from({ length: 5 }).map((_, index) => (
<Skeleton.Input
key={index}
style={{ width: `${Math.random() * 50 + 80}px`, marginRight: 16 }}
active
/>
))}
</div>
{Array.from({ length: rows }).map((_, index) => (
<div key={index} className="skeleton-table-row">
{Array.from({ length: 5 }).map((_, cellIndex) => (
<Skeleton.Input
key={cellIndex}
style={{ width: `${Math.random() * 60 + 60}px`, marginRight: 16 }}
active
/>
))}
</div>
))}
</div>
);
const renderCardSkeleton = () => (
<div className="skeleton-card-container">
{Array.from({ length: rows }).map((_, index) => (
<Card key={index} className="skeleton-card">
<Skeleton active avatar paragraph={{ rows: 2 }} />
</Card>
))}
</div>
);
const renderFormSkeleton = () => (
<div className="skeleton-form-container">
<Card>
<div className="skeleton-form-title">
<Skeleton.Input style={{ width: 150 }} active />
</div>
{Array.from({ length: rows }).map((_, index) => (
<div key={index} className="skeleton-form-row">
<div className="skeleton-form-label">
<Skeleton.Input style={{ width: 80 }} active />
</div>
<div className="skeleton-form-control">
<Skeleton.Input style={{ width: '100%' }} active />
</div>
</div>
))}
<div className="skeleton-form-actions">
<Skeleton.Button style={{ width: 80, marginRight: 8 }} active />
<Skeleton.Button style={{ width: 80 }} active />
</div>
</Card>
</div>
);
switch (type) {
case 'table':
return renderTableSkeleton();
case 'card':
return renderCardSkeleton();
case 'form':
return renderFormSkeleton();
case 'page':
default:
return renderPageSkeleton();
}
};
export default SkeletonLoading;

View File

@ -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<SmartLoadingProps> = ({
fallback,
enablePreload = true
}) => {
const [loadingType, setLoadingType] = useState<'page' | 'table' | 'form' | 'card'>('page');
const [shouldShow, setShouldShow] = useState(false);
const [location, setLocation] = useState<any>(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 <SkeletonLoading type={loadingType} rows={getRowsByType(loadingType)} />;
};
/**
*
*/
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;

View File

@ -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);
}
}

View File

@ -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<TabVirtualizerProps> = ({
children,
tabKey,
isActive,
isLoaded,
isLoading,
lastAccessTime,
onReload,
className = '',
}) => {
const containerRef = useRef<HTMLDivElement>(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 (
<div className={`tab-virtualizer loading ${className}`} ref={containerRef}>
<PageLoading />
</div>
);
}
// 如果未加载(被卸载),显示占位符
if (!isLoaded) {
return (
<div className={`tab-virtualizer unloaded ${className}`} ref={containerRef}>
<div className="unloaded-placeholder">
<div className="placeholder-icon">
<ClockCircleOutlined style={{ fontSize: 48, color: '#d9d9d9' }} />
</div>
<div className="placeholder-content">
<h3></h3>
<p className="placeholder-desc">
使
</p>
{lastAccessTime && (
<p className="last-access-time">
{formatLastAccessTime(lastAccessTime)}
</p>
)}
<Button
type="primary"
icon={<ReloadOutlined />}
onClick={() => onReload(tabKey)}
className="reload-btn"
>
</Button>
</div>
</div>
</div>
);
}
// 正常渲染内容
return (
<div className={`tab-virtualizer loaded ${className}`} ref={containerRef}>
{children}
</div>
);
};
export default TabVirtualizer;