iloom-flatten/AGENTS.md

55 KiB
Raw Blame History

采购计划系统 - AGENTS.md

项目概述

纺织行业采购计划管理系统,支持三种角色(采购商/布行、纺织厂、水洗厂)的全流程协作管理。

系统架构

技术栈

层级 技术 说明
前端框架 React 18 + TypeScript 函数组件 + Hooks
构建工具 Webpack 5 开发服务器端口 3015
样式方案 TailwindCSS 3 + PostCSS 原子化 CSS
动画库 Framer Motion 页面过渡、交互动画
图标库 Lucide React 现代化图标
图表库 Recharts 数据可视化
路由 React Router v6 HashRouter 模式
后端服务 Meoo Cloud (Supabase) PostgreSQL + Auth + RLS
包管理 pnpm 依赖管理

项目目录结构

/home/project/
├── AGENTS.md                    # 项目文档(本文件)
├── package.json                 # 项目依赖配置
├── webpack.config.js            # Webpack 配置
├── tailwind.config.js           # TailwindCSS 配置
├── postcss.config.js            # PostCSS 配置
├── tsconfig.json                # TypeScript 配置
├── index.html                   # HTML 模板
├── migrations/                  # 数据库迁移文件
│   ├── 20260523_052639_create_base_tables.sql
│   ├── 20260523_052709_create_plan_tables.sql
│   ├── 20260523_052735_create_warehouse_tables.sql
│   ├── 20260523_052800_create_process_and_payment_tables.sql
│   ├── 20260523_052842_enable_rls_all_tables.sql
│   ├── 20260523_052936_create_test_users.sql
│   ├── 20260523_053030_seed_companies_and_profiles.sql
│   ├── 20260523_053110_seed_plans_and_related.sql
│   ├── 20260523_124703_add_company_id_to_inventory.sql
│   ├── 20260523_124742_create_product_yarn_ratios_table.sql
│   └── 20260523_131935_add_production_price_to_products.sql
├── src/
│   ├── index.tsx                # 应用入口
│   ├── App.tsx                  # 根组件(路由配置)
│   ├── styles/
│   │   └── index.css            # 全局样式
│   ├── types/
│   │   └── index.ts             # 类型定义
│   ├── supabase/
│   │   ├── client.ts            # Supabase 客户端(自动生成)
│   │   └── types.ts             # 数据库类型(自动生成)
│   ├── utils/
│   │   └── shareCrypto.ts       # 分享链接加密工具
│   ├── hooks/
│   │   ├── useResponsive.ts     # 响应式 Hook
│   │   └── useTheme.ts          # 主题 Hook
│   ├── contexts/
│   │   └── AuthContext.tsx      # 认证上下文
│   ├── components/
│   │   ├── PlanCard.tsx         # 计划卡片组件
│   │   ├── ProcessFlow.tsx      # 流程节点组件
│   │   ├── ProgressBar.tsx      # 进度条组件
│   │   └── StatusBadge.tsx      # 状态标签组件
│   └── pages/
│       ├── LoginPage.tsx        # 登录页
│       ├── RegisterPage.tsx     # 注册页
│       ├── RoleSelectPage.tsx   # 角色选择页
│       ├── MemberManage.tsx     # 子账号管理页
│       ├── ImportPlanPage.tsx   # 导入计划页(分享链接)
│       ├── purchaser/           # 采购商页面
│       │   ├── Dashboard.tsx
│       │   ├── PlanOverview.tsx
│       │   ├── NewPlan.tsx
│       │   ├── WarehouseManage.tsx
│       │   └── FactoryManage.tsx
│       ├── textile/             # 纺织厂页面
│       │   ├── Dashboard.tsx
│       │   ├── PlanOverview.tsx
│       │   ├── YarnWarehouse.tsx
│       │   ├── FabricWarehouse.tsx
│       │   └── PaymentPending.tsx
│       └── washing/             # 水洗厂页面
│           └── Dashboard.tsx
└── skills/                      # 技能文档
    ├── meoo-cloud/
    ├── mobile-dev/
    └── react-project/

重构后新增目录结构2025-05-24

src/
├── components/
│   ├── warehouse/               # 仓库管理组件
│   │   ├── WarehouseNav.tsx     # 仓库导航组件
│   │   ├── ProductForm.tsx      # 产品表单模态框
│   │   ├── InboundForm.tsx      # 入库表单模态框
│   │   ├── ProductList.tsx      # 产品列表组件(桌面+移动端)
│   │   └── InboundRecords.tsx   # 入库记录列表
│   └── plans/                   # 计划管理组件
│       ├── PlanGroup.tsx        # 计划分组组件
│       └── ShareModal.tsx       # 分享弹窗组件
├── hooks/
│   ├── useInventoryData.ts      # 库存数据获取Hook带缓存
│   └── usePlanData.ts           # 计划数据获取Hook带缓存
└── utils/
    ├── constants.ts             # 公共常量配置
    └── helpers.ts               # 通用工具函数

路由结构

URL 路径 页面 说明
/login LoginPage 登录页
/role-select RoleSelectPage 角色选择页
/purchaser PurchaserDashboard 采购商首页
/purchaser/plans PlanOverview 采购商计划总览
/purchaser/plans/new NewPlan 新建纺织计划
/purchaser/warehouse WarehouseManage 仓库管理
/purchaser/factories FactoryManage 工厂管理
/textile TextileDashboard 纺织厂首页
/textile/plans TextilePlanOverview 纺织厂计划总览
/textile/yarn-warehouse YarnWarehouse 原料纱仓库
/textile/payments PaymentPending 待结款
/washing WashingDashboard 水洗厂首页
/import ImportPlanPage 通过分享链接导入关联计划

数据安全

RLS 策略设计

所有表启用 Row Level Security (RLS),基于公司 ID 进行数据隔离:

SELECT 策略 修改策略
companies 开放访问 仅本公司
profiles 本公司成员 仅自己
production_plans 采购商或关联工厂 仅采购商
plan_factories 开放访问 仅采购商
plan_process_steps 开放访问 采购商或关联工厂
inventory_records 相关方 仅本公司
yarn_ratios 开放访问 仅采购商
yarn_stock 仅本公司 仅本公司
warehouses 仅本公司 仅本公司
payments 付款方或收款方 相关方
products 仅本公司 仅本公司

辅助函数

  • get_user_master_company_id() - 获取当前用户(含子账号)的公司 ID

数据架构

ER 关系图

