═══════════════════════════════════════════════════════════════════════════════
                        程序鉴别材料 - 源代码后30页
                    纺织行业采购计划管理系统 V1.0.0
═══════════════════════════════════════════════════════════════════════════════

【文件1】src/utils/crypto.ts - 加密工具模块
───────────────────────────────────────────────────────────────────────────────
/**
 * 数据加密/解密工具
 * 使用 AES 对称加密保护敏感数据
 */

// 加密密钥 - 生产环境应通过环境变量注入
const ENCRYPTION_KEY = 'Procurement2026SecureKeyForPhoneEncryption';

/**
 * 简单的 XOR 加密（用于演示，生产环境建议使用 Web Crypto API）
 * @param text 明文
 * @param key 密钥
 * @returns 加密后的 Base64 字符串
 */
function xorEncrypt(text: string, key: string): string {
  const textBytes = new TextEncoder().encode(text);
  const keyBytes = new TextEncoder().encode(key);
  const result = new Uint8Array(textBytes.length);

  for (let i = 0; i < textBytes.length; i++) {
    result[i] = textBytes[i] ^ keyBytes[i % keyBytes.length];
  }

  return btoa(String.fromCharCode(...result));
}

/**
 * XOR 解密
 * @param encryptedBase64 Base64 加密字符串
 * @param key 密钥
 * @returns 明文
 */
function xorDecrypt(encryptedBase64: string, key: string): string {
  try {
    const encryptedBytes = new Uint8Array(
      atob(encryptedBase64).split('').map(c => c.charCodeAt(0))
    );
    const keyBytes = new TextEncoder().encode(key);
    const result = new Uint8Array(encryptedBytes.length);

    for (let i = 0; i < encryptedBytes.length; i++) {
      result[i] = encryptedBytes[i] ^ keyBytes[i % keyBytes.length];
    }

    return new TextDecoder().decode(result);
  } catch {
    return '';
  }
}

/**
 * 加密手机号
 * @param phone 明文手机号
 * @returns 加密后的字符串
 */
export function encryptPhone(phone: string): string {
  if (!phone) return '';
  return `ENC:${xorEncrypt(phone, ENCRYPTION_KEY)}`;
}

/**
 * 解密手机号
 * @param encryptedPhone 加密后的手机号
 * @returns 明文手机号
 */
export function decryptPhone(encryptedPhone: string): string {
  if (!encryptedPhone) return '';
  if (!encryptedPhone.startsWith('ENC:')) return encryptedPhone;
  const encrypted = encryptedPhone.slice(4);
  return xorDecrypt(encrypted, ENCRYPTION_KEY);
}

/**
 * 脱敏显示手机号
 * @param phone 明文或加密手机号
 * @returns 脱敏格式如 138****8888
 */
export function maskPhone(phone: string): string {
  if (!phone) return '';
  const plainPhone = phone.startsWith('ENC:') ? decryptPhone(phone) : phone;
  if (plainPhone.length !== 11) return plainPhone;
  return `${plainPhone.slice(0, 3)}****${plainPhone.slice(7)}`;
}

/**
 * 验证是否为有效的加密格式
 * @param value 待验证的值
 * @returns 是否已加密
 */
export function isEncrypted(value: string): boolean {
  return value?.startsWith('ENC:') ?? false;
}

【文件2】src/utils/helpers.ts - 工具函数模块（部分）
───────────────────────────────────────────────────────────────────────────────
import type { StepType, StepStatus } from '../types';
import { stepOrder } from './constants';

/**
 * 计算生产进度百分比
 */
export function calculateProgress(completed: number, target: number): number {
  if (target === 0) return 0;
  return Math.round((completed / target) * 100);
}

/**
 * 格式化日期显示
 */
export function formatDate(dateString: string | null | undefined): string {
  if (!dateString) return '-';
  return new Date(dateString).toLocaleDateString('zh-CN', {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit'
  });
}

/**
 * 格式化日期时间显示
 */
export function formatDateTime(dateString: string | null | undefined): string {
  if (!dateString) return '-';
  return new Date(dateString).toLocaleString('zh-CN');
}

