diff --git a/.microcompact/mpp307nk/functions.Grep:1.txt b/.microcompact/mpp307nk/functions.Grep:1.txt new file mode 100644 index 0000000..2060d45 --- /dev/null +++ b/.microcompact/mpp307nk/functions.Grep:1.txt @@ -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":" 产量: 10000米"}]},{"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":"
纱线名称/占比
"}]},{"file":"src/pages/textile/FabricWarehouse.tsx","matches":[{"line":28,"content":" target_quantity: 10000,"}]},{"file":"src/pages/purchaser/NewPlan.tsx","matches":[{"line":524,"content":" "},{"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":"
纱线名称/占比
"}]},{"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} \ No newline at end of file diff --git a/.microcompact/mpp307pa/functions.Grep:3.txt b/.microcompact/mpp307pa/functions.Grep:3.txt new file mode 100644 index 0000000..ebbb548 --- /dev/null +++ b/.microcompact/mpp307pa/functions.Grep:3.txt @@ -0,0 +1 @@ +{"results":[{"file":"migrations/20260523_124742_create_product_yarn_ratios_table.sql","matches":[{"line":8,"content":"CREATE TABLE IF NOT EXISTS product_yarn_ratios ("},{"line":12,"content":" ratio DECIMAL(5,2) NOT NULL DEFAULT 0,"},{"line":17,"content":"ALTER TABLE product_yarn_ratios ENABLE ROW LEVEL SECURITY;"},{"line":20,"content":"CREATE POLICY company_select_product_yarn_ratios ON product_yarn_ratios FOR SELECT USING (true);"},{"line":21,"content":"CREATE POLICY company_insert_product_yarn_ratios ON product_yarn_ratios FOR INSERT WITH CHECK (true);"},{"line":22,"content":"CREATE POLICY company_update_product_yarn_ratios ON product_yarn_ratios FOR UPDATE USING (true);"},{"line":23,"content":"CREATE POLICY company_delete_product_yarn_ratios ON product_yarn_ratios FOR DELETE USING (true);"}]},{"file":"migrations/20260523_052709_create_plan_tables.sql","matches":[{"line":39,"content":"CREATE TABLE public.yarn_ratios ("},{"line":43,"content":" ratio DECIMAL NOT NULL DEFAULT 1,"}]},{"file":"migrations/20260523_053110_seed_plans_and_related.sql","matches":[{"line":20,"content":"INSERT INTO public.yarn_ratios (plan_id, yarn_name, ratio, amount_per_meter, total_amount) VALUES"}]},{"file":"migrations/20260526_112452_add_yarn_type_to_product_yarn_ratios.sql","matches":[{"line":6,"content":"ALTER TABLE product_yarn_ratios ADD COLUMN IF NOT EXISTS yarn_type TEXT DEFAULT 'all';"}]},{"file":"migrations/20260523_170036_fix_yarn_ratios_select_rls.sql","matches":[{"line":6,"content":"DROP POLICY IF EXISTS users_select_yarn_ratios ON public.yarn_ratios; CREATE POLICY users_select_yarn_ratios ON public.yarn_ratios FOR SELECT USING (EXISTS (SELECT 1 FROM public.production_plans WHERE id = plan_id AND (purchaser_id = public.get_user_master_company_id() OR EXISTS (SELECT 1 FROM public.plan_factories WHERE plan_id = production_plans.id AND factory_id = public.get_user_master_company_id()))));"}]},{"file":"migrations/20260526_112537_add_yarn_type_to_yarn_ratios.sql","matches":[{"line":6,"content":"ALTER TABLE yarn_ratios ADD COLUMN IF NOT EXISTS yarn_type TEXT DEFAULT 'all';"}]},{"file":"migrations/20260523_162926_update_yarn_ratios_rls.sql","matches":[{"line":8,"content":"DROP POLICY IF EXISTS users_select_yarn_ratios ON public.yarn_ratios;"},{"line":9,"content":"DROP POLICY IF EXISTS users_insert_yarn_ratios ON public.yarn_ratios;"},{"line":10,"content":"DROP POLICY IF EXISTS users_delete_yarn_ratios ON public.yarn_ratios;"},{"line":13,"content":"CREATE POLICY users_select_yarn_ratios ON public.yarn_ratios"},{"line":18,"content":"CREATE POLICY users_insert_yarn_ratios ON public.yarn_ratios"},{"line":23,"content":"CREATE POLICY users_update_yarn_ratios ON public.yarn_ratios"},{"line":28,"content":"CREATE POLICY users_delete_yarn_ratios ON public.yarn_ratios"}]},{"file":"migrations/20260523_163000_sync_company_id_data.sql","matches":[{"line":14,"content":"-- 更新 yarn_ratios 的 company_id(通过 production_plans 关联)"},{"line":15,"content":"UPDATE public.yarn_ratios yr"}]},{"file":"migrations/20260523_163054_add_share_link_rls_policies.sql","matches":[{"line":20,"content":"-- yarn_ratios:工厂可以查看关联计划的纱线配比"},{"line":21,"content":"CREATE POLICY factory_select_yarn_ratios_via_share ON public.yarn_ratios"},{"line":25,"content":" WHERE plan_id = yarn_ratios.plan_id"}]},{"file":"migrations/20260523_162846_add_company_id_to_yarn_ratios.sql","matches":[{"line":7,"content":"-- 为 yarn_ratios 表添加 company_id 字段"},{"line":8,"content":"ALTER TABLE public.yarn_ratios ADD COLUMN IF NOT EXISTS company_id UUID REFERENCES public.companies(id) ON DELETE CASCADE;"},{"line":11,"content":"CREATE INDEX IF NOT EXISTS idx_yarn_ratios_company_id ON public.yarn_ratios(company_id);"}]},{"file":"migrations/20260523_162653_drop_remaining_old_policies.sql","matches":[{"line":23,"content":"-- 删除 product_yarn_ratios 表上的旧宽松策略"},{"line":24,"content":"DROP POLICY IF EXISTS company_select_product_yarn_ratios ON public.product_yarn_ratios;"},{"line":25,"content":"DROP POLICY IF EXISTS company_insert_product_yarn_ratios ON public.product_yarn_ratios;"},{"line":26,"content":"DROP POLICY IF EXISTS company_update_product_yarn_ratios ON public.product_yarn_ratios;"},{"line":27,"content":"DROP POLICY IF EXISTS company_delete_product_yarn_ratios ON public.product_yarn_ratios;"}]},{"file":"migrations/20260523_163245_add_missing_rls_policies.sql","matches":[{"line":40,"content":"-- 为 product_yarn_ratios 表添加 RLS 策略"},{"line":41,"content":"CREATE POLICY users_select_product_yarn_ratios ON public.product_yarn_ratios"},{"line":44,"content":"CREATE POLICY users_insert_product_yarn_ratios ON public.product_yarn_ratios"},{"line":47,"content":"CREATE POLICY users_update_product_yarn_ratios ON public.product_yarn_ratios"},{"line":50,"content":"CREATE POLICY users_delete_product_yarn_ratios ON public.product_yarn_ratios"}]},{"file":"migrations/20260523_165901_fix_yarn_ratios_delete_rls.sql","matches":[{"line":6,"content":"DROP POLICY IF EXISTS users_delete_yarn_ratios ON public.yarn_ratios; CREATE POLICY users_delete_yarn_ratios ON public.yarn_ratios FOR DELETE USING (EXISTS (SELECT 1 FROM public.production_plans WHERE id = plan_id AND purchaser_id = public.get_user_master_company_id()));"}]},{"file":"migrations/20260523_163116_fix_share_link_rls_v2.sql","matches":[{"line":9,"content":"DROP POLICY IF EXISTS factory_select_yarn_ratios_via_share ON public.yarn_ratios;"},{"line":10,"content":"DROP POLICY IF EXISTS users_select_yarn_ratios ON public.yarn_ratios;"},{"line":18,"content":"-- yarn_ratios:允许查看(配合计划查看)"},{"line":19,"content":"DROP POLICY IF EXISTS users_select_yarn_ratios ON public.yarn_ratios;"},{"line":20,"content":"DROP POLICY IF EXISTS users_insert_yarn_ratios ON public.yarn_ratios;"},{"line":21,"content":"DROP POLICY IF EXISTS users_update_yarn_ratios ON public.yarn_ratios;"},{"line":22,"content":"DROP POLICY IF EXISTS users_delete_yarn_ratios ON public.yarn_ratios;"},{"line":24,"content":"CREATE POLICY users_select_yarn_ratios ON public.yarn_ratios"},{"line":27,"content":"CREATE POLICY users_insert_yarn_ratios ON public.yarn_ratios"},{"line":32,"content":"CREATE POLICY users_update_yarn_ratios ON public.yarn_ratios"},{"line":37,"content":"CREATE POLICY users_delete_yarn_ratios ON public.yarn_ratios"}]},{"file":"migrations/20260523_052842_enable_rls_all_tables.sql","matches":[{"line":13,"content":"ALTER TABLE public.yarn_ratios ENABLE ROW LEVEL SECURITY;"},{"line":45,"content":"-- yarn_ratios: 所有人可查看和操作"},{"line":46,"content":"CREATE POLICY anon_select_yarn_ratios ON public.yarn_ratios FOR SELECT USING (true);"},{"line":47,"content":"CREATE POLICY anon_insert_yarn_ratios ON public.yarn_ratios FOR INSERT WITH CHECK (true);"},{"line":48,"content":"CREATE POLICY anon_delete_yarn_ratios ON public.yarn_ratios FOR DELETE USING (true);"}]},{"file":"migrations/20260523_162604_create_yarn_rls.sql","matches":[{"line":28,"content":"-- yarn_ratios 表 RLS 策略"},{"line":30,"content":"CREATE POLICY users_select_yarn_ratios ON public.yarn_ratios"},{"line":38,"content":"CREATE POLICY users_insert_yarn_ratios ON public.yarn_ratios"},{"line":46,"content":"CREATE POLICY users_delete_yarn_ratios ON public.yarn_ratios"}]},{"file":"migrations/20260523_162407_drop_old_rls_policies.sql","matches":[{"line":23,"content":"DROP POLICY IF EXISTS anon_select_yarn_ratios ON public.yarn_ratios;"},{"line":24,"content":"DROP POLICY IF EXISTS anon_insert_yarn_ratios ON public.yarn_ratios;"},{"line":25,"content":"DROP POLICY IF EXISTS anon_delete_yarn_ratios ON public.yarn_ratios;"}]}],"pattern":"yarn_ratios|ratio","path":"migrations","totalMatches":62} \ No newline at end of file diff --git a/.microcompact/mpp307qv/functions.Edit:6.txt b/.microcompact/mpp307qv/functions.Edit:6.txt new file mode 100644 index 0000000..26dda3a --- /dev/null +++ b/.microcompact/mpp307qv/functions.Edit:6.txt @@ -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→ + 525→
+ 526→ {yarns.filter(y => y.name).map((yarn, idx) => ( + 527→ + 528→ {yarnTypeLabels[yarn.yarn_type || 'all']}:{yarn.name}   占比:{(yarn.ratio * 100).toFixed(0)}% + 529→ + 530→ ))} + 531→ {!yarns.some(y => y.name) && -} + 532→
+ 533→