auth.users (Supabase内置)
    │
    ├── 1:1 ── profiles (用户配置)
    │              │
    │              ├── N:1 ── companies (公司)
    │              │              │
    │              │              ├── 1:N ── company_members (公司成员/子账号)
    │              │              │
    │              │              ├── 1:N ── factories (工厂)
    │              │              │              │
    │              │              │              ├── N:M ── production_plans (通过 plan_factories 关联)
    │              │              │
    │              │              ├── 1:N ── warehouses (仓库)
    │              │              │              │
    │              │              │              ├── 1:N ── inventory_records (库存记录)
    │              │              │
    │              │              ├── 1:N ── yarn_stock (纱线库存)
    │              │
    │              ├── 1:N ── production_plans (采购商创建的计划)
    │              │              │
    │              │              ├── 1:N ── yarn_ratios (纱线配比)
    │              │              │
    │              │              ├── 1:N ── plan_process_steps (流程节点)
    │              │              │
    │              │              ├── N:M ── factories (通过 plan_factories 关联)
    │              │
    │              ├── 1:N ── payments (结款记录)

表结构详细设计

1. profiles - 用户配置表

字段 类型 说明
id UUID (PK) 关联 auth.users.id
username TEXT (UNIQUE) 用户名
phone TEXT 手机号
company_id UUID (FK) 所属公司
is_master BOOLEAN 是否主账号
master_id UUID (FK) 主账号ID子账号使用
created_at TIMESTAMPTZ 创建时间

2. companies - 公司表

字段 类型 说明
id UUID (PK) 公司ID
name TEXT 公司名称
role app_role 公司类型purchaser/textile/washing
address TEXT 公司地址
contact_phone TEXT 联系电话
created_at TIMESTAMPTZ 创建时间

3. company_members - 公司成员表

字段 类型 说明
id UUID (PK) 成员记录ID
company_id UUID (FK) 公司ID
user_id UUID (FK) 用户ID
role TEXT 成员角色
created_at TIMESTAMPTZ 加入时间

4. factories - 工厂表

字段 类型 说明
id UUID (PK) 工厂ID
company_id UUID (FK) 所属公司ID
name TEXT 工厂名称
type factory_type 工厂类型textile/washing
address TEXT 工厂地址
contact_person TEXT 联系人
contact_phone TEXT 联系电话
share_link TEXT 分享链接
created_at TIMESTAMPTZ 创建时间

5. production_plans - 生产计划表

字段 类型 说明
id UUID (PK) 计划ID
plan_code TEXT (UNIQUE) 系统自动生成计划唯一识别码
product_name TEXT 成品名称
color TEXT 颜色
fabric_code TEXT 坯布唯一识别码(纯字母)
color_code TEXT 色号(数字排序)
remark TEXT 备注60*40等
purchaser_id UUID (FK) 采购商公司ID
target_quantity INTEGER 计划产量(米)
completed_quantity INTEGER 已完成产量(米)
yarn_usage_per_meter DECIMAL 每米布纱用量(g/m)
production_price DECIMAL 生产采购价(元/米)
status plan_status 状态pending/producing/completed
start_time TIMESTAMPTZ 计划开始时间
created_by UUID (FK) 创建人
created_at TIMESTAMPTZ 创建时间
updated_at TIMESTAMPTZ 更新时间

6. plan_factories - 计划与工厂关联表

字段 类型 说明
id UUID (PK) 关联ID
plan_id UUID (FK) 计划ID
factory_id UUID (FK) 工厂ID
factory_type factory_type 工厂类型textile/washing
created_at TIMESTAMPTZ 关联时间

7. yarn_ratios - 纱线配比表

字段 类型 说明
id UUID (PK) 配比ID
plan_id UUID (FK) 计划ID
yarn_name TEXT 纱线名称
ratio DECIMAL 配比比例
amount_per_meter DECIMAL 每米用量(g/m)
total_amount DECIMAL 总用纱量
created_at TIMESTAMPTZ 创建时间

8. plan_process_steps - 计划流程节点表

字段 类型 说明
id UUID (PK) 节点ID
plan_id UUID (FK) 计划ID
step_type step_type 节点类型confirm/yarn_purchase/dyeing/machine_start/fabric_warehouse
status step_status 状态pending/active/completed
timestamp TIMESTAMPTZ 确认时间戳(精确到秒)
operator_id UUID (FK) 操作人
notes TEXT 备注
created_at TIMESTAMPTZ 创建时间

9. warehouses - 仓库表

字段 类型 说明
id UUID (PK) 仓库ID
company_id UUID (FK) 所属公司ID
name TEXT 仓库名称
type warehouse_type 类型raw_fabric/fabric/finished/yarn
location TEXT 仓库位置
created_at TIMESTAMPTZ 创建时间

10. inventory_records - 入库记录表

字段 类型 说明
id UUID (PK) 记录ID
plan_id UUID (FK) 关联计划ID
warehouse_id UUID (FK) 仓库ID
quantity DECIMAL 入库米数
warehouse_location TEXT 仓库位置
operator_id UUID (FK) 操作人
created_at TIMESTAMPTZ 入库时间

11. yarn_stock - 纱线库存表

字段 类型 说明
id UUID (PK) 纱线ID
company_id UUID (FK) 所属公司ID
warehouse_id UUID (FK) 仓库ID
name TEXT 纱线名称
spec TEXT 规格
quantity DECIMAL 库存数量(kg)
min_stock DECIMAL 最低库存预警(kg)
unit TEXT 单位
updated_at TIMESTAMPTZ 更新时间

12. payments - 结款记录表

字段 类型 说明
id UUID (PK) 结款ID
plan_id UUID (FK) 关联计划ID
from_company_id UUID (FK) 付款方公司ID
to_company_id UUID (FK) 收款方公司ID
amount DECIMAL 结款金额
quantity DECIMAL 结款数量(米)
price_per_meter DECIMAL 单价(元/米)
status payment_status 状态pending/completed
paid_at TIMESTAMPTZ 结款时间
created_at TIMESTAMPTZ 创建时间

自定义枚举类型

-- 应用角色(公司类型)
CREATE TYPE app_role AS ENUM ('purchaser', 'textile', 'washing');

-- 工厂类型
CREATE TYPE factory_type AS ENUM ('textile', 'washing');

-- 计划状态
CREATE TYPE plan_status AS ENUM ('pending', 'producing', 'completed');

-- 流程节点类型
CREATE TYPE step_type AS ENUM ('confirm', 'yarn_purchase', 'dyeing', 'machine_start', 'fabric_warehouse');

-- 流程节点状态
CREATE TYPE step_status AS ENUM ('pending', 'active', 'completed');

-- 仓库类型
CREATE TYPE warehouse_type AS ENUM ('raw_fabric', 'fabric', 'finished', 'yarn');

-- 结款状态
CREATE TYPE payment_status AS ENUM ('pending', 'completed');

技术栈

  • 前端React 18 + Webpack + TailwindCSS + Framer Motion + Lucide Icons
  • 后端Meoo Cloud (Supabase) - PostgreSQL + Auth + RLS
  • 路由HashRouter (react-router-dom v6)
  • 包管理pnpm

