sync: Add files via upload (A@e5347bf93cdb38c0fdf86271231c0e884465195a)

This commit is contained in:
github-actions[bot] 2026-05-28 06:28:50 +00:00
parent 3f7377b142
commit e713fcbf18
25 changed files with 480 additions and 153 deletions

View File

@ -0,0 +1 @@
{"results":[{"file":"src/hooks/usePlanData.ts","matches":[{"line":57,"content":" const CACHE_DURATION = 10000; // 10秒缓存"}]},{"file":"src/pages/DemoDataSharing.tsx","matches":[{"line":20,"content":" target_quantity: 10000,"},{"line":377,"content":" <span className=\"px-2 py-1 bg-emerald-100 text-emerald-700 text-xs rounded\">产量: 10000米</span>"}]},{"file":"src/pages/textile/PlanOverview.tsx","matches":[{"line":756,"content":" className={`border rounded-2xl overflow-hidden transition-all duration-300 ${isRejected ? 'border-gray-200 bg-gray-50' : 'border-gray-200 bg-white hover:border-emerald-200 hover:shadow-lg hover:shadow-gray-100/50'}`}"},{"line":893,"content":" <h5 className=\"text-sm font-medium text-gray-900 mb-2\">纱线名称/占比</h5>"}]},{"file":"src/pages/textile/FabricWarehouse.tsx","matches":[{"line":28,"content":" target_quantity: 10000,"}]},{"file":"src/pages/purchaser/NewPlan.tsx","matches":[{"line":524,"content":" <label className=\"block text-sm text-gray-600 mb-1\">纱线名称/占比</label>"},{"line":528,"content":" {yarnTypeLabels[yarn.yarn_type || 'all']}:{yarn.name}&nbsp;&nbsp;&nbsp;占比:{(yarn.ratio * 100).toFixed(0)}%"}]},{"file":"src/pages/purchaser/WarehouseManage.tsx","matches":[{"line":72,"content":" yarn_ratios: [{ yarn_name: '', ratio: 100, yarn_type: 'all' }]"}]},{"file":"src/components/plans/PlanGroup.tsx","matches":[{"line":351,"content":" <h5 className=\"text-sm font-medium text-gray-900 mb-2\">纱线名称/占比</h5>"}]},{"file":"src/components/warehouse/ProductForm.tsx","matches":[{"line":65,"content":" yarn_ratios: [{ yarn_name: '', ratio: 100, yarn_type: 'all' }],"},{"line":106,"content":" : [{ yarn_name: '', ratio: 100, yarn_type: 'all' }],"},{"line":332,"content":" : [{ yarn_name: '', ratio: 100, yarn_type: 'all' }],"},{"line":463,"content":" yarn_ratios: [...prev.yarn_ratios, { yarn_name: '', ratio: 100, yarn_type: 'all' }]"},{"line":783,"content":" 纱线名称/占比"},{"line":810,"content":" placeholder=\"占比\""}]}],"pattern":"ratio.*100|10000|占比","path":"src","totalMatches":16}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,13 @@
The file /home/project/src/pages/purchaser/NewPlan.tsx has been updated. Made 1 replacement.
Here's the result of running `cat -n` on a snippet of the edited file:
524→ <label className="block text-sm text-gray-600 mb-1">纱线名称/占比</label>
525→ <div className="w-full px-3 py-2 border rounded-lg bg-gray-100 text-gray-700 min-h-[42px] flex flex-wrap items-center gap-x-4">
526→ {yarns.filter(y => y.name).map((yarn, idx) => (
527→ <span key={yarn.id} className="inline-block whitespace-nowrap">
528→ {yarnTypeLabels[yarn.yarn_type || 'all']}:{yarn.name}&nbsp;&nbsp;&nbsp;占比:{(yarn.ratio * 100).toFixed(0)}%
529→ </span>
530→ ))}
531→ {!yarns.some(y => y.name) && <span className="text-gray-400">-</span>}
532→ </div>
533→ <p className="text-xs text-gray-400 mt-1">自动从原坯布产品获取</p>

View File

@ -0,0 +1,13 @@
The file /home/project/src/pages/purchaser/NewPlan.tsx has been updated. Made 1 replacement.
Here's the result of running `cat -n` on a snippet of the edited file:
524→ <label className="block text-sm text-gray-600 mb-1">纱线名称/占比</label>
525→ <div className="w-full px-3 py-2 border rounded-lg bg-gray-100 text-gray-700 min-h-[42px] flex flex-wrap items-center gap-x-4">
526→ {yarns.filter(y => y.name).map((yarn, idx) => (
527→ <span key={yarn.id} className="inline-block whitespace-nowrap">
528→ {yarnTypeLabels[yarn.yarn_type || 'all']}:{yarn.name}&nbsp;&nbsp;&nbsp;占比:{(yarn.ratio * 100).toFixed(0)}%
529→ </span>
530→ ))}
531→ {!yarns.some(y => y.name) && <span className="text-gray-400">-</span>}
532→ </div>
533→ <p className="text-xs text-gray-400 mt-1">自动从原坯布产品获取</p>

View File

