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

685 lines
22 KiB
Plaintext
Raw Normal View History

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