开发规范

  • 所有数据操作通过 Supabase client禁止 mock 数据
  • RLS 策略命名:<scope>_<action>_<table> 英文 snake_case
  • 用户认证使用 {username}@meoo.local 虚拟邮箱
  • 操作结果必须通过 Toast 反馈给用户
  • UUID 作为所有表主键

账号安全策略

用户名唯一性

  • profiles 表 username 字段 已设置数据库级唯一约束UNIQUE
  • 注册时:检查用户名是否已存在,返回具体错误提示
  • 子账号创建时:同样进行全局用户名唯一性检查,避免与其他主账号或子账号冲突
  • 错误提示"该用户名已被使用,请更换其他用户名"

UI/UX 优化记录

2025-05-23 纺织厂工作台优化

动画流畅度优化

  • Framer Motion 动画参数优化

    • 使用 ease: [0.25, 0.46, 0.45, 0.94] 替代默认 ease提供更自然的动画曲线
    • 缩短动画时长0.35s),减少用户等待感
    • 添加 will-change-transform 启用硬件加速
    • 优化 stagger 延迟0.08s),避免动画过于密集
  • ProcessFlow 组件优化

    • 当前步骤图标使用 scale: [1, 1.15, 1] 呼吸动画
    • 弹窗使用 AnimatePresence 实现平滑进出
    • 输入框展开/收起使用 ease: [0.25, 0.46, 0.45, 0.94]