自动从原坯布产品获取

\ No newline at end of file diff --git a/.microcompact/mpp307tm/functions.Edit:9.txt b/.microcompact/mpp307tm/functions.Edit:9.txt new file mode 100644 index 0000000..26dda3a --- /dev/null +++ b/.microcompact/mpp307tm/functions.Edit:9.txt @@ -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→ + 525→
+ 526→ {yarns.filter(y => y.name).map((yarn, idx) => ( + 527→ + 528→ {yarnTypeLabels[yarn.yarn_type || 'all']}:{yarn.name}   占比:{(yarn.ratio * 100).toFixed(0)}% + 529→ + 530→ ))} + 531→ {!yarns.some(y => y.name) && -} + 532→
+ 533→

自动从原坯布产品获取

\ No newline at end of file diff --git a/.microcompact/mpp307vk/functions.Edit:14.txt b/.microcompact/mpp307vk/functions.Edit:14.txt new file mode 100644 index 0000000..040e6c6 --- /dev/null +++ b/.microcompact/mpp307vk/functions.Edit:14.txt @@ -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→
纱线名称/占比
+ 352→
+ 353→ {plan.yarn_ratios.map((yarn, index) => ( + 354→ + 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→ + 358→ ))} + 359→
+ 360→ {plan.yarn_usage_per_meter && ( + + +🚫 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. + \ No newline at end of file diff --git a/.microcompact/mpp307wt/functions.Edit:15.txt b/.microcompact/mpp307wt/functions.Edit:15.txt new file mode 100644 index 0000000..fc04b26 --- /dev/null +++ b/.microcompact/mpp307wt/functions.Edit:15.txt @@ -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→
纱线名称/占比
+ 894→
+ 895→ {plan.yarnRatios.map((yarn, index) => ( + 896→ + 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→ + 900→ ))} + 901→
+ 902→ {plan.yarn_usage_per_meter && ( + + +🚫 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. + \ No newline at end of file diff --git a/.todo/763bf11a-6a49-43d4-9c9e-642425531cda/todos.json b/.todo/763bf11a-6a49-43d4-9c9e-642425531cda/todos.json new file mode 100644 index 0000000..bb094ea --- /dev/null +++ b/.todo/763bf11a-6a49-43d4-9c9e-642425531cda/todos.json @@ -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" + } +] \ No newline at end of file diff --git a/.todo/da383ad7-73d7-45a4-9b99-5956991b4fda/todos.json b/.todo/da383ad7-73d7-45a4-9b99-5956991b4fda/todos.json index 27657a3..fb28ebe 100644 --- a/.todo/da383ad7-73d7-45a4-9b99-5956991b4fda/todos.json +++ b/.todo/da383ad7-73d7-45a4-9b99-5956991b4fda/todos.json @@ -1,25 +1,25 @@ [ { "id": "1", - "content": "创建 yarn_stock_records 原料纱出入库记录表", + "content": "检查并优化重复动画渲染问题", "status": "completed", "priority": "high" }, { "id": "2", - "content": "添加 RLS 策略和索引", + "content": "优化 staggerChildren 动画性能", "status": "completed", "priority": "high" }, { "id": "3", - "content": "修改 YarnAllocationModal 记录出库到 yarn_stock_records", + "content": "检查 will-change 使用是否合理", "status": "completed", - "priority": "high" + "priority": "medium" }, { "id": "4", - "content": "在原料纱仓库页面显示出入库记录", + "content": "优化列表渲染动画", "status": "completed", "priority": "medium" } diff --git a/AGENTS.md b/AGENTS.md index 88689a2..815ea2b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -119,7 +119,8 @@ src/ │ ├── useInventoryData.ts # 库存数据获取Hook(带缓存) │ ├── usePlanData.ts # 计划数据获取Hook(带缓存) │ ├── useResponsive.ts # 响应式Hook -│ └── useTheme.ts # 主题Hook +│ ├── useTheme.ts # 主题Hook +│ └── usePerformance.ts # 性能检测Hook(动画降级) └── utils/ ├── constants.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) - **原料纱仓库出入库记录功能** - 创建 `yarn_stock_records` 表,记录原料纱出入库历史 diff --git a/README.md b/README.md index 7f38b17..6c3c4a3 100644 --- a/README.md +++ b/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) - **原料纱仓库出入库记录功能** - 创建 `yarn_stock_records` 表,记录原料纱出入库历史 diff --git a/package.json b/package.json index 7628f56..41ae493 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "procurement-planning", - "version": "1.0.0", + "version": "1.0.31", "scripts": { "dev": "webpack serve", "build": "webpack --mode production", diff --git a/src/components/NotificationCenter.tsx b/src/components/NotificationCenter.tsx index 03636bc..5891798 100644 --- a/src/components/NotificationCenter.tsx +++ b/src/components/NotificationCenter.tsx @@ -246,7 +246,7 @@ export function NotificationCenter({ theme = 'amber' }: NotificationCenterProps key={notification.id} initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} - transition={{ delay: index * 0.05 }} + transition={{ delay: Math.min(index * 0.02, 0.1) }} onClick={handleNotificationClick} className={`p-4 hover:bg-gray-50 cursor-pointer transition-colors ${ !notification.is_read ? 'bg-blue-50/30' : '' diff --git a/src/components/PageTransition.tsx b/src/components/PageTransition.tsx index 2ca77a6..237d637 100644 --- a/src/components/PageTransition.tsx +++ b/src/components/PageTransition.tsx @@ -6,81 +6,63 @@ interface PageTransitionProps { className?: string; } -// 页面进入动画配置 - 使用更流畅的动画曲线 +// 简化的页面进入动画 - 仅使用opacity,避免transform const pageVariants: Variants = { initial: { opacity: 0, - y: 20, - scale: 0.98, }, animate: { opacity: 1, - y: 0, - scale: 1, transition: { - duration: 0.4, - ease: [0.25, 0.46, 0.45, 0.94], // 更自然的动画曲线 - staggerChildren: 0.08, - delayChildren: 0.05, + duration: 0.2, + ease: 'easeOut', }, }, exit: { opacity: 0, - y: -10, - scale: 0.98, transition: { - duration: 0.25, - ease: [0.25, 0.46, 0.45, 0.94], + duration: 0.15, }, }, }; -// 子元素动画 +// 简化的子元素动画 export const itemVariants: Variants = { initial: { opacity: 0, - y: 15, - scale: 0.98, }, animate: { opacity: 1, - y: 0, - scale: 1, transition: { - duration: 0.35, - ease: [0.25, 0.46, 0.45, 0.94], + duration: 0.2, + ease: 'easeOut', }, }, }; -// 卡片悬停动画 +// 卡片悬停动画 - 简化效果 export const cardHoverVariants: Variants = { rest: { scale: 1, - y: 0, transition: { - duration: 0.25, - ease: [0.25, 0.46, 0.45, 0.94], + duration: 0.15, }, }, hover: { - scale: 1.02, - y: -4, + scale: 1.01, transition: { - duration: 0.25, - ease: [0.25, 0.46, 0.45, 0.94], + duration: 0.15, }, }, tap: { - scale: 0.98, - y: 0, + scale: 0.99, transition: { duration: 0.1, }, }, }; -// 列表项交错动画容器 +// 列表项交错动画容器 - 减少stagger延迟 export const containerVariants: Variants = { initial: { opacity: 0, @@ -88,26 +70,22 @@ export const containerVariants: Variants = { animate: { opacity: 1, transition: { - staggerChildren: 0.08, - delayChildren: 0.1, + staggerChildren: 0.03, + delayChildren: 0.05, }, }, }; -// 列表项动画 +// 简化的列表项动画 - 仅opacity export const listItemVariants: Variants = { initial: { opacity: 0, - x: -20, - scale: 0.98, }, animate: { opacity: 1, - x: 0, - scale: 1, transition: { - duration: 0.35, - ease: [0.25, 0.46, 0.45, 0.94], + duration: 0.2, + ease: 'easeOut', }, }, }; @@ -120,62 +98,62 @@ export const fadeInVariants: Variants = { animate: { opacity: 1, transition: { - duration: 0.4, - ease: [0.25, 0.46, 0.45, 0.94], + duration: 0.25, + ease: 'easeOut', }, }, exit: { opacity: 0, transition: { - duration: 0.2, + duration: 0.15, }, }, }; -// 滑入动画(从底部) +// 滑入动画(从底部)- 简化 export const slideUpVariants: Variants = { initial: { opacity: 0, - y: 30, + y: 10, }, animate: { opacity: 1, y: 0, transition: { - duration: 0.5, - ease: [0.25, 0.46, 0.45, 0.94], + duration: 0.25, + ease: 'easeOut', }, }, }; -// 滑入动画(从右侧) +// 滑入动画(从右侧)- 简化 export const slideRightVariants: Variants = { initial: { opacity: 0, - x: 30, + x: 10, }, animate: { opacity: 1, x: 0, transition: { - duration: 0.4, - ease: [0.25, 0.46, 0.45, 0.94], + duration: 0.25, + ease: 'easeOut', }, }, }; -// 缩放动画 +// 缩放动画 - 简化 export const scaleVariants: Variants = { initial: { opacity: 0, - scale: 0.9, + scale: 0.95, }, animate: { opacity: 1, scale: 1, transition: { - duration: 0.4, - ease: [0.25, 0.46, 0.45, 0.94], + duration: 0.25, + ease: 'easeOut', }, }, }; @@ -188,38 +166,36 @@ export function PageTransition({ children, className = '' }: PageTransitionProps animate="animate" exit="exit" className={className} - style={{ willChange: 'transform, opacity' }} > {children} ); } -// 带动画的页面包装器(用于路由切换) +// 带动画的页面包装器(用于路由切换)- 简化版 export function AnimatedPage({ children, className = '' }: PageTransitionProps) { return ( {children} ); } -// 交错动画容器 +// 交错动画容器 - 限制最大stagger数量 export function StaggerContainer({ children, className = '', - staggerDelay = 0.08, - delayChildren = 0.1 + staggerDelay = 0.03, + delayChildren = 0.05 }: PageTransitionProps & { staggerDelay?: number; delayChildren?: number }) { return ( + {children} + + ); +} + +// 高性能模式下的静态包装器(无动画) +export function StaticPage({ children, className = '' }: PageTransitionProps) { + return
{children}
; +} + +// 简化的淡入组件 +export function FadeIn({ + children, + className = '', + delay = 0 +}: PageTransitionProps & { delay?: number }) { + return ( + {children} diff --git a/src/components/VersionUpdate.tsx b/src/components/VersionUpdate.tsx index d21e4e6..31a07f7 100644 --- a/src/components/VersionUpdate.tsx +++ b/src/components/VersionUpdate.tsx @@ -11,7 +11,7 @@ interface VersionInfo { } // 当前版本 -const CURRENT_VERSION = '1.0.2'; +const CURRENT_VERSION = '1.0.31'; // 模拟版本更新数据(实际应从服务器获取) const LATEST_VERSION: VersionInfo = { diff --git a/src/hooks/usePerformance.ts b/src/hooks/usePerformance.ts new file mode 100644 index 0000000..af1edc9 --- /dev/null +++ b/src/hooks/usePerformance.ts @@ -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 = { + 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(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; diff --git a/src/pages/purchaser/AccountsPayable.tsx b/src/pages/purchaser/AccountsPayable.tsx index 7bb643f..c5c19fc 100644 --- a/src/pages/purchaser/AccountsPayable.tsx +++ b/src/pages/purchaser/AccountsPayable.tsx @@ -222,7 +222,7 @@ export function AccountsPayable() { key={payable.id} initial={{ opacity: 0, y: 15 }} 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" > {/* 头部 */} diff --git a/src/pages/textile/Dashboard.tsx b/src/pages/textile/Dashboard.tsx index e52bb6b..eca27f1 100644 --- a/src/pages/textile/Dashboard.tsx +++ b/src/pages/textile/Dashboard.tsx @@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { useAuth } from '../../contexts/AuthContext'; 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 { useResponsive } from '../../hooks/useResponsive'; 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() { const navigate = useNavigate(); const { auth, logout } = useAuth(); @@ -133,11 +122,9 @@ export function TextileDashboard() {
- 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)?.display_name || auth.user?.username || '用户'} @@ -182,12 +169,10 @@ export function TextileDashboard() {
) : ( - +
{/* 统计卡片 - 与采购商保持一致 */}
- 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" > @@ -199,11 +184,9 @@ export function TextileDashboard() {

{stats.all}

累计关联

- +
- 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" > @@ -215,11 +198,9 @@ export function TextileDashboard() {

{stats.producing}

正在生产

- +
- 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" > @@ -231,11 +212,11 @@ export function TextileDashboard() {

{stats.completed}

已入库

- + {/* 最近计划 - 左右滑动展示 - 与采购商保持一致 */} - +

最近计划

{/* 左右滑动按钮 */} @@ -347,29 +327,27 @@ export function TextileDashboard() { )} )} -
+ {/* 快捷操作 - 与采购商保持一致 */} - +

