685 lines
22 KiB
Plaintext
685 lines
22 KiB
Plaintext
═══════════════════════════════════════════════════════════════════════════════
|
||
程序鉴别材料 - 源代码后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页)
|
||
═══════════════════════════════════════════════════════════════════════════════
|