移动端适配优化

  • 响应式断点

    • 使用 sm:640pxmd:768px断点
    • 字体大小:text-[10px](移动端)→ text-xssmtext-smmd
    • 间距:p-3(移动端)→ sm:p-4md:p-6
  • 触控优化

    • 按钮添加 whileTap={{ scale: 0.98 }} 点击反馈
    • 输入框添加 touch-manipulation 防止双击缩放
    • 输入框添加 inputModepattern 优化移动端键盘
    • 增大触控区域:min-height: 44pxCSS
  • 布局适配

    • 坯布入库输入框:移动端垂直排列(flex-col),桌面端水平排列(sm:flex-row
    • 统计卡片3列网格移动端缩小间距和字体
    • 表格:添加 overflow-x-auto 支持横向滚动

实时数据更新

  • FabricWarehouse 实时订阅
    • 使用 Supabase Realtime 订阅 inventory_records
    • 入库操作后自动刷新,无需手动刷新页面
    • 组件卸载时自动取消订阅

文件变更

  • src/pages/textile/Dashboard.tsx - 动画和移动端适配
  • src/pages/textile/PlanOverview.tsx - 动画和移动端适配
  • src/pages/textile/YarnWarehouse.tsx - 动画和移动端适配
  • src/pages/textile/FabricWarehouse.tsx - 动画、移动端适配、实时订阅
  • src/pages/textile/PaymentPending.tsx - 动画和移动端适配
  • src/components/ProcessFlow.tsx - 动画优化和移动端输入框
  • src/styles/index.css - 移动端触控优化CSS

更新日志

2025-05-25

  • 产品图片上传功能
    • 为采购商原坯布仓库产品管理添加图片上传功能
    • 支持 JPG、PNG 格式图片上传,最大 5MB
    • 产品列表展示缩略图,无图片时显示默认占位图标
    • 产品表单支持图片预览、重新上传和删除功能
    • 使用 Supabase Storage 存储图片文件
  • 数据库变更
    • products 表新增 image_url 字段
    • 创建 product_images 公共存储桶
    • 添加存储桶 RLS 策略
  • UI 优化
    • 纺织厂工作台"原坯布仓库"更名为"已生产坯布"
    • 更新首页快捷操作按钮标签

Bug 修复记录

2025-05-25 TypeScript 类型兼容性问题

问题描述

添加图片功能后出现类型错误:

Types of property 'image_url' are incompatible.
Type 'string | null | undefined' is not assignable to type 'string | null'

根本原因

  • ProductWithInventory 接口扩展了 Product 类型
  • 数据库生成的 Product 类型中 image_urlstring | null
  • 但前端扩展类型中重复定义了 image_url?: string | null,导致类型冲突

修复方案

移除 ProductWithInventory 接口中重复的 image_url 字段定义,让类型从父接口继承。

代码变更

  • 文件src/types/index.ts
  • 改动:删除 ProductWithInventory 接口中的 image_url 字段

经验教训与最佳实践

1. 类型扩展原则

  • 当接口 extends 另一个类型时,避免重复定义父类型中已存在的字段
  • 数据库生成的类型(Tables<'products'>)是单一事实来源,前端扩展接口只应添加计算属性或关联数据
  • 使用 Omit<T, K>Pick<T, K> 可以明确表达类型意图

2. 可选字段的语义

  • ?: string | null: string | null 在 TypeScript 中有不同含义
  • 数据库字段通常定义为 : string | null(必须存在,可为 null
  • 前端表单数据可能使用 ?: string(可选,不存在或存在)
  • 混用这两种语义会导致类型不兼容

3. 类型检查时机

  • 在修改类型定义后,立即运行 pnpm run typecheck 验证
  • 类型错误往往在使用处才暴露,而非定义处
  • 建议启用 TypeScript 严格模式(strict: true)提前发现问题

4. 数据库迁移后的类型同步

  • 执行数据库迁移后Supabase 会自动更新 src/supabase/types.ts
  • 需要检查前端自定义类型是否需要相应调整
  • 建议建立迁移后的类型检查清单

2025-05-23 采购商仓库管理数据同步问题

问题描述

采购商进入仓库管理页面时,系统崩溃报错:

Uncaught TypeError: Cannot read properties of undefined (reading '66f0af18-8d29-493a-ab5a-9ded734d71b8')

修复后仍发现"各工厂库存"数据未正确与纺织厂数据同步。

根本原因

  1. 变量作用域问题planToFactoryName 变量在代码块内部定义,但在后面的代码块中被访问,导致 undefined 错误
  2. 代码结构混乱:工厂信息查询逻辑分散在多个地方,导致数据流不清晰
  3. 工厂分组键值错误:按工厂分组库存时使用了错误的键值(factory_id 而非 factory_name

修复方案

  1. 提前获取工厂信息:将工厂信息查询(plan_factories + companies)移到 fetchData 函数开头,确保后续所有逻辑都能访问
  2. 统一变量命名:避免重复定义相同用途的变量,使用清晰的命名区分不同阶段的映射关系
  3. 修复分组逻辑:使用工厂名称作为分组键,确保正确汇总各工厂库存

代码变更

  • 文件src/pages/purchaser/WarehouseManage.tsx
  • 主要改动
    • 将工厂信息查询提前到数据获取阶段的开头
    • 合并重复的查询逻辑,避免多次查询相同数据
    • 修复 fabricCodeFactoryInventory 的分组键使用
    • 确保 planToFactoryName 在整个函数作用域内可用

经验教训

  • 块级作用域陷阱JavaScript/TypeScript 的块级作用域容易导致变量访问问题,特别是在复杂的异步数据获取逻辑中
  • 数据流清晰性:相关数据查询应该集中在一起,避免分散在代码各处
  • 变量命名规范:相同用途的变量应该统一命名,避免重复定义造成混淆

2025-05-23 入库记录弹窗组件

功能描述

创建统一的入库记录弹窗组件 InventoryRecordsModal,用于在采购商和纺织厂页面展示完整的入库记录详情。

组件特性

  • 统计摘要:显示总匹数和总米数
  • 记录列表:按时间倒序显示所有入库记录
  • 详细信息:包含日期时间、计划编号、工厂名称、批号、匹数、米数、来源
  • 动画效果:使用 Framer Motion 实现平滑的弹窗动画

使用方式

import { InventoryRecordsModal } from '../../components/InventoryRecordsModal';

// 在页面中使用
<InventoryRecordsModal
  isOpen={recordsModalOpen}
  onClose={() => setRecordsModalOpen(false)}
  records={selectedRecords}
  title="产品名称 - 入库记录"
/>

文件变更

  • src/components/InventoryRecordsModal.tsx - 新建弹窗组件
  • src/pages/purchaser/WarehouseManage.tsx - 集成弹窗组件
  • src/pages/textile/FabricWarehouse.tsx - 集成弹窗组件

2025-05-23 移动端适配优化

纺织厂原坯布仓库移动端适配

  • 桌面端:保持表格布局,完整展示所有字段
  • 移动端:使用卡片式布局,避免横向滚动
  • 响应式断点:使用 sm: 断点640px区分桌面和移动端

卡片设计规范

  • 计划编号和规格横向排列
  • 计划/入库数量分开显示
  • 最近一次入库记录显示在底部
  • "全部记录"按钮简化为"全部 (X条)"节省空间
  • 圆角卡片设计,带边框分隔

Bug 修复知识库

分类一JavaScript/TypeScript 作用域问题

Bug: 变量提升导致的 undefined 错误

现象Uncaught TypeError: Cannot read properties of undefined (reading 'xxx')

根本原因

  • 在代码块内部定义的变量,在后面的代码块中被访问
  • JavaScript 块级作用域导致变量不可访问

修复方案

  • 将变量定义提前到函数作用域顶部
  • 确保变量在使用前已定义

示例代码

// 错误planToFactoryName 在后面代码块中使用
if (condition) {
  const planToFactoryName = {};
}
// planToFactoryName 在这里访问不到

// 正确:提前定义
const planToFactoryName: Record<string, string> = {};
if (condition) {
  // 填充数据
  planToFactoryName[id] = name;
}
// 可以正常访问

相关文件src/pages/purchaser/WarehouseManage.tsx


分类二:数据一致性问题

Bug: 入库记录与计划数量不一致

现象

  • 计划显示已入库 7400 米
  • 仓库只显示 4400 米
  • 数据不同步

根本原因

  • 纺织厂入库时,先检查是否有仓库
  • 有仓库时才创建 inventory_records 记录
  • 但无论是否有仓库,都更新 completed_quantity
  • 导致 completed_quantity 增加,但 inventory_records 缺失

修复方案

// 错误逻辑
if (warehouse) {
  await supabase.from('inventory_records').insert({...});
}
await supabase.from('production_plans').update({ completed_quantity: newQty });

// 正确逻辑
await supabase.from('production_plans').update({ completed_quantity: newQty });
await supabase.from('inventory_records').insert({
  warehouse_id: warehouse?.id || null, // 允许 null
  ...
});

相关文件src/pages/textile/PlanOverview.tsx


Bug: 数据修复 - 补充缺失的入库记录

现象:历史数据存在 completed_quantityinventory_records 不一致

修复步骤

  1. 查询不一致的数据:
SELECT p.id, p.plan_code, p.completed_quantity, COALESCE(SUM(ir.quantity), 0) as inventory_total
FROM production_plans p
LEFT JOIN inventory_records ir ON ir.plan_id = p.id
GROUP BY p.id
HAVING p.completed_quantity != COALESCE(SUM(ir.quantity), 0);
  1. 为缺失记录的计划创建仓库(如果不存在)
  2. 补充缺失的入库记录

相关文件:数据库修复脚本


分类三:空值处理问题

Bug: NaN 显示问题

现象:弹窗中显示 "NaN 米"

根本原因

  • 入库记录中的 meters 字段可能为 undefinednull
  • 直接进行数学运算导致 NaN

修复方案

// 错误
const totalMeters = records.reduce((sum, r) => sum + r.meters, 0);

// 正确
const totalMeters = records.reduce((sum, r) => sum + (r.meters || r.quantity || 0), 0);

// 显示时
<span>{record.meters || record.quantity || 0} </span>

相关文件src/components/InventoryRecordsModal.tsx


分类四UI/UX 问题

Bug: 移动端表格横向溢出

现象:移动端表格内容超出屏幕,需要横向滚动

根本原因

  • 使用 <table> 布局,列数过多
  • 没有针对移动端做适配

修复方案

  • 桌面端:保持表格布局 hidden sm:block
  • 移动端:使用卡片式布局 sm:hidden
  • 响应式断点:sm:640px

相关文件src/pages/textile/FabricWarehouse.tsx


分类五:代码组织问题

Bug: 重复代码和逻辑分散

现象

  • 相同功能在多个地方重复实现
  • 工厂查询逻辑分散在多处
  • 难以维护

修复方案

  • 提取公共组件(如 InventoryRecordsModal
  • 将相关数据查询集中在一起
  • 统一变量命名规范

相关文件

  • src/components/InventoryRecordsModal.tsx(新建)
  • src/pages/purchaser/WarehouseManage.tsx
  • src/pages/textile/FabricWarehouse.tsx

开发规范总结

1. 数据一致性原则

  • 原子操作:相关联的数据更新必须在同一事务中完成
  • 校验机制:关键数据变更后,添加校验逻辑确保一致性
  • 修复脚本:建立数据修复机制,处理历史不一致数据

2. 空值处理规范

  • 默认值:所有数值计算必须提供默认值
  • 可选链:使用 ?.|| 处理可能为空的属性
  • 类型安全TypeScript 严格模式,定义完整的接口类型

3. 响应式设计规范

  • 移动优先:先设计移动端,再适配桌面端
  • 断点选择:使用标准断点 sm:640pxmd:768px
  • 布局切换:表格 ↔ 卡片,根据屏幕宽度切换

4. 组件化开发

  • 单一职责:每个组件只做一件事
  • 可复用性:提取公共组件,避免重复代码
  • Props 设计:清晰的 Props 接口,支持灵活配置

5. 调试技巧

  • 日志输出:关键节点添加 console.log 输出调试信息
  • 数据对比:前后端数据对比,定位不一致问题
  • 数据库查询:直接使用 SQL 查询验证数据状态

2025-05-24 代码重构记录

重构背景

解决代码可维护性问题单体文件过大WarehouseManage.tsx 1992行、代码重复、性能隐患。

重构内容

1. 类型系统重构

文件: src/types/index.ts

  • 新增仓库管理相关类型:ProductPriceHistory, ProductYarnRatio, ProductInRecord, FactoryInventory, ProductWithInventory, FabricBatch
  • 新增计划管理相关类型:PlanWithSteps, PlanGroup, StatusConfig
  • 新增流程节点类型:ProcessNode, StepTypeConfig
  • 新增表单类型:YarnInput
  • 新增通用工具类型:LoadingState, PaginationState

2. 工具函数提取

文件: src/utils/constants.ts

  • statusMap - 状态映射配置
  • simpleStatusMap - 简化状态映射
  • stepTypeConfig - 步骤类型配置
  • stepNames - 步骤名称映射
  • stepOrder - 步骤顺序
  • animationConfig - 动画配置(标准/快速/慢速过渡、弹簧动画)
  • paginationConfig - 分页配置
  • progressThresholds - 进度阈值

文件: src/utils/helpers.ts

  • calculateProgress - 计算生产进度百分比
  • formatDate / formatDateTime / formatTime - 日期格式化
  • sortByStepType - 按步骤类型排序
  • findFirstPendingIndex - 查找第一个待确认节点
  • generateProductCode - 生成产品码
  • extractParamsFromLink - 从分享链接提取参数
  • debounce / throttle - 防抖节流函数
  • safeParseNumber / safeParseInt - 安全数字解析
  • groupProductsByNameAndWeight - 按名称和克重分组产品
  • calculateSummary - 计算汇总数据

3. 自定义 Hooks 优化

文件: src/hooks/useInventoryData.ts

  • 功能:优化的库存数据获取 Hook
  • 特性:
    • 5秒数据缓存避免重复请求
    • 并行数据获取Promise.all
    • 实时订阅自动刷新
    • 错误处理

文件: src/hooks/usePlanData.ts

  • 功能:优化的计划数据获取 Hook
  • 特性:
    • 10秒数据缓存
    • 分页加载支持
    • 操作人名称缓存
    • 实时订阅自动刷新

4. 组件拆分

WarehouseManage.tsx 重构:

  • 原文件1992行 → 新主文件约300行
  • 拆分组件:
    • WarehouseNav.tsx - 仓库类型和子菜单导航
    • ProductForm.tsx - 产品创建/编辑表单模态框
    • InboundForm.tsx - 入库记录表单模态框
    • ProductList.tsx - 产品列表(支持桌面端表格和移动端卡片)
    • InboundRecords.tsx - 入库记录列表

PlanOverview.tsx 重构:

  • 原文件711行 → 新主文件约250行
  • 拆分组件:
    • PlanGroup.tsx - 工厂分组和计划卡片
    • ShareModal.tsx - 分享链接弹窗

性能优化

优化项 实现方式 效果
数据缓存 useRef 缓存数据5-10秒有效期 减少重复请求
分页加载 每次加载20条支持加载更多 避免大数据量渲染
并行请求 Promise.all 并行获取关联数据 减少等待时间
useMemo/useCallback 缓存计算结果和函数引用 减少重渲染
组件拆分 大文件拆分为职责单一组件 提升可维护性

代码质量提升

指标 重构前 重构后
最大文件行数 1992行 约300行
类型定义分散度 多处重复定义 统一在 types/index.ts
状态映射重复 多处硬编码 统一在 constants.ts
动画配置重复 多处硬编码 统一在 constants.ts
数据获取逻辑 分散在组件中 封装为自定义 Hooks

文件变更清单

新增文件:

  • src/utils/constants.ts
  • src/utils/helpers.ts
  • src/hooks/useInventoryData.ts
  • src/hooks/usePlanData.ts
  • src/components/warehouse/WarehouseNav.tsx
  • src/components/warehouse/ProductForm.tsx
  • src/components/warehouse/InboundForm.tsx
  • src/components/warehouse/ProductList.tsx
  • src/components/warehouse/InboundRecords.tsx
  • src/components/plans/PlanGroup.tsx
  • src/components/plans/ShareModal.tsx

修改文件:

  • src/types/index.ts - 扩展类型定义
  • src/pages/purchaser/WarehouseManage.tsx - 重构为精简主文件
  • src/pages/purchaser/PlanOverview.tsx - 重构为精简主文件

2025-05-24 出入库历史功能开发记录

需求背景

采购商工作台-原坯布仓库的产品管理模块需要增加出入库历史查看功能,方便用户追踪每个产品的库存变动记录。

遇到的问题

问题1类型定义不匹配

现象ProductInRecord 类型定义与数据库实际返回的字段不一致,导致类型检查报错。

根本原因

  • 原类型定义只包含前端展示需要的字段(time, rolls, meters, source 等)
  • 但数据库查询返回的是完整的表字段(id, product_id, company_id, created_at 等)
  • TypeScript 严格模式下,类型不匹配导致赋值错误

错误信息

Type '{ batch_no: string; company_id: string | null; created_at: string; ... }' is missing the following properties from type 'ProductInRecord': time, source

解决方案 扩展 ProductInRecord 类型定义,包含所有数据库字段,同时保留前端展示用的可选扩展字段:

export interface ProductInRecord {
  // 数据库字段(必需)
  id: string;
  product_id: string;
  company_id: string | null;
  batch_no: string;
  rolls: number;
  meters: number;
  notes: string | null;
  operator_id: string | null;
  warehouse_id: string | null;
  created_at: string;
  // 前端展示用扩展字段(可选)
  time?: string;
  plan_code?: string;
  factory_name?: string;
  source?: 'self' | 'textile';
  ...
}

经验教训

  • 类型定义应该与数据库表结构保持一致
  • 前端特有的展示字段应该标记为可选(?
  • 使用 TypeScript 严格模式可以提前发现这类问题

实现方案

1. 组件层修改ProductList.tsx

新增 Props

  • onViewStockHistory: (product: ProductWithInventory) => void - 查看出入库历史回调

桌面端操作列

  • 在"价格历史"按钮前添加"出入库历史"按钮(紫色样式)
  • 操作按钮顺序:出入库历史 → 价格历史 → 编辑 → 删除

移动端操作按钮

  • 第一行:出入库历史(紫色)、价格历史(绿色)
  • 第二行:编辑(蓝色)、删除(红色)
  • 分两行布局避免按钮过于拥挤

2. 页面层修改WarehouseManage.tsx

新增状态

const [stockHistoryModalOpen, setStockHistoryModalOpen] = useState(false);
const [selectedStockHistory, setSelectedStockHistory] = useState<ProductInRecord[]>([]);

新增处理函数

const handleViewStockHistory = useCallback(async (product: ProductWithInventory) => {
  const { data } = await supabase
    .from('product_inventory_records')
    .select('*')
    .eq('product_id', product.id)
    .order('created_at', { ascending: false });

  setSelectedStockHistory(data || []);
  setSelectedProductName(`${product.product_name}-${product.weight}g-${product.color}`);
  setStockHistoryModalOpen(true);
}, []);

弹窗设计

  • 标题:{产品名称} - 出入库历史
  • 内容展示:序号、时间、批号、匹数、米数、备注
  • 空状态:显示"暂无出入库记录"
  • 样式:与价格历史弹窗保持一致(灰色卡片背景)

3. 类型层修改types/index.ts

修复 ProductInRecord 类型

  • 添加所有数据库表字段作为必需字段
  • 保留原有前端扩展字段作为可选字段
  • 确保与 Tables<'product_inventory_records'> 兼容

文件变更清单

修改文件

  • src/components/warehouse/ProductList.tsx - 添加出入库历史按钮和回调
  • src/pages/purchaser/WarehouseManage.tsx - 添加出入库历史弹窗和查询逻辑
  • src/types/index.ts - 修复 ProductInRecord 类型定义

UI 设计规范

按钮颜色体系

功能 颜色 Tailwind 类
出入库历史 紫色 text-purple-600, bg-purple-50
价格历史 绿色 text-emerald-600, bg-emerald-50
编辑 蓝色 text-blue-600, bg-blue-50
删除 红色 text-red-600, bg-red-50

弹窗布局

  • 最大宽度:max-w-2xl约672px
  • 最大高度:max-h-[80vh],内容区 max-h-[60vh]
  • 卡片内边距:p-4
  • 列表间距:space-y-3

分享链接安全策略

加密机制

  • 分享链接使用 src/utils/shareCrypto.ts 进行加密/解密
  • 加密内容仅包含:planId + factoryType + timestamp
  • 使用 Base64 + 盐值混淆 + 字符串反转进行加密

敏感信息保护

  • 分享链接不传输:成品名称、颜色、色号等敏感信息
  • 导入页面不展示:接收方通过链接导入计划时,页面不显示成品名称、颜色、色号
  • 仅展示:计划编号、坯布识别码、计划产量、每米布纱用量、生产采购价、备注

Bug 修复知识库(扩展)

分类六React 组件生命周期问题

Bug: 内存泄漏 - 未清理的订阅和定时器

现象

  • 页面切换后控制台报错:Can't perform a React state update on an unmounted component
  • 内存占用持续增长
  • 实时订阅重复触发

根本原因

  • 组件卸载时未清理 Supabase Realtime 订阅
  • 未清除 setInterval/setTimeout 定时器
  • 事件监听器未移除

修复方案

// 错误:未清理订阅
useEffect(() => {
  const channel = supabase
    .channel('inventory_changes')
    .on('postgres_changes', {...}, callback)
    .subscribe();
}, []);

// 正确:清理订阅
useEffect(() => {
  const channel = supabase
    .channel('inventory_changes')
    .on('postgres_changes', {...}, callback)
    .subscribe();

  return () => {
    channel.unsubscribe();
  };
}, []);

// 定时器清理
useEffect(() => {
  const timer = setInterval(fetchData, 5000);
  return () => clearInterval(timer);
}, []);

相关文件src/hooks/useRealtime.ts, src/hooks/useInventoryData.ts


分类七Supabase RLS 策略问题

Bug: RLS 策略导致数据无法插入

现象

  • 插入数据时报错:new row violates row-level security policy for table "xxx"
  • 部分用户无法查看数据
  • 子账号无法操作主账号数据

根本原因

  • RLS 策略条件过于严格
  • 未考虑子账号场景(需要通过 master_id 获取 company_id
  • 策略中使用了错误的字段比较

修复方案

-- 错误:仅检查 user_id
CREATE POLICY users_insert_inventory ON inventory_records
  FOR INSERT WITH CHECK (auth.uid() = operator_id);

-- 正确:检查公司权限(支持子账号)
CREATE OR REPLACE FUNCTION get_user_master_company_id()
RETURNS UUID AS $$
DECLARE
  v_company_id UUID;
  v_master_id UUID;
BEGIN
  SELECT company_id, master_id INTO v_company_id, v_master_id
  FROM profiles WHERE id = auth.uid();

  IF v_master_id IS NOT NULL THEN
    SELECT company_id INTO v_company_id
    FROM profiles WHERE id = v_master_id;
  END IF;

  RETURN v_company_id;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- 使用辅助函数创建策略
CREATE POLICY company_insert_inventory ON inventory_records
  FOR INSERT WITH CHECK (
    company_id = get_user_master_company_id()
  );

相关文件migrations/*_create_*_rls.sql


分类八Webpack 构建问题

Bug: 开发服务器热更新失效

现象

  • 修改代码后页面不自动刷新
  • 控制台报错 WebSocket connection failed
  • 热模块替换 (HMR) 不工作

根本原因

  • Webpack devServer 配置缺少 hot: true
  • allowedHosts 配置不正确
  • historyApiFallback 未启用

修复方案

// webpack.config.js
development: {
  devServer: {
    port: 3015,
    hot: true,                    // 启用热更新
    historyApiFallback: true,     // 支持前端路由
    allowedHosts: ['all', '.alibaba-inc.com'], // 允许所有主机
    client: {
      overlay: {
        errors: true,
        warnings: false
      }
    }
  }
}

相关文件webpack.config.js


Bug: 生产构建产物体积过大

现象

  • 构建后的 JS 文件超过 2MB
  • 首屏加载时间过长
  • 代码未压缩

修复方案

// webpack.config.js
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin()],
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all'
        }
      }
    }
  }
};

分类九TypeScript 类型问题

Bug: 严格模式下的隐式 any 错误

现象

  • 编译报错:Parameter 'xxx' implicitly has an 'any' type
  • 类型推断失败
  • 第三方库类型定义缺失

修复方案

// tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true
  }
}

// 代码中显式声明类型
// 错误
const handleClick = (e) => { ... };

// 正确
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => { ... };

// 第三方库类型声明
// 创建 src/types/declarations.d.ts
declare module 'some-untyped-lib' {
  export function doSomething(): void;
}

Bug: 泛型约束导致的类型不匹配

现象

  • 泛型组件使用时类型推断错误
  • Type 'T' is not assignable to type 'xxx'

修复方案

// 错误:泛型约束不明确
interface Props<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}

// 正确:添加约束
interface Props<T extends { id: string }> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}

// 使用
interface User {
  id: string;
  name: string;
}

<List<User> items={users} renderItem={(user) => <span>{user.name}</span>} />

分类十:状态管理问题

Bug: 状态更新异步导致的竞态条件

现象

  • 快速点击按钮时数据不一致
  • 表单提交后状态未同步更新
  • 多个组件间状态不同步

根本原因

  • 直接修改状态而非使用函数式更新
  • 异步操作未正确处理依赖
  • 缺少乐观更新

修复方案

// 错误:直接依赖旧状态
const handleIncrement = () => {
  setCount(count + 1); // 可能使用过时的 count
};

// 正确:使用函数式更新
const handleIncrement = () => {
  setCount(prev => prev + 1);
};

// 复杂状态更新
const [state, setState] = useState({ count: 0, loading: false });

const updateState = useCallback((updates: Partial<typeof state>) => {
  setState(prev => ({ ...prev, ...updates }));
}, []);

// 乐观更新示例
const handleAddItem = async (item: Item) => {
  // 乐观更新 UI
  setItems(prev => [...prev, { ...item, id: 'temp-' + Date.now() }]);

  try {
    const { data } = await supabase.from('items').insert(item).select().single();
    // 用真实数据替换临时数据
    setItems(prev => prev.map(i => i.id.startsWith('temp-') ? data : i));
  } catch (error) {
    // 回滚
    setItems(prev => prev.filter(i => !i.id.startsWith('temp-')));
    toast.error('添加失败');
  }
};

相关文件src/stores/index.ts


分类十一:表单处理问题

Bug: 受控组件与非受控组件混用警告

现象

  • 控制台警告:A component is changing an uncontrolled input to be controlled
  • 表单值初始化后无法修改

根本原因

  • 初始值为 undefinednull,后续变为有值
  • React 无法确定是受控还是非受控组件

修复方案

// 错误:初始值可能为 undefined
const [value, setValue] = useState<string | undefined>();

// 正确:始终提供非 undefined 初始值
const [value, setValue] = useState('');

// 对象表单
const [form, setForm] = useState({
  name: '',
  email: '',
  age: 0
});

// 处理可能为 null 的数据
const [user, setUser] = useState<User | null>(null);
// 渲染时
<input value={user?.name ?? ''} onChange={...} />

Bug: 表单验证时机问题

现象

  • 提交时才显示验证错误,用户体验差
  • 实时验证过于频繁,性能问题
  • 异步验证导致表单状态混乱

修复方案

// 使用防抖进行实时验证
const debouncedValidate = useMemo(
  () => debounce((value: string) => {
    const error = validateEmail(value);
    setErrors(prev => ({ ...prev, email: error }));
  }, 300),
  []
);

// 表单提交验证
const handleSubmit = async (e: React.FormEvent) => {
  e.preventDefault();

  const errors = validateForm(formData);
  if (Object.keys(errors).length > 0) {
    setErrors(errors);
    return;
  }

  setSubmitting(true);
  try {
    await submitForm(formData);
  } finally {
    setSubmitting(false);
  }
};

分类十二CSS/Tailwind 问题

Bug: Tailwind 类名未生效

现象

  • 样式未应用
  • 自定义配置的颜色/间距未生效
  • 生产构建后样式丢失

根本原因

  • tailwind.config.js 配置错误
  • content 配置未包含所有文件路径
  • PostCSS 配置问题

修复方案

// tailwind.config.js
module.exports = {
  content: [
    './src/**/*.{js,jsx,ts,tsx}',  // 确保包含所有组件文件
    './index.html'
  ],
  theme: {
    extend: {
      colors: {
        primary: {
          50: '#eff6ff',
          500: '#3b82f6',
          600: '#2563eb',
        }
      }
    }
  }
};

// postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {}
  }
};

Bug: 移动端样式被桌面端覆盖

现象

  • 移动端样式在桌面端显示正常,但在移动端错误
  • 响应式断点不生效

根本原因

  • Tailwind 的类名顺序问题
  • 未理解移动优先原则

修复方案

<!-- 错误:桌面优先 -->
<div class="hidden md:block sm:hidden">...</div>

<!-- 正确:移动优先 -->
<!-- 默认样式(移动端)→ sm640px+)→ md768px+ -->
<div class="text-sm sm:text-base md:text-lg">
  移动端小字 → 平板中字 → 桌面大字
</div>

<!-- 显示/隐藏 -->
<div class="hidden sm:block">
  仅在大于 640px 时显示
</div>

<div class="sm:hidden">
  仅在小于 640px 时显示
</div>

分类十三:性能优化问题

Bug: 大数据列表渲染卡顿

现象

  • 列表超过 100 条时滚动卡顿
  • 内存占用高
  • 帧率下降

修复方案

// 1. 虚拟列表
import { useVirtualizer } from '@tanstack/react-virtual';

const VirtualList = ({ items }) => {
  const parentRef = useRef<HTMLDivElement>(null);

  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50,
  });

  return (
    <div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
      <div style={{ height: `${virtualizer.getTotalSize()}px` }}>
        {virtualizer.getVirtualItems().map((virtualItem) => (
          <div
            key={virtualItem.key}
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%',
              transform: `translateY(${virtualItem.start}px)`
            }}
          >
            {items[virtualItem.index]}
          </div>
        ))}
      </div>
    </div>
  );
};

