sync: Add files via upload (A@e5347bf93cdb38c0fdf86271231c0e884465195a)
This commit is contained in:
parent
3f7377b142
commit
e713fcbf18
1
.microcompact/mpp307nk/functions.Grep:1.txt
Normal file
1
.microcompact/mpp307nk/functions.Grep:1.txt
Normal 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} 占比:{(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}
|
||||||
1
.microcompact/mpp307pa/functions.Grep:3.txt
Normal file
1
.microcompact/mpp307pa/functions.Grep:3.txt
Normal file
File diff suppressed because one or more lines are too long
13
.microcompact/mpp307qv/functions.Edit:6.txt
Normal file
13
.microcompact/mpp307qv/functions.Edit:6.txt
Normal 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} 占比:{(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>
|
||||||
13
.microcompact/mpp307tm/functions.Edit:9.txt
Normal file
13
.microcompact/mpp307tm/functions.Edit:9.txt
Normal 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} 占比:{(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>
|
||||||
27
.microcompact/mpp307vk/functions.Edit:14.txt
Normal file
27
.microcompact/mpp307vk/functions.Edit:14.txt
Normal 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>
|
||||||
27
.microcompact/mpp307wt/functions.Edit:15.txt
Normal file
27
.microcompact/mpp307wt/functions.Edit:15.txt
Normal 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>
|
||||||
32
.todo/763bf11a-6a49-43d4-9c9e-642425531cda/todos.json
Normal file
32
.todo/763bf11a-6a49-43d4-9c9e-642425531cda/todos.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
13
AGENTS.md
13
AGENTS.md
@ -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` 表,记录原料纱出入库历史
|
||||||
|
|||||||
10
README.md
10
README.md
@ -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` 表,记录原料纱出入库历史
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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' : ''
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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
152
src/hooks/usePerformance.ts
Normal 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;
|
||||||
@ -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"
|
||||||
>
|
>
|
||||||
{/* 头部 */}
|
{/* 头部 */}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -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"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -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"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 玻璃态效果 */
|
/* 玻璃态效果 */
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user