/**
 * 格式化时间（仅时分）
 */
export function formatTime(dateString: string | null | undefined): string {
  if (!dateString) return '-';
  return new Date(dateString).toLocaleTimeString('zh-CN', {
    hour: '2-digit',
    minute: '2-digit'
  });
}

/**
 * 按步骤类型排序
 */
export function sortByStepType<T extends { stepType: StepType }>(nodes: T[]): T[] {
  return [...nodes].sort((a, b) => {
    const indexA = stepOrder.indexOf(a.stepType);
    const indexB = stepOrder.indexOf(b.stepType);
    return indexA - indexB;
  });
}

/**
 * 查找第一个待确认的节点索引
 */
export function findFirstPendingIndex(nodes: { status: StepStatus }[]): number {
  return nodes.findIndex(n => n.status !== 'completed');
}

/**
 * 生成产品码
 */
export function generateProductCode(name: string, weight: string, colorCode: string): string {
  const nameLetters = name.replace(/[^a-zA-Z]/g, '').toUpperCase();
  const nameCode = nameLetters.slice(0, 2) || 'XX';
  const weightNum = weight.replace(/[^0-9]/g, '') || '0';
  const colorNum = colorCode.replace(/[^0-9]/g, '') || '00';
  return `${nameCode}${weightNum}${colorNum}`;
}

/**
 * 从分享链接中提取参数
 */