// 2. 分页加载
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);

const loadMore = async () => {
  const { data } = await supabase
    .from('items')
    .select('*')
    .range(page * 20, (page + 1) * 20 - 1);

  if (data.length < 20) setHasMore(false);
  setItems(prev => [...prev, ...data]);
  setPage(p => p + 1);
};

// 3. 使用 React.memo 和 useMemo
const MemoizedItem = React.memo(({ item }) => {
  return <div>{item.name}</div>;
});

相关文件src/components/VirtualList.tsx


分类十六:沙箱环境兼容性问题

Bug: Webpack devServer 在沙箱环境中预览加载失败

现象

  • 开发服务器启动正常,端口 3015 在监听
  • 但 curl 请求超时,无法获取 HTML 内容
  • 浏览器预览无法加载

根本原因

  1. 沙箱网络限制net.ipv4.conf.all.forwarding = 0IP 转发被禁用)
  2. Webpack devServer 配置问题
    • host: '127.0.0.1' 无法在沙箱中访问
    • static 配置指向不存在的 public 目录
    • devMiddleware.writeToDisk 与沙箱环境不兼容
  3. 与其他项目对比Vite 在沙箱中可以正常工作Webpack devServer 存在兼容性问题

修复方案

// webpack.config.js - 标准配置
module.exports = {
  // ...其他配置
  devServer: {
    port: 3015,
    host: '0.0.0.0',  // 必须使用 0.0.0.0
    allowedHosts: 'all',
    hot: true,
    historyApiFallback: true,
    // 不要配置 static 和 devMiddleware使用默认行为
  },
};

