iloom-flatten/outputs/程序鉴别材料-源代码后30页.txt

685 lines
22 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

═══════════════════════════════════════════════════════════════════════════════
程序鉴别材料 - 源代码后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页
═══════════════════════════════════════════════════════════════════════════════