═══════════════════════════════════════════════════════════════════════════════ 程序鉴别材料 - 源代码后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(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; loading: boolean; error: string | null; } interface PlanActions { setPlans: (plans: ProductionPlan[]) => void; addPlan: (plan: ProductionPlan) => void; updatePlan: (id: string, updates: Partial) => void; deletePlan: (id: string) => void; selectPlan: (plan: ProductionPlan | null) => void; setPlanSteps: (planId: string, steps: PlanProcessStep[]) => void; updatePlanStep: (planId: string, stepId: string, updates: Partial) => void; setLoading: (loading: boolean) => void; setError: (error: string | null) => void; } export const usePlanStore = create()( 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) => void; deleteItem: (id: string) => void; setLoading: (loading: boolean) => void; setError: (error: string | null) => void; } export const useInventoryStore = create()( 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()( 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()( 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 = { 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(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 (
{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 (

{config.label}

{node.timestamp && (

{new Date(node.timestamp).toLocaleString('zh-CN')} {node.operatorName && ` · ${node.operatorName}`}

)}
{isActive && canConfirm && ( )} {isCompleted && }
{isExpanded && (