关键配置点

  1. host: '0.0.0.0' - 必须绑定到所有接口,而不是 127.0.0.1
  2. 不要配置 static - 让 webpack 使用内存中的编译产物
  3. 不要配置 devMiddleware.writeToDisk - 沙箱中不需要写入磁盘
  4. 保持简单 - 避免复杂的 devServer 配置

验证方法

# 1. 构建验证
pnpm run build

# 2. 检查 dist 目录
ls -la dist/

# 3. 启动开发服务器(沙箱中 curl 可能仍超时,但不影响实际功能)
pnpm run dev

替代方案

  • 使用 pnpm run build 生成静态文件
  • 在实际部署环境中测试预览功能
  • 考虑迁移到 Vite沙箱兼容性更好

相关文件

  • webpack.config.js
  • package.json

学习反思与最佳实践

1. 环境差异意识

问题:在本地开发环境正常工作的配置,在沙箱环境中可能失效。 教训

  • 沙箱环境有严格的网络限制IP 转发禁用、localhost 访问受限)
  • 需要针对沙箱环境调整开发服务器配置
  • 不要假设所有环境行为一致

2. 配置简化原则

问题:过度配置导致兼容性问题。 教训

  • 使用框架/工具的默认配置作为起点
  • 只在必要时添加自定义配置
  • 复杂的配置组合可能在特定环境中失效