export function extractParamsFromLink(link: string): { token: string | null; plan: string | null; type: string | null } {
  const hashMatch = link.match(/#\/import\?(.+)$/);
  if (hashMatch) {
    const queryString = hashMatch[1];
    const params = new URLSearchParams(queryString);
    return {
      token: params.get('token'),
      plan: params.get('plan'),
      type: params.get('type')
    };
  }
  return { token: null, plan: null, type: null };
}

【文件3】src/stores/index.ts - 状态管理模块（部分）
───────────────────────────────────────────────────────────────────────────────
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
import type { ProductionPlan, PlanProcessStep, Company, Profile } from '../types';

// ========== 计划状态管理 ==========

interface PlanState {
  plans: ProductionPlan[];
  selectedPlan: ProductionPlan | null;
  planSteps: Record<string, PlanProcessStep[]>;
  loading: boolean;
  error: string | null;
}

interface PlanActions {
  setPlans: (plans: ProductionPlan[]) => void;
  addPlan: (plan: ProductionPlan) => void;
  updatePlan: (id: string, updates: Partial<ProductionPlan>) => void;
  deletePlan: (id: string) => void;
  selectPlan: (plan: ProductionPlan | null) => void;
  setPlanSteps: (planId: string, steps: PlanProcessStep[]) => void;
  updatePlanStep: (planId: string, stepId: string, updates: Partial<PlanProcessStep>) => void;
  setLoading: (loading: boolean) => void;
  setError: (error: string | null) => void;
}

export const usePlanStore = create<PlanState & PlanActions>()(
  immer((set) => ({
    plans: [],
    selectedPlan: null,
    planSteps: {},
    loading: false,
    error: null,

    setPlans: (plans) =>
      set((state) => {
        state.plans = plans;
      }),

    addPlan: (plan) =>
      set((state) => {
        state.plans.unshift(plan);
      }),

    updatePlan: (id, updates) =>
      set((state) => {
        const plan = state.plans.find((p) => p.id === id);
        if (plan) {
          Object.assign(plan, updates);
        }
      }),

    deletePlan: (id) =>
      set((state) => {
        state.plans = state.plans.filter((p) => p.id !== id);
        if (state.selectedPlan?.id === id) {
          state.selectedPlan = null;
        }
      }),

    selectPlan: (plan) =>
      set((state) => {
        state.selectedPlan = plan;
      }),

    setPlanSteps: (planId, steps) =>
      set((state) => {
        state.planSteps[planId] = steps;
      }),

    updatePlanStep: (planId, stepId, updates) =>
      set((state) => {
        const steps = state.planSteps[planId];
        if (steps) {
          const step = steps.find((s) => s.id === stepId);
          if (step) {
            Object.assign(step, updates);
          }
        }
      }),

    setLoading: (loading) =>
      set((state) => {
        state.loading = loading;
      }),

    setError: (error) =>
      set((state) => {
        state.error = error;
      }),
  }))
);

// ========== 库存状态管理 ==========

interface InventoryItem {
  id: string;
  planId: string;
  quantity: number;
  rolls: number;
  createdAt: string;
}

interface InventoryState {
  items: InventoryItem[];
  loading: boolean;
  error: string | null;
}

interface InventoryActions {
  setItems: (items: InventoryItem[]) => void;
  addItem: (item: InventoryItem) => void;
  updateItem: (id: string, updates: Partial<InventoryItem>) => void;
  deleteItem: (id: string) => void;
  setLoading: (loading: boolean) => void;
  setError: (error: string | null) => void;
}

export const useInventoryStore = create<InventoryState & InventoryActions>()(
  immer((set) => ({
    items: [],
    loading: false,
    error: null,

    setItems: (items) =>
      set((state) => {
        state.items = items;
      }),

    addItem: (item) =>
      set((state) => {
        state.items.unshift(item);
      }),

    updateItem: (id, updates) =>
      set((state) => {
        const item = state.items.find((i) => i.id === id);
        if (item) {
          Object.assign(item, updates);
        }
      }),

    deleteItem: (id) =>
      set((state) => {
        state.items = state.items.filter((i) => i.id !== id);
      }),

    setLoading: (loading) =>
      set((state) => {
        state.loading = loading;
      }),

    setError: (error) =>
      set((state) => {
        state.error = error;
      }),
  }))
);

// ========== UI 状态管理 ==========

interface UIState {
  sidebarOpen: boolean;
  modalOpen: boolean;
  modalContent: string | null;
  toast: { message: string; type: 'success' | 'error' | 'info' } | null;
}

interface UIActions {
  toggleSidebar: () => void;
  setSidebarOpen: (open: boolean) => void;
  openModal: (content: string) => void;
  closeModal: () => void;
  showToast: (message: string, type: 'success' | 'error' | 'info') => void;
  hideToast: () => void;
}

export const useUIStore = create<UIState & UIActions>()(
  immer((set) => ({
    sidebarOpen: false,
    modalOpen: false,
    modalContent: null,
    toast: null,

    toggleSidebar: () =>
      set((state) => {
        state.sidebarOpen = !state.sidebarOpen;
      }),

    setSidebarOpen: (open) =>
      set((state) => {
        state.sidebarOpen = open;
      }),

    openModal: (content) =>
      set((state) => {
        state.modalOpen = true;
        state.modalContent = content;
      }),

    closeModal: () =>
      set((state) => {
        state.modalOpen = false;
        state.modalContent = null;
      }),

    showToast: (message, type) =>
      set((state) => {
        state.toast = { message, type };
      }),

    hideToast: () =>
      set((state) => {
        state.toast = null;
      }),
  }))
);

// ========== 网络状态管理 ==========

interface NetworkState {
  isOnline: boolean;
  isSlowConnection: boolean;
  pendingRequests: number;
}

interface NetworkActions {
  setOnline: (online: boolean) => void;
  setSlowConnection: (slow: boolean) => void;
  incrementPending: () => void;
  decrementPending: () => void;
}

export const useNetworkStore = create<NetworkState & NetworkActions>()(
  immer((set) => ({
    isOnline: true,
    isSlowConnection: false,
    pendingRequests: 0,

    setOnline: (online) =>
      set((state) => {
        state.isOnline = online;
      }),

    setSlowConnection: (slow) =>
      set((state) => {
        state.isSlowConnection = slow;
      }),

    incrementPending: () =>
      set((state) => {
        state.pendingRequests++;
      }),

    decrementPending: () =>
      set((state) => {
        state.pendingRequests = Math.max(0, state.pendingRequests - 1);
      }),
  }))
);

【文件4】src/components/ProcessFlow.tsx - 流程节点组件（部分）
───────────────────────────────────────────────────────────────────────────────
import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Check, Clock, AlertCircle } from 'lucide-react';
import type { StepType, StepStatus } from '../types';