快捷操作

- {getQuickActions().map((action, index) => ( - ( + ))}
- - +
+ )} @@ -404,11 +382,8 @@ export function TextileDashboard() { {/* 账户卡片弹窗 */} {showAccountCard && (
setShowAccountCard(false)}> - e.stopPropagation()} > {/* 头部 */} @@ -551,7 +526,7 @@ export function TextileDashboard() { 退出登录
-
+ )} diff --git a/src/pages/textile/FabricWarehouse.tsx b/src/pages/textile/FabricWarehouse.tsx index 247cd47..be89bb5 100644 --- a/src/pages/textile/FabricWarehouse.tsx +++ b/src/pages/textile/FabricWarehouse.tsx @@ -288,7 +288,7 @@ export function FabricWarehouse() { initial={{ opacity: 0, y: 15 }} animate={{ opacity: 1, y: 0 }} 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" > {/* 分组头部 */}
diff --git a/src/pages/textile/PlanOverview.tsx b/src/pages/textile/PlanOverview.tsx index 9e8a85a..1d0934f 100644 --- a/src/pages/textile/PlanOverview.tsx +++ b/src/pages/textile/PlanOverview.tsx @@ -715,7 +715,7 @@ export function TextilePlanOverview() { toggleGroup(group.purchaserId)} 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" >
- !isRejected && setExpandedPlan(expandedPlan === plan.id ? null : plan.id)} - whileTap={isRejected ? {} : { scale: 0.995 }} - className={`p-4 sm:p-5 transition-colors will-change-transform ${isRejected ? 'cursor-default' : 'cursor-pointer hover:bg-gray-50/50'}`} + className={`p-4 sm:p-5 transition-colors ${isRejected ? 'cursor-default' : 'cursor-pointer hover:bg-gray-50/50'}`} >
@@ -796,11 +795,11 @@ export function TextilePlanOverview() { initial={{ width: 0 }} animate={{ width: `${progress}%` }} 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" />
-
+
{expandedPlan === plan.id && (
@@ -249,7 +249,7 @@ export function YarnWarehouse() { key={record.id} initial={{ opacity: 0, x: -10 }} 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" >
diff --git a/src/pages/washing/CompletedFabric.tsx b/src/pages/washing/CompletedFabric.tsx index 15727fd..ea45209 100644 --- a/src/pages/washing/CompletedFabric.tsx +++ b/src/pages/washing/CompletedFabric.tsx @@ -150,7 +150,7 @@ export function CompletedFabric() { key={item.id} initial={{ opacity: 0, y: 10 }} 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}`)} className="p-4 hover:bg-gray-50 transition-colors cursor-pointer" > diff --git a/src/pages/washing/PaymentPending.tsx b/src/pages/washing/PaymentPending.tsx index 6e31ef3..1e1d479 100644 --- a/src/pages/washing/PaymentPending.tsx +++ b/src/pages/washing/PaymentPending.tsx @@ -132,8 +132,8 @@ export function WashingPaymentPending() { key={item.id} initial={{ opacity: 0, y: 15 }} animate={{ opacity: 1, y: 0 }} - transition={{ delay: index * 0.08, duration: 0.35, 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" + 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" >
diff --git a/src/pages/washing/PendingFabric.tsx b/src/pages/washing/PendingFabric.tsx index 21b25c6..aa6339f 100644 --- a/src/pages/washing/PendingFabric.tsx +++ b/src/pages/washing/PendingFabric.tsx @@ -121,7 +121,7 @@ export function PendingFabric() { key={item.id} initial={{ opacity: 0, y: 10 }} 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}`)} className="p-4 hover:bg-gray-50 transition-colors cursor-pointer" > diff --git a/src/styles/index.css b/src/styles/index.css index bc7fdd1..4db981e 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -143,18 +143,27 @@ body { @keyframes fadeIn { from { opacity: 0; - transform: translateY(10px); } to { opacity: 1; - transform: translateY(0); + } +} + +@keyframes fadeInScale { + from { + opacity: 0; + transform: scale(0.95); + } + to { + opacity: 1; + transform: scale(1); } } @keyframes slideIn { from { opacity: 0; - transform: translateX(-20px); + transform: translateX(-10px); } to { opacity: 1; @@ -181,11 +190,70 @@ body { } .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 { - 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; } /* 玻璃态效果 */