3. 调试方法改进

问题:陷入反复修改配置的循环,没有定位真正原因。 教训

  • 先检查环境限制(sysctl net.ipv4.conf.all.forwarding
  • 对比正常项目和异常项目的差异
  • 使用最小化配置验证(逐步添加配置项)
  • 接受某些限制(如沙箱中 curl 无法访问)

4. 文档记录重要性

问题:花费大量时间排查已知问题。 教训

  • 及时记录环境特定的配置要求
  • 建立常见问题知识库
  • 记录失败尝试和成功方案

5. 技术选型考虑

问题Webpack devServer 在沙箱中存在兼容性问题。 教训

  • 技术选型需要考虑目标运行环境
  • Vite 在沙箱环境中兼容性更好
  • 对于新项目,优先考虑沙箱友好的工具链

沙箱环境开发指南

已知限制

  1. 网络访问localhost/127.0.0.1 访问受限
  2. IP 转发net.ipv4.conf.all.forwarding = 0
  3. 端口:仅 3015 可用
  4. 进程限制:某些命令执行超时

推荐配置

// webpack.config.js
devServer: {
  port: 3015,
  host: '0.0.0.0',
  allowedHosts: 'all',
  hot: true,
  historyApiFallback: true,
}

验证流程

  1. pnpm run typecheck - 类型检查
  2. pnpm run build - 构建验证
  3. ls -la dist/ - 检查产物
  4. pnpm run dev - 启动服务器(沙箱中预览可能受限)

故障排查

  • 检查端口占用:lsof -i :3015
  • 检查 IP 转发:sysctl net.ipv4.conf.all.forwarding
  • 简化配置:移除 static/devMiddleware 等复杂配置
  • 对比其他项目:参考正常工作的项目配置

分类十四:浏览器兼容性问题

Bug: 旧版浏览器不支持新特性

现象

  • 在 Safari 13 或 IE11 中白屏
  • 某些 API 报错 xxx is not defined

修复方案

// webpack.config.js
module.exports = {
  target: ['web', 'es5'], // 编译到 ES5
  module: {
    rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              ['@babel/preset-env', {
                targets: {
                  browsers: ['> 1%', 'last 2 versions', 'not dead']
                },
                useBuiltIns: 'usage',
                corejs: 3
              }]
            ]
          }
        }
      }
    ]
  }
};