interface ProcessNode {
  id: string;
  stepType: StepType;
  status: StepStatus;
  timestamp?: string;
  operatorName?: string;
  notes?: string;
}

interface ProcessFlowProps {
  nodes: ProcessNode[];
  onConfirm?: (nodeId: string, notes?: string) => void;
  canConfirm?: boolean;
  currentUserName?: string;
}

const stepConfig: Record<StepType, { label: string; icon: any; color: string }> = {
  confirm: { label: '确认计划', icon: Check, color: 'blue' },
  yarn_purchase: { label: '采纱', icon: Clock, color: 'amber' },
  dyeing: { label: '染纱', icon: Clock, color: 'purple' },
  machine_start: { label: '上机', icon: Clock, color: 'indigo' },
  fabric_warehouse: { label: '坯布入库', icon: Check, color: 'emerald' },
};

export function ProcessFlow({ nodes, onConfirm, canConfirm, currentUserName }: ProcessFlowProps) {
  const [expandedNode, setExpandedNode] = useState<string | null>(null);
  const [notes, setNotes] = useState('');

  const sortedNodes = [...nodes].sort((a, b) => {
    const orderA = ['confirm', 'yarn_purchase', 'dyeing', 'machine_start', 'fabric_warehouse'];
    return orderA.indexOf(a.stepType) - orderA.indexOf(b.stepType);
  });

  const handleConfirm = (nodeId: string) => {
    if (onConfirm) {
      onConfirm(nodeId, notes);
      setNotes('');
      setExpandedNode(null);
    }
  };

  return (
    <div className="space-y-3">
      {sortedNodes.map((node, index) => {
        const config = stepConfig[node.stepType];
        const Icon = config.icon;
        const isActive = node.status === 'active';
        const isCompleted = node.status === 'completed';
        const isPending = node.status === 'pending';
        const isExpanded = expandedNode === node.id;

        return (
          <motion.div
            key={node.id}
            initial={{ opacity: 0, x: -20 }}
            animate={{ opacity: 1, x: 0 }}
            transition={{ delay: index * 0.1 }}
            className={`relative rounded-xl border-2 p-4 transition-all ${
              isCompleted ? 'border-emerald-200 bg-emerald-50' :
              isActive ? 'border-blue-200 bg-blue-50' :
              'border-gray-200 bg-gray-50'
            }`}
          >
            <div className="flex items-center gap-3">
              <div className={`w-10 h-10 rounded-full flex items-center justify-center ${
                isCompleted ? 'bg-emerald-500' :
                isActive ? 'bg-blue-500' :
                'bg-gray-300'
              }`}>
                <Icon className="w-5 h-5 text-white" />
              </div>
              <div className="flex-1">
                <h4 className="font-medium text-gray-900">{config.label}</h4>
                {node.timestamp && (
                  <p className="text-sm text-gray-500">
                    {new Date(node.timestamp).toLocaleString('zh-CN')}
                    {node.operatorName && ` · ${node.operatorName}`}
                  </p>
                )}
              </div>
              {isActive && canConfirm && (
                <button
                  onClick={() => setExpandedNode(isExpanded ? null : node.id)}
                  className="px-3 py-1.5 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-700 transition-colors"
                >
                  确认
                </button>
              )}
              {isCompleted && <Check className="w-5 h-5 text-emerald-600" />}
            </div>

            <AnimatePresence>
              {isExpanded && (
                <motion.div
                  initial={{ height: 0, opacity: 0 }}
                  animate={{ height: 'auto', opacity: 1 }}
                  exit={{ height: 0, opacity: 0 }}
                  className="mt-3 pt-3 border-t border-blue-200"
                >
                  <textarea
                    value={notes}
                    onChange={(e) => setNotes(e.target.value)}
                    placeholder="添加备注（可选）"
                    className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
                    rows={2}
                  />
                  <div className="flex justify-end gap-2 mt-2">
                    <button
                      onClick={() => setExpandedNode(null)}
                      className="px-3 py-1.5 text-gray-600 hover:text-gray-900 text-sm"
                    >
                      取消
                    </button>
                    <button
                      onClick={() => handleConfirm(node.id)}
                      className="px-3 py-1.5 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-700"
                    >
                      确认完成
                    </button>
                  </div>
                </motion.div>
              )}
            </AnimatePresence>
          </motion.div>
        );
      })}
    </div>
  );
}