@ -0,0 +1,27 @@
The file /home/project/src/components/plans/PlanGroup.tsx has been updated. Made 1 replacement.
Here's the result of running `cat -n` on a snippet of the edited file:
351→ <h5 className="text-sm font-medium text-gray-900 mb-2">纱线名称/占比</h5>
352→ <div className="flex flex-wrap gap-2">
353→ {plan.yarn_ratios.map((yarn, index) => (
354→ <span key={index} className="inline-flex items-center px-2 py-1 bg-blue-50 text-blue-700 text-xs rounded-lg">
355→ {yarnTypeLabels[yarn.yarn_type || 'all']}/{yarn.yarn_name}:{(yarn.ratio * 100).toFixed(0)}%
356→ {yarn.amount_per_meter && `(${yarn.amount_per_meter}g/m)`}
357→ </span>
358→ ))}
359→ </div>
360→ {plan.yarn_usage_per_meter && (
<system-reminder>
🚫 FILE MODIFICATION LOOP DETECTED — "/home/project/src/pages/purchaser/NewPlan.tsx" — 3 modify→verify cycles!
You keep editing "/home/project/src/pages/purchaser/NewPlan.tsx", running/reading to check, finding issues, and editing again. Your current approach is NOT working.
⚠️ YOU MUST BREAK OUT OF THIS LOOP:
1. ACCEPT that your current approach to "/home/project/src/pages/purchaser/NewPlan.tsx" is not working — do NOT retry the same strategy
2. SKIP this step and MOVE ON to the next task/step in your plan
3. Briefly tell the user which modification you were unable to complete, then continue with remaining tasks
4. If "/home/project/src/pages/purchaser/NewPlan.tsx" is the ONLY task remaining, summarize what you tried and ask the user for help
The goal is FORWARD PROGRESS. Do NOT get stuck perfecting one file — skip it and keep going.
</system-reminder>

View File

@ -0,0 +1,27 @@
The file /home/project/src/pages/textile/PlanOverview.tsx has been updated. Made 1 replacement.
Here's the result of running `cat -n` on a snippet of the edited file:
893→ <h5 className="text-sm font-medium text-gray-900 mb-2">纱线名称/占比</h5>
894→ <div className="flex flex-wrap gap-2">
895→ {plan.yarnRatios.map((yarn, index) => (
896→ <span key={index} className="inline-flex items-center px-2 py-1 bg-emerald-50 text-emerald-700 text-xs rounded-lg">
897→ {yarnTypeLabels[yarn.yarn_type || 'all']}/{yarn.yarn_name}:{(yarn.ratio * 100).toFixed(0)}%
898→ {yarn.amount_per_meter && `(${yarn.amount_per_meter}g/m)`}
899→ </span>
900→ ))}
901→ </div>
902→ {plan.yarn_usage_per_meter && (
<system-reminder>
🚫 FILE MODIFICATION LOOP DETECTED — "/home/project/src/pages/purchaser/NewPlan.tsx" — 3 modify→verify cycles!
You keep editing "/home/project/src/pages/purchaser/NewPlan.tsx", running/reading to check, finding issues, and editing again. Your current approach is NOT working.
⚠️ YOU MUST BREAK OUT OF THIS LOOP:
1. ACCEPT that your current approach to "/home/project/src/pages/purchaser/NewPlan.tsx" is not working — do NOT retry the same strategy
2. SKIP this step and MOVE ON to the next task/step in your plan
3. Briefly tell the user which modification you were unable to complete, then continue with remaining tasks
4. If "/home/project/src/pages/purchaser/NewPlan.tsx" is the ONLY task remaining, summarize what you tried and ask the user for help
The goal is FORWARD PROGRESS. Do NOT get stuck perfecting one file — skip it and keep going.
</system-reminder>

View File

@ -0,0 +1,32 @@
[
{
"id": "1",
"content": "分析动画卡顿根本原因",
"status": "completed",
"priority": "high"
},
{
"id": "2",
"content": "创建性能配置Hook支持低配置模式",
"status": "completed",
"priority": "high"
},
{
"id": "3",
"content": "优化PageTransition减少动画复杂度",
"status": "completed",
"priority": "high"
},
{
"id": "4",
"content": "优化高频页面动画Dashboard、PlanOverview",
"status": "completed",
"priority": "high"
},
{
"id": "5",
"content": "添加CSS硬件加速和渲染优化",
"status": "completed",
"priority": "medium"
}
]

View File

@ -1,25 +1,25 @@
[ [
{ {
"id": "1", "id": "1",
"content": "创建 yarn_stock_records 原料纱出入库记录表", "content": "检查并优化重复动画渲染问题",
"status": "completed", "status": "completed",
"priority": "high" "priority": "high"
}, },
{ {
"id": "2", "id": "2",
"content": "添加 RLS 策略和索引", "content": "优化 staggerChildren 动画性能",
"status": "completed", "status": "completed",
"priority": "high" "priority": "high"
}, },
{ {
"id": "3", "id": "3",
"content": "修改 YarnAllocationModal 记录出库到 yarn_stock_records", "content": "检查 will-change 使用是否合理",
"status": "completed", "status": "completed",
"priority": "high" "priority": "medium"
}, },
{ {
"id": "4", "id": "4",
"content": "在原料纱仓库页面显示出入库记录", "content": "优化列表渲染动画",
"status": "completed", "status": "completed",
"priority": "medium" "priority": "medium"
} }

View File

@ -119,7 +119,8 @@ src/
│ ├── useInventoryData.ts # 库存数据获取Hook带缓存 │ ├── useInventoryData.ts # 库存数据获取Hook带缓存
│ ├── usePlanData.ts # 计划数据获取Hook带缓存 │ ├── usePlanData.ts # 计划数据获取Hook带缓存
│ ├── useResponsive.ts # 响应式Hook │ ├── useResponsive.ts # 响应式Hook
│ └── useTheme.ts # 主题Hook │ ├── useTheme.ts # 主题Hook
│ └── usePerformance.ts # 性能检测Hook动画降级
└── utils/ └── utils/
├── constants.ts # 公共常量配置 ├── constants.ts # 公共常量配置
├── helpers.ts # 通用工具函数 ├── helpers.ts # 通用工具函数
@ -468,6 +469,16 @@ CREATE TYPE payment_status AS ENUM ('pending', 'completed');
## 更新日志 ## 更新日志
### 2025-05-28 (v1.0.31)
- **动画性能优化**
- 创建 `usePerformance` Hook自动检测设备性能级别
- 支持三种性能模式:高性能/中等性能/低性能
- 自动检测系统减少动画偏好设置
- 简化 PageTransition 组件动画,仅使用 opacity
- 优化 Dashboard 页面,使用 CSS transition 替代 Framer Motion
- 添加 CSS 硬件加速优化GPU加速、内容可见性
- 支持低配置电脑流畅运行
### 2025-05-28 (v1.0.3) ### 2025-05-28 (v1.0.3)
- **原料纱仓库出入库记录功能** - **原料纱仓库出入库记录功能**
- 创建 `yarn_stock_records` 表,记录原料纱出入库历史 - 创建 `yarn_stock_records` 表,记录原料纱出入库历史

View File

@ -213,6 +213,16 @@ import { InventoryRecordsModal } from '../../components/InventoryRecordsModal';
## 更新日志 ## 更新日志
### 2025-05-28 (v1.0.31)
- **动画性能优化**
- 创建 `usePerformance` Hook自动检测设备性能级别
- 支持三种性能模式:高性能/中等性能/低性能
- 自动检测系统减少动画偏好设置
- 简化页面过渡动画,仅使用 opacity 变化
- 优化 Dashboard 页面,使用 CSS transition 替代复杂动画
- 添加 CSS 硬件加速优化GPU加速、内容可见性
- 支持低配置电脑流畅运行
### 2025-05-28 (v1.0.3) ### 2025-05-28 (v1.0.3)
- **原料纱仓库出入库记录功能** - **原料纱仓库出入库记录功能**
- 创建 `yarn_stock_records` 表,记录原料纱出入库历史 - 创建 `yarn_stock_records` 表,记录原料纱出入库历史

View File

@ -1,6 +1,6 @@
{ {
"name": "procurement-planning", "name": "procurement-planning",
"version": "1.0.0", "version": "1.0.31",
"scripts": { "scripts": {
"dev": "webpack serve", "dev": "webpack serve",
"build": "webpack --mode production", "build": "webpack --mode production",

View File

@ -246,7 +246,7 @@ export function NotificationCenter({ theme = 'amber' }: NotificationCenterProps
key={notification.id} key={notification.id}
initial={{ opacity: 0, y: 10 }} initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.05 }} transition={{ delay: Math.min(index * 0.02, 0.1) }}
onClick={handleNotificationClick} onClick={handleNotificationClick}
className={`p-4 hover:bg-gray-50 cursor-pointer transition-colors ${ className={`p-4 hover:bg-gray-50 cursor-pointer transition-colors ${
!notification.is_read ? 'bg-blue-50/30' : '' !notification.is_read ? 'bg-blue-50/30' : ''

View File

@ -6,81 +6,63 @@ interface PageTransitionProps {
className?: string; className?: string;
} }
// 页面进入动画配置 - 使用更流畅的动画曲线 // 简化的页面进入动画 - 仅使用opacity避免transform
const pageVariants: Variants = { const pageVariants: Variants = {
initial: { initial: {
opacity: 0, opacity: 0,
y: 20,
scale: 0.98,
}, },
animate: { animate: {
opacity: 1, opacity: 1,
y: 0,
scale: 1,
transition: { transition: {
duration: 0.4, duration: 0.2,
ease: [0.25, 0.46, 0.45, 0.94], // 更自然的动画曲线 ease: 'easeOut',
staggerChildren: 0.08,
delayChildren: 0.05,
}, },
}, },
exit: { exit: {
opacity: 0, opacity: 0,
y: -10,
scale: 0.98,
transition: { transition: {
duration: 0.25, duration: 0.15,
ease: [0.25, 0.46, 0.45, 0.94],
}, },
}, },
}; };
// 子元素动画 // 简化的子元素动画
export const itemVariants: Variants = { export const itemVariants: Variants = {
initial: { initial: {
opacity: 0, opacity: 0,
y: 15,
scale: 0.98,
}, },
animate: { animate: {
opacity: 1, opacity: 1,
y: 0,
scale: 1,
transition: { transition: {
duration: 0.35, duration: 0.2,
ease: [0.25, 0.46, 0.45, 0.94], ease: 'easeOut',
}, },
}, },
}; };
// 卡片悬停动画 // 卡片悬停动画 - 简化效果
export const cardHoverVariants: Variants = { export const cardHoverVariants: Variants = {
rest: { rest: {
scale: 1, scale: 1,
y: 0,
transition: { transition: {
duration: 0.25, duration: 0.15,
ease: [0.25, 0.46, 0.45, 0.94],
}, },
}, },
hover: { hover: {
scale: 1.02, scale: 1.01,
y: -4,
transition: { transition: {
duration: 0.25, duration: 0.15,
ease: [0.25, 0.46, 0.45, 0.94],
}, },
}, },
tap: { tap: {
scale: 0.98, scale: 0.99,
y: 0,
transition: { transition: {
duration: 0.1, duration: 0.1,
}, },
}, },
}; };
// 列表项交错动画容器 // 列表项交错动画容器 - 减少stagger延迟
export const containerVariants: Variants = { export const containerVariants: Variants = {
initial: { initial: {
opacity: 0, opacity: 0,
@ -88,26 +70,22 @@ export const containerVariants: Variants = {
animate: { animate: {
opacity: 1, opacity: 1,
transition: { transition: {
staggerChildren: 0.08, staggerChildren: 0.03,
delayChildren: 0.1, delayChildren: 0.05,
}, },
}, },
}; };
// 列表项动画 // 简化的列表项动画 - 仅opacity
export const listItemVariants: Variants = { export const listItemVariants: Variants = {
initial: { initial: {
opacity: 0, opacity: 0,
x: -20,
scale: 0.98,
}, },
animate: { animate: {
opacity: 1, opacity: 1,
x: 0,
scale: 1,
transition: { transition: {
duration: 0.35, duration: 0.2,
ease: [0.25, 0.46, 0.45, 0.94], ease: 'easeOut',
}, },
}, },
}; };
@ -120,62 +98,62 @@ export const fadeInVariants: Variants = {
animate: { animate: {
opacity: 1, opacity: 1,
transition: { transition: {
duration: 0.4, duration: 0.25,
ease: [0.25, 0.46, 0.45, 0.94], ease: 'easeOut',
}, },
}, },
exit: { exit: {
opacity: 0, opacity: 0,
transition: { transition: {
duration: 0.2, duration: 0.15,
}, },
}, },
}; };
// 滑入动画(从底部) // 滑入动画(从底部)- 简化
export const slideUpVariants: Variants = { export const slideUpVariants: Variants = {
initial: { initial: {
opacity: 0, opacity: 0,
y: 30, y: 10,
}, },
animate: { animate: {
opacity: 1, opacity: 1,
y: 0, y: 0,
transition: { transition: {
duration: 0.5, duration: 0.25,
ease: [0.25, 0.46, 0.45, 0.94], ease: 'easeOut',
}, },
}, },
}; };
// 滑入动画(从右侧) // 滑入动画(从右侧)- 简化
export const slideRightVariants: Variants = { export const slideRightVariants: Variants = {
initial: { initial: {
opacity: 0, opacity: 0,
x: 30, x: 10,
}, },
animate: { animate: {
opacity: 1, opacity: 1,
x: 0, x: 0,
transition: { transition: {
duration: 0.4, duration: 0.25,
ease: [0.25, 0.46, 0.45, 0.94], ease: 'easeOut',
}, },
}, },
}; };
// 缩放动画 // 缩放动画 - 简化
export const scaleVariants: Variants = { export const scaleVariants: Variants = {
initial: { initial: {
opacity: 0, opacity: 0,
scale: 0.9, scale: 0.95,
}, },
animate: { animate: {
opacity: 1, opacity: 1,
scale: 1, scale: 1,
transition: { transition: {
duration: 0.4, duration: 0.25,
ease: [0.25, 0.46, 0.45, 0.94], ease: 'easeOut',
}, },
}, },
}; };
@ -188,38 +166,36 @@ export function PageTransition({ children, className = '' }: PageTransitionProps
animate="animate" animate="animate"
exit="exit" exit="exit"
className={className} className={className}
style={{ willChange: 'transform, opacity' }}
> >
{children} {children}
</motion.div> </motion.div>
); );
} }
// 带动画的页面包装器(用于路由切换) // 带动画的页面包装器(用于路由切换)- 简化版
export function AnimatedPage({ children, className = '' }: PageTransitionProps) { export function AnimatedPage({ children, className = '' }: PageTransitionProps) {
return ( return (
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1 }}
exit={{ opacity: 0, y: -10 }} exit={{ opacity: 0 }}
transition={{ transition={{
duration: 0.35, duration: 0.2,
ease: [0.25, 0.46, 0.45, 0.94], ease: 'easeOut',
}} }}
className={className} className={className}
style={{ willChange: 'transform, opacity' }}
> >
{children} {children}
</motion.div> </motion.div>
); );
} }
// 交错动画容器 // 交错动画容器 - 限制最大stagger数量
export function StaggerContainer({ export function StaggerContainer({
children, children,
className = '', className = '',
staggerDelay = 0.08, staggerDelay = 0.03,
delayChildren = 0.1 delayChildren = 0.05
}: PageTransitionProps & { staggerDelay?: number; delayChildren?: number }) { }: PageTransitionProps & { staggerDelay?: number; delayChildren?: number }) {
return ( return (
<motion.div <motion.div
@ -242,7 +218,7 @@ export function StaggerContainer({
); );
} }
// 交错动画子项 // 交错动画子项 - 简化
export function StaggerItem({ export function StaggerItem({
children, children,
className = '' className = ''
@ -251,7 +227,29 @@ export function StaggerItem({
<motion.div <motion.div
variants={itemVariants} variants={itemVariants}
className={className} className={className}
style={{ willChange: 'transform, opacity' }} >
{children}
</motion.div>
);
}
// 高性能模式下的静态包装器(无动画)
export function StaticPage({ children, className = '' }: PageTransitionProps) {
return <div className={className}>{children}</div>;
}
// 简化的淡入组件
export function FadeIn({
children,
className = '',
delay = 0
}: PageTransitionProps & { delay?: number }) {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.2, delay }}
className={className}
> >
{children} {children}
</motion.div> </motion.div>

View File

@ -11,7 +11,7 @@ interface VersionInfo {
} }
// 当前版本 // 当前版本
const CURRENT_VERSION = '1.0.2'; const CURRENT_VERSION = '1.0.31';
// 模拟版本更新数据(实际应从服务器获取) // 模拟版本更新数据(实际应从服务器获取)
const LATEST_VERSION: VersionInfo = { const LATEST_VERSION: VersionInfo = {

152
src/hooks/usePerformance.ts Normal file
View File

@ -0,0 +1,152 @@
import { useState, useEffect, useCallback } from 'react';
export type PerformanceLevel = 'high' | 'medium' | 'low';
interface PerformanceConfig {
level: PerformanceLevel;
enableAnimations: boolean;
enableStagger: boolean;
maxStaggerItems: number;
reduceMotion: boolean;
enableComplexEffects: boolean;
}
const defaultConfig: Record<PerformanceLevel, PerformanceConfig> = {
high: {
level: 'high',
enableAnimations: true,
enableStagger: true,
maxStaggerItems: 50,
reduceMotion: false,
enableComplexEffects: true,
},
medium: {
level: 'medium',
enableAnimations: true,
enableStagger: true,
maxStaggerItems: 20,
reduceMotion: false,
enableComplexEffects: false,
},
low: {
level: 'low',
enableAnimations: false,
enableStagger: false,
maxStaggerItems: 0,
reduceMotion: true,
enableComplexEffects: false,
},
};
export function usePerformance() {
const [config, setConfig] = useState<PerformanceConfig>(defaultConfig.medium);
const [isReady, setIsReady] = useState(false);
// 检测设备性能
useEffect(() => {
const detectPerformance = () => {
// 检查是否偏好减少动画
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (prefersReducedMotion) {
setConfig(defaultConfig.low);
setIsReady(true);
return;
}
// 检测硬件性能
const memory = (navigator as any).deviceMemory;
const cores = navigator.hardwareConcurrency || 2;
// 检测是否为低性能设备
const isLowEnd =
(memory && memory <= 4) ||
(cores <= 2) ||
/Android 4|Android 5|Android 6/i.test(navigator.userAgent) ||
/iPhone OS [456]_/i.test(navigator.userAgent);
// 检测是否为高性能设备
const isHighEnd =
(memory && memory >= 8) &&
(cores >= 6);
if (isLowEnd) {
setConfig(defaultConfig.low);
} else if (isHighEnd) {
setConfig(defaultConfig.high);
} else {
setConfig(defaultConfig.medium);
}
setIsReady(true);
};
detectPerformance();
// 监听性能偏好变化
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
const handleChange = (e: MediaQueryListEvent) => {
if (e.matches) {
setConfig(defaultConfig.low);
}
};
mediaQuery.addEventListener('change', handleChange);
return () => mediaQuery.removeEventListener('change', handleChange);
}, []);
// 手动设置性能级别
const setPerformanceLevel = useCallback((level: PerformanceLevel) => {
setConfig(defaultConfig[level]);
}, []);
// 获取动画配置
const getAnimationProps = useCallback((baseDelay: number = 0, index: number = 0) => {
if (!config.enableAnimations) {
return {};
}
if (!config.enableStagger || index >= config.maxStaggerItems) {
return {
initial: { opacity: 0 },
animate: { opacity: 1 },
transition: { duration: 0.2 },
};
}
const delay = Math.min(index * 0.03, 0.15) + baseDelay;
return {
initial: { opacity: 0, y: 8 },
animate: { opacity: 1, y: 0 },
transition: { duration: 0.25, delay },
};
}, [config]);
// 获取页面过渡配置
const getPageTransition = useCallback(() => {
if (!config.enableAnimations) {
return {
initial: {},
animate: {},
exit: {},
transition: { duration: 0 },
};
}
return {
initial: { opacity: 0 },
animate: { opacity: 1 },
exit: { opacity: 0 },
transition: { duration: 0.2 },
};
}, [config]);
return {
config,
isReady,
setPerformanceLevel,
getAnimationProps,
getPageTransition,
};
}
export default usePerformance;

View File

@ -222,7 +222,7 @@ export function AccountsPayable() {
key={payable.id} key={payable.id}
initial={{ opacity: 0, y: 15 }} initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.08, duration: 0.35 }} transition={{ delay: Math.min(index * 0.03, 0.2), duration: 0.25 }}
className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden" className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden"
> >
{/* 头部 */} {/* 头部 */}

View File

@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useAuth } from '../../contexts/AuthContext'; import { useAuth } from '../../contexts/AuthContext';
import { supabase } from '../../supabase/client'; import { supabase } from '../../supabase/client';
import { motion } from 'framer-motion';
import { Link2, Warehouse, CreditCard, ArrowLeft, Users, Factory, LogOut, HelpCircle, X, BookOpen, MessageSquare, Info, Camera, FileText, TrendingUp, Package } from 'lucide-react'; import { Link2, Warehouse, CreditCard, ArrowLeft, Users, Factory, LogOut, HelpCircle, X, BookOpen, MessageSquare, Info, Camera, FileText, TrendingUp, Package } from 'lucide-react';
import { useResponsive } from '../../hooks/useResponsive'; import { useResponsive } from '../../hooks/useResponsive';
import { OnboardingGuide, shouldShowOnboarding } from '../../components/OnboardingGuide'; import { OnboardingGuide, shouldShowOnboarding } from '../../components/OnboardingGuide';
@ -29,16 +28,6 @@ const getQuickActions = () => {
]; ];
}; };
const containerVariants = {
hidden: { opacity: 0 },
visible: { opacity: 1, transition: { staggerChildren: 0.1 } }
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0, transition: { duration: 0.4 } }
};
export function TextileDashboard() { export function TextileDashboard() {
const navigate = useNavigate(); const navigate = useNavigate();
const { auth, logout } = useAuth(); const { auth, logout } = useAuth();
@ -133,11 +122,9 @@ export function TextileDashboard() {
<header className="bg-white/80 backdrop-blur-xl border-b border-gray-200/50 sticky top-0 z-10"> <header className="bg-white/80 backdrop-blur-xl border-b border-gray-200/50 sticky top-0 z-10">
<div className="max-w-7xl mx-auto px-3 sm:px-4 md:px-8 py-3 sm:py-4 flex justify-between items-center"> <div className="max-w-7xl mx-auto px-3 sm:px-4 md:px-8 py-3 sm:py-4 flex justify-between items-center">
<div className="flex items-center gap-2 sm:gap-3"> <div className="flex items-center gap-2 sm:gap-3">
<motion.button <button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => setShowAccountCard(true)} onClick={() => setShowAccountCard(true)}
className="w-8 h-8 sm:w-10 sm:h-10 bg-gradient-to-br from-emerald-500 to-green-600 rounded-xl flex items-center justify-center shadow-lg shadow-emerald-500/20 cursor-pointer overflow-hidden" className="w-8 h-8 sm:w-10 sm:h-10 bg-gradient-to-br from-emerald-500 to-green-600 rounded-xl flex items-center justify-center shadow-lg shadow-emerald-500/20 cursor-pointer overflow-hidden active:scale-95 transition-transform"
> >
{(auth.user as any)?.image_url ? ( {(auth.user as any)?.image_url ? (
<img <img
@ -151,7 +138,7 @@ export function TextileDashboard() {
) : ( ) : (
<Factory className="w-4 h-4 sm:w-5 sm:h-5 text-white" /> <Factory className="w-4 h-4 sm:w-5 sm:h-5 text-white" />
)} )}
</motion.button> </button>
<div> <div>
<h1 className="text-sm sm:text-lg font-bold text-gray-900 flex items-center gap-2 whitespace-nowrap"> <h1 className="text-sm sm:text-lg font-bold text-gray-900 flex items-center gap-2 whitespace-nowrap">
<span className="sm:hidden truncate max-w-[80px]">{(auth.user as any)?.display_name || auth.user?.username || '用户'}</span> <span className="sm:hidden truncate max-w-[80px]">{(auth.user as any)?.display_name || auth.user?.username || '用户'}</span>
@ -182,12 +169,10 @@ export function TextileDashboard() {
<div className="w-8 h-8 border-2 border-emerald-500 border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-emerald-500 border-t-transparent rounded-full animate-spin" />
</div> </div>
) : ( ) : (
<motion.div variants={containerVariants} initial="hidden" animate="visible" className="space-y-4 sm:space-y-6"> <div className="space-y-4 sm:space-y-6 animate-fade-in">
{/* 统计卡片 - 与采购商保持一致 */} {/* 统计卡片 - 与采购商保持一致 */}
<div className="grid grid-cols-3 gap-2 sm:gap-4"> <div className="grid grid-cols-3 gap-2 sm:gap-4">
<motion.div <div
variants={itemVariants}
whileHover={isMobile ? {} : { scale: 1.02, y: -2 }}
onClick={() => navigate('/textile/plans')} onClick={() => navigate('/textile/plans')}
className="bg-white rounded-xl sm:rounded-2xl p-3 sm:p-5 shadow-sm border border-gray-100 cursor-pointer hover:border-emerald-300 hover:shadow-md transition-all" className="bg-white rounded-xl sm:rounded-2xl p-3 sm:p-5 shadow-sm border border-gray-100 cursor-pointer hover:border-emerald-300 hover:shadow-md transition-all"
> >
@ -199,11 +184,9 @@ export function TextileDashboard() {
</div> </div>
<p className="text-xl sm:text-3xl font-bold text-gray-900">{stats.all}</p> <p className="text-xl sm:text-3xl font-bold text-gray-900">{stats.all}</p>
<p className="text-xs text-gray-400 mt-0.5 sm:mt-1"></p> <p className="text-xs text-gray-400 mt-0.5 sm:mt-1"></p>
</motion.div> </div>
<motion.div <div
variants={itemVariants}
whileHover={isMobile ? {} : { scale: 1.02, y: -2 }}
onClick={() => navigate('/textile/plans?filter=producing')} onClick={() => navigate('/textile/plans?filter=producing')}
className="bg-white rounded-xl sm:rounded-2xl p-3 sm:p-5 shadow-sm border border-gray-100 cursor-pointer hover:border-emerald-300 hover:shadow-md transition-all" className="bg-white rounded-xl sm:rounded-2xl p-3 sm:p-5 shadow-sm border border-gray-100 cursor-pointer hover:border-emerald-300 hover:shadow-md transition-all"
> >
@ -215,11 +198,9 @@ export function TextileDashboard() {
</div> </div>
<p className="text-xl sm:text-3xl font-bold text-gray-900">{stats.producing}</p> <p className="text-xl sm:text-3xl font-bold text-gray-900">{stats.producing}</p>
<p className="text-xs text-gray-400 mt-0.5 sm:mt-1"></p> <p className="text-xs text-gray-400 mt-0.5 sm:mt-1"></p>
</motion.div> </div>
<motion.div <div
variants={itemVariants}
whileHover={isMobile ? {} : { scale: 1.02, y: -2 }}
onClick={() => navigate('/textile/plans?filter=completed')} onClick={() => navigate('/textile/plans?filter=completed')}
className="bg-white rounded-xl sm:rounded-2xl p-3 sm:p-5 shadow-sm border border-gray-100 cursor-pointer hover:border-amber-300 hover:shadow-md transition-all" className="bg-white rounded-xl sm:rounded-2xl p-3 sm:p-5 shadow-sm border border-gray-100 cursor-pointer hover:border-amber-300 hover:shadow-md transition-all"
> >
@ -231,11 +212,11 @@ export function TextileDashboard() {
</div> </div>
<p className="text-xl sm:text-3xl font-bold text-gray-900">{stats.completed}</p> <p className="text-xl sm:text-3xl font-bold text-gray-900">{stats.completed}</p>
<p className="text-xs text-gray-400 mt-0.5 sm:mt-1"></p> <p className="text-xs text-gray-400 mt-0.5 sm:mt-1"></p>
</motion.div> </div>
</div> </div>
{/* 最近计划 - 左右滑动展示 - 与采购商保持一致 */} {/* 最近计划 - 左右滑动展示 - 与采购商保持一致 */}
<motion.div variants={itemVariants} className="bg-white rounded-xl sm:rounded-2xl shadow-sm border border-gray-100 p-3 sm:p-4 md:p-6"> <div className="bg-white rounded-xl sm:rounded-2xl shadow-sm border border-gray-100 p-3 sm:p-4 md:p-6">
<div className="flex justify-between items-center mb-3 sm:mb-4"> <div className="flex justify-between items-center mb-3 sm:mb-4">
<h2 className="text-sm sm:text-base font-semibold text-gray-900 whitespace-nowrap"></h2> <h2 className="text-sm sm:text-base font-semibold text-gray-900 whitespace-nowrap"></h2>
<button <button
@ -257,12 +238,11 @@ export function TextileDashboard() {
<div className="relative"> <div className="relative">
{/* 滑动容器 */} {/* 滑动容器 */}
<div className="overflow-hidden"> <div className="overflow-hidden">
<motion.div <div
className="flex" className="flex transition-transform duration-300 ease-out"
animate={{ x: -currentPlanIndex * 100 + '%' }} style={{ transform: `translateX(-${currentPlanIndex * 100}%)` }}
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
> >
{producingPlans.map((plan, i) => { {producingPlans.map((plan) => {
const progress = plan.target_quantity > 0 const progress = plan.target_quantity > 0
? Math.round((plan.completed_quantity / plan.target_quantity) * 100) ? Math.round((plan.completed_quantity / plan.target_quantity) * 100)
: 0; : 0;
@ -308,7 +288,7 @@ export function TextileDashboard() {
</div> </div>
); );
})} })}
</motion.div> </div>
</div> </div>
{/* 左右滑动按钮 */} {/* 左右滑动按钮 */}
@ -347,29 +327,27 @@ export function TextileDashboard() {
)} )}
</div> </div>
)} )}
</motion.div> </div>
{/* 快捷操作 - 与采购商保持一致 */} {/* 快捷操作 - 与采购商保持一致 */}
<motion.div variants={itemVariants} className="bg-white rounded-xl sm:rounded-2xl shadow-sm border border-gray-100 p-3 sm:p-4 md:p-6"> <div className="bg-white rounded-xl sm:rounded-2xl shadow-sm border border-gray-100 p-3 sm:p-4 md:p-6">
<h2 className="text-sm sm:text-base font-semibold text-gray-900 mb-3 sm:mb-4 whitespace-nowrap"></h2> <h2 className="text-sm sm:text-base font-semibold text-gray-900 mb-3 sm:mb-4 whitespace-nowrap"></h2>
<div className="grid grid-cols-3 sm:grid-cols-4 gap-2 sm:gap-3"> <div className="grid grid-cols-3 sm:grid-cols-4 gap-2 sm:gap-3">
{getQuickActions().map((action, index) => ( {getQuickActions().map((action) => (
<motion.button <button
key={action.id} key={action.id}
whileHover={isMobile ? {} : { scale: 1.03, y: -2 }}
whileTap={{ scale: 0.97 }}
onClick={() => navigate(action.path)} onClick={() => navigate(action.path)}
className="group flex flex-col items-center p-2 sm:p-3 rounded-lg sm:rounded-xl border border-gray-100 hover:border-transparent hover:shadow-md transition-all bg-gray-50/50 hover:bg-white" className="group flex flex-col items-center p-2 sm:p-3 rounded-lg sm:rounded-xl border border-gray-100 hover:border-transparent hover:shadow-md transition-all bg-gray-50/50 hover:bg-white active:scale-95"
> >
<div className={`w-8 h-8 sm:w-10 sm:h-10 rounded-lg sm:rounded-xl flex items-center justify-center mb-1 sm:mb-2 bg-gradient-to-br ${action.gradient} shadow-sm group-hover:shadow-md transition-shadow`}> <div className={`w-8 h-8 sm:w-10 sm:h-10 rounded-lg sm:rounded-xl flex items-center justify-center mb-1 sm:mb-2 bg-gradient-to-br ${action.gradient} shadow-sm group-hover:shadow-md transition-shadow`}>
<action.icon className="w-4 h-4 sm:w-5 sm:h-5 text-white" /> <action.icon className="w-4 h-4 sm:w-5 sm:h-5 text-white" />
</div> </div>
<span className="text-[10px] sm:text-xs font-medium text-gray-700 group-hover:text-gray-900 text-center whitespace-nowrap">{action.label}</span> <span className="text-[10px] sm:text-xs font-medium text-gray-700 group-hover:text-gray-900 text-center whitespace-nowrap">{action.label}</span>
</motion.button> </button>
))} ))}
</div> </div>
</motion.div> </div>
</motion.div> </div>
)} )}
</div> </div>
@ -404,11 +382,8 @@ export function TextileDashboard() {
{/* 账户卡片弹窗 */} {/* 账户卡片弹窗 */}
{showAccountCard && ( {showAccountCard && (
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4" onClick={() => setShowAccountCard(false)}> <div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4" onClick={() => setShowAccountCard(false)}>
<motion.div <div
initial={{ opacity: 0, scale: 0.95 }} className="bg-white rounded-2xl w-full max-w-sm shadow-2xl overflow-hidden animate-fade-in-scale"
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
className="bg-white rounded-2xl w-full max-w-sm shadow-2xl overflow-hidden"
onClick={e => e.stopPropagation()} onClick={e => e.stopPropagation()}
> >
{/* 头部 */} {/* 头部 */}
@ -551,7 +526,7 @@ export function TextileDashboard() {
<span className="font-medium">退</span> <span className="font-medium">退</span>
</button> </button>
</div> </div>
</motion.div> </div>
</div> </div>
)} )}
</div> </div>

View File

@ -288,7 +288,7 @@ export function FabricWarehouse() {
initial={{ opacity: 0, y: 15 }} initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: groupIndex * 0.08, duration: 0.35, ease: [0.25, 0.46, 0.45, 0.94] }} transition={{ delay: groupIndex * 0.08, duration: 0.35, ease: [0.25, 0.46, 0.45, 0.94] }}
className="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden will-change-transform" className="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden"
> >
{/* 分组头部 */} {/* 分组头部 */}
<motion.div <motion.div

View File

@ -128,8 +128,8 @@ export function PaymentPending() {
key={item.id} key={item.id}
initial={{ opacity: 0, y: 15 }} initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.08, duration: 0.35, ease: [0.25, 0.46, 0.45, 0.94] }} transition={{ delay: Math.min(index * 0.03, 0.2), duration: 0.25, ease: [0.25, 0.46, 0.45, 0.94] }}
className="bg-white rounded-xl shadow-sm p-4 sm:p-5 border border-gray-200 will-change-transform" className="bg-white rounded-xl shadow-sm p-4 sm:p-5 border border-gray-200"
> >
<div className="flex items-start justify-between gap-2"> <div className="flex items-start justify-between gap-2">
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">

View File

@ -715,7 +715,7 @@ export function TextilePlanOverview() {
<motion.div <motion.div
onClick={() => toggleGroup(group.purchaserId)} onClick={() => toggleGroup(group.purchaserId)}
whileTap={{ scale: 0.995 }} whileTap={{ scale: 0.995 }}
className="flex items-center justify-between p-3 sm:p-4 cursor-pointer hover:bg-gray-50 transition-colors will-change-transform" className="flex items-center justify-between p-3 sm:p-4 cursor-pointer hover:bg-gray-50 transition-colors"
> >
<div className="flex items-center gap-2 sm:gap-3"> <div className="flex items-center gap-2 sm:gap-3">
<motion.button <motion.button
@ -750,15 +750,14 @@ export function TextilePlanOverview() {
return ( return (
<motion.div <motion.div
key={plan.id} key={plan.id}
initial={{ opacity: 0, y: 10 }} initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: planIndex * 0.05, duration: 0.3 }} transition={{ delay: Math.min(planIndex * 0.03, 0.3), duration: 0.25 }}
className={`border rounded-2xl overflow-hidden transition-all duration-300 ${isRejected ? 'border-gray-200 bg-gray-50' : 'border-gray-200 bg-white hover:border-emerald-200 hover:shadow-lg hover:shadow-gray-100/50'}`} className={`border rounded-2xl overflow-hidden transition-all duration-200 ${isRejected ? 'border-gray-200 bg-gray-50' : 'border-gray-200 bg-white hover:border-emerald-200 hover:shadow-md'}`}
> >
<motion.div <div
onClick={() => !isRejected && setExpandedPlan(expandedPlan === plan.id ? null : plan.id)} onClick={() => !isRejected && setExpandedPlan(expandedPlan === plan.id ? null : plan.id)}
whileTap={isRejected ? {} : { scale: 0.995 }} className={`p-4 sm:p-5 transition-colors ${isRejected ? 'cursor-default' : 'cursor-pointer hover:bg-gray-50/50'}`}
className={`p-4 sm:p-5 transition-colors will-change-transform ${isRejected ? 'cursor-default' : 'cursor-pointer hover:bg-gray-50/50'}`}
> >
<div className="flex items-start justify-between gap-3"> <div className="flex items-start justify-between gap-3">
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
@ -796,11 +795,11 @@ export function TextilePlanOverview() {
initial={{ width: 0 }} initial={{ width: 0 }}
animate={{ width: `${progress}%` }} animate={{ width: `${progress}%` }}
transition={{ duration: 0.6, delay: 0.1, ease: [0.25, 0.46, 0.45, 0.94] }} transition={{ duration: 0.6, delay: 0.1, ease: [0.25, 0.46, 0.45, 0.94] }}
className="h-full bg-gradient-to-r from-emerald-500 to-green-500 rounded-full will-change-transform" className="h-full bg-gradient-to-r from-emerald-500 to-green-500 rounded-full"
/> />
</div> </div>
</div> </div>
</motion.div> </div>
<AnimatePresence> <AnimatePresence>
{expandedPlan === plan.id && ( {expandedPlan === plan.id && (
<motion.div <motion.div

View File

@ -201,7 +201,7 @@ export function YarnWarehouse() {
key={yarn.id} key={yarn.id}
initial={{ opacity: 0, x: -10 }} initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
transition={{ delay: index * 0.05, duration: 0.25 }} transition={{ delay: Math.min(index * 0.02, 0.15), duration: 0.2 }}
className="p-3 sm:p-4 flex items-center justify-between hover:bg-gray-50 transition-colors" className="p-3 sm:p-4 flex items-center justify-between hover:bg-gray-50 transition-colors"
> >
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
@ -249,7 +249,7 @@ export function YarnWarehouse() {
key={record.id} key={record.id}
initial={{ opacity: 0, x: -10 }} initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
transition={{ delay: index * 0.05, duration: 0.25 }} transition={{ delay: Math.min(index * 0.02, 0.15), duration: 0.2 }}
className="p-3 sm:p-4 hover:bg-gray-50 transition-colors" className="p-3 sm:p-4 hover:bg-gray-50 transition-colors"
> >
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">

View File

@ -150,7 +150,7 @@ export function CompletedFabric() {
key={item.id} key={item.id}
initial={{ opacity: 0, y: 10 }} initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.05 }} transition={{ delay: Math.min(index * 0.02, 0.1) }}
onClick={() => navigate(`/washing/plans?id=${item.id}`)} onClick={() => navigate(`/washing/plans?id=${item.id}`)}
className="p-4 hover:bg-gray-50 transition-colors cursor-pointer" className="p-4 hover:bg-gray-50 transition-colors cursor-pointer"
> >

View File

@ -132,8 +132,8 @@ export function WashingPaymentPending() {
key={item.id} key={item.id}
initial={{ opacity: 0, y: 15 }} initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.08, duration: 0.35, ease: [0.25, 0.46, 0.45, 0.94] }} transition={{ delay: Math.min(index * 0.03, 0.2), duration: 0.25, ease: [0.25, 0.46, 0.45, 0.94] }}
className="bg-white rounded-xl shadow-sm p-4 sm:p-5 border border-gray-200 will-change-transform" className="bg-white rounded-xl shadow-sm p-4 sm:p-5 border border-gray-200"
> >
<div className="flex items-start justify-between gap-2"> <div className="flex items-start justify-between gap-2">
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">

View File

@ -121,7 +121,7 @@ export function PendingFabric() {
key={item.id} key={item.id}
initial={{ opacity: 0, y: 10 }} initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.05 }} transition={{ delay: Math.min(index * 0.02, 0.1) }}
onClick={() => navigate(`/washing/plans?id=${item.id}`)} onClick={() => navigate(`/washing/plans?id=${item.id}`)}
className="p-4 hover:bg-gray-50 transition-colors cursor-pointer" className="p-4 hover:bg-gray-50 transition-colors cursor-pointer"
> >

View File

@ -143,18 +143,27 @@ body {
@keyframes fadeIn { @keyframes fadeIn {
from { from {
opacity: 0; opacity: 0;
transform: translateY(10px);
} }
to { to {
opacity: 1; opacity: 1;
transform: translateY(0); }
}
@keyframes fadeInScale {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
} }
} }
@keyframes slideIn { @keyframes slideIn {
from { from {
opacity: 0; opacity: 0;
transform: translateX(-20px); transform: translateX(-10px);
} }
to { to {
opacity: 1; opacity: 1;
@ -181,11 +190,70 @@ body {
} }
.animate-fade-in { .animate-fade-in {
animation: fadeIn 0.3s ease-out; animation: fadeIn 0.2s ease-out;
}
.animate-fade-in-scale {
animation: fadeInScale 0.2s ease-out;
} }
.animate-slide-in { .animate-slide-in {
animation: slideIn 0.3s ease-out; animation: slideIn 0.2s ease-out;
}
/* 低配置设备优化 - 仅使用opacity动画 */
@media (prefers-reduced-motion: reduce) {
.animate-fade-in,
.animate-fade-in-scale,
.animate-slide-in {
animation: none;
opacity: 1;
transform: none;
}
}
/* 硬件加速优化 */
.gpu-accelerated {
transform: translateZ(0);
backface-visibility: hidden;
perspective: 1000px;
}
/* 内容可见性优化 - 延迟渲染 */
.content-visibility-auto {
content-visibility: auto;
contain-intrinsic-size: 0 500px;
}
/* 渲染层优化 */
.render-layer {
will-change: transform;
transform: translateZ(0);
}
/* 避免布局抖动 */
.contain-layout {
contain: layout style;
}
.contain-strict {
contain: strict;
}
/* 图片优化 */
img {
content-visibility: auto;
}
/* 滚动优化 */
.scroll-smooth {
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch;
}
/* 减少重绘区域 */
.isolate {
isolation: isolate;
} }
/* 玻璃态效果 */ /* 玻璃态效果 */