// 使用 polyfill
import 'core-js/stable';
import 'regenerator-runtime/runtime';

分类十五:测试相关问题

Bug: 异步测试超时

现象

  • 测试报错 Timeout - Async callback was not invoked within the 5000ms
  • Supabase 操作未正确 mock

修复方案

// jest.config.js
module.exports = {
  testTimeout: 10000, // 增加超时时间
  setupFilesAfterEnv: ['<rootDir>/src/__tests__/setup.ts']
};

// setup.ts
import '@testing-library/jest-dom';

// Mock Supabase
jest.mock('../supabase/client', () => ({
  supabase: {
    from: jest.fn(() => ({
      select: jest.fn().mockReturnThis(),
      insert: jest.fn().mockReturnThis(),
      update: jest.fn().mockReturnThis(),
      delete: jest.fn().mockReturnThis(),
      eq: jest.fn().mockReturnThis(),
      single: jest.fn().mockResolvedValue({ data: null, error: null })
    })),
    auth: {
      getUser: jest.fn().mockResolvedValue({
        data: { user: { id: 'test-user-id' } }
      })
    }
  }
}));

// 测试用例
import { render, screen, waitFor } from '@testing-library/react';

test('loads data', async () => {
  render(<Component />);

  await waitFor(() => {
    expect(screen.getByText('Loaded')).toBeInTheDocument();
  }, { timeout: 3000 });
});

调试技巧总结

1. 网络请求调试

// 在 supabase 客户端添加日志
const supabase = createClient(url, key, {
  db: {
    schema: 'public'
  },
  global: {
    fetch: (...args) => {
      console.log('Supabase Request:', args[0]);
      return fetch(...args);
    }
  }
});

2. 性能分析

// 使用 React DevTools Profiler
// 或使用 console.time
console.time('dataFetch');
const data = await fetchData();
console.timeEnd('dataFetch');

// 使用 Performance API
const mark = performance.mark('start');
// ... 操作
performance.measure('operation', 'start');

3. 错误边界

// ErrorBoundary.tsx
class ErrorBoundary extends React.Component<
  { children: React.ReactNode },
  { hasError: boolean; error: Error | null }
> {
  constructor(props: { children: React.ReactNode }) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error: Error) {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    console.error('Error caught by boundary:', error, errorInfo);
    // 上报错误到监控服务
  }

  render() {
    if (this.state.hasError) {
      return <ErrorFallback error={this.state.error} />;
    }
    return this.props.children;
  }
}

4. 开发环境检查清单

  • 控制台无警告/错误
  • React DevTools 无不必要的重渲染
  • Network 面板无重复请求
  • Lighthouse 评分 > 90
  • 移动端模拟器测试通过
  • 类型检查通过 pnpm run typecheck
  • 代码规范检查通过 pnpm run lint