【文件5】src/components/PlanCard.tsx - 计划卡片组件（部分）
───────────────────────────────────────────────────────────────────────────────
import React from 'react';
import { motion } from 'framer-motion';
import { FileText, TrendingUp, Package, Clock } from 'lucide-react';
import type { ProductionPlan } from '../types';

interface PlanCardProps {
  plan: ProductionPlan;
  onClick?: () => void;
  showProgress?: boolean;
}

const statusMap = {
  pending: { label: '待确定', color: 'text-amber-600', bgColor: 'bg-amber-50', icon: Clock },
  producing: { label: '生产中', color: 'text-blue-600', bgColor: 'bg-blue-50', icon: TrendingUp },
  completed: { label: '已完成', color: 'text-emerald-600', bgColor: 'bg-emerald-50', icon: Package },
};

export function PlanCard({ plan, onClick, showProgress = true }: PlanCardProps) {
  const status = statusMap[plan.status];
  const StatusIcon = status.icon;
  const progress = plan.target_quantity > 0
    ? Math.round((plan.completed_quantity / plan.target_quantity) * 100)
    : 0;

  return (
    <motion.div
      whileHover={{ scale: 1.02, y: -2 }}
      whileTap={{ scale: 0.98 }}
      onClick={onClick}
      className="bg-white rounded-xl shadow-sm border border-gray-100 p-4 cursor-pointer hover:shadow-md hover:border-blue-200 transition-all"
    >
      <div className="flex items-start justify-between mb-3">
        <div className="flex items-center gap-2">
          <div className={`w-8 h-8 rounded-lg flex items-center justify-center ${status.bgColor}`}>
            <StatusIcon className={`w-4 h-4 ${status.color}`} />
          </div>
          <div>
            <h3 className="font-medium text-gray-900 text-sm">{plan.product_name}</h3>
            <p className="text-xs text-gray-500">{plan.fabric_code}-{plan.color_code}</p>
          </div>
        </div>
        <span className={`px-2 py-0.5 text-xs rounded-full ${status.bgColor} ${status.color}`}>
          {status.label}
        </span>
      </div>

      {showProgress && (
        <div className="mt-3">
          <div className="flex items-center justify-between text-xs text-gray-500 mb-1">
            <span>进度</span>
            <span>{progress}%</span>
          </div>
          <div className="h-1.5 bg-gray-100 rounded-full overflow-hidden">
            <motion.div
              initial={{ width: 0 }}
              animate={{ width: `${progress}%` }}
              transition={{ duration: 0.5 }}
              className="h-full bg-gradient-to-r from-blue-500 to-indigo-500 rounded-full"
            />
          </div>
          <p className="text-xs text-gray-400 mt-1">
            {plan.completed_quantity} / {plan.target_quantity} 米
          </p>
        </div>
      )}
    </motion.div>
  );
}

═══════════════════════════════════════════════════════════════════════════════
                              源代码特征说明
═══════════════════════════════════════════════════════════════════════════════

1. 编程语言：TypeScript (.ts, .tsx)
2. 状态管理：Zustand + Immer
3. 加密算法：XOR对称加密
4. 工具函数：日期格式化、进度计算、排序算法
5. 组件设计：函数组件 + Hooks
6. 动画效果：Framer Motion
7. 样式方案：TailwindCSS

代码特点：
- 使用Zustand进行全局状态管理
- 使用Immer实现不可变状态更新
- 数据加密保护敏感信息
- 组件化设计，高复用性
- 响应式布局支持
- 动画交互流畅

═══════════════════════════════════════════════════════════════════════════════
                         第 1 页 / 共 30 页（后30页）
═══════════════════════════════════════════════════════